COM6001 Computing with Big Data · Week 9
Feature Transformation (3): map / apply & EDA (1): Aggregating Statistics, Sampling, Correlation
COM6001 · Week 9 · Source: Lecture09_EDA_1.pdf / Lecture09_EDA_1.ipynb(Jupyter notebook export, 35 pages)· Lecturer: Dr. Aileen Hou
Contents · 12 sections
🎯 本講重點 (Learning outcomes)
- 用
Series.map()逐個 element 轉換數值:傳入 dictionary、function name、lambda 或 custom function。 - 用
df.apply(func, axis=…)逐個 column(axis=0)或逐個 row(axis=1)套用 function,例如用多個 columns 生成新 feature;知道apply慢,係 last resort。 - 知道 Exploratory Data Analysis (EDA) 係乜(What / Why / How)同主要類型。
- Aggregating statistics:
mean()、median()、std()、quantile()、sum()、nunique()、mode()、count()、describe();用include=/exclude=、numeric_only=True、select_dtypes()揀 dtype。 - 用
df.agg()一次過做多個 aggregation,或者對唔同 columns 用唔同 functions(dictionary mapping)。 - Sampling:
df.sample()嘅用途同參數(n、frac、replace、random_state)。 - Correlation:
df.corr()(Pearson / Kendall / Spearman)、點樣解讀 correlation matrix、用unstack()搵最強 correlations。 - Semi-structured data(XML / JSON)概念同 XML vs JSON vs CSV 幾時用(programming 部分唔考)。
⭐ 考試必溫 Top-5
- map vs apply vs agg:
map逐個 element(Series);apply逐個 row / column 套一個 function;agg一次過套多個 aggregation functions,而且可以每個 column 用唔同 function。 axis=0= 逐個 column 計(default,結果每個 column 一個值);axis=1= 逐個 row 計(結果每個 row 一個值)。- Built-in function 名要加引號(
'mean'),自己 define 嘅 function 唔加引號(my_func)。 - 揀 dtype:
describe(include=/exclude=);sum/mean/mode(numeric_only=True);非數值 columns 先select_dtypes(exclude='number')再 aggregate。 - Correlation coefficient 喺 −1 至 +1:越近 ±1 越強,越近 0 越弱;Pearson 量 linear,Spearman / Kendall 用 ranks(量 monotonic,對 outliers 較 robust);識讀 correlation matrix(diagonal = 1、symmetric)。
開頭 outline 重溫 Feature Transformation(one-hot / ordinal encoding、cleaning texts、scaling),今講接住講 outline 最後兩項 Mapping new values 同 Applying functions,然後開始 EDA。Section 3 Semi-Structured Data 標明「programming not required by exam」,但最尾 Summary 仍然列咗識認 XML / JSON、同 CSV 嘅概念分別、幾時用。
1. Review: Feature Transformation
Outline 重溫之前學過嘅 feature transformation 工具:
- Categorical → numerical:nominal data 用 one-hot encoding(
pd.get_dummies());ordinal data 用 ordinal encoder(OrdinalEncoder)。詳見 Week 7。 - Cleaning texts(
.straccessor、regex)同 Scaling(Standard / MinMax / Robust)。詳見 Week 8。 - 今講新內容:Mapping new values(
map)同 Applying functions(apply)。
2. Mapping new values:Series.map()
map 同 apply 令我哋可以對 DataFrame 嘅每個 element,或者每個 row / column,有效率咁做 custom transformation。
map() 定義
- Substitute each value in a Series / DataFrame with another value or a function’s output,即係逐個 element 換值。
- Series 同 DataFrame 都有
map(DataFrame.map要 pandas 2.1.0+);課堂用Series.map()。 - Mapping dictionary 或 function 經參數
arg=傳入。
s.map(
arg: 'Callable | Mapping | Series',
na_action: "Literal['ignore'] | None" = None,
) -> 'Series'
arg 可以係 Callable(function)、Mapping(dictionary)或 Series。na_action='ignore' 會令 NaN 直接保留做 NaN,唔會傳入 function。
Dataset:air_quality.pkl(95,685 rows × 24 columns 嘅空氣質素 data,有 PM2.5、PM10、TEMP、RAIN、wd(wind direction)、WSPM(wind speed)、station,仲有上堂整好嘅 PM2.5_cat、TEMP_category、day_of_week_name 等)。
import pandas as pd
dir_path = "./Datasets/week_9/"
air_quality = pd.read_pickle(dir_path + "air_quality.pkl")
2.1 傳入 dictionary 做 arg
# change Mon - Fri to Weekday
myMap = {'Monday':'Weekday', 'Tuesday':'Weekday', 'Wednesday':'Weekday',
'Thursday':'Weekday', 'Friday':'Weekday',
'Saturday':'Weekend', 'Sunday':'Weekend', }
air_quality['day_of_week_name'].value_counts()
# Sunday 13931, Saturday 13812, Friday 13782, Thursday 13633,
# Monday 13536, Tuesday 13523, Wednesday 13468
air_quality['day_of_week_name'].map(myMap).value_counts()
# Weekday 67942
# Weekend 27743
檢查:Weekend = 13931 + 13812 = 27743 ✓;Weekday = 其餘 5 日加埋 = 67942 ✓。
用 dictionary 做 map 時,如果某個值唔喺 dictionary 入面(例如串錯 'monday'),結果會變 NaN,唔會保留原值。所以 map 完記得 .isna().sum() 檢查。(比較:Series.replace(dict) 就會保留冇對應嘅原值。)
2.2 傳入 function name 做 arg
air_quality['day_of_week_name'].map(arg=len) # 'Friday' -> 6, 'Tuesday' -> 7
air_quality['day_of_week_name'].map(str.upper) # 'Friday' -> 'FRIDAY'
air_quality['day_of_week_name'].str.upper() # 結果一樣(Week 8 嘅 .str accessor)
注意傳入嘅係 function 本身(len、str.upper),唔好加括號:map(len()) 係錯,因為咁會即刻 call 咗 function。
2.3 傳入 lambda function
air_quality['day_of_week_name'].map(
lambda x: 'Weekend' if x in ['Saturday', 'Sunday'] else 'Weekday')
lambda 係一行嘅 anonymous function:lambda 輸入: 輸出。x 每次代表 Series 入面一個 value。結果同 dictionary 版本一樣,但寫法短好多(唔使列晒 7 日)。
2.4 傳入 custom function(function name)
def mapfunc(x):
if x in ['Saturday', 'Sunday']:
return 'weekend_mapfunc'
else:
return 'weekday_mapfunc'
air_quality['day_of_week_name'] = air_quality['day_of_week_name'].map(mapfunc)
呢度將結果 assign 返入原 column,所以原本嘅星期名會被覆蓋。Notebook 之後用 pd.read_pickle(...) reset 返個 DataFrame。map 本身唔會 in-place 改 data,一定要 assign 先會保存。
2.5 Q1:wind direction 例子
air_quality['wd'] 有 16 種方向(NE、ENE、E、NW、N、SW、ESE、SSW、NNE、S、WSW、SE、NNW、WNW、SSE、W)。取第一個字母就可以歸納做 4 個主方向:
def wd_to_letter(wd_full):
return wd_full[0]
air_quality['wd'].map(wd_to_letter).value_counts()
# N 34068, S 26054, E 22699, W 12864
練習答案參考(notebook 留空畀學生做):
# Q1.1 lambda function -> 'E', 'W', 'S', 'N'
air_quality['wd'].map(lambda x: x[0]).value_counts()
# Q1.2 full function -> 'East', 'West', 'South', 'North'
def wd_to_name(wd_full):
names = {'E': 'East', 'W': 'West', 'S': 'South', 'N': 'North'}
return names[wd_full[0]]
air_quality['wd'].map(wd_to_name).value_counts()
注意 wd_full[0] 只睇第一個字母,所以 "NE"、"NNE"、"NW"、"NNW" 全部歸做 N;"ENE"、"ESE" 歸 E。呢個係一種將 high-cardinality categorical feature 減少類別嘅方法(同 Week 7 嘅 binning 目的相似:減少 categories)。
3. Applying functions:df.apply()
apply() 重點
- 將同一個 function 套用喺每個 sub-dimension(每個 row 或者每個 column)。
- Highly flexible,但通常有更快嘅 alternatives:DataFrame / Series 本身有 built-in methods(例如
df.mean()),盡量用佢哋。apply比較慢,use it as the last resort。 - 沿一個 axis 套用:
axis=0(default)= column by column;axis=1= row by row。 args=()傳額外 input arguments。- Built-in functions 用引號:
df.apply('mean');自己 define 嘅 function 唔用引號:df.apply(my_func)。
3.1 Column-wise vs row-wise
# column-wise(axis=0, default):每個 column 一個值
air_quality[['PM2.5', 'PM10']].apply('mean') # PM2.5 83.477884, PM10 111.899959
air_quality[['PM2.5', 'PM10']].mean() # 同樣結果,而且更快
# row-wise(axis=1):每個 row 一個值
air_quality[['PM2.5', 'PM10']].apply('mean', axis=1) # 9.0, 4.0, 4.0, 5.0, 4.5, ...
air_quality[['PM2.5', 'PM10']].mean(axis=1) # 同樣結果
axis=0(default) | axis=1 | |
|---|---|---|
| Function 每次收到 | 一整個 column | 一整個 row |
| 結果長度 | = column 數(例子:2 個值) | = row 數(例子:95,685 個值) |
| 例子 | 每個污染物嘅平均值 | 每個時間點 PM2.5 同 PM10 嘅平均 |
口訣:axis 係「沿住邊個方向收埋」:axis=0 將 rows 收埋(剩返 columns),axis=1 將 columns 收埋(剩返 rows)。
3.2 點解 apply(axis=1) 咁慢?(速度比較)
def pm_ratio(row):
return row['PM2.5']/row['PM10']
%timeit pm_ratio(air_quality) # 成個 df 傳入,call 一次:44.4 μs
pm_ratio(air_quality.iloc[0]) # 只傳一個 row:np.float64(1.0)
%%timeit
air_quality[['PM2.5', 'PM10']].apply(pm_ratio, axis=1) # call 95,685 次:300 ms
%%timeit
air_quality['PM2.5']/air_quality['PM10'] # vectorized,不 call function:44.8 μs
pm_ratio(row) 個參數 row 定義得好鬆:只要 row['PM2.5'] 攞到數字,佢可以係一個 row,亦可以係成個 DataFrame。直接傳成個 DataFrame 只 call 一次,pandas 用 vectorized 運算(44 μs);apply(axis=1) 要逐 row call 95,685 次 Python function(300 ms),大約慢 6,700 倍(300 ms ÷ 44.4 μs ≈ 6,757)。所以有 built-in / vectorized 寫法就唔好用 apply。
μs = microsecond = s;ms = millisecond = s。%timeit 測一行,%%timeit 測成個 cell(Jupyter magic commands)。
3.3 用 apply() 生成新 features
apply 最常見用途:用 axis=1,根據多個現有 features 建立新 feature,尤其係 built-in functions 做唔到嘅邏輯。
# prepare the function to generate a new feature about activity decision
def activity_decision(row):
if (row['PM2.5_cat'] in ['Good', 'Moderate']) & (row['TEMP_category'] in ['Warm', 'Hot']) & (row['RAIN'] == 0):
return 'Go outside'
else:
return 'Stay inside'
# call the function using apply along axis=1
air_quality.apply(activity_decision, axis=1)
# 0 Stay inside, 1 Stay inside, ... Length: 95685
每次 row 係一個 row(Series),所以可以同時睇 PM2.5_cat、TEMP_category、RAIN 三個 columns。要保存就 air_quality['activity'] = air_quality.apply(activity_decision, axis=1)。
Function 入面 row['PM2.5_cat'] in [...] 得出嘅係普通 Python True / False,所以用 and 都得;Notebook 用 & 都 work,因為兩邊都係 bool。但喺 Boolean mask(成個 Series)嗰陣一定要用 & 加括號,唔可以用 and。
.apply(func) 每次攞 DataFrame 嘅一個 row 或 column(一個 Series),再對嗰個 Series 逐個套用 func。
4. Exploratory Data Analysis (EDA)
- What:EDA is a process of analyzing datasets to summarize their main characteristics. Any method that does not include formal modeling or hypothesis testing to look at the data is EDA, which often involves summary statistics and data visualizations.
- Why:在作任何 assumptions 之前先睇 data。幫手 identify obvious errors、了解 patterns、detect outliers / anomalous events、搵 variables 之間有趣嘅關係。
- How:Check for mistakes, assumptions;Detect outliers (cross-feature);Discover relationships among features;Preliminary selection of appropriate models。
Main types of EDA
| 類型 | 內容 | 幾時教 |
|---|---|---|
| Summary statistics | Aggregating statistics | 今講 |
| Sampling from DataFrames | 今講 | |
| Correlations | 今講 | |
| Grouping by | 下一講 | |
| Pivoting tables | 下一講 | |
| Visualization | Charts / plots | 之後幾講 |
「EDA 唔包括 formal modeling 或 hypothesis testing」:hypothesis testing 正正係 COM6005 Module 3 嘅 inferential statistics;EDA 屬於 COM6005 Module 1 嘅 descriptive statistics。COM6004 Chapter 3 Data Visualization 就係 EDA 嘅 visualization 部分。
5. Basic Aggregating Statistic Methods
Aggregating statistics 將一堆數值總結成一個值,可以用喺:一個 Series / column、DataFrame by columns、DataFrame by rows。
| Function | 用途 |
|---|---|
mean()、median() | Centre of the data |
std() | Spread of the data |
min()、max() | Range / span of the data |
quantile(0.25)、quantile(0.75) | Check the IQR(、) |
sum() | 加總;對 Boolean 就係數 True 嘅數目 |
nunique() | Number of unique values |
describe() | Summary of data |
mode() | Most popular values |
nlargest()、nsmallest() | Extreme values |
count() | Count non-NA cells for each column or row |
5.1 Series 例子:TEMP 同 PM2.5
air_quality['TEMP'].count() # 95685(non-NA 數目)
air_quality['PM2.5_cat'].count() # 95685(category 都數得)
air_quality['TEMP'].mean() # 13.729
air_quality['TEMP'].median() # 14.6
[air_quality['TEMP'].quantile(0.25), air_quality['TEMP'].quantile(0.75)] # [3.5, 23.4]
air_quality['PM2.5'].mean() # 83.478
air_quality['PM2.5'].median() # 59.0
[air_quality['PM2.5'].quantile(0.25), air_quality['PM2.5'].quantile(0.75)] # [23.0, 116.0]
air_quality['RAIN'].sum() # 6171.3
- TEMP:mean 13.73 ≈ median 14.6,IQR = 23.4 − 3.5 = 19.9,分佈大致對稱(mean 略低過 median,有少少 left skew)。
- PM2.5:mean 83.48 明顯 > median 59.0 → right-skewed(少數極高污染值拉高 mean)。IQR = 116 − 23 = 93。用 Week 6 嘅 outlier rule:upper fence = ,max 821 遠高過佢 → 有 outliers。
- 所以 skewed data 用 median / IQR 描述中心同 spread 比 mean / std 更 robust。
Mean vs median、skewness、IQR、five-number summary 同 COM6005 Module 1 Descriptive Statistics 一樣;outlier fence 見 COM6001 Week 6。
5.2 成個 DataFrame 做 aggregation:numeric_only vs select_dtypes
air_quality.info():dtypes 有 category(2)、datetime64[ns](1)、float64(12)、int32(6)、object(3)。直接 sum() 成個 DataFrame 會撞到非數值 columns,所以要揀 columns:
air_quality.sum(numeric_only=True) # numerical columns only(bool columns included)
air_quality.select_dtypes(include='number').sum() # 數值 columns(bool columns not included)
air_quality['wd'].sum() # object column:會將所有 strings 駁埋一齊(無意義)
兩個結果喺呢個 dataset 一樣(18 個數值 columns),因為 dataset 冇 bool column。分別只係 bool:numeric_only=True 會計 bool,select_dtypes(include='number') 唔會。
air_quality['PM2.5_cat'].value_counts()
# Unhealthy 34257, Moderate 19853, Good 13576,
# Unhealthy for sensitive groups 12457, Very unhealthy 10821, Hazardous 4721
air_quality['PM2.5_cat'].mode() # 0 Unhealthy (ordered category)
type(air_quality['PM2.5_cat'].mode()) # pandas.core.series.Series
mode() 回傳 Series,唔係單一值因為可以有多個 modes(並列最多次)。要攞最常見嗰個值,用 position 0:air_quality['PM2.5_cat'].mode()[0]。
5.3 Separating numerical and categorical columns
| 方法 | 適用 | 例子 / 限制 |
|---|---|---|
describe(include=…, exclude=…) | describe() | air_quality.describe(include=['number','category']);可以一個或多個 types;listed dtype 最少要有一個存在,否則 error(例如 include=['bool'],因為 dataset 冇 bool column) |
numeric_only=True | sum()、mean()、mode() 等 aggregative functions | air_quality.mean(numeric_only=True);唔可以指定 dtype;numeric_only=False 唔係「只揀非數值」,而係包晒所有 columns |
select_dtypes(include=/exclude=) 然後 aggregate | 想對非數值 columns 做 aggregation | air_quality.select_dtypes(exclude='number').mode() |
air_quality.mean(numeric_only=True) # PM2.5 83.48, PM10 111.90, TEMP 13.73, PRES 1011.40, ...
air_quality.mode(numeric_only=False) # 所有 columns:27603 rows × 24 columns(!)
5.4 Q2–Q4 練習
# Q2: five-number statistics for all float64 columns
air_quality.describe(include='float64')
# Q3: describe() for String columns(String 喺 pandas 係 object dtype)
air_quality.describe(include='object') # 輸出 count / unique / top / freq
# Q4
air_quality.select_dtypes(exclude='number').mode() # 27603 rows × 6 columns
air_quality.select_dtypes(exclude=['number','datetime']).mode() # 1 row:
# wd station PM2.5_cat TEMP_category day_of_week_name
# 0 NE Tiantan Unhealthy coldest Sunday
mode() 有 27603 rows?date_time column(datetime 唔係 number,所以冇被 exclude)入面好多 timestamps 出現次數一樣多,並列最多次。mode() 會回傳所有並列嘅 modes,所以 date_time 有 27603 個 modes;其他 columns 只有 1 個 mode,餘下 rows 就用 NaN 補齊。Exclude 埋 'datetime' 之後就只剩 1 row。(Q3 亦說明 describe() 對 object column 唔會出 five-number summary,而係 count / unique / top / freq。)
6. agg() for Aggregating Statistics
單一 aggregation 直接用 df.mean()、df.sum() 就得。如果想一次過睇幾個(例如 mean 同 sum 喺同一個 output),就用 df.agg()。
DataFrame.agg(func=None, axis=0, *args, **kwargs)
func:built-in function 名用引號(.agg('mean'));user-defined function 唔用引號(.agg(my_func))。axis:default 0。axis=0apply to columns;axis=1apply to rows。
air_quality[['TEMP','PM2.5']].agg([my_sum,'min','max','mean']) 嘅結果:一個 4 行 × 2 欄嘅表——my_sum:TEMP 1.313702e+06、PM2.5 7.987581e+06;min:−16.8、2.0;max:41.6、821.0;mean:13.72945、83.47788。即係一次過將自訂 function(my_sum,冇引號)同 built-in functions(有引號)套落每個 column,每個 function 變一行。
6.1 單一 function(同 apply 相似)
air_quality[['TEMP','PM2.5']].agg('mean', axis=1) # 每 row:4.25, 1.65, 0.80, ...
air_quality[['TEMP','PM2.5']].agg('mean', axis=0) # 每 column:TEMP 13.729446, PM2.5 83.477884
例如 row 0:TEMP = −0.5,PM2.5 = 9.0 → (−0.5 + 9.0) / 2 = 4.25 ✓。
6.2 多個 functions(list)
def my_sum(df):
return df.sum(axis=0)
air_quality[['TEMP','PM2.5']].agg([my_sum, 'min', 'max', 'mean']).T
# my_sum min max mean
# TEMP 1.313702e+06 -16.8 41.6 13.729446
# PM2.5 7.987581e+06 2.0 821.0 83.477884
.T(transpose)將 rows / columns 對調,令每個 feature 一行,易讀啲。留意 list 入面 my_sum 冇引號,'min' 有引號。
6.3 用 **kwargs 傳額外參數
air_quality[['TEMP','PM2.5']].agg('quantile', q=0.3) # TEMP 5.6, PM2.5 29.0(30th percentile)
def count_between(col, low, high):
return ((col < high) & (col > low)).sum()
air_quality['PM2.5'].agg(count_between, low=20, high=200, axis=0) # 64692
q=0.3、low=20、high=200 都係 keyword arguments,會原封不動傳入 function。count_between 用 Boolean mask 再 .sum() 數 True(PM2.5 喺 20–200 之間嘅 rows = 64,692)。
6.4 唔同 features 用唔同 functions(dictionary mapping)
# this is correct usage of agg() with different functions on different columns
air_quality[['PM2.5', 'PM2.5_cat']].agg({'PM2.5': ['min','max','mean'],
'PM2.5_cat':'nunique'})
# PM2.5 PM2.5_cat
# min 2.000000 NaN
# max 821.000000 NaN
# mean 83.477884 NaN
# nunique NaN 6.0
.agg()可以對唔同 features 用唔同 functions:傳入 {column: function 或 [functions]} dictionary。結果係一個 table,某 column 冇用到嘅 function 位置就係 NaN。呢個係 apply 做唔到(或者好麻煩)嘅。
6.5 Q5:自訂 function + agg
要求:WSPM 顯示 min / max / mean;wd 用自訂 function most_two_freq() 回傳最常見兩個值嘅 tuple。
def most_two_freq(col):
vc = col.value_counts() # 已經由大至小排序
return (vc.index[0], vc.index[1])
most_two_freq(air_quality['wd']) # ('NE', 'ENE')
air_quality[['WSPM', 'wd']].agg({'WSPM': ['min', 'max', 'mean'],
'wd': most_two_freq})
# WSPM wd
# min 0.000000 NaN
# max 12.000000 NaN
# mean 1.692044 NaN
# most_two_freq NaN (NE, ENE)
(Notebook 只顯示 expected output,function 留空;以上係參考答案。)
6.6 Summary on aggregative statistics
- 可以用一個值總結一個 column 嘅 functions:
count()、sum()、mean()、std()、max()、min()、mode()、nunique()… - Exam 好多都可以加
axis=1改成 row 方向:df.count(axis=1)、df.nunique(axis=1)、df.sum(axis=1)、df.mean(axis=1)、df.min(axis=1)、df.max(axis=1)、df.mode(axis=1)。 describe()用include=/exclude=;sum()、mean()、mode()用numeric_only=True;非數值 columns 先select_dtypes()。.agg()嘅好處:唔同 features 用唔同 functions。Built-in function 用' '包住,user-defined 唔使。
6.7 map vs apply vs agg 比較
map() | apply() | agg() | |
|---|---|---|---|
| 作用單位 | 每個 element | 每個 row / column(一個 Series) | 每個 column(或 row),可以多個 functions |
| 常見對象 | Series | DataFrame(亦有 Series.apply) | DataFrame / Series |
輸入 arg / func | dict、function、lambda、Series | 一個 function | function、list of functions、dict {column: function(s)} |
| 典型用途 | 換值 / recode(Monday → Weekday) | 用多個 columns 生成新 feature(axis=1) | Summary statistics 一次過出晒 |
| 輸出長度 | 同原 Series 一樣 | 視乎 function(aggregate → 縮短;逐 row 轉換 → 一 row 一值) | 通常縮短成 summary |
Q6 答案參考
- A:student ID
p123456→123456(逐個 cell 轉換)→map(),例如df['id'].map(lambda x: x[-6:]),或者 Week 8 嘅df['id'].str[-6:]。 - B:所有 columns 一行平均值 → 直接用 built-in
df.mean()(或df.agg('mean')/df.apply('mean'))。 - C:salary 用 mean、occupation 用 mode(唔同 columns 唔同 functions)→
agg()with a mapping:df.agg({'salary': 'mean', 'occupation': lambda s: s.mode()[0]})。
7. Sampling
- 用 sampling「take a peek」睇下 data。
- Large data:random sample 令 computation 容易啲。
- Machine learning:用一組 random samples train model,另外 hold 一組 samples 做 testing。
- Outcomes 唔平均(imbalanced)時做 over- 或 under-sampling。
DataFrame.sample(n=None, frac=None, replace=False, weights=None,
random_state=None, axis=None, ignore_index=False)
| 參數 | 意思 |
|---|---|
n | 抽幾多 rows(例如 200) |
frac | 抽幾大比例(例如 0.1 = 10%);同 n 二揀一 |
replace | False(default)= without replacement,同一 row 唔會抽兩次;True = with replacement,可能重複 |
weights | 每個 row 被抽中嘅權重(可用嚟 over-sample 少數類) |
random_state | Random seed,固定之後每次抽到一樣嘅 sample(reproducible) |
ignore_index | True 就將結果 index 重設為 0…n−1 |
Sampling.png:上面係全部 data 嘅 air_quality['TEMP'].hist(),兩條箭咀指去下面兩幅唔同嘅 sample(200) histogram:兩幅大致形狀同上面似(−10 至 35、兩個峰),但每條 bar 高度都唔同,即係每次 sample 結果有少少差異。
全部 95,685 rows 嘅 TEMP histogram:範圍約 −17 至 42;0–5 有一個峰(約 14,700),10 附近有凹位(約 12,300),20–25 係最高峰(約 17,800),35 以上同 −10 以下都好少。sample(200):同樣係雙峰(約 5–10 同 20 左右),範圍只係約 −8 至 35,冇咗兩邊最極端嘅值,同下面 describe() 比較嘅結論一樣。
air_quality['TEMP'].hist() # 全部 95,685 rows 嘅 histogram
air_quality.sample(200)["TEMP"].hist() # 只用 200 rows,形狀大致相似但較粗糙
air_quality.sample(2000, replace = True).duplicated().sum() # 16(有重複 rows)
replace=True 唔會避免同一 row 被抽多次,所以 output 可能有 duplicated rows,可以用 duplicated().sum() 檢查(呢次 2000 個入面有 16 個重複)。
Sample vs population:比較 describe()
| sample(200) 第 1 次 | sample(200) 第 2 次 | 全部 95,685 rows | |
|---|---|---|---|
| mean | 14.02 | 14.50 | 13.73 |
| std | 11.80 | 11.46 | 11.32 |
| min | −8.0 | −8.4 | −16.8 |
| 25% | 2.98 | 4.65 | 3.5 |
| 50% | 13.75 | 16.2 | 14.6 |
| 75% | 24.65 | 24.15 | 23.4 |
| max | 35.7 | 36.2 | 41.6 |
- 每次 sample 結果都唔同(冇設
random_state)→ sampling variability。 - Mean、median、std 同全體好接近:200 rows 已經可以大致描述 TEMP 分佈。
- 但 min / max(extreme values)差好遠:極端值好少,細 sample 好難抽中。所以用 sample 搵 outliers 唔可靠。
Sample statistic 估計 population parameter、sampling variability:COM6005 Module 1(descriptive vs inferential)同 Module 3 Confidence Interval。COM6004 Chapter 2 亦講 sampling(with / without replacement、sample size)。Train / test split 同 over-/under-sampling 會喺 COM6004 Classification 用到。
8. Correlation
假設每個 record 有兩個 random features(兩個 columns),我哋想量度佢哋點樣一齊變動(move together)。例如身高 同體重 :身高增加時,體重係咪都傾向增加?Covariance 同 Correlation 就係用嚟量度呢種 co-movement。
旁邊註明 correlation 係 covariance "Normalized w.r.t. the std":除以兩個 standard deviations 之後,數值就固定喺 −1 至 +1 之間,唔受單位影響。(留意呢度 covariance 用 ;pandas cov() 預設用 ,但係計 correlation 時分子分母會抵銷,所以 corr() 結果一樣。)
- Correlation coefficient 喺 −1 至 +1 之間。
- 越接近 1(或 −1),relationship 越強;越接近 0,relationship 越弱。
- 正數 = 一齊升(positive);負數 = 一個升另一個跌(negative)。
DataFrame.corr(method='pearson', min_periods=1, numeric_only=False)
method:default 用 Pearson coefficient;亦可以揀 Kendall 或 Spearman。min_periods:計 correlation 最少要幾多 observations;唔夠就回傳 NaN。numeric_only:只用數值 columns。
8.1 三種 methods(細 DataFrame 例子)
data = {'A': [45, 37, 42],
'B': [38, 31, 26],
'C': [10, 15, 17]}
df = pd.DataFrame(data)
df.corr(method='pearson', min_periods=3)
# A B C
# A 1.000000 0.458388 -0.583324
# B 0.458388 1.000000 -0.989268
# C -0.583324 -0.989268 1.000000
df.corr(method='kendall', min_periods=3) # A-B 0.333, A-C -0.333, B-C -1.0
df.corr(method='spearman', min_periods=3) # A-B 0.5, A-C -0.5, B-C -1.0
df.corr(min_periods=4) # 全部 NaN:只有 3 rows,唔夠 4 個 observations
| Pearson | Spearman | Kendall | |
|---|---|---|---|
| 量度 | Linear relationship | Monotonic relationship(strength and direction) | Strength of dependence(基於 rank 嘅 concordance) |
| 用乜計 | Actual values | Ranks | Ranks |
| 類型 | Parametric | Non-parametric | Non-parametric |
| Outliers / non-linear | 敏感 | More robust | More robust |
| Range | −1 至 +1(−1 perfect negative,0 none,+1 perfect positive) | ||
8.2 手計驗證(補充,slides 冇列公式)
Pearson A vs B:,。Deviations:A = (3.67, −4.33, 0.67),B = (6.33, −0.67, −5.67)。
- 分子:
- 分母:
- ✓(同 output 0.458388 一致)
Spearman A vs B:先轉 ranks。A = (45, 37, 42) → ranks (3, 1, 2);B = (38, 31, 26) → ranks (3, 2, 1)。Rank 差 ,,:
結果 0.5,同 output 一致 ✓。
Kendall A vs B:3 對 pairs。(row0, row1):A 跌、B 跌 → concordant;(row0, row2):A 跌、B 跌 → concordant;(row1, row2):A 升、B 跌 → discordant。 ✓。
Pearson correlation 同 COM6005 Module 4 Linear Regression 直接相關(simple linear regression 嘅 );COM6004 Chapter 2 亦有 correlation 做 similarity measure。
8.3 Linear vs monotonic:S、S1、S2、S3 例子
s = pd.Series([1, 2, 3, 4, 5])
s1 = pd.Series([5, 6, 7, 8, 9]) # s + 4
s2 = 5 - s # 4, 3, 2, 1, 0
s3 = s**3 # 1, 8, 27, 64, 125
df = pd.DataFrame({'S': s, 'S1': s1, 'S2': s2, 'S3': s3})
df.corr()
# S S1 S2 S3
# S 1.000000 1.000000 -1.000000 0.943118
# S1 1.000000 1.000000 -1.000000 0.943118
# S2 -1.000000 -1.000000 1.000000 -0.943118
# S3 0.943118 0.943118 -0.943118 1.000000
- S vs S1 = +1:S1 = S + 4,perfect linear(加常數唔影響 correlation)。
- S vs S2 = −1:S2 = 5 − S,perfect negative linear。
- S vs S3 = 0.943(唔係 1):S3 = 係完全 monotonic(一路升),但唔係直線,所以 Pearson < 1。如果用
method='spearman',ranks 完全一致,結果會係 1。 - Correlation 只講兩者一齊變動,唔代表 causation。
8.4 Air quality 例子
# if you have long columns, this is not recommended
air_quality[['PM2.5','PM10']].corr() # 0.877604
# if you have long columns, this is more recommended to avoid long computation time
air_quality[['PM2.5','PM10']].sample(200).corr() # 0.665054
air_quality[['PM2.5','RAIN']].corr() # -0.018063(幾乎冇 linear relationship)
Notebook 建議長 columns 先 sample() 再 corr() 去慳計算時間,但同一對 features,全體得 0.878,200 rows sample 只得 0.665:細 sample 嘅 correlation 估計可以偏差好大(尤其 PM2.5 呢類有極端值嘅 skewed data)。慳時間嘅同時要留意 sample size。
8.5 Q7:所有數值 columns 嘅 correlation matrix
air_quality.corr() 會出錯,因為有幾個 non-numeric columns。Notebook 嘅 corr_mat shape 係 (12, 12),columns 係 PM2.5、PM10、SO2、NO2、CO、O3、TEMP、PRES、DEWP、RAIN、WSPM、time_since_2022_weeks,啱啱好係 12 個 float64 columns,所以參考答案:
corr_mat = air_quality.select_dtypes(include='float64').corr()
corr_mat.shape # (12, 12)
corr_mat.loc['PM2.5','TEMP'] # -0.0903
corr_mat.loc['PM2.5',:].abs() # abs() 睇強度,唔理正負
corr_mat['PM2.5'].nlargest(5) # PM2.5 1.0, PM10 0.847, CO 0.767, NO2 0.671, SO2 0.464
corr_mat['PM2.5'].nsmallest(5) # WSPM -0.311, O3 -0.158, TEMP -0.090, PRES -0.048, time_since_2022_weeks -0.023
Notebook 入面 corr_mat 嘅 PM2.5–PM10 係 0.846921,但 In[113] 直接計係 0.877604。Notebook 冇顯示 Q7 答案點樣計 corr_mat,所以講義冇交代差異原因;以上 select_dtypes 寫法係根據 shape 同 column 名推斷。
8.6 Q8:解讀 correlation matrix
- 點解 diagonal 永遠係 1? 每個 feature 同自己比較,完全一齊變動(perfect positive linear relationship)。
- 總共幾多個 cross-correlation coefficients? 12 × 12 = 144 格,減 12 個 diagonal = 132 個 off-diagonal;matrix 係 symmetric(),所以 unique pairs = 個。
- PM2.5 同其他 features 嘅關係:
- Strong positive:PM10(0.85)、CO(0.77)、NO2(0.67)→ 呢啲污染物通常一齊升(同源,例如燃燒 / 交通)。
- Moderate positive:SO2(0.46)。
- Weak negative:WSPM(−0.31)→ 風速大,污染物較易被吹散;O3(−0.16)。
- Almost none(接近 0):TEMP(−0.09)、PRES(−0.05)、RAIN(−0.02)、time_since_2022_weeks(−0.02)。
8.7 搵最強嘅 six correlations:unstack()
corr_mat.unstack().sort_values(ascending=False).duplicated() # Length: 144
strongest_corr = corr_mat.unstack().sort_values(ascending=False).drop_duplicates().head(6)
# PM2.5 PM2.5 1.000000
# PM10 PM2.5 0.846921
# DEWP TEMP 0.826201
# PM2.5 CO 0.766816
# CO NO2 0.714192
# PM2.5 NO2 0.670551
unstack():將 12 × 12 matrix 變成 144 個值嘅 Series,index 係 (row, column) pair。sort_values(ascending=False):由大至小排。drop_duplicates():因為 matrix symmetric,(PM10, PM2.5) 同 (PM2.5, PM10) 數值一樣,排完會相鄰;drop 走重複值。12 個 diagonal 1.0 亦會縮剩一個。head(6):攞頭 6 個。
- 結果第一個係 PM2.5–PM2.5 嘅 1.0(self-correlation),所以真正嘅 feature pairs 只有 5 對。
drop_duplicates()係按數值去重:如果兩對唔同 features 恰巧 correlation 一模一樣,都會被 drop 錯。ascending=False只搵最強 positive;想包括強 negative,要先.abs()再排。
9. Semi-Structured Data(programming 唔考)
標題寫明「Introduction to Semi-Structured Data (programming not required by exam)」。但 Summary 要求:can recognize XML or JSON format;can tell conceptually their differences from CSV;can tell when to use them。所以概念要識,parsing code 唔使背。
9.1 Structured vs Semi-structured vs Unstructured
| 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 |
同 COM6002 Week 1 嘅 structured / semi-structured / unstructured data 分類一樣;JSON / API data collection 見 COM6002 Week 2 Data Collection。
9.2 點解 XML 同 JSON 係 semi-structured
<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%"}
]
}
- Self-describing:每個值都帶住自己嘅名(tag
<name>或 key"name"),schema 嵌喺 data 入面。 - Hierarchical:可以 nested(
grades入面有math、science)。 - Flexible:Alice 有 grades,Bob 冇 grades 但有 attendance,兩個 records 結構唔同都得;CSV 就要每 row 同一組 columns。
- 認樣:XML 用
<tag>…</tag>同 attributes(id="101");JSON 用{ }objects、[ ]arrays、"key": value。
9.3 XML vs JSON vs CSV:幾時用
| Format | Use when… |
|---|---|
| XML | 需要 strong schema validation 同 data integrity(用 XML schema definition .xsd file 檢查 conformance,睇 .xsd 就知所有 tags 同 tree structure,例如 data.gov.hk 嘅 special traffic news);document-oriented data;enterprise systems 要 standardized data exchange;需要 rich metadata 同 namespaces |
| JSON | Building web APIs / web services;application configuration files;mobile app development;real-time data exchange |
| CSV | Spreadsheet / simple tabular data;quick data analysis 同 prototyping;data migration between systems;non-technical users 要睇 / 改 data |
9.4 Parsing 例子(只供參考,唔考 programming)
import xml.etree.ElementTree as ET
tree = ET.parse(dir_path+'CountryData.xml')
root = tree.getroot()
print("Root Tag:", root.tag) # data
for child in root:
print(child.tag, child.attrib) # country {'name': 'Liechtenstein'} / Singapore / Panama
for country in root.findall('country'):
name = country.get('name') # 讀 attribute
gdppc = int(country.find('gdppc').text) # 讀 child tag 嘅 text
# Highest 141100, Lowest 13600, Average 71533.33
XML 係一棵 tree:root → children(country)→ 再落一層(gdppc)。Attribute 用 .get(),tag 入面嘅文字用 .find(...).text。
import requests
url = "https://nominatim.openstreetmap.org/search"
params = {"q": "Shatin", "format": "jsonv2", "limit": 1}
headers = {"User-Agent": "my-notebook-geocoder/1.0 (...)"} # Nominatim usage policy 要求
response = requests.get(url, params=params, headers=headers, timeout=15)
response.raise_for_status()
results = response.json() # JSON → Python list / dict
top = results[0]
float(top["lat"]), float(top["lon"]) # 22.3815527, 114.1904697
JSON API 例子(OpenStreetMap geocoder):送 HTTP request,response.json() 將 JSON 轉成 Python list / dict,再用 key 攞值。
10. Summary
- Semi-structured data:識認 XML / JSON format;概念上分得清同 CSV 嘅分別;知道幾時用。
- EDA: Aggregating statistics:
- Basic aggregating stat functions:
sum()、mean()… - Aggregate 之前識揀唔同 dtypes。
- Exam 知道 map / apply / agg 嘅分別。
- Exam 知道點解要 sample。
- Exam 識計 correlation matrix 同描述佢(邊啲 features strongly / weakly correlated)。
- Basic aggregating stat functions:
自我測驗 Self-test
Q1. s = pd.Series(['Mon', 'Sat', 'mon']),s.map({'Mon': 'Weekday', 'Sat': 'Weekend'}) 輸出係乜?
['Weekday', 'Weekend', NaN]。Dictionary mapping 冇對應 key('mon' 細楷)就變 NaN,唔會保留原值。
Q2. df[['PM2.5','PM10']].apply('mean') 同 df[['PM2.5','PM10']].apply('mean', axis=1) 各自輸出幾多個值?
第一個(axis=0):每個 column 一個值 → 2 個值。第二個(axis=1):每個 row 一個值 → 95,685 個值。
Q3. 點解 df['PM2.5']/df['PM10'](44.8 μs)比 df[['PM2.5','PM10']].apply(pm_ratio, axis=1)(300 ms)快咁多?
前者係 vectorized operation,pandas 一次過喺底層處理成個 column;後者要逐 row call Python function 95,685 次,每次都有 function call 同建立 row Series 嘅 overhead。所以 apply 係 last resort。
Q4. 以下邊句會出錯?(a) df.apply('mean') (b) df.apply(my_func) (c) df.apply('my_func')(my_func 係 notebook 自己 define)
(c)。自己 define 嘅 function 唔應該加引號;字串會被當成 pandas built-in function 名去搵,搵唔到就出錯。(a) built-in 加引號正確;(b) user-defined 唔加引號正確。
Q5. 寫一行 code:對 Price 計 min、max、mean,同時對 District 計 nunique。
df.agg({'Price': ['min', 'max', 'mean'], 'District': 'nunique'})。輸出係 4 rows × 2 columns 嘅 table,冇用到嘅組合係 NaN。
Q6. describe(include=['bool']) 喺 air_quality 會出錯,點解?想對 object columns 計 mode 應該點寫?
因為 include= 列出嘅 dtype 最少要有一個喺 DataFrame 存在,而 air_quality 冇 bool column。Object columns 計 mode:air_quality.select_dtypes(include='object').mode()(numeric_only 唔可以揀非數值 columns)。
Q7. 一個 column 嘅 mean = 83.5,median = 59.0。描述分佈形狀,並建議用乜統計量描述中心。
Mean 明顯大過 median → right-skewed(少數極大值拉高 mean)。應該用 median(同 IQR)描述中心同 spread,因為佢哋對 outliers robust。
Q8. 列出三個 sampling 嘅原因;sample(2000, replace=True) 同 sample(2000) 有乜分別?
原因:(1) large data 用 random sample 令計算容易;(2) ML 用 random samples train,hold 另一組做 testing;(3) outcomes 唔平均時做 over-/under-sampling。replace=True 係 with replacement,同一 row 可以被抽多次(可用 duplicated().sum() 檢查);default replace=False 每 row 最多抽一次。
Q9. ,。Pearson 同 Spearman correlation 邊個會等於 1?點解?
Spearman = 1:ranks 完全一樣(1, 2, 3, 4),monotonic increasing。Pearson < 1(約 0.98):關係唔係直線。同 notebook S vs S3(0.943)道理一樣。
Q10. 一個 10 × 10 correlation matrix 有幾多個 unique cross-correlation coefficients?點解 unstack() 之後要 drop_duplicates()?
個。Matrix 係 symmetric,(A, B) 同 (B, A) 數值一樣,unstack() 後每對出現兩次(diagonal 1.0 亦重複 10 次),排序後 drop 重複先唔會重複計。
Q11. 以下 data 各適合 XML、JSON 定 CSV?(a) 手機 app 同 server 實時交換資料;(b) 銀行之間要嚴格 schema validation 嘅 data exchange;(c) 俾 non-technical 同事用 Excel 改嘅銷售表。
(a) JSON(mobile app / real-time / web API);(b) XML(strong schema validation,用 .xsd;enterprise standardized exchange);(c) CSV(spreadsheet / simple tabular,non-technical users 可睇可改)。
一頁總結 Cheat sheet
s.map(arg):逐 element;arg= dict(冇 key → NaN)、function name(len、str.upper,唔加括號)、lambda(lambda x: … if … else …)、custom function;要 assign 先保存。df.apply(func, axis=0/1, args=()):每次一個 column(0, default)或 row(1);built-in 用'mean',自訂用my_func;慢,last resort;axis=1用多個 columns 生成新 feature。- Vectorized(
df['a']/df['b'],μs)≫apply(axis=1)(ms,逐 row call)。 - EDA = 唔包 formal modeling / hypothesis testing,用 summary statistics + visualization 總結 data;目的:搵 errors、patterns、outliers、relationships、初步揀 model。
- Aggregation:
mean/mediancentre;stdspread;min/maxrange;quantile(0.25/0.75)IQR;sum(Boolean 數 True);nunique;mode(回傳 Series,用[0]);count(non-NA);nlargest/nsmallest;describe。 - 揀 dtype:
describe(include=/exclude=)(dtype 要存在);sum/mean/mode(numeric_only=True)(唔可以指定 dtype,包 bool);非數值 →select_dtypes(exclude='number')再 aggregate。 mode()對 datetime 會回傳所有並列 modes → 好多 rows + NaN 補齊。df.agg(func, axis, **kwargs):單一('mean')、list([my_sum,'min','max'])、kwargs('quantile', q=0.3)、dict({'PM2.5':['min','max'], 'PM2.5_cat':'nunique'})。.Ttranspose。- 選 method:逐 cell 換值 →
map;成行 / 成列套 function →apply;多個 / 唔同 columns 唔同 summary →agg。 df.sample(n, frac, replace, weights, random_state);原因:peek、大 data 易計、train/test、over-/under-sampling;sample 估 mean/std 準,但 min/max 唔準。df.corr(method='pearson'|'kendall'|'spearman', min_periods);−1 至 +1,近 ±1 強、近 0 弱;Pearson = linear(actual values),Spearman / Kendall = ranks(non-parametric、robust to outliers、monotonic);observations <min_periods→ NaN。- Matrix:diagonal = 1,symmetric,unique pairs = ;PM2.5 強正相關 PM10 / CO / NO2,弱負 WSPM,近 0 TEMP / RAIN。
- 最強 pairs:
corr_mat.unstack().sort_values(ascending=False).drop_duplicates().head(6)(第一個係 self 1.0)。 - Structured(rigid schema:SQL、CSV)/ Semi-structured(self-describing、hierarchical、flexible:XML、JSON、YAML、NoSQL)/ Unstructured(text、image、video)。XML → schema validation(.xsd)、enterprise、metadata;JSON → web API、config、mobile、real-time;CSV → tabular、quick analysis、migration、non-technical users。