Polars

概要

Polars(ポーラーズ)はRust製のデータ操作ライブラリです。pandasより校則で、機能も豊富です。Python用のほか、R用Polars R パッケージを書き直しました参照)もあります。

Polarsのマスコットは白熊(ホッキョクグマ、polar bear)です。パンダより強そうです。

インストールは pip install polars です。

例(フォントによっては表が崩れます):

import polars as pl

df = pl.read_csv("https://okumuralab.org/~okumura/stat/data/pop2022.csv")
df
shape: (47, 4)
┌──────┬──────────┬─────────┬─────────┐
│ 番号 ┆ 都道府県 ┆ 男      ┆ 女      │
│ ---  ┆ ---      ┆ ---     ┆ ---     │
│ i64  ┆ str      ┆ i64     ┆ i64     │
╞══════╪══════════╪═════════╪═════════╡
│ 1    ┆ 北海道   ┆ 2450393 ┆ 2733294 │
│ 2    ┆ 青森     ┆ 589143  ┆ 653938  │
│ 3    ┆ 岩手     ┆ 581809  ┆ 624670  │
│ 4    ┆ 宮城     ┆ 1106183 ┆ 1162172 │
│ 5    ┆ 秋田     ┆ 452370  ┆ 504466  │
│ …    ┆ …        ┆ …       ┆ …       │
│ 43   ┆ 熊本     ┆ 829853  ┆ 917660  │
│ 44   ┆ 大分     ┆ 538934  ┆ 592206  │
│ 45   ┆ 宮崎     ┆ 511039  ┆ 567274  │
│ 46   ┆ 鹿児島   ┆ 759364  ┆ 846055  │
│ 47   ┆ 沖縄     ┆ 732981  ┆ 752689  │
└──────┴──────────┴─────────┴─────────┘

1列目の都道府県番号は01〜47の文字列として読みたいですね:

df = pl.read_csv("https://okumuralab.org/~okumura/stat/data/pop2022.csv",
                 schema_overrides=[pl.String])
df
shape: (47, 4)
┌──────┬──────────┬─────────┬─────────┐
│ 番号 ┆ 都道府県 ┆ 男      ┆ 女      │
│ ---  ┆ ---      ┆ ---     ┆ ---     │
│ str  ┆ str      ┆ i64     ┆ i64     │
╞══════╪══════════╪═════════╪═════════╡
│ 01   ┆ 北海道   ┆ 2450393 ┆ 2733294 │
│ 02   ┆ 青森     ┆ 589143  ┆ 653938  │
│ 03   ┆ 岩手     ┆ 581809  ┆ 624670  │
│ 04   ┆ 宮城     ┆ 1106183 ┆ 1162172 │
│ 05   ┆ 秋田     ┆ 452370  ┆ 504466  │
│ …    ┆ …        ┆ …       ┆ …       │
│ 43   ┆ 熊本     ┆ 829853  ┆ 917660  │
│ 44   ┆ 大分     ┆ 538934  ┆ 592206  │
│ 45   ┆ 宮崎     ┆ 511039  ┆ 567274  │
│ 46   ┆ 鹿児島   ┆ 759364  ┆ 846055  │
│ 47   ┆ 沖縄     ┆ 732981  ┆ 752689  │
└──────┴──────────┴─────────┴─────────┘

schema_overrides はリスト等で、例えば schema_overrides=[pl.String, pl.String, pl.Int32, pl.Int32] のように列ごとに指定できます。4列全部を文字列にするには dtypes=[pl.String]*4 とします。

型は pl.String(文字列)、pl.Float64pl.Float32pl.Int64pl.Int32pl.Int16pl.Int8pl.UInt64pl.UInt32pl.UInt16pl.UInt8 などが使えます。

Pandasと比べて、0から始まる行インデックスが付きません。

別の例:

URL = "https://www.data.jma.go.jp/cpdinfo/temp/list/csv/an_wld.csv"
df1 = pl.read_csv(URL, encoding="cp932")

これは悪名高き気象庁のCSVファイルで、ときどき数値の後に * が付いてエラーになります。pandasはエラーになったら面倒ですが、Polarsなら次のように回避できます:

df1 = pl.read_csv(URL, encoding="cp932", ignore_errors=True)
df1
shape: (134, 4)
┌──────┬──────────┬────────┬────────┐
│ 年   ┆ 世界全体 ┆ 北半球 ┆ 南半球 │
│ ---  ┆ ---      ┆ ---    ┆ ---    │
│ i64  ┆ f64      ┆ f64    ┆ f64    │
╞══════╪══════════╪════════╪════════╡
│ 1891 ┆ -0.78    ┆ -0.88  ┆ -0.68  │
│ 1892 ┆ -0.89    ┆ -1.0   ┆ -0.74  │
│ 1893 ┆ -0.94    ┆ -1.06  ┆ -0.79  │
│ 1894 ┆ -0.86    ┆ -0.93  ┆ -0.77  │
│ 1895 ┆ -0.82    ┆ -0.95  ┆ -0.67  │
│ …    ┆ …        ┆ …      ┆ …      │
│ 2020 ┆ 0.34     ┆ 0.51   ┆ 0.16   │
│ 2021 ┆ 0.22     ┆ 0.35   ┆ 0.09   │
│ 2022 ┆ 0.24     ┆ 0.35   ┆ 0.11   │
│ 2023 ┆ 0.54     ┆ 0.68   ┆ 0.38   │
│ 2024 ┆ 0.62     ┆ 0.79   ┆ 0.42   │
└──────┴──────────┴────────┴────────┘

