Métodos mais rápidos para modificar tabelas de atributos com Python?

12

Há um tempo atrás, escrevi uma função rápida do Python para converter uma tabela de atributos em um dicionário python, onde a chave é obtida de um campo de ID exclusivo especificado pelo usuário (normalmente o campo OID). Além disso, por padrão, todos os campos são copiados para o dicionário, mas incluímos um parâmetro que permite especificar apenas um 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

Isso funciona muito bem para conjuntos de dados relativamente pequenos, mas eu apenas o executei em uma tabela contendo cerca de 750.000 linhas e 15 campos - cerca de 100 MB em um geodatabase de arquivo. Nesses, a função é executada muito mais lentamente do que eu esperava: cerca de 5-6 minutos (e isso é depois de copiar a tabela para a área de in_memorytrabalho). Eu realmente gostaria de encontrar uma maneira de acelerar a conversão em dicionário ou obter algumas dicas sobre uma estratégia melhor para manipular grandes quantidades de dados de atributos usando Python.

UpdateCursors não funcionará bem para mim, porque quando uma linha é alterada, ela pode causar alterações em várias outras. Fazer um loop e processá-los um de cada vez é muito complicado para o que eu preciso.

nmpeterson
fonte
2
O fator limitante no quanto você pode otimizar seu script pode ser o tempo que leva para percorrer o cursor. Você comparou o tempo necessário para percorrer o cursor sem criar seus dicionários?
18713 Jason
2
@ Jason comentando as linhas de subdict = {}através del subdictproduz um tempo de processamento de cerca de 10 segundos.
Npmeterson
Você provavelmente sabe mais sobre isso do que eu, mas a única outra coisa que eu ofereceria em termos de otimização é verificar se a chamada subdict[field] = row[cursor_fields.index(field)]é mais rápida do que a chamada subdict[field] = row.getValue(field). No último cenário, você executaria uma etapa ... embora a diferença de desempenho entre a indexação de duas listas ( cursor_fieldse row) e o uso de um único processo ESRI possa não ser muito melhor e apenas pior!
Jason

Respostas:

16

Acho que o problema é provavelmente suas duas linhas em que você está passando os campos e acrescentando cada campo individualmente ao seu subdictdicionário.

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

Seu rowobjeto já é uma tupla na mesma ordem que seus campos, aproveite isso e use a zipfunção

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

Isso resultou em uma classe de recursos de geodatabase de 218k registros e 16 campos em 8 segundos no meu sistema.

Edit: Tentei um teste mais rigoroso. 518k registros em uma conexão SDE remota com 16 campos, incluindo OBJECTID e Shape, são executados em 32 bits. 11 segundos :)

blord-castillo
fonte
1
Observe que eu criei key_fieldo primeiro campo para poder usar o row[0]para referenciar o valor de key_field. Eu também tive que mudar sua variável dictpara attdict. dict é uma palavra-chave, e sem a palavra-chave que eu não poderia usardict(zip())
blord-castillo
6
Esperto. Este é exatamente o tipo de Python idiomático doce que arcpy.dase destina a ativar.
Jason Scheirer
Ótima visão. Adoro o método, e realmente ajudou.
Npmeterson 19/03/2013