PolarsのConfig使い方まとめ

## はじめに

Polars を書いていると、EDA などでデータフレームを出力する際「微妙にかゆいところに手が届かないな・・・」となることがあります。例えば

「デフォルトのテーブル表示列数、もっと増やしたいな・・」

や、

「文字列型の要素、省略されて"..."になってるからもっと表示したいな・・」

などです。  
そんな時に初めて触るのが Polars の `Config` クラスじゃないでしょうか？（僕はそうでした）  
この例の場合

```python
pl.Config.set_tbl_cols(n)
```

```python
pl.Config.set_fmt_str_lengths(n)
```

と設定することで表示の制限を指定した `n` 列・文字まで増やせます。  
この記事は、そんな `Config` クラスについて「他にどんな機能があるんだろう？」という素朴な疑問から生まれた記事です。ニッチな設定項目も多いですが、知っておくと便利な機能もあるかもしれません。

そして本記事は [Polars Advent Calendar](https://qiita.com/advent-calendar/2024/polars) 最終日の記事です！ほかの記事もぜひご覧ください😎

## 1. Polars Config とは
Polars の `Config` クラスは、データフレームの表示やストリーミング、ログ出力などの設定を管理する仕組みです。  
「データフレームを表示するときに列名をもっと見やすくしたい」「数値の小数点以下桁数を指定したい」など、ちょっとしたニーズを満たすのに役立ちます。

Polars の設定周りはデフォルトでも十分便利ですが、設定をカスタマイズすることで開発中のデバッグ効率が上がったり、大量のカラム・行があるデータフレームを扱いやすくなるかもしれません。

## 2. Polars Config の使い方
### 2.1 設定の変更方法
`Config` クラスはいくつかの操作インターフェースを持っています。

#### 2.1.1 シンプルな設定方法
もっとも単純な使い方は、単に `Config.set_xxx` 系のクラスメソッドを呼び出すだけです。

```python
import polars as pl
from polars import Config

# テーブル表示列数を 10 列に増やす
Config.set_tbl_cols(10)

# 文字列型の省略を 50 文字にする
Config.set_fmt_str_lengths(50)

# 小数点以下 3 桁まで表示する
Config.set_float_precision(3)
```

また、単に `Config` クラスをインスタンス化することでも設定が反映されます。この際、設定したい項目をコンストラクタで指定します。クラスメソッド `Config.set_xxx` から `set_` を取ったものが対応します。
```python
import polars as pl
from polars import Config

# テーブル表示列数を 10 列に増やす
Config(tbl_cols=10)

# 文字列型の省略を 50 文字にする
Config(fmt_str_lengths=50)

# 小数点以下 3 桁まで表示する
Config(float_precision=3)
```

このように一度設定すると、そのセッション中は設定が引き継がれます。  

#### 2.1.2 コンテキストマネージャとしての使い方
`Config` はコンテキストマネージャとして利用できます。`with` ブロックの中で設定を変更すると、ブロックを抜けた後は元の状態に戻ります。

```python
import polars as pl
from polars import Config

df = pl.DataFrame({
    "col1": ["a" * 60, "b" * 60],
    "col2": ["c", "d"]
})

# 元の設定
print("=== Before Config ===")
print(df)

with Config() as cfg:
    # ブロック内でのみ有効な設定
    cfg.set_tbl_cols(50)
    cfg.set_fmt_str_lengths(80)
    print("=== Inside Config Context ===")
    print(df)

# ブロックを抜けると設定が元に戻る
print("=== After Config ===")
print(df)
```

**出力**
```plaintext
=== Before Config ===
shape: (2, 2)
┌─────────────────────────────────┬──────┐
│ col1                            ┆ col2 │
│ ---                             ┆ ---  │
│ str                             ┆ str  │
╞═════════════════════════════════╪══════╡
│ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… ┆ c    │
│ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb… ┆ d    │
└─────────────────────────────────┴──────┘
=== Inside Config Context ===
shape: (2, 2)
┌──────────────────────────────────────────────────────────────┬──────┐
│ col1                                                         ┆ col2 │
│ ---                                                          ┆ ---  │
│ str                                                          ┆ str  │
╞══════════════════════════════════════════════════════════════╪══════╡
│ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ┆ c    │
│ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ┆ d    │
└──────────────────────────────────────────────────────────────┴──────┘
=== After Config ===
shape: (2, 2)
┌─────────────────────────────────┬──────┐
│ col1                            ┆ col2 │
│ ---                             ┆ ---  │
│ str                             ┆ str  │
╞═════════════════════════════════╪══════╡
│ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… ┆ c    │
│ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb… ┆ d    │
└─────────────────────────────────┴──────┘
```

### 2.2 Config の確認
現状の設定は `Config.state()` とすることで取得できます。この際、引数 `if_set` に `True` を渡すことで、`None` 以外の設定値が取得でき便利です。デフォルトが `None` でない設定項目もあるため「自分が設定した項目を表示」というわけではありません。
```python
Config.state(if_set=True)
```
**出力**
```plaintext
{'POLARS_FMT_MAX_COLS': '10',
 'POLARS_FMT_STR_LEN': '50',
 'set_fmt_float': 'mixed',
 'set_float_precision': 3,
 'set_thousands_separator': '',
 'set_decimal_separator': '.',
 'set_trim_decimal_zeros': False}
```

### 2.3 Config のリセット
設定は `Config.restore_defaults()` とすることですべてデフォルトに戻せます。
```python
Config.restore_defaults()
```

### 2.4 Config のセーブ・ロード
Polars では一度設定した内容を JSON 形式でセーブ・ロードできます。

```python
# 設定を JSON 文字列として取得
saved_config = Config.save()

# 別の場所で設定をロード
Config.load(saved_config)
```

ファイルとして保存し、それを読み込むこともできます。

```python
# 設定をファイルに書き出し
Config.save_to_file("my_config.json")

# 後でロード
Config.load_from_file("my_config.json")
```

最強のコンフィグが出来上がったら大事に保存しておきましょう。

## 3. 設定項目の紹介

本記事の冒頭で紹介した `Config.set_tbl_cols(n)` や `Config.set_fmt_str_lengths(n)` 以外にも、たくさんの設定項目が用意されています。  
ここでは主なカテゴリーごとにまとめていきます。

### 3.1 テーブルの表示関連

#### 3.1.1 要素の表示制限の制御
- **`Config.set_tbl_cols(n)`**  
  データフレームを表示する際、最大で何列まで見せるかを設定します。デフォルトだとカラムが多いと `...` で省略されがちですが、増やしたい場合に便利です。  
  `None` を指定するとデフォルトに戻ります。`-1` など負の値を指定すると無制限になります。

- **`Config.set_fmt_str_lengths(n)`**  
  文字列が途中で省略されている際に表示する文字数を増やします。もしくは文字列が長すぎる場合に、何文字で切り捨てるかを設定します。

- **`Config.set_tbl_rows(n)`**  
  テーブルの表示時、最大で何行まで表示するかを設定します。行数が多いデータフレームのレコードをたくさん見たい時におすすめです。こちらも `-1` で無制限にできますが、巨大なデータセットでこれをやるとフリーズするので注意。

- **`Config.set_fmt_table_cell_list_len(n)`**  
  リスト型の列において、セルの要素を最大何個まで表示するかを設定できます。

#### 3.1.2 アラインメント
- **`Config.set_tbl_cell_alignment("LEFT" | "CENTER" | "RIGHT")`**  
  テーブル全体のセルをどのように配置するかを設定できます。  
- **`Config.set_tbl_cell_numeric_alignment("LEFT" | "CENTER" | "RIGHT")`**  
  数値列だけアラインメントを変えたいときに活用できます。

#### 3.1.3 テーブルの見た目関連
- **`Config.set_ascii_tables([active])`**  
  テーブルの罫線を ASCII 文字で表示するかどうかを設定します。 後述の `Config.set_tbl_formatting` で format を"ASCII_FULL" とした時と同じです。
- **`Config.set_tbl_hide_column_names([active])`**  
  列名を非表示にするか設定します。  
- **`Config.set_tbl_hide_column_data_types([active])`**  
  データ型の表示をオフにできます。

##### 3.1.3.1 `set_tbl_formatting`
テーブル表示のフォーマットを設定します。設定できるフォーマットが沢山あるためワクワク設定項目ではあるのですが、このフォーマット指定は基本的に `print` での出力が対象のため、Notebook 環境ではいまいちかもしれません。
しかし、そんな中おすすめの設定は "MARKDOWN" のフォーマット指定です。
```python
import polars as pl
from polars import Config

df = pl.DataFrame(
    {"abc": [-2.5, 5.0], "mno": ["hello", "world"], "xyz": [True, False]}
)
with Config(
    tbl_formatting="MARKDOWN",
    tbl_hide_column_data_types=True,
    tbl_hide_dataframe_shape=True,
):
    print(df)
```
**出力**
```plaintext
| abc  | mno   | xyz   |
|------|-------|-------|
| -2.5 | hello | true  |
| 5.0  | world | false |
```
出力が Markdown 形式になってくれるためそのままコピーしてドキュメントにペースト、のような活用ができそうですね。

### 3.2 その他

#### 3.2.1 `auto-structify`
**`Config.set_auto_structify([active])`** では、複数のカラムをまとめる処理（multi-output expression）を自動的に [Struct](https://docs.pola.rs/api/python/stable/reference/api/polars.datatypes.Struct.html) 型に変換するかどうかを設定します。  
  デフォルトでは `False` ですが、これを `True` にすると、例えば `select(pl.col("a"), pl.col("b"))` のように複数の列を取り出す操作が、1つの Struct カラムとして扱われるようになります。

```python
import polars as pl
from polars import Config

df = pl.DataFrame({
    "v": [1, 2, 3],
    "v2": [4, 5, 6]
})

# auto_structify を有効にしたコンテキスト
with Config(auto_structify=True):
    out = df.select(pl.all())

# 結果は struct カラムとしてまとめられる
print(out)
```

**出力**
```
shape: (3, 1)
┌───────────┐
│ v         │
│ ---       │
│ struct[2] │
╞═══════════╡
│ {1,4}     │
│ {2,5}     │
│ {3,6}     │
└───────────┘
```
これまでの設定項目と異なり、処理結果自体が変わってしまうため注意が必要です

#### 3.2.2 `set_verbose`
**`Config.set_verbose([active])`** では、ロギングやデバッグ情報を有効にするかどうかを設定します。
デフォルトでは `False` で、`True` にすることで処理ごとにログが出力されるようになります。

```python
import polars as pl
from polars import Config

# コンテキスト内で一時的に verbose をオン
with Config(verbose=True):
    # ここでの Polars の処理がログ付きで実行される
    df = pl.DataFrame({"a": [1, 2, 3]})
    df_filtered = df.filter(pl.col("a") > 1)
    print(df_filtered)
```
ログ自体は標準エラー出力のため、デフォルトの Notebook ではログは見られません。
**出力（コマンドラインで実行）**
```plaintext
dataframe filtered ←これがログ、標準エラー出力
shape: (2, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 2   │
│ 3   │
└─────┘
```
この機能、めっちゃ便利かも？と思いましたがそもそも Polars はエラーメッセージもそこそこ親切ですし、デバッグ目的だとそこまで嬉しくなさそう？
定常的なバッチ処理などでログを常に出力しておくところでエラー時の分析に役立てる、というのはありそうですね。めっちゃ活用してるよ！というケースがありましたら是非教えてほしいです！

## 4. デコレータを利用
おまけ要素、デコレータを用いて設定スコープを制御する例を紹介します。

### 4.1 関数実行中のみ特定の設定を適用
コンテキストマネージャと同じ発想で、デコレータとして `Config` を適用できます。  
関数実行前に設定が適用され、関数終了後は元に戻るため、スコープが管理しやすくなります。このような動作にする場合は `Config` クラスのコンストラクタに `apply_on_context_enter=True` というオプションを設定します。

```python
import polars as pl
from polars import Config

cfg_ascii_frames = Config(ascii_tables=True, apply_on_context_enter=True)

@cfg_ascii_frames
def write_markdown_frame_to_stdout(df: pl.DataFrame) -> None:
    print(df)  # ASCII罫線のテーブル表示が適用される
```
`apply_on_context_enter` を指定しない場合（デフォルトで `False`）`Config` クラスがインスタンス化したタイミングで即座に設定がグローバルへ反映されてしまいます。

### 4.2 複数の `Config` インスタンスを使い分ける
表示用、デバッグ用など、複数の設定オブジェクトを使い分けることも可能です。

```python
import polars as pl
from polars import Config

# デバッグ用にverboseを有効にしておきたいConfig
cfg_verbose = Config(verbose=True, apply_on_context_enter=True)
# Markdown出力用
cfg_markdown = Config(tbl_formatting="MARKDOWN", apply_on_context_enter=True)

@cfg_markdown
def write_markdown_frame(df: pl.DataFrame) -> None:
    # 何らかの処理
    print(df) # マークダウンフォーマットで出力される

@cfg_verbose
def debug_something(df: pl.DataFrame) -> None:
    # 何らかの処理　←処理ごとにログがエラー出力される
    print(df) # マークダウンフォーマットでない、デフォルトの出力
```

## 5. まとめ
本記事では、Polars の Config クラスについて取り扱いました。
一部普段使いできそうな設定項目もありますが、基本はニッチで「どこで使うねん」というものも多かったですね。しかし、知っておくとそこそこ便利・・かも？？です。少なくとも僕は Polars 愛が増しました。

皆さんにとっての最強の設定が見つかったら是非教えてください！