Pythonの表形式データ処理の高速なライブラリ「Polars」でよく用いられる機能を、間違いやすい点にも触れつつ紹介します。表題の通り、Pandasの知識がなくても大丈夫です。

目次

はじめに

Pythonで表形式データを処理する際に、真っ先に候補に挙がるライブラリはPandasでしょう。
Pandasでは、DataFrame(データフレーム)と呼ばれる表形式のオブジェクトを用いて、列名を指定したり、条件に従って行を絞ったり、各行に一律な操作を実行したりすることができます。
ところで、インターネットの波に乗っていると、Pandasに関連して、Polarsと呼ばれるライブラリを目にしたことがあるかもしれません。
Polarsをざっくり説明するなら「Pandasより高速な表形式データ処理ライブラリ」です。
どれくらい高速かの解説はネットの海の他サイトに譲ります。

というわけで、Pythonで表形式データを処理したい際は、Polarsを使ってみましょう。以下では、Polarsで頻繁に用いられる基本的な機能を紹介します。

より高度な処理は別の機会に記事にします。

インポート

Polarsをインポートするには、まずPolarsをpipでインストールする必要があります。(Anacondaがあればcondaでも可)

なお、Polarsはplと略されることが多いです。以下でもplという略称を使用します。


!pip install polars

import polars as pl

Polarsを書く上での注意点

PolarsのDataFrameの特徴

PolarsのDataFrameは列名と各列の要素からなります。

  • 列名の特徴
    • 空白のDataFrameを除いて、各列には必ず列名が存在し、かつ、それらが一意(ユニーク)な列名でなければなりません。もし重複した列名を設定しようとすると、エラーが出るか、既存のDataFrameに影響を与えます。
  • 各列の要素
    • 各列の要素は全ての行で同じ型でなければなりません。なお、各行の要素は列ごとに異なっていても構いません。Polarsでは列の全ての行に一律にする処理がほとんどであるため、手持ちのデータのどちらを列や行とするかは事前に決めておきましょう。
  • その他
    • 行名や行番号はありません。ただし、これらを疑似的に付与したり利用したりすることは可能です。

Polarsで用いられるExpression(エクスプレッション)について

Polarsには、DataFrame(データフレーム、1列または複数列のオブジェクト)やSeries(シリーズ、1列だけのオブジェクト)以外に、Expression(エクスプレッション)と呼ばれる重要な独特の概念があります。
Expressionが何かを直感的に説明するのは難しいですが、条件式や計算式のようなものであると考えてください。

ここでExpressionの例を紹介します。DataFrameにおいて、"ID"と"Name"という名前の列の要素を指定するExpressionは以下のように書くことが多いです。


pl.col("ID", "Name")

# または

pl.col(["ID", "Name"])

# または

[pl.col("ID"), pl.col("Name")]

Expressionは変数として保持しておくことができます。


expr1 = pl.col("ID", "Name")

# または

expr2 = pl.col(["ID", "Name"])

# または

expr3 = [pl.col("ID"), pl.col("Name")]

これらのExpressionを引数として、dfというDataFrameの"ID"と"Name"という名前の列を選択するには以下のように書けます。


df.select(expr1)

メソッドの種類の注意点

Polarsで用いるメソッド(処理)の書き方は以下の4種類があります。
以下の例のように、"mean"は4種全てがありますが、他のメソッドはそうでないものがほとんどですので、これらを混同しないように心がけましょう。
また、返り値の違いも意識しましょう。

  • polarsのメソッド。
  • polars.Expressionのメソッド。
  • polars.DataFrameのメソッド。
  • polars.Seriesのメソッド。

# その1 polarsのメソッド。例:
mean_expr = pl.mean("Age") # -> Expr

# その2 polars.Expressionのメソッド。例:
self_mean_expr = pl.col("Age").mean() # -> self (Expr)

# その3 polars.DataFrameのメソッド。例:
df = pl.DataFrame()
mean_df = df.mean() # -> DataFrame

# その4 polars.Seriesのメソッド。例:
sr = pl.Series()
mean_fl = sr.mean() # -> Float

Polarsで能く用いられる機能

DataFrameの生成

DataFrameの生成は大きく分けて「読み込み」か「変換」の2種類あります。

読み込み

CSVファイル、JSONファイル、Excelファイルなどを読み込んで生成することができます。


input_csv = "input.csv"
df_csv = pl.read_csv(input_csv)

input_json = "input.json"
df_json = pl.read_json(input_json)

input_excel = "input.xlsx"
df_excel = pl.read_excel(input_excel)

変換

辞書型変数、Pandas.DataFrame、Numpy.ndarrayなどから変換して生成することができます。ただし、以下の例のように、それぞれの挙動が微妙に異なる点に注意しましょう。

辞書型変数から変換

import polars as pl

data = {
    "店舗名": ["東京タワー店", "浅草寺店", "秋葉原店", "東京タワー店", "浅草寺店"],
    "曜日": ["土曜日", "土曜日", "日曜日", "日曜日", "土曜日"],
    "売上額": [500000, 300000, 450000, 600000, 350000],
    "客数": [200, 150, 180, 250, 160]
}

df_dict = pl.DataFrame(data)

# または

df_dict = pl.from_dict(data)

display(df_dict)
Pandas.DataFrameから変換

Polarsにはindexが無いので、Pandasのindexはデフォルトでは引き継がれません。include_index = Trueを指定すると引き継がれます。


import pandas as pd
data = pd.DataFrame(
    {
        "店舗名": ["東京タワー店", "浅草寺店", "秋葉原店", "東京タワー店", "浅草寺店"],
        "曜日": ["土曜日", "土曜日", "日曜日", "日曜日", "土曜日"],
        "売上額": [500000, 300000, 450000, 600000, 350000],
        "客数": [200, 150, 180, 250, 160]
    }
)

df_pandas = pl.DataFrame(data)

# または

df_pandas = pl.from_pandas(data)

display(df_pandas)
Numpy.ndarrayから変換

ndarrayではすべての要素が同じ型になるので、Polarsで読み込んだ際にもすべての列が同じ型になります。

なお、numpyからの変換に限らず、DataFrame内のデータ型はPolars独自のものが使われます(例 : pl.Float64, pl.Int64, pl.Datetime, pl.String)が、printやdisplay(Jupyterのみ)ではstrなどと表示されることがあります。

型はschema_overridesという引数に辞書型で渡すことで変更できます。(例 : "schema_overrides={"客数":pl.Int64}")


import numpy as np
data = np.array(
    [
        ["東京タワー店", "浅草寺店", "秋葉原店", "東京タワー店", "浅草寺店"],
        ["土曜日", "土曜日", "日曜日", "日曜日", "土曜日"],
        [500000, 300000, 450000, 600000, 350000],
        [200, 150, 180, 250, 160]
    ]
)
schema = ["店舗名", "曜日", "売上額", "客数"]

df_numpy = pl.DataFrame(data, schema, schema_overrides={"客数":pl.Int64})

# または

df_numpy = pl.from_numpy(data, schema, schema_overrides={"客数":pl.Int64})

display(df_numpy)

DataFrameの出力

DataFrameはCSV, JSON, Excelなどに出力できます。


output_csv = "output.csv"
df.write_csv(output_csv)

output_json = "output.json"
df.write_json(output_json)

output_excel = "output.xlsx"
df.write_excel(output_excel)

DataFrameの基本情報

行数と列数

shapeというDataFrameのメソッドで行数と列数をタプルで取得できます。
行数と列数をバラでほしい場合は、heightwidthというメソッドを使用できます。
lenでは行数を取得できます。


data = {
    "店舗名": ["東京タワー店", "浅草寺店", "秋葉原店", "東京タワー店", "浅草寺店"],
    "曜日": ["土曜日", "土曜日", "日曜日", "日曜日", "土曜日"],
    "売上額": [500000, 300000, 450000, 600000, 350000],
    "客数": [200, 150, 180, 250, 160]
}

df_example = pl.DataFrame(data)   # 以下、df_sampleを各機能の例に用います

print(df_example.shape)
print(len(df_example))
print(df_example.height)
print(df_example.width)
(5, 4)
5
5
4

列名とデータ型

schemaというDataFrameのメソッドで列名とデータ型を辞書型で取得できます。
列名とデータ型をバラでほしい場合は、columnsdtypesというメソッドを使用できます。


print(df_example.schema)
print(df_example.columns)
print(df_example.dtypes)
OrderedDict({'店舗名': String, '曜日': String, '売上額': Int64, '客数': Int64})
['店舗名', '曜日', '売上額', '客数']
[String, String, Int64, Int64]