得られた df の扱い方は、pandasとやや異なります。例:

df.query("都道府県 == '東京'")          # pandas
df.filter(pl.col("都道府県") == "東京") # Polars  

df.to_pandas()df.to_numpy() でpandasやnumpyに変換することもできます。

pandasによるクエリと同じことをPolarsで行ってみましょう。使うのは、さきほど

df = pl.read_csv("https://okumuralab.org/~okumura/stat/data/pop2022.csv",
                 schema_overrides=[pl.String])

で読み込んだデータです。まずは東京都だけ選びます:

df.filter(pl.col("都道府県") == "東京")
shape: (1, 4)
┌──────┬────────────┬─────────┬─────────┐
│ 番号 ┆ 都道府県   ┆ 男      ┆ 女      │
│ ---  ┆ ---        ┆ ---     ┆ ---     │
│ str  ┆ str        ┆ i64     ┆ i64     │
╞══════╪════════════╪═════════╪═════════╡
│ 13   ┆ 東京       ┆ 6775557 ┆ 7019376 │
└──────┴────────────┴─────────┴─────────┘

元の df は変わりません。変えたいなら df = df.filter(pl.col("都道府県") == "東京") のように代入し直します(ここでは行いません)。

男と女の列だけ表示させたいなら、次のようにします(これはpandasと同様です):

df.filter(pl.col("都道府県") == "東京")[["男", "女"]]
shape: (1, 2)
┌─────────┬─────────┐
│ 男      ┆ 女      │
│ ---     ┆ ---     │
│ i64     ┆ i64     │
╞═════════╪═════════╡
│ 6775557 ┆ 7019376 │
└─────────┴─────────┘

男女の人口はわかりましたが、合計の人口もほしいですね。「計」という欄を追加しましょう:

df = df.with_columns((pl.col("男") + pl.col("女")).alias("計"))
df.head()  # 頭の部分だけ見る
shape: (5, 5)
┌──────┬────────────┬─────────┬─────────┬─────────┐
│ 番号 ┆ 都道府県   ┆ 男      ┆ 女      ┆ 計      │
│ ---  ┆ ---        ┆ ---     ┆ ---     ┆ ---     │
│ str  ┆ str        ┆ i64     ┆ i64     ┆ i64     │
╞══════╪════════════╪═════════╪═════════╪═════════╡
│ 01   ┆ 北海道     ┆ 2450393 ┆ 2733294 ┆ 5183687 │
│ 02   ┆ 青森       ┆ 589143  ┆ 653938  ┆ 1243081 │
│ 03   ┆ 岩手       ┆ 581809  ┆ 624670  ┆ 1206479 │
│ 04   ┆ 宮城       ┆ 1106183 ┆ 1162172 ┆ 2268355 │
│ 05   ┆ 秋田       ┆ 452370  ┆ 504466  ┆ 956836  │
└──────┴────────────┴─────────┴─────────┴─────────┘

df = df.with_columns(pl.Series(name="計", values=df["男"] + df["女"])) でもよさそうです。

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

df[["男", "女", "計"]].sum()
shape: (1, 3)
┌──────────┬──────────┬───────────┐
│ 男       ┆ 女       ┆ 計        │
│ ---      ┆ ---      ┆ ---       │
│ i64      ┆ i64      ┆ i64       │
╞══════════╪══════════╪═══════════╡
│ 61420626 ┆ 64507276 ┆ 125927902 │
└──────────┴──────────┴───────────┘

この時点での日本の人口は 125927902 人でした。男より女のほうが多いようですね。

都道府県別に男女比が知りたくなりました。「男女比」という欄を追加しましょう。

df = df.with_columns((pl.col("男") / pl.col("女")).alias("男女比"))

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

df.filter(pl.col("男女比") < 0.9)

&(and)や |(or)で繋げるときは、各不等式を括弧で囲みます:

df.filter((pl.col("男女比") < 0.9) & (pl.col("計") >= 1000000))

抽出されたものは、元の順番(都道府県コード順)に並んでいます。これを男女比の昇順(小さい順)に並べ替えてみます:

df.filter(pl.col("男女比") < 0.9).sort("男女比")

これは次のように分けて書いてもかまいません:

df1 = df.filter(pl.col("男女比") < 0.9)
df1.sort("男女比")

降順(大きい順)にするには descending=True というオプションを付けます:

df.filter(pl.col("男女比") < 0.9).sort("男女比", descending=True)

データ全体を人口の合計でソートし、大きい順に並び替えて、頭の3行を表示しましょう:

df.sort("計", descending=True).head(3)

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

df2 = pl.read_csv("https://okumuralab.org/~okumura/stat/data/area.csv",
                  schema_overrides=[pl.String])

dfdf2 を、共通する「番号」「都道府県」で結合(join)し、df に代入します:

df = df.join(df2, on=["番号", "都道府県"])

人口密度を求めてみましょう:

df = df.with_columns((pl.col("計") / pl.col("面積_km2")).alias("人口密度"))

問題:人口密度の大きい順に並べて、上位10件(.head(10))、下位10件(.tail(10))を表示してください。