
共変量に欠損があるとChronos-2の予測はどう変わるか
Chronos-2は、Amazonが公開している時系列予測の基盤モデルです。モデル本体はHugging Face Hubで公開されており、同じamazon/chronos-2を次の3つのパッケージから使えます。
chronos-forecastingAutoGluon-TimeSeriesDarts
本記事では、過去共変量と未来共変量に欠損があるとき、3つのパッケージで予測が異なる仕組みを確認します。
データは自転車シェアリングに関するもので、予測対象は利用台数、共変量は気温です。本データの1コマは1時間です。
- 過去:2011年1月1日 00:00から2012年12月30日 23:00までの17,520コマ
- 予測:2012年12月31日 00:00から23:00までの24コマ
結論
過去共変量
- 欠損(行が無い)場合
chronos-forecastingは、行を作りません。Dartsは、行を足してNaNにします。AutoGluon-TimeSeriesは、行を足して直近の値で埋めます。
- 欠損(行がある)場合
chronos-forecastingとDartsは、NaNのままです。AutoGluon-TimeSeriesは、直近の値で埋めます。
未来共変量
- 欠損(行が無い)場合
chronos-forecasting、AutoGluon-TimeSeries、Dartsは、基本的に1コマでも行がないとエラーになります。
- 欠損(行がある)場合
chronos-forecastingとDartsは、NaNのままです。AutoGluon-TimeSeriesは、その未来の中の直近の値で埋めます。すべて欠損のときは、その過去の中の中央値で埋めます。
過去共変量の例です。2011年1月3日の02:00と03:00は、元データに行がありません。
| 日時 | 元データ 共変量(気温) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理あり) |
AutoGluon-TimeSeries(内部前処理あり) |
|---|---|---|---|---|
| 2011-01-03 01:00:00 | 8.20 | 8.20 | 8.20 | 8.20 |
| 2011-01-03 02:00:00 | 行なし | 行なし | NaN | 直近の値(8.20) |
| 2011-01-03 03:00:00 | 行なし | 行なし | NaN | 直近の値(8.20) |
| 2011-01-03 04:00:00 | 6.56 | 6.56 | 6.56 | 6.56 |
未来共変量の例です。2012年12月31日の24時間は、行があり、気温がすべてNaNです。
| 日時 | 元データ 共変量(気温) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理なし) |
AutoGluon-TimeSeries(内部前処理あり) |
|---|---|---|---|---|
| 2012-12-31 00:00:00 | NaN | NaN | NaN | 過去共変量の中央値 |
| 2012-12-31 01:00:00 | NaN | NaN | NaN | 過去共変量の中央値 |
| 2012-12-31 02:00:00 | NaN | NaN | NaN | 過去共変量の中央値 |
| 2012-12-31 03:00:00 | NaN | NaN | NaN | 過去共変量の中央値 |
※太字は、各Pythonパッケージが内部処理で加えた前処理結果です。
検証環境
Python: 3.11
chronos-forecasting: 2.3.2
autogluon.timeseries: 1.6.3
u8darts: 0.41.0
torch: 2.10.0
transformers: 5.14.1
scikit-learn: 1.7.2
pandas: 2.3.3
matplotlib: 3.11.2
japanize-matplotlib: 1.1.3
使用するデータ
OpenMLは、機械学習用データセットの共有サービスです。今回はscikit-learnのfetch_openml()を使い、OpenMLからBike Sharing Demandを取得します。これは、自転車シェアリングサービスの利用台数と、その時点の気温などが記録されたデータセットです。
利用台数の単変量予測に、気温を共変量として加えて予測します。今回は過去と未来の共変量である気温を、ともに欠損させます。
前処理はこちら
取得したデータには年・月・時刻を表す列がありますが、日を表す列がありません。データが時系列順に並んでいることを利用し、hourが前の行より小さくなった箇所を日付の切り替わりとして、日を補います。もともと行がなかった165時間は、行を足しません。
import pandas as pd
from sklearn.datasets import fetch_openml
bike_sharing = fetch_openml(
"Bike_Sharing_Demand",
version=2,
as_frame=True,
)
df = bike_sharing.frame.copy()
df["year_num"] = df["year"].astype(int)
df["month_num"] = df["month"].astype(int)
df["hour_num"] = df["hour"].astype(int)
df["day_num"] = (
df.groupby(["year_num", "month_num"], sort=False)["hour_num"]
.transform(lambda s: s.diff().lt(0).cumsum() + 1)
)
df["日時"] = pd.to_datetime(
{
"year": df["year_num"] + 2011,
"month": df["month_num"],
"day": df["day_num"],
}
) + pd.to_timedelta(df["hour_num"], unit="h")
df = (
df.rename(columns={"temp": "気温", "count": "利用台数"})
.assign(時系列ID="自転車シェアリング")
.sort_values(["時系列ID", "日時"])
.reset_index(drop=True)
)
prediction_start = pd.Timestamp("2012-12-31 00:00:00")
context_df = df[df["日時"] < prediction_start][["時系列ID", "日時", "気温", "利用台数"]].copy()
future_df = df[df["日時"] >= prediction_start][["時系列ID", "日時", "気温"]].copy()
Chronos-2の予測に必要な、次の2つのDataFrameを作りました。
context_df:2011年1月1日 00:00から2012年12月30日 23:00までの気温と利用台数future_df:2012年12月31日 00:00から23:00までの気温

