วนรอบ 16 ล้านบันทึกโดยใช้ ArcPy?


13

ฉันมีตารางที่มี 8 คอลัมน์และประมาณ 16.7 ล้านเรคคอร์ด ฉันต้องใช้ชุดสมการ if-else บนคอลัมน์ ฉันเขียนสคริปต์โดยใช้โมดูล UpdateCursor แต่หลังจากบันทึกไปสองสามล้านครั้งมันมีหน่วยความจำไม่เพียงพอ ฉันสงสัยว่าจะมีวิธีที่ดีกว่าในการประมวลผลระเบียน 16.7 ล้านรายการเหล่านี้หรือไม่

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"

อัปเดต # 1

ฉันรันสคริปต์เดียวกันบนคอมพิวเตอร์ที่มี RAM 40 gb (คอมพิวเตอร์เดิมมี RAM เพียง 12 gb) มันเสร็จสมบูรณ์หลังจาก ~ 16 ชั่วโมง ฉันรู้สึกว่า 16 ชั่วโมงนั้นยาวเกินไป แต่ฉันไม่เคยทำงานกับชุดข้อมูลขนาดใหญ่เช่นนี้ดังนั้นฉันจึงไม่รู้ว่าจะเกิดอะไรขึ้น การเพิ่มสคริปต์ใหม่นี้arcpy.env.parallelProcessingFactor = "100%"เท่านั้น ฉันพยายามสองวิธีที่แนะนำ (1) ทำ 1 ล้านบันทึกใน batches และ (2) โดยใช้ SearchCursor และเขียนผลลัพธ์ไปยัง csv ฉันจะรายงานความคืบหน้าในไม่ช้า

อัปเดต # 2

SearchCursor และการอัปเดต CSV ทำงานได้อย่างยอดเยี่ยม! ฉันไม่มีเวลาทำงานที่แม่นยำฉันจะอัปเดตโพสต์เมื่อฉันอยู่ในสำนักงานในวันพรุ่งนี้ แต่ฉันจะบอกว่าเวลาทำงานโดยประมาณคือประมาณ 5-6 นาทีซึ่งค่อนข้างน่าประทับใจ ฉันไม่ได้คาดหวัง ฉันแบ่งปันรหัสที่ยังไม่ขัดของฉันความคิดเห็นและการปรับปรุงใด ๆ ที่ได้รับการต้อนรับ:

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"

อัปเดต# 3อัปเดตสุดท้าย เวลาทำงานทั้งหมดของสคริปต์คือ ~ 199.6 วินาที / 3.2 นาที


1
คุณใช้ 64 บิต (Background หรือ Server หรือ Pro) หรือไม่?
KHibma

ลืมพูดถึง ฉันใช้ 10.4 x64 ในพื้นหลัง
cptpython

ผู้สนับสนุน Devils - คุณลองใช้งานในโหมดเบื้องหน้าหรือจาก IDLE โดยดูที่สคริปต์ของคุณว่าคุณไม่จำเป็นต้องเปิด ArcMap ไหม?
Hornbydd

เรียกใช้มันเป็นสคริปต์แบบสแตนด์อโลนหรือถ้าคุณรู้ว่า SQL อัปโหลดเชปกับ PostgreSQL และทำมันมี
Ziggy

1
ฉันเข้าใจว่ามันเป็นโอเพ่นซอร์ส แต่กระบวนการอนุมัติใช้เวลาประมาณ 1-2 สัปดาห์และนี่เป็นช่วงเวลาที่อ่อนไหวดังนั้นฉันจึงไม่คิดว่ามันจะเป็นไปได้ในกรณีนี้
cptpython

คำตอบ:


4

คุณสามารถเขียน Objectid และผลลัพธ์ของการคำนวณ (cate_2) ไปยังไฟล์ csv จากนั้นเข้าร่วม csv กับไฟล์ต้นฉบับของคุณเติมฟิลด์เพื่อเก็บผลลัพธ์ วิธีนี้คุณไม่ได้อัปเดตตารางโดยใช้เคอร์เซอร์ DA คุณสามารถใช้เคอร์เซอร์ค้นหา


ฉันคิดเช่นเดียวกันเนื่องจากมีการสนทนาที่นี่และพวกเขากำลังพูดถึงชุดข้อมูลที่ใหญ่กว่าเดิม
Hornbydd

