Skip to content

Commit b9d2003

Browse files
committed
Docs: Refine Pandas copy=False/ROUND caveats and PostgreSQL FILTER/HashAgg claims
1 parent 157ffd7 commit b9d2003

2 files changed

Lines changed: 20 additions & 18 deletions

File tree

SQL/Leetcode/Basic select/1211. Queries Quality and Percentage/Claude Sonnet 4.6 Extended/Queries_Quality_and_Percentage_pandas.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,12 @@ df["poor"] = (df["rating"] < 3).astype("float64")
242242

243243
## 10) 改善戦略
244244

245-
| 戦略 | 手法 | 効果 |
246-
| ------------------------------------ | --------------------------------------------------- | ----------------------------- |
247-
| **不要列を触らない** | `.to_numpy()[mask]` で必要列だけを numpy 配列に抽出 | `result` 列のコピーゼロ |
248-
| **copy=False** | `pd.DataFrame({...}, copy=False)` | numpy 配列を参照渡し |
249-
| **`.to_numpy()` で集計後の値を取得** | pandas インデックスのオーバーヘッドを排除 | 丸め処理が高速化 |
250-
| **float32 は使わない** | `float32` は精度落ちで ROUND_HALF_UP が狂う | 精度保証のため `float64` 固定 |
245+
| 戦略 | 手法 | 効果 |
246+
| ------------------------------------ | --------------------------------------------------- | ------------------------------------------ |
247+
| **不要列を触らない** | `.to_numpy()[mask]` で必要列だけを numpy 配列に抽出 | `result` 列のコピーゼロ |
248+
| **copy=False** | `pd.DataFrame({...}, copy=False)` | numpy 配列を参照渡し(コピー回避を試みる) |
249+
| **`.to_numpy()` で集計後の値を取得** | pandas インデックスのオーバーヘッドを排除 | 丸め処理が高速化 |
250+
| **float32 は使わない** | `float32` は精度落ちで ROUND_HALF_UP が狂う | 精度保証のため `float64` 固定 |
251251

