pandas Nullable Integer 数据类型完整指南:用 Int64 与 pandas.NA 告别“整数变浮点“的精度陷阱
2026/9/19 22:09:54 网站建设 项目流程

pandas Nullable Integer 数据类型完整指南:用 Int64 与 pandas.NA 告别"整数变浮点"的精度陷阱

【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas

本指南基于 pandas 官方用户手册(integer_na.rst)编写,系统讲解 pandas 的可空整数(nullable integer)扩展类型:为什么整数列一旦含缺失值就会被迫变成浮点数、IntegerArray如何在保持整数语义的同时使用pandas.NA表示缺失,以及从构造、运算到归约的全套实战用法。读完本文,你将掌握pd.array([...], dtype="Int64")的正确打开方式,理解pandas.arraySeries在 dtype 推断上的差异,并能安全地在含缺失值的整数列上执行算术、比较、分组与求和操作而不丢失精度。

为什么需要"可空整数":NaN 把整数变成了浮点

在 pandas 的 缺失数据处理 章节中可以看到,pandas 主要使用NaN表示缺失值。由于NaN在 NumPy 中是浮点数,任何含缺失值的整数数组都会被强制提升为浮点类型。对大多数场景这无伤大雅,但如果你的整数列是标识符(identifier)——例如用户 ID、订单号、外键——情况就变得棘手:

  1. 语义混乱:将 ID 从整数转成浮点,类型信息被破坏,序列化、对接数据库或下游校验时容易出错;
  2. 精度丢失:部分大整数无法被浮点数精确表示(IEEE 754 双精度浮点只有 52 位尾数,超过2^53的整数会丢失精度),例如9007199254740993这样的 ID 在 float64 中会被舍入。

pandas 给出的答案是可空整数扩展类型arrays.IntegerArray使用pandas.NA作为缺失值(而不是NaN),让缺失值不再"污染"整数 dtype。

构造 IntegerArray:pd.array + 大写的 "Int64"

IntegerArray是 pandas 内部实现的一种扩展类型,可以通过pd.array配合整数 dtype 构造。注意官方强烈推荐显式指定 dtype,避免依赖推断规则。

import pandas as pd import numpy as np # 方式一:显式 dtype 对象 arr = pd.array([1, 2, None], dtype=pd.Int64Dtype()) arr # <IntegerArray> # [1, 2, <NA>] # Length: 3, dtype: Int64

字符串别名:"Int64"(大写 I)与 NumPy 的 'int64' 不同

"Int64"(注意大写的I)是字符串别名,用于和 NumPy 的'int64'dtype 区分:

pd.array([1, 2, np.nan], dtype="Int64") # <IntegerArray> # [1, 2, <NA>] # Length: 3, dtype: Int64

所有 NA 类值统一归一化为 pandas.NA

构造时,各种缺失值记号(np.nanNonepd.NA)都会被统一替换为pandas.NA

pd.array([1, 2, np.nan, None, pd.NA], dtype="Int64") # <IntegerArray> # [1, 2, <NA>, <NA>, <NA>] # Length: 5, dtype: Int64

存入 Series / DataFrame

构造好的数组可以像普通 NumPy 数组一样存入Series

pd.Series(arr) # 0 1 # 1 2 # 2 <NA> # dtype: Int64

也可以直接把列表对象连同 dtype 一起传给Series构造器:

pd.Series([1, 2, None], dtype="Int64")

从源码看,pd.array([1, None])会走_coerce_to_data_and_mask的推断路径(numeric.py),默认采用np.int64作为底层存储 dtype(对应IntegerDtype._default_np_dtype,见 integer.py)。

⚠️ 关键陷阱:pandas.array 与 Series 的 dtype 推断规则不同

官方文档明确警告:当前pandas.arraypandas.Series使用不同的 dtype 推断规则

# pandas.array 会推断出可空整数 dtype pd.array([1, None]) # <IntegerArray> # [1, <NA>] # Length: 2, dtype: Int64 pd.array([1, 2]) # <IntegerArray> # [1, 2] # Length: 2, dtype: Int64
# 出于向后兼容,Series 将其推断为普通整数或浮点 dtype pd.Series([1, None]) # 0 1.0 # 1 NaN # dtype: float64 pd.Series([1, 2]) # 0 1 # 1 2 # dtype: int64

看到差异了吗?pd.Series([1, None])得到的是float64(含有NaN的浮点列),而不是可空整数。为避免混淆,官方建议始终显式提供 dtype

pd.array([1, None], dtype="Int64") pd.Series([1, None], dtype="Int64")

文档同时说明:未来可能会为Series提供推断可空整数 dtype 的选项。

预占位列的最佳实践:用 pd.Series(pd.NA, dtype="Int64") 而非直接赋 pd.NA

如果先创建一个全NA的新列、之后再用真实数据填充(例如df['new_col'] = pd.NA),该列的 dtype 会被设为object,后续性能明显劣于合适的类型。更优做法是:

df = pd.DataFrame() df['objects'] = pd.Series(pd.NA, dtype="Int64") df.dtypes # objects Int64 # dtype: object

(或其他支持NA的 dtype,如"Float64""string")。注意直接df['new_col'] = pd.NA会得到objectdtype:

df = pd.DataFrame() df['objects'] = pd.NA df.dtypes # objects object # dtype: object

底层实现:两个 NumPy 数组(data + mask)

要理解可空整数的行为,值得看一眼它的内部表示。IntegerArray继承自NumericArray(numeric.py),后者又继承自BaseMaskedArray(masked.py)。其 docstring 明确描述了内部结构(integer.py):

  • data:一个 dtype 合适的 NumPy 整数数组,存放实际数值;
  • mask:一个布尔数组,标记缺失位置(True表示缺失)。

