ArcPyを使用して1600万件のレコードをループしますか?


13

8つの列と1670万件のレコードを持つテーブルがあります。列に対してif-else方程式のセットを実行する必要があります。UpdateCursorモジュールを使用してスクリプトを記述しましたが、数百万件のレコードを記録した後、メモリが不足します。これらの1670万レコードを処理するより良い方法があるかどうか疑問に思っていました。

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 GBのRAMを搭載したコンピューターで同じスクリプトを実行しました(元のコンピューターには12 GBのRAMしかありませんでした)。〜16時間後に正常に完了しました。16時間は長すぎると感じていますが、このような大規模なデータセットを扱ったことがないので、何を期待すべきかわかりません。このスクリプトへの唯一の新しい追加はarcpy.env.parallelProcessingFactor = "100%"です。私は2つの提案された方法を試しています(1)バッチで100万件のレコードを実行し、(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ビット(バックグラウンドまたはサーバーまたはプロ)を使用していますか?
KHibma 16

言及するのを忘れました。バックグラウンドで10.4 x64を実行しています。
cptpython 16

Devils advocate- ArcMapを開く必要がないスクリプトを見て、フォアグラウンドまたはIDLEから実行しようとしましたか?
ホーンビッド16

スタンドアロンスクリプトとしてか、SQLを知っていれば、それを実行するのPostgreSQLへのシェープファイルをアップロードし、そこにそれを行う
ジギー

1
私はそれがオープンソースであることを理解していますが、承認プロセスには1〜2週間かかり、これは時間に敏感であるため、この例では実現可能ではないと思います。
cptpython 16

回答:


4

Objectidと計算結果(cate_2)をcsvファイルに書き込むことができます。次に、csvを元のファイルに結合し、フィールドに入力して、結果を保存します。この方法では、DAカーソルを使用してテーブルを更新しません。検索カーソルを使用できます。


ここで議論があるのと同じことを考えていて、彼らはさらに大きなデータセットについて話している。
ホーンビッド16

ありがとう、klewis。これは有望に思えます。FelixIPの提案と興味深い議論とともに試してみますが、これを数十回実行する必要があります。
cptpython 16

見事に働いた!質問を最新のスクリプトで更新しました。ありがとう!
cptpython 16

2

申し訳ありませんが、この古いスレッドを復活させ続けます。アイデアは、結合ラスターでif-elseステートメントを実行し、ルックアップで新しいフィールドを使用して新しいラスターを作成することでした。データを表としてエクスポートすることで問題を複雑にし、@ Alex Tereshenkovによって対処された非効率的なワークフローを導入しました。明らかなことに気づいた後、@ FelixIPによって提案されたとおり、データを17個のクエリ(それぞれ100万個)にバッチ処理しました。各バッチの完了には平均で約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 RAMを搭載したコンピューターでこれを実行したとき、合計で約16時間かかったと言っていました。しかし、今では17個のバッチに分割し、合計で約23分かかりました。あれは正しいですか?
ianbroad 16

正しい。最初の実行は40 GBのRAMで約16時間かかり、2回目の実行では約23分+ Lookup新たに定義されたカテゴリのラスタを実行およびエクスポートするのにさらに約15分かかりました。
cptpython 16

arcpy.env.parallelProcessingFactor = "100%"スクリプトに影響を与えないメモです。その環境を活用するツールはそこにありません。
KHibma 16

正解です。コードを編集します。
cptpython 16

1

CalculateField_managementを使用するように変更してみてください。これにより、カーソルを使用したループが回避され、カテゴリ値のオプションの外観により、これを4つのサブプロセスが順番に生成されるように設定できます。各サブプロセスが終了すると、次のサブプロセスを開始する前にメモリが解放されます。各サブプロセスを生成する小さな(ミリ秒)ヒットを取得します。

または、現在のアプローチを維持する場合は、一度にx行を使用するサブプロセスを用意します。それを駆動するためのメインプロセスがあり、以前と同様に、終了するたびに記憶をスカベンジングし続けます。この方法で(特にスタンドアロンのpythonプロセスを介して)それを行うことのボーナスは、GILを回避するpythonのマルチスレッド処理で、すべてのコアをサブプロセスの生成としてより活用できることです。これは、ArcPyと、過去に大規模なデータチャーンを行うために使用したアプローチで可能です。明らかにデータのチャンクを抑えておくと、メモリ不足になってしまいます。


私の経験では、arcpy.da.UpdateCursorの使用はarcpy.CalculateField_managementよりもはるかに高速です。55.000.000のビルド機能で実行するスクリプトを作成しましたが、CalculateFieldツールでは約5倍遅くなりました。
オファーマン16

ポイントは、4つのサブプロセスをセットアップし、ここで実際のピンチポイントであるメモリを清掃することです。2番目の段落で説明したように、サブプロセスを行で分割できますが、単一の選択よりも少し管理がかかります。
MappaGnosis 16

1

データ操作ロジックは、CASE式を使用してUPDATE SQLステートメントとして記述できますgdal-filegdb。これは、インストール済みのOSGeo4Wなど、GDAL / OGRを使用して実行できます。

ここで使用するワークフロー、ある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)

