月×国ごとの GROUP BY 集計 + 条件付き SUM による O(N) 実装
Transactions
テーブルから、
月(YYYY-MM)× 国
の組み合わせごとに以下の4指標を集計する問題です。
| id | country | state | amount | trans_date |
|---|---|---|---|---|
| 121 | US | approved | 1000 | 2018-12-18 |
| 122 | US | declined | 2000 | 2018-12-19 |
| 123 | US | approved | 2000 | 2019-01-01 |
| 124 | NULL | approved | 2000 | 2019-01-07 |
SUM は
NULL を返す →
COALESCE(...,0) 必須
GROUP BY は NULL
値を1つのグループとして保持しますが、pandas の
groupby はデフォルトで NULL
キーを除外します
SELECT
TO_CHAR(trans_date, 'YYYY-MM') AS month,
country,
COUNT(*) AS trans_count,
COUNT(*) FILTER (WHERE state = 'approved') AS approved_count,
SUM(amount) AS trans_total_amount,
COALESCE(SUM(amount) FILTER (WHERE state = 'approved'), 0) AS approved_total_amount
FROM Transactions
GROUP BY
TO_CHAR(trans_date, 'YYYY-MM'),
country;
import pandas as pd
def monthly_transactions(transactions: pd.DataFrame) -> pd.DataFrame:
"""
Returns:
pd.DataFrame: 列名と順序は
[month, country, trans_count, approved_count,
trans_total_amount, approved_total_amount]
"""
# ★ copy() 廃止: 必要列のみで軽量 DataFrame を新規構築
is_approved = (transactions['state'] == 'approved').astype('int8') # int8 で省メモリ
tmp = pd.DataFrame({
'month' : transactions['trans_date'].dt.to_period('M').astype(str),
'country' : transactions['country'],
'id' : transactions['id'],
'amount' : transactions['amount'],
'is_approved' : is_approved,
'approved_amt' : transactions['amount'] * is_approved,
})
out = (
tmp.groupby(['month', 'country'], sort=False, dropna=False) # ★ dropna=False
.agg(
trans_count = ('id', 'count'),
approved_count = ('is_approved', 'sum'),
trans_total_amount = ('amount', 'sum'),
approved_total_amount = ('approved_amt', 'sum'),
)
.reset_index()
)
# dtype を明示的に int に統一(NaN キー混在時の float64 混入を防ぐ)
out[['trans_count', 'approved_count',
'trans_total_amount', 'approved_total_amount']] = \
out[['trans_count', 'approved_count',
'trans_total_amount', 'approved_total_amount']].astype(int)
return out
groupby
はデフォルト
dropna=True で
country=NaN
のグループを無言で除外する。dropna=False
で NaN キーも1グループとして保持。
.astype('int8')
で承認フラグを 1 byte/行に圧縮(int64 比 87.5% 削減)。copy()
廃止で全列複製コストをゼロに。
フローの説明:
1. trans_date から
YYYY-MM
形式の月文字列列を生成する
2.
state == 'approved' の
boolean を
int8
に変換し、フラグ列・金額列を付与する
3. month × country で
GROUP BY。★
dropna=False
で
country=NaN
を保持する
4.
COUNT / SUM
で4指標を一括集計する
5.
COALESCE / astype(int)
で NULL・dtype を統一する
6. reset_index() で
month・country を通常列に昇格し返却する
| フェーズ | SQL | Pandas | 備考 |
|---|---|---|---|
| 月文字列生成 | O(N) | O(N) | ベクトル演算 |
| GROUP BY / groupby.agg | O(N) | O(N) | ハッシュ集計。G ≪ N なら線形近似 |
| COALESCE / astype | O(G) | O(G) | G = 月×国のユニーク数 |
| 合計 | O(N) | O(N) | sort=False でソートコスト回避 |
| 手法 | 時間 | 空間 | 可読性 | NULL 安全 |
|---|---|---|---|---|
| ✅ FILTER + COALESCE | O(N) | O(G) | ◎ | ◎ |
| CASE WHEN | O(N) | O(G) | ○ | 要 COALESCE |
| サブクエリ結合 | O(N) | O(G×2) | △ | 要注意 |
| pandas apply | O(N×G) | O(N) | ○ | △ |