Pandas 資料處理基礎
Pandas 資料處理基礎
介紹
Pandas 是非常著名的開源資料處理庫,我們可以通過它完成對資料集進行快速讀取、轉換、過濾、分析等一系列操作。除此之外,Pandas 擁有強大的缺失資料處理與資料透視功能,可謂是資料預處理中的必備利器。
知識點
- 資料型別
- 資料讀取
- 資料選擇
- 資料刪減
- 資料填充
Pandas 是非常著名的開源資料處理庫,其基於 NumPy 開發,該工具是 Scipy 生態中為了解決資料分析任務而設計。Pandas 納入了大量庫和一些標準的資料模型,提供了高效地操作大型資料集所需的函式和方法。
特有的資料結構是 Pandas 的優勢和核心。簡單來講,我們可以將任意格式的資料轉換為 Pandas 的資料型別,並使用 Pandas 提供的一系列方法進行轉換、操作,最終得到我們期望的結果。
所以,我們首先需要了解並熟悉 Pandas 支援的資料型別。
資料型別
Pandas 的資料型別主要有以下幾種,它們分別是:Series(一維陣列),DataFrame(二維陣列),Panel(三維陣列),Panel4D(四維陣列),PanelND(更多維陣列)。其中 Series 和 DataFrame 應用的最為廣泛,幾乎佔據了使用頻率 90% 以上。
Series
Series 是 Pandas 中最基本的一維陣列形式。其可以儲存整數、浮點數、字串等型別的資料。Series 基本結構如下:
pandas.Series(data=None, index=None)
其中,data
可以是字典,或者NumPy 裡的 ndarray 物件等。index
下面,我們基於 Python 字典新建一個示例 Series。
%matplotlib inline
import pandas as pd
s = pd.Series({'a': 10, 'b': 20, 'c': 30})
s
a 10
b 20
c 30
dtype: int64
如上所示,該 Series 的資料值是 10, 20, 30,索引為 a, b, c,資料值的型別預設識別為 int64
。你可以通過 type
來確認 s
的型別。
type(s)
pandas.core.series.Series
由於 Pandas 基於 NumPy 開發。那麼 NumPy 的資料型別 ndarray
多維陣列自然就可以轉換為 Pandas 中的資料。而 Series 則可以基於 NumPy 中的一維資料轉換。
import numpy as np
s = pd.Series(np.random.randn(5))
s
0 -2.580004
1 0.129963
2 -1.540962
3 -0.434943
4 -0.153004
dtype: float64
如上所示,我們給出了 NumPy 生成的一維隨機陣列,最終得到的 Series 索引預設從 0 開始,而數值型別為 float64
。
DataFrame
DataFrame 是 Pandas 中最為常見、最重要且使用頻率最高的資料結構。DataFrame 和平常的電子表格或 SQL 表結構相似。你可以把 DataFrame 看成是 Series 的擴充套件型別,它彷彿是由多個 Series 拼合而成。它和 Series 的直觀區別在於,資料不但具有行索引,且具有列索引。
來源DataFrame 基本結構如下:
pandas.DataFrame(data=None, index=None, columns=None)
區別於 Series,其增加了 columns
列索引。DataFrame 可以由以下多個型別的資料構建:
- 一維陣列、列表、字典或者 Series 字典。
- 二維或者結構化的
numpy.ndarray
。 - 一個 Series 或者另一個 DataFrame。
例如,我們首先使用一個由 Series 組成的字典來構建 DataFrame。
df = pd.DataFrame({'one': pd.Series([1, 2, 3]),
'two': pd.Series([4, 5, 6])})
df
one | two | |
---|---|---|
0 | 1 | 4 |
1 | 2 | 5 |
2 | 3 | 6 |
當不指定索引時,DataFrame 的索引同樣是從 0 開始。我們也可以直接通過一個列表構成的字典來生成 DataFrame。
df = pd.DataFrame({'one': [1, 2, 3],
'two': [4, 5, 6]})
df
one | two | |
---|---|---|
0 | 1 | 4 |
1 | 2 | 5 |
2 | 3 | 6 |
或者反過來,由帶字典的列表生成 DataFrame。
df = pd.DataFrame([{'one': 1, 'two': 4},
{'one': 2, 'two': 5},
{'one': 3, 'two': 6}])
df
one | two | |
---|---|---|
0 | 1 | 4 |
1 | 2 | 5 |
2 | 3 | 6 |
NumPy 的多維陣列非常常用,同樣可以基於二維數值來構建一個 DataFrame。
pd.DataFrame(np.random.randint(5, size=(2, 4)))
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 3 | 3 | 1 | 4 |
1 | 1 | 3 | 3 | 0 |
至此,你應該已經清楚了 Pandas 常用的 Series 和 DataFrame 資料型別。Series 實際上可以被初略看出是隻有 1 列資料的 DataFrame。當然,這個說法不嚴謹,二者的核心區別仍然是 Series 沒有列索引。你可以觀察如下所示由 NumPy 一維隨機陣列生成的 Series 和 DataFrame。
pd.Series(np.random.randint(5, size=(5,)))
0 0
1 4
2 3
3 2
4 4
dtype: int32
pd.DataFrame(np.random.randint(5, size=(5,)))
0 | |
---|---|
0 | 0 |
1 | 1 |
2 | 1 |
3 | 3 |
4 | 2 |
關於 Pandas 中的 Panel 等資料型別我們就不再介紹。首先是這些資料型別用的很少,其次就算你用到了,也可以通過從 DataFrame 等學到的技巧進行遷移應用,萬變不離其宗。
資料讀取
我們想要使用 Pandas 來分析資料,那麼首先需要讀取資料。大多數情況下,資料都來源於外部的資料檔案或者資料庫。Pandas 提供了一系列的方法來讀取外部資料,非常全面。下面,我們以最常用的 CSV 資料檔案為例進行介紹。
讀取資料 CSV 檔案的方法是 pandas.read_csv()
,你可以直接傳入一個相對路徑,或者是網路 URL。
df = pd.read_csv("https://labfile.oss.aliyuncs.com/courses/906/los_census.csv")
df
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
3 | 90003 | 66266 | 26.3 | 32631 | 33635 | 15642 | 4.22 |
4 | 90004 | 62180 | 34.8 | 31302 | 30878 | 22547 | 2.73 |
... | ... | ... | ... | ... | ... | ... | ... |
314 | 93552 | 38158 | 28.4 | 18711 | 19447 | 9690 | 3.93 |
315 | 93553 | 2138 | 43.3 | 1121 | 1017 | 816 | 2.62 |
316 | 93560 | 18910 | 32.4 | 9491 | 9419 | 6469 | 2.92 |
317 | 93563 | 388 | 44.5 | 263 | 125 | 103 | 2.53 |
318 | 93591 | 7285 | 30.9 | 3653 | 3632 | 1982 | 3.67 |
319 rows × 7 columns
由於 CSV 儲存時是一個二維的表格,那麼 Pandas 會自動將其讀取為 DataFrame 型別。
現在你應該就明白了,DataFrame 是 Pandas 構成的核心。一切的資料,無論是外部讀取還是自行生成,我們都需要先將其轉換為 Pandas 的 DataFrame 或者 Series 資料型別。實際上,大多數情況下,這一切都是設計好的,無需執行額外的轉換工作。
pd.read_
字首開始的方法還可以讀取各式各樣的資料檔案,且支援連線資料庫。這裡,我們不再依次贅述,你可以閱讀 官方文件相應章節 熟悉這些方法以及搞清楚這些方法包含的引數。
你可能又一個疑問:為什麼要將資料轉換為 Series 或者 DataFrame 結構?
實際上,我現在就可以先回答這個問題。因為 Pandas 針對資料操作的全部方法都是基於 Pandas 支援的資料結構設計的。也就是說,只有 Series 或者 DataFrame 才能使用 Pandas 提供的方法和函式進行處理。所以,學習真正資料處理方法之前,我們需要將資料轉換生成為 Series 或 DataFrame 型別。
基本操作
通過上面的內容,我們已經知道一個 DataFrame 結構大致由 3 部分組成,它們分別是列名稱、索引和資料。
來源接下來,我們就學習針對 DataFrame 的基本操作。本次課程中,我們不會刻意強調 Series,因為你在 DataFrame 上學習的大多數方法和技巧都適用於對 Series 進行處理,二者同根同源。
上面,我們已經讀取了一個外部資料,這是洛杉磯的人口普查資料。有些時候,我們讀取的檔案很大。如果全部輸出預覽這些檔案,既不美觀,又很耗時。還好,Pandas 提供了 head()
和 tail()
方法,它可以幫助我們只預覽一小塊資料。
df.head() # 預設顯示前 5 條
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
3 | 90003 | 66266 | 26.3 | 32631 | 33635 | 15642 | 4.22 |
4 | 90004 | 62180 | 34.8 | 31302 | 30878 | 22547 | 2.73 |
df.tail(7) # 指定顯示後 7 條
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
312 | 93550 | 74929 | 27.5 | 36414 | 38515 | 20864 | 3.58 |
313 | 93551 | 50798 | 37.0 | 25056 | 25742 | 15963 | 3.18 |
314 | 93552 | 38158 | 28.4 | 18711 | 19447 | 9690 | 3.93 |
315 | 93553 | 2138 | 43.3 | 1121 | 1017 | 816 | 2.62 |
316 | 93560 | 18910 | 32.4 | 9491 | 9419 | 6469 | 2.92 |
317 | 93563 | 388 | 44.5 | 263 | 125 | 103 | 2.53 |
318 | 93591 | 7285 | 30.9 | 3653 | 3632 | 1982 | 3.67 |
Pandas 還提供了統計和描述性方法,方便你從巨集觀的角度去了解資料集。describe()
相當於對資料集進行概覽,會輸出該資料集每一列資料的計數、最大值、最小值等。
df.describe()
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
count | 319.000000 | 319.000000 | 319.000000 | 319.000000 | 319.000000 | 319.000000 | 319.000000 |
mean | 91000.673981 | 33241.341693 | 36.527586 | 16391.564263 | 16849.777429 | 10964.570533 | 2.828119 |
std | 908.360203 | 21644.417455 | 8.692999 | 10747.495566 | 10934.986468 | 6270.646400 | 0.835658 |
min | 90001.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 90243.500000 | 19318.500000 | 32.400000 | 9763.500000 | 9633.500000 | 6765.500000 | 2.435000 |
50% | 90807.000000 | 31481.000000 | 37.100000 | 15283.000000 | 16202.000000 | 10968.000000 | 2.830000 |
75% | 91417.000000 | 44978.000000 | 41.000000 | 22219.500000 | 22690.500000 | 14889.500000 | 3.320000 |
max | 93591.000000 | 105549.000000 | 74.000000 | 52794.000000 | 53185.000000 | 31087.000000 | 4.670000 |
Pandas 基於 NumPy 開發,所以任何時候你都可以通過 .values
將 DataFrame 轉換為 NumPy 陣列。
df.values
array([[9.1371e+04, 1.0000e+00, 7.3500e+01, ..., 1.0000e+00, 1.0000e+00,
1.0000e+00],
[9.0001e+04, 5.7110e+04, 2.6600e+01, ..., 2.8642e+04, 1.2971e+04,
4.4000e+00],
[9.0002e+04, 5.1223e+04, 2.5500e+01, ..., 2.6347e+04, 1.1731e+04,
4.3600e+00],
...,
[9.3560e+04, 1.8910e+04, 3.2400e+01, ..., 9.4190e+03, 6.4690e+03,
2.9200e+00],
[9.3563e+04, 3.8800e+02, 4.4500e+01, ..., 1.2500e+02, 1.0300e+02,
2.5300e+00],
[9.3591e+04, 7.2850e+03, 3.0900e+01, ..., 3.6320e+03, 1.9820e+03,
3.6700e+00]])
這也就說明了,你可以同時使用 Pandas 和 NumPy 提供的 API 對同一資料進行操作,並在二者之間進行隨意轉換。這就是一個非常靈活的工具生態圈。
除了 .values
,DataFrame 支援的常見屬性可以通過 官方文件相應章節 檢視。其中常用的有:
df.index # 檢視索引
RangeIndex(start=0, stop=319, step=1)
df.columns # 檢視列名
Index(['Zip Code', 'Total Population', 'Median Age', 'Total Males',
'Total Females', 'Total Households', 'Average Household Size'],
dtype='object')
df.shape # 檢視形狀
(319, 7)
資料選擇
在資料預處理過程中,我們往往會對資料集進行切分,只將需要的某些行、列,或者資料塊保留下來,輸出到下一個流程中去。這也就是所謂的資料選擇,或者資料索引。
由於 Pandas 的資料結構中存在索引、標籤,所以我們可以通過多軸索引完成對資料的選擇。
基於索引數字選擇
當我們新建一個 DataFrame 之後,如果未自己指定行索引或者列對應的標籤,那麼 Pandas 會預設從 0 開始以數字的形式作為行索引,並以資料集的第一行作為列對應的標籤。其實,這裡的「列」也有數字索引,預設也是從 0 開始,只是未顯示出來。
所以,我們首先可以基於數字索引對資料集進行選擇。這裡用到的 Pandas 中的 .iloc
方法。該方法可以接受的型別有:
- 整數。例如:
5
- 整數構成的列表或陣列。例如:
[1, 2, 3]
- 布林陣列。
- 可返回索引值的函式或引數。
下面,我們使用上方的示例資料進行演示。
首先,我們可以選擇前 3 行資料。這和 Python 或者 NumPy 裡面的切片很相似。
df.iloc[:3]
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
我們還可以選擇特定的一行。
df.iloc[5]
Zip Code 90005.0
Total Population 37681.0
Median Age 33.9
Total Males 19299.0
Total Females 18382.0
Total Households 15044.0
Average Household Size 2.5
Name: 5, dtype: float64
那麼選擇多行,是不是 df.iloc[1, 3, 5]
這樣呢?
答案是錯誤的。df.iloc[]
的 [[行],[列]]
裡面可以同時接受行和列的位置,如果你直接鍵入 df.iloc[1, 3, 5]
就會報錯。
所以,很簡單。如果你想要選擇 1,3,5 行,可以這樣做。
df.iloc[[1, 3, 5]]
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
3 | 90003 | 66266 | 26.3 | 32631 | 33635 | 15642 | 4.22 |
5 | 90005 | 37681 | 33.9 | 19299 | 18382 | 15044 | 2.50 |
選擇行學會以後,選擇列就應該能想到怎麼辦了。例如,我們要選擇第 2-4 列。
df.iloc[:, 1:4]
Total Population | Median Age | Total Males | |
---|---|---|---|
0 | 1 | 73.5 | 0 |
1 | 57110 | 26.6 | 28468 |
2 | 51223 | 25.5 | 24876 |
3 | 66266 | 26.3 | 32631 |
4 | 62180 | 34.8 | 31302 |
... | ... | ... | ... |
314 | 38158 | 28.4 | 18711 |
315 | 2138 | 43.3 | 1121 |
316 | 18910 | 32.4 | 9491 |
317 | 388 | 44.5 | 263 |
318 | 7285 | 30.9 | 3653 |
319 rows × 3 columns
這裡選擇 2-4 列,輸入的卻是 1:4
。這和 Python 或者 NumPy 裡面的切片操作非常相似。既然我們能定位行和列,那麼只需要組合起來,我們就可以選擇資料集中的任何資料了。
基於標籤名稱選擇
除了根據數字索引選擇,還可以直接根據標籤對應的名稱選擇。這裡用到的方法和上面的 iloc
很相似,少了個 i
為 df.loc[]
。
df.loc[]
可以接受的型別有:
- 單個標籤。例如:
2
或'a'
,這裡的2
指的是標籤而不是索引位置。 - 列表或陣列包含的標籤。例如:
['A', 'B', 'C']
。 - 切片物件。例如:
'A':'E'
,注意這裡和上面切片的不同之處,首尾都包含在內。 - 布林陣列。
- 可返回標籤的函式或引數。
下面,我們來演示 df.loc[]
的用法。先選擇前 3 行:
df.loc[0:2]
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
再選擇 1,3,5 行:
df.loc[[0, 2, 4]]
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
4 | 90004 | 62180 | 34.8 | 31302 | 30878 | 22547 | 2.73 |
然後,選擇 2-4 列:
df.loc[:, 'Total Population':'Total Males']
Total Population | Median Age | Total Males | |
---|---|---|---|
0 | 1 | 73.5 | 0 |
1 | 57110 | 26.6 | 28468 |
2 | 51223 | 25.5 | 24876 |
3 | 66266 | 26.3 | 32631 |
4 | 62180 | 34.8 | 31302 |
... | ... | ... | ... |
314 | 38158 | 28.4 | 18711 |
315 | 2138 | 43.3 | 1121 |
316 | 18910 | 32.4 | 9491 |
317 | 388 | 44.5 | 263 |
318 | 7285 | 30.9 | 3653 |
319 rows × 3 columns
最後,選擇 1,3 行和 Median Age 後面的列:
df.loc[[0, 2], 'Median Age':]
Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|
0 | 73.5 | 0 | 1 | 1 | 1.00 |
2 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
資料刪減
雖然我們可以通過資料選擇方法從一個完整的資料集中拿到我們需要的資料,但有的時候直接刪除不需要的資料更加簡單直接。Pandas 中,以 .drop
開頭的方法都與資料刪減有關。
DataFrame.drop
可以直接去掉資料集中指定的列和行。一般在使用時,我們指定 labels
標籤引數,然後再通過 axis
指定按列或按行刪除即可。當然,你也可以通過索引引數刪除資料,具體檢視官方文件。
df.drop(labels=['Median Age', 'Total Males'], axis=1)
Zip Code | Total Population | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|
0 | 91371 | 1 | 1 | 1 | 1.00 |
1 | 90001 | 57110 | 28642 | 12971 | 4.40 |
2 | 90002 | 51223 | 26347 | 11731 | 4.36 |
3 | 90003 | 66266 | 33635 | 15642 | 4.22 |
4 | 90004 | 62180 | 30878 | 22547 | 2.73 |
... | ... | ... | ... | ... | ... |
314 | 93552 | 38158 | 19447 | 9690 | 3.93 |
315 | 93553 | 2138 | 1017 | 816 | 2.62 |
316 | 93560 | 18910 | 9419 | 6469 | 2.92 |
317 | 93563 | 388 | 125 | 103 | 2.53 |
318 | 93591 | 7285 | 3632 | 1982 | 3.67 |
319 rows × 5 columns
DataFrame.drop_duplicates
則通常用於資料去重,即剔除資料集中的重複值。使用方法非常簡單,指定去除重複值規則,以及 axis
按列還是按行去除即可。
df.drop_duplicates()
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
3 | 90003 | 66266 | 26.3 | 32631 | 33635 | 15642 | 4.22 |
4 | 90004 | 62180 | 34.8 | 31302 | 30878 | 22547 | 2.73 |
... | ... | ... | ... | ... | ... | ... | ... |
314 | 93552 | 38158 | 28.4 | 18711 | 19447 | 9690 | 3.93 |
315 | 93553 | 2138 | 43.3 | 1121 | 1017 | 816 | 2.62 |
316 | 93560 | 18910 | 32.4 | 9491 | 9419 | 6469 | 2.92 |
317 | 93563 | 388 | 44.5 | 263 | 125 | 103 | 2.53 |
318 | 93591 | 7285 | 30.9 | 3653 | 3632 | 1982 | 3.67 |
319 rows × 7 columns
除此之外,另一個用於資料刪減的方法 DataFrame.dropna
也十分常用,其主要的用途是刪除缺少值,即資料集中空缺的資料列或行。
df.dropna()
Zip Code | Total Population | Median Age | Total Males | Total Females | Total Households | Average Household Size | |
---|---|---|---|---|---|---|---|
0 | 91371 | 1 | 73.5 | 0 | 1 | 1 | 1.00 |
1 | 90001 | 57110 | 26.6 | 28468 | 28642 | 12971 | 4.40 |
2 | 90002 | 51223 | 25.5 | 24876 | 26347 | 11731 | 4.36 |
3 | 90003 | 66266 | 26.3 | 32631 | 33635 | 15642 | 4.22 |
4 | 90004 | 62180 | 34.8 | 31302 | 30878 | 22547 | 2.73 |
... | ... | ... | ... | ... | ... | ... | ... |
314 | 93552 | 38158 | 28.4 | 18711 | 19447 | 9690 | 3.93 |
315 | 93553 | 2138 | 43.3 | 1121 | 1017 | 816 | 2.62 |
316 | 93560 | 18910 | 32.4 | 9491 | 9419 | 6469 | 2.92 |
317 | 93563 | 388 | 44.5 | 263 | 125 | 103 | 2.53 |
318 | 93591 | 7285 | 30.9 | 3653 | 3632 | 1982 | 3.67 |
319 rows × 7 columns
對於提到的這 3 個常用的資料刪減方法,大家一定要通過給出的連結去閱讀官方文件。這些常用方法沒有太多需要注意的地方,通過文件瞭解其用法即可,所以我們也不會化簡為繁地進行介紹。
資料填充
既然提到了資料刪減,反之則可能會遇到資料填充的情況。而對於一個給定的資料集而言,我們一般不會亂填資料,而更多的是對缺失值進行填充。
在真實的生產環境中,我們需要處理的資料檔案往往沒有想象中的那麼美好。其中,很大機率會遇到的情況就是缺失值。缺失值主要是指資料丟失的現象,也就是資料集中的某一塊資料不存在。除此之外、存在但明顯不正確的資料也被歸為缺失值一類。例如,在一個時間序列資料集中,某一段資料突然發生了時間流錯亂,那麼這一小塊資料就是毫無意義的,可以被歸為缺失值。
檢測缺失值
Pandas 為了更方便地檢測缺失值,將不同型別資料的缺失均採用 NaN
標記。這裡的 NaN 代表 Not a Number,它僅僅是作為一個標記。例外是,在時間序列裡,時間戳的丟失採用 NaT
標記。
Pandas 中用於檢測缺失值主要用到兩個方法,分別是:isna()
和 notna()
,故名思意就是「是缺失值」和「不是缺失值」。預設會返回布林值用於判斷。
接下來,我們人為生成一組包含缺失值的示例資料。
df = pd.DataFrame(np.random.rand(9, 5), columns=list('ABCDE'))
# 插入 T 列,並打上時間戳
df.insert(value=pd.Timestamp('2017-10-1'), loc=0, column='Time')
# 將 1, 3, 5 列的 1,3,5,7 行置為缺失值
df.iloc[[1, 3, 5, 7], [0, 2, 4]] = np.nan
# 將 2, 4, 6 列的 2,4,6,8 行置為缺失值
df.iloc[[2, 4, 6, 8], [1, 3, 5]] = np.nan
df
Time | A | B | C | D | E | |
---|---|---|---|---|---|---|
0 | 2017-10-01 | 0.134833 | 0.404493 | 0.039677 | 0.124987 | 0.235375 |
1 | NaT | 0.327418 | NaN | 0.415117 | NaN | 0.955035 |
2 | 2017-10-01 | NaN | 0.806375 | NaN | 0.043618 | NaN |
3 | NaT | 0.284264 | NaN | 0.372243 | NaN | 0.072319 |
4 | 2017-10-01 | NaN | 0.426517 | NaN | 0.496821 | NaN |
5 | NaT | 0.294960 | NaN | 0.069315 | NaN | 0.128785 |
6 | 2017-10-01 | NaN | 0.200127 | NaN | 0.961028 | NaN |
7 | NaT | 0.527589 | NaN | 0.650305 | NaN | 0.306959 |
8 | 2017-10-01 | NaN | 0.120425 | NaN | 0.988731 | NaN |
然後,通過 isna()
或 notna()
中的一個即可確定資料集中的缺失值。
df.isna()
Time | A | B | C | D | E | |
---|---|---|---|---|---|---|
0 | False | False | False | False | False | False |
1 | True | False | True | False | True | False |
2 | False | True | False | True | False | True |
3 | True | False | True | False | True | False |
4 | False | True | False | True | False | True |
5 | True | False | True | False | True | False |
6 | False | True | False | True | False | True |
7 | True | False | True | False | True | False |
8 | False | True | False | True | False | True |
上面已經對預設值的產生、檢測進行了介紹。實際上,面對缺失值一般就是填充和剔除兩項操作。填充和清除都是兩個極端。如果你感覺有必要保留缺失值所在的列或行,那麼就需要對缺失值進行填充。如果沒有必要保留,就可以選擇清除缺失值。
其中,缺失值剔除的方法 dropna()
已經在上面介紹過了。下面來看一看填充缺失值 fillna()
方法。
首先,我們可以用相同的標量值替換 NaN
,比如用 0
。
df.fillna(0)
Time | A | B | C | D | E | |
---|---|---|---|---|---|---|
0 | 2017-10-01 00:00:00 | 0.134833 | 0.404493 | 0.039677 | 0.124987 | 0.235375 |
1 | 0 | 0.327418 | 0.000000 | 0.415117 | 0.000000 | 0.955035 |
2 | 2017-10-01 00:00:00 | 0.000000 | 0.806375 | 0.000000 | 0.043618 | 0.000000 |
3 | 0 | 0.284264 | 0.000000 | 0.372243 | 0.000000 | 0.072319 |
4 | 2017-10-01 00:00:00 | 0.000000 | 0.426517 | 0.000000 | 0.496821 | 0.000000 |
5 | 0 | 0.294960 | 0.000000 | 0.069315 | 0.000000 | 0.128785 |
6 | 2017-10-01 00:00:00 | 0.000000 | 0.200127 | 0.000000 | 0.961028 | 0.000000 |
7 | 0 | 0.527589 | 0.000000 | 0.650305 | 0.000000 | 0.306959 |
8 | 2017-10-01 00:00:00 | 0.000000 | 0.120425 | 0.000000 | 0.988731 | 0.000000 |
除了直接填充值,我們還可以通過引數,將缺失值前面或者後面的值填充給相應的缺失值。例如使用缺失值前面的值進行填充:
df.fillna(method='pad')
Time | A | B | C | D | E | |
---|---|---|---|---|---|---|
0 | 2017-10-01 | 0.134833 | 0.404493 | 0.039677 | 0.124987 | 0.235375 |
1 | 2017-10-01 | 0.327418 | 0.404493 | 0.415117 | 0.124987 | 0.955035 |
2 | 2017-10-01 | 0.327418 | 0.806375 | 0.415117 | 0.043618 | 0.955035 |
3 | 2017-10-01 | 0.284264 | 0.806375 | 0.372243 | 0.043618 | 0.072319 |
4 | 2017-10-01 | 0.284264 | 0.426517 | 0.372243 | 0.496821 | 0.072319 |
5 | 2017-10-01 | 0.294960 | 0.426517 | 0.069315 | 0.496821 | 0.128785 |
6 | 2017-10-01 | 0.294960 | 0.200127 | 0.069315 | 0.961028 | 0.128785 |
7 | 2017-10-01 | 0.527589 | 0.200127 | 0.650305 | 0.961028 | 0.306959 |
8 | 2017-10-01 | 0.527589 | 0.120425 | 0.650305 | 0.988731 | 0.306959 |
或者是後面的值:
df.fillna(method='bfill')
Time | A | B | C | D | E | |
---|---|---|---|---|---|---|
0 | 2017-10-01 | 0.134833 | 0.404493 | 0.039677 | 0.124987 | 0.235375 |
1 | 2017-10-01 | 0.327418 | 0.806375 | 0.415117 | 0.043618 | 0.955035 |
2 | 2017-10-01 | 0.284264 | 0.806375 | 0.372243 | 0.043618 | 0.072319 |
3 | 2017-10-01 | 0.284264 | 0.426517 | 0.372243 | 0.496821 | 0.072319 |
4 | 2017-10-01 | 0.294960 | 0.426517 | 0.069315 | 0.496821 | 0.128785 |
5 | 2017-10-01 | 0.294960 | 0.200127 | 0.069315 | 0.961028 | 0.128785 |
6 | 2017-10-01 | 0.527589 | 0.200127 | 0.650305 | 0.961028 | 0.306959 |
7 | 2017-10-01 | 0.527589 | 0.120425 | 0.650305 | 0.988731 | 0.306959 |
8 | 2017-10-01 | NaN | 0.120425 | NaN | 0.988731 | NaN |
最後一行由於沒有對於的後序值,自然繼續存在缺失值。
上面的例子中,我們的缺失值是間隔存在的。那麼,如果存在連續的缺失值是怎樣的情況呢?試一試。首先,我們將資料集的第 2,4 ,6 列的第 3,5 行也置為缺失值。
df.iloc[[3, 5], [1, 3, 5]] = np.nan
然後來正向填充:
df.fillna(method='pad')
Time | A | B | C | D | E | |
---|---|---|---|---|---|---|
0 | 2017-10-01 | 0.134833 | 0.404493 | 0.039677 | 0.124987 | 0.235375 |
1 | 2017-10-01 | 0.327418 | 0.404493 | 0.415117 | 0.124987 | 0.955035 |
2 | 2017-10-01 | 0.327418 | 0.806375 | 0.415117 | 0.043618 | 0.955035 |
3 | 2017-10-01 | 0.327418 | 0.806375 | 0.415117 | 0.043618 | 0.955035 |
4 | 2017-10-01 | 0.327418 | 0.426517 | 0.415117 | 0.496821 | 0.955035 |
5 | 2017-10-01 | 0.327418 | 0.426517 | 0.415117 | 0.496821 | 0.955035 |
6 | 2017-10-01 | 0.327418 | 0.200127 | 0.415117 | 0.961028 | 0.955035 |
7 | 2017-10-01 | 0.527589 | 0.200127 | 0.650305 | 0.961028 | 0.306959 |
8 | 2017-10-01 | 0.527589 | 0.120425 | 0.650305 | 0.988731 | 0.306959 |
可以看到,連續缺失值也是按照前序數值進行填充的,並且完全填充。這裡,我們可以通過 limit=
引數設定連續填充的限制數量。
df.fillna(method='pad', limit=1) # 最多填充一項
Time | A | B | C | D | E | |
---|---|---|---|---|---|---|
0 | 2017-10-01 | 0.134833 | 0.404493 | 0.039677 | 0.124987 | 0.235375 |
1 | 2017-10-01 | 0.327418 | 0.404493 | 0.415117 | 0.124987 | 0.955035 |
2 | 2017-10-01 | 0.327418 | 0.806375 | 0.415117 | 0.043618 | 0.955035 |
3 | 2017-10-01 | NaN | 0.806375 | NaN | 0.043618 | NaN |
4 | 2017-10-01 | NaN | 0.426517 | NaN | 0.496821 | NaN |
5 | 2017-10-01 | NaN | 0.426517 | NaN | 0.496821 | NaN |
6 | 2017-10-01 | NaN | 0.200127 | NaN | 0.961028 | NaN |
7 | 2017-10-01 | 0.527589 | 0.200127 | 0.650305 | 0.961028 | 0.306959 |
8 | 2017-10-01 | 0.527589 | 0.120425 | 0.650305 | 0.988731 | 0.306959 |
除了上面的填充方式,還可以通過 Pandas 自帶的求平均值方法等來填充特定列或行。舉個例子:
df.fillna(df.mean()['C':'E'])
Time | A | B | C | D | E | |
---|---|---|---|---|---|---|
0 | 2017-10-01 | 0.134833 | 0.404493 | 0.039677 | 0.124987 | 0.235375 |
1 | NaT | 0.327418 | NaN | 0.415117 | 0.523037 | 0.955035 |
2 | 2017-10-01 | NaN | 0.806375 | 0.368367 | 0.043618 | 0.499123 |
3 | NaT | NaN | NaN | 0.368367 | 0.523037 | 0.499123 |
4 | 2017-10-01 | NaN | 0.426517 | 0.368367 | 0.496821 | 0.499123 |
5 | NaT | NaN | NaN | 0.368367 | 0.523037 | 0.499123 |
6 | 2017-10-01 | NaN | 0.200127 | 0.368367 | 0.961028 | 0.499123 |
7 | NaT | 0.527589 | NaN | 0.650305 | 0.523037 | 0.306959 |
8 | 2017-10-01 | NaN | 0.120425 | 0.368367 | 0.988731 | 0.499123 |
對 C 列到 E 列用平均值填充。
插值填充
插值是數值分析中一種方法。簡而言之,就是藉助於一個函式(線性或非線性),再根據已知資料去求解未知資料的值。插值在資料領域非常常見,它的好處在於,可以儘量去還原資料本身的樣子。
我們可以通過 interpolate()
方法完成線性插值。當然,其他一些插值演算法可以閱讀官方文件瞭解。
# 生成一個 DataFrame
df = pd.DataFrame({'A': [1.1, 2.2, np.nan, 4.5, 5.7, 6.9],
'B': [.21, np.nan, np.nan, 3.1, 11.7, 13.2]})
df
A | B | |
---|---|---|
0 | 1.1 | 0.21 |
1 | 2.2 | NaN |
2 | NaN | NaN |
3 | 4.5 | 3.10 |
4 | 5.7 | 11.70 |
5 | 6.9 | 13.20 |
對於上面存在的缺失值,如果通過前後值,或者平均值來填充是不太能反映出趨勢的。這時候,插值最好使。我們用預設的線性插值試一試。
df_interpolate = df.interpolate()
df_interpolate
A | B | |
---|---|---|
0 | 1.10 | 0.210000 |
1 | 2.20 | 1.173333 |
2 | 3.35 | 2.136667 |
3 | 4.50 | 3.100000 |
4 | 5.70 | 11.700000 |
5 | 6.90 | 13.200000 |
下圖展示了插值後的資料,明顯看出插值結果符合資料的變化趨勢。如果按照前後資料順序填充,則無法做到這一點。
對於 interpolate()
支援的插值演算法,也就是 method=
。下面給出幾條選擇的建議:
- 如果你的資料增長速率越來越快,可以選擇
method='quadratic'
二次插值。 - 如果資料集呈現出累計分佈的樣子,推薦選擇
method='pchip'
。 - 如果需要填補預設值,以平滑繪圖為目標,推薦選擇
method='akima'
。
當然,最後提到的 method='akima'
,需要你的環境中安裝了 Scipy 庫。除此之外,method='barycentric'
和 method='pchip'
同樣也需要 Scipy 才能使用。
資料視覺化
NumPy,Pandas,Matplotlib 構成了一個完善的資料分析生態圈,所以 3 個工具的相容性也非常好,甚至共享了大量的介面。當我們的資料是以 DataFrame 格式呈現時,可以直接使用 Pandas 提供的 DataFrame.plot
方法呼叫 Matplotlib 介面繪製常見的圖形。
例如,我們使用上面插值後的資料 df_interpolate
繪製線形圖。
df_interpolate.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x1e8b1c2d160>
其他樣式的圖形也很簡單,指定 kind=
引數即可。
df_interpolate.plot(kind='bar')
<matplotlib.axes._subplots.AxesSubplot at 0x1e8b1cea670>
更多的圖形樣式和引數,閱讀官方文件中的詳細說明。Pandas 繪圖雖然不可能做到 Matplotlib 的靈活性,但是其簡單易用,適合於資料的快速呈現和預覽。
其他用法
由於 Pandas 包含的內容實在太多,除了閱讀完整的官方文件,很難做到通過一個實驗或者一個課程進行全面瞭解。當然,本課程的目的是帶大家熟悉 Pandas 的常用基礎方法,至少你大致清楚了 Pandas 是什麼,能幹什麼。
除了上面提到的一些方法和技巧,實際上 Pandas 常用的還有: