Polars 0.20 恒例の破壊的変更の一足先のご紹介 

# はじめに

全国のPolarsファンのみなさまこんにちは。カジュアルに破壊的変更を行うPolarsのversion 0.20 のリリースが近づいてきました。そこで、今回の破壊的変更を一足先にご紹介いたします。ちなみに `map_dict()` が `replace()` に rename されていたのは[最近知りました](https://twitter.com/momijiame/status/1734849212033212692)。

## Disclamers

この内容は本家release noteの [pr#12872](https://github.com/pola-rs/polars/pull/12872/files) について、重要度が大きいと思われる変更点**だけ**を2023年12月16日時点で日本語で紹介しているだけです。おそらく未来の時点で読んでも（また破壊的変更が加わっていて）参考にならない可能性があります。翻訳上のミスや解釈の誤りなどありましたら、コメント欄でご指摘いただけますと助かります。

# version 0.20 破壊的変更内容抜粋

## join 時の null の扱い

- 0.19まで: join のkeyにnullが存在する場合、null自体もjoinのkeyの対象としていた
- 0.20から: null はデフォルトでは join key の対象としない。 `join_nulls=True` で0.19までの挙動と同じになる。

一般のSQLエンジンの挙動に近くなると思う。なお実行速度アップにも貢献するらしい。

**Example**

Before:

```pycon
>>> df1 = pl.DataFrame({"a": [1, 2, None], "b": [4, 4, 4]})
>>> df2 = pl.DataFrame({"a": [None, 2, 3], "c": [5, 5, 5]})
>>> df1.join(df2, on="a", how="inner")
shape: (2, 3)
┌──────┬─────┬─────┐
│ a    ┆ b   ┆ c   │
│ ---  ┆ --- ┆ --- │
│ i64  ┆ i64 ┆ i64 │
╞══════╪═════╪═════╡
│ null ┆ 4   ┆ 5   │
│ 2    ┆ 4   ┆ 5   │
└──────┴─────┴─────┘
```

After:

```pycon
>>> df1.join(df2, on="a", how="inner")
shape: (1, 3)
┌─────┬─────┬─────┐
│ a   ┆ b   ┆ c   │
│ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ i64 │
╞═════╪═════╪═════╡
│ 2   ┆ 4   ┆ 5   │
└─────┴─────┴─────┘
>>> df1.join(df2, on="a", how="inner", join_nulls=True)  # Keeps previous behavior
shape: (2, 3)
┌──────┬─────┬─────┐
│ a    ┆ b   ┆ c   │
│ ---  ┆ --- ┆ --- │
│ i64  ┆ i64 ┆ i64 │
╞══════╪═════╪═════╡
│ null ┆ 4   ┆ 5   │
│ 2    ┆ 4   ┆ 5   │
└──────┴─────┴─────┘
```

### 個人的感想

既存のSQLエンジンの挙動と整合的になるので好ましい変更。

## outer join での 左右のkeyの列をキープするようにする

言葉で説明しづらいので、次のexampleを見てください。

**Example**

Before:

```pycon
>>> df1 = pl.DataFrame({"L1": ["a", "b", "c"], "L2": [1, 2, 3]})
>>> df2 = pl.DataFrame({"L1": ["a", "c", "d"], "R2": [7, 8, 9]})
>>> df1.join(df2, on="L1", how="outer")
shape: (4, 3)
┌─────┬──────┬──────┐
│ L1  ┆ L2   ┆ R2   │
│ --- ┆ ---  ┆ ---  │
│ str ┆ i64  ┆ i64  │
╞═════╪══════╪══════╡
│ a   ┆ 1    ┆ 7    │
│ c   ┆ 3    ┆ 8    │
│ d   ┆ null ┆ 9    │
│ b   ┆ 2    ┆ null │
└─────┴──────┴──────┘
```

After:

```pycon
>>> df1.join(df2, on="L1", how="outer")
shape: (4, 4)
┌──────┬──────┬──────────┬──────┐
│ L1   ┆ L2   ┆ L1_right ┆ R2   │
│ ---  ┆ ---  ┆ ---      ┆ ---  │
│ str  ┆ i64  ┆ str      ┆ i64  │
╞══════╪══════╪══════════╪══════╡
│ a    ┆ 1    ┆ a        ┆ 7    │
│ b    ┆ 2    ┆ null     ┆ null │
│ c    ┆ 3    ┆ c        ┆ 8    │
│ null ┆ null ┆ d        ┆ 9    │
└──────┴──────┴──────────┴──────┘
>>> df1.join(df2, on="a", how="outer_coalesce")  # Keeps previous behavior
shape: (4, 3)
┌─────┬──────┬──────┐
│ L1  ┆ L2   ┆ R2   │
│ --- ┆ ---  ┆ ---  │
│ str ┆ i64  ┆ i64  │
╞═════╪══════╪══════╡
│ a   ┆ 1    ┆ 7    │
│ c   ┆ 3    ┆ 8    │
│ d   ┆ null ┆ 9    │
│ b   ┆ 2    ┆ null │
└─────┴──────┴──────┘
```

### 個人的感想

pandasの `merge(..., how="outer")` 的な結果が変更となるので要注意。 
SQLエンジンでの挙動としてはPostgreSQL系の扱いと同じになる。というかSQLエンジンによって full outer join がkeyを潰す（1列）のか残す（2列）か挙動が違うことを[初めて知った](https://chat.openai.com/share/1f3e5282-664a-4ece-b3d2-ed6c88099342)。列数が変わるので既存コードが動かなくなるレベルの破壊的変更。ぞくぞくしますね。


## `count` にて null values を無視するようになる。

`Expr` or `Series` のメソッドの挙動の変更です。

after: `count()` -> null含まない個数。 `len()` null含む個数。

**Example**

Before:

```pycon
>>> df = pl.DataFrame({"a": [1, 2, None]})
>>> df.select(pl.col("a").count())
shape: (1, 1)
┌─────┐
│ a   │
│ --- │
│ u32 │
╞═════╡
│ 3   │
└─────┘
```

After:

```pycon
>>> df.select(pl.col("a").count())
shape: (1, 1)
┌─────┐
│ a   │
│ --- │
│ u32 │
╞═════╡
│ 2   │
└─────┘
>>> df.select(pl.col("a").len())  # Mirrors previous behavior
shape: (1, 1)
┌─────┐
│ a   │
│ --- │
│ u32 │
╞═════╡
│ 3   │
└─────┘
```

### 個人的感想

挙動としてはflavorな変更。既存コードで値が結果が変わった場合はこれを疑うべき。

## `NaN` values が equal あつかいされる

Floating `NaN` は、一般的（IEEE754的）には `NaN` 同士の比較演算子を適用した結果は全て `False` となる。その挙動に従わなり equal での比較が `True` となる。

**Example**

Before:

```pycon
>>> s = pl.Series([1.0, float("nan"), float("inf")])
>>> s == s
shape: (3,)
Series: '' [bool]
[
        true
        false
        true
]
```

After:

```pycon
>>> s == s
shape: (3,)
Series: '' [bool]
[
        true
        true
        true
]
```

### 個人的感想。

IEEE 754のルールを逸脱する危険な変更。個人的に全く賛同できない。

### その他変更

- `replace` メソッドの修正（マッチしない場合はnullとなるなど）
- datetime系の要素のIntが効率化（例: dt.month や dt.hour が u32型 から i8型へ）（なぜ u8 でないのであろう？）
- `value_counts()` のカラム名が `counts` から `count` に変更となる
- cumk系関数が`cum*` から `cum_*` に renameされる


以上です。破壊的変更を楽しむくらいPolars愛に溢れる皆様はゾクゾクできましたでしょうか。