Data Science — Pandas 資料科學程式馬拉松

D8-D18

Joe Chao
23 min readMay 23, 2021

D8 Pandas 物件的定義與屬性

補充:函式與函式庫

函式庫是由一堆函式所組成的集合,又稱為是模組、套件或是大陸用語包、模塊等詞。而函式是指一段程式碼的片段封裝而成的函式,可以用於重複呼叫。

Pandas 的重要特性

  • 提供了快速高效的 DataFrame 結構
  • 對於資料格式有高度的銜接性,包含 CSV、Excel 或資料庫(SQL)皆能提供彈性的讀寫工具

Series 的常用屬性

Series 其實就是 NumPy 的 Array 的加工品,所以 Array 有的屬性, Series 絕大部分都可延用:

import pandas as pds = pd.Series([1, 2, 3])
print(s)
# 0 1
# 1 2
# 2 3
# dtype: int64
print(type(s))
# <class 'pandas.core.series.Series'>

DataFrame

import pandas as pddf = pd.DataFrame([1, 2, 3])
print(df)
# 0
# 0 1
# 1 2
# 2 3
print(type(df))
# <class 'pandas.core.frame.DataFrame'>

Series、DataFrame 與 NdArray 的比較

  • DataFrame 代表的是用「資料」的角度去思考程式中的實踐應該長什麼樣子、應該要提供哪些方法,是以資料的觀點出發。
  • 那你可以會問既然 Series、DataFrame 都是由陣列所封裝而成的加工品,那為什麼不直接用陣列就好呢?陣列當中所有資料型態必須相同,而 DataFrame 是由 Series 所組成,也就說同一個欄位形態相同,欄位與欄位間可不相同。

D9 使用 Pandas DataFrame 的初始化

建立 Pandas 物件

在 NumPy 中有很多種初始化陣列的方法,但是在 Pandas 當中我們都建議「先直接將資料轉換看看」,可以細分成兩種方法:

  1. 從原有的物件轉換
  2. 從外部資料讀取而來

Series

如果資料的樣態是一維的話,例如 … 。直接採用 pd.Series(…) 進行轉換:

import pandas as pds = pd.Series([1,2,3],  index=['Amy', 'Bob', 'Tom'])
print(s)
# Amy 1
# Bob 2
# Tom 3
# dtype: int64

從 Array 轉換而來

如果資料的樣態是一維陣列的話,用 pd.DataFrame(…) 進行轉換的話會變成僅有一個欄位的表格:

import pandas as pddf = pd.DataFrame([1, 2, 3], index=['a', 'b', 'c'], columns=['No'])
print(df)
# No
# a 1
# b 2
# c 3

如果資料的樣態是二維陣列的話,用 pd.DataFrame(…) 進行轉換的話會變成多個的表格:

import pandas as pddf = pd.DataFrame([[1, 2, 3], [4, 5, 6]], index=['a', 'b'], columns=['A', 'B', 'C'])
print(df)
# A B C
# a 1 2 3
# b 4 5 6

DataFrame 的來源樣態可以很多元

import pandas as pddf = pd.DataFrame([[1, 2, 3], [4, 5, 6]], index=['a', 'b'], columns=['A', 'B', 'C'])
print(df)
# A B C
# a 1 2 3
# b 4 5 6
print(df['B'])
# a 2
# b 5
# Name: B, dtype: int64
print(type(df['B']))
# <class 'pandas.core.series.Series'>

D10 Pandas DataFrame 的資料選取

從 DataFrame 選取資料

資料選取是 DataFrame 最重要的操作之一,仰賴於內部的 index 結構,讓我們得以高效的資料選取。在 DataFrame 當中選取資料有幾種方式:

  • 利用欄位名稱選取單行資料
  • 利用欄位名稱選取多行資料
  • 利用列索引位置選取單列/多列資料
  • 用 loc, iloc, ix 取得行與列
  • 用 iat, at 取得資料
  • 根據條件篩選資料(遮罩)

