☰
2024电动汽车数据集可视化分析:从清洗到交互看板的实战指南
2026/10/2 9:52:28 网站建设 项目流程

简介:2024年全电动汽车数据集可视化分析案例包含原始数据与配套代码,面向数据分析初学者及新能源汽车行业观察者,可帮助理解电动汽车市场规模、用户偏好与充电设施分布等维度。压缩包共3个文件,含csv格式的Electric Vehicle Population Data、用于逐步执行数据清洗与统计分析的ipynb代码,以及用于网页端展示图表结论的html文件,整体大小仅3.8MB,轻量便于下载与快速启动。目前已有196人学习,案例覆盖品牌销量对比、不同区域接受度、续航与充电次数关系等分析主题。通过运行代码,读者不仅能直接复现从数据清洗到相关性分析再到可视化呈现的完整流程,掌握pandas、matplotlib等常用库的实操方法,还能基于已有分析框架替换自己的数据,灵活调整图表参数,从而拓展到其他行业数据项目中,兼具学习与二次开发价值。

1. 2024 年全电动汽车数据集可视化分析,值得花一个周末跑完的理由

「2024 年全电动汽车数据集可视化分析」这个包,拿到手第一眼就是一份数据集加一段代码,看起来平平无奇。真正跑起来才发现,这组数据把车型、续航、价格、电池容量、充电规格这些平时分散在不同渠道的字段收进了同一张表,而且刻意保留了缺失值、单位混用和重复记录。想从里面挖出「2024 年热门车型价格分布」「长续航和电池容量是不是强相关」这类结论,正好需要完整过一遍数据清洗、聚合、Matplotlib 出图、Plotly 交互、成果交付的链路。下面就把这套数据的处理方法、值得调的参数和几个容易翻车的地方写清楚,适合刚接触数据分析项目的人照着敲,也适合有经验的读者直接跳过代码看参数和决策边界。

2. 拆开 2024 全电动汽车数据集:字段口径、单位混用和清洗三座大山

2.1 先看字段清单:哪些列是「事实」,哪些列是「口径」

第一步不是画图,而是先把文件读进 pandas,确认行数、列数和每列的数据类型。这个动作花不了三十秒,但能提前暴露七成以上的数据问题。

import pandas as pd df = pd.read_csv("electric_vehicles_2024.csv") print(df.shape) # 行数、列数 print(df.dtypes) # 每列类型,重点看有没有 object 冒充数值 print(df.head()) # 前几行用于肉眼核对单位与格式

读进来之后,你会发现一个典型现象:价格、整备质量这些列被正确识别成 int64 或 float64,但续航、电池容量这类「数值加单位」的列大概率变成 object。别急着转换,先按字段角色分个类,我一般分成四组:

字段分组典型字段作图时的作用
标识类Make, Model, Year, Trim分组、筛选、图例
价格类MSRP, Price条形图、趋势图、散点图纵轴
性能类Range, Battery_Capacity, Charge_Time直方图、散点图横轴、相关性分析
物理类Mass_kg, Motor_Power, Seats双轴图、箱线图的辅助维度

这样做的好处是,后面每画一张图,都能快速回答「这张图的横轴是哪个字段、单位是什么、要不要参与清洗」。很多人拿到数据集直接 df.describe() 然后就开始画图,结果画到一半发现续航单位有英里也有公里,返工成本反而更高。

2.2 清洗第一关:单位混用是电动汽车数据集里最常见的脏数据

同样是续航里程,北美车型习惯用英里,欧洲和国内车型用公里;同样是电池容量,有的记录写 kWh,有的写 Ah 甚至 Wh。可视化分析本身不做单位换算的话,直方图会出现两个离得很远的峰,相关性分析也会被整体拉偏。

# 统一续航单位为公里;原列单位若是英里就乘 1.60934 df["Range_km"] = df["Range"] * 1.60934 # 统一电池容量单位为 kWh;原列单位若是 Wh 就先除 1000 df["Battery_kWh"] = df["Battery_Capacity"] / 1000.0 # 缺失值处理:按品牌组内中位数填充,并单独留一列记录填充状态 df["Range_km"] = df.groupby("Make")["Range_km"].transform( lambda x: x.fillna(x.median()) ) df["Range_km_filled"] = df["Range_km"].isna()