上段の赤い印が、もともと行がなかった時刻です。この165時間は行がありません。下段は2011年1月2日〜4日と、2012年10月31日の24時間を拡大しています。
欠損値処理前
気温のNaNを、そのまま渡します。50%予測で比べます。誤差は12月31日の実測利用台数に対する値です。
過去は17,355行です。2011年1月3日の02:00と03:00は、行がありません。予測する24時間は行があり、気温はすべてNaNです。context_dfとfuture_dfを、3つのパッケージへ渡します。
future_df["気温"] = np.nan
chronos-forecasting
from chronos import Chronos2Pipeline
pipeline = Chronos2Pipeline.from_pretrained(
"amazon/chronos-2",
device_map="cpu",
)
pred_chronos = pipeline.predict_df(
df=context_df,
future_df=future_df,
prediction_length=24,
quantile_levels=[0.1, 0.5, 0.9],
id_column="時系列ID",
timestamp_column="日時",
target="利用台数",
freq="h",
)
AutoGluon-TimeSeries
from autogluon.timeseries import TimeSeriesPredictor
train = context_df.rename(
columns={"時系列ID": "item_id", "日時": "timestamp"}
)
known = future_df.rename(
columns={"時系列ID": "item_id", "日時": "timestamp"}
)
predictor = TimeSeriesPredictor(
prediction_length=24,
target="利用台数",
freq="h",
known_covariates_names=["気温"],
quantile_levels=[0.1, 0.5, 0.9],
).fit(
train_data=train,
hyperparameters={"Chronos2": {"model_path": "amazon/chronos-2"}},
)
pred_autogluon = (
predictor.predict(data=train, known_covariates=known)
.reset_index()
.rename(columns={"item_id": "時系列ID", "timestamp": "日時"})
)
Darts
from darts import TimeSeries
from darts.models import Chronos2Model
from darts.utils.likelihood_models import QuantileRegression
series = TimeSeries.from_dataframe(
df=context_df.assign(利用台数=context_df["利用台数"].astype("float32")),
time_col="日時",
value_cols="利用台数",
freq="h",
)
cov_df = pd.concat(
[context_df[["日時", "気温"]], future_df[["日時", "気温"]]],
ignore_index=True,
).sort_values("日時")
future_cov = TimeSeries.from_dataframe(
df=cov_df.assign(気温=cov_df["気温"].astype("float32")),
time_col="日時",
value_cols="気温",
freq="h",
)
model = Chronos2Model(
input_chunk_length=8192,
output_chunk_length=24,
likelihood=QuantileRegression(quantiles=[0.1, 0.5, 0.9]),
hub_model_name="amazon/chronos-2",
pl_trainer_kwargs={"accelerator": "cpu"},
)
model.fit(series=series, future_covariates=future_cov)
forecast = model.predict(
n=24,
future_covariates=future_cov,
predict_likelihood_parameters=True,
)
pred_darts = forecast.to_dataframe().reset_index().rename(
columns={
"利用台数_q0.100": "0.1",
"利用台数_q0.500": "0.5",
"利用台数_q0.900": "0.9",
}
)
pred_darts["時系列ID"] = "自転車シェアリング"
context_df
2011年1月3日01:00から04:00では、02:00と03:00は行がありません。利用台数です。
| 日時 | 元データ 予測対象(利用台数) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理あり) |
AutoGluon-TimeSeries(内部前処理あり) |
|---|---|---|---|---|
| 2011-01-03 01:00:00 | 2 | 2 | 2 | 2 |
| 2011-01-03 02:00:00 | 行なし | 行なし | NaN | NaN |
| 2011-01-03 03:00:00 | 行なし | 行なし | NaN | NaN |
| 2011-01-03 04:00:00 | 1 | 1 | 1 | 1 |
共変量(気温)です。
| 日時 | 元データ 共変量(気温) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理あり) |
AutoGluon-TimeSeries(内部前処理あり) |
|---|---|---|---|---|
| 2011-01-03 01:00:00 | 8.20 | 8.20 | 8.20 | 8.20 |
| 2011-01-03 02:00:00 | 行なし | 行なし | NaN | 8.20 |
| 2011-01-03 03:00:00 | 行なし | 行なし | NaN | 8.20 |
| 2011-01-03 04:00:00 | 6.56 | 6.56 | 6.56 | 6.56 |
future_df
2012年12月31日00:00から03:00では、利用台数はNaNです。
| 日時 | 元データ 予測対象(利用台数) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理なし) |
AutoGluon-TimeSeries(内部前処理なし) |
|---|---|---|---|---|
| 2012-12-31 00:00:00 | NaN | NaN | NaN | NaN |
| 2012-12-31 01:00:00 | NaN | NaN | NaN | NaN |
| 2012-12-31 02:00:00 | NaN | NaN | NaN | NaN |
| 2012-12-31 03:00:00 | NaN | NaN | NaN | NaN |
気温はすべてNaNです。AutoGluon-TimeSeriesは、過去データの中の中央値20.5で埋めます。
| 日時 | 元データ 共変量(気温) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理なし) |
AutoGluon-TimeSeries(内部前処理あり) |
|---|---|---|---|---|
| 2012-12-31 00:00:00 | NaN | NaN | NaN | 20.5 |
| 2012-12-31 01:00:00 | NaN | NaN | NaN | 20.5 |
| 2012-12-31 02:00:00 | NaN | NaN | NaN | 20.5 |
| 2012-12-31 03:00:00 | NaN | NaN | NaN | 20.5 |
予測結果
過去と未来の気温が欠損したまま渡すと、3つのパッケージの予測は異なります。
| 方法 | MAE(台) | RMSE(台) | MAPE |
|---|---|---|---|
chronos-forecasting |
36.48 | 57.88 | 71.33% |
AutoGluon-TimeSeries |
84.76 | 130.00 | 174.85% |
Darts |
46.58 | 66.28 | 93.09% |

