Pandas 踩坑指南(FAQ):内存统计、真值判断、NA 表示与整数溢出的权威避坑手册 📅 发布时间:2026/9/19 5:59:51 👁 浏览次数: Pandas 踩坑指南FAQ内存统计、真值判断、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 官方用户手册的 Frequently Asked Questions 章节源文件位于 doc/source/user_guide/gotchas.rst整理而成系统梳理了使用 pandas 时最容易踩到的十余类经典陷阱涵盖DataFrame内存统计、if/真值判断、UDF 变异、缺失值NA表示、整数溢出、NumPy 兼容参数、线程安全、字节序、嵌套列表存储等主题。读完本文你将掌握每个陷阱的成因、复现样例、底层实现原理以及可直接落地的解决方案。一、DataFrame 内存占用统计1.1 通过info()查看整体内存占用调用DataFrame.info()时会显示DataFrame包含索引的内存占用情况。是否显示该信息由一个配置选项display.memory_usage控制完整选项列表参见 doc/source/user_guide/options.rst 中的可用选项章节。以下面的DataFrame为例调用info()即可看到内存占用import numpy as np import pandas as pd dtypes [ int64, float64, datetime64[ns], timedelta64[ns], complex128, object, bool, ] n 5000 data {t: np.random.randint(100, sizen).astype(t) for t in dtypes} df pd.DataFrame(data) df[categorical] df[object].astype(category) df.info()输出结果中memory usage: 312.0 KB行尾的号表示真实内存占用可能更高——因为 pandas 默认不统计dtypeobject列中每个 Python 对象实际占用的内存。传入memory_usagedeep可以开启更精确的内存报告它会完整核算所包含对象的内存开销。这一深度自省选项是可选的因为深层内省可能比较昂贵耗时耗资源df.info(memory_usagedeep)默认情况下该显示选项为True但也可以在调用info()时显式传入memory_usage参数覆盖它。从源码看DataFrame.info()的完整签名定义在 pandas/core/frame.py其memory_usage参数接受bool | str | NoneTrue总是显示、False从不显示、deep等价于True 加深度内省。文档注释特别说明非深度内省时内存是基于列 dtype 与行数估算的假设对应 dtype 的每个值消耗相同内存而深度内省则执行真实内存计算、代价是消耗计算资源。底层的格式化逻辑位于 pandas/io/formats/info.py如memory_usage_bytes、memory_usage_string等实现最终以 base-2 人类可读单位1KB 1024 字节呈现。1.2 通过memory_usage()查看每列内存每列的内存占用可以通过DataFrame.memory_usage()方法获取。它返回一个Series索引为列名、值为各列内存字节数df.memory_usage() # 整个 DataFrame 的总内存占用 df.memory_usage().sum()默认情况下返回的Series中包含索引的内存占用作为第一项。传入indexFalse可以将其抑制df.memory_usage(indexFalse)DataFrame.info()显示的内存占用正是通过memory_usage()方法计算得出的其实现见 pandas/core/frame.pyindex参数控制是否包含索引贡献、deepTrue时深度内省objectdtype 以统计系统级内存消耗。在 pandas/io/formats/info.py 中可以看到 info 输出正是通过self.data.memory_usage(indexTrue, deepdeep).sum()来汇总总内存。配置项display.memory_usage在 pandas/core/config_init.py 中定义合法值为True、False或字符串deep。与之配合的还有display.max_info_columns默认 100与display.max_info_rows等选项它们共同决定info()输出的详细程度。相关主题类别型Categorical数据的内存优化参见 doc/source/user_guide/categorical.rst 中的 Categorical Memory Usage 章节。二、在 if/真值语句中使用 pandaspandas 遵循 NumPy 的约定当尝试把对象转换为bool时会抛出异常。这在if语句或使用布尔运算符and、or、not时都会发生。原因在于下面的代码结果存在歧义if pd.Series([False, True, False]): pass它应该因为长度非零而为True还是因为含有False值而为False结果不明确因此 pandas 直接抛出ValueErrorif pd.Series([False, True, False]): print(I was true) # ValueError: The truth value of a Series is ambiguous. # Use a.empty, a.bool(), a.item(), a.any() or a.all().你需要显式声明对DataFrame/Series的判定意图例如使用any()、all()或empty。如果只是想判断 pandas 对象是否为None可以这样写if pd.Series([False, True, False]) is not None: print(I was not None)下面展示如何检查是否存在任意一个值为Trueif pd.Series([False, True, False]).any(): print(I am any)2.1 位运算布尔比较位运算布尔操作符如、!与标量比较时返回逐元素比较的布尔Seriess pd.Series(range(5)) s 4 # 0 False # 1 False # 2 False # 3 False # 4 True # dtype: bool更多布尔比较示例参见 doc/source/user_guide/basics.rst 的布尔比较章节。2.2 关于in运算符的陷阱在Series上使用 Python 的in运算符测试的是索引成员资格而非值的成员资格s pd.Series(range(5), indexlist(abcde)) 2 in s # False2 不在索引中 b in s # Trueb 在索引中如果觉得惊讶请记住Python 字典上的in测试的是键而不是值而Series是类字典dict-like结构。要测试值成员资格请使用Series.isins.isin([2]) # a False # b False # c True # d False # e False # dtype: bool s.isin([2]).any() # True对于DataFramein同样作用于列轴即测试列名的成员资格。三、UDF 方法中的变异陷阱Mutating with UDF本节适用于所有接受用户定义函数UDF的 pandas 方法特别是DataFrame.apply、DataFrame.aggregate、DataFrame.transform和DataFrame.filter。编程中有一条通用规则不要在迭代容器时修改变异它。变异会使迭代器失效引发意外行为。看下面的例子values [0, 1, 2, 3, 4, 5] n_removed 0 for k, value in enumerate(values): idx k - n_removed if value % 2 1: del values[idx] n_removed 1 else: values[idx] value 1 values # [1, 1, 3, 3, 5, 5]人们通常会预期结果是[1, 3, 5]但实际结果完全不是这样。当使用接受 UDF 的 pandas 方法时pandas 内部往往正在迭代DataFrame或其他 pandas 对象。因此如果 UDF 变异了DataFrame就会出现类似意外行为。下面是一个DataFrame.apply的类似示例会抛出异常def f(s): s.pop(a) return s df pd.DataFrame({a: [1, 2, 3], b: [4, 5, 6]}) df.apply(f, axiscolumns) # ValueError: cannot reindex on an axis with duplicate labels解决办法是先做一份拷贝使变异不作用于正在被迭代的容器上values [0, 1, 2, 3, 4, 5] n_removed 0 for k, value in enumerate(values.copy()): # 注意 values.copy() idx k - n_removed if value % 2 1: del values[idx] n_removed 1 else: values[idx] value 1 values # [1, 3, 5]pandas 侧的对应写法def f(s): s s.copy() # 在函数内部拷贝避免影响被迭代的容器 s.pop(a) return s df pd.DataFrame({a: [1, 2, 3], b: [4, 5, 6]}) df.apply(f, axiscolumns)四、NumPy 类型的缺失值NA表示4.1 为什么选np.nan作为 NA 表示由于 NumPy 和 Python 底层缺乏对NA缺失值的原生支持NA本可以有如下两种实现方案掩码数组masked array方案一个数据数组外加一个布尔数组指示每个值是否存在哨兵值方案使用特殊哨兵值、位模式或哨兵值集合来在各类 dtype 中标记NA。pandas 最终为 NumPy 类型选定了特殊值np.nanNot-A-Number作为NA值并提供了DataFrame.isna、DataFrame.notna等 API 函数用于跨 dtype 检测 NA 值。这个选择的代价是含缺失值的整数数据会被强制转成 float 类型详见 doc/source/user_guide/missing_data.rst 与下文 4.3 节。Series.isna、Series.notna的实现位于 pandas/core/series.py。4.2 NA 引入时的类型提升Type Promotion当通过reindex或其他手段向既有Series/DataFrame引入 NA 时为了存储 NAboolean 和 integer 类型会被提升为不同 dtype。提升规则汇总如下表类型类别Typeclass存储 NA 时的提升 dtypePromotion dtype for storing NAsfloating浮点不变object不变integer整数转为float64boolean布尔转为object4.3 对整数 NA 的支持由于 NumPy 底层缺少高性能的NA支持最直接的牺牲品就是无法在整数数组中表示 NA。例如s pd.Series([1, 2, 3, 4, 5], indexlist(abcde)) s s.dtype # dtype(int64) s2 s.reindex([a, b, c, f, u]) s2 # a 1.0 # b 2.0 # c 3.0 # f NaN # u NaN # dtype: float64 s2.dtype # dtype(float64)这一取舍主要出于内存与性能的考量同时也能保证结果Series仍然是数值型。如果你需要表示可能缺失的整数请使用 pandas 或 pyarrow 提供的可空整数扩展 dtypeInt8DtypeInt16DtypeInt32DtypeInt64DtypeArrowDtypes_int pd.Series([1, 2, 3, 4, 5], indexlist(abcde), dtypepd.Int64Dtype()) s_int s_int.dtype # Int64Dtype s2_int s_int.reindex([a, b, c, f, u]) s2_int # a 1 # b 2 # c 3 # f NA # u NA # dtype: Int64 s2_int.dtype # Int64Dtype # 使用 pyarrow 后端 s_int_pa pd.Series([1, 2, None], dtypeint64[pyarrow]) s_int_pa更多细节参见 doc/source/user_guide/integer_na.rst 与 doc/source/user_guide/pyarrow.rst。4.4 为什么不让 NumPy 像 R 那样实现 NA很多人建议 NumPy 直接模仿领域特定的统计编程语言 R 的NA支持。原因之一与 NumPy 的类型层次结构NumPy type hierarchy有关R 语言只有屈指可数的内建数据类型——integer、numeric浮点、character和boolean其NA类型通过为每种类型预留特殊位模式来标记缺失值。虽然这套做法套用到完整的 NumPy 类型层次上并非不可能但对 8 位和 16 位的小数据类型尤其会构成更实质性的权衡实现工程量也更大。不过现在通过使用掩码 NumPy 类型如Int64Dtype或 PyArrow 类型ArrowDtype已经可以享受 R 式的NA语义了。五、整数溢出Integer Overflowpandas 使用 NumPy 的定宽整数类型默认int64而不是 Python 的任意精度整数。这意味着整数算术可能静默溢出而不会抛出任何错误series pd.Series([406, 372, 496, 41, 63, 118, 311, 271, 431, 95, 57, 52]) series.dtype # dtype(int64)这些整数的乘积超出了int64的表示范围结果会静默回绕wrap aroundseries.prod() # -1420135685546113024 错误的回绕结果对比纯 Python 的实现它使用任意精度整数并给出正确结果python_product 1 for val in [406, 372, 496, 41, 63, 118, 311, 271, 431, 95, 57, 52]: python_product * val python_product # 215281427888069468007711702040934928749674218326070040294575854880这一行为源自 NumPy会影响所有整数算术运算加法、减法、乘法等而不只是Series.prod。NumPy 文档中关于溢出错误的章节对此有专门论述。如果你的计算可能超出int64范围可以先把数据转成float64对超大值会损失精度或者通过objectdtype 使用 Python 内建整数series.astype(float64).prod() series.astype(object).prod()六、NumPy 兼容关键字参数Compatibility kwargs许多 pandas 方法会接受额外的关键字参数通常以**kwargs形式传入以保证与 NumPy API 兼容。当 pandas 对象被传给 NumPy 函数例如np.sum(df)时NumPy 常常会把自己的参数如out、keepdims传给 pandas 方法。为了避免报错pandas 接受这些以默认值传入的关键字。历史上 pandas 曾大量忽略这些附加参数但现在会严格校验它们即确保只能以默认值传入或在不需要它们做 NumPy 兼容时将其弃用。如果你传入一个 pandas 未实现的参数将触发错误或弃用警告。因此你只应依赖 pandas 方法签名中明确文档化的参数。从源码可以看到相关机制在 pandas/core/arraylike.py 中当 ufunc 的nout 1或调用带out关键字时pandas 会走dispatch_ufunc_with_out分支pandas/core/arraylike.py即先不带out计算、再把结果写入给定的out对象并通过where掩码进行赋值——这正是为了兼容 NumPy 调用约定而设计的处理路径。七、与 NumPy 的统计差异对于Series和DataFrame对象var()按N-1归一化以产生总体方差的无偏估计而 NumPy 的numpy.var按N归一化衡量样本方差。注意DataFrame.cov()在 pandas 和 NumPy 中都是按N-1归一化的。这意味着对同一组数据pd.Series([...]).var()与np.var([...])的计算结果会存在差异需要根据总体还是样本的语义选择合适的 API。八、线程安全Thread-safetypandas并非 100% 线程安全。已知问题与DataFrame.copy()方法有关。如果你在多个线程之间共享DataFrame并频繁拷贝建议在发生数据拷贝的线程内持有锁locks。这一限制意味着在多线程场景如并发读取共享 DataFrame 并各自复制做处理下必须显式引入同步机制例如threading.Lock不能依赖 pandas 内部的隐式保护。九、字节序问题Byte-ordering issues有时你需要处理在字节序与当前 Python 运行机器不同的机器上创建的数据。该问题的一个常见症状是类似下面的错误Traceback ... ValueError: Big-endian buffer not supported on little-endian compiler解决办法是在把底层 NumPy 数组传给Series或DataFrame构造器之前先将其转换为系统的原生字节序示例如下x np.array(list(range(10)), i4) # big endian大端 newx x.byteswap().view(x.dtype.newbyteorder()) # 强制转为原生字节序 s pd.Series(newx)更多细节参见 NumPy 文档中关于字节交换byteswapping的章节。要点是跨平台/跨机器传输二进制数据时务必先统一字节序再交给 pandas 构造器。十、在 DataFrame/Series 中存储列表可以在DataFrame或Series的单元格中存储 Python 列表或其他集合但最好避免这样做。原因如下包含列表的列其 dtype 是object每个单元格只持有指向独立 Python 对象的引用因此该列比原生 dtype 列占用多得多的内存对该列的任何操作都无法使用 pandas 的向量化代码路径由于列表不可哈希not hashable这样的列不能用作分组group或合并merge键需要哈希值的方法如DataFrame.drop_duplicates会抛出TypeError。df pd.DataFrame( { name: [A.J. Price] * 3, opponent: [76ers, blazers, bobcats], } ) df[nearest_neighbors] [[Zach LaVine, Jeremy Lin, Nate Robinson]] * 3 df df.dtypes # name object # opponent object # nearest_neighbors object # dtype: object正确的做法是改用**长格式long format**每个列表元素占一行。已有的列表值列可以用DataFrame.explode转换exploded df.explode(nearest_neighbors) exploded # name opponent nearest_neighbors # 0 A.J. Price 76ers Zach LaVine # 0 A.J. Price blazers Jeremy Lin # 0 A.J. Price bobcats Nate Robinson exploded.dtypes # name object # opponent object # nearest_neighbors object # dtype: object现在每个元素在原生类型列中各占一行常规的向量化操作、分组和连接都可以正常使用。如果计算结束后需要嵌套展示可以用agg(list)重建列表exploded.groupby([name, opponent], sortFalse)[nearest_neighbors].agg(list)对于分隔符字符串列与其创建列表作为中间步骤不如直接用.str方法拆分成多列或哑变量indicator variablesser pd.Series([a|b, b, a|c]) ser.str.split(|, expandTrue) # 0 1 # 0 a b # 1 b NaN # 2 a c ser.str.get_dummies(sep|) # a b c # 0 1 1 0 # 1 0 1 0 # 2 1 0 1小结pandas 十大陷阱速查陷阱症状解决方案内存统计不完整info()中 object 列内存带用memory_usagedeep深度统计if Series/DataFrameValueError: truth value is ambiguous显式使用.any()/.all()/.emptyin运算符判断值与直觉不符Series用isin()DataFrame测列名UDF 中变异容器apply等结果异常或报错先.copy()再修改整数列含 NA自动变float64用Int64Dtype等可空扩展 dtype整数溢出结果静默回绕、数值错误astype(float64)或astype(object)NumPy 兼容参数未知参数报错/弃用警告只传方法签名中明确文档化的参数方差口径var()与np.var()结果不同按总体/样本语义选择 API线程共享拷贝拷贝结果不确定在拷贝处显式加锁字节序不符Big-endian buffer not supported构造前byteswap().view(newbyteorder())单元格存列表内存巨大、无法分组/去重用explode()转长格式这些陷阱中的大部分都可以在本仓库源码中找到直接的实现依据内存统计与info()输出逻辑见 pandas/core/frame.py 与 pandas/io/formats/info.py配置项见 pandas/core/config_init.pyNumPy 兼容 ufunc 分发见 pandas/core/arraylike.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),仅供参考