據(jù)分析:從清洗到經(jīng)濟評估的完整實踐)
簡介一份面向高校師生及Python初學(xué)者與數(shù)據(jù)分析學(xué)習(xí)者的校園消費行為分析項目基于校園卡消費數(shù)據(jù)完整實現(xiàn)從數(shù)據(jù)加載、預(yù)處理、特征工程到KMeans消費聚類、群體畫像與Plotly交互可視化的流程并借助Streamlit提供Web交互界面可輔助理解學(xué)生經(jīng)濟狀況與消費規(guī)律。壓縮包共24個文件大小約13.06MB主要包含Python源碼、CSV樣例數(shù)據(jù)、Markdown運行文檔、PDF與DOCX說明報告以及pyc編譯文件、啟動腳本和演示圖片各類型分工明確便于對照學(xué)習(xí)與二次開發(fā)。目前已有142人學(xué)習(xí)下載。資源覆蓋完整的項目結(jié)構(gòu)數(shù)據(jù)加載模塊支持多種編碼與合并預(yù)處理模塊完成異常過濾和時間特征提取模型模塊基于KMeans默認(rèn)劃分低中高三個消費群可視化模塊生成消費時段分布、食堂占比和三維聚類圖另有說明報告梳理方案細節(jié)適合用于課程設(shè)計、畢業(yè)設(shè)計或數(shù)據(jù)挖掘入門實踐。1. 校園消費行為分析與學(xué)生經(jīng)濟評估一張校園卡流水能算出什么一所三萬人的高校一卡通系統(tǒng)每天產(chǎn)生幾十萬條消費流水。食堂、超市、開水房、浴室、校醫(yī)院每筆記錄都帶著學(xué)號、時間、商戶、金額。這套數(shù)據(jù)表面上是后勤對賬的依據(jù)但換個視角它就是一份高頻更新的學(xué)生生活成本樣本。校園消費行為分析做的事是把這些流水清洗成規(guī)范的結(jié)構(gòu)化數(shù)據(jù)再提取每名學(xué)生的消費水平、消費習(xí)慣和消費結(jié)構(gòu)學(xué)生經(jīng)濟評估則是基于這些特征輔助判斷學(xué)生的實際經(jīng)濟狀況為資助評定、勤工助學(xué)崗位分配提供數(shù)據(jù)參考。項目資源里通常包含完整的 Python 源碼、脫敏后的校園消費數(shù)據(jù)集、運行文檔教程和說明報告適合數(shù)據(jù)分析課程設(shè)計、高校信息化部門的數(shù)據(jù)挖掘?qū)嵺`以及想拿真實場景練手 Python 全流程的從業(yè)者。2. 消費流水?dāng)?shù)據(jù)結(jié)構(gòu)與清洗拿到一份校園卡數(shù)據(jù)先做什么2.1 字段設(shè)計與數(shù)據(jù)字典先搞清楚每一列在說什么校園消費流水在不同學(xué)校的導(dǎo)出格式略有差異但核心字段基本一致。一份典型的流水表通常包含學(xué)號、消費時間、消費金額、商戶編號、商戶名稱、消費類型、余額這幾列。學(xué)號是關(guān)聯(lián)學(xué)生基本信息的唯一鍵消費時間精確到秒金額單位為元。消費類型字段常見取值包括“ POS 消費”“補貼消費”“退款”“圈存”其中退款和圈存不是真實消費行為做行為分析前必須篩掉或單獨統(tǒng)計。拿到數(shù)據(jù)的第一件事不是寫代碼而是逐字段確認(rèn)口徑。常見做法是寫一段簡單的 pandas 探查腳本輸出每列的非空數(shù)量、唯一值數(shù)量和樣例值對照數(shù)據(jù)字典核對。學(xué)號列如果混入教職工卡號或者消費金額出現(xiàn)負(fù)數(shù)都會在后續(xù)分析中產(chǎn)生干擾。字符編碼也要注意從 Oracle 導(dǎo)出的數(shù)據(jù)常有 GBK 編碼問題讀入時指定 encodingutf-8 或 encodinggbk 要提前試一遍否則中文商戶名會變成亂碼。import pandas as pd df pd.read_csv(campus_card_2023.csv, encodingutf-8, dtype{student_id: str}) print(df.shape) print(df.dtypes) for col in df.columns: print(col, df[col].nunique(), df[col].isnull().sum()) print(df[col].head(3).tolist())這段代碼的作用是快速摸清數(shù)據(jù)集的整體情況。dtype 參數(shù)把學(xué)號強制讀成字符串防止 Excel 里的科學(xué)計數(shù)法把學(xué)號變成浮點數(shù)nunique 統(tǒng)計唯一值數(shù)量用來判斷字段是不是主鍵isnull 統(tǒng)計空值數(shù)量字段缺失嚴(yán)重的列需要單獨處理。數(shù)據(jù)探查階段不要急著做任何轉(zhuǎn)換先把每一列的真實分布看清楚后面清洗才有依據(jù)。2.2 清洗規(guī)則與代碼金額異常、時間異常、重復(fù)消費怎么處理清洗邏輯按優(yōu)先級排列順序不能亂。第一步剔除退款和圈存記錄只保留真實消費第二步過濾金額異常比如金額小于 0.01 元或大于 200 元的單筆消費前者通常是設(shè)備誤讀后者可能是批量采購或代刷不屬于日常消費第三步去重同一學(xué)號在同一秒同商戶金額完全相同的記錄視為重復(fù)流水第四步處理商戶名稱中的空格和別名。# 僅保留真實消費 df df[df[trade_type] 消費].copy() # 金額范圍過濾0.01 ~ 200 元 df df[(df[amount] 0.01) (df[amount] 200)].copy() # 去重同一學(xué)號、同一秒、同一商戶、同一金額 df df.drop_duplicates(subset[student_id, trade_time, merchant_id, amount]) # 商戶名稱清洗去掉首尾空格替換全角空格 df[merchant_name] df[merchant_name].str.strip().str.replace(\u3000, ) df df.reset_index(dropTrue) print(df.shape)這個清洗漏斗是后續(xù)所有分析的基石。金額上限 200 元不是拍腦袋定的校園食堂和超市單筆消費極少超過這個值超過的多半是代購或集中采購對個人經(jīng)濟評估會產(chǎn)生誤導(dǎo)。去重條件里的四個字段缺一不可只按學(xué)號和金額去重會誤刪同一食堂同菜價的正常記錄。清洗后建議按學(xué)號、時間排序?qū)С鲋虚g文件方便后面排查問題時回查數(shù)據(jù)這一步也叫數(shù)據(jù)備份與恢復(fù)的準(zhǔn)備工作。2.3 數(shù)據(jù)質(zhì)量驗證清洗前后對比與口徑核對清洗完成不代表數(shù)據(jù)能用還要做一輪質(zhì)量驗證。對比清洗前后的總筆數(shù)、總金額、覆蓋學(xué)號數(shù)這些指標(biāo)能反映清洗邏輯是否過度或不足。比如全校三萬學(xué)生清洗后只剩一萬人有消費記錄那說明數(shù)據(jù)導(dǎo)出時可能漏了部分食堂或部分時間段需要重新拉取。更細的口徑核對方式是抽查某幾個學(xué)生把他們的逐筆流水打出來人工核對確認(rèn)金額和商戶名稱字段沒有解析錯位。before_count len(df_raw) after_count len(df) print(f清洗前 {before_count} 條清洗后 {after_count} 條刪除 {(before_count-after_count)/before_count:.1%}) valid_students df[student_id].nunique() print(f覆蓋學(xué)生數(shù) {valid_students})刪除比例超過 15% 時就要懷疑數(shù)據(jù)源本身有問題。正常情況下退款和圈存記錄占比約 5%~8%金額異常和重復(fù)記錄占比很低總刪除比例超過 15% 大概率是導(dǎo)出時翻倍或主鍵設(shè)計有問題。覆蓋學(xué)生數(shù)是另一個關(guān)鍵指標(biāo)如果少于全校人數(shù)的 80%說明部分樓棟或校區(qū)的消費機沒有納入采集范圍評估結(jié)論會有偏。3. 消費行為特征提取從流水到每名學(xué)生一張畫像3.1 消費水平特征日均消費、月均消費和消費分位數(shù)行為分析的第一步是把逐筆流水匯總成學(xué)生維度特征。最常見的特征組是消費水平類總消費金額、日均消費、月均消費、消費天數(shù)、單筆平均金額、消費金額中位數(shù)。日均消費比總消費更能反映真實水平因為總金額受在校天數(shù)影響兩個消費水平相同的學(xué)生一個全年在校一個經(jīng)?;丶铱偨痤~會差很多。中位數(shù)比均值更穩(wěn)健不會被偶爾一次大額消費拉偏。計算這些特征時要先按學(xué)號和時間戳排序再按月做匯總。月均消費的計算要注意跨月問題一筆 23:59 的記錄屬于哪個月取決于記賬時間統(tǒng)一按 trade_time 所在的自然月歸類即可。消費天數(shù)要按日期去重不能直接數(shù)流水條數(shù)否則一頓飯刷三次會被當(dāng)成三天。df[trade_date] pd.to_datetime(df[trade_time]).dt.date df[trade_month] pd.to_datetime(df[trade_time]).dt.month student_daily df.groupby([student_id, trade_date]).agg( daily_amount(amount, sum), daily_count(amount, count) ).reset_index() student_level student_daily.groupby(student_id).agg( total_amount(daily_amount, sum), active_days(daily_amount, count), avg_daily_amount(daily_amount, mean), median_daily_amount(daily_amount, median), max_daily_amount(daily_amount, max) ).reset_index()student_level 的每一行就是一名學(xué)生的消費水平畫像。active_days 是實際消費天數(shù)這個指標(biāo)能識別“幽靈學(xué)生”——學(xué)號在冊但幾乎不消費可能是休學(xué)、交換或卡丟失。avg_daily_amount 和 median_daily_amount 的差值如果長期偏大說明該學(xué)生的消費波動劇烈可能有不規(guī)律的校外就餐習(xí)慣這類學(xué)生的經(jīng)濟評估要結(jié)合其他特征綜合判斷不能只看均值。3.2 消費結(jié)構(gòu)特征食堂、超市、水房與生活必需消費占比消費結(jié)構(gòu)比消費水平更能說明問題。食堂消費是生存型消費超市消費偏生活改善型水房和浴室消費是剛需。把商戶名稱歸類成幾個功能大類再計算每類消費金額占總金額的比例就能看出一個學(xué)生的錢主要花在哪里。消費結(jié)構(gòu)特征在貧困生認(rèn)定中非常關(guān)鍵經(jīng)濟困難學(xué)生的食堂消費占比通常偏高生活改善型消費占比偏低。def categorize_merchant(name): if any(k in name for k in [一食堂, 二食堂, 三食堂, 風(fēng)味餐廳, 清真]): return canteen elif any(k in name for k in [超市, 小賣部, 便利店]): return market elif any(k in name for k in [開水, 浴室, 洗衣]): return daily else: return other df[category] df[merchant_name].apply(categorize_merchant) category_pivot df.groupby([student_id, category])[amount].sum().unstack(fill_value0) for cat in [canteen, market, daily]: category_pivot[f{cat}_pct] category_pivot[cat] / category_pivot.sum(axis1)商戶歸類規(guī)則的先后順序有講究把“風(fēng)味餐廳”放在超市前面因為商戶名稱可能同時包含“超市”和“餐廳”字樣。category_pivot 最終要 merge 回 student_level形成完整的特征寬表。canteen_pct 的特征分布通常呈現(xiàn)兩頭尖中間平的形態(tài)大部分學(xué)生在 50%~80% 之間低于 30% 的學(xué)生大概率經(jīng)常在校外就餐高于 90% 的學(xué)生則高度依賴校內(nèi)食堂這兩類人群需要分別關(guān)注。4. 學(xué)生經(jīng)濟評估模型閾值打分還是聚類分檔4.1 經(jīng)濟評估指標(biāo)體系與標(biāo)簽設(shè)計經(jīng)濟評估不能直接拿消費金額給學(xué)生貼“貧困”標(biāo)簽因為消費水平和家庭經(jīng)濟狀況之間隔著個人消費習(xí)慣這層緩沖。合理的做法是構(gòu)造一個綜合指標(biāo)把消費水平、消費結(jié)構(gòu)、消費規(guī)律性和穩(wěn)定性組合起來。指標(biāo)體系常見三層第一層是消費能力分由日均消費、月均消費和單筆均值構(gòu)成第二層是消費質(zhì)量分由食堂占比、超市占比和必需消費占比構(gòu)成第三層是消費穩(wěn)定性分由消費天數(shù)的月間變異系數(shù)衡量。標(biāo)簽設(shè)計有兩種路線。第一種是閾值打分法給每個指標(biāo)設(shè)分段閾值累加得到經(jīng)濟壓力指數(shù)分?jǐn)?shù)越高代表經(jīng)濟壓力越大第二種是聚類分檔法用無監(jiān)督聚類把學(xué)生分成若干檔再結(jié)合已知的資助名單校驗檔位的合理性。兩種方法各有利弊閾值打分可解釋性強但閾值設(shè)定有主觀性聚類分檔客觀但聚類結(jié)果需要人工命名檔位。我一般先做聚類看分布形態(tài)再用閾值法校準(zhǔn)檔位邊界。4.2 聚類分檔實現(xiàn)特征標(biāo)準(zhǔn)化與 KMeans 參數(shù)選擇聚類前必須做標(biāo)準(zhǔn)化否則金額特征的數(shù)值范圍會碾壓比例特征聚類結(jié)果基本等于只按消費金額分組。標(biāo)準(zhǔn)化用 StandardScaler 即可不需要做更復(fù)雜的歸一化。KMeans 的 K 值可以用輪廓系數(shù)選但校園消費數(shù)據(jù)的類別邊界本身模糊經(jīng)驗上 K3 或 K4 效果較好K3 對應(yīng)“消費水平較高、中等、較低”三檔K4 多出來的一檔往往是“消費水平高但食堂占比異常高”的特殊人群。from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans feature_cols [avg_daily_amount, median_daily_amount, active_days, canteen_pct, market_pct, daily_pct] X student_profile[feature_cols].fillna(0) X_scaled StandardScaler().fit_transform(X) kmeans KMeans(n_clusters4, random_state42, n_init10) student_profile[economic_cluster] kmeans.fit_predict(X_scaled) cluster_summary student_profile.groupby(economic_cluster)[feature_cols].mean() print(cluster_summary.round(2))n_init10 是 KMeans 在 sklearn 1.2 之后的默認(rèn)值作用是用多個隨機質(zhì)心初始化跑多次取最優(yōu)避免局部最優(yōu)。fillna(0) 處理的是那些整月沒有任何消費記錄的學(xué)生他們的占比特征為空補零表示不參與該維度計算。跑完聚類后不要急著用先看每個簇的特征均值是否和人工經(jīng)驗一致如果聚類結(jié)果出現(xiàn)“高消費高食堂占比”這種反直覺組合說明標(biāo)準(zhǔn)化前的特征里有強相關(guān)性需要考慮刪除冗余特征或改用 PCA 降維后再聚類。4.3 模型結(jié)果校驗與資助名單的交叉驗證聚類分檔完成后必須做一輪外部校驗。最常見的做法是把聚類結(jié)果與學(xué)校已有的貧困生資助名單做交叉比對計算高經(jīng)濟壓力檔位中學(xué)生被認(rèn)定為困難生的比例。這個比例不需要很高因為資助認(rèn)定還包含家庭突發(fā)變故等消費數(shù)據(jù)看不到的因素但比例如果低于 10%說明特征設(shè)置或聚類 K 值有問題需要回溯調(diào)整。aid_df pd.read_csv(aid_list.csv, dtype{student_id: str}) merged student_profile.merge(aid_df, onstudent_id, howleft) merged[is_aided] merged[aid_level].notnull().astype(int) cross merged.groupby(economic_cluster)[is_aided].agg([mean, count]) cross.columns [aid_ratio, student_count] print(cross.round(3))aid_ratio 就是每個簇中獲得資助的學(xué)生占比。經(jīng)濟壓力最高的簇這個比例一般應(yīng)在 25%~40% 之間比例太低說明聚類沒抓住貧困特征比例超過 50% 反而要懷疑特征里混入了資助結(jié)果相關(guān)的泄漏變量比如直接用了補貼金額做特征。5. 校園消費分析的常見問題與避坑數(shù)據(jù)質(zhì)量、隱私邊界與模型翻車點5.1 現(xiàn)象清洗后覆蓋學(xué)生數(shù)驟減遇到過項目跑完清洗流程原本三萬學(xué)生的數(shù)據(jù)集只剩一萬八千人有消費記錄。逐層排查發(fā)現(xiàn)退款和圈存記錄的 trade_type 字段取值不統(tǒng)一有的叫“退款”有的叫“退費”還有的用數(shù)字編碼“3”表示。原因是不同校區(qū)的消費機系統(tǒng)版本不一致導(dǎo)出的字典表沒同步。解決方式是在清洗前先打印所有 trade_type 的唯一值列表人工確認(rèn)全部取值后寫成過濾白名單不要用黑名單過濾。5.2 現(xiàn)象聚類結(jié)果出現(xiàn)“高消費高食堂占比”簇K4 的聚類結(jié)果里出現(xiàn)一個日均消費很高但食堂消費占比也接近 90% 的簇初看違反直覺。查數(shù)據(jù)發(fā)現(xiàn)這些學(xué)生的消費記錄集中在風(fēng)味餐廳和清真食堂這些商戶的人均消費顯著高于普通食堂。解決方式是在商戶歸類時把風(fēng)味餐廳單獨拆成一個類別而不是和普通食堂合并。這個坑的本質(zhì)是商戶分類粒度太粗把均價差異大的商戶混在一起特征信息量被稀釋了。5.3 現(xiàn)象部分學(xué)生消費天數(shù)極少導(dǎo)致評估失真有些學(xué)生一個月只在食堂消費三到五天日均消費算出來異常高被聚類分到高消費檔。人工回查發(fā)現(xiàn)這部分學(xué)生是校外實習(xí)或走讀生只偶爾回校吃飯。解決方式是在特征矩陣中增加 active_days 的過濾邏輯消費天數(shù)少于當(dāng)月天數(shù) 30% 的學(xué)生不參與聚類單獨標(biāo)記為 low_activity 人群由輔導(dǎo)員線下核實。這個處理在分析報告里必須寫明否則評估結(jié)論會被質(zhì)疑。5.4 現(xiàn)象跨學(xué)期數(shù)據(jù)拼接后消費水平整體抬升項目數(shù)據(jù)集如果橫跨兩個學(xué)期直接按全年匯總算日均消費會發(fā)現(xiàn)高消費檔人數(shù)多了不少。原因是春季學(xué)期有開學(xué)季和節(jié)日消費高峰秋季學(xué)期有迎新季不同學(xué)期的消費物價也有調(diào)整。解決方式是引入學(xué)期字段按月計算特征時加入學(xué)期虛擬變量或者干脆只分析一個完整學(xué)期的數(shù)據(jù)保證口徑一致。多學(xué)期分析要按月生成特征后做時序?qū)Ρ炔荒苤苯悠戳魉?.5 現(xiàn)象評估模型跑完無法落地聚類報告做得再漂亮如果學(xué)校輔導(dǎo)員看不懂模型結(jié)論就進不了資助認(rèn)定流程。踩過的坑是只輸出簇編號和特征均值沒有給每個學(xué)生一句自然語言描述。解決方式是生成評估結(jié)果時附帶規(guī)則化的文字說明比如“該生日均消費低于全校 25% 分位食堂消費占比高于 80%建議關(guān)注”并且提供按學(xué)院、按年級的匯總交叉表。模型結(jié)果的價值在決策鏈路里不在模型本身。6. 把評估結(jié)果用起來消費畫像可視化與按月更新的評估流程聚類分檔只是項目的中途站真正的終點是把結(jié)果嵌入學(xué)校的學(xué)生工作流程。每月初從一卡通系統(tǒng)增量導(dǎo)出上月流水跑一遍清洗和特征提取更新每名學(xué)生的經(jīng)濟評估檔案再把結(jié)果推送給學(xué)生工作管理系統(tǒng)。這個過程要寫成定時腳本不能靠人工每月手動跑一次否則維護成本會拖垮項目。import datetime today datetime.date.today() first_day today.replace(day1) last_month_end first_day - datetime.timedelta(days1) last_month_start last_month_end.replace(day1) monthly_raw load_monthly_data(last_month_start, last_month_end) monthly_clean clean_flow(monthly_raw) monthly_features build_student_features(monthly_clean) monthly_profile update_profile(monthly_features, last_month_end)這段代碼展示的是月度更新流程的框架三個函數(shù)分別對應(yīng)清洗、特征提取和畫像更新。load_monthly_data 從數(shù)據(jù)倉庫按時間范圍取數(shù)clean_flow 復(fù)用第 2 章的清洗邏輯build_student_features 復(fù)用第 3 章的特征工程update_profile 把新月份的特征合并進歷史畫像并重算分檔。關(guān)鍵點是特征的口徑要和上月嚴(yán)格一致任何規(guī)則改動都要先對歷史數(shù)據(jù)回放否則累計畫像會出現(xiàn)突變??梢暬窗迨亲屧u估結(jié)果被非技術(shù)用戶接受的最有效手段。用 echarts 數(shù)據(jù)可視化做一個簡單的月度趨勢頁面左側(cè)是全校消費水平分布直方圖右側(cè)是各學(xué)院的經(jīng)濟壓力分檔占比橫向柱狀圖下邊是重點關(guān)注學(xué)生列表。echarts 的配置項不復(fù)雜關(guān)鍵是數(shù)據(jù)接口要設(shè)計好前端只按學(xué)院、年級、月份三個維度篩選取數(shù)。接口返回的 JSON 結(jié)構(gòu)建議預(yù)先聚合好不要讓前端做二次統(tǒng)計否則大數(shù)據(jù)量下頁面會卡頓。最后說一個習(xí)慣項目交付時除了代碼和運行文檔一定要附一份數(shù)據(jù)字典和清洗規(guī)則的變更記錄表。哪個月改了金額過濾閾值哪個月新增了商戶分類逐條記下來。這個項目的坑幾乎都出在數(shù)據(jù)口徑上沒有變更記錄三個月后再跑一次全量分析你會發(fā)現(xiàn)自己都看不懂自己之前定的規(guī)則。做這個方向最深的體會是校園消費行為分析的技術(shù)門檻不高難點在于對數(shù)據(jù)口徑的死磕和對業(yè)務(wù)邊界的拿捏。經(jīng)濟評估結(jié)果能幫輔導(dǎo)員發(fā)現(xiàn)那些不會主動開口的困難學(xué)生但也可能因為數(shù)據(jù)缺失誤傷自尊心。所以我在每次生成重點關(guān)注名單時都會在報告首頁加一行提醒消費數(shù)據(jù)只能作為參考線索最終認(rèn)定必須結(jié)合線下談話。這份克制和學(xué)生工作者的經(jīng)驗判斷一樣重要希望幫到你。本文還有配套的精品資源點擊獲取