逻辑说明:groupby 加 transform 是「组内填充」的标准写法,它会按品牌分别计算中位数并回填到每一行,而不是拿全局中位数硬套所有品牌。这样保住了品牌之间的差异,比如高端品牌续航普遍偏高,用全局中位数会把它的缺失值填低。参数说明:英里转公里的系数固定为 1.60934;Wh 转 kWh 是除以 1000。至于要不要保留「是否填充」这一列,取决于后面做什么——如果只是画分布图,可以不要;如果后续要跑回归模型,建议保留,方便做敏感性分析。

这里有个容易忽略的选择:为什么用中位数而不是均值?因为续航和价格都呈右偏分布,均值会被少数长续航车型拉高,组内中位数更接近「大多数车的水平」。如果某品牌所有车型的续航都缺失,组内中位数也是空值,此时再对剩余 NaN 做一次全局填充或直接丢弃都可以,数据集里这类记录占比通常很小。

2.3 重复记录和「价格等于零」的异常清洗,下手要狠

除了单位混用,这份数据集里还有两类「脏」:重复记录和占位型异常值。同一个车型出现在不同批次、不同来源的抓取里,这是多源数据集合并后的通病;而价格为 0 或负数的记录,基本可以断定不是真实售价,而是缺失值的占位写法。如果不处理,后面算品牌均价时,这些 0 会把均价显著拉低。

# 去重:先按价格排序,再按关键列组合去重,保留非零价格那条记录 df = df.sort_values("Price").drop_duplicates( subset=["Make", "Model", "Trim"], keep="last" ) # 去掉价格为 0 或负数的记录 df = df[df["Price"] > 0] # 去掉续航为 0 的记录(真实续航为 0 的产品不存在) df = df[df["Range_km"] > 0]

参数说明:sort_values("Price") 的目的是让价格高的记录排在后面,再用 keep="last" 保留它们;如果你希望保留价格最低的那条,就把 keep 改成 "first"。subset 字段按实际数据集调整,一般至少用 Make + Model + Year,三者完全相同才判定为重复。执行完这两步,行数通常会减少 3% 到 8%,这个规模在可视化分析里完全可以接受。别舍不得删,把占位型 0 和重复记录留在图里,最后的均价趋势和续航分布都会失真,到那时再排查就麻烦了。

清洗完可以用 df.describe() 再扫一眼各列分布,确认最小值、最大值、分位数都落在合理区间。这一步是后面所有图表的「后悔药」,提前吃,后面省事。

3. 选对可视化工具链:为什么不用 Excel,也不一上来就上 BI

3.1 三个工具的分工逻辑,别指望一个工具干完所有事

做 python 数据分析与可视化时,最常见的默认组合是 pandas 加 Matplotlib,再加一个 Plotly 兜底交互。真正动手前先想清楚:这份数据集是要交付一个静态分析报告,还是要交给业务方自己筛选查看?前者用 Matplotlib 完全够,后者需要 Plotly 或 BI 工具。

Excel 适合数据量小、字段少、一次性探索的场景,但它有两个硬伤:一是聚合逻辑藏在单元格里,换一批数据要手动改公式;二是图表的配色、标注、批量导出很难脚本化。BI 工具(Power BI、Tableau 这类)适合企业里需要业务部门自助分析的情况,但对个人做数据集练习来说,搭建数据源、维护权限和仪表板配置的代价偏高,而且代码不可复现。我自己的分法是:

  • pandas:负责数据清洗、聚合、透视,产出分析用的中间表;
  • Matplotlib + Seaborn:负责静态图的版式和密度表达,输出 PNG/SVG 给报告;
  • Plotly:负责把两三张关键图转成交互式 HTML,交付给不写代码的人。

3.2 用 Matplotlib 画品牌均价 Top 10:最小可运行代码

先做一个最常用的图:各品牌平均售价的 Top 10 条形图。这段代码是「聚合-排序-截取-画图」的完整链路,后面所有类似的条形图都可以套这个模板。

import matplotlib.pyplot as plt price_by_make = ( df.groupby("Make")["Price"] .mean() .sort_values(ascending=False) .head(10) ) plt.figure(figsize=(10, 6)) price_by_make.plot(kind="barh") plt.xlabel("Average Price (USD)") plt.title("Top 10 EV Makes by Average Price") plt.gca().invert_yaxis() # 让均价最高的品牌出现在顶部 plt.tight_layout() plt.show()

