Python交通事故数据分析与可视化项目全流程拆解
简介基于Python的中国交通事故数据分析可视化系统毕业设计项目面向计算机相关专业正在完成毕业设计、课程设计或期末大作业的学生也适合需要Python数据分析实战练习的学习者解决从交通事故数据清洗、预处理、特征分析到可视化呈现的完整实现问题可作为同类选题的直接参考范本。压缩包共7个文件整体大小约245KB包含3个Jupyter Notebook承载完整数据分析代码、3个CSV数据文件原始事故数据与预处理后的分析数据和1个HTML可视化页面用于展示最终图表与结论。项目经导师指导并认可评审分为99分代码完整确保可运行目前已有98人学习下载。读者可从中掌握pandas数据处理、事故特征探索与可视化图表设计的整体思路并可复用其中的数据预处理脚本与Notebook代码快速迁移到自己的项目中。1. 先拆个彻底这个 Python 交通事故项目到底值不值得跑做数据分析项目的都知道真正拉开差距的不是画图炫不炫而是你拿到一份原始 CSV 之后敢不敢说自己能把空值、时间格式、重复记录一次性清干净。这份基于 Python 的中国交通事故数据分析可视化系统我拆完之后最大感受是它不是那种网上只放几个图表截图、代码跑起来全是报错的半成品而是把「模拟原始数据 → 清洗预处理 → 探索性分析 → 可视化出图 → 导出 HTML 报告」这条完整链路都给你塞进了一个压缩包里特别适合做 Python 毕业设计、数据分析课程大作业或者想拿真实业务场景练手的人。它在省事的同时也留了几个坑比如假数据口径、中文字体、时间排序这些我后面会一条条讲到。2. 数据底子与选型先弄清楚三个 CSV 和两组 Notebook 的分工2.1 文件清单里每一份是干什么的解压之后你会看到七样东西别急着打开 notebook 开跑先花两分钟搞清楚它们之间的关系。我按自己的习惯把它们分成三层原始层是fake_accident_data.csv它是模拟出来的事故原始记录字段里带省份、时间、天气、事故类型、伤亡人数这些信息用来模拟一张未经处理的事故登记表中间层是preprocessed_accident_data.csv和preprocessed_data.csv这是清洗之后的结果你会发现记录数变了、时间字段变成了标准格式、空值被处理掉了展示层是accident.ipynb、wwa.ipynb和accident.html前两个是完整分析过程最后一个是导出好的可视化报告浏览器打开就能看。主目录里的main是你跑通整个流程图之后生成的中间文件我建议第一次跑先别删后面调试要对照它确认清洗前后发生了哪些变化。2.2 为什么选 Pandas Jupyter Matplotlib 这套组合有人会问都做可视化了为什么不用 Tableau 或者 ECharts 写个大屏这里有个非常实际的选型理由。毕设答辩时老师关心的不是你的大屏转场动画好不好看而是你处理数据的逻辑是否清晰、每一步是不是可解释。Jupyter Notebook 天然适合这种场景因为它把「思考过程」和「结果图表」放在同一个文档里考官翻到哪一格都能看到你的代码和输出。Pandas 负责清洗和聚合这是整个项目最花时间的地方Matplotlib 和 Seaborn 负责出图虽然默认样式朴素但胜在可控性强每个坐标轴、每个标签都能手动调。可视化大屏那套炫酷效果等你答辩完想丰富作品集时再补也不迟。先读一遍数据确认这个数据底子长什么样import pandas as pd acc pd.read_csv(fake_accident_data.csv, encodingutf-8-sig) print(原始形状, acc.shape) print(acc.dtypes) print(前3行预览) print(acc.head(3))这份代码的核心是解决「未知数据的探索」问题。encodingutf-8-sig是我推荐的首选参数因为带sig会比不带更稳能兼容 Excel 导出的 CSV 在首行添加 BOM 头的情况shape帮你拿到行数和列数dtypes看出每个字段的类型——这一步一定要做不然你根本不知道日期到底是不是字符串、死亡人数是不是被读成了浮点。后面所有清洗动作都建立在这三行输出的基础上。2.3 字段设计对分析维度的影响观察完数据类型之后值得把字段挨个在心里过一遍因为这决定了你能做哪些维度的分析。我整理了一份字段用途对照表建议你对照自己的数据处理逻辑确认一遍字段名常见命名类型分析用途datetimeobject → datetime提取月份、小时做时间趋势分析provinceobject省份维度分布横向条形图weatherobject天气与事故类型交叉分析accident_typeobject事故类别计数与占比death_count / injury_countint严重程度对比数值校验road_typeobject道路条件维度字段命名在不同的版本里可能略有差异但核心思路一致时间字段必须转类型省份、天气这类文本字段适合分组计数伤亡数字必须有下限校验。理清这层你就知道acc.ipynb里为什么一会儿 groupby一会儿画热力图了。3. 数据清洗与预处理把假数据变成能说清故事的干净表3.1 清洗前的状态检查先记录脏数据证据很多初学者一上来就dropna()这是最容易翻车的动作。因为你根本不记得原始数据里有多少空值、多少重复行到后期写论文时老师问一句「清洗前后数据量变化是多少、为什么变少两行」你答不上来。我一般会先做一次状态快照import pandas as pd acc pd.read_csv(fake_accident_data.csv, encodingutf-8-sig) # 状态快照用于论文中说明清洗前的情况 snapshot { 原始行数: len(acc), 重复行数: acc.duplicated().sum(), 空值数量: acc.isnull().sum().sum(), 各列空值: acc.isnull().sum().to_dict(), } print(snapshot)这段代码实际上是在做数据审计。duplicated().sum()统计的是重复行的总数isnull().sum().sum()是所有列空值和to_dict()把每列空值数转换成一个可读的字典。建议把输出的数字抄到你的实验记录里后面写论文「数据来源与预处理」那一章时能直接用这也是答辩老师最容易追问的地方。3.2 时间字段转换字符串日期引发的连锁 bug事故数据里最典型的坑是时间列。原始 CSV 里的datetime字段往往长这样2023-05-12 08:30:00看起来没什么问题但它是字符串不是时间类型。字符串状态下你没法dt.hour没法按月份排序画出来的折线图横坐标全是乱的。正确的转换姿势acc[datetime] pd.to_datetime(acc[datetime], errorscoerce) acc[month] acc[datetime].dt.month acc[hour] acc[datetime].dt.hour # 空值检查转换失败的行会被置为 NaT invalid_time acc[datetime].isna().sum() acc acc.dropna(subset[datetime]) print(f时间解析失败{invalid_time} 行已删除)errorscoerce是这里的核心参数它的含义是凡是解析不了的时间值一律转成NaT而不是抛异常中断程序。这样你能拿到一个失败数量invalid_time再做定向删除。dt.month和dt.hour是两个最常用的时间提取操作分别拿到月份和小时下一步做月度趋势、24 小时分布都要靠它们。特别提醒dropna时一定要加subset参数只针对时间列处理别把天气列或省份列的空值一起删了——这两种空的处理逻辑完全不同。3.3 数值字段的边界校验负的死亡人数必须处理模拟数据虽然整体规律但难免会有异常值比如死亡人数为负数、受伤人数远大于总记录数之类的情况。这类字段如果不做边界校验画图时会出现一个离谱的负柱答辩时解释不清。常见做法是先描述再处理# 边界描述 print(死亡人数统计) print(acc[death_count].describe()) # 规则清洗死亡人数和受伤人数不能为负数 acc acc[acc[death_count] 0] acc acc[acc[injury_count] 0] acc acc.reset_index(dropTrue) print(清洗后行数, len(acc))describe()会输出最小值、最大值、均值等统计量建议最先看min这一行一旦是负数就说明数据源有问题。 0的布尔过滤是整段代码的安全底线reset_index(dropTrue)是清理索引的常规操作因为在多次过滤之后索引全乱了不重置会导致后面groupby和画图出现「跳行」的偶然问题。3.4 导出预处理结果给后续可视化提供稳定输入清洗最终的产出不是一堆打印语句而是一份干净的 CSV方便你在acc.ipynb、wwa.ipynb两个 Notebook 之间反复读取不用每次重新执行清洗逻辑acc.to_csv(preprocessed_accident_data.csv, indexFalse, encodingutf-8-sig) print(已输出预处理数据行数, len(acc))indexFalse防止 Pandas 把行号写进 CSV 的第一列encodingutf-8-sig保证 Excel 打开时中文不乱码。从这里开始你的分析工作流就变成了「读取预处理数据 → 聚合 → 可视化」这一行的稳定输出是你所有图表的唯一数据来源。如果后面发现某张图的数据不对先回到这一步重新比对别直接在绘图代码里打补丁。4. 探索性分析与可视化从聚合结果到四张核心图表4.1 省份维度横向条形图展示事故数量分布拿到预处理数据后第一个值得分析的维度是省份。为什么用横向条形图而不是纵向柱状图因为省份名称是中文短的能用 2 个字长的能用 5 个字纵向柱状图的 x 轴标签一密集遮挡问题就会让人头疼。横向条形图能很好地解决这个问题省份名放在 y 轴读起来也符合视觉习惯import pandas as pd import matplotlib.pyplot as plt acc pd.read_csv(preprocessed_accident_data.csv, encodingutf-8-sig) province_cnt acc.groupby(province).size().reset_index(namecounts) province_cnt province_cnt.sort_values(counts, ascendingTrue) fig, ax plt.subplots(figsize(10, 8)) ax.barh(province_cnt[province].tail(10), province_cnt[counts].tail(10)) ax.set_title(事故数量 Top10 省份) ax.set_xlabel(事故数量) ax.set_ylabel(省份) plt.tight_layout() plt.savefig(province_top10.png, dpi200, bbox_inchestight) plt.show()groupby(province).size().reset_index(namecounts)是统计最喜欢用的语法size()表示每个省份的记录数reset_index把分组后的 Series 还原成普通 DataFrame。排序时我特意用ascendingTrue、画图时取tail(10)这样排名第一的省份出现在条形图最上方读图最自然。figsize(10, 8)适合 10 条数据的横向条形图太窄会显得标签拥挤保存参数dpi200够答辩投影用bbox_inchestight可以防止标题被截断。4.2 时间维度月度走势折线图与坐标轴密度控制事故随时间的变化趋势是另一个必做维度。我用月份聚合画折线图效果稳定不需要任何额外库monthly acc.groupby(month).size() fig, ax plt.subplots(figsize(10, 4)) ax.plot(monthly.index, monthly.values, markero, color#C0392B) ax.set_xticks(range(1, 13)) ax.set_xticklabels([f{m}月 for m in range(1, 13)]) ax.set_ylabel(事故数量) ax.set_title(各月份事故数量走势) ax.grid(True, linestyle--, alpha0.3) plt.tight_layout() plt.savefig(monthly_trend.png, dpi200) plt.show()这里有一个容易踩的细节直接用 Pandas 的.plot()也能画但默认 x 轴标签常常是「1 2 3 ... 12」这种干巴巴的数字不专业。set_xticks和set_xticklabels这两行的作用就是显式地指定刻度位置和刻度名称把 1 变 1 月。很多新手画时间图时横坐标密集成一团毛线原因就是忽略了这两个方法。markero是为了在折线上标出数据点论文中用黑白打印也能看清趋势。另外grid我习惯开后加虚线透明样式能让读者更专注看数据。4.3 交叉维度天气与事故类型的热力图单维度图虽然清晰但热力图这种交叉分析才是答辩时的加分项。它能同时体现两个分组变量的关系比如恶劣天气下追尾事故是不是明显增多。Seaborn 的heatmap可以直接吃 Pandas 的透视表import seaborn as sns crosstab pd.crosstab(acc[weather], acc[accident_type]) fig, ax plt.subplots(figsize(8, 6)) sns.heatmap(crosstab, annotTrue, fmtd, cmapYlOrRd, linewidths0.5, linecolorwhite) ax.set_title(天气状况与事故类型交叉分析) ax.set_xlabel(事故类型) ax.set_ylabel(天气状况) plt.tight_layout() plt.savefig(weather_type_heatmap.png, dpi200) plt.show()pd.crosstab是透视表函数它生成行是天气、列是事故类型的计数矩阵。annotTrue表示在格子中显示数字fmtd表示用整数格式显示不然你会看到1.0这种很业余的输出。配色cmapYlOrRd从浅黄到深红深色自然代表高发组合。linewidths0.5用于分隔每个小格子视觉上更加整齐。这个图值得多花点时间观察有时候某个格子的数值远超其他格子那就说明天气和事故类型之间存在明显的相关关系可以直接写进论文的分析结论。4.4 多图组合输出让 Notebook 的报告感更强单张图保存成 PNG 当然没问题但如果能做到在同一个 Notbook 里分区域展示报告感会强很多。我用subplots做一张 1 行 2 列的组合图左边是省份分布、右边是月度趋势让对比更直观fig, axes plt.subplots(1, 2, figsize(14, 5)) # 左边月度趋势 axes[0].plot(monthly.index, monthly.values, markero) axes[0].set_title(月度事故数量) axes[0].set_xticks(range(1, 13)) # 右边天气占比饼图 weather_cnt acc[weather].value_counts() axes[1].pie(weather_cnt.values, labelsweather_cnt.index, autopct%.1f%%, startangle90) axes[1].set_title(天气分布占比) plt.tight_layout() plt.savefig(combined_chart.png, dpi200) plt.show()subplots(1, 2)生成左右两个子图返回的axes是一个包含两个坐标轴对象的数组。value_counts()直接统计天气字段的类别频次是画饼图前最简单高效的聚合工具。autopct%.1f%%在饼图中心显示保留一位小数的百分比startangle90让第一块扇区从 12 点方向开始画符合直觉。这个组合图在答辩展示中非常实用一张 PPT 能容纳两种信息。5. 避坑与排查我在复现这套项目时踩过的五个坑5.1 图里的中文全部变方块现象代码运行无报错但所有标题、轴标签的中文都显示成了无法辨认的方框。 原因Matplotlib 默认字体库不包含中文字体这是最常见且最常被忽略的问题。 解决在绘图代码前显式指定中文字体。Windows 下最省事的方式是设置黑体或微软雅黑import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei] plt.rcParams[axes.unicode_minus] False注意第二行axes.unicode_minus要设置为False否则坐标轴上的负号会显示成一个小方块或乱码。5.2 Notebook 里ImportError: No module named pandas现象双击打开acc.ipynb执行第一行import pandas as pd就报错但自己单独写一个 .py 文件又能正常运行。 原因电脑上装了多个 Python 环境Notebook 的 kernel 指向的不是你安装了 pandas 的那个环境。 解决我一般用 Anaconda 环境管理。在终端里检查当前环境conda activate base python -m pip install pandas matplotlib seaborn jupyter python -m ipykernel install --user --name base然后在 Notebook 右上角的 Kernel 菜单中选择刚才安装的base环境。这个坑在刚接触 Python 的新手里出现概率极高不是代码问题是环境串线问题。5.3 清洗前后行数对不上找不到原因现象明明只删了重复行结果记录数少了上百行仔细核对代码也没发现哪里多删除了一片。 原因清洗过程中多次过滤前一步用了isna()判断是否保留后一步又用dropna()删掉整个行几个动作叠加导致数据丢失。 解决把清洗过程分段执行每段打印一次行数并记录每段删除的行数。我通常的做法是加一个计数器字典log {} log[删除重复] acc.duplicated().sum() acc acc.drop_duplicates() log[删除空时间] acc[datetime].isna().sum() acc acc.dropna(subset[datetime]) print(log)这样每一步删掉多少行、是因为哪个规则删掉的全都留痕论文里直接可引用。5.4 导入wwa.ipynb后代码能跑但导出的 HTML 图表全部空白现象在 Jupyter 里点File → Download as → HTML生成的网页打开后图表区域是一片空白但代码输出区域还在。 原因这个是你的浏览器缓存或 Notebook 未执行完造成的。如果 Notebook 里的图表是最后一次运行后显示的那么导出时页面会尝试重新渲染输出流的图片数据一旦内存中的图像数据被回收或 kernel 重启过HTML 里就丢失了图。 解决强制从上到下完整执行一次所有单元格再导出jupyter nbconvert --to html --execute acc.ipynb --output accident.html--execute参数的作用是在转换前自动重启 kernel 并按顺序执行全部单元格这能规避「没执行完就导出」的问题。5.5 假数据分布太“均匀”答辩被追问时解释不清楚现象画出来的省份分布图特别平均每个省份的事故数量几乎一致不符合真实业务里「东部省份明显高于西北」的常识。 原因fake_accident_data.csv是模拟数据生成时为了演示方便默认均匀分布。它不是真实统计年鉴数据。 解决最稳妥的做法是在论文「数据来源」一节明确写清「本研究使用模拟数据验证方法流程」并在图表下方加一句脚注。如果你需要做更真实的分布可以自己手动调整权重生成一份新数据保留旧文件做对照。我的习惯是往下调province列的采样概率用random.choices加weights参数跑一次就能得到一条更有说服力的长尾分布。6. 从 Notebook 到答辩材料把演示过程做成闭环6.1 用 nbconvert 输出一份导师能直接打开的报告上面已经提过基础的导出命令这里再给一个自己常用的组合。导出 HTML 只是其中一环建议把每个 Notebook 的执行结果也保留下来方便在论文附录里引用jupyter nbconvert --to html --execute acc.ipynb --output accident_report.html jupyter nbconvert --to html --execute wwa.ipynb --output weather_report.html如果导出结果太大可以加--template classic参数它会让页面变得比较朴素但不会丢失任何图像内容。答辩前我会把这个 HTML 文件放到和preprocessed_accident_data.csv同级的目录下用浏览器在投影上完整走一遍。6.2 给图表补一个业务可读的「结论标注」图像文件本身不会说话答辩时老师盯着图看十秒自己心里得有底。我的习惯是每张图保存后在 Notebook 的 Markdown 单元格里写两句话这图为什么存在、结论是什么。案例写法如下结论月度趋势图显示 12 月事故数量明显上升。 可能原因冬季天气复杂、道路结冰概率增加。 局限性本数据为模拟数据趋势结论仅在数据范围内有效。这种结构的好处是让整个 Notebook 有「发现问题 → 解释问题」的完整链路而非图表罗列。老师翻到任何一张图都能看到对应的文字记录这会直接拉高印象分。6.3 自我验证用抽样与汇总检查结果合理性在正式提交前我会做一次反向校验。具体做法是把清洗后的数据按省份分组汇总再手工抽取一个省份核对明细记录数是否一致check pd.read_csv(preprocessed_accident_data.csv, encodingutf-8-sig) pivot check.groupby(province).size().reset_index(namecounts) # 抽查一个省份该省明细行数应等于分组数 sample_province 广东 detail_count (check[province] sample_province).sum() pivot_count pivot.loc[pivot[province] sample_province, counts].iloc[0] print(f明细行数{detail_count}分组计数{pivot_count})这个验证的意义在于它能排查出清洗阶段常见的重复删除或索引错位问题。两个值相等说明数据一致不等请回到上一节的分段日志去找丢失在哪一步。从那以后我每次交付任何数据报告都会强制自己走一遍「抽样 → 汇总 → 对照」的验证流程这个习惯帮我挡掉了至少三次数据不一致的事故希望也能帮到你。本文还有配套的精品资源点击获取