2026年10月25日、高速なデータ分析ライブラリPolarsの2.1.0が公開されました。10月6日の2.0.0から、およそ3週間での新版です。
今回、初心者にいちばん関係するのは、文字列の列と数値の列の足し算がエラーになったことです。2.0では、エラーを出さずに、思いもよらない結果を返していました。
ほかにも、新しい割り算の関数や、SQLで使える書き方がたくさん加わっています。今回は、2.0.0と2.1.0で同じコードを動かしながら、何が変わったのかを見ていきます。
まずは2.1で何が変わったのかを整理しよう¶
最初に、2.1.0の主な変更を表にまとめます。内容は、PolarsのGitHubにある2.1.0のリリースノートにもとづいています。
| 分類 | 主な変更 |
|---|---|
| 互換性を壊す変更 | 文字列と数値をまぜた計算を、eagerでも禁止 |
| 新機能 | 0に向かって切り捨てる割り算Expr.truncdiv() |
| SQL | 再帰的なWITH句、generate_series、CURRENT_DATEなどに対応 |
| ファイル | 暗号化されたParquetファイルの読み書きに対応 |
| 速度 | 88件の性能改善 |
リリースノートには、互換性を壊す変更が1件、性能改善が88件、機能追加が42件並んでいます。PyPIの記録によると、日本時間では26日の未明に配布が始まりました。
2.0.0で何が変わったのかは、こちらで紹介しています。【関連記事】Polars 2.0がリリース。ストリーミングが標準に
文字列と数値の足し算がエラーになった¶
まずは、唯一の互換性を壊す変更から見ていきましょう。リリースノートには、eagerで、文字列と数値をまぜた計算を禁止したと書かれています。
eagerとlazyとは¶
Polarsには、計算をすぐに実行するeagerと、まとめて最適化してから実行するlazyという、2つの書き方があります。SeriesやDataFrameを直接計算するのがeager、LazyFrameを使うのがlazyです。
Polarsの基本的な使い方は、こちらで紹介しています。【関連記事】PythonのPolarsとは?
2.0.0と2.1.0で動かしてみた¶
実際に試してみましょう。CSVを読み込んだときなどに、数字が文字列のまま入ってしまうのはよくあることですよね。
そんな文字列の列に、数値の列を足してみます。
import polars as pl
code = pl.Series("code", ["10", "20"])
n = pl.Series("n", [1, 2])
try:
print((code + n).to_list())
except Exception as e:
print(type(e).__name__, e)
2.0.0と2.1.0を、それぞれ新しい仮想環境に入れ、手元のPython 3.11で実行した結果です。
Polars 2.0.0
['101', '202']
Polars 2.1.0
InvalidOperationError arithmetic on dtypes str and i64 is not allowed; try an explicit cast first
2.0.0では、10と1を足して11になるのではなく、'101'という文字列が返ってきました。数値の1が文字列に変えられ、文字列としてつなげられていたのです。
2.1.0では、文字列と整数の計算は許されないというエラーになりました。エラーの文面では、先に型を変換するよう案内されています。
実はlazyでは前からエラーだった¶
同じ計算を、DataFrameのselectや、LazyFrameで試してみました。すると、2.0.0でもすでにエラーになりました。
| 書き方 | 2.0.0 | 2.1.0 |
|---|---|---|
| Series同士の足し算 | ['101', '202'] |
エラー |
数値のSeriesに"x"を足す |
['1x', '2x'] |
エラー |
| DataFrameのselectで列同士を足す | エラー | エラー |
| LazyFrameで列同士を足す | エラー | エラー |
つまり2.1.0では、Seriesを直接計算したときも、selectやLazyFrameと同じルールにそろったわけです。書き方によって結果が違うという、わかりにくさがなくなりました。
型を変換してから計算しよう¶
エラーになったときは、castで型を変換してから計算します。2.1.0で試すと、次のようになりました。
print((code.cast(pl.Int64) + n).to_list())
print((code + n.cast(pl.String)).to_list())
[11, 22]
['101', '202']
数値として足したいなら文字列の列を整数に、文字列としてつなげたいなら数値の列を文字列に変えます。どちらのつもりなのかを、コードではっきり書くことになります。
型の変換の基本は、こちらで紹介しています。【関連記事】Pythonの型変換とは?
0に向かって切り捨てる割り算truncdiv¶
続いて、新しく加わった機能を見ていきましょう。リリースノートによると、Expr.truncdiv()という、0に向かって切り捨てる割り算が加わりました。
//との違いを試してみた¶
Pythonの//は、小さいほうに向かって切り捨てる割り算です。マイナスの数で、違いが出ます。
df = pl.DataFrame({"x": [7, -7]})
print(df.with_columns(
floor=pl.col("x") // 2,
trunc=pl.col("x").truncdiv(2),
).rows())
2.1.0で実行した結果です。2.0.0では、truncdivがないというAttributeErrorになりました。
[(7, 3, 3), (-7, -4, -3)]
7を2で割った場合は、どちらも3です。ところが-7の場合は、//では-4、truncdivでは-3になりました。
-3.5を、小さいほうに切り捨てると-4、0に向かって切り捨てると-3になるからです。C言語やJavaの整数の割り算は0に向かって切り捨てるので、ほかの言語で作ったデータと結果を合わせたいときに便利です。
//がマイナスの数でどう動くかは、こちらで詳しく解説しています。【関連記事】Pythonの割り算とは?
SQLでできることが増えた¶
Polarsは、SQLでもデータを集計できます。2.1.0では、SQLまわりの機能追加がとても多くなっています。
3つの書き方を試してみた¶
今回は、generate_series、再帰的なWITH句、CURRENT_DATEの3つを試しました。generate_seriesは連番の表を作る関数、再帰的なWITH句は自分自身を参照しながら行を増やしていく書き方です。
ctx = pl.SQLContext()
queries = [
"SELECT * FROM generate_series(1, 3)",
"""
WITH RECURSIVE t(n) AS (
SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 5
)
SELECT * FROM t
""",
"SELECT CURRENT_DATE AS today",
]
for q in queries:
print(ctx.execute(q, eager=True).rows())
2.1.0で実行した結果です。
[(1,), (2,), (3,)]
[(1,), (2,), (3,), (4,), (5,)]
[(datetime.date(2026, 10, 26),)]
連番の表、1から5まで増えていく表、今日の日付が、それぞれ得られました。2.0.0では、3つとも対応していないというSQLInterfaceErrorになっています。
リリースノートには、ほかにもREGEXP_REPLACEやDATE_TRUNC、ASOF結合など、多くのSQLの書き方が並んでいます。ほかのデータベースで覚えたSQLが、Polarsでもそのまま通りやすくなったのは嬉しいところです。
そのほかの変更¶
ファイルの読み書きでは、暗号化されたParquetファイルの読み込みと書き出しに対応しました。Parquetは、データ分析でよく使われる、列ごとにデータをまとめて保存する形式です。
88件の性能改善¶
性能改善は88件にのぼります。メモリに収まらない大きなデータを扱う処理や、Parquetの読み書き、結合などで、細かな改善が積み重ねられています。
ただし、リリースノートには全体として何倍速くなったという数字は載っていません。速さは、データや処理の内容によって変わると考えておきましょう。
NumPyのマスク付き配列を、欠けた値として読めるように¶
もう1つ、静かに結果が変わる機能追加を紹介します。リリースノートによると、NumPyのマスク付き配列との変換に対応しました。
マスク付き配列とは、値が欠けている場所に印を付けたNumPyの配列のことです。印の付いた場所には、仮の値が入っています。
平均点が変わった¶
テストの点数のうち、2人目だけ欠席で測れなかった、という場面を考えてみましょう。欠席の場所には仮に0を入れ、印を付けておきます。
import numpy as np
import polars as pl
# 2つ目の値は、測れなかったので印(マスク)を付けておく
arr = np.ma.masked_array([80, 0, 95], mask=[False, True, False])
s = pl.Series("score", arr)
print(s.to_list())
print(s.mean())
2.0.0と2.1.0で実行した結果です。
Polars 2.0.0
[80, 0, 95]
58.333333333333336
Polars 2.1.0
[80, None, 95]
87.5
2.0.0では、印が無視され、仮に入れた0がそのまま点数として扱われました。その結果、平均点は欠席者を0点として数えた58.3点になっています。
2.1.0では、印の付いた場所が欠けた値を表すNoneになり、平均点は出席した2人の87.5点になりました。NumPyでマスクを使っている方は、上げると集計の結果が変わる可能性があるので注意してください。
逆向きの変換もできます。2.1.0では、to_numpy()にmasked=Trueを付けると、Noneの場所に印を付けたマスク付き配列が返ってきました。
上げる前にやっておきたいこと¶
最後に、Polarsを2.1.0へ上げる前に確かめておきたいことを整理します。
| 順番 | 確認すること |
|---|---|
| 1 | Seriesを直接計算する箇所で、文字列と数値をまぜていないか |
| 2 | まぜていたら、castでどちらの型にそろえるかを決める |
| 3 | NumPyのマスク付き配列から作ったSeriesの集計結果が変わらないか |
| 4 | //で割っている箇所で、マイナスの数のときの結果は意図どおりか |
| 5 | 使うPolarsの版を、requirements.txtなどで固定しているか |
私は10年ほどエンジニアとして開発に関わってきましたが、CSVから読んだ金額が文字列のまま足され、集計結果がとんでもない桁数になっていたのを見たことがあります。黙って変な結果を返すより、エラーで止まってくれるほうがずっと安全です。
pandasから移ってきた方は、型の扱いの違いにも気をつけてください。pandasの基本は、こちらで紹介しています。【関連記事】Pythonのpandasとは?
まとめ¶
2026年10月25日、Polars 2.1.0が公開されました。Seriesでの文字列と数値の計算がエラーになり、0に向かって切り捨てるtruncdivや、SQLの再帰的なWITH句なども加わっています。
上げたあとにエラーが出たら、それは今まで見過ごしていた型のずれかもしれません。castでどちらの型にしたいのかをはっきり書いて、より安心なコードにしていきましょう。
ここまでお読みいただきありがとうございました。