[Python] Polars Tips

Python

dataframeの作成

データフレームを作成します。

data = {"cols1": ["a", "b"], "cols2": [1, 2]}
df = pl.DataFrame(data)

作成する際に、型も指定できます。

data = {"col1": [1, 2], "col2": [3, 4]}
df = pl.DataFrame(data, schema={"col1": pl.Float32, "col2": pl.Int64})

データの読み込み、書き出し

# csv
# 読み込み
df = pl.read_csv("train.csv")
# 書き出し
df.write_csv("train.csv")

# parquet
# 読み込み
df = pl.read_parquet("train.parquet")
# 書き出し
df.write_parquet("train.parquet")

# よく使う引数
df = pl.read_parquet("train.parquet", n_rows=100, columns=["col1", "col2", "col3"])

filter

データの検索や抜き出すときなどに使用します。

df.filter(pl.col("foo") == 0)

# 複数条件 
df.filter((pl.col("foo") < 3) & (pl.col("ham") == "a"))

新規カラムの作成

新しいカラム名はaliasで指定します。

# カラムaの2倍の値を、カラムcに作成
df = df.with_columns((pl.col("a") ** 2).alias("c"))

リストを新しいカラムとして追加します。サイズが合わないとエラーになるので、注意です

# listの値をnew_colとして追加
df = df.with_columns(pl.Series(name='new_col', values=list))

型変換(cast)

カラムの型を変換します。カラムの型を調べるのはhead()を使うのが楽だと思います。

df = df.with_columns([pl.col("vote_sum").cast(pl.Float32)])
Float32
Float64
Int8
Int16
Int32
Int64
UInt8
UInt16
UInt32
UInt64

データフレームの結合(join)

pandasでいう、mergeとほぼ一緒です。

how: {‘inner’, ‘left’, ‘outer’, ‘semi’, ‘anti’, ‘cross’}
suffix: joinしたときに、重複するカラム名に付加する名前

df = df.join(subs, how='left', on=['session', "aid"])

結合(concat)

df = pl.concat([df1, df2])

# 横方向
df = pl.concat([df1, df2], how="horizontal")

カラム名のリネーム(rename)

カラム名のリネームです。

df = df.rename({'a': 'a_x', 'b': 'b_x'})

ソート(sort)

降順にしたいときは、reverse=Trueにします。

df = df.sort("a", descending=True)

また、カラムごとに昇順降順を指定することができます。

df = df.sort([pl.col("a"), pl.col("b")],descending=[False, True]) 

groupby.agg

指定したグループごとに、様々な演算を施した値を取得できます。

