Python'da bir alandaki en küçük sayıyı bulmanın daha hızlı bir yolu var mı?


10

Arcgis Desktop 10.3.1 kullanma Bir listeye değerler eklemek için bir arama imleci kullanan bir komut dosyası var ve sonra en küçük tamsayıyı bulmak için min () kullanın. Değişken daha sonra bir komut dosyasında kullanılır. Feature sınıfının 200.000 satırı vardır ve komut dosyasının tamamlanması çok uzun zaman alır. Bunu daha hızlı yapmanın bir yolu var mı? Şu anda, uzun sürmesi nedeniyle bir senaryo yazmak yerine sadece elle yapacağımı düşünüyorum.

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


Kullanmamanızın bir nedeni var arcpy.Statistics_analysismı? desktop.arcgis.com/tr/arcmap/10.3/tools/analysis-toolbox/…
Berend

Evet. Bir yerden başlamam ve çok nadiren arcpy ile herhangi bir programlama yapmam gerekiyor. Pek çok insanın bu kadar çok yaklaşım önerebilmesi harika. Yeni şeyler öğrenmenin en iyi yolu budur.
Robert Buckley

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

Yanıtlar:


15

Betiğinizin yavaşlamasına neden olabilecek birkaç şey görebiliyorum. Muhtemelen çok yavaş olan şey arcpy.CalculateField_management()işlevdir. Bir imleç kullanmalısınız, birkaç büyüklükte daha hızlı olacaktır. Ayrıca, ArcGIS Desktop 10.3.1 kullandığınızı, ancak çok daha yavaş olan eski ArcGIS 10.0 stili imleçleri kullandığınızı söylediniz.

200K'lık bir listede bile min () işlemi oldukça hızlı olacaktır. Bu küçük parçacığı çalıştırarak bunu doğrulayabilirsiniz; bir göz açıp kapayıncaya kadar gerçekleşir:

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

Bunun daha hızlı olup olmadığına bakı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)

DÜZENLE:

Bazı zamanlama testleri yaptım ve şüphelendiğim gibi, saha hesaplayıcı yeni stil imlecinin neredeyse iki katı kadar sürdü. İlginç bir şekilde, eski stil imleci alan hesaplayıcıdan ~ 3 kat daha yavaştı. 200.000 rastgele nokta oluşturdum ve aynı alan adlarını kullandım.

Her işlevi zamanlamak için bir dekoratör işlevi kullanıldı (işlevlerin kurulumunda ve parçalanmasında hafif bir ek yük olabilir, bu yüzden timeit modülü snippet'leri test etmek için biraz daha doğru olacaktır).

Sonuçlar burada:

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

Ve işte kullandığım kod ( timeitdekoratörü kullanmak için her şeyi bireysel işlevlere böldüm):

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)

Ve son olarak, konsolumdan asıl çıktı buydu.

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

Edit 2: Sadece bazı güncellenmiş testler yayınladım, benim timeitfonksiyonumla hafif bir kusur buldum .


r [0] = (r [0] - değer) / 20.0 TypeError: -: 'NoneType' ve 'int' için desteklenmeyen işlenen tür (ler)
Robert Buckley

Bu sadece bazı boş değerleriniz olduğu anlamına gelir "XKoordInt". Düzenlememe bakın, tek yapmanız gereken null'ları atlamak.
crmackey

2
Dikkatli ol range. ArcGIS hala Python 2.7 kullanıyor, bu yüzden a döndürüyor list. Ancak 3.x'te, rangeoptimizasyonları olabilen kendi özel nesne türüdür. min(list(range(200000)))Basit bir listeyle çalışmanızı sağlayacak daha güvenilir bir test olacaktır . timeitPerformans testi için modülü kullanmayı da düşünün .
jpmc26

Listeler yerine kümeler kullanarak biraz daha zaman kazanabilirsiniz. Bu şekilde yinelenen değerler depolamazsınız ve yalnızca benzersiz değerleri ararsınız.
Fezter

