ArcPy का उपयोग करके 16 मिलियन रिकॉर्ड के माध्यम से लूपिंग?


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

मैंने 40 जीबी रैम वाले कंप्यूटर पर एक ही स्क्रिप्ट चलाई (मूल कंप्यूटर में केवल 12 जीबी रैम थी)। यह ~ 16 घंटे के बाद सफलतापूर्वक पूरा हुआ। मुझे लगता है कि 16 घंटे बहुत लंबा है, लेकिन मैंने इतने बड़े डेटासेट के साथ कभी काम नहीं किया है, इसलिए मुझे नहीं पता कि क्या करना है। इस स्क्रिप्ट का एकमात्र नया जोड़ है arcpy.env.parallelProcessingFactor = "100%"। मैं दो सुझाए गए तरीकों की कोशिश कर रहा हूं (1) बैचों में 1 मिलियन रिकॉर्ड कर रहा हूं और (2) सीएससी के लिए खोजकर्ता और लेखन सामग्री का उपयोग कर रहा हूं। जल्द ही प्रगति पर रिपोर्ट दूंगा।

अद्यतन # 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 बिट (या तो बैकग्राउंड या सर्वर या प्रो) का उपयोग कर रहे हैं?
कहिम्मा

ज़िक्र करना भूल गया। मैं बैकग्राउंड में 10.4 x64 चला रहा हूं।
cptpython

डेविल्स एडवोकेट - क्या आपने इसे अग्रभूमि में या IDLE से चलाने की कोशिश की है क्योंकि आपकी स्क्रिप्ट को देखकर आपको ArcMap खुला होने की आवश्यकता नहीं है?
१६:०६ में हॉर्नबीड

इसे स्टैंडअलोन स्क्रिप्ट के रूप में चलाएं या यदि आप एसक्यूएल को जानते हैं, तो शेपफाइल को पोस्टग्रैसक्यूएल पर अपलोड करें और इसे वहां करें
ज़िगी

1
मैं समझता हूं कि यह खुला स्रोत है, लेकिन यह अनुमोदन प्रक्रिया ~ 1-2 सप्ताह लगती है, और यह समय संवेदनशील है इसलिए मुझे नहीं लगता कि यह इस उदाहरण में संभव है।
18 सितंबर को cptpython

जवाबों:


4

आप एक सीएसवी फ़ाइल के लिए ऑब्जेक्ट की गणना और परिणाम (cate_2) लिख सकते हैं। फिर अपनी मूल फ़ाइल में सीएसवी से जुड़ें, परिणाम को संरक्षित करने के लिए एक फ़ील्ड को पॉप्युलेट करें। इस तरह आप DA कर्सर का उपयोग करके तालिका को अपडेट नहीं कर रहे हैं। आप एक खोज कर्सर का उपयोग कर सकते हैं।


मैं एक ही बात सोच रहा था क्योंकि यहाँ एक चर्चा है और वे और भी बड़े डेटासेट के बारे में बात कर रहे हैं।
हॉर्नबीड

धन्यवाद, क्लेविस यह आशाजनक लगता है। मैं इसे फेलिक्सिप के सुझाव और दिलचस्प चर्चा के साथ कोशिश करूंगा, हालांकि मुझे इसे कुछ दर्जन बार चलाना होगा।
cptpython

शानदार ढंग से काम किया! मैंने नवीनतम स्क्रिप्ट के साथ प्रश्न को अपडेट किया है। धन्यवाद!
cptpython

2

माफी, अगर मैं इस पुराने धागे को पुनर्जीवित करता रहूं। विचार यह था कि संयोजन रेखापुंज पर if-else स्टेटमेंट निष्पादित करें और फिर नया रेखापुंज बनाने के लिए लुकअप में नए फ़ील्ड का उपयोग करें। मैंने डेटा को तालिका के रूप में निर्यात करके समस्या को जटिल कर दिया और अयोग्य वर्कफ़्लो पेश किया, जिसे @ एलेक्स टेरेशेनकोव द्वारा संबोधित किया गया था। स्पष्ट महसूस करने के बाद, मैंने डेटा को 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()

