Looping melalui 16 juta catatan menggunakan ArcPy?

13

Saya punya tabel dengan 8 kolom dan ~ 16,7 juta catatan. Saya perlu menjalankan satu set persamaan if-else pada kolom. Saya telah menulis skrip menggunakan modul UpdateCursor, tetapi setelah beberapa juta catatan kehabisan memori. Saya bertanya-tanya apakah ada cara yang lebih baik untuk memproses 16,7 juta catatan ini.

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"

PEMBARUAN # 1

Saya menjalankan skrip yang sama di komputer dengan 40 gb RAM (komputer asli hanya memiliki 12 gb RAM). Ini berhasil diselesaikan setelah ~ 16 jam. Saya merasa bahwa 16 jam terlalu lama, tetapi saya tidak pernah bekerja dengan set data yang besar sehingga saya tidak tahu apa yang diharapkan. Satu-satunya tambahan baru untuk skrip ini adalah arcpy.env.parallelProcessingFactor = "100%". Saya mencoba dua metode yang disarankan (1) melakukan 1 juta catatan dalam batch dan (2) menggunakan SearchCursor dan menulis output ke csv. Saya akan segera melaporkan perkembangannya.

PEMBARUAN # 2

Pembaruan SearchCursor dan CSV bekerja dengan sangat baik! Saya tidak memiliki waktu menjalankan yang tepat, saya akan memperbarui pos ketika saya berada di kantor besok tapi saya akan mengatakan perkiraan waktu berjalan ~ 5-6 menit yang cukup mengesankan. Saya tidak mengharapkannya. Saya membagikan kode saya yang tidak dipoles, semua komentar dan perbaikan disambut:

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"

UPDATE # 3 Pembaruan akhir. Total waktu menjalankan skrip adalah ~ 199,6 detik / 3,2 menit.

cptpython
sumber
1
Apakah Anda menggunakan 64bit (baik Latar Belakang atau Server atau Pro)?
KHibma
Lupa menyebutkan. Saya menjalankan 10,4 x64 di Latar Belakang.
cptpython
Pendukung Setan - sudahkah Anda mencoba menjalankannya di latar depan atau dari IDLE ketika melihat skrip Anda, Anda tidak perlu membuka ArcMap?
Hornbydd
jalankan sebagai skrip mandiri atau jika Anda tahu SQL, unggah shapefile ke PostgreSQL dan lakukan di sana
ziggy
1
Saya mengerti ini open source, tapi proses persetujuannya memakan waktu ~ 1-2 minggu, dan ini waktu yang sensitif jadi saya pikir itu tidak layak dalam hal ini.
cptpython

Jawaban:

4

Anda dapat menulis Objectid dan hasil perhitungan (cate_2) ke file csv. Kemudian gabungkan csv ke file asli Anda, isi bidang, untuk mempertahankan hasilnya. Dengan cara ini Anda tidak memperbarui tabel menggunakan kursor DA. Anda dapat menggunakan kursor Pencarian.

Klewis
sumber
Saya memikirkan hal yang sama karena ada diskusi di sini dan mereka berbicara tentang dataset yang lebih besar.
Hornbydd
Terima kasih, klewis. Ini kedengarannya menjanjikan. Saya akan mencobanya bersama dengan saran FelixIP, dan diskusi yang menarik meskipun saya harus menjalankan ini beberapa lusin kali.
cptpython
Bekerja dengan cemerlang! Saya telah memperbarui pertanyaan dengan skrip terbaru. Terima kasih!
cptpython
2

Maaf, jika saya terus menghidupkan kembali utas lama ini. Idenya adalah untuk melakukan pernyataan if-else pada raster gabungan dan kemudian menggunakan bidang baru di pencarian untuk membuat raster baru. Saya mempersulit masalah dengan mengekspor data sebagai tabel dan memperkenalkan alur kerja yang tidak efisien yang ditangani oleh @Alex Tereshenkov. Setelah menyadari yang jelas, saya mengumpulkan data menjadi 17 pertanyaan (masing-masing 1 juta) seperti yang disarankan oleh @FelixIP. Butuh rata-rata setiap batch ~ 1,5 menit untuk menyelesaikan dan total waktu run ~ 23,3 menit. Metode ini menghilangkan kebutuhan untuk bergabung dan saya pikir metode ini paling baik menyelesaikan tugas. Berikut ini skrip yang direvisi untuk referensi di masa mendatang:

import arcpy, time
from arcpy import env

