第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. notna( ) . all ( 0 )
School True
Class False
ID False
Gender False
Address True
Height True
Weight False
Math False
Physics False
dtype: bool
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 , 0 , 3 ] , dtype= 'bool' )
0 True
1 False
2 True
dtype: bool
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 ] )
pd. Series( [ 1 , None ] )
0 1.0
1 NaN
dtype: 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則會根據缺失類型和數據類型而改變
先將pandas的series對象轉成strings對象,再使用字符串相關函數。
s. str [ 0 ]
0 a
1 <NA>
2 b
dtype: string
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
Python isdigit() 方法檢測字符串是否只由數字組成。
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
0.411864
1
B
-0.687861
2
C
-0.627106
3
D
-0.266889
4
NaN
-1.235148
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. mean( 0 )
A 2.0
B 3.0
C 4.0
dtype: float64
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( inplace= True )
s. interpolate( ) . plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x1c830628198>
此時的插值與索引無關
s. index = np. sort( np. random. randint( 50 , 300 , 8 ) )
s. interpolate( )
91 1.0
98 10.0
126 15.0
153 -5.0
153 -2.0
153 8.0
182 18.0
260 28.0
dtype: float64
s. interpolate( ) . plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x1c8326fbe80>
(b)與索引有關的插值
method中的index和time選項可以使插值線性地依賴索引,即插值爲索引的線性函數
s. interpolate( method= 'index' ) . plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dca0c4d0>
如果索引是時間,那麼可以按照時間長短插值,對於時間序列將在第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 0x1c832777358>
s_t. interpolate( method= 'time' ) . plot( )
<matplotlib.axes._subplots.AxesSubplot at 0x1c8327ffda0>
2. 高級插值方法
此處的高級指的是與線性插值相比較,例如樣條插值、多項式插值、阿基瑪插值等(需要安裝Scipy),方法詳情請看這裏
關於這部分僅給出一個官方的例子,因爲插值方法是數值分析的內容,而不是Pandas中的基本知識:
np. random. randn( 37 )
np. arange( 1 , 10.1 , .25 )
array([ 1. , 1.25, 1.5 , 1.75, 2. , 2.25, 2.5 , 2.75, 3. ,
3.25, 3.5 , 3.75, 4. , 4.25, 4.5 , 4.75, 5. , 5.25,
5.5 , 5.75, 6. , 6.25, 6.5 , 6.75, 7. , 7.25, 7.5 ,
7.75, 8. , 8.25, 8.5 , 8.75, 9. , 9.25, 9.5 , 9.75,
10. ])
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} )
display( df. head( ) )
df. plot( )
linear
quadratic
cubic
0
1.699633
1.699633
1.699633
1
1.885096
1.885096
1.885096
2
0.082041
0.082041
0.082041
3
3.804304
3.804304
3.804304
4
4.156255
4.760702
4.853245
<matplotlib.axes._subplots.AxesSubplot at 0x1c833fbb128>
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%的列?
方法一
df_d = pd. DataFrame( { 'A' : [ np. nan, np. nan, np. nan] , 'B' : [ np. nan, 3 , 2 ] , 'C' : [ 3 , 2 , 1 ] } )
temp= df_d. isna( ) . sum ( ) / ( df_d. count( ) + df_d. isna( ) . sum ( ) )
for i in temp[ temp> 0.25 ] . index :
print ( type ( i) , i)
df_d. drop( columns= i, inplace= True )
display( df_d)
<class 'str'> A
B
C
0
NaN
3
1
3.0
2
2
2.0
1
<class 'str'> B
方法二
df_d = pd. DataFrame( { 'A' : [ np. nan, np. nan, np. nan] , 'B' : [ np. nan, 3 , 2 ] , 'C' : [ 3 , 2 , 1 ] } )
def drop_col ( df, col_name, cutoff= 0.25 ) :
n = len ( df)
cnt = df[ col_name] . count( )
if ( float ( cnt) / n) < ( 1 - cutoff) :
df. drop( col_name, axis= 1 , inplace= True )
for i in df_d. columns:
print ( i)
drop_col( df_d, i, 0.25 )
display( df_d)
A
B
C
0
NaN
3
1
3.0
2
2
2.0
1
B
C
【問題二】 什麼是Nullable類型?請談談爲什麼要引入這個設計?
感覺是爲了統一三種缺失符號np.nan,None,NaT的混亂的情況,對於缺失值處理進行統一。
np.nan
不等於任何東西
用equals函數時自動略過兩側都是np.nan的單元格
因爲np.nan在numpy中是浮點型,因此導致數據集讀入的時候即使原來是整數的列,只要有缺失字符無法轉變爲浮點,只能歸併爲爲object類型的(‘O’))
布爾類型np.nan填充爲True
修改布爾列表改變列表類型
None
等於自身
布爾值爲False
修改布爾列表不改變數據類型
使用equals函數是不會被略過
NaT
時序版本的np.nan
與自己不等
使用equals會被跳過
【問題三】 對於一份有缺失值的數據,可以採取哪些策略或方法深化對它的瞭解?
首先分析數據的類型
1.如果數據量很大刪去這些帶有缺失值的數據無關緊要的話可以考慮把帶有缺失值的數據刪去
2.如果缺失的部分是不可或缺的關鍵部分那樣的話這些數據就變成了垃圾數據必須刪去
3.如果數據量比較小,並且缺失的數據影響不是特別大,可以考慮填充缺失值,具體的填充方式根據數據的具體含義跟客觀規律來定。
缺失值從數據分佈上可被分爲三類 (Gelman and Hill 2006, Little and Rubin(2002)) :
missing completely at random (MCAR), missing at random (MAR),and missing not at random (MNAR)。
完全隨機缺失(MCAR):某一變量缺失值不依賴於其他任何原因的完全隨機缺失
隨機缺失(MAR):某一變量的缺失與其他變量相關但與該變量本身的數值不相關
非隨機缺失(NMAR):某一變量的缺失和該變量本身的數值相關, e.g.,
儀器的最低檢測線:某被檢測物質的含量低於該檢測線則會產生非隨機缺失(left-censored missing)
當缺失存在時,解決的方法一般有不處理、刪除還有缺失值的觀測和填充缺失值三種。每種方法在某種特殊的情境下都可能是最優的。例如當數據的規模爲3000*3000時存在一個單元的缺失,而且不涉及到時間序列等等前後單元格關聯較強的方法,可以直接刪除該還有缺失值的觀測。例如當你使用的數據挖掘算法或者數據分析算法允許缺失值存在,並且在設計算法的時候就考慮到了存在缺失值的情況下如何挖掘更多的信息,你也可以選擇不做處理直接分析含有缺失值的數據。然而可能注意到了,上述的例子有着較大侷限性,比如現實生活一般沒有缺失如此輕微的數據集,比如我上述假設的算法好像真的根本就是不存在的。所以我們只能考慮填充缺失值這條路。
2. 練習
【練習一】現有一份虛擬數據集,列類型分別爲string/浮點/整型,請解決如下問題:
(a)請以列類型讀入數據,並選出C爲缺失值的行。
pd. read_csv( 'data/Missing_data_one.csv' ) . head( )
pd. read_csv( 'data/Missing_data_one.csv' ) . convert_dtypes( ) . head( )
A
B
C
0
not_NaN
0.922
4
1
not_NaN
0.700
<NA>
2
not_NaN
0.503
8
3
not_NaN
0.938
4
4
not_NaN
0.952
10
a= pd. read_csv( 'data/Missing_data_one.csv' ) . convert_dtypes( )
a[ a[ 'C' ] . isna( ) ]
A
B
C
1
not_NaN
0.700
<NA>
5
not_NaN
0.972
<NA>
11
not_NaN
0.736
<NA>
19
not_NaN
0.684
<NA>
21
not_NaN
0.913
<NA>
(b)現需要將A中的部分單元轉爲缺失值,單元格中的最小轉換概率爲25%,且概率大小與所在行B列單元的值成正比。
方法一
b= pd. read_csv( 'data/Missing_data_one.csv' ) . convert_dtypes( )
for i in b. index:
if np. random. rand( ) < ( 0.25 * b. at[ i, 'B' ] / b[ 'B' ] . min ( ) ) :
b. at[ i, 'A' ] = np. nan
display( b. head( ) )
A
B
C
0
not_NaN
0.922
4
1
<NA>
0.700
<NA>
2
<NA>
0.503
8
3
<NA>
0.938
4
4
<NA>
0.952
10
方法二
b= pd. read_csv( 'data/Missing_data_one.csv' ) . convert_dtypes( )
b[ 'A' ] = pd. Series( list ( zip ( b[ 'A' ] . values, b[ 'B' ] . values) ) ) . apply ( lambda x: x[ 0 ] if np. random. rand( ) < ( 0.25 * x[ 1 ] / b[ 'B' ] . min ( ) ) else pd. NA)
b. head( )
A
B
C
0
<NA>
0.922
4
1
<NA>
0.700
<NA>
2
not_NaN
0.503
8
3
not_NaN
0.938
4
4
<NA>
0.952
10
【練習二】 現有一份缺失的數據集,記錄了36個人來自的地區、身高、體重、年齡和工資,請解決如下問題:
(a)統計各列缺失的比例並選出在後三列中至少有兩個非缺失值的行。
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
缺失的比例
a= pd. read_csv( 'data/Missing_data_two.csv' ) . convert_dtypes( )
a. isna( ) . sum ( ) / ( a. count( ) + a. isna( ) . sum ( ) )
編號 0.000000
地區 0.000000
身高 0.000000
體重 0.222222
年齡 0.250000
工資 0.222222
dtype: float64
取出至少兩個非缺失的行
temp= list ( a. columns) [ - 3 : ]
temp
a[ temp] [ a[ temp] . isna( ) . sum ( 1 ) <= 1 ] . head( )
體重
年齡
工資
0
NaN
47
15905
1
91.80
25
<NA>
3
59.95
77
5434
4
NaN
62
4242
5
78.42
55
13959
(b)請結合身高列和地區列中的數據,對體重進行合理插值。
方法一
b= pd. read_csv( 'data/Missing_data_two.csv' ) . convert_dtypes( )
for i in b[ '地區' ] . values. unique( ) :
b. loc[ b[ b[ '地區' ] == i] . sort_values( by= '身高' ) . index, '體重' ] = b[ b[ '地區' ] == i] . sort_values( by= '身高' ) [ '體重' ] . interpolate( )
display( b. head( ) )
編號
地區
身高
體重
年齡
工資
0
1
A
157.50
53.58
47
15905
1
2
B
202.00
91.80
25
<NA>
2
3
C
169.09
62.18
<NA>
<NA>
3
4
A
166.61
59.95
77
5434
4
5
B
185.19
81.75
62
4242
方法二
b= pd. read_csv( 'data/Missing_data_two.csv' ) . convert_dtypes( )
for name , group in b. groupby( '地區' ) :
b. loc[ group. index, '體重' ] = group. sort_values( by= '身高' ) [ '體重' ] . interpolate( )
b. head( )
編號
地區
身高
體重
年齡
工資
0
1
A
157.50
53.58
47
15905
1
2
B
202.00
91.80
25
<NA>
2
3
C
169.09
62.18
<NA>
<NA>
3
4
A
166.61
59.95
77
5434
4
5
B
185.19
81.75
62
4242
參考答案寫法
b= pd. read_csv( 'data/Missing_data_two.csv' ) . convert_dtypes( )
for name , group in b. groupby( '地區' ) :
b. loc[ group. index, '體重' ] = group[ [ '身高' , '體重' ] ] . sort_values( by= '身高' ) . interpolate( )
b. head( )
編號
地區
身高
體重
年齡
工資
0
1
A
157.50
53.58
47
15905
1
2
B
202.00
91.80
25
<NA>
2
3
C
169.09
62.18
<NA>
<NA>
3
4
A
166.61
59.95
77
5434
4
5
B
185.19
81.75
62
4242