則與實(shí)戰(zhàn)操作指南)
pandas 缺失值哨兵體系全解析NA 與 NaT 的語義、傳播規(guī)則與實(shí)戰(zhàn)操作指南【免費(fèi)下載鏈接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more項(xiàng)目地址: https://gitcode.com/gh_mirrors/pa/pandas缺失值是數(shù)據(jù)處理中最常見也最容易踩坑的主題。pandas 并不只用一種方式表示缺失值不同數(shù)據(jù)類型擁有各自的哨兵sentinel——np.nan、NaT、NA與None各司其職。本文以 doc/source/reference/missing_value.rst 定義的 NA 與 NaT 兩條主線為核心結(jié)合 doc/source/user_guide/missing_data.rst 的完整實(shí)操指南深入講解這些哨兵的適用場景、NA的三值邏輯傳播語義、isna/notna檢測方法以及插入、轉(zhuǎn)換、計(jì)算、填充與插值的完整處理鏈路。讀完本文你將能準(zhǔn)確判斷數(shù)據(jù)中缺失值的類型、理解NA與np.nan在運(yùn)算中的根本差異并熟練運(yùn)用dropna、fillna、interpolate、replace等工具完成數(shù)據(jù)清洗。缺失值哨兵總覽四種表示各司其職pandas 官方 API 參考頁 doc/source/reference/missing_value.rst 明確指出兩個(gè)核心哨兵NA是可空nullable數(shù)據(jù)類型的缺失值表示NaT是timedelta與datetime數(shù)據(jù)的缺失值表示。而完整的哨兵家族見 doc/source/user_guide/missing_data.rst 開頭部分包含四種哨兵適用數(shù)據(jù)類型特點(diǎn)numpy.nanNumPy 數(shù)據(jù)類型float、bool、object 等使用時(shí)會強(qiáng)制將原 dtype 提升為np.float64或objectNaTnp.datetime64、np.timedelta64、PeriodDtype時(shí)間類數(shù)據(jù)的專屬缺失值NAStringDtype、Int64Dtype及其他位寬、Float64Dtype、BooleanDtype、ArrowDtype保持原始數(shù)據(jù)類型不被破壞Noneobject類型Python 原生空值isna會將其視為缺失之所以需要這么多哨兵根本原因在于不同底層類型缺失的物理表示不同。以np.nan為例它是浮點(diǎn)數(shù)的合法取值無法塞進(jìn)int64數(shù)組所以一旦整數(shù)序列出現(xiàn)缺失pandas 只能把整個(gè)列提升為float64import numpy as np import pandas as pd # np.nan 強(qiáng)制類型提升int64 變?yōu)?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]) # 結(jié)果 dtype 變?yōu)?object因?yàn)?np.bool_ 無法表示缺失而NaTNot a Time則是datetime64/timedelta64內(nèi)部用特殊整數(shù)值iNaT表示的哨兵能保住時(shí)間 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專為可空擴(kuò)展類型設(shè)計(jì)讓缺失值不再破壞原始 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無論哨兵是哪一種統(tǒng)一的檢測入口都是isna與notna頂層函數(shù)與Series/DataFrame方法均有。它們把None也視為缺失值這是與 Python 原生判斷的重要區(qū)別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。從實(shí)現(xiàn)層面看isna/notna的底層邏輯位于 pandas/core/dtypes/missing.py而哨兵本身的定義則在 Cython 層NA對應(yīng) pandas/_libs/missing.pyx 中定義的NAType單例類C_NA NAType()NaT對應(yīng) pandas/_libs/tslibs/nattype.pyx 中繼承自datetime的_NaT類及其單例NaTType。NA 語義詳解傳播、Kleene 三值邏輯與布爾上下文NA是NAType的單例標(biāo)量在 pandas/_libs/missing.pyx 中通過C_NA NAType()實(shí)例化目標(biāo)是提供一個(gè)跨數(shù)據(jù)類型一致的缺失指示符替代過去np.nan、None、pd.NaT隨類型而變的混亂局面。例如可空整數(shù)Int64序列中的缺失值就是NAs pd.Series([1, 2, None], dtypeInt64) s[2] is pd.NA # True需要特別強(qiáng)調(diào)的是pandas 默認(rèn)不會自動使用這些可空類型必須顯式指定 dtype或通過convert_dtypes()轉(zhuǎn)換見下文轉(zhuǎn)換為可空類型小節(jié)。算術(shù)運(yùn)算中的傳播一般規(guī)則缺失值在算術(shù)運(yùn)算中傳播propagate。操作數(shù)之一是未知時(shí)結(jié)果也是未知這與np.nan類似pd.NA 1 # NA a * pd.NA # NA但存在少數(shù)結(jié)果已知的特例pd.NA ** 0 # 1 1 ** pd.NA # 1比較運(yùn)算中的傳播比較運(yùn)算中NA同樣傳播這是與np.nan的重大差異——np.nan的比較總是返回Falsepd.NA 1 # NA pd.NA pd.NA # NA pd.NA 2.5 # NA要判斷一個(gè)值是否等于NA請使用pd.isna(pd.NA)。邏輯運(yùn)算Kleene 三值邏輯對于邏輯運(yùn)算、|、~NA遵循三值邏輯也稱為 Kleene 邏輯與 R、SQL、Julia 一致僅在邏輯上必要時(shí)才傳播缺失值。以邏輯或|為例若一個(gè)操作數(shù)已經(jīng)是True無論另一個(gè)是True還是False結(jié)果必為True此時(shí)NA不傳播True | False # True True | pd.NA # True pd.NA | True # True但若一個(gè)操作數(shù)是False結(jié)果取決于另一個(gè)操作數(shù)的值此時(shí)NA傳播False | True # True False | False # False False | pd.NA # NA邏輯與同理可推導(dǎo)一個(gè)操作數(shù)為False時(shí)結(jié)果必為FalseNA不傳播一個(gè)操作數(shù)為True時(shí)NA傳播。布爾上下文NA 不能當(dāng)布爾值用因?yàn)镹A的真實(shí)值未知把它轉(zhuǎn)成布爾值是有歧義的因此bool(pd.NA)會拋出TypeErrorbool(pd.NA) # TypeError: boolean value of NA is ambiguous這帶來三個(gè)實(shí)踐要點(diǎn)不能在需要布爾判斷的上下文使用NA例如if condition:中的condition若可能是NA就會報(bào)錯(cuò)。此時(shí)應(yīng)先用isna判斷或提前填充缺失值。Python 關(guān)鍵字and、or、not無法與NA共用——它們強(qiáng)制對操作數(shù)調(diào)用bool()且無法被重寫因此同樣拋TypeError。這還會造成操作數(shù)位置不對稱的現(xiàn)象True and pd.NA # TypeError對右側(cè)操作數(shù)調(diào) bool pd.NA and True # TypeError先對左側(cè)操作數(shù)調(diào) bool應(yīng)改用位運(yùn)算符、|、~它們會分發(fā)到__and__、__or__、__invert__并遵循上述 Kleene 邏輯。類似的限制也適用于Series/DataFrame對象參與if語句的情況文檔中引用了 doc/source/user_guide/gotchas.rst 的 truth 一節(jié)。與 NumPy 數(shù)組混用object dtype 與 ufunc把可空數(shù)組轉(zhuǎn)換為 NumPy 數(shù)組時(shí)NA會保留在 object dtype 數(shù)組中任何必須產(chǎn)出布爾結(jié)果的 NumPy 操作都會遇到同樣的TypeError。典型場景是逐元素比較NumPy 對缺失條目得到NA然后在把結(jié)果轉(zhuǎn)換為 bool dtype 時(shí)失敗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還實(shí)現(xiàn)了 NumPy 的__array_ufunc__協(xié)議多數(shù) ufunc 可以直接作用于NA并返回NAnp.log(pd.NA) # NA np.add(pd.NA, 1) # NA但需要注意目前涉及 ndarray 與NA的 ufunc 會返回填滿 NA 的 object dtype 數(shù)組如np.greater(a, pd.NA)文檔說明其返回類型在未來可能變化。NaT時(shí)間數(shù)據(jù)的缺失哨兵NaTNot a Time是datetime64、timedelta64與PeriodDtype的缺失值。其實(shí)現(xiàn)位于 pandas/_libs/tslibs/nattype.pyxcdef class _NaT(datetime)繼承自 CPython 的datetime類并在此基礎(chǔ)上將一切運(yùn)算都短路為返回NaT或False——這正是pd.NaT pd.NaT為False的根源。NaT的行為與np.nan高度一致任何與NaT的運(yùn)算都得到NaT算術(shù)或缺失/假值比較。例如在reindex產(chǎn)生缺失時(shí)時(shí)間序列自動填充NaT而保持 dtype見上文示例。對于類型標(biāo)注場景文檔建議使用pandas.api.typing.NaTType作為NaT的類型提示同理用pandas.api.typing.NAType標(biāo)注NA。插入缺失值與轉(zhuǎn)換為可空類型直接賦值插入向Series/DataFrame賦值即可插入缺失值pandas 會根據(jù)目標(biāo) 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 # 此時(shí) s 中同時(shí)存在 None 與 np.nanisna 都會識別為缺失convert_dtypes一鍵切換到可空類型如果數(shù)據(jù)來自read_csv等 IO 方法dtype 是自動推斷的大概率是np.nan式的 float/object。此時(shí)用DataFrame.convert_dtypes()/Series.convert_dtypes()可把數(shù)據(jù)轉(zhuǎn)換為支持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計(jì)算中的缺失值傳播、skipna 與空值特例對象間運(yùn)算傳播缺失Series間的算術(shù)運(yùn)算會讓缺失值逐元素傳播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統(tǒng)計(jì)計(jì)算默認(rèn)跳過缺失值pandas 的統(tǒng)計(jì)與計(jì)算類方法std、sum、mean、min等默認(rèn)跳過缺失值這與 NumPy 的規(guī)約函數(shù)截然不同numpy.std遇到nan會返回nan而 pandas 的Series.std默認(rèn)忽略缺失。若希望把缺失值計(jì)入計(jì)算需傳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 行為兩個(gè)特殊的空值語義需要記住求和NA 值或空數(shù)據(jù)按 0 處理pd.Series([np.nan]).sum()返回0.0pd.Series([], dtypefloat64).sum()返回0.0求積NA 值或空數(shù)據(jù)按 1 處理pd.Series([np.nan]).prod()與空序列的prod()均返回1.0。累積方法cumsum、cumprod默認(rèn)忽略 NA但在結(jié)果數(shù)組中保留它們的位置skipnaFalse可讓 NA 也參與計(jì)算ser pd.Series([1, np.nan, 3, np.nan]) ser.cumsum() # [1, NaN, 4, NaN] ser.cumsum(skipnaFalse) # [1, NaN, NaN, NaN]清理缺失數(shù)據(jù)dropna、fillna、interpolate、replacedropna丟棄含缺失的行或列df pd.DataFrame([[np.nan, 1, 2], [1, 2, np.nan], [1, 2, 3]]) df.dropna() # 默認(rèn) axis0丟棄含缺失的行 df.dropna(axis1) # 丟棄含缺失的列 ser pd.Series([1, pd.NA], dtypeint64[pyarrow]) ser.dropna() # 可空類型同樣支持fillna按值、按列、按方向填充用標(biāo)量填充全部 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 下三種填充值都會被統(tǒng)一替換為該 dtype 對應(yīng)的正確 NA 哨兵。向前/向后填充空檔并可限制連續(xù)填充數(shù)量df.ffill() df.bfill() df.ffill(limit1) # 每個(gè)空檔最多向前填充 1 個(gè)值用對齊的Series/DataFrame填充例如用每列均值填充dff為含 NA 的 10×3 DataFramedff.fillna(dff.mean())DataFrame.where也能達(dá)到同樣效果dff.where(pd.notna(dff), dff.mean(), axiscolumns)。interpolate多種插值方法默認(rèn)線性插值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按時(shí)間間隔比例插值針對浮點(diǎn)索引用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) # 按實(shí)際 x 值插值安裝 scipy 后可將 1-D 插值例程名傳給method官方文檔給出實(shí)踐建議以遞增速率增長的時(shí)間序列 →methodbarycentric數(shù)值近似累積分布函數(shù) →methodpchip以平滑繪圖為目標(biāo)填充 →methodakima多項(xiàng)式/樣條逼近必須指定階數(shù)df.interpolate(methodspline, order2)、df.interpolate(methodpolynomial, order2)。插值限位參數(shù)文檔專設(shè) Interpolation limits 一節(jié)是高頻考點(diǎn)limit限制每個(gè)空檔中連續(xù)填充的 NA 數(shù)量。注意它只做部分填充、不會跳過超過 limit 的空檔——例如[NaN, NaN, 5, NaN, NaN, NaN, 13, NaN, NaN]中5與13之間有三個(gè) NAlimit1時(shí)只填充前一個(gè)其余保留limit_direction默認(rèn)forward可改為backward或bothlimit_area限制只填充內(nèi)部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)還可結(jié)合Series.reindex在擴(kuò)展后的新索引上插值實(shí)現(xiàn)從已有數(shù)據(jù)推斷新觀測點(diǎn)。replace值替換與正則替換replace的用法與fillna類似但通用性更強(qiáng)可替換任意值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\等價(jià)于\\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 作位置參數(shù)re.compile編譯過的正則對象同樣是合法輸入所有正則示例都可將to_replace改為regex命名參數(shù)傳遞但此時(shí)value必須顯式命名或使用嵌套字典形式。從源碼理解哨兵實(shí)現(xiàn)三個(gè)關(guān)鍵源碼位置可以幫助你從原理層徹底理解本文內(nèi)容NA的定義pandas/_libs/missing.pyx 中定義了NAType類第 404 行附近文件底部通過C_NA NAType()第 594 行實(shí)例化為可在 Cython 層共享的單例。同一文件還實(shí)現(xiàn)了is_matching_na等底層 NA 匹配邏輯第 84 行起它區(qū)分None、NA、NaT、nan等不同哨兵的類型匹配——這正是isna能統(tǒng)一識別各種哨兵的底層支撐。NaT的定義pandas/_libs/tslibs/nattype.pyx 中cdef class _NaT(datetime)第 86 行繼承自datetime并重寫運(yùn)算行為NaTType第 349 行是其對外暴露的類型。這解釋了為什么pd.NaT pd.NaT為False以及為什么時(shí)間序列的算術(shù)、比較對NaT一律傳播。isna/notna的統(tǒng)一入口pandas/core/dtypes/missing.py 定義了isna/notna的公共函數(shù)簽名第 74 行附近并分發(fā)到各擴(kuò)展數(shù)組的isna()實(shí)現(xiàn)見 pandas/core/arrays/base.py 的ExtensionArray.isna抽象方法以及masked.py、datetimelike.py、arrow/array.py、categorical.py等各自的重寫。從代碼結(jié)構(gòu)可以推斷pandas 的缺失值體系本質(zhì)上是統(tǒng)一的檢測 APIisna/notna 分類型的哨兵表示nan/NaT/NA/None 分類型的運(yùn)算語義傳播或三值邏輯三層架構(gòu)。理解這三層就能在面對任何缺失值問題時(shí)迅速定位是表示問題該用哪種哨兵還是語義問題運(yùn)算如何傳播。小結(jié)處理 pandas 缺失值的核心心法可以濃縮為五條先識別哨兵類型np.nanNumPy 類型、NaT時(shí)間類型、NA可空類型、Noneobject 類型檢測永遠(yuǎn)用isna/notna絕不用因?yàn)樗猩诒c自身的相等比較都是FalseNA走 Kleene 三值邏輯算術(shù)傳播、比較傳播、邏輯運(yùn)算僅在必要時(shí)傳播且禁止出現(xiàn)在布爾上下文bool()、and/or/not計(jì)算默認(rèn)skipnaTrue需要計(jì)入缺失時(shí)顯式傳skipnaFalsesum/prod對空值有 0/1 特例清洗三板斧dropna丟棄、fillna/interpolate填充、replace替換配合convert_dtypes可以先把數(shù)據(jù)升級為語義更清晰的可空類型再處理?!久赓M(fèi)下載鏈接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more項(xiàng)目地址: https://gitcode.com/gh_mirrors/pa/pandas創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考