Pythonのデータ処理を高速化する：Polarsライブラリ入門



## はじめに

Pythonでデータ処理を行う際、多くの開発者がPandasを利用していますが、より高速で効率的な選択肢があることをご存知でしょうか？本記事では、Rust言語で実装された高性能なデータフレームライブラリ「Polars」について詳しく解説します。Polarsは、大規模データセットの処理に優れ、直感的なAPIを提供しながら、驚異的な処理速度を実現します。以下の章では、Polarsの基本的な使い方から応用的なテクニックまで、実践的なコード例を交えて紹介していきます。

## 第1章：Polarsのインストールと基本設定

Polarsは簡単にインストールできます。まずは、pipを使ってPolarsをインストールしましょう。

```python
pip install polars
```

インストールが完了したら、Pythonスクリプトで以下のようにインポートします。

```python
import polars as pl

# バージョン確認
print(pl.__version__)
```

Polarsは継続的に更新されているため、最新バージョンを使用することをお勧めします。バージョンを確認し、必要に応じて更新してください。

## 第2章：データフレームの作成

Polarsでデータフレームを作成する方法はいくつかあります。最も一般的な方法は、辞書やリストから作成する方法です。

```python
# 辞書からデータフレームを作成
data = {
    "名前": ["田中", "佐藤", "鈴木"],
    "年齢": [25, 30, 28],
    "都市": ["東京", "大阪", "名古屋"]
}
df = pl.DataFrame(data)

print(df)

# リストからデータフレームを作成
data_list = [
    ["田中", 25, "東京"],
    ["佐藤", 30, "大阪"],
    ["鈴木", 28, "名古屋"]
]
df_list = pl.DataFrame(data_list, columns=["名前", "年齢", "都市"])

print(df_list)
```

Polarsのデータフレームは、PandasのDataFrameと似ていますが、より高速で効率的です。列指向のデータ構造を採用しており、大規模なデータセットでも優れたパフォーマンスを発揮します。

## 第3章：データの読み込みと書き込み

Polarsは様々なファイル形式からデータを読み込むことができます。CSVファイルを例に見てみましょう。

```python
# CSVファイルの読み込み
df = pl.read_csv("data.csv")

# データの確認
print(df.head())

# CSVファイルへの書き込み
df.write_csv("output.csv")

# Parquetファイルの読み込みと書き込み
df_parquet = pl.read_parquet("data.parquet")
df_parquet.write_parquet("output.parquet")
```

Polarsは、CSVやParquet以外にも、JSON、Excel、SQLデータベースなど、多様なデータソースに対応しています。大規模なデータセットを扱う場合は、特にParquet形式が推奨されます。

## 第4章：データの選択と絞り込み

Polarsでは、データの選択や絞り込みを直感的に行うことができます。

```python
# 特定の列を選択
selected_df = df.select(["名前", "年齢"])

# 条件に基づいて行を絞り込む
filtered_df = df.filter(pl.col("年齢") > 25)

# 複数の条件を組み合わせる
complex_filter = df.filter((pl.col("年齢") > 25) & (pl.col("都市") == "東京"))

print(selected_df)
print(filtered_df)
print(complex_filter)
```

`select`メソッドで列を選択し、`filter`メソッドで条件に基づいて行を絞り込むことができます。Polarsの強力な点は、これらの操作が非常に高速であることです。

## 第5章：データの変換と新しい列の追加

Polarsでは、既存の列を変換したり、新しい列を追加したりするのも簡単です。

```python
# 年齢を基に新しい列を作成
df_with_category = df.with_columns(
    pl.when(pl.col("年齢") < 30).then("若年層")
    .otherwise("中年層")
    .alias("年齢層")
)

# 複数の列を同時に追加
df_multi_columns = df.with_columns([
    (pl.col("年齢") + 1).alias("来年の年齢"),
    (pl.col("名前").str.lengths()).alias("名前の長さ")
])

print(df_with_category)
print(df_multi_columns)
```

`with_columns`メソッドを使用すると、既存の列を変換したり、新しい列を追加したりできます。条件分岐や文字列操作など、複雑な処理も簡潔に記述できます。

## 第6章：グループ化と集計

データ分析では、グループ化と集計操作が頻繁に行われます。Polarsでは、これらの操作を効率的に実行できます。

```python
# 都市ごとの平均年齢を計算
grouped_df = df.group_by("都市").agg(
    pl.col("年齢").mean().alias("平均年齢")
)

# 複数の集計操作を同時に行う
multi_agg_df = df.group_by("都市").agg([
    pl.col("年齢").mean().alias("平均年齢"),
    pl.col("年齢").max().alias("最高年齢"),
    pl.col("年齢").min().alias("最低年齢"),
    pl.count("名前").alias("人数")
])

print(grouped_df)
print(multi_agg_df)
```

`group_by`メソッドでグループ化し、`agg`メソッドで集計操作を行います。複数の集計操作を同時に行うことで、処理効率が向上します。

## 第7章：欠損値の処理

実際のデータセットでは、欠損値の処理が重要になります。Polarsでは、欠損値を効率的に処理できます。

```python
# 欠損値を含むデータフレームを作成
df_with_null = pl.DataFrame({
    "A": [1, 2, None, 4],
    "B": [None, 2.5, 3.0, 4.5],
    "C": ["a", None, "c", "d"]
})

# 欠損値を含む行を削除
df_dropna = df_with_null.drop_nulls()

# 特定の列の欠損値を埋める
df_fillna = df_with_null.with_columns([
    pl.col("A").fill_null(0),
    pl.col("B").fill_null(pl.col("B").mean()),
    pl.col("C").fill_null("不明")
])

print(df_dropna)
print(df_fillna)
```

`drop_nulls`メソッドで欠損値を含む行を削除したり、`fill_null`メソッドで欠損値を特定の値で埋めたりできます。Polarsは、大規模なデータセットでも高速に欠損値処理を行えます。

## 第8章：データの結合

複数のデータセットを結合する操作も、Polarsで簡単に行えます。

```python
# 2つのデータフレームを作成
df1 = pl.DataFrame({
    "ID": [1, 2, 3],
    "名前": ["田中", "佐藤", "鈴木"]
})

df2 = pl.DataFrame({
    "ID": [2, 3, 4],
    "年齢": [25, 30, 35]
})

# 内部結合
inner_join = df1.join(df2, on="ID", how="inner")

# 左外部結合
left_join = df1.join(df2, on="ID", how="left")

# 完全外部結合
outer_join = df1.join(df2, on="ID", how="outer")

print(inner_join)
print(left_join)
print(outer_join)
```

`join`メソッドを使用して、様々な種類の結合操作を行うことができます。結合のキーや方法を指定することで、柔軟なデータ統合が可能です。

## 第9章：時系列データの処理

Polarsは時系列データの処理にも強みを持っています。日付や時刻に関する操作を効率的に行えます。

```python
import polars as pl

# 時系列データを含むデータフレームを作成
df_time = pl.DataFrame({
    "日付": pl.date_range(start="2023-01-01", end="2023-12-31", interval="1d"),
    "値": pl.arange(0, 365)
})

# 月ごとの集計
monthly_agg = df_time.group_by_dynamic("日付", every="1mo").agg([
    pl.col("値").mean().alias("平均値"),
    pl.col("値").max().alias("最大値")
])

# 週ごとの移動平均
weekly_moving_avg = df_time.with_columns(
    pl.col("値").rolling_mean(window_size="7d").alias("7日移動平均")
)

print(monthly_agg)
print(weekly_moving_avg)
```

`group_by_dynamic`メソッドを使用して時間間隔ごとの集計を行ったり、`rolling_mean`などの関数で移動平均を計算したりできます。Polarsの時系列機能は、金融データ分析や需要予測など、様々な分野で活用できます。

## 第10章：データの可視化

Polarsは直接的な可視化機能を持っていませんが、他のライブラリと組み合わせることで、効果的なデータ可視化が可能です。ここでは、matplotlibとの連携例を紹介します。

```python
import polars as pl
import matplotlib.pyplot as plt

# サンプルデータの作成
df = pl.DataFrame({
    "x": range(10),
    "y": [i**2 for i in range(10)]
})

# matplotlibでプロット
plt.figure(figsize=(10, 6))
plt.plot(df["x"], df["y"], marker='o')
plt.title("Polarsデータフレームの可視化")
plt.xlabel("X軸")
plt.ylabel("Y軸")
plt.grid(True)
plt.show()
```

Polarsのデータフレームは、簡単にPythonのリストや配列に変換できるため、matplotlibやseabornなどの可視化ライブラリと相性が良いです。大規模データセットの場合、Polarsで前処理や集計を行ってから可視化すると、効率的にデータを探索できます。

## 第11章：高度なデータ操作：Window関数

Polarsは、SQLのWindow関数に相当する強力な機能を提供しています。これにより、複雑なデータ分析タスクを簡単に実行できます。

```python
import polars as pl

# サンプルデータの作成
df = pl.DataFrame({
    "部門": ["A", "A", "B", "B", "C", "C"],
    "売上": [100, 150, 200, 250, 300, 350]
})

# 部門ごとの累積和を計算
df_cumsum = df.with_columns([
    pl.col("売上").cum_sum().over("部門").alias("部門内累積売上"),
    pl.col("売上").cum_sum().alias("全体累積売上")
])

# 部門ごとのランキングを計算
df_rank = df.with_columns([
    pl.col("売上").rank().over("部門").alias("部門内ランク"),
    pl.col("売上").rank().alias("全体ランク")
])

print(df_cumsum)
print(df_rank)
```

`over`メソッドを使用することで、特定のグループ内での計算や、全体に対する計算を柔軟に行うことができます。これは、売上分析や顧客セグメンテーションなど、ビジネス分析で頻繁に必要とされる操作です。

## 第12章：大規模データセットの効率的な処理

Polarsの強みの一つは、大規模データセットを効率的に処理できることです。ここでは、遅延評価（Lazy Evaluation）を使用した例を紹介します。

```python
import polars as pl

# 大規模CSVファイルを想定
# 実際には大きなファイルへのパスを指定します
file_path = "large_dataset.csv"

# 遅延評価を使用してクエリを構築
lazy_df = pl.scan_csv(file_path)

result = (
    lazy_df
    .filter(pl.col("age") > 30)
    .group_by("city")
    .agg([
        pl.col("salary").mean().alias("平均給与"),
        pl.col("salary").max().alias("最高給与")
    ])
    .sort("平均給与", descending=True)
    .limit(10)
    .collect()
)

print(result)
```

`scan_csv`関数を使用することで、ファイル全体をメモリに読み込むことなく処理を行えます。遅延評価により、Polarsは最適化されたクエリプランを生成し、効率的にデータを処理します。


## 第13章：カスタム関数の適用

Polarsでは、カスタム関数を適用して複雑なデータ処理を行うことができます。ここでは、UDFを使用した例を紹介します。

```python
import polars as pl

# サンプルデータの作成
df = pl.DataFrame({
    "名前": ["田中", "佐藤", "鈴木"],
    "年齢": [25, 30, 35],
    "給与": [300000, 400000, 500000]
})

# カスタム関数の定義
def salary_category(salary):
    if salary < 350000:
        return "低"
    elif salary < 450000:
        return "中"
    else:
        return "高"

# UDFを使用して新しい列を追加
df_with_category = df.with_columns(
    pl.col("給与").apply(salary_category).alias("給与カテゴリ")
)

print(df_with_category)
```

`apply`メソッドを使用することで、Pythonの関数をPolarsのデータフレームに適用できます。これにより、複雑なビジネスロジックや条件分岐を含む処理も実現可能です。

## 第14章：メモリ効率の最適化

Polarsは、メモリ効率を重視して設計されていますが、さらに最適化するテクニックがあります。

```python
import polars as pl

# 大規模データセットを想定
large_df = pl.DataFrame({
    "ID": range(1000000),
    "値": [i * 2 for i in range(1000000)]
})

# メモリ使用量の確認
print(large_df.estimated_size())

# 整数型の最適化
optimized_df = large_df.with_columns([
    pl.col("ID").cast(pl.UInt32),
    pl.col("値").cast(pl.Int32)
])

print(optimized_df.estimated_size())

# 必要な列のみを選択
selected_df = optimized_df.select(["ID"])

print(selected_df.estimated_size())
```

データ型の最適化や不要な列の削除により、メモリ使用量を大幅に削減できます。`estimated_size`メソッドを使用して、データフレームのメモリ使用量を確認し、最適化の効果を測定できます。

## 第15章：Polarsの拡張機能と高度な使用法

Polarsには、より高度な機能や拡張機能があります。ここでは、文字列操作と正規表現の使用例を紹介します。

```python
import polars as pl

# サンプルデータの作成
df = pl.DataFrame({
    "テキスト": ["Hello, World!", "Python 3.9", "Polars 0.15.1", "Data Science"]
})

# 文字列操作
df_string_ops = df.with_columns([
    pl.col("テキスト").str.to_uppercase().alias("大文字"),
    pl.col("テキスト").str.contains("Python").alias("Pythonを含む"),
    pl.col("テキスト").str.extract(r"(\d+\.\d+)", 1).alias("バージョン")
])

print(df_string_ops)

# 正規表現を使用した複雑な操作
df_regex = df.with_columns([
    pl.col("テキスト").str.extract(r"([A-Za-z]+)", 1).alias("最初の単語"),
    pl.col("テキスト").str.count_match(r"\b\w+\b").alias("単語数")
])

print(df_regex)
```

Polarsの文字列操作機能は非常に強力で、大規模なテキストデータの処理にも適しています。正規表現を使用することで、複雑なパターンマッチングや抽出操作も簡単に行えます。

以上、Polarsライブラリの基本から応用まで、15章にわたって詳しく解説しました。Polarsは高速で効率的なデータ処理を可能にし、大規模データセットの分析に特に適しています。Pythonでのデータ分析作業を効率化したい方は、ぜひPolarsを試してみてください。その速度と使いやすさに、きっと驚かれることでしょう。