ArcPy kullanarak 16 milyon kayıttan mı geçiyorsunuz?


13

8 sütun ve ~ 16,7 milyon kayıt içeren bir tablo var. Ben sütunlarda if-else denklemleri bir dizi çalıştırmak gerekir. UpdateCursor modülünü kullanarak bir senaryo yazdım, ancak birkaç milyon kayıttan sonra bellek bitiyor. Bu 16,7 milyon kaydı işlemek için daha iyi bir yol olup olmadığını merak ediyordum.

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"

GÜNCELLEME # 1

Aynı komut dosyasını 40 gb RAM ile bir bilgisayarda çalıştırdım (orijinal bilgisayarda sadece 12 gb RAM vardı). ~ 16 saat sonra başarıyla tamamlandı. 16 saatin çok uzun olduğunu hissediyorum, ancak hiç bu kadar büyük veri kümesiyle çalışmadım, bu yüzden ne bekleyeceğimi bilmiyorum. Bu senaryoya tek yeni ekleme arcpy.env.parallelProcessingFactor = "100%". Ben iki önerilen yöntemleri (1) toplu işlerde 1 milyon kayıt yapmak ve (2) SearchCursor kullanarak ve csv çıktıları yazma deniyorum. Kısa süre içinde ilerlemeyi rapor edeceğim.

GÜNCELLEME # 2

SearchCursor ve CSV güncellemesi harika çalıştı! Kesin çalışma süreleri yok, yarın ofiste olduğumda yazıyı güncelleyeceğim, ancak yaklaşık çalışma süresinin ~ 5-6 dakika olduğunu söyleyebilirim ki bu oldukça etkileyici. Beklemiyordum. Herhangi bir yorum ve iyileştirme bekliyoruz cilasız kod paylaşıyorum:

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"

GÜNCELLEME # 3 Son güncelleme. Komut dosyasının toplam çalışma süresi ~ 199.6 saniye / 3.2 dakikadır.


1
64bit mi kullanıyorsunuz (Arka Plan veya Sunucu veya Pro)?
KHibma

Bahsetmeyi unuttum. Arka planda 10.4 x64 çalıştırıyorum.
cptpython

Şeytanlar savunucusu - senaryoyu inceleyerek ön planda veya IDLE'den çalıştırmayı denediniz mi, ArcMap'ı açmanıza gerek yok mu?
Hornbydd

bağımsız bir komut dosyası olarak çalıştırın veya SQL biliyorsanız, şekil dosyasını PostgreSQL'e yükleyin ve orada yapın
ziggy

1
Açık kaynak olduğunu anlıyorum, ancak onay süreci ~ 1-2 hafta sürüyor ve bu zamana duyarlı, bu yüzden bu durumda uygulanabilir olduğunu düşünmüyorum.
cptpython

Yanıtlar:


4

Objectid ve hesaplama sonucunu (cate_2) bir csv dosyasına yazabilirsiniz. Ardından sonucu korumak için csv'yi orijinal dosyanıza ekleyin, bir alanı doldurun. Bu şekilde DA imlecini kullanarak tabloyu güncellemezsiniz. Bir Arama imleci kullanabilirsiniz.


Burada bir tartışma olduğu gibi düşünüyordum ve daha da büyük veri kümelerinden bahsediyorlardı.
Hornbydd

Teşekkürler, klewis. Kulağa umut verici geliyor. FelixIP'in önerisi ve ilginç tartışmalarla birlikte deneyeceğim, ancak bunu birkaç düzine kez çalıştırmak zorunda kalacağım.
cptpython

Zekice çalıştı! Soruyu en son senaryo ile güncelledim. Teşekkürler!
cptpython

2

Özür dilerim, eğer bu eski ipliği yeniden canlandırırsam. Fikir, birleştirme rasterinde if-else ifadelerini gerçekleştirmek ve ardından yeni bir raster oluşturmak için Arama'daki yeni alanı kullanmaktı. Verileri tablo olarak dışa aktararak sorunu karmaşık hale getirdim ve @Alex Tereshenkov tarafından ele alınan verimsiz iş akışını tanıttım. Açık olanı fark ettikten sonra, verileri @FelixIP tarafından önerildiği gibi 17 sorguya (her biri 1 milyon) topluyorum. Her bir partinin tamamlanması ortalama ~ 1.5 dakika sürdü ve toplam çalışma süresi ~ 23.3 dakika idi. Bu yöntem birleştirme ihtiyacını ortadan kaldırır ve bence bu yöntem görevi en iyi şekilde yerine getirir. İşte ileride başvurmak için gözden geçirilmiş bir komut dosyası:

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()