逻辑说明:groupby 按品牌分组后对 Price 求均值,sort_values 降序排列,head(10) 截取前十个,然后直接用 Series.plot 画水平条形图。这里特意用 barh 而不是 bar,是因为品牌名称通常较长,竖排条形图的 x 轴标签会挤成一团。参数说明:figsize=(10, 6) 是 PPT 半页图的常见尺寸;invert_yaxis() 不调用的话,图表顶部是均价最低的品牌,读图顺序不符合大多数人的直觉。

这张图适合回答「哪个品牌均价最高」这类问题,但要注意它掩盖了组内差异——特斯拉既卖几万美元的入门车型,也有十几万的高性能版本,只看均价会把它的价格带宽度完全抹掉。所以均价图一般配合箱线图一起看。

import seaborn as sns top_makes = price_by_make.index.tolist() plt.figure(figsize=(12, 6)) sns.boxplot( data=df[df["Make"].isin(top_makes)], x="Make", y="Price", order=top_makes ) plt.xticks(rotation=45) plt.tight_layout() plt.show()

参数说明:order 参数传入我们刚才算出的 Top 10 品牌列表,保证箱线图的排序和条形图一致;rotation=45 让品牌名斜着显示,避免互相遮挡。通过对比条形图和箱线图,能一眼看出哪些品牌是「全线高价」,哪些品牌是「有高价也有走量车型」。

3.3 什么时候切到 Plotly:三种典型场景

Matplotlib 擅长静态出版级图,但交互场景它做起来很别扭。我认定需要切到 Plotly 的情形有三种:第一,读者需要悬停看到具体型号名而不是只看一个数据点;第二,需要缩放、框选局部点;第三,交付对象不装 Python。这三个条件只要中一个,就直接上 Plotly。

import plotly.express as px fig = px.scatter( df, x="Range_km", y="Price", color="Make", hover_data=["Model", "Battery_kWh"], title="Price vs. Range - 2024 EV Dataset" ) fig.write_html("price_range.html") fig.show()

参数说明:hover_data 控制在悬停提示里额外显示哪些字段,放两三个最合适,放多了 Tooltip 会变成一整块表格,反而难读。color="Make" 会自动为每个品牌分配一种颜色,如果品牌太多,可以用 color_discrete_sequence 参数指定调色板,比如 px.colors.qualitative.Set2。write_html 生成的单个 HTML 文件可直接发给别人,对方用浏览器打开就能交互,不需要装 Python 或 Jupyter。唯一需要注意的是文件体积,后面避坑部分会详细讲。

4. 把「报表」变成「洞察」:四张核心图的落地代码与参数调优

4.1 续航分布直方图:bins 是最值得反复试的参数

拿到清洗后的数据,第一张图应该是续航分布的直方图。它能快速回答「这份数据集的车型是偏短续航还是长续航」「有没有明显的双峰结构」。直方图最关键的参数是 bins 和 range,很多人忽略后者,导致横轴上出现一大段没有数据的空白区间。

plt.figure(figsize=(10, 5)) plt.hist( df["Range_km"], bins=30, range=(0, 800), color="#2a6f97", edgecolor="white", alpha=0.85 ) plt.axvline(df["Range_km"].median(), color="red", linestyle="--", label="median") plt.xlabel("Range (km)") plt.ylabel("Count") plt.legend() plt.tight_layout() plt.show()

参数说明:bins=30 在千条量级的数据里是比较稳的选择,太少会丢掉双峰信息,太多会出现大量空桶,画出来像梳子。range=(0, 800) 用来截掉极端值,如果数据里有续航超过 1000 公里的概念车,不设 range 的话整张图会被拉伸得很难看。axvline 画中位数参考线,比手动在标题里写「中位数为 420 公里」更直观。

这里有个常见的认知坑:直方图的形状对 bins 的起始位置和宽度非常敏感。同样是这份数据,bins=15 和 bins=60 画出来的结论可能不同——前者可能看不出双峰,后者会显得噪声很大。一个可用经验是:先用 bins=30 扫一眼形状,再尝试 20 和 40,如果三种参数下图的形态基本一致,说明结论稳;如果差异很大,说明样本量不足,需要转为画累积分布函数(CDF)来替代直方图。

