(點選上方公眾號,可快速關註)
來源:強波的技術部落格
qiangbo.space/2018-03-12/advance_pandas/
在前面一篇文章中,我們對pandas做了一些入門介紹。本文是它的進階篇。在這篇文章中,我們會講解一些更深入的知識。
前言
本文緊接著前一篇的入門教程,會介紹一些關於pandas的進階知識。建議讀者在閱讀本文之前先看完pandas入門教程。 同樣的,本文的測試資料和原始碼可以在這裡獲取: Github:pandas_tutorial 。
資料訪問
在入門教程中,我們已經使用過訪問資料的方法。這裡我們再集中看一下。
註:這裡的資料訪問方法既適用於Series,也適用於DataFrame。
基礎方法:[]和.
這是兩種最直觀的方法,任何有面向物件程式設計經驗的人應該都很容易理解。下麵是一個程式碼示例:
# select_data.py
import pandas as pd
import numpy as np
series1 = pd.Series([1, 2, 3, 4, 5, 6, 7],
index=[“C”, “D”, “E”, “F”, “G”, “A”, “B”])
print(“series1[‘E’] = {} \n”.format(series1[‘E’]));
print(“series1.E = {} \n”.format(series1.E));
這段程式碼輸出如下:
series1[‘E’] = 3
series1.E = 3
註1:對於類似屬性的訪問方式.來說,要求索引元素必須是有效的Python識別符號的時候才可以,而對於series1.1這樣的索引是不行的。
註2:[]和.提供了簡單和快速訪問pands資料結構的方法。這種方法非常的直觀。然而,由於要訪問的資料型別並不是事先知道的,因此使用這兩種方法方式存在一些最佳化限制。因此對於產品級的程式碼來說,pandas官方建議使用pandas庫中提供的資料訪問方法。
loc與iloc
在入門教程中,我們已經提到了這兩個運運算元:
-
loc:透過行和列的索引來訪問資料
-
iloc:透過行和列的下標來訪問資料
註意:索引的型別可能是整數。
實際上,當DataFrame透過這兩個運運算元訪問資料,可以只指定一個索引來訪問一行的資料,例如:
# select_data.py
df1 = pd.DataFrame({“note” : [“C”, “D”, “E”, “F”, “G”, “A”, “B”],
“weekday”: [“Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”, “Sun”]},
index=[‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’])
print(“df1.loc[‘2’]:\n{}\n”.format(df1.loc[‘2’]))
這裡透過索引’2’可以方法到第2行的所有資料,因此它的輸出如下:
df1.loc[‘2’]:
note D
weekday Tue
Name: 2, dtype: object
除此之外,透過這兩個運運算元我們還可以訪問某個範圍之內的資料,例如這樣:
# select_data.py
print(“series1.loc[‘E’:’A’]=\n{}\n”.format(series1.loc[‘E’:‘A’]));
print(“df1.iloc[2:4]=\n{}\n”.format(df1.iloc[2:4]))
這段程式碼輸出如下:
series1.loc[‘E’:‘A’]=
E 3
F 4
G 5
A 6
dtype: int64
df1.iloc[2:3]=
note weekday
3 E Wed
4 F Thu
at與iat
這兩個運運算元用來訪問單個的元素值(Scalar Value)。類似的:
-
at:透過行和列的索引來訪問資料
-
iat:透過行和列的下標來訪問資料
# select_data.py
print(“series1.at[‘E’]={}\n”.format(series1.at[‘E’]));
print(“df1.iloc[4,1]={}\n”.format(df1.iloc[4,1]))
這兩行程式碼輸出如下:
series1.at[‘E’]=3
df1.iloc[4,1]=Fri
Index物件
在入門教程我們也已經簡單介紹過Index,Index提供了查詢,資料對齊和重新索引所需的基礎資料結構。 最直接的,我們可以透過一個陣列來建立Index物件。在建立的同時我們還可以透過name指定索引的名稱:
# index.py
index = pd.Index([‘C’,‘D’,‘E’,‘F’,‘G’,‘A’,‘B’], name=‘note’)
Index類提供了很多的方法進行各種操作,這個建議讀者直接查詢API說明即可,這裡不多做說明。稍微提一下的是,Index物件可以互相之間做集合操作,例如:
# index.py
a = pd.Index([1,2,3,4,5])
b = pd.Index([3,4,5,6,7])
print(“a|b = {}\n”.format(a|b))
print(“a&b; = {}\n”.format(a&b))
print(“a.difference(b) = {}\n”.format(a.difference(b)))
這幾個運算的結果如下:
a|b = Int64Index([1, 2, 3, 4, 5, 6, 7], dtype=‘int64’)
a&b = Int64Index([3, 4, 5], dtype=‘int64’)
a.difference(b) = Int64Index([1, 2], dtype=‘int64’)
Index類有很多的子類,下麵是最常見的一些:
-
RangeIndex
-
CategoricalIndex
-
MultiIndex
-
IntervalIndex
-
DatetimeIndex
-
…
MultiIndex
MultiIndex,或者稱之為Hierarchical Index是指資料的行或者列透過多層次的標簽來進行索引。 例如,我們要透過一個MultiIndex描述三個公司在三年內每個季度的營業額,可以這樣:
# multiindex.py
import pandas as pd
import numpy as np
multiIndex = pd.MultiIndex.from_arrays([
[‘Geagle’, ‘Geagle’, ‘Geagle’, ‘Geagle’,
‘Epple’, ‘Epple’, ‘Epple’, ‘Epple’, ‘Macrosoft’,
‘Macrosoft’, ‘Macrosoft’, ‘Macrosoft’, ],
[‘S1’, ‘S2’, ‘S3’, ‘S4’, ‘S1’, ‘S2’, ‘S3’, ‘S4’, ‘S1’, ‘S2’, ‘S3’, ‘S4’]],
names=(‘Company’, ‘Turnover’))
這段程式碼輸出如下:
multiIndex =
MultiIndex(levels=[[‘Epple’, ‘Geagle’, ‘Macrosoft’], [‘S1’, ‘S2’, ‘S3’, ‘S4’]],
labels=[[1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, 2], [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]],
names=[‘Company’, ‘Turnover’])
從這個輸出可以看出,MultiIndex中的levels陣列數量對應了索引的級別數量,labels對應了levels中元素的下標。 下麵我們用一個隨機數來構造一個DataFrame:
# multiindex.py
df = pd.DataFrame(data=np.random.randint(0, 1000, 36).reshape(–1, 12),
index=[2016, 2017, 2018],
columns=multiIndex)
print(“df = \n{}\n”.format(df))
這裡創建出了36個[0, 1000)之間的隨機數,然後組裝成3行12列的矩陣(如果你對NumPy不熟悉可以訪問NumPy官網學習,或者看一下我之前寫過的:Python 機器學習庫 NumPy 教程)。 上面這段程式碼輸出如下:
這個輸出很直觀的可以看出三個公司在三年內每個季度的營業額。 有了多級索引,我們可以方便的進行資料的篩選,例如:
-
透過df.loc[2017, ([‘Geagle’, ‘Epple’, ‘Macrosoft’] ,’S1′)])篩選出三個公司2017年第一季度的營業額
-
透過df.loc[2018, ‘Geagle’]篩選出Geagle公司2018年每個季度的營業額
它們輸出如下:
2017 S1:
Company Turnover
Geagle S1 734
Epple S1 187
Macrosoft S1 244
Name: 2017, dtype: int64
Geagle 2018:
Turnover
S1 769
S2 707
S3 458
S4 782
Name: 2018, dtype: int64
資料整合
Concat(enate):串聯,連線,級連
Append:附加,增補
Merge:融合,歸併,合併
Join:合併,接合,交接
Concat與Append
concat函式的作用是將多個資料串聯到一起。例如,某個多條資料分散在3個地方記錄,最後我們將三個資料新增到一起。下麵是一個程式碼示例:
# concat_append.py
import pandas as pd
import numpy as np
df1 = pd.DataFrame({‘Note’: [‘C’, ‘D’],
‘Weekday’: [‘Mon’, ‘Tue’]},
index=[1, 2])
df2 = pd.DataFrame({‘Note’: [‘E’, ‘F’],
‘Weekday’: [‘Wed’, ‘Thu’]},
index=[3, 4])
df3 = pd.DataFrame({‘Note’: [‘G’, ‘A’, ‘B’],
‘Weekday’: [‘Fri’, ‘Sat’, ‘Sun’]},
index=[5, 6, 7])
df_concat = pd.concat([df1, df2, df3], keys=[‘df1’, ‘df2’, ‘df3’])
print(“df_concat=\n{}\n”.format(df_concat))
這裡我們透過keys指定了三個資料的索引劃分,最後的資料中會由此存在MultiIndex。這段程式碼輸出如下:
df_concat=
Note Weekday
df1 1 C Mon
2 D Tue
df2 3 E Wed
4 F Thu
df3 5 G Fri
6 A Sat
7 B Sun
請仔細思考一下df_concat結構與原先三個資料結構的關係:其實它就是將原先三個資料縱向串聯起來了。另外,請關註一下MultiIndex結構。 concat函式預設是以axis=0(行)為主進行串聯。如果需要,我們可以指定axis=1(列)為主進行串聯:
# concat_append.py
df_concat_column = pd.concat([df1, df2, df3], axis=1)
print(“df_concat_column=\n{}\n”.format(df_concat_column))
這個結構輸出如下:
請再次觀察一下這裡的結果和原先三個資料結構之間的關係。 concat是將多個資料串聯在一起。類似的,對於某個具體的資料來說,我們可以在其資料基礎上新增(append)其他資料來進行串聯:
# concat_append.py
df_append = df1.append([df2, df3])
print(“df_append=\n{}\n”.format(df_append))
這個操作的結果和之前的concat是一樣的:
df_append=
Note Weekday
1 C Mon
2 D Tue
3 E Wed
4 F Thu
5 G Fri
6 A Sat
7 B Sun
Merge與Join
pandas中的Merge操作和SQL陳述句中的Join操作是類似的。Join操作可以分為下麵幾種:
-
INNER
-
LEFT OUTER
-
RIGHT OUTER
-
FULL OUTER
-
CROSS
關於這幾種的Join操作的含義請參閱其他資料,例如維基百科:Join (SQL)。 使用pandas進行Merge操作很簡單,下麵是一段程式碼示例:
# merge_join.py
import pandas as pd
import numpy as np
df1 = pd.DataFrame({‘key’: [‘K1’, ‘K2’, ‘K3’, ‘K4’],
‘A’: [‘A1’, ‘A2’, ‘A3’, ‘A8’],
‘B’: [‘B1’, ‘B2’, ‘B3’, ‘B8’]})
df2 = pd.DataFrame({‘key’: [‘K3’, ‘K4’, ‘K5’, ‘K6’],
‘A’: [‘A3’, ‘A4’, ‘A5’, ‘A6’],
‘B’: [‘B3’, ‘B4’, ‘B5’, ‘B6’]})
print(“df1=n{}n”.format(df1))
print(“df2=n{}n”.format(df2))
merge_df = pd.merge(df1, df2)
merge_inner = pd.merge(df1, df2, how=‘inner’, on=[‘key’])
merge_left = pd.merge(df1, df2, how=‘left’)
merge_left_on_key = pd.merge(df1, df2, how=‘left’, on=[‘key’])
merge_right_on_key = pd.merge(df1, df2, how=‘right’, on=[‘key’])
merge_outer = pd.merge(df1, df2, how=‘outer’, on=[‘key’])
print(“merge_df=\n{}\n”.format(merge_df))
print(“merge_inner=\n{}\n”.format(merge_inner))
print(“merge_left=\n{}\n”.format(merge_left))
print(“merge_left_on_key=\n{}\n”.format(merge_left_on_key))
print(“merge_right_on_key=\n{}\n”.format(merge_right_on_key))
print(“merge_outer=\n{}\n”.format(merge_outer))
這段程式碼說明如下:
-
merge函式的join引數的預設值是“inner”,因此merge_df是兩個資料的inner join的結果。另外,在不指明的情況下,merge函式使用所有同名的列名作為key來進行運算。
-
merge_inner是指定了列的名稱進行inner join。
-
merge_left是left outer join的結果
-
merge_left_on_key是指定了列名進行left outer join的結果
-
merge_right_on_key是指定了列名進行right outer join的結果
-
merge_outer是full outer join的結果
這裡的結果如下,請觀察一下結果與你的預算是否一致:
df1=
A B key
0 A1 B1 K1
1 A2 B2 K2
2 A3 B3 K3
3 A8 B8 K4
df2=
A B key
0 A3 B3 K3
1 A4 B4 K4
2 A5 B5 K5
3 A6 B6 K6
merge_df=
A B key
0 A3 B3 K3
merge_inner=
A_x B_x key A_y B_y
0 A3 B3 K3 A3 B3
1 A8 B8 K4 A4 B4
merge_left=
A B key
0 A1 B1 K1
1 A2 B2 K2
2 A3 B3 K3
3 A8 B8 K4
merge_left_on_key=
A_x B_x key A_y B_y
0 A1 B1 K1 NaN NaN
1 A2 B2 K2 NaN NaN
2 A3 B3 K3 A3 B3
3 A8 B8 K4 A4 B4
merge_right_on_key=
A_x B_x key A_y B_y
0 A3 B3 K3 A3 B3
1 A8 B8 K4 A4 B4
2 NaN NaN K5 A5 B5
3 NaN NaN K6 A6 B6
merge_outer=
A_x B_x key A_y B_y
0 A1 B1 K1 NaN NaN
1 A2 B2 K2 NaN NaN
2 A3 B3 K3 A3 B3
3 A8 B8 K4 A4 B4
4 NaN NaN K5 A5 B5
5 NaN NaN K6 A6 B6
DataFrame也提供了join函式來根據索引進行資料合併。它可以被用於合併多個DataFrame,這些DataFrame有相同的或者類似的索引,但是沒有重覆的列名。預設情況下,join函式執行left join。另外,假設兩個資料有相同的列名,我們可以透過lsuffix和rsuffix來指定結果中列名的字首。下麵是一段程式碼示例:
# merge_join.py
df3 = pd.DataFrame({‘key’: [‘K1’, ‘K2’, ‘K3’, ‘K4’],
‘A’: [‘A1’, ‘A2’, ‘A3’, ‘A8’],
‘B’: [‘B1’, ‘B2’, ‘B3’, ‘B8’]},
index=[0, 1, 2, 3])
df4 = pd.DataFrame({‘key’: [‘K3’, ‘K4’, ‘K5’, ‘K6’],
‘C’: [‘A3’, ‘A4’, ‘A5’, ‘A6’],
‘D’: [‘B3’, ‘B4’, ‘B5’, ‘B6’]},
index=[1, 2, 3, 4])
print(“df3=\n{}\n”.format(df3))
print(“df4=\n{}\n”.format(df4))
join_df = df3.join(df4, lsuffix=‘_self’, rsuffix=‘_other’)
join_left = df3.join(df4, how=‘left’, lsuffix=‘_self’, rsuffix=‘_other’)
join_right = df1.join(df4, how=‘outer’, lsuffix=‘_self’, rsuffix=‘_other’)
print(“join_df=\n{}\n”.format(join_df))
print(“join_left=\n{}\n”.format(join_left))
print(“join_right=\n{}\n”.format(join_right))
這段程式碼輸出如下:
資料集合和分組操作
很多時候,我們會需要對批次的資料進行分組統計或者再處理,groupby,agg,apply就是用來做這件事的。
-
groupby將資料分組,分組後得到pandas.core.groupby.DataFrameGroupBy型別的資料。
-
agg用來進行合計操作,agg是aggregate的別名。
-
apply用來將函式func分組化並將結果組合在一起。
這些概念都很抽象,我們還是透過程式碼來進行說明。
# groupby.py
import pandas as pd
import numpy as np
df = pd.DataFrame({
‘Name’: [‘A’,‘A’,‘A’,‘B’,‘B’,‘B’,‘C’,‘C’,‘C’],
‘Data’: np.random.randint(0, 100, 9)})
print(‘df=\n{}\n’.format(df))
groupby = df.groupby(‘Name’)
print(“Print GroupBy:”)
for name, group in groupby:
print(“Name: {}\nGroup:\n{}\n”.format(name, group))
在這段程式碼中,我們生成了9個[0, 100)之間的隨機數,資料的第一列是[‘A’,’A’,’A’,’B’,’B’,’B’,’C’,’C’,’C’]。然後我們以Name列進行groupby,得到的結果會根據將Name列值一樣的分組在一起,我們將得到的結果進行了列印。這段程式碼的輸出如下:
df=
Data Name
0 34 A
1 44 A
2 57 A
3 81 B
4 78 B
5 65 B
6 73 C
7 16 C
8 1 C
Print GroupBy:
Name: A
Group:
Data Name
0 34 A
1 44 A
2 57 A
Name: B
Group:
Data Name
3 81 B
4 78 B
5 65 B
Name: C
Group:
Data Name
6 73 C
7 16 C
8 1 C
groupby並不是我們的最終目的,我們的目的是希望分組後還要對這些資料進行進一步的統計或者處理。pandas庫本身就提供了很多進行操作的函式,例如:count,sum,mean,median,std,var,min,max,prod,first,last。這些函式的名稱很容易明白它的作用。 例如:groupby.sum()就是對結果進行求和執行。 除了直接呼叫這些函式之外,我們也可以透過agg函式來達到這個目的,這個函式接收其他函式的名稱,例如這樣:groupby.agg([‘sum’])。 透過agg函式,可以一次性呼叫多個函式,並且可以為結果列指定名稱。 像這樣:groupby.agg([(‘Total’, ‘sum’), (‘Min’, ‘min’)])。 這裡的三個呼叫輸出結果如下:
# groupby.py
Sum:
Data
Name
A 135
B 224
C 90
Agg Sum:
Data
sum
Name
A 135
B 224
C 90
Agg Map:
Data
Total Min
Name
A 135 34
B 224 65
C 90 1
除了對資料集合進行統計,我們也可以透過apply函式進行分組資料的處理。像這樣:
# groupby.py
def sort(df):
return df.sort_values(by=‘Data’, ascending=False)
print(“Sort Group: \n{}\n”.format(groupby.apply(sort)))
在這段程式碼中,我們定義了一個排序函式,並應用在分組資料上,這裡最終的輸出如下:
Sort Group:
Data
Name
A 2 57
1 44
0 34
B 3 81
4 78
5 65
C 6 73
7 16
8 1
時間相關
時間是應用程式中很頻繁需要處理的邏輯,尤其是對於金融,科技,商業等領域。 當我們在討論時間,我們討論的可能是下麵三種情況中的一種:
-
某個具體的時間點(Timestamp),例如:今天下午一點整
-
某個時間範圍(Period),例如:整個這個月
-
某個時間間隔(Interval),例如:每週二上午七點整
Python語言提供了時間日期相關的基本API,它們位於datetime, time, calendar幾個模組中。下麵是一個程式碼示例:
# time.py
import datetime as dt
import numpy as np
import pandas as pd
now = dt.datetime.now();
print(“Now is {}”.format(now))
yesterday = now – dt.timedelta(1);
print(“Yesterday is {}\n”.format(yesterday.strftime(‘%Y-%m-%d’)))
在這段程式碼中,我們列印了今天的日期,並透過timedelta進行了日期的減法運算。這段程式碼輸出如下: 藉助pandas提供的介面,我們可以很方便的獲得以某個時間間隔的時間序列,例如這樣:
# time.py
this_year = pd.date_range(dt.datetime(2018, 1, 1),
dt.datetime(2018, 12, 31), freq=‘5D’)
print(“Selected days in 2018: \n{}\n”.format(this_year))
這段程式碼獲取了整個2018年中從元旦開始,每隔5天的日期序列。 date_range函式的詳細說明見這裡:pandas.date_range 這段程式碼的輸出如下:
我們得到的傳回值是DatetimeIndex型別的,我們可以建立一個DataFrame並以此作為索引:
# time.py
df = pd.DataFrame(np.random.randint(0, 100, this_year.size), index=this_year)
print(“Jan: \n{}\n”.format(df[‘2018-01’]))
在這段程式碼中,我們建立了與索引數量一樣多的[0, 100)間的隨機整數,並用this_year作為索引。用DatetimeIndex作索引的好處是,我們可以直接指定某個範圍來選擇資料,例如,透過df[‘2018-01’]選出所有1月份的資料。 這段程式碼輸出如下:
Jan:
0
2018–01–01 61
2018–01–06 85
2018–01–11 66
2018–01–16 11
2018–01–21 34
2018–01–26 2
2018–01–31 97
圖形展示
pandas的圖形展示依賴於matplotlib庫。對於這個庫,我們在後面會專門講解,因為這裡僅僅提供一個簡單的程式碼示例,讓大家感受一下圖形展示的樣子。 程式碼示例如下:
# plot.py
import matplotlib.pyplot as plt
import pandas as pd
data = pd.read_csv(“data/housing.csv”)
data.hist(bins=50, figsize=(15, 12))
plt.show()
這段程式碼讀取了一個CSV檔案,這個檔案中包含了一些關於房價的資訊。在讀取完之後,透過直方圖(hist)將其展示了出來。 該CSV檔案的內容見這裡:pandas_tutorial/data/housing.csv 直方圖結果如下所示:
結束語
雖然本文的標題是“進階篇”,我們也討論了一些更深入的知識。但很顯然,這對於pandas來說,仍然是很皮毛的東西。由於篇幅所限,更多的內容在今後的時候,有機會我們再來一起探討。 讀者朋友也可以根據官網上的檔案進行更深入的學習。
看完本文有收穫?請轉發分享給更多人
關註「資料分析與開發」,提升資料技能