@Fezter Dağılımına bağlıdır. Tüm değerleri sağlama ve inşaat sırasında her birinin sette olup olmadığını kontrol etme maliyetini aşmak için yeterli tam kopya olması gerekir. Örneğin, yalnızca% 1'i çoğaltılırsa, muhtemelen maliyete değmez. Ayrıca değer kayan nokta ise, pek çok kesin kopya olmayacağına dikkat edin.
jpmc26

1

@Crmackey'nin işaret ettiği gibi, yavaş bölüm büyük olasılıkla hesaplama alanı yönteminden kaynaklanmaktadır. Diğer uygun çözümlere bir alternatif olarak ve verilerinizi saklamak için bir coğrafi veritabanı kullandığınızı varsayarsak, güncelleme imlecini yapmadan önce artan düzende sıralamak için Sipariş sql komutunu kullanabilirsiniz.

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)

Bu durumda where cümlesi, sorguyu yapmadan önce null değerlerini kaldırır veya güncellemeden önce None değerini denetleyen diğer örneği kullanabilirsiniz.


Güzel! İlk rekoru yükselterek ve yakalayarak kullanmak, tüm değerleri alıp sonra bulmaktan daha hızlı olacaktır min(). Performans kazancını göstermek için hız testlerime de dahil edeceğim.
crmackey

Nerede olduğunu merak edeceğim. Ekstra sql işlemleri yavaş yaparsa şaşırmam.
16:15, dslamb

2
zamanlama ölçütleri eklendi, düzenlememe bakın. Ve doğru olduğunu düşünüyorum, sql biraz ek yük eklemek gibiydi, ancak tüm listeyi 0.56saniyeler içinde adım adım imleci gerçekleştirdi , bu beklediğim gibi bir performans kazancı kadar değil.
crmackey

1

Daha fazla bellek yoğun olmasına rağmen, bu gibi durumlarda numpy de kullanabilirsiniz.

Verileri bir numpy dizisine yüklerken ve sonra tekrar veri kaynağına geri döndüğünüzde yine de bir şişe boynu alacaksınız, ancak performans farkının daha büyük veri kaynakları ile daha iyi olduğunu (numpy'nin lehine), özellikle de birden fazla veriye ihtiyacınız varsa istatistikler / hesaplamaları .:

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

Neden tabloyu artan olarak sıralamıyorsunuz, ardından ilk satırın değerini almak için bir arama imleci kullanıyorsunuz? 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

Ben örtecek SearchCursorbir de jeneratör ifade (yani min()hız ve özlülük ikisi için). Daha sonra bir datipte jeneratör ifadesinden minimum değeri ekleyin UpdateCursor. Aşağıdaki gibi bir şey:

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)

İşiniz SearchCursorbittiğinde kapalı olmamalı mı ?
jpmc26

1
@ jpmc26 Bir imleç, imlecin tamamlanmasıyla serbest bırakılabilir. Kaynak (İmleçler ve kilitleme): pro.arcgis.com/en/pro-app/arcpy/get-started/… . Esri'den
Aaron

0

Döngünüzde, her yineleme için yeniden değerlenen iki işlev başvurunuz vardır.

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

Referansların döngü dışında olması daha hızlı (ancak biraz daha karmaşık) olmalıdır:

getvalue = row.getValue
append = ListVal.append

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

Bu gerçekten yavaşlamaz mı? Aslında veri tipinin yerleşik append()yöntemi için yeni bir ayrı başvuru oluşturuyorsunuz list. Onun darboğazının gerçekleştiği yer olduğunu sanmıyorum, para hesapla alan fonksiyonunun suçlu olduğuna bahse girerim. Bu, alan hesap makinesinin yeni bir stil imlecine karşı zamanlamasıyla doğrulanabilir.
crmackey

1
aslında ben de zamanlamaları ile ilgilenen olur :) Ama orijinal kod kolay bir yerine ve bu nedenle hızlı kontrol.
Mat

Ben bir süre geri imleçler vs saha hesap makinesi üzerinde bazı testler yaptım biliyorum. Başka bir test yapacağım ve bulgularımı cevabımda bildireceğim. Eski ve yeni imleç hızını da göstermenin iyi olacağını düşünüyorum.
crmackey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.