Có cách nào nhanh hơn trong python trong việc tìm số nhỏ nhất trong một trường không?


10

Sử dụng arcgis desktop 10.3.1 Tôi có một tập lệnh sử dụng con trỏ tìm kiếm để nối các giá trị vào danh sách và sau đó sử dụng min () để tìm số nguyên nhỏ nhất. Biến sau đó được sử dụng trong một tập lệnh. Lớp Feature có 200.000 hàng và tập lệnh mất rất nhiều thời gian để hoàn thành. Có cách nào để làm điều này nhanh hơn? Hiện tại tôi nghĩ rằng tôi sẽ chỉ làm nó bằng tay chứ không phải viết một kịch bản do thời gian cần thiết.

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

Tôi nghĩ rằng có một cách không có Python nhanh hơn để làm điều này mà bạn dường như đang làm việc tại gis.stackexchange.com/q/197873/115
PolyGeo

Bất kỳ lý do tại sao bạn không sử dụng arcpy.Statistics_analysis? desktop.arcgis.com/en/arcmap/10.3/tools/analysis-toolbox/iêu
Berend

Đúng. Tôi phải bắt đầu ở đâu đó và chỉ hiếm khi thực hiện bất kỳ chương trình nào với arcpy. Thật tuyệt vời khi rất nhiều người có thể đề xuất rất nhiều cách tiếp cận. Đây là cách tốt nhất để học những điều mới.
Robert Buckley

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

Câu trả lời:


15

Tôi có thể thấy một số điều có thể khiến kịch bản của bạn bị chậm. Điều có khả năng rất chậm là arcpy.CalculateField_management()chức năng. Bạn nên sử dụng một con trỏ, nó sẽ nhanh hơn vài độ. Ngoài ra, bạn cho biết bạn đang sử dụng ArcGIS Desktop 10.3.1, nhưng bạn đang sử dụng các con trỏ kiểu ArcGIS 10.0 cũ, tốc độ cũng chậm hơn nhiều.

Thao tác min () ngay cả trong danh sách aK 200K sẽ khá nhanh. Bạn có thể xác minh điều này bằng cách chạy đoạn mã nhỏ này; nó xảy ra trong chớp mắt:

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

Xem nếu điều này là nhanh hơn:

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)

BIÊN TẬP:

Tôi đã chạy một số bài kiểm tra thời gian và như tôi nghi ngờ, máy tính trường mất gần gấp đôi con trỏ kiểu mới. Thật thú vị, con trỏ kiểu cũ chậm hơn ~ 3 lần so với máy tính trường. Tôi đã tạo ra 200.000 điểm ngẫu nhiên và sử dụng cùng tên trường.

Một chức năng trang trí được sử dụng để thời gian mỗi chức năng (có thể có một số chi phí nhỏ trong việc thiết lập và phá đổ các chức năng, như vậy có lẽ các timeit mô-đun sẽ là một chút chính xác hơn để đoạn thử nghiệm).

Đây là kết quả:

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

Và đây là đoạn mã tôi đã sử dụng (chia mọi thứ thành các hàm riêng lẻ để sử dụng trình timeittrang trí):

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)

Và cuối cùng, đây là những gì bản in thực tế là từ bảng điều khiển của tôi.

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

Chỉnh sửa 2: Chỉ cần đăng một số bài kiểm tra cập nhật, tôi thấy một lỗ hổng nhỏ với timeitchức năng của mình .


r [0] = (r [0] - value) / 20.0 TypeError: loại toán hạng không được hỗ trợ cho -: 'noneType' và 'int'
Robert Buckley

Điều đó chỉ có nghĩa là bạn có một số giá trị null trong "XKoordInt". Xem chỉnh sửa của tôi, tất cả những gì bạn phải làm là bỏ qua null.
crmackey

2
Hãy cẩn thận với range. ArcGIS vẫn sử dụng Python 2.7, vì vậy nó trả về a list. Nhưng trong 3.x, rangelà loại đối tượng đặc biệt của riêng nó có thể có tối ưu hóa. Một bài kiểm tra đáng tin cậy hơn min(list(range(200000)))sẽ đảm bảo bạn sẽ làm việc với một danh sách đơn giản. Cũng xem xét sử dụng các timeitmô-đun để thử nghiệm hiệu suất.
jpmc26

Bạn có thể có thể có thêm thời gian bằng cách sử dụng các bộ thay vì danh sách. Bằng cách đó, bạn không lưu trữ các giá trị trùng lặp và bạn chỉ tìm kiếm trên các giá trị duy nhất.
Fezter

@Fezter Nó phụ thuộc vào việc phân phối. Sẽ phải có đủ các bản sao chính xác để vượt quá chi phí băm tất cả các giá trị và kiểm tra xem mỗi cái có trong tập hợp trong quá trình xây dựng hay không. Ví dụ, nếu chỉ có 1% được nhân đôi, có lẽ nó không đáng giá. Cũng lưu ý rằng nếu giá trị là dấu phẩy động, không có nhiều khả năng trùng lặp chính xác.
jpmc26

