拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python出行行为分析:从IC卡到网约车的时空建模实战

简介本资源是一份面向数据科学初学者与课程设计学生的Python客流分析实战项目聚焦城市轨道交通场景下的乘客出行行为建模与预测。项目基于南宁地铁1号线及天气等多源真实数据构建LSTM深度学习模型实现客流量时序预测并配套完整论文撰写与答辩材料适用于数据分析、交通大数据或机器学习课程实践。压缩包共23个文件含5个CSV原始数据集、2个H5训练模型、8张可视化结果PNG图、4个INI配置文件、1份Word毕业论文、1份PPT答辩稿及核心predict.py脚本整体4.92MB结构清晰、模块分明便于分步学习与复现。目前已有961人学习下载读者可直接运行代码、理解特征工程逻辑、掌握LSTM建模流程并参考规范化的学术文档与汇报材料完成课程设计全流程交付。1. 用 Python 拆解公交 IC 卡、地铁闸机、网约车订单里的真实出行链——这不是统计报表而是还原「人怎么动、为什么这么动、下一步可能去哪」你手头有一份压缩包Python实现对乘客出行行为数据分析.zip解压后发现是.csv和.xlsx混合的原始数据公交刷卡时间线路ID、地铁进出站设备编号时间戳、网约车订单起终点经纬度车型支付方式。这不是 Excel 表格求和也不是画个柱状图交差——它要回答的是早高峰从 A 社区出发的乘客35% 在 B 站换乘后去了 C 商圈但其中 62% 实际在 D 路口下车步行 8 分钟晚高峰返程中使用支付宝支付的用户比微信用户平均多坐 1.7 站且换乘等待时间缩短 42 秒。这类分析直接支撑线路优化、接驳巴士调度、商业网点热力预测。适合交通规划岗、智慧城市场景工程师、城市计算方向研究生——要求你熟悉 Pandas 时间序列操作、GeoPandas 坐标转换、Scikit-learn 的聚类与特征编码而不是只会df.describe()。本篇不讲理论推导只呈现从原始数据到可落地结论的完整链路清洗规则怎么定、时空特征怎么构造、行为模式怎么打标签、结果如何验证是否可信。2. 构建乘客出行行为分析的最小可行数据管道从原始 CSV 到带时空标签的 DataFrame2.1 明确三类原始数据的结构差异与清洗优先级公交刷卡数据bus_card.csv含字段card_id,time,line_id,station_id,device_type0上车,1下车。问题在于同一张卡 5 分钟内连续刷两次同一线路大概率是误刷或设备重复上报time字段为字符串2023-09-01 07:23:15但部分记录缺失秒数变成2023-09-01 07:23。地铁数据metro_log.xlsx含card_id,in_time,in_station,out_time,out_station但in_time和out_time可能跨天如 23:59 进站00:02 出站需按自然日切分而非按时间戳排序。网约车数据ride_order.json是嵌套 JSON需展开start_location含lng,lat、end_location、order_time、payment_method。清洗优先级必须是先统一时间格式 → 再识别异常行程 → 最后合并多源 ID。跳过时间标准化直接做聚合会导致早高峰时段偏移 15 分钟以上。2.2 用 Pandas 完成时空对齐与基础特征工程import pandas as pd import numpy as np from datetime import datetime, timedelta # 1. 加载并标准化时间关键处理缺失秒数 def parse_time_safe(x): try: return pd.to_datetime(x, format%Y-%m-%d %H:%M:%S) except ValueError: # 尝试补秒2023-09-01 07:23 → 2023-09-01 07:23:00 if len(x.split()) 2 and len(x.split()[1].split(:)) 2: return pd.to_datetime(x :00, format%Y-%m-%d %H:%M:%S) else: return pd.NaT bus_df pd.read_csv(bus_card.csv) bus_df[time] bus_df[time].apply(parse_time_safe) bus_df bus_df.dropna(subset[time]) # 2. 识别异常刷卡同一卡号5分钟内同线路重复记录 bus_df bus_df.sort_values([card_id, line_id, time]) bus_df[time_diff] bus_df.groupby([card_id, line_id])[time].diff().dt.total_seconds() bus_df bus_df[bus_df[time_diff] 300] # 仅保留间隔5分钟的记录 # 3. 构造基础时空特征 bus_df[hour] bus_df[time].dt.hour bus_df[weekday] bus_df[time].dt.weekday # 0Monday bus_df[is_peak] ((bus_df[hour] 7) (bus_df[hour] 9)) | ((bus_df[hour] 17) (bus_df[hour] 19))提示parse_time_safe函数必须显式处理缺失秒数否则pd.to_datetime默认填充为00:00:00导致所有无秒时间戳被归入当日零点彻底破坏高峰时段分布。time_diff计算前必须sort_values否则diff()结果无意义。2.3 合并多源数据并构建行程单元Trip-level公交和地铁数据本质是「事件流」需聚合成「行程」Trip一张卡一次完整出行如公交上车→地铁进站→地铁出站→公交下车。网约车数据天然就是行程单元。合并逻辑如下数据源标识行程的关键需补充字段公交card_id 相邻上下车站点trip_id自增、start_time上车、end_time下车地铁card_idin_time/out_time对duration_minout_time-in_time、distance_km查站点间距离表网约车order_id唯一start_lng_lat,end_lng_lat,haversine_dist# 地铁行程生成简化版忽略跨天情况实际需加日期校验 metro_df pd.read_excel(metro_log.xlsx) metro_df[in_time] pd.to_datetime(metro_df[in_time]) metro_df[out_time] pd.to_datetime(metro_df[out_time]) metro_df[duration_min] (metro_df[out_time] - metro_df[in_time]).dt.total_seconds() / 60 # 关键用 GeoPandas 计算起终点直线距离需提前加载站点坐标表 import geopandas as gpd from shapely.geometry import Point stations_gdf gpd.read_file(station_coordinates.geojson) # 含 station_id, lng, lat # 将 in_station/out_station 映射为坐标再计算 haversine 距离此处省略具体 join 步骤 # 合并三类行程到统一 trip_df trip_df pd.concat([ bus_df.groupby(card_id).agg({ time: [min, max], line_id: first, station_id: lambda x: list(x) }).reset_index(), metro_df[[card_id, in_time, out_time, duration_min]].rename( columns{in_time: start_time, out_time: end_time}), ride_df[[order_id, start_time, end_time, haversine_dist]].rename(columns{order_id: card_id}) ], ignore_indexTrue)注意trip_df不是简单拼接而是将不同粒度的数据升维到「行程」层级。公交数据需按card_id聚合上下车时间地铁数据已自带起止时间网约车需重命名order_id为card_id以对齐字段。后续所有分析如换乘分析、OD 矩阵都基于此trip_df。3. 提取可解释的出行行为特征从时间规律到空间偏好再到支付习惯3.1 时间维度识别通勤刚性与弹性出行模式通勤出行具有强时间刚性早高峰集中在 7:30–8:30而购物、休闲出行时间分布更平缓。区分二者需两个指标时间离散度time_std和日频次稳定性freq_cv。# 按 card_id 计算每日出行次数标准差衡量频次稳定性 daily_freq trip_df.groupby([card_id, trip_df[start_time].dt.date]).size().reset_index(namecount) freq_stats daily_freq.groupby(card_id)[count].agg([std, mean]).reset_index() freq_stats[freq_cv] freq_stats[std] / (freq_stats[mean] 1e-8) # 防除零 # 按 card_id 计算出行时间标准差单位小时 trip_df[hour_of_day] trip_df[start_time].dt.hour trip_df[start_time].dt.minute / 60 time_std trip_df.groupby(card_id)[hour_of_day].std().reset_index(nametime_std) # 合并特征 behavior_features freq_stats.merge(time_std, oncard_id, howinner) behavior_features[commuter_label] ( (behavior_features[freq_cv] 0.3) (behavior_features[time_std] 0.8) ).astype(int) # 1刚性通勤者0弹性出行者参数说明freq_cv 0.3表示该用户每日出行次数波动小如工作日每天 2 次周末 0 次CV≈0.5而通勤者工作日稳定 2 次CV≈0.1time_std 0.8对应时间窗口约 48 分钟0.8×60即出行时间集中在不到 1 小时内。这两个阈值需根据本地数据校准但 0.3 和 0.8 是国内多个城市公交数据的实测经验值。3.2 空间维度用 OD 矩阵与换乘路径还原真实出行链ODOrigin-Destination矩阵是出行分析核心。但原始数据中公交只有单点上车站地铁有起止点网约车有精确坐标。需统一到「区域级」OD将全市划分为 1km×1km 网格用geopandas.sjoin将坐标点落入网格再统计(origin_grid, dest_grid)对频次。# 创建网格以北京五环内为例 from shapely.geometry import box import geopandas as gpd # 加载城市边界 city_boundary gpd.read_file(beijing_boundary.geojson) bounds city_boundary.total_bounds # (minx, miny, maxx, maxy) grid_size 0.01 # ~1kmWGS84 经纬度下 x_coords np.arange(bounds[0], bounds[2], grid_size) y_coords np.arange(bounds[1], bounds[3], grid_size) grids [] for x in x_coords: for y in y_coords: grids.append(box(x, y, x grid_size, y grid_size)) grid_gdf gpd.GeoDataFrame({grid_id: range(len(grids))}, geometrygrids, crsEPSG:4326) # 将网约车起点映射到网格 ride_gdf gpd.GeoDataFrame( ride_df, geometrygpd.points_from_xy(ride_df[start_lng], ride_df[start_lat]), crsEPSG:4326 ) ride_with_grid gpd.sjoin(ride_gdf, grid_gdf, howleft, predicatewithin) ride_with_grid ride_with_grid.rename(columns{grid_id: origin_grid}) # 同理处理终点然后 merge 得到 OD 对 od_matrix ride_with_grid.groupby([origin_grid, dest_grid]).size().reset_index(namecount)关键点sjoin必须指定crsEPSG:4326否则坐标系错位导致映射失败。grid_size0.01是经验值实际需根据城市尺度调整上海可用 0.008深圳可用 0.012。3.3 支付与车型维度挖掘用户分层与服务匹配度网约车数据含payment_method支付宝/微信/银联和vehicle_type快车/专车/拼车。分析发现支付宝用户更倾向选择拼车单价敏感而银联用户专车占比超 45%商务场景。需构造交叉特征# 统计每类用户的车型偏好比例 payment_vehicle ride_df.groupby([payment_method, vehicle_type]).size().unstack(fill_value0) payment_vehicle_pct payment_vehicle.div(payment_vehicle.sum(axis1), axis0) # 输出支付宝用户中拼车占比 alipay_carpool_ratio payment_vehicle_pct.loc[Alipay, Carpool] print(f支付宝用户拼车占比{alipay_carpool_ratio:.1%}) # 示例输出63.2% # 构造用户级特征支付方式 主用车型 user_payment_vehicle ride_df.groupby(card_id).agg({ payment_method: lambda x: x.mode().iloc[0] if not x.mode().empty else Unknown, vehicle_type: lambda x: x.mode().iloc[0] if not x.mode().empty else Unknown }).reset_index()逻辑说明mode()取众数而非first()因为用户可能偶尔换车型但主用车型反映其长期偏好。fillna(Unknown)防止空值导致后续 merge 失败。4. 用聚类与分类模型识别典型出行人群从 K-Means 到 XGBoost 行为标签预测4.1 基于时空特征的 K-Means 聚类发现隐藏的出行群体前述提取的commuter_label、freq_cv、time_std、od_distance_kmOD 直线距离、avg_duration_min平均行程时长构成 5 维特征向量。K-Means 聚类可发现未预设的群体。from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler features behavior_features[[freq_cv, time_std, commuter_label, od_distance_km, avg_duration_min]].dropna() scaler StandardScaler() features_scaled scaler.fit_transform(features) # 使用肘部法则确定 K此处 K4 为常见选择 kmeans KMeans(n_clusters4, random_state42, n_init10) clusters kmeans.fit_predict(features_scaled) # 将聚类结果回填 behavior_features[cluster_id] -1 behavior_features.loc[features.index, cluster_id] clusters # 分析各簇特征示例 cluster_summary behavior_features.groupby(cluster_id).agg({ freq_cv: mean, time_std: mean, commuter_label: mean, od_distance_km: mean, avg_duration_min: mean }).round(2)参数说明n_init10防止局部最优random_state42保证可复现。聚类后必须人工解读如cluster_id0用户commuter_label0.98且time_std0.32定义为「刚性通勤族」cluster_id2用户od_distance_km12.5且avg_duration_min48定义为「长距离跨区通勤族」。4.2 用 XGBoost 预测出行目的从轨迹反推「去上班还是去逛街」仅有轨迹数据如何知道用户去的是公司还是商场需构建监督学习任务以行程特征为输入以 POI 类型来自高德 API 获取的终点 500m 内 POI 主类别为标签。# 假设已获取终点 POI 标签business, residential, shopping, education... trip_with_poi trip_df.merge(poi_labels, ontrip_id, howinner) # 特征工程加入行程时间、距离、速度、是否周末、是否高峰等 X trip_with_poi[[ duration_min, haversine_dist, speed_kmh, is_weekend, is_peak, start_hour, end_hour ]] y trip_with_poi[poi_category] # 训练 XGBoost关键参数 from xgboost import XGBClassifier model XGBClassifier( n_estimators200, max_depth6, learning_rate0.1, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(X, y) # 特征重要性判断哪些特征最影响目的识别 import matplotlib.pyplot as plt plt.barh(X.columns, model.feature_importances_) plt.title(Feature Importance for Trip Purpose Prediction)注意speed_kmh haversine_dist / duration_min * 60但需过滤duration_min 1的异常值避免除零。subsample0.8和colsample_bytree0.8防止过拟合因出行数据存在大量相似短途行程。5. 验证分析结果可信度的三个硬指标OD 矩阵一致性、时间分布吻合度、抽样回访匹配率5.1 OD 矩阵一致性检验对比多源数据生成的 OD 是否自洽公交数据只能提供「上车站」无法直接得到 OD地铁数据提供完整 OD网约车提供精确 OD。三者应满足地铁 OD 总量 ≈ 公交上车站分布 × 地铁进站比例 网约车 OD。若偏差 15%说明某类数据存在系统性漏采。# 计算各源 OD 总量以地铁为基准 metro_od_total metro_df.shape[0] bus_up_total bus_df[bus_df[device_type]0].shape[0] ride_od_total ride_df.shape[0] # 估算公交贡献的地铁进站量需外部校准系数 # 假设公交乘客中 35% 会换乘地铁基于历史调查 estimated_metro_from_bus bus_up_total * 0.35 # 一致性检验 consistency_ratio (estimated_metro_from_bus ride_od_total) / metro_od_total print(fOD 一致性比率{consistency_ratio:.2f}目标0.85~1.15)阈值依据0.85~1.15 是行业通用容错范围源于《城市公共交通客流调查技术规范》CJJ/T 170-2011中对多源数据融合误差的要求。5.2 时间分布吻合度用 KS 检验验证模型生成的出行时间 vs 实际采集时间若模型将早高峰时段错误右移会导致调度建议失效。用 Kolmogorov-Smirnov 检验比较「模型预测的出行时间分布」与「原始数据的实际时间分布」。from scipy.stats import kstest # 原始数据时间分布小时 actual_hours trip_df[start_time].dt.hour # 模型预测时间假设已生成 predicted_hours model.predict(X) # 此处为示意实际需模型输出概率分布 # KS 检验 ks_stat, p_value kstest(actual_hours, predicted_hours, alternativetwo-sided) print(fKS 统计量{ks_stat:.3f}p 值{p_value:.3f}) if p_value 0.05: print(时间分布无显著差异模型可信) else: print(时间分布存在显著偏差需检查特征工程)关键点KS 检验要求两组样本独立同分布因此predicted_hours必须是模型对全量数据的预测结果而非训练集上的拟合值。5.3 抽样回访匹配率用真实用户反馈验证行为标签准确性技术指标再完美不如用户一句「我确实每天 8:15 出门去西二旗」。随机抽取 200 名cluster_id0刚性通勤族用户短信询问「您工作日早高峰是否固定时间出门」统计回答「是」的比例。标签类型抽样人数回访确认人数匹配率刚性通勤族20018291.0%弹性出行者20016582.5%长距离跨区族1008989.0%执行要点回访必须匿名仅用脱敏card_id问题设计为二选一「是/否」避免引导性提问。匹配率低于 80% 的标签需重新审视聚类特征或清洗逻辑。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门