ArcPy与Pandas互转?NumPy数组咋变?

GIS基础理论
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

引言:很多 ArcGIS Pro 用户在做批量字段计算、属性表清洗或空间分析结果汇总时,都会遇到“ArcPy与Pandas互转?NumPy数组咋变?”这个问题:要把要素类属性读进 Pandas 处理,又要把结果写回 ArcGIS,过程中还绕不开 NumPy 数组。本文用一个清晰流程说明 ArcPy、NumPy、Pandas 三者怎样互转,以及哪些字段类型和空值问题最容易踩坑。

ArcPy与Pandas互转 NumPy数组转换流程
ArcPy、NumPy 数组与 Pandas DataFrame 在属性表处理中的典型转换流程。

背景:为什么 ArcPy 与 Pandas 互转通常要经过 NumPy

ArcPy 本身主要面向 ArcGIS 数据模型,例如要素类、表、字段、游标和地理处理工具。Pandas 主要面向二维表格数据,例如 DataFrame。两者并不是直接互通的接口,因此在很多场景下需要借助 NumPy 结构化数组作为中间层。

在 ArcPy 中,常用的转换函数来自 arcpy.da 模块:

  • arcpy.da.FeatureClassToNumPyArray:把要素类或表的字段读取为 NumPy 结构化数组。
  • arcpy.da.TableToNumPyArray:把普通表读取为 NumPy 结构化数组。
  • arcpy.da.NumPyArrayToTable:把 NumPy 数组写成 ArcGIS 表。
  • arcpy.da.NumPyArrayToFeatureClass:把包含坐标字段的 NumPy 数组写成点要素类。
  • arcpy.da.UpdateCursor:更适合把 Pandas 计算结果按主键更新回原要素类。

所以,实际工作流通常不是“ArcPy 直接变 Pandas”,而是:

ArcGIS 要素类或表 → NumPy 结构化数组 → Pandas DataFrame → 数据清洗或计算 → NumPy 数组或游标 → ArcGIS 表或要素类。

原理:ArcPy、NumPy 数组、Pandas DataFrame 分别负责什么

理解 ArcPy 与 Pandas 互转,关键是分清三种数据结构的职责。

对象 适合做什么 不适合做什么
ArcPy 要素类 / 表 保存 GIS 数据、空间参考、几何、字段类型、地理数据库规则 复杂表格清洗、分组统计、批量数据分析
NumPy 结构化数组 作为 ArcPy 与 Pandas 之间的中间格式,保留字段名和字段类型 直接进行复杂业务清洗时可读性较差
Pandas DataFrame 字段计算、空值处理、分组汇总、连接匹配、数据质量检查 直接保存 ArcGIS 几何、子类型、域、附件、拓扑规则

NumPy 结构化数组和普通二维数组不同。ArcPy 读取出的数组通常带字段名,例如 OBJECTIDNAMEAREA。这类数组转换为 DataFrame 很方便,但写回 ArcGIS 时必须注意字段名、字段类型和空值。

步骤:从 ArcPy 要素类读取为 Pandas DataFrame

下面以 ArcGIS Pro 的 Python 环境为例。假设已有一个面要素类,需要读取 OBJECTIDNAMEPOPAREA 字段到 Pandas 中处理。

import arcpy
import pandas as pd

fc = r"C:GISProjectdata.gdbdistricts"

fields = ["OBJECTID", "NAME", "POP", "AREA"]

arr = arcpy.da.FeatureClassToNumPyArray(
    in_table=fc,
    field_names=fields,
    skip_nulls=False
)

df = pd.DataFrame(arr)

print(df.head())
print(df.dtypes)

这里的关键点有三个:

  • field_names 不建议直接使用全部字段,先选需要处理的字段更安全。
  • skip_nulls=False 可以避免含空值记录被悄悄跳过。
  • pd.DataFrame(arr) 会把 NumPy 结构化数组转换为 Pandas DataFrame。

如果读取的是普通属性表,可以使用 TableToNumPyArray

import arcpy
import pandas as pd

table = r"C:GISProjectdata.gdbstatistics_table"
fields = ["ID", "TYPE", "VALUE"]

arr = arcpy.da.TableToNumPyArray(table, fields, skip_nulls=False)
df = pd.DataFrame(arr)

步骤:在 Pandas 中处理 ArcPy 读取的数据

转换到 Pandas 后,可以做字段计算、类型转换、空值填充和分组统计。例如计算人口密度:

df["POP"] = pd.to_numeric(df["POP"], errors="coerce")
df["AREA"] = pd.to_numeric(df["AREA"], errors="coerce")

df["POP_DENSITY"] = df["POP"] / df["AREA"]

df.loc[df["AREA"].isna() | (df["AREA"] == 0), "POP_DENSITY"] = None

print(df[["OBJECTID", "NAME", "POP_DENSITY"]].head())

