Pandas處理缺失數(shù)據(jù)的方式匯總

許多教程中的數(shù)據(jù)與現(xiàn)實世界中的數(shù)據(jù)有很大不同,現(xiàn)實世界中的數(shù)據(jù)很少是干凈且同質(zhì)的。
尤其是,許多有趣的數(shù)據(jù)集都會存在一定程度的數(shù)據(jù)缺失。
更復雜的是,不同的數(shù)據(jù)來源可能會用不同的方式表示缺失數(shù)據(jù)。
我們將討論處理缺失數(shù)據(jù)的一些常規(guī)注意事項,了解 Pandas 如何表示缺失數(shù)據(jù),并探索 Pandas 在 Python 中處理缺失數(shù)據(jù)的一些內(nèi)置工具。
在本書中及整個過程中,我會將缺失數(shù)據(jù)統(tǒng)稱為 null、NaN 或 NA 值。
缺失數(shù)據(jù)約定的權(quán)衡
為了在表格或 DataFrame 中跟蹤缺失數(shù)據(jù)的存在,已經(jīng)開發(fā)了多種方法。通常,這些方法圍繞兩種策略展開:使用全局指示缺失值的 掩碼,或選擇一個表示缺失項的 哨兵值。
在掩碼方法中,掩碼可以是一個完全獨立的布爾數(shù)組,也可以通過占用數(shù)據(jù)表示中的某一位來局部指示值的缺失狀態(tài)。
在哨兵值方法中,哨兵值可以是某種特定于數(shù)據(jù)的約定,比如用 –9999 或某個罕見的位模式表示缺失的整數(shù)值,或者采用更通用的約定,比如用 NaN(非數(shù)字 Not a Number)表示缺失的浮點值,這是 IEEE 浮點規(guī)范中的特殊值。
這兩種方法都存在權(quán)衡。使用單獨的掩碼數(shù)組需要分配額外的布爾數(shù)組,這會增加存儲和計算的開銷。哨兵值則減少了可表示的有效值范圍,并且可能需要額外(通常是非優(yōu)化的)CPU 和 GPU 算術(shù)邏輯,因為像 NaN 這樣的常用特殊值并不適用于所有數(shù)據(jù)類型。
正如大多數(shù)沒有普遍最優(yōu)選擇的情況一樣,不同的語言和系統(tǒng)采用了不同的約定。例如,R 語言在每種數(shù)據(jù)類型中使用保留的位模式作為指示缺失數(shù)據(jù)的哨兵值,而 SciDB 系統(tǒng)則為每個單元格附加一個額外的字節(jié)來指示 NA 狀態(tài)。
Pandas 中的缺失數(shù)據(jù)
Pandas 處理缺失值的方式受到其對 NumPy 包的依賴的限制,而 NumPy 對于非浮點數(shù)據(jù)類型并沒有內(nèi)置的 NA(缺失值)概念。
或許 Pandas 可以像 R 一樣,為每種數(shù)據(jù)類型指定位模式來表示空值,但這種方法實際上非常繁瑣。R 只有 4 種主要數(shù)據(jù)類型,而 NumPy 支持的類型遠遠多于此:例如,R 只有一種整數(shù)類型,而 NumPy 在考慮不同的位寬、符號和字節(jié)序后,基本整數(shù)類型就有 14 種。如果要在所有可用的 NumPy 類型中保留特定的位模式,將導致在各種類型的操作中需要大量特殊處理,甚至可能需要為 NumPy 包開發(fā)新的分支。此外,對于較小的數(shù)據(jù)類型(如 8 位整數(shù)),犧牲一位作為掩碼會顯著減少其可表示的數(shù)值范圍。
由于這些限制和權(quán)衡,Pandas 在存儲和處理空值時有兩種“模式”:
- 默認模式是使用哨兵值(sentinel)來表示缺失數(shù)據(jù),根據(jù)數(shù)據(jù)類型使用
NaN或None作為哨兵值。 - 另外,你可以選擇使用 Pandas 提供的可空數(shù)據(jù)類型(nullable dtypes,后文會詳細介紹),這會創(chuàng)建一個額外的掩碼數(shù)組來跟蹤缺失項,并將這些缺失項以特殊的
pd.NA值呈現(xiàn)給用戶。
無論采用哪種方式,Pandas API 提供的數(shù)據(jù)操作和處理方法都會以可預測的方式處理和傳播這些缺失項。但為了更好地理解這些選擇背后的原因,我們需要快速了解一下 None、NaN 和 NA 的權(quán)衡。像往常一樣,我們先導入 NumPy 和 Pandas:
import numpy as np import pandas as pd
None 作為哨兵值
對于某些數(shù)據(jù)類型,Pandas 使用 None 作為哨兵值。None 是一個 Python 對象,這意味著任何包含 None 的數(shù)組都必須具有 dtype=object,即它必須是一個 Python 對象的序列。
例如,觀察如果你將 None 傳遞給 NumPy 數(shù)組會發(fā)生什么:
vals1 = np.array([1, None, 2, 3]) vals1
array([1, None, 2, 3], dtype=object)
這種 dtype=object 表示 NumPy 能為數(shù)組內(nèi)容推斷出的最佳通用類型是 Python 對象。
以這種方式使用 None 的缺點是,對數(shù)據(jù)的操作會在 Python 層面進行,開銷遠大于對原生類型數(shù)組的高效操作:
%timeit np.arange(1E6, dtype=int).sum()
2.89 ms ± 24.6 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit np.arange(1E6, dtype=object).sum()
26.7 ms ± 454 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)
進一步,由于 Python 不支持使用 None 進行算術(shù)運算,因此諸如 sum 或 min 之類的聚合操作通常會導致錯誤:
vals1.sum()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[5], line 1
----> 1 vals1.sum()
File d:\Source\Repos\Visual Studio Code\MachineLearning-notes\.venv\Lib\site-packages\numpy\_core\_methods.py:53, in _sum(a, axis, dtype, out, keepdims, initial, where)
51 def _sum(a, axis=None, dtype=None, out=None, keepdims=False,
52 initial=_NoValue, where=True):
---> 53 return umr_sum(a, axis, dtype, out, keepdims, initial, where)
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
鑒于此,Pandas 不會在其數(shù)值數(shù)組中使用 None 作為哨兵值。
NaN:缺失的數(shù)值數(shù)據(jù)
另一種缺失數(shù)據(jù)的哨兵值是 NaN,它有所不同;NaN 是一種特殊的浮點值,被所有采用標準 IEEE 浮點表示法的系統(tǒng)所識別:
vals2 = np.array([1, np.nan, 3, 4]) vals2
array([ 1., nan, 3., 4.])
請注意,NumPy 為該數(shù)組選擇了本地浮點類型:這意味著與之前的 object 數(shù)組不同,這個數(shù)組支持被編譯代碼加速的快速操作。
需要記住的是,NaN 有點像數(shù)據(jù)病毒——它會“感染”它接觸到的任何其他對象。
無論進行何種運算,與 NaN 進行算術(shù)運算的結(jié)果仍然是另一個 NaN:
1 + np.nan
nan
0 * np.nan
nan
這意味著對這些值進行聚合操作是有定義的(即不會導致錯誤),但結(jié)果并不總是有用:
vals2.sum(), vals2.min(), vals2.max()
(np.float64(nan), np.float64(nan), np.float64(nan))
也就是說,NumPy 提供了能夠識別 NaN 并忽略這些缺失值的聚合函數(shù)版本:
np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
(np.float64(8.0), np.float64(1.0), np.float64(4.0))
NaN 的主要缺點在于它僅適用于浮點類型;對于整數(shù)、字符串或其他類型,并沒有等價的 NaN 值。
Pandas 中的 NaN 和 None
NaN 和 None 各有其用途,Pandas 能夠幾乎無縫地處理這兩者,并在適當?shù)臅r候自動進行轉(zhuǎn)換:
pd.Series([1, np.nan, 2, None])
0 1.0 1 NaN 2 2.0 3 NaN dtype: float64
對于沒有可用哨兵值的數(shù)據(jù)類型,當存在 NA 值時,Pandas 會自動進行類型轉(zhuǎn)換。
例如,如果我們在一個整數(shù)數(shù)組中設置某個值為 np.nan,它會自動被提升為浮點類型以容納 NA:
x = pd.Series(range(2), dtype=int) x
0 0 1 1 dtype: int64
x[0] = None x
0 NaN 1 1.0 dtype: float64
請注意,除了將整數(shù)數(shù)組轉(zhuǎn)換為浮點型之外,Pandas 還會自動將 None 轉(zhuǎn)換為 NaN 值。
雖然與 R 這類領(lǐng)域?qū)S谜Z言中對 NA 值更統(tǒng)一的處理方式相比,這種哨兵值/類型轉(zhuǎn)換的“魔法”看起來有些取巧,但實際上 Pandas 的這種做法在實踐中效果很好,而且據(jù)我的經(jīng)驗,只有極少數(shù)情況下會引發(fā)問題。
下表列出了在 Pandas 中引入 NA 值時的類型提升約定:
| 類型類別 | 存儲 NA 時的轉(zhuǎn)換方式 | NA 哨兵值 |
|---|---|---|
| floating | 無變化 | np.nan |
| object | 無變化 | None 或 np.nan |
| integer | 轉(zhuǎn)換為 float64 | np.nan |
| boolean | 轉(zhuǎn)換為 object | None 或 np.nan |
請記住,在 Pandas 中,字符串數(shù)據(jù)始終以 object 類型存儲。
Pandas 可空數(shù)據(jù)類型
在早期版本的 Pandas 中,NaN 和 None 作為哨兵值是唯一可用的缺失數(shù)據(jù)表示方法。這帶來的主要問題在于隱式類型轉(zhuǎn)換:例如,無法表示帶有缺失數(shù)據(jù)的真正整數(shù)數(shù)組。
為了解決這個問題,Pandas 后來引入了可空數(shù)據(jù)類型(nullable dtypes),它們與常規(guī)數(shù)據(jù)類型的區(qū)別在于名稱的大小寫(例如,pd.Int32 與 np.int32)。為了向后兼容,只有在明確指定時才會使用這些可空數(shù)據(jù)類型。
例如,下面是一個包含缺失數(shù)據(jù)的整數(shù)序列 Series,它由一個包含三種可用缺失值標記的列表創(chuàng)建:
pd.Series([1, np.nan, 2, None, pd.NA], dtype='Int32')
0 1 1 <NA> 2 2 3 <NA> 4 <NA> dtype: Int32
這種表示方法在本文后續(xù)討論的所有操作中都可以與其他表示方法互換使用。
對缺失值的操作
如前所述,Pandas 將 None、NaN 和 NA 視為本質(zhì)上可以互換的缺失值標記。
為了方便這一約定,Pandas 提供了多種方法來檢測、移除和替換 Pandas 數(shù)據(jù)結(jié)構(gòu)中的缺失值。
這些方法包括:
isnull:生成一個布爾掩碼,用于指示缺失值notnull:與isnull相反dropna:返回過濾后的數(shù)據(jù)副本,移除缺失值fillna:返回填充或插補缺失值后的數(shù)據(jù)副本
我們將簡要探索并演示這些常用方法的用法。
檢測空值
Pandas 數(shù)據(jù)結(jié)構(gòu)有兩個用于檢測空數(shù)據(jù)的有用方法:isnull 和 notnull。
這兩個方法都會返回數(shù)據(jù)上的布爾掩碼。例如:
data = pd.Series([1, np.nan, 'hello', None])
data.isnull()
0 False 1 True 2 False 3 True dtype: bool
如數(shù)據(jù)索引和選擇中所述,布爾掩碼可以直接作為 Series 或 DataFrame 的索引使用:
data[data.notnull()]
0 1 2 hello dtype: object
isnull() 和 notnull() 方法對于 DataFrame 對象也會產(chǎn)生類似的布爾結(jié)果。
刪除空值
除了這些掩碼方法外,還有一些便捷方法,如 dropna(用于移除 NA 值)和 fillna(用于填充 NA 值)。對于 Series,其結(jié)果很直接:
data.dropna()
0 1 2 hello dtype: object
對于 DataFrame,有更多的選項。
請看下面的 DataFrame:
df = pd.DataFrame([[1, np.nan, 2],
[2, 3, 5],
[np.nan, 4, 6]])
df
| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 1.0 | NaN | 2 |
| 1 | 2.0 | 3.0 | 5 |
| 2 | NaN | 4.0 | 6 |
我們無法從 DataFrame 中刪除單個值;只能刪除整行或整列。
根據(jù)具體應用,你可能需要刪除行或列,因此 dropna 為 DataFrame 提供了多種選項。
默認情況下,dropna 會刪除所有包含任意空值的行:
df.dropna()
| 0 | 1 | 2 | |
|---|---|---|---|
| 1 | 2.0 | 3.0 | 5 |
或者,你也可以沿不同的軸刪除 NA 值。使用 axis=1 或 axis='columns' 可以刪除所有包含空值的列:
df.dropna(axis='columns')
| 2 | |
|---|---|
| 0 | 2 |
| 1 | 5 |
| 2 | 6 |
但這樣也會丟棄一些有用的數(shù)據(jù);你可能更希望只刪除那些全部為 NA 的行或列,或者 NA 占多數(shù)的行或列。
這可以通過 how 或 thresh 參數(shù)來指定,從而精細控制允許通過的空值數(shù)量。
默認情況下,how='any',即只要某行或某列包含空值就會被刪除。
你也可以指定 how='all',這樣只有那些全部為空值的行或列才會被刪除:
df[3] = np.nan df
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | NaN | 2 | NaN |
| 1 | 2.0 | 3.0 | 5 | NaN |
| 2 | NaN | 4.0 | 6 | NaN |
df.dropna(axis='columns', how='all')
| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 1.0 | NaN | 2 |
| 1 | 2.0 | 3.0 | 5 |
| 2 | NaN | 4.0 | 6 |
df.dropna(axis='rows', thresh=3)
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 1 | 2.0 | 3.0 | 5 | NaN |
這里,第一行和最后一行被刪除了,因為它們每行只有兩個非空值。
填充空值
有時候,與其刪除 NA 值,你可能更希望用一個有效值來替換它們。
這個值可以是像零這樣的單個數(shù)字,也可以是通過已有有效值進行插補或插值得到的某種值。
你可以使用 isnull 方法作為掩碼進行原地替換,但由于這是一個非常常見的操作,Pandas 提供了 fillna 方法,它會返回一個用指定值替換空值后的數(shù)組副本。
來看下面的 Series 示例:
data = pd.Series([1, np.nan, 2, None, 3], index=list('abcde'), dtype='Int32')
data
a 1 b <NA> c 2 d <NA> e 3 dtype: Int32
我們可以用單個值(例如零)來填充 NA 項:
data.fillna(0)
a 1 b 0 c 2 d 0 e 3 dtype: Int32
我們可以指定前向填充(forward fill),將前一個有效值向前傳播:
# 前向填充,舊方法,在后續(xù)Pandas版本中可能會被棄用 data.fillna(method='ffill')
\AppData\Local\Temp\ipykernel_13548\3988156040.py:2: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead. data.fillna(method='ffill') a 1 b 1 c 2 d 2 e 3 dtype: Int32
# 前向填充 data.ffill()
a 1 b 1 c 2 d 2 e 3 dtype: Int32
或者我們可以指定反向填充(backward fill),將下一個有效值向后傳播:
# 后向填充,舊方法,在后續(xù)Pandas版本中可能會被棄用 data.fillna(method='bfill')
\AppData\Local\Temp\ipykernel_13548\1439583404.py:2: FutureWarning: Series.fillna with 'method' is deprecated and will raise in a future version. Use obj.ffill() or obj.bfill() instead. data.fillna(method='bfill') a 1 b 2 c 2 d 3 e 3 dtype: Int32
# 后向填充 data.bfill()
a 1 b 2 c 2 d 3 e 3 dtype: Int32
在 DataFrame 的情況下,選項類似,但我們還可以指定填充操作應沿著哪個 axis(軸)進行:
df
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | NaN | 2 | NaN |
| 1 | 2.0 | 3.0 | 5 | NaN |
| 2 | NaN | 4.0 | 6 | NaN |
df.ffill(axis=1)
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 1.0 | 2.0 | 2.0 |
| 1 | 2.0 | 3.0 | 5.0 | 5.0 |
| 2 | NaN | 4.0 | 6.0 | 6.0 |
請注意,如果在前向填充時沒有可用的前一個值,NA 值將保持不變。
總結(jié)
本文介紹了現(xiàn)實世界中缺失數(shù)據(jù)的常見情況及其在 Pandas 中的處理方式。我們討論了缺失值的兩種主要表示方法(掩碼和哨兵值),并重點介紹了 Pandas 對 None、NaN 和 pd.NA 的支持及其背后的權(quán)衡。通過示例演示了缺失值的檢測(isnull、notnull)、刪除(dropna)和填充(fillna、ffill、bfill)等常用操作。此外,還介紹了 Pandas 的可空數(shù)據(jù)類型(如 Int32),使得帶缺失值的整數(shù)數(shù)據(jù)能夠被更好地支持。掌握這些方法有助于在數(shù)據(jù)分析過程中更高效、靈活地處理缺失數(shù)據(jù)問題。
以上就是Pandas處理缺失數(shù)據(jù)的方式匯總的詳細內(nèi)容,更多關(guān)于Pandas處理缺失數(shù)據(jù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解python websocket獲取實時數(shù)據(jù)的幾種常見鏈接方式
這篇文章主要介紹了詳解python websocket獲取實時數(shù)據(jù)的幾種常見鏈接方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-07-07
Python中的?Numpy?數(shù)組形狀改變及索引切片
這篇文章主要介紹了Python中的?Numpy?數(shù)組形狀改變及索引切片,Numpy提供了一個reshape()方法,它可以改變數(shù)組的形狀,返回一個新的數(shù)組,更多相關(guān)內(nèi)容需要的小伙伴可以參考下面文章2022-05-05
Python實現(xiàn)堡壘機模式下遠程命令執(zhí)行操作示例
這篇文章主要介紹了Python實現(xiàn)堡壘機模式下遠程命令執(zhí)行操作,結(jié)合實例形式分析了Python堡壘機模式執(zhí)行遠程命令的原理與相關(guān)操作技巧,需要的朋友可以參考下2019-05-05
python溫度轉(zhuǎn)換華氏溫度實現(xiàn)代碼
這篇文章主要介紹了python溫度轉(zhuǎn)換華氏溫度實現(xiàn)代碼內(nèi)容,有需要的朋友們可以測試下。2020-12-12

