Vòng qua 16 triệu hồ sơ bằng ArcPy?


13

Tôi có một bảng có 8 cột và ~ 16,7 triệu bản ghi. Tôi cần chạy một bộ phương trình if-other trên các cột. Tôi đã viết một tập lệnh bằng mô-đun UpdateCthon, nhưng sau vài triệu bản ghi, nó hết bộ nhớ. Tôi đã tự hỏi nếu có một cách tốt hơn để xử lý 16,7 triệu hồ sơ này.

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"

CẬP NHẬT # 1

Tôi đã chạy cùng một kịch bản trên một máy tính có RAM 40 gb (máy tính ban đầu chỉ có 12 gb RAM). Nó hoàn thành thành công sau ~ 16 giờ. Tôi cảm thấy 16 giờ là quá dài, nhưng tôi chưa bao giờ làm việc với bộ dữ liệu lớn như vậy nên tôi không biết phải mong đợi điều gì. Sự bổ sung mới duy nhất cho kịch bản này là arcpy.env.parallelProcessingFactor = "100%". Tôi đang thử hai phương pháp được đề xuất (1) thực hiện 1 triệu bản ghi theo đợt và (2) sử dụng SearchCoder và viết kết quả đầu ra cho csv. Tôi sẽ báo cáo về tiến độ trong thời gian ngắn.

CẬP NHẬT # 2

Bản cập nhật SearchCthon và CSV đã hoạt động rất tốt! Tôi không có thời gian chạy chính xác, tôi sẽ cập nhật bài đăng khi tôi ở văn phòng vào ngày mai nhưng tôi sẽ nói thời gian chạy gần đúng là ~ 5-6 phút, khá ấn tượng. Tôi đã không mong đợi nó. Tôi đang chia sẻ mã chưa được đánh bóng của mình, mọi bình luận và cải tiến đều được hoan nghênh:

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"

CẬP NHẬT # 3 Cập nhật cuối cùng. Tổng thời gian chạy cho tập lệnh là ~ 199,6 giây / 3,2 phút.


1
Bạn có đang sử dụng 64 bit (Nền hoặc Máy chủ hoặc Pro) không?
KHibma

Quên đề cập đến. Tôi đang chạy 10,4 x64 trong Nền.
cptpython

Ma quỷ ủng hộ - bạn đã thử chạy nó ở nền trước hoặc từ IDLE khi xem tập lệnh của bạn, bạn không cần phải mở ArcMap?
Hornbydd

chạy nó dưới dạng một tập lệnh độc lập hoặc nếu bạn biết SQL, hãy tải shapefile lên PostgreQuery và thực hiện nó ở đó
ngoằn ngoèo

1
Tôi hiểu nó là nguồn mở, nhưng quá trình phê duyệt mất ~ 1-2 tuần và đây là thời điểm nhạy cảm nên tôi không nghĩ nó khả thi trong trường hợp này.
cptpython

Câu trả lời:


4

Bạn có thể viết Objectid và kết quả tính toán (cate_2) vào tệp csv. Sau đó, tham gia csv vào tệp gốc của bạn, điền vào một trường, để giữ nguyên kết quả. Bằng cách này, bạn không cập nhật bảng bằng con trỏ DA. Bạn có thể sử dụng con trỏ Tìm kiếm.


Tôi đã suy nghĩ giống như có một cuộc thảo luận ở đây và họ đang nói về những bộ dữ liệu lớn hơn.
Hornbydd

Cảm ơn, klewis. Điều này nghe có vẻ hứa hẹn. Tôi sẽ thử nó cùng với gợi ý của FelixIP và cuộc thảo luận thú vị mặc dù tôi sẽ phải chạy nó vài chục lần.
cptpython

Làm việc rực rỡ! Tôi đã cập nhật câu hỏi với kịch bản mới nhất. Cảm ơn!
cptpython

2

Xin lỗi, nếu tôi tiếp tục hồi sinh chủ đề cũ này. Ý tưởng là thực hiện các câu lệnh if-other trên raster kết hợp và sau đó sử dụng trường mới trong Tra cứu để tạo một raster mới. Tôi đã giải quyết vấn đề bằng cách xuất dữ liệu dưới dạng bảng và đưa ra quy trình làm việc không hiệu quả được giải quyết bởi @Alex Tereshenkov. Sau khi nhận ra điều hiển nhiên, tôi đã gộp dữ liệu thành 17 truy vấn (mỗi truy vấn 1 triệu) như được đề xuất bởi @FelixIP. Trung bình phải mất mỗi đợt trung bình ~ 1,5 phút để hoàn thành và tổng thời gian chạy là ~ 23,3 phút. Phương pháp này giúp loại bỏ nhu cầu tham gia và tôi nghĩ phương pháp này hoàn thành tốt nhất nhiệm vụ. Đây là một kịch bản sửa đổi để tham khảo trong tương lai:

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