ขอบคุณ klewis ฟังดูมีแนวโน้ม ฉันจะลองพร้อมกับคำแนะนำของ FelixIP และการสนทนาที่น่าสนใจแม้ว่าฉันจะต้องเรียกใช้สองสามครั้งนี้
cptpython

ทำงานเก่ง! ฉันได้อัปเดตคำถามด้วยสคริปต์ล่าสุด ขอบคุณ!
cptpython

2

ขออภัยถ้าฉันยังคงฟื้นเธรดเก่านี้ แนวคิดคือการดำเนินการคำสั่ง if-else บนการรวมแรสเตอร์จากนั้นใช้ฟิลด์ใหม่ในการค้นหาเพื่อสร้างแรสเตอร์ใหม่ ฉันซับซ้อนปัญหาด้วยการส่งออกข้อมูลเป็นตารางและแนะนำเวิร์กโฟลว์ที่ไม่มีประสิทธิภาพซึ่งได้รับการแก้ไขโดย @Alex Tereshenkov หลังจากทราบชัดเจนฉันแบตช์ข้อมูลเป็น 17 แบบสอบถาม (แต่ละ 1 ล้าน) ตามที่ @FelixIP แนะนำ ใช้เวลาแต่ละชุดโดยเฉลี่ยประมาณ 1.5 นาทีจึงจะเสร็จสมบูรณ์และเวลาดำเนินการทั้งหมดคือประมาณ 23.3 นาที วิธีนี้ไม่จำเป็นสำหรับการเข้าร่วมและฉันคิดว่าวิธีนี้จะทำงานได้ดีที่สุด นี่คือสคริปต์ที่ได้รับการแก้ไขสำหรับการอ้างอิงในอนาคต:

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

ดังนั้นเพื่อให้แน่ใจว่าฉันเข้าใจสิ่งนี้อย่างถูกต้อง ในโพสต์ต้นฉบับของคุณคุณบอกว่าเมื่อคุณรันสิ่งนี้บนคอมพิวเตอร์ที่มี RAM ขนาด 40GB จะใช้เวลาทั้งหมดประมาณ 16 ชั่วโมง แต่ตอนนี้คุณแยกมันออกเป็น 17 แบทช์และใช้เวลาทั้งหมดประมาณ 23 นาที ถูกต้องหรือไม่
ianbroad

แก้ไข. การวิ่งครั้งแรกใช้เวลา ~ 16 ชั่วโมงโดยมี RAM 40 GB และการวิ่งครั้งที่สองใช้เวลา ~ 23 นาที + อีกประมาณ 15 นาทีเพื่อทำการLookupส่งออกแรสเตอร์ด้วยหมวดหมู่ที่กำหนดใหม่
cptpython

เพียงแค่ทราบว่าarcpy.env.parallelProcessingFactor = "100%"ไม่มีผลกับสคริปต์ของคุณ ฉันไม่เห็นเครื่องมือใด ๆ ในนั้นที่ใช้ประโยชน์จากสภาพแวดล้อมนั้น
KHibma

คุณถูกต้อง ฉันจะแก้ไขรหัส
cptpython

1

คุณอาจจะลองเปลี่ยนไปใช้CalculateField_management วิธีนี้จะหลีกเลี่ยงการวนซ้ำผ่านการใช้เคอร์เซอร์และด้วยลักษณะของตัวเลือกของคุณสำหรับค่าหมวดหมู่คุณสามารถตั้งค่านี้เป็นสี่กระบวนการย่อยที่วางเรียงตามลำดับ เมื่อแต่ละกระบวนการย่อยเสร็จสิ้นหน่วยความจำจะถูกปล่อยออกมาก่อนที่จะเริ่มต้นกระบวนการถัดไป คุณใช้เวลาเล็กน้อย (มิลลิวินาที) ในการวางไข่แต่ละกระบวนการย่อย