def cursor():
    combine = "D:/mosaic.gdb/combine_2013"
    #arcpy.AddField_management(combine,"cat_1","SHORT")
    fields = ['wat_agg', 'dev_agg', 'herb_agg','forest_agg', 'cat_1']
    batch = ['"OBJECTID" >= 1 AND "OBJECTID" <= 1000000', '"OBJECTID" >= 1000001 AND "OBJECTID" <= 2000000', '"OBJECTID" >= 2000001 AND "OBJECTID" <= 3000000', '"OBJECTID" >= 3000001 AND "OBJECTID" <= 4000000', '"OBJECTID" >= 4000001 AND "OBJECTID" <= 5000000', '"OBJECTID" >= 5000001 AND "OBJECTID" <= 6000000', '"OBJECTID" >= 6000001 AND "OBJECTID" <= 7000000', '"OBJECTID" >= 7000001 AND "OBJECTID" <= 8000000', '"OBJECTID" >= 8000001 AND "OBJECTID" <= 9000000', '"OBJECTID" >= 9000001 AND "OBJECTID" <= 10000000', '"OBJECTID" >= 10000001 AND "OBJECTID" <= 11000000', '"OBJECTID" >= 11000001 AND "OBJECTID" <= 12000000', '"OBJECTID" >= 12000001 AND "OBJECTID" <= 13000000', '"OBJECTID" >= 13000001 AND "OBJECTID" <= 14000000', '"OBJECTID" >= 14000001 AND "OBJECTID" <= 15000000', '"OBJECTID" >= 15000001 AND "OBJECTID" <= 16000000', '"OBJECTID" >= 16000001 AND "OBJECTID" <= 16757856']
    for i in batch:
        start_time = time.time()
        with arcpy.da.UpdateCursor(combine, fields, i) as cursor:
            for row in cursor:
            # row's 0,1,2,3,4,5 = water, dev, herb, forest, category
            #classficiation water = 1; dev = 2; herb = 3; ; forest = 4
                if (row[0] >= 0 and row[0] >= row[3]):
                    row[4] = 1
                elif (row[1] > 1):
                    row[4] = 2
                elif (row[2] > 180):
                    row[4] = 3
                elif (row[3] >= 0 and row[3] > row[0]):
                    row[4] = 4
                cursor.updateRow(row)
        end_time =  time.time() - start_time
        print str(end_time) + " - Seconds"

cursor()
cptpython
sumber
Jadi, hanya untuk memastikan saya memahami ini dengan benar. Dalam posting asli Anda, Anda mengatakan bahwa ketika Anda menjalankan ini pada komputer dengan 40GB RAM, butuh ~ 16 jam total. Tapi sekarang Anda membaginya menjadi 17 batch, dan butuh ~ 23 menit total. Apakah itu benar?
ianbroad
Benar. Run pertama memakan waktu ~ 16 jam dengan 40 GB RAM dan run kedua mengambil ~ 23 menit + lagi ~ 15 menit untuk melakukan Lookupdan mengekspor raster dengan kategori yang baru ditentukan.
cptpython
Hanya catatan yang arcpy.env.parallelProcessingFactor = "100%"tidak berpengaruh pada skrip Anda. Saya tidak melihat alat apa pun di sana yang memanfaatkan lingkungan itu.
KHibma
Kamu benar. Saya akan mengedit kode.
cptpython
1

Anda dapat mencoba mengubah menggunakan CalculateField_management . Ini menghindari perulangan menggunakan kursor dan, dengan melihat opsi Anda untuk nilai kategori, Anda bisa mengatur ini sebagai empat subproses yang muncul secara berurutan. Ketika setiap subproses selesai, memorinya dilepaskan sebelum memulai yang berikutnya. Anda menerima hit kecil (milidetik) untuk menelurkan setiap subproses.

Atau, jika Anda ingin mempertahankan pendekatan Anda saat ini, miliki subproses yang mengambil x-rows sekaligus. Memiliki proses utama untuk mengendarainya dan, seperti sebelumnya, Anda terus mencari memori Anda setiap kali selesai. Bonus melakukannya dengan cara ini (terutama melalui proses python yang berdiri sendiri) adalah bahwa Anda dapat menggunakan lebih banyak dari semua core Anda sebagai sub-sub proses pemuliaan dalam multthreading python yang Anda dapatkan di sekitar GIL. Ini dimungkinkan dengan ArcPy dan pendekatan yang saya gunakan di masa lalu untuk melakukan churn data besar-besaran. Jelas menjaga potongan data Anda turun kalau tidak Anda akan kehabisan memori lebih cepat!

