Najszybsze metody modyfikowania tabel atrybutów za pomocą Pythona?

12

Jakiś czas temu napisałem szybką funkcję Pythona do konwertowania tabeli atrybutów na słownik Pythona, w której klucz jest pobierany z unikalnego pola identyfikatora określonego przez użytkownika (zazwyczaj pola OID). Dodatkowo domyślnie wszystkie pola są kopiowane do słownika, ale zawarłem parametr pozwalający na określenie tylko podzestawu.

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

Działa to świetnie w przypadku stosunkowo małych zestawów danych, ale właśnie uruchomiłem go na tabeli zawierającej około 750 000 wierszy i 15 pól - około 100 MB w geobazie pliku. Na nich funkcja działa znacznie wolniej, niż się spodziewałam: około 5-6 minut (i to po skopiowaniu tabeli do in_memoryobszaru roboczego). Naprawdę chciałbym znaleźć sposób na przyspieszenie konwersji na słownik lub uzyskać lepszy wgląd w lepszą strategię manipulowania dużą ilością danych atrybutów za pomocą Pythona.

UpdateCursors nie będzie dla mnie działał dobrze, ponieważ gdy zmienia się jeden wiersz, może on wywołać zmiany w kilku innych. Pętlowanie i przetwarzanie ich pojedynczo jest zbyt kłopotliwe dla tego, czego potrzebuję.

nmpeterson
źródło
2
Czynnikiem ograniczającym, w jakim stopniu możesz zoptymalizować skrypt, może być czas potrzebny na iterację kursora. Czy porównałeś czas potrzebny na iterację kursora bez budowania słowników?
Jason
2
@Jason komentując linie od subdict = {}do, del subdictdaje czas przetwarzania około 10 sekund.
nmpeterson
Prawdopodobnie wiesz o tym więcej niż ja, ale jedyną rzeczą, którą zaoferowałbym w zakresie optymalizacji, jest sprawdzenie, czy dzwonienie subdict[field] = row[cursor_fields.index(field)]jest szybsze niż dzwonienie subdict[field] = row.getValue(field). W tym drugim scenariuszu wykonujesz jeden krok ... chociaż różnica w wydajności między indeksowaniem dwóch list ( cursor_fieldsi row) a użyciem pojedynczego procesu ESRI może nie być znacznie lepsza, a może nawet gorsza!
Jason

Odpowiedzi:

16

Myślę, że problemem są prawdopodobnie dwie linie, w których przechodzisz nad polami i dołączasz każde pole osobno do subdictsłownika.

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

Twój rowobiekt jest już krotką w tej samej kolejności co twoje pola, skorzystaj z tego i użyj zipfunkcji.

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

Spowodowało to przeszukanie klasy obiektów geobazy danych z 16 polowymi rekordami i 16 polami w 8 sekund w moim systemie.

Edycja: Wypróbowałem bardziej rygorystyczny test. 518k rekordów przez zdalne połączenie SDE z 16 polami, w tym OBJECTID i Shape, działającymi w wersji 32-bitowej 11 sekund :)

blord-castillo
źródło
1
Zauważ, że zrobiłem key_fieldpierwsze pole, aby móc polegać na używaniu row[0]do odwoływania się do wartości key_field. Musiałem także zmienić twoją zmienną dictna attdict. dict jest słowem kluczowym i bez tego słowa kluczowego nie byłbym w stanie użyćdict(zip())
blord-castillo
6
Sprytny. Jest to dokładnie taki słodki idiomatyczny Python, który arcpy.dama umożliwić.
Jason Scheirer
Świetny wgląd. Uwielbiam tę metodę i to naprawdę pomogło.
nmpeterson