หรือถ้าคุณต้องการที่จะรักษาแนวทางปัจจุบันของคุณให้มีกระบวนการย่อยที่ใช้เวลาแถว x ในแต่ละครั้ง มีกระบวนการหลักในการขับเคลื่อนและก่อนหน้านี้คุณเก็บหน่วยความจำของคุณทุกครั้งที่เสร็จสิ้น โบนัสในการทำเช่นนี้ (โดยเฉพาะอย่างยิ่งผ่านกระบวนการหลามแบบสแตนด์อโลน) คือคุณสามารถใช้ประโยชน์จากคอร์ทั้งหมดของคุณให้มากขึ้นเพื่อวางโพรเซสที่วางไข่ในการมัลติเธรดแบบไพ ธ อนที่คุณได้รอบ GIL สิ่งนี้เป็นไปได้ด้วย ArcPy และวิธีการที่ฉันใช้ในอดีตเพื่อทำข้อมูลจำนวนมหาศาล เห็นได้ชัดว่าเก็บข้อมูลไว้ไม่งั้นคุณจะหมดความจำเร็วขึ้น!


จากประสบการณ์ของฉันการใช้ arcpy.da.UpdateCursor นั้นเร็วกว่า arcpy.CalculateField_management ฉันเขียนสคริปต์ที่ทำงานบนคุณสมบัติการสร้าง 55.000.000 มันช้าลงประมาณ 5 เท่าด้วยเครื่องมือ CalculateField
offermann

จุดคือการตั้งค่าสี่ subprocesses และไล่ความทรงจำที่เป็นจุดหยิกจริงที่นี่ ในขณะที่ฉันร่างในย่อหน้าที่สองคุณสามารถแยกกระบวนการย่อยตามแถว แต่นั่นต้องใช้การจัดการมากกว่าการเลือกเพียงครั้งเดียว
MappaGnosis

1

ตรรกะการจัดการข้อมูลสามารถเขียนเป็นคำสั่ง SQL ที่อัพเดทโดยใช้นิพจน์ CASE ซึ่งคุณสามารถดำเนินการโดยใช้ GDAL / OGR เช่นผ่าน OSGeo4W พร้อมกับการgdal-filegdbติดตั้ง

นี่คือเวิร์กโฟลว์ซึ่งใช้osgeo.ogrแทน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 ล้านระเบียนแบบสอบถามนี้ใช้เวลา 18 นาที ดังนั้นอาจใช้เวลาประมาณ 4 ถึง 5 ชั่วโมงในการประมวลผล 16 ล้านเรคคอร์ด


น่าเสียดายที่สคริปต์เป็นส่วนหนึ่งของสคริปต์ที่มีขนาดใหญ่กว่าarcpyแต่ฉันขอขอบคุณคำตอบ ฉันพยายามใช้ GDAL ช้ากว่านี้
cptpython

1

การอัปเดตรหัสในส่วน # 2 ในคำถามของคุณไม่แสดงว่าคุณเข้าร่วม.csvไฟล์อย่างไรกลับไปที่ตารางต้นฉบับในฐานข้อมูลไฟล์ของคุณ คุณบอกว่าสคริปต์ของคุณใช้เวลาประมาณ 5 นาทีในการทำงาน สิ่งนี้ฟังดูดีถ้าคุณส่งออก.csvไฟล์โดยไม่ทำการเชื่อมต่อใด ๆ เมื่อคุณจะพยายามนำ.csvไฟล์กลับไปที่ ArcGIS คุณจะพบกับปัญหาประสิทธิภาพการทำงาน

1) คุณไม่สามารถเชื่อมต่อโดยตรงจาก.csvไปยังตารางฐานข้อมูลเนื่องจาก.csvไฟล์ไม่มี OID (การมีเขตข้อมูลที่คำนวณด้วยค่าที่ไม่ซ้ำกันจะไม่ช่วยให้คุณต้องแปลง.csvไฟล์ของคุณเป็นตารางฐานข้อมูล) ดังนั้นเวลาหลายนาทีสำหรับTable To Tableเครื่องมือ GP (คุณสามารถใช้in_memoryพื้นที่ทำงานเพื่อสร้างตารางชั่วคราวที่นั่นจะเร็วขึ้นเล็กน้อย)