グラフはこちら
import japanize_matplotlib
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
history = context_df.tail(48)
actual_df = df[df["日時"] >= prediction_start][["時系列ID", "日時", "利用台数"]].copy()
observed = pd.concat(
[history[["日時", "利用台数"]], actual_df[["日時", "利用台数"]]],
ignore_index=True,
).sort_values("日時")
forecast_colors = {
"chronos-forecasting": "#d62728",
"AutoGluon-TimeSeries": "#1f77b4",
"Darts": "#ff7f0e",
}
preds = [
(pred_chronos, "chronos-forecasting"),
(pred_autogluon, "AutoGluon-TimeSeries"),
(pred_darts, "Darts"),
]
fig, ax = plt.subplots(figsize=(14, 6), dpi=100)
ax.plot(
observed["日時"],
observed["利用台数"],
label="実測値",
color="black",
marker="o",
markersize=3,
)
for pred_df, method_name in preds:
ax.plot(
pd.to_datetime(pred_df["日時"]),
pred_df["0.5"],
label=method_name,
color=forecast_colors[method_name],
marker="o",
markersize=4,
)
ax.axvline(prediction_start, color="gray", linestyle="--", label="予測開始")
ax.set_xlim(observed["日時"].min(), observed["日時"].max())
ax.set(title="過去と未来の気温が欠損した24時間予測", xlabel="日時", ylabel="利用台数(台)")
ax.xaxis.set_major_formatter(mdates.DateFormatter("%m/%d %H:%M"))
ax.grid(alpha=0.3)
ax.legend()
plt.xticks(rotation=45)
plt.tight_layout()
fig.savefig("過去と未来の気温が欠損した24時間予測.png")
欠損値処理後
AutoGluon-TimeSeriesと同じ埋め方を、渡す前に行います。なかった165時間は行を足し、利用台数はNaNのまま、過去の気温は直近の値で埋めます。未来の気温は中央値20.5です。2011年1月3日の02:00と03:00は、8.20になります。context_dfとfuture_dfを、3つのパッケージへ渡します。
observed = context_df.set_index("日時")[["気温", "利用台数"]]
context_index = pd.date_range(observed.index.min(), observed.index.max(), freq="h")
hourly = observed.reindex(context_index)
context_df = (
hourly.rename_axis("日時")
.reset_index()
.assign(時系列ID="自転車シェアリング")[["時系列ID", "日時", "気温", "利用台数"]]
)
context_df["気温"] = context_df["気温"].ffill().bfill()
future_df["気温"] = context_df["気温"].median()
chronos-forecasting
from chronos import Chronos2Pipeline
pipeline = Chronos2Pipeline.from_pretrained(
"amazon/chronos-2",
device_map="cpu",
)
pred_chronos = pipeline.predict_df(
df=context_df,
future_df=future_df,
prediction_length=24,
quantile_levels=[0.1, 0.5, 0.9],
id_column="時系列ID",
timestamp_column="日時",
target="利用台数",
freq="h",
)
AutoGluon-TimeSeries
from autogluon.timeseries import TimeSeriesPredictor
train = context_df.rename(
columns={"時系列ID": "item_id", "日時": "timestamp"}
)
known = future_df.rename(
columns={"時系列ID": "item_id", "日時": "timestamp"}
)
predictor = TimeSeriesPredictor(
prediction_length=24,
target="利用台数",
freq="h",
known_covariates_names=["気温"],
quantile_levels=[0.1, 0.5, 0.9],
).fit(
train_data=train,
hyperparameters={"Chronos2": {"model_path": "amazon/chronos-2"}},
)
pred_autogluon = (
predictor.predict(data=train, known_covariates=known)
.reset_index()
.rename(columns={"item_id": "時系列ID", "timestamp": "日時"})
)
Darts
from darts import TimeSeries
from darts.models import Chronos2Model
from darts.utils.likelihood_models import QuantileRegression
series = TimeSeries.from_dataframe(
df=context_df.assign(利用台数=context_df["利用台数"].astype("float32")),
time_col="日時",
value_cols="利用台数",
freq="h",
)
cov_df = pd.concat(
[context_df[["日時", "気温"]], future_df[["日時", "気温"]]],
ignore_index=True,
).sort_values("日時")
future_cov = TimeSeries.from_dataframe(
df=cov_df.assign(気温=cov_df["気温"].astype("float32")),
time_col="日時",
value_cols="気温",
freq="h",
)
model = Chronos2Model(
input_chunk_length=8192,
output_chunk_length=24,
likelihood=QuantileRegression(quantiles=[0.1, 0.5, 0.9]),
hub_model_name="amazon/chronos-2",
pl_trainer_kwargs={"accelerator": "cpu"},
)
model.fit(series=series, future_covariates=future_cov)
forecast = model.predict(
n=24,
future_covariates=future_cov,
predict_likelihood_parameters=True,
)
pred_darts = forecast.to_dataframe().reset_index().rename(
columns={
"利用台数_q0.100": "0.1",
"利用台数_q0.500": "0.5",
"利用台数_q0.900": "0.9",
}
)
pred_darts["時系列ID"] = "自転車シェアリング"
context_df
2011年1月3日01:00から04:00では、02:00と03:00に行を足しています。利用台数はNaNのままです。
| 日時 | 元データ 予測対象(利用台数) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理なし) |
AutoGluon-TimeSeries(内部前処理なし) |
|---|---|---|---|---|
| 2011-01-03 01:00:00 | 2 | 2 | 2 | 2 |
| 2011-01-03 02:00:00 | NaN | NaN | NaN | NaN |
| 2011-01-03 03:00:00 | NaN | NaN | NaN | NaN |
| 2011-01-03 04:00:00 | 1 | 1 | 1 | 1 |
共変量(気温)は、直近の値です。02:00と03:00は8.20です。
| 日時 | 元データ 共変量(気温) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理なし) |
AutoGluon-TimeSeries(内部前処理なし) |
|---|---|---|---|---|
| 2011-01-03 01:00:00 | 8.20 | 8.20 | 8.20 | 8.20 |
| 2011-01-03 02:00:00 | 8.20 | 8.20 | 8.20 | 8.20 |
| 2011-01-03 03:00:00 | 8.20 | 8.20 | 8.20 | 8.20 |
| 2011-01-03 04:00:00 | 6.56 | 6.56 | 6.56 | 6.56 |
future_df
2012年12月31日00:00から03:00では、利用台数はNaNです。
| 日時 | 元データ 予測対象(利用台数) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理なし) |
AutoGluon-TimeSeries(内部前処理なし) |
|---|---|---|---|---|
| 2012-12-31 00:00:00 | NaN | NaN | NaN | NaN |
| 2012-12-31 01:00:00 | NaN | NaN | NaN | NaN |
| 2012-12-31 02:00:00 | NaN | NaN | NaN | NaN |
| 2012-12-31 03:00:00 | NaN | NaN | NaN | NaN |
共変量(気温)は、過去データの中の中央値20.5です。
| 日時 | 元データ 共変量(気温) | chronos-forecasting(内部前処理なし) |
Darts(内部前処理なし) |
AutoGluon-TimeSeries(内部前処理なし) |
|---|---|---|---|---|
| 2012-12-31 00:00:00 | 20.5 | 20.5 | 20.5 | 20.5 |
| 2012-12-31 01:00:00 | 20.5 | 20.5 | 20.5 | 20.5 |
| 2012-12-31 02:00:00 | 20.5 | 20.5 | 20.5 | 20.5 |
| 2012-12-31 03:00:00 | 20.5 | 20.5 | 20.5 | 20.5 |
予測結果
予測結果は3つのパッケージ共に小数第2位まで同じであることがわかりました。NaNのまま渡したAutoGluon-TimeSeriesと小数第2位まで同じです。
| 方法 | MAE(台) | RMSE(台) | MAPE |
|---|---|---|---|
chronos-forecasting |
84.76 | 130.00 | 174.85% |
AutoGluon-TimeSeries |
84.76 | 130.00 | 174.85% |
Darts |
84.76 | 130.00 | 174.85% |