MappaGnosis
sumber
Dalam pengalaman saya, menggunakan arcpy.da.UpdateCursor jauh lebih cepat daripada arcpy.CalculateField_management. Saya menulis sebuah skrip yang berjalan di 55.000.000 fitur bangunan, itu sekitar 5 kali lebih lambat dengan alat CalculateField.
offermann
Intinya adalah mengatur empat subproses dan mengais memori karena itu adalah titik sejumput yang sebenarnya di sini. Seperti yang saya uraikan dalam paragraf kedua Anda dapat membagi subproses dengan baris, tetapi itu membutuhkan sedikit manajemen lebih dari satu pilihan.
MappaGnosis
1

Logika manipulasi data dapat ditulis sebagai pernyataan SQL UPDATE menggunakan ekspresi KASUS, yang dapat Anda jalankan menggunakan GDAL / OGR, misalnya melalui OSGeo4W dengan gdal-filegdbdiinstal.

Inilah alur kerjanya, yang menggunakan osgeo.ogralih-alih arcpy:

import time
from osgeo import ogr

ds = ogr.Open('D:/mosaic.gdb', 1)
if ds is None:
    raise ValueError("You don't have a 'FileGDB' driver, or the dataset doesn't exist")
sql = '''\
UPDATE combo_table SET cate_2 = CASE
    WHEN wat_agg >= 0 AND wat_agg > forest_agg THEN 1
    WHEN dev_agg > 1 THEN 2
    WHEN herb_agg > 180 THEN 3
    WHEN forest_agg >= 0 AND forest_agg > wat_agg THEN 4
    END
'''
start_time = time.time()
ds.ExecuteSQL(sql, dialect='sqlite')
ds = None  # save, close
end_time =  time.time() - start_time
print("that took %.1f seconds" % end_time)

Di meja yang sama dengan lebih dari 1 juta catatan, kueri ini memakan waktu 18 menit. Jadi mungkin masih butuh ~ 4 hingga 5 jam untuk memproses 16 juta rekaman.

Mike T
sumber
Sayangnya skrip ini merupakan bagian dari skrip yang lebih besar yang ditulis menggunakan arcpytetapi saya menghargai jawabannya. Saya perlahan mencoba menggunakan GDAL lebih banyak.
cptpython
1

Pembaruan kode di bagian # 2 dalam pertanyaan Anda tidak menunjukkan bagaimana Anda bergabung dengan .csvfile kembali ke tabel asli di geodatabase file Anda. Anda mengatakan bahwa skrip Anda membutuhkan waktu ~ 5 menit untuk dijalankan. Ini terdengar adil jika Anda hanya mengekspor .csvfile tanpa melakukan penggabungan. Ketika Anda akan mencoba untuk membawa .csvfile kembali ke ArcGIS, Anda akan menemukan masalah kinerja.

1) Anda tidak dapat melakukan penggabungan langsung dari .csvke tabel geodatabase, karena .csvfile tersebut tidak memiliki OID (memiliki bidang yang dihitung dengan nilai unik tidak akan membantu karena Anda masih perlu mengonversi .csvfile Anda ke tabel geodatabase). Jadi, beberapa menit untuk Table To Tablealat GP (Anda bisa menggunakan in_memoryruang kerja untuk membuat tabel temp di sana, akan sedikit lebih cepat).

2) Setelah Anda memuatnya .csvke dalam tabel geodatabase, Anda ingin membuat indeks pada bidang yang akan Anda ikuti (dalam kasus Anda, sumber objectidvaue dari .csvfile. Ini akan memakan waktu beberapa menit pada tabel baris 16mln.

3) Maka Anda perlu menggunakan alat Add Joinatau Join FieldGP. Tidak ada yang akan bekerja dengan baik di meja besar Anda.

4) Setelah itu, Anda perlu melakukan Calculate Fieldalat GP untuk menghitung bidang yang baru bergabung. Banyak menit di sini; bahkan lebih lagi, perhitungan bidang membutuhkan lebih banyak waktu ketika bidang yang berpartisipasi dalam perhitungan berasal dari tabel gabungan.

Singkatnya, Anda tidak akan mendapatkan apa pun yang mendekati 5 menit yang Anda sebutkan. Jika Anda akan berhasil dalam satu jam, saya akan terkesan.

Untuk menghindari berurusan dengan pemrosesan dataset besar dalam ArcGIS, saya sarankan mengambil data Anda di luar ArcGIS ke dalam pandaskerangka data dan melakukan semua perhitungan Anda di sana. Ketika Anda selesai, cukup tulis baris data frame kembali ke tabel geodatabase baru da.InsertCursor(atau Anda bisa memotong tabel yang sudah ada dan menulis baris Anda ke sumbernya).

