課題 ── 依頼台帳から何を知りたいか

例の課題は、文書確認の依頼台帳(Excel、40 行、架空)から、「依頼部署と種別ごとに、依頼から完了までの日数と差戻しの割合を、月ごとに知りたい」というものです。台帳の列は、依頼ID・文書名・種別・依頼部署・依頼日・担当者・状態・指摘件数・完了日です。

この課題を、分解・抽象化・パターン認識・アルゴリズム設計・検証の 5 つの考え方で順に解きます。

1. 分解 ── 答えられる小さな問いに分ける

  1. 1 件ごとに「完了までの日数」を出す
  2. 1 件ごとに「差戻しか」を決める
  3. 月・部署・種別の組にまとめる
  4. まとめた数を表にして書き出す

大きな問いのままでは、どこから手を付けるかも、答えが正しいかも分かりません。「1 件について答えられる問い」まで分けると、1 行ずつ手で確かめられます。

2. 抽象化 ── 要らない情報を捨て、言葉を定義する

使うか理由
依頼日・完了日使う日数の計算に要る
状態使う差戻しか、完了しているかを決める
依頼部署・種別使うまとめる単位
依頼ID使う1 件を見分け、件数を数える
文書名・担当者使わない問いに関係しない。個人に関わる情報は、要らなければ持ち出さない
指摘件数使わない(今回)問いに無い

言葉の定義: 「完了までの日数」は、完了日 − 依頼日(状態が 完了 か 差戻し の行だけ)。「差戻しの割合」は、差戻しの件数 ÷(完了 + 差戻しの件数)。受付・確認中の行は、日数にも割合にも入れません。定義を先に書くと、Copilot に頼むときも、結果を確かめるときも、同じ物差しを使えます。

3. パターン認識 ── 同じ形の計算を見つける

「部署ごと」「種別ごと」「月ごと」は、どれも「行をある基準で組に分け、組ごとに同じ計算をして、結果を 1 つの表にまとめる」という同じ形です。pandas の文書は、この形を、基準で組に分ける・組ごとに関数を当てる・結果をまとめる、の 3 段として説明しています(groupby)。

月ごとの組は、日付を月の単位に変えれば同じ形になります。pandas の文書によると、Series.dt.to_period は日付を指定した頻度の期間に変えます(月なら "M")。

4. アルゴリズム設計 ── 順番を決めて書く

  1. 台帳を読む(pandas.read_excel)
  2. 状態が 完了・差戻し の行だけを残す
  3. 日数の列を作る(完了日 − 依頼日)
  4. 差戻しの印の列を作る
  5. 依頼月の列を作る(依頼日を月の期間に)
  6. 部署・種別・月の組にし、件数・日数の中央値・差戻しの割合を出す
  7. 表を新しいブックに書き出す

「残す → 列を足す → 組にする → まとめる」の順にすると、各段の途中の表を見て確かめられます。

集計のコード(見本の台帳で実行)

Python手元で動く
"""Tally a request ledger: days to finish and return rate by department, type and month."""
import pandas as pd

ledger = pd.read_excel("samples/request_ledger.xlsx", sheet_name="依頼台帳")
done = ledger[ledger["状態"].isin(["完了", "差戻し"])].copy()
done["日数"] = (pd.to_datetime(done["完了日"]) - pd.to_datetime(done["依頼日"])).dt.days
done["差戻し"] = done["状態"] == "差戻し"
done["依頼月"] = pd.to_datetime(done["依頼日"]).dt.to_period("M")

summary = (done.groupby(["依頼部署", "種別", "依頼月"])
               .agg(件数=("依頼ID", "count"), 日数の中央値=("日数", "median"), 差戻しの割合=("差戻し", "mean"))
               .reset_index())