Vì vậy, chỉ để đảm bảo rằng tôi hiểu chính xác điều này. Trong bài đăng gốc của bạn, bạn đã nói rằng khi bạn chạy nó trên một máy tính có RAM 40 GB, phải mất tổng cộng ~ 16 giờ. Nhưng bây giờ bạn chia nó thành 17 đợt, và mất tổng cộng ~ 23 phút. Đúng không?
ianbroad

Chính xác. Lần chạy đầu tiên mất ~ 16 giờ với RAM 40 GB và lần chạy thứ hai mất ~ 23 phút + thêm 15 phút để thực hiện Lookupvà xuất raster với các danh mục mới được xác định.
cptpython

Chỉ là một lưu ý arcpy.env.parallelProcessingFactor = "100%"không có ảnh hưởng đến kịch bản của bạn. Tôi không thấy bất kỳ công cụ nào trong đó tận dụng môi trường đó.
KHibma

Bạn hoàn toàn đúng. Tôi sẽ chỉnh sửa mã.
cptpython

1

Bạn có thể thử thay đổi sang sử dụng Tính toánField_man quản lý . Điều này tránh vòng lặp thông qua việc sử dụng các con trỏ và, bằng cách nhìn vào các tùy chọn của bạn cho giá trị danh mục, bạn có thể thiết lập điều này khi bốn quy trình con được sinh ra liên tục. Khi mỗi tiến trình con kết thúc, bộ nhớ của nó được giải phóng trước khi bắt đầu tiến trình tiếp theo. Bạn mất một cú đánh nhỏ (mili giây) để sinh ra mỗi quy trình con.

Hoặc, nếu bạn muốn giữ cách tiếp cận hiện tại của mình, hãy có một quy trình con có các hàng x tại một thời điểm. Có một quy trình chính để điều khiển nó và, như trước đây, bạn tiếp tục ghi nhớ bộ nhớ của mình mỗi khi nó kết thúc. Phần thưởng khi thực hiện theo cách này (đặc biệt là thông qua quy trình python độc lập) là bạn có thể sử dụng nhiều hơn tất cả các lõi của mình khi sinh ra các quy trình con trong đa luồng của python mà bạn có được xung quanh GIL. Điều này là có thể với ArcPy và một cách tiếp cận mà tôi đã sử dụng trong quá khứ để thực hiện các cuộc đảo lộn dữ liệu lớn. Rõ ràng là giữ khối dữ liệu của bạn xuống nếu không bạn sẽ hết bộ nhớ nhanh hơn!


Theo kinh nghiệm của tôi, sử dụng arcpy.da.UpdateC tiền nhanh hơn arcpy.CalculateField_man quản lý. Tôi đã viết một tập lệnh chạy trên 55.000.000 tính năng xây dựng, nó chậm hơn khoảng 5 lần với công cụ Tính toán.
offermann

Vấn đề là thiết lập bốn quy trình con và quét sạch bộ nhớ vì đó là điểm khó khăn thực sự ở đây. Khi tôi phác thảo trong đoạn thứ hai, bạn có thể chia các quy trình con thành các hàng, nhưng điều đó sẽ quản lý nhiều hơn một chút so với một lựa chọn duy nhất.
MappaGnosis

1

Logic thao tác dữ liệu có thể được viết dưới dạng câu lệnh CẬP NHẬT bằng cách sử dụng biểu thức CASE, mà bạn có thể thực thi bằng GDAL / OGR, ví dụ thông qua OSGeo4W gdal-filegdbđược cài đặt.

Đây là quy trình làm việc, sử dụng osgeo.ogrthay vì 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)

Trên một bảng tương tự chỉ với hơn 1 triệu bản ghi, truy vấn này mất 18 phút. Vì vậy, có thể vẫn mất ~ 4 đến 5 giờ để xử lý 16 triệu hồ sơ.


Thật không may, kịch bản là một phần của một kịch bản lớn hơn được viết bằng cách sử dụng arcpynhưng tôi đánh giá cao câu trả lời. Tôi đang dần cố gắng sử dụng GDAL nhiều hơn.
cptpython

1

Bản cập nhật mã trong phần # 2 trong câu hỏi của bạn không cho thấy cách bạn tham gia .csvtệp trở lại bảng gốc trong cơ sở dữ liệu địa lý tệp của bạn. Bạn nói rằng kịch bản của bạn mất ~ 5 phút để chạy. Điều này nghe có vẻ công bằng nếu bạn chỉ xuất .csvtệp mà không thực hiện bất kỳ phép nối nào. Khi bạn cố gắng đưa .csvtệp trở lại ArcGIS, bạn sẽ gặp các vấn đề về hiệu suất.

