據(jù)分析與Python聚類建模實戰(zhàn))
簡介這是一套圍繞學生校園消費行為分析題的Python建模項目專為高校期末大作業(yè)、課程設計等場景打造。資源包共6個文件包含5個Python腳本和1個原始數(shù)據(jù)壓縮包腳本按照不同任務模塊編寫如基本數(shù)據(jù)探查、消費特征提取、關聯(lián)分析或預測建模等下載解壓后無需額外修改即可運行并可直接作為答辯材料的基礎工程。整個zip體積僅4.89MB目錄層級簡潔便于按任務順序理解代碼邏輯。目前已有618人學習下載是同類作業(yè)中熱度較高的參考模板。讀者可獲得一整套可復用的分析流程框架、數(shù)據(jù)預處理與建模代碼、任務拆解思路及結果展示方式尤其適合希望快速搭建項目骨架、降低排錯成本的同學。1. 校園消費行為分析到底在做什么先把期末大作業(yè)的驗收點擺出來晚上十點半的食堂刷卡機前排著剛下晚課的學生一張一卡通流水背后是幾十萬條可以建模的數(shù)據(jù)。這份期末大作業(yè)最容易踩的誤區(qū)是以為把數(shù)據(jù)丟給 sklearn跑出一個聚類結果就完事。真實的驗收點有三個——能不能把原始流水清洗成一張可分析的特征表能不能用合適的建模方法分出有業(yè)務含義的人群以及能不能在答辯現(xiàn)場十分鐘內把「數(shù)據(jù)—特征—模型—結論」這條鏈路講清楚。這里的 Python 建模落點不是比拼準確率而是比拼可解釋性適合正在做數(shù)據(jù)分析課設、數(shù)學建模入門、或者想認真完成期末大作業(yè)的同學。2. 數(shù)據(jù)清洗與特征工程把一卡通流水變成能建模的二維表2.1 拿到原始流水后先別急著建模先檢查字段和樣本校園一卡通消費流水在不同學校的導出格式略有差異但核心字段基本穩(wěn)定卡號或學號、交易時間、交易金額、商戶名稱、商戶類型、收支標志。最好的習慣是先用 Excel 或 pandas 打開前 100 行肉眼看一遍數(shù)據(jù)長得什么樣而不是直接開始寫聚合代碼。曾經(jīng)有人拿到數(shù)據(jù)后直接按卡號分組求均值結果發(fā)現(xiàn)「人均消費」只有 0.15 元一查才知道有大量刷卡充值記錄混在消費記錄里充值金額都是整百把均值拉低了。我一般會先寫一段極簡的檢查代碼輸出數(shù)據(jù)的行數(shù)、唯一卡數(shù)、時間范圍和金額分布這幾個指標能快速暴露數(shù)據(jù)集的真實規(guī)模和分析難度。重點是看三段信息交易時間是不是統(tǒng)一格式、金額列有沒有負數(shù)、同一張卡同一秒同一商戶是否存在重復記錄。這三類問題如果不處理后續(xù)所有統(tǒng)計口徑都會偏而且偏得悄無聲息。import pandas as pd df pd.read_csv(card_flow.csv, parse_dates[trade_time]) print(總記錄數(shù):, len(df)) print(唯一卡號數(shù):, df[card_no].nunique()) print(時間范圍:, df[trade_time].min(), -, df[trade_time].max()) print(金額描述:) print(df[amount].describe()) print(負數(shù)金額條數(shù):, (df[amount] 0).sum()) print(重復記錄條數(shù):, df.duplicated().sum())這段代碼先讀數(shù)據(jù)再把交易時間解析為 datetime 類型。nunique 用于數(shù)出去重后的卡號數(shù)量duplicated 用來判斷全字段重復的行有多少。輸出結果之后你會對本次建模的數(shù)據(jù)形態(tài)有個整體印象數(shù)據(jù)量是幾萬還是幾十萬時間跨度是三個月還是一整年負數(shù)是偶爾幾筆還是大面積存在。先看全貌再動手避免后續(xù)在錯誤假設上堆代碼。2.2 處理退款負數(shù)、充值記錄和重復刷卡常見的坑有兩個方向。第一個方向是負金額食堂窗口操作失誤后的退款、超市退貨、洗衣房余額退回都會造成負數(shù)交易。如果分析目標是「消費行為」我建議把負數(shù)交易單獨拎出來只在退款分析里用不混進消費頻率和消費金額的計算。第二個方向是充值記錄一卡通充值本質是轉賬不是消費通常交易類型字段里會有「充值」「消費」的標記沒有標記的話可以用商戶類型或者金額規(guī)律來過濾。刪除或標記這些記錄后再處理重復刷卡。有些食堂閘機數(shù)據(jù)會同時寫入兩條相同記錄時間戳精確到秒且金額一樣這種情況直接用 drop_duplicates 刪掉。但要注意如果刷完卡發(fā)現(xiàn)有退款退款記錄和原消費記錄成對出現(xiàn)此時不能簡單按全字段去重否則會把正常的一進一出也刪掉。處理時我通常只對「卡號 商戶 時間 金額」四字段完全相同的行去重保留第一次出現(xiàn)。df df[df[amount] 0].copy() # 先排除退款和充值只看正向消費 df_clean df.drop_duplicates( subset[card_no, merchant, trade_time, amount], keepfirst ) print(清洗后記錄數(shù):, len(df_clean))這里的邏輯是先假設金額大于 0 都是消費需要你確認自己的數(shù)據(jù)里有沒有負的消費、有沒有正數(shù)充值。drop_duplicates 的 subset 參數(shù)非常關鍵它指定了「按哪些列判斷重復」避免把同一時間在同一個商戶買兩筆相同金額商品的正常行為誤刪。keep 參數(shù)用 first 保留第一條在時間序列場景下等于默認選擇先發(fā)生的記錄。2.3 時間特征拆分星期、小時、早晚高峰一卡通流水里的交易時間是最有建模價值的字段它決定了你能不能在報告里畫「一天消費曲線」和「一周消費熱力圖」。清洗完成后我會把時間拆成三個新列小時、星期、是否為工作日。小時用來判斷早餐、午餐、晚餐、夜宵的高峰分布星期用來分析周末和上學日的差異工作日標記則幫助后續(xù)分群時識別作息規(guī)律。dt df_clean[trade_time] df_clean[hour] dt.dt.hour df_clean[weekday] dt.dt.weekday # 0周一, 6周日 df_clean[is_workday] df_clean[weekday].apply(lambda x: 1 if x 5 else 0)dt.hour 和 dt.weekday 是 pandas 的 datetime 訪問器效率高且不容易寫錯。weekday 范圍是 0 到 60 代表周一6 代表周日。is_workday 是自定義規(guī)則默認周一至周五算工作日。如果你的學校周六還有課或者周五下午就放假這個標記要按自己學校的校歷調整不能照抄默認值。2.4 構建「以人為單位」的消費特征表建模時最常用的主體是「人」也就是每張卡對應一行特征。把流水按卡號分組統(tǒng)計消費總金額、總次數(shù)、平均金額、消費活躍天數(shù)、高頻時段等字段最終得到一張寬表。這張表就是后續(xù) KMeans、DBSCAN 的輸入。daily df_clean.groupby([card_no]).agg( total_amount(amount, sum), total_count(amount, count), avg_amount(amount, mean), active_days(trade_time, nunique), avg_hour(hour, mean), ) daily[count_per_active_day] daily[total_count] / daily[active_days] print(daily.head())groupby 后面的 agg 是 pandas 推薦的聚合寫法每一行指定新列名、原始列和聚合函數(shù)。total_amount 是總消費額total_count 是總刷卡次數(shù)active_days 是去重后的活躍天數(shù)用 nunique 統(tǒng)計。avg_hour 是消費時間的均值它有一個隱患跨午夜的作息會被平均到奇怪的位置比如晚上 23 點和凌晨 1 點的平均會得到 12 點。這種情況我用「圓形均值」處理或者干脆把它換成前三個固定時段的消費占比。2.5 衍生特征三餐占比、夜間消費比例、消費熵基礎特征只能描述消費多少不能描述消費習慣。期末答辯時最能打動老師的是「這個簇的人早餐比例明顯高」而不是「這個簇平均消費 12.3 元」。因此我額外構建三類衍生特征。第一類是三餐和夜宵占比用小時區(qū)間切分。第二類是夜間消費比例把 23 點到次日 5 點定義為夜宵時段統(tǒng)計夜宵金額占總金額的比例。第三類是消費熵衡量一個人刷卡金額的分散程度——如果一個人每頓都是 10 元整熵很低如果忽而 3 元忽而 40 元熵很高說明飲食結構波動大。def meal_type(hour): if 6 hour 10: return breakfast elif 10 hour 15: return lunch elif 15 hour 20: return dinner elif 20 hour 24 or hour 5: return night else: return other df_clean[meal] df_clean[hour].apply(meal_type) meal_cross pd.crosstab(df_clean[card_no], df_clean[meal], normalizeindex) def entropy(series): p series[series 0] return -(p * np.log(p)).sum()import numpy as np from scipy.stats import entropy as scipy_entropy def cal_entropy_by_group(group): val_counts group[amount].value_counts(normalizeTrue) return scipy_entropy(val_counts)第一個函數(shù)按小時區(qū)間把每筆交易歸入早餐、午餐、晚餐或夜宵。pd.crosstab 配合 normalizeindex 得到每張卡在不同餐段的消費次數(shù)占比。第二個函數(shù)計算金額分布的熵我直接調用 scipy.stats.entropy它接受概率分布數(shù)組并返回非負熵值。熵越大代表不同金額的分布越分散越小代表消費金額集中在少數(shù)幾個固定檔位比如總是刷 8 元和 12 元套餐。2.6 標準化為什么用 StandardScaler不用 MinMaxScaler特征表建好后量綱差異非常明顯消費總金額可能是幾千早餐占比只有 0.1。如果直接把原始值喂給聚類算法金額會主導距離計算。常見做法是做標準化。StandardScaler 會把每個特征變成均值為 0、方差為 1 的分布適合 KMeans 和 DBSCAN 這種基于距離的模型。MinMaxScaler 會把數(shù)據(jù)壓縮到 0~1 區(qū)間問題在于它很容易被極端值拉偏而校園消費里炒飯、夜宵或校外大額支出很容易產(chǎn)生極端值。from sklearn.preprocessing import StandardScaler feature_cols [total_amount, total_count, avg_amount, active_days, count_per_active_day, breakfast_ratio, dinner_ratio, night_ratio, entropy] X daily[feature_cols].fillna(0) scaler StandardScaler() X_scaled scaler.fit_transform(X)fillna(0) 是因為有些卡可能沒有夜宵記錄占比為空。fit_transform 在這段代碼里直接完成了兩步操作fit 計算均值和標準差transform 應用標準化。等到建模階段你只需把 X_scaled 傳入聚類算法。注意保留原始特征表 daily后面畫圖、打標簽、寫報告都需要它而不是直接使用標準化后的矩陣。3. 建模方案KMeans、DBSCAN、孤立森林怎么選3.1 先定目標再選模型不要先跑代碼很多期末作業(yè)翻車的原因是拿到數(shù)據(jù)就開始找聚類代碼跑完卻講不清每一類代表什么。建模之前必須回答一個問題這次分析到底要得出什么結論如果你的題目是「分析學生消費行為」最合理的目標是分群畫像把學生分成規(guī)律干飯型、高消費夜宵型、低消費節(jié)儉型等若干類。如果你的題目是「識別異常消費」那應該用異常檢測模型而不是聚類。如果你的題目是「根據(jù)前兩個月消費預測后一個月」那就進入回歸或時間序列的范疇不是同一套流程。在這份校園消費分析作業(yè)里最常見也最容易講清楚的是「聚類 畫像」。KMeans 能分出消費水平不同的群體每個群體的特征中心就是畫像。DBSCAN 則適合發(fā)現(xiàn)不規(guī)則形狀的群體還有機會把「每天只刷一頓飯」的極端群體標記為噪聲點。孤立森林不是聚類它的用途是識別偏離正常消費模式的人。建議把它們放在同一份作業(yè)里分別回答「共性分群」和「異常個體」兩個問題報告結構會很完整。3.2 聚類數(shù)怎么定肘部法則和輪廓系數(shù)KMeans 需要手動指定簇數(shù) k這個 k 不能拍腦袋。業(yè)界用的兩個指標分別是 SSE 肘部法則和輪廓系數(shù)。SSE 是簇內樣本到中心點的距離平方和k 越大 SSE 越小但下降到某個點后收益驟減那個拐點就是肘部。輪廓系數(shù)則衡量每個樣本和自身簇的相似度同類別緊湊、不同類別分離時系數(shù)高范圍是 -1 到 1越接近 1 越好。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score sse [] sil [] k_range range(2, 9) for k in k_range: km KMeans(n_clustersk, random_state42, n_init10) labels km.fit_predict(X_scaled) sse.append(km.inertia_) sil.append(silhouette_score(X_scaled, labels)) for k, s, si in zip(k_range, sse, sil): print(fk{k}, SSE{s:.1f}, silhouette{si:.4f})KMeans 的計算受初始中心點影響所以每一次迭代都指定 random_state42保證結果可復現(xiàn)。n_init10 表示用 10 組不同的初始中心運行算法最后返回 SSE 最小的一組避免隨機性帶來翻車。silhouette_score 在 X_scaled 上計算取值越大說明簇與簇之間分離越好。我的習慣是優(yōu)先看肘部拐點再看 silhouette 是否超過 0.25如果超過 0.5 反而要警惕特征是否過于簡單比如只靠金額一列劈成了兩半。3.3 用 KMeans 跑通最小建模流程并打上人群標簽確定 k 之后就可以跑最終的聚類。以 k4 為例把聚類結果合并回原始特征表就能看到每一類人的消費規(guī)律。這里還要注意一點KMeans 聚類是按距離切分的它不知道食堂、超市、奶茶店的業(yè)務含義打完標簽后必須回看特征均值給每個簇起一個人類看得懂的名字。k 4 km KMeans(n_clustersk, random_state42, n_init10) cluster_labels km.fit_predict(X_scaled) daily[cluster] cluster_labels cluster_profile daily.groupby(cluster)[feature_cols].mean() print(cluster_profile.round(2))fit_predict 一次性完成訓練和預測返回每個樣本的簇編號。groupby 后取均值得到每類人的平均消費金額、平均活躍天數(shù)、早餐占比等??吹綌?shù)據(jù)后再命名cluster 0 如果平均金額高、活躍天數(shù)多、夜宵占比高就命名為「活躍夜宵型」cluster 1 如果總金額低、早餐占比高、頻次少就命名為「規(guī)律早餐型」。命名是報告的點睛之筆不要直接寫「群組 0」。3.4 DBSCAN 的取舍噪點本身就是一種結果KMeans 會把每個樣本都分到一個簇哪怕這個樣本和其他任何樣本都不像。DBSCAN 則會把這些樣本直接標記為噪點也就是標簽 -1。在校園消費場景里這很有價值低活躍、偶發(fā)消費、作息不規(guī)律的學生會在 DBSCAN 中被暴露出來。不過 DBSCAN 對參數(shù)非常敏感兩個參數(shù) eps 描述鄰域半徑min_samples 描述成為核心對象所需的最少樣本數(shù)。from sklearn.cluster import DBSCAN db DBSCAN(eps0.8, min_samples10) db_labels db.fit_predict(X_scaled) n_clusters_db len(set(db_labels)) - (1 if -1 in db_labels else 0) print(DBSCAN 發(fā)現(xiàn)簇數(shù):, n_clusters_db) print(噪點數(shù)量:, (db_labels -1).sum()) daily[dbscan_label] db_labelseps 表示如果某個樣本的鄰域內至少有 min_samples 個樣本它才會被當作核心點。eps 設太小會把群體拆得零碎設太大又把所有人都劃進一個大簇。對于標準化后的數(shù)據(jù)我一般把 eps 設在 0.5 到 1.2 之間min_samples 設在 10 到 20。這里的技巧是先跑一次看噪點比例是否在 5%~15%噪音比例太高說明參數(shù)過緊可以立刻調整。3.5 用孤立森林找異常消費不需要任何標簽孤立森林是「無監(jiān)督異常檢測」算法它不依賴標簽而是隨機切分特征空間異常點因為分布稀疏只需要很少的切分次數(shù)就能被孤立出來。校園消費場景中它可以識別刷卡金額異常大、消費頻率異常低或夜間消費異常多的人。這里要特別強調異常不等于壞人可能是校外人員借卡、訪客臨時消費也可能是有學生因病長時間不在校。from sklearn.ensemble import IsolationForest iso IsolationForest(contamination0.02, random_state42) anomaly_labels iso.fit_predict(X_scaled) daily[anomaly] anomaly_labels print(daily[anomaly].value_counts())contamination 參數(shù)表示你預估的數(shù)據(jù)集中異常值比例這里設 0.02 也就是假設 2% 的人消費模式異常。fit_predict 返回 1 表示正常-1 表示異常注意方向和聚類標簽正好相反寫報告前容易看反。IsolationForest 的核心優(yōu)點是快幾十萬行數(shù)據(jù)也只需要幾秒鐘。缺點是結果不好解釋它不會告訴你為什么異常需要回到原始特征表逐個看異常樣本的數(shù)值。3.6 驗證聚類不是「隨機碰運氣」聚類模型的驗證是期末答辯時最容易被人追問的環(huán)節(jié)。老師說「你這個聚類結果會不會換一組初始值就完全變了」如果你答不上來前后就會陷入被動。我通常做兩個驗證第一個是穩(wěn)定性驗證把 random_state 改成其他值多次跑 KMeans比較每個簇的人數(shù)比例是否保持穩(wěn)定第二個是樣本驗證隨機抽取 80% 的樣本重新聚類再把剩余 20% 預測到最近的簇中心觀察整體分布是否一致。from sklearn.model_selection import train_test_split X_train, X_test train_test_split(X_scaled, test_size0.2, random_state7) km_temp KMeans(n_clusters4, random_state0, n_init10).fit(X_train) train_labels km_temp.predict(X_train) test_labels km_temp.predict(X_test) print(pd.Series(train_labels).value_counts(normalizeTrue).sort_index()) print(pd.Series(test_labels).value_counts(normalizeTrue).sort_index())這里用 train_test_split 把樣本切分成 80% 訓練、20% 測試。predict 方法會把新樣本分配到距離最近的中心。如果兩次的人群占比差距在 3% 以內說明結果不是偶然。如果差距很大優(yōu)先檢查特征表是否被少量極端值主導。4. 期末大作業(yè)避坑指南5 條踩過的數(shù)據(jù)與建???.1 時間字段解析失敗按天聚合結果全是 NaN現(xiàn)象用 pd.to_datetime 轉換交易時間后發(fā)現(xiàn)小時列全部變成 NaN按天聚合后圖像一片空白。 原因原始數(shù)據(jù)里時間格式不統(tǒng)一有的行是「2025-03-12 12:30:45」有的行是「2025/03/12 12:30」甚至還有「2025年3月12日」。to_datetime 無法自動識別所有格式解析失敗的行變成了空值。 解決先用 errorscoerce 強制轉換標記解析失敗的行再檢查失敗樣本的時間格式寫一個自定義解析函數(shù)補齊。最簡單的方式是讓 to_datetime 的 format 參數(shù)明確指定格式。不要相信 Excel 導出的時間是「標準格式」必須有這一層檢查。4.2 退款負數(shù)拉低均值消費金額變成負的現(xiàn)象早餐平均消費是 -3.2 元食堂搶了學生的錢。 原因退款記錄被當作消費納入聚合負金額抵消了正金額。 解決把金額列拆成正負兩套邏輯。如果只做消費分析直接保留 amount 0需要研究退款時把負數(shù)的絕對值拿出來單獨分析。注意充值記錄通常是正數(shù)如果不按交易類型區(qū)分也會混進消費金額。建議先看交易類型字段的值分布再決定過濾條件。4.3 去重把正常購買記錄誤刪了現(xiàn)象同一張卡在同一家店買了兩次相同金額的飲料時間相差 1 秒結果被全字段去重刪除。 原因沒有指定去重列直接用 drop_duplicates() 把「卡號 商戶 時間 金額」四列完全相同的記錄當成了重復數(shù)據(jù)。其實一秒鐘刷兩次卡是合理的人類行為比如一次刷卡本人消費一次幫同學帶飯。 解決只對高頻重復類型做去重判斷標準是「同一秒內同一商戶同一金額且同一卡號」。如果數(shù)據(jù)里有交易流水號字段優(yōu)先用流水號去重。沒有流水號時subset 要加上三個以上維度避免誤刪。4.4 卡號字符串混進特征聚類結果被「身份證號」支配現(xiàn)象聚類出的四個簇分別對應卡號開頭四個數(shù)字段業(yè)務上毫無意義。 原因把 card_no 或學號列當成了數(shù)值特征標準化后進入聚類模型。KMeans 基于歐氏距離卡號中任何一點數(shù)字差異都會被當成距離導致聚類完全偏離消費行為。 解決第一步是刪除卡號、學號、姓名等身份標識列第二步檢查特征表里是否有其他高基數(shù)列比如商戶名稱。需要用獨熱編碼處理商戶偏好字段或者直接不把商戶名稱作為聚類輸入改用它構造「食堂消費占比」這類聚合特征。4.5 輪廓系數(shù)很高但人群畫像完全解釋不通現(xiàn)象k2 時輪廓系數(shù)高達 0.6看起來聚類效果很好但打開畫像發(fā)現(xiàn)一群人全是總消費金額極高的夜宵大戶另一群人全是幾乎不刷卡的低頻用戶中間人群被強行切成了兩半。 原因特征中存在極端值KMeans 對這種尖峰分布極其敏感少數(shù)高消費樣本把整個簇中心拉走了。輪廓系數(shù)衡量的是幾何分離程度不判斷業(yè)務合理性。 解決先對特征做裁剪比如 95% 分位數(shù)截斷極端消費金額或者使用分位數(shù)變換替代標準化。更重要的是必須回看每個簇的人數(shù)比例。如果人數(shù)最少那一類只占 2%它更可能是噪聲而不是人群此時通過 W 檢驗調整 k 或改用 DBSCAN 處理離群點。5. 可視化與結果報告讓期末答辯在 10 分鐘內有底氣5.1 三種能在一頁 PPT 上講清楚的圖數(shù)據(jù)建模的成果需要用圖形呈現(xiàn)評卷老師沒有耐心一行行看 DataFrame。我通常畫三張圖第一張是所有學生的分時段消費折線圖展示早中晚高峰第二張是周消費熱力圖橫軸星期、縱軸小時用顏色深淺表示消費次數(shù)第三張是每個聚類小組的平均日消費金額分布圖用來突出分群差異。import matplotlib.pyplot as plt import seaborn as sns plt.rcParams[font.sans-serif] [SimHei, PingFang SC, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False fig, axs plt.subplots(1, 3, figsize(16, 4)) hourly_count df_clean.groupby(hour).size() axs[0].plot(hourly_count.index, hourly_count.values, markero) axs[0].set_title(分時段消費次數(shù)) pivot df_clean.pivot_table(indexhour, columnsweekday, valuesamount, aggfunccount).fillna(0) sns.heatmap(pivot, axaxs[1], cmapYlOrRd) axs[1].set_title(每周消費規(guī)律熱力圖) daily_group daily.groupby(cluster)[total_count].mean() daily_group.plot(kindbar, axaxs[2], color[#4C72B0, #55A868, #C44E52, #8172B2]) axs[2].set_title(各簇平均消費次數(shù)) plt.tight_layout()先設置中文字體否則圖表里的中文會變成方塊這是 matplotlib 默認字體導致的。分時段曲線把「早中晚三個高峰」一句話講完。熱力圖的 x 軸是周一到周日y 軸是 0 到 23 點能直觀看到周末上午消費明顯后移。第三張柱狀圖用于展示各簇畫像差異柱子的高度對比就能支撐「人群可分」的結論。5.2 用表格把人群畫像輸出成報告素材圖像適合口頭表達書面報告里還需要一張精確的表格。我一般會把每個簇的核心特征整理成這種格式簇標簽、自定義人群名、人數(shù)占比、平均日消費金額、平均日活躍次數(shù)、早餐占比、夜宵占比。這張表可以直接粘貼到期末報告的模型結果部分也能在答辯被追問時快速定位到對應數(shù)據(jù)。簇人群標簽人數(shù)占比日均消費(元)日均次數(shù)早餐占比夜宵占比0規(guī)律早餐型28%9.62.142%5%1活躍夜宵型19%21.33.49%38%2節(jié)制消費型36%6.81.231%11%3周末集中型17%13.51.614%20%表格里的值來自 cluster_profile 的聚合結果不要手工填數(shù)也不要先把報告寫好了再倒推數(shù)據(jù)。如果發(fā)現(xiàn)某種類型的占比太小比如只有 2%說明這個簇可能是異常值簇應該回去調整模型參數(shù)而不是硬搬進報告。5.3 報告結構抽掉代碼也能自洽期末報告嚇退同學的往往不是分析而是不知道按什么順序寫。我建議直接按項目流程組織第一部分寫背景與目標說明為什么用校園消費數(shù)據(jù)做建模想解決什么問題第二部分寫數(shù)據(jù)來源與預處理包括字段說明和清洗流程第三部分寫特征工程解釋你選擇了哪幾個特征、為什么沒有選別的第四部分寫模型選擇與參數(shù)確定第五部分寫分群結果與人群畫像最后一段寫對學生消費管理和個性化服務的建議。其中第三部分最容易被一筆帶過。實際上老師最看重的是特征構造思路。你可以寫「用 pandas 從原始流水中拆出小時、星期、餐段三類時間特征」也可以展開說明「消費熵衡量了消費習慣的穩(wěn)定性」后者更能體現(xiàn)建模思考。但如果你的作業(yè)要求里沒有要求寫建議那最后一段不寫也沒問題不用為了湊篇幅硬編對策。5.4 被老師問「為什么選 KMeans」時這樣回答不慌張答這種問題講究「性能 業(yè)務」雙線。先說業(yè)務這次分析的目的是給全校學生做消費人群分層KMeans 的每個簇可以用中心點描述天然適合輸出畫像。再說性能KMeans 在大樣本下速度快算法復雜度接近線性咱們學校這條數(shù)據(jù)集有二十多萬條流水跑一遍只需要幾秒鐘。更重要的是它只有 k 一個超參數(shù)調參路徑清晰不像 DBSCAN 的 eps 和 min_samples 需要反復試。如果老師追問為什么不用 GMM 或層次聚類可以說 GMM 假設每個簇服從高斯分布校園消費金額分布實際上偏態(tài)明顯這個假設不一定成立層次聚類的計算量隨樣本數(shù)增長較快面對幾萬張卡的特征表不劃算?;卮饡r不需要推倒重來只要把 KMeans 的優(yōu)勢和你對數(shù)據(jù)分布的觀察講清楚即可。6. 收尾一步用一個月做穩(wěn)定性驗證讓報告再上一個等級期末作業(yè)交完后真正拉開差距的是驗證部分。我最后一件事永遠是「留一個月數(shù)據(jù)當驗證集用前幾個月的參數(shù)復跑一次」。具體操作是把整個數(shù)據(jù)集按月份切分比如用 3—5 月數(shù)據(jù)建模再用 6 月數(shù)據(jù)計算每個樣本到已得簇中心的距離重新分配簇標簽最后對比兩輪聚類人數(shù)比例。如果一個簇的人數(shù)比例從 28% 跳到 64%說明建模只抓住了月份效應而不是消費習慣需要回到特征設計去加入「學期前半段 / 后半段」標記。另一個值得做的擴展是特征重要性驗證。把聚類標簽作為目標變量用隨機森林訓練一個分類器看哪些特征貢獻度最高。如果早餐占比排第一說明你的分群主要靠早餐習慣切分報告里就要突出描述這個特征如果消費總金額排第一則要承認這是個「消費水平分層」模型而非「作息規(guī)律分層」模型。這一步不復雜卻能讓報告里所有結論都有依據(jù)。我當年做這份作業(yè)時最深的教訓是急著跑結果跳過驗證直接寫結論。答辯時老師對著投影儀說「你把 5 月的數(shù)據(jù)抽出來跑一遍」結果簇分布完全變了當場冷場。后來我養(yǎng)成了先切一個月數(shù)據(jù)做穩(wěn)定性驗證的習慣再小的分析也按這個流程走。希望幫到你這 10 分鐘會讓你在講臺上的語氣跟別人不一樣。本文還有配套的精品資源點擊獲取