파이썬에서 필드에서 가장 작은 숫자를 찾는 더 빠른 방법이 있습니까?


10

arcgis desktop 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")

나는 당신이에서 작업 할 듯이 할 수있는 빠른 노 파이썬 방법이 생각 gis.stackexchange.com/q/197873/115이
PolyGeo

왜 당신이 사용하지 않는 이유는 arcpy.Statistics_analysis무엇입니까? desktop.arcgis.com/ko/arcmap/10.3/tools/analysis-toolbox/…
Berend

예. 어딘가에서 시작해야하며 arcpy로 프로그래밍을 거의하지 않아도됩니다. 많은 사람들이 많은 접근법을 제안 할 수 있다는 것은 환상적입니다. 이것이 새로운 것을 배우는 가장 좋은 방법입니다.
Robert Buckley

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

답변:


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)

편집하다:

나는 몇 가지 타이밍 테스트를 실행했으며, 의심 할 때 필드 계산기는 새로운 스타일 커서보다 거의 두 배나 걸렸습니다. 흥미롭게도 이전 스타일 커서는 필드 계산기보다 ~ 3 배 느 렸습니다. 200,000 개의 임의의 점을 만들고 동일한 필드 이름을 사용했습니다.

데코레이터 기능을 사용하여 각 기능의 시간을 정했습니다 (기능 설정 및 해제시 약간의 오버 헤드가 발생할 수 있으므로 시간 모듈이 스 니펫을 테스트하는 데 약간 더 정확할 수 있음).

결과는 다음과 같습니다.

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 오류 : 피연산자되지 않는 유형 (들) - 'NoneType'과 '내부'
로버트 버클리

이는에 null 값이 있음을 의미합니다 "XKoordInt". 내 편집을 참조하십시오. 널을 건너 뛰기 만하면됩니다.
crmackey

2
주의하십시오 range. ArcGIS는 여전히 Python 2.7을 사용하므로을 반환합니다 list. 그러나 3.x에서는 range최적화가 가능한 고유 한 객체입니다. 보다 안정적인 테스트는 min(list(range(200000)))일반 목록으로 작업하고 있는지 확인하는 것입니다. 또한 timeit성능 테스트 를 위해 모듈 사용을 고려하십시오 .
jpmc26

목록이 아닌 세트를 사용하여 시간을 더 확보 할 수 있습니다. 이렇게하면 중복 값을 저장하지 않고 고유 한 값만 검색합니다.
Fezter

@Fezter 배포판에 따라 다릅니다. 모든 값을 해시하고 각 값이 구성 중에 설정되어 있는지 확인하는 데 필요한 정확한 중복 값이 ​​충분해야합니다. 예를 들어, 1 % 만 복제하면 비용이 들지 않습니다. 또한 값이 부동 소수점 인 경우 정확한 복제본이 많지 않을 수 있습니다.
jpmc26 2016 년

1

@crmackey가 지적했듯이 느린 부분은 아마도 계산 필드 방법 때문일 것입니다. 다른 적합한 솔루션의 대안으로, 지오 데이터베이스를 사용하여 데이터를 저장한다고 가정하면 업데이트 커서를 수행하기 전에 Order By 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

나는 그것이 어디에 있는지 궁금합니다. 여분의 SQL 작업으로 인해 속도가 느려지더라도 놀라지 않을 것입니다.
dslamb 2016 년

2
타이밍 벤치 마크가 추가되었습니다. 내 편집을 참조하십시오. 그리고 당신이 맞다고 생각합니다 .SQL은 약간의 오버 헤드를 추가하는 것처럼 보였지만 전체 목록을 0.56몇 초씩 단계별로 이동시키는 커서를 수행 했으므로 예상했던 것보다 성능이 향상되지 않았습니다.
crmackey

1

메모리 사용량이 많지만 이와 같은 경우에는 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

나는 랩 것이다 SearchCursorA의 발전기 표현 (즉 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/…
Aaron

0

루프에는 각 반복에 대해 재평가되는 두 개의 함수 참조가 있습니다.

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

1
실제로 나는 타이밍에 관심이있을 것이다 :) 그러나 그것은 원래 코드에서 쉽게 교체 할 수 있으므로 빨리 확인됩니다.
Matt

커서 대 필드 계산기에서 잠시 벤치 마크 테스트를 수행 한 것을 알고 있습니다. 나는 또 다른 시험을하고 나의 답을 찾은 결과를보고 할 것이다. 오래된 커서 속도와 새로운 커서 속도도 표시하는 것이 좋습니다.
crmackey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.