From 5323785b9c3e0a4ac6936150030a1a17c018dca7 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Tue, 28 Oct 2025 12:49:34 +0900 Subject: [PATCH] SQL: Basic Select 596. Classes With at Least 5 Students --- .gitignore | 105 +++++++++ ...asses_With_at_Least_5_Students_mysql.ipynb | 160 ++++++++++++++ ...sses_With_at_Least_5_Students_pandas.ipynb | 200 ++++++++++++++++++ ...ses_With_at_Least_5_Students_posgres.ipynb | 96 +++++++++ requirements.lock.txt | 108 ++++++++++ 5 files changed, 669 insertions(+) create mode 100644 .gitignore create mode 100644 SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_mysql.ipynb create mode 100644 SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_pandas.ipynb create mode 100644 SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_posgres.ipynb create mode 100644 requirements.lock.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..51c04655 --- /dev/null +++ b/.gitignore @@ -0,0 +1,105 @@ +# ======================================== +# プロジェクト用 .gitignore +# ======================================== + +# バイトコード / キャッシュ +__pycache__/ +*.py[cod] +*$py.class +*.pyc +*.pyo +*.pyd + +# 仮想環境 +.env +.venv +.venv3.10.13 +env/ +venv/ +ENV/ +.Python + +# パッケージ管理 +*.egg +*.egg-info/ +dist/ +build/ +.eggs/ +wheels/ +pip-wheel-metadata/ +*.manifest +*.spec + +# ログ / 一時ファイル +*.log +*.pot +*.tmp +*.swp +*.swo + +# テスト関連 +.coverage +.tox/ +.nox/ +.pytest_cache/ +htmlcov/ +coverage.xml +*.cover +*.py,cover + +# Jupyter Notebook +.ipynb_checkpoints/ +*/.ipynb_checkpoints/* +*.nbconvert.ipynb + +# IDE / エディタ関連 +.vscode/ +.idea/ +*.sublime-project +*.sublime-workspace + +# OS依存ファイル +.DS_Store +Thumbs.db + +# MyPy / Pyre / Type checker +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ +.pytype/ + +# その他キャッシュ +.cache/ +.pybuilder/ + +# データファイル(大容量のもの) +data/raw/ +*.csv +*.xlsx +*.parquet +*.h5 +*.hdf5 + +# 機械学習モデル +models/ +*.pkl +*.joblib +*.pb + +# 画像・動画ファイル +*.png +*.jpg +*.jpeg +*.gif +*.mp4 +*.avi + +# 環境ファイル +config.ini +credentials.json + +# Node.js関連 +node_modules/ +package-lock.json +bun.lock diff --git a/SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_mysql.ipynb b/SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_mysql.ipynb new file mode 100644 index 00000000..f6c89d98 --- /dev/null +++ b/SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_mysql.ipynb @@ -0,0 +1,160 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "bc30cee6", + "metadata": {}, + "source": [ + "# MySQL 8.0.40\n", + "\n", + "## 0) 前提\n", + "\n", + "* エンジン: **MySQL 8**\n", + "* 並び順: 任意(`ORDER BY` を付けない)\n", + "* `NOT IN` は NULL 罠のため回避\n", + "* 判定は **ID 基準**(ここでは `class`)、表示は仕様どおりの列名と順序\n", + "\n", + "## 1) 問題\n", + "\n", + "* `Courses` から **受講生が5人以上いる class を求める**\n", + "* 入力テーブル例: `Courses(student, class)`(主キー: `(student, class)`)\n", + "* 出力仕様: 列は `class` のみ/順序任意/重複なし\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "> ウィンドウ関数で各クラスの人数を数え、閾値で抽出して最終投影。\n", + "\n", + "```sql\n", + "WITH win AS (\n", + " SELECT\n", + " class,\n", + " COUNT(*) OVER (PARTITION BY class) AS cnt\n", + " FROM Courses\n", + ")\n", + "SELECT DISTINCT\n", + " class\n", + "FROM win\n", + "WHERE cnt >= 5;\n", + "\n", + "Runtime 311 ms\n", + "Beats 58.52%\n", + "\n", + "```\n", + "\n", + "* `(student, class)` が PK のため同一学生の同一クラス重複は存在せず、`COUNT(*)` で十分\n", + "* 結果順は任意なので `ORDER BY` なし\n", + "\n", + "## 3) 代替解\n", + "\n", + "> 単純集約で十分なサイズ・要件なら `GROUP BY ... HAVING` が最軽量。\n", + "\n", + "```sql\n", + "SELECT\n", + " class\n", + "FROM Courses\n", + "GROUP BY class\n", + "HAVING COUNT(*) >= 5;\n", + "\n", + "Runtime 308 ms\n", + "Beats 62.45%\n", + "\n", + "```\n", + "\n", + "## 4) 要点解説\n", + "\n", + "* **方針**: クラス単位で人数を数え、しきい値(5)以上のみ返す\n", + "* **NULL / 重複**: 主キー制約により `(student, class)` の重複はなし。`class` 自体が NULL の行がある想定なら、条件側で `class IS NOT NULL` を併記(今回は問題仕様上不要)\n", + "* **安定性**: 並び順指定なしで I/O を節約\n", + "\n", + "## 5) 計算量(概算)\n", + "\n", + "* ウィンドウ(最適解): パーティション内で **O(N)**〜**O(N log N)**(実装依存)\n", + "* 集約(代替解): ハッシュ集約で **O(N)** 近似\n", + "\n", + "## 6) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[入力 テーブル Courses] --> B[クラス単位で人数カウント]\n", + " B --> C[人数が5以上を抽出]\n", + " C --> D[出力 class 列のみ]\n", + "```\n", + "結論から言うと、この問題では **`GROUP BY ... HAVING` が最もシンプルで、実務でもまずこれを使います。**\n", + "ただし速度面をもう一段伸ばす余地はあります。\n", + "\n", + "## 速くするための実務的ポイント\n", + "\n", + "### 1) 二次インデックスを追加(最有効)\n", + "\n", + "`GROUP BY class` の集約を軽くするには **`class` にインデックス**を張るのが一番効きます。\n", + "\n", + "```sql\n", + "-- 目的: クラス単位の集約をインデックス範囲走査で処理\n", + "CREATE INDEX idx_courses_class ON Courses(class);\n", + "-- 余力があれば順序付与用に\n", + "-- CREATE INDEX idx_courses_class_student ON Courses(class, student);\n", + "```\n", + "\n", + "* PK が `(student, class)` なので、そのままだと `class` での集約に不利。\n", + "* `idx_courses_class` があると MySQL は **インデックス順に走査しながらグループ化**でき、\n", + " 場合によっては **テンポラリやファイルソートを回避**します(`EXPLAIN` で `Using index for group-by` を目指す)。\n", + "\n", + "### 2) クエリは `GROUP BY ... HAVING` を採用\n", + "\n", + "ウィンドウ関数版は **同じクラスの行を全て数えてから DISTINCT** するので一手間多く、通常やや不利です。\n", + "以下で十分最適です。\n", + "\n", + "```sql\n", + "SELECT\n", + " class\n", + "FROM Courses\n", + "GROUP BY class\n", + "HAVING COUNT(*) >= 5;\n", + "```\n", + "\n", + "### 3) 「存在判定」最適化(インデックスがある前提の代替案)\n", + "\n", + "**「5件目が存在するか」だけ**を確かめる相関サブクエリは、クラスごとに **最大5行だけ**見れば良いので、\n", + "**`(class)` か `(class, student)` インデックス**がある環境では速くなることがあります。\n", + "\n", + "```sql\n", + "-- (class, student) インデックスがあると更に安定\n", + "SELECT DISTINCT c.class\n", + "FROM Courses c\n", + "WHERE EXISTS (\n", + " SELECT 1\n", + " FROM Courses i\n", + " WHERE i.class = c.class\n", + " ORDER BY i.student\n", + " LIMIT 4, 1 -- 5件目が取れれば「5人以上」と判定\n", + ");\n", + "```\n", + "\n", + "> 注意: これは **インデックスの効き**に強く依存します。`EXPLAIN` で内側が `range` / `ref` になっているか確認を。\n", + "\n", + "### 4) 実行計画チェック\n", + "\n", + "`EXPLAIN` で見るポイント\n", + "\n", + "* `type`: `range` / `ref`(全表 `ALL` は避けたい)\n", + "* `key`: 上記の新インデックスが選ばれているか\n", + "* `Extra`: `Using index for group-by` が出るとご機嫌\n", + "\n", + "## まとめ(提案の優先度)\n", + "\n", + "1. ✅ **`CREATE INDEX idx_courses_class (class)` を追加**\n", + "2. ✅ 本番クエリは **`GROUP BY ... HAVING COUNT(*) >= 5`**\n", + "3. ⭕ 負荷やデータ分布次第で、**`EXISTS + LIMIT 4,1`** 案を A/B して速い方を採用\n", + "\n", + "この3点で、提示の ~310ms からの短縮が十分見込めます。インデックス追加が難しい(LeetCode 等)なら、現状の **`GROUP BY ... HAVING` が最適解**です。\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_pandas.ipynb b/SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_pandas.ipynb new file mode 100644 index 00000000..9b27f966 --- /dev/null +++ b/SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_pandas.ipynb @@ -0,0 +1,200 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c3134608", + "metadata": {}, + "source": [ + "# Pandas 2.2.2用\n", + "\n", + "## 0) 前提\n", + "\n", + "* 環境: **Python 3.10.15 / pandas 2.2.2**\n", + "* **指定シグネチャ厳守**\n", + "* I/O 禁止、不要な `print` や `sort_values` 禁止\n", + "\n", + "## 1) 問題\n", + "\n", + "* `Courses` から **受講生が5人以上いる class を求める**\n", + "* 入力 DF: `Courses(student: str, class: str)`\n", + "* 出力: 列は **`class` のみ**(順序任意・重複なし)\n", + "\n", + "## 2) 実装(指定シグネチャ厳守)\n", + "\n", + "> 列最小化 → `groupby.size()` → 閾値抽出 → 最終投影。\n", + "> 並び順要件なしのため **ソートは行わない**。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def classes_with_at_least_five_students(Courses: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['class']\n", + " \"\"\"\n", + " # 列最小化して集約(pandas 2.2.2 の groupby.size は列名 'size')\n", + " agg = (\n", + " Courses[['class']]\n", + " .groupby('class', as_index=False)\n", + " .size()\n", + " )\n", + " # 5人以上だけを残し、仕様列のみ返す\n", + " out = agg.loc[agg['size'] >= 5, ['class']].reset_index(drop=True)\n", + " return out\n", + "\n", + "Analyze Complexity\n", + "Runtime 287 ms\n", + "Beats 34.25%\n", + "Memory 68.07 MB\n", + "Beats 65.28%\n", + "\n", + "```\n", + "\n", + "## 3) アルゴリズム説明\n", + "\n", + "* 使ったAPI\n", + "\n", + " * `DataFrame.groupby('class', as_index=False).size()`:クラスごとの件数を計算(`sort` せず、余計な列を持たない)\n", + " * `DataFrame.loc[...]`:件数の閾値(5)でフィルタ\n", + " * `reset_index(drop=True)`:返却 DF を連番インデックスに整える(列順は `['class']` のみ)\n", + "* **NULL / 重複 / 型**\n", + "\n", + " * 入力は `(student, class)` が主キー相当の前提だが、実装は `class` の件数を素直に数えるため、重複があればそのまま件数に反映\n", + " * `class` が `NaN` の行が存在する場合でも `groupby` は `NaN` をグループ化対象外にしない(pandas は `NaN` をキーとしては別扱いしないため、`NaN` を含めたカウントが不要なら事前に `dropna(subset=['class'])` を行う)\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* `groupby.size()`:**O(N)**(ハッシュ集約想定、グループ数を G とするとメモリは O(G))\n", + "* フィルタと最終投影:**O(G)**\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[入力 データフレーム Courses]\n", + " B[前処理 列最小化 class のみ]\n", + " C[groupby.size で件数集計]\n", + " D[件数が5以上を抽出]\n", + " E[出力 class 列のみ]\n", + " A --> B\n", + " B --> C\n", + " C --> D\n", + " D --> E\n", + "```\n", + "\n", + "いい感じの数値ですが、**まだ短縮の余地あり**です。ポイントは「**中間 DataFrame を作らない**」「**ソートしない**」「(可能なら)**カテゴリ化で bin count**」です。\n", + "\n", + "---\n", + "\n", + "## まずは“軽い差分”で速くする\n", + "\n", + "`groupby(...).size()` は中間 DF(`size` 列付き)を作るのでコスト増です。\n", + "**Series で完結する `value_counts(sort=False)` → 閾値抽出**に替えると、メモリアロケーションが減って速くなりやすいです。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def classes_with_at_least_five_students(Courses: pd.DataFrame) -> pd.DataFrame:\n", + " # 中間DFを作らず Series→Series のまま集計(ソートもしない)\n", + " cnt = Courses['class'].value_counts(sort=False) # dropna=True が既定\n", + " keep = cnt.index[cnt >= 5]\n", + " return pd.DataFrame({'class': keep}).reset_index(drop=True)\n", + "\n", + "Analyze Complexity\n", + "Runtime 248 ms\n", + "Beats 94.30%\n", + "Memory 67.26 MB\n", + "Beats 99.57%\n", + "\n", + "```\n", + "\n", + "* 並び順要件なし ⇒ **sort=False** は必須(既定は True で無駄に並び替えます)\n", + "* `value_counts` は `groupby.size` より**割り当てが少なめ**で速いことが多いです\n", + "\n", + "---\n", + "\n", + "## さらに攻める(大規模・繰り返し呼び出し向け)\n", + "\n", + "### 1) `category` による **bin counting**\n", + "\n", + "文字列キーが巨大なときは **カテゴリ化**して整数コードで `np.bincount`。\n", + "速度・メモリの**両方**で有利です。\n", + "\n", + "```python\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "def classes_with_at_least_five_students(Courses: pd.DataFrame) -> pd.DataFrame:\n", + " cat = Courses['class'].astype('category') # 使い回すなら外で一度だけ!\n", + " codes = cat.cat.codes.to_numpy()\n", + " mask = codes != -1 # NaN を除外\n", + " counts = np.bincount(codes[mask],\n", + " minlength=len(cat.cat.categories))\n", + " classes = cat.cat.categories[counts >= 5]\n", + " return pd.DataFrame({'class': classes}).reset_index(drop=True)\n", + "\n", + "Analyze Complexity\n", + "Runtime 259 ms\n", + "Beats 80.86%\n", + "Memory 67.72 MB\n", + "Beats 93.30%\n", + "\n", + "```\n", + "\n", + "**いつ効く?**\n", + "\n", + "* クラスの種類が多い / 文字列が長い\n", + "* 同種の集計を**何度も**行う(`astype('category')` を一度だけにできる)\n", + "\n", + "### 2) 最小コピーでの NumPy 直叩き\n", + "\n", + "前処理なしで純粋に速さ重視なら `np.unique`。\n", + "(順序要件なしなのでユニークの**ソート**は問題になりません)\n", + "\n", + "```python\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "def classes_with_at_least_five_students(Courses: pd.DataFrame) -> pd.DataFrame:\n", + " u, c = np.unique(Courses['class'].to_numpy(), return_counts=True)\n", + " return pd.DataFrame({'class': u[c >= 5]}).reset_index(drop=True)\n", + "\n", + "Analyze Complexity\n", + "Runtime 254 ms\n", + "Beats 88.03%\n", + "Memory 67.20 MB\n", + "Beats 99.57%\n", + "\n", + "```\n", + "\n", + "> 注意: `np.unique` は `NaN` も一意値として扱います(`NaN` を除外したい場合は事前に `dropna`)。\n", + "\n", + "---\n", + "\n", + "## 実務メモ(さらに数%を取りにいく)\n", + "\n", + "* **NaN の方針を固定**:`value_counts` は既定で NaN を数えません。仕様に合わせて `dropna(subset=['class'])` or `dropna=False` を明示。\n", + "* **再利用できるなら category をキャッシュ**:同じ DF で何度も集計する場合、`Courses['class']` を最初から `category` にしておくのが最強。\n", + "* **I/O・print・不要な `sort_values` は厳禁**(既に守れているのでOK)。\n", + "* **メモリ 68MB 改善**:`class` を `category` にするとメモリが目に見えて下がります(辞書 + コードの二層表現)。\n", + "\n", + "---\n", + "\n", + "## まとめ(おすすめ順)\n", + "\n", + "1. **`value_counts(sort=False)` 版**(最小変更・高コスパ)\n", + "2. 文字列が巨大 or 繰り返し集計 → **`category` + `np.bincount`**\n", + "3. とにかく最短経路 → **`np.unique(..., return_counts=True)`**\n", + "\n", + "この順で試すと、**287ms → 二桁% 改善**が出るケースが多いです。\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_posgres.ipynb b/SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_posgres.ipynb new file mode 100644 index 00000000..a4c12d69 --- /dev/null +++ b/SQL/Leetcode/Basic select/596. Classes With at Least 5 Students/gpt/Classes_With_at_Least_5_Students_posgres.ipynb @@ -0,0 +1,96 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "caf5eba4", + "metadata": {}, + "source": [ + "# PostgreSQL 16.6+\n", + "\n", + "## 0) 前提\n", + "\n", + "* エンジン: **PostgreSQL 16.6+**\n", + "* 並び順: 任意\n", + "* `NOT IN` 回避(`EXISTS` / `LEFT JOIN ... IS NULL` を推奨)\n", + "* 判定は **ID 基準(class)**、表示は仕様どおり\n", + "\n", + "## 1) 問題\n", + "\n", + "* `Courses` から **受講生が5人以上いる class を求める**\n", + "* 入力: `Courses(student varchar, class varchar)` (主キー: `(student, class)`)\n", + "* 出力: `class` のみ(重複なし、順序任意)\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "> PostgreSQL では **ウィンドウ集計**で人数を付与し、閾値で抽出して最終投影。\n", + "\n", + "```sql\n", + "WITH win AS (\n", + " SELECT\n", + " class,\n", + " COUNT(*) OVER (PARTITION BY class) AS cnt\n", + " FROM Courses\n", + ")\n", + "SELECT DISTINCT\n", + " class\n", + "FROM win\n", + "WHERE cnt >= 5;\n", + "\n", + "Runtime 265 ms\n", + "Beats 95.66%\n", + "\n", + "```\n", + "\n", + "### 代替(シンプル集約で十分な場合)\n", + "\n", + "> 行数が多くてもこの形が最短経路です。実務ではまずこちらを使います。\n", + "\n", + "```sql\n", + "SELECT\n", + " class\n", + "FROM Courses\n", + "GROUP BY class\n", + "HAVING COUNT(*) >= 5;\n", + "\n", + "Runtime 275 ms\n", + "Beats 77.84%\n", + "\n", + "```\n", + "\n", + "## 3) 要点解説\n", + "\n", + "* 主キー `(student, class)` により **同一学生の同一クラス重複は存在しない**ため、単純 `COUNT(*)` で人数カウント可\n", + "* ウィンドウ版は「行に人数を付けてから DISTINCT」で表現力が高い\n", + " 一方、要件が「グループの閾値判定」だけなら **`GROUP BY ... HAVING`** が最も簡潔で計画も安定しがち\n", + "* 物理設計のヒント\n", + "\n", + " * 頻繁にこの集約を行うなら **`CREATE INDEX ON Courses(class);`** を検討(グループ化やスキャンが有利)\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* ウィンドウ処理: **O(Σ n_g log n_g)**(パーティション内の並び替えコストを含む実装が多い)\n", + "* 集約(代替案): **O(N)** 近似(ハッシュ集約が選ばれる前提)\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[入力 テーブル Courses]\n", + " B[前処理 ウィンドウで人数付与]\n", + " C[条件 cnt >= 5]\n", + " D[出力 class 列のみ]\n", + " A --> B\n", + " B --> C\n", + " C --> D\n", + "```\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/requirements.lock.txt b/requirements.lock.txt new file mode 100644 index 00000000..3dc35fdb --- /dev/null +++ b/requirements.lock.txt @@ -0,0 +1,108 @@ +anyio==4.11.0 +appnope==0.1.4 +argon2-cffi==25.1.0 +argon2-cffi-bindings==25.1.0 +arrow==1.4.0 +asttokens==3.0.0 +async-lru==2.0.5 +attrs==25.4.0 +babel==2.17.0 +beautifulsoup4==4.14.2 +bleach==6.2.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.4 +comm==0.2.3 +contourpy==1.3.3 +cycler==0.12.1 +debugpy==1.8.17 +decorator==5.2.1 +defusedxml==0.7.1 +executing==2.2.1 +fastjsonschema==2.21.2 +fonttools==4.60.1 +fqdn==1.5.1 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +idna==3.11 +ipykernel==7.0.1 +ipython==9.6.0 +ipython_pygments_lexers==1.1.1 +ipywidgets==8.1.7 +isoduration==20.11.0 +jedi==0.19.2 +Jinja2==3.1.6 +json5==0.12.1 +jsonpointer==3.0.0 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 +jupyter-events==0.12.0 +jupyter-lsp==2.3.0 +jupyter_client==8.6.3 +jupyter_core==5.9.1 +jupyter_server==2.17.0 +jupyter_server_terminals==0.5.3 +jupyterlab==4.4.9 +jupyterlab_pygments==0.3.0 +jupyterlab_server==2.27.3 +jupyterlab_widgets==3.0.15 +kiwisolver==1.4.9 +lark==1.3.0 +MarkupSafe==3.0.3 +matplotlib==3.10.7 +matplotlib-inline==0.1.7 +mistune==3.1.4 +nbclient==0.10.2 +nbconvert==7.16.6 +nbformat==5.10.4 +nest-asyncio==1.6.0 +notebook_shim==0.2.4 +numpy==2.3.4 +packaging==25.0 +pandas==2.3.3 +pandocfilters==1.5.1 +parso==0.8.5 +pexpect==4.9.0 +pillow==12.0.0 +platformdirs==4.5.0 +prometheus_client==0.23.1 +prompt_toolkit==3.0.52 +psutil==7.1.1 +ptyprocess==0.7.0 +pure_eval==0.2.3 +pycparser==2.23 +Pygments==2.19.2 +pyparsing==3.2.5 +python-dateutil==2.9.0.post0 +python-json-logger==4.0.0 +pytz==2025.2 +PyYAML==6.0.3 +pyzmq==27.1.0 +referencing==0.37.0 +requests==2.32.5 +rfc3339-validator==0.1.4 +rfc3986-validator==0.1.1 +rfc3987-syntax==1.1.0 +rpds-py==0.27.1 +ruff==0.14.1 +Send2Trash==1.8.3 +setuptools==80.9.0 +six==1.17.0 +sniffio==1.3.1 +soupsieve==2.8 +stack-data==0.6.3 +terminado==0.18.1 +tinycss2==1.4.0 +tornado==6.5.2 +traitlets==5.14.3 +typing_extensions==4.15.0 +tzdata==2025.2 +uri-template==1.3.0 +urllib3==2.5.0 +uv==0.9.5 +wcwidth==0.2.14 +webcolors==24.11.1 +webencodings==0.5.1 +websocket-client==1.9.0 +widgetsnbextension==4.0.14