Pythonでフィールドの最小数を見つけるより速い方法はありますか?


10

arcgisデスクトップの使用10.3.1私は、検索カーソルを使用してリストに値を追加し、min()を使用して最小の整数を検索するスクリプトがあります。その後、変数はスクリプトで使用されます。Featureクラスには200,000行があり、スクリプトの完了には非常に長い時間がかかります。これをもっと速くする方法はありますか?現時点では、時間がかかるため、スクリプトを書くのではなく、手作業で行うと思います。

import arcpy
fc = arcpy.env.workspace = arcpy.GetParameterAsText(0)
Xfield = "XKoordInt"
cursor = arcpy.SearchCursor(fc)
ListVal = []
for row in cursor:
    ListVal.append(row.getValue(Xfield))
value = min(ListVal)-20
print value
expression = "(!XKoordInt!-{0})/20".format(value)
arcpy.CalculateField_management (fc, "Matrix_Z" ,expression, "PYTHON")

これを行うためのより高速な非Pythonの方法があると思います。これは、gis.stackexchange.com
q / 197873/115

あなたが使用していない理由は何arcpy.Statistics_analysisですか?desktop.arcgis.com/en/arcmap/10.3/tools/analysis-toolbox/...
Berend

はい。私はどこかから始めなければならず、arcpyを使用してプログラミングを行う必要はほとんどありません。多くの人が多くのアプローチを提案できるのは素晴らしいことです。これは新しいことを学ぶための最良の方法です。
Robert Buckley

min_val = min([i[0] for i in arcpy.da.SearchCursor(fc,Xfield)])
BERA 2018年

回答:


15

スクリプトが遅くなっている原因がいくつかあります。非常に遅いと思われるのはarcpy.CalculateField_management()機能です。あなたはカーソルを使うべきです、それは数桁速くなります。また、ArcGIS Desktop 10.3.1を使用しているとのことですが、ArcGIS 10.0スタイルの古いカーソルを使用しているため、速度も非常に遅くなります。

200Kのリストでもmin()操作はかなり高速になります。これを確認するには、この小さなスニペットを実行します。それは瞬く間に起こります:

>>> min(range(200000)) # will return 0, but is still checking a list of 200,000 values very quickly

これがもっと速いかどうか見てください:

import arcpy
fc = arcpy.env.workspace = arcpy.GetParameterAsText(0)
Xfield = "XKoordInt"
with arcpy.da.SearchCursor(fc, [Xfield]) as rows:
    ListVal = [r[0] for r in rows]

value = min(ListVal) - 20
print value

# now update
with arcpy.da.UpdateCursor(fc, [Xfield, 'Matrix_Z']) as rows:
    for r in rows:
        if r[0] is not None:
            r[1] = (r[0] - value) / 20.0
            rows.updateRow(r)

編集:

私はいくつかのタイミングテストを実行しましたが、私が思ったように、フィールド計算機は新しいスタイルのカーソルのほぼ2倍の時間がかかりました。興味深いことに、古いスタイルのカーソルはフィールド計算機より3倍遅くなりました。200,000個のランダムポイントを作成し、同じフィールド名を使用しました。

各関数の時間を計測するためにデコレーター関数が使用されました(関数のセットアップと破棄で若干のオーバーヘッドが発生する可能性があるため、timeitモジュールはスニペットをテストするためにもう少し正確になります)。

結果は次のとおりです。

Getting the values with the old style cursor: 0:00:19.23 
Getting values with the new style cursor: 0:00:02.50 
Getting values with the new style cursor + an order by sql statement: 0:00:00.02

And the calculations: 

field calculator: 0:00:14.21 
old style update cursor: 0:00:42.47 
new style cursor: 0:00:08.71

そしてここに私が使用したコードがあります(timeitデコレータを使用するためにすべてを個々の関数に分解します):

import arcpy
import datetime
import sys
import os

def timeit(function):
    """will time a function's execution time
    Required:
        function -- full namespace for a function
    Optional:
        args -- list of arguments for function
        kwargs -- keyword arguments for function
    """
    def wrapper(*args, **kwargs):
        st = datetime.datetime.now()
        output = function(*args, **kwargs)
        elapsed = str(datetime.datetime.now()-st)[:-4]
        if hasattr(function, 'im_class'):
            fname = '.'.join([function.im_class.__name__, function.__name__])
        else:
            fname = function.__name__
        print'"{0}" from {1} Complete - Elapsed time: {2}'.format(fname, sys.modules[function.__module__], elapsed)
        return output
    return wrapper

@timeit
def get_value_min_old_cur(fc, field):
    rows = arcpy.SearchCursor(fc)
    return min([r.getValue(field) for r in rows])

@timeit
def get_value_min_new_cur(fc, field):
    with arcpy.da.SearchCursor(fc, [field]) as rows:
        return min([r[0] for r in rows])

