简介:这是一份面向数据分析、市场研究及电动汽车行业爱好者的真实电动汽车数据集,围绕2025年销售的3K+条记录,覆盖特斯拉、宝马、日产等品牌的电池规格、续航里程、充电方式、价格、产地、安全等级及销量等属性,可用于车型对比、电池技术趋势和价格区间分析。资源打包为zip压缩包,共3个文件,分别提供xlsx表格便于Excel透视、csv通用格式适合编程建模、json结构适合Web应用集成,体积仅492KB,数据规整、便于上手。目前已有204人学习下载,适合需要快速获取结构化电动车数据的学习者或研究者使用。数据中还包含多个年份、插电混动车型及部分缺失字段,使用者可借此练习数据清洗和单位统一,也可观察全球主流车企在2025年的产品布局,并进一步开展安全评级与续航表现等交叉分析。
1. 电动汽车数据集:3K+真实记录撑得起哪些分析
2025年做电动汽车相关的数据项目,最不缺的是模型,最缺的是一份能直接落地的真实车辆数据。这份电动汽车数据集把特斯拉、宝马、日产的电池规格和2025年销售数据收成了3K+条记录,覆盖了二手残值评估、电池衰减回归、区域市场价差这些常见的从业场景,也是新车价格预测模型能起步的最小样本量。适合正在做新能源行情分析的工程师、准备用真实车辆数据训练价格与续航分类模型、又不想从零写爬虫的团队,拿到之后可以直接验证想法、跑通基线,不必从抓取和清洗的泥潭里起步。
2. 先读懂这批数据:电池规格与销售字段到底怎么对齐
2.1 电池规格字段要拆开看:容量、电压、测试标准各说各话
拿到这个数据集先别急着做特征工程,先把“电池规格”四个字拆开。真实来源往往是官网参数表、论坛、第三方数据库混在一起,字段叫法很乱。我一般会强制要求至少保留五列:电池类型(三元锂/磷酸铁锂)、标称总容量(kWh)、可用容量(kWh)、标称电压(V)、续航里程(km)。缺任何一个,后面换算都容易出问题。
这里有个特别容易翻车的地方:同一款车,NEDC、WLTP、EPA三种测试标准测出来的续航差距很大。欧洲2020年前的车喜欢标NEDC,美国车标EPA,中方厂商不少拿CLTC。比如特斯拉Model 3长续航,EPA大约在570-600km,换WLTP能到629km,NEDC甚至敢标到668km。所以看到range_km字段时,必须同时确认test_std字段,否则别直接拿它建续航回归模型。你可以在后续处理里新建一列test_std,把“NEDC/WLTP/EPA/CLTC”四个值归一,缺掉的置NaN。
日产Leaf的电池规格字段尤其要小心。Leaf在官方文档里同时会出现电池包kWh值和电芯Ah值,第三方采集员经常把“40Ah电芯”直接当“40kWh电池包”写进去。所幸电池包标称电压通常在同一行里,标准的换算公式是kWh = 电压(V) × 电流(Ah) / 1000。比如一组标称360V、66Ah的电池包,算出来是23.76kWh,对应早期24kWh的Leaf,而不是66kWh。这个坑在二手欧洲车的规格表里最常见,单位不统一,一不注意就把整个分布的右尾拉歪。
宝马i3也是典型的演进样本。早期i3用60Ah电芯,电池包约22kWh;中期改款用94Ah,约33kWh;末期用120Ah,约42kWh。三款电池包外观尺寸几乎一样,但BMS软件版本、充电功率、可用容量都不一样。如果你只按“BMW i3”去合并销售表,必然出错。反过来,如果销售表里恰好有电池容量字段,你就可以反推它是哪一代电池,从而补全年款信息。这就是为什么我坚持把电池容量作为合并主键的一部分,而不是辅助信息。
2.2 销售数据字段还有一套口径:价格、地区、时间
销售数据比电池规格更脏。首先是价格:同一款Model Y,美国、欧洲、中国、东南亚的价格不一样,而且有些是含税指导价,有些是补贴后地价,有些是二手车成交价。要做跨市场对比,必须先归一化币种和税务口径。我一般新建price_usd、price_includes_tax两列,原始价格字段永远保留不动,这是血泪教训:改原始列会丢掉下游复盘的依据。
时间字段是另一个坑。2025年销售数据不是一个时间点,而是月度或季度记录。特斯拉每个季度会对长续航版做临时降价促销,宝马i系列经销商折扣甚至能到15%,如果按“2025年全年平均价”建模,实际是在用一个不存在的价格。正确做法是保留sale_year、sale_month两列,把价格当成时变特征,别把2025年的12个月合成一列全年均价。很多研究残值的模型会忽略这一点,结果就是对促销月的价格预测系统性偏高。
字段对齐策略上,我的经验是“品牌+车型+年款”只能做辅助键,真正的合并主键要带电池规格。原因很简单:Model 3在2021款之后同时卖过标准续航(约55-62kWh)和长续航(约75-82kWh),单看车型名,销售表里根本分不出来是哪个配置。把电池容量作为主键一部分之后,两条数据才能落到同一行。这里还藏着一个多对多问题:同一车型同一个容量,可能覆盖两个年款,所以我的merge_key通常是“品牌_车型_电池容量_年款”。
最后建议把字段字典固定下来,我倾向于在进模型之前把表格规范成19到20列。这个步骤不复杂但特别重要:字段没从一开始统一,后面每做一次合并就多一次脏数据。字段字典设计好之后,无论原始数据叫“Battery Capacity”还是“电池容量”,都映射到同一列,后续特征工程和模型验证才不用反复返工。
| 标准字段 | 类型 | 示例 | 说明 |
|---|---|---|---|
| brand | str | tesla | 厂商统一小写 |
| model | str | model_3 | 车型归一化 |
| year | int | 2024 | 年款 |
| battery_kwh_raw | float | 82.0 | 厂商标称总容量 |
| battery_kwh_usable | float | 79.0 | BMS可用容量 |
| voltage | float | 400 | 标称平台电压 |
| range_km | float | 629 | 续航,必须配test_std |
| test_std | str | wltp | wltp/epa/nedc/cltc |
| price_usd | float | 42990 | 归一化美元价格 |
| price_type | str | msrp | msrp/dealer/used/after_incentive |
| sale_month | int | 202503 | 2025年3月 |
3. 把3K+记录落进本地:数据整理与字段标准化的最小流程
3.1 先做数据勘察:不要跳过shape、dtypes和缺失值
拿到数据后第一件事不是读文件内容,而是跑一个标准的勘察脚本。很多从报表系统导出的CSV带BOM头,Excel打开过再存一次,编码和分隔符都会变化,所以read_csv要用utf-8-sig并显式声明分隔符。勘察脚本的核心是三件事:看形状、看字段类型、看缺失分布。
import pandas as pd import numpy as np df = pd.read_csv("ev_dataset_2025.csv", encoding="utf-8-sig") print("shape:", df.shape) print("columns:", df.columns.tolist()) print(df.dtypes) missing = df.isna().sum() print(missing[missing > 0]) print(df["brand"].value_counts(dropna=False))这段代码里,df.shape告诉你记录数和字段数是否和标题描述一致,如果少了超过10%,先怀疑读取问题,再怀疑原始文件被截断。df.dtypes是找“数字被读成字符串”的最快路径,比如price列是object而不是float64,说明里面有逗号或货币符号。最后一行value_counts可以快速看品牌分布是否真的集中在特斯拉、宝马、日产三家。
参数说明:encoding="utf-8-sig"是为了吃掉UTF-8 BOM头,Windows下导出的CSV几乎都有;dropna=False能把缺失值单独显示成NaN一行。我习惯在前三步不drop任何行,纯粹摸清底数,避免在没看清分布的情况下误删数据。
3.2 电池规格字段统一成数值型kWh
电池规格往往是字符串,比如“82 kWh (usable 79 kWh)”“66 Ah 350V”“600 Wh”。一步到位做单位归一化的脚本长这样:
import re def parse_capacity(text): if pd.isna(text): return np.nan s = str(text).lower().replace(" ", "").replace(",", "") m = re.search(r"(\d+(?:\.\d+)?)\s*(kwh|wh|ah)", s) if not m: return np.nan val = float(m.group(1)) unit = m.group(2) if unit == "wh": return round(val / 1000, 2) if unit == "kwh": return round(val, 2) vm = re.search(r"(\d+(?:\.\d+)?)\s*v", s) if not vm: return np.nan return round(val * float(vm.group(1)) / 1000, 2) df["battery_kwh_parsed"] = df["battery_spec_raw"].apply(parse_capacity)逻辑说明:先把字符串里的空格和逗号全部去掉,让“82 kWh”变成“82kwh”,再用正则优先匹配数字加单位。匹配到Wh就除以1000,匹配到Ah则必须在同一字符串里找到电压V,否则直接返回NaN。这个设计是故意的:Ah没电压就是不可换算的脏数据,猜一个值会让后面整个回归都不稳。
参数说明:正则中的“\d+(?:.\d+)?”同时覆盖了“82”和“82.1”两种写法;“\s*”是保险,虽然前面已经去过空格,但万一换行符没被replace掉也能兜住。这里有个易错点:字符串“79kwh”和“82kwh”可能同时出现在同一行里,regex默认只匹配第一处,所以我会在解析前先去截最短的数值段,或者直接用findall加min,避免把“usable 79”当成总容量。
提示:解析结果出来后,直接对比battery_kwh_parsed和原始列的分布。如果原始列有“总容量/可用容量”两种语义,务必拆成battery_kwh_raw和battery_kwh_usable两列,别混在一个字段里。
3.3 2025年销售时间与价格口径清洗
时间字段和价格字段是清洗的重头戏。sale_month如果以整数形式存在,比如“202501”,直接用pd.to_datetime解析;如果带中划线“2025-01”,就把format参数改掉。价格的脏数据通常是逗号、货币符号、空格混着来。
def clean_price(price_str): s = str(price_str).replace(",", "").replace("$", "").replace(" ", "").strip() try: return float(s) except ValueError: return np.nan df["sale_month_dt"] = pd.to_datetime(df["sale_month"].astype(str), format="%Y%m") df["price_usd"] = df["price"].apply(clean_price)逻辑说明:clean_price把美元符号、千分位逗号、中间空格全部去掉后再转float,转不动说明原值可能是“约45万”这类非结构化文本,直接置NaN比猜一个数更安全。sale_month先转字符串再指定format="%Y%m",是为了兼容“202501”和“20251”这种Excel把前导零吞掉的情况。
参数说明:astype(str)是把整型时间列转字符串,pd.to_datetime遇到“20251”这种非法格式会报错,所以实际项目中我一般加errors="coerce",这样解析失败会变成NaT而不是中断流程。价格清洗后一定要再画一次describe(),如果price_usd的最小值小于1000,基本可以断定单位不是美元,而是人民币标价被误判成美元了。
4. 多源合并与特征工程:让3K+记录能喂给模型
4.1 合并主键:品牌、车型、电池容量三段式
现在数据集里有电池规格表、销售表,可能还有一份年款配置表。直接按品牌和车型合并一定会撞车,因为同一车型在不同年款有不同电池容量。我的做法是构造三段式merge_key。
def norm_key(x): s = str(x).lower() return re.sub(r"[^a-z0-9]+", "_", s).strip("_") df["brand_key"] = df["brand"].map(norm_key) df["model_key"] = df["model"].map(norm_key) df["pack_key"] = df["battery_kwh_parsed"].round(1) df["merge_key"] = df["brand_key"] + "__" + df["model_key"] + "__" + df["pack_key"].astype(str)逻辑说明:norm_key先把“BMW”转成“bmw”,把“Model 3”转成“model_3”,统一大小写和分隔符。pack_key把容量四舍五入到一位小数,兼容“82.0”和“82”因为数据结构不同导致的微小误差。
参数说明:round(1)并不是万能药,如果两份数据一份用总容量、一份用可用容量,四舍五入也救不回来,必须先靠battery_kwh_raw/usable两列统一到同一种语义再做round。还有一个细节:nm口品牌的“i3”会保留数字,不会有问题;“M3”在特斯拉语境里是Model 3,但在宝马语境是M3,所以merge_key前面加了brand_key,用品牌做第一段隔离。
合并时我还会检查merge_key的重复率。正常情况下每个key对应一条记录,如果某个key对应多条,大概率是两个年款共用同一组电池规格,那就必须把year加进merge_key。价差模型里,年款这个变量太重要了,不能省。
4.2 构造可用特征:能耗、充电倍率、单位容量价格
合并完字段后,数据集已经具备进模型的基本形状。但原始字段不适合直接喂给线性模型,因为续航、容量、价格之间的量纲差异太大。我会先构造三个解释性强的特征。
df["energy_eff"] = df["range_km"] / df["battery_kwh_parsed"] # km/kWh df["c_rate"] = df["fast_charge_kw"] / df["battery_kwh_parsed"] # 近似充电倍率 df["price_per_kwh"] = df["price_usd"] / df["battery_kwh_parsed"]逻辑说明:energy_eff是每度电能跑多少公里,数值越大越省电,国产车通常在6-8km/kWh,老款Leaf能到5左右,高性能车反而可能低于4。c_rate是最大快充功率除以容量,800V平台车型通常能到2C以上,400V车型多在1-2C之间,这个特征可以帮你快速识别平台电压等级。price_per_kwh是把价格除以容量,得到“每度电多少钱”,用来横向比较不同容量的定价合理性,这在残值分析里是个很敏感的指标。
参数说明:如果原始数据没有fast_charge_kw字段,c_rate这一列就直接不构造,不要用快充功率缺失的行硬算。另外,价格在不同地区差异很大,price_per_kwh最好分地区、分品牌来看,不然特斯拉和日产的对比会被汇率和税费政策带偏。
做完特征之后,我建议再做一轮相关性检查。把energy_eff、c_rate、price_per_kwh、price_usd、battery_kwh_parsed放进corr(),如果price_per_kwh和battery_kwh_parsed的相关系数超过0.9,说明价格基本由容量决定,这个数据集很适合做回归;如果相关系数接近0,则说明市场定价被品牌和年份主导,容量只是辅助变量。3K+条记录做相关性检查是秒出的,完全跑得动。
4.3 切分训练验证:按时间切,别随机切
销售数据天然带时间属性,做价格预测时绝对不能把2025年全年混在一起随机切。我见过太多次这种翻车:训练集里包含了月底降价记录,验证集里出现了月初记录,模型指标好得反常,一上线就崩。正确做法是排序后按月份切。
from sklearn.model_selection import train_test_split df = df.sort_values("sale_month_dt") valid = df[df["sale_month_dt"] >= "2025-10-01"] train = df[df["sale_month_dt"] < "2025-10-01"] X_train = pd.get_dummies(train[["brand", "model", "battery_kwh_parsed"]], columns=["brand", "model"]) y_train = train["price_usd"] X_valid = pd.get_dummies(valid[["brand", "model", "battery_kwh_parsed"]], columns=["brand", "model"]) X_valid = X_valid.reindex(columns=X_train.columns, fill_value=0)逻辑说明:先把数据按时间升序排好,取2025年10月之后做验证集,之前做训练集。get_dummies把品牌和车型展开成哑变量,这样特斯拉、宝马、日产的品牌效应会被模型显式学习。X_valid reindex到X_train的列集,是为了防止验证集里出现训练集没有见到的新车型导致报错。
参数说明:10月这个切分点你可以按数据量调整,一般是最后2-3个月做验证。stratify只在随机切分时有意义,时间切分没有必要加。如果你要预测的是“未来价格”,那么训练集里绝不能出现晚于验证集日期的任何记录,哪怕那些记录价格更“正确”。时序泄漏是所有销售数据建模里最隐蔽的问题,四舍五入的merge_key坑是数据质量,时间顺序坑是方法论,两个都要单独检查。
5. 实名避坑:整理这类电动车数据集最容易翻车的五个细节
5.1 电池容量单位混用
现象:battery_spec_raw里同时出现“66 Ah”“58 kWh”“600 Wh”,一个字段三项三种单位。
原因:数据源混合了电芯参数、电池包参数和第三方二手网站的自定义字段。欧洲二手车网站偏爱Ah,国内网站几乎清一色kWh,抓取合并时没人统一。
解决:先按3.2里的parse_capacity统一到kWh。Ah必须带电压换算,如果同一行里找不到电压字段,直接置NaN,不要用同车型中位数去填Ah类缺失。单位混用的数据一旦混进训练集,容量特征会平白多出几倍的噪声。
5.2 总容量与可用容量被当成一列
现象:同一车型两个来源,一个标82kWh,一个标79kWh,两个都对。
原因:厂商对外宣传往往标电池包总容量,BMS系统实际可用的部分会锁掉3-5kWh。不同数据源一个抓官网总容量,一个抓车主App里的可用容量。
解决:先建battery_kwh_raw和battery_kwh_usable两列。做残值分析和续航预测全用usable,因为价格和续航是由用户可用的电量决定的;做充电时间粗算和能耗标称对比才用raw。建模前在数据字典里写明你用哪一列,否则模型系数没有可解释性。
5.3 2025年销售价格口径不一致
现象:同样一台Model Y,一条记录42990美元,另一条折合下来38900美元。
原因:前者是含税落地价,后者是不含税裸车指导价;更隐蔽的是有人把“补贴后价格”混了进来,导致同配置同时间出现两个相差悬殊的价位。
解决:增加price_type列,值域设为msrp/dealer/used/after_incentive,并在建模时只保留其中一种。做新车价格分析用msrp,做二手车残值用used,千万不要混着训练。这个过程靠脚本检查可能发现不了,得直接看同一merge_key下的价格极差,大于15%就要手工核对。
5.4 电池规格缺失率过高
现象:3K+条记录里20%的电池容量为空,直接drop后,长续航、高配车型被删掉一大半,样本分布完全偏移。
原因:老款Leaf和宝马i3早期发布时官方参数不完整,第三方数据源不采集电池规格;越老的车型缺失越严重。
解决:先用官方配置表做一次补全,剩下的按brand+model+year分组取中位数插补,同时加is_imputed标记列。中位数计算时要先剔除本组内的NaN行,如果一组全是NaN,就别硬补,直接保留缺失并交给树模型处理。
5.5 时序泄漏
现象:价格模型在验证集上R²高得离谱,接近0.95,一放到真实业务里预测就整体偏移。
原因:把2025年全年数据混在一起后随机切分,训练集包含了月底的降价记录,而验证集用了月初的价格,模型等于提前看到了“未来”。
解决:永远按sale_month排序后切分,验证集在时间上严格晚于训练集。对销售数据做随机切分的唯一后果就是指标失真,没有例外。代码层面可以加一个断言:assert valid["sale_month_dt"].min() > train["sale_month_dt"].max(),把这条写进流程,谁改切分逻辑都会立刻被发现。
6. 基线验证:用价格-容量回归检验这批数据值不值得投入
数据整理完,先不要急着上XGBoost或深度学习。我习惯先跑一个价格对容量的简单线性回归,这一步能花两分钟告诉你数据质量到底行不行。
import statsmodels.api as sm sub = df.dropna(subset=["price_usd", "battery_kwh_parsed"]).copy() X = sm.add_constant(sub["battery_kwh_parsed"]) y = sub["price_usd"] ols = sm.OLS(y, X).fit() print(ols.summary().tables[1])逻辑说明:battery_kwh_parsed的系数应当显著为正,p值远小于0.05。如果系数为负,基本可以断定前面某个环节出了问题,最可能是容量单位混用或价格口径不一致。R²在0.2到0.5之间都算正常,因为价格还被品牌、年款、地区强烈影响,单靠容量解释不了全部。接下来你可以把brand、range_km加进特征再看R²,如果从0.3涨到0.7,说明数据集的结构是健康的,品牌效应被模型成功捕捉。
这一版的教训我记忆很深。以前拿到一批“2024纯电车型数据”,没有做单位统一,直接喂回归,容量系数居然为负。排查了两个小时,发现约两成容量字段是Ah没换算,约三成价格混了汇率。那种翻车最伤的不是数据本身,而是时间:你永远不知道是代码错了还是市场疯了。从那以后,无论数据多急,我都先跑一遍这个基线回归,R²异常低就直接拦下整个流程,不带着脏数据往下走。3K+条记录跑这个检查完全够用,算下来也就一眨眼的功夫。希望这套整理流程能帮你在自己的数据集上少踩同样的坑。
本文还有配套的精品资源,点击获取