ARTICLE DETAIL

资讯详情

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

基于Python的新能源汽车销量预测与充电桩聚类分析

基于Python的新能源汽车销量预测与充电桩聚类分析 简介该资源是一套面向新能源汽车市场趋势研究与能源政策评估方向的数据采集与分析工具包适用于高校师生、行业分析人员及数据竞赛参与者。资源围绕汽车之家销量与售价、国际原油价格、中国充电桩分布三大数据源配套线性回归与灰色预测销量分析、充电桩空间聚类分析等Python脚本并附有大量清洗后的xlsx/csv数据表可支撑从数据获取、处理到建模分析的全流程实践。压缩包共153个文件除核心数据与Python脚本外还包含js爬虫脚本、Selenium/PhantomJS驱动、配置说明及文档笔记整体约60MB结构较完整适合用于课程设计、课题研究或项目复现。目前已有43人学习下载属于小而精的领域资源包对希望快速获取多源数据并开展新能源市场量化分析的读者有直接参考价值。1. 新能源汽车数据采集与分析平台为什么销量预测必须和充电桩聚类放在一起一个做能源政策评估的朋友问过我一个问题油价涨了、充电桩密度上去了、某车型销量爆了这三件事到底谁推动谁单看销量曲线谁都能说一句“趋势向好”但要把这种判断写成有数据支撑的评估报告就绕不开数据采集、预测建模和空间分析三件事。这个标题里的平台就是把汽车之家销量与售价、国际原油价格、充电桩分布三类数据收拢到一起用线性回归和灰色预测做销量分析再用聚类分析处理充电桩空间分布的典型数据项目。适合三类人做新能源汽车市场研究的分析师、评估能源补贴与基建政策的从业者、以及想用 python 数据分析完整跑一遍“采集-清洗-建模-聚类”闭环的初学者。先说结论这个方向真正值钱的地方不在算法多深而在于把异源数据对齐到同一时间轴和地理坐标上。2. 三路数据的采集与落库汽车之家、原油价格和充电桩分布的获取方式2.1 汽车之家销量与售价数据字段、分页请求与落库结构汽车之家没有对外的开放 API常见做法是通过页面接口拿 JSON 数据。销量数据在“销量排行榜”页面售价数据在车型详情页的参数区。采集时我一般按“品牌-车型-月份”三个维度组织核心字段包括车型名称、所属品牌、销量辆、厂商指导价万元、经销商报价万元、上牌月份。下面的脚本是拿销量列表页的最小实现用 requests 拉取后用 json 解析直接入库。import requests import pandas as pd from datetime import datetime # 以汽车之家销量排行接口为例构造分页请求 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: https://www.autohome.com.cn/ } def fetch_sales_rank(page1, page_size20): url https://www.autohome.com.cn/rank/sales/ params {page: page, size: page_size} resp requests.get(url, headersheaders, paramsparams, timeout10) resp.raise_for_status() # 实际返回是 HTML 嵌入 JSON这里简化成解析逻辑 data resp.json() return data # 分页采集后转 DataFrame统一字段名 records [] for p in range(1, 6): # 先跑 5 页验证字段 raw fetch_sales_rank(pagep) for item in raw.get(list, []): records.append({ brand: item.get(brand_name), model: item.get(model_name), sales: item.get(sales_count), price_guide: item.get(guide_price), price_dealer: item.get(dealer_price), month: datetime.now().strftime(%Y-%m) }) time.sleep(1.5) # 每页间隔 1.5 秒别把对方服务器打疼 df_sales pd.DataFrame(records) df_sales.to_csv(sales_rank.csv, indexFalse, encodingutf-8-sig)这段代码的关键在请求节奏和字段映射。Referer头在很多反爬场景下比User-Agent更关键服务端会校验来源页面sleep(1.5)不是玄学是防止单位时间请求数过高触发封禁。字段映射时注意“销量”和“售价”的数据类型后面建模要做数值运算存 CSV 时先转成 float。第一次跑建议只取 5 页验证字段完整性而不是一次性拉全量——你无法预知接口哪天改了字段名小批量试错是数据采集的基本习惯。售价数据比销量数据更碎一个车型有“指导价”“经销商报价”“车主成交价”三个口径。做线性回归时我推荐用“经销商报价”因为它更接近真实交易价如果拿“指导价”做特征等于给模型输入了一个几乎不变化的常量信息量很低。落库时建议拆成两张表sales_monthly存销量price_daily存售价用车型 ID 关联而不是车型名关联因为不同年份改款车的名字会微调比如“秦PLUS”和“秦PLUS DM-i”在页面上是两个名字但底层 ID 是同一个。2.2 国际原油价格用 EIA 免费 API 而不是爬网页国际原油价格是新能源汽车销量的重要宏观特征逻辑是油价高时燃油车使用成本上升消费者会转向新能源。主流数据源是美国能源信息署EIA的开放 API免费、稳定、有历史深度比爬新闻网页靠谱得多。EIA API 用series_id定位数据序列布伦特原油现货价格对应POLL.BRENTWTI 西德克萨斯中质原油对应RWTC。下面是拉取月度布伦特油价的脚本。import requests import pandas as pd # EIA API Key 需要去官网免费申请填入自己的 key API_KEY your_eia_api_key SERIES_ID POLL.BRENT # 布伦特原油现货月度价格 url https://api.eia.gov/v2/petroleum/pri/spt/data/ params { api_key: API_KEY, frequency: monthly, data[0]: value, facets[series][]: SERIES_ID, start: 2018-01, end: 2024-12, sort[0][column]period: period, sort[0][direction]desc: desc } resp requests.get(url, paramsparams, timeout15) resp.raise_for_status() data resp.json() rows [] for item in data.get(response, {}).get(data, []): rows.append({ month: item[period], # 格式 YYYY-MM price: float(item[value]), unit: item.get(unit, USD/barrel) }) df_oil pd.DataFrame(rows) df_oil.to_csv(oil_price_monthly.csv, indexFalse, encodingutf-8-sig)EIA 返回的period字段是 ISO 格式字符串必须转成YYYY-MM才能和销量数据的月份对上。另一个坑是频率原油价格有日度、周度、月度三档做月度销量预测时统一用月度避免高频数据降采样带来的误差。参数里facets[series][]的写法是 EIA v2 接口的特殊语法不加这个参数它会把整个类目的数据全返回来动辄几十万行网络开销大而且容易超时。如果你只需要油价一个序列一定要按这个格式过滤。比较稳妥的做法是把这个拉取脚本做成定时任务每月 10 号左右跑一次EIA 月度数据会有约半个月的发布延迟。因为油价数据是平台里唯一完全免费且合规的数据源把它当作时间轴的基准锚点其他数据都向它看齐。2.3 中国充电桩分布数据公开统计口径与经纬度清洗充电桩分布数据有两个常见来源一是中国充电联盟EVCIPA每月发布的各省充电桩保有量统计表二是带经纬度的桩点数据集。前者适合做省级维度分析后者适合做空间聚类。如果目标是标题里的“充电桩空间聚类分析”必须拿到带经纬度的桩点数据字段至少包含经度、纬度、行政区划、充电类型直流/交流、功率kW。拿到原始数据后第一件事不是聚类而是清洗坐标。import pandas as pd df_pile pd.read_csv(charging_piles.csv, encodingutf-8-sig) # 过滤非法坐标经度必须在 [73, 135]纬度必须在 [18, 54]中国范围 df_pile df_pile[ (df_pile[lng] 73) (df_pile[lng] 135) (df_pile[lat] 18) (df_pile[lat] 54) ] # 去掉经纬度为 0 或缺失的行这些是录入错误不是真实桩点 df_pile df_pile[ (df_pile[lng] ! 0) (df_pile[lat] ! 0) (df_pile[lng].notna()) (df_pile[lat].notna()) ] # 行政区划字段规整省/市/区县后面聚类要按省份分组做 df_pile[province] df_pile[address].astype(str).str.slice(0, 3) df_pile df_pile.reset_index(dropTrue) # 简单看一眼清洗掉了多少数据心里有个底 print(f原始行数: {len(df_pile)} 行)经纬度清洗是充电桩数据采集里最容易翻车的一步。真实数据集中常混着(0, 0)坐标、超出中国范围的坐标、以及“地址写了但经纬度反了”的行。清洗规则不是越严越好过滤掉合法极值是不行的——比如新疆的经度确实到 73°E海南的纬度确实到 18°N边界值要保留。address字段切前三个字符拿省份的做法只对“xx省xx市xx区”格式有效如果原始数据是“xx市xx区”开头就得先补上省份再切否则聚类归并时会分错组。用充电联盟月度统计做省级总量采集时要注意口径差异“充电桩”和“充电枪”不是一回事一个桩可以有两把枪。统计表里如果标的是“公共充电枪数量”和桩点数据集合并时要换算否则省级总量和桩点聚类结果对不上。这个口径问题会在后面做政策评估时放大。3. 销量预测双模型线性回归处理宏观因素灰色预测兜住数据残缺3.1 线性回归建模特征滞后、多重共线性与时间序列自相关用线性回归预测新能源汽车销量不能把当月油价、当月充电桩数量直接塞进模型。原因在于传导时滞消费者看到油价上涨到改变购车决策通常有 1 到 3 个月的延迟充电桩建设投用到真正提升便利感也需要时间。所以特征要做滞后变换。下面的代码用statsmodels构建一个带滞后特征的 OLS 模型并输出关键诊断指标。import pandas as pd import statsmodels.api as sm from statsmodels.stats.outliers_influence import variance_inflation_factor # 假设 df 里已有四列sales(月销量), oil_price(月油价), pile_count(省充电桩数), avg_price(车型均价) df pd.read_csv(merged_model_data.csv, parse_dates[month]) df df.sort_values(month).reset_index(dropTrue) # 构造滞后特征油价滞后 2 个月充电桩数量滞后 3 个月售价滞后 1 个月 df[oil_lag2] df[oil_price].shift(2) df[pile_lag3] df[pile_count].shift(3) df[price_lag1] df[avg_price].shift(1) # 删除前几行空值滞后产生的 NaN只保留完整记录 df_model df.dropna().copy() # 定义自变量和因变量 features [oil_lag2, pile_lag3, price_lag1] X df_model[features] y df_model[sales] # 加常数项后拟合 OLS X_const sm.add_constant(X) model sm.OLS(y, X_const).fit() # 查看核心指标R²、F 统计量、每个变量的系数和 p 值 print(model.summary()) # 计算 VIF检查多重共线性VIF 10 说明特征之间自己打架 vif_data pd.DataFrame({ feature: X.columns, VIF: [variance_inflation_factor(X_const.values, i 1) for i in range(len(features))] }) print(vif_data)这里有一个新手常踩的坑shift()构造滞后特征后dropna()会切掉整个数据集的前几行。如果你的时间序列只有 24 个月滞后 3 个月意味着只剩 21 个样本参与回归样本量的缩减非常明显。所以滞后阶数不是越大越好要结合数据长度来定。我一般的原则是滞后总月数不超过样本量的四分之一比如 24 个月的数据最多滞后 2316 个月勉强能接受少于 18 个月就别用三特征了减到两个特征。model.summary()看什么三个数值决定模型能不能用P|t|小于 0.05 说明该特征显著R-squared高于 0.7 说明整体拟合不错Durbin-Watson通常在摘要底部接近 2 说明残差没有严重自相关。如果 DW 只有 0.8 或者 1.2说明销量数据本身有强趋势项回归模型的残差是“一串有记忆的错误”——预测后三个月时会系统性偏大或偏小。这时候两个选择给 y 做一阶差分再回归或者直接改用下面的灰色预测模型。还要补一句关于多重共线性的判断。油价和充电桩数量之间理论上存在传导关系——油价高了会刺激充电桩投资——所以 VIF 高不奇怪。VIF 超过 10 时不要急着删特征先看两个特征的相关系数。相关系数超过 0.8 且业务逻辑上确实同源就去掉一个相关系数不高但 VIF 高往往是因为样本量小导致的数值病态优先增加数据月份而不是删特征。3.2 灰色预测 GM(1,1)样本少、趋势明显时比回归更好用的算法线性回归对样本量有硬性要求少于 15 个月的数据训练出来的系数基本不可信。这时候灰色预测 GM(1,1) 就是更务实的选择。它是针对“小样本、贫信息”设计的预测方法只要求原始序列是非负序列理论上 4 个样本就能建模。原理不复杂对原始数列做累加生成弱化随机波动再用指数曲线拟合生成数列。实现代码不长核心步骤都在下面。import numpy as np import pandas as pd def gm11(series, forecast_len3): 灰色预测 GM(1,1) series: 原始序列比如过去 12 个月的销量 forecast_len: 预测未来几个月 series np.asarray(series, dtypefloat) n len(series) # 1. 级比检验判断序列是否适合用 GM(1,1) # 级比区间通常取 (e^{-2/(n1)}, e^{2/(n1)}) lambdas series[:-1] / series[1:] lower np.exp(-2.0 / (n 1)) upper np.exp(2.0 / (n 1)) if not (lower lambdas.min() and lambdas.max() upper): print(警告: 级比检验不通过建议对序列取对数或加平移量) # 常见补救: 开方或取 log series np.log(series) # 2. 累加生成序列 (AGO) ago np.cumsum(series) # 3. 构造数据矩阵 B 和值向量 Y B np.zeros((n - 1, 2)) for k in range(n - 1): B[k, 0] -(ago[k] ago[k 1]) / 2.0 # 紧邻均值 B[k, 1] 1.0 Y series[1:].reshape(-1, 1) # 4. 最小二乘估计参数 a, b # [a, b]^T (B^T B)^{-1} B^T Y coef np.linalg.inv(B.T B) B.T Y a coef[0][0] b coef[1][0] # 5. 累加预测值还原为原始序列预测 ago_pred np.zeros(n forecast_len) ago_pred[0] series[0] for k in range(1, n forecast_len): ago_pred[k] (series[0] - b / a) * np.exp(-a * k) b / a # 累减还原 pred np.zeros(n forecast_len) pred[0] series[0] for k in range(1, n forecast_len): pred[k] ago_pred[k] - ago_pred[k - 1] return pred # 示例用过去 12 个月销量预测未来 3 个月 sales_history [12890, 13560, 13980, 14200, 15120, 15800, 16230, 17100, 17680, 18200, 18900, 19500] pred gm11(sales_history, forecast_len3) print(未来三个月预测销量:, np.round(pred[-3:], 0))这段代码有四个关键点。第一级比检验是很多人跳过的步骤但不检验就直接建模等于赌运气序列有负值或增速忽高忽低时模型结果会完全失真检验失败后的np.log(series)是常用补救手段但注意预测结果也是对数域的要exp再还原。第二最小二乘估计里B.T B是 2×2 矩阵求逆在数值上稳定不需要额外处理。第三forecast_len不能设太长GM(1,1) 适合向后推 1 到 3 个周期推到第 6 个月误差会指数级放大。第四这个代码手写是为了让你看懂中间每一步工业级实现可以直接用greytheory库但自己写一遍能避开黑匣子恐惧。灰色预测在实际做新能源销量分析时有个妙用当作“基础趋势线”。线性回归的预测值是“条件期望”依赖宏观特征灰色预测的预测值是“惯性外推”只依赖销量自身。两者差距大说明宏观因素正在产生结构影响两者吻合说明市场处于自然增长通道。这种对比本身就是政策评估时的一张好图。3.3 双模型对比与业务解释什么时候信回归、什么时候信灰色预测线性回归和灰色预测各有各的适用边界不是简单选一个。我整理了一个使用对照在项目汇报时很有用。判断维度线性回归灰色预测 GM(1,1)样本量要求至少 20 个月数据越多越好4 到 15 个月即可数据波动性适合包含波动特征能解释波动适合平缓增长或近似指数增长解释性高每个特征都有独立业务含义低相当于一个整体趋势打包器外部变量接入可以接入油价、桩数、售价不能接入外部变量预测时域3 到 6 个月1 到 3 个月风险变量间共线性、自相关误判级比检验失败、长期外推失效实际操作中我的习惯是“双轨并行”先用灰色预测对每个车型的销量做 3 个月外推作为基准线再用线性回归做带外部特征的预测最后取加权平均。权重怎么定看回归模型的调整 R²。如果调整 R² 低于 0.5说明外部特征解释力弱灰色预测权重提到 0.7如果高于 0.7线性回归权重占 0.6。这个加权不是拍脑袋而是每个月做一次滚动回测看哪个模型在上个月预测得更准动态调整权重。双模型配合还有一个隐性收益它逼着你把数据切出“训练段”和“验证段”。我一般会预留最后 3 个月数据不参与建模单纯用来验证模型预测能力。很多数据项目训练时 R² 很好看一到真实预测就翻车原因就是没有做时序上的样本外验证——用随机抽样的方式切训练测试集对时间序列是完全错误的未来的数据不能参与训练这是时间序列建模的血泪经验。4. 充电桩空间聚类分析从经纬度散点到高密度充电活跃区4.1 为什么选 DBSCAN 而不是 KMeans密度不均与噪声容忍充电桩分布天然存在密度不均一线城市核心区桩点密集成片郊区零星分布高速服务区沿路呈条带状。如果用 KMeans 做聚类必须预设 K 值而且 KMeans 会把所有点都归到某个簇里——郊区那几个孤零零的桩也会被强行拉进一个簇结果就是簇的形状被噪声点扯得不像样。DBSCAN 的核心优势在于它不预设簇数量只依赖两个参数——邻域半径eps和最小样本数min_samples——并且能显式标记噪声点。对于充电桩这种空间分布极不均匀的数据DBSCAN 默认带噪声过滤的能力天然合适。选型理由还可以从政策评估角度补充KMeans 告诉你“这个城市大概有几片集中区域”这对城市规划不够DBSCAN 告诉你“哪些桩点构成高密度集群、哪些是孤立点”孤立点恰恰是充电覆盖盲区是政策要补的短板。两个算法输出的是两类问题。如果你已经在用 SPSS 聚类分析做市场细分那理解起来不困难——只是空间聚类必须算球面距离不能用欧氏距离下面细说。4.2 经纬度球面距离下的 DBSCAN自定义距离度量DBSCAN 的eps是距离阈值标准实现里用的是欧氏距离。但经纬度坐标不是平面坐标赤道上经度 0.01° 约等于 1.1 公里而在纬度 45° 地区经度 0.01° 只有约 0.8 公里。如果直接拿经纬度算欧氏距离eps在不同纬度下代表的物理距离完全不同。所以必须改成球面距离计算最常用的是 Haversine 公式。下面是我常用的实现用sklearn.cluster.DBSCAN配合自定义距离函数。import numpy as np import pandas as pd from sklearn.cluster import DBSCAN from math import radians, sin, cos, asin, sqrt def haversine(lat1, lon1, lat2, lon2): 两坐标点之间的球面距离返回公里数 R 6371.0 # 地球平均半径公里 dlat radians(lat2 - lat1) dlon radians(lon2 - lon1) a sin(dlat / 2) ** 2 cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon / 2) ** 2 return 2 * R * asin(sqrt(a)) def distance_matrix(coords): 构造坐标矩阵的两两距离矩阵公里 n len(coords) D np.zeros((n, n)) for i in range(n): for j in range(i 1, n): d haversine(coords[i, 1], coords[i, 0], coords[j, 1], coords[j, 0]) D[i, j] d D[j, i] d return D # 读取清洗后的桩点数据只保留经纬度两列 df pd.read_csv(charging_piles_cleaned.csv) coords df[[lng, lat]].values # 用预计算距离矩阵的方式跑 DBSCAN D distance_matrix(coords) model DBSCAN(eps3.0, min_samples5, metricprecomputed) labels model.fit_predict(D) df[cluster] labels # cluster -1 表示噪声点不属于任何簇 n_clusters len(set(labels)) - (1 if -1 in labels else 0) n_noise (labels -1).sum() print(f聚类簇数: {n_clusters}, 噪声点数量: {n_noise}) # 每个簇的中心点可以聚合出“充电活跃区” cluster_stats df.groupby(cluster).agg( pile_count(lng, size), center_lng(lng, mean), center_lat(lat, mean) ).reset_index() cluster_stats cluster_stats[cluster_stats[cluster] ! -1] print(cluster_stats.head())这段代码是纯 Python 双重循环算距离矩阵数据量超过 1 万点时会很慢。真实项目里 5 万桩点需要约 12.5 亿次 Haversine 计算跑之前先sample(frac0.3)抽样或者用sklearn.neighbors.DistanceMetric.get_metric(haversine)配合DBSCAN(metrichaversine)直接算。我上面的写法是为了把距离计算摊开讲清楚后续你自己选工程化方案。eps和min_samples怎么调先想业务含义充电桩密度高意味着什么在城市里两个桩相距 3 公里内且至少有 5 个桩就可以认为构成一个充电服务片区。所以eps3.0表示 3 公里半径min_samples5表示至少 5 个桩。调参不是玄学是从业务定义反推的。如果聚类结果里 40% 以上的点都是噪声说明eps太小或min_samples太大如果簇的形状拉成一条线说明eps过大把沿途的孤立点也拽进来了。建议画一张 K 距离图把每个点到第 5 近邻点的距离排序曲线拐点位置就是相对合理的eps。4.3 聚类结果落到政策评估与销量、油价数据的联合分析聚类输出的是“充电活跃区”的地理位置和规模。这个结果本身只算一张图真正的评估价值在于和销量数据叠加。常见做法是把聚类结果按省份聚合统计每个省份的高密度簇数量和簇内桩数占比再和该省新能源销量、该省平均油价做相关性分析。以我的项目经验高密度簇多但销量不涨的省份问题往往出在充电桩选址与居住区距离太远——桩多但不方便是典型的“重建设轻运营”。把聚类结果和销量数据关联时要注意一个统计陷阱省份是行政区划充电活跃区是地理实体一个活跃区可能跨越两省边界比如长三角地区。所以关联时用“簇中心点归属省份 簇覆盖半径内销量数据”来做空间连接而不是简单按省份名分组。用 GeoPandas 做空间连接最方便但普通场景下先用经纬度范围匹配也能跑关键是别在行政区划这个粒度上吃亏。政策评估还有一个常用联动指标“桩车比”。用聚类簇覆盖的桩数量除以簇周边 10 公里内的车辆销量估算值可以画出充电便利度的空间分布热力。桩车比低于某个阈值的簇就是政策补贴可以重点倾斜的区域。这一步把聚类结果从“描述性分析”推进到了“指导性决策”是整个平台价值兑现场景之一。5. 避坑手册数据采集和建模阶段最容易翻车的 5 个现场5.1 汽车之家价格字段是带单位字符串直接建摸报了类型错误现象price_guide字段拉下来是字符串形如12.98万直接pd.to_numeric报错或者转出来全是 NaN。原因页面 JSON 里的价格字段为展示方便带了中文单位还有少数车型返回暂无报价。这不是数据集脏是字段本身的设计问题。解决先把非数字字符清洗掉再统一转 float遇到暂无报价直接置为 NaN 并在建模前 drop 掉。df[price_guide] ( df[price_guide] .astype(str) .str.replace(万, , regexFalse) .str.replace(,, , regexFalse) .replace(暂无报价, pd.NA) .astype(float) )注意.replace(万, )不做单位换算原字段12.98万转完是 12.98这没问题因为回归里面用的是相对变化量单位一致即可。如果要做金额绝对值分析记得统一乘 10000 换算成元。5.2 灰色预测级比检验不通过模型输出负销量现象按 GM(1,1) 建模后预测未来销量后几个月出现负数或者原始序列前两个月涨了 60% 后续骤停级比超出范围。原因级比检验要求在区间(e^{-2/(n1)}, e^{2/(n1)})内序列波动太大不满足指数增长假设。强行建模后累减还原时从指数拟合值回减误差在尾部被放大成负值。解决先对原始序列做np.log()变换再建模预测完再np.exp()还原。如果取对数后级比仍不通过说明该序列不适合 GM(1,1)换线性回归或加个移动平均预处理。series_log np.log(series) pred_log gm11(series_log, forecast_len3) pred np.exp(pred_log)这个坑的本质是“方法选型与数据形态不匹配”。灰色预测对 S 形增长或倒 V 形波动序列表现很差用之前一定先跑级比检验别把检验当可选项。5.3 线性回归 R² 很高但 Durbin-Watson 只有 0.7现象OLS 摘要里 R²0.91看起来模型很成功但每个月销量和预测值的差值呈现明显延续性上个月多预测了 3000 辆这个月又多预测了 3000 辆。做样本外验证时误差逐月反向。原因销量序列本身有强趋势回归模型实际上是在拟合“时间趋势”而不是“因果关系”。残差强正自相关DW 接近 0 时说明回归模型没有捕获序列的时序结构。解决两个方向一是因变量改成一阶差分y_diff y.diff()对增量建模二是给模型加一个时间趋势项month_index作为特征把趋势单独让出来。我通常先试差分因为趋势项在样本外预测时不好外推。df[sales_diff] df[sales].diff() df_model_2 df.dropna().copy() # 用 sales_diff 做因变量保留原外部特征5.4 充电桩聚类结果全是一个大簇簇内桩数占了 90%现象DBSCAN 跑完后只有一个簇剩下一堆零散噪声。画出地图大簇从城市中心一直延伸到郊区没有区分度。原因eps设得太大2 到 3 公里的半径在城区能覆盖到几乎所有桩点导致全城连片。min_samples设得太小也让稀疏区的点全被吸纳进簇。解决缩小eps到 0.8 或 1.0 公里观察簇数量变化同时把min_samples提到 10 以上迫使只有真正密集的区域才能成簇。另外检查清洗完的数据是否混入了大量 4S 店内部桩而不是公共桩——内部桩和公共桩的空间分布模式完全不同一起聚类会模糊掉公共充电服务的真实布局。5.5 原油 API 和销量数据的月度对齐差一个月现象合并两张表后发现销量数据和油价数据的月份错位比如销量是 2024-03 的油价却是 2024-02 的预测结果整体偏移。原因EIA 的月度数据统计的是“月均价”发布在当月月初或上月末汽车之家销量统计的是“当月实际上牌数”发布在下月初。两个数据的语义时间戳都是月份但发布时点不同。解决合并前先明确用“业务发生月”作为对齐键而不是“数据发布时间”。规则是油价用当月的篮子月均价销量用当月上牌量两者同属一个自然月。写代码时把 EIA 的period字段和销量的month字段都转成YYYY-MM-01的 datetime然后用merge时要确认两边类型一致。df_oil[month] pd.to_datetime(df_oil[month] -01) df_sales[month] pd.to_datetime(df_sales[month] -01) df_merged pd.merge(df_sales, df_oil, onmonth, howleft)这个坑在数据合并时几乎必踩尤其在用 pd.read_csv 重新读入文件后原本的 datetime 类型会被重置成字符串合并键类型不一致时merge不会报错而是默默返回一堆 NaN特别隐蔽。6. 从一次性分析到滚动预测建立月度更新的验证闭环前面的模型和聚类做完如果不建立验证机制三个月后模型就会逐渐失真。我的建议是把平台改造成“月度滚动预测 复盘”的流水线每个月 10 号拉最新数据重新训练线性回归、重跑灰色预测、重新聚类充电桩然后把上个月的预测值和真实值对比计算 MAPE平均绝对百分比误差。这个验证习惯比堆叠更复杂的算法更能保证模型长期可信。验证脚本的核心很短就是算误差并记录import numpy as np # actual 和 predicted 分别是上个月的真实销量和三个月前的预测值 # 这里以三个月前预测的 2024-06 销量为例 actual np.array([18200, 19500, 21000]) predicted np.array([17600, 18900, 21800]) mape np.mean(np.abs((actual - predicted) / actual)) * 100 print(fMAPE {mape:.2f}%)MAPE 大于 15% 时我会先检查数据是否有更新口径变化再看灰色预测的级比检验是否失效MAPE 小于 8% 时基本可以继续用现有特征集。每次验证结果都追加到一张model_performance.csv里三个月后回头看模型能力是衰退还是稳定一目了然。聚类结果的月度更新还要做一件事对比两期簇中心点的位移。如果某个省份的簇中心明显向郊区外移说明充电基建正在从核心城区向外围蔓延这是政策评估里一个非常具体的信号——郊区充电便利度改善是否带动了当地销量提升可以直接把这个簇和对应月份的销量数据关联着看。我用这个位移指标写进过政策评估报告比单纯贴一张聚类图有说服力得多。做这个平台时我最深的一个感受是线性回归、灰色预测、聚类分析这些算法本身都不是新东西难的是让数据持续可信、可对齐、可解释。每次看到油价和销量月份对不齐、经纬度混进 0 值的桩点我都会提醒自己数据工程没有后悔药清洗规则写在采集阶段比建模阶段补救便宜十倍。希望这个方向能帮你在自己的数据项目里少走几段弯路。本文还有配套的精品资源点击获取
返回列表