ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python数据分析实战:运输车辆驾驶行为分析与风险司机识别

Python数据分析实战:运输车辆驾驶行为分析与风险司机识别 简介这份PDF面向具备Python基础、希望切入交通与物流数据分析场景的学习者以运输车辆驾驶行为分析为主线串联数据采集、预处理、特征工程、统计分析与可视化全流程。内容围绕GPS定位、速度、加速度及急加速急减速事件等监控数据展开并引入气象与道路状况等外部数据源讲解缺失值处理、异常值识别、时间戳转换以及急加速急减速次数、每公里事件频次、速度与加速度标准差等特征构建方法最后用Pandas描述性统计、Matplotlib与Seaborn分布图、箱线图和相关性热力图呈现驾驶行为特征。资源包共1个PDF文件约126KB篇幅紧凑、代码示例完整适合作为课程案例或项目实战参考。目前已有460人学习可帮助读者掌握从原始车辆数据到安全驾驶评估的完整分析思路并迁移至路线优化、驾驶员培训等实际场景。1. 运输车辆驾驶行为分析一份能直接跑通的 Python 实战教程手上拿到一份运输车辆的 GPS 轨迹数据领导要你三天内出一份驾驶行为分析报告识别哪些司机急加速急减速频繁、哪些路段超速高发——这是很多做 Python 数据分析的同行都会遇到的场景。这份《Python数据分析实战运输车辆驾驶行为分析》案例教程就是围绕这个真实需求展开的从 CSV 原始数据读入到缺失值清洗、特征工程、描述性统计、可视化再到相关性分析和风险司机识别整条链路都有可复制的代码。它适合两类人一是刚学完 Pandas 基础、想找一个完整项目练手的入门者二是手头正好有车辆监控数据、需要快速搭出分析框架的从业者。教程用的是 NumPy、Pandas、Matplotlib、Seaborn 这套最通用的技术栈不依赖冷门库Jupyter Notebook 里逐段跑就能出图。下面我按自己复现这份教程的节奏把关键步骤、参数含义和踩过的坑拆开讲。2. 数据预处理从原始 GPS 表到可分析特征表2.1 先搞清楚字段结构再动手拿到vehicle_data.csv别急着read_csv就往下跑先确认字段。这份教程假设的字段包括车辆 ID、时间戳、经纬度、速度、加速度这是运输企业监控系统最典型的导出格式。但实际拿到的表往往多出几十列方向角、点火状态、里程表读数等也可能少列。我一般先做一次字段体检import pandas as pd data pd.read_csv(vehicle_data.csv) print(data.shape) # 行数、列数先判断数据量级 print(data.dtypes) # 每列类型重点看 timestamp 是不是 object print(data.isnull().sum()) # 每列缺失值数量决定清洗策略 print(data.head(3)) # 看真实值长什么样shape用来判断数据规模几万行和几百万行的处理策略完全不同dtypes里如果timestamp是object类型说明读进来是字符串后面必须转isnull().sum()直接告诉你哪几列脏得最厉害。这一步花两分钟能省掉后面半小时的报错排查。2.2 缺失值、异常值和时间戳三件事教程里的清洗代码很精简但每一行都有讲究# 缺失值直接删 data.dropna(inplaceTrue) # 时间戳转 datetime data[timestamp] pd.to_datetime(data[timestamp]) # 异常值速度超过 200km/h 视为异常 data data[data[speed] 200]dropna()默认删除任何含缺失值的行简单粗暴。但如果你的数据缺失集中在某一列比如加速度列大面积缺失全删会损失大量样本这时应该改成按列填充或只删关键列缺失的行。pd.to_datetime不加format参数时靠自动推断遇到2024/01/05 08:30和2024-01-05 08:30:00混用会报错稳妥做法是显式指定format%Y-%m-%d %H:%M:%S。速度阈值 200km/h 是教程给的假设值实际运输车辆货车、客车限速普遍在 100km/h 以内我一般会把阈值压到 120超过的基本是 GPS 漂移或设备故障属于该删的噪声。注意dropna(inplaceTrue)会直接修改原 DataFrame如果你后面还想对比清洗前后的样本量先备份一份data_raw data.copy()。2.3 特征工程急加速急减速怎么算才靠谱这是整份教程最核心的一段也是坑最多的地方。教程用加速度的一阶差分来判断急加速急减速data[acceleration_change] data[acceleration].diff() data[acceleration_change_abs] data[acceleration_change].abs() threshold 2 data[hard_acceleration] (data[acceleration_change_abs] threshold) (data[acceleration] 0) data[hard_deceleration] (data[acceleration_change_abs] threshold) (data[acceleration] 0)diff()算的是相邻两行的加速度差值threshold 2表示加速度在相邻采样点间跳变超过 2 m/s² 就算一次急操作。这个阈值不是拍脑袋来的——正常驾驶的加速度变化平缓急踩油门或急刹车时加速度会在 12 秒内突变。但这里有个隐藏前提数据必须是按车辆、按时间排好序的。如果原始数据是多辆车混在一起且没排序diff()会把 A 车最后一行和 B 车第一行相减算出完全错误的跳变。正确做法是先data.sort_values([vehicle_id, timestamp], inplaceTrue)再算差分。每公里急加速次数的计算教程写得更绕data[distance] data.groupby(vehicle_id)[speed].cumsum() / 3.6 data[distance_diff] data[distance].diff() data[hard_acceleration_per_km] ( data[hard_acceleration].groupby(data[vehicle_id].cumsum()).cumsum() / data[distance_diff] )speed.cumsum() / 3.6是把速度累加再换算成米本质是用「速度×采样间隔」近似积分求里程前提是采样间隔均匀比如固定 1 秒一个点。如果采样间隔不固定这个近似会失真得改成(speed / 3.6 * time_diff).cumsum()。groupby(data[vehicle_id].cumsum())这个写法有点绕它利用车辆 ID 变化时 cumsum 跳变的特性来分组效果上等价于按车辆分组做累积。分母distance_diff是相邻两点的距离增量做除法时如果某两点距离为 0车辆静止会得到inf需要额外处理data[hard_acceleration_per_km] data[hard_acceleration_per_km].replace([float(inf), -float(inf)], 0)这一步不做后面画箱线图时会出现被inf拉爆的坐标轴图基本没法看。3. 描述性统计与可视化让数据开口说话3.1 describe() 之外你该看什么教程用data.describe()做描述性统计输出均值、标准差、四分位数等。这个函数对数值列一视同仁但分析驾驶行为时真正该盯的是几个关键指标指标关注点异常信号speed 均值整体驾驶速度水平均值过高说明超速普遍speed 标准差速度波动程度标准差大说明驾驶不稳hard_acceleration_per_km每公里急加速次数明显高于车队均值即高风险acceleration 极值加速度上下限超出 ±5 m/s² 多为设备噪声describe()默认只统计数值列如果vehicle_id被读成了数值也会混进去记得先data[vehicle_id] data[vehicle_id].astype(str)。另外describe()的百分位数默认是 25/50/75分析急加速这种长尾分布时建议手动加percentiles[.5, .9, .95, .99]看 95 分位和 99 分位才能抓住极端司机。3.2 三张图定位问题司机教程给了速度分布直方图、按车辆的急加速箱线图、单车加速度时序图这三张图各有用途顺序也有讲究。速度分布用sns.histplot(data[speed], kdeTrue, bins30)bins30是分箱数数据量大时可以加到 50 让分布更细。kdeTrue叠加核密度曲线能直观看出速度是单峰还是双峰——双峰往往意味着城市道路和高速两种工况混在一起。import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(10, 6)) sns.histplot(data[speed], kdeTrue, bins30) plt.title(Speed Distribution) plt.xlabel(Speed (km/h)) plt.ylabel(Frequency) plt.show()箱线图按车辆 ID 分组看急加速频率是识别高风险司机最直接的手段plt.figure(figsize(12, 6)) sns.boxplot(xvehicle_id, yhard_acceleration_per_km, datadata) plt.xticks(rotation45) plt.show()rotation45是防止车辆 ID 标签重叠车辆多的时候这个参数必加。箱线图里箱体越高、须越长的车辆驾驶行为越激进。但要注意如果某辆车样本量很少比如只跑了 10 公里它的统计值波动极大不能直接和其他跑了几千公里的车比最好先按里程过滤掉样本不足的车辆。单车加速度时序图用来下钻vehicle_id data[vehicle_id].iloc[0] vehicle_data data[data[vehicle_id] vehicle_id] plt.figure(figsize(12, 6)) plt.plot(vehicle_data[timestamp], vehicle_data[acceleration], labelAcceleration) plt.grid(True) plt.legend() plt.show()data[vehicle_id].iloc[0]取第一辆车实际分析时应该换成你从箱线图里挑出的高风险车辆 ID。时序图能看出急加速急减速是集中在某段时间比如某个路段还是全程散布这直接决定了后续是培训司机还是排查路段。3.3 相关性分析别只看数字教程用data[[speed,acceleration,hard_acceleration_per_km,hard_deceleration_per_km]].corr()算相关矩阵再用sns.heatmap画出来。这里有个常见误读相关系数高不代表因果。比如急加速和急减速的相关性通常很高不是因为急加速导致急减速而是因为两者都反映「驾驶激进」这个共同因子。correlation_matrix data[[speed, acceleration, hard_acceleration_per_km, hard_deceleration_per_km]].corr() plt.figure(figsize(8, 6)) sns.heatmap(correlation_matrix, annotTrue, cmapcoolwarm) plt.show()annotTrue把数值标在格子里cmapcoolwarm用冷暖色区分正负相关。看热力图时重点找绝对值大于 0.5 的格子低于 0.3 的基本可以认为没关系。另外corr()默认用皮尔逊系数只对线性关系敏感如果怀疑是非线性关系换成methodspearman更稳。4. 避坑与排查复现这份教程时最容易翻车的五处4.1 时间戳解析报错或时区错乱现象pd.to_datetime抛ValueError: Unknown string format或者转出来的时间比实际早/晚 8 小时。原因原始时间戳格式不统一或带时区信息被自动转换。解决先print(data[timestamp].head(10))看真实格式用format参数显式指定带时区的用utcTrue统一后再dt.tz_convert(Asia/Shanghai)。4.2 diff() 跨车辆计算导致特征全错现象急加速次数异常偏高几乎每行都被标记。原因数据未按车辆和时间排序diff()在车辆边界处算出巨大跳变。解决特征工程前强制data.sort_values([vehicle_id, timestamp], inplaceTrue)并reset_index(dropTrue)。4.3 每公里指标出现 inf 或 NaN现象箱线图纵轴被拉到几万或hard_acceleration_per_km全是 NaN。原因车辆静止时distance_diff为 0除法产生inf或某车辆只有一行数据diff()结果为 NaN。解决除法后replace([inf, -inf], 0)并过滤掉行数少于阈值的车辆。4.4 中文标签显示成方块现象图表标题、轴标签里的中文变成方框。原因Matplotlib 默认字体不含中文。解决plt.rcParams[font.sans-serif] [SimHei]和plt.rcParams[axes.unicode_minus] FalseMac 系统换成[Arial Unicode MS]。4.5 内存爆掉现象几百万行数据跑groupby时卡死或MemoryError。原因全量数据一次性载入并做多重 groupby。解决读数据时用dtype指定列类型vehicle_id用 category速度用 float32或分车辆 chunk 处理后再合并。5. 从分析到落地把风险司机名单跑出来分析做完最终要交付的是一份能直接给车队管理用的高风险司机清单。这一步教程没展开但实际项目里最关键。我的做法是在特征工程之后加一段聚合打分# 按车辆聚合关键风险指标 risk data.groupby(vehicle_id).agg( total_distance_km(distance_diff, sum), hard_acc_per_km(hard_acceleration_per_km, mean), hard_dec_per_km(hard_deceleration_per_km, mean), speed_std(speed, std), max_speed(speed, max) ).reset_index() # 过滤样本不足的车辆里程少于 50 公里不参与排名 risk risk[risk[total_distance_km] 50000] # 简单加权打分权重按业务重要性调 risk[risk_score] ( risk[hard_acc_per_km] * 0.4 risk[hard_dec_per_km] * 0.4 risk[speed_std] * 0.2 ) # 按分数降序取前 20 名 risk_top risk.sort_values(risk_score, ascendingFalse).head(20) print(risk_top[[vehicle_id, risk_score, hard_acc_per_km, max_speed]])total_distance_km这里单位是米所以过滤阈值写 50000 对应 50 公里。权重 0.4/0.4/0.2 是我根据「急操作比速度波动更能反映驾驶风险」这个经验设的实际项目里应该和车队安全员对齐。risk_score只是排序依据不是绝对风险值别拿去和别的车队横向比。验证这套流程是否跑通我一般做两个检查一是随机抽一辆高风险车把它的加速度时序图单独画出来肉眼确认急加速标记点确实落在曲线尖峰上二是把threshold从 2 调到 1.5 和 2.5 各跑一遍看高风险名单是否稳定——如果名单剧烈变化说明阈值太敏感需要结合业务重新标定。从那以后我每次做驾驶行为分析都强制先跑一遍「排序 去重 时间戳校验」三件套再进特征工程这三步不做后面所有指标都是空中楼阁。希望这份拆解能帮你少走几个弯路把这份教程真正跑成自己项目里的东西。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进