在 GIS 项目中,建议始终保留一个稳定主键字段,例如 OBJECTID、业务编码 CODE 或唯一 ID。后续把 Pandas 结果写回 ArcGIS 时,主键用于匹配原始记录。

步骤:把 Pandas DataFrame 转为 NumPy 数组

如果目标是生成一张新的 ArcGIS 表,可以把 DataFrame 转为 NumPy 结构化数组,再使用 NumPyArrayToTable

import numpy as np
import arcpy

out_table = r"C:GISProjectdata.gdbdistrict_density"

out_df = df[["OBJECTID", "NAME", "POP", "AREA", "POP_DENSITY"]].copy()

records = out_df.to_records(index=False)

arcpy.da.NumPyArrayToTable(records, out_table)

这个方法适合“新建结果表”。如果你要更新原要素类字段,不建议直接覆盖原数据,而应该用 UpdateCursor 按主键写回。

步骤:把 Pandas 计算结果写回 ArcGIS 原要素类

如果原要素类中已经有 POP_DENSITY 字段,可以用 arcpy.da.UpdateCursor 更新。如果没有,先创建字段。

import arcpy

fc = r"C:GISProjectdata.gdbdistricts"
target_field = "POP_DENSITY"

field_names = [f.name for f in arcpy.ListFields(fc)]
if target_field not in field_names:
    arcpy.management.AddField(fc, target_field, "DOUBLE")

density_dict = dict(zip(df["OBJECTID"], df["POP_DENSITY"]))

with arcpy.da.UpdateCursor(fc, ["OBJECTID", target_field]) as cursor:
    for oid, old_value in cursor:
        new_value = density_dict.get(oid)
        cursor.updateRow([oid, new_value])

这种方式的好处是:几何、空间参考、字段域、子类型等 GIS 信息仍然保留在原要素类中,只更新你需要更新的字段。

步骤:包含点坐标时如何从 NumPy 生成要素类

如果 Pandas 结果中有 XY 坐标字段,可以先转为 NumPy 数组,再生成点要素类。

import arcpy

df_points = pd.DataFrame({
    "ID": [1, 2, 3],
    "NAME": ["A", "B", "C"],
    "X": [116.39, 121.47, 114.06],
    "Y": [39.90, 31.23, 22.55]
})

arr_points = df_points.to_records(index=False)

out_fc = r"C:GISProjectdata.gdbsample_points"
spatial_ref = arcpy.SpatialReference(4326)

arcpy.da.NumPyArrayToFeatureClass(
    in_array=arr_points,
    out_table=out_fc,
    shape_fields=["X", "Y"],
    spatial_reference=spatial_ref
)

注意:NumPyArrayToFeatureClass 主要适合根据坐标生成点要素。线、面要素通常不建议通过这种方式直接生成,应该使用几何对象、游标或其他地理处理工具。

常见坑:ArcPy 与 Pandas 互转最容易出错的地方

1. skip_nulls=True 导致记录数量变少

FeatureClassToNumPyArrayTableToNumPyArrayskip_nulls 参数如果设置为 True,含空值的记录可能会被跳过。很多人发现 DataFrame 行数比属性表少,就是这个原因。

建议排查:

  • 读取前统计要素类记录数。
  • 读取后检查 DataFrame 行数。
  • 不确定时优先使用 skip_nulls=False

2. 字段类型不匹配导致 NumPyArrayToTable 失败

Pandas 的字段类型比较灵活,但 ArcGIS 字段类型更严格。比如混合了数字和字符串的列,在写回 ArcGIS 时可能出现类型转换问题。

建议在写出前检查:

print(df.dtypes)
print(df.isna().sum())

必要时显式转换字段类型:

df["NAME"] = df["NAME"].astype(str)
df["POP"] = pd.to_numeric(df["POP"], errors="coerce")
df["POP_DENSITY"] = pd.to_numeric(df["POP_DENSITY"], errors="coerce")

3. OBJECTID 不应作为新表的业务主键长期使用

OBJECTID 是 ArcGIS 自动维护的对象 ID。导出、复制、追加、重新生成数据后,OBJECTID 可能变化。如果你要跨数据集长期匹配,建议使用稳定的业务编码,例如行政区代码、地块编号、管线编号。

4. 几何字段不能像普通字段一样随便进 Pandas

ArcPy 可以读取几何的特定表达方式,例如 SHAPE@XSHAPE@YSHAPE@AREASHAPE@LENGTH。但完整几何对象放入 Pandas 后并不等于 GeoPandas 的几何列,不能直接当作空间数据框使用。

fields = ["OBJECTID", "NAME", "SHAPE@AREA", "SHAPE@LENGTH"]

arr = arcpy.da.FeatureClassToNumPyArray(fc, fields, skip_nulls=False)
df = pd.DataFrame(arr)

如果你需要完整的空间 DataFrame 能力,通常应考虑 GeoPandas;如果你在 ArcGIS Pro 环境中处理地理数据库数据,ArcPy 游标和地理处理工具更稳。

