本教程節選自joyful pandas
第6章 缺失數據
在接下來的兩章中,會接觸到數據預處理中比較麻煩的類型,即缺失數據和文本數據(尤其是混雜型文本)
Pandas在步入1.0後,對數據類型也做出了新的嘗試,尤其是Nullable類型和String類型,瞭解這些可能在未來成爲主流的新特性是必要的
import pandas as pd
import numpy as np
df = pd. read_csv( 'data/table_missing.csv' )
df. head( )
School
Class
ID
Gender
Address
Height
Weight
Math
Physics
0
S_1
C_1
NaN
M
street_1
173
NaN
34.0
A+
1
S_1
C_1
NaN
F
street_2
192
NaN
32.5
B+
2
S_1
C_1
1103.0
M
street_2
186
NaN
87.2
B+
3
S_1
NaN
NaN
F
street_2
167
81.0
80.4
NaN
4
S_1
C_1
1105.0
NaN
street_4
159
64.0
84.8
A-
一、缺失觀測及其類型
1. 瞭解缺失信息
(a)isna和notna方法
對Series使用會返回布爾列表
df[ 'Physics' ] . isna( ) . head( )
0 False
1 False
2 False
3 True
4 False
Name: Physics, dtype: bool
df[ 'Physics' ] . notna( ) . head( )
0 True
1 True
2 True
3 False
4 True
Name: Physics, dtype: bool
對DataFrame使用會返回布爾表
df. isna( ) . head( )
School
Class
ID
Gender
Address
Height
Weight
Math
Physics
0
False
False
True
False
False
False
True
False
False
1
False
False
True
False
False
False
True
False
False
2
False
False
False
False
False
False
True
False
False
3
False
True
True
False
False
False
False
False
True
4
False
False
False
True
False
False
False
False
False
但對於DataFrame我們更關心到底每列有多少缺失值
df. isna( ) . sum ( )
School 0
Class 4
ID 6
Gender 7
Address 0
Height 0
Weight 13
Math 5
Physics 4
dtype: int64
此外,可以通過第1章中介紹的info函數查看缺失信息
df. info( )
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 35 entries, 0 to 34
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 School 35 non-null object
1 Class 31 non-null object
2 ID 29 non-null float64
3 Gender 28 non-null object
4 Address 35 non-null object
5 Height 35 non-null int64
6 Weight 22 non-null float64
7 Math 30 non-null float64
8 Physics 31 non-null object
dtypes: float64(3), int64(1), object(5)
memory usage: 2.6+ KB
(b)查看缺失值的所以在行
以最後一列爲例,挑出該列缺失值的行
df[ df[ 'Physics' ] . isna( ) ]
School
Class
ID
Gender
Address
Height
Weight
Math
Physics
3
S_1
NaN
NaN
F
street_2
167
81.0
80.4
NaN
8
S_1
C_2
1204.0
F
street_5
162
63.0
33.8
NaN
13
S_1
C_3
1304.0
NaN
street_2
195
70.0
85.2
NaN
22
S_2
C_2
2203.0
M
street_4
155
91.0
73.8
NaN
(c)挑選出所有非缺失值列
使用all就是全部非缺失值,如果是any就是至少有一個不是缺失值
df[ df. notna( ) . all ( 1 ) ]
School
Class
ID
Gender
Address
Height
Weight
Math
Physics
5
S_1
C_2
1201.0
M
street_5
159
68.0
97.0
A-
6
S_1
C_2
1202.0
F
street_4
176
94.0
63.5
B-
12
S_1
C_3
1303.0
M
street_7
188
82.0
49.7
B
17
S_2
C_1
2103.0
M
street_4
157
61.0
52.5
B-
21
S_2
C_2
2202.0
F
street_7
194
77.0
68.5
B+
25
S_2
C_3
2301.0
F
street_4
157
78.0
72.3
B+
27
S_2
C_3
2303.0
F
street_7
190
99.0
65.9
C
28
S_2
C_3
2304.0
F
street_6
164
81.0
95.5
A-
29
S_2
C_3
2305.0
M
street_4
187
73.0
48.9
B
2. 三種缺失符號
(a)np.nan
np.nan是一個麻煩的東西,首先它不等與任何東西,甚至不等於自己
np. nan == np. nan
False
np. nan == 0
False
np. nan == None
False
在用equals函數比較時,自動略過兩側全是np.nan的單元格,因此結果不會影響
df. equals( df)
True
其次,它在numpy中的類型爲浮點,由此導致數據集讀入時,即使原來是整數的列,只要有缺失值就會變爲浮點型
type ( np. nan)
float
pd. Series( [ 1 , 2 , 3 ] ) . dtype
dtype('int64')
pd. Series( [ 1 , np. nan, 3 ] ) . dtype
dtype('float64')
此外,對於布爾類型的列表,如果是np.nan填充,那麼它的值會自動變爲True而不是False
pd. Series( [ 1 , np. nan, 3 ] , dtype= 'bool' )
0 True
1 True
2 True
dtype: bool
但當修改一個布爾列表時,會改變列表類型,而不是賦值爲True
s = pd. Series( [ True , False ] , dtype= 'bool' )
s[ 1 ] = np. nan
s
0 1.0
1 NaN
dtype: float64
在所有的表格讀取後,無論列是存放什麼類型的數據,默認的缺失值全爲np.nan類型
因此整型列轉爲浮點;而字符由於無法轉化爲浮點,因此只能歸併爲object類型(‘O’),原來是浮點型的則類型不變
df[ 'ID' ] . dtype
dtype('float64')
df[ 'Math' ] . dtype
dtype('float64')
df[ 'Class' ] . dtype
dtype('O')
(b)None
None比前者稍微好些,至少它會等於自身
None == None
True
它的布爾值爲False
pd. Series( [ None ] , dtype= 'bool' )
0 False
dtype: bool
修改布爾列表不會改變數據類型
s = pd. Series( [ True , False ] , dtype= 'bool' )
s[ 0 ] = None
s
0 False
1 False
dtype: bool
s = pd. Series( [ 1 , 0 ] , dtype= 'bool' )
s[ 0 ] = None
s
0 False
1 False
dtype: bool
在傳入數值類型後,會自動變爲np.nan
type ( pd. Series( [ 1 , None ] ) [ 1 ] )
numpy.float64
只有當傳入object類型是保持不動,幾乎可以認爲,除非人工命名None,它基本不會自動出現在Pandas中
type ( pd. Series( [ 1 , None ] , dtype= 'O' ) [ 1 ] )
NoneType
在使用equals函數時不會被略過,因此下面的情況下返回False
pd. Series( [ None ] ) . equals( pd. Series( [ np. nan] ) )
False
(c)NaT
NaT是針對時間序列的缺失值,是Pandas的內置類型,可以完全看做時序版本的np.nan,與自己不等,且使用equals是也會被跳過
s_time = pd. Series( [ pd. Timestamp( '20120101' ) ] * 5 )
s_time
0 2012-01-01
1 2012-01-01
2 2012-01-01
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
s_time[ 2 ] = None
s_time
0 2012-01-01
1 2012-01-01
2 NaT
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
s_time[ 2 ] = np. nan
s_time
0 2012-01-01
1 2012-01-01
2 NaT
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
s_time[ 2 ] = pd. NaT
s_time
0 2012-01-01
1 2012-01-01
2 NaT
3 2012-01-01
4 2012-01-01
dtype: datetime64[ns]
type ( s_time[ 2 ] )
pandas._libs.tslibs.nattype.NaTType
s_time[ 2 ] == s_time[ 2 ]
False
s_time. equals( s_time)
True
s = pd. Series( [ True , False ] , dtype= 'bool' )
s[ 1 ] = pd. NaT
s
0 True
1 True
dtype: bool
3. Nullable類型與NA符號
這是Pandas在1.0新版本中引入的重大改變,其目的就是爲了(在若干版本後)解決之前出現的混亂局面,統一缺失值處理方法
“The goal of pd.NA is provide a “missing” indicator that can be used consistently across data types (instead of np.nan, None or pd.NaT depending on the data type).”——User Guide for Pandas v-1.0
官方鼓勵用戶使用新的數據類型和缺失類型pd.NA
(a)Nullable整形
對於該種類型而言,它與原來標記int上的符號區別在於首字母大寫:‘Int’
s_original = pd. Series( [ 1 , 2 ] , dtype= "int64" )
s_original
0 1
1 2
dtype: int64
s_new = pd. Series( [ 1 , 2 ] , dtype= "Int64" )
s_new
0 1
1 2
dtype: Int64
它的好處就在於,其中前面提到的三種缺失值都會被替換爲統一的NA符號,且不改變數據類型
s_original[ 1 ] = np. nan
s_original
0 1.0
1 NaN
dtype: float64
s_new[ 1 ] = np. nan
s_new
0 1
1 <NA>
dtype: Int64
s_new[ 1 ] = None
s_new
0 1
1 <NA>
dtype: Int64
s_new[ 1 ] = pd. NaT
s_new
0 1
1 <NA>
dtype: Int64
(b)Nullable布爾
對於該種類型而言,作用與上面的類似,記號爲boolean
s_original = pd. Series( [ 1 , 0 ] , dtype= "bool" )
s_original
0 True
1 False
dtype: bool
s_new = pd. Series( [ 0 , 1 ] , dtype= "boolean" )
s_new
0 False
1 True
dtype: boolean
s_original[ 0 ] = np. nan
s_original
0 NaN
1 0.0
dtype: float64
s_original = pd. Series( [ 1 , 0 ] , dtype= "bool" )
s_original[ 0 ] = None
s_original
0 False
1 False
dtype: bool
s_new[ 0 ] = np. nan
s_new
0 <NA>
1 True
dtype: boolean
s_new[ 0 ] = None
s_new
0 <NA>
1 True
dtype: boolean
s_new[ 0 ] = pd. NaT
s_new
0 <NA>
1 True
dtype: boolean
需要注意的是,含有pd.NA的布爾列表在1.0.2之前的版本作爲索引時會報錯,這是一個之前的bug ,現已經修復
s = pd. Series( [ 'dog' , 'cat' ] )
s[ s_new]
1 cat
dtype: object
(c)string類型
該類型是1.0的一大創新,目的之一就是爲了區分開原本含糊不清的object類型,這裏將簡要地提及string,因爲它是第7章的主題內容
它本質上也屬於Nullable類型,因爲並不會因爲含有缺失而改變類型
s = pd. Series( [ 'dog' , 'cat' ] , dtype= 'string' )
s
0 dog
1 cat
dtype: string
s[ 0 ] = np. nan
s
0 <NA>
1 cat
dtype: string
s[ 0 ] = None
s
0 <NA>
1 cat
dtype: string
s[ 0 ] = pd. NaT
s
0 <NA>
1 cat
dtype: string
此外,和object類型的一點重要區別就在於,在調用字符方法後,string類型返回的是Nullable類型,object則會根據缺失類型和數據類型而改變
s = pd. Series( [ "a" , None , "b" ] , dtype= "string" )
s. str . count( 'a' )
0 1
1 <NA>
2 0
dtype: Int64
s2 = pd. Series( [ "a" , None , "b" ] , dtype= "object" )
s2. str . count( "a" )
0 1.0
1 NaN
2 0.0
dtype: float64
s. str . isdigit( )
0 False
1 <NA>
2 False
dtype: boolean
s2. str . isdigit( )
0 False
1 None
2 False
dtype: object
4. NA的特性
(a)邏輯運算
只需看該邏輯運算的結果是否依賴pd.NA的取值,如果依賴,則結果還是NA,如果不依賴,則直接計算結果
True | pd. NA
True
pd. NA | True
True
False | pd. NA
<NA>
False & pd. NA
False
True & pd. NA
<NA>
取值不明直接報錯
(b)算術運算和比較運算
這裏只需記住除了下面兩類情況,其他結果都是NA即可
pd. NA ** 0
1
1 ** pd. NA
1
其他情況:
pd. NA + 1
<NA>
"a" * pd. NA
<NA>
pd. NA == pd. NA
<NA>
pd. NA < 2.5
<NA>
np. log( pd. NA)
<NA>
np. add( pd. NA, 1 )
<NA>
5. convert_dtypes方法
這個函數的功能往往就是在讀取數據時,就把數據列轉爲Nullable類型,是1.0的新函數
pd. read_csv( 'data/table_missing.csv' ) . dtypes
School object
Class object
ID float64
Gender object
Address object
Height int64
Weight float64
Math float64
Physics object
dtype: object
pd. read_csv( 'data/table_missing.csv' ) . convert_dtypes( ) . dtypes
School string
Class string
ID Int64
Gender string
Address string
Height Int64
Weight Int64
Math float64
Physics string
dtype: object
二、缺失數據的運算與分組
1. 加號與乘號規則
使用加法時,缺失值爲0
s = pd. Series( [ 2 , 3 , np. nan, 4 ] )
s. sum ( )
9.0
使用乘法時,缺失值爲1
s. prod( )
24.0
使用累計函數時,缺失值自動略過
s. cumsum( )
0 2.0
1 5.0
2 NaN
3 9.0
dtype: float64
s. cumprod( )
0 2.0
1 6.0
2 NaN
3 24.0
dtype: float64
s. pct_change( )
0 NaN
1 0.500000
2 0.000000
3 0.333333
dtype: float64
2. groupby方法中的缺失值
自動忽略爲缺失值的組
df_g = pd. DataFrame( { 'one' : [ 'A' , 'B' , 'C' , 'D' , np. nan] , 'two' : np. random. randn( 5 ) } )
df_g
one
two
0
A
-1.126645
1
B
0.924595
2
C
-2.076309
3
D
-0.312150
4
NaN
0.961543
df_g. groupby( 'one' ) . groups
{'A': Int64Index([0], dtype='int64'),
'B': Int64Index([1], dtype='int64'),
'C': Int64Index([2], dtype='int64'),
'D': Int64Index([3], dtype='int64')}
三、填充與剔除
1. fillna方法
(a)值填充與前後向填充(分別與ffill方法和bfill方法等價)
df[ 'Physics' ] . fillna( 'missing' ) . head( )
0 A+
1 B+
2 B+
3 missing
4 A-
Name: Physics, dtype: object
df[ 'Physics' ] . fillna( method= 'ffill' ) . head( )
0 A+
1 B+
2 B+
3 B+
4 A-
Name: Physics, dtype: object
df[ 'Physics' ] . fillna( method= 'backfill' ) . head( )
0 A+
1 B+
2 B+
3 A-
4 A-
Name: Physics, dtype: object
(b)填充中的對齊特性
df_f = pd. DataFrame( { 'A' : [ 1 , 3 , np. nan] , 'B' : [ 2 , 4 , np. nan] , 'C' : [ 3 , 5 , np. nan] } )
df_f. fillna( df_f. mean( ) )
A
B
C
0
1.0
2.0
3.0
1
3.0
4.0
5.0
2
2.0
3.0
4.0
返回的結果中沒有C,根據對齊特點不會被填充
df_f. fillna( df_f. mean( ) [ [ 'A' , 'B' ] ] )
A
B
C
0
1.0
2.0
3.0
1
3.0
4.0
5.0
2
2.0
3.0
NaN
2. dropna方法
(a)axis參數
df_d = pd. DataFrame( { 'A' : [ np. nan, np. nan, np. nan] , 'B' : [ np. nan, 3 , 2 ] , 'C' : [ 3 , 2 , 1 ] } )
df_d
A
B
C
0
NaN
NaN
3
1
NaN
3.0
2
2
NaN
2.0
1
df_d. dropna( axis= 0 )
df_d. dropna( axis= 1 )
(b)how參數(可以選all或者any,表示全爲缺失去除和存在缺失去除)
df_d. dropna( axis= 1 , how= 'all' )
B
C
0
NaN
3
1
3.0
2
2
2.0
1
(c)subset參數(即在某一組列範圍中搜索缺失值)
df_d. dropna( axis= 0 , subset= [ 'B' , 'C' ] )
A
B
C
1
NaN
3.0
2
2
NaN
2.0
1
四、插值(interpolation)
1. 線性插值
(a)索引無關的線性插值
默認狀態下,interpolate會對缺失的值進行線性插值
s = pd. Series( [ 1 , 10 , 15 , - 5 , - 2 , np. nan, np. nan, 28 ] )
s
0 1.0
1 10.0
2 15.0
3 -5.0
4 -2.0
5 NaN
6 NaN
7 28.0
dtype: float64
s. interpolate( )
0 1.0
1 10.0
2 15.0
3 -5.0
4 -2.0
5 8.0
6 18.0
7 28.0
dtype: float64
s. interpolate( ) . plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7df20af50>
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-F8OSm1IB-1592922494838)(output_146_1.png)]
此時的插值與索引無關
s. index = np. sort( np. random. randint( 50 , 300 , 8 ) )
s. interpolate( )
69 1.0
71 10.0
84 15.0
117 -5.0
119 -2.0
171 8.0
219 18.0
236 28.0
dtype: float64
s. interpolate( ) . plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dfc69890>
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CNaMB0nd-1592922494844)(output_149_1.png)]
(b)與索引有關的插值
method中的index和time選項可以使插值線性地依賴索引,即插值爲索引的線性函數
s. interpolate( method= 'index' ) . plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dca0c4d0>
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wBaFDwBY-1592922494849)(output_151_1.png)]
如果索引是時間,那麼可以按照時間長短插值,對於時間序列將在第9章詳細介紹
s_t = pd. Series( [ 0 , np. nan, 10 ]
, index= [ pd. Timestamp( '2012-05-01' ) , pd. Timestamp( '2012-05-07' ) , pd. Timestamp( '2012-06-03' ) ] )
s_t
2012-05-01 0.0
2012-05-07 NaN
2012-06-03 10.0
dtype: float64
s_t. interpolate( ) . plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dc964850>
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DMOge1Qe-1592922494856)(output_154_1.png)]
s_t. interpolate( method= 'time' ) . plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dc8eda10>
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mx4YEg4L-1592922494859)(output_155_1.png)]
2. 高級插值方法
此處的高級指的是與線性插值相比較,例如樣條插值、多項式插值、阿基瑪插值等(需要安裝Scipy),方法詳情請看這裏
關於這部分僅給出一個官方的例子,因爲插值方法是數值分析的內容,而不是Pandas中的基本知識:
ser = pd. Series( np. arange( 1 , 10.1 , .25 ) ** 2 + np. random. randn( 37 ) )
missing = np. array( [ 4 , 13 , 14 , 15 , 16 , 17 , 18 , 20 , 29 ] )
ser[ missing] = np. nan
methods = [ 'linear' , 'quadratic' , 'cubic' ]
df = pd. DataFrame( { m: ser. interpolate( method= m) for m in methods} )
df. plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dc86f810>
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CdrzyFtp-1592922494865)(output_157_1.png)]
3. interpolate中的限制參數
(a)limit表示最多插入多少個
s = pd. Series( [ 1 , np. nan, np. nan, np. nan, 5 ] )
s. interpolate( limit= 2 )
0 1.0
1 2.0
2 3.0
3 NaN
4 5.0
dtype: float64
(b)limit_direction表示插值方向,可選forward,backward,both,默認前向
s = pd. Series( [ np. nan, np. nan, 1 , np. nan, np. nan, np. nan, 5 , np. nan, np. nan, ] )
s. interpolate( limit_direction= 'backward' )
0 1.0
1 1.0
2 1.0
3 2.0
4 3.0
5 4.0
6 5.0
7 NaN
8 NaN
dtype: float64
(c)limit_area表示插值區域,可選inside,outside,默認None
s = pd. Series( [ np. nan, np. nan, 1 , np. nan, np. nan, np. nan, 5 , np. nan, np. nan, ] )
s. interpolate( limit_area= 'inside' )
0 NaN
1 NaN
2 1.0
3 2.0
4 3.0
5 4.0
6 5.0
7 NaN
8 NaN
dtype: float64
s = pd. Series( [ np. nan, np. nan, 1 , np. nan, np. nan, np. nan, 5 , np. nan, np. nan, ] )
s. interpolate( limit_area= 'outside' )
0 NaN
1 NaN
2 1.0
3 NaN
4 NaN
5 NaN
6 5.0
7 5.0
8 5.0
dtype: float64
五、問題與練習
1. 問題
【問題一】 如何刪除缺失值佔比超過25%的列?
【問題二】 什麼是Nullable類型?請談談爲什麼要引入這個設計?
【問題三】 對於一份有缺失值的數據,可以採取哪些策略或方法深化對它的瞭解?
2. 練習
【練習一】現有一份虛擬數據集,列類型分別爲string/浮點/整型,請解決如下問題:
(a)請以列類型讀入數據,並選出C爲缺失值的行。
(b)現需要將A中的部分單元轉爲缺失值,單元格中的最小轉換概率爲25%,且概率大小與所在行B列單元的值成正比。
pd. read_csv( 'data/Missing_data_one.csv' ) . head( )
A
B
C
0
not_NaN
0.922
4.0
1
not_NaN
0.700
NaN
2
not_NaN
0.503
8.0
3
not_NaN
0.938
4.0
4
not_NaN
0.952
10.0
【練習二】 現有一份缺失的數據集,記錄了36個人來自的地區、身高、體重、年齡和工資,請解決如下問題:
(a)統計各列缺失的比例並選出在後三列中至少有兩個非缺失值的行。
(b)請結合身高列和地區列中的數據,對體重進行合理插值。
pd. read_csv( 'data/Missing_data_two.csv' ) . head( )
編號
地區
身高
體重
年齡
工資
0
1
A
157.50
NaN
47.0
15905.0
1
2
B
202.00
91.80
25.0
NaN
2
3
C
169.09
62.18
NaN
NaN
3
4
A
166.61
59.95
77.0
5434.0
4
5
B
185.19
NaN
62.0
4242.0