0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Polars によるデータ加工テクニック

Last updated at Posted at 2025-04-16

思いついたら追記しよう

列をまとめて選択

公式ドキュメント

  • 文字列の列を NFKC で Unicode 正規化して前後の空白を削除
    df.with_columns(cs.string().str.normalize("NFKC").str.strip_chars())
  • 途中計算の列名をアンダースコア始まりとしておいて出力前にまとめて削除
    df.drop(cs.starts_with("_"))
  • altair でグラフを書こうとしたら duration が対応していなかったのでまとめて削除
    df.drop(cs.duration())
  • やっぱり分単位の経過時間として残したくなったのでまとめて計算
    df.with_columns(cs.duration().dt.total_seconds() / 60)

列名を一括変更

公式ドキュメント

  • スプレッドシートへ出力するときに _ 区切りの列名を改行区切りにする
    df.with_columns(pl.all().name.map(lambda x: x.replace("_", "\n")))

横持ちタプルを縦持ちタプルに整形

Excel や DB でやりがちな (key, value) の固定長配列を横持ちとする構造を Polars で集計しやすい縦持ちに変形する。

公式ドキュメント

整形前

shape: (10, 7)
┌───────┬───────┬─────────┬───────┬─────────┬───────┬─────────┐
│ index ┆ key01 ┆ value01 ┆ key02 ┆ value02 ┆ key03 ┆ value03 │
│ ---   ┆ ---   ┆ ---     ┆ ---   ┆ ---     ┆ ---   ┆ ---     │
│ i64   ┆ i64   ┆ f64     ┆ i64   ┆ f64     ┆ i64   ┆ f64     │
╞═══════╪═══════╪═════════╪═══════╪═════════╪═══════╪═════════╡
│ 0     ┆ 0     ┆ 0.0     ┆ 10    ┆ 0.0     ┆ 20    ┆ 0.0     │
│ 1     ┆ 1     ┆ 1.0     ┆ 11    ┆ 2.0     ┆ 21    ┆ 3.0     │
│ 2     ┆ 2     ┆ 2.0     ┆ 12    ┆ 4.0     ┆ 22    ┆ 6.0     │
│ 3     ┆ 3     ┆ 3.0     ┆ 13    ┆ 6.0     ┆ 23    ┆ 9.0     │
│ 4     ┆ 4     ┆ 4.0     ┆ 14    ┆ 8.0     ┆ 24    ┆ 12.0    │
│ 5     ┆ 5     ┆ 5.0     ┆ 15    ┆ 10.0    ┆ 25    ┆ 15.0    │
│ 6     ┆ 6     ┆ 6.0     ┆ 16    ┆ 12.0    ┆ 26    ┆ 18.0    │
│ 7     ┆ 7     ┆ 7.0     ┆ 17    ┆ 14.0    ┆ 27    ┆ 21.0    │
│ 8     ┆ 8     ┆ 8.0     ┆ 18    ┆ 16.0    ┆ 28    ┆ 24.0    │
│ 9     ┆ 9     ┆ 9.0     ┆ 19    ┆ 18.0    ┆ 29    ┆ 27.0    │
└───────┴───────┴─────────┴───────┴─────────┴───────┴─────────┘

整形中 (select まで)

shape: (10, 4)
┌───────┬────────────────────┬─────────────┬───────────────────┐
│ index ┆ _id                ┆ key         ┆ value             │
│ ---   ┆ ---                ┆ ---         ┆ ---               │
│ i64   ┆ list[str]          ┆ list[i64]   ┆ list[f64]         │
╞═══════╪════════════════════╪═════════════╪═══════════════════╡
│ 0     ┆ ["01", "02", "03"] ┆ [0, 10, 20] ┆ [0.0, 0.0, 0.0]   │
│ 1     ┆ ["01", "02", "03"] ┆ [1, 11, 21] ┆ [1.0, 2.0, 3.0]   │
│ 2     ┆ ["01", "02", "03"] ┆ [2, 12, 22] ┆ [2.0, 4.0, 6.0]   │
│ 3     ┆ ["01", "02", "03"] ┆ [3, 13, 23] ┆ [3.0, 6.0, 9.0]   │
│ 4     ┆ ["01", "02", "03"] ┆ [4, 14, 24] ┆ [4.0, 8.0, 12.0]  │
│ 5     ┆ ["01", "02", "03"] ┆ [5, 15, 25] ┆ [5.0, 10.0, 15.0] │
│ 6     ┆ ["01", "02", "03"] ┆ [6, 16, 26] ┆ [6.0, 12.0, 18.0] │
│ 7     ┆ ["01", "02", "03"] ┆ [7, 17, 27] ┆ [7.0, 14.0, 21.0] │
│ 8     ┆ ["01", "02", "03"] ┆ [8, 18, 28] ┆ [8.0, 16.0, 24.0] │
│ 9     ┆ ["01", "02", "03"] ┆ [9, 19, 29] ┆ [9.0, 18.0, 27.0] │
└───────┴────────────────────┴─────────────┴───────────────────┘

整形後

shape: (30, 4)
┌───────┬─────┬─────┬───────┐
│ index ┆ _id ┆ key ┆ value │
│ ---   ┆ --- ┆ --- ┆ ---   │
│ i64   ┆ str ┆ i64 ┆ f64   │
╞═══════╪═════╪═════╪═══════╡
│ 0     ┆ 01  ┆ 0   ┆ 0.0   │
│ 0     ┆ 02  ┆ 10  ┆ 0.0   │
│ 0     ┆ 03  ┆ 20  ┆ 0.0   │
│ 1     ┆ 01  ┆ 1   ┆ 1.0   │
│ 1     ┆ 02  ┆ 11  ┆ 2.0   │
│ …     ┆ …   ┆ …   ┆ …     │
│ 8     ┆ 02  ┆ 18  ┆ 16.0  │
│ 8     ┆ 03  ┆ 28  ┆ 24.0  │
│ 9     ┆ 01  ┆ 9   ┆ 9.0   │
│ 9     ┆ 02  ┆ 19  ┆ 18.0  │
│ 9     ┆ 03  ┆ 29  ┆ 27.0  │
└───────┴─────┴─────┴───────┘

コード

import numpy as np
import polars as pl
import polars.selectors as cs

df = pl.DataFrame({
    "index": np.arange(10),
    "key01": np.arange(10),
    "value01": 1.0 * np.arange(10),
    "key02": np.arange(10, 20),
    "value02": 2.0 * np.arange(10),
    "key03": np.arange(20, 30),
    "value03": 3.0 * np.arange(10),
})

(
    df
    .select(
        "index",
        # NOTE: 列がソート済みと仮定している
        _id=pl.lit(df.select(cs.starts_with("key")).columns).list.eval(
            pl.element().str.extract(r"(\d+)")
        ),
        key=pl.concat_list(cs.starts_with("key")),
        value=pl.concat_list(cs.starts_with("value")),
    )
    .explode("_id", "key", "value")
)
key と value で型が同じなら使えるやつ

unpivot の時点で keyvalue が同じ列になってしまうため、型昇格ができない組み合わせだと死ぬ

import numpy as np
import polars as pl
import polars.selectors as cs

df = pl.DataFrame({
    "index": np.arange(10),
    "key01": np.arange(10),
    "value01": 1 * np.arange(10),
    "key02": np.arange(10, 20),
    "value02": 2 * np.arange(10),
    "key03": np.arange(20, 30),
    "value03": 3 * np.arange(10),
})

(
    df.unpivot(
        cs.starts_with("key", "value"),
        index=["index"],
        variable_name="__variable",
        value_name="__value",
    )
    .with_columns(
        pl.col("__variable")
        .str.extract_groups(r"^(?<_name>.*?)(?<_id>\d+)$")
        .struct.unnest()
    )
    .pivot("_name", index=["index", "_id"], values="__value")
    .sort("index", "_id")
)

自己結合

polars.DataFrame.pipe を使うと再代入や一次変数をなくせる。

整形前

shape: (9, 3)
┌───────┬────────┬───────┐
│ index ┆ index2 ┆ value │
│ ---   ┆ ---    ┆ ---   │
│ i64   ┆ i64    ┆ f64   │
╞═══════╪════════╪═══════╡
│ 1     ┆ 1      ┆ 0.0   │
│ 1     ┆ 2      ┆ 2.0   │
│ 1     ┆ 3      ┆ 4.0   │
│ 2     ┆ 1      ┆ 6.0   │
│ 2     ┆ 2      ┆ 8.0   │
│ 2     ┆ 3      ┆ 10.0  │
│ 3     ┆ 1      ┆ 12.0  │
│ 3     ┆ 2      ┆ 14.0  │
│ 3     ┆ 3      ┆ 16.0  │
└───────┴────────┴───────┘

整形後

shape: (9, 4)
┌───────┬────────┬───────┬────────────┐
│ index ┆ index2 ┆ value ┆ value_diff │
│ ---   ┆ ---    ┆ ---   ┆ ---        │
│ i64   ┆ i64    ┆ f64   ┆ f64        │
╞═══════╪════════╪═══════╪════════════╡
│ 1     ┆ 1      ┆ 0.0   ┆ null       │
│ 1     ┆ 2      ┆ 2.0   ┆ 2.0        │
│ 1     ┆ 3      ┆ 4.0   ┆ 2.0        │
│ 2     ┆ 1      ┆ 6.0   ┆ null       │
│ 2     ┆ 2      ┆ 8.0   ┆ 2.0        │
│ 2     ┆ 3      ┆ 10.0  ┆ 2.0        │
│ 3     ┆ 1      ┆ 12.0  ┆ null       │
│ 3     ┆ 2      ┆ 14.0  ┆ 2.0        │
│ 3     ┆ 3      ┆ 16.0  ┆ 2.0        │
└───────┴────────┴───────┴────────────┘