Kode lengkap yang saya tulis untuk benchmark ini adalah di bawah ini:

import time
from functools import wraps
import arcpy
import pandas as pd

def report_time(func):
    '''Decorator reporting the execution time'''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, round(end-start,3))
        return result
    return wrapper

#----------------------------------------------------------------------
@report_time
def make_df(in_table,limit):
    columns = [f.name for f in arcpy.ListFields(in_table) if f.name != 'OBJECTID']
    cur = arcpy.da.SearchCursor(in_table,columns,'OBJECTID < {}'.format(limit))
    rows = (row for row in cur)
    df = pd.DataFrame(rows,columns=columns)
    return df

#----------------------------------------------------------------------
@report_time
def calculate_field(df):
    df.ix[(df['DataField2'] % 2 == 0), 'Category'] = 'two'
    df.ix[(df['DataField2'] % 4 == 0), 'Category'] = 'four'
    df.ix[(df['DataField2'] % 5 == 0), 'Category'] = 'five'
    df.ix[(df['DataField2'] % 10 == 0), 'Category'] = 'ten'
    df['Category'].fillna('other', inplace=True)
    return df

#----------------------------------------------------------------------
@report_time
def save_gdb_table(df,out_table):
    rows_to_write = [tuple(r[1:]) for r in df.itertuples()]
    with arcpy.da.InsertCursor(out_table,df.columns) as ins_cur:
        for row in rows_to_write:
            ins_cur.insertRow(row)

#run for tables of various sizes
for limit in [100000,500000,1000000,5000000,15000000]:
    print '{:,}'.format(limit).center(50,'-')

    in_table = r'C:\ArcGIS\scratch.gdb\BigTraffic'
    out_table = r'C:\ArcGIS\scratch.gdb\BigTrafficUpdated'
    if arcpy.Exists(out_table):
        arcpy.TruncateTable_management(out_table)

    df = make_df(in_table,limit=limit)
    df = calculate_field(df)
    save_gdb_table(df, out_table)
    print

Di bawah ini adalah output dari IO Debug (jumlah yang dilaporkan adalah jumlah baris dalam tabel yang digunakan) dengan info waktu eksekusi untuk fungsi individual:

---------------------100,000----------------------
('make_df', 1.141)
('calculate_field', 0.042)
('save_gdb_table', 1.788)

---------------------500,000----------------------
('make_df', 4.733)
('calculate_field', 0.197)
('save_gdb_table', 8.84)

--------------------1,000,000---------------------
('make_df', 9.315)
('calculate_field', 0.392)
('save_gdb_table', 17.605)

--------------------5,000,000---------------------
('make_df', 45.371)
('calculate_field', 1.903)
('save_gdb_table', 90.797)

--------------------15,000,000--------------------
('make_df', 136.935)
('calculate_field', 5.551)
('save_gdb_table', 275.176)

Memasukkan baris dengan da.InsertCursormembutuhkan waktu yang konstan, yaitu, jika memasukkan 1 baris, katakanlah, 0,1 detik, untuk memasukkan 100 baris akan memakan waktu 10 detik. Sayangnya, 95% + dari total waktu eksekusi dihabiskan untuk membaca tabel geodatabase dan kemudian memasukkan baris kembali ke geodatabase.

Hal yang sama berlaku untuk membuat pandasbingkai data dari da.SearchCursorgenerator dan untuk menghitung bidang. Karena jumlah baris dalam tabel geodatabase sumber Anda berlipat ganda, demikian juga waktu eksekusi skrip di atas. Tentu saja, Anda masih perlu menggunakan Python 64bit karena selama eksekusi, beberapa struktur data yang lebih besar akan ditangani dalam memori.

Alex Tereshenkov
sumber
Sebenarnya, saya akan mengajukan pertanyaan lain yang akan berbicara tentang keterbatasan metode yang saya gunakan, karena saya mengalami masalah yang Anda bahas di atas jadi terima kasih! Apa yang saya coba capai: menggabungkan empat raster dan kemudian melakukan pernyataan if-else berdasarkan kolom dan menuliskan output ke dalam kolom baru dan akhirnya melakukan Lookupuntuk membuat raster berdasarkan nilai-nilai di kolom baru. Metode saya memiliki banyak langkah yang tidak perlu dan alur kerja yang tidak efisien, saya seharusnya menyebutkan ini dalam pertanyaan awal saya. Hidup dan belajar. Saya akan mencoba skrip Anda akhir minggu ini.
cptpython