予測時点で手元にあった情報を結ぶなら、右表の「利用可能になった時刻」をキーにして、direction="backward"を使います。 nearestは将来の行も選ぶので、時刻が近いだけでは予測の入力にできません。古い値を使える上限があるならtoleranceを設定し、結合後にどの時刻の行が付いたかを確かめます。
時系列の分析や予測用の表をpandasで作り始めた人向けです。以下では架空の倉庫で、出荷予測を出すときに見えていた在庫を復元します。同じ例で、出来事が起きた時刻と、システムから使えるようになった時刻の違いも確認できます。
利用できた時刻で結合し、右表の行をたどる
倉庫の予測と在庫を2種類の時刻で記録する
9時5分に倉庫Aの出荷予測を出すとします。在庫の計測は9時2分にありましたが、その数値がシステムに届いたのは9時6分です。9時5分に見えていたのは、8時54分に計測され、8時57分に届いた値でした。
事後分析の表には両方の値がそろっています。計測時刻だけで結合すると、9時5分の時点でまだ読めなかった値を使ってしまいます。右表には「実際に発生した時刻」と「処理に使えた時刻」を別々に持ちます。後日データが訂正されるなら、訂正前と訂正後を別行として保持し、訂正を取得した時刻も残します。
| 倉庫 | 計測時刻 | 利用可能時刻 | 在庫 | 9時5分から見た状態 |
|---|---|---|---|---|
| A | 08:54 | 08:57 | 12 | 取得済み |
| A | 09:02 | 09:06 | 7 | まだ取得前 |
| B | 09:00 | 09:04 | 30 | 別倉庫 |
5行を結合して、実際に選ばれた行を検算する
左表の順序を後で戻せるように依頼IDを入れ、右表からは在庫だけでなく利用可能時刻も残します。例の時刻は最初から日本時間と分かっているため、文字列から日時へ変換した後にAsia/Tokyoを付けています。時刻の由来が違う複数のログを結ぶ場合は、元の時刻情報を調べてから共通のタイムゾーンへそろえます。
import pandas as pd
requests = pd.DataFrame(
[
("A1", "A", "09:05"),
("A2", "A", "09:06"),
("A3", "A", "09:17"),
("B1", "B", "09:05"),
("C1", "C", "09:05"),
],
columns=["request_id", "warehouse", "prediction_at"],
)
stock = pd.DataFrame(
[
("A", "08:54", "08:57", 12),
("A", "09:02", "09:06", 7),
("B", "09:00", "09:04", 30),
],
columns=["warehouse", "observed_at", "available_at", "stock"],
)
def to_jst(values):
return pd.to_datetime("2026-11-10 " + values).dt.tz_localize("Asia/Tokyo")
requests["prediction_at"] = to_jst(requests["prediction_at"])
for col in ["observed_at", "available_at"]:
stock[col] = to_jst(stock[col])
def join_stock(direction="backward", exact=True, minutes=10):
return pd.merge_asof(
requests.sort_values("prediction_at"),
stock.sort_values("available_at"),
left_on="prediction_at",
right_on="available_at",
by="warehouse",
direction=direction,
allow_exact_matches=exact,
tolerance=pd.Timedelta(minutes=minutes),
).set_index("request_id").reindex(requests["request_id"])
result = join_stock()
assert result.loc[["A1", "A2", "B1"], "stock"].tolist() == [12, 7, 30]
assert result.loc[["A3", "C1"], "stock"].isna().all()
age = result["prediction_at"] - result["available_at"]
assert age.dropna().between(pd.Timedelta(0), pd.Timedelta("10min")).all()
print(result[["warehouse", "stock", "available_at"]])
A1は8分前の12、A2は同時刻の7、B1は1分前の30です。A3はAの最後の値から11分経っているので、10分以内の条件では欠損です。C1には同じ倉庫の候補自体がありません。欠損が同じ見た目でも、その理由は違います。
最も近い値と、過去に使えた値を切り分ける
pandasのmerge_asof公式リファレンス(2026年11月10日確認)には、左表の各行を起点に、右表のキーを過去方向、未来方向、または両方向から探す違いが記載されています。A1の左右の候補は8分前と1分後です。単純な近さなら1分後の7が選ばれます。
| 探し方 | 選択時刻 | 在庫 | 適した用途 |
|---|---|---|---|
| backward | 08:57 | 12 | その時点で使えた過去の値 |
| forward | 09:06 | 7 | 後で起きた最初の事象を調べる |
| nearest | 09:06 | 7 | 前後が許される記録の突き合わせ |
| backward・許容5分 | 該当なし | 欠損 | 古い値を採用しない入力 |
assert join_stock("nearest").at["A1", "stock"] == 7
assert join_stock("forward").at["A1", "stock"] == 7
assert pd.isna(join_stock(minutes=5).at["A1", "stock"])
例えばセンサーの時計ずれを見つける調査なら前後を許すこともあります。その表をそのまま予測の学習に使わないよう、どちらの目的で結合したかを書いておきます。差の符号も残しておけば、正の遅れと未来参照を別々に集計できます。
同じ時刻に取得した値を使ってよいか決める
A2は9時6分ちょうどの予測です。在庫の更新が終わってから予測を始める運用なら、同時刻の7を使えます。一方、ログが分単位しかなく、どちらの処理が先だったか分からないなら、同時刻の値を使ったと結論づけられません。
strict = join_stock(exact=False)
assert strict.at["A2", "stock"] == 12
allow_exact_matches=Falseは同じ時刻の候補を外します。この例では、それより前の8時57分が9分差で残ります。時刻の粒度を上げたり、処理の順番を表す連番を保持したりできるなら、同時刻を一律に捨てる前に、その情報で判断できるかを確かめます。
並べ替えと欠損を点検し、再計算できる形にする
倉庫より先に、結合キーが表全体で昇順か確認する
by="warehouse"は同じ倉庫の中で候補を探す指定です。結合キーの昇順は、倉庫ごとに並んでいるかではなく、表全体で確認します。例の右表を「倉庫、利用可能時刻」で並べると、Aの09:06の直後にBの09:04が来ます。倉庫ごとには正しくても時刻の列が逆行します。
結合に渡す左右の表を分けて保存し、is_monotonic_increasingで確認すると、どちらが逆行しているか見つけられます。日時へ変換できない行があれば、結合前に一覧へ出し、どの元データを直すか決めます。日付列にNaTが入る原因を検査する手順では、変換できなかった元の文字列を残す方法を扱っています。
欠損が多いときは、許容時間を外した対照表を作る
診断用の結合だけ許容時間を外し、通常の結果と依頼IDで突き合わせると、候補はあるが古いケースと、同じ倉庫で過去候補がないケースを分けられます。この例ならA3は「古い」、C1は「候補なし」です。通常の結果の欠損をゼロに置くと、在庫が本当にゼロの倉庫と区別できなくなります。
同じ倉庫・同じ利用可能時刻の右表が複数ある場合は、ソートだけで採用基準を決めないようにします。たとえば受信の連番が正しく付いているなら、同じ時刻の中で最大の連番を選んだことを記録します。連番がなく、値が衝突しているなら両行を保留にし、どちらがそのとき使えたかを確認します。更新日で重複行を選ぶときの検算も参考になります。
候補行の時刻、左表の行数、許容時間を記録する
検収では、左の依頼IDが一意か、結合の前後で件数が一致するか、採用行の時刻が条件どおりかを確認します。値そのものが欠損の右行もあり得るので、「行が見つかったか」は在庫列ではなく右の時刻や行IDで判断します。日付変更を含む実データでは日付つきのまま引き算を行います。
引き継ぎでは、例としてA1の元行と候補行を両方残し、「9時5分までに見えた直近、差は8分、上限は10分」と一文で再現できる形にします。許容時間は更新間隔と、古い値を使った場合の影響を見て決めます。倉庫ごとに更新間隔が違うなら、件数や欠損率を倉庫別に見てから、閾値を共通にするか決めます。
特徴量の時刻と、正解の確定時刻を別々に確認する
この例では予測に入れる在庫の取得時刻を守りました。過去の行を訓練に入れる際には、その行の正解が評価開始までに確定していたかも別に見ます。出荷数が翌日の締めで確定するなら、当日に予測した行をその日のうちに正解付きで訓練へ渡せません。TimeSeriesSplitのgapを決める手順では、その境界を検査できます。
まずA1を手計算し、次に許容時間を5分へ変えて欠損になることを説明できれば、結合の基本は確認できています。より長く学ぶ場合は、右表に同一時刻の訂正を1行足し、連番で決めた採用行が変わる範囲だけを検算してみてください。
結合を含むデータ分析の基礎を学び直す
日時型や表の操作から質問したい場合は、Python WinnerのPythonマスターを比較できます。公式案内ではPythonの基礎とデータ分析の基礎をマンツーマンで学べます(2026年11月10日確認)。時系列の近似結合が教材で扱われるかは、今回のようなサンプル表を相談に持参して確認してください。
よくある質問
Q. nearestは必ず過去の行を優先しますか?
前後から近いキーを探す指定なので、将来の行が近ければそちらが選ばれます。予測入力ではbackwardと利用可能時刻を組み合わせます。
Q. backwardなら未来参照を防げますか?
右のキーが本当にその値を使えた時刻であることが前提です。計測時刻の後で取り込まれた値や、上書きで消えた旧版も点検します。
Q. byも並べ替えますか?
公式リファレンスでは左右の結合キーの昇順が必要です。追加のby列の並べ替えは必須ではありません。
Q. toleranceの10分ちょうどは入りますか?
許容範囲の境界は採用条件に含まれます。運用で10分未満が必要なら、結合後に差を比較します。
Q. 右表に候補がない行は消えますか?
左の行は残り、対応する右側の列が欠損になります。件数と依頼IDを結合前後で照合すると確認できます。
Q. 同じ時刻を除くと次に古い候補も消えますか?
同時刻は除かれますが、過去方向で許容時間内に別候補があれば採用されます。