COM6001 Computing with Big Data · Week 10
EDA (2): Group By, Pivot Tables & Intro to Data Visualization
COM6001 · Week 10 · Source: Lecture10_EDA_2.pdf(Jupyter notebook export, 35 pages)· Lecturer: Dr. Aileen Hou
Contents · 17 sections
🎯 本講重點 (Learning outcomes)
- 明白 groupby 嘅三個步驟:split → apply → combine,同
DataFrameGroupByobject 係乜。 - 識用 groupby methods:
get_group()、groups.keys()、first()/last()、count()vssize()。 - 對 groups 套 built-in function(
mean()等)、custom function、lambda function;用agg()傳 list(同一組 functions)或 dict(每個 column 唔同 function);用rename(columns=…)改名。 - 知道 group → selection → function 嘅次序最快(time profiling)。
- Multi-level groupby:
by=['quarter','TEMP_category'],次序決定 grouping hierarchy。 - pivot_table:
values、index、columns、aggfunc、fill_value、margins、margins_name;識由 output 反推 statement。 - 點解要 data visualization(下星期重點);semi-structured data(XML / JSON)重溫(programming 唔考)。
⭐ 考試必溫 Top-5
- Groupby = split → apply → combine;
df.groupby(by=…)本身只係一個DataFrameGroupByobject((key, sub-DataFrame) tuples),要再套 function 先有結果。 count()= 每個 column 嘅 non-NA 數目(DataFrame);size()= 每個 group 嘅總行數(Series,包 NaN)。agg(['mean','max'])= 所有 non-grouper columns 用同一組 functions;agg({'TEMP':['mean','max'], 'PM2.5':'mean'})= 唔同 columns 唔同 functions。Built-in 加引號,自訂 function 唔加。- 寫法
df.groupby(by='quarter')['TEMP'].mean()(group → selection → function)最快又最短;caller DataFrame 一定要包含 grouper column。 pd.pivot_table(df, values=, index=, columns=, aggfunc='mean', fill_value=, margins=):groupby 只會將 groups 放喺 index,pivot table 可以將 groups 同時放喺 index 同 columns(2D grouping);考試會要你由 output 寫返個 statement。
EDA 主要類型重溫:Summary statistics(Aggregating statistics、Sampling、Correlations 係上堂;Groupby、Pivoting tables 係今堂),Visualization 係之後幾堂。Section 3 Semi-Structured Data 標明「programing not required by exam」,內容同 Week 9 最尾一樣。最尾 Summary 列出:Groupby(syntax、single-level with built-in / defined / lambda functions、multi-level)同 Pivot Table(2D grouping、column / index / value、write out the statement based on results)。
0. Data preparation:air_quality dataset
import pandas as pd
path = './Datasets/week_10/'
air_quality = pd.read_pickle(path + 'air_quality.pkl')
air_quality.info()
# RangeIndex: 95685 entries, 27 columns
# date_time (datetime64), PM2.5, PM10, SO2, NO2, CO, O3, TEMP, PRES, DEWP, RAIN,
# wd (object), WSPM, station (object), year, month, day, hour, quarter,
# day_of_week_num, day_of_week_name, time_until_2022 (timedelta64),
# time_until_2022_days, time_until_2022_weeks, prior_2016_ind (bool),
# PM2.5_cat (category), TEMP_category (category)
呢個係之前幾個星期清理同 feature engineering 完嘅北京空氣質素 data(3 個 stations:Dongsi、Gucheng、Tiantan)。所有 columns 都冇 missing values。留意 notebook 用 read_pickle 而唔係 read_csv:pickle 會保留 dtype(例如 category、datetime64),CSV 讀返嚟就要自己再指定 dtype=。
Grouper 應該係 categorical(或者少量 distinct values 嘅)feature:quarter、month、year、station、wd、TEMP_category、PM2.5_cat、day_of_week_name。用 PM2.5 呢類 continuous 數值做 grouper 就會有幾百個 groups,冇意義(除非先 binning,即係 Week 7 整 TEMP_category 嗰種做法)。
1. Group By 概念
- Split:根據某啲 criteria(categorical groupers)將 data 分成 groups。
- Apply:對每個 group 獨立套用一個 function。
- Combine:將各 group 嘅 function 結果合併成一個 data structure。
用途例子:計每個 category group 嘅 statistics(例如每個月嘅平均 PM2.5)。
左邊 my_data:type / value = (a, 5)、(a, 3)、(b, 7)、(a, 8)、(b, 2)。Split based on type 分成兩個細 table:a 組 (5, 3, 8) 同 b 組 (7, 2)。每組各自 apply mean():a → 5.33、b → 4.5。最後 Combine 成一個結果 table:type a 5.33、b 4.5。即係 my_data.groupby('type')['value'].mean()。
1.1 Syntax
DataFrame.groupby(by=None, axis=0, level=None,
as_index=True, sort=True,
group_keys=True, squeeze=NoDefault.no_default,
observed=False, dropna=True)
by:mapping、function、label,或者 list of labels。如果by係 function,會對 object 嘅每個 index value call 一次;如果傳 dict 或 Series,就用 dict / Series 嘅 VALUES 決定 groups。最常用係傳 column name(label)或 list of column names。axis:{0 or 'index', 1 or 'columns'},default 0。- 其他參數(講義冇詳細講):
as_index=True令 grouper 變 index;sort=True將 group keys 排序;dropna=True會掉走 grouper 係 NaN 嘅 rows。 - Returns:
DataFrameGroupBy,一個包含 groups 資訊嘅 groupby object。
1.2 小例子 my_data
my_data = pd.DataFrame({'type': ['a','a','b','a','b'],
'value': [5,3,7,8,2],
'size': [1,2,3,4,5]})
my_data # 'type' can be used as the grouper as it is categorical
# type value size
# 0 a 5 1
# 1 a 3 2
# 2 b 7 3
# 3 a 8 4
# 4 b 2 5
1.3 The groupby object
Exam df.groupby(by=…) 嘅直接 output 係 DataFrameGroupBy object,佢係一個由 (grouper value, DataFrame) tuples 組成嘅 collection。但 df.groupby(by=…).get_group('key') 就會俾返個 sub-DataFrame。
my_data.groupby(by='type')
# <pandas.api.typing.DataFrameGroupBy object at 0x...> ← 只係 object,未計任何嘢
list(my_data.groupby(by='type'))[0] # 第一個 tuple
# ('a',
# type value size
# 0 a 5 1
# 1 a 3 2
# 3 a 8 4)
my_data.groupby(by='type').get_group('a') # sub-group DataFrame
想像你將一疊考卷按班別分成幾堆(split),但未計分。要再講「每堆計平均分」(apply + combine)先有結果。所以 print 個 groupby object 只會見到 <DataFrameGroupBy object>。可以用 for loop 睇每堆:
for key, sub_df in my_data.groupby(by='type'):
print(key) # 'a' 然後 'b'
print(sub_df) # 對應嘅 sub-DataFrame2. Groupby methods:查看 group 特性
| Method | 做乜 | Output |
|---|---|---|
get_group('key') | 攞某個 group 嘅 sub-DataFrame(包含所有 features,連 grouper column) | DataFrame |
count() | 每個 group、每個 feature 嘅 non-NA 數目 | DataFrame |
size() | 每個 group 嘅 element(row)數目 | Series |
first() / last() | 每個 group 第一 / 最後一個 record(每個 column 第一 / 最後一個 non-NA 值) | DataFrame(grouper 做 index) |
groups.keys() | 所有 group keys | dict_keys |
2.1 get_group()
my_data.groupby(by='type').get_group('a')
# type value size
# 0 a 5 1
# 1 a 3 2
# 3 a 8 4
my_data[my_data['type'] == 'a'] # equivalent way:Boolean filtering(Week 5)
my_data.groupby(by='type').get_group('b')
# type value size
# 2 b 7 3
# 4 b 2 5
get_group('a') 同 Boolean filtering my_data[my_data['type']=='a'] 結果完全一樣,而且輸出 grouper feature 同其他所有 columns,index 保留原本嘅 row labels(0, 1, 3)。
2.2 groups.keys()
my_data.groupby(by='type').groups.keys()
# dict_keys(['a', 'b'])
.groups 係一個 dict:key = group 名,value = 屬於嗰個 group 嘅 row labels,所以 .keys() 就係所有 groups 嘅名。
2.3 first() & last()
my_data.groupby(by='type').first()
# value size
# type
# a 5 1
# b 7 3
my_data.groupby(by='type').last()
# value size
# type
# a 8 4
# b 2 5
Exam 用途:按時間 group,再睇每個 group 嘅第一 / 最後一個 record,可以好快發現 missing data 或 inconsistencies。
# Easier to find out that the first entry of 2014 is missing
air_quality[['date_time','year']].groupby(by='year').first()
# date_time
# year
# 2013 2013-03-01 00:00:00
# 2014 2014-01-01 01:00:00 ← 應該係 00:00:00!
# 2015 2015-01-01 00:00:00
# 2016 2016-01-01 00:00:00
# 2017 2017-01-01 00:00:00
air_quality[['date_time','year']].groupby(by='year').last()
# 2013 2013-12-31 23:00:00
# 2014 2014-12-31 23:00:00
# 2015 2015-12-31 23:00:00
# 2016 2016-12-31 23:00:00
# 2017 2017-02-28 23:00:00
- Data 係 hourly,每年第一筆應該係
YYYY-01-01 00:00:00。2014 第一筆係 01:00 → 2014-01-01 00:00 嗰個鐘頭嘅 record 唔見咗(可能係 Week 6 清 missing values 時 drop 咗)。 - 2013 由 3 月 1 日 開始、2017 去到 2 月 28 日 完:呢個係 dataset 本身嘅收集期(2013-03 至 2017-02),唔係錯誤,但做 yearly 比較時要記住 2013 同 2017 唔係完整年份(例如 yearly sum 會偏細)。
2.4 count() vs size()
import numpy as np
my_data = pd.DataFrame({'type': ['a','a','b','a','b'],
'value': [5, np.nan, 7, 8, 2],
'size': [1,2,3,4,5]})
my_data.groupby(by='type').count() # count for non-NA values in each column
# value size
# type
# a 2 3 ← type 'a' 嘅 value column 只有兩個 non-NA
# b 2 2
my_data.groupby(by='type').size() # not column-wise, but overall size
# type
# a 3 ← 'a' 有 3 行,唔理有冇 NaN
# b 2
# dtype: int64
count() | size() | |
|---|---|---|
| 計乜 | Non-null values | All values(the shape),包括 NaN |
| 逐 column? | 係,每個 column 各自數 | 唔係,每個 group 一個數 |
| Output | DataFrame | Series |
| my_data 中 group a | value = 2,size = 3 | 3 |
如果某 group 嘅 count() < size(),代表嗰個 column 喺嗰個 group 有 missing values:size() - count() 就係每個 group 每個 column 嘅 NaN 數目。
同 Week 9 嘅 df.count()(成個 DataFrame 每 column 嘅 non-NA 數)同 df.shape 一樣道理,只係而家係「每個 group 各自計」。用 size() - count() 搵 missing values 嘅分佈,連返 Week 6 missing values 分析(例如睇 missing 係咪集中喺某個 station → 可能唔係 MCAR)。
3. 對 groups 套用 built-in functions
可以對分好嘅 groups 套其他 built-in functions。Default 係對每個 group 獨立套用,好似對幾個 mini DataFrames 分別套用咁。例如 mean() 會對每個 grouped DataFrame 計,結果再 combine 成一個 DataFrame。
.groupby().mean() 規則- Output 唔會包括 grouper column(grouper 變咗 index)。
- 跟住嘅 function 會套用喺所有 non-grouper columns。
# automatically excludes the grouper column ('type')
my_data.groupby(by='type').mean()
# value size
# type
# a 6.5 2.333333
# b 4.5 4.000000
手計 check(呢個係有 NaN 嗰個版本):group a 嘅 value = (5 + 8) / 2 = 6.5(NaN 自動略過);size = (1 + 2 + 4) / 3 ≈ 2.333。Group b:value = (7 + 2) / 2 = 4.5;size = (3 + 5) / 2 = 4。
air_quality.groupby(by='month').mean(numeric_only=True)
# PM2.5 PM10 SO2 ... TEMP ...
# month
# 1 98.547996 118.321682 30.437413 ... -1.705837
# ...
# 8 56.235265 77.137440 4.601087 ... 26.834656
# ...
# 12 115.889403 143.330803 24.262568 ... -0.070568
Air quality 有 object columns(wd、station、day_of_week_name)同 category columns,計唔到 mean,所以要加 numeric_only=True(同 Week 9 嘅 df.mean(numeric_only=True) 一樣)。
3.1 Q1:Show the average PM2.5 over months
# answer(參考)
air_quality[['PM2.5','month']].groupby(by='month').mean()
# 或者(最快寫法,見 Section 4)
air_quality.groupby(by='month')[['PM2.5']].mean()
# PM2.5
# month
# 1 98.547996
# 2 83.785755
# 3 98.300096
# 4 74.878336
# 5 65.205977
# 6 71.619663
# 7 75.996913
# 8 56.235265
# 9 64.049654
# 10 95.848617
# 11 101.436812
# 12 115.889403
Notebook 嘅預期 output 係一個有 PM2.5 column header 嘅 DataFrame,所以用 [['PM2.5']](double brackets);用 ['PM2.5'] 就會得到 Series。Insight:冬天(11、12、1 月)PM2.5 最高,8 月最低(≈56)—— 冬天燒煤取暖 + 大氣穩定、夏天多雨洗走污染物。
- 你嘅 caller df 一定要包括 grouper column。
- 除咗 grouper feature,最少要多一個 feature 先可以套 function。
# this is wrong —— 'month' 唔喺 air_quality[['PM2.5']] 入面 → KeyError
# air_quality[['PM2.5']].groupby(by='month').mean(numeric_only=True)
# correct
air_quality[['PM2.5','month']].groupby(by='month').mean()4. Time profiling:group / function / selection 嘅次序
通常我哋只關心某啲 features 嘅 statistics,所以會加一個 selection 步驟。例如想知每個 quarter 嘅平均溫度:按 quarter groupby,再對每個 group 套 mean。所以典型 groupby 操作有三步:group、function、selection。三步可以用唔同次序達到同一結果,但速度唔同。
%%timeit
air_quality.groupby(by='quarter').mean(numeric_only=True)['TEMP']
# Group -> function -> selection
# full scale grouping and mean();slow speed but short code
# 6.09 ms ± 54.7 μs per loop
%%timeit
air_quality[['TEMP','quarter']].groupby(by='quarter').mean(numeric_only=True)
# Selection -> group -> function
# long code but modest speed
# 1.59 ms ± 23 μs per loop
%%timeit
air_quality.groupby(by='quarter')['TEMP'].mean(numeric_only=True)
# Group -> selection -> function
# fast speed and short code
# 956 μs ± 12.9 μs per loop
| 次序 | 寫法 | 時間 | 評語 |
|---|---|---|---|
| Group → Function → Selection | df.groupby('quarter').mean(numeric_only=True)['TEMP'] | 6.09 ms | 最慢:所有 numeric columns 都計咗 mean,最後先揀 TEMP(浪費);code 短 |
| Selection → Group → Function | df[['TEMP','quarter']].groupby('quarter').mean() | 1.59 ms | 中等:先 copy 一個細 DataFrame;code 長 |
| Group → Selection → Function | df.groupby('quarter')['TEMP'].mean() | 956 μs | 最快又短:split 完只對 TEMP 計 |
原則:越早減少要計嘅 columns 越好,而且唔好計完先掉。最快約為最慢嘅 6 倍(6.09 / 0.956 ≈ 6.4)。
groupby('quarter')['TEMP'].mean() 回傳 Series;groupby('quarter')[['TEMP']].mean() 回傳 DataFrame。第二種寫法 (Selection → Group → Function) 回傳 DataFrame。考試由 output 反推 code 時要留意有冇 column header。
groupby 就係 SQL 嘅 GROUP BY:df.groupby('quarter')['TEMP'].mean() ≈ SELECT quarter, AVG(TEMP) FROM air_quality GROUP BY quarter;。SQL 都係只 SELECT 需要嘅 columns 先有效率。見 COM6002 Week 4 SQL。
5. 套用 custom-defined functions:agg()
用 df.groupby().agg() 將自己 define 嘅 function 套落每個 group。Function 收到嘅係每個 group 嘅一個 column(Series),要回傳一個值。
def max_min(col):
return col.max() - col.min() # range / span
air_quality[['TEMP','PM2.5']].agg(max_min) # 唔 groupby:成個 column 計
# TEMP 58.4
# PM2.5 819.0
# dtype: float64
air_quality[['PM2.5','PM10','quarter']].groupby(by='quarter').agg(max_min)
# PM2.5 PM10
# quarter
# 1 818.0 992.0
# 2 531.0 984.0
# 3 372.0 860.0
# 4 738.0 791.0
自訂 function 傳入時唔加引號、唔加括號(agg(max_min),唔係 agg('max_min') 或 agg(max_min()))。
5.1 Q2:用 lambda function 計 PM2.5 同 PM10 嘅 span
# answer(參考)
air_quality[['PM2.5','PM10','quarter']].groupby(by='quarter')\
.agg(lambda col: col.max() - col.min())
# PM2.5 PM10
# quarter
# 1 818.0 992.0
# 2 531.0 984.0
# 3 372.0 860.0
# 4 738.0 791.0
Lambda 版同 max_min 結果一樣,只係唔使先 def。(Lambda 見 Week 3。)
6. 用 agg() 一次套多個 functions
df.groupby().agg([function list]):對所有 non-grouper columns 套同一組 functions。df.groupby().agg({function mapping}):對唔同 non-grouper columns 套唔同 functions。- 例:
air_quality.groupby('quarter').agg({'TEMP': ['mean', 'max'], 'PM2.5': 'mean'})
6.1 List:同一組 functions
air_quality[['PM2.5','PM10','quarter']].groupby(by='quarter').agg(['describe', max_min])
# 兩層 column header:
# PM2.5 ... PM10 ...
# describe max_min
# count mean std min 25% 50% 75% max PM2.5
# quarter
# 1 23786.0 94.069142 97.763512 3.0 16.0 64.0 139.0 821.0 818.0
# 2 23766.0 70.628065 55.120769 2.0 30.0 57.0 97.0 533.0 531.0
# 3 24089.0 65.190722 51.676983 3.0 24.0 52.0 94.0 375.0 372.0
# 4 24044.0 104.022908 105.065528 3.0 21.0 69.0 158.0 741.0 738.0
每個 column(PM2.5、PM10)都套 describe(8 個統計量)同 max_min,所以 output 有 multi-level columns。Check:quarter 1 PM2.5 max − min = 821 − 3 = 818 ✓。
PDF 第 12 頁 output 太闊被截斷(PM10 部分睇唔到),In [45] 嘅 code 亦截斷喺 agg(['describe',max_m…;上面按可見部分推斷為 agg(['describe', max_min])。
6.2 Dict:唔同 columns 唔同 functions
air_quality[['PM2.5','RAIN','TEMP','quarter']].groupby(by='quarter')\
.agg({'PM2.5': 'describe',
'RAIN': 'sum',
'TEMP': max_min})
# PM2.5 RAIN TEMP
# count mean std min 25% 50% 75% max RAIN TEMP
# quarter
# 1 23786.0 94.069142 97.763512 3.0 16.0 64.0 139.0 821.0 135.7 43.0
# 2 23766.0 70.628065 55.120769 2.0 30.0 57.0 97.0 533.0 1470.5 41.7
# 3 24089.0 65.190722 51.676983 3.0 24.0 52.0 94.0 375.0 4018.0 32.9
# 4 24044.0 104.022908 105.065528 3.0 21.0 69.0 158.0 741.0 547.1 39.4
Insight:Quarter 3(7–9 月)總雨量最多(4018.0)而 PM2.5 mean 最低(65.19);Quarter 1 雨最少(135.7)。同 Q1 嘅「夏天雨洗走污染物」一致。(Correlation ≠ causation,只係 EDA 提出嘅 hypothesis。)
6.3 改 column 名:rename(columns=…) 同 backslash 換行
air_quality.groupby(by='quarter').agg({'TEMP': ['mean', 'max'], 'PM2.5': 'mean'})\
.rename(columns={'TEMP': 'Temp mean max', 'PM2.5': 'PM2.5 Mean'})
# 用 backslash \ 換行,chain 多個 methods 時易讀啲
air_quality[['PM2.5','RAIN','TEMP','quarter']]\
.groupby(by='quarter').agg({'PM2.5': 'describe',
'RAIN': 'sum',
'TEMP': max_min})\
.rename(columns={'PM2.5': 'PM2.5 Describe',
'RAIN': 'Rain(sum)',
'TEMP': 'Temp(Max_Min)'})
# Output 嘅頂層 header 變成 'PM2.5 Describe'、'Rain(sum)'、'Temp(Max_Min)'
\ 必須係嗰行最後一個字元(後面唔可以有空格或 comment)。另一個做法係成個 expression 用括號包住 ( … ),括號入面可以隨意換行。rename 嘅 dict 係 {舊名: 新名},同 Week 4 renaming 一樣。
7. Multi-level groupby
- 可以用多個 criteria group data:
by=['quarter', 'TEMP_category']。 by=入面 groupers 嘅次序決定 grouping hierarchy:先按 quarter 分,每個 quarter 入面再按五個 TEMP categories 分。- 調轉
['TEMP_category', 'quarter'],grouping 次序(index 層次)亦會跟住變。
air_quality[['PM2.5','PM10','TEMP_category','quarter']]\
.groupby(by=['quarter','TEMP_category']).mean()
# PM2.5 PM10
# quarter TEMP_category
# 1 Very Cold 94.070465 112.947423
# Cold 92.376038 118.648440
# Warm 99.279817 157.780088
# Hot 94.577281 181.143389
# Very Hot 17.833333 25.000000
# 2 Very Cold 34.000000 39.166667
# ...
# 3 Cold 26.565217 57.028986 ← Q3 冇 Very Cold
# Warm 55.122137 75.210125
# Hot 63.162094 81.228182
# Very Hot 68.854851 89.901204
# 4 Very Cold 118.953695 145.494827
# ...
Output 嘅 index 係 MultiIndex(兩層:quarter、TEMP_category)。Quarter 3(夏天)冇 "Very Cold" 嘅 rows,所以冇呢個組合。
In [48] 嘅 code 喺 PDF 截斷喺 groupby(by=['quarter','TEMP…;從 output(PM2.5、PM10 數值似 mean)推斷結尾係 .mean()。
observedTEMP_category 係 category dtype。舊版 pandas default observed=False 會列出所有 category 組合(包括冇 data 嘅,例如 Q3 + Very Cold → NaN);講義 output 冇呢行,因為講義用嘅新版 pandas(info() 顯示 pandas.DataFrame)groupby default 已改為 observed=True。如果你部機見到多咗 NaN rows,加 observed=True 就可以。
Quarter 1 嘅 "Very Hot" PM2.5 只係 17.8,同其他組差好遠 —— 好可能嗰個 group 只有好少 rows(冬天好少 Very Hot),平均值唔可靠。做 multi-level groupby 時記得用 size() 睇每組有幾多 samples。
air_quality.groupby(by=['quarter','TEMP_category']).size()7.1 Q3:PM2.5 顯示 mean、RAIN 顯示 sum
# answer(參考)
air_quality[['PM2.5','RAIN','TEMP_category','quarter']]\
.groupby(by=['quarter','TEMP_category'])\
.agg({'PM2.5': 'mean', 'RAIN': 'sum'})
# PM2.5 RAIN
# quarter TEMP_category
# 1 Very Cold 94.070465 67.9
# Cold 92.376038 52.3
# Warm 99.279817 15.5
# Hot 94.577281 0.0
# Very Hot 17.833333 0.0
# 2 Very Cold 34.000000 0.0
# Cold 59.876121 190.1
# Warm 65.706344 478.7
# Hot 76.560711 771.0
# Very Hot 71.260904 30.7
# 3 Cold 26.565217 0.0
# Warm 55.122137 478.1
# Hot 63.162094 3354.4
# Very Hot 68.854851 185.5
# 4 Very Cold 118.953695 106.1
# ...
Check:Quarter 1 RAIN 各組加埋 = 67.9 + 52.3 + 15.5 + 0 + 0 = 135.7,正正等於 Section 6.2 quarter 1 嘅 RAIN sum ✓(multi-level sum 加返埋 = single-level sum)。
8. Pivot table 概念
Groupby 永遠只將 groups 顯示喺 index。如果想將某啲 groups 顯示喺 columns 呢?就用 PivotTable。
- PivotTable 係一個強大嘅工具,可以 slice and dice data。
- 可以用一個精簡嘅 table 追蹤同分析幾十萬個 data points,從唔同角度睇 data。
- 支援喺 indices 同 columns 兩邊按 keys 做 aggregation(2D grouping)。
Excel PivotTable 截圖:表頭 "Sum of SALES",rows 係 MONTH(January、February、April…December),每個月下面再分 CUSTOMER(Foo Bars、123 Warehousing、Acme, inc.、Demo Company、Smith and Co.、ABC Telecom、Fake Brothers、Widget Corp),columns 係 YEAR(2014、2015),格入面係 SALES 總和(例如 February / 123 Warehousing:2014 年 2,000,000、2015 年 0)。右邊 PivotTable Fields 面板:兩條箭咀顯示 ROWS 放咗 MONTH 同 CUSTOMER,COLUMNS 放 YEAR,VALUES 放 Sum of SALES。對應 pandas:pd.pivot_table(df, index=['MONTH','CUSTOMER'], columns='YEAR', values='SALES', aggfunc='sum')。
就係 Excel 嘅 PivotTable,亦同 COM6002 Week 5 Power BI 嘅 Matrix visual 一樣(Rows、Columns、Values 三個位)。同 COM6005 Module 3B 嘅 contingency table 都好似:aggfunc='count' 嘅 pivot table 就係兩個 categorical variables 嘅 contingency table,可以拎去做 Chi-squared test。
8.1 Student dataset
student = pd.read_csv(path + 'student.csv')
student
# sex internet studytime familysize age score
# 0 F no 2. 2 - 5 hours >3 18 6
# 1 F yes 2. 2 - 5 hours >3 17 6
# 2 F yes 2. 2 - 5 hours <=3 15 10
# 3 F yes 3. 5 - 10 hours >3 15 15
# ...
# 394 M yes 1. <2 hours <=3 19 9
# 395 rows × 6 columns
studytime 嘅值前面加咗 "1. ", "2. " 等數字,令 alphabetical sorting 等於 ordinal 次序(<2 → 2–5 → 5–10 → >10 hours)。
8.2 Syntax
DataFrame.pivot_table(values=None, index=None, columns=None, aggfunc='mean',
fill_value=None, margins=False, dropna=True,
margins_name='All', observed=False, sort=True)
pandas.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean',
fill_value=None, margins=False, dropna=True,
margins_name='All', observed=False, sort=True)
| Parameter | 意思 |
|---|---|
values | 要套 aggfunc 嘅 column(s)(被計算嘅數) |
index | 用嚟分組、顯示喺 index(rows) 嘅 feature(s) |
columns | 用嚟分組、顯示喺 columns 嘅 feature(s) |
aggfunc | 用嘅 function,default 係 'mean';可以係 string、function、list 或 dict |
fill_value | 用指定值填 NaN(冇 samples 嘅組合) |
margins / margins_name | True 時加 row / column overall statistics;名 default 係 'All' |
兩種寫法等價:pd.pivot_table(student, …)(data 做第一個參數)同 student.pivot_table(…)(DataFrame method)。
9. Single-level pivot table
- 按
'sex'分組,顯示喺 index; - 按
'internet'分組,顯示喺 column; - 每個 group 對
'score'套 aggfunc(呢度係 defaultmean)。
# using pandas.pivot_table()
pd.pivot_table(student, values='score', index='sex', columns='internet')
# using df.pivot_table()
student.pivot_table(values='score', index='sex', columns='internet')
# internet no yes
# sex
# F 9.184211 10.141176
# M 9.714286 11.125786
# 驗證其中一格:Boolean filtering
student[(student['sex']=='F') & (student['internet']=='yes')]['score'].mean()
# np.float64(10.141176470588235) ✓
每一格 = 「index 值 AND column 值」嗰個 subset 嘅 score mean。Insight:無論男女,有 internet 嘅學生平均分都高約 1 分。
以上 pivot table 等於 multi-level groupby 再將第二層 index 搬去 columns:
student.groupby(by=['sex','internet'])['score'].mean() # 兩層都喺 index(4 rows)
student.groupby(by=['sex','internet'])['score'].mean().unstack() # = pivot_table(2×2)
所以 Q4 嗰種「兩個 groupers 都喺 index」嘅 output,用 groupby 或者 pivot_table(index=['sex','internet'],冇 columns)都得。
9.1 手計小例子
假設只有 6 個學生:
| sex | internet | score |
|---|---|---|
| F | yes | 12 |
| F | no | 8 |
| F | yes | 10 |
| M | no | 9 |
| M | yes | 14 |
| M | yes | 16 |
pivot_table(values='score', index='sex', columns='internet', margins=True):
| sex \ internet | no | yes | All |
|---|---|---|---|
| F | 8 | (12+10)/2 = 11 | (12+8+10)/3 = 10 |
| M | 9 | (14+16)/2 = 15 | (9+14+16)/3 = 13 |
| All | (8+9)/2 = 8.5 | (12+10+14+16)/4 = 13 | 69/6 = 11.5 |
All 係用原始 data 重新計:F 嘅 All = 10,唔係 (8 + 11) / 2 = 9.5。因為 F/yes 有 2 個人、F/no 只有 1 個人,人數唔同,直接平均兩格會錯(weighted vs unweighted)。
10. Multi-level pivot table
index同columns都可以接受多過一個 grouper(list)。- 如果某啲組合冇 samples,就會顯示 NaN。
- 可以用
fill_value將 NaN 填成你指定嘅值。
pd.pivot_table(student,
index=['sex', 'internet'],
columns=['studytime', 'familysize'],
values='score')
# studytime 1. <2 hours 2. 2 - 5 hours 3. 5 - 10 hours 4. >10 hours
# familysize <=3 >3 <=3 >3 <=3 >3 <=3 >3
# sex internet
# F no 10.000000 9.833333 8.000000 9.333333 NaN 8.600000 15.0 5.50000
# yes 12.666667 9.352941 10.466667 9.229508 15.4 10.414634 11.5 11.11111
# M no 9.250000 9.875000 9.400000 9.700000 NaN 12.000000 NaN NaN
# yes 10.000000 10.302326 12.080000 11.000000 15.0 13.700000 11.0 11.87500
pd.pivot_table(student,
index=['sex', 'internet'],
columns=['studytime', 'familysize'],
values='score',
fill_value=-999) # filling missing values
# 原本 NaN 嘅格變成 -999.0
Output 有 4 rows(2 sex × 2 internet)同 8 columns(4 studytime × 2 familysize),兩邊都係兩層 MultiIndex。例如 F / no / 5–10 hours / ≤3 冇學生 → NaN。
用 -999 呢類 sentinel value 方便一眼睇出邊格冇 data,但如果之後再對個 table 計 mean / plot,-999 會嚴重扭曲結果。如果填 0,會同「真係 0 分」混淆。fill_value 主要係為咗顯示;分析時通常保留 NaN。(連返 Week 6:sentinel values 本身就係 missing values 嘅一種偽裝。)
10.1 Margins:row 同 column 嘅 overall statistics
pd.pivot_table(student, index=['familysize'], columns=['sex'],
values='score', margins=True)
# sex F M All
# familysize
# <=3 10.980769 11.016129 11.000000
# >3 9.628205 10.864000 10.177936
# All 9.966346 10.914439 10.415190
pd.pivot_table(student, index=['familysize'], columns=['sex'],
values='score', margins=True, margins_name='Overall')
# 'All' 改名做 'Overall'
最右 column = 每個 familysize(唔分性別)嘅 mean;最底 row = 每個性別(唔分 familysize)嘅 mean;右下角 10.415 = 全部 395 個學生嘅 mean score。
11. 由 output 反推 pivot_table statement(Q4–Q6)
Exam Summary 寫明要識 「Write out the statement based on results」。方法:
- 睇左邊(row labels)有咩 groupers →
index=(多層就用 list,次序由外到內)。 - 睇頂部 header 有咩 groupers 嘅值(例如 F / M、no / yes)→
columns=。 - 睇 header 有冇出現 data column 名(score、age)→
values=。 - 睇 header 有冇 function 名(max、min、mean、
<lambda>、自訂 function 名)→aggfunc=;冇出現 = default'mean'。 - 多咗
Allrow / column →margins=True;出現特殊數值(-999)→fill_value。
Q4
# score
# sex internet
# F no 9.184211
# yes 10.141176
# M no 9.714286
# yes 11.125786
分析:sex、internet 兩個 groupers 都喺 index;頂部只有 score(values);冇 function 名 → mean。
# answer(參考)
pd.pivot_table(student, index=['sex', 'internet'], values='score')
已對照:三張目標圖嘅數字同各題 Out[ ] 完全一樣(Q4:F/no 9.184211 … M/yes 11.125786;Q5:max / min / mean / max_min 四組 score;Q6:age 嘅 max、min 同 score 嘅 median,按 studytime × sex 排),所以下面由 output 推斷嘅答案適用。參考答案仍然係推斷出嚟,講義冇直接提供。PDF 第 23 頁嘅 In [63] 係另一個示範:aggfunc={'age': ['min', 'max'], 'score': lambda s: s.max() - s.min()},即係可以用 dict 俾每個 column 指定唔同 function,header 會出現 <lambda>。
Q5
# max min mean max_min
# score score score score
# sex internet
# F no 18 0 9.184211 18
# yes 19 0 10.141176 19
# M no 18 0 9.714286 18
# yes 20 0 11.125786 20
分析:index 同 Q4 一樣;頂層 header 係 function 名(max、min、mean、max_min)→ aggfunc 係一個 list,次序要跟 output;第二層係 score → values。max_min 係 Section 5 嘅自訂 function(唔加引號)。
# answer(參考)
pd.pivot_table(student, index=['sex', 'internet'], values='score',
aggfunc=['max', 'min', 'mean', max_min])
Check:F / no:max − min = 18 − 0 = 18 ✓。
Q6
# age score
# max min median
# sex F M F M F M
# studytime
# 1. <2 hours 18 22 15 15 11.0 10.0
# 2. 2 - 5 hours 19 20 15 15 10.0 11.0
# 3. 5 - 10 hours 20 18 15 15 11.0 14.0
# 4. >10 hours 19 18 15 15 11.0 12.5
分析:index = studytime;最底層 header 係 F / M → columns = sex;頂層 age、score → values 有兩個;中層 function 名:age 用 max、min,score 用 median → aggfunc 係 dict。
# answer(參考)
pd.pivot_table(student, index='studytime', columns='sex',
values=['age', 'score'],
aggfunc={'age': ['max', 'min'], 'score': 'median'})
講義隨後示範咗一個變化版本:score 改用 lambda 計 range,header 就顯示 <lambda>:
pd.pivot_table(student, index='studytime', columns='sex',
values=['age', 'score'],
aggfunc={'age': ['min', 'max'],
'score': lambda s: s.max() - s.min()})
# age score
# max min <lambda>
# sex F M F M F M
# studytime
# 1. <2 hours 18 22 15 15 18 19
# 2. 2 - 5 hours 19 20 15 15 18 19
# 3. 5 - 10 hours 20 18 15 15 19 9
# 4. >10 hours 19 18 15 15 18 20
Built-in → 顯示 function 名(max、median);自訂 def function → 顯示 function 名(max_min);lambda → 顯示 <lambda>。所以見到 <lambda> 就知 aggfunc 用咗 lambda。留意講義 dict 寫 ['min','max'],但 output 次序係 max 先 min 後,即係 output 嘅 column 次序未必同 code 嘅 list 次序一樣;由 output 反推時,答啱用咗邊啲 functions 最重要。
12. 綜合練習:air_quality(Q7–Q8)
air_quality = pd.read_pickle(path + 'air_quality.pkl') # reset data
# extract the main wind direction using the first letter in wd column
air_quality['wd'] = air_quality['wd'].map(lambda wd: wd[0]) # 'NNE' -> 'N'
同 Week 9 Q1 一樣,用 map + lambda 將 16 個方向減成 N / S / E / W 四個主方向。
12.1 Q7:每個 station × quarter 嘅 PM2.5 mean 同最常見 wind direction
'mode' 唔可以直接放入 agg
# This cannot work
air_quality.groupby(by=['station', 'quarter'])[['PM2.5','wd']]\
.agg({'PM2.5': 'mean', 'wd': 'mode'})
mode 回傳嘅係一個 Series(可以有多個 modes),唔係單一值,同 'mean'、'sum' 呢類 aggregation functions 唔兼容。解決:用 pd.Series.mode 做 aggregation function,或者用 lambda 回傳第一個 mode。
# answer(參考)
air_quality.groupby(by=['station', 'quarter'])[['PM2.5','wd']]\
.agg({'PM2.5': 'mean', 'wd': lambda x: x.mode()[0]})
# 或者 'wd': pd.Series.mode
# PM2.5 wd
# station quarter
# Dongsi 1 93.588818 N
# 2 71.323896 S
# 3 67.782695 E
# 4 106.970248 N
# Gucheng 1 96.199428 N
# 2 71.678643 S
# 3 63.241872 N
# 4 104.526163 N
# Tiantan 1 92.363923 N
# 2 68.861329 S
# 3 64.771197 E
# 4 100.806901 N
help(pd.Series.mode) 重點:The mode is the value that appears most often. There can be multiple modes. Always returns Series even if only one value is returned;modes 按 sorted order 排;dropna=True(default)唔計 NaN。例如 pd.Series([2, 4, 8, 2, 4, None]).mode() → [2.0, 4.0](兩個 modes)。
pd.Series.mode vs lambda x: x.mode()[0]如果某個 group 有兩個並列 modes,pd.Series.mode 會喺嗰格放一個 array(例如 ['N','S']),而 x.mode()[0] 只會攞 sorted 後第一個。後者保證每格一個值,所以通常比較安全。
Your findings(參考):
- 三個 stations 嘅 pattern 好相似:Quarter 1 同 4(冬季)主要吹 N 風、Quarter 2 吹 S 風、Quarter 3 多數 E(Gucheng 係 N)。符合北方冬季北風、夏季南風嘅季候風 pattern。
- PM2.5 mean 喺 Quarter 4 最高(100–107),Quarter 3 最低(63–68);三個 stations 之間差別細。
12.2 Q8:pivot table —— index = station & wd,columns = quarter,values = PM2.5 & RAIN 嘅平均
# answer(參考)
pd.pivot_table(air_quality, index=['station', 'wd'], columns='quarter',
values=['PM2.5', 'RAIN']) # aggfunc default = 'mean'
# PM2.5 RAIN ...
# quarter 1 2 3 4 1 2 ...
# station wd
# Dongsi E 134.003638 82.732953 75.416631 147.194187 0.007796 0.097122 ...
# N 63.808857 51.850025 52.454326 82.620099 0.004598 0.094820 ...
# S 107.178741 81.182463 80.621811 110.947271 0.006295 0.032948 ...
# W 78.463012 61.620117 51.212283 67.737500 0.000120 0.027539 ...
# Gucheng E 110.791290 77.483591 60.320216 115.419139 ...
# ...
# Tiantan ...
Your insights(參考):
- 同一 station、同一 quarter 入面,E 同 S 風時 PM2.5 明顯高過 N 風(例如 Dongsi Q1:E 134 vs N 64)。北風由較乾淨、開闊嘅北面吹過嚟,有助驅散污染;東 / 南風就由人口同工業密集區帶污染物過嚟。
- Quarter 1 同 4 嘅 PM2.5 普遍最高;RAIN 嘅 hourly mean 喺 Q1 接近 0,Q2 開始上升。
13. 點解要 Data Visualization(下週預告)
單靠睇 tables 好難理解 data 入面嘅 patterns 同 relationships。Visualization 幫我哋揭示一啲淨睇數字唔明顯嘅 insights 同 trends。例如上面 Q8 嗰個 pivot table,好難一眼睇出邊個 quarter 污染最嚴重、wind direction 同佢點樣相關;用 heatmap 或 bar chart 就即刻睇到。Visualization 令我哋更易識別 trends、outliers、relationships,指引之後嘅分析同決策。
import seaborn as sns
import matplotlib.pyplot as plt
# Example catplot:x = quarter,每個 wd 一條 bar(hue),高度 = PM2.5 mean
sns.catplot(x="quarter", hue="wd", y="PM2.5", kind="bar", data=air_quality)
plt.show()
# Correlation heatmap(連 Week 9 df.corr())
sns.heatmap(air_quality[['PM2.5','TEMP','WSPM','RAIN','quarter']].corr(), cmap='coolwarm', annot=True) # cmap 係按圖推斷
catplot:x 軸 quarter 1–4,每個 quarter 四條 bar(wd = N 藍、W 橙、E 綠、S 紅),bar 頂有 error bar。四個 quarter 都係 E 最高(Q1 約 128、Q4 約 138),S 第二(約 107、113),N 同 W 較低;Q2、Q3 整體最低(約 54–81)。即係 Q1、Q4(冬季)加上東風,PM2.5 最嚴重。
heatmap:PM2.5、TEMP、WSPM、RAIN、quarter 五個 variables 嘅 correlation matrix,格入面有數字(即係用咗 annot=True),顏色由藍(負)經白到紅(正)。對角線全部係 1;最強嘅係 PM2.5 vs WSPM = −0.28(風速越大,PM2.5 越低);其次 WSPM vs quarter −0.16、TEMP vs quarter 0.15、PM2.5 vs TEMP −0.14;其他差唔多係 0。由藍白紅嘅配色推斷,cmap 應該係 'coolwarm'(PDF 只見到 cmap='c…,所以呢點係推斷)。
Visualization 嘅原則同圖表種類可以預習 COM6004 Chapter 3 Data Exploration & Visualization。catplot(kind="bar") 其實就係將 groupby mean 畫出嚟:air_quality.groupby(['quarter','wd'])['PM2.5'].mean()。
14. Semi-Structured Data(programming 唔考)
呢部分同 Week 9 Section 最尾一樣,重溫重點即可。
| Data type | Structure | Examples | Characteristics |
|---|---|---|---|
| Structured | Rigid schema, fixed format | SQL databases, CSV files, Excel spreadsheets | Pre-defined schema, tabular format, consistent structure |
| Semi-structured | Flexible schema, self-describing | XML, JSON, YAML, NoSQL databases | Schema is embedded in data, hierarchical, flexible structure |
| Unstructured | No predefined structure | Text documents, images, videos, audio files | No organized schema, difficult to parse programmatically |
點解 XML 同 JSON 係 semi-structured:tag / key 名本身描述咗 data(self-describing),而且同一 collection 入面唔同 records 可以有唔同 structure —— 例如 Alice 有 grades(math、science),Bob 冇 grades 但有 attendance。CSV 就做唔到,每行一定要同一組 columns。
<students>
<student id="101">
<name>Alice</name>
<grades><math>A</math><science>B+</science></grades>
</student>
<student id="102">
<name>Bob</name>
<!-- Different structure allowed -->
<attendance>95%</attendance>
</student>
</students>
{"students": [
{"id": 101, "name": "Alice", "grades": {"math": "A", "science": "B+"}},
{"id": 102, "name": "Bob", "attendance": "95%"}
]}
| Format | 幾時用 |
|---|---|
| XML | 需要 strong schema validation 同 data integrity(用 .xsd XML schema definition 檢查 conformance,睇 .xsd 就知所有 tags 同 tree structure,例如 data.gov.hk 嘅 special traffic news);document-oriented data;enterprise systems 要 standardized data exchange;需要 rich metadata 同 namespaces |
| JSON | Web APIs / web services;application configuration files;mobile app development;real-time data exchange |
| CSV | Spreadsheet / simple tabular data;quick analysis 同 prototyping;systems 之間 data migration;non-technical users 要睇 / 改 |
講義仲有兩個 parsing 例子(唔考 programming):
- XML:
xml.etree.ElementTree——ET.parse()→getroot();root.tag、root.attrib;for child in root遍歷 children;root.findall('country')、country.get('name')(attribute)、country.find('gdppc').text(child element 文字)。CountryData.xml 有 3 個 countries(Liechtenstein 141100、Singapore 59900、Panama 13600),平均 GDP per capita = 71533.33。 - JSON:用
requests.get(url, params=…, headers=…)call OpenStreetMap Nominatim geocoder API(要設 User-Agent header),response.raise_for_status()檢查錯誤,response.json()parse 成 Python list / dict,再攞results[0]["lat"]、["lon"](HSUHK ≈ 22.377, 114.213)。
Structured / semi-structured / unstructured 同 NoSQL(document databases 存 JSON)見 COM6002 Week 1 同 Week 7;用 API 收集 data 見 COM6002 Week 2 Data Collection。
自我測驗 Self-test
Q1. Groupby 嘅三個步驟係乜?print(df.groupby('type')) 會顯示乜?
Split(按 grouper 分組)→ Apply(每組獨立套 function)→ Combine(合併結果)。直接 print 只會顯示 <DataFrameGroupBy object at 0x…>,因為 groupby object 只係 (key, sub-DataFrame) tuples 嘅 collection,未套任何 function。要用 get_group('a') 或者 .mean() 等先有結果。
Q2. df = pd.DataFrame({'g':['x','x','y','x'], 'v':[1, np.nan, 3, np.nan]})。寫出 df.groupby('g').count() 同 df.groupby('g').size() 嘅結果。
count()(DataFrame):x → v = 1,y → v = 1(只數 non-NA)。size()(Series):x → 3,y → 1(數總行數,包 NaN)。x 組 size − count = 2 個 missing values。
Q3. 以下三句邊句最快?點解?(a) df.groupby('quarter').mean(numeric_only=True)['TEMP'] (b) df[['TEMP','quarter']].groupby('quarter').mean() (c) df.groupby('quarter')['TEMP'].mean()
(c) 最快(956 μs),其次 (b)(1.59 ms),(a) 最慢(6.09 ms)。(a) 先對所有 numeric columns 計 mean 先揀 TEMP,浪費大量計算;(b) 要先 copy 一個 sub-DataFrame;(c) group 完只對 TEMP 一個 column 計,又短又快(group → selection → function)。
Q4. air_quality[['PM2.5']].groupby(by='month').mean() 有咩問題?點改?
Caller DataFrame 只有 PM2.5,冇 grouper column month → KeyError。要包含 grouper:air_quality[['PM2.5','month']].groupby(by='month').mean(),或者 air_quality.groupby(by='month')[['PM2.5']].mean()。
Q5. 寫一句 groupby:按 station 分組,PM2.5 計 mean 同 max,RAIN 計 sum,然後將 RAIN 改名做 'Total Rain'。
air_quality.groupby(by='station').agg({'PM2.5': ['mean', 'max'], 'RAIN': 'sum'}).rename(columns={'RAIN': 'Total Rain'})。唔同 columns 用唔同 functions → dict;PM2.5 要多個 functions → dict value 用 list。
Q6. 點解 .agg({'wd': 'mode'}) 唔得?有咩兩個解決方法?
mode 永遠回傳 Series(可能有多個 modes),唔係 aggregation 要求嘅單一值。方法:(1) 'wd': pd.Series.mode;(2) 'wd': lambda x: x.mode()[0](攞第一個 mode,保證每格一個值)。
Q7. by=['quarter','TEMP_category'] 同 by=['TEMP_category','quarter'] 有乜分別?
數值一樣(同一啲組合),但 hierarchy 唔同:前者先按 quarter 分(外層 index),每個 quarter 入面再按 TEMP_category 分;後者外層係 TEMP_category,內層係 quarter。Output 嘅 MultiIndex 次序同排列會唔同。
Q8. 寫出 pivot_table statement 產生以下 output:index 係 studytime;columns 頂層係 mean 同 max,中層係 score,底層係 no / yes;最底有一行 Total。
pd.pivot_table(student, index='studytime', columns='internet', values='score', aggfunc=['mean', 'max'], margins=True, margins_name='Total')。Function 名喺頂層 → aggfunc 係 list;no / yes 係 internet 嘅值 → columns;Total → margins + margins_name。
Q9. 小例子:F 有 scores 12(yes)、8(no)、10(yes);M 有 9(no)、14(yes)、16(yes)。pivot_table(values='score', index='sex', columns='internet', margins=True) 嘅 F 行 All 係幾多?點解唔係 9.5?
F 行 All = (12 + 8 + 10) / 3 = 10。Margins 用原始 data 重新計 mean;9.5 係錯誤咁將兩格(no = 8、yes = 11)直接平均,冇考慮 yes 有 2 人、no 得 1 人。
Q10. Groupby 同 pivot table 最大分別係乜?student.pivot_table(values='score', index='sex', columns='internet') 用 groupby 點寫?
Groupby 嘅 groups 永遠只喺 index;pivot table 可以同時將 groups 放 index 同 columns(2D grouping),而且有 fill_value、margins。等價 groupby:student.groupby(['sex','internet'])['score'].mean().unstack()。
Q11. 一個 dataset 每筆 record 嘅欄位都唔同(有啲有 grades,有啲只有 attendance),要用喺 web API。應該用 CSV、XML 定 JSON?佢屬於邊類 data?
JSON(web APIs、real-time exchange)。佢係 semi-structured data:flexible、self-describing schema,hierarchical,唔同 records 可以有唔同 structure。CSV 要固定 columns,唔適合。
一頁總結 Cheat sheet
- EDA types:summary statistics(aggregating、sampling、correlation、groupby、pivot table)+ visualization。
- Groupby = split → apply → combine;grouper 用 categorical features;
df.groupby(by=…)→DataFrameGroupBy((key, sub-df) tuples)。 by:label / list of labels / function(作用於 index)/ dict 或 Series(用 VALUES 分組)。- Methods:
get_group('a')(=df[df['type']=='a']);groups.keys();first()/last()(按年 group 檢查 missing / 不一致,例如 2014 第一筆係 01:00);count()non-NA(DataFrame)vssize()全部行數(Series)。 groupby().mean():output 唔包 grouper(變 index),套落所有 non-grouper columns;有非數值 columns 加numeric_only=True。- Caller df 一定要包 grouper column + 最少一個其他 feature。
- 速度:Group → Selection → Function
df.groupby('q')['TEMP'].mean()(956 μs)> Selection → Group → Function(1.59 ms)> Group → Function → Selection(6.09 ms)。 agg(max_min)/agg(lambda c: c.max()-c.min());agg([list])所有 columns 同一組;agg({dict})每 column 唔同;built-in 加引號,自訂唔加。.rename(columns={'old':'new'});長 chain 用\換行。- Multi-level:
by=['quarter','TEMP_category'],次序 = hierarchy → MultiIndex。 mode回傳 Series → agg 用pd.Series.mode或lambda x: x.mode()[0]。- Pivot table:groups 可以喺 index 同 columns;
pd.pivot_table(df, values=, index=, columns=, aggfunc='mean', fill_value=, margins=False, margins_name='All')或df.pivot_table(…)。 - 冇 samples 嘅組合 → NaN →
fill_value=-999;margins=True用原始 data 計 overall(唔係格仔平均)。 - 反推 statement:左邊 → index;頂部 group 值 → columns;data column 名 → values;function 名 /
<lambda>→ aggfunc(list 或 dict);冇 function 名 → mean;All → margins。 - Pivot ≈
groupby([a,b])[v].mean().unstack()。 - Visualization:tables 難睇 pattern → heatmap / bar chart(
sns.catplot、sns.heatmap)揭示 trends、outliers、relationships(下週)。 - Structured(rigid)/ Semi-structured(XML、JSON,self-describing、hierarchical)/ Unstructured;XML → schema validation(.xsd)、enterprise;JSON → web API、config、mobile、real-time;CSV → tabular、quick analysis、non-technical users。