4.2 价格与续航散点图:相关性的真相和离群点标注

散点图是看价格与续航关系的最直接方式。很多人拿到数据第一件事就是画散点图然后强行拟合一条直线,但电动汽车数据集里这条关系通常不是理想线性——高端车型价格翻倍,续航却只提升三成。更值得关注的是那些「贵且续航短」或「便宜且续航长」的离群点。

import numpy as np x = df["Range_km"] y = df["Price"] fit = np.polyfit(x, y, 1) plt.figure(figsize=(10, 6)) plt.scatter(x, y, s=18, alpha=0.4, color="#333") plt.plot(x, np.polyval(fit, x), color="#c1121f", lw=2, label="linear fit") # 抽稀标注离群点:约每 50 个点里取一个做判断 step = max(1, len(df) // 50) for idx in df.index[::step]: row = df.loc[idx] if row["Price"] > 150000 or (row["Range_km"] < 200 and row["Price"] > 80000): plt.annotate( f"{row['Make']} {row['Model']}", xy=(row["Range_km"], row["Price"]), xytext=(8, 8), textcoords="offset points", fontsize=7 ) plt.xlabel("Range (km)") plt.ylabel("Price (USD)") plt.legend() plt.tight_layout() plt.show()

逻辑说明:polyfit(x, y, 1) 做一元线性回归,np.polyval 在同一条 x 上算出拟合值,画出回归线。annotate 部分的抽稀逻辑用 df.index[::step]——每约 50 行取一行做判断,避免对几千个点逐个标标签,画布上会叠成一片黑色。判断条件里的两个阈值是按这份数据的实际分布定的:价格超过 15 万美元,或者续航低于 200 公里但价格高于 8 万美元,都是「不符合主趋势」的典型。参数说明:s=18 控制点大小,alpha=0.4 控制透明度,数据量大时二者配合能减少重叠区域的噪声。xytext=(8, 8) 是标签相对数据点的偏移量,单位像素,字体设成 7 号,在 10 英寸宽的画布上不至于遮太多点。

画完后顺手算一下 Pearson 相关系数:

print(df["Range_km"].corr(df["Price"]))

如果系数在 0.5 到 0.7 之间,属于「中等偏正相关」,说明价格确实和续航有关,但远不是决定性因素,这时候散点图上的离群点才是真正的分析价值。

4.3 年度均价趋势线:rolling 窗口设错会让拐点「慢半拍」

如果数据集里带年份字段,可以画一条年度均价趋势线。但直接按年分组求均值再做折线图,往往折线很抖——某些小众品牌在个别年份可能只有一辆车,均价被单辆车带飞。用 rolling 平滑能压住这种抖动,但窗口参数有讲究。

yearly_price = ( df.groupby("Year")["Price"] .mean() .reset_index() ) yearly_price["Price_smooth"] = yearly_price["Price"].rolling( window=3, center=True, min_periods=1 ).mean() plt.figure(figsize=(10, 5)) plt.plot( yearly_price["Year"], yearly_price["Price"], "o-", label="raw" ) plt.plot( yearly_price["Year"], yearly_price["Price_smooth"], "s--", label="rolling(3)" ) plt.xlabel("Year") plt.ylabel("Average Price (USD)") plt.legend() plt.tight_layout() plt.show()

参数说明:window=3 表示取当前值、前一个、后一个的均值;center=True 把窗口中心放在当前点上,而不是默认的向左偏移;min_periods=1 保证首尾年份至少有一个值就能算出平滑结果。这三个参数里最容易踩坑的是 center——如果不设,平滑曲线会比真实拐点晚一年出现,在分析报告里被较真的读者一眼抓包。

还有一个细节:如果年份列本身是 object 类型,比如「2024 款」这样的字符串,直接用 groupby 会把每个字符串当成独立分组,排序也会乱。先用 pd.to_numeric 提取年份,或者用 str.extract 正则抓数字:

df["Year"] = df["Model_Year"].astype(str).str.extract(r"(\d{4})").astype(float)

4.4 电池容量与整备质量双轴图:单位不同的图别硬拼

