2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ネットスーパーのデータはでかすぎてExcelでは開かないのでPolarsで集計する

Last updated at Posted at 2025-12-22

本記事はQiita Advent Calender 2025 わたなべの13日目です!
13日目は、私渡邊が作成します!

昨年度のアドベントカレンダーで、でかいデータをPythonで扱う手段としてPolarsを紹介しました。

ChatGPT Image 2025年12月22日 09_10_40.png

Polars = 白熊なのでこの画像を生成しましたが、このご時世にとても不謹慎ということに気づきました。。。

今は、ネットスーパーから異動というか出向して会社が変わったので、数GB単位の大容量なテーブルを扱う機会も少なくなりました。一つの区切りとして1年間でたまった知見を忘備録的に記事化します。

  • Pandasは知っているがPolarsは使ったことない
  • Polars使ったもののうまく使いこなせなかった

という方にはピッタリかと思います。

  • Excelで集計しているが、データがでかすぎて重くて困っている

という方にもよいかもしれませんが、Pythonでのデータ前処理の経験がないと難しい部分があるかもしれません。

pandas から polars に移行するメリット

割と語りつくされているところではあるので簡単に。

  • 処理が速い
    • 一度時間を図ったことがありますが、扱うデータが1GBを超えてくると4倍程度pandasの方がかかりました
  • 大きなデータを扱える
    • 以前の記事にも書いたLazyframeによって、pandasではdaskが必要になるデータもpolarsであれば扱えます
  • 型推論が強い
    • polars側はRustで書かれていることもあり型定義がしっかりしています
    • メソッドチェーンでつながるものは動きますし、つながらないものは絶対に動きません。そのため、実行後エラーが少ないです

pandas から polars に移行する際のつまりどころ

もちろん、import pandas as pdimport polars as plに修正して、pdplにおきかえるだけでは動きません。同じdataframe型のテーブルデータを扱うライブラリですが、設計思想が大きく異なるのです。

私自身が詰まったところは主に3つあります。

  1. 列ごとの型定義
  2. 列の名称変更・追加
  3. 破壊的変更の多さ

それぞれ、内容とTipsを紹介します。

1. 列ごとの型定義

polarsは列ごとの型定義が厳密です。例えば、int32の列にstringが入っていると、読み込み時にエラーが発生します(むしろこれが普通で、型混在していても読めるpandasが変)。

基本的には開始数行を読んで列ごとの型推論が実行されるのですが、途中で異なる型のデータが出てくるとエラーが出ます。

データきれいにしておけ、で済む場合もあるのですが小売のデータ扱うときに困るのがJANコードを扱う行などですね。特に弊社のネットスーパーでは予約商品を扱う場合、通常の商品と扱いを分けるためにJANコード13桁末尾のチェックディジットをアルファベット1文字で置換する運用をしています。

そうなると、int32で読んでいた列に突如stringが出現することになりエラーとなります。

1.1. stringとfloatでザックリ型定義する

長期にわたって運用し続けるコードであれば厳密に定義してもよいのですが、ちょっと相関係数みたいちょっと回帰式だしたいというときには非常に手間です。

なので、サッと読むときはstringとfloatでザックリと定義することで、分析に入るまでの時間を短縮していました。

result_lf = pl.scan_csv('./data/Aug_all.csv')
num_cols = [
  '商品金額合計_税抜',
  '送料_税抜',
  '手数料_税抜',
  'ポイント利用値引き',
  '消費税',
  '注文金額合計_税込',
]
schema = { col: pl.Float64 if col in num_cols else pl.Utf8 for col in result_lf.columns}
result_lf = pl.scan_csv('./data/Aug_all.csv', schema=schema, null_values=['NULL'])

こんな感じで数字のcolumnを指定し、該当する列はpl.Float64型、それ以外の列はpl.Utf8の文字列型として扱うようにし始めたところ、読み込み時のエラーから解放されました。

lazyframeの挙動について

このコード、result_lfを2回定義していてわかりにくいかもしれませんが、1回目のscan_csvは列のlistを取得するために実行しています。
その後に、再度result_lfをschema付きで再代入しています。
前述のschema違いによるエラーはpl.Lazyframe().collect()でテーブル全体を読み込んだ時に発生するので、1回目のpl.scan_csv()時点では発生しません。

ちなみにschemaのdictは、

  • scan_csv()schemaの引数(今は違うかも)
  • read_excel()schema_overridesの引数

にそれぞれ指定しないとエラー出ます。

1.2. encodingの注意点

これはpandasでも見られる事例。Shift-JISで定義された日本語のデータを読むときに文字化けする現象ですね。

encoding='cp932' を引数に指定すれば読めるというところも同じなのですが、scan_csv(), read_csv()の引数にencoding='cp932'を指定すると型エラーがでて読めません

幸いread_excel()の引数としてはencoding='cp932'をとれますので、Shift-JISのデータはexcelに変換して読むことが必要になります。

2. 列の名称変更・追加

pandasはdf['c'] = df['a'] + df['b']のような感じで雑に変更できますが、polarsは一切許されません。列の変更・追加に関する手続きはすべてdf.with_columns()を通じて実行されます。

2.1. 名称変更

pandasでは列名をrename('a', 'b')のような感じで実施できた記憶がありますが、polarsはできません。具体的には新しい名前の列を作る古い名前の列を削除するという順序で実施します。

result_lf = (
  result_lf
  .with_columns(
    pl.col('a').alias('b') ## 列名が変更されるのではなくaと同じ列のbが生成される
  )
  .drop(['a']) ## 古い列名のaをdrop(1列の場合はlistでなくてもよいが、複数選択する場合はlistで渡すということを忘れないように常々listで書いてる)
)

2.2. 型変更

一般的な型の変更はcastで実行します

lf = (
  lf
  .with_columns(
    pl.col('a').cast(pl.Utf8) ## 文字列型に変換
  )
)

面倒なのがtimestampに変更する場合。

result_lf = (
  result_lf
  .with_columns(
    pl.col('会員登録日時').str.strptime(pl.Datetime, "%Y-%m-%d %H:%M:%S%.3f").alias('会員登録日時'),
    pl.col('配送日').str.strptime(pl.Date, "%Y-%m-%d %H:%M:%S%.3f").alias('配送日'),
    pl.col('注文日').str.strptime(pl.Date, "%Y-%m-%d %H:%M:%S%.3f").alias('注文日'),
    pl.col('注文日').str.strptime(pl.Time, "%Y-%m-%d %H:%M:%S%.3f").alias('注文時間')
  )
)

こんな感じで、str.strptime()で型を変更します。
パースする形式を.3fまで正確に書かないといけないところは少し手間ですね。

関連して、pl.durationという時間差を表すデータ型があり、差分を列に型安全な状態で保持できるところは便利です。

2.3. group_byとsort

店舗a, b, c, dについてまとめて、a, b, c, dの順に並べたいという場合があるかと思います。

def store_sorter(df: pl.DataFrame):
  return (
    df
    .group_by('店舗名').sum()
    .with_columns(
      pl
      .when(pl.col('店舗名').str.contains('a')).then(pl.lit(0))
      .when(pl.col('店舗名').str.contains('b')).then(pl.lit(1))
      .when(pl.col('店舗名').str.contains('c')).then(pl.lit(2))
      .when(pl.col('店舗名').str.contains('d')).then(pl.lit(3))
      .otherwise(pl.lit(4))
      .alias('store_sorter')
    )
    .sort(['store_sorter'])
    .drop('store_sorter')
  )

こんな感じで、group_byの後で、0, 1, 2, 3, 4となる列を追加してsort。その後sortに使用した列をdropが早いです。

with_columns()の中で、when().then().otherwise()をチェーンすることで、条件分岐した列を作れるので、それの応用となります。

参照元の数があまりにも多く、when.thenでの記述が困難な場合は、名前と順番の数字からなるdataframeを生成し、join(how="left")した方が早いです

3. 破壊的変更の多さ

タイトルの通り、後方互換性が非常に悪いです。
アップデートしたら動かないと思った方がよいです。
こればっかりは、気を付けるしかないですね。

参考|広く違いをまとめた場合

チャッピーにもまとめてもらったものをzenn scrapに置いています。

pandas触ったことある方はイメージしやすいと思います。

Polars触ってみませんか?

以上の内容に気を付けていただければ、問題なく高速なデータ分析・機械学習の前処理が実現できます。polars dataframeの対応範囲は広く、sklearn(というかnumpy)matplotlib, seabornもpolars dataframeのまま動きます。

xlwingsでexcelにdataframeをそのまま貼り付ける場合は、pandasのdataframeでないと動きませんが、いざというときはpolars.dataframe().to_pandas()があるので、pandasのdataframeに変換して動かせます。

ぜひ、polars使ってみてください!熊流行ってますし。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?