@timeit
def get_value_sql(fc, field):
    """good suggestion to use sql order by by dslamb :) """
    wc = "%s IS NOT NULL"%field
    sc = (None,'Order By %s'%field)
    with arcpy.da.SearchCursor(fc, [field]) as rows:
        for r in rows:
            # should give us the min on the first record
            return r[0]

@timeit
def test_field_calc(fc, field, expression):
    arcpy.management.CalculateField(fc, field, expression, 'PYTHON')

@timeit
def old_cursor_calc(fc, xfield, matrix_field, value):
    wc = "%s IS NOT NULL"%xfield
    rows = arcpy.UpdateCursor(fc, where_clause=wc)
    for row in rows:
        if row.getValue(xfield) is not None:

            row.setValue(matrix_field, (row.getValue(xfield) - value) / 20)
            rows.updateRow(row)

@timeit
def new_cursor_calc(fc, xfield, matrix_field, value):
    wc = "%s IS NOT NULL"%xfield
    with arcpy.da.UpdateCursor(fc, [xfield, matrix_field], where_clause=wc) as rows:
        for r in rows:
            r[1] = (r[0] - value) / 20
            rows.updateRow(r)


if __name__ == '__main__':
    Xfield = "XKoordInt"
    Mfield = 'Matrix_Z'
    fc = r'C:\Users\calebma\Documents\ArcGIS\Default.gdb\Random_Points'

    # first test the speed of getting the value
    print 'getting value tests...'
    value = get_value_min_old_cur(fc, Xfield)
    value = get_value_min_new_cur(fc, Xfield)
    value = get_value_sql(fc, Xfield)

    print '\n\nmin value is {}\n\n'.format(value)

    # now test field calculations
    expression = "(!XKoordInt!-{0})/20".format(value)
    test_field_calc(fc, Xfield, expression)
    old_cursor_calc(fc, Xfield, Mfield, value)
    new_cursor_calc(fc, Xfield, Mfield, value)

そして最後に、これが私のコンソールから実際に出力されたものです。

>>> 
getting value tests...
"get_value_min_old_cur" from <module '__main__' from 'C:/Users/calebma/Desktop/speed_test2.py'> Complete - Elapsed time: 0:00:19.23
"get_value_min_new_cur" from <module '__main__' from 'C:/Users/calebma/Desktop/speed_test2.py'> Complete - Elapsed time: 0:00:02.50
"get_value_sql" from <module '__main__' from 'C:/Users/calebma/Desktop/speed_test2.py'> Complete - Elapsed time: 0:00:00.02


min value is 5393879


"test_field_calc" from <module '__main__' from 'C:/Users/calebma/Desktop/speed_test2.py'> Complete - Elapsed time: 0:00:14.21
"old_cursor_calc" from <module '__main__' from 'C:/Users/calebma/Desktop/speed_test2.py'> Complete - Elapsed time: 0:00:42.47
"new_cursor_calc" from <module '__main__' from 'C:/Users/calebma/Desktop/speed_test2.py'> Complete - Elapsed time: 0:00:08.71
>>> 

編集2:更新されたテストをいくつか投稿したところ、timeit機能にわずかな欠陥が見つかりました。


r [0] =(r [0]-値)/ 20.0 TypeError:-: 'NoneType'および 'int'でサポートされていないオペランドタイプ
Robert Buckley

これは、にnull値があることを意味します"XKoordInt"。編集を参照してください。ヌルをスキップするだけです。
crmackey 2016年

2
に注意してくださいrange。ArcGISは引き続きPython 2.7を使用するため、を返しますlist。しかし、3.xでは、range最適化が可能な独自の特殊なオブジェクトです。より信頼性の高いテストはmin(list(range(200000)))です。これは、プレーンなリストで作業していることを確認します。また、timeitモジュールをパフォーマンステストに使用することも検討してください。
jpmc26 2016年

リストではなくセットを使用することで、さらに時間を稼ぐことができるでしょう。これにより、重複する値を保存せず、一意の値のみを検索します。
Fezter

@Fezterディストリビューションによって異なります。すべての値をハッシュし、それぞれの値が構築中にセットに含まれているかどうかをチェックするコストを上回るには、十分に正確な複製が必要です。たとえば、1%しか複製されない場合、それはおそらくコストに見合わないでしょう。また、値が浮動小数点の場合、正確な重複が多くなることはほとんどありません。
jpmc26 2016年

1

@crmackeyが指摘しているように、遅い部分はおそらくフィールド計算メソッドによるものです。他の適切なソリューションの代わりとして、ジオデータベースを使用してデータを格納している場合、SQLによるコマンドを使用して、更新カーソルを実行する前に昇順で並べ替えることができます。

start = 0
Xfield = "XKoordInt"
minValue = None
wc = "%s IS NOT NULL"%Xfield
sc = (None,'Order By %s'%Xfield)
with arcpy.da.SearchCursor(fc, [Xfield],where_clause=wc,sql_clause=sc) as uc:
    for row in uc:
        if start == 0:
            minValue = row[0]
            start +=1
        row[0] = (row[0] - value) / 20.0
        uc.updateRow(row)

この場合、where句はクエリを実行する前にnullを削除します。または、更新前にNoneをチェックする他の例を使用できます。


いいね!最初のレコードを昇順で取得して順序を使用すると、すべての値を取得してからを見つけるよりも確実に高速になりますmin()。これを速度テストにも含めて、パフォーマンスの向上を示します。
crmackey 2016年

それがどこにランクされるかを知りたいと思います。追加のSQL操作によって速度が遅くなっても、驚くことはありません。
dslamb

2
タイミングベンチマークが追加されました。私の編集を参照してください。そして、私はあなたが正しかったと思います、sqlはいくつかの余分なオーバーヘッドを追加するように見えましたが、リスト全体を0.56秒単位で移動するカーソルを実行しませんでした。
crmackey

1

このような場合はnumpyを使用することもできますが、メモリを集中的に使用します。

numpy配列にデータを読み込んでから再びデータソースに戻すと、ボトルネックが発生しますが、特に複数のデータソースが必要な場合は、データソースが大きいほどパフォーマンスの違いが(numpyの方が)優れていることがわかりました。統計/計算:

import arcpy
import numpy as np
fc = arcpy.env.workspace = arcpy.GetParameterAsText(0)
Xfield = "XKoordInt"

allvals = arcpy.da.TableToNumPyArray(fc,['OID@',Xfield])
value = allvals[Xfield].min() - 20

print value

newval = np.zeros(allvals.shape,dtype=[('id',int),('Matrix_Z',int)])
newval['id'] = allvals['OID@']
newval['Matrix_Z'] = (allvals[Xfield] - value) / 20

arcpy.da.ExtendTable(fc,'OBJECTID',newval,'id',False)

1

テーブルを昇順に並べ替えてから、検索カーソルを使用して最初の行の値を取得しますか?http://pro.arcgis.com/en/pro-app/tool-reference/data-management/sort.htm

import arcpy
workspace = r'workspace\file\path'
arcpy.env.workspace = workspace

input = "input_data"
sort_table = "sort_table"
sort_field = "your field"

arcpy.Sort_management (input, sort_table, sort_field)

min_value = 0

count= 0
witha arcpy.da.SearchCursor(input, [sort_field]) as cursor:
    for row in cursor:
        count +=1
        if count == 1: min_value +=row[0]
        else: break
del cursor

1

私はラップでしょうSearchCursorジェネレータ式(すなわちmin()、速度と簡潔両方のために)。次に、ジェネレータ式の最小値をda型に組み込みUpdateCursorます。次のようなもの:

import arcpy

fc = r'C:\path\to\your\geodatabase.gdb\feature_class'

minimum_value = min(row[0] for row in arcpy.da.SearchCursor(fc, 'some_field')) # Generator expression

with arcpy.da.UpdateCursor(fc, ['some_field2', 'some_field3']) as cursor:
    for row in cursor:
        row[1] = (row[0] - (minimum_value - 20)) / 20 # Perform the calculation
        cursor.updateRow(row)

SearchCursor使い終わったら閉じないでください。
jpmc26 2016年

1
@ jpmc26カーソルの完了により、カーソルを解放できます。ソース(カーソルとロック):pro.arcgis.com/en/pro-app/arcpy/get-started/…。Esriからの別の実施例(実施例2参照):pro.arcgis.com/en/pro-app/arcpy/data-access/...
アーロン

0

ループには、反復ごとに再評価される2つの関数参照があります。

for row in cursor: ListVal.append(row.getValue(Xfield))

ループの外に参照がある方が高速ですが(少し複雑です):

getvalue = row.getValue
append = ListVal.append

for row in cursor:
    append(getvalue(Xfield))

これは実際にそれを遅くしませんか?実際にappend()は、listデータ型の組み込みメソッドの新しい個別の参照を作成しています。これが彼のボトルネックが発生している場所だとは思わない。お金を計算するフィールド関数が原因だと思う。これは、フィールド計算機と新しいスタイルのカーソルのタイミングを計ることによって確認できます。
crmackey 2016年

1
実際には私もタイミングに興味があります:)しかし、それは元のコードで簡単に置き換えることができるため、すばやくチェックされます。
マット

しばらくの間、カーソル対フィールド計算機でいくつかのベンチマークテストを行ったことを知っています。私は別のテストを行い、私の回答で私の発見を報告します。古いカーソル速度と新しいカーソル速度を表示するのも良いと思います。
crmackey 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.