公的な統計には、とがあります。速報は集計が完全に終わる前に、早さを優先して公表されるもので、後から確報に置き換わります。そのとき数字が変わります。
から取る場合も、同じ期間を指定して取り直すと違う値が返ります。変わるのは直近の値だけとは限りません。統計によっては、数年前の数字までまとめて改定されることがあります。基準となる年を切り替えたときや、算出方法を見直したときです。
この性質は、取得と保存の設計に直接影響します。「一度取ったら確定」という前提で作ると、古い数字を持ち続けたまま、新しい数字と混ざる状態になります。
上書きすると、過去が消える
素直に作ると、同じ期間のデータは上書きします。2026年1月の値を取り直したら、既存の行を新しい値で置き換える。これは一見正しく、実際たいていの場合は問題ありません。
困るのは、過去に出したレポートを再現できなくなることです。3か月前に作った資料の数字と、今日同じ集計を回した数字が違う。どちらも正しいのですが、なぜ違うのかを説明できません。上書きしていると、当時の値がどこにも残っていないからです。
データの期間(2026年1月分)と、その数字が公表された時点は別の情報です。両方を列として持てば、「2026年3月時点で公表されていた2026年1月の値」を後から取り出せます。行数は増えますが、再現できるようになります。
import pandas as pd
def append_vintage(store: pd.DataFrame, new: pd.DataFrame) -> pd.DataFrame: """同じ期間の値でも、公表時点が違えば別の行として残す。
store / new ともに period(対象期間)と published_at(公表時点)を持つ想定。 """ for col in ("period", "published_at", "value"): if col not in new.columns: raise KeyError(f"必要な列が無い: {col}")
merged = pd.concat([store, new], ignore_index=True) # 同じ (期間, 公表時点) が二重に入るのは取得の重複。後勝ちで潰す return merged.drop_duplicates(subset=["period", "published_at"], keep="last")
def as_known_at(store: pd.DataFrame, when: str) -> pd.DataFrame: """指定した時点で「知られていた」値だけを取り出す。""" visible = store[store["published_at"] <= when] return ( visible.sort_values("published_at") .groupby("period", as_index=False) .last()[["period", "value", "published_at"]] )全部を残す必要はない
保存先を にしておけば、行が増えても読み込みは範囲を絞れます。公表のたびに全期間を残すと、量が急速に増えます。月次の統計を毎月全期間ぶん保存すれば、行数は月ごとに増え続けます。すべての統計でこれをやる必要はありません。
| 用途 | 残し方 |
|---|---|
| 社外に出す資料の根拠 | 公表時点ごとに全部残す |
| 社内の傾向把握 | 最新だけでよい |
| 改定の大きさを見たい | 速報と確報の2つだけ残す |
| 過去の判断を検証したい | 公表時点ごとに全部残す |
判断の基準は、あとで「当時の数字」を問われるかどうかです。問われる可能性があるなら残す、無いなら最新だけでよい。社外に出した数字は、ほぼ確実に問われます。
生データを残していれば、あとから作れる
公表時点ごとに残すのが大変に見えるなら、に取得した応答をそのまま置いておくだけでも足ります。取得日でフォルダを分けておけば、それ自体が公表時点の記録になります。整形した表は、必要になってから作れます。
この形なら、後から「やっぱり公表時点ごとに追いたい」となっても対応できます。逆に整形後の表だけを上書き保存していると、過去の値はどこにも残っていません。判断を先送りできるという意味で、生データを残す価値はここにもあります。
import jsonfrom datetime import datefrom pathlib import Path
RAW = Path("data/raw/stats")
def save_vintage(stat_id: str, payload, fetched: date | None = None) -> Path: """取得日でフォルダを分ける。同じ日に2回取っても同じ場所に落ちる(冪等)。""" fetched = fetched or date.today() d = RAW / stat_id / fetched.isoformat() d.mkdir(parents=True, exist_ok=True) path = d / "payload.json" path.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8") return path
def list_vintages(stat_id: str) -> list[date]: """保存されている公表時点の一覧。""" base = RAW / stat_id if not base.exists(): return [] return sorted(date.fromisoformat(p.name) for p in base.iterdir() if p.is_dir())季節調整値はさらに変わりやすい
を施した値は、原数値より頻繁に変わります。調整の計算には過去のデータ全体を使うため、新しい月が1つ増えるだけで、過去の調整値も微妙に変わります。
だから調整済みの値を長期保存する意味は薄く、原数値を保存して、必要なときに調整するほうが再現性が高くなります。公表されている調整済みの値を使うなら、公表時点を必ず添えてください。
また、調整済みの値と原数値を同じ列に混ぜないでください。季節の変動が入っているものと除かれているものを比較しても意味がありません。次回は、公的データによくある「人が読む形」の表を扱います。
数字が変わることを前提にした設計は、最初は面倒に見えます。ただ、一度この形にしておけば、後から「当時の数字は何だったか」と問われても答えられます。答えられないと、資料の信頼そのものが揺らぎます。
改定がいつ行われるかは、統計ごとに公表予定が出ています。これを見ておけば、いつ取り直せばよいかが分かります。毎日取りに行く必要はありません。
この問題は公的統計に限りません。社内の売上データでも、締め処理の前後で数字が変わります。後から確定する数字を扱うときは、同じ考え方がそのまま使えます。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。