Apakah ada cara yang lebih cepat dalam python untuk menemukan angka terkecil dalam suatu bidang?

10

Menggunakan arcgis desktop 10.3.1 Saya memiliki skrip yang menggunakan kursor pencarian untuk menambahkan nilai ke daftar dan kemudian menggunakan min () untuk menemukan integer terkecil. Variabel tersebut kemudian digunakan dalam skrip. Kelas Fitur memiliki 200.000 baris dan skrip membutuhkan waktu yang sangat lama untuk diselesaikan. Apakah ada cara untuk melakukan ini lebih cepat? Saat ini saya pikir saya hanya akan melakukannya dengan tangan daripada menulis naskah karena lamanya waktu yang dibutuhkan.

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")
Robert Buckley
sumber
Saya pikir ada cara no-Python yang lebih cepat untuk melakukan ini yang sepertinya sedang Anda kerjakan di gis.stackexchange.com/q/197873/115
PolyGeo
Ada alasan mengapa Anda tidak menggunakan arcpy.Statistics_analysis? desktop.arcgis.com/en/arcmap/10.3/tools/analysis-toolbox/…
Berend
Iya. Saya harus mulai di suatu tempat dan hanya sangat jarang melakukan pemrograman dengan arcpy. Luar biasa bahwa begitu banyak orang dapat menyarankan begitu banyak pendekatan. Ini adalah cara terbaik untuk mempelajari hal-hal baru.
Robert Buckley
min_val = min([i[0] for i in arcpy.da.SearchCursor(fc,Xfield)])
BERA

Jawaban:

15

Saya dapat melihat beberapa hal yang mungkin menyebabkan skrip Anda lambat. Hal yang mungkin sangat lambat adalah arcpy.CalculateField_management()fungsinya. Anda harus menggunakan kursor, dengan beberapa besaran lebih cepat. Juga, Anda mengatakan Anda menggunakan ArcGIS Desktop 10.3.1, tetapi Anda menggunakan kursor gaya lama ArcGIS 10.0, yang juga jauh lebih lambat.

Operasi min () bahkan pada daftar 200K akan cukup cepat. Anda dapat memverifikasi ini dengan menjalankan cuplikan kecil ini; itu terjadi dalam sekejap mata:

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

Lihat apakah ini lebih cepat:

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)

EDIT:

Saya menjalankan beberapa tes waktu dan seperti yang saya duga, kalkulator lapangan mengambil hampir dua kali lipat panjang kursor gaya baru. Menariknya, kursor gaya lama ~ 3x lebih lambat dari kalkulator lapangan. Saya membuat 200.000 poin acak dan menggunakan nama bidang yang sama.

Fungsi dekorator digunakan untuk menentukan waktu setiap fungsi (mungkin ada sedikit overhead dalam pengaturan dan meruntuhkan fungsi, jadi mungkin modul timeit akan sedikit lebih akurat untuk menguji snippet).

Inilah hasilnya:

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

Dan di sini adalah kode yang saya gunakan (memecah semuanya menjadi fungsi individu untuk menggunakan timeitdekorator):

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)

Dan akhirnya, inilah hasil cetak sebenarnya dari konsol saya.

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

Sunting 2: Baru saja memposting beberapa tes yang diperbarui, saya menemukan sedikit cacat pada timeitfungsi saya .

Crmackey
sumber
r [0] = (r [0] - value) / 20.0 TypeError: tipe operan yang tidak didukung untuk -: 'NoneType' dan 'int'
Robert Buckley
Itu hanya berarti Anda memiliki beberapa nilai nol di "XKoordInt". Lihat hasil edit saya, yang harus Anda lakukan adalah melewati nol.
crmackey
2
Hati-hati dengan range. ArcGIS masih menggunakan Python 2.7, jadi ia mengembalikan a list. Tetapi dalam 3.x, rangeadalah jenis objeknya sendiri yang mungkin memiliki optimisasi. Tes yang lebih andal adalah min(list(range(200000))), yang akan memastikan Anda bekerja dengan daftar sederhana. Juga pertimbangkan untuk menggunakan timeitmodul untuk pengujian kinerja.
jpmc26
Anda mungkin bisa mendapatkan lebih banyak waktu dengan menggunakan set daripada daftar. Dengan begitu Anda tidak menyimpan nilai duplikat, dan Anda hanya mencari nilai unik.
Fezter
@Feater Itu tergantung pada distribusinya. Harus ada duplikat tepat yang cukup untuk melebihi biaya hashing semua nilai dan memeriksa apakah masing-masing dalam set selama konstruksi. Misalnya, jika hanya 1% digandakan, itu mungkin tidak sepadan dengan biayanya. Perhatikan juga bahwa jika nilainya floating point, tidak mungkin ada banyak duplikat yang tepat.
jpmc26
1

Seperti yang ditunjukkan oleh @crmackey, porsi lambat mungkin disebabkan oleh metode bidang hitung. Sebagai alternatif dari solusi lain yang sesuai, dan dengan asumsi Anda menggunakan geodatabase untuk menyimpan data Anda, Anda bisa menggunakan perintah Order By sql untuk mengurutkan dalam urutan naik sebelum melakukan kursor pembaruan.

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)

Dalam hal ini klausa tempat menghapus nol sebelum melakukan kueri, atau Anda dapat menggunakan contoh lain yang memeriksa Tidak ada sebelum memperbarui.

dslamb
sumber
Bagus! Menggunakan urutan dengan menaik dan meraih catatan pertama pasti akan lebih cepat daripada mendapatkan semua nilai dan kemudian menemukan min(). Saya akan memasukkan ini dalam tes kecepatan saya juga untuk menunjukkan peningkatan kinerja.
crmackey
Saya akan penasaran melihat di mana peringkatnya. Saya tidak akan terkejut jika operasi sql ekstra membuatnya lambat.
dslamb
2
tolok ukur waktu telah ditambahkan, lihat hasil edit saya. Dan saya pikir Anda benar, sql tampaknya menambahkan beberapa overhead tambahan tetapi melakukan kursor yang langkah melalui seluruh daftar dengan 0.56detik, yang tidak sebanyak keuntungan kinerja seperti yang saya harapkan.
crmackey
1

Anda juga dapat menggunakan numpy dalam kasus-kasus seperti ini, meskipun akan lebih intensif memori.

Anda masih akan mendapatkan leher botol saat memuat data ke array numpy dan kemudian kembali ke sumber data lagi, tetapi saya telah menemukan bahwa perbedaan kinerja lebih baik (mendukung numpy) dengan sumber data yang lebih besar, terutama jika Anda memerlukan banyak statistik / perhitungan .:

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)
Genius Jahat
sumber
1

Mengapa tidak mengurutkan tabel naik, lalu gunakan kursor pencarian untuk mengambil nilai untuk baris pertama? 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
crld
sumber
1

Saya akan membungkusnya SearchCursordalam ekspresi generator (yaitu min()) untuk kedua kecepatan dan ringkas. Kemudian gabungkan nilai minimum dari ekspresi generator dalam suatu datipe UpdateCursor. Sesuatu seperti yang berikut ini:

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)
Harun
sumber
Bukankah SearchCursorseharusnya ditutup ketika Anda selesai dengan itu?
jpmc26
1
@ jpmc26 Kursor dapat dilepaskan dengan selesainya kursor. Sumber (Kursor dan penguncian): pro.arcgis.com/en/pro-app/arcpy/get-started/… . Contoh lain dari Esri (lihat contoh 2): pro.arcgis.com/en/pro-app/arcpy/data-access/…
Aaron
0

Dalam loop Anda, Anda memiliki dua referensi fungsi yang direvaluasi untuk setiap iterasi.

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

Seharusnya lebih cepat (tapi sedikit lebih rumit) untuk memiliki referensi di luar loop:

getvalue = row.getValue
append = ListVal.append

for row in cursor:
    append(getvalue(Xfield))
Matte
sumber
Bukankah ini benar-benar memperlambatnya? Anda sebenarnya membuat referensi terpisah baru untuk append()metode bawaan dari listtipe data. Saya tidak berpikir ini adalah di mana kemacetannya terjadi, saya berani bertaruh uang menghitung fungsi bidang adalah pelakunya. Ini dapat diverifikasi dengan menghitung kalkulator lapangan vs. kursor gaya baru.
crmackey
1
sebenarnya saya akan tertarik pada timing juga :) Tapi itu adalah penggantian yang mudah dalam kode asli dan karena itu cepat diperiksa.
Matte
Saya tahu saya melakukan beberapa tes benchmark beberapa waktu lalu pada kursor vs kalkulator lapangan. Saya akan melakukan tes lain dan melaporkan temuan saya dalam jawaban saya. Saya pikir juga bagus untuk menunjukkan kecepatan kursor lama vs baru juga.
crmackey