Schnellste Methoden zum Ändern von Attributtabellen mit Python?

12

Vor einiger Zeit habe ich eine schnelle Python-Funktion zum Konvertieren einer Attributtabelle in ein Python-Wörterbuch geschrieben, bei der der Schlüssel einem benutzerdefinierten eindeutigen ID-Feld entnommen wird (normalerweise dem OID-Feld). Außerdem werden standardmäßig alle Felder in das Wörterbuch kopiert, aber ich habe einen Parameter eingefügt, mit dem nur eine Teilmenge angegeben werden kann.

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

Dies funktioniert hervorragend für relativ kleine Datasets, aber ich habe es gerade für eine Tabelle ausgeführt, die ungefähr 750.000 Zeilen und 15 Felder enthält - ungefähr 100 MB in einer File-Geodatabase. Auf diesen läuft die Funktion viel langsamer als ich erwartet hätte: ungefähr 5-6 Minuten (und dies ist nach dem Kopieren der Tabelle in den in_memoryArbeitsbereich). Ich möchte wirklich einen Weg finden, um die Konvertierung in ein Wörterbuch zu beschleunigen, oder einen Einblick in eine bessere Strategie für die Bearbeitung großer Mengen von Attributdaten mit Python erhalten.

UpdateCursors funktioniert bei mir nicht gut, da bei Änderungen an einer Zeile möglicherweise Änderungen an mehreren anderen Zeilen ausgelöst werden. Es ist zu umständlich, sie nacheinander durchzugehen und zu verarbeiten, als dass ich das benötige.

nmpeterson
quelle
2
Der begrenzende Faktor für die Optimierung Ihres Skripts ist möglicherweise die Zeit, die zum Durchlaufen des Cursors benötigt wird. Haben Sie die Zeit verglichen, die zum Durchlaufen des Cursors benötigt wird, ohne Ihre Wörterbücher zu erstellen?
Jason
2
@Jason, der die Zeilen von subdict = {}bis auskommentiert, del subdictergibt eine Verarbeitungszeit von etwa 10 Sekunden.
NMPETERSON
Sie wissen wahrscheinlich mehr darüber als ich, aber die einzige andere Sache, die ich in Bezug auf die Optimierung anbieten würde, ist zu prüfen, ob das Anrufen subdict[field] = row[cursor_fields.index(field)]schneller ist als das Anrufen subdict[field] = row.getValue(field). Im letzteren Szenario würden Sie einen Schritt ausführen ... obwohl der Unterschied in der Leistung zwischen der Indizierung von zwei Listen ( cursor_fieldsund row) und der Verwendung eines einzelnen ESRI-Prozesses möglicherweise nicht viel besser und möglicherweise sogar noch schlechter ist!
Jason

Antworten:

16

Ich denke, das Problem ist wahrscheinlich Ihre zwei Zeilen, in denen Sie über die Felder gehen und jedes Feld einzeln an Ihr subdictWörterbuch anhängen .

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

Ihr rowObjekt ist bereits ein Tupel in der gleichen Reihenfolge wie Ihre Felder, nutzen Sie dies und nutzen Sie die zipFunktion.

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

Dadurch wurde eine Geodatabase-Feature-Class mit 218.000 Einträgen und 16 Feldern in 8 Sekunden auf meinem System durchsucht.

Bearbeiten: Versuchte einen strengeren Test. 518k-Datensätze über eine Remote-SDE-Verbindung mit 16 Feldern, einschließlich OBJECTID und Shape, werden mit 32-Bit ausgeführt. 11 Sekunden :)

Blord-Castillo
quelle
1
Beachten Sie, dass ich key_fielddas erste Feld erstellt habe, damit ich mich darauf verlassen kann row[0], den Wert von zu referenzieren key_field. Ich musste auch Ihre Variable dictauf ändern attdict. dict ist ein Schlüsselwort, und ohne dieses Schlüsselwort könnte ich es nicht verwendendict(zip())
blord-castillo
6
Klug. Dies ist genau die Art von süßer idiomatischer Python, arcpy.dadie es ermöglichen soll.
Jason Scheirer
Großartige Einsicht. Ich liebe die Methode und sie hat wirklich geholfen.
NMPETERSON