也就是说,缺失信息与数值本身分离存储,数值部分始终保持整数 dtype,因此无需像传统方案那样把整数提升成浮点。这正好解释了为什么IntegerArray能"鱼与熊掌兼得":既有原生的整数表示,又能表达缺失。

IntegerArray的构造参数为:

参数类型说明
valuesnumpy.ndarray1 维整数 dtype 数组
masknumpy.ndarray1 维布尔 dtype 数组,标记缺失位置
copybool, default False是否复制valuesmask

IntegerDtype在底层存储上使用_internal_fill_value = 1填充掩码位置(避免向上转型),并以np.int64作为默认存储 dtype。构造时若掩码存在,values[mask]处会被填充该值(见 numeric.py)。

可用的 dtype 全家桶

从 integer.py 可以看到,IntegerDtype派生出一系列注册好的具体 dtype,覆盖有符号与无符号整数:

  • 有符号:Int8DtypeInt16DtypeInt32DtypeInt64Dtype,对应"Int8"~"Int64"
  • 无符号:UInt8DtypeUInt16DtypeUInt32DtypeUInt64Dtype,对应"UInt8"~"UInt64"

它们之间的映射定义在模块末尾的NUMPY_INT_TO_DTYPE字典中(integer.py),例如np.dtype(np.int8)对应Int8Dtype()。构造示例:

pd.array([1, None, 3], dtype=pd.Int32Dtype()) pd.array([1, None, 3], dtype="UInt16")

运算行为:向 NumPy 语义看齐,缺失值自动传播

涉及可空整数数组的运算与 NumPy 数组行为类似:缺失值会传播,需要时数据会强制转换为其他 dtype

s = pd.Series([1, 2, None], dtype="Int64") # 算术:缺失值传播 s + 1 # 0 2 # 1 3 # 2 <NA> # dtype: Int64 # 比较:缺失值传播,结果中缺失仍为 <NA> s == 1 # 0 True # 1 False # 2 <NA> # dtype: boolean # 切片操作 s.iloc[1:3] # 1 2 # 2 <NA> # dtype: Int64 # 与其他 dtype 运算:自动对齐类型,结果按需提升 s + s.iloc[1:3].astype("Int8") # 0 <NA> # 1 4 # 2 <NA> # dtype: Int64 # 需要时强制转换:加上浮点后结果变为可空浮点 s + 0.01 # 0 1.01 # 1 2.01 # 2 <NA> # dtype: Float64

注意s + s.iloc[1:3]的结果在索引 0 处为<NA>,这展示了可空整数在对齐运算(类似 join 的索引对齐)时缺失值的传播语义。

在 DataFrame 中协同工作

这些 dtype 可以作为DataFrame的列类型参与运算:

df = pd.DataFrame({"A": s, "B": [1, 1, 3], "C": list("aab")}) df # A B C # 0 1 1 a # 1 2 1 a # 2 <NA> 3 b df.dtypes # A Int64 # B int64 # C object # dtype: object

df中混合了可空整数列A、普通整数列B和对象列C,各列保持各自的 dtype。

合并、重塑与类型转换

# concat 合并后各列 dtype 保持 pd.concat([df[["A"]], df[["B", "C"]]], axis=1).dtypes # A Int64 # B int64 # C object # dtype: object # 可空整数可转回普通浮点 df["A"].astype(float) # 0 1.0 # 1 2.0 # 2 NaN # dtype: float64

astype(float)时,<NA>会被转换为NaN,这正是"有缺失的整数被迫变浮点"的经典场景——当你确实需要浮点结果时,可以显式完成这一转换。

归约与 groupby 操作

sum等归约操作以及groupby聚合同样开箱即用:

df.sum(numeric_only=True) # A 3.0 # B 5.0 # dtype: float64 df.sum() # A 3 # B 5 # C aab # dtype: object df.groupby("B").A.sum() # B # 1 3 # 3 0 # Name: A, dtype: Int64

注意三处细节:

  1. df.sum(numeric_only=True)的结果是float64A列的和从Int64转为浮点);
  2. 全列df.sum()C列按字符串拼接;
  3. df.groupby("B").A.sum()的返回列保持Int64dtype——分组求和后的结果仍然是可空整数,缺失值语义得以保留。

标量缺失值:pandas.NA

IntegerArray使用pandas.NA作为标量缺失值。对单个缺失元素做切片会返回pandas.NA

a = pd.array([1, None], dtype="Int64") a[1] # <NA>

这一点与np.nan有本质区别:pd.NA是 pandas 统一的缺失值标记,属于pandas自己的 NA 语义体系(pandas.NA实例),其参与运算时遵循"缺失传播 + 结果类型提升"的规则,不会像np.nan那样把整数 dtype 直接拉成浮点。

小结与推荐用法

场景推荐写法
创建可空整数数组pd.array([1, 2, None], dtype="Int64")
创建含缺失的整数 Seriespd.Series([1, 2, None], dtype="Int64")
给 DataFrame 预填 NA 列df['col'] = pd.Series(pd.NA, dtype="Int64")
无符号变体"UInt8"~"UInt64"
转回浮点s.astype(float)<NA>NaN

核心结论:凡是对"整数 ID 不得变浮点"有硬性要求的场景(标识符列、精确大整数、下游强类型约束),都应使用可空整数 dtype;对普通分析场景,float64+NaN仍是轻量默认选择。构造时始终显式传入 dtype,是规避pandas.arraySeries推断差异的最稳妥做法。想深入了解扩展类型的通用机制,可继续阅读 扩展类型开发指南 与 缺失数据处理;想看源码级实现,可直接研读 pandas/core/arrays/integer.py 及其基类 pandas/core/arrays/numeric.py、pandas/core/arrays/masked.py。

【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询