TitanicでPolarsに入門する



# この資料について
- [Polars Data Crunch #3](https://polars-ja.connpass.com/event/325132/) でのLT用の、Polars入門者向けの資料です
- Kaggleでよく使われる、**タイタニックのデータ**を題材にします
- 内容自体は他の色んな方が書いていることのn番煎じですが、動画はまだないはず…！
    - 動画は[コチラ](https://www.youtube.com/live/O69a_V8m-jU?si=ZdqmZKS6Kd-8w3kW)[^thx]
- **資料の立ち位置**：網羅性✗、正確性✗、ひと通り触れる○
- コード：[Kaggle notebook](https://www.kaggle.com/code/jintanaka/titanic-polars)、[Google colab](https://colab.research.google.com/gist/tanaka-jin/0d129e3e2d4c1e593c2a380958d08524/titanic-polars.ipynb)、[Github gist](https://gist.github.com/tanaka-jin/0d129e3e2d4c1e593c2a380958d08524)

[^thx]: もちろんみなさまの記事も参考にしています。ありがとうございます
---

# タイタニックについて

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/3966c759-32a6-d26e-5f13-ac0b7024433e.png)
https://www.behance.net/gallery/74714377/Titanic-Infographic/modules/434407837

---

# タイタニックデータについて
- Kaggleの入門用として、「誰が生き残ったか」を予測する課題がある
- データ分析を始めて最初に触るデータがこれなケースも少なくない（はず）
- 今回使うカラム
    - Survived: 生き残ったか
    - Sex: 性別
    - Pclass: 旅客クラス（1等、2等、3等）

---

# polarsとは？
- 早い、お手軽、書きやすいの三拍子が揃った話題のライブラリ
- 詳しくはこちらを！

https://qiita.com/_jinta/items/fac13f09e8e8a5769b79

- 本資料では、polarsを使った一番ベーシックな部分の処理を流れに沿って説明


---

# データの読み込み

```python
import polars as pl
df = pl.read_csv('https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv')
```

---

# データの表示

```python
display(df)
# print(df)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/8b015297-be65-1c32-c8db-346fbda2da72.png)


- 行数、列数も出力してくれる
- 列ごとに型が決まってる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/c78c6745-3714-ba86-7aa3-0015c69307fd.png)


---

# 列を取り出してみよう
- `select` で列を取り出すことができる
- 列を指定するには、`pl.col` を使う[^col]
- 複数指定することで、複数列取り出すことも可能

```python
df.select(pl.col("Sex"))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/963ba9ea-2072-19c3-5444-cabc0fd9d5cd.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/632ac37a-fa1f-30da-eddb-555351faea4d.png)


[^col]: `select` では `pl.col` は使わないで、単に列名だけ記述してもいい。

---

# 条件に当てはまる行を取り出してみよう
- `filter` でレコードの抽出ができる
- ここでは、`Sex` という列が `"male"` であるようなレコードを取り出す

```python
df.filter(pl.col("Sex") == "male")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/5f90ea0a-20f9-150c-80ce-b73bebff500c.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/7784b5c7-08bc-61af-0004-e7cea2e6bcf9.png)


---


# エクスプレッション
- 先ほどの `pl.col("Sex") == "male"` の部分を、expressionと言う
- エクスプレッションは、列に対して何かしらの操作を加えたものを返す、いわば関数のようなもの
- 実は pl.Expr はどこでも使える　→　先ほどと全く同じものを `select` で使ってみよう

```python
df.select(pl.col("Sex") == "male")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/78c00992-1dbf-0fd0-2b51-01e79c6c6594.png)

- → やや見にくいので、元のカラムと一緒に出力する

---

# 別のカラム名を付けよう
- `alias` をExpressionに続けると、列名を変更できる[^ha]
- ※同じ名前の列を複数持つことはできない

```python
df.select(pl.col("Sex"), (pl.col("Sex") == "male").alias("is_male"))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/c75b562f-a78e-53a4-343b-b9527355980e.png)

[^ha]: `pl.col("Sex")` もExpressionだし、`pl.col("Sex") == "male"` もそうだし、 ` (pl.col("Sex") == "male").alias("is_male"))` 全体もExpressionnである

---

# 応用例：男性の生存率を求めてみよう
- `filter` と `select` を組み合わせて求められる
- Polarsでは `.` を付けて、処理を続けて書くことが出来る
    - 上から順に処理される
- 書き方のお作法としてよく見るものは以下2つ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/47ce1f7a-efba-835e-4d2f-965c054060d9.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/e9d9ef0a-306d-b08c-f713-c86dd6ac8821.png)

- これが長く続くと凄く複雑なことをしているように見えるが、シンプルなパーツを組み合わせてるだけで、いわばレゴみたいなもの
- ※`mean()` の振る舞いとして、先ほどと違い出力が1行に集約されていることが分かる

---

# グループごとの集約
- わざわざ男女それぞれについて行を抽出してそこから平均を算出しなくても、グループごとの集約が可能
- `Pclass` ごとの生存確率を求めてみる
- `group_by("...").agg()` を使う [^tango]

```python
df.group_by('Pclass').agg(
    pl.col("Survived").mean()
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/02ee2820-f693-ea11-747c-d84122289676.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/9883790f-e157-466a-0eab-16ae23c96b4d.png)

