共変量に欠損があるとChronos-2の予測はどう変わるか

共変量に欠損があるとChronos-2の予測はどう変わるか

Amazonの時系列予測モデルChronos-2を3つのPythonパッケージ(chronos-forecasting、AutoGluon-TimeSeries、Darts)で利用する際、共変量の欠損値処理がパッケージごとに異なることを確認しました。その仕組みと差異を詳しく解説します。
2026.10.02

Chronos-2は、Amazonが公開している時系列予測の基盤モデルです。モデル本体はHugging Face Hubで公開されており、同じamazon/chronos-2を次の3つのパッケージから使えます。

  • chronos-forecasting
  • AutoGluon-TimeSeries
  • Darts

本記事では、過去共変量と未来共変量に欠損があるとき、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を作りました。

  1. context_df:2011年1月1日 00:00から2012年12月30日 23:00までの気温と利用台数
  2. future_df:2012年12月31日 00:00から23:00までの気温

context_dfの利用台数と欠損

上段の赤い印が、もともと行がなかった時刻です。この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%

過去と未来の気温が欠損した24時間予測

グラフはこちら
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%

過去と未来の気温を埋めた24時間予測

グラフはこちら
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")

参考

この記事をシェアする

関連記事