DuckDBでSQL

DuckDB というデータベースの Python API を使ってSQLの実習をしてみましょう。CSV、JSON、Parquet形式のファイルを読んだり、pandasやPollarsのデータフレームやArrowのテーブルを操作したりできます。

まずは pip でインストールします:

pip install duckdb

インポートします:

import duckdb

ここでは duckdb.read_csv() という関数でCSVデータを読んでみます。データはローカルのファイルでもかまいませんが、ここでは Web で公開されている CSV ファイルを URL で指定します。ここでは2022年1月1日現在の住民基本台帳に基づく都道府県別・男女別人口データを扱います(元データはe-Statの住民基本台帳に基づく人口、人口動態及び世帯数調査 / 調査の結果)。読み込んだデータをここでは pop という名前のオブジェクト(duckdb.duckdb.DuckDBPyRelation)に格納します。2行目は単に pop とだけ打ち込んで、データの中身を表示しています:

pop = duckdb.read_csv("https://okumuralab.org/~okumura/stat/data/pop2022.csv")
pop
┌─────────┬──────────┬─────────┬─────────┐
│  番号   │ 都道府県 │   男    │   女    │
│ varchar │ varchar  │  int64  │  int64  │
├─────────┼──────────┼─────────┼─────────┤
│ 01      │ 北海道   │ 2450393 │ 2733294 │
│ 02      │ 青森     │  589143 │  653938 │
│ 03      │ 岩手     │  581809 │  624670 │
│ 04      │ 宮城     │ 1106183 │ 1162172 │
│ 05      │ 秋田     │  452370 │  504466 │
│ 06      │ 山形     │  511409 │  545273 │
│ 07      │ 福島     │  904388 │  936856 │
│ 08      │ 茨城     │ 1449442 │ 1440935 │
│ 09      │ 栃木     │  971696 │  970798 │
│ 10      │ 群馬     │  963793 │  979874 │
│ ·       │  ·       │     ·   │     ·   │
│ ·       │  ·       │     ·   │     ·   │
│ ·       │  ·       │     ·   │     ·   │
│ 38      │ 愛媛     │  637522 │  704017 │
│ 39      │ 高知     │  327834 │  365535 │
│ 40      │ 福岡     │ 2430957 │ 2677550 │
│ 41      │ 佐賀     │  386622 │  425571 │
│ 42      │ 長崎     │  622766 │  697289 │
│ 43      │ 熊本     │  829853 │  917660 │
│ 44      │ 大分     │  538934 │  592206 │
│ 45      │ 宮崎     │  511039 │  567274 │
│ 46      │ 鹿児島   │  759364 │  846055 │
│ 47      │ 沖縄     │  732981 │  752689 │
├─────────┴──────────┴─────────┴─────────┤
│ 47 rows (20 shown)           4 columns │
└────────────────────────────────────────┘

この pop というテーブルの特定の行を抜き出します(選択)。最後のセミコロン ; はSQLでは必須ですが、ここでは省略してもかまいません:

duckdb.sql("select * from pop where 都道府県='東京';")
┌─────────┬──────────┬─────────┬─────────┐
│  番号   │ 都道府県 │   男    │   女    │
│ varchar │ varchar  │  int64  │  int64  │
├─────────┼──────────┼─────────┼─────────┤
│ 13      │ 東京     │ 6775557 │ 7019376 │
└─────────┴──────────┴─────────┴─────────┘

さらに特定の列だけ表示させます(射影):

duckdb.sql("select 男, 女 from pop where 都道府県='東京';")

「番号」と「都道府県」と男女の人口の「計」だけの表 tot を新たに作ります:

tot = duckdb.sql("select 番号, 都道府県, 男 + 女 as 人口 from pop;")

これで確認のため tot 全体を表示してみてもいいのですが、スペースを食いますので、頭の5件だけ表示してみましょう:

duckdb.sql("select * from tot limit 5;")
┌─────────┬──────────┬─────────┐
│  番号   │ 都道府県 │  人口   │
│ varchar │ varchar  │  int64  │
├─────────┼──────────┼─────────┤
│ 01      │ 北海道   │ 5183687 │
│ 02      │ 青森     │ 1243081 │
│ 03      │ 岩手     │ 1206479 │
│ 04      │ 宮城     │ 2268355 │
│ 05      │ 秋田     │  956836 │
└─────────┴──────────┴─────────┘

全国の合計も知りたいですね:

duckdb.sql("select sum(人口) from tot;")
┌─────────────┐
│ sum("人口") │
│   int128    │
├─────────────┤
│   125927902 │
└─────────────┘

この時点での日本の人口は 125927902 人でした。

都道府県別に男女比が知りたくなりました。「番号」と「都道府県」と「男女比」からなる表 ratio を作ってみましょう。

ratio = duckdb.sql("select 番号, 都道府県, 男 / 女 as 男女比 from pop;")

男が少ない都道府県、例えば男女比が 90% 未満のデータを抽出しましょう:

duckdb.sql("select * from ratio where 男女比 < 0.9;")
┌─────────┬──────────┬────────────────────┐
│  番号   │ 都道府県 │       男女比       │
│ varchar │ varchar  │       double       │
├─────────┼──────────┼────────────────────┤
│ 01      │ 北海道   │ 0.8964981447293998 │
│ 05      │ 秋田     │ 0.8967304040311934 │
│ 39      │ 高知     │ 0.8968607657269482 │
│ 42      │ 長崎     │ 0.8931246584988434 │
│ 46      │ 鹿児島   │ 0.8975350302285312 │
└─────────┴──────────┴────────────────────┘

もう一つデータを読み込んでみます。こちらは各都道府県の面積です:

area = duckdb.read_csv("https://okumuralab.org/~okumura/stat/data/area.csv")

これと男女計 tot を結合して、人口密度を求めてみましょう:

density = duckdb.sql("select 番号, 都道府県, 計 / 面積_km2 as 人口密度 from area natural join tot;")

人口密度の大きい順に並べて、上位10件を表示します:

duckdb.sql("select * from density order by 人口密度 desc limit 10;")
┌─────────┬──────────┬────────────────────┐
│  番号   │ 都道府県 │      人口密度      │
│ varchar │ varchar  │       double       │
├─────────┼──────────┼────────────────────┤
│ 13      │ 東京     │  6287.428727695357 │
│ 27      │ 大阪     │  4619.041945709907 │
│ 14      │ 神奈川   │  3814.084681925417 │
│ 11      │ 埼玉     │ 1944.7957343163716 │
│ 23      │ 愛知     │ 1455.3065347032273 │
│ 12      │ 千葉     │ 1223.6306349975764 │
│ 40      │ 福岡     │ 1024.4612987388025 │
│ 28      │ 兵庫     │  653.3314684648759 │
│ 47      │ 沖縄     │  650.8873924986747 │
│ 26      │ 京都     │  544.5327609383809 │
├─────────┴──────────┴────────────────────┤
│ 10 rows                       3 columns │
└─────────────────────────────────────────┘

DuckDBのテーブルはpandasのデータフレームに変換できます:

df = pop.df()
df.head()
   番号 都道府県        男        女
0  01  北海道  2450393  2733294
1  02   青森   589143   653938
2  03   岩手   581809   624670
3  04   宮城  1106183  1162172
4  05   秋田   452370   504466

pandasのデータフレームはDuckDBで直接扱えます。

duckdb.sql("select * from df;")