100万件をわずかに超えるレコードを持つ同様のテーブルでは、このクエリには18分かかりました。そのため、1600万件のレコードを処理するには、まだ4〜5時間かかります。


残念ながら、このスクリプトは、使用して作成されたより大きなスクリプトの一部ですが、arcpyその答えに感謝しています。私はゆっくりとGDALを使用しようとしています。
cptpython 16

1

質問のセクション2のコードの更新では.csv、ファイルジオデータベース内の元のテーブルにファイルを戻す方法を示していません。スクリプトの実行に約5分かかったと言います。.csv結合せずにファイルをエクスポートしただけの場合、これは公平に聞こえます。.csvファイルをArcGIS に戻そうとすると、パフォーマンスの問題が発生します。

1).csvジオデータベーステーブルから直接結合することは.csvできません。ファイルにはOIDがないためです(一意の値で計算されたフィールドを持つことは、.csvファイルをジオデータベーステーブルに変換する必要があるため、役に立ちません)。そのため、Table To TableGPツールの場合は数分かかります(in_memoryワークスペースを使用して一時テーブルを作成すると、少し速くなります)。

2)を.csvジオデータベーステーブルにロードした後、結合を行うフィールド(この場合、ファイルのソースobjectid値)にインデックスを作成し.csvます。これには、16mln行のテーブルで数分かかります。

3)次に、Add JoinまたはJoin FieldGPツールを使用する必要があります。どちらも、大きなテーブルではうまく機能しません。

4)その後、Calculate FieldGPツールを実行して、新しく結合されたフィールドを計算する必要があります。ここに何分も行きます。さらに、計算に参加するフィールドが結合テーブルからのものである場合、フィールドの計算に時間がかかります。

一言で言えば、あなたが言及した5分に近いものは何も得られません。あなたが1時間でそれを作るならば、私は感銘を受けるでしょう。

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

以下は、個々の関数の実行時間に関する情報を含む、デバッグ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%以上がジオデータベーステーブルの読み取りに費やされ、その後、ジオデータベースに行が挿入されます。

同じことがpandasda.SearchCursorジェネレーターからのデータフレームの作成とフィールドの計算にも適用されます。ソースジオデータベーステーブルの行数が2倍になると、上記のスクリプトの実行時間も2倍になります。もちろん、実行中は64ビットPythonを使用する必要があります。いくつかの大きなデータ構造はメモリで処理されます。


実際、私が使用した方法の制限について話す別の質問をするつもりでした。なぜなら、あなたが上記で対処した問題に遭遇したので、ありがとう!私が達成しようとしていること:4つのラスタを結合し、列に基づいてif-elseステートメントを実行し、出力を新しい列に書き出し、最後に新しい列Lookupの値に基づいてラスタを作成します。私の方法には多くの不必要なステップと非効率的なワークフローがありましたが、元の質問でこれを言及すべきでした。生活し、学びます。ただし、今週後半にスクリプトを試します。
cptpython 16
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.