1) Bạn không thể tham gia trực tiếp từ .csvbảng cơ sở dữ liệu địa lý, vì .csvtệp không có OID (có trường được tính toán với các giá trị duy nhất sẽ không giúp ích vì bạn vẫn sẽ cần chuyển đổi .csvtệp của mình thành bảng cơ sở dữ liệu địa lý). Vì vậy, vài phút cho Table To Tablecông cụ GP (bạn có thể sử dụng in_memorykhông gian làm việc để tạo bảng tạm thời ở đó, sẽ nhanh hơn một chút).

2) Sau khi bạn đã tải .csvvào bảng cơ sở dữ liệu địa lý, bạn sẽ muốn xây dựng một chỉ mục trên trường bạn sẽ thực hiện tham gia (trong trường hợp của bạn, objectidvaue nguồn từ .csvtệp. Điều này sẽ mất vài phút trên bảng hàng 16 triệu.

3) Sau đó, bạn sẽ cần sử dụng các công cụ Add Joinhoặc Join FieldGP. Không phải sẽ thực hiện tốt trên bàn lớn của bạn.

4) Sau đó, bạn cần thực hiện Calculate Fieldcông cụ GP để tính (các) trường mới tham gia. Nhiều phút đi đến đây; thậm chí nhiều hơn, tính toán trường mất nhiều thời gian hơn khi các trường tham gia tính toán đến từ một bảng đã tham gia.

Nói một cách dễ hiểu, bạn sẽ không nhận được bất cứ điều gì gần 5 phút mà bạn đề cập. Nếu bạn sẽ làm nó trong một giờ, tôi sẽ rất ấn tượng.

Để tránh xử lý việc xử lý các bộ dữ liệu lớn trong ArcGIS, tôi khuyên bạn nên đưa dữ liệu của mình bên ngoài ArcGIS vào pandaskhung dữ liệu và thực hiện tất cả các tính toán của bạn ở đó. Khi bạn đã hoàn tất, chỉ cần viết các hàng khung dữ liệu trở lại vào bảng cơ sở dữ liệu địa lý mới với da.InsertCursor(hoặc bạn có thể cắt bớt bảng hiện có của mình và ghi các hàng của bạn vào trong nguồn nguồn).

Mã hoàn chỉnh tôi đã viết để điểm chuẩn dưới đây:

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

Dưới đây là đầu ra từ IO Debug (số được báo cáo là số hàng trong bảng được sử dụng) với thông tin về thời gian thực hiện cho các chức năng riêng lẻ:

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

Chèn một hàng da.InsertCursormất một thời gian không đổi, nghĩa là, nếu chèn 1 hàng mất, giả sử, 0,1 giây, để chèn 100 hàng sẽ mất 10 giây. Đáng buồn thay, 95% + tổng thời gian thực hiện được dành để đọc bảng cơ sở dữ liệu địa lý và sau đó chèn các hàng trở lại cơ sở dữ liệu địa lý.

Điều tương tự cũng được áp dụng để tạo pandaskhung dữ liệu từ trình da.SearchCursortạo và tính toán (các) trường. Khi số lượng hàng trong bảng cơ sở dữ liệu địa lý nguồn của bạn tăng gấp đôi, thì thời gian thực hiện của tập lệnh ở trên cũng tăng theo. Tất nhiên, bạn vẫn cần sử dụng Python 64 bit vì trong quá trình thực thi, một số cấu trúc dữ liệu lớn hơn sẽ được xử lý trong bộ nhớ.


Trên thực tế, tôi sẽ hỏi một câu hỏi khác sẽ nói về những hạn chế của phương pháp tôi đã sử dụng, vì tôi gặp phải những vấn đề bạn đã giải quyết ở trên nên cảm ơn! Những gì tôi đang cố gắng thực hiện: kết hợp bốn trình quét và sau đó thực hiện câu lệnh if-other dựa trên các cột và viết kết quả đầu ra vào một cột mới và cuối cùng thực hiện Lookupđể tạo raster dựa trên các giá trị trong cột mới. Phương pháp của tôi có nhiều bước không cần thiết và quy trình làm việc không hiệu quả, đáng lẽ tôi nên đề cập đến vấn đề này trong câu hỏi ban đầu của mình. Sống và học hỏi. Tôi sẽ thử kịch bản của bạn vào cuối tuần này mặc dù.
cptpython
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.