Eu tenho uma tabela com 8 colunas e ~ 16,7 milhões de registros. Eu preciso executar um conjunto de equações if-else nas colunas. Eu escrevi um script usando o módulo UpdateCursor, mas depois de alguns milhões de registros, ele fica sem memória. Fiquei me perguntando se existe uma maneira melhor de processar esses 16,7 milhões de registros.
import arcpy
arcpy.TableToTable_conversion("combine_2013", "D:/mosaic.gdb", "combo_table")
c_table = "D:/mosaic.gdb/combo_table"
fields = ['dev_agg', 'herb_agg','forest_agg','wat_agg', 'cate_2']
start_time = time.time()
print "Script Started"
with arcpy.da.UpdateCursor(c_table, fields) as cursor:
for row in cursor:
# row's 0,1,2,3,4 = dev, herb, forest, water, category
#classficiation water = 1; herb = 2; dev = 3; forest = 4
if (row[3] >= 0 and row[3] > row[2]):
row[4] = 1
elif (row[2] >= 0 and row[2] > row[3]):
row[4] = 4
elif (row[1] > 180):
row[4] = 2
elif (row[0] > 1):
row[4] = 3
cursor.updateRow(row)
end_time = time.time() - start_time
print "Script Complete - " + str(end_time) + " seconds"
ATUALIZAÇÃO # 1
Executei o mesmo script em um computador com 40 GB de RAM (o computador original tinha apenas 12 GB de RAM). Concluiu com sucesso após ~ 16 horas. Sinto que 16 horas são muito longas, mas nunca trabalhei com um conjunto de dados tão grande, então não sei o que esperar. A única nova adição a esse script é arcpy.env.parallelProcessingFactor = "100%"
. Estou tentando dois métodos sugeridos (1) fazendo 1 milhão de registros em lotes e (2) usando o SearchCursor e gravando saídas no CSV. Vou relatar o progresso em breve.
ATUALIZAÇÃO # 2
A atualização do SearchCursor e CSV funcionou de maneira brilhante! Eu não tenho os tempos de execução precisos, atualizarei a postagem quando estiver no escritório amanhã, mas diria que o tempo de execução aproximado é de aproximadamente 5-6 minutos, o que é bastante impressionante. Eu não estava esperando por isso. Estou compartilhando meu código não polido. Todos os comentários e melhorias são bem-vindos:
import arcpy, csv, time
from arcpy import env
arcpy.env.parallelProcessingFactor = "100%"
arcpy.TableToTable_conversion("D:/mosaic.gdb/combine_2013", "D:/mosaic.gdb", "combo_table")
arcpy.AddField_management("D:/mosaic.gdb/combo_table","category","SHORT")
# Table
c_table = "D:/mosaic.gdb/combo_table"
fields = ['wat_agg', 'dev_agg', 'herb_agg','forest_agg','category', 'OBJECTID']
# CSV
c_csv = open("D:/combine.csv", "w")
c_writer = csv.writer(c_csv, delimiter= ';',lineterminator='\n')
c_writer.writerow (['OID', 'CATEGORY'])
c_reader = csv.reader(c_csv)
start_time = time.time()
with arcpy.da.SearchCursor(c_table, fields) as cursor:
for row in cursor:
#skip file headers
if c_reader.line_num == 1:
continue
# row's 0,1,2,3,4,5 = water, dev, herb, forest, category, oid
#classficiation water = 1; dev = 2; herb = 3; ; forest = 4
if (row[0] >= 0 and row[0] > row[3]):
c_writer.writerow([row[5], 1])
elif (row[1] > 1):
c_writer.writerow([row[5], 2])
elif (row[2] > 180):
c_writer.writerow([row[5], 3])
elif (row[3] >= 0 and row[3] > row[0]):
c_writer.writerow([row[5], 4])
c_csv.close()
end_time = time.time() - start_time
print str(end_time) + " - Seconds"
ATUALIZAÇÃO # 3 Atualização final. O tempo total de execução do script é de ~ 199,6 segundos / 3,2 minutos.
fonte
Respostas:
Você pode gravar o Objectid e o resultado do cálculo (cate_2) em um arquivo csv. Em seguida, junte o csv ao seu arquivo original, preencha um campo para preservar o resultado. Dessa forma, você não está atualizando a tabela usando o cursor DA. Você pode usar um cursor de pesquisa.
fonte
Desculpas, se eu continuar revivendo esse tópico antigo. A idéia era executar as instruções if-else na varredura combinada e, em seguida, usar o novo campo na Pesquisa para criar uma nova varredura. Eu compliquei o problema exportando os dados como uma tabela e introduzi um fluxo de trabalho ineficiente, abordado por @Alex Tereshenkov. Depois de perceber o óbvio, agrupei os dados em 17 consultas (1 milhão cada), conforme sugerido pelo @FelixIP. Cada lote levou em média ~ 1,5 minutos para concluir e o tempo total de execução foi de ~ 23,3 minutos. Esse método elimina a necessidade de junções e acho que esse método realiza melhor a tarefa. Aqui está um script revisado para referência futura:
fonte
Lookup
e exportar a varredura com categorias recém-definidas.arcpy.env.parallelProcessingFactor = "100%"
não tem efeito no seu script. Não vejo nenhuma ferramenta que aproveite esse ambiente.Você pode tentar mudar para usar CalculateField_management . Isso evita a repetição do uso de cursores e, pela aparência de suas opções para o valor da categoria, você pode configurá-lo como quatro subprocessos gerados sequencialmente. À medida que cada subprocesso termina, sua memória é liberada antes de iniciar o próximo. Você leva um pequeno golpe (milissegundo) gerando cada subprocesso.
Ou, se você quiser manter sua abordagem atual, tenha um subprocesso que ocupa x linhas por vez. Tenha um processo principal para controlá-lo e, como antes, você continua limpando sua memória cada vez que termina. O bônus de fazê-lo dessa maneira (especialmente através de um processo python independente) é que você pode usar mais todos os seus núcleos como subprocessos geradores no multithreading de python que você contorna o GIL. Isso é possível com o ArcPy e uma abordagem que eu usei no passado para fazer grandes rotações de dados. Obviamente, mantenha seus blocos de dados baixos, caso contrário você ficará sem memória mais rapidamente!
fonte
A lógica de manipulação de dados pode ser escrita como uma instrução UPDATE SQL usando uma expressão CASE, que você pode executar usando GDAL / OGR, por exemplo, via OSGeo4W com
gdal-filegdb
instalado.Aqui está o fluxo de trabalho, que usa em
osgeo.ogr
vez dearcpy
:Em uma tabela semelhante com pouco mais de 1 milhão de registros, essa consulta levou 18 minutos. Portanto, ainda pode levar de 4 a 5 horas para processar 16 milhões de registros.
fonte
arcpy
mas agradeço a resposta. Estou lentamente tentando usar mais o GDAL.A atualização do código na seção 2 da sua pergunta não mostra como você está ingressando no
.csv
arquivo de volta à tabela original no geodatabase do arquivo. Você diz que seu script levou ~ 5 minutos para ser executado. Parece justo se você exportou o.csv
arquivo apenas sem fazer nenhuma junção. Ao tentar trazer o.csv
arquivo de volta ao ArcGIS, você encontrará os problemas de desempenho.1) Você não pode fazer junções diretamente da
.csv
tabela de geodatabase, porque o.csv
arquivo não possui um OID (ter um campo calculado com valores exclusivos não ajudará, pois você ainda precisará converter seu.csv
arquivo em uma tabela de geodatabase). Portanto, alguns minutos para aTable To Table
ferramenta GP (você pode usar oin_memory
espaço de trabalho para criar uma tabela temporária lá, será um pouco mais rápido).2) Após carregar a
.csv
tabela em um geodatabase, convém criar um índice no campo em que você faria a junção (no seu caso, o valor da fonteobjectid
do.csv
arquivo. Isso levaria alguns minutos na tabela de linhas de 16 mln).3) Então você precisaria usar as ferramentas
Add Join
ouJoin Field
GP. Nenhum deles terá um bom desempenho em suas tabelas grandes.4) Depois, você precisa fazer a
Calculate Field
ferramenta GP para calcular os campos recém-unidos. Muitos minutos vão aqui; ainda mais, o cálculo do campo leva mais tempo quando os campos que participam do cálculo são provenientes de uma tabela unida.Em uma palavra, você não terá nada perto de cinco minutos mencionados. Se você chegar em uma hora, eu ficaria impressionado.
Para evitar lidar com o processamento de grandes conjuntos de dados no ArcGIS, sugiro levar seus dados para fora do ArcGIS em um
pandas
quadro de dados e fazer todos os seus cálculos lá. Quando terminar, basta escrever as linhas do quadro de dados novamente em uma nova tabela de geodatabase comda.InsertCursor
(ou você pode truncar sua tabela existente e gravar suas linhas na fonte).O código completo que escrevi para comparar isso está abaixo:
Abaixo está a saída do Debug IO (o número relatado é o número de linhas em uma tabela usada) com informações sobre o tempo de execução para funções individuais:
Inserir uma linha com
da.InsertCursor
leva um tempo constante, ou seja, se inserir 1 linha leva, digamos, 0,1 segundo, para inserir 100 linhas, leva 10 segundos. Infelizmente, mais de 95% do tempo total de execução é gasto lendo a tabela de geodatabase e inserindo as linhas novamente no geodatabase.O mesmo se aplica à criação de um
pandas
quadro de dados a partir de umda.SearchCursor
gerador e ao cálculo do (s) campo (s). À medida que o número de linhas na tabela de geodatabase de origem dobra, o tempo de execução do script acima também. Obviamente, você ainda precisará usar o Python de 64 bits, pois durante a execução, algumas estruturas de dados maiores serão tratadas na memória.fonte
Lookup
para criar uma varredura com base nos valores da nova coluna. Meu método teve muitas etapas desnecessárias e fluxo de trabalho ineficiente. Eu deveria ter mencionado isso na minha pergunta original. Viva e aprenda. Vou tentar o seu script ainda esta semana.