據(jù)分析系統(tǒng)實(shí)戰(zhàn):從數(shù)據(jù)清洗到RFM用戶分層)
簡(jiǎn)介面向Python課程設(shè)計(jì)與期末大作業(yè)的電商平臺(tái)數(shù)據(jù)分析系統(tǒng)完整資源適合需要可直接運(yùn)行項(xiàng)目作為參考的高校學(xué)生。資源圍繞電商業(yè)務(wù)數(shù)據(jù)設(shè)計(jì)實(shí)現(xiàn)了用戶行為分析、RFM用戶分層、回購(gòu)率統(tǒng)計(jì)、銷售趨勢(shì)分析、渠道來(lái)源拆解等常見(jiàn)模塊覆蓋數(shù)據(jù)分析課程中的典型實(shí)驗(yàn)場(chǎng)景同時(shí)附帶臟數(shù)據(jù)處理方式說(shuō)明和README文檔便于理解數(shù)據(jù)清洗、特征計(jì)算與可視化輸出的完整流程。壓縮包共30個(gè)文件以7個(gè)Python源碼腳本為主體實(shí)現(xiàn)數(shù)據(jù)清洗、特征分析與圖表繪制輔以19張結(jié)果展示圖與3個(gè)緩存文件整體僅1.68MB輕量易部署源碼經(jīng)本地編譯通過(guò)評(píng)審分98分助教審定難度適中可直接用于期末大作業(yè)、課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)參考。目前已有197人學(xué)習(xí)瀏覽作為高分項(xiàng)目范例能幫助讀者快速搭建電商數(shù)據(jù)分析框架并借鑒實(shí)現(xiàn)思路。1. 為什么電商數(shù)據(jù)分析是大作業(yè)里的“安全牌”這個(gè)題到底在考你什么Python 大作業(yè)電商平臺(tái)數(shù)據(jù)分析系統(tǒng)實(shí)現(xiàn)源碼文檔說(shuō)明高分項(xiàng)目這類題目幾乎每個(gè)開(kāi) Python 課的學(xué)校都會(huì)出現(xiàn)一版。它不是讓你做一個(gè)能下單的商城也不是讓你訓(xùn)練一個(gè)預(yù)測(cè)模型而是要把一整條數(shù)據(jù)處理鏈路走通從拿到或構(gòu)造訂單數(shù)據(jù)到清洗、聚合、算指標(biāo)再到畫(huà)圖、寫(xiě)結(jié)論、打包成一份能答辯的文檔。評(píng)分老師看的就是“你是不是真的會(huì)分析而不是會(huì)抄代碼”。這個(gè)方向適合兩類人一類是課程要求必須做“信息系統(tǒng)類”大作業(yè)想找一個(gè)數(shù)據(jù)量可控、工作量肉眼可見(jiàn)的題目另一類是求職作品集里缺一個(gè)數(shù)據(jù)展示項(xiàng)目想用半天到兩天時(shí)間補(bǔ)上一個(gè)完整案例。它不依賴 GPU、不依賴真實(shí)業(yè)務(wù)接口一臺(tái)普通筆記本就能跑完。前面把技術(shù)棧和目錄結(jié)構(gòu)定好后面就是照方抓藥的事。2. 技術(shù)選型與工程結(jié)構(gòu)先擺平“用什么”和“怎么放”再談寫(xiě)代碼2.1 技術(shù)棧決策純 Python 腳本還是帶 Web 殼先回答一個(gè)最常見(jiàn)的問(wèn)題這個(gè)系統(tǒng)到底要不要做成網(wǎng)頁(yè)很多高分模板會(huì)給你一個(gè) Flask 頁(yè)面打開(kāi)能看到圖表和數(shù)據(jù)表格但這不代表你必須這么做。我見(jiàn)過(guò)兩種做法各有各的道理方案組成適合情況答辯風(fēng)險(xiǎn)純腳本版pandas 清洗 matplotlib 出圖 控制臺(tái)輸出結(jié)論課程只要求“分析報(bào)告 圖表”功能偏少容易被問(wèn)“系統(tǒng)在哪里”帶 Web 殼版腳本版基礎(chǔ) Flask 渲染頁(yè)面 ECharts 圖表課程要求“系統(tǒng)”或展示給非技術(shù) audience工作量多一天但效果顯著我的建議是如果老師明確說(shuō)了“系統(tǒng)”兩個(gè)字就做帶 Web 殼的版本。這里的“系統(tǒng)”在答辯語(yǔ)境下就等于“有界面、能交互”。但如果時(shí)間只夠一天腳本版也能過(guò)只要文檔里寫(xiě)清楚“數(shù)據(jù)入庫(kù)—清洗—分析—可視化”四層結(jié)構(gòu)把腳本輸出截圖放進(jìn)文檔觀感并不差。下文默認(rèn)按腳本版為主、最后補(bǔ) Web 殼的方案講這樣兩頭都能落。技術(shù)棧選型上沒(méi)有懸念pandas 做數(shù)據(jù)清洗和聚合matplotlib 做靜態(tài)圖表Flask 只用來(lái)把結(jié)果展示成網(wǎng)頁(yè)。不用 Django因?yàn)橹槐┞稁讉€(gè)路由Django 的 admin、ORM、遷移機(jī)制在這里全是負(fù)資產(chǎn)。數(shù)據(jù)庫(kù)用 SQLite 還是直接 CSV如果你沒(méi)學(xué)過(guò) SQL直接用 CSV pandas.read_csv 就行減少一個(gè)變量如果你會(huì)用 pandas 的 merge、groupby數(shù)據(jù)量在幾萬(wàn)行以內(nèi)時(shí) CSV 完全不輸數(shù)據(jù)庫(kù)。2.2 目錄設(shè)計(jì)與文檔聯(lián)動(dòng)源碼和文檔說(shuō)明怎么配套很多人的項(xiàng)目只有一個(gè) main.py跑完輸出幾張 png這在小作業(yè)里可以但在“系統(tǒng) 文檔說(shuō)明”的評(píng)分結(jié)構(gòu)下很吃虧。我一般會(huì)把工程拆成這樣你直接照著建ecommerce_analysis/ ├── data/ │ ├── raw_orders.csv # 原始訂單數(shù)據(jù)不手動(dòng)改 │ └── clean_orders.csv # 清洗后數(shù)據(jù)分析只用這份 ├── scripts/ │ ├── generate_data.py # 造數(shù)腳本保證可復(fù)現(xiàn) │ ├── clean_data.py # 清洗腳本 │ ├── analysis.py # 核心指標(biāo)計(jì)算 │ └── visualize.py # 生成圖表 ├── output/ │ ├── daily_sales.png │ ├── top10_products.png │ ├── rfm_heatmap.png │ └── metrics_summary.json # 指標(biāo)匯總Web 版直接用 ├── web_app/ │ └── app.py # Flask 入口可選 ├── docs/ │ └── 說(shuō)明文檔.md └── requirements.txt每個(gè)文件只干一件事是這套結(jié)構(gòu)能避免答辯翻車的核心。比如 generate_data.py 只負(fù)責(zé)產(chǎn)出 raw_orders.csvclean_data.py 只讀 raw絕不回頭改源文件analysis.py 輸出 JSON 而不是直接 print 一眼就過(guò)。這樣文檔里寫(xiě)“各模塊職責(zé)”時(shí)你就有話可說(shuō)而不是籠統(tǒng)的一句“主程序完成所有功能”。requirements.txt 也必須有這是文檔能復(fù)現(xiàn)的前提pandas2.1.4 matplotlib3.8.2 flask3.0.0 openpyxl3.1.22.3 環(huán)境準(zhǔn)備與本機(jī)跑通最小命令別用系統(tǒng)全局 Python大作業(yè)最大的隱性扣分項(xiàng)就是“換個(gè)環(huán)境跑不起來(lái)”。用虛擬環(huán)境隔離也能在文檔里寫(xiě)上“virtualenv 創(chuàng)建獨(dú)立運(yùn)行環(huán)境”這一句加分話。cd ecommerce_analysis python -m venv venv # Windows: venv\Scripts\activate # macOS / Linux: source venv/bin/activate pip install -r requirements.txt說(shuō)明python -m venv venv 創(chuàng)建虛擬環(huán)境到 venv 目錄激活后 pip 裝的包只對(duì)當(dāng)前項(xiàng)目生效。裝依賴用 -r 從 requirements.txt 讀取避免手工逐個(gè)裝漏掉。如果你用的是 PyCharm創(chuàng)建項(xiàng)目時(shí)可以直接選 Virtualenv效果一樣。到這一步環(huán)境能跑通后面的流程就都是確定性的了。3. 數(shù)據(jù)準(zhǔn)備與清洗沒(méi)有干凈數(shù)據(jù)后面全是廢圖3.1 造一份可復(fù)現(xiàn)的模擬訂單數(shù)據(jù)隨機(jī)種子決定你報(bào)告里的“真實(shí)性”電商平臺(tái)的真實(shí)訂單數(shù)據(jù)拿不到也絕不能拿爬蟲(chóng)去搞。最常見(jiàn)、最可靠的做法是自己造一份帶業(yè)務(wù)含義的模擬數(shù)據(jù)。造數(shù)不是隨便 random要讓分布大致合理晚上和周末訂單多、爆品銷量高但單價(jià)低、數(shù)碼類客單價(jià)明顯高于零食類。這樣你分析出來(lái)的結(jié)論才像話。import random import pandas as pd from datetime import datetime, timedelta random.seed(42) # 商品池不同品類給不同價(jià)格區(qū)間 products [ {name: 無(wú)線鼠標(biāo), category: 數(shù)碼配件, min_price: 49, max_price: 89}, {name: 機(jī)械鍵盤(pán), category: 數(shù)碼配件, min_price: 199, max_price: 499}, {name: 手機(jī)支架, category: 數(shù)碼配件, min_price: 9, max_price: 29}, {name: 保溫杯, category: 家居日用, min_price: 39, max_price: 129}, {name: 收納箱, category: 家居日用, min_price: 25, max_price: 79}, {name: 零食禮包, category: 食品, min_price: 29, max_price: 99}, ] # 時(shí)間范圍近 90 天 start_date datetime(2024, 1, 1) end_date datetime(2024, 3, 31) rows [] for order_id in range(1, 3001): # 周末和晚上下單概率更高 day_offset random.randint(0, (end_date - start_date).days) order_time start_date timedelta(daysday_offset, hoursrandom.randint(10, 22), minutesrandom.randint(0, 59)) if order_time.weekday() 5: weight 2 # 周末訂單翻倍 else: weight 1 if 19 order_time.hour 22: weight 1 # 晚間再加權(quán) for _ in range(random.randint(1, 3)): # 一單可能含多件商品 product random.choice(products) qty random.randint(1, 3) if weight 1 else random.randint(1, 2) price round(random.uniform(product[min_price], product[max_price]), 2) rows.append({ order_id: order_id, user_id: random.randint(1000, 1500), product_name: product[name], category: product[category], quantity: qty, price: price, amount: round(price * qty, 2), order_time: order_time.strftime(%Y-%m-%d %H:%M:%S), }) df pd.DataFrame(rows) df.to_csv(data/raw_orders.csv, indexFalse, encodingutf-8-sig) print(f生成 {len(df)} 條訂單明細(xì))邏輯說(shuō)明random.seed(42) 固定隨機(jī)種子保證每次運(yùn)行生成完全相同的數(shù)據(jù)。你文檔里寫(xiě)的所有數(shù)值都來(lái)自這份確定性的數(shù)據(jù)否則兩次運(yùn)行結(jié)果不同答辯時(shí)老師讓你重跑一遍就對(duì)不上。weight 變量模擬業(yè)務(wù)權(quán)重周末權(quán)重為 2、晚間額外加 1但注意它只影響購(gòu)買(mǎi)件數(shù)概率不是直接給金額乘系數(shù)——這只是為了讓數(shù)據(jù)分布更像真實(shí)訂單不必做成精確的概率模型。參數(shù)說(shuō)明訂單數(shù) 3000用戶數(shù) 5011000-1500時(shí)間范圍 90 天。這三個(gè)參數(shù)決定你后續(xù)圖表的密度3000 條明細(xì)畫(huà)日趨勢(shì)足夠飽滿501 個(gè)用戶做分層也夠。如果你的報(bào)告需要月度對(duì)比就把日期范圍改成 6 個(gè)月并把訂單數(shù)加到 6000 以上。生成 CSV 時(shí)用 encodingutf-8-sig這是給 Excel 用的編碼Excel 直接打開(kāi)不會(huì)亂碼——很多教程只寫(xiě) utf-8Windows 上 Excel 打開(kāi)就是一堆亂碼這一步值得記住。3.2 清洗三步去重、補(bǔ)空、拒掉異常訂單造出來(lái)的數(shù)據(jù)也要走一遍真實(shí)清洗流程這是文檔里“數(shù)據(jù)預(yù)處理”章節(jié)的素材。清洗本身不在于數(shù)據(jù)有多臟而在于你展示了“我知道這些坑存在并做了處理”。import pandas as pd df pd.read_csv(data/raw_orders.csv) # 1. 去重同一訂單號(hào)加同一商品名才算重復(fù) df.drop_duplicates(subset[order_id, product_name], inplaceTrue) # 2. 缺失值金額為空或用戶為空的行直接剔除 df.dropna(subset[amount, user_id], inplaceTrue) # 3. 異常值數(shù)量0、金額0、單價(jià)9999 都視為臟數(shù)據(jù) df df[(df[quantity] 0) (df[amount] 0) (df[price] 9999)] # 4. 時(shí)間轉(zhuǎn) datetime方便后續(xù)按天/月聚合 df[order_time] pd.to_datetime(df[order_time]) df[order_date] df[order_time].dt.date df.to_csv(data/clean_orders.csv, indexFalse, encodingutf-8-sig) print(f清洗前 {len(df)} 行清洗后保留 {len(df)} 行)邏輯說(shuō)明drop_duplicates 的 subset 參數(shù)限定判斷重復(fù)的列不要全列去重——同一個(gè)訂單含兩條不同商品是正常業(yè)務(wù)全列去重會(huì)誤刪。dropna 只針對(duì)關(guān)鍵字段因?yàn)樯唐访笔Э赡懿挥绊懡痤~匯總而 user_id 缺失會(huì)導(dǎo)致后續(xù) RFM 分析無(wú)法做用戶分組。異常值過(guò)濾用布爾條件組合這里不用 replace直接剔除是更干凈的做法。參數(shù)說(shuō)明price 9999 是防呆閾值你可以按業(yè)務(wù)調(diào)整為 100000關(guān)鍵是它必須比商品池里的最高單價(jià)大一個(gè)量級(jí)。清洗完的行數(shù)建議和原始行數(shù)對(duì)比在文檔里寫(xiě)“共剔除 N 行無(wú)效數(shù)據(jù)”這就是數(shù)據(jù)質(zhì)量的量化證明。上面這段代碼只是標(biāo)準(zhǔn)流程如果你自己模擬數(shù)據(jù)時(shí)沒(méi)生成缺失值可以在 generate_data.py 里留 5% 的空金額字段——故意制造臟數(shù)據(jù)再清洗報(bào)告反而更有說(shuō)服力。3.3 Excel 落盤(pán)與對(duì)賬拿 Excel 透視表當(dāng)裁判清洗后的數(shù)據(jù)除了存 CSV我會(huì)再導(dǎo)出一份 Excel目的是給文檔附件用也方便老師自己打開(kāi)篩選。這不是冗余而是讓審核者能親手核對(duì)你算的每個(gè)數(shù)。import pandas as pd df pd.read_csv(data/clean_orders.csv) # 按天匯總銷售額寫(xiě)入 Excel 的 sheet1 daily df.groupby(order_date)[amount].sum().reset_index() monthly df.groupby(df[order_time].dt.month)[amount].sum().reset_index() with pd.ExcelWriter(output/agg_tables.xlsx, engineopenpyxl) as writer: daily.to_excel(writer, sheet_name日銷售額, indexFalse) monthly.to_excel(writer, sheet_name月銷售額, indexFalse)代碼說(shuō)明groupby 聚合后要 reset_index如果不重置order_date 會(huì)變成索引列to_excel 寫(xiě)出來(lái)會(huì)帶一層奇怪的行名。pd.ExcelWriter 配合 with 語(yǔ)句能確保文件寫(xiě)完后關(guān)閉句柄不然在 Windows 上文件可能被占用第二次運(yùn)行報(bào) PermissionError。參數(shù)說(shuō)明engineopenpyxl 是寫(xiě) xlsx 必需的后端xls 老格式是 xlwt別混用。這個(gè) Excel 文件生成后你可以在 Excel 里插入透視表拉一個(gè)“日銷售額”和程序算出來(lái)的數(shù)對(duì)一遍——對(duì)上了說(shuō)明 groupby 邏輯沒(méi)問(wèn)題對(duì)不上優(yōu)先檢查 order_date 是否真的轉(zhuǎn)了 datetime 類型。這一步對(duì)賬花五分鐘能避免“圖表數(shù)據(jù)和描述文字對(duì)不上”這種最尷尬的答辯事故。4. 分析指標(biāo)與可視化從平均值到 RFM 的完整鏈路4.1 時(shí)間趨勢(shì)分析先 resample 再畫(huà)線別直接 plot 原始數(shù)據(jù)做完清洗第一個(gè)必做的分析是時(shí)間趨勢(shì)。我見(jiàn)過(guò)很多人把 3000 行訂單直接按天 groupby 后畫(huà)線畫(huà)出來(lái)鋸齒狀極其難看。正確做法是按天聚合后再做時(shí)間序列重采樣把“天數(shù)”這個(gè)單位統(tǒng)一。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(data/clean_orders.csv) df[order_time] pd.to_datetime(df[order_time]) df.set_index(order_time, inplaceTrue) # 按天求和再取 7 日均線平滑 daily df[amount].resample(D).sum() weekly daily.rolling(window7, min_periods1).mean() plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(12, 5)) ax.plot(daily.index, daily.values, label日銷售額, color#888, linewidth0.8) ax.plot(weekly.index, weekly.values, label7日均線, color#E63946, linewidth2) ax.set_title(近90天銷售額趨勢(shì)) ax.legend() plt.tight_layout() plt.savefig(output/daily_sales.png, dpi150)邏輯說(shuō)明resample(D) 是把索引為時(shí)間的數(shù)據(jù)重采樣到天和單純的 groupby 日期不同它會(huì)把沒(méi)有訂單的日期也補(bǔ)成 0畫(huà)出來(lái)是一條連續(xù)的時(shí)間軸不會(huì)有斷點(diǎn)。rolling(window7) 取 7 天滑動(dòng)平均把周內(nèi)波動(dòng)抹平趨勢(shì)線才看得出整體方向——這是報(bào)告里“銷售額整體呈上升趨勢(shì)”這句話的唯一依據(jù)。參數(shù)說(shuō)明figsize(12, 5) 長(zhǎng)寬比適合在文檔里插入不會(huì)過(guò)高占版dpi150 是打印清晰度的底線低于 100 放到答辯 PPT 里會(huì)明顯發(fā)虛。字體設(shè)置里 SimHei 是黑體Microsoft YaHei 是微軟雅黑前者在 Linux 上通常沒(méi)有所以列兩個(gè)備選系統(tǒng)按順序找。4.2 Top10 商品與二八法則用條形圖把差異拉出來(lái)趨勢(shì)圖證明“總量”接下來(lái)必須展示“結(jié)構(gòu)”。Top10 商品貢獻(xiàn)了多少銷售額在電商報(bào)告里幾乎必提。這一步能引出二八法則的分析結(jié)論讓你的報(bào)告從“羅列圖表”升級(jí)到“有觀點(diǎn)”。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(data/clean_orders.csv) top10 (df.groupby(product_name)[amount] .sum() .sort_values(ascendingFalse) .head(10)) total_sales df[amount].sum() top10_share top10.sum() / total_sales * 100 fig, ax plt.subplots(figsize(10, 6)) ax.barh(top10.index[::-1], top10.values[::-1], color#2A9D8F) ax.set_xlabel(銷售額元) ax.set_title(fTop10 商品銷售額貢獻(xiàn)占比 {top10_share:.1f}%) plt.tight_layout() plt.savefig(output/top10_products.png, dpi150) print(top10)邏輯說(shuō)明sort_values(ascendingFalse) 按銷售額降序排head(10) 截前 10。barh 是橫向條形圖類別名稱長(zhǎng)時(shí)橫向更易讀。這里用 top10.index[::-1] 反轉(zhuǎn)順序讓最大值在頂部如果不反轉(zhuǎn)matplotlib 默認(rèn)從底部往上排最大項(xiàng)反而不在最顯眼的位置。參數(shù)說(shuō)明top10_share 是 Top10 銷售額占總銷售額的百分比這個(gè)數(shù)通常是報(bào)告里最有沖擊力的一個(gè)數(shù)字。例如算出來(lái)可能是 45% 左右你就可以在文檔里寫(xiě)“Top10 商品貢獻(xiàn)了約 45% 的營(yíng)收頭部商品集中度明顯”——有數(shù)據(jù)有判斷這就是高分項(xiàng)目和普通作業(yè)的差別。4.3 RFM 用戶分層找出“高價(jià)值沉默用戶”用戶分層是電商數(shù)據(jù)分析系統(tǒng)的標(biāo)配內(nèi)容也是拉開(kāi)檔次的模塊。RFM 三個(gè)字母分別代表最近一次消費(fèi)時(shí)間、消費(fèi)頻率、消費(fèi)金額。實(shí)現(xiàn)不復(fù)雜但必須講清理由你為什么要分層因?yàn)檫\(yùn)營(yíng)資源有限得優(yōu)先服務(wù)值得服務(wù)的用戶。import pandas as pd df pd.read_csv(data/clean_orders.csv) ref_date df[order_time].max() pd.Timedelta(days1) # 按用戶聚合 RFM 三指標(biāo) rfm df.groupby(user_id).agg( recency(order_time, lambda x: (ref_date - x.max()).days), frequency(order_id, count), monetary(amount, sum) ) # 四分位數(shù)分檔數(shù)值高于中位數(shù)的記為 1否則 0 def score(series): return (series series.median()).astype(int) rfm[R], rfm[F], rfm[M] score(rfm[recency]), score(rfm[frequency]), score(rfm[monetary]) # 分層規(guī)則8 類用戶合并成 4 類主型 def rfm_label(row): if row[R] 1 and row[F] 1 and row[M] 1: return 重要價(jià)值用戶 if row[M] 1: return 高消費(fèi)潛力用戶 if row[R] 1: return 活躍普通用戶 return 沉默風(fēng)險(xiǎn)用戶 rfm[label] rfm.apply(rfm_label, axis1) rfm.to_csv(output/rfm_result.csv, encodingutf-8-sig) # 看一眼分層結(jié)果 print(rfm[label].value_counts())邏輯說(shuō)明recency 用 ref_date 減去每個(gè)用戶最近一次下單日期得到“距今多少天沒(méi)消費(fèi)”frequency 用 order_id 出現(xiàn)次數(shù)統(tǒng)計(jì)注意 agg 里第一個(gè)元組 (order_time, lambda ...) 表示對(duì) order_time 列應(yīng)用 lambda(order_id, count) 表示對(duì) order_id 計(jì)數(shù)——字段映射別寫(xiě)反。四分位數(shù)分檔是 RFM 最常見(jiàn)實(shí)現(xiàn)數(shù)值大于全體中位數(shù)為 1否則為 0。閾值用中位數(shù)而不是平均值因?yàn)橄M(fèi)金額分布通常右偏平均值會(huì)被大額用戶拉高中位數(shù)更穩(wěn)。參數(shù)說(shuō)明ref_date 取數(shù)據(jù)最大時(shí)間 1 天確?!白罱M(fèi)”是相對(duì)完整日期區(qū)間計(jì)算的。分層規(guī)則這里只寫(xiě)了 4 類完整 RFM 是 238 類但我建議報(bào)告里合并成 4 類因?yàn)?8 類會(huì)分散用戶數(shù)答辯時(shí)解釋成本高。你可以把 rfm_result.csv 導(dǎo)入 Excel 篩選某個(gè)縣體用戶 ID并去明細(xì)表里驗(yàn)證這個(gè)用戶的真實(shí)消費(fèi)記錄——能驗(yàn)證說(shuō)明分層不是黑匣子。4.4 熱力圖與圖表保存可視化配置統(tǒng)一入口圖表風(fēng)格統(tǒng)一是“高分項(xiàng)目”的外觀底線。如果每張圖顏色、字體、尺寸都不一樣老師掃一眼就覺(jué)得是拼湊的。我會(huì)把可視化配置抽到一個(gè)公共函數(shù)然后熱力圖用 seaborn 風(fēng)格畫(huà)出來(lái)展示 RFM 分布。import pandas as pd import matplotlib.pyplot as plt # 統(tǒng)一圖表風(fēng)格 plt.rcParams[figure.figsize] (10, 6) plt.rcParams[axes.grid] True plt.rcParams[grid.linestyle] -- plt.rcParams[grid.alpha] 0.4 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False rfm pd.read_csv(output/rfm_result.csv) # 畫(huà) R、F、M 三個(gè)分檔的平均值雷達(dá)式對(duì)比 summary rfm.groupby(label)[[recency, frequency, monetary]].mean() # 對(duì)數(shù)值歸一化到 0-1避免金額數(shù)值過(guò)大壓扁其他軸 summary_norm (summary - summary.min()) / (summary.max() - summary.min()) fig, ax plt.subplots(figsize(8, 6)) im ax.imshow(summary_norm.T, cmapYlOrRd, aspectauto) ax.set_xticks(range(len(summary_norm.index))) ax.set_xticklabels(summary_norm.index, rotation30) ax.set_yticks(range(3)) ax.set_yticklabels([最近消費(fèi), 消費(fèi)頻率, 消費(fèi)金額]) plt.colorbar(im, axax, label歸一化均值) plt.tight_layout() plt.savefig(output/rfm_heatmap.png, dpi150)邏輯說(shuō)明imshow 把數(shù)值矩陣渲染為熱力圖cmapYlOrRd 從黃到紅漸變數(shù)值越大顏色越深。summary_norm.T 做轉(zhuǎn)置讓每一列是一個(gè)用戶類型、每一行是一個(gè)指標(biāo)這樣豎向看某個(gè)用戶類型的三項(xiàng)指標(biāo)強(qiáng)弱更直觀。歸一化到 0-1 是必要的否則 monetary 均值幾千recency 均值幾十兩者沒(méi)法在同一張圖上比較顏色深淺。參數(shù)說(shuō)明aspectauto 讓單元格自動(dòng)鋪滿畫(huà)布不加這個(gè)參數(shù)熱力圖會(huì)被拉成正方形導(dǎo)致變形。rotation30 讓用戶類型的標(biāo)簽斜著排避免文字相互遮擋。這一張熱力圖配合上一節(jié)的柱狀圖、趨勢(shì)圖整套圖表組合已經(jīng)覆蓋“時(shí)間、結(jié)構(gòu)、人群”三個(gè)維度文檔里每個(gè)分析結(jié)論都能落到圖上。5. 避坑會(huì)毀掉分?jǐn)?shù)的 5 個(gè)常見(jiàn)問(wèn)題5.1 中文亂碼與方塊字編碼問(wèn)題現(xiàn)場(chǎng)現(xiàn)象matplotlib 畫(huà)出的圖里所有中文標(biāo)題變成方框CSV 用 Excel 打開(kāi)后中文全是亂碼或者讀 CSV 直接報(bào) UnicodeDecodeError。原因三個(gè)環(huán)節(jié)各有差異。matplotlib 默認(rèn)字體不支持中文pandas to_csv 默認(rèn) utf-8而 Excel 默認(rèn) GBK 打開(kāi)pandas read_csv 默認(rèn)按系統(tǒng)編碼讀取Windows 下可能是 GBK。解決matplotlib 在 pyplot 畫(huà)圖前設(shè)置 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] 和 plt.rcParams[axes.unicode_minus] FalseCSV 寫(xiě)入統(tǒng)一 encodingutf-8-sig讀取時(shí)如果報(bào)錯(cuò)顯式指定 pd.read_csv(path, encodingutf-8)還是報(bào)錯(cuò)就試試 encodinggbk。這個(gè)坑最值得提前規(guī)避因?yàn)閬y碼圖一旦生成只能重跑沒(méi)有后悔藥。5.2 日期軸變成密集黑線沒(méi)做重采樣現(xiàn)象畫(huà)日銷售額趨勢(shì)時(shí)橫軸密密麻麻擠成一團(tuán)線條雜亂看不出規(guī)律。原因直接把每行的訂單時(shí)間作為橫軸繪圖沒(méi)有先 resample 到天導(dǎo)致同一日期出現(xiàn)多條記錄或者日期格式不統(tǒng)一。另一個(gè)原因是日期索引沒(méi)有排序matplotlib 按出現(xiàn)順序畫(huà)線線就來(lái)回折。解決先 df[order_time] pd.to_datetime(...)然后 set_index(order_time)再 resample(D).sum()。resample 會(huì)自動(dòng)按時(shí)間排序并補(bǔ)齊空日期。如果只想顯示 1 月到 3 月可以先用 df.loc[2024-01-01:2024-03-31] 切片再聚合。5.3 圖糊了或被截?cái)郿pi 和 tight_layout 同時(shí)生效現(xiàn)象保存的 png 放到文檔里模糊或圖例、標(biāo)題被截掉一半。原因dpi 低于 100圖在文檔里被拉伸后像素不夠figsize 設(shè)置過(guò)大、內(nèi)容超出畫(huà)布邊界。解決保存時(shí)統(tǒng)一 dpi150畫(huà)布 figsize 控制在 (10, 6) 到 (12, 6) 之間。打印前調(diào) plt.tight_layout()它會(huì)自動(dòng)收縮留白避免標(biāo)題被子圖遮擋。生成后先本地打開(kāi)看一眼別直接插進(jìn)文檔——圖被截?cái)噙@一項(xiàng)幾乎每個(gè)班都有幾個(gè)人中招。5.4 造數(shù)“太假”分布不合理被老師當(dāng)場(chǎng)問(wèn)住現(xiàn)象答辯時(shí)老師說(shuō)“你這數(shù)據(jù)怎么每天銷售額都一樣高”或者“凌晨 3 點(diǎn)還在瘋狂下單”原因random.randint 均勻生成時(shí)間沒(méi)有模擬真實(shí)人群作息商品價(jià)格區(qū)間太窄導(dǎo)致銷量和金額高度線性相關(guān)。解決generate_data.py 里對(duì)時(shí)間加權(quán)——晚上和周末提高下單概率工作日上午 10 點(diǎn)到下午 4 點(diǎn)保持常規(guī)凌晨直接降低到接近零。價(jià)格區(qū)間要拉開(kāi)差距數(shù)碼配件定價(jià)幾十到幾百食品就幾塊到幾十。還可以再往數(shù)據(jù)里加 3% 的退款記錄amount 為負(fù)清洗時(shí)剔除這樣報(bào)告里就能寫(xiě)“剔除了 N 條退款異常數(shù)據(jù)”真實(shí)性明顯提升。5.5 文檔和代碼對(duì)不上換個(gè)電腦跑不出來(lái)現(xiàn)象代碼在你自己電腦上運(yùn)行正常拷到老師機(jī)器上 ImportError 或者路徑報(bào)錯(cuò)。原因依賴沒(méi)鎖版本用絕對(duì)路徑讀文件中文文件名在 macOS 和 Windows 編碼不一致。解決所有文件路徑用相對(duì)路徑比如 data/clean_orders.csv不要寫(xiě) C:/Users/xxx/Project/...。requirements.txt 鎖住 pandas、matplotlib、flask 的版本號(hào)。跑之前確認(rèn)當(dāng)前工作目錄在項(xiàng)目根目錄而不是在子目錄里執(zhí)行腳本。最后用 Python 的 ifname main: 包住所有執(zhí)行代碼防止被 import 時(shí)意外跑一遍。這三件事做齊換環(huán)境基本不會(huì)再翻車。6. 進(jìn)階技巧把靜態(tài)圖升級(jí)成數(shù)據(jù)看板再用一張表自證沒(méi)算錯(cuò)如果還想往上夠一夠可以把 matplotlib 的 png 換成網(wǎng)頁(yè)看板工作量不大但觀感提升明顯。Flask 里寫(xiě)一個(gè)路由讀取 analysis.py 輸出的 metrics_summary.json把日銷售額、Top10、RFM 分布渲染成 ECharts 圖表。關(guān)鍵在于Flask 只做數(shù)據(jù)中轉(zhuǎn)不做計(jì)算所有指標(biāo)都在 analysis.py 里算好網(wǎng)頁(yè)端只是換一種形式展示。我一般會(huì)在 app.py 里寫(xiě)一個(gè) /dashboard 路由返回一個(gè)模板頁(yè)頁(yè)面上的圖表數(shù)據(jù)全部來(lái)自 JSON 文件——這樣你可以在答辯現(xiàn)場(chǎng)刷新頁(yè)面給老師看比翻 png 圖片有說(shuō)服力得多。計(jì)算正確性上最后務(wù)必跑一次對(duì)賬腳本把 analysis.py 算出的月度總銷售額和 agg_tables.xlsx 里 Excel 透視表的數(shù)核對(duì)。對(duì)不上優(yōu)先查 groupby 是否漏了過(guò)濾條件對(duì)得上文檔里可以加一句“輸出結(jié)果已與 Excel 透視表交叉驗(yàn)證”。這套流程做完你會(huì)發(fā)現(xiàn)自己對(duì)數(shù)據(jù)鏈路的理解比照著教程敲二十遍都有用。我當(dāng)年做類似項(xiàng)目時(shí)就是在 RFM 分層上吃了虧——閾值用平均值而不是中位數(shù)導(dǎo)致所有用戶都被歸到“沉默風(fēng)險(xiǎn)”答辯時(shí)被問(wèn)得說(shuō)不出話后來(lái)習(xí)慣所有分箱先畫(huà)分布圖再定閾值這個(gè)習(xí)慣一直留到現(xiàn)在。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取