ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

新能源汽车市场数据分析:多源采集、销量预测与充电桩聚类实战

新能源汽车市场数据分析:多源采集、销量预测与充电桩聚类实战 简介面向新能源汽车市场趋势研究与能源政策评估的数据采集与分析资源包整合汽车之家销量与售价、国际原油价格、中国充电桩分布等公开数据并配套线性回归与灰色预测销量建模、充电桩空间聚类分析等代码与数据适合高校师生、行业分析师及能源政策研究人员直接用于数据驱动的市场判断与政策复盘。压缩包共153个文件、60.04MB以33个xlsx和18个csv表格数据、9个Python脚本为核心辅以45个js采集脚本、ipynb分析笔记、json配置文件及说明文档覆盖数据抓取、清洗、建模、可视化完整链路目录层级清晰按数据、模型、图表分模块组织。当前已有43人学习。从中可获取汽车之家销量售价抓取工具、布伦特与WTI原油日/周度序列、充电桩空间分布与聚类分析模板以及灰色预测和线性回归的可执行样例能有效支撑新能源市场渗透率、区域充电需求及油价联动等议题研究。1. 新能源汽车市场研究这个数据平台到底在解决什么问题汽车销量数据、油价、充电桩位置这三类数据放到一起能回答的问题远比单看销量要深。比如某个月新能源汽车销量突然上涨政策补贴是主因还是油价突破某个阈值后的连带反应充电桩密度和区域销量的空间关系能不能提前一个季度做产能规划这些问题如果手头只有一张销量报表基本无从下手。这个标题所指向的平台本质上是把多源异构数据采集、销量预测建模、空间聚类分析三条线串起来为市场趋势研究和能源政策评估提供定量依据。整套方案的核心逻辑并不复杂先解决数据“有没有”和“干不干净”的问题再做两类预测——线性回归管短周期、依赖多因子的场景灰色预测管小样本、趋势相对平稳的场景最后用聚类把充电桩分布划成热点区域和销量预测结果叠在一起看政策落地的空间差异。适合的人群也很明确车企市场部做区域投放决策的分析师、充电运营商的网络规划人员、研究新能源政策效果的机构以及正在做数据分析毕业设计或项目实战的学生。实际动手时容易被卡住的点反而不在算法上而在数据采集的稳定性和多源数据口径对齐上。这篇笔记会按“数据采集 → 销量预测 → 空间聚类”的顺序把每步的参数设置、代码逻辑和踩过的坑拆开讲保证你照着能复现一套可用的分析流程。2. 多源数据采集与落库把波动数据变成可分析的稳定数据2.1 数据源拆分与采集边界哪些数据值得采、多久采一次先明确一下标题里四类数据的采集难度和更新节奏。汽车之家销量与售价数据属于半结构化网页数据销量按月更新、售价按车型配置差异较大采集频率设置为每周一次即可满足趋势分析。国际原油价格数据公开API很多日更频率建议每天定时拉取。中国充电桩分布数据属于空间点数据其分布随运营商建站节奏变化一月采集一次就够。最后一类数据——政策文本或补贴公告不在标题直接列出但如果你要做能源政策评估需要单独维护一张政策事件表按季度人工整理。采集架构上我不建议一上来就上分布式爬虫多数场景下用调度框架加轻量脚本就够了。常见做法是写一个基于 Python 的采集层每个数据源对应一个独立的 crawler 脚本输出统一为 CSV 或 JSON 暂存再通过一个 loader 脚本写入 SQLite 或 MySQL。SQLite 适合单机学习和验证项目规模变大后换 MySQL代码改动只集中在数据库连接层。# data_collector.py import requests import pandas as pd from datetime import datetime def fetch_crude_oil_price(api_url, api_key): headers {Authorization: fBearer {api_key}} resp requests.get(api_url, headersheaders, timeout10) data resp.json() df pd.DataFrame(data[records]) df[fetch_time] datetime.now().isoformat() return df def fetch_car_sales_from_local_html(html_path): # 汽车之家页面结构频繁变化不建议线上实时解析 # 更稳妥的方式是先用 selenium 保存已渲染的 HTML 快照再本地解析 df pd.read_html(html_path, encodingutf-8)[0] df[collect_date] datetime.now().date() return df def wash_and_save(df, table_name, db_engine): df.drop_duplicates(inplaceTrue) df.to_sql(table_name, db_engine, if_existsappend, indexFalse)逻辑说明每个函数只负责一个数据源返回统一格式的 DataFrame。原油价格通过带鉴权的 API 拉取销量数据从本地保存的 HTML 快照中解析避免对方网站频繁变动造成采集任务中断。参数说明timeout10是 HTTP 请求超时上限防止单个源卡死整个采集链路if_existsappend表示重复运行脚本时只追加新数据不会覆盖历史表。fetch_time和collect_date字段是审计字段后续排查数据异常时定位是哪个批次写入的。2.2 落库与调度配置cron 定时任务和断点续采采集脚本写好后要解决“怎么让它每天自动跑”的问题。我一般用 cron 做定时调度不用复杂的工作流引擎。三个采集脚本分开调度油价每天 8:00 执行销量每周一 09:00 执行充电桩每月 1 日 02:00 执行。错峰执行的好处是避免同一时间打满带宽和数据库连接。# crontab 配置示例 0 8 * * * cd /opt/nev_data python data_collector.py --source oil 0 9 * * 1 cd /opt/nev_data python data_collector.py --source sales 0 2 1 * * cd /opt/nev_data python data_collector.py --source charger # 断点续采采集到一半失败时用 --offset 参数跳过已处理部分 python data_collector.py --source charger --offset 5000断点续采需要在 crawler 内部加一个状态记录每完成一个分页或一个城市的数据就把当前偏移量写入一个status.json文件。下次启动时读取该文件从断点处继续。这个机制应对充电桩这种大批量数据时非常必要否则每次中断都要全量重采。采集过程中的数据质量校验有两个关键点。第一销量数据要校验“月度总量 各车型销量之和”不平就说明解析漏了行。第二油价数据要校验“日期唯一”同一个日期出现两条数据说明源站接口参数没带对日期格式。我在实际项目里吃过亏油价接口的日期参数有一次从YYYY-MM-DD变成了YYYY/MM/DD脚本没报错写进库里全是重复数据后来做线性回归时发现样本量异常膨胀回查才知道是这个原因。3. 销量预测与能源政策评估线性回归与灰色预测的选型与参数调优3.1 线性回归的输入特征怎么构造为什么不能只放历史销量很多初学者做销量预测直接把上月销量作为 X本月销量作为 y跑一个一元线性回归结果精度还行但可解释性很差。放在这个项目里更合理的做法是把特征分为三类价格类车型均价、电池成本指数、能源类国际原油价格月度均值、充电桩保有量环比、政策类补贴金额、新能源牌照发放量。这样才能回答“能源政策评估”这个目标——你能分离出每个因子对销量的边际贡献。在构造特征时比较合理的线性回归模型形式为sales β0 β1 * avg_price β2 * oil_price β3 * charger_count β4 * subsidy ε# linear_regression_sales.py import pandas as pd import statsmodels.api as sm df pd.read_sql(SELECT * FROM sales_features, db_engine) features [avg_price, oil_price, charger_count, subsidy] X df[features] y df[sales_volume] X sm.add_constant(X) model sm.OLS(y, X).fit() print(model.summary()) # 模型诊断检查残差是否随预测值增大而增大 resid model.resid fitted model.fittedvalues print(f残差与拟合值相关系数: {resid.corr(fitted):.3f})逻辑说明statsmodels的OLS返回完整回归报告包含每个特征的系数、P 值和置信区间。参数说明add_constant必须加否则模型会强制过原点导致截距项被错误归入某个特征的系数。残差与拟合值的相关系数如果接近正负 1说明存在异方差需要对 y 取对数或使用加权最小二乘。我看报告时重点看两个指标R-squared是否大于 0.7以及oil_price的系数是否显著为正。如果油价系数为负先检查是否存在多重共线性——油价和补贴往往在不同时期有反向变动相关性过高时考虑先用差分处理序列。3.2 灰色预测 GM(1,1)小样本场景下的销量趋势外推线性回归需要较多样本量一般要求 20 条以上才有稳定的参数估计。但新能源市场的细分车型数据往往只有 6 到 12 个月的销量记录这时候我习惯用灰色预测 GM(1,1) 补一段趋势外推。它的核心思路是对原始序列做一次累加生成让数据呈现指数增长规律再用一阶微分方程拟合。# grey_prediction_gm11.py import numpy as np def gm11(x0, predict_len3): x0 np.array(x0, dtypefloat) n len(x0) x1 np.cumsum(x0) # 一次累加生成 z1 (x1[:-1] x1[1:]) / 2.0 # 紧邻均值生成 B np.vstack([-z1, np.ones(n - 1)]).T Y x0[1:] # 最小二乘法估计参数 a发展系数和 b灰作用量 params np.linalg.lstsq(B, Y, rcondNone)[0] a, b params # 累加序列的预测 x1_pred np.zeros(n predict_len) x1_pred[0] x0[0] for k in range(1, n predict_len): x1_pred[k] (x0[0] - b / a) * np.exp(-a * (k - 1)) b / a x0_pred np.diff(x1_pred, prependx1_pred[0]) return x0_pred[:predict_len], a sales_series [8.2, 9.1, 10.5, 11.8, 13.6] pred, a_val gm11(sales_series, predict_len3) print(f下三个月预测值: {pred}) print(f发展系数 a {a_val:.3f}-a 小于 0.3 表示适合使用该模型)逻辑说明GM(1,1) 最核心的部分是最小二乘法拟合参数 a 和 bnp.linalg.lstsq返回最小范数解适合这里的小规模矩阵。参数说明-a的值是关键判断依据——小于 0.3 时模型可用于中短期预测0.3 到 0.5 之间仅适合短期预测超过 0.5 则说明序列波动太大灰色预测不适用应换用 ARIMA 或 Prophet。预测长度建议不超过原始序列长度的三分之一外推太久误差会指数放大。实际使用中我通常先算后验差比值 CC 小于 0.35 才认为模型精度等级为“好”低于这个标准就切回线性回归。3.3 两种模型如何配合先看样本量再看残差形态选型规则可以总结为样本量大于等于 24 个月优先用线性回归因为可解释性更强小于 24 个月用灰色预测因为它只需要 4 个以上样本点就能出结果。但两者不是互斥的——我在项目里经常用灰色预测做基准线再用线性回归的残差做修正相当于一个简单的集成。具体操作时有个重要步骤把两个模型放在同一张图上对比看残差的均值是否偏向同一侧。如果灰色预测在最近 3 个月系统性低估而线性回归最近 3 个月系统性高估说明市场受某个政策因素影响两者都没有捕捉到。这时候回到政策事件表查一下最近有没有补贴退坡或新牌照发放的政策公告。这个环节是我觉得对整个能源政策评估最有价值的——模型告诉你“发生了什么”人工补丁告诉你“为什么发生”。政策评估的量表我一般这样设计把每个月的预测误差拆成“政策事件影响”和“市场自然波动”两部分。具体用事件窗口法政策发布日前后各 15 天的销量均值和全月均值对比差值超过 5% 就认定该政策对市场有可观测影响。这个方法不需要复杂的因果推断模型但对政策评估来说足够稳健。4. 充电桩分布空间聚类分析从经纬度到区域热点的 K-Means 实战4.1 为什么用 K-Means而不是等网格划分或行政区划充电桩分布数据落到地图上是密密麻麻的点直接看不出规律。按行政区划统计是最常见做法但问题是行政边界和经济活动边界往往不对齐——一个大型产业园区可能横跨两个行政区的交界处充电需求连成一片按区统计就会割裂掉。等网格划分看似公平但网格大小难选定500 米×500 米在城市密度下可能全是热点在郊区又什么都分不出来。K-Means 聚类的特点是完全由数据密度驱动自动把经纬度空间里距离相近的点归成一群反映的是真实的充电需求几何而不是人为的行政边界。再加上标题里要的是“空间聚类分析”K-Means 是实现成本最低、结果最容易被业务解读的方法。# charger_clustering.py import pandas as pd from sklearn.cluster import KMeans import numpy as np df pd.read_csv(charger_points.csv) coords df[[longitude, latitude]].values # 经纬度转换为平面坐标近似纬度方向 1 度 ≈ 111 km经度方向乘以 cos(纬度) df[x_km] df[longitude] * np.cos(np.radians(df[latitude])) * 111 df[y_km] df[latitude] * 111 X df[[x_km, y_km]].values # 用肘部法则选择 K inertia [] for k in range(2, 11): km KMeans(n_clustersk, n_init10, random_state42) km.fit(X) inertia.append(km.inertia_) # 观察 inertia 下降曲线的拐点选择 K best_k 5 km KMeans(n_clustersbest_k, n_init10, random_state42) df[cluster_label] km.fit_predict(X) cluster_summary df.groupby(cluster_label).agg( count(charger_id, count), center_lat(latitude, mean), center_lon(longitude, mean), ) print(cluster_summary)逻辑说明代码先把经纬度转成以公里为单位的平面坐标核心原因是 K-Means 基于欧氏距离计算直接用经纬度会让聚类结果在高纬度地区变形。参数说明n_init10表示 K-Means 算法从 10 个不同初始中心点出发各跑一遍再取最优结果避免落入局部最优解random_state42固定随机种子保证结果可复现。肘部法则的原理是K 从 2 增加到 3 时簇内距离平方和下降幅度很大继续增加时下降变缓拐点对应的 K 就是数据结构的自然聚类数。实际看拐点要结合业务判断——如果拐点在 4 和 6 之间都不明显优先选更小数目的聚类便于运营决策落地。4.2 聚类结果如何与销量预测叠加输出政策评估的空间热点图聚类完成后要回答的核心问题是充电桩热点区域和新能源汽车销量高区域的重合度如何重合度高说明基础设施配套到位重合度低说明该区域存在“有桩无车”或“有车无桩”的供需错配。# overlay_analysis.py sales_by_district pd.read_excel(sales_by_district.xlsx) # 将销量区域与充电桩簇关联计算每个簇中心点所在的行政区 from sklearn.neighbors import KDTree tree KDTree(X) sales_coords sales_by_district[[longitude, latitude]].values _, idx tree.query(sales_coords, k1) sales_by_district[nearest_cluster] df.iloc[idx.flatten()][cluster_label].values cross_table pd.crosstab(sales_by_district[nearest_cluster], sales_by_district[sales_level]) print(cross_table)逻辑说明用 KDTree 做最近邻匹配把每个行政区中心点归到最近的充电桩簇。参数说明k1表示只找最近的一个邻居这个值不建议调大因为业务上只需要最直接的归属关系取多个邻居会让区域归属变得模糊。crosstab生成行列交叉表行是聚类编号列是销量等级高、中、低单元格数值代表区域数量。如果某个簇聚集了大量高销量区域但充电桩数量排名靠后这就是政策资源倾斜的空间候选点。200 字以外的细节在输出图表时我习惯用 matplotlib 的scatter绘制经纬度散点图颜色映射到cluster_label再叠加热力图库。但图表只是辅助沟通工具真正做政策评估时数字表比图更有说服力——跨表里可以直接算出“高销量低桩数”的具体区域名单这个名单才是决策者需要的。5. 踩坑排查四个数据模块里的高发问题与修复路径5.1 汽车之家销量数据解析时表格结构变化导致字段错位现象连续三周采集正常某天突然发现销量数字整体翻倍检查后发现 5 月销量变成了 4 月销量的两倍。原因汽车之家的销量表格从横向表头“厂商-车型-销量-同比”变成了“厂商-车型-新能源销量-总销量”的结构pd.read_html默认解析第一个表格没有做表头语义校验把两列销量数都读进来了。解决在解析脚本里增加表头断言要求必须包含“车型”和“销量”两个字段如果断言失败就发送告警暂停写入。代码里加一行assert 销量 in df.columns, 表格结构可能已变比读完再慢慢检查省事得多。还有一层防护是设置销量字段的合理值域比如单车型月销量大于 10 万辆或小于 100 辆时自动标红人工复核。5.2 原油价格数据日期时区和晚上预约任务时间不匹配现象每天早上的油价数据跑完当天下午的汇总报表里总缺当天的值要手动补一次。原因API 返回的日期是 UTC 时间而 cron 调度和业务报表用北京时间。早上 8 点执行任务时UTC 时间是凌晨 0 点当天的数据还没发布API 返回的是前一天的。解决把采集任务从 8:00 改到 10:30 执行并且请求 API 时显式传date2024-06-15格式不做默认的“今天”偏移。代码里统一用datetime.datetime.utcnow() timedelta(hours8)计算业务日期把这个日期转成字符串传给 API。5.3 灰色预测出现负的销量预测值现象某个车型的销量序列是[2.1, 1.8, 1.5, 1.3]GM(1,1) 预测下一个点直接变成-0.2。原因原始序列处于持续下降段灰色预测的发展系数 a 为正且偏大指数衰减外推穿越了零轴。这是灰色预测的已知边界它假设序列是近似指数规律强下降趋势且样本短时这个假设很容易破。解决先用预处理把下降序列转换为增长序列——对原序列取相反数再平移一个常数预测完成后反向还原。更稳妥的方法是当序列呈严格下降趋势时放弃灰色预测改用线性回归的下降趋势外推因为线性模型不会产生负值的情况。我在代码里加了判断如果x0[-1] x0[-2]直接走线性回归分支。5.4 K-Means 聚类中心落在海里或无人区现象聚类结果展示出来某个簇的中心点落在了一片水域或者山区的中央看起来很奇怪但该簇确实包含了很多充电桩。原因K-Means 计算中心点时用的是簇内所有点的均值坐标如果某个簇呈狭长带状中心点会被拉到经纬度的“中央”而真实桩群分布在两端。另一个原因是边界值噪声点的权重过大。解决输出报告中不用簇中心点坐标用每个簇的“重心桩”——即距离中心点最近的真实充电桩坐标。这个坐标一定有实际地址业务人员可以直接派单去现场核查。处理方式是centroid_idx np.argmin(np.linalg.norm(X - km.cluster_centers_[i], axis1))然后取对应充电桩的坐标。聚类完成后叠加行政边界数据进行校正把落在水域内的中心点标记为“待人工确认”。6. 平台落地进阶从单次分析脚本升级为可复用的工作流聚合分析做完你会发现每个脚本都是独立运行的换一个数据集或换一个城市维度就要重新改代码。我把这个项目里的工作流拆成四个阶段你可以按这个顺序做平台化升级。第一阶段数据版本管理。每次采集完成后为数据文件打上日期标签比如sales_2025_06.csv和crude_oil_2025_06.csv。这保证了三个月后回看分析结果时知道当时的模型是用哪些数据训练的。没有版本管理的数据分析项目本质上不可复现。第二阶段模型评分自动汇总。每次跑完线性回归后把 R²、AIC/BIC 和每个特征的系数写入一个model_metrics.csv。这样能看到某个政策发布前后油价系数的变化幅度这是评估政策影响的最直接证据。第三阶段定时输出报告。用 Jupyter Notebook 做分析报告模板通过papermill传参数执行生成带图表的 HTML 报告。报告内容包括销量预测表、充电桩聚类分布、政策事件对销量的影响区间。推送方式用邮件或企业微信机器人就看团队习惯了。进阶验证方面我强烈建议做一次伪样本外测试拿 2024 年 1 月到 8 月的数据训练模型预测 9 月到 11 月的销量再和真实值对比计算平均绝对百分比误差MAPE。灰色预测模型在这个测试里如果 MAPE 超过 15%说明数据上有突变事件需要把政策变量显式加入线性回归。测试脚本可以复用前面gm11函数只需要把预测目标改为 3 个月并和真实数据对齐。一个具体技巧供参考在聚类输出时把每个簇的充电桩数量和簇内新能源汽车销量做比值定义“每桩销量指数”。这个指数大于某个阈值比如 1.5的簇对应的行政区就是充电需求尚未被满足的区域优先扩建选址。这个指标比单纯看桩数或销量都更精准。我在不同城市跑这套方案的感受是数据清洗比建模费力三倍但清洗做得越扎实后面换模型、换参数时就越省心。线性回归给了我解释灰色预测给了我预测聚类给了我空间决策依据三者合起来才是一个支撑能源政策评估的完整拼图。这套流程做下来最大的收获不是跑通代码而是建立起“先看数据边界、再选模型、最后用业务指标验证”的决策习惯。希望帮到你。本文还有配套的精品资源点击获取
返回列表