グラフはこちら
import japanize_matplotlib
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
history = context_df.tail(48)
actual_df = df[df["日時"] >= prediction_start][["時系列ID", "日時", "利用台数"]].copy()
observed = pd.concat(
[history[["日時", "利用台数"]], actual_df[["日時", "利用台数"]]],
ignore_index=True,
).sort_values("日時")
forecast_colors = {
"chronos-forecasting": "#d62728",
"AutoGluon-TimeSeries": "#1f77b4",
"Darts": "#ff7f0e",
}
preds = [
(pred_chronos, "chronos-forecasting"),
(pred_autogluon, "AutoGluon-TimeSeries"),
(pred_darts, "Darts"),
]
fig, ax = plt.subplots(figsize=(14, 6), dpi=100)
ax.plot(
observed["日時"],
observed["利用台数"],
label="実測値",
color="black",
marker="o",
markersize=3,
)
for pred_df, method_name in preds:
ax.plot(
pd.to_datetime(pred_df["日時"]),
pred_df["0.5"],
label=method_name,
color=forecast_colors[method_name],
marker="o",
markersize=4,
)
ax.axvline(prediction_start, color="gray", linestyle="--", label="予測開始")
ax.set_xlim(observed["日時"].min(), observed["日時"].max())
ax.set(title="過去と未来の気温を埋めた24時間予測", xlabel="日時", ylabel="利用台数(台)")
ax.xaxis.set_major_formatter(mdates.DateFormatter("%m/%d %H:%M"))
ax.grid(alpha=0.3)
ax.legend()
plt.xticks(rotation=45)
plt.tight_layout()
fig.savefig("過去と未来の気温を埋めた24時間予測.png")








