Les méthodes les plus rapides pour modifier les tables d'attributs avec Python?

12

Il y a quelque temps, j'ai écrit une fonction Python rapide pour convertir une table d'attributs en un dictionnaire Python, où la clé est extraite d'un champ ID unique spécifié par l'utilisateur (généralement le champ OID). De plus, par défaut, tous les champs sont copiés dans le dictionnaire, mais j'ai inclus un paramètre permettant de spécifier uniquement un sous-ensemble.

def make_attribute_dict(fc, key_field, attr_list=['*']):
    dict = {}
    fc_field_objects = arcpy.ListFields(fc)
    fc_fields = [field.name for field in fc_field_objects if field.type != 'Geometry']
    if attr_list == ['*']:
        valid_fields = fc_fields
    else:
        valid_fields = [field for field in attr_list if field in fc_fields]
    if key_field not in valid_fields:
        cursor_fields = valid_fields + [key_field]
    else:
        cursor_fields = valid_fields
    with arcpy.da.SearchCursor(fc, cursor_fields) as cursor:
        for row in cursor:
            key = row[cursor_fields.index(key_field)]
            subdict = {}
            for field in valid_fields:
                subdict[field] = row[cursor_fields.index(field)]
            dict[key] = subdict
            del subdict
    return dict

Cela fonctionne très bien pour des ensembles de données relativement petits, mais je viens de l'exécuter sur une table contenant environ 750 000 lignes et 15 champs - environ 100 Mo dans une géodatabase fichier. Sur ceux-ci, la fonction s'exécute beaucoup plus lentement que ce à quoi je m'attendais: environ 5-6 minutes (et c'est après avoir copié la table dans l' in_memoryespace de travail). J'aimerais vraiment trouver un moyen d'accélérer la conversion en dictionnaire, ou obtenir un aperçu d'une meilleure stratégie pour manipuler de grandes quantités de données d'attributs à l'aide de Python.

UpdateCursors ne fonctionnera pas bien pour moi, car lorsqu'une ligne change, elle a le potentiel de déclencher des changements dans plusieurs autres. Les parcourir et les traiter un par un est trop lourd pour ce dont j'ai besoin.

nmpeterson
la source
2
Le facteur limitant dans quelle mesure vous pouvez optimiser votre script peut être le temps qu'il faut pour parcourir le curseur. Avez-vous comparé le temps nécessaire pour parcourir le curseur sans construire vos dictionnaires?
Jason
2
@Jason commentant les lignes de part en subdict = {}part del subdictdonne un temps de traitement d'environ 10 secondes.
nmpeterson
Vous en savez probablement plus à ce sujet que moi, mais la seule autre chose que j'offrirais en termes d'optimisation est de savoir si l'appel subdict[field] = row[cursor_fields.index(field)]est plus rapide que l'appel subdict[field] = row.getValue(field). Dans ce dernier scénario, vous effectuez une étape ... bien que la différence de performances entre l'indexation de deux listes ( cursor_fieldset row) et l'utilisation d'un seul processus ESRI ne soit pas beaucoup mieux et pourrait même être pire!
Jason

Réponses:

16

Je pense que le problème est probablement vos deux lignes où vous allez sur les champs et ajoutez chaque champ individuellement à votre subdictdictionnaire.

for field in valid_fields:
    subdict[field] = row[cursor_fields.index(field)]

Votre rowobjet est déjà un tuple dans le même ordre que vos champs, profitez-en et utilisez la zipfonction.

def make_attribute_dict(fc, key_field, attr_list=['*']):
    attdict = {}
    fc_field_objects = arcpy.ListFields(fc)
    fc_fields = [field.name for field in fc_field_objects if field.type != 'Geometry']
    if attr_list == ['*']:
        valid_fields = fc_fields
    else:
        valid_fields = [field for field in attr_list if field in fc_fields]
    #Ensure that key_field is always the first field in the field list
    cursor_fields = [key_field] + list(set(valid_fields) - set([key_field]))
    with arcpy.da.SearchCursor(fc, cursor_fields) as cursor:
        for row in cursor:
            attdict[row[0]] = dict(zip(cursor.fields,row))
    return attdict

Cela a traversé une classe d'entités de géodatabase fichier de champ record de 218k en 8 secondes sur mon système.

Edit: J'ai essayé un test plus rigoureux. 518k enregistrements sur une connexion SDE distante avec 16 champs, dont OBJECTID et Shape, exécutés en 32 bits. 11 secondes :)

blord-castillo
la source
1
Notez que j'ai créé key_fieldle premier champ afin de pouvoir compter sur l'utilisation row[0]pour référencer la valeur de key_field. J'ai également dû changer votre variable dicten attdict. dict est un mot-clé, et sans ce mot-clé je ne pourrais pas utiliserdict(zip())
blord-castillo
6
Intelligent. C'est exactement le genre de doux Python idiomatique qui arcpy.daest censé permettre.
Jason Scheirer
Grande perspicacité. J'adore la méthode et ça a vraiment aidé.
nmpeterson