最后一个核心图是看电池容量和整备质量随年份的变化趋势。这两个变量量纲完全不通——一个是 kWh,一个是 kg,放在同一张图里只能做双轴。双轴图本身不是坏事,但做得不好就容易误导读者,让人觉得两条趋势线的数值是可以直接比较的。我做这类图的原则是:颜色、刻度标签、曲线三者严格一一对应。

fig, ax1 = plt.subplots(figsize=(10, 5)) color_batt = "#0077b6" color_mass = "#e07a5f" ax1.scatter( df["Year"], df["Battery_kWh"], s=15, alpha=0.3, color=color_batt, label="Battery (kWh)" ) ax1.set_ylabel("Battery capacity (kWh)", color=color_batt) ax1.tick_params(axis="y", labelcolor=color_batt) ax2 = ax1.twinx() ax2.scatter( df["Year"], df["Mass_kg"], s=15, alpha=0.3, color=color_mass, label="Mass (kg)" ) ax2.set_ylabel("Curb mass (kg)", color=color_mass) ax2.tick_params(axis="y", labelcolor=color_mass) ax1.legend(loc="upper left") ax2.legend(loc="upper right") plt.tight_layout() plt.show()

逻辑说明:ax1 是主坐标轴,twinx() 生成共享 x 轴的右侧副坐标轴。两个轴的刻度颜色分别和曲线颜色一致,这样读者即使不看图例,也能通过颜色建立「哪条线对应哪个轴」的映射。用 scatter 而不是折线,是因为同一年的不同车型之间没有时间上的先后关系,连成线反而制造出「按时间演进」的错觉。参数说明:alpha=0.3 在这里是必须的,几百个点叠加后,透明度低会直接黑成一团。

这张图的价值在于:能看出电池容量逐年上升的同时,整备质量有没有同步走高。如果两者同向变化,说明续航增长一部分是靠增重换来的,这个发现比单纯看续航数字有意义得多。

5. 避坑笔记:全电动汽车可视化分析里最容易翻车的五个现场

5.1 数值列被 pandas 读成 object,画图直接报错

现象:代码执行到 plt.hist 或 df.corr() 时报 TypeError,提示不能把字符串和数值做比较。

原因:数据集某列里混入了占位符,比如「—」「未知」「N/A」,pandas 读取时为了保住这些字符串,把整列类型抬高成了 object。

解决:用 pd.to_numeric 配合 errors="coerce" 强制转换,转失败的值会变成 NaN,再走缺失值处理逻辑。

df["Price"] = pd.to_numeric(df["Price"], errors="coerce") df = df.dropna(subset=["Price"])

这个坑的特点是:df.head() 看不出问题,只有跑到画图或数值运算那一行才爆出来。所以清洗阶段对每一列都要有明确的类型预期,不要依赖 pandas 的自动识别。

5.2 品牌太多时散点图糊成一片,图例还占半张图

现象:按品牌着色的散点图里,三四十个品牌挤在一个图例里,图例占了画布四分之一,数据点叠成黑团。

原因:品牌数量远超图例的合理承载范围,同时散点的透明度和尺寸没有调低。

解决:先只保留 Top 10 或 Top 15 品牌再画图,其余品牌合并成一个「Other」分组;散点用 alpha=0.4、s=15 这套参数打底;图例用 bbox_to_anchor=(1.02, 1) 移到画布外。

top_brands = df["Make"].value_counts().head(10).index df_plot = df[df["Make"].isin(top_brands)].copy() df_plot.loc[~df_plot["Make"].isin(top_brands), "Make"] = "Other"

这样图例只剩下 11 项,配色也能分辨,整个图的可读性比硬塞全部品牌高出一个量级。

5.3 Matplotlib 中文标签全部变成方块

现象:plt.title 里的中文在 Windows 和 Linux 服务器上显示为一个个方框。

原因:Matplotlib 默认字体是 DejaVu Sans,不含中文字形。

解决:在脚本开头统一指定中文字体,并关闭负号显示的问题。

plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "WenQuanYi Micro Hei"] plt.rcParams["axes.unicode_minus"] = False # 解决负号显示为方块的问题

