Python 100 Days Day73 Introduction to Pandas-4
26 Sep 2022 | beginner pythonauthor: jackfrued
Pandas的應用-4
DataFrame的應用
數據分析
經過前面的學習,我們已經將數據準備就緒而且變成了我們想要的樣子,接下來就是最為重要的數據分析階段了。當我們拿到一大堆數據的時候,如何從數據中迅速的解讀出有價值的信息,這就是數據分析要解決的問題。首先,我們可以獲取數據的描述性統計信息,通過描述性統計信息,我們可以了解數據的集中趨勢和離散趨勢。
例如,我們有如下所示的學生成績表。
import numpy as np
import pandas as pd
scores = np.random.randint(50, 101, (5, 3))
names = ('關羽', '張飛', '趙雲', '馬超', '黃忠')
courses = ('語文', '數學', '英語')
df = pd.DataFrame(data=scores, columns=courses, index=names)
df
輸出:
語文 數學 英語
關羽 96 72 73
張飛 72 70 97
趙雲 74 51 79
馬超 100 54 54
黃忠 89 100 88
我們可以通過DataFrame對象的方法mean、max、min、std、var等方法分別獲取每個學生或每門課程的平均分、最高分、最低分、標準差、方差等信息,也可以直接通過describe方法直接獲取描述性統計信息,代碼如下所示。
計算每門課程成績的平均分。
df.mean()
輸出:
語文 86.2
數學 69.4
英語 78.2
dtype: float64
計算每個學生成績的平均分。
df.mean(axis=1)
輸出:
關羽 80.333333
張飛 79.666667
趙雲 68.000000
馬超 69.333333
黃忠 92.333333
dtype: float64
計算每門課程成績的方差。
df.var()
輸出:
語文 161.2
數學 379.8
英語 265.7
dtype: float64
說明:通過方差可以看出,數學成績波動最大,最不穩定。
獲取每門課程的描述性統計信息。
df.describe()
輸出:
語文 數學 英語
count 5.000000 5.000000 5.000000
mean 86.200000 69.400000 78.200000
std 12.696456 19.488458 16.300307
min 72.000000 51.000000 54.000000
25% 74.000000 54.000000 73.000000
50% 89.000000 70.000000 79.000000
75% 96.000000 72.000000 88.000000
max 100.000000 100.000000 97.000000
排序和Top-N
如果需要對數據進行排序,可以使用DataFrame對象的sort_values方法,該方法的by參數可以指定根據哪個列或哪些列進行排序,而ascending參數可以指定升序或是降序。例如,下面的代碼展示了如何將學生表按語文成績排降序。
df.sort_values(by='語文', ascending=False)
輸出:
語文 數學 英語
馬超 100 54 54
關羽 96 72 73
黃忠 89 100 88
趙雲 74 51 79
張飛 72 70 97
如果DataFrame數據量很大,排序將是一個非常耗費時間的操作。有的時候我們只需要獲得排前N名或後N名的數據,這個時候其實沒有必要對整個數據進行排序,而是直接利用堆結構找出Top-N的數據。DataFrame的nlargest和nsmallest方法就提供對Top-N操作的支持,代碼如下所示。
找出語文成績前3名的學生信息。
df.nlargest(3, '語文')
輸出:
語文 數學 英語
馬超 100 54 54
關羽 96 72 73
黃忠 89 100 88
找出數學成績最低的3名學生的信息。
df.nsmallest(3, '數學')
輸出:
語文 數學 英語
趙雲 74 51 79
馬超 100 54 54
張飛 72 70 97
分組聚合操作
我們先從 Excel 文件中讀取一組銷售數據,然後再為大家演示如何進行分組聚合操作。
df = pd.read_excel('2020年銷售數據.xlsx')
df.head()
說明:如果需要上面例子中的 Excel 文件,可以通過百度雲盤進行獲取。鏈接:https://pan.baidu.com/s/1NhWtYcpFzF72cxcsoDoXjQ?pwd=swg1,提取碼:swg1。
輸出:
銷售日期 銷售區域 銷售渠道 銷售訂單 品牌 售價 銷售數量
0 2020-01-01 上海 拼多多 182894-455 八匹馬 99 83
1 2020-01-01 上海 抖音 205635-402 八匹馬 219 29
2 2020-01-01 上海 天貓 205654-021 八匹馬 169 85
3 2020-01-01 上海 天貓 205654-519 八匹馬 169 14
4 2020-01-01 上海 天貓 377781-010 皮皮蝦 249 61
如果我們要統計每個銷售區域的銷售總額,可以先通過“售價”和“銷售數量”計算出銷售額,為DataFrame添加一個列,代碼如下所示。
df['銷售額'] = df['售價'] * df['銷售數量']
df.head()
輸出:
銷售日期 銷售區域 銷售渠道 銷售訂單 品牌 售價 銷售數量 銷售額
0 2020-01-01 上海 拼多多 182894-455 八匹馬 99 83 8217
1 2020-01-01 上海 抖音 205635-402 八匹馬 219 29 6351
2 2020-01-01 上海 天貓 205654-021 八匹馬 169 85 14365
3 2020-01-01 上海 天貓 205654-519 八匹馬 169 14 2366
4 2020-01-01 上海 天貓 377781-010 皮皮蝦 249 61 15189
然後再根據“銷售區域”列對數據進行分組,這里我們使用的是DataFrame對象的groupby方法。分組之後,我們取“銷售額”這個列在分組內進行求和處理,代碼和結果如下所示。
df.groupby('銷售區域').銷售額.sum()
輸出:
銷售區域
上海 11610489
北京 12477717
南京 1767301
安徽 895463
廣東 1617949
江蘇 537079
浙江 687862
福建 10178227
Name: 銷售額, dtype: int64
如果我們要統計每個月的銷售總額,我們可以將“銷售日期”作為groupby`方法的參數,當然這里需要先將“銷售日期”處理成月,代碼和結果如下所示。
df.groupby(df['銷售日期'].dt.month).銷售額.sum()
輸出:
銷售日期
1 5409855
2 4608455
3 4164972
4 3996770
5 3239005
6 2817936
7 3501304
8 2948189
9 2632960
10 2375385
11 2385283
12 1691973
Name: 銷售額, dtype: int64
接下來我們將難度升級,統計每個銷售區域每個月的銷售總額,這又該如何處理呢?事實上,groupby方法的第一個參數可以是一個列表,列表中可以指定多個分組的依據,大家看看下面的代碼和輸出結果就明白了。
df.groupby(['銷售區域', df['銷售日期'].dt.month]).銷售額.sum()
輸出:
銷售區域 銷售日期
上海 1 1679125
2 1689527
3 1061193
4 1082187
5 841199
6 785404
7 863906
8 734937
9 1107693
10 412108
11 825169
12 528041
北京 1 1878234
2 1807787
3 1360666
4 1205989
5 807300
6 1216432
7 1219083
8 645727
9 390077
10 671608
11 678668
12 596146
南京 7 841032
10 710962
12 215307
安徽 4 341308
5 554155
廣東 3 388180
8 469390
9 365191
11 395188
江蘇 4 537079
浙江 3 248354
8 439508
福建 1 1852496
2 1111141
3 1106579
4 830207
5 1036351
6 816100
7 577283
8 658627
9 769999
10 580707
11 486258
12 352479
Name: 銷售額, dtype: int64
如果希望統計出每個區域的銷售總額以及每個區域單筆金額的最高和最低,我們可以在DataFrame或Series對象上使用agg方法並指定多個聚合函數,代碼和結果如下所示。
df.groupby('銷售區域').銷售額.agg(['sum', 'max', 'min'])
輸出:
sum max min
銷售區域
上海 11610489 116303 948
北京 12477717 133411 690
南京 1767301 87527 1089
安徽 895463 68502 1683
廣東 1617949 120807 990
江蘇 537079 114312 3383
浙江 687862 90909 3927
福建 10178227 87527 897
如果希望自定義聚合後的列的名字,可以使用如下所示的方法。
df.groupby('銷售區域').銷售額.agg(銷售總額='sum', 單筆最高='max', 單筆最低='min')
輸出:
銷售總額 單筆最高 單筆最低
銷售區域
上海 11610489 116303 948
北京 12477717 133411 690
南京 1767301 87527 1089
安徽 895463 68502 1683
廣東 1617949 120807 990
江蘇 537079 114312 3383
浙江 687862 90909 3927
福建 10178227 87527 897
如果需要對多個列使用不同的聚合函數,例如“統計每個銷售區域銷售額的平均值以及銷售數量的最低值和最高值”,我們可以按照下面的方式來操作。
df.groupby('銷售區域')[['銷售額', '銷售數量']].agg({
'銷售額': 'mean', '銷售數量': ['max', 'min']
})
輸出:
銷售額 銷售數量
mean max min
銷售區域
上海 20622.538188 100 10
北京 20125.350000 100 10
南京 22370.898734 100 11
安徽 26337.147059 98 16
廣東 32358.980000 98 10
江蘇 29837.722222 98 15
浙江 27514.480000 95 20
福建 18306.163669 100 10
透視表和交叉表
上面的例子中,“統計每個銷售區域每個月的銷售總額”會產生一個看起來很長的結果,在實際工作中我們通常把那些行很多列很少的表成為“窄表”,如果我們不想得到這樣的一個“窄表”,可以使用DataFrame的pivot_table方法或者是pivot_table函數來生成透視表。透視表的本質就是對數據進行分組聚合操作,根據 A 列對 B 列進行統計,如果大家有使用 Excel 的經驗,相信對透視表這個概念一定不會陌生。例如,我們要“統計每個銷售區域的銷售總額”,那麽“銷售區域”就是我們的 A 列,而“銷售額”就是我們的 B 列,在pivot_table函數中分別對應index和values參數,這兩個參數都可以是單個列或者多個列。
pd.pivot_table(df, index='銷售區域', values='銷售額', aggfunc='sum')
輸出:

注意:上面的結果操作跟之前用
groupby的方式得到的結果有一些區別,groupby操作後,如果對單個列進行聚合,得到的結果是一個Series對象,而上面的結果是一個DataFrame對象。
如果要統計每個銷售區域每個月的銷售總額,也可以使用pivot_table函數,代碼如下所示。
pd.pivot_table(df, index=['銷售區域', df['銷售日期'].dt.month], values='銷售額', aggfunc='sum')
上面的操作結果是一個DataFrame,但也是一個長長的“窄表”,如果希望做成一個行比較少列比較多的“寬表”,可以將index參數中的列放到columns參數中,代碼如下所示。
pd.pivot_table(
df, index='銷售區域', columns=df['銷售日期'].dt.month,
values='銷售額', aggfunc='sum', fill_value=0
)
說明:
pivot_table函數的fill_value=0會將空值處理為0。
輸出:

使用pivot_table函數時,還可以通過添加margins和margins_name參數對分組聚合的結果做一個匯總,具體的操作和效果如下所示。
df['月份'] = df['銷售日期'].dt.month
pd.pivot_table(
df, index='銷售區域', columns='月份',
values='銷售額', aggfunc='sum', fill_value=0,
margins=True, margins_name='總計'
)
輸出:

交叉表就是一種特殊的透視表,它不需要先構造一個DataFrame對象,而是直接通過數組或Series對象指定兩個或多個因素進行運算得到統計結果。例如,我們要統計每個銷售區域的銷售總額,也可以按照如下所示的方式來完成,我們先準備三組數據。
sales_area, sales_month, sales_amount = df['銷售區域'], df['月份'], df['銷售額']
使用crosstab函數生成交叉表。
pd.crosstab(
index=sales_area, columns=sales_month, values=sales_amount, aggfunc='sum'
).fillna(0).applymap(int)
說明:上面的代碼使用了
DataFrame對象的fillna方法將空值處理為0,再使用applymap方法將數據類型處理成整數。
數據可視化
一圖勝千言,我們對數據進行透視的結果,最終要通過圖表的方式呈現出來,因為圖表具有極強的表現力,能夠讓我們迅速的解讀數據中隱藏的價值。和Series一樣,DataFrame對象提供了plot方法來支持繪圖,底層仍然是通過matplotlib庫實現圖表的渲染。關於matplotlib的內容,我們在下一個章節進行詳細的探討,這里我們只簡單的講解plot方法的用法。
例如,我們想通過一張柱狀圖來比較“每個銷售區域的銷售總額”,可以直接在透視表上使用plot方法生成柱狀圖。我們先導入matplotlib.pyplot模塊,通過修改繪圖的參數使其支持中文顯示。
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'FZJKai-Z03S'
說明:上面的
FZJKai-Z03S是我電腦上已經安裝的一種支持中文的字體的名稱,字體的名稱可以通過查看用戶主目錄下.matplotlib文件夾下名為fontlist-v330.json的文件來獲得,而這個文件在執行上面的命令後就會生成。
使用魔法指令配置生成矢量圖。
%config InlineBackend.figure_format = 'svg'
繪制“每個銷售區域銷售總額”的柱狀圖。
temp = pd.pivot_table(df, index='銷售區域', values='銷售額', aggfunc='sum')
temp.plot(figsize=(8, 4), kind='bar')
plt.xticks(rotation=0)
plt.show()
說明:上面的第3行代碼會將橫軸刻度上的文字旋轉到0度,第4行代碼會顯示圖像。
輸出:

如果要繪制餅圖,可以修改plot方法的kind參數為pie,然後使用定制餅圖的參數對圖表加以定制,代碼如下所示。
temp.sort_values(by='銷售額', ascending=False).plot(
figsize=(6, 6), kind='pie', y='銷售額',
autopct='%.2f%%', pctdistance=0.8,
wedgeprops=dict(linewidth=1, width=0.35)
)
plt.legend(loc='center')
plt.show()
輸出:

Comments