列の抽出

DataFrameとして抽出

selectメソッド

列を指定して抽出し、列数を減らします。selectというメソッドを用いますが、感覚的には「選択」というよりも「DataFrameを新たに生成」と考えるほうがしっくりきます。


select_columns = ["店舗名", "曜日", "客数"]

df_selected_1 = df_example.select(
    select_columns, 
)

display(df_selected_1)

shape: (5, 3)

店舗名 曜日 客数
str str i64
"東京タワー店" "土曜日" 200
"浅草寺店" "土曜日" 150
"秋葉原店" "日曜日" 180
"東京タワー店" "日曜日" 250
"浅草寺店" "土曜日" 160
selectメソッドの注意点

ただし、selectには、文字列型、Expression型、およびそれらのリストなどを複数渡せますが、リストとそれ以外とを混ぜこぜに渡してしまうと、下の例のように、予期せぬ挙動を招く恐れがあるので注意しましょう。
なお、文字列を渡した場合はpl.colとして解釈されるようです。


column_1 = ["店舗名"]
column_2 = "曜日"
column_3 = pl.col("客数")

df_selected_badly = df_example.select(
    column_1, column_2, column_3
)
display(df_selected_badly)

shape: (5, 3)

literal 曜日 客数
list[str] str i64
["店舗名"] "土曜日" 200
["店舗名"] "土曜日" 150
["店舗名"] "日曜日" 180
["店舗名"] "日曜日" 250
["店舗名"] "土曜日" 160
selectによる列の並べ替え

また、DataFrameの列の並べ替えは、selectに与える引数の順番を並び替えることで行います。


select_columns_order = ["曜日", "店舗名", "客数", "売上額"]

df_selected_2 = df_example.select(
    select_columns_order, 
)

display(df_selected_2)

shape: (5, 4)

曜日 店舗名 客数 売上額
str str i64 i64
"土曜日" "東京タワー店" 200 500000
"土曜日" "浅草寺店" 150 300000
"日曜日" "秋葉原店" 180 450000
"日曜日" "東京タワー店" 250 600000
"土曜日" "浅草寺店" 160 350000
excludeによる列の除外

excludeというExpressionで、指定した列以外を抽出することもできます。excludeの引数にも文字列やExpressionやリストを使用できます。


exclude_columns = ["客数"]

df_selected_3 = df_example.select(
    pl.exclude(exclude_columns)
)

display(df_selected_3)

shape: (5, 3)

店舗名 曜日 売上額
str str i64
"東京タワー店" "土曜日" 500000
"浅草寺店" "土曜日" 300000
"秋葉原店" "日曜日" 450000
"東京タワー店" "日曜日" 600000
"浅草寺店" "土曜日" 350000
aliasによる列名の変更

aliasというExpressionで列名を変更します。

なお、aliasはExpressionのメソッドとして書くことができますが、文字列のメソッドとしては定義されていないので注意しましょう。

また、aliasはExpressionのExpressionであり、"pl.alias"と書くことはできない点にも注意しましょう。


select_elements = [
    "店舗名", 
    "曜日", 
    pl.col("売上額").alias("Sales")
]

df_selected_3 = df_example.select(select_elements)
display(df_selected_3)

shape: (5, 3)

店舗名 曜日 Sales
str str i64
"東京タワー店" "土曜日" 500000
"浅草寺店" "土曜日" 300000
"秋葉原店" "日曜日" 450000
"東京タワー店" "日曜日" 600000
"浅草寺店" "土曜日" 350000
allによる全ての列の選択

allというExpressionで全ての列を指定できます。

しかし、元のDataFrameと全く同じものが返ってくるので、一見して意味のないExpressionだと考えられるかもしれません。

allの使いどころとしては、例えば、ExpressionのExpressionは"pl."の直後には書けませんが、"pl.all()."の直後には書くことができるので、そのようなケースに対応できます。


df_selected_4 = df_example.select(
    pl.all()
)
display(df_selected_4)