注意三个字体覆盖了 Windows 和 Linux 的常见环境。如果在 macOS 上跑,需要再加 "PingFang SC" 或 "Arial Unicode MS"。这个问题最坑的地方在于:代码在本机跑得好好的,部署到服务器或发给同事,图里全是方块。

5.4 清洗后的 CSV 用 Excel 打开,中文全变乱码

现象:df.to_csv("clean.csv", index=False) 导出后,Excel 打开看到「鏄煶」这类乱码。

原因:Excel 默认按本地 ANSI 编码解析 CSV,而 pandas 默认写 UTF-8 无 BOM。

解决:导出时指定 utf-8-sig 编码,它会在文件头写入 BOM 标记,Excel 才能正确识别。

df.to_csv("ev_clean.csv", index=False, encoding="utf-8-sig")

这个坑属于典型的「自己电脑上没问题,交付给别人就出事」,尤其是非技术背景的同事,他们只会双击 CSV,不会去调编码。

5.5 Plotly 导出的 HTML 文件巨大,发出去别人打不开

现象:fig.write_html() 出来的单文件有几十 MB,微信里发送后对方浏览器卡死或空白。

原因:默认配置会把整个 plotly.js 库内嵌进 HTML,一份库就有几 MB 到几十 MB。

解决:使用 include_plotlyjs="cdn" 参数,HTML 会改为引用在线库,体积骤减;缺点是对方必须联网才能打开。如果必须离线交付,建议保留默认嵌入,但只放两到三张交互图,不要一个 HTML 里塞几十张图。

fig.write_html("price_range.html", include_plotlyjs="cdn")

这五个坑,前两个是新手最容易碰到的翻车现场,后三个属于「交付时才会暴露」的隐性坑。做数据可视化分析有一条血泪经验:图在自己的电脑上画出来只算完成三成,真正的问题是换机器、换办公软件、发给别人之后还能不能原样显示。

6. 进阶:把核心图拼成可交互 HTML 看板,交付前先过自查清单

6.1 用 iframe 拼装多张 Plotly 图

如果只看一份静态报告,前面那些图已经够用。但实际工作中,业务方经常会问「能不能让我自己筛选看看」。这时我会把三张核心图分别导出为三个 HTML,再写一个简单的 index.html 把它们嵌进去。相比 Plotly 的 make_subplots 硬拼,独立文件的好处是每张图都能单独打开、单独缩放、单独悬停,互不干扰。

scatter.write_html("view_price_range.html", include_plotlyjs="cdn") hist.write_html("view_range_hist.html", include_plotlyjs="cdn") fit.write_html("view_price_trend.html", include_plotlyjs="cdn") html_index = """ <!DOCTYPE html> <html> <head><meta charset="utf-8"><title>2024 EV Analysis</title></head> <body> <h1>2024 电动汽车数据集分析看板</h1> <iframe src="view_price_range.html" width="100%" height="520"></iframe> <iframe src="view_range_hist.html" width="100%" height="520"></iframe> <iframe src="view_price_trend.html" width="100%" height="520"></iframe> </body> </html> """ with open("index.html", "w", encoding="utf-8") as f: f.write(html_index)

参数说明:每个 iframe 的 height 设成 520px,是浏览器一屏内比较舒适的高度;宽度全部取 100%,保证在不同屏幕下自适应。文件命名用 view_ 前缀,和人手交互时能直观看出这组文件是「展示页」而不是原始数据,避免交付时混淆。

6.2 交付前的三道自查清单

HTML 拼好后,我会打开 index.html 逐图检查三件事:

第一,横轴单位是否正确。凡是用 RANGE 的地方,确认展示的是公里而不是英里;凡是用价格的地方,确认币种是美元且没有混入 0 占位值。第二,图例和颜色是否能对上。按品牌着色时,品牌列表是否和 Top N 一致,「Other」分组有没有混进主品牌。第三,悬停提示里的数值是否落在清洗后的合理范围,比如续航不是负数、价格不是 0。

最后留一道附加验证:比较 Price 的均值和中位数,如果两者相差超过 20%,说明数据有长尾,看图时优先参考中位数而非均值。这是我每次做电动汽车数据可视化分析都会保留的一道自查,虽然有点像玄学,但它确实帮我挡掉过好几次把异常值当趋势去汇报的尴尬。希望这套流程和参数能帮你少踩几个坑,跑数据顺利。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询