祝日は、お客さんが少ないのでしょうか
自転車店には、貸出の表と、別に祝日の一覧があります。店長は「祝日は暇ですか」と聞きます。
どちらの表も、1つだけでは答えられません。貸出の表には祝日の列がなく、祝日の一覧には貸出がないからです。2つに共通する日付で、つなぐ必要があります。
このレッスンのゴール
メモ
共通の列で2つの表を結合し、結合が狙いどおりだったか確かめられる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 結合 | 列の値が同じ行どうしを、横に並べてつなぐこと | merge |
| キー | 2つの表が共通に持つ列で、相手の行を探す手がかり | date |
| 左結合 | 最初の表の行をすべて残す。相手がいなくても残す | |
| 内部結合 | 両方の表に相手がいる行だけを残す | |
| 相手なし | もう一方の表に見つからなかった行 | |
| 一意 | 列の中に1回しか現れないこと |
学生が入力するセルは、このプレビューでは空白です。
2つの表と、共通の列
データは、小さな表に分けて持つことがよくあります。1つの事実を、1回だけ書くためです。祝日の一覧は1回だけ書き、365行にコピーはしません。
両方を使うときは、表を結合します。2つの表が持つ列を選びます。それがキーです。片方の各行が、もう片方からキーの値が同じ行を探します。
2つ目の表を作る
レッスンバーのスタートを押し、ラボが動くまで待ちます。端末に Python はありません。Run は、docker exec jupyter でラボの中のコマンドを動かします。
ラボにあるのは hires.csv だけです。最初の Run が、2025年の祝日8日を入れた2つ目のファイル holidays.csv を書きます。
祝日の一覧を書く
docker exec jupyter sh -c "printf '%s\n' 'date,holiday' '2025-01-01,元日' '2025-01-13,成人の日' '2025-02-11,建国記念の日' '2025-03-20,春分の日' '2025-04-29,昭和の日' '2025-05-05,こどもの日' '2025-07-21,海の日' '2025-11-03,文化の日' > holidays.csv"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
読み返す
docker exec jupyter cat holidays.csvここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
左結合は、すべての行を残す
左結合は、最初の表から始めます。その行はすべて残ります。相手が見つかれば、新しい列が埋まります。見つからなければ、空のままです。
表に情報を足すときの、安全な選び方です。列を足しても、1日も失いません。
例: 3日分を結合した結果
| date | hires | holiday |
|---|---|---|
| 2025-03-19 | 129 | (空) |
| 2025-03-20 | 125 | 春分の日 |
| 2025-03-21 | 141 | (空) |
学生が入力するセルは、このプレビューでは空白です。
結合して、祝日を表示する
docker exec jupyter python3 -c "import pandas as pd; h=pd.read_csv('hires.csv'); b=pd.read_csv('holidays.csv'); j=h.merge(b, on='date', how='left'); print(len(h), len(b), len(j)); print(j['holiday'].notna().sum()); print(j[j['holiday'].notna()][['date','weekday','holiday','hires']].to_string(index=False))"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
手作業の結合を Python で
# 手作業の結合です。貸出の各行が、祝日の中から自分の日付を探します。
hires = [["2025-03-19", 129], ["2025-03-20", 125], ["2025-03-21", 141]]
holidays = {"2025-03-20": "春分の日"}
for row in hires:
name = "なし"
if row[0] in holidays:
name = holidays[row[0]]
print(row[0], row[1], name)
# holidays に "2025-03-21": "金曜" を足して、もう一度実行してください。
左結合、内部結合、外部結合
内部結合は、両方の表に相手がいる行だけを残します。外部結合は、相手の有無にかかわらず、両方の表の行をすべて残します。
選び方で行数が変わり、あとで出すすべての数が変わります。どの結合を使い、なぜそれにしたかを、言葉にしてください。
結合の種類ごとに行数を数える
docker exec jupyter python3 -c "import pandas as pd; h=pd.read_csv('hires.csv'); b=pd.read_csv('holidays.csv'); print(len(h.merge(b, on='date', how='inner')), len(h.merge(b, on='date', how='left')), len(h.merge(b, on='date', how='outer')))"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
グループに分けて、人数も読む
結合したら、問いを立てます。各日を祝日かどうかで分け、平日だけを残し、グループごとに貸出の平均を出します。
各グループに何日あるかも見ます。8日の平均は、253日の平均よりずっと弱い根拠です。
祝日と、ふつうの平日を比べる
docker exec jupyter python3 -c "import pandas as pd; h=pd.read_csv('hires.csv'); b=pd.read_csv('holidays.csv'); j=h.merge(b, on='date', how='left'); j['is_holiday']=j['holiday'].notna(); w=j[j['weekday'].isin(['Mon','Tue','Wed','Thu','Fri'])]; print(w.groupby('is_holiday')['hires'].agg(['count','mean']).round(1))"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
キーが重なると、行が増える
2つ目の表でキーが重なると、最初の表の行は、その写し1つごとに1回ずつ書かれます。表が大きくなり、あとの合計もすべて大きすぎます。
pandas は確かめてくれます。validate="one_to_one" を渡すと、キーが一意でないときにエラーで止まります。
祝日を1つ重ねて、もう一度結合する
docker exec jupyter python3 -c "import pandas as pd; h=pd.read_csv('hires.csv'); b=pd.read_csv('holidays.csv'); b=pd.concat([b, b.iloc[[0]]]); print(len(h.merge(b, on='date', how='left')), b['date'].is_unique)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
pandas に断らせる
docker exec jupyter python3 -c "import pandas as pd; h=pd.read_csv('hires.csv'); b=pd.read_csv('holidays.csv'); b=pd.concat([b, b.iloc[[0]]]); h.merge(b, on='date', how='left', validate='one_to_one')" 2>&1 | tail -n 1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
左結合なら行数は変わらない、と思いがちです。増えることがあります。左結合は最初の表の行を失いませんが、キーが重なるとその行の写しができます。結合の前と後で、行数を比べましょう。
さらに: 祝日の翌日
ノートブックで、祝日と hires.csv をもう一度結合します。祝日の翌日の、貸出の平均を出してください。
ヒント: 結合の前に、祝日の日付へ pd.to_datetime(...) + pd.Timedelta(days=1) で1日足します。先に、結果の予想を言葉にします。