ArcPy를 사용하여 1600 만 개의 레코드를 반복합니까?


13

열 8 개와 ~ 1670 만 개의 레코드가있는 테이블이 있습니다. 열에 if-else 방정식 세트를 실행해야합니다. UpdateCursor 모듈을 사용하여 스크립트를 작성했지만 몇 백만 레코드 후에 메모리가 부족합니다. 이 1670 만 개의 레코드를 처리하는 더 좋은 방법이 있는지 궁금합니다.

import arcpy

arcpy.TableToTable_conversion("combine_2013", "D:/mosaic.gdb", "combo_table")

c_table = "D:/mosaic.gdb/combo_table"

fields = ['dev_agg', 'herb_agg','forest_agg','wat_agg', 'cate_2']

start_time = time.time()
print "Script Started"
with arcpy.da.UpdateCursor(c_table, fields) as cursor:
    for row in cursor:
        # row's 0,1,2,3,4 = dev, herb, forest, water, category
        #classficiation water = 1; herb = 2; dev = 3; forest = 4
        if (row[3] >= 0 and row[3] > row[2]):
            row[4] = 1
        elif (row[2] >= 0 and row[2] > row[3]):
            row[4] = 4
        elif (row[1] > 180):
            row[4] = 2
        elif (row[0] > 1):
            row[4] = 3
        cursor.updateRow(row)
end_time = time.time() - start_time
print "Script Complete - " +  str(end_time) + " seconds"

업데이트 # 1

40GB RAM이있는 컴퓨터에서 동일한 스크립트를 실행했습니다 (원래 컴퓨터에는 12GB RAM 만 있음). ~ 16 시간 후에 성공적으로 완료되었습니다. 나는 16 시간이 너무 길다고 생각하지만 그렇게 큰 데이터 세트를 사용한 적이 없으므로 기대할 바가 없습니다. 이 스크립트에 새로 추가 된 것은 arcpy.env.parallelProcessingFactor = "100%"입니다. 나는 두 가지 제안 된 방법을 시도하고 있습니다. (1) 백만 개의 레코드를 일괄 처리하고 (2) SearchCursor를 사용하고 csv에 출력을 작성합니다. 진행 상황을 곧보고하겠습니다.

업데이트 # 2

SearchCursor 및 CSV 업데이트가 훌륭하게 작동했습니다! 나는 정확한 실행 시간이 없지만 내일 사무실에있을 때 게시물을 업데이트하지만 대략적인 실행 시간은 ~ 5-6 분이라고 말하면 꽤 인상적입니다. 나는 그것을 기대하지 않았다. 닦지 않은 코드를 공유하고 있으며 의견과 개선 사항이 있으면 언제든지 환영합니다.

import arcpy, csv, time
from arcpy import env

arcpy.env.parallelProcessingFactor = "100%"

arcpy.TableToTable_conversion("D:/mosaic.gdb/combine_2013", "D:/mosaic.gdb", "combo_table")
arcpy.AddField_management("D:/mosaic.gdb/combo_table","category","SHORT")

# Table
c_table = "D:/mosaic.gdb/combo_table"
fields = ['wat_agg', 'dev_agg', 'herb_agg','forest_agg','category', 'OBJECTID']

# CSV
c_csv = open("D:/combine.csv", "w")
c_writer = csv.writer(c_csv, delimiter= ';',lineterminator='\n')
c_writer.writerow (['OID', 'CATEGORY'])
c_reader = csv.reader(c_csv)

start_time = time.time()
with arcpy.da.SearchCursor(c_table, fields) as cursor:
    for row in cursor:
        #skip file headers
        if c_reader.line_num == 1:
            continue
        # row's 0,1,2,3,4,5 = water, dev, herb, forest, category, oid
        #classficiation water = 1; dev = 2; herb = 3; ; forest = 4
        if (row[0] >= 0 and row[0] > row[3]):
            c_writer.writerow([row[5], 1])
        elif (row[1] > 1):
            c_writer.writerow([row[5], 2])
        elif (row[2] > 180):
            c_writer.writerow([row[5], 3])
        elif (row[3] >= 0 and row[3] > row[0]):
            c_writer.writerow([row[5], 4])