summary["依頼月"] = summary["依頼月"].astype(str)
summary.to_excel("out_summary.xlsx", index=False)
print("rows in ledger:", len(ledger), "/ rows used:", len(done), "/ groups:", len(summary))
print(summary.head(8).to_string(index=False))
実行結果(2026-09-12)
rows in ledger: 40 / rows used: 25 / groups: 15
依頼部署      種別     依頼月  件数  日数の中央値  差戻しの割合
 人事部  Web 記事 2026-07   1     6.0     0.0
 人事部 プレスリリース 2026-07   1    12.0     1.0
 人事部     提案書 2026-07   1     5.0     0.0
 人事部     提案書 2026-08   1     3.0     1.0
 人事部    社内通知 2026-07   1    12.0     0.0
 人事部    社内通知 2026-08   3    14.0     0.0
 営業部  Web 記事 2026-07   1    14.0     1.0
 営業部     契約書 2026-08   1     5.0     0.0

5. 検証 ── 別の道で同じ答えになるか確かめる

  1. 組ごとの件数の合計が、残した行の数と一致するか
  2. 日数に負の値が無いか(完了日が依頼日より前の行が無いか)
  3. いちばん大きな組を選び、groupby を使わずに条件で絞り込んで数えた数と一致するか
  4. 差戻しの割合が 0 以上 1 以下か

検証のコード(assert が 1 つでも外れると止まる)

Python手元で動く
"""Check the ledger tally by a second route: invariants and one group counted by plain filtering."""
import pandas as pd

ledger = pd.read_excel("samples/request_ledger.xlsx", sheet_name="依頼台帳")
done = ledger[ledger["状態"].isin(["完了", "差戻し"])].copy()
done["日数"] = (pd.to_datetime(done["完了日"]) - pd.to_datetime(done["依頼日"])).dt.days
by_group = done.groupby(["依頼部署", "種別"]).size()

assert by_group.sum() == len(done), "group counts must add up to the rows used"
assert (done["日数"] >= 0).all(), "a finish date is earlier than its request date"

dept, kind = by_group.idxmax()  # the largest group
manual = ((ledger["依頼部署"] == dept) & (ledger["種別"] == kind) & ledger["状態"].isin(["完了", "差戻し"])).sum()
assert manual == by_group[(dept, kind)], "groupby and plain filtering disagree"
rate = (done["状態"] == "差戻し").mean()
assert 0 <= rate <= 1, "a rate must be between 0 and 1"
print("largest group checked by plain filtering:", dept, kind, int(manual), "rows")
print("all checks passed")
実行結果(2026-09-12)
largest group checked by plain filtering: 広報部 プレスリリース 5 rows
all checks passed

検証は、集計と同じ道(groupby)で数え直すのではなく、別の道(条件での絞り込み)で数えます。同じ道で数え直すと、同じ誤りを 2 回くり返すだけになりうるからです。

6. Copilot に頼むとき ── 分解と定義をそのまま指示にする

Copilot に書かせる指示の例

pandas で、Excel の依頼台帳(シート名 依頼台帳。列: 依頼ID, 文書名, 種別, 依頼部署, 依頼日, 担当者, 状態, 指摘件数, 完了日)を集計してください。 定義: 完了までの日数=完了日−依頼日(状態が 完了 か 差戻し の行だけ)。差戻しの割合=差戻しの件数÷(完了+差戻しの件数)。 依頼部署・種別・依頼月(依頼日の月)ごとに、件数・日数の中央値・差戻しの割合を出し、新しいブックに書き出してください。文書名と担当者の列は使わないでください。 あわせて、件数の合計が使った行数と一致すること、日数に負の値が無いことを確かめる assert を書いてください。

「差戻し率」とだけ書くと分母が決まらないため、受付や確認中の行を分母に入れたコードが返っても、指示に照らしては誤りと言えません。だから定義を指示に書きます。

まとめ ── 5 つの考え方の使いどころ

考え方問い例での答え
分解1 件について答えられる問いは何か日数、差戻しかどうか
抽象化何を捨て、言葉をどう定義するか文書名・担当者を捨てる。日数と割合を式で定義する
パターン認識同じ形の計算はどこか部署・種別・月の組ごとの集計(groupby)
アルゴリズム設計どの順で処理するか残す → 列を足す → 組にする → まとめる
検証別の道で同じ答えになるか合計の一致・負の値が無い・1 組を絞り込みで数える

同じ考え方を、1 つの作業を最初から最後まで通して使う例は 着想から運用までの通し手順 にあります。