Yani, bunu doğru anladığımdan emin olmak için. Orijinal yayında, bunu 40GB RAM ile bir bilgisayarda çalıştırdığınızda, toplamda ~ 16 saat sürdüğünü söylediniz. Ama şimdi 17 partiye ayırdığınıza göre, toplamda ~ 23 dakika sürdü. Bu doğru mu?
ianbroad

Doğru. İlk çalışma, 40 GB RAM ile ~ 16 saat sürdü ve ikinci çalışma Lookup, yeni tanımlanmış kategorilerle raster gerçekleştirmek ve vermek için ~ 23 dakika + başka bir ~ 15 dakika sürdü .
cptpython

arcpy.env.parallelProcessingFactor = "100%"Senaryonuz üzerinde hiçbir etkisi olmayan bir not . İçeride bu ortamdan yararlanan hiçbir araç görmüyorum.
KHibma

Haklısın. Kodu düzenleyeceğim.
cptpython

1

CalculateField_management'ı kullanmayı değiştirmeyi deneyebilirsiniz . Bu, imleçleri kullanarak döngüyü önler ve kategori değeri için seçeneklerinize bakarak bunu sırayla ortaya çıkan dört alt işlem olarak ayarlayabilirsiniz. Her alt işlem bittiğinde, bir sonraki işleme başlamadan önce belleği serbest bırakılır. Her bir alt işlemi küçük bir (milisaniye) isabet alırsınız.

Ya da, mevcut yaklaşımınızı korumak istiyorsanız, her seferinde x satırı alan bir alt işleme sahip olun. Onu sürmek için ana bir işleminiz var ve daha önce olduğu gibi, her bittiğinde belleğinizi temizlemeye devam ediyorsunuz. Bu şekilde yapmanın bonusu (özellikle tek başına bir python işlemi yoluyla), PIL'in etrafındaki Python'un çoklu iş parçacığında yumurtlama alt süreçleri olarak tüm çekirdeklerinizi daha fazla kullanabilmenizdir. Bu, ArcPy ve geçmişte büyük veri değişimleri yapmak için kullandığım bir yaklaşımla mümkün. Açıkçası veri parçalarınızı saklayın aksi takdirde bellek daha hızlı tükenir!


Deneyimlerime göre arcpy.da.UpdateCursor kullanmak arcpy.CalculateField_management'tan çok daha hızlı. 55.000.000 bina özelliği üzerinde çalışan bir senaryo yazdım, CalculateField aracıyla yaklaşık 5 kat daha yavaştı.
offermann

Buradaki nokta, dört alt işlem ayarlamak ve buradaki gerçek sıkıştırma noktası gibi belleği atmaktır. İkinci paragrafta özetlediğim gibi, alt süreçleri satırlara bölebilirsiniz, ancak bu tek bir seçimden biraz daha fazla yönetim gerektirir.
MappaGnosis

1

Veri işleme mantığı, GDAL / OGR kullanarak, örneğin gdal-filegdbkurulu OSGeo4W ile çalıştırabileceğiniz bir CASE ifadesi kullanılarak bir UPDATE SQL ifadesi olarak yazılabilir .

Şunun osgeo.ogryerine kullanılan iş akışı şöyledir 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)

1 milyonun üzerinde kaydı olan benzer bir tabloda, bu sorgu 18 dakika sürdü. Dolayısıyla, 16 milyon kaydı işlemek ~ 4 ila 5 saat sürebilir.


Ne yazık ki senaryo kullanılarak yazılmış daha büyük bir script'in bir parçası arcpyama cevabı takdir ediyorum. Yavaşça GDAL'ı daha fazla kullanmaya çalışıyorum.
cptpython

1