1

Như @crmackey chỉ ra, phần chậm có lẽ là do phương pháp tính toán trường. Thay thế cho các giải pháp phù hợp khác và giả sử bạn đang sử dụng cơ sở dữ liệu địa lý để lưu trữ dữ liệu của mình, bạn có thể sử dụng lệnh Order By sql để sắp xếp theo thứ tự tăng dần trước khi thực hiện con trỏ cập nhật.

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)

Trong trường hợp này, mệnh đề where sẽ loại bỏ null trước khi thực hiện truy vấn hoặc bạn có thể sử dụng ví dụ khác kiểm tra Không có trước khi cập nhật.


Đẹp! Sử dụng thứ tự bằng cách tăng dần và lấy bản ghi đầu tiên chắc chắn sẽ nhanh hơn nhận được tất cả các giá trị và sau đó tìm thấy min(). Tôi cũng sẽ bao gồm điều này trong các bài kiểm tra tốc độ của mình để thể hiện mức tăng hiệu suất.
crmackey

Tôi sẽ tò mò muốn xem nó xếp hạng ở đâu. Tôi sẽ không ngạc nhiên nếu các hoạt động sql thêm làm cho nó chậm.
dslamb

2
điểm chuẩn thời gian đã được thêm vào, xem chỉnh sửa của tôi. Và tôi nghĩ rằng bạn đã đúng, sql dường như thêm một số chi phí phụ nhưng nó đã thực hiện con trỏ đi qua toàn bộ danh sách trong 0.56vài giây, điều này không đạt được nhiều hiệu suất như tôi mong đợi.
crmackey

1

Bạn cũng có thể sử dụng numpy trong các trường hợp như thế này, mặc dù nó sẽ tốn nhiều bộ nhớ hơn.

Bạn vẫn sẽ nhận được cổ chai khi tải dữ liệu vào một mảng gọn gàng và sau đó quay lại nguồn dữ liệu một lần nữa, nhưng tôi đã thấy rằng sự khác biệt hiệu suất là tốt hơn (có lợi cho numpy) với các nguồn dữ liệu lớn hơn, đặc biệt là nếu bạn cần nhiều thống kê / tính toán.:

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

Tại sao không sắp xếp bảng tăng dần, sau đó sử dụng con trỏ tìm kiếm để lấy giá trị cho hàng đầu tiên? http://pro.arcgis.com/en/pro-app/tool-reference/data-manloyment/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

Tôi sẽ gói SearchCursortrong một biểu thức máy phát điện (tức là min()) cho cả tốc độ và sự cô đọng. Sau đó kết hợp giá trị tối thiểu từ biểu thức trình tạo trong một daloại UpdateCursor. Một cái gì đó như sau:

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)

Không nên SearchCursorđóng cửa khi bạn hoàn thành nó?
jpmc26

1
@ jpmc26 Một con trỏ có thể được giải phóng khi hoàn thành con trỏ. Nguồn (Con trỏ và khóa): pro.arcgis.com/en/pro-app/arcpy/get-started/ . Một ví dụ khác từ Esri (xem ví dụ 2): pro.arcgis.com/en/pro-app/arcpy/data-access/ mẹo
Aaron

0

Trong vòng lặp của bạn, bạn có hai tham chiếu hàm được đánh giá lại cho mỗi lần lặp.

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

Nó sẽ nhanh hơn (nhưng phức tạp hơn một chút) để có các tham chiếu bên ngoài vòng lặp:

getvalue = row.getValue
append = ListVal.append

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

Điều này sẽ không thực sự làm nó chậm lại? Bạn thực sự đang tạo một tham chiếu riêng mới cho append()phương thức dựng sẵn của listkiểu dữ liệu. Tôi không nghĩ đây là nơi mà nút cổ chai của anh ta đang xảy ra, tôi sẽ đặt cược tiền là chức năng trường tính toán là thủ phạm. Điều này có thể được xác minh bằng cách định thời gian cho máy tính trường so với con trỏ kiểu mới.
crmackey

1
thực sự tôi cũng sẽ quan tâm đến thời gian biểu :) Nhưng nó là một sự thay thế dễ dàng trong mã gốc và do đó được kiểm tra nhanh.
Mờ

Tôi biết tôi đã thực hiện một số bài kiểm tra điểm chuẩn một thời gian trước về con trỏ so với máy tính trường. Tôi sẽ làm một bài kiểm tra khác và báo cáo kết quả của tôi trong câu trả lời của tôi. Tôi nghĩ cũng sẽ tốt khi hiển thị tốc độ con trỏ cũ và mới.
crmackey
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.