252252
```
253253
float32(0.07) = 0.07000000029802322... ← 精度落ちで丸めが狂う ❌
@@ -286,7 +286,7 @@ def queries_stats(queries: pd.DataFrame) -> pd.DataFrame:
286286
score = rat / pos
287287
poor = (rat < 3).astype("float64")
288288

289-
# Step4) 最小 DataFrame を copy=False で構築(numpy 配列を参照渡し
289+
# Step4) 最小 DataFrame を copy=False で構築(コピー回避を試みる
290290
tmp = pd.DataFrame({"q": names, "s": score, "p": poor}, copy=False)
291291

292292
# Step5) groupby + .mean()(named agg より高速な Cython パス)
@@ -324,7 +324,7 @@ def queries_stats(queries: pd.DataFrame) -> pd.DataFrame:
324324
+ pos = queries["position"].to_numpy(dtype="float64")[mask]
325325
+ rat = queries["rating"].to_numpy(dtype="float64")[mask]
326326
+ tmp = pd.DataFrame({"q": names, "s": rat/pos, "p": (rat<3).astype("float64")},
327-
+ copy=False) # 参照渡し
327+
+ copy=False) # コピー回避を試みる
328328
+ agg = tmp.groupby("q", sort=False, as_index=False).mean() # Cython 最適パス
329329
+ v = agg[["s","p"]].to_numpy() # pandas オーバーヘッド排除
330330
+ return pd.DataFrame({
@@ -341,19 +341,21 @@ def queries_stats(queries: pd.DataFrame) -> pd.DataFrame:
341341
| API / 手法 | 役割 | 最適化ポイント |
342342
| -------------------------- | ------------------------------- | ----------------------------- |
343343
| `.to_numpy()[mask]` | 列ごとに必要部分だけ抽出 | `result` 列を完全スキップ |
344-
| `pd.DataFrame(copy=False)` | numpy 配列を参照渡しで DF 構築 | メモリコピーゼロ |
344+
| `pd.DataFrame(copy=False)` | コピー回避を試みて DF 構築 | メモリコピーを極力減らす |
345345
| `.groupby().mean()` | Cython 最適化された集計パス | named `agg()` より高速 |
346346
| `agg[cols].to_numpy()` | 集計結果を numpy 配列として取得 | pandas インデックス管理を排除 |
347347
| `np.floor(v*100+0.5)/100` | ROUND_HALF_UP(SQL 互換) | ベクトル演算で全行一括処理 |
348348

349349
**NULL / 重複 / 型の保証:**
350350

351-
| ケース | 対処 |
352-
| ------------------- | ----------------------------------------------------- |
353-
| `query_name = NULL` | `.notna().to_numpy()` で mask を作成し明示除外 |
354-
| 重複行 | 仕様上カウント対象 → 除外不要 |
355-
| 型の精度 | `float32` は精度落ちのリスクあり → `float64` 固定 |
356-
| ROUND_HALF_UP | `np.floor(x*100+0.5)/100` で SQL `ROUND()` と完全一致 |
351+
| ケース | 対処 |
352+
| ------------------- | --------------------------------------------------------------------------- |
353+
| `query_name = NULL` | `.notna().to_numpy()` で mask を作成し明示除外 |
354+
| 重複行 | 仕様上カウント対象 → 除外不要 |
355+
| 型の精度 | `float32` は精度落ちのリスクあり → `float64` 固定 |
356+
| ROUND_HALF_UP | 本問の非負データ範囲では `np.floor(x*100+0.5)/100` は SQL `ROUND()` と一致※ |
357+
358+
> ※ 負の数に対する挙動(例: PostgreSQL `ROUND(-1.5)` = `-2` に対して上記数式は `-1` になる等)は異なりますが、本問の quality と poor_query_percentage は非負であるため問題ありません。
357359
358360
---
359361

@@ -389,7 +391,7 @@ final : 22.7 ms 7.97 MB ← result 列スキップ + copy=False
389391
flowchart TD
390392
A["入力: queries DataFrame<br>query_name / result / position / rating"]
391393
B["notna().to_numpy() → mask<br>bool配列 O(N)"]
392-
C["to_numpy()[mask] × 3列のみ<br>result 列を完全スキップ ✅<br>copy=False で参照渡し ✅"]
394+
C["to_numpy()[mask] × 3列のみ<br>result 列を完全スキップ ✅<br>copy=False でコピー回避を試みる ✅"]
393395
D["numpy ベクトル演算<br>score = rat / pos<br>poor = rat < 3 → float64"]
394396
E["pd.DataFrame(copy=False)<br>最小構成の一時 DF"]
395397
F["groupby.mean()<br>Cython 最適化パス 🔥<br>sort=False でハッシュのみ"]

SQL/Leetcode/Basic select/1211. Queries Quality and Percentage/Claude Sonnet 4.6 Extended/Queries_Quality_and_Percentage_postgresql.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ GROUP BY g.query_name;
111111
| ポイント | 詳細 |
112112
| ---------------------------------- | --------------------------------------------------------------------------------------- |
113113
| **`rating::NUMERIC / position`** | `INT / INT` は整数除算になるため、`::NUMERIC` で明示キャスト |
114-
| **`FILTER (WHERE rating < 3)`** | PostgreSQL 独自の条件付き集計。`SUM(CASE WHEN...)` より高速・簡潔 |
114+
| **`FILTER (WHERE rating < 3)`** | PostgreSQL 独自の条件付き集計。`SUM(CASE WHEN...)` に比べて簡潔で可読性が高い |
115115
| **`AVG(is_poor) * 100.0`** | is_poor を 0/1 にすると `AVG = 割合``* 100` でパーセント変換 |
116116
| **`ROUND(..., 2)`** | `NUMERIC` 型に対して正確に小数2桁を保証(`FLOAT` は誤差あり) |
117117
| **`WHERE query_name IS NOT NULL`** | 重複行ではなく、`GROUP BY` により独立したNULLグループが生成されるのを防ぐために除外する |
@@ -232,7 +232,7 @@ GROUP BY query_name;
232232
flowchart TD
233233
A["Queries テーブル<br>フルスキャン O(N)"]
234234
A_F["WHERE query_name IS NOT NULL<br>NULL行を除外"]
235-
B["GROUP BY query_name<br>Hash Aggregate"]
235+
B["GROUP BY query_name<br>HashAggregate / GroupAggregate<br>(プランナがコスト等から選択)"]
236236
C["AVG(rating::float8 / position)<br>FLOAT演算 CPU ネイティブ ⚡"]
237237
D["FILTER(rating < 3)<br>COUNT条件集計"]
238238
E["::NUMERIC キャスト<br>最後の1回のみ"]

0 commit comments

Comments
 (0)