loc函數:通過行索引”Index”中的具體值來取行數據(如取”Index”為”A”的行

iloc函數:通過行號來取行數據(如取第二行的數據)

用法

合併資料

串連(concat)

pandas 的可以將多個物件的資料合成一個新物件。DataFrame 的串連(concat)可以在任何指定的欄位進行結合,如資料對其後出現遺漏值將會填入 NaN。

使用 concat 串聯參數 axis=0 縱向結合後發現 stock_data1 不存在 high 欄位,stock_data2 不存在 close 欄位,那麼該位子會被填入 NaN。

使用concat串聯參數axis=1橫向結合,會以列索引標籤對齊。

串連(concat)在合併資料時,連結類型預設是外連結(outer join)連集的操作,連結類型可以用 join 參數調整,如 join=’inner’ 表示連結型態為內連結(inner join)交集的操作。

合併(merge)

合併是藉由找出一或多個行或列索引的吻合值,然後將兩資料結合。

以下為針對合併欄位 (key)date 做合併,合併方式為 how=’outer’ 外連結,如除了合併欄位 date 之外還有相同欄位名稱,pandas 將會自動把重複欄位名稱加上 ‘_x’ 代表左邊 DataFrame stock_data1 的重複欄位open,加上 ‘_y’ 代表右邊 DataFrame stock_data2 的重複欄位 open。

pd.merge() 預設連結類型為內連結(inner join),參數 how 可以更改連結類型,

inner : 兩資料集的交集

outer: 兩資料集的聯集

left : 只使用左資料的合併欄位(key)

right : 只使用右資料的合併欄位(key)

合併的另一種方法.join(),利用兩個 DataFrame 的索引標籤(index)進行連結操作,在這裡要注意,除了 date 是索引標籤(index)以外兩資料還有一個 open 欄位名稱重複,因為 join 不像 merge 會自動對於重複欄位產生字尾,所以需要參數 lsuffix、rsuffix 加上指定字尾。

利用欄位名稱選取單行資料

可以使用類似於列表跟字串的索引選取方法,直接使用 […] 的方式挑選出欄位:

import pandas as pddf = pd.DataFrame([[1, 2, 3], [4, 5, 6]], index=['a', 'b'], columns=['A', 'B', 'C'])print(df[['A', 'B']])
print(df[['A', 'C']])

利用列索引位置選取單列/多列資料

除了利用索引的方式之外,在列表也可以用切片取出部分的元素。在 DataFrame 則可以使用切片的方式選出以列為單位的資料:

import pandas as pddf = pd.DataFrame([[1, 2, 3], [4, 5, 6]], index=['a', 'b'], columns=['A', 'B', 'C'])print(df[0:1])
print(df[0:2])

用 loc, iloc, ix 取得行與列

前面兩種方法可以利用行或列的角度來選取資料,不過一次僅能做一個維度的篩選。為了有效的使用 DataFrame 二維的特性,在 Pandas 當中提供了一種座標選取的方法 .loc[…]:

import pandas as pddf = pd.DataFrame([[1, 2, 3], [4, 5, 6]], index=['a', 'b'], columns=['A', 'B', 'C'])print(df.loc['a', 'A']) # 1
print(df.loc['a', ['A', 'B']])
print(df.loc[['a', 'b'], 'A'])
print(df.loc[['a', 'b'], ['A', 'B']])

根據條件篩選資料(遮罩)

D11 Pandas 中的算術運算特性

相同欄位的算術運算

  • 在 DataFrame 的算術運算也有「對齊」的特性:
mport pandas as pddf1 = pd.DataFrame([[1, 2, 3]])
df2 = pd.DataFrame([[1, 1, 1]])
print(df1 + df2)
# 0 1 2
# 0 2 3 4
  • DataFrame 也有廣播的特性:在 DataFrame 的「廣播」的特性也比較嚴格,只有支援常數的廣播:

比較和邏輯運算

比較運算是用來判斷數值之間的比較關係,邏輯運算適用於布林值的組合。在 NumPy 當中,我們會利用比較/邏輯運算所產生的布林陣列作為遮罩的條件,從陣列中篩選出滿足條件的元素。

print(df > 2)
# A B C
# a False False True
# b True True True
print(df[df > 2])
# A B C
# a NaN NaN 3
# b 4.0 5.0 6

DataFrame 中的排序

import pandas as pddf = pd.DataFrame({
'col1': ['A', 'a', 'B', 'b'],
'col2': [2, 1, 9, 8],
})
df.sort_values(by=['col1'])

DataFrame 的字串方法

  • df.str.findall() :對所有欄位的文字進行 re.findall()
  • df.str.replace() :對所有欄位的文字進行取代
  • df.str.contains() :對所有欄位的文字進行包含子字串的檢查
  • df.str.count() :對所有欄位的文字進行計數操作
  • df.str.split() :對所有欄位的文字進行分割操作

統計函式 平均值mean()

  • 首先是最常使用到的平均值 mean(),pandas 可針對指定欄位算平均值,如果沒指定會對全部欄位算平均值。
  • 如果想要個別計算列或者是行總平均:

統計函式 加總sum() 、個數count()

  • 加總 : 計算總和,時常用在計算家庭開銷
  • 個數 : 計算個數,時常用在出遊時的點名

統計函式:中位數median()

  • 中位數通常使用在有否贏過 50% 的數據,假如薪資中為數為 4 萬,超過 4 萬即為贏過 50% 的人,反之亦然。
  • 中位數:通過把所有觀察值高低排序後找出正中間的一個作為中位數。 如果觀察值有偶數個,則中位數不唯一,通常取最中間的兩個數值的平均數作爲中位數。
  • 以利用中位數算出各科中位數,如果今天數學考了 60 分超過了中位數的 58 分,我就可以說我數學贏過了全班一半的同學。

統計函式:百分位數quantile()

  • 百分位數使用在觀察數據百分比,最常運用到的是升學分數的百分位數。
  • 百分位數:將一組數據從小到大排序,並計算相應的累計百分位,則某一百分位所對應數據的值就稱為這一百分位的百分位數。如果百分位數設定在 50% 即為中位數。

統計函式:最大值max()、最小值min()

統計函式:標準差std(),變異數var()

統計函式:相關係數corr()

相關係數 : 皮爾遜積矩相關係數(Pearson product-moment correlation coefficient)用於度量兩個變數X和Y之間的相關程度(線性相依)。在自然科學領域中,該係數廣泛用於度量兩個變數之間的線性相依程度。相關係數的值介於 –1 與 +1 之間,即 –1≤r≤+1。其性質如下:

  1. 當 r>0 時,表示兩變數正相關,r<0 時,兩變數為負相關,r=0 時,表示兩變數間無線性相關關係。
  2. 一般可按三級劃分:|r|<0.4 為低度線性相關;0.4≤|r|<0.7 為顯著性相關;0.7≤|r|<1為高度線性相關。

自訂義的行或列函式應用 apply()

D12 Pandas 迭代與重複操作

橫向的資料迭代

如果我們想要對以「筆」為單位的資料迭代的話,最暴力的方法可以這樣做:

import pandas as pddf = pd.DataFrame({
'name': ['Alice', 'Bob'],
'age': [20, 32]
})
for i in range(len(df)):
print(df.iloc[i])

第二種方法可以直接用 DataFrame 內建的 iterative 方法:

for d in df.iteritems():
print(d)
for d in df.iterrows():
print(d)
for d in df.itertuples():
print(d)
1.
('name', 0 Alice 1 Bob Name: name, dtype: object)
('age', 0 20 1 32 Name: age, dtype: int64)
2.
(0, name Alice age 20 Name: 0, dtype: object)
(1, name Bob age 32 Name: 1, dtype: object)
3.
Pandas(Index=0, name='Alice', age=20)
Pandas(Index=1, name='Bob', age=32)

apply

第三種方法是使用 Pandas 當中的 apply 方法,apply 是一種用於逐行或逐列的循環處理方法,常搭配 lambda 匿名函式一起使用:

df.apply(lambda x: x.max() - x.min())

map

另外一種跟 apply 很像的方法叫做 map:

df = pd.DataFrame({
'score': [98, 67, 85],
'age': [20, 32, 28]
})
df['age'].map(lambda x: -x)

applymap

在 Pandas 當中,有一種同時 apply 和 map 方法稱為 applymap:

df = pd.DataFrame({
'score': [98, 67, 85],
'age': [20, 32, 28]
})
df.applymap(lambda x: -x)

Map、 Apply、Applymap

接著,我們總結一下這三種類似的方法本質上有何差異:

  • map:對 series 所有元素作一樣的操作
  • apply:對 series 或 dataframe 逐行或逐列做一樣的操作
  • applymap:對 dataframe 所有元素做一樣的操作

不建議在 DataFrame 進行迭代操作

如同我們在 NumPy 提過的,不建議在 DataFrame 進行迭代操作。原因是這樣就回到了 Python 列表的操作,無法享受矩陣特性帶來的優勢。

D13 Pandas Dataframe 的新增與刪除

從 DataFrame 中插入或刪除資料

對於一個 DataFrame 可以進行新增或刪除的操作,又可以分為行或是列的方向:

  • = 可以用來增加行(欄)
  • append() 可以用來新增列(資料)
  • del 或 pop() 可以用來刪除行(欄)
  • drop() 可以用來刪除列(資料)

= 可以用來增加行(欄)

可以利用指派運算(=)值些產生新的欄位:

df = pd.DataFrame([[1], [2]], columns = ['a'])
df['b'] = pd.Series([3, 4])

append() 可以用來新增列(資料)

df = pd.DataFrame([[1, 2]], columns = ['a', 'b'])
df = df.append(pd.DataFrame([[3, 4]], columns = ['a', 'b']))

但仔細看一下,會發現索引重複了,這邊利用 reset_index() 修正:

df = df.append(pd.DataFrame([[3, 4]], columns = ['a', 'b']))
df = df.reset_index(drop=True)

del 或 pop() 可以用來刪除行(欄)

drop() 可以用來刪除列(資料)

DataFrame 的合併與重組

除了對於一個 DataFrame 的操作之外,我們也會需要 DataFrame 跟 DataFrame 之間的操作,常見可以分為以下幾種:

  • 連集(Concat)
  • 合併(merge)
  • 連接(Join)
  • 分組(Group)

連集(Concat)

利用連集(Concat)上下相拼:

one = pd.DataFrame({
'id':[1, 2],
'Name': ['Alex', 'Amy'],
})
two = pd.DataFrame({
'id':[1, 2],
'Name': ['Bob', 'Tom']
})
pd.concat([one, two])

有時候會有索引重複的現象,請務必要修正:

one = pd.DataFrame({
'id':[1, 2],
'Name': ['Alex', 'Amy'],
})
two = pd.DataFrame({
'id':[1, 2],
'Name': ['Bob', 'Tom']
})
pd.concat([one, two]).reset_index(drop=True)

合併(merge)

利用合併(merge)實現欄位左右相拼:

one = pd.DataFrame({
'id':[1, 2],
'Name': ['Alex', 'Amy'],
})
two = pd.DataFrame({
'id':[1, 2],
'Score': [98, 60]
})
pd.merge(one, two, on='id')

連接(Join)

利用連接(Join)實現索引左右相拼:

one = pd.DataFrame({
'Name': ['Alex', 'Amy'],
})
two = pd.DataFrame({
'Score': [98, 60]
})
one.join(two)

分組(Group)

另外有一種比較特別的用法稱為是分組(Group),會依照資料內容重新組裝:

df = pd.DataFrame({
'A' : ['foo', 'bar', 'foo', 'bar'],
'B' : ['one', 'one', 'two', 'three'],
'C' : [1,2,3,4],
'D' : [10, 20, 30, 40]
})
df.groupby('A').sum()
df.groupby('A').agg(sum)
df.groupby(['A','B']).sum()

D14 Pandas 的外部資料存取

建立 Pandas 物件

在 NumPy 中有很多種初始化陣列的方法,但是在 Pandas 當中我們都建議「先直接將資料轉換看看」,可以細分成兩種方法:

  • 從原有的物件轉換
  • 從外部資料讀取而來

認識類別資料

變數的特徵屬於非數值型態。需利用一組的標記、類別、性質或名稱以區別每個基本單位的特徵、屬性。無法以數值表示的統計資料,如航班編號、性別、學歷、旅遊同伴、頭髮顏色、宗教等。類別資料中可以分為兩類順序性與一般性兩種

  • 順序性 : 類別之間存在順序性,例如:衣服尺寸[XL,L,M]、長度[短,中,長]
  • 一般性 : 類別之間沒有順序關係,例如 : 顏色[黃,綠,藍]、性別[男,女]

大部分的模型都是基於數學運算,字串無法套入數學模型進行運算,在此先對其進行 encoding編碼(將類別資料轉成數字)才能進一步對其做分析。

對於順序性的類別資料,需要有順序性的 encoding 方法,可以使用 sklearn 中的 LabelEncoder()。

對於一般性的類別資料,則不需要有順序的編碼,可以使用 pandas 中的 get_dummies()

認識缺值處理方法與應用函式 定值補值 / 前後補值

df.fillna(number / mean .etc)
# 函式一樣使用 fillna(),我們只需要進一步運用參數method=‘ffill’即可填補前一列數值, method=‘bfill’ 填補後一列數值
temp_data.fillna(method='fill')

Missing Value 是什麼?

很常在讀寫資料的過程中,產出 NaN 的型態,那這個東西到底是什麼呢?

NaN 是一種被定義在 NumPy 中的特殊型態,全名為 Not a Number,通常被當成是「空值/缺失值」使用。

缺值資料的處理

缺值是程式或模型中無法正確計算的資料,必須先處理乾淨後才可以進行後面的操作。常見的處理策略如下:

  • 直接刪除含有缺失值的資料或欄位
  • 利用人工填補遺失值
  • 利用常數或通用值填補遺失值
  • 利用類似資料/全部資料的統計值值填補遺失值
  • 利用統計方法進行補值(內差/回歸)
  • 利用機器學習方法進行補值(預測)

D15 用 Pandas 撰寫樞紐分析表(略)

D16 用 Pandas 執行聚合運算(Split-Apply-Combine Strategy)

認識 groupby

在數據分析中時常會分析不同族群的資料,例如,學生分數資料(如表 1),你想分析男生與女生的各科差異,前幾天有教到檢索可以將資料分成男生資料與女生資料,在將各資料算平均值(如圖 2),在這裡有一個函數 goupby 可以一行指令執行以上的邏輯(下圖 3)。

你的 dataframe 變數名稱.groupby(['要分析之行的名稱']).運算函數名稱()
上圖為表一

認識 Split-Apply-Combine 策略

以剛剛學生資料來分解一下 groupby 的邏輯過程

  • Split:將大的數據集拆成可獨立計算的小數據集(拆成男生、女生資料)
  • Apply:獨立計算各個小數據集(成績取平均)
  • Combine:將小數據集運算結果合併

將 DataFrame 依照A、B 、C 拆成三個小數據集[split],各自計算總合[Apply],合併結果輸出[Combine]

  • 拆分成 A、B 、C 小數據集的方法為 groupby

Groupby 針對多個欄位做分析

Groupby 也可以針對多個欄位做分析,例如,學生成績資料多一欄位 c 班級(class),想對班級以及性別做分類,在 groupby 中加入兩個欄位名稱即可(如下圖),此時 groupby 自動會生成多維度索引(multiple index)

Groupby 針對欄位做多個分析

Groupby也可以針對欄位做多個分析,例如,學生成績資料,想針對性別做成績平均以及標準差的計算,在 groupby 後加入 agg() (如下圖),在 agg 中加入計算的邏輯(mean,std),此時 groupby 自動會生成多維度欄位(multiple columns)

注意 agg() 中要給的是函數名稱而非函數,所以在函數名稱後面不用加 ()。

Groupby 同時針對多個欄位做多個分析

  • Groupby 也可以同時針對多個欄位做多個分析,例如,學生成績資料,想針對性別、班級做成績平均以及最高分的計算
  • 合併了多欄位以及多分析

D17 Pandas 時間序列資料處理

時間序列資料處理

  • 所有資料中只要有時間關係就需要使用到時間序列的資料型態,因為資料之間是有時間關係的,資料之間的時間距離也不盡相同,例如下表,紅框內同樣差一個月,但是相差的天數不同
  • 以每個月月底資料來檢視,這組資料並無缺值,但是以日資料來看,就缺了很多筆資料了
  • 時間序列的資料非常注重間的間隔

可以運用 resample 函數將年轉成季,如沒有值的填上 nan。

df = pd.Series([1, 2, 3, 4, 5], index=pd.period_range('2021-01-01', freq='Y', periods=5))
df

更改時間頻率如果從年轉成季該怎麼做?

df.resample('Q', convention='start').asfreq()

D18 Pandas 大型資料處理與效能調校

效能調校

接下來有三個方法帶給大家,可以大幅減少程式的執行時間

  1. 讀取資料型態選最快速的
  2. 多使用內建函數
  3. 向量化的資料處理

--

--

Joe Chao

會計背景,但目前在管顧實習。喜歡很多事情,於是都選擇把它一一記錄下來。