介绍
天气是现实世界中发生许多事情的主要驱动因素。事实上,它非常重要,以至于将其纳入机器学习模型中通常会使任何预测模型受益。
想想以下场景:
- 公共交通机构试图预测系统中的延误和拥堵
- 能源供应商希望估计明天的太阳能发电量,以用于能源交易
- 活动组织者需要预测参与者的数量,以确保满足安全标准
- 农场需要安排未来一周的收获作业
可以说,在上述场景中,如果不将天气作为因素之一纳入模型中,该模型要么没有意义,要么不如其本来可能好。
令人惊讶的是,虽然有很多在线资源专注于如何预测天气本身,但几乎没有任何资源展示如何有效地获取和使用天气数据作为特征,即将其作为输入来预测其他东西。这就是本文要讨论的内容。
概述
首先,我们将强调使用天气数据进行建模所面临的挑战,介绍常用的模型以及提供商。然后,我们将进行案例研究,使用其中一个提供商的数据构建一个可以预测纽约出租车乘车次数的机器学习模型。
通过阅读本文,您将学到以下内容:
- 使用天气数据进行建模的挑战
- 可用的天气模型和提供商
- 处理时间序列数据的典型ETL和特征构建步骤
- 使用SHAP值评估特征重要性
本文作为数据科学博文的一部分发布。
挑战
衡量与预测天气
对于生产中的机器学习模型,我们需要(1)实时数据以产生实时预测和(2)大量历史数据以训练能够执行此操作的模型。

显然,当进行实时预测时,我们将使用当前的天气预报作为输入,因为它是关于未来发生情况的最新估计。例如,当预测明天的太阳能发电量时,我们需要的模型输入是关于明天天气的预报。
模型训练怎么办?
如果我们希望模型在实际世界中表现良好,训练数据需要反映实时数据。在模型训练中,我们需要选择使用历史测量数据还是历史预测数据。历史测量数据仅反映结果,即天气站记录的内容。然而,实时模型将使用预测而不是测量值,因为在模型进行预测时,测量值尚不可用。
如果有机会获取历史预测数据,应始终优先使用它们,因为这样可以在进行实时预测时以完全相同的条件下训练模型。

考虑以下示例:每当有大量云层时,太阳能发电厂将产生较少的电力。基于历史测量数据进行训练的模型将学习到当云覆盖特征显示高值时,几乎百分之百地意味着电力不足。另一方面,基于历史预测数据进行训练的模型将学习到另一个维度:预测距离。当进行数天之后的预测时,高云覆盖值仅是一个估计,并不意味着那天一定会多云。在这种情况下,模型在预测太阳能发电量时将只能部分依赖于这个特征,并在考虑其他特征时进行预测。
格式
天气数据≠天气数据。有很多因素可以排除一组特定的天气数据,使其变得完全没有用处。其中主要因素包括:
- 粒度:记录是否每小时、每3小时、每天都有?
- 变量:是否包含我需要的特征?
- 空间分辨率:一条记录涵盖多少平方公里?
- 预测范围:预测能提前多久?
- 预测更新:新的预测多久创建一次?
此外,数据的形状或格式可能难以处理。您需要创建的任何额外的ETL步骤可能会引入错误,并且数据的时间相关性可能会使这项工作变得非常繁琐。
实时 vs. 旧数据
一天或一周之前的数据通常以CSV转储、FTP服务器或者最好的情况是在单独的API端点上提供,但与实时预测端点通常有不同的字段。这会导致数据不匹配的风险,并且可能会增加ETL中的复杂性。
费用
费用可能会因供应商和所需的天气数据类型而有极大的差异。例如,供应商可能按每个坐标点收费,当需要许多位置时可能会成为问题。获取历史天气预报通常非常困难且昂贵。
天气模型
数值天气预报模型通常被称为模拟天气的各个方面的物理行为。有很多这样的模型,它们在格式(见上文)、覆盖的地球部分以及准确性方面都有所不同。
以下是最广泛使用的天气模型的快速列表:
- GFS:最为人知的标准模型,广泛使用,全球
- CFS:比GFS不太准确,用于长期气候预测,全球
- ECMWF:最准确但价格昂贵的模型,全球
- UM:英国最准确的模型,也提供全球数据
- WRF:开源代码,可生成DIY区域天气预报

供应商
供应商将天气模型的数据传递给最终用户。通常情况下,他们还在标准天气模型之上拥有自己的专有预测模型。以下是一些已知的供应商:
- AccuWeather
- MetOffice
- OpenWeatherMap
- AerisWeather
- DWD(德国)
- Meteogroup(英国)
BlueSky API
对于机器学习用例,上述提到的供应商要么不提供历史预测,要么获取和组合数据的过程都很繁琐且昂贵。相比之下,blueskyapi.io提供了一个简单的API,可以调用以获取实时和历史天气预报,格式相同,使数据的流水线处理非常简单。原始数据来自GFS,这是最广泛使用的天气模型。
案例研究:纽约出租车行程
想象一下,您在纽约拥有一家出租车公司,并希望预测出租车行程数量,以优化您的员工和车队规划。由于您可以访问纽约的历史组合出租车数据,您决定利用它并创建一个机器学习模型。

