หลามมีวิธีที่เร็วกว่าในการค้นหาจำนวนที่น้อยที่สุดในฟิลด์หรือไม่?


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_analysis? desktop.arcgis.com/th/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(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

นั่นก็หมายความว่าคุณมีค่า null "XKoordInt"บางอย่างในของคุณ ดูการแก้ไขของฉันสิ่งที่คุณต้องทำคือข้ามโมฆะ
crmackey

2
rangeโปรดใช้ความระมัดระวังด้วย ArcGIS ยังคงใช้ Python 2.7 listดังนั้นจึงส่งกลับ แต่ใน 3.x rangeเป็นวัตถุชนิดพิเศษซึ่งอาจมีการปรับให้เหมาะสม การทดสอบที่น่าเชื่อถือมากขึ้นmin(list(range(200000)))นั้นจะช่วยให้คุณทำงานกับรายการธรรมดาได้ นอกจากนี้ให้พิจารณาใช้timeitโมดูลสำหรับการทดสอบประสิทธิภาพ
jpmc26

คุณอาจได้รับเวลาเพิ่มขึ้นโดยใช้ชุดมากกว่ารายการ ด้วยวิธีนี้คุณจะไม่เก็บค่าซ้ำกันและคุณค้นหาค่าที่ไม่ซ้ำกันเท่านั้น
Fezter

@Fezter มันขึ้นอยู่กับการกระจาย จะต้องมีการทำซ้ำที่แน่นอนเพียงพอที่จะเกินดุลค่าใช้จ่ายของการ hashing ค่าทั้งหมดและตรวจสอบว่าแต่ละคนอยู่ในชุดในระหว่างการก่อสร้าง เช่นหากทำซ้ำเพียง 1% ก็อาจไม่คุ้มกับต้นทุน นอกจากนี้โปรดทราบว่าหากค่าเป็นจุดลอยตัวก็ไม่น่าจะมีจำนวนซ้ำกันแน่นอน
jpmc26

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 ก่อนที่จะทำการสืบค้นหรือคุณสามารถใช้ตัวอย่างอื่นที่ตรวจสอบว่าไม่มีก่อนทำการอัพเดท


ดี! 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()) ทั้งความเร็วและรวบรัด จากนั้นรวมค่าต่ำสุดจากการแสดงออกของเครื่องกำเนิดไฟฟ้าในประเภท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

1
@ jpmc26 เคอร์เซอร์สามารถปล่อยออกมาโดยการเสร็จสิ้นของเคอร์เซอร์ แหล่งที่มา (เคอร์เซอร์และล็อค): pro.arcgis.com/en/pro-app/arcpy/get-started/... อีกตัวอย่างจาก Esri (ดูตัวอย่างที่ 2): pro.arcgis.com/th/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()วิธีการbuiltin ของlistประเภทข้อมูล ฉันไม่คิดว่านี่คือสิ่งที่คอขวดของเขากำลังเกิดขึ้นฉันจะเดิมพันเงินที่ฟังก์ชั่นการคำนวณเป็นผู้ร้าย สิ่งนี้สามารถตรวจสอบได้โดยกำหนดเวลาที่เครื่องคำนวณฟิลด์เทียบกับเคอร์เซอร์สไตล์ใหม่
crmackey

1
จริง ๆ แล้วฉันก็จะสนใจในการกำหนดเวลาเช่นกัน :) แต่มันเป็นเรื่องง่ายที่จะแทนที่ในรหัสเดิมและตรวจสอบอย่างรวดเร็ว
Matte

ฉันรู้ว่าฉันได้ทำการทดสอบเกณฑ์มาตรฐานบ้างครู่หนึ่งย้อนกลับไปที่เคอร์เซอร์และเครื่องคำนวณภาคสนาม ฉันจะทำแบบทดสอบอื่นและรายงานสิ่งที่พบในคำตอบของฉัน ฉันคิดว่ามันจะเป็นการดีหากแสดงความเร็วเคอร์เซอร์เก่าและใหม่เช่นกัน
crmackey
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.