तो, बस यह सुनिश्चित करने के लिए कि मैं इसे सही ढंग से समझ रहा हूं। अपने मूल पोस्ट में आपने कहा था कि जब आप इसे 40GB रैम वाले कंप्यूटर पर चलाते हैं, तो इसमें कुल ~ 16 घंटे लगते थे। लेकिन अब जब आप इसे 17 बैचों में विभाजित करते हैं, और इसमें कुल 23 मिनट लगते हैं। क्या वो सही है?
.नबेरोड

सही बात। पहला रन 40 जीबी रैम के साथ ~ 16 घंटे का लिया गया और दूसरे रन Lookupको नए परिभाषित श्रेणियों के साथ रेखापुंज करने और निर्यात करने के लिए ~ 23 मिनट + एक और ~ 15 मिनट लगे ।
cptpython

बस एक नोट जिसका arcpy.env.parallelProcessingFactor = "100%"आपकी स्क्रिप्ट पर कोई प्रभाव नहीं है। मुझे वहां कोई भी उपकरण दिखाई नहीं देता जो उस वातावरण का लाभ उठाता हो।
काहेमा

तुम सही हो। मैं कोड संपादित करूंगा।
15 सितंबर को

1

आप CalculateField_management का उपयोग करके बदलने की कोशिश कर सकते हैं । यह कर्सर के उपयोग के माध्यम से लूपिंग से बचा जाता है और, श्रेणी के मूल्य के लिए आपके विकल्पों के अनुसार, आप इसे चार उपप्रकारों के रूप में क्रमिक रूप से स्थापित कर सकते हैं। जैसा कि प्रत्येक उपप्रकार खत्म हो जाता है इसकी स्मृति अगले एक को शुरू करने से पहले जारी की जाती है। आप एक छोटे (मिलीसेकंड) हिट प्रत्येक सबप्रोसेस लेते हैं।

या, यदि आप अपना वर्तमान दृष्टिकोण रखना चाहते हैं, तो एक उपप्रकार रखें जो एक बार में एक्स-पंक्तियाँ लेता है। इसे चलाने के लिए एक मुख्य प्रक्रिया है और, पहले की तरह, आप अपनी मेमोरी को हर बार खत्म करने के बाद मैला करते रहते हैं। इसे इस तरह से करने का बोनस (विशेष रूप से एक स्टैंड-अलोन पायथन प्रक्रिया के माध्यम से) यह है कि आप अपने सभी कोर का अधिक उपयोग कर सकते हैं जैसे कि अजगर के मल्टीथ्रेडिंग में सबप्रोसेसिंग के रूप में आप जीआईएल के आसपास मिलते हैं। यह आर्कपी के साथ संभव है और एक दृष्टिकोण जो मैंने अतीत में बड़े पैमाने पर डेटा मंथन करने के लिए उपयोग किया है। जाहिर है अपने डेटा का हिस्सा नीचे रखो अन्यथा आप तेजी से स्मृति से बाहर चला जाएगा!


मेरे अनुभव में, arcpy.da.UpdateCursor का उपयोग करना आर्कपी से बेहतर है। मैंने एक स्क्रिप्ट लिखी थी जो 55.000.000 निर्माण सुविधाओं पर चलती है, यह कैलकुलेटफिल्ड टूल के साथ लगभग 5 गुना धीमी थी।
ऑफ़रमैन

बिंदु चार उपप्रकारों को स्थापित करने और स्मृति को परिमार्जन करने के लिए है क्योंकि यहां वास्तविक चुटकी बिंदु है। जैसा कि मैं दूसरे पैराग्राफ में रेखांकित करता हूं कि आप उपप्रकारों को पंक्तियों द्वारा विभाजित कर सकते हैं, लेकिन यह एक एकल चयन की तुलना में थोड़ा अधिक प्रबंधन लेता है।
मप्पाग्नोसिस

1

डेटा हेरफेर लॉजिक को एक CASE एक्सप्रेशन का उपयोग करते हुए UPDATE SQL स्टेटमेंट के रूप में लिखा जा सकता है, जिसे आप 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 मिनट लगे। इसलिए 16 मिलियन रिकॉर्ड को संसाधित करने में अभी भी ~ 4 से 5 घंटे लग सकते हैं।


दुर्भाग्य से स्क्रिप्ट का उपयोग करते हुए लिखी गई एक बड़ी स्क्रिप्ट का हिस्सा है arcpyलेकिन मैं उत्तर की सराहना करता हूं। मैं धीरे-धीरे GDAL का उपयोग करने की कोशिश कर रहा हूं।
cptpython