2) หลังจากที่คุณโหลด.csvลงในตารางฐานข้อมูลคุณจะต้องสร้างดัชนีในฟิลด์ที่คุณจะเข้าร่วม (ในกรณีของคุณแหล่งที่มาobjectidvaue จาก.csvไฟล์นี้จะใช้เวลาไม่กี่นาทีในตารางแถว 16mln

3) จากนั้นคุณจะต้องใช้เครื่องมือAdd JoinหรือJoin FieldGP จะไม่ทำงานได้ดีบนโต๊ะขนาดใหญ่ของคุณ

4) หลังจากนั้นคุณต้องทำCalculate Fieldเครื่องมือ GP เพื่อคำนวณฟิลด์ที่เข้าร่วมใหม่ หลายนาทีไปที่นี่ ยิ่งการคำนวณภาคสนามใช้เวลามากขึ้นเมื่อเขตข้อมูลที่มีส่วนร่วมในการคำนวณมาจากตารางที่เข้าร่วม

ในคำใด ๆ คุณจะไม่ได้รับอะไรใกล้ 5 นาทีที่คุณพูดถึง หากคุณจะทำในอีกหนึ่งชั่วโมงฉันจะประทับใจ

เพื่อหลีกเลี่ยงการจัดการกับการประมวลผลชุดข้อมูลขนาดใหญ่ภายใน ArcGIS ฉันขอแนะนำให้นำข้อมูลของคุณนอก ArcGIS มาไว้ในpandasกรอบข้อมูลและทำการคำนวณทั้งหมดของคุณที่นั่น เมื่อเสร็จแล้วให้เขียนแถวเฟรมข้อมูลกลับเข้าไปในตารางฐานข้อมูลใหม่ด้วยda.InsertCursor(หรือคุณสามารถตัดตารางที่มีอยู่ของคุณออกแล้วเขียนแถวของคุณลงในแหล่งข้อมูลหนึ่ง)

รหัสที่สมบูรณ์ที่ฉันเขียนถึงมาตรฐานนี้อยู่ด้านล่าง:

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

ด้านล่างคือผลลัพธ์จาก Debug IO (จำนวนที่รายงานคือจำนวนแถวในตารางที่ใช้) พร้อมข้อมูลเกี่ยวกับเวลาดำเนินการสำหรับแต่ละฟังก์ชัน:

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

การแทรกแถวที่มีda.InsertCursorเวลาคงที่นั่นคือถ้าการแทรก 1 แถวใช้เวลาพูด 0.1 วินาทีเพื่อแทรก 100 แถวจะใช้เวลา 10 วินาที น่าเสียดายที่ 95% + ของเวลาดำเนินการทั้งหมดนั้นใช้เวลาอ่านตารางฐานข้อมูลแล้วจึงแทรกแถวกลับไปยังฐานข้อมูลภูมิศาสตร์

เช่นเดียวกับการสร้างpandasเฟรมข้อมูลจากda.SearchCursorเครื่องกำเนิดไฟฟ้าและการคำนวณเขตข้อมูล เมื่อจำนวนแถวในตารางฐานข้อมูลต้นทางของคุณเพิ่มขึ้นเป็นสองเท่าดังนั้นเวลาดำเนินการของสคริปต์ด้านบน แน่นอนคุณยังต้องใช้64 บิต Pythonในระหว่างการดำเนินการโครงสร้างข้อมูลขนาดใหญ่บางอย่างจะถูกจัดการในหน่วยความจำ


ที่จริงแล้วฉันจะถามคำถามอื่นที่จะพูดคุยเกี่ยวกับข้อ จำกัด ของวิธีการที่ฉันใช้เพราะฉันพบปัญหาที่คุณได้กล่าวมาข้างต้นขอบคุณมาก! สิ่งที่ฉันพยายามทำให้สำเร็จ: รวมสี่ rasters แล้วทำตามคำสั่ง if-else ตามคอลัมน์และเขียนผลลัพธ์ลงในคอลัมน์ใหม่และในที่สุดก็ทำการLookupสร้าง raster ตามค่าในคอลัมน์ใหม่ วิธีการของฉันมีขั้นตอนที่ไม่จำเป็นและขั้นตอนการทำงานที่ไม่มีประสิทธิภาพฉันควรพูดถึงเรื่องนี้ในคำถามเดิมของฉัน ใช้ชีวิตและเรียนรู้ ฉันจะลองใช้สคริปต์ของคุณในสัปดาห์นี้
cptpython
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.