我们将使用可以从纽约网站下载的数据,点击这里。
首先导入一些库:
import pandas as pd
import numpy as np
import holidays
import datetime
import pytz
from dateutil.relativedelta import relativedelta
from matplotlib import pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
import shap
import pyarrow
预处理出租车数据
timezone = pytz.timezone("US/Eastern")
dates = pd.date_range("2022-04", "2023-03", freq="MS", tz=timezone)
为了获得我们的出租车数据集,我们需要遍历文件并创建一个按小时计数的聚合数据框架。这将需要大约20秒的时间来完成。
aggregated_dfs = []
for date in dates:
print(date)
df = pd.read_parquet(
f"./data/yellow_tripdata_{date.strftime('%Y-%m')}.parquet",
engine='pyarrow'
)
df["timestamp"] = pd.DatetimeIndex(
df["tpep_pickup_datetime"], tz=timezone, ambiguous='NaT'
).floor("H")
# 数据清洗,有时候会包含错误的时间戳
df = df[
(df.timestamp >= date) &
(df.timestamp < date + relativedelta(months=1))
]
aggregated_dfs.append(
df.groupby(["timestamp"]).agg({"trip_distance": "count"}
).reset_index())
df = pd.concat(aggregated_dfs).reset_index(drop=True)
df.columns = ["timestamp", "count"]
让我们来看看数据。前两天:
df.head(48).plot("timestamp", "count")
所有数据:
fig, ax = plt.subplots()
fig.set_size_inches(20, 8)
ax.plot(df.timestamp, df["count"])
ax.xaxis.set_major_locator(plt.MaxNLocator(10))
有趣的是,我们可以看到在一些假日期间,出租车乘车次数相对较少。从时间序列的角度来看,数据中没有明显的趋势或异方差性。
特征工程出租车数据
接下来,我们将添加一些在时间序列预测中常用的典型特征。
编码时间戳的各个部分
df["hour"] = df["timestamp"].dt.hour
df["day_of_week"] = df["timestamp"].dt.day_of_week
编码假日
us_holidays = holidays.UnitedStates()
df["date"] = df["timestamp"].dt.date
df["holiday_today"] = [ind in us_holidays for ind in df.date]
df["holiday_tomorrow"] = [ind + datetime.timedelta(days=1) in us_holidays for ind in df.date]
df["holiday_yesterday"] = [ind - datetime.timedelta(days=1) in us_holidays for ind in df.date]
BlueSky 天气数据
现在我们来到有趣的部分:天气数据。下面是如何使用 BlueSky 天气 API 的步骤。对于 Python 用户,可以通过 pip 安装:
pip install blueskyapi
但也可以直接使用 cURL。
BlueSky 的基本 API 是免费的。建议通过网站获取 API 密钥,因为这将提高可以从 API 获取的数据量。
通过付费订阅,可以获得额外的天气变量、更频繁的预测更新、更好的细粒度等,但对于本案例研究来说,这并不需要。
import blueskyapi
client = blueskyapi.Client() # 在这里使用 API 密钥来提升数据限制
我们需要选择位置、预测距离和感兴趣的天气变量。让我们获取一整年的天气预测,以匹配出租车数据。
# 纽约
lat = 40.5
lon = 106.0
weather = client.forecast_history(
lat=lat,
lon=lon,
min_forecast_moment="2022-04-01T00:00:00+00:00",
max_forecast_moment="2023-04-01T00:00:00+00:00",
forecast_distances=[3,6], # 提前几小时
columns=[
'precipitation_rate_at_surface',
'apparent_temperature_at_2m',
'temperature_at_2m',
'total_cloud_cover_at_convective_cloud_layer',
'wind_speed_gust_at_surface',
'categorical_rain_at_surface',
'categorical_snow_at_surface'
],
)
weather.iloc[0]
这就是我们在获取天气数据方面需要做的全部工作!
合并数据
我们需要确保天气数据正确映射到出租车数据上。为此,我们需要“目标时刻”——预报天气的时间。我们通过添加 forecast_moment + forecast_distance 来获取这个时间:
weather["target_moment"] = weather.forecast_moment + pd.to_timedelta(
weather.forecast_distance, unit="h"
)
在合并数据时,典型的问题是时间戳的数据类型和时区意识。让我们匹配时区来确保正确地合并它们。
df["timestamp"] = [timezone.normalize(ts).astimezone(pytz.utc) for ts in df["timestamp"]]
weather["target_moment"] = weather["target_moment"].dt.tz_localize('UTC')
作为最后一步,我们将出租车数据中的任何时间戳与最新的可用天气预报进行合并。
d = pd.merge_asof(df, weather, left_on="timestamp", right_on="target_moment", direction="nearest")
d.iloc[0]
我们的数据集已经完整了!
模型
在建模之前,通常有几个检查的步骤,比如目标变量是否平稳以及数据中是否存在缺失或异常值。然而,为了简单起见,我们将继续使用提取和创建的特征,直接拟合一个现成的随机森林模型:
d = d[~d.isnull().any(axis=1)].reset_index(drop=True)
X = d[
[
"day_of_week",
"hour",
"holiday_today",
"holiday_tomorrow",
"holiday_yesterday",
"precipitation_rate_at_surface",
"apparent_temperature_at_2m",
"temperature_at_2m",
"total_cloud_cover_at_convective_cloud_layer",
"wind_speed_gust_at_surface",
"categorical_rain_at_surface",
"categorical_snow_at_surface"
]
]
y = d["count"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.33, random_state=42, shuffle=False
)
rf = RandomForestRegressor()
rf.fit(X_train, y_train)
pred_train = rf.predict(X_train)
plt.figure(figsize=(50,8))
plt.plot(y_train)
plt.plot(pred_train)
plt.show()
pred_test = rf.predict(X_test)
plt.figure(figsize=(50,8))
plt.plot(y_test.reset_index(drop=True))
plt.plot(pred_test)
plt.show()
正如预期的那样,在测试集中与训练集相比,准确性有所下降。这可以改进,但总体而言,预测似乎是合理的,尽管在非常高的值方面有时保守。
print("MAPE is", round(mean_absolute_percentage_error(y_test,pred_test) * 100, 2), "%")
MAPE 为 17.16%
没有天气的模型
为了确认添加天气数据是否改善了模型,让我们将其与一个基准模型进行比较,该模型使用除天气数据以外的所有数据进行拟合:
X = d[
[
"day_of_week",
"hour",
"holiday_today",
"holiday_tomorrow",
"holiday_yesterday"
]
]
y = d["count"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.33, random_state=42, shuffle=False
)
rf0 = RandomForestRegressor(random_state=42)
rf0.fit(X_train, y_train)
pred_train = rf0.predict(X_train)
pred_test = rf0.predict(X_test)
print("MAPE is", round(mean_absolute_percentage_error(y_test,pred_test) * 100, 2), "%")
MAPE为17.76%
添加天气数据使出租车乘车预测的MAPE提高了0.6%。尽管这个百分比看起来不多,但对于企业的运营而言,这样的改进可能会产生重大影响。
特征重要性
除了指标之外,让我们来看看特征的重要性。我们将使用SHAP软件包,该软件包使用shap值来解释每个特征对模型的个体、边际贡献,即它检查一个个体特征对其他特征的贡献程度。
explainer = shap.Explainer(rf)
shap_values = explainer(X_test)
这将需要几分钟的时间,因为它在所有特征上运行了大量的“如果”场景:如果缺少任何特征,这将如何影响整体预测准确性?
shap.plots.beeswarm(shap_values)
我们可以看到,迄今为止最重要的解释性变量是一天中的小时和一周中的日期。这是很有道理的。出租车乘车次数在一天中和一周中变化很大,高度周期性。一些天气数据也被证明是有用的。当天气寒冷时,出租车乘车次数会增加。然而,温度在某种程度上也可能是出租车需求中一般年度季节性影响的代理。另一个重要的特征是阵风,当有更多的阵风时,出租车使用较少。这里的一个假设可能是在暴风雨天气下交通流量较小。
进一步模型改进
- 考虑从现有数据创建更多特征,例如将目标变量从前一天或前一周延迟。
- 频繁地重新训练模型将确保始终捕捉到趋势。当在真实世界中使用模型时,这将产生重大影响。
- 考虑添加更多外部数据,例如纽约的交通拥堵数据。
- 考虑其他时间序列模型和工具,如Facebook Prophet。