新しく作るカラム名はsuffixかaliasが使えます。
これ以外にも、多くの関数があるので、公式ドキュメント(https://pola-rs.github.io/polars/py-polars/html/reference/dataframe/groupby.html)を参考にしてください。

data = {"group": ["a", "a", "a", "b", "b", "b"], "value": [1, 2, 3, 4, 5, 6]}
df = pl.DataFrame(data)

df >>>
┌───────┬───────┐
│ group ┆ value │
│ ---   ┆ ---   │
│ str   ┆ i64   │
╞═══════╪═══════╡
│ a     ┆ 1     │
│ a     ┆ 2     │
│ a     ┆ 3     │
│ b     ┆ 4     │
│ b     ┆ 5     │
│ b     ┆ 5     │
└───────┴───────┘
df = df.group_by("group", maintain_order=True).agg(
        [
            pl.sum("value").name.suffix("_sum"),   # groupごとにvalueを合算
            pl.mean("value").name.suffix("_mean"), # groupごとのvalueの平均
            pl.count("value").alias("count"), # groupごとのvalueの数
            pl.n_unique("value").alias("unique_count"), # groupごとのユニークなvalueの数
        ]
)

>>> df
┌───────┬───────────┬────────────┬───────┬──────────────┐
│ group ┆ value_sum ┆ value_mean ┆ count ┆ unique_count │
│ ---   ┆ ---       ┆ ---        ┆ ---   ┆ ---          │
│ str   ┆ i64       ┆ f64        ┆ u32   ┆ u32          │
╞═══════╪═══════════╪════════════╪═══════╪══════════════╡
│ a     ┆ 6         ┆ 2.0        ┆ 3     ┆ 3            │
│ b     ┆ 14        ┆ 4.666667   ┆ 3     ┆ 2            │
└───────┴───────────┴────────────┴───────┴──────────────┘

sortと組み合わせて、グループごとにvalueの高いidを抜き出す、ということもできます。

data = {"group": ["a", "a", "a", "b", "b", "b"], 
        "id": [100, 101, 102, 103, 104, 105], 
        "value": [1, 2, 3, 4, 5, 6]}
df = pl.DataFrame(data)

>>> df
┌───────┬─────┬───────┐
│ group ┆ id  ┆ value │
│ ---   ┆ --- ┆ ---   │
│ str   ┆ i64 ┆ i64   │
╞═══════╪═════╪═══════╡
│ a     ┆ 100 ┆ 1     │
│ a     ┆ 101 ┆ 2     │
│ a     ┆ 102 ┆ 3     │
│ b     ┆ 103 ┆ 4     │
│ b     ┆ 104 ┆ 5     │
│ b     ┆ 105 ┆ 6     │
└───────┴─────┴───────┘
df = (df.sort("value", reverse=True) # valueを降順で並び替える
        .groupby('group').agg([pl.col('id').head(2).alias('labels')]) # groupごとに上からidの値を抜き出し、labelsカラムに格納する
     )

>>> df 
┌───────┬────────────┐
│ group ┆ labels     │
│ ---   ┆ ---        │
│ str   ┆ list[i64]  │
╞═══════╪════════════╡
│ a     ┆ [102, 101] │
│ b     ┆ [105, 104] │
└───────┴────────────┘

列の削除

df = df.drop('col_bool')

欠損値の確認

null_counts = df.select([
    pl.col(name).is_null().sum().alias(name + "_null_count") 
    for name in df.columns
])

# 各カラムのnull数を改行して表示
for column in null_counts.columns:
    print(f"{column}: {null_counts[column][0]}")

欠損値のカラム削除

一つでも欠損値がある行を削除します。

df = df.drop_nulls()

欠損値を埋め(fill_null)

df = df.with_columns(pl.col('missing_cols').fill_null(0))
df = df.with_columns(pl.col('missing_cols').fill_nan(-1))

重複の削除(unique)

df = df.unique(subset=["col1"])

定数の列追加(lit)

df = df.with_columns([
    pl.lit(3.14).alias("pie")
])

map_elements

pandasでいうapplyのようなものです。

# 簡単なデータフレームの作成
df = pl.DataFrame({
    "text": ["apple", "banana", "cherry", "date"]
})

# 特定の値のセットを作成
fruit_set = {"apple", "cherry"}

# map_elementsを使って1と0を割り当てる
df = df.with_columns(
    pl.col("text").map_elements(lambda x: 1 if x in fruit_set else 0, return_dtype=pl.Int8).alias("is_fruit")
)

# 結果を表示
print(df)

shape: (4, 2)
┌────────┬──────────┐
│ text   ┆ is_fruit │
│ ---    ┆ ---      │
│ str    ┆ i8       │
╞════════╪══════════╡
│ apple  ┆ 1        │
│ banana ┆ 0        │
│ cherry ┆ 1        │
│ date   ┆ 0        │
└────────┴──────────┘

展開(explode)

リストを展開します。applyのところで紹介した、リスト化と組み合わせて使えます。

data = {"group": ["a"], 
        "value": [[1, 2, 3]]}
df = pl.DataFrame(data)

>>> df
┌───────┬───────────┐
│ group ┆ value     │
│ ---   ┆ ---       │
│ str   ┆ list[i64] │
╞═══════╪═══════════╡
│ a     ┆ [1, 2, 3] │
└───────┴───────────┘
df = df.explode("value")

>>> df
┌───────┬───────┐
│ group ┆ value │
│ ---   ┆ ---   │
│ str   ┆ i64   │
╞═══════╪═══════╡
│ a     ┆ 1     │
│ a     ┆ 2     │
│ a     ┆ 3     │
└───────┴───────┘

カラム同士を演算

data = {"col1": [1, 2], "col2": [3, 4]}
df = pl.DataFrame(data)

df = df.with_columns((df.get_column("col1") + df.get_column("col2")).alias("col1+col2"))

>>> df
┌──────┬──────┬───────────┐
│ col1 ┆ col2 ┆ col1+col2 │
│ ---  ┆ ---  ┆ ---       │
│ i64  ┆ i64  ┆ i64       │
╞══════╪══════╪═══════════╡
│ 1    ┆ 3    ┆ 4         │
│ 2    ┆ 4    ┆ 6         │
└──────┴──────┴───────────┘

スペース区切りの文章をリスト化

data = {"cols": ["a b c"]}
df = pl.DataFrame(data)

df = df.with_columns(pl.col('cols').apply(lambda s: s.split()).alias("cols_new")) # スペース区切りの文章をリスト化

>>> df
┌───────┬─────────────────┐
│ cols  ┆ cols_new        │
│ ---   ┆ ---             │
│ str   ┆ list[str]       │
╞═══════╪═════════════════╡
│ a b c ┆ ["a", "b", "c"] │
└───────┴─────────────────┘

値をリストで取り出す

col_list = df["col"].to_list()

グループごとに通し番号を振る

data = {"group": ["a", "a", "a", "b", "b", "b"], 
        "value": [1, 2, 3, 4, 5, 6]}
df = pl.DataFrame(data)

df = (df.with_columns([pl.lit(1).alias("n")]) # 後の、cum_sum関数のために、1で埋めたnカラムを作成
        .with_columns(pl.col("n").cum_sum().over("group").alias("serial")) # groupごとの通し番号をserialカラムとして追加
     )

>>> df
┌───────┬───────┬─────┬────────┐
│ group ┆ value ┆ n   ┆ serial │
│ ---   ┆ ---   ┆ --- ┆ ---    │
│ str   ┆ i64   ┆ i32 ┆ i32    │
╞═══════╪═══════╪═════╪════════╡
│ a     ┆ 1     ┆ 1   ┆ 1      │
│ a     ┆ 2     ┆ 1   ┆ 2      │
│ a     ┆ 3     ┆ 1   ┆ 3      │
│ b     ┆ 4     ┆ 1   ┆ 1      │
│ b     ┆ 5     ┆ 1   ┆ 2      │
│ b     ┆ 6     ┆ 1   ┆ 3      │
└───────┴───────┴─────┴────────┘

ISO 8601 フォーマットの変換

df = df.with_columns(
    (pl.col("timestamp").str.strptime(pl.Datetime, "%Y-%m-%dT%H:%M:%S%Z")),
    (pl.col("timestamp").str.strptime(pl.Datetime, "%Y-%m-%dT%H:%M:%S%Z").dt.year().alias("year")),
    (pl.col("timestamp").str.strptime(pl.Datetime, "%Y-%m-%dT%H:%M:%S%Z").dt.month().alias("month")),
    (pl.col("timestamp").str.strptime(pl.Datetime, "%Y-%m-%dT%H:%M:%S%Z").dt.day().alias("day")),
    (pl.col("timestamp").str.strptime(pl.Datetime, "%Y-%m-%dT%H:%M:%S%Z").dt.hour().alias("hour")),
    (pl.col("timestamp").str.strptime(pl.Datetime, "%Y-%m-%dT%H:%M:%S%Z").dt.minute().alias("minute")),
    (pl.col("timestamp").str.strptime(pl.Datetime, "%Y-%m-%dT%H:%M:%S%Z").dt.second().alias("second"))
)
タイトルとURLをコピーしました