All Revision Notes

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,同 DataFrameGroupBy object 係乜。
  • 識用 groupby methods:get_group()、groups.keys()、first() / last()、count() vs size()。
  • 對 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

  1. Groupby = split → apply → combine;df.groupby(by=…) 本身只係一個 DataFrameGroupBy object((key, sub-DataFrame) tuples),要再套 function 先有結果。
  2. count() = 每個 column 嘅 non-NA 數目(DataFrame);size() = 每個 group 嘅總行數(Series,包 NaN)。
  3. agg(['mean','max']) = 所有 non-grouper columns 用同一組 functions;agg({'TEMP':['mean','max'], 'PM2.5':'mean'}) = 唔同 columns 唔同 functions。Built-in 加引號,自訂 function 唔加。
  4. 寫法 df.groupby(by='quarter')['TEMP'].mean()(group → selection → function)最快又最短;caller DataFrame 一定要包含 grouper column。
  5. 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=。

💡 邊啲 columns 適合做 grouper?

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 概念

⭐ Groupby 三步曲
  1. Split:根據某啲 criteria(categorical groupers)將 data 分成 groups。
  2. Apply:對每個 group 獨立套用一個 function。
  3. Combine:將各 group 嘅 function 結果合併成一個 data structure。

用途例子:計每個 category group 嘅 statistics(例如每個月嘅平均 PM2.5)。

🖼️ groupby.png(PDF 第 3 頁)

左邊 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
💡 直覺:groupby object = 「分好咗堆,未做嘢」

想像你將一疊考卷按班別分成幾堆(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-DataFrame

2. 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 keysdict_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
💡 點樣讀呢兩個 output
  • 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() vs size()
count()size()
計乜Non-null valuesAll values(the shape),包括 NaN
逐 column?係,每個 column 各自數唔係,每個 group 一個數
OutputDataFrameSeries
my_data 中 group avalue = 2,size = 33

如果某 group 嘅 count() < size(),代表嗰個 column 喺嗰個 group 有 missing values:size() - count() 就係每個 group 每個 column 嘅 NaN 數目。

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)—— 冬天燒煤取暖 + 大氣穩定、夏天多雨洗走污染物。

⚠️ Note:caller DataFrame 一定要有 grouper column
  • 你嘅 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 → Selectiondf.groupby('quarter').mean(numeric_only=True)['TEMP']6.09 ms最慢:所有 numeric columns 都計咗 mean,最後先揀 TEMP(浪費);code 短
Selection → Group → Functiondf[['TEMP','quarter']].groupby('quarter').mean()1.59 ms中等:先 copy 一個細 DataFrame;code 長
Group → Selection → Functiondf.groupby('quarter')['TEMP'].mean()956 μs最快又短:split 完只對 TEMP 計

原則:越早減少要計嘅 columns 越好,而且唔好計完先掉。最快約為最慢嘅 6 倍(6.09 / 0.956 ≈ 6.4)。

💡 Output 形狀

groupby('quarter')['TEMP'].mean() 回傳 Series;groupby('quarter')[['TEMP']].mean() 回傳 DataFrame。第二種寫法 (Selection → Group → Function) 回傳 DataFrame。考試由 output 反推 code 時要留意有冇 column header。

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

⭐ list vs dict
  • 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)'
💡 Backslash 換行

\ 必須係嗰行最後一個字元(後面唔可以有空格或 comment)。另一個做法係成個 expression 用括號包住 ( … ),括號入面可以隨意換行。rename 嘅 dict 係 {舊名: 新名},同 Week 4 renaming 一樣。

7. Multi-level groupby

⭐ 多個 groupers
  • 可以用多個 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()。

⚠️ Category grouper 同 observed

TEMP_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)。
🖼️ pivot.png(PDF 第 17 頁)

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')。

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)
⭐ Key parameters
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_nameTrue 時加 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(呢度係 default mean)。
# 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 ↔ groupby + unstack

以上 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 個學生:

sexinternetscore
Fyes12
Fno8
Fyes10
Mno9
Myes14
Myes16

pivot_table(values='score', index='sex', columns='internet', margins=True):

sex \ internetnoyesAll
F8(12+10)/2 = 11(12+8+10)/3 = 10
M9(14+16)/2 = 15(9+14+16)/3 = 13
All(8+9)/2 = 8.5(12+10+14+16)/4 = 1369/6 = 11.5
⚠️ 易錯:margins 唔係「格仔嘅平均」

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。

⚠️ fill_value 要小心

用 -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」。方法:

⭐ 反推步驟
  1. 睇左邊(row labels)有咩 groupers → index=(多層就用 list,次序由外到內)。
  2. 睇頂部 header 有咩 groupers 嘅值(例如 F / M、no / yes)→ columns=。
  3. 睇 header 有冇出現 data column 名(score、age)→ values=。
  4. 睇 header 有冇 function 名(max、min、mean、<lambda>、自訂 function 名)→ aggfunc=;冇出現 = default 'mean'。
  5. 多咗 All row / 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')
🖼️ Q5.png、Q6.png、Q7.png(PDF 第 21–23 頁)

已對照:三張目標圖嘅數字同各題 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
💡 Header 揭示 function 種類

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 同 heatmap(PDF 第 30–31 頁)

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…,所以呢點係推斷)。

14. Semi-Structured Data(programming 唔考)

呢部分同 Week 9 Section 最尾一樣,重溫重點即可。

Data typeStructureExamplesCharacteristics
StructuredRigid schema, fixed formatSQL databases, CSV files, Excel spreadsheetsPre-defined schema, tabular format, consistent structure
Semi-structuredFlexible schema, self-describingXML, JSON, YAML, NoSQL databasesSchema is embedded in data, hierarchical, flexible structure
UnstructuredNo predefined structureText documents, images, videos, audio filesNo 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
JSONWeb APIs / web services;application configuration files;mobile app development;real-time data exchange
CSVSpreadsheet / 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)。

自我測驗 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)vs size() 全部行數(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。