结论
就是这样!您已经创建了一个使用天气的简单模型,可以在实践中使用。
在本文中,我们讨论了天气数据在各个行业的预测模型中的重要性,以及有效使用天气数据所面临的挑战,并介绍了可用的数值天气预报模型和供应商,重点介绍了BlueSky API作为获取实时和历史预报的经济高效的途径。通过对纽约出租车乘车的预测案例研究,本文提供了一个实际操作的示范,展示了如何在机器学习中使用天气数据,教授您所有开始的基本技能:
- 时间序列数据的典型ETL和特征构建步骤
- 通过BlueSky API进行天气数据的ETL和特征构建
- 对时间序列进行简单随机森林模型的拟合和评估
- 使用shap值评估特征重要性
要点
- 尽管将天气数据整合到现有的机器学习模型中可能非常复杂,但现代天气数据服务(如BlueSky API)大大减轻了工作量。
- 将BlueSky的天气数据整合到模型中,增强了纽约出租车案例研究的预测准确性,突显出天气在日常运营中的可见实际作用。
- 零售、农业、能源、交通等许多行业以类似或更大的方式受益,因此需要良好的天气预报整合,以改善其自身的预测能力,并增强其运营效率和资源配置。
常见问题
本文中显示的媒体不属于Analytics Vidhya,并由作者自行决定使用。