ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

pandas 缺失值哨兵体系全解析:NA 与 NaT 的语义、传播规则与实战操作指南

pandas 缺失值哨兵体系全解析:NA 与 NaT 的语义、传播规则与实战操作指南 pandas 缺失值哨兵体系全解析NA 与 NaT 的语义、传播规则与实战操作指南【免费下载链接】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 并不只用一种方式表示缺失值不同数据类型拥有各自的哨兵sentinel——np.nan、NaT、NA与None各司其职。本文以 doc/source/reference/missing_value.rst 定义的 NA 与 NaT 两条主线为核心结合 doc/source/user_guide/missing_data.rst 的完整实操指南深入讲解这些哨兵的适用场景、NA的三值逻辑传播语义、isna/notna检测方法以及插入、转换、计算、填充与插值的完整处理链路。读完本文你将能准确判断数据中缺失值的类型、理解NA与np.nan在运算中的根本差异并熟练运用dropna、fillna、interpolate、replace等工具完成数据清洗。缺失值哨兵总览四种表示各司其职pandas 官方 API 参考页 doc/source/reference/missing_value.rst 明确指出两个核心哨兵NA是可空nullable数据类型的缺失值表示NaT是timedelta与datetime数据的缺失值表示。而完整的哨兵家族见 doc/source/user_guide/missing_data.rst 开头部分包含四种哨兵适用数据类型特点numpy.nanNumPy 数据类型float、bool、object 等使用时会强制将原 dtype 提升为np.float64或objectNaTnp.datetime64、np.timedelta64、PeriodDtype时间类数据的专属缺失值NAStringDtype、Int64Dtype及其他位宽、Float64Dtype、BooleanDtype、ArrowDtype保持原始数据类型不被破坏Noneobject类型Python 原生空值isna会将其视为缺失之所以需要这么多哨兵根本原因在于不同底层类型缺失的物理表示不同。以np.nan为例它是浮点数的合法取值无法塞进int64数组所以一旦整数序列出现缺失pandas 只能把整个列提升为float64import numpy as np import pandas as pd # np.nan 强制类型提升int64 变为 float64 pd.Series([1, 2], dtypenp.int64).reindex([0, 1, 2]) # 0 1.0 # 1 2.0 # 2 NaN # dtype: float64 pd.Series([True, False], dtypenp.bool_).reindex([0, 1, 2]) # 结果 dtype 变为 object因为 np.bool_ 无法表示缺失而NaTNot a Time则是datetime64/timedelta64内部用特殊整数值iNaT表示的哨兵能保住时间 dtypepd.Series([1, 2], dtypenp.dtype(timedelta64[ns])).reindex([0, 1, 2]) # 0 NaT # 1 1 days # 2 2 days # dtype: timedelta64[ns] pd.Series([1, 2], dtypenp.dtype(datetime64[ns])).reindex([0, 1, 2]) # 0 NaT # 1 2020-01-01 00:00:00 示例值 # dtype: datetime64[ns] pd.Series([2020, 2020], dtypepd.PeriodDtype(D)).reindex([0, 1, 2]) # PeriodDtype 同样使用 NaT 作为缺失值而NA专为可空扩展类型设计让缺失值不再破坏原始 dtypepd.Series([1, 2], dtypeInt64).reindex([0, 1, 2]) # 0 NA # 1 1 # 2 2 # dtype: Int64 pd.Series([True, False], dtypeboolean[pyarrow]).reindex([0, 1, 2]) # dtype: boolean[pyarrow]缺失值以 NA 表示检测缺失isna 与 notna无论哨兵是哪一种统一的检测入口都是isna与notna顶层函数与Series/DataFrame方法均有。它们把None也视为缺失值这是与 Python 原生判断的重要区别ser pd.Series([pd.Timestamp(2020-01-01), pd.NaT]) pd.isna(ser) # 0 False # 1 True # dtype: bool ser pd.Series([1, None], dtypeobject) pd.isna(ser) # 0 False # 1 True绝对不要用判断缺失值。pandas 文档明确警告np.nan、NaT、NA之间的相等比较并不像None那样工作——None None为True但np.nan np.nan # False pd.NaT pd.NaT # False pd.NA pd.NA # False因此在布尔上下文中Series pd.NA无法提供与isna相同的信息量必须改用isna/notna。从实现层面看isna/notna的底层逻辑位于 pandas/core/dtypes/missing.py而哨兵本身的定义则在 Cython 层NA对应 pandas/_libs/missing.pyx 中定义的NAType单例类C_NA NAType()NaT对应 pandas/_libs/tslibs/nattype.pyx 中继承自datetime的_NaT类及其单例NaTType。NA 语义详解传播、Kleene 三值逻辑与布尔上下文NA是NAType的单例标量在 pandas/_libs/missing.pyx 中通过C_NA NAType()实例化目标是提供一个跨数据类型一致的缺失指示符替代过去np.nan、None、pd.NaT随类型而变的混乱局面。例如可空整数Int64序列中的缺失值就是NAs pd.Series([1, 2, None], dtypeInt64) s[2] is pd.NA # True需要特别强调的是pandas 默认不会自动使用这些可空类型必须显式指定 dtype或通过convert_dtypes()转换见下文转换为可空类型小节。算术运算中的传播一般规则缺失值在算术运算中传播propagate。操作数之一是未知时结果也是未知这与np.nan类似pd.NA 1 # NA a * pd.NA # NA但存在少数结果已知的特例pd.NA ** 0 # 1 1 ** pd.NA # 1比较运算中的传播比较运算中NA同样传播这是与np.nan的重大差异——np.nan的比较总是返回Falsepd.NA 1 # NA pd.NA pd.NA # NA pd.NA 2.5 # NA要判断一个值是否等于NA请使用pd.isna(pd.NA)。逻辑运算Kleene 三值逻辑对于逻辑运算、|、~NA遵循三值逻辑也称为 Kleene 逻辑与 R、SQL、Julia 一致仅在逻辑上必要时才传播缺失值。以逻辑或|为例若一个操作数已经是True无论另一个是True还是False结果必为True此时NA不传播True | False # True True | pd.NA # True pd.NA | True # True但若一个操作数是False结果取决于另一个操作数的值此时NA传播False | True # True False | False # False False | pd.NA # NA逻辑与同理可推导一个操作数为False时结果必为FalseNA不传播一个操作数为True时NA传播。布尔上下文NA 不能当布尔值用因为NA的真实值未知把它转成布尔值是有歧义的因此bool(pd.NA)会抛出TypeErrorbool(pd.NA) # TypeError: boolean value of NA is ambiguous这带来三个实践要点不能在需要布尔判断的上下文使用NA例如if condition:中的condition若可能是NA就会报错。此时应先用isna判断或提前填充缺失值。Python 关键字and、or、not无法与NA共用——它们强制对操作数调用bool()且无法被重写因此同样抛TypeError。这还会造成操作数位置不对称的现象True and pd.NA # TypeError对右侧操作数调 bool pd.NA and True # TypeError先对左侧操作数调 bool应改用位运算符、|、~它们会分发到__and__、__or__、__invert__并遵循上述 Kleene 逻辑。类似的限制也适用于Series/DataFrame对象参与if语句的情况文档中引用了 doc/source/user_guide/gotchas.rst 的 truth 一节。与 NumPy 数组混用object dtype 与 ufunc把可空数组转换为 NumPy 数组时NA会保留在 object dtype 数组中任何必须产出布尔结果的 NumPy 操作都会遇到同样的TypeError。典型场景是逐元素比较NumPy 对缺失条目得到NA然后在把结果转换为 bool dtype 时失败arr pd.array([a, b, None], dtypestring) np.asarray(arr) # array([a, b, NA], dtypeobject) np.asarray(arr) np.array([a, z])[:, None] # TypeError: boolean value of NA is ambiguous解决办法是向ExtensionArray.to_numpy显式传入na_value替换成 NumPy 可比较的值如None或np.nanarr.to_numpy(dtypeobject, na_valueNone) np.array([a, z])[:, None]pandas.NA还实现了 NumPy 的__array_ufunc__协议多数 ufunc 可以直接作用于NA并返回NAnp.log(pd.NA) # NA np.add(pd.NA, 1) # NA但需要注意目前涉及 ndarray 与NA的 ufunc 会返回填满 NA 的 object dtype 数组如np.greater(a, pd.NA)文档说明其返回类型在未来可能变化。NaT时间数据的缺失哨兵NaTNot a Time是datetime64、timedelta64与PeriodDtype的缺失值。其实现位于 pandas/_libs/tslibs/nattype.pyxcdef class _NaT(datetime)继承自 CPython 的datetime类并在此基础上将一切运算都短路为返回NaT或False——这正是pd.NaT pd.NaT为False的根源。NaT的行为与np.nan高度一致任何与NaT的运算都得到NaT算术或缺失/假值比较。例如在reindex产生缺失时时间序列自动填充NaT而保持 dtype见上文示例。对于类型标注场景文档建议使用pandas.api.typing.NaTType作为NaT的类型提示同理用pandas.api.typing.NAType标注NA。插入缺失值与转换为可空类型直接赋值插入向Series/DataFrame赋值即可插入缺失值pandas 会根据目标 dtype 自动选择合适的哨兵# float dtype赋 None 变成 np.nan ser pd.Series([1., 2., 3.]) ser.loc[0] None # 0 NaN # datetime dtype赋 np.nan 变成 NaT ser pd.Series([pd.Timestamp(2021), pd.Timestamp(2021)]) ser.iloc[0] np.nan # 0 NaT # pyarrow boolean赋 None 变成 NA ser pd.Series([True, False], dtypeboolean[pyarrow]) ser.iloc[0] None对于object类型pandas 会原样保留所赋的值s pd.Series([a, b, c], dtypeobject) s.loc[0] None s.loc[1] np.nan # 此时 s 中同时存在 None 与 np.nanisna 都会识别为缺失convert_dtypes一键切换到可空类型如果数据来自read_csv等 IO 方法dtype 是自动推断的大概率是np.nan式的 float/object。此时用DataFrame.convert_dtypes()/Series.convert_dtypes()可把数据转换为支持NA的可空类型如Int64Dtype、ArrowDtype等import io data io.StringIO(a,b\n,True\n2,) df pd.read_csv(data) df.dtypes # a object # b object # dtype: object df_conv df.convert_dtypes() df_conv.dtypes # a string # b boolean # dtype: object计算中的缺失值传播、skipna 与空值特例对象间运算传播缺失Series间的算术运算会让缺失值逐元素传播ser1 pd.Series([np.nan, np.nan, 2, 3]) ser2 pd.Series([np.nan, 1, np.nan, 4]) ser1 ser2 # 0 NaN # 1 NaN # 2 NaN # 3 7.0统计计算默认跳过缺失值pandas 的统计与计算类方法std、sum、mean、min等默认跳过缺失值这与 NumPy 的规约函数截然不同numpy.std遇到nan会返回nan而 pandas 的Series.std默认忽略缺失。若希望把缺失值计入计算需传skipnaFalseser pd.Series([1.0, np.nan, 3.0]) ser.std() # 1.0忽略 NaN ser.std(skipnaFalse) # NaN np.std(ser.to_numpy(), ddof1) # NaNNumPy 行为两个特殊的空值语义需要记住求和NA 值或空数据按 0 处理pd.Series([np.nan]).sum()返回0.0pd.Series([], dtypefloat64).sum()返回0.0求积NA 值或空数据按 1 处理pd.Series([np.nan]).prod()与空序列的prod()均返回1.0。累积方法cumsum、cumprod默认忽略 NA但在结果数组中保留它们的位置skipnaFalse可让 NA 也参与计算ser pd.Series([1, np.nan, 3, np.nan]) ser.cumsum() # [1, NaN, 4, NaN] ser.cumsum(skipnaFalse) # [1, NaN, NaN, NaN]清理缺失数据dropna、fillna、interpolate、replacedropna丢弃含缺失的行或列df pd.DataFrame([[np.nan, 1, 2], [1, 2, np.nan], [1, 2, 3]]) df.dropna() # 默认 axis0丢弃含缺失的行 df.dropna(axis1) # 丢弃含缺失的列 ser pd.Series([1, pd.NA], dtypeint64[pyarrow]) ser.dropna() # 可空类型同样支持fillna按值、按列、按方向填充用标量填充全部 NAdata {np: [1.0, np.nan, np.nan, 2], arrow: pd.array([1.0, pd.NA, pd.NA, 2], dtypefloat64[pyarrow])} df pd.DataFrame(data) df.fillna(0)用字典按列指定不同的填充值df.fillna({np: 0, arrow: 1})object 列可精确控制填充的是哪种 NA 值df pd.DataFrame({a: [pd.NA, np.nan, None]}, dtypeobject) df.fillna(None) # 全部变成 None df.fillna(np.nan) # 全部变成 np.nan df.fillna(pd.NA) # 全部变成 pd.NA非 object dtype 下三种填充值都会被统一替换为该 dtype 对应的正确 NA 哨兵。向前/向后填充空档并可限制连续填充数量df.ffill() df.bfill() df.ffill(limit1) # 每个空档最多向前填充 1 个值用对齐的Series/DataFrame填充例如用每列均值填充dff为含 NA 的 10×3 DataFramedff.fillna(dff.mean())DataFrame.where也能达到同样效果dff.where(pd.notna(dff), dff.mean(), axiscolumns)。interpolate多种插值方法默认线性插值df pd.DataFrame({ A: [1, 2.1, np.nan, 4.7, 5.6, 6.8], B: [0.25, np.nan, np.nan, 4, 12.2, 14.4], }) df.interpolate()针对DatetimeIndex可用methodtime按时间间隔比例插值针对浮点索引用methodvaluests2 ts.iloc[[0, 1, 3, 7, 9]] ts2.interpolate() ts2.interpolate(methodtime) idx [0.0, 1.0, 10.0] ser pd.Series([0.0, np.nan, 10.0], idx) ser.interpolate() # 按位置等距插值 ser.interpolate(methodvalues) # 按实际 x 值插值安装 scipy 后可将 1-D 插值例程名传给method官方文档给出实践建议以递增速率增长的时间序列 →methodbarycentric数值近似累积分布函数 →methodpchip以平滑绘图为目标填充 →methodakima多项式/样条逼近必须指定阶数df.interpolate(methodspline, order2)、df.interpolate(methodpolynomial, order2)。插值限位参数文档专设 Interpolation limits 一节是高频考点limit限制每个空档中连续填充的 NA 数量。注意它只做部分填充、不会跳过超过 limit 的空档——例如[NaN, NaN, 5, NaN, NaN, NaN, 13, NaN, NaN]中5与13之间有三个 NAlimit1时只填充前一个其余保留limit_direction默认forward可改为backward或bothlimit_area限制只填充内部inside被有效值包围的空档或外部outside序列首尾的空档。组合示例ser.interpolate(limit1, limit_directionbackward) ser.interpolate(limit1, limit_directionboth) ser.interpolate(limit_directionboth, limit_areainside, limit1) ser.interpolate(limit_directionbackward, limit_areaoutside)还可结合Series.reindex在扩展后的新索引上插值实现从已有数据推断新观测点。replace值替换与正则替换replace的用法与fillna类似但通用性更强可替换任意值df pd.DataFrame(np.eye(3)) df_missing df.replace(0, np.nan) # 把 0 替换成缺失 df_filled df_missing.replace(np.nan, 2) # 把缺失替换成 2 df_filled.replace([1, 44], [2, 28]) # 列表批量替换 df_filled.replace({1: 44, 2: 28}) # 映射字典替换正则替换需配合regexTrue且注意r前缀原始字符串中反斜杠的语义r\等价于\\d {a: list(range(4)), b: list(ab..), c: [a, b, np.nan, d]} df pd.DataFrame(d) df.replace(., np.nan) # 字面替换 df.replace(r\s*\.\s*, np.nan, regexTrue) # 正则去除空白后替换 df.replace([r\., r(a)], [dot, r\1stuff], regexTrue) # 捕获组回溯引用 df.replace({b: r\s*\.\s*}, {b: np.nan}, regexTrue) # 按列映射 df.replace(regex[r\s*\.\s*, ra|b], valueplaceholder) # regex 作位置参数re.compile编译过的正则对象同样是合法输入所有正则示例都可将to_replace改为regex命名参数传递但此时value必须显式命名或使用嵌套字典形式。从源码理解哨兵实现三个关键源码位置可以帮助你从原理层彻底理解本文内容NA的定义pandas/_libs/missing.pyx 中定义了NAType类第 404 行附近文件底部通过C_NA NAType()第 594 行实例化为可在 Cython 层共享的单例。同一文件还实现了is_matching_na等底层 NA 匹配逻辑第 84 行起它区分None、NA、NaT、nan等不同哨兵的类型匹配——这正是isna能统一识别各种哨兵的底层支撑。NaT的定义pandas/_libs/tslibs/nattype.pyx 中cdef class _NaT(datetime)第 86 行继承自datetime并重写运算行为NaTType第 349 行是其对外暴露的类型。这解释了为什么pd.NaT pd.NaT为False以及为什么时间序列的算术、比较对NaT一律传播。isna/notna的统一入口pandas/core/dtypes/missing.py 定义了isna/notna的公共函数签名第 74 行附近并分发到各扩展数组的isna()实现见 pandas/core/arrays/base.py 的ExtensionArray.isna抽象方法以及masked.py、datetimelike.py、arrow/array.py、categorical.py等各自的重写。从代码结构可以推断pandas 的缺失值体系本质上是统一的检测 APIisna/notna 分类型的哨兵表示nan/NaT/NA/None 分类型的运算语义传播或三值逻辑三层架构。理解这三层就能在面对任何缺失值问题时迅速定位是表示问题该用哪种哨兵还是语义问题运算如何传播。小结处理 pandas 缺失值的核心心法可以浓缩为五条先识别哨兵类型np.nanNumPy 类型、NaT时间类型、NA可空类型、Noneobject 类型检测永远用isna/notna绝不用因为所有哨兵与自身的相等比较都是FalseNA走 Kleene 三值逻辑算术传播、比较传播、逻辑运算仅在必要时传播且禁止出现在布尔上下文bool()、and/or/not计算默认skipnaTrue需要计入缺失时显式传skipnaFalsesum/prod对空值有 0/1 特例清洗三板斧dropna丢弃、fillna/interpolate填充、replace替换配合convert_dtypes可以先把数据升级为语义更清晰的可空类型再处理。【免费下载链接】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),仅供参考
返回列表