1

आपके प्रश्न में अनुभाग # 2 में कोड का अद्यतन यह नहीं दर्शाता है कि आप .csvफ़ाइल को अपनी फ़ाइल में मूल तालिका में वापस कैसे शामिल कर रहे हैं । आप कहते हैं कि आपकी स्क्रिप्ट को चलाने के लिए ~ 5 मिनट लगते हैं। यह उचित लगता है यदि आपने केवल .csvबिना किसी जोड़ के फ़ाइल निर्यात की है । जब आप .csvफ़ाइल को वापस आर्कगिस में लाने की कोशिश करेंगे , तो आप प्रदर्शन के मुद्दों से टकराएंगे।

1) आप सीधे .csvजियोडेटाबेस टेबल से नहीं जुड़ सकते , क्योंकि .csvफ़ाइल में OID नहीं है (अद्वितीय मानों के साथ गणना की गई फ़ील्ड में आपकी मदद नहीं की जाएगी क्योंकि आपको अभी भी अपनी .csvफ़ाइल को जियोडैटेबेस तालिका में बदलने की आवश्यकता होगी )। तो, Table To Tableजीपी टूल के लिए कई मिनट (आप in_memoryवहां एक अस्थायी तालिका बनाने के लिए कार्यक्षेत्र का उपयोग कर सकते हैं , थोड़ा तेज होगा)।

2) आपके द्वारा .csvजियोडेटाबेस टेबल में लोड करने के बाद , आप उस फील्ड पर एक इंडेक्स बनाना चाहेंगे, जिसमें आप जॉइन करेंगे (आपके मामले में, फाइल objectidसे सोर्स .csvवाऊ। यह 16mln रो टेबल पर कुछ मिनट लगेगा।

3) तो फिर आप या तो उपयोग करने की आवश्यकता होगी Add Joinया Join Fieldजीपी उपकरण। न ही आपकी बड़ी तालिकाओं पर अच्छा प्रदर्शन करेगा।

4) बाद में, आपको Calculate Fieldनए सम्मिलित क्षेत्र (एस) की गणना करने के लिए जीपी उपकरण करने की आवश्यकता है । कई मिनट यहाँ जाते हैं; इससे भी अधिक, क्षेत्र गणना में अधिक समय लगता है जब गणना में भाग लेने वाले क्षेत्र एक सम्मिलित तालिका से आ रहे हैं।

एक शब्द में, आपको 5min के करीब कुछ भी नहीं मिलेगा जिसका आप उल्लेख करते हैं। अगर आप इसे एक घंटे में बना लेंगे, तो मैं प्रभावित होऊंगा।

आर्कजीआईएस के भीतर बड़े डेटासेट के प्रसंस्करण से निपटने के लिए, मैं सुझाव देता हूं कि आप अपने डेटा को आर्कजीआईएस के बाहर एक 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

नीचे डिबग आईओ से आउटपुट है (रिपोर्ट की गई तालिका में प्रयुक्त पंक्तियों की संख्या है) किसी व्यक्ति के लिए निष्पादन समय की जानकारी के साथ:

---------------------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 बिट पायथन का उपयोग करने की आवश्यकता है क्योंकि निष्पादन के दौरान, कुछ बड़े डेटा संरचनाओं को स्मृति में संभाला जाएगा।


वास्तव में, मैं एक और सवाल पूछने जा रहा था जो मेरे द्वारा उपयोग की जाने वाली विधि की सीमाओं के बारे में बात करेगा, क्योंकि मैं उन समस्याओं में भाग गया था जिन्हें आपने ऊपर धन्यवाद दिया है! मैं जो पूरा करने की कोशिश कर रहा हूं: चार रिस्तेदारों को मिलाएं और फिर कॉलम के आधार पर if-else स्टेटमेंट करें और आउटपुट को एक नए कॉलम में लिखें और अंत Lookupमें नए कॉलम में मानों के आधार पर रैस्टर बनाने के लिए प्रदर्शन करें। मेरी विधि में कई आवश्यक कदम और अक्षम कार्य थे, मुझे अपने मूल प्रश्न में इसका उल्लेख करना चाहिए था। जियो और सीखो। मैं इस सप्ताह के अंत में आपकी स्क्रिप्ट की कोशिश करूंगा।
cptpython
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.