c_csv.close()
end_time =  time.time() - start_time
print str(end_time) + " - Seconds"

업데이트 # 3 최종 업데이트. 스크립트의 총 실행 시간은 ~ 199.6 초 /3.2 분입니다.


1
64 비트 (Background 또는 Server 또는 Pro)를 사용하고 있습니까?
KHibma

언급을 잊었다. 백그라운드에서 10.4 x64를 실행 중입니다.
cptpython

악마 옹호자 -ArcMap을 열 필요가없는 스크립트를 보면서 전경 이나 IDLE 에서 실행 해 보셨습니까?
Hornbydd

독립형 스크립트로 실행하거나 SQL을 알고있는 경우 shapefile을 PostgreSQL에 업로드하여 수행하십시오.
ziggy

1
나는 그것이 오픈 소스라는 것을 알고 있지만 승인 절차는 ~ 1-2 주가 걸리며 시간에 민감 하므로이 경우에는 가능하지 않다고 생각합니다.
cptpython

답변:


4

Objectid와 계산 결과 (cate_2)를 csv 파일에 쓸 수 있습니다. 그런 다음 csv를 원본 파일에 결합하고 필드를 채워 결과를 보존하십시오. 이 방법으로 DA 커서를 사용하여 테이블을 업데이트하지 않습니다. 검색 커서를 사용할 수 있습니다.


나는 여기 에 토론이있는 것과 같은 것을 생각하고 있었고 더 큰 데이터 세트에 대해 이야기하고 있습니다.
Hornbydd

고마워, 클로이 스 유망한 소리. FelixIP의 제안과 함께 시도해 볼 것이며, 수십 번 실행해야 할 흥미로운 토론이 있습니다.
cptpython

훌륭하게 일했습니다! 최신 스크립트로 질문을 업데이트했습니다. 감사!
cptpython

2

이 오래된 실을 계속 부활 시키면 사과합니다. 아이디어는 결합 래스터에서 if-else 문을 수행 한 다음 조회의 새 필드를 사용하여 새 래스터를 만드는 것입니다. 데이터를 테이블로 내 보내서 문제를 복잡하게 만들었고 @Alex Tereshenkov가 해결 한 비효율적 인 워크 플로를 도입했습니다. 명백한 사실을 깨달은 후 @FelixIP가 제안한대로 17 개의 쿼리 (각 백만 건)로 데이터를 일괄 처리했습니다. 각 배치를 완료하는 데 평균 ~ 1.5 분이 소요되었으며 총 실행 시간은 ~ 23.3 분입니다. 이 방법은 조인의 필요성을 없애고이 방법이 작업을 가장 잘 수행한다고 생각합니다. 나중에 참조 할 수 있도록 수정 된 스크립트는 다음과 같습니다.

import arcpy, time
from arcpy import env

def cursor():
    combine = "D:/mosaic.gdb/combine_2013"
    #arcpy.AddField_management(combine,"cat_1","SHORT")
    fields = ['wat_agg', 'dev_agg', 'herb_agg','forest_agg', 'cat_1']
    batch = ['"OBJECTID" >= 1 AND "OBJECTID" <= 1000000', '"OBJECTID" >= 1000001 AND "OBJECTID" <= 2000000', '"OBJECTID" >= 2000001 AND "OBJECTID" <= 3000000', '"OBJECTID" >= 3000001 AND "OBJECTID" <= 4000000', '"OBJECTID" >= 4000001 AND "OBJECTID" <= 5000000', '"OBJECTID" >= 5000001 AND "OBJECTID" <= 6000000', '"OBJECTID" >= 6000001 AND "OBJECTID" <= 7000000', '"OBJECTID" >= 7000001 AND "OBJECTID" <= 8000000', '"OBJECTID" >= 8000001 AND "OBJECTID" <= 9000000', '"OBJECTID" >= 9000001 AND "OBJECTID" <= 10000000', '"OBJECTID" >= 10000001 AND "OBJECTID" <= 11000000', '"OBJECTID" >= 11000001 AND "OBJECTID" <= 12000000', '"OBJECTID" >= 12000001 AND "OBJECTID" <= 13000000', '"OBJECTID" >= 13000001 AND "OBJECTID" <= 14000000', '"OBJECTID" >= 14000001 AND "OBJECTID" <= 15000000', '"OBJECTID" >= 15000001 AND "OBJECTID" <= 16000000', '"OBJECTID" >= 16000001 AND "OBJECTID" <= 16757856']
    for i in batch:
        start_time = time.time()
        with arcpy.da.UpdateCursor(combine, fields, i) as cursor:
            for row in cursor:
            # row's 0,1,2,3,4,5 = water, dev, herb, forest, category
            #classficiation water = 1; dev = 2; herb = 3; ; forest = 4
                if (row[0] >= 0 and row[0] >= row[3]):
                    row[4] = 1
                elif (row[1] > 1):
                    row[4] = 2
                elif (row[2] > 180):
                    row[4] = 3
                elif (row[3] >= 0 and row[3] > row[0]):
                    row[4] = 4
                cursor.updateRow(row)
        end_time =  time.time() - start_time
        print str(end_time) + " - Seconds"