Sorunuzdaki # 2 bölümündeki kodun güncellenmesi, .csvdosyayı dosya coğrafi veritabanınızdaki orijinal tabloya nasıl eklediğinizi göstermez. Senaryonuzun çalıştırılması ~ 5 dakika sürdü. .csvDosyayı yalnızca herhangi bir birleştirme yapmadan dışa aktardıysanız bu kulağa hoş geliyor . .csvDosyayı ArcGIS'e geri getirmeye çalıştığınızda , performans sorunlarına ulaşacaksınız.

1) Dosyanın bir OID'si olmadığından (benzersiz değerlerle hesaplanan bir alana sahip olmak.csv , .csvdosyanızı yine de bir coğrafi veri tabanı tablosuna dönüştürmeniz gerekeceğinden) doğrudan coğrafi veri tabanı tablosuna katılamazsınız . Yani, GP aracı için birkaç dakika ( geçici bir tablo oluşturmak için çalışma alanını kullanabilirsiniz, biraz daha hızlı olacaktır)..csvTable To Tablein_memory

Eğer yüklü sonra 2) .csvbir coğrafi veritabanı tabloya, size katılmak yapacağını alanında bulunan bir dizin kurmak isterim (sizin durumunuzda, kaynak objectidgelen vaue .csvdosyası. Bu 16mln satır masaya birkaç dakika sürer.

3) O zaman ya Add Joinda Join FieldGP araçlarını kullanmanız gerekir . İkisi de büyük masalarınızda iyi performans göstermez.

4) Daha sonra, Calculate Fieldyeni katılmış alanları hesaplamak için GP aracını yapmanız gerekir . Birçok dakika buraya; daha da fazlası, hesaplamaya katılan alanlar birleştirilmiş tablodan geldiğinde alan hesaplaması daha fazla zaman alır.

Tek kelimeyle, bahsettiğiniz 5 dakikaya yakın bir şey elde edemezsiniz. Bir saat içinde yaparsan çok etkilenirim.

ArcGIS içindeki büyük veri kümelerinin işlenmesinden kaçınmak için, verilerinizi ArcGIS dışında bir pandasveri çerçevesine almanızı ve tüm hesaplamalarınızı orada yapmanızı öneririm . İşiniz bittiğinde, veri çerçevesi satırlarını yeni bir coğrafi veritabanı tablosuna tekrar yazın da.InsertCursor(veya mevcut tablonuzu kısaltabilir ve satırlarınızı kaynak olana yazabilirsiniz).

Bunu karşılaştırmak için yazdığım kodun tamamı aşağıda:

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

Aşağıda, hata ayıklama G / Ç'den (bildirilen sayı, kullanılan bir tablodaki satır sayısıdır) ayrı ayrı işlevler için yürütme süresi hakkında bilgi bulunmaktadır:

---------------------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)

Bir satır eklemek da.InsertCursorsabit bir zaman alır, yani 1 satır eklemek, örneğin 0,1 saniye, 100 satır eklemek 10 saniye sürer. Ne yazık ki, toplam yürütme süresinin% 95'i +, coğrafi veritabanı tablosunu okumak ve ardından satırları coğrafi veritabanına geri eklemek için harcanmaktadır.

pandasAynısı, bir da.SearchCursorjeneratörden bir veri çerçevesi yapmak ve alan (lar) ı hesaplamak için de geçerlidir. Kaynak coğrafi veritabanı tablonuzdaki satır sayısı ikiye katlandıkça, yukarıdaki komut dosyasının yürütme süresi de artar. Tabii ki, 64bit Python'u hala yürütme sırasında kullanmanız gerekiyor , bazı büyük veri yapıları bellekte işlenecek.


Aslında, kullandığım yöntemin sınırlamaları hakkında konuşacak başka bir soru soracaktım, çünkü yukarıda ele aldığınız problemlerle karşılaştım, teşekkürler! Ne başarmaya çalışıyorum: dört Lookupraster birleştirmek ve sonra sütunlara dayalı if-else deyimi gerçekleştirmek ve çıktıları yeni bir sütuna yazmak ve nihayet yeni sütundaki değerlere dayalı raster oluşturmak için gerçekleştirin . Metodumda birçok gereksiz adım ve verimsiz iş akışı vardı, bunu orijinal sorumda belirtmeliydim. Yaşa ve öğren. Senaryonuzu bu hafta daha sonra deneyeceğim.
cptpython
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.