據(jù)分析:從數(shù)據(jù)清洗到可視化大屏全流程實(shí)戰(zhàn))
1. 這個(gè)畢業(yè)設(shè)計(jì)為什么人人都在做但大多數(shù)只是PPT項(xiàng)目基于大數(shù)據(jù)的共享單車數(shù)據(jù)分析——如果你去查近五年大數(shù)據(jù)方向的本科學(xué)位論文這個(gè)題目絕對(duì)能排進(jìn)前三。原因很簡(jiǎn)單它自帶一個(gè)幾乎完美的敘事邏輯——共享單車的騎行記錄天然具備海量、高維、時(shí)空連續(xù)的特征正好滿足大數(shù)據(jù)項(xiàng)目的所有展示需求。但我在帶畢設(shè)、評(píng)閱論文這兩年里看過太多看起來做了很多、實(shí)際什么都沒做的版本界面做得花里胡哨一打開數(shù)據(jù)分析部分柱狀圖用的是Excel預(yù)置模板數(shù)據(jù)量只有幾千條技術(shù)棧只有Pandas加Matplotlib答辯一追問你的數(shù)據(jù)清洗放在哪一層Hive起了什么作用當(dāng)場(chǎng)沉默。先給這篇文章定個(gè)位如果你選了或者打算選這個(gè)題目本文會(huì)把整個(gè)項(xiàng)目從選題、數(shù)據(jù)獲取、數(shù)據(jù)清洗、離線數(shù)倉(cāng)搭建、核心分析模型、可視化大屏到答辯準(zhǔn)備的完整鏈路拆開講所有方案的取舍背后都有理由不是那種步驟123的工具文。適合正在做畢設(shè)、想把它做成真正能打的項(xiàng)目的同學(xué)也適合研究生入門大數(shù)據(jù)想找一個(gè)完整練手場(chǎng)景的讀者。這個(gè)題目真正的難點(diǎn)不是寫幾個(gè)SQL看幾條趨勢(shì)而是能不能構(gòu)造出一條完整的大數(shù)據(jù)流水線數(shù)據(jù)量大到什么程度才值得用分布式、清洗邏輯為什么不能全丟給Python腳本、Hive里的表到底該怎么分層、最后的可視化怎么把一個(gè)分析結(jié)論講給答辯老師聽。說白了畢設(shè)題目滿大街都是區(qū)別在技術(shù)深度和邏輯閉環(huán)。從技術(shù)棧上說這就是一條很標(biāo)準(zhǔn)的離線數(shù)倉(cāng)路線Flask ECharts做展示層Hive做離線分析層HDFS做存儲(chǔ)層Spark/MapReduce做清洗層。有些學(xué)校要求必須用到Hadoop生態(tài)有些只要求大數(shù)據(jù)方法你可以根據(jù)自己學(xué)校的指導(dǎo)原則調(diào)整。下面我按實(shí)際開發(fā)的順序把每一步的關(guān)鍵節(jié)點(diǎn)和坑位都過一遍。2. 先把需求拆明白共享單車分析到底要分析出什么很多同學(xué)拿到題目第一件事就是找數(shù)據(jù)、跑環(huán)境結(jié)果環(huán)境配了一周方向還是糊的。我建議先花半天時(shí)間把分析需求拆清楚因?yàn)槟愕倪x題、開題報(bào)告、論文的研究?jī)?nèi)容部分、以及后期的工作量全部由這個(gè)需求決定。共享單車的數(shù)據(jù)分析通常圍繞五個(gè)方向展開時(shí)間維度分時(shí)段的騎行量波動(dòng)、工作日的早晚高峰效應(yīng)、周末娛樂出行特征。這是潮汐現(xiàn)象的核心也是最好出成果的部分??臻g維度起點(diǎn)/終點(diǎn)熱點(diǎn)區(qū)域識(shí)別、熱門騎行路徑OD對(duì)、區(qū)域之間的流動(dòng)關(guān)系。用戶維度騎行時(shí)長(zhǎng)的分布、單次里程、付費(fèi)類型單次卡、周卡、月卡對(duì)行為的影響。車輛維度車輛周轉(zhuǎn)率、單車的日均使用次數(shù)、車輛調(diào)度壓力評(píng)估。環(huán)境維度天氣、溫度、空氣質(zhì)量對(duì)騎行量的影響這部分需要拼接外部數(shù)據(jù)適合作加分項(xiàng)。有了這個(gè)框架你再去設(shè)計(jì)數(shù)據(jù)字典、設(shè)計(jì)清洗規(guī)則、設(shè)計(jì)Hive表結(jié)構(gòu)就會(huì)很自然。比如你知道自己要做時(shí)段分析那數(shù)據(jù)表里開始時(shí)間、結(jié)束時(shí)間就必須清洗成統(tǒng)一的yyyy-MM-dd HH:mm:ss格式你要做熱點(diǎn)區(qū)域那經(jīng)緯度就必須做有效范圍檢查并且考慮用網(wǎng)格化或者GeoHash聚類。2.1 初步需求到技術(shù)選型的映射很多同學(xué)不明白為什么非要用Hive和Spark直覺上Pandas也能算。這里的關(guān)鍵是數(shù)據(jù)量級(jí)和多步驟加工這兩個(gè)維度。如果你的數(shù)據(jù)量是百萬(wàn)級(jí)Pandas確實(shí)跑得動(dòng)但一旦要反復(fù)清洗、多表關(guān)聯(lián)、周期性重算單機(jī)的內(nèi)存和代碼維護(hù)成本就成了瓶頸。Hive的好處是數(shù)據(jù)落在HDFS上一表到底SQL表達(dá)清晰Hive on Spark能利用分布式算力跑億級(jí)數(shù)據(jù)整套流程遷移到真實(shí)生產(chǎn)環(huán)境毫無違和感。我推薦的最小可行技術(shù)棧是環(huán)節(jié)工具選型理由數(shù)據(jù)采集Python爬蟲自采 模擬數(shù)據(jù)補(bǔ)全可控能湊足規(guī)模數(shù)據(jù)存儲(chǔ)HDFS Hive分區(qū)表滿足大數(shù)據(jù)敘事支持增量數(shù)據(jù)清洗Spark或MapReduce離線任務(wù)分布式清洗體現(xiàn)工程能力分析計(jì)算Hive SQL Spark SQL易于說明分析邏輯可視化Flask后端 ECharts大屏開發(fā)效率高圖表演示效果好這里有個(gè)重要的取舍建議清洗場(chǎng)景不強(qiáng)的話不建議死磕MapReduce手寫代碼原因后面我會(huì)專門說。用Spark的DataFrame API或者Spark SQL清洗代碼量少、控制力強(qiáng)答辯時(shí)也更好解釋。2.2 數(shù)據(jù)量級(jí)多少條才算大數(shù)據(jù)這也是答辯必問的問題。我的經(jīng)驗(yàn)是本科畢設(shè)的數(shù)據(jù)量單表達(dá)到300萬(wàn)~1000萬(wàn)條是比較舒服的區(qū)間既能體現(xiàn)分布式優(yōu)勢(shì)又不至于讓集群處理時(shí)間太慢。低于50萬(wàn)條Hive的啟動(dòng)開銷都比計(jì)算時(shí)間大超過兩千萬(wàn)條你自己的測(cè)試機(jī)跑一次全量清洗可能就要等十幾分鐘反復(fù)調(diào)試心態(tài)容易崩。數(shù)據(jù)量可以從兩個(gè)方向湊一是公開數(shù)據(jù)集后面會(huì)講怎么找二是自建一個(gè)騎行業(yè)務(wù)模擬器按真實(shí)用戶行為分布生成數(shù)據(jù)。很多人擔(dān)心模擬數(shù)據(jù)拿不上臺(tái)面其實(shí)只要生成規(guī)則是基于真實(shí)統(tǒng)計(jì)規(guī)律比如早晚高峰的概率權(quán)重、熱門區(qū)域的經(jīng)緯度集中度合理說明它是在公開數(shù)據(jù)集基礎(chǔ)上的補(bǔ)充完全站得住腳。生產(chǎn)環(huán)境里測(cè)試數(shù)據(jù)、脫敏數(shù)據(jù)本來就是常態(tài)。3. 數(shù)據(jù)從哪來公開數(shù)據(jù)集爬取、業(yè)務(wù)模擬器和CSV入庫(kù)全流程這個(gè)環(huán)節(jié)勸退的人最多。我見過有同學(xué)花兩個(gè)月找數(shù)據(jù)最后拿了一個(gè)CSV硬湊。下面把可行的路子一次說清。3.1 公開數(shù)據(jù)集的推薦次序優(yōu)先級(jí)最高的是有正式開放接口的數(shù)據(jù)源。比如Citi Bike NYC紐約共享單車系統(tǒng)開放了歷史騎行記錄按月提供CSV下載單月數(shù)據(jù)量就有幾十萬(wàn)到上百萬(wàn)條。字段包括起終點(diǎn)站名、起終點(diǎn)經(jīng)緯度、開始結(jié)束時(shí)間、會(huì)員類型字段質(zhì)量和敘事效果都很好。Divvy Bikes Chicago芝加哥的共享單車開放數(shù)據(jù)字段類似。Kaggle數(shù)據(jù)集搜索搜bike sharing dataset有很多整理好的版本部分包含天氣數(shù)據(jù)。國(guó)內(nèi)部分城市數(shù)據(jù)開放平臺(tái)有些城市有公共自行車歷史記錄字段和國(guó)內(nèi)騎行場(chǎng)景更接近。我不建議一上來就去找所謂爬蟲教程去抓商業(yè)單車平臺(tái)的數(shù)據(jù)。一方面合規(guī)風(fēng)險(xiǎn)高另一方面數(shù)據(jù)沒有穩(wěn)定的公開出口抓下來格式亂、字段臟反而拖進(jìn)度。實(shí)際做的過程中我推薦以紐約Citi Bike為底如果學(xué)?;蛘邔?dǎo)師希望有國(guó)內(nèi)數(shù)據(jù)再疊加業(yè)務(wù)模擬器生成一份本地化數(shù)據(jù)兩種數(shù)據(jù)源互為補(bǔ)充。3.2 一個(gè)可控?cái)?shù)據(jù)規(guī)模的自建模擬器思路假設(shè)你也想生成一份符合自己分析邏輯的數(shù)據(jù)而不是完全依賴外部數(shù)據(jù)集。模擬器的核心不是隨機(jī)數(shù)而是概率分布。我當(dāng)時(shí)寫了一個(gè)Python腳本用權(quán)重表模擬三類用戶單次卡、周卡、月卡用戶的出行決策import random import datetime import csv # 每個(gè)時(shí)段的基礎(chǔ)騎行概率模擬早晚高峰 hour_prob [0.005,0.002,0.001,0.001,0.002,0.008,0.02,0.045,0.03, 0.025,0.02,0.03,0.04,0.035,0.03,0.045,0.06,0.075, 0.055,0.03,0.025,0.02,0.015,0.008] def generate_one_record(record_date, user_id): start_hour random.choices(range(24), weightshour_prob)[0] start_minute random.randint(0, 59) start_time datetime.datetime(record_date.year, record_date.month, record_date.day, start_hour, start_minute) duration int(random.gauss(11, 4)) # 單位分鐘均值11分鐘的出行 if duration 1: duration 1 end_time start_time datetime.timedelta(minutesduration) # 經(jīng)緯度從城市熱點(diǎn)網(wǎng)格中采樣熱點(diǎn)區(qū)域壓低距離 start_lat, start_lng sample_hot_point(start) end_lat, end_lng sample_nearby(start_lat, start_lng, 0.02) return [record_date.isoformat(), start_time.isoformat(), end_time.isoformat(), round(start_lat,6), round(start_lng,6), round(end_lat,6), round(end_lng,6), duration, random.choice([single,weekly,monthly]), user_id]它生成的數(shù)據(jù)量由你決定想加到一千萬(wàn)條就多跑幾天。需要說明的是模擬器生成的數(shù)據(jù)必須保存原始未清洗版本這樣后期清洗邏輯才有東西可做千萬(wàn)不能一步到位生成干凈數(shù)據(jù)答辯會(huì)穿幫。3.3 數(shù)據(jù)入庫(kù)與字段設(shè)計(jì)我建議不分?jǐn)?shù)據(jù)來源統(tǒng)一落到同一套字段結(jié)構(gòu)里方便后續(xù)寫清洗腳本。字段名類型說明record_idstring全局唯一IDuser_idstring用戶標(biāo)識(shí)脫敏后的IDstart_timestring騎行開始時(shí)間ISO格式end_timestring騎行結(jié)束時(shí)間ISO格式start_lat / start_lngdouble起點(diǎn)經(jīng)緯度end_lat / end_lngdouble終點(diǎn)經(jīng)緯度duration_minint騎行時(shí)長(zhǎng)分鐘bike_idstring車輛IDuser_typestring用戶類型single/weekly/monthlysourcestring數(shù)據(jù)來源標(biāo)記入庫(kù)時(shí)直接用Python把CSV寫到HDFS上注意先模擬分布式目錄結(jié)構(gòu)比如按data/raw/2024/01/part-xxx.csv存放。這一步看似簡(jiǎn)單但數(shù)據(jù)進(jìn)HDFS前要不要預(yù)處理是有講究的我建議不要預(yù)處理讓清洗階段去面對(duì)臟數(shù)據(jù)這樣清洗環(huán)節(jié)才有工作量。hdfs dfs -mkdir -p /user/bike/raw/2024 hdfs dfs -put ./data/raw/2024/part-001.csv /user/bike/raw/2024/4. 臟數(shù)據(jù)的治理思路清洗不是把事情做對(duì)這么簡(jiǎn)單數(shù)據(jù)清洗是貫穿整個(gè)項(xiàng)目的隱形工作量也是最容易被低估的部分。我給一個(gè)經(jīng)驗(yàn)比例一個(gè)合格的共享單車分析項(xiàng)目清洗的時(shí)間要占60%以上分析、可視化反而快。因?yàn)榉治鼋Y(jié)論的說服力完全建立在數(shù)據(jù)質(zhì)量上。4.1 清洗規(guī)則的設(shè)計(jì)層次清洗規(guī)則我按硬規(guī)則、軟規(guī)則、統(tǒng)計(jì)規(guī)則三層來設(shè)計(jì)。硬規(guī)則指那些一眼就能判定的異常記錄ID為空、時(shí)間字段解析失敗、經(jīng)緯度越界緯度不在[-90, 90]經(jīng)度不在[-180, 180]、騎行時(shí)長(zhǎng)為負(fù)或?yàn)?。這些直接過濾掉或拋入異常表。# Spark偽代碼 from pyspark.sql.functions import when, col, to_timestamp df spark.read.csv(hdfs:///user/bike/raw/2024/*, headerTrue) df_clean df.filter( (col(record_id).isNotNull()) (col(start_lat).between(-90, 90)) (col(end_lng).between(-180, 180)) (col(duration_min) 0) (to_timestamp(col(start_time)).isNotNull()) )軟規(guī)則指那些疑似異常但需要判斷的騎行時(shí)長(zhǎng)超過24小時(shí)、單次騎行距離超過20公里、起終點(diǎn)經(jīng)緯度完全相同卻標(biāo)稱騎行半小時(shí)。這類記錄不一定要?jiǎng)h除可以單獨(dú)標(biāo)記flag_abnormal字段分析時(shí)排除、論文里作為清洗統(tǒng)計(jì)維度呈現(xiàn)。統(tǒng)計(jì)規(guī)則是更高階的比如某輛單車一天被使用50次以上超過物理極限判定為采集設(shè)備異常再比如某用戶凌晨3點(diǎn)到5點(diǎn)連續(xù)騎行4小時(shí)大概率是數(shù)據(jù)上報(bào)邏輯bug按業(yè)務(wù)邏輯剔除。統(tǒng)計(jì)規(guī)則是答辯時(shí)的加分項(xiàng)因?yàn)樗w現(xiàn)的是你懂業(yè)務(wù)而不是只會(huì)寫代碼。4.2 用Spark清洗時(shí)的資源陷阱清洗數(shù)據(jù)時(shí)最容易踩的一個(gè)坑是Spark默認(rèn)spark.sql.shuffle.partitions是200一旦你頻繁join、groupBy會(huì)產(chǎn)生大量小文件。如果清洗完的數(shù)據(jù)有幾千個(gè)小于1MB的小文件后面Hive查起來會(huì)慢得離譜。解決方法是清洗任務(wù)結(jié)束后加一個(gè)重分區(qū)操作df_clean.coalesce(8).write.mode(overwrite).partitionBy(dt).parquet(/user/bike/clean)這里用Parquet而不是CSV是有講究的。Parquet列式存儲(chǔ)加Snappy壓縮Hive掃描時(shí)只需要讀相關(guān)列I/O開銷遠(yuǎn)小于純文本。答辯老師問起來為什么用Parquet這本身就是一個(gè)很好的技術(shù)點(diǎn)。4.3 清洗完了怎么驗(yàn)收清洗有沒有完成不能靠拍腦袋。我習(xí)慣在清洗腳本里輸出一組驗(yàn)收指標(biāo)總記錄數(shù)、剔除記錄數(shù)、剔除原因分布、清洗后字段完整性、目標(biāo)文件大小。這些指標(biāo)直接寫入一個(gè)quality_report目錄。驗(yàn)收項(xiàng)期望值字段完整性100%無空關(guān)鍵字段時(shí)間解析成功率99.9%以上經(jīng)緯度合法率99.9%以上騎行時(shí)長(zhǎng)大于0占比100%剔除記錄總數(shù)控制在5%以內(nèi)超出說明源數(shù)據(jù)問題大這些數(shù)據(jù)不只是給自己看寫論文實(shí)驗(yàn)數(shù)據(jù)預(yù)處理那一章時(shí)直接可以畫一張柱狀圖展示清洗前后對(duì)比內(nèi)容一下子就充實(shí)了。5. Hive數(shù)倉(cāng)分層與坐標(biāo)解析從OD表到城市騎行熱力數(shù)據(jù)清洗完接下來是把數(shù)據(jù)組織成適合分析的結(jié)構(gòu)。這一部分直接決定你的分析流程是絲滑還是腰椎間盤突出。5.1 為什么一定要設(shè)計(jì)分層如果你的分析就是在原始表上反復(fù)WHERE start_time 2024-01-01 GROUP BY start_station短期看沒什么問題一旦分析維度多了每次寫復(fù)雜SQL都是一場(chǎng)災(zāi)難。我建議至少分成三層ODS層原始數(shù)據(jù)層、DWD層明細(xì)數(shù)據(jù)層、ADS層應(yīng)用數(shù)據(jù)層。ODS層清洗前的原始數(shù)據(jù)一進(jìn)HDFS就不動(dòng)。DWD層清洗后的標(biāo)準(zhǔn)化明細(xì)數(shù)據(jù)Parquet存儲(chǔ)按天分區(qū)字段統(tǒng)一。ADS層面向具體分析主題的匯總表比如按小時(shí)統(tǒng)計(jì)表、按站點(diǎn)統(tǒng)計(jì)表、按用戶類型統(tǒng)計(jì)表。-- DWD層建表示例 CREATE TABLE dwd_bike_trip ( record_id STRING, user_id STRING, bike_id STRING, start_time TIMESTAMP, end_time TIMESTAMP, start_lat DOUBLE, start_lng DOUBLE, end_lat DOUBLE, end_lng DOUBLE, duration_min INT, user_type STRING ) PARTITIONED BY (dt STRING) STORED AS PARQUET;Hive分區(qū)字段不能和處理字段重名比如start_time只能作為普通列分區(qū)用dt這個(gè)獨(dú)立字符串字段。這個(gè)坑我見有人踩過分區(qū)字段和普通字段重名建表能成功但查詢時(shí)會(huì)收到詭異報(bào)錯(cuò)定位半天找不到原因。5.2 OD數(shù)據(jù)轉(zhuǎn)成網(wǎng)格數(shù)據(jù)的關(guān)鍵步驟共享單車有一個(gè)天然便于分析的特征它記錄的是起點(diǎn)和終點(diǎn)。大多數(shù)同學(xué)的用法是畫一個(gè)散點(diǎn)圖這個(gè)太浪費(fèi)了。我的建議是把起終點(diǎn)經(jīng)緯度轉(zhuǎn)成網(wǎng)格IDGrid ID把連續(xù)空間離散化然后按網(wǎng)格統(tǒng)計(jì)。INSERT INTO TABLE ads_grid_hot PARTITION (dt2024-06-01) SELECT FLOOR(start_lat * 1000) AS grid_lat, FLOOR(start_lng * 1000) AS grid_lng, COUNT(*) AS trip_cnt, SUM(duration_min) AS total_duration FROM dwd_bike_trip WHERE dt 2024-06-01 GROUP BY FLOOR(start_lat * 1000), FLOOR(start_lng * 1000);為什么乘以1000因?yàn)榻?jīng)緯度保留4位小數(shù)時(shí)乘以1000取整會(huì)把同一個(gè)約100米范圍內(nèi)的點(diǎn)聚合到一起這個(gè)尺度恰好對(duì)應(yīng)城市街區(qū)尺度非常適合騎行情景區(qū)間。你要是直接拿原始經(jīng)緯度做聚簇容易產(chǎn)生大量格點(diǎn)很稀疏的小單元沒有分析價(jià)值。網(wǎng)格化的好處一是分析粒度均勻二是為后面的可視化階段從經(jīng)緯度散點(diǎn)轉(zhuǎn)為區(qū)塊熱力打下基礎(chǔ)三是在碰撞熱力圖上天然就是一張網(wǎng)格圖不用額外聚合。5.3 坐標(biāo)糾偏與城市匹配的真實(shí)問題做國(guó)內(nèi)數(shù)據(jù)實(shí)驗(yàn)的同學(xué)會(huì)遇到一個(gè)經(jīng)典問題GPS坐標(biāo)是火星坐標(biāo)系GCJ-02和Web地圖上的WGS-84坐標(biāo)存在偏移。如果你直接拿API給的數(shù)據(jù)去ECharts上畫地圖熱力點(diǎn)會(huì)偏移幾百米在高德底圖上非常明顯。處理辦法是寫一個(gè)坐標(biāo)轉(zhuǎn)換函數(shù)或者直接反查一個(gè)公共的GCJ-02轉(zhuǎn)WGS-84算法。紐約Citi Bike的數(shù)據(jù)本身是WGS-84畫地圖時(shí)沒有這個(gè)問題這也是我推薦它作為基礎(chǔ)數(shù)據(jù)集的原因之一。如果混用了國(guó)內(nèi)模擬數(shù)據(jù)建議統(tǒng)一轉(zhuǎn)到偏置坐標(biāo)系后再入數(shù)倉(cāng)否則后面地圖展示會(huì)亂套。6. 核心分析模型潮汐、熱點(diǎn)、騎行時(shí)長(zhǎng)與天氣聯(lián)動(dòng)這是項(xiàng)目的靈魂部分。分析項(xiàng)目好不好不是看圖表多不多而是看能不能用數(shù)據(jù)回答幾個(gè)有洞察性的問題。6.1 時(shí)間維度核心模型早晚高峰潮汐共享單車最經(jīng)典的分析就是24小時(shí)騎行量分布。用ADS層的小時(shí)統(tǒng)計(jì)表按工作日和周末分組對(duì)比你會(huì)得到兩條截然不同的曲線。工作日出現(xiàn)兩個(gè)明顯的波峰——早高峰7~9點(diǎn)和晚高峰17~19點(diǎn)周末則是一個(gè)平緩的午后高峰。這背后的業(yè)務(wù)含義就是通勤替代率。SELECT hour(start_time) AS hour, CASE WHEN DAYOFWEEK(start_time) IN (1,7) THEN weekend ELSE workday END AS day_type, COUNT(*) AS trip_cnt FROM dwd_bike_trip WHERE dt BETWEEN 2024-01-01 AND 2024-03-31 GROUP BY hour(start_time), CASE WHEN DAYOFWEEK(start_time) IN (1,7) THEN weekend ELSE workday END ORDER BY hour(start_time);更進(jìn)一步可以做潮汐對(duì)比矩陣早高峰起點(diǎn)集中、終點(diǎn)分散晚高峰反過來的方向性這種分析直接對(duì)應(yīng)調(diào)度策略——哪個(gè)區(qū)域早上需要投車哪個(gè)區(qū)域晚上需要回收車。這個(gè)結(jié)論寫到論文里很有說服力。6.2 空間維度核心模型熱門區(qū)域與OD流向基于網(wǎng)格熱力表可以用一個(gè)簡(jiǎn)單的閾值識(shí)別熱點(diǎn)某個(gè)網(wǎng)格日均騎行量超過全體網(wǎng)格均值的兩倍標(biāo)記為熱點(diǎn)區(qū)域。若再用DBSCAN做一次密度聚類效果更好因?yàn)闊狳c(diǎn)網(wǎng)格自然形成空間簇能把熱鬧的行政區(qū)或商圈圈出來。OD流向分析建議用桑基圖來表達(dá)。從起點(diǎn)網(wǎng)格到終點(diǎn)網(wǎng)格的流量構(gòu)成一個(gè)巨大的有向圖但直接畫全部會(huì)糊成一團(tuán)。可以只取流量TOP20的OD對(duì)展示區(qū)域之間的潮汐流動(dòng)。比如從CBD早上流出多、晚上流入多這種模式配合?;鶊D一眼看懂。6.3 騎行時(shí)長(zhǎng)與用戶分類分布形態(tài)暴露業(yè)務(wù)特征騎行時(shí)長(zhǎng)分布通常是有偏的正態(tài)分布均值在10~15分鐘尾部會(huì)拖到60分鐘以上。很多分析到此為止但有一個(gè)有意思的點(diǎn)把用戶類型分開統(tǒng)計(jì)你會(huì)看到月卡用戶單次騎行時(shí)長(zhǎng)普遍長(zhǎng)于單次卡用戶且周末的月卡用戶騎行呈現(xiàn)長(zhǎng)尾——他們更像是騎行休閑而不是通勤剛需。這個(gè)結(jié)果能引出運(yùn)營(yíng)維度的討論要不要針對(duì)月卡用戶做周末長(zhǎng)距離騎行的激勵(lì)騎行時(shí)長(zhǎng)的異常值和超短時(shí)騎行小于1分鐘也可以做一個(gè)小專題比如小于1分鐘的騎行記錄占X%可能是熄火重騎或定位漂移說明你連業(yè)務(wù)的細(xì)支末節(jié)都考慮到了。6.4 與天氣數(shù)據(jù)的聯(lián)動(dòng)分析天氣數(shù)據(jù)要額外采集但效果很好。我從公開天氣API按天拉取城市的氣溫、降雨量和風(fēng)力等級(jí)然后和每天的總騎行量做關(guān)聯(lián)。清洗時(shí)要注意降雨量字段大量為0是正常現(xiàn)象做分組對(duì)比時(shí)按無雨vs小雨vs中雨以上分段而不是當(dāng)數(shù)值回歸。一個(gè)簡(jiǎn)單但有效的分析維度是雨后3小時(shí)騎行量恢復(fù)曲線——降雨停止后騎行量的反彈速度能側(cè)面反映路面積水和用戶心理恢復(fù)時(shí)間。這部分如果時(shí)間夠放一小節(jié)論文會(huì)很出彩。6.5 分析結(jié)果的落地建議分析模型不是越多越好而是每條分析都必須對(duì)應(yīng)一個(gè)可操作的結(jié)論。比如分析主題分析結(jié)論建議潮汐分析早高峰起點(diǎn)熱點(diǎn)區(qū)域應(yīng)加大車輛投放熱點(diǎn)識(shí)別熱點(diǎn)區(qū)域3公里內(nèi)增加調(diào)度頻次用戶時(shí)長(zhǎng)分類月卡用戶長(zhǎng)時(shí)騎行推薦周中長(zhǎng)線騎行推薦天氣影響雨天適當(dāng)減少投放并延長(zhǎng)調(diào)度間隔把這些結(jié)論做成數(shù)據(jù)→洞察→建議的完整鏈路答辯老師問你分析出了什么時(shí)你就有了理直氣壯的答案。7. Flask ECharts可視化大屏怎么把結(jié)論演出來可視化階段很多人的做法是用Jupyter Notebook隨便畫幾個(gè)圖截圖放到論文里。我不反對(duì)但那樣答辯演示的效果很差。我推薦的方案是做一個(gè)數(shù)據(jù)可視化看板頁(yè)面用Flask做后端接口ECharts做前端圖表展示核心分析結(jié)果。7.1 Flask接口怎么設(shè)計(jì)后端不需要做得很重核心就是讀取Hive/Spark SQL分析后的ADS層數(shù)據(jù)以JSON接口返回。一個(gè)建議在ADS層把數(shù)據(jù)提前匯總好而不是每次請(qǐng)求都去跑Hive否則頁(yè)面刷一下要等十幾秒極度影響演示體驗(yàn)。from flask import Flask, jsonify import pandas as pd app Flask(__name__) app.route(/api/trend/hourly) def hourly_trend(): df pd.read_parquet(./ads_data/hourly_trend.parquet) return jsonify({ hours: df[hour].tolist(), workday: df[workday_cnt].tolist(), weekend: df[weekend_cnt].tolist() }) if __name__ __main__: app.run(host0.0.0.0, port5000)這里有一點(diǎn)經(jīng)驗(yàn)如果Hive在遠(yuǎn)程集群Flask在本地不要想著在Web接口里直接連Hive網(wǎng)絡(luò)不穩(wěn)定還容易超時(shí)。正確做法是分析SQL在集群上跑完結(jié)果同步到本地Parquet或MySQLFlask只讀本地結(jié)果集。7.2 圖表選型的匹配邏輯ECharts圖表很多但用不好就會(huì)變成大雜燴。我按分析主題給一個(gè)匹配建議分析主題推薦圖表為什么24小時(shí)騎行趨勢(shì)折線圖/面積圖工作日與周末雙線直觀對(duì)比雙峰曲線空間熱力地圖散點(diǎn)熱力圖熱點(diǎn)一目了然適合大屏熱門OD流動(dòng)?;鶊D流量方向和規(guī)模同時(shí)呈現(xiàn)騎行時(shí)長(zhǎng)分布直方圖/箱線圖暴露分布形態(tài)和異常尾部天氣影響分組柱狀圖無雨/小雨/中雨對(duì)比差異明顯頁(yè)面布局上建議做單頁(yè)大屏風(fēng)格核心指標(biāo)總訂單數(shù)、日均騎行量、平均騎行時(shí)長(zhǎng)、活躍車輛數(shù)放頂部卡片下面放趨勢(shì)、熱力圖、?;鶊D三個(gè)主圖。不需要翻頁(yè)打開就演示答辯時(shí)不用找文件。7.3 ECharts地圖無法顯示的排查做地圖熱力最容易出的問題是ECharts默認(rèn)地圖數(shù)據(jù)沒有你需要的城市GeoJSON。解決辦法是注冊(cè)地圖fetch(/static/beijing.json).then(res res.json()).then(geoJson { echarts.registerMap(beijing, geoJson); chart.setOption({ geo: { map: beijing }, series: [{ type: scatter, coordinateSystem: geo, data: points }] }); });一個(gè)隱藏坑GeoJSON文件里的坐標(biāo)順序是[longitude, latitude]如果你的數(shù)據(jù)是[latitude, longitude]畫出來的點(diǎn)會(huì)全部錯(cuò)位到海里去。我第一次實(shí)現(xiàn)時(shí)這個(gè)問題找了一晚上。解決方式是統(tǒng)一封裝一個(gè)轉(zhuǎn)換函數(shù)確保數(shù)據(jù)字段順序和GeoJSON一致。7.4 數(shù)據(jù)可視化不只是展示答辯演示最容易翻車的地方是老師問這個(gè)圖說明了什么你說我也不太清楚可能是早高峰吧。為了避免這種尷尬每個(gè)圖表頁(yè)面建議配一句注釋文本把圖的結(jié)論直接寫出來。這個(gè)注釋既是給答辯老師看的也是給你自己救命用的。比如?;鶊D下方寫早高峰TOP20 OD流向顯示從住宅區(qū)網(wǎng)格向CBD網(wǎng)格方向流量占52.3%反向占21.6%反映出明顯的通勤潮汐特征。8. 集群部署與跑批任務(wù)從單機(jī)偽分布式到集群的完整路徑這個(gè)題目既然掛上了大數(shù)據(jù)部署環(huán)節(jié)是繞不開的。但很多同學(xué)在集群部署上消耗了大量時(shí)間這里我給一條盡量平滑的路線。8.1 單機(jī)還是集群按畢設(shè)工作量來定如果你學(xué)校沒有高性能服務(wù)器資源單機(jī)偽分布式模式完全夠用。Hadoop的偽分布式和真實(shí)分布式在計(jì)算框架、Hive語(yǔ)義、Spark應(yīng)用上是一樣的差別只在規(guī)模。數(shù)據(jù)量控制在三四百萬(wàn)條時(shí)單機(jī)8G內(nèi)存的虛擬機(jī)完全可以穩(wěn)定跑完。如果你有兩臺(tái)以上機(jī)器建議搭一個(gè)小集群1個(gè)NameNode 2個(gè)DataNodeHDFS的副本策略會(huì)出現(xiàn)真實(shí)的數(shù)據(jù)分發(fā)這對(duì)論文中的分布式存儲(chǔ)與計(jì)算章節(jié)是很好的素材。但注意不要花超過三天時(shí)間在集群搭建上這不是畢設(shè)的重點(diǎn)。8.2 環(huán)境版本匹配的慘痛經(jīng)驗(yàn)版本選錯(cuò)是所有入門者最容易掉進(jìn)去的坑。Hadoop、Spark、Hive、JDK四者版本不匹配會(huì)出現(xiàn)各種奇怪的報(bào)錯(cuò)。我實(shí)測(cè)下來相對(duì)穩(wěn)定的一套組合是組件版本JDK1.80_202或相近版本Hadoop3.3.xSpark3.3.x對(duì)應(yīng)Scala 2.12Hive3.1.x用Hive on Spark需要額外配置如果只是想做好畢設(shè)我強(qiáng)烈推薦用Ambari或者Docker Compose起一套預(yù)配置的集群而不是從源碼編譯去折騰。比如用docker-compose直接拉一個(gè)Hive Spark的環(huán)境鏡像好處是環(huán)境一致不會(huì)因?yàn)槟愕谋緳C(jī)配置不同而全家桶崩盤。踩過一次集群咕咕叫后你就會(huì)認(rèn)同環(huán)境穩(wěn)定是研發(fā)效率的前提別把自己寶貴的時(shí)間耗在編譯報(bào)錯(cuò)上。8.3 跑批任務(wù)的編排思路分析SQL不是一次跑完就完事往往需要多次迭代調(diào)試。我喜歡把整套運(yùn)行流程腳本化一個(gè) shell 腳本按順序執(zhí)行清洗任務(wù)→DWD構(gòu)建→ADS匯總→結(jié)果同步到可視化層。復(fù)盤一次完整的處理鏈路大概10到15分鐘能走一遍。這樣熬夜調(diào)圖的時(shí)候也不用擔(dān)心手動(dòng)漏跑哪一步。#!/bin/bash echo step1: clean raw data spark-submit --master local[*] clean_job.py echo step2: build dwd hive -f build_dwd.sql echo step3: build ads hive -f build_ads.sql echo step4: sync to local hdfs dfs -getmerge /user/bike/ads/hourly_trend ./data/ads/hourly_trend.csv腳本化還有一個(gè)好處寫論文系統(tǒng)實(shí)現(xiàn)部分時(shí)可以直接把整套流程圖截出來實(shí)習(xí)面試時(shí)也能講我有一個(gè)自動(dòng)化跑批的規(guī)范習(xí)慣。9. 我踩過最深的幾個(gè)坑希望你繞開這一節(jié)本來想寫十來個(gè)后來篩出五個(gè)最有代表性的每一條都是我在折騰這個(gè)項(xiàng)目時(shí)真實(shí)膝蓋中箭的地方。9.1 數(shù)據(jù)量大不等于文件多爬蟲生成CSV時(shí)如果每個(gè)小時(shí)一個(gè)文件最終可能有幾百個(gè)CSVHive外部表建好后查詢時(shí)MetaStore掃描小文件的開銷會(huì)吃掉大量性能。解決辦法是在寫入HDFS之前先把當(dāng)天所有的CSV合并成一個(gè)或者跨度為小時(shí)級(jí)的幾個(gè)大文件小文件會(huì)成為分布式系統(tǒng)的隱藏殺手。9.2 Parquet表查詢慢先別懷疑集群如果Hive查一張Parquet表突然變慢先檢查查詢是否觸發(fā)了map-side的完整掃描。另一個(gè)常見問題是Parquet表的列裁剪需要較高版本的Hive舊版本可能對(duì)嵌套類型支持不佳導(dǎo)致它把整表讀出來再過濾。遇到這種詭異性能問題先看執(zhí)行計(jì)劃EXPLAIN SELECT * FROM dwd_bike_trip WHERE dt2024-06-01;如果看到Map 1階段顯示掃描了所有分區(qū)說明分區(qū)裁剪失效了檢查你的dt字段類型是不是和查詢條件匹配。類型不匹配是分區(qū)裁剪失敗最常見的原因。9.3 時(shí)間字段的時(shí)區(qū)陷阱如果你用了國(guó)際公開數(shù)據(jù)集數(shù)據(jù)里的時(shí)間很可能是UTC時(shí)間轉(zhuǎn)成中國(guó)時(shí)區(qū)或者紐約本地時(shí)區(qū)需要明確偏移量。如果不轉(zhuǎn)換你做早高峰分析時(shí)會(huì)發(fā)現(xiàn)出行高峰出現(xiàn)在奇怪的時(shí)間段再用業(yè)務(wù)邏輯解釋半天其實(shí)只是時(shí)區(qū)沒對(duì)齊。統(tǒng)一在清洗階段處理df df.withColumn(local_start_time, from_utc_timestamp(start_time, America/New_York))9.4 ECharts刷新卡頓不是網(wǎng)絡(luò)問題當(dāng)你的熱力圖數(shù)據(jù)點(diǎn)超過幾萬(wàn)個(gè)時(shí)前端一次性渲染會(huì)卡成PPT。解決辦法是在后端接口層把網(wǎng)格聚合結(jié)果做抽樣或分級(jí)只返回超過閾值的TOP網(wǎng)格這樣前端數(shù)據(jù)量小、渲染流暢大屏演示時(shí)體驗(yàn)完全不一樣。這不是什么黑科技但知道的人確實(shí)不多。9.5 答辯前最后一次全量跑批最惡心的崩潰場(chǎng)景答辯前一天你新改了一處清洗邏輯跑完發(fā)現(xiàn)結(jié)果圖表全亂了。為了避免這個(gè)情況我的建議是鎖定一個(gè)提交版本后絕不再改分析邏輯除非是純前端樣式的調(diào)整。每次改動(dòng)都全量重跑一次并對(duì)比舊結(jié)果的一致性。寧可丑一點(diǎn)不能答辯時(shí)崩。10. 論文結(jié)構(gòu)組織與答辯追問清單最后快速說說論文怎么寫、答辯怎么應(yīng)對(duì)。論文結(jié)構(gòu)不要照抄模板要按你做的東西說理。10.1 論文的章節(jié)節(jié)奏第一章緒論共享單車調(diào)度問題的實(shí)際背景大數(shù)據(jù)技術(shù)應(yīng)用于交通數(shù)據(jù)的價(jià)值。這部分不需要長(zhǎng)篇大論重點(diǎn)寫為什么用大數(shù)據(jù)、為什么是共享單車場(chǎng)景。第二章關(guān)鍵技術(shù)Hadoop、Hive、Spark、ECharts各自在這個(gè)項(xiàng)目里的定位每節(jié)配一個(gè)應(yīng)用場(chǎng)景描述別寫教科書定義。第三章系統(tǒng)需求與架構(gòu)設(shè)計(jì)畫出你的分層架構(gòu)圖說明每層的輸入輸出。這一張圖的價(jià)值大于大段文字。第四章數(shù)據(jù)獲取與預(yù)處理重點(diǎn)寫清洗規(guī)則和差分統(tǒng)計(jì)附上清洗前后對(duì)比表。這是工作量最大的部分篇幅可以給足。第五章離線數(shù)倉(cāng)構(gòu)建ODS/DWD/ADS的表的字段設(shè)計(jì)、分區(qū)策略、建表語(yǔ)句。附幾張數(shù)據(jù)抽樣截圖。第六章分析模型設(shè)計(jì)與實(shí)現(xiàn)每個(gè)分析主題一個(gè)小節(jié)說清楚指標(biāo)定義、SQL/算法邏輯、結(jié)果解讀、業(yè)務(wù)建議。這是論文最核心的章節(jié)也是最容易拉開差距的部分。第七章可視化系統(tǒng)實(shí)現(xiàn)接口設(shè)計(jì)、圖表選型、頁(yè)面效果截圖。第八章總結(jié)復(fù)盤項(xiàng)目成果和不足說明進(jìn)一步工作方向坦白說明哪些數(shù)據(jù)是模擬補(bǔ)充的以及在實(shí)際生產(chǎn)中需要驗(yàn)證的內(nèi)容。10.2 答辯高頻追問清單老師問得最多的幾個(gè)問題我整理成一份清單你可以逐條準(zhǔn)備為什么用Hive而不用MySQL答數(shù)據(jù)量百萬(wàn)級(jí)、分析場(chǎng)景多、需要周期重算Hive的分布式存儲(chǔ)和類SQL語(yǔ)法更適合離線批處理。你的數(shù)據(jù)清洗占比多少、為什么答占開發(fā)時(shí)間六成清洗規(guī)則分三層設(shè)計(jì)源數(shù)據(jù)質(zhì)量問題存在比例統(tǒng)計(jì)。Spark和MapReduce你選哪個(gè)、為什么Hybrid答清洗邏輯復(fù)雜時(shí)用Spark API表達(dá)能力強(qiáng)如果要求必須體現(xiàn)MapReduce也可以簡(jiǎn)單清洗環(huán)節(jié)用MR實(shí)現(xiàn)復(fù)雜統(tǒng)計(jì)用Spark但必須解釋清楚兩者各自適用的場(chǎng)景。你的分析結(jié)論如果和業(yè)務(wù)常識(shí)沖突怎么辦答回查數(shù)據(jù)和清洗邏輯確認(rèn)不是異常值導(dǎo)致如果確認(rèn)是異常可以在論文里作為數(shù)據(jù)異常分析小節(jié)呈現(xiàn)反而是亮點(diǎn)。系統(tǒng)延遲多少、能實(shí)時(shí)嗎答這是離線分析系統(tǒng)數(shù)據(jù)延遲為T1業(yè)務(wù)決策場(chǎng)景是調(diào)度復(fù)盤而非實(shí)時(shí)預(yù)警如果需要實(shí)時(shí)可用KafkaFlink替換但不屬于本課題范圍。最后再說兩句做項(xiàng)目的心里話我做這類畢設(shè)項(xiàng)目見得多了很多人最終做的不是大數(shù)據(jù)分析而是給數(shù)據(jù)套了層殼。等你真的把數(shù)據(jù)量、清洗邏輯、數(shù)倉(cāng)分層、分析模型、可視化串起來以后你會(huì)發(fā)現(xiàn)每一層都會(huì)腐蝕你的耐心跑批一次十幾分鐘、圖表坐標(biāo)反了、Hive分區(qū)不生效、ECharts地圖少了一塊。但恰恰是這些坑會(huì)把一個(gè)照著教程完成作業(yè)的人變成一個(gè)能獨(dú)立交付數(shù)據(jù)項(xiàng)目的人。如果你現(xiàn)在剛開始做建議動(dòng)手前先把數(shù)據(jù)量定下來、把分析主題定下來、把表結(jié)構(gòu)設(shè)計(jì)定下來再開始寫代碼。這個(gè)項(xiàng)目真正適合你的地方不是學(xué)會(huì)某個(gè)工具而是理解數(shù)據(jù)從產(chǎn)生、采集、清洗、建模到?jīng)Q策的完整旅程——這一趟走完你的畢業(yè)設(shè)計(jì)才是一門真正的數(shù)據(jù)分析課。