[^tango]: Polarsでは単語ごとに `_` で分けるという命名規則で設計されてるので、 `groupby` ではなく `group_by` 。


---

# 集約を列として追加しよう

- データフレームに列を追加する場合、`select` ではなく `with_columns` を用いる
    - 追加する列は、Expressionを用いて表現する
- `Pclass` ごとの生存確率を追加してみよう
- `mean` をグループごとに行うためには、`over` でグループを指定する。この際、先ほどと違って行数が減ることはないことに注意！

```Python
df.with_columns(
    pl.col("Survived").mean().over("Pclass").alias("生存確率")
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/6b5b46d8-3a19-bfd0-2628-37ceb51160ce.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/afe69581-615d-0510-228e-8933b076a8f1.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/fe54d881-069b-9885-62d4-4019f8d8b07f.png)

---

# おまけ：提出してみよう
- 性別、クラスごとの生存確率が平均よりも高い場合、1: "生存" と予測させてみる

```python
# テストデータを読み込み
df_test = pl.read_csv('/kaggle/input/titanic/test.csv')

# trainデータを使って、性別。クラスごとの生存確率を算出
df_agg = df.group_by('Pclass', 'Sex').agg(
    pl.col("Survived").mean()
)

# 突合して、平均より高いか否かで算出
df_submission = (
    df_test
    .join(df_agg, on=["Pclass", "Sex"])
    .select(
        "PassengerId",
        (pl.col("Survived") >= pl.col("Survived").mean()).cast(pl.Int8)
    )
)
```

- `join`: 他のデータフレームとの結合。結合Keyを `on` で指定する
- `cast`: 型の変換。ここではBooleanのカラムを、整数に変換している


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/0c3c75b7-6606-6ae8-f616-9111d0e475de.png)


---

# 結果のファイル出力

```python
df_submission.write_csv("submission.csv")
```

- CSVファイルへの出力は `to_csv` ではなく、`write_csv` [^write]
- これで、一番簡単なモデルを提出できます！
- ちなみにスコアは `0.76555`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665792/391dc8ad-2ef8-2a4e-284d-477f0aaa04e9.png)

[^write]: `read` の対義語は `to` じゃなくて `write` !! ちなみにpandasからの変換は `from_pandas` ですし、pandasへの変換は `to_pandas` なので対応が取れています

---

# Polarsのコードを書く時の頭の使い方


1. 何の操作をやりたいかを考える（列を増やすか、行を絞るか、ソートするか、など）
1. 1に該当するcontext (`select`, `filter` など) を書く
1. `()` のなかに、まず `pl.col` と書く
1. やりたいエクスプレッションを書く
1. 以上を繰り返す

※あくまで１個人の感想です。


---

# その他、おすすめの情報
- Lazyフレーム
    - [遅延読み込み(Lazy) - Polars 日本語ユーザーガイド](https://polars-ja.github.io/docs-ja/user-guide/lazy/)
    - 後からまとめて処理を実行する
    - その際に、内部でいい感じに最適化してくれる
- Plot
    - [可視化 - Polars 日本語ユーザーガイド](https://polars-ja.github.io/docs-ja/user-guide/misc/visualization/#altair)
    - 組み込みの可視化メソッドが存在
- アクセサ
    - 例えば：`pl.col('Sex').str.to_uppercase` のように文字列に関する処理は `.str` にまとめられている


---
# 参考

- [Titanic Tutorial](https://www.kaggle.com/code/alexisbcook/titanic-tutorial/notebook)
- [【Polars版】『PythonではじめるKaggleスタートブック』のサンプルコード - u++の備忘録](https://upura.hatenablog.com/entry/2023/01/25/080238)
- [Polars 日本語ユーザーガイド - Polars 日本語ユーザーガイド](https://polars-ja.github.io/docs-ja/)
- [polars-ja/polars-cheat-sheet-ja: 日本語版 Polars cheat sheet](https://github.com/polars-ja/polars-cheat-sheet-ja?tab=readme-ov-file)

---

# ありがとうございました

---

# 注