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

我认为,您似乎正在gis.stackexchange.com/q/197873/115
PolyGeo

为什么不使用arcpy.Statistics_analysisdesktop.arcgis.com/en/arcmap/10.3/tools/analysis-toolbox/…–
Berend

是。我必须从某个地方开始,并且很少需要使用arcpy进行任何编程。如此众多的人能够提出这么多的方法,真是太神奇了。这是学习新事物的最好方法。
罗伯特·巴克利

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

Answers:


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个随机点,并使用了相同的字段名称。

装饰器函数用于为每个函数计时(可能在设置和拆除函数时会花费一些时间,因此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

那只是意味着您的中有一些空值"XKoordInt"。查看我的编辑,您所要做的就是跳过null。
crmackey

2
请注意range。ArcGIS仍然使用Python 2.7,因此返回list。但是在3.x中,range它是它自己的一种特殊对象,可以进行优化。一个更可靠的测试是min(list(range(200000))),它将确保您使用的是简单列表。还可以考虑使用该timeit模块进行性能测试。
jpmc26 2016年

通过使用集合而不是列表,您可能会获得更多时间。这样,您就不会存储重复的值,而只搜索唯一的值。
Fezter

@Fezter取决于分布。必须有足够的精确重复项,以超过对所有值进行哈希处理并在构造过程中检查每个值是否在集合中的成本。例如,如果仅复制1%,则可能不值得花费。还要注意,如果该值是浮点数,则不可能有很多精确的重复项。
jpmc26 2016年

1

正如@crmackey指出的那样,速度较慢的部分可能是由于calculate field方法造成的。作为其他合适解决方案的替代方法,并假设您使用地理数据库存储数据,则可以在执行更新游标之前使用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子句在执行查询之前会删除空值,或者您可以使用另一个示例在更新前检查None。


真好!使用升序顺序并抓住第一条记录的顺序肯定比获取所有值然后找到记录更快min()。我还将在速度测试中包括这一点,以显示性能提升。
crmackey

我很想知道它的排名。如果多余的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())。然后将生成器表达式中的最小值合并为datype 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 2013年

1
@ jpmc26可以通过完成游标来释放游标。源(游标和锁定):pro.arcgis.com/en/pro-app/arcpy/get-started/...。从ESRI的另一个例子中(见实施例2):pro.arcgis.com/en/pro-app/arcpy/data-access/...
亚伦

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
实际上,我也会对计时感兴趣:)但这是在原始代码中的轻松替换,因此可以快速检查。
哑光

我知道我在游标与字段计算器之间做了一些基准测试。我将进行另一项测试,并在答案中报告我的发现。我认为也可以显示旧光标速度与新光标速度。
crmackey
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.