使用ArcPy遍历1600万条记录?


13

我有一个包含8列和约1,670万条记录的表。我需要在列上运行一组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

我在具有40 gb RAM的计算机上运行了相同的脚本(原始计算机只有12 gb RAM)。约16小时后,它成功完成。我觉得16小时太长了,但是我从来没有使用过如此大的数据集,所以我不知道会发生什么。此脚本唯一的新增功能是arcpy.env.parallelProcessingFactor = "100%"。我正在尝试两种建议的方法(1)批量处理100万条记录,(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位(“后台”或“服务器”或“专业版”)?
KHibma '16

忘了提。我在后台运行10.4 x64。
cptpython

恶魔拥护者-您是否尝试过在前台或从IDLE中运行它,因为不需要打开ArcMap即可查看脚本?
Hornbydd

作为独立脚本运行它,或者如果您知道SQL,则将shapefile上传到PostgreSQL并在其中执行
ziggy

1
我了解它是开源的,但是批准过程大约需要1-2周,而且这是时间敏感的,因此我认为在这种情况下不可行。
cptpython

Answers:


4

您可以将Objectid和计算结果(cate_2)写入csv文件。然后将csv加入到您的原始文件中,填充一个字段,以保留结果。这样,您就不会使用DA游标更新表。您可以使用搜索光标。


我在想与这里的讨论相同的事情,他们在谈论更大的数据集。
Hornbydd

谢谢,克莱维斯。这听起来很有希望。我将与FelixIP的建议一起进行尝试,并进行有趣的讨论,尽管我将不得不运行数十次。
cptpython

工作出色!我已经用最新的脚本更新了问题。谢谢!
cptpython '16

2

抱歉,如果我继续恢复这个旧线程。这个想法是在合并栅格上执行if-else语句,然后使用Lookup中的new字段创建一个新栅格。我通过将数据导出为表格并引入了@Alex Tereshenkov解决的效率低下的工作流程,使问题变得更加复杂。认识到明显的问题后,按照@FelixIP的建议,我将数据分为17个查询(每个100万个)。每个批次平均需要约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 '16

正确。第一次运行耗时约16个小时,具有40 GB RAM,第二次运行耗时约23分钟,再花费约15分钟来执行Lookup和导出具有新定义类别的栅格。
cptpython

只是arcpy.env.parallelProcessingFactor = "100%"对您的脚本没有影响的注释。我看不到那里有任何可以利用该环境的工具。
KHibma '16

没错 我将编辑代码。
cptpython

1

您可以尝试更改为使用CalculateField_management。这样可以避免使用游标进行循环,并且通过选择类别值的外观,可以将其设置为按顺序产生的四个子流程。随着每个子进程的完成,在开始下一个进程之前,将释放其内存。您需要花费很小的时间(毫秒)才能生成每个子流程。

或者,如果您想保留当前的方法,则有一个子过程一次占用x行。有一个主要的过程来驱动它,并且像以前一样,每次完成时,您都会不断地寻找内存。这样做的好处(尤其是通过独立的python进程)是,您可以在python的多线程中产生GIL时更多地利用所有内核作为生成子进程。使用ArcPy和我过去使用过的进行大量数据搅动的方法是可以做到的。显然,请减少数据量,否则您将更快地耗尽内存!


以我的经验,使用arcpy.da.UpdateCursor比arcpy.CalculateField_management更快。我编写了一个脚本,该脚本可以运行于55.000.000的构建特征上,而使用CalculateField工具时,它的速度要慢5倍左右。
offermann

关键是要设置四个子进程并清除内存,因为这才是真正的瓶颈。正如我在第二段中概述的那样,您可以按行划分子流程,但这比单选需要更多的管理。
MappaGnosis

1

可以使用CASE表达式将数据操作逻辑编写为UPDATE SQL语句,您可以使用GDAL / OGR(例如,通过已gdal-filegdb安装的OSGeo4W)执行该表达式。

这是工作流程,它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)您不能直接从.csv地理数据库表中进行联接,因为该.csv文件没有OID(具有使用唯一值计算的字段将无济于事,因为您仍然需要将.csv文件转换为地理数据库表)。因此,Table To Table使用GP工具需要几分钟(您可以使用in_memory工作区在此处创建临时表,速度会稍快)。

2)将加载.csv到地理数据库表后,您需要在要进行objectid联接的字段上建立索引(在这种情况下,是.csv文件的源值。这在16百万行表上需要花费几分钟。

3)然后,您需要使用Add JoinJoin FieldGP工具。两者在您的大桌子上都不会表现良好。

4)之后,您需要执行Calculate FieldGP工具来计算新加入的字段。几分钟去这里;当参与计算的字段来自联接表时,字段计算将花费更多时间。

一言以蔽之,您提到的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

以下是Debug 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%以上花费在读取地理数据库表,然后将行插入回地理数据库中。

这同样适用于pandasda.SearchCursor生成器制作数据帧和计算字段。当源地理数据库表中的行数增加一倍时,上述脚本的执行时间也会增加。当然,您仍然需要使用64位Python,因为在执行过程中,一些较大的数据结构将在内存中处理。


实际上,我要问的另一个问题是我使用的方法的局限性,因为我遇到了您上面已经解决的问题,非常感谢!我要完成的工作是:合并四个栅格,然后基于这些列执行if-else语句,然后将输出写到新列中,最后执行Lookup基于新列中的值创建栅格的操作。我的方法有许多不必要的步骤,工作流程效率低下,我应该在我最初的问题中提到这一点。活到老,学到老。我将在本周晚些时候尝试您的脚本。
cptpython
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.