shape: (5, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"東京タワー店" "土曜日" 500000 200
"浅草寺店" "土曜日" 300000 150
"秋葉原店" "日曜日" 450000 180
"東京タワー店" "日曜日" 600000 250
"浅草寺店" "土曜日" 350000 160

Seriesとして抽出

get_columnというDataFrameのメソッドでは、指定した1列をSeriesとして作成します。

ただし、get_columnの引数は必ず列名(文字列型)でなければならず、pl.colなどのExpressionは使用できない点に注意しましょう。

また、複数列のSeriesを一つのget_columnで作成することはできません。

似たような名前のメソッドとしてget_columnsがありますが、これはDataFrameの全部の列をSeriesのリストに変換するものなので、混同しないように注意しましょう。


sr_sales = df_example.get_column("売上額")
display(sr_sales)

shape: (5,)

売上額
i64
500000
300000
450000
600000
350000

計算と統計

演算子による計算

Pythonの四則演算子(+, -, *, /, //, %)とExpressionとを用いた計算が指定できます。計算結果もExpressionになります。

なお、下の例では、元のDataFrameには「一人当たりの売上」や「売上の剰余」なんて列は存在しません。
このことから、selectは「選択」というよりも「DataFrameを新たに生成」と考えるほうがしっくりくることでしょう。

また、これらを使用する際には、一つ一つの計算式を()で囲まないと、aliasで予期せぬ挙動になる恐れがあるので注意しましょう。


culc_columns = [
    (
        pl.col("売上額") / pl.col("客数")
    ).alias("一人当たり売上"),
    (
        pl.col("売上額") % pl.col("客数")
    ).alias("売上の剰余")
]

df_culc = df_example.select(
    culc_columns
)
display(df_culc)

shape: (5, 2)

一人当たり売上 売上の剰余
f64 i64
2500.0 0
2000.0 0
2500.0 0
2400.0 0
2187.5 80

対数、角度、三角関数や移動平均などのExpressionも用意されていますが、本記事では割愛します。

列の統計

sum(総和)、mean(平均値)、max(最大値)など。
いくつかの計算は、DataFrameのメソッドやExpressionなどの複数通りの書き方ができます。

ただし、これらの引数は列名(文字列)のみを許容し、Expressionは許容しない点に注意しましょう。

なお、DataFrameでの計算ではDataFrameが返ることが多いですが、Seriesでの計算ではFloatが返ることが多いです。


df_sum_1 = df_example.select(
    pl.sum("売上額", "客数")
)

# または

df_sum_2 = df_example.select(
    pl.col("売上額", "客数").sum()
)

# または

df_sum_3 = df_example.select(
    pl.col("売上額", "客数")
).sum()

# または

sr_sum = df_example.get_column(
    "売上額"
).sum()

display(df_sum_1, df_sum_2, df_sum_3)
display(sr_sum)

shape: (1, 2)

売上額 客数
i64 i64
2200000 940

shape: (1, 2)

売上額 客数
i64 i64
2200000 940

shape: (1, 2)

売上額 客数
i64 i64
2200000 940
2200000

統計の一覧

describeというDataFrameのメソッドで、各列の平均値や標準偏差、四分位数などを一覧できます。
また、列の長さやnull(欠損値)の個数も見られます。


display(df_example.describe())

shape: (9, 5)

statistic 店舗名 曜日 売上額 客数
str str str f64 f64
"count" "5" "5" 5.0 5.0
"null_count" "0" "0" 0.0 0.0
"mean" null null 440000.0 188.0
"std" null null 119373.363863 39.623226
"min" "東京タワー店" "土曜日" 300000.0 150.0
"25%" null null 350000.0 160.0
"50%" null null 450000.0 180.0
"75%" null null 500000.0 200.0
"max" "秋葉原店" "日曜日" 600000.0 250.0

列の追加

with_columnsとlitによる定数や文字列の追加

with_columnsというDataFrameのメソッドで、新たな列を追加できます。
with_columnsの引数に、Expressionおよびそれらのリストなどを指定して、aliasで列名を命名することで、列が右端に追加されます。

with_columnsはExpressionではなくDataFrameのメソッドであるという点に注意しましょう。

反対に、aliasはExpressionであってDataFrameのメソッドではないという点にも注意しましょう。

ここでは、litというExpression(リテラルの意)で、全ての行に等しい値を設定することもできます。


df_with_columns_1 = df_example.with_columns(
    pl.lit("東京").alias("都道府県")
)
display(df_with_columns_1)

shape: (5, 5)

店舗名 曜日 売上額 客数 都道府県
str str i64 i64 str
"東京タワー店" "土曜日" 500000 200 "東京"
"浅草寺店" "土曜日" 300000 150 "東京"
"秋葉原店" "日曜日" 450000 180 "東京"
"東京タワー店" "日曜日" 600000 250 "東京"
"浅草寺店" "土曜日" 350000 160 "東京"

場合分けによる動的な列の追加

when, then, otherwiseというExpressionで、If(-Then)-Else形式の行別の処理を書くことができます。
whenの引数には条件のExpressionが入り、thenとotherwiseの引数には計算のExpressionが入ります。

複数のwhenを繋げた場合、先に書かれたwhenの条件およびthenの処理が優先されます。

whenとthenは交互に書かれて同数でなければならない点と、otherwiseはExpressionの末尾に1つだけ必須である点に注意しましょう。

また、when-then-otherwiseのExpressionは長くなりがちなので、改行・インデントや括弧付けで読みやすくし、最後にaliasを忘れないように注意しましょう。


df_with_columns_2 = df_example.with_columns(
    pl.when(
        pl.col("売上額") == pl.max("売上額")
    ).then(
        pl.lit("売上が最高")
    ).when(
        pl.col("客数") == pl.max("客数")
    ).then(
        pl.lit("客数が最高")   # 実際には"売上が最高"が優先される
    ).when(
        pl.col("売上額") / pl.col("客数") == (pl.col("売上額") / pl.col("客数")).max()
    ).then(
        pl.lit("一人当たり売上が最高")
    ).otherwise(
        pl.col("店舗名") + pl.col("曜日")
    ).alias("備考欄")
)
display(df_with_columns_2)

shape: (5, 5)

店舗名 曜日 売上額 客数 備考欄
str str i64 i64 str
"東京タワー店" "土曜日" 500000 200 "一人当たり売上が最高"
"浅草寺店" "土曜日" 300000 150 "浅草寺店土曜日"
"秋葉原店" "日曜日" 450000 180 "一人当たり売上が最高"
"東京タワー店" "日曜日" 600000 250 "売上が最高"
"浅草寺店" "土曜日" 350000 160 "浅草寺店土曜日"

列の上書き

aliasによる既存の列名の指定

with_columnsで既存の列名を指定した場合、その列を上書きできます。
更新前の列データは消えてしまう点に注意しましょう。


df_updated_1 = df_example.with_columns(
    pl.lit(1000).alias("客数")
)
display(df_updated_1)

shape: (5, 4)

店舗名 曜日 売上額 客数
str str i64 i32
"東京タワー店" "土曜日" 500000 1000
"浅草寺店" "土曜日" 300000 1000
"秋葉原店" "日曜日" 450000 1000
"東京タワー店" "日曜日" 600000 1000
"浅草寺店" "土曜日" 350000 1000

aliasを使用しない場合

Expressionの計算に対してaliasをしない場合、デフォルトの列名は「最初に登場したExpression」に基づきます。

以下の例では、pl.col("売上額")が最初に登場するので、デフォルトの列名も"売上額"になります。
すなわち、既存の"売上額"列のデータが上書きされます。


df_updated_2 = df_example.with_columns(
    (
        pl.col("売上額") / pl.col("客数")
    )
)
display(df_updated_2)

shape: (5, 4)

店舗名 曜日 売上額 客数
str str f64 i64
"東京タワー店" "土曜日" 2500.0 200
"浅草寺店" "土曜日" 2000.0 150
"秋葉原店" "日曜日" 2500.0 180
"東京タワー店" "日曜日" 2400.0 250
"浅草寺店" "土曜日" 2187.5 160

with_columnsの注意点

with_columnsにはExpressionを複数渡すことができますが、その場合は各々が並列で処理されるため、互いを参照できないという点に注意しましょう。


df_updated_3 = df_example.with_columns(
    pl.lit(1000).alias("客数"),
    (pl.col("売上額") / pl.col("客数")).alias("一人当たり売上")
)
display(df_updated_3)

shape: (5, 5)

店舗名 曜日 売上額 客数 一人当たり売上
str str i64 i32 f64
"東京タワー店" "土曜日" 500000 1000 2500.0
"浅草寺店" "土曜日" 300000 1000 2000.0
"秋葉原店" "日曜日" 450000 1000 2500.0
"東京タワー店" "日曜日" 600000 1000 2400.0
"浅草寺店" "土曜日" 350000 1000 2187.5

行の選択

sliceによる選択

行の選択にはsliceというDataFrameのメソッドを使います。
引数には開始行番号と行数の二つを渡します。

通常のlistをsliceする場合とは引数が異なるため注意しましょう。


df_sliced = df_example.slice(1, 3)
display(df_sliced)

shape: (3, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"浅草寺店" "土曜日" 300000 150
"秋葉原店" "日曜日" 450000 180
"東京タワー店" "日曜日" 600000 250

最初や最後の行を選択

head, tailというメソッドで、DataFrameの先頭または末尾の数行を選択します。
引数に行数を指定でき、デフォルトでは5行です。


df_head = df_example.head(1)
df_tail = df_example.tail(1)

display(df_head, df_tail)

shape: (1, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"東京タワー店" "土曜日" 500000 200

shape: (1, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"浅草寺店" "土曜日" 350000 160

sampleによるランダムな選択

sampleというメソッドで、ランダムな数行を選択します。
最初の引数は行数で、それ以外の引数については割愛します。
デフォルトでは1行です。


df_sampled = df_example.sample()
display(df_sampled)

shape: (1, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"東京タワー店" "日曜日" 600000 250

行のフィルタリング

filterによる条件付け

filterというDataFrameのメソッドを用いて、条件に従って行を絞り込みます。
条件として文字列やExpressionとPythonの条件演算子(==, !=, >=, >, <=, <)などを用いることができます。
filterにおける文字列はpl.lit(リテラル)として解釈されるようです。


df_filtered_1 = df_example.filter(
    pl.col("店舗名") != "東京タワー店"
)

display(df_filtered_1)

shape: (3, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"浅草寺店" "土曜日" 300000 150
"秋葉原店" "日曜日" 450000 180
"浅草寺店" "土曜日" 350000 160

条件式の組み合わせ

&や|を使用して条件のExpressionを組み合わせることができます。ただし、NOTを表す演算子は!ではなく~です。

また、これらを使用する際には、一つ一つの条件を()で囲まないとエラーになるので注意しましょう。


df_filtered_2 = df_example.filter(
    ~(pl.col("店舗名") == "東京タワー店") &
    (pl.col("売上額") >= pl.mean("売上額"))
)

display(df_filtered_2)

shape: (1, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"秋葉原店" "日曜日" 450000 180

uniqueによる要素の抽出

uniqueというDataFrameのメソッドで、指定した列のデータが重複した行は一つを残して削除します。
引数に列名を渡すと、その列の重複のみを考慮します。
複数の列名を渡す場合はリストで渡します。
引数がない場合は全ての列が重複する場合のみ削除されます。


unique_columns = ["店舗名", "曜日"]
df_unique = df_example.unique(unique_columns)

display(df_unique)

shape: (4, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"東京タワー店" "日曜日" 600000 250
"東京タワー店" "土曜日" 500000 200
"秋葉原店" "日曜日" 450000 180
"浅草寺店" "土曜日" 300000 150

行の並べ替え

sortによる基本的な並べ替え

sortまたはsort_byを使用して行を並べ替えます。

sortはDataFrameのメソッドとExpressionのメソッド(Expressionを返す)とがありますが、"pl."で始まる書き方は出来ないので注意しましょう。

その一方でsort_byはExpressionのメソッドです。

DataFrameのメソッドのsortは、引数に指定された列を基準にDataFrameの全ての列を並び替えます。
これにはselect(pl.all().sort_by("列名"))が対応しています。
なぜなら、sort_byは引数に指定された列を基準に複数の列を並び替えるExpressionだからです。

なお、これらのメソッドの引数に渡せる列名は文字列かExpressionかそれらのリストで、それ以外の引数として降順(decending)やnull(欠損値)を末尾に表示するかどうか(nulls_last)があります。

さらに、引数には複数の列を指定できます。
複数列が指定された場合は、左の引数(列名)から優先的に並べ替えられます。
この際、decendingには基準にする列数と同じ長さのTrue/Falseのリストを渡すことによって、各列の降順/昇順を制御できます。


df_sorted_1 = df_example.sort("店舗名", "曜日")

# または

df_sorted_1 = df_example.select(pl.all().sort_by("店舗名", "曜日"))

display(df_sorted_1)

shape: (5, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"東京タワー店" "土曜日" 500000 200
"東京タワー店" "日曜日" 600000 250
"浅草寺店" "土曜日" 300000 150
"浅草寺店" "土曜日" 350000 160
"秋葉原店" "日曜日" 450000 180

もう一つのsort

ではExpressionのメソッドであるsortは何をするかというと、直前のExpressionで選択された全ての列をバラバラに並べ替えます

すなわち、行データを維持しないという点に注意しましょう。

sort_byやDataFrameのメソッドのsortとは対応していないので、混同しないように注意しましょう。


df_sorted_2 = df_example.select(pl.all().sort())

display(df_sorted_2)

shape: (5, 4)

店舗名 曜日 売上額 客数
str str i64 i64
"東京タワー店" "土曜日" 300000 150
"東京タワー店" "土曜日" 350000 160
"浅草寺店" "土曜日" 450000 180
"浅草寺店" "日曜日" 500000 200
"秋葉原店" "日曜日" 600000 250

グループ化

group_byというDataFrameのメソッドで、指定した列のデータに従って各行をグループ分けします。
なお、group_byには複数の列を指定できます。

その直後にaggというメソッドを付けることで、各グループごとに総和、平均、最大値などを取得できます。

なお、グループの順番は毎回ランダムなのですが、group_byの後は指定した列を基準にsortすることで一定の結果を見られます。


df_grouped = df_example.group_by("曜日").agg(
    pl.max("客数").alias("最大客数"),
    pl.mean("客数").alias("平均客数"),
    pl.sum("客数").alias("合計客数"),
).sort("曜日")

display(df_grouped)

shape: (2, 4)

曜日 最大客数 平均客数 合計客数
str i64 f64 i64
"土曜日" 200 170.0 510
"日曜日" 250 215.0 430

ピボットテーブルの作成

pivotによる列の指定

pivotというDataFrameのメソッドで、ピボットテーブルを作成できます。

引数として、indexは表側(行名)とする列名、columnsは表頭(列名)とする列名、valuesは値を参照する列名があります。

なお、複数の列を表側や表頭とする場合は、列名のリストを渡す必要があります。

さらに、オプション引数として、aggregate_functionはvaluesに対する総和、平均、最大値などの計算を指定できます。


df_pivot = df_example.pivot(
    values="売上額", index="店舗名", columns="曜日", aggregate_function="mean"
)

display(df_pivot)

shape: (3, 3)

店舗名 土曜日 日曜日
str f64 f64
"東京タワー店" 500000.0 600000.0
"浅草寺店" 325000.0 null
"秋葉原店" null 450000.0

meltによるピボットテーブルの解除

pivotの逆のことをするメソッドとしてmeltがあります。

meltの引数のid_varsはキーとする列、value_varsはデータとする列(指定しない場合はid_vars以外の全ての列)で、さらにvariable_nameやvalue_nameという引数で、下の例で"variable", "value"になっている列名を命名できます。


df_melt = df_pivot.melt(id_vars="店舗名")

display(df_melt)

shape: (6, 3)

店舗名 variable value
str str f64
"東京タワー店" "土曜日" 500000.0
"浅草寺店" "土曜日" 325000.0
"秋葉原店" "土曜日" null
"東京タワー店" "日曜日" 600000.0
"浅草寺店" "日曜日" null
"秋葉原店" "日曜日" 450000.0

まとめ

Polarsの基本的な使い方を説明しました。
SQLの心得がある方には見覚えのあるメソッド名が並んでいたかもしれません。
RやPandasの心得がある方には反対に、細かな違いが目についたかもしれません。
いずれにせよ、これらの機能で簡単な処理はPolarsでできるはずなので、あとはExpressionの使い勝手を実践で身に着けていけば大丈夫です。

Polarsの機能は他にもまだまだあり、selectorによる列の選択、文字列や配列をデータとして持つ列の処理、複数のDataFrameを用いた結合や比較、そしてLazyFrameについての説明をしたかったところですが、長くなってしまったので次回に回します。
また、グラフのプロット(図示)の記事もそのうち作成します。

なにはともあれ、Polarsがさらによく知られ、Polars使いが一人でも多く増えることを願っています。
そうすれば、Polarsについて検索しているのに予測変換でPandasにすり替えられるというお節介も減ることでしょう。