COM6001 Computing with Big Data · Week 8
Text Cleaning & Feature Scaling
COM6001 · Week 8 · Source: Lecture08_Text_Cleaning_Scaling.pdf(Jupyter notebook export, 18 pages)· Lecturer: Dr. Aileen Hou
Contents · 12 sections
🎯 本講重點 (Learning outcomes)
- 用 string accessor
.str對成個 column 做 string methods:lower()/upper()、strip()、replace()、len()、contains()、startswith()、slicing.str[-3:]。 - 用 Boolean mask 組合多個 text 條件(
&、~)去 filter / count rows(NYC taxi 例子)。 - Regular expressions (Regex):
.\d\w\s*+?^$;Pythonre.findall()、re.sub();pandas.str.extract()、.str.contains(pattern)(HK property address 例子)。 - Feature scaling:點解要 scale(K-means distance 例子);
StandardScaler、MinMaxScaler、RobustScaler嘅公式、對 outliers 嘅敏感度、output range 同幾時用邊個。
⭐ 考試必溫 Top-5
- 三條 scaling 公式:Standard ;Min-max ;Robust 。
- Outlier 敏感度:MinMax 最敏感 > Standard 中等 > Robust 最唔敏感;只有 MinMax 有固定 range [0, 1]。
- Rules of thumb:無強 outliers / distance-based 或 assume normality → Standard;要固定 interval / activation function input → MinMax;好多 outliers / heavy tails → Robust。
- Scaler 只可以 fit 喺 training data,validation/test 用已 fit 嘅 scaler 去
transform(避免 data leakage)。 .straccessor + string method 處理成個 column;regex 基本符號同str.extract()(capture group( ))/str.contains()用法。
封面寫住「COM 6001 Lecture 07」,但檔名同內容係 Lecture 08。開頭 outline 列咗 Review of Feature Transformation、Cleaning texts、Scaling、Mapping new values、Applying functions,不過今份 notebook 實際只有 Cleaning texts 同 Scaling 兩部分有內容;最尾 summary 亦重溫 Week 7 嘅 datetime、encoding、binning。
1. Review: Feature Transformation(Week 7 回顧)
Outline 一開始重溫上堂嘅 categorical → numerical 轉換:
- Nominal data(無次序,例如顏色)→ one-hot encoding:
pd.get_dummies(df, prefix=...)。 - Ordinal data(有次序,例如 Low/Mid/High)→ ordinal encoder:
OrdinalEncoder(categories=[[...]])。
Summary slide 仲提到:datetime 用 .dt accessor;binning 要分清 cut()(equal-width / 自訂 edges)同 qcut()(equal-sized / quantile-based)。詳細內容睇 Week 7。
2. Data preparation:NYC Federal Taxi dataset
import pandas as pd
dir_path = './Datasets/week_8/'
nyc = pd.read_csv(dir_path + 'nyc_federal_taxi.csv')
nyc.info() # 267 rows × 7 columns,全部 dtype = object
nyc.head(3)
7 個 columns:Date、Time、PU_Address(pick-up)、DO_Address(drop-off)、Routing Details、PU_Address.1、Status(例如 Cancelled / Arrived / Assigned)。全部都係 object(string),所以呢份 data 正正需要 text cleaning。
PU_Address.1 呢個名係 pandas 自動加嘅:原 CSV 有兩個 column 都叫 PU_Address,read_csv 會將第二個改名做 PU_Address.1 避免重複。
3. Cleaning texts with string methods
Text data 通常由唔同 users / collectors、喺唔同 platforms 輸入,所以格式好少一致。Slides 列出常見問題同對應 method:
| 問題 | Method | Slides 例子 |
|---|---|---|
| Upper/lower cases 唔統一 | .str.lower() / .str.upper() | nyc['PU_Address'].str.lower() |
| White spaces(頭尾多咗空格) | .str.strip() | nyc['PU_Address'] = nyc['PU_Address'].str.strip() |
| Abbreviations 唔統一 | .str.replace(old, new) | nyc['PU_Address'].str.replace('ny;', 'new york;') |
| Fixed-length terms(zipcode、postcode、時間)長度檢查 | .str.len() | nyc['Time'].str.len() → 全部應該係 8 |
| Filter 含某 keyword 嘅 entries | .str.contains('kw') | nyc[nyc['PU_Address'].str.contains('airport')] |
| 抽每格某一段 characters | slicing .str[a:b] | nyc['PU_Address'].str[-3:] |
| 開頭 / 結尾係某 sub-string | .str.startswith() / .str.endswith() | nyc[nyc['PU_Address'].str.startswith('best western')] |
記住:一定要經 .str accessor,string method 先會 apply 到 每一個 cell(cell level)。
Observation 1 on the NYC Taxi Data
- Cases 唔統一(有啲大楷、有啲細楷)。
- 有啲 white spaces 肉眼唔易見,例如
head(10)入面 line 3 同 line 6 尾有多餘空格。
證據:nyc['PU_Address'].str[-3:] 抽最後三個字元,大部分係 NY;、NJ;,但 row 2 出咗 Y;,因為尾有一個空格,最後三格變成「Y; 」。
nyc['PU_Address'] = nyc['PU_Address'].str.lower() # 統一做細楷
nyc['PU_Address'] = nyc['PU_Address'].str.strip() # 去頭尾空白
nyc['PU_Address'].str[-3:] # 而家全部係 'ny;' / 'nj;'
print("hello world\n") # 會多印一行空行
print("hello world\n".strip()) # strip() 連 \n 都會去埋
print(" hello world ".strip()) # 'hello world'
- String methods 唔會 in-place 修改:
nyc['PU_Address'].str.lower()只會回傳新 Series,一定要 assign 返:nyc['PU_Address'] = ...。 strip()只去 頭尾 嘅 whitespace(space、\n、\t),中間嘅多個空格唔會處理 → 要用 regexre.sub(r'\s+', ' ', text)(見 Section 4)。contains()default 係 case-sensitive:lower 咗PU_Address之後要搵'airport';但DO_Address冇 lower,所以 Task 4 要搵'Flushing'(大楷 F)。
Tasks(課堂練習)
Exam 呢幾題係典型「寫一行 pandas code」題型:
# Task 1:刪走每個地址尾嘅 state field(空格 + 州名縮寫 + ';'),例如 " ny;"
nyc['PU_Address'] = nyc['PU_Address'].str[:-4] # 去最後 4 個字元
# Task 2:有幾多條 route 由 airport 出發?
nyc[nyc['PU_Address'].str.contains('airport')].shape[0] # 29
# Task 3:PU_Address 以 'best western' 開頭,而且去 airport(DO_Address 含 airport)
nyc[(nyc['PU_Address'].str.startswith('best western'))
& (nyc['DO_Address'].str.contains('Airport'))] # 1 row(index 102)
# Task 4:狀態係 'Arrived',而且 drop-off 係 Flushing
nyc[nyc['DO_Address'].str.contains('Flushing') & (nyc['Status'] == 'Arrived')] # 2 rows
PDF 入面 Task 3 嗰行 code 喺 & (nyc['DO_Addre… 被截斷,上面 contains('Airport') 係按題目意思補返(DO_Address 冇 lower,原文寫 "John F Kennedy International Airport");請對照原 notebook 第 5 頁。
Task 1 嘅 .str[:-4] 假設每格尾都係「空格 + 兩個字母 + ;」。Output 第 262 行(907 59 st., bk ny; st: w66 st./broadway, ny)顯示有啲地址有兩段,只會刪最後一段——固定長度 slicing 對格式唔一致嘅 data 唔夠 robust,呢個正正係引入 regex 嘅原因。另外組合條件時每個條件要加括號,用 & / |,唔可以用 and / or。
4. Introduction to Regular Expressions (Regex)
Regular expressions 係用嚟 match、search、manipulate text 嘅 patterns。Data science 用途:
- Cleaning messy text data
- Extracting specific information from strings
- Validating data formats(emails、phone numbers 等)
Lecturer 特別提到:course project 好大機會遇到格式唔一致嘅 dataset,regex 可以有效處理。Python 用 re module:import re。
| Pattern | Meaning | Example |
|---|---|---|
. | Any single character | a.c matches "abc", "a1c" |
\d | Any digit (0-9) | \d\d\d matches "123" |
\w | Any word character (letters, digits, underscore) | \w+ matches "hello" |
\s | Any whitespace | a\sb matches "a b" |
* | Zero or more occurrences | ab*c matches "ac", "abc", "abbc" |
+ | One or more occurrences | ab+c matches "abc", "abbc"(not "ac") |
? | Zero or one occurrence | colou?r matches "color" and "colour" |
^ | Start of string | ^Hello:以 "Hello" 開頭 |
$ | End of string | world$:以 "world" 結尾 |
ab*c 嘅 * 只修飾 b,所以係「a、任意個 b(可以 0 個)、c」。* vs + 嘅分別就係「0 個得唔得」:ab*c match "ac",ab+c 唔 match。? 就係「可有可無」。
Using re module with Python
import re
# Example 1:搵晒所有數字(一個或多個連續 digits)
text = "Order #12345 was placed on 2026-10-19"
re.findall(r'\d+', text) # ['12345', '2026', '10', '19']
# Example 2:抽 email address
text = "Contact us at info@example.com or support@test.org"
re.findall(r'[\w\.-]+@[\w\.-]+\.\w+', text) # ['info@example.com', 'support@test.org']
# Example 3:將多個空格變一個
text = "This are many spaces"
re.sub(r'\s+', ' ', text) # 'This are many spaces'
re.findall(pattern, text):回傳所有 non-overlapping matches 嘅 list。re.sub(pattern, repl, text):將所有 match 替換成repl。r'...'(raw string):令\d嘅 backslash 唔會俾 Python 當 escape character。- Email pattern 拆解:
[\w\.-]+(一個或多個 word char / 點 / 減號)+@+ domain[\w\.-]++\.(真正嘅點,要 escape)+\w+(com / org)。[...]係 character class:入面任何一個字元都 match。
Regex with pandas:HK property data
pandas 可以直接經 .str accessor 用 regex。例子用 HK property dataset(Datasetv2.csv,2020–2023 交易),抽 1000 rows:
data_path = "./Datasets/week_8/"
df_full = pd.read_csv(data_path + "Datasetv2.csv")
df_short = df_full.sample(1000, random_state=42) # 固定 random_state,結果可重現
df_short['date'] = pd.to_datetime(df_short['date'], format='%d/%m/%Y', errors='coerce')
df_short['date'].max(), df_short['date'].min() # 2023-03-09, 2020-03-11
address 例子:Grand Jete, Phase 1, Tower 2, 7/F, Flat J、Miami Mansion, 3/F, Flat 3、Tierra Verde, Phase 2, Block 10, Lower Floor, ...。
# Task 1:抽 estate name(第一個逗號之前嘅部分)→ 用嚟同其他 dataset 合併計 per-estate trends
df['Estate'] = df['address'].str.extract(r'^(.*?)(?:,|$)')
# Grand Jete, Phase 1, ... → 'Grand Jete'
# Miami Mansion, 3/F, ... → 'Miami Mansion'
# Task 2:抽 Phase 編號
df['Phase'] = df['address'].str.extract(r'Phase\s+(\d+)')
# 'Phase 1' → '1';'Phase 4B' → '4';冇 Phase → NaN
# Task 3:數有幾多 rows 冇 floor pattern
floor_pattern = r"\b(?:Higher|Middle|Low|Lower)\s+Floor\b"
rows_without_floor_category = (
~df_short["address"].str.contains(floor_pattern, case=False, na=False)
).sum()
print(rows_without_floor_category) # 771逐個拆解:
^(.*?)(?:,|$):^由開頭;( )係 capture group,str.extract()回傳嘅就係呢部分;.*?係 lazy(non-greedy):盡量 match 少啲字元;(?: )係 non-capturing group(只分組、唔抽出);,|$表示「遇到逗號或者去到字串尾」就停。所以抽到第一個逗號前嘅 estate name;冇逗號嘅地址就成個抽晒。Phase\s+(\d+):字面 "Phase" + 一個或多個空白 + capture 一個或多個 digits。"4B" 只抽到 4,因為\d唔 match B。冇 match 嘅 row 回傳NaN。\b(?:Higher|Middle|Low|Lower)\s+Floor\b:\b係 word boundary(防止 match 到 "Slower Floor" 呢類字中間);|係 OR;case=False唔分大細楷;na=False將 missing 當 False;~反轉 Boolean;.sum()數 True 嘅數目(True = 1)。
- Greedy vs lazy:如果寫
^(.*)(?:,|$)(冇?),.*會食到最後一個逗號,抽到 "Grand Jete, Phase 1, Tower 2, 7/F",唔係 estate name。 str.extract()一定要有至少一個 capture group( ),否則會出 error;(?: )唔計 capture group。str.contains(pattern)default 會將 pattern 當 regex(regex=True),所以搵 "." 呢類特殊字元時要 escape 或者設regex=False。
COM6002 SQL 入面 LIKE '%airport%' ≈ pandas str.contains('airport');LIKE 'best western%' ≈ str.startswith('best western')。COM6004 Chapter 2 Data Quality(inconsistent values、duplicate data)就係呢類 text cleaning 要解決嘅問題。
5. Scaling:點解要 scale?
- 唔同 variables 通常有唔同 scales:例如一個 feature 係 0–10(產品價錢),另一個係 10,000–50,000(有幾多間店賣)。
- Scaling:converting the scale of numeric data so that they're on a comparable scale。
- 點解重要?例如 unsupervised learning 嘅 K-means clustering:clusters 係根據 inter-sample distances 搵出嚟,距離近嘅會被分入同一個 cluster。
| age | income | |
|---|---|---|
| John | 25 | 31,000 |
| Amy | 60 | 30,000 |
| Mike | 55 | 28,000 |
John 同 Amy 年齡相差 100%+,income 只差約 3%;但因為 income 嘅數值大好多,Euclidean distance 幾乎完全由 income 決定,結論變成「Amy 同 John 近過同 Mike」——age 完全冇影響力。Scaling 之後兩個 features 先至有可比較嘅影響力。
三種方法嘅共通目標:(或多或少)將 data 嘅 center 變 0、spread 變 1。
Demo data:9000 個 正常值 + 100 個 高端 outliers。
import numpy as np
X = pd.DataFrame({
'value': np.concatenate([
np.random.normal(50, 10, 9000), # majority normal values
np.random.normal(100, 5, 100), # outliers on the high end
])
})
X.hist() # 主峰喺 50 附近,100 附近有一小撮 outliers
6. Standard Scaling (StandardScaler)
- 將 variables 轉成 standard normal variables(mean = 0, std = 1)。
- 方法:減 mean,再除 standard deviation。
- Mean 同 std 會被 outliers 拉動,但冇 min-max scaling 咁嚴重。
- 唔會限制喺某個指定 range。
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
X_ss = ss.fit_transform(X)
X_ss.mean() # 4.06e-16 ≈ 0
X_ss.std() # 1.0
Before / after histogram 嘅形狀完全一樣,只係 x-axis 由 20–120 變成大約 −3 至 6(outliers 喺 4 附近)。Scaling 係 linear transformation,唔會改變 distribution 形狀。
呢個就係 COM6005 嘅 z-score 。COM6004 Chapter 2 嘅 standardization / normalization 亦係同一回事。注意 sklearn 用 population std(除 ),pandas .std() default 除 。
7. Min-max Scaling (MinMaxScaler)
- 將 variables 轉成 (0, 1) interval 入面嘅連續值。
- 將最小值 map 去 0、最大值 map 去 1。
- Sensitive to outliers:想所有值喺 0 至 1 之間就好用,但可以被 outliers 嚴重影響。
from sklearn.preprocessing import MinMaxScaler
mm = MinMaxScaler()
X_mm = mm.fit_transform(X)
X_mm.mean() # 0.360
X_mm.std() # 0.104 ← spread 好細
點解 std 只有 0.104?因為 max 係 outlier(約 120),正常值(30–70)全部被壓縮喺 0.2–0.5 呢個窄區間。Comparison plot 入面 MinMax 嘅 density curve 係一條好尖(peak ≈ 4)嘅窄峰。
8. Robust Scaling (RobustScaler)
- 同 Min-Max 類似,但改為用 interquartile range。
- 減 median,再按 quantile range scale(default 係 IQR = 25th 至 75th quantile 之間嘅距離)。
- 將 IQR()map 成 1;避免 outliers 造成偏差。
- Robust to outliers,但 唔保證 結果喺 0 至 1 之間。
from sklearn.preprocessing import RobustScaler
rs = RobustScaler()
X_rs = rs.fit_transform(X)
X_rs.mean() # 0.025(median-centered,所以 mean 唔一定係 0)
X_rs.std() # 0.831
Robust scaling 用嘅 median、、、IQR 同 Week 7 outlier detection 嘅 IQR method 一樣:佢哋都係 resistant statistics,唔會被極端值拉動(COM6005 Module 1:median vs mean)。
Worked example(自己計,加深理解)
Data:(100 係 outlier)。mean = 40,population std = ,min = 10,max = 100,,,,IQR = 20。
| x | Standard | Min-max | Robust |
|---|---|---|---|
| 10 | −0.949 | 0 | −1.0 |
| 20 | −0.632 | 0.111 | −0.5 |
| 30 | −0.316 | 0.222 | 0 |
| 40 | 0 | 0.333 | 0.5 |
| 100 | 1.897 | 1 | 3.5 |
觀察:Min-max 將 4 個正常值擠入 0–0.33;Robust 令正常值之間距離保持清楚(每 10 單位 = 0.5),outlier 就明顯咁「突出」喺 3.5,超出 [0, 1]。
9. Comparison of Scaling Methods
| StandardScaler | MinMaxScaler | RobustScaler | |
|---|---|---|---|
| 公式 | |||
| Effect on outliers | Moderately affected:mean、std 隨 outliers 移動 | Highly sensitive:outliers 將大部分 data 壓縮入細範圍 | Much less sensitive:median 同 IQR 減低極端值影響 |
| Output range | 無固定 range;mean ≈ 0,unit variance | 固定,通常 [0, 1];保留 relative spacing | 無固定 range;保留 median-centered spread,對 long tails robust |
| When to prefer | Algorithms assume zero-centered data:PCA、logistic regression、linear models、k-means | 需要 bounded range:neural networks with sigmoids、visualization、expect [0, 1] 嘅 algorithms | Data 有 outliers,想減低影響但 唔 clip |
Practical considerations
- Exam Always fit scaler on training data only,再用已 fit 嘅 scaler transform validation / test data。
- MinMaxScaler 喺新 data 超出 training 嘅 min/max 時會有問題(extrapolation:output 會 < 0 或 > 1)。
- 有 outliers、而且要保留 non-outliers 之間嘅 relative order 時,RobustScaler 通常係 safe default。
- StandardScaler 通常改善 gradient-based methods 嘅 convergence;MinMax 將 inputs bounded,可以加快 neural nets 訓練。
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X_train, X_test = train_test_split(X, test_size=0.2, random_state=42)
ss = StandardScaler()
X_train_s = ss.fit_transform(X_train) # 只喺 train 計 mean / std
X_test_s = ss.transform(X_test) # test 用 train 嘅 mean / std,唔好再 fit!
如果喺成個 dataset(包括 test)先 fit scaler,test data 嘅資訊(佢嘅 mean、max 等)就「漏」咗入 training,test performance 會偏樂觀。對 test set 用 fit_transform 係錯,只可以 transform。
- No strong outliers,algorithms assume normality 或者用 distances → StandardScaler。
- Need fixed interval 或者 activation functions 嘅 inputs → MinMaxScaler。
- Many outliers 或 heavy tails → RobustScaler。
COM6004 Chapter 4 KNN 同 Chapter 5 K-means / DBSCAN 都係 distance-based,一定要先 scale features;Chapter 2 嘅 proximity measures(Euclidean distance)亦提過 standardization。
10. Summary on Feature Transformation
- Date and Time:用
.dtaccessor。 - Feature encoding:one-hot vs ordinal encoding 嘅 use cases。
- Binning:
cut()vsqcut()嘅分別。 - Cleaning texts:唔好忘記 string accessor
.str;.str+ string method 可以處理成個 column / df。 - Feature scaling:唔同 scalers 嘅 use cases,幾時用邊個。
自我測驗 Self-test
Q1. nyc['PU_Address'].str.lower() 執行之後,nyc 入面嘅 column 變咗細楷未?
未。String methods 回傳新 Series,唔會 in-place 修改。要寫 nyc['PU_Address'] = nyc['PU_Address'].str.lower()。
Q2. 寫一行 code:數有幾多 rows 嘅 PU_Address(已 lower)含 "airport" 而且 Status 係 "Cancelled"。
nyc[nyc['PU_Address'].str.contains('airport') & (nyc['Status'] == 'Cancelled')].shape[0]。也可以用 (...).sum() 直接數 Boolean mask。注意 & 兩邊條件要加括號。
Q3. 以下邊個 strings 會被 ab+c match?"ac"、"abc"、"abbbc"。換成 ab*c 呢?
ab+c:abc、abbbc("ac" 唔得,因為 + 要至少一個 b)。ab*c:三個都 match(* 容許 0 個 b)。
Q4. re.findall(r'\d+', "Flat 12, 3/F, Block 7B") 回傳乜?
['12', '3', '7']。\d+ 抽每段連續 digits;"7B" 只抽到 7。
Q5. 點解抽 estate name 要用 ^(.*?)(?:,|$) 而唔係 ^(.*)(?:,|$)?(?:...) 有乜作用?
.*? 係 lazy,會喺第一個逗號停;.* 係 greedy,會食到最後一個逗號。(?:...) 係 non-capturing group:用嚟組合「逗號或字串尾」,但唔會被 str.extract() 抽出嚟,所以結果只有一個 column(estate name)。
Q6. Data = [2, 4, 6, 8, 50]。計 x = 8 嘅 min-max 同 robust scaled value(,)。
Min-max:。Robust:median ,IQR = 8 − 4 = 4,。Min-max 將 2–8 擠入 0–0.125,因為 outlier 50 決定咗 max。
Q7. Demo data 經 MinMaxScaler 之後 std 只有 0.104,經 RobustScaler 之後係 0.831。解釋點解。
MinMax 嘅分母係 max − min,被高端 outliers(約 100–120)撐大,正常值被壓縮喺細範圍,所以 spread 細。Robust 嘅分母係 IQR,只由中間 50% data 決定,唔受 outliers 影響,正常值保持原本嘅 spread(std 接近 1)。
Q8. 下面三個情況各揀邊個 scaler?(a) neural network 用 sigmoid activation;(b) income data 有好多極端富豪;(c) 冇明顯 outliers,做 PCA。
(a) MinMaxScaler(需要 bounded [0, 1] input);(b) RobustScaler(many outliers / heavy tail);(c) StandardScaler(PCA assume zero-centered data)。
Q9. 以下 code 有乜問題?
X_train_s = ss.fit_transform(X_train); X_test_s = ss.fit_transform(X_test)
Test set 唔應該再 fit。應該用 ss.transform(X_test),用 training data 嘅 mean / std。再 fit 會令 train 同 test 用唔同標準 scale,亦違反「fit on training data only」。
Q10. 喺 age/income 例子,點解 d(J, A) ≈ 1000.61 證明需要 scaling?
之中 相對 可以忽略,distance 幾乎等於 income 差距。Age 差 35 年(100%+)完全冇影響 clustering,所以要 scale 到 comparable scale。
一頁總結 Cheat sheet
.straccessor:lower/upper(cases)、strip(頭尾空白、\n)、replace(abbreviations)、len(fixed-length 檢查)、contains(keyword filter)、startswith/endswith、.str[-3:]/.str[:-4](slicing)。- String methods 唔 in-place → 要 assign 返;
containscase-sensitive(可設case=False、na=False)。 - 組合條件:
df[(cond1) & (cond2)];反轉~;數目.shape[0]或.sum()。 - Regex:
.任意字元;\ddigit;\wword char;\swhitespace;*0+;+1+;?0 或 1;^開頭;$結尾;\bword boundary;( )capture;(?: )non-capturing;|OR;.*?lazy。 re.findall(p, s)回傳 list;re.sub(p, repl, s)替換;s.str.extract(p)抽 capture group(冇 match → NaN);s.str.contains(p)Boolean。- Scaling 原因:features scale 唔同 → distance-based algorithms(K-means)被大數值 feature 主導()。
- Standard:,mean 0 std 1,無固定 range,outliers 中度影響。
- Min-max:,[0, 1],對 outliers 最敏感,新 data 超出 min/max 有 extrapolation 問題。
- Robust:,median-centered、IQR → 1,最 robust,但唔保證喺 [0, 1]。
- Syntax:
from sklearn.preprocessing import StandardScaler / MinMaxScaler / RobustScaler;X_s = scaler.fit_transform(X);輸出係 NumPy array。 - Fit on training only →
transformvalidation / test。 - Rules:無 outliers / distances / normality → Standard;fixed interval / activation → MinMax;outliers / heavy tails → Robust。