¿Los métodos más rápidos para modificar tablas de atributos con Python?

12

Hace un tiempo, escribí una función rápida de Python para convertir una tabla de atributos en un diccionario de Python, donde la clave se toma de un campo de ID único especificado por el usuario (generalmente el campo OID). Además, de forma predeterminada, todos los campos se copian en el diccionario, pero he incluido un parámetro que permite especificar solo un subconjunto.

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

Esto funciona muy bien para conjuntos de datos relativamente pequeños, pero solo lo ejecuté en una tabla que contiene aproximadamente 750,000 filas y 15 campos, alrededor de 100 MB en una geodatabase de archivos. En estos, la función funciona mucho más lentamente de lo que esperaba: alrededor de 5-6 minutos (y esto es después de copiar la tabla en el in_memoryespacio de trabajo). Realmente me gustaría encontrar una manera de acelerar la conversión al diccionario, u obtener una idea de una mejor estrategia para manipular grandes cantidades de datos de atributos utilizando Python.

UpdateCursors no funcionará bien para mí, porque cuando cambia una fila, tiene el potencial de provocar cambios en varias otras. Recorrerlos y procesarlos uno por uno es demasiado engorroso para lo que necesito.

nmpeterson
fuente
2
El factor limitante de cuánto puede optimizar su secuencia de comandos podría ser el tiempo que tarda en recorrer el cursor. ¿Has comparado el tiempo que lleva recorrer el cursor sin construir tus diccionarios?
Jason
2
@Jason comentando las líneas a subdict = {}través de del subdictproduce un tiempo de procesamiento de aproximadamente 10 segundos.
nmpeterson
Probablemente sepa más sobre esto que yo, pero lo único que ofrecería en términos de optimización es ver si las llamadas subdict[field] = row[cursor_fields.index(field)]son más rápidas que las llamadas subdict[field] = row.getValue(field). En el último escenario, estaría realizando un paso ... ¡aunque la diferencia en el rendimiento entre indexar dos listas ( cursor_fieldsy row) y usar un solo proceso de ESRI puede no ser mucho mejor e incluso podría ser peor!
Jason

Respuestas:

16

Creo que el problema es probablemente sus dos líneas donde va sobre los campos y agrega cada campo individualmente a su subdictdiccionario.

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

Su rowobjeto ya es una tupla en el mismo orden que sus campos, aproveche eso y use la zipfunción.

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

Esto atravesó una clase de entidad de geodatabase de archivos de campo de registro de 218k en 8 segundos en 8 segundos en mi sistema.

Editar: Intenté una prueba más rigurosa. Los registros de 518k en una conexión SDE remota con 16 campos, incluidos OBJECTID y Shape, se ejecutan a 32 bits. 11 segundos :)

castillo-blord
fuente
1
Tenga en cuenta que hice key_fieldel primer campo para poder confiar en el uso row[0]para hacer referencia al valor de key_field. También tuve que cambiar tu variable dicta attdict. dict es una palabra clave, y sin esa palabra clave que no podía usardict(zip())
blord-Castillo
66
Inteligente. Este es exactamente el tipo de Python dulce idiomático que arcpy.daestá destinado a habilitar.
Jason Scheirer
Gran perspicacia. Me encanta el método, y realmente ayudó.
nmpeterson