コード

複雑な計算を入れられるやつ

df = pl.DataFrame({
    "index": [1] * 3 + [2] * 3 + [3] * 3,
    "index2": [1, 2, 3] * 3,
    "value": 2.0 * np.arange(9),
})

(
    df.pipe(
        lambda _df: _df.join(
            _df,
            how="left",
            left_on=["index", "index2"],
            right_on=["index", pl.col("index2") + 1],
        )
    )
    .with_columns(value_diff=pl.col("value") - pl.col("value_right"))
    .drop(cs.ends_with("_right"))
)

複雑な計算をしない場合

df = pl.DataFrame({
    "index": [1] * 3 + [2] * 3 + [3] * 3,
    "index2": [1, 2, 3] * 3,
    "value": 2.0 * np.arange(9),
})

df.with_columns(
    value_diff=pl.col("value").diff().over("index", order_by="index2"),
)

腐った ON, OFF 信号の前処理

コード

import matplotlib.pyplot as plt
import polars as pl

df = pl.DataFrame({
    #          x, x, x, x        x, x, x, x
    "on":  [0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0],
    "off": [1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1],
}).with_row_index()

df_out = (
    df.with_columns(
        # ON, OFF 信号を立ち上がりパルスだけ残す
        pl.col("on", "off").cast(pl.Int64).diff().clip(0, 1).cast(pl.Boolean)
        .name.suffix("_pulse")
    )
    .with_columns(
        # ON の立ち上がりの時は ON (OFF 立ち上がりでも無視)
        state=pl.when("on_pulse").then(True)
        # OFF 立ち上がり単独の時は OFF
        .when("off_pulse").then(False)
        # それ以外は前の状態から継続
        .otherwise(None)
        .fill_null(strategy="forward")
    )
    # 初期状態だけ定義できないので OFF を与える
    .fill_null(False)
)

fig, ax = plt.subplots(nrows=5, layout="constrained")
ax[0].step("index", "on", where="post", data=df_out)
ax[0].set_ylabel("ON")
ax[1].step("index", "off", where="post", data=df_out)
ax[1].set_ylabel("OFF")
ax[2].step("index", "on_pulse", where="post", data=df_out)
ax[2].set_ylabel("ON Pulse")
ax[3].step("index", "off_pulse", where="post", data=df_out)
ax[3].set_ylabel("OFF Pulse")
ax[4].step("index", "state", where="post", data=df_out)
ax[4].set_ylabel("ON State")
fig.show()

結果

Figure_1.png

指定値が出てくるまで選択

特定のイベントが来たら後のデータはいらないときに

import numpy as np
import polars as pl

df = pl.DataFrame({
    "index": np.arange(8),
    "event": [0, 2, 100, 0, 42, 0, 100, 3],
    "value": np.linspace(0.1, 0.8, 8),
})

df.filter((pl.col("event") == 42).rle_id() <= 1)
shape: (8, 3)
┌───────┬───────┬───────┐
│ index ┆ event ┆ value │
│ ---   ┆ ---   ┆ ---   │
│ i64   ┆ i64   ┆ f64   │
╞═══════╪═══════╪═══════╡
│ 0     ┆ 0     ┆ 0.1   │
│ 1     ┆ 2     ┆ 0.2   │
│ 2     ┆ 100   ┆ 0.3   │
│ 3     ┆ 0     ┆ 0.4   │
│ 4     ┆ 42    ┆ 0.5   │
│ 5     ┆ 0     ┆ 0.6   │
│ 6     ┆ 100   ┆ 0.7   │
│ 7     ┆ 3     ┆ 0.8   │
└───────┴───────┴───────┘
shape: (5, 3)
┌───────┬───────┬───────┐
│ index ┆ event ┆ value │
│ ---   ┆ ---   ┆ ---   │
│ i64   ┆ i64   ┆ f64   │
╞═══════╪═══════╪═══════╡
│ 0     ┆ 0     ┆ 0.1   │
│ 1     ┆ 2     ┆ 0.2   │
│ 2     ┆ 100   ┆ 0.3   │
│ 3     ┆ 0     ┆ 0.4   │
│ 4     ┆ 42    ┆ 0.5   │
└───────┴───────┴───────┘
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?