电动汽车数据集清洗与价格建模:电池容量解析与sklearn实战
发布时间:2026/10/1 9:24:38来源:尧图网络
简介面向新能源汽车行业分析与机器学习建模场景这份数据集提供2025年三千余条真实电动汽车记录覆盖特斯拉、宝马、日产等品牌包含电池化学成分与容量、续航里程、充电标准与时间、价格、制造产地、自动驾驶等级、二氧化碳排放、安全评级、2024年销量及保修年限等关键字段可用于市场趋势研判、车型对比和预测模型训练。压缩包共三个文件分别提供XLSX表格、CSV数据与JSON格式大小仅492KB便于Excel查看、Pandas读取或程序化调用。已有203人浏览/学习。数据记录以唯一ID标识每款车型包含多品牌、多年份混合样本并保留分类字段的供应商差异适合数据清洗、特征工程和新能源车研究入门及进阶练习。1. 电动汽车数据集值钱但先得想清楚它能回答什么问题2025 年前后冒出的这批以 3K 条记录为卖点的电动汽车数据集可不像网上拼来的表格那样只写着“品牌、车型、价格”它把特斯拉、宝马、日产的主力车型电池规格和同年销售数据放在一张表里。这类数据真正值钱的地方不是“能查配置”而是能回答“某个电池版本的车实际成交价中位数是多少”“三元锂和磷酸铁锂版本价格差在哪个区间”“2025 年新上牌记录里哪个品牌把长续航版本做成了主力”这类问题。对做新能源行业分析、二手车定价模型、电池容量趋势研究的人这是一份能当原料用的基础表。但我先泼一盆冷水这类数据集从来不会“打开就能用”。车企官网的电池容量写着 60kWh到了第三方采集表里可能变成“60 千瓦时”“60 kWh”“60000Wh”三种写法续航里程有 NEDC、WLTP、CLTC 三种口径混在同一条记录里的情况我见过太多次。这篇文章我就按真实动手做数据分析的路线讲清楚拿到这份电动汽车数据集之后怎么拆字段、怎么清洗、怎么建模、有哪些坑是新手一定会踩的。整个过程只用 pandas 和 sklearn 就能落地不需要分布式环境也不需要写一堆 Spark 代码。2. 把电池规格从“人类文本”拆成“机器能算的列”字段设计与清洗的第一次选择2.1 3K 记录里躺着哪几类字段销售事实、电池参数、车型标识拿到表以后我第一件事不是看数据而是看列名和每列的非空比例。常见的做法是先读入再快速info()但真正的老手会在读入前就确认分隔符和编码。这类 CSV 通常是 UTF-8 编码偶尔有 Excel 导出的 UTF-8 with BOM 文件列名首列会多出一个\ufeff如果后面要按列名取数这一步就要先处理。import pandas as pd df pd.read_csv(ev_2025_records.csv, encodingutf-8-sig) print(df.shape) print(df.columns.tolist()) print(df.dtypes)这里encodingutf-8-sig是处理 BOM 的保险写法即使文件没有 BOM 也不会出错。读取后先看shape确认是不是 3000 多条记录再看dtypes判断哪些列被错误识别成了 object。这一步不需要写任何业务逻辑但能帮你提前发现问题比如价格列被读成了字符串或者年份列变成了浮点数。看完成列类型紧接着就是人工过一遍字段含义。以我手上这类数据集的常见结构字段大约分成三组第一组是车型标识包括品牌、车型名称、年款、车身形式这些都是分类变量第二组是电池参数包括电池容量kWh、续航里程km、电池类型、充电功率这组是后面建模的主要特征第三组是销售事实包括销售日期、价格、销量或上牌数量。三组字段的用途完全不同车型标识用于分组和横向对比电池参数用于特征工程销售事实用于标注目标变量千万不能混在一起处理。这里有个容易忽略的点很多第三方数据集的“销售数据”并不是真实上牌数而是线索量、订单量或经销商报备量。我看到过把订单量当销量算市占率的翻车案例。所以在动手前应该先确认这个“2025 年销售数据”到底是什么口径。数据集里如果只写了“销售数据”四个字我一般会按“记录条数”来处理而不去断言它是上牌量还是批发量做分析的时候也只说“记录分布”不说“市场占有率”。2.2 用 pandas 把电池容量和续航拆成数值列电池容量列是这类数据集里最混乱的地方。常见的原始值包括“60 kWh”“60kWh”“60 千瓦时”“60.0”“60000 Wh”以及干脆就是空值。要把这些统一成数值最稳的方法是先把它变成字符串再用正则提取数字部分和单位部分。import re df[电池容量_raw] df[电池容量].astype(str) def parse_battery_capacity(s): s s.strip().lower() if pd.isna(s) or s nan or s : return None # 匹配数字部分兼容小数 num_match re.search(r(\d(?:\.\d)?), s) if not num_match: return None num float(num_match.group(1)) # 判断单位kwh / 千瓦时 / 度 - 保留ah - 按电压换算wh - 除以1000 if ah in s: return None # Ah 需要电压值才能换算见 5.1 if wh in s and kwh not in s: return num / 1000.0 return num df[电池容量_kWh] df[电池容量_raw].apply(parse_battery_capacity)这段代码的逻辑说明先统一成小写再用正则抓第一个数字最后按单位换算。Ah这种单位不能直接换算成 kWh因为还需要电压参数强行按照 3.7V 去算三元锂电池、按 3.2V 去算磷酸铁锂电池结果会和你最终的建模结果强烈挂钩但数据源里并不会明确告诉你每一条记录对应的电压平台。所以这里遇到Ah我选择返回None宁可后续填充或丢弃也不能拿错误数据硬算。这里有另一个细节字符串里可能出现“60/100 kWh”这样的区间写法正则只会抓到第一个 60。这种情况我会在apply之后再检查一下电池容量_raw中是否还有包含/的值单独列出来人工处理。不要想着写一个万能正则一次搞定数据清洗的本质是分层处理。续航里程的处理逻辑和电池容量类似但它多了一个工况维度也就是 NEDC、WLTP、CLTC 的区别。我会把续航拆成两列一列是纯数值一列是工况类型。def parse_range(s): s str(s).strip().upper() if s NAN or s : return None, None num_match re.search(r(\d(?:\.\d)?), s) if not num_match: return None, None num float(num_match.group(1)) if NEDC in s or 新标 in s: condition NEDC elif WLTP in s: condition WLTP elif CLTC in s or CLTC-P in s: condition CLTC else: condition UNKNOWN return num, condition df[[续航里程数值, 续航工况]] df[续航里程].apply(lambda x: pd.Series(parse_range(x)))parse_range返回两个值分别存到两列。后续做对比分析时要么只保留同一个工况的数据要么把不同工况做系数折算。这里我多说一句不要轻易做工况折算。NEDC 和 WLTP 的差异不是固定系数能描述的它和车型、电池热管理、驾驶循环都有关系WLTP 数值普遍是 NEDC 的 0.75 到 0.9 倍但具体到每一款车差异很大。建模的时候更推荐的做法是把工况作为分类特征放进模型让模型自己去学这个差异而不是预先折算。2.3 单位与测试工况是两条独立的轴不要塞进同一列接上面的思路数据集的字段设计有个很容易犯的错把单位写在值里把工况写在续航数值后面比如“500kmNEDC”。这种设计对人来说很友好但对后续的查询、分组、画图都是灾难每次操作都得先解析字符串。我做表格的时候会把一切可计算的物理量拆成“数值列 单位列 口径列”三部分。电池容量拆成电池容量_kWh单位统一成 kWh续航里程拆成续航里程数值单位统一成 km工况单独一列续航工况。销售价格同理拆成价格_万元和价格_币种避免出现美元和人民币混在同一列里。这样设计的直接好处是 pandas 的groupby、describe、corr都能直接作用于数值列不需要每次都写自定义解析函数。df[价格_万元] pd.to_numeric(df[价格], errorscoerce) df[价格_币种] df[价格].apply(lambda x: USD if USD in str(x).upper() or $ in str(x) else CNY)errorscoerce会把无法转换成数字的值变成 NaN这样方便后续统一处理但要注意别把合法价格干掉了。比如“35.8万元”里有“万元”两个字to_numeric直接转不出来需要先去掉非数字字符再转。实际处理时我用的是先做单位拆列再去掉单位字符、转成纯数字顺序不能乱。单位识别错了后面所有按价格分组统计的结果都会偏。3. 对 3K 条真实电动汽车数据集做 EDA特斯拉、宝马、日产谁在卖什么电池3.1 品牌×电池容量分布看入门版和长续航版的分层清洗完字段就进入真正的探索性分析阶段。我先做品牌和电池容量的交叉分布重点看三个品牌的电池版本分层这能直接反映各家的产品策略特斯拉 Model 3 后驱版一般在 60kWh 左右长续航版到 75kWh 以上宝马 iX3 这种单版本车型容量集中在 74kWh 附近日产 Leaf 则可能在 40kWh 和 62kWh 两个档位上明显分开。eda df.groupby([品牌, pd.cut(df[电池容量_kWh], bins[0, 50, 65, 80, 100, np.inf])]).size().unstack(fill_value0) print(eda)pd.cut把电池容量分成四到五个区间这样比直接看原始分布更直观。这里的区间划分不是拍脑袋而是根据当前市场主流 EV 电池容量分布来定的小于 50kWh 一般是 A0 级小车50-65kWh 是紧凑型入门65-80kWh 是中型车主流水准80kWh 以上就是长续航或性能版。当然具体区间阈值可以根据数据集实际情况调整比如某一年份集中了一批 100kWh 以上的车型那就要单独加一个区间。这一步做完常见的三个结论模式是特斯拉的容量分布呈双峰甚至三峰对应标准版、长续航版、高性能版宝马比较集中说明产品线相对精简日产如果只有 Leaf 一款车那两档电池容量与年款强相关。这类结论是后续做车型分层建模的重要依据如果建模时把所有品牌混在一起电池容量对价格的影响会被“品牌溢价”这个更高权重的变量完全遮蔽。3.2 容量与续航的散点及相关系数为什么会被工况口径撕裂接下来做电池容量与续航里程的关系分析。直觉上容量越大续航越长但实际算相关系数往往只有 0.7 左右远达不到接近 1 的水平原因就出在续航工况不一致上。import matplotlib.pyplot as plt plt.figure(figsize(8, 6)) for condition, grp in df.groupby(续航工况): plt.scatter(grp[电池容量_kWh], grp[续航里程数值], labelcondition, alpha0.6) plt.xlabel(电池容量 kWh) plt.ylabel(续航里程 km) plt.legend() plt.show() print(df.groupby(续航工况)[续航里程数值].mean())这段代码同时画散点和分组均值。这里有一个经常会忽略的操作要点画图之前要确认续航工况列里没有混入“UNKNOWN”之外的错误值比如把 CLTC 写成 CTLC、把 WLTP 写成 WTLC。这种拼写错误不会报错但会生成一个单独的图例分组往往成为最显眼的离群点来源。我习惯先df[续航工况].value_counts()看一下枚举值是否干净。看到散点图以后不要急着下结论。如果图上出现明显的两个簇先想想是不是工况不同导致的而不是车型差异导致的。区分方法是给每个簇填充颜色如果颜色大多数按品牌或车系分布那就是产品策略差异如果颜色按工况分布那就是口径差异需要按工况拆分后重新审视曲线关系。3.3 按年款看 2025 年的销售记录结构数据集标记了“2025 年销售数据”这意味着每一行记录都有一个时间戳或年款字段。我一般会再盘一次年款分布确认这 3K 条记录是单一年份的还是横跨多个年份的。这两者对应完全不同的分析方式单一年份能做截面分析多年份能做趋势分析。if 销售日期 in df.columns: df[销售年份] pd.to_datetime(df[销售日期], errorscoerce).dt.year elif 年款 in df.columns: df[销售年份] df[年款].astype(int) print(df[销售年份].value_counts().sort_index())这里要注意一个坑pd.to_datetime处理“2025/3/15”和“2025-03-15”都没问题但遇到“2025.3.15”这种带中文句号的格式会全部返回 NaT。如果销售年份列大量缺失先把原始字符串的格式统计一遍再说不要急着fillna。大多数时候销售记录里同一年份会落在 2025 年但年款字段会显示“2024 款”“2025 款”这两个概念经常被混淆。我的处理建议是销售年份用于时间维度分析年款用于车型代际区分二者都保留不要合并成一个字段。4. 用电池规格和销售数据做价格模型从相关性筛选到一个能跑的 baseline4.1 特征选择哪些字段能进模型哪些只能当标签走到建模这一步先要明确一个问题你打算预测什么数据集里最合适的连续目标变量是“价格”因为价格直接受电池容量、品牌定位、车型级别、续航能力影响而且读者也最容易理解。续航里程也可以做目标但它和电池容量相关性太高做回归容易变成在学一个固定换算关系业务价值没有那么强。cat_cols [品牌, 车身形式, 电池类型, 续航工况] for c in cat_cols: if c in df.columns: df[c] df[c].astype(category) feat_cols [电池容量_kWh, 电机功率_kW, 车身形式_code, 品牌_code, 续航工况_code]分类变量转成类别型只是第一步建模前还要转成数值编码。LabelEncoder在这里是可以用的因为它处理的是无序分类变量对应的树模型输入对线性回归则不建议单独使用需要转成 one-hot。我用的是pd.get_dummies生成稀疏列直接喂给线性回归。特征范围方面以下是我的常见做法电池容量、电机功率、车身尺寸是物理特征无论线性模型还是树模型都可以直接用品牌、电池类型三元锂/磷酸铁锂、驱动形式是类别特征需要编码价格是目标变量单位统一为万元续航里程建议先不放进特征集因为它和电池容量的相关系数通常超过 0.75放进模型会掩盖其他特征的信息让回归系数的解读变得困难。4.2 最小可运行的回归 baseline训练、评估和最容易翻车的三种做法基线模型的搭建要遵守一个原则先跑通再调优。我不主张一上来就用 XGBoost第一步先用带了OneHotEncoder的线性回归把数据管线跑通确认没有空值和类型错误之后再考虑更复杂的模型。import numpy as np from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.preprocessing import OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.metrics import mean_absolute_error, r2_score # 构造完整的训练数据 model_df df.dropna(subset[电池容量_kWh, 价格_万元]).copy() # 分类特征做 one-hot数值特征直接保留 ct ColumnTransformer([ (num, passthrough, [电池容量_kWh, 电机功率_kW]), (cat, OneHotEncoder(handle_unknownignore), [品牌, 车身形式, 电池类型]) ]) pipe Pipeline([ (encoder, ct), (model, LinearRegression()) ]) X model_df[[电池容量_kWh, 电机功率_kW, 品牌, 车身形式, 电池类型]] y model_df[价格_万元] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) pipe.fit(X_train, y_train) y_pred pipe.predict(X_test) print(MAE:, mean_absolute_error(y_test, y_pred)) print(R2:, r2_score(y_test, y_pred))这段代码里最关键的是OneHotEncoder(handle_unknownignore)这个参数必须写。因为测试集里可能出现训练集没见过的车型或电池类型如果不忽略未知类别预测阶段直接报错。ColumnTransformer把数值列直接透传分类列做 one-hot线性回归拿到的是拼接后的稠密矩阵或稀疏矩阵sklearn 内部会自动处理不需要手动toarray。跑完基线你需要关注三件事第一MAE 绝对值是多少如果 MAE 超过 10 万元说明模型连“平均水平”都没学到第二R2 是不是负数如果测试集 R2 为负说明模型解释力比“直接取均值”还差大概率是特征没对齐或存在严重的口径错误第三特征系数是否可解释比如电池容量的系数应为正品牌的系数差异应大致反映品牌溢价。最容易翻车的不是模型本身而是这三件事一是划分训练集之前没有检查重复记录导致同一个车型的相近配置同时出现在训练集和测试集里MAE 低到失真二是没有做价格去除异常值的处理把“87 万”和“8.7万”混在同一个模型里R2 被离群点拉得很高但实际预测效果很差三是把 2025 年新上市的车型和 2020 款老车型混在一起训练时间跨度带来的配置差异会被模型误当成品牌差异或容量差异来学习。5. 把电动汽车数据集清洗干净四大常见坑全是这几年的血泪经验5.1 电池容量到底是 kWh 还是 Ah同列不同单位靠字符指纹来排查现象数据集的电池容量列里90% 的行是“60kWh”但少量行显示“4.0Ah”。直接画图时出现一个孤立点在 4.0 附近完全脱离主分布。原因部分型号的电池规格在数据源中用的是单体容量或电池包安时数经过抓取后混进了同一列。只写了 Ah 没有电压值就不可能准确换算成 kWh。解决单独把这部分记录筛选出来看电压上下文。如果同车型、同年款的其他记录有 kWh 值可以用这些值估算电压平台否则宁可删除这些行。我的筛选做法是用字符指纹。所谓字符指纹就是把每一行的容量字段转成“数字单位”的正则结构单位和数字分别存开这样value_counts一眼就能看出有哪些单位混入。unit_count df[电池容量_raw].str.extract(r([a-zA-Z\u4e00-\u9fa5])$)[0].value_counts() print(unit_count)这里$锚定字符串结尾把单位部分单独抓出来。统计结果如果出现“kwh”“千瓦时”“KWH”“Ah”等不同写法说明单位需要归一化。对kwh的大小写变体统一为小写再匹配对Ah的行不要做任何换算直接标记为缺失等后续做记录剔除或人工补数。5.2 NEDC 续航被当 WLTP 用模型误差直接放大一倍现象建模时把续航里程直接作为特征模型表现稳定但换一个数据集之后同一个模型的 MAE 翻倍。重新检查发现两个数据集的续航工况分布完全不同。原因续航里程的数值强弱由测试工况决定NEDC 普遍比 WLTP 高 10% 到 25%。模型学到了“续航里程”和“价格”之间的关系但这个关系其实是“工况 车型”混杂后的伪关系一旦数据分布变化预测立刻失效。解决永远把续航工况当成一个独立特征别写死在流程里。建模时要么按工况分别建模要么把工况当作类别特征参与训练。做预测的时候先确认输入数据的续航口径和你训练数据一致不一致就用保守策略——同一车型的当前工况加上 WLTP 与 NEDC 的经验差异做粗调整但调整系数不做全局统一而是分车系来处理。5.3 同一车型、同一电池版本2025 年记录里出现两条“看似重复”的行现象df.duplicated()查到多条完全重复的行看起来是采集重复了删掉后发现记录数从 3K 变成 2K 出头删得太多。原因这些“重复”并不是完全重复。用duplicated()默认的整行判断可能没问题但这类数据往往存在隐藏字段例如销售城市、经销商代号、内饰颜色或者根本没被显示在 DataFrame 里的子版本编号导致了同车型多条记录其实是合法的重复。解决先df.duplicated(subset[品牌, 车型, 电池容量_kWh, 续航里程数值, 价格_万元])看一下核心字段维度下的重复情况再做决定。如果核心字段重复但存在其他销售属性不同按业务逻辑处理dup_core df.duplicated(subset[品牌, 车型名称, 电池容量_kWh, 续航里程数值], keepFalse) dup_summary df[dup_core].sort_values([品牌, 车型名称]) print(dup_summary)处理策略上我习惯按“版本标识”来去重如果同车型同电池容量但价格不同通常是不同配置版本保留如果完全相同则删除重复行。如果数据集提供了 VIN 或配置单号优先用这个字段判断。5.4 销售价格与厂商指导价混用口径不统一做统计时偏差极大现象做品牌均价排序时特斯拉排到第一但细看发现特斯拉的价格用的都是官网指导价而宝马用的却是经销商终端的折后落地价两者完全不是一回事。原因采集来源不同。第三方数据集经常会把官网指导价和实际成交价放进同一个“价格”字段没有对齐口径。指导价通常高于最终成交价 3% 到 10%但在新能源车型上个别车型指导价和实际落地价能差 20% 以上。解决先看价格列的分布形态。如果大量价格的尾数都是 0 或 9且呈明显的分组特征多半是指导价如果价格有非常零散的小数位比如 29.98、30.51多半是成交价。对价格列做统计时优先按分位数而不是均值来报告。如果数据集中提供了价格类型或价格来源字段那就直接按价格类型分组不要混合统计。6. 自己给自己当复核用分组对比和排序稳定性检验 3K 条记录的数据质量6.1 分品牌核对均价与中位价建模做完了数据质量到底行不行不是由模型 R2 说了算而是要回头用最简单的统计量来检验。我会先按品牌计算价格的平均值、中位数、样本量再和公开渠道的标价对比看差异是否在合理范围内。price_check df.groupby(品牌).agg( record_count(价格_万元, count), mean_price(价格_万元, mean), median_price(价格_万元, median) ).round(2) print(price_check)这里用中位数和均值一起看是因为均值会被个别高端车型带偏中位数更能代表该品牌主力车型的价格带。如果特斯拉的均价在 35 万左右、中位数在 32 万到 36 万之间说明长续航和高性能版本比较多如果宝马中位数明显高于均值说明产品线里低配走量车型偏多。这些数字如果和常识相差太远不要怀疑市场的多样性先怀疑数据本身大概率是价格列混入了不同币种或把美元当人民币计价了。6.2 用秩相关替代线性相关做鲁棒性检验线性回归主要看变量之间的线性相关性而真实数据里往往有离群值。这种情况下我会用 Spearman 秩相关来对变量关系做一个非参数版本的验证而不是直接相信 Pearson 相关系数。from scipy.stats import spearmanr spearman_val, p_value spearmanr( df[电池容量_kWh].dropna(), df.loc[df[电池容量_kWh].notna(), 价格_万元] ) print(Spearman correlation:, round(spearman_val, 3))考虑到电池容量和价格之间有品牌这个混杂因素我会在做完整体相关系数之后再按品牌拆分做一次组内相关看相关方向是否在每组内保持一致。如果整体相关系数为正但某个大品牌内部相关系数变成负的那通常说明价格被“品牌档次”主导而不是被电池容量主导这时候线性回归输出的容量系数就没有稳定的业务解释力。这是我做数据核验时最常留的一个习惯任何一个数据集拿到手先做一次分组均值对比再跑一次秩相关花不了三分钟却能挡住大多数在清洗阶段埋下的隐患。希望这套流程能帮你在处理电动汽车数据集时少走弯路把精力留在真正有业务价值的分析上。本文还有配套的精品资源点击获取
网站建设高端定制企业官网