はじめに
株式会社LITALICOでエンジニアをしています@yknoguchiです。
この記事は『LITALICO Advent Calendar 2023』10日目の記事です。
ちなみに今日は僕の誕生日でもあります。めでたい!
来年もきっと誕生日駆動アドベントカレンダーをやると思います。
この記事の特徴
この記事では、Polarsの詳しい使い方の解説は行いません。
この記事の目標は、以下のとおりです。
「これを読むことで最低限のPolarsの使い方を覚え、とりあえずすぐにPolarsを触れる」
QiitaにはPolarsの解説記事が上がっていますので、詳しい使い方はそちらをご確認ください。この記事ではあくまでPolarsを始める最初の一歩を想定しています。そのため、必要最低限の機能しか紹介していません。
Polarsとは
Polarsとは、Pythonで大量のデータフレームを集計するときに使用するライブラリです。
その用途のスタンダートのライブラリはPandasなのですが、最近はPolarsが徐々に人気が出てきています。その理由としては、処理が書きやすいことや実行が高速などの理由があります。
(他にもこちらの記事で様々な特徴が紹介されています。→超高速…だけじゃない!Pandasに代えてPolarsを使いたい理由)
準備
インストール
Polarsはpipから簡単にインストールできます。
> pip install polars
インポート
基本的にPolarsはplと略して使用します。
import polars
import polars as pl # 基本的にはこっち
ファイルの読み書き
ファイルの読み込み
df = pl.read_csv("data.csv")
ファイルへ書き込み
Polarsではutf8以外の書き込みに対応していないため、その場合はPandasに変換するか、Pythonのopenメソッドで書き込んでください。
df.write_csv("data.csv")
データの計算・集計方法
PolarsではPandasに負けず劣らずの処理ができますが、処理の書き方はだいぶ異なります。そのためイメージしやすいように、先にPolarsでのデータ処理の手順フォーマット(僕が勝手に作成した)を紹介します。
しかし、最初に述べたとおり最低限しか紹介しません。ここで紹介したやり方だけではPolarsのすべての機能を使えるわけではありません。
Polarsの基本的な使用フォーマット
Polarsでは基本的にselect
とfilter
メソッドを使用していきます。そしてそれぞれが、列のデータ選択(+処理)、行のデータ選択を担っています。
また、それぞれのメソッドの中で操作を行いたいときは、pl.Expr
クラスを使用します。特にpl.col
は列の操作を行うときにたくさん使用します。
特にselect
では、特定の列に対して処理を加えて新しい列として追加することができます。なので、基本的な処理手順は以下のようになります。
df.select(
# ここで使用する列の選択
# または特定の列に対して処理を行う
# 場合に応じては処理後、別のカラムとしてデータを追加
).filter(
# ここでデータの中から使用する行を抽出
).select(
).filter... # これを繰り返す
暴論ではありますが、最低限のデータフレームの操作は上のselect
とfilter
を繰り返すだけで実装が行えます。select→列の操作、filter→行の操作と対応がわかりやすいですね。
列の選択・操作 select
select
で特定の列を取り出すことができます。具体的な使い方としては以下をご覧ください。
df.select(
"name",
"age",
)
# 出力
# shape: (3, 3)
# ┌──────┬─────┐
# │ name ┆ age │
# │ --- ┆ --- │
# │ str ┆ i64 │
# ╞══════╪═════╡
# │ one ┆ 10 │
# │ one ┆ 20 │
# │ two ┆ 30 │
# └──────┴─────┘
上のサンプルでは"name"と"age"とい名前の列を取り出しています。直感的で簡単ですね。
しかも、Polarsでは取り出す列の名前を配列にする必要がありません。(配列にする必要がないだけで、配列で指定しても同様の結果が返ってきます。)
# 配列で列名を指定してもOK
df.select(
["name","age"]
)
# 出力
# shape: (3, 3)
# ┌──────┬─────┐
# │ name ┆ age │
# │ --- ┆ --- │
# │ str ┆ i64 │
# ╞══════╪═════╡
# │ one ┆ 10 │
# │ one ┆ 20 │
# │ two ┆ 30 │
# └──────┴─────┘
列の操作 pl.col()
select
で列を指定することができたら、次は列の操作をしたくなります。その時はselectの中にpl.col()
を使用しましょう。
df.select(
"name",
pl.col("age")*2
)
# 出力
# shape: (3, 3)
# ┌──────┬─────┐
# │ name ┆ age │
# │ --- ┆ --- │
# │ str ┆ i64 │
# ╞══════╪═════╡
# │ one ┆ 20 │
# │ one ┆ 40 │
# │ two ┆ 60 │
# └──────┴─────┘
こうすることで、age
の列が2倍になって結果が返されます。
もしもこの処理結果を別のカラムにしたいときは、.alias
を使用しましょう。
df.select(
"name",
"age",
(pl.col("age")*2).alias("*2")
# ↑コードが `*2` で終わってるとメソッドチェーンができないので括弧で囲っている
)
# 出力
# shape: (3, 3)
# ┌──────┬─────┬─────┐
# │ name ┆ age ┆ *2 │
# │ --- ┆ --- ┆ --- │
# │ str ┆ i64 ┆ i64 │
# ╞══════╪═════╪═════╡
# │ one ┆ 10 ┆ 20 │
# │ one ┆ 20 ┆ 40 │
# │ two ┆ 30 ┆ 60 │
# └──────┴─────┴─────┘
場合わけを使用した列の操作 pl.when
少し細かい処理を書こうとしたら、場合分けが必要になります。その時はpl.when
を使用しましょう。
pl.when
はwhen
、then
、otherwise
を組み合わせて使用します。
df.select(
"name",
"age",
pl.when(pl.col("age")>18)
.then("成人")
.otherwise("未成年")
.alias("成人済みか?") # whenにもaliasが使用可能
)
# 出力
# shape: (3, 3)
# ┌──────┬─────┬──────────────┐
# │ name ┆ age ┆ 成人済みか? │
# │ --- ┆ --- ┆ --- │
# │ str ┆ i64 ┆ bool │
# ╞══════╪═════╪══════════════╡
# │ one ┆ 10 ┆ false │
# │ one ┆ 20 ┆ true │
# │ two ┆ 30 ┆ true │
# └──────┴─────┴──────────────┘
行の選択 filter
filter
で行の絞り込みが行えます。基本的には行ごとに絞り込みを行うので、上にも登場したpl.col
を使います。
df.filter(
pl.col("age") > 10
)
# 出力
# shape: (2, 2)
# ┌──────┬─────┐
# │ name ┆ age │
# │ --- ┆ --- │
# │ str ┆ i64 │
# ╞══════╪═════╡
# │ one ┆ 20 │
# │ two ┆ 30 │
# └──────┴─────┘
複数条件も指定できます。
df.filter(
(pl.col("age") > 10)
& (pl.col("age") < 30)
# pythonでは&や|の演算優先順位が高いため、括弧で囲う
)
# 出力
# shape: (2, 2)
# ┌──────┬─────┐
# │ name ┆ age │
# │ --- ┆ --- │
# │ str ┆ i64 │
# ╞══════╪═════╡
# │ one ┆ 20 │
# └──────┴─────┘
もちろん、違う列で条件指定したり、select
で追加した新しい列に対しても操作可能です。
まとめ
さて、これまでselect
とfilter
を使用したPolarsの使用方法を紹介しました。
では、それを踏まえて以下のコードを読んでみましょう。(※参考のためのコードなので、冗長な部分があります。)
df.select(
"name",
"age",
pl.when(pl.col("age")>18)
.then(True)
.otherwise(False)
.alias("成人済みか?")
).filter(
pl.col("成人済みか?")
).select(
"name",
"age"
)
はい。これを上から読んでいくと、このように解釈できます。
- 使用する列を選択する
-
name
とage
をそのまま使用 -
age
が18以上かをboolで表す成人済みか?
の列を追加
-
- 使用する行を選択する
-
成人済みか?
の列がTrue
であるもののみを表示
-
- 使用する列を選択する
-
name
とage
をそのまま使用(成人済みか?
の列は表示しない)
-
よって出力は以下のようになります。
df.select(
"name",
"age",
pl.when(pl.col("age")>18)
.then(True)
.otherwise(False)
.alias("成人済みか?")
).filter(
pl.col("成人済みか?")
).select(
"name",
"age"
)
# 出力
# shape: (2, 2)
# ┌──────┬─────┐
# │ name ┆ age │
# │ --- ┆ --- │
# │ str ┆ i64 │
# ╞══════╪═════╡
# │ one ┆ 20 │
# │ two ┆ 30 │
# └──────┴─────┘
と、このようにselect
とfilter
を交互に使用していくだけで、簡単に、わかりやすくデータフレームの操作ができました。
個人的に、自分の頭の中で思ったことをそのままその順番で書き連ねられるところが、Polarsの良さだなぁと思っています。
覚えていたらより便利なメソッドたち
列をすべて選択 pl.all()
select
するときに、すべての列を選択できます。
df.select(
pl.all()
)
指定した列以外を選択 pl.exclude()
select
するときに、指定した列以外を選択します。
指定したい列が多いときに便利。複数指定も可能。
df.select(
pl.exclude("name")
)
値がnullかを判別 .is_null()
Pandasでいうisna()
のようなもの。
欠損値がある行を使用しないときなどに使う。
df.select(
pl.col("name").is_null() # nameが欠損している行のみ表示
)
値がnullではないことを判別 .is_not_null()
.is_null()
の逆。
データが入っていたらTrue。
df.select(
pl.col("name").is_not_null() # nameのデータが入っている行のみ表示
)
終わりに
ここまでで、Polarsの基本の使い方を紹介してきました。
「まとめ」にも書いているとおり、Polarsは慣れると可読性がとても高いと感じています。しかも処理速度が早いので、多少雑に書いても速度が全く気になりません。
そのため、僕個人の話ですが、今までPandasを使用していたところをPolarsに変えたら、生産性がだいぶ上がりました。ほんとすごい。
この記事を書くにあたって
Polarsに移行するときに少し思っていたのが、「新しい何かを導入するとき、さっくり使い方を把握するまでの時間をなるべく短縮したい」ということでした。
PolarsとPandasのどちらがいいかを比べるときに、Polarsの使い方を学ぶのに1週間もかかっていたら、少し勿体無い気がします。もちろん慎重な比較も必要ですが、 学ぶのが早いに越したことはありません。 そのため、最初の取っ掛かりが簡単であればあるほど、そのハードルが低くなるかと思います。この記事はそのハードルを下げることを目的として執筆しました。
なので、Polarsを普段から使用している方々が見ると、「もっといい書き方があるぞ!」「勝手にフォーマットを作るな!」などのご意見が出てくるかもしれませんが、ご容赦ください。
Polarsには、今回紹介した以外の便利な機能がたくさんあります。この記事で「Polas良さそうじゃん」と思っていただいたら、そのときにまた調べて見てください。いろいろ出てきます。
この記事が少しでもPolarsに興味を持っていただくきっかけになれば幸いです。
最後に
一番最初に書きましたとおり、この記事は『LITALICO Advent Calendar 2023』10日目の記事です。
明日は@gorilla_gardenさんと@H-Asakawaさんが担当されます。お楽しみに!!
そして僕は24日にも記事を投稿予定です。そちらもお楽しみにしていただければ幸いです。