はじめに
polarsとは
polarsはpythonで使える高速のデータフレームライブラリです。
データフレームライブラリといえばpandas🐼が有名ですが、それに対するpolars🐻❄️です。
今回はpolarsに慣れるため、kaggleのコンペSpaceship Titanicに、結果を提出してみます。
やること
- テーブルデータの読み書き
- 平均、標準偏差などの基本統計量の確認
- ユニークな要素の確認
- 欠損値の処理
やらないこと
- コンペのスコアは気にしません
- 高度な解析は行いません
事前準備
実行環境にはgoogle colaboratoryを使用します。
また、colab上でkaggle apiを叩いてデータのダウンロードや提出を行います。
下記ページがわかりやすかった。
https://take-tech-engineer.com/kaggle-colab-api/
やってみる
csvファイルのリード
import polars as pl
ldf = pl.scan_csv("train.csv")
先頭、末尾の行を見る
print('head', ldf.head(10).collect())
print('teil', ldf.tail(10).collect())
# result
'''
head shape: (5, 14)
┌─────────────┬────────────┬───────────┬───────┬───┬────────┬────────┬───────────────┬─────────────┐
│ PassengerId ┆ HomePlanet ┆ CryoSleep ┆ Cabin ┆ … ┆ Spa ┆ VRDeck ┆ Name ┆ Transported │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ bool ┆ str ┆ ┆ f64 ┆ f64 ┆ str ┆ bool │
╞═════════════╪════════════╪═══════════╪═══════╪═══╪════════╪════════╪═══════════════╪═════════════╡
│ 0001_01 ┆ Europa ┆ false ┆ B/0/P ┆ … ┆ 0.0 ┆ 0.0 ┆ Maham ┆ false │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Ofracculy ┆ │
│ 0002_01 ┆ Earth ┆ false ┆ F/0/S ┆ … ┆ 549.0 ┆ 44.0 ┆ Juanna Vines ┆ true │
│ 0003_01 ┆ Europa ┆ false ┆ A/0/S ┆ … ┆ 6715.0 ┆ 49.0 ┆ Altark Susent ┆ false │
│ 0003_02 ┆ Europa ┆ false ┆ A/0/S ┆ … ┆ 3329.0 ┆ 193.0 ┆ Solam Susent ┆ false │
│ 0004_01 ┆ Earth ┆ false ┆ F/1/S ┆ … ┆ 565.0 ┆ 2.0 ┆ Willy ┆ true │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Santantines ┆ │
└─────────────┴────────────┴───────────┴───────┴───┴────────┴────────┴───────────────┴─────────────┘
teil shape: (5, 14)
┌───────────┬────────────┬───────────┬──────────┬───┬────────┬────────┬──────────────┬─────────────┐
│ Passenger ┆ HomePlanet ┆ CryoSleep ┆ Cabin ┆ … ┆ Spa ┆ VRDeck ┆ Name ┆ Transported │
│ Id ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
│ --- ┆ str ┆ bool ┆ str ┆ ┆ f64 ┆ f64 ┆ str ┆ bool │
│ str ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
╞═══════════╪════════════╪═══════════╪══════════╪═══╪════════╪════════╪══════════════╪═════════════╡
│ 9276_01 ┆ Europa ┆ false ┆ A/98/P ┆ … ┆ 1643.0 ┆ 74.0 ┆ Gravior ┆ false │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Noxnuther ┆ │
│ 9278_01 ┆ Earth ┆ true ┆ G/1499/S ┆ … ┆ 0.0 ┆ 0.0 ┆ Kurta ┆ false │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Mondalley ┆ │
│ 9279_01 ┆ Earth ┆ false ┆ G/1500/S ┆ … ┆ 1.0 ┆ 0.0 ┆ Fayey Connon ┆ true │
│ 9280_01 ┆ Europa ┆ false ┆ E/608/S ┆ … ┆ 353.0 ┆ 3235.0 ┆ Celeon ┆ false │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Hontichre ┆ │
│ 9280_02 ┆ Europa ┆ false ┆ E/608/S ┆ … ┆ 0.0 ┆ 12.0 ┆ Propsh ┆ true │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Hontichre ┆ │
└───────────┴────────────┴───────────┴──────────┴───┴────────┴────────┴──────────────┴─────────────┘
'''
基本統計量を表示する
データの総数、nullデータの数、平均、標準偏差、最大、最小、中央値、25パーセンタイル、75パーセンタイルの値が確認できる。
print(ldf.collect().describe())
# result
'''
shape: (9, 15)
┌───────────┬───────────┬──────────┬───────────┬───┬───────────┬───────────┬───────────┬───────────┐
│ describe ┆ Passenger ┆ HomePlan ┆ CryoSleep ┆ … ┆ Spa ┆ VRDeck ┆ Name ┆ Transport │
│ --- ┆ Id ┆ et ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ ed │
│ str ┆ --- ┆ --- ┆ f64 ┆ ┆ f64 ┆ f64 ┆ str ┆ --- │
│ ┆ str ┆ str ┆ ┆ ┆ ┆ ┆ ┆ f64 │
╞═══════════╪═══════════╪══════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪═══════════╡
│ count ┆ 8693 ┆ 8693 ┆ 8693.0 ┆ … ┆ 8693.0 ┆ 8693.0 ┆ 8693 ┆ 8693.0 │
│ null_coun ┆ 0 ┆ 201 ┆ 217.0 ┆ … ┆ 183.0 ┆ 188.0 ┆ 200 ┆ 0.0 │
│ t ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ mean ┆ null ┆ null ┆ 0.358306 ┆ … ┆ 311.13877 ┆ 304.85479 ┆ null ┆ 0.503624 │
│ ┆ ┆ ┆ ┆ ┆ 8 ┆ 1 ┆ ┆ │
│ std ┆ null ┆ null ┆ 0.47953 ┆ … ┆ 1136.7055 ┆ 1145.7171 ┆ null ┆ 0.500016 │
│ ┆ ┆ ┆ ┆ ┆ 35 ┆ 89 ┆ ┆ │
│ min ┆ 0001_01 ┆ Earth ┆ 0.0 ┆ … ┆ 0.0 ┆ 0.0 ┆ Aard ┆ 0.0 │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Curle ┆ │
│ max ┆ 9280_02 ┆ Mars ┆ 1.0 ┆ … ┆ 22408.0 ┆ 24133.0 ┆ Zubeneb ┆ 1.0 │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Pasharne ┆ │
│ median ┆ null ┆ null ┆ 0.0 ┆ … ┆ 0.0 ┆ 0.0 ┆ null ┆ 1.0 │
│ 25% ┆ null ┆ null ┆ null ┆ … ┆ 0.0 ┆ 0.0 ┆ null ┆ null │
│ 75% ┆ null ┆ null ┆ null ┆ … ┆ 59.0 ┆ 46.0 ┆ null ┆ null │
└───────────┴───────────┴──────────┴───────────┴───┴───────────┴───────────┴───────────┴───────────┘
'''
nullが含まれない行を抽出する
print(ldf.drop_nulls().collect())
#result
'''
shape: (6_606, 14)
┌───────────┬────────────┬───────────┬──────────┬───┬────────┬────────┬──────────────┬─────────────┐
│ Passenger ┆ HomePlanet ┆ CryoSleep ┆ Cabin ┆ … ┆ Spa ┆ VRDeck ┆ Name ┆ Transported │
│ Id ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
│ --- ┆ str ┆ bool ┆ str ┆ ┆ f64 ┆ f64 ┆ str ┆ bool │
│ str ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
╞═══════════╪════════════╪═══════════╪══════════╪═══╪════════╪════════╪══════════════╪═════════════╡
│ 0001_01 ┆ Europa ┆ false ┆ B/0/P ┆ … ┆ 0.0 ┆ 0.0 ┆ Maham ┆ false │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Ofracculy ┆ │
│ 0002_01 ┆ Earth ┆ false ┆ F/0/S ┆ … ┆ 549.0 ┆ 44.0 ┆ Juanna Vines ┆ true │
│ 0003_01 ┆ Europa ┆ false ┆ A/0/S ┆ … ┆ 6715.0 ┆ 49.0 ┆ Altark ┆ false │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Susent ┆ │
│ 0003_02 ┆ Europa ┆ false ┆ A/0/S ┆ … ┆ 3329.0 ┆ 193.0 ┆ Solam Susent ┆ false │
│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
│ 9278_01 ┆ Earth ┆ true ┆ G/1499/S ┆ … ┆ 0.0 ┆ 0.0 ┆ Kurta ┆ false │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Mondalley ┆ │
│ 9279_01 ┆ Earth ┆ false ┆ G/1500/S ┆ … ┆ 1.0 ┆ 0.0 ┆ Fayey Connon ┆ true │
│ 9280_01 ┆ Europa ┆ false ┆ E/608/S ┆ … ┆ 353.0 ┆ 3235.0 ┆ Celeon ┆ false │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Hontichre ┆ │
│ 9280_02 ┆ Europa ┆ false ┆ E/608/S ┆ … ┆ 0.0 ┆ 12.0 ┆ Propsh ┆ true │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Hontichre ┆ │
└───────────┴────────────┴───────────┴──────────┴───┴────────┴────────┴──────────────┴─────────────┘
'''
nullが含まれる行を抽出する
カラムごとにnullチェックを実施して表示。
for c in ldf.columns:
n_ldf = ldf.filter(pl.col(c).is_null())
print(c, n_ldf.collect())
# result
'''
PassengerId shape: (0, 14)
┌─────────────┬────────────┬───────────┬───────┬───┬─────┬────────┬──────┬─────────────┐
│ PassengerId ┆ HomePlanet ┆ CryoSleep ┆ Cabin ┆ … ┆ Spa ┆ VRDeck ┆ Name ┆ Transported │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ bool ┆ str ┆ ┆ f64 ┆ f64 ┆ str ┆ bool │
╞═════════════╪════════════╪═══════════╪═══════╪═══╪═════╪════════╪══════╪═════════════╡
└─────────────┴────────────┴───────────┴───────┴───┴─────┴────────┴──────┴─────────────┘
HomePlanet shape: (201, 14)
┌─────────────┬────────────┬───────────┬──────────┬───┬───────┬────────┬─────────────┬─────────────┐
│ PassengerId ┆ HomePlanet ┆ CryoSleep ┆ Cabin ┆ … ┆ Spa ┆ VRDeck ┆ Name ┆ Transported │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ bool ┆ str ┆ ┆ f64 ┆ f64 ┆ str ┆ bool │
╞═════════════╪════════════╪═══════════╪══════════╪═══╪═══════╪════════╪═════════════╪═════════════╡
│ 0064_02 ┆ null ┆ true ┆ E/3/S ┆ … ┆ 0.0 ┆ 0.0 ┆ Colatz Keen ┆ true │
│ 0119_01 ┆ null ┆ false ┆ A/0/P ┆ … ┆ 65.0 ┆ 6898.0 ┆ Batan ┆ false │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Coning ┆ │
│ 0210_01 ┆ null ┆ true ┆ D/6/P ┆ … ┆ 0.0 ┆ 0.0 ┆ Arraid ┆ true │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Inicont ┆ │
│ 0242_01 ┆ null ┆ false ┆ F/46/S ┆ … ┆ 283.0 ┆ 0.0 ┆ Almone Sté ┆ false │
│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
│ 9194_01 ┆ null ┆ false ┆ E/603/S ┆ … ┆ 13.0 ┆ 3147.0 ┆ null ┆ false │
│ 9248_01 ┆ null ┆ false ┆ F/1792/S ┆ … ┆ 207.0 ┆ 0.0 ┆ Gian Perle ┆ true │
│ 9257_01 ┆ null ┆ false ┆ F/1892/P ┆ … ┆ 24.0 ┆ 0.0 ┆ Ties Apple ┆ false │
│ 9274_01 ┆ null ┆ true ┆ G/1508/P ┆ … ┆ 0.0 ┆ 0.0 ┆ Chelsa ┆ true │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Bullisey ┆ │
└─────────────┴────────────┴───────────┴──────────┴───┴───────┴────────┴─────────────┴─────────────┘
以下略
'''
列の要素を確認
列ごとにユニークな要素を確認する。
全部出力すると大変なので headで先頭だけ表示する。
for c in ldf.columns:
unq_df = ldf.select(c).unique().collect()
print(f"{c}\tsize:{len(unq_df)}\tsample{unq_df.head()}")
'''
PassengerId size:8693 sampleshape: (5, 1)
┌─────────────┐
│ PassengerId │
│ --- │
│ str │
╞═════════════╡
│ 9179_03 │
│ 6399_01 │
│ 5535_03 │
│ 6593_02 │
│ 8521_01 │
└─────────────┘
HomePlanet size:4 sampleshape: (4, 1)
┌────────────┐
│ HomePlanet │
│ --- │
│ str │
╞════════════╡
│ null │
│ Earth │
│ Europa │
│ Mars │
└────────────┘
以下略
'''
新たな特徴量を作る
Cabinは'/'で3つのブロックに分けられそう。
def reconstruct(df):
return df.with_columns(
pl.col('Cabin')
.str.splitn('/', 3)
.struct.rename_fields(['Cabin_area', 'Cabin_number', 'Cabin_type'])
).unnest('Cabin').with_columns(
pl.col('Cabin_number')
.cast(int)
)
appended_ldf = reconstruct(ldf).drop_nulls()
print(appended_ldf.select('PassengerId', 'Cabin_area', 'Cabin_number', 'Cabin_type').head(10).collect())
'''
shape: (10, 4)
┌─────────────┬────────────┬──────────────┬────────────┐
│ PassengerId ┆ Cabin_area ┆ Cabin_number ┆ Cabin_type │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ i64 ┆ str │
╞═════════════╪════════════╪══════════════╪════════════╡
│ 0001_01 ┆ B ┆ 0 ┆ P │
│ 0002_01 ┆ F ┆ 0 ┆ S │
│ 0003_01 ┆ A ┆ 0 ┆ S │
│ 0003_02 ┆ A ┆ 0 ┆ S │
│ … ┆ … ┆ … ┆ … │
│ 0006_01 ┆ F ┆ 2 ┆ S │
│ 0007_01 ┆ F ┆ 3 ┆ S │
│ 0008_01 ┆ B ┆ 1 ┆ P │
│ 0008_03 ┆ B ┆ 1 ┆ P │
└─────────────┴────────────┴──────────────┴────────────┘
'''
データの分布を可視化する
列ごとにどの要素が多いか(少ないか)を集計する。
今回は関数を作ってみた。
def calc_distribution(data:pl.DataFrame, column_name:str) -> pl.DataFrame:
return data.select(
pl.col(column_name).value_counts()
.struct.rename_fields(['value', 'count'])
).unnest(column_name)
print(calc_distribution(appended_ldf, "HomePlanet").collect())
'''
shape: (3, 2)
┌────────┬───────┐
│ value ┆ count │
│ --- ┆ --- │
│ str ┆ u32 │
╞════════╪═══════╡
│ Earth ┆ 3566 │
│ Mars ┆ 1367 │
│ Europa ┆ 1673 │
└────────┴───────┘
'''
ダミー変数化する
カテゴリデータはダミー変数にした方が扱いやすい。
ダミー変換化したいcolumnをリストで指定して処理する例。
target_to_dummies = ["HomePlanet", "CryoSleep", "Cabin_area", "Cabin_type", "Destination", "VIP"]
dummied = appended_ldf.select(target_to_dummies).collect().to_dummies()
remaind = appended_ldf.drop(target_to_dummies).collect()
dummied_df = pl.concat([remaind, dummied], how="horizontal")
print(dummied_df)
'''
shape: (6_606, 30)
┌───────────┬────────────┬──────┬───────────┬───┬────────────┬──────────────┬───────────┬──────────┐
│ Passenger ┆ Cabin_numb ┆ Age ┆ RoomServi ┆ … ┆ Destinatio ┆ Destination_ ┆ VIP_false ┆ VIP_true │
│ Id ┆ er ┆ --- ┆ ce ┆ ┆ n_PSO ┆ TRAPPIST-1e ┆ --- ┆ --- │
│ --- ┆ --- ┆ f64 ┆ --- ┆ ┆ J318.5-22 ┆ --- ┆ u8 ┆ u8 │
│ str ┆ i64 ┆ ┆ f64 ┆ ┆ --- ┆ u8 ┆ ┆ │
│ ┆ ┆ ┆ ┆ ┆ u8 ┆ ┆ ┆ │
╞═══════════╪════════════╪══════╪═══════════╪═══╪════════════╪══════════════╪═══════════╪══════════╡
│ 0001_01 ┆ 0 ┆ 39.0 ┆ 0.0 ┆ … ┆ 0 ┆ 1 ┆ 1 ┆ 0 │
│ 0002_01 ┆ 0 ┆ 24.0 ┆ 109.0 ┆ … ┆ 0 ┆ 1 ┆ 1 ┆ 0 │
│ 0003_01 ┆ 0 ┆ 58.0 ┆ 43.0 ┆ … ┆ 0 ┆ 1 ┆ 0 ┆ 1 │
│ 0003_02 ┆ 0 ┆ 33.0 ┆ 0.0 ┆ … ┆ 0 ┆ 1 ┆ 1 ┆ 0 │
│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
│ 9278_01 ┆ 1499 ┆ 18.0 ┆ 0.0 ┆ … ┆ 1 ┆ 0 ┆ 1 ┆ 0 │
│ 9279_01 ┆ 1500 ┆ 26.0 ┆ 0.0 ┆ … ┆ 0 ┆ 1 ┆ 1 ┆ 0 │
│ 9280_01 ┆ 608 ┆ 32.0 ┆ 0.0 ┆ … ┆ 0 ┆ 0 ┆ 1 ┆ 0 │
│ 9280_02 ┆ 608 ┆ 44.0 ┆ 126.0 ┆ … ┆ 0 ┆ 1 ┆ 1 ┆ 0 │
└───────────┴────────────┴──────┴───────────┴───┴────────────┴──────────────┴───────────┴──────────┘
'''
提出ファイルを作る
値はランダム。
csvファイルを出力する前にpandasに変換したのは、提出ファイルのTrue, Falseの頭文字を大文字にするため。
polarsのままだと、true, falseとなってしまい、scoreが0になります。
import random
df = pl.read_csv('test.csv')
passenger = df.select('PassengerId')
transported = pl.DataFrame(
{
'Transported':[random.choice([True, False]) for _ in range(len(passenger))]
}
)
submit = pl.concat([passenger, transported], how='horizontal')
submit.to_pandas().to_csv("submit.csv", index=False)
提出
作ったファイルをkaggle APIを使用して提出する。
!kaggle competitions submit -c spaceship-titanic -f submit.csv -m "comment"
最後に
大まかにやりたいことはできた。(と思う。)
早い、安い、うまいのでpolarsを流行らせたい!