第6章 缺失數據

第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") #此處重新加一句是因爲前面賦值改變了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>

取值不明直接報錯

# bool(pd.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
# 元素與先前n 個元素的相差百分比。
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
# type(df_g.groupby('one'))
{'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)
A B C
df_d.dropna(axis=1)
C
0 3
1 2
2 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>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-f47WxJ3W-1592656468366)(output_152_1.png)]

此時的插值與索引無關

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>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tGhXY6Ug-1592656468368)(output_155_1.png)]

(b)與索引有關的插值

method中的index和time選項可以使插值線性地依賴索引,即插值爲索引的線性函數

s.interpolate(method='index').plot()
#可以看到與上面的區別
<matplotlib.axes._subplots.AxesSubplot at 0x7fe7dca0c4d0>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3XVOm4Fh-1592656468369)(output_157_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 0x1c832777358>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-k89zNREC-1592656468372)(output_160_1.png)]

s_t.interpolate(method='time').plot()
<matplotlib.axes._subplots.AxesSubplot at 0x1c8327ffda0>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bzZuaZEY-1592656468373)(output_161_1.png)]

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>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ksRK4QqX-1592656468374)(output_164_2.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')
# s.interpolate(limit_direction='forward')
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
C
0 3
1 2
2 1
方法二
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
0 3
1 2
2 1
C
C
0 3
1 2
2 1

【問題二】 什麼是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時存在一個單元的缺失,而且不涉及到時間序列等等前後單元格關聯較強的方法,可以直接刪除該還有缺失值的觀測。例如當你使用的數據挖掘算法或者數據分析算法允許缺失值存在,並且在設計算法的時候就考慮到了存在缺失值的情況下如何挖掘更多的信息,你也可以選擇不做處理直接分析含有缺失值的數據。然而可能注意到了,上述的例子有着較大侷限性,比如現實生活一般沒有缺失如此輕微的數據集,比如我上述假設的算法好像真的根本就是不存在的。所以我們只能考慮填充缺失值這條路。

# df_d.dropna(axis=1,subset=[(df_d.isna().sum()/(df_d.count()+df_d.isna().sum()).values>0.25))]

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[b['地區']==i].sort_values(by='身高'))
#     display(b[b['地區']==i].sort_values(by='身高').index)
# .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['體重'].plot()
# b=pd.read_csv('data/Missing_data_two.csv').convert_dtypes()
# b[b['地區']=='A'].sort_values(by='身高')['體重'].interpolate()
方法二
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['體重']=b['體重'].round(decimals=2)
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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章