cursor()

그래서, 나는 이것을 올바르게 이해하고 있는지 확인하십시오. 원래 게시물에서 40GB RAM이있는 컴퓨터 에서이 작업을 실행하면 총 16 시간이 걸렸다 고 말했습니다. 그러나 이제는 17 개의 배치로 나누었고 총 23 분이 걸렸습니다. 그 맞습니까?
ianbroad

옳은. 첫 번째 실행은 40GB RAM으로 약 16 시간이 걸렸고 두 번째 실행은 Lookup새로 정의 된 범주로 래스터 를 수행 하고 내보내는 데 ~ 23 분 + 또 다른 ~ 15 분이 걸렸습니다 .
cptpython

arcpy.env.parallelProcessingFactor = "100%"스크립트에 영향을 미치지 않는 메모입니다 . 해당 환경을 활용하는 도구가 없습니다.
KHibma

당신은 맞습니다. 코드를 편집하겠습니다.
cptpython

1

CalculateField_management 사용으로 변경해 볼 수 있습니다. 이것은 커서 사용을 통한 루핑을 피하고 범주 값에 대한 옵션의 모양에 따라 순차적으로 생성 된 4 개의 하위 프로세스로 설정할 수 있습니다. 각 하위 프로세스가 완료되면 다음 하위 프로세스를 시작하기 전에 메모리가 해제됩니다. 각 하위 프로세스를 생성하는 데 약간의 시간 (밀리 초)이 걸립니다.

또는 현재 접근 방식을 유지하려면 한 번에 x 행이 필요한 하위 프로세스를 갖습니다. 드라이브를 구동하는 주요 프로세스가 있으며 이전과 마찬가지로 메모리가 끝날 때마다 계속 메모리를 청소하십시오. 이런 식으로 (특히 독립형 파이썬 프로세스를 통해) 수행하면 보너스는 모든 코어를 GIL을 둘러싼 파이썬의 멀티 스레딩에서 하위 프로세스를 생성하는 것으로 더 많이 사용할 수 있다는 것입니다. 이것은 ArcPy와 과거에 대규모 데이터 이탈을 수행하는 데 사용한 접근법으로 가능합니다. 분명히 데이터 덩어리를 줄이십시오. 그렇지 않으면 메모리가 더 빨리 소모됩니다!


내 경험상 arcpy.da.UpdateCursor를 사용하는 것이 arcpy.CalculateField_management보다 훨씬 빠릅니다. 55.000.000 건물 기능에서 실행되는 스크립트를 작성했는데 CalculateField 도구를 사용하면 약 5 배 느립니다.
offermann

요점은 4 개의 하위 프로세스를 설정하고 실제 핀치 지점 인 것처럼 메모리를 청소하는 것입니다. 두 번째 단락에서 간략히 설명했듯이 하위 프로세스를 행으로 나눌 수 있지만 단일 선택보다 약간 더 많은 관리가 필요합니다.
MappaGnosis

1

데이터 조작 로직은 CASE 표현식을 사용하여 UPDATE SQL 문으로 작성 될 수 있으며, GDAL / OGR을 사용하여 (예 : OSGeo4W가 gdal-filegdb설치된 경우) 실행할 수 있습니다 .

다음 osgeo.ogr대신에 사용하는 워크 플로 가 있습니다 arcpy.

import time
from osgeo import ogr

ds = ogr.Open('D:/mosaic.gdb', 1)
if ds is None:
    raise ValueError("You don't have a 'FileGDB' driver, or the dataset doesn't exist")
sql = '''\
UPDATE combo_table SET cate_2 = CASE
    WHEN wat_agg >= 0 AND wat_agg > forest_agg THEN 1
    WHEN dev_agg > 1 THEN 2
    WHEN herb_agg > 180 THEN 3
    WHEN forest_agg >= 0 AND forest_agg > wat_agg THEN 4
    END
'''
start_time = time.time()
ds.ExecuteSQL(sql, dialect='sqlite')
ds = None  # save, close
end_time =  time.time() - start_time
print("that took %.1f seconds" % end_time)

백만 개가 넘는 레코드가있는 유사한 테이블에서이 쿼리는 18 분이 걸렸습니다. 따라서 1600 만 개의 레코드를 처리하는 데 여전히 4 ~ 5 시간이 소요될 수 있습니다.


불행히도 스크립트는 사용하여 작성된 더 큰 스크립트의 일부 arcpy이지만 답변을 주셔서 감사합니다. 천천히 GDAL을 더 많이 사용하려고합니다.
cptpython

1

귀하의 질문에있는 # 2 섹션의 코드 업데이트는 .csv파일 지오 데이터베이스의 원래 테이블에 파일을 다시 결합하는 방법을 보여주지 않습니다 . 스크립트를 실행하는 데 ~ 5 분이 걸렸습니다. .csv조인을 수행하지 않고 파일을 내 보낸 경우에만 적합 합니다. .csv파일을 ArcGIS로 다시 가져 오려고 하면 성능 문제가 발생합니다.

1) 파일 에 OID가 없으므로 파일을 .csv지오 데이터베이스 테이블로 직접 조인 할 수 없습니다 (유일한 값으로 계산 된 필드가 있으면 파일을 지오 데이터베이스 테이블로 변환해야하므로 도움이되지 않습니다 ). 따라서 GP 도구의 경우 몇 분이 걸립니다 ( 작업 공간을 사용하여 임시 테이블을 만들면 약간 빠릅니다)..csv.csvTable To Tablein_memory

2)를 .csv지오 데이터베이스 테이블에 로드 한 후 조인 할 필드 ( 파일 의 소스 objectid값) .csv에 인덱스를 작성하려고합니다. 16mln 행 테이블에 몇 분이 걸립니다.

3) 그런 다음 Add Join또는 Join FieldGP 도구를 사용해야합니다 . 큰 테이블에서도 잘 수행되지 않습니다.

4) 그런 Calculate Field다음 새로 가입 한 필드를 계산하려면 GP 도구를 수행해야합니다 . 많은 분이 여기에 간다; 또한 계산에 참여하는 필드가 조인 된 테이블에서 오는 경우 필드 계산에 더 많은 시간이 걸립니다.

한마디로, 당신은 당신이 언급 한 5 분 가까이 아무것도 얻지 못할 것입니다. 한 시간 안에 만들면 감동받을 것입니다.

ArcGIS 내에서 큰 데이터 세트를 처리하지 않으려면 ArcGIS 외부의 pandas데이터를 데이터 프레임 으로 가져 와서 모든 계산을 수행 하는 것이 좋습니다 . 완료되면 데이터 프레임 행을 새 지오 데이터베이스 테이블에 다시 쓰십시오 da.InsertCursor(또는 기존 테이블을 잘라 내고 소스 테이블에 행을 쓸 수 있음).

이것을 벤치마킹하기 위해 작성한 전체 코드는 다음과 같습니다.

import time
from functools import wraps
import arcpy
import pandas as pd

def report_time(func):
    '''Decorator reporting the execution time'''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, round(end-start,3))
        return result
    return wrapper

#----------------------------------------------------------------------
@report_time
def make_df(in_table,limit):
    columns = [f.name for f in arcpy.ListFields(in_table) if f.name != 'OBJECTID']
    cur = arcpy.da.SearchCursor(in_table,columns,'OBJECTID < {}'.format(limit))
    rows = (row for row in cur)
    df = pd.DataFrame(rows,columns=columns)
    return df

#----------------------------------------------------------------------
@report_time
def calculate_field(df):
    df.ix[(df['DataField2'] % 2 == 0), 'Category'] = 'two'
    df.ix[(df['DataField2'] % 4 == 0), 'Category'] = 'four'
    df.ix[(df['DataField2'] % 5 == 0), 'Category'] = 'five'
    df.ix[(df['DataField2'] % 10 == 0), 'Category'] = 'ten'
    df['Category'].fillna('other', inplace=True)
    return df

#----------------------------------------------------------------------
@report_time
def save_gdb_table(df,out_table):
    rows_to_write = [tuple(r[1:]) for r in df.itertuples()]
    with arcpy.da.InsertCursor(out_table,df.columns) as ins_cur:
        for row in rows_to_write:
            ins_cur.insertRow(row)

#run for tables of various sizes
for limit in [100000,500000,1000000,5000000,15000000]:
    print '{:,}'.format(limit).center(50,'-')

    in_table = r'C:\ArcGIS\scratch.gdb\BigTraffic'
    out_table = r'C:\ArcGIS\scratch.gdb\BigTrafficUpdated'
    if arcpy.Exists(out_table):
        arcpy.TruncateTable_management(out_table)

    df = make_df(in_table,limit=limit)
    df = calculate_field(df)
    save_gdb_table(df, out_table)
    print

다음은 개별 함수의 실행 시간에 대한 정보와 함께 디버그 IO의 출력 (보고 된 수는 사용 된 테이블의 행 수)입니다.

---------------------100,000----------------------
('make_df', 1.141)
('calculate_field', 0.042)
('save_gdb_table', 1.788)

---------------------500,000----------------------
('make_df', 4.733)
('calculate_field', 0.197)
('save_gdb_table', 8.84)

--------------------1,000,000---------------------
('make_df', 9.315)
('calculate_field', 0.392)
('save_gdb_table', 17.605)

--------------------5,000,000---------------------
('make_df', 45.371)
('calculate_field', 1.903)
('save_gdb_table', 90.797)

--------------------15,000,000--------------------
('make_df', 136.935)
('calculate_field', 5.551)
('save_gdb_table', 275.176)

행을 삽입하는 da.InsertCursor데 일정한 시간이 걸립니다. 즉, 1 개의 행을 삽입하는 경우 (예 : 0.1 초) 100 개의 행을 삽입하는 데 10 초가 걸립니다. 안타깝게도 총 실행 시간의 95 % 이상이 지오 데이터베이스 테이블을 읽은 다음 행을 지오 데이터베이스에 다시 삽입하는 데 소비됩니다.

생성기 pandas에서 데이터 프레임 을 만들고 da.SearchCursor필드를 계산할 때도 마찬가지입니다 . 소스 지오 데이터베이스 테이블의 행 수가 두 배가되면 위 스크립트 실행 시간도 두 배가됩니다. 물론, 실행하는 동안에도 64 비트 Python 을 사용해야합니다 . 일부 더 큰 데이터 구조는 메모리에서 처리됩니다.


실제로, 나는 당신이 위에서 언급 한 문제에 부딪 쳤기 때문에 내가 사용한 방법의 한계에 대해 이야기 할 또 다른 질문을 할 것입니다! 내가 성취하려고하는 것 : 네 개의 래스터를 결합 한 다음 열을 기반으로 if-else 문을 수행하고 출력을 새 열에 쓰고 마지막으로 새 열의 Lookup값을 기반으로 래스터를 만듭니다. 내 방법에는 많은 불필요한 단계와 비효율적 인 워크 플로우가 있었으므로 원래 질문에서이를 언급해야합니다. 살고 배우십시오. 그래도 이번 주 후반에 스크립트를 시험해 볼 것입니다.
cptpython
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.