ClickHouseというDBがあります。
このDBにはサーバを建てることなく、ローカルでエンジンを使用する機能が用意されています。
自分は、clickhouse-local をS3に吐かれたアクセスログの調査や、集計クエリを作成するためDBのCSVダンプファイルに対してSQLを試すのに使用しています。chDB は触ってこなかったので、使ってみたいと思います。
ちなみにそれらの違いは以下の通りです。
| 特徴 | clickhouse-local | chDB |
|---|---|---|
| 実行モデル | CLI(単体実行ツール) | インプロセス(ライブラリとしてアプリ内に組込) |
| サーバー必要性 | 不要 | 不要 |
| 対象 | 単発クエリ/ローカル分析 | アプリ内 SQL エンジン |
| API | CLI / SQL インターフェイス | Python/Go/Rust/Node.js API |
| データ処理 | ファイル/ドライブ上データ | プロセス内でデータ分析 |
Jupyter Notebook + chDB
インプロセスとのことで、Jupyter Notebookで使うのを試してみたいと思います。データはawsのMLチュートリアル用データ(banking.csv)をDLして使います。
さくっとインストールと、プロセス立ち上げをします。
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ pip install notebook chdb
(.venv) $ jupyter notebook
以降は、Jupyter NotebookのMarkdown出力です。一部カラムが長い箇所を編集しています。
所管としては
- 1つ目のクエリ実行のみファイル読み込みで時間がかかるが、その後は高速
- クエリの結果は変数(
result)に入るので、扱いやすそう-
DataFrame以外にもJSONLinesなど
-
chdbサンプル
import chdb
file = './banking.csv'
1. データの読み込み
query = f"""
SELECT * FROM file({file}, CSVWithNames)
LIMIT 5
"""
result = chdb.query(query, "DataFrame")
result
| age | job | marital | … | |
|---|---|---|---|---|
| 0 | 44 | blue-collar | married | … |
| 1 | 53 | technician | married | … |
| 2 | 28 | management | single | … |
| 3 | 39 | services | married | … |
| 4 | 55 | retired | married | … |
5 rows × 21 columns
2. 職業別の集計
query = f"""
SELECT
job,
COUNT(*) as count,
SUM(CASE WHEN y = 1 THEN 1 ELSE 0 END) as subscribed,
ROUND(SUM(CASE WHEN y = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as subscription_rate,
ROUND(AVG(age), 1) as avg_age
FROM file({file}, CSVWithNames)
GROUP BY job
ORDER BY count DESC
"""
result = chdb.query(query, "DataFrame")
result
| job | count | subscribed | subscription_rate | avg_age | |
|---|---|---|---|---|---|
| 0 | admin. | 10422 | 1352 | 12.97 | 38.2 |
| 1 | blue-collar | 9254 | 638 | 6.89 | 39.6 |
| 2 | technician | 6743 | 730 | 10.83 | 38.5 |
| 3 | services | 3969 | 323 | 8.14 | 37.9 |
| 4 | management | 2924 | 328 | 11.22 | 42.4 |
| 5 | retired | 1720 | 434 | 25.23 | 62.0 |
| 6 | entrepreneur | 1456 | 124 | 8.52 | 41.7 |
| 7 | self-employed | 1421 | 149 | 10.49 | 39.9 |
| 8 | housemaid | 1060 | 106 | 10.00 | 45.5 |
| 9 | unemployed | 1014 | 144 | 14.20 | 39.7 |
| 10 | student | 875 | 275 | 31.43 | 25.9 |
| 11 | unknown | 330 | 37 | 11.21 | 45.6 |
3. 年齢層別の集計
query = f"""
SELECT
CASE
WHEN age < 30 THEN '20代'
WHEN age < 40 THEN '30代'
WHEN age < 50 THEN '40代'
WHEN age < 60 THEN '50代'
ELSE '60代以上'
END as age_group,
COUNT(*) as count,
SUM(CASE WHEN y = 1 THEN 1 ELSE 0 END) as subscribed,
ROUND(SUM(CASE WHEN y = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as subscription_rate
FROM file({file}, CSVWithNames)
GROUP BY age_group
ORDER BY MIN(age)
"""
result = chdb.query(query, "DataFrame")
result
| age_group | count | subscribed | subscription_rate | |
|---|---|---|---|---|
| 0 | 20代 | 5669 | 922 | 16.26 |
| 1 | 30代 | 16938 | 1715 | 10.13 |
| 2 | 40代 | 10526 | 834 | 7.92 |
| 3 | 50代 | 6862 | 697 | 10.16 |
| 4 | 60代以上 | 1193 | 472 | 39.56 |
4. 月別の集計
query = f"""
SELECT
month,
COUNT(*) as count,
SUM(CASE WHEN y = 1 THEN 1 ELSE 0 END) as subscribed,
ROUND(SUM(CASE WHEN y = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as subscription_rate
FROM file({file}, CSVWithNames)
GROUP BY month
ORDER BY
CASE month
WHEN 'jan' THEN 1
WHEN 'feb' THEN 2
WHEN 'mar' THEN 3
WHEN 'apr' THEN 4
WHEN 'may' THEN 5
WHEN 'jun' THEN 6
WHEN 'jul' THEN 7
WHEN 'aug' THEN 8
WHEN 'sep' THEN 9
WHEN 'oct' THEN 10
WHEN 'nov' THEN 11
WHEN 'dec' THEN 12
END
"""
result = chdb.query(query, "DataFrame")
result
| month | count | subscribed | subscription_rate | |
|---|---|---|---|---|
| 0 | mar | 546 | 276 | 50.55 |
| 1 | apr | 2632 | 539 | 20.48 |
| 2 | may | 13769 | 886 | 6.43 |
| 3 | jun | 5318 | 559 | 10.51 |
| 4 | jul | 7174 | 649 | 9.05 |
| 5 | aug | 6178 | 655 | 10.60 |
| 6 | sep | 570 | 256 | 44.91 |
| 7 | oct | 718 | 315 | 43.87 |
| 8 | nov | 4101 | 416 | 10.14 |
| 9 | dec | 182 | 89 | 48.90 |