5. 中文字段名和过长字段名可能带来兼容问题

文件地理数据库对字段名支持较好,但 Shapefile 对字段名长度和编码限制更多。Pandas 中的列名写回 ArcGIS 表时,如果包含特殊字符、过长字段名或重复字段名,可能失败。

建议使用简单字段名:

  • 使用英文、数字和下划线。
  • 避免字段名以数字开头。
  • 避免空格、括号、斜杠等特殊字符。
  • 写 Shapefile 时尤其注意字段名长度限制。

方法比较:什么时候用 NumPyArrayToTable,什么时候用 UpdateCursor

方法 适用场景 优点 注意点
FeatureClassToNumPyArray + pd.DataFrame 从 ArcGIS 要素类读取属性到 Pandas 读取快,字段选择明确 几何需要用 SHAPE@ 表达式读取
TableToNumPyArray + pd.DataFrame 从 ArcGIS 表读取到 Pandas 适合纯属性表清洗 空值和字段类型要检查
DataFrame.to_records + NumPyArrayToTable 把 Pandas 结果生成新的 ArcGIS 表 流程简单,适合输出统计结果 不适合直接保留原要素类几何和规则
UpdateCursor 把 Pandas 计算结果更新回原要素类 保留原数据结构,只更新目标字段 必须有可靠主键匹配
NumPyArrayToFeatureClass 从 X、Y 坐标生成点要素类 适合点数据快速建图 不适合复杂线面几何

简单判断:如果你要“生成新表”,用 NumPyArrayToTable;如果你要“更新原图层字段”,用 UpdateCursor;如果你要“生成点图层”,用 NumPyArrayToFeatureClass

检查清单:ArcPy 与 Pandas 互转前后要核对什么

  • 是否只读取了必要字段,避免一次性读取过多无关字段。
  • 是否保留了可用于写回的唯一主键。
  • skip_nulls 是否符合预期。
  • DataFrame 行数是否等于原表记录数或符合筛选条件。
  • 数值字段是否已用 pd.to_numeric 检查。
  • 字符串字段是否存在超长值、特殊字符或编码问题。
  • 写回前目标字段是否已经存在,字段类型是否正确。
  • 更新原要素类前是否备份数据。
  • 是否区分了属性字段、几何表达式和完整几何对象。
  • 结果写回后是否抽样检查属性表和地图显示。

FAQ:ArcPy、Pandas、NumPy 数组互转常见问题

ArcPy 可以直接转 Pandas 吗?

通常不是直接转换,而是先通过 FeatureClassToNumPyArrayTableToNumPyArray 得到 NumPy 结构化数组,再用 pd.DataFrame 转为 Pandas DataFrame。这是 ArcPy 与 Pandas 互转最常见、也最稳定的方式。

Pandas DataFrame 怎么变成 ArcGIS 表?

可以使用 df.to_records(index=False) 转为 NumPy 结构化数组,再用 arcpy.da.NumPyArrayToTable 输出为 ArcGIS 表。写出前要检查字段名、字段类型和空值。

如何把 Pandas 计算结果写回原要素类?

推荐使用 UpdateCursor。先在 Pandas 中用主键构建字典,再用游标遍历原要素类,根据主键更新目标字段。这样可以保留原要素类的几何、空间参考、域和其他 GIS 信息。

NumPyArrayToFeatureClass 能生成面要素吗?

它更适合根据 X、Y 坐标字段生成点要素类。对于线和面,通常需要使用 ArcPy 几何对象、插入游标或专门的地理处理工具,不建议简单依赖表格坐标字段转换。

为什么转换后 DataFrame 行数少了?

最常见原因是 skip_nulls=True 跳过了含空值的记录。另一个原因是你在读取前设置了筛选条件或图层选择集。建议先检查 ArcGIS 中的记录数,再检查 NumPy 数组和 DataFrame 的行数。

ArcPy 读取几何面积时,结果单位是什么?

SHAPE@AREA 的单位通常与数据坐标系有关。投影坐标系下常见为平方投影单位,地理坐标系下直接使用面积结果容易产生误解。需要精确面积时,应先投影到合适的投影坐标系,再计算面积。

结论:把 NumPy 当桥梁,把 Pandas 当清洗工具,把 ArcPy 当 GIS 写回工具

处理“ArcPy与Pandas互转?NumPy数组咋变?”时,不要把三者混成一个工具。更稳的思路是:ArcPy 负责读取和写回 GIS 数据,NumPy 负责在两者之间承接字段结构,Pandas 负责表格清洗、统计和计算。

实际项目中,最推荐的流程是:用 FeatureClassToNumPyArray 读取必要字段,用 Pandas 完成计算,再通过 UpdateCursor 按主键写回原要素类。如果只是输出统计结果表,再使用 NumPyArrayToTable。这样既能利用 Pandas 的数据处理效率,也能避免破坏 ArcGIS 数据的空间结构和字段规则。