Mobike 共享单车分析项目
项目目标
这个项目从单车骑行订单中分析用户的时间、距离与停放位置。核心目标是找出高频停车热点,为停车架、电子围栏、巡检和车辆调度提供数据依据。
不要混淆两个问题:结束位置热点说明车常停在哪里;起始位置热点才说明用户通常从哪里需要车。当前项目主要完成前者,不能只凭结束位置直接决定每个地点该投放多少辆车。
整体数据流程
原始 CSV 订单先经过清洗,再计算时长、距离、日期和高峰标签;随后导出统计表,提取结束位置,使用 KNN 参考 DBSCAN 的半径,并通过 KMeans 与 DBSCAN 得到停车区域和热点;最后由 Flask 大屏读取生成的 CSV 展示结果。
原始数据需要什么
输入 CSV 至少需要:orderid、bikeid、userid、start_time、end_time、start_location_x、start_location_y、end_location_x、end_location_y。
其中 x 通常是经度、y 通常是纬度,应该先用少量样本核对坐标确实位于上海。订单号、车辆号和用户号是标识,不应该当作普通数值计算。
分析脚本做了什么
mobikeAnalyzier_optimized.py 的处理顺序是:
- 检查输入文件、列名和数据类型。不能识别的时间或坐标会被记为缺失值,避免整批任务中断。
- 用
end_time - start_time计算每一单时长,并换算成总分钟ttl_min。这比把时长转成字符串后再拆分更准确。 - 从开始时间得到星期、工作日/周末、小时和早晚高峰标签。新版直接取本地小时,不用
utctimetuple(),避免时区造成高峰偏移。 - 用 Haversine 球面距离计算起终点真实距离
distance,单位为公里;不再把经纬度的欧氏距离误当作公里数。 - 输出时长、距离、小时等基础统计表,再对结束位置进行聚类。
分析后主要文件在 data/:
mobike_enriched.csv:原始订单加上全部新字段。gbtimeListToDF.csv:骑行时长分布。hour_num_df.csv:24 小时订单量。gbDisListToDF.csv:骑行距离分布。locDisDF.csv:有效结束坐标。kmeans_centers.csv:KMeans 区域中心和规模。borderPointsDF.csv:距 KMeans 中心约 300 米的点。dbscan_locations.csv:每个结束点的 DBSCAN 标签。dbscan_cluster_counts.csv:各 DBSCAN 热点规模。knn_k_distance.png、dbscan_clusters.png:两张分析图。
KNN 的作用
此项目中的 KNN 不预测类别,只是一个"量尺"。对每个结束位置找第 200 个最近位置,记录该距离,排序并绘制 KNN 距离图。
曲线平缓说明点很密集;曲线突然上升的拐点表示开始进入稀疏区域。拐点附近的距离可用来选择 DBSCAN 的 eps,例如 0.30 km。
KNN 不会选出 DBSCAN 核心点,也不会把已经算好的距离直接传给 DBSCAN;它只给参数选择提供参考,DBSCAN 会重新计算邻域。
DBSCAN 的作用
默认配置为半径 eps=0.30 km、最少样本 min_samples=200。DBSCAN 将结束位置分为:
- 核心点:自身 300 米内至少有 200 个点,代表稳定高密度停车热点内部。
- 边界点:自身不够 200 个点,但处于某核心点 300 米邻域中,代表热点外围。
- 噪声点:自身不够密集,也不靠近任何核心点,标签为
-1。
边界点不等于"300 米内不足 200 点";它还必须靠近一个核心点。噪声点通常不适合建设固定大型停车点,但可能需要回收或调度。
KMeans 的作用
KMeans 需要预先指定区域数,例如 70 个。它不断调整 70 个中心,使结束位置尽量靠近某个中心,用于获得粗略停车区域。经纬度会先临时换算成公里平面坐标,避免把"度"直接当距离。KMeans 的边界点概念与 DBSCAN 边界点不是同一个概念。
如何运行分析
在项目根目录运行:
powershell
cd E:\program\mobike-master
pip install numpy pandas matplotlib scikit-learn
python .\mobikeAnalyzier_optimized.py --input .\data\mobike_shanghai_sample_updated.csv --output-dir .\data
成功后可看到类似摘要:有效结束位置数、KNN 邻居数、DBSCAN 热点数量、噪声点数量和 Davies-Bouldin 评分。评分一般越低越好,脚本已经排除 -1 噪声后才计算评分。
可视化大屏部署
先成功运行分析脚本。然后把 mobike_dashboard.py 放在 dataVisiable/,把 mobike_dashboard.html 放在 dataVisiable/templates/。
powershell
cd E:\program\mobike-master\dataVisiable
pip install flask pandas numpy
python .\mobike_dashboard.py
浏览器访问 http://127.0.0.1:5000/mobike-dashboard。
大屏读取增强订单、DBSCAN 位置/规模和 KMeans 中心,展示订单量、平均时长和距离、高峰占比、24 小时分布、时长/距离直方图、结束位置、停车中心与热点排行。直方图中的 P99 指第 99 百分位:保留所有数据,但将最极端的 1% 压入最后一档,避免极端值拉伸坐标轴。
旧版 dataVisiable.py 有未使用的 MySQL 连接;没有本地数据库时会启动失败。新的大屏只读取 CSV,不需要 MySQL,也不应在源代码中写死密码。
常见问题
ModuleNotFoundError:安装缺失库,例如 pip install numpy pandas matplotlib scikit-learn flask。
assignment destination is read-only:在坐标转换处使用 to_numpy(dtype=float, copy=True);copy=True 创建可修改数组。
大屏找不到 CSV:先确认分析脚本成功运行,且输出目录中的增强数据、DBSCAN 位置和聚类统计文件存在。
如何走到"投放多少车"
需要将起点和终点归入同一套区域,并按时间段统计:
净缺车量 = 开始订单数 - 结束订单数
净缺车量大于零,代表车辆被骑走更多,可能需要补车;小于零代表车辆流入更多,可能积压,需要调走部分。早高峰、晚高峰要分开统计,否则会掩盖住宅区与办公区之间的潮汐流动。
若已知可调度车辆总数,可按各区域的正净缺车量分配补车权重;但最终数量还必须考虑实时库存、停车容量、禁停区、天气、活动和调度成本。
最终理想产物是一张运营表:时间段、区域、当前库存、预测需求、净缺车量、建议补/收车数和优先级。这才是从聚类热点分析走向精细化投放调度的完整闭环。
mobikeAnalyzier_optimized.py 逐行注释
以下行号对应你目前 E:\program\mobike-master\mobikeAnalyzier_optimized.py 的版本。括号、逗号、空行和函数结束行不单独解释;其余每一条实际执行、配置或定义的语句均按其用途说明。
基础设置(1--36)
- 1--6 :文件说明;告诉后来阅读的人脚本用于骑行与停车位置分析,且可用
--help查看参数。 - 8:允许较新的类型写法,避免 Python 在读取类型标注时产生兼容问题。
- 10:导入命令行参数工具;因此输入路径和聚类参数不用写死在代码中。
- 11:导入路径工具;用它拼接文件路径,不受 Windows/Linux 斜杠差异影响。
- 12、16:导入画图工具;用于生成 KNN 和 DBSCAN 的 PNG 图。
- 15:使用无窗口绘图模式;服务器或终端环境也能保存图,不会卡在弹出的绘图窗口。
- 17:导入 NumPy;负责快速进行大量数字和三角函数计算。
- 18:导入 Pandas;负责把 CSV 当作表格读取、清洗、分组和导出。
- 19:导入两种聚类方法;KMeans 用于粗分区域,DBSCAN 用于找真正密集热点。
- 20:导入聚类评分方法;用一个数字辅助判断热点划分是否清晰。
- 21:导入最近邻搜索;避免自己写很慢的两层循环找附近点。
- 24:定义地球半径;经纬度转公里时必须有它。
- 25:定义上海中心坐标;用于计算订单起点离市中心多远。
- 26--36:定义必需列名;读入数据后先检查,避免缺列还继续分析而得到错误结果。
可调整参数(39--69)
- 39--40:定义参数读取函数,并创建命令行参数解析器。
- 41--46 :
--input指定原始订单 CSV;默认使用原项目的数据路径。 - 47--52 :
--output-dir指定所有结果 CSV、PNG 的存放目录。 - 53:设置 KNN 的第 200 个邻居;用于画出挑选 DBSCAN 半径的参考图。
- 54:设置 KMeans 暂定分成 70 片停车区域;可按城市大小调整。
- 55:KMeans 尝试 10 次并选较好的结果;降低一次随机初始位置造成的偶然性。
- 56:DBSCAN 的邻居半径设为 0.30 km,即 300 米。
- 57:DBSCAN 至少 200 个点才认定为热点;过滤偶然停放的零散位置。
- 58--66:边界点距离中心约 0.30 km;保留旧脚本"找区域边缘"的分析意图,但单位改为公里。
- 67:边界允许 ±0.01 km(10 米)误差;现实数据不可能恰好落在同一圆周上。
- 68:固定随机种子;多次运行产生相同 KMeans 结果,便于对比。
- 69:返回用户真正传入的参数。
实际地理距离(72--91)
- 72--77:定义距离函数,接受两组经纬度;既能算一条订单,也能一次算整列订单。
- 78:说明函数返回公里,而不是经纬度的"度"。
- 81--84:把经纬度转换为弧度;三角函数和球面距离公式都要求弧度。
- 85--86:计算两点经度、纬度的差。
- 87--90:使用 Haversine 球面距离公式;比把经纬度当平面坐标更符合实际地球距离。
- 91 :将角度距离换算为公里;
clip防止极小的浮点误差造成开方异常。
读取与清洗(94--113)
- 94:定义读取和检查原始 CSV 的函数。
- 95--96:文件不存在就立刻说明准确路径并停止;避免后续报难懂的错误。
- 97:读取 CSV 成表格。
- 98--100:检查必需列;缺任何一列都不能完成本分析。
- 101--103 :订单、车辆、用户 ID 改为文本;ID 是标识,不应该参与数值运算,缺失 ID 也不会变成字符串
nan。 - 104--105:把开始和结束时间转为时间类型;无法识别的内容记为空值而不中断整个任务。
- 106--112:把四个坐标列转数值;异常文字会变为空值,防止距离公式出错。
- 113:返回清洗后的原始表。
每笔订单的特征(116--162)
- 116--117:定义特征生成函数;目的为每笔订单补齐分析所需字段。
- 118:复制数据,避免意外改写调用者手里的原表。
- 119:结束时间减开始时间,得到骑行时长。
- 120:把时长统一换成秒,便于正确计算总分钟数。
- 121 :秒除以 60,得到总骑行分钟数
ttl_min。 - 125:负时长通常是坏数据;保留空值,而不是强行拆出错误的小时和分钟。
- 126:把合法时长拆成天、时、分、秒。
- 127--130 :写回旧项目仍会用到的四个时长字段;
Int64支持缺失值。 - 134:从开始时间取星期几;1 是周一、7 是周日。
- 135:从开始时间取 0--23 的小时;本地时间直接反映用户骑行时段。
- 136:保存星期编号。
- 137--139 :周六、周日标记为
weekends,其余为weekdays;缺失时间仍保持缺失。 - 141:旧可视化使用 1--24 小时,因此在内部 0--23 的基础上加 1。
- 142--146:将 7--8 时和 17--20 时标为高峰;其余标为非高峰。
- 148--154:一次性计算所有订单的起终点真实距离并保留三位小数;比逐行调用函数快得多。
- 155--161:一次性计算所有起点距上海中心的距离;用于判断骑行分布是否偏市中心。
- 162:返回增加新字段后的完整表。
导出基础统计(165--189)
- 165--166:定义导出兼容旧可视化的统计文件函数。
- 167:若输出目录不存在则创建;避免保存时失败。
- 168--173 :按总骑行分钟分组并计数,按时间排序,写入
gbtimeListToDF.csv。 - 174--177 :按小时分组并计数,写入
hour_num_df.csv,用于 24 小时柱状图。 - 178--182 :按骑行距离分组并计数,写入
gbDisListToDF.csv,用于距离分布图。 - 184--187:只保留终点坐标完整的记录,并把字段改为更直观的 longitude、latitude。
- 188 :写入
locDisDF.csv,供位置散点图使用。 - 189:返回有效终点位置,后续聚类无需再读一次 CSV。
坐标换算与 KNN 图(192--221)
- 192--193:定义"经纬度转本地公里坐标"函数;KMeans 在公里坐标上计算更合理。
- 194--195:使用全部位置的中位数作为参考原点;中位数不容易被远处异常点拉偏。
- 196--197:定义纬度和经度每一度约等于多少公里;经度比例会随纬度变化。
- 199 :取得可写的坐标数组;
copy=True防止 Pandas 返回只读数组而报错。 - 200--201:减去原点并乘比例,得到东向、北向各多少公里。
- 202:返回公里坐标以及原点,之后可换回经纬度。
- 205--212:执行上述过程的反向换算;将 KMeans 中心重新变成可用于地图的经纬度。
- 215:定义 KNN 距离图保存函数。
- 216:创建画布。
- 217:从小到大画出每个点到第 N 个邻居的距离;曲线拐点是 DBSCAN 半径的参考。
- 218:写清图题和单位,防止图难以理解。
- 219--220:整理图边距并保存 PNG。
- 221:关闭图,避免循环或长程序占用内存。
聚类与热点(224--293)
- 224:定义停车位置聚类流程。
- 225--227:没有有效终点就跳过聚类;空数据无法训练模型。
- 229:记录点数量,便于打印分析结果。
- 230:把终点转为本地公里坐标,供 KMeans 和边界点计算。
- 231:转为纬度、经度弧度,供 Haversine KNN 与 DBSCAN 使用;顺序必须是纬度在前。
- 232--233:保证邻居数、最小样本数不超过实际数据量,也不能小于 1。
- 236--237:建立 Haversine 最近邻模型并查询每个点的邻居距离。
- 238:取每个点到第 N 个邻居的距离并换成公里。
- 239:保存 KNN 拐点参考图。
- 241:保证 KMeans 类数不超过数据量。
- 242--246:按固定随机种子训练 KMeans,得到若干粗略停车区域。
- 247:取出每个区域的中心位置(公里坐标)。
- 252--253:找每个结束位置离最近中心有多远;代替原来"每个中心对每个点"很慢的双层循环。
- 254:距离中心约 300 米的点标为边界点。
- 255--257:去重后写出边界点 CSV。
- 258--262 :中心坐标转回经纬度,并附上每个中心包含多少点,写入
kmeans_centers.csv。 - 264--269:训练 DBSCAN;半径转换成弧度,使用 Haversine 距离,识别高密度停车热点。
- 270 :取得每个点的聚类编号;
-1表示噪声或分散位置。 - 271--272:排除噪声后计算实际识别出的热点数量。
- 273--275:至少存在两个热点时才计算 Davies--Bouldin 分数;否则这个分数没有意义。
- 276--279 :统计每个热点包含多少点,输出
dbscan_cluster_counts.csv。 - 280 :把每个结束坐标及其 DBSCAN 编号写入
dbscan_locations.csv。 - 282--288:画出不同聚类颜色的散点图并保存;用于肉眼检查热点位置是否合理。
- 290--293:在终端打印最终摘要:点数、KNN 设置、热点数、噪声数与评分。
主流程(296--312)
- 296:定义总调度函数。
- 297:读取启动命令传入的参数。
- 298--299:检查距离参数是否合法;DBSCAN 半径不能是 0 或负数。
- 300:读取并清洗原始订单数据。
- 301:统计开始或结束时间无法识别的订单数。
- 302:统计结束早于开始的异常订单数。
- 303--304:若有异常数据就提示用户;程序仍可分析其他正常数据。
- 305:计算时长、距离、日期、高峰等特征。
- 306:生成旧可视化需要的基础 CSV,并取得终点位置。
- 307:保存包含所有新增字段的完整数据表。
- 308:执行 KNN、KMeans、DBSCAN 的位置热点分析。
- 311--312 :只有直接运行该文件时才调用
main();被别的 Python 文件导入时不会自动开始分析。