Open6

PolarsとPandasの微妙な仕様の違い

chimuichimuchimuichimu

「PolarsとPandasで同じような処理ができるけど、微妙にその仕様が違う」という落とし穴的な情報がまとまったものはなさそうなので、備忘録的にメモしておく。

chimuichimuchimuichimu

Groupby実行時の順序性担保

PandasはGroupby実行時に集約に使う列の順序性が保持されるが、Polarsはデフォルトでは保持されない。

Pandas

df = pd.DataFrame(
    {
        "a": ["a", "b", "a", "b", "c"],
        "b": [1, 2, 1, 3, 3],
    }
)
df.groupby("a").sum()

出力結果

a b
a 2
b 5
c 3

バージョン:1.5.3

Polars

df = pl.DataFrame(
    {
        "a": ["a", "b", "a", "b", "c"],
        "b": [1, 2, 1, 3, 3],
    }
)
df.groupby("a").sum()

出力結果

a b
b 5
c 3
a 2

ちなみにmaintain_order=Trueのオプションを指定することで、Polarsでも順序性の保持は可能(ただし公式ドキュメントによるとデフォルトより実行速度が下がるとのこと)

df.groupby("a", maintain_order=True).sum().to_pandas()

出力結果

a b
a 2
b 5
c 3

バージョン:0.17.3

chimuichimuchimuichimu

countが欠損値を含むか否か

Pandasのpandas.DataFrame.countはNoneなどの欠損値を数えないのに対して、Polarsのpolars.Expr.countは欠損値を数える。

Pandas

df = pd.DataFrame(
    {
        "a": ["a", "b", "a", "b", "c"],
        "b": [None, 2, 1, 3, np.nan],
    }
)
df.count()

出力結果

a b
5 3

バージョン:1.5.3

Polars

df = pl.DataFrame(
    {
        "a": ["a", "b", "a", "b", "c"],
        "b": [None, 2, 1, 3, np.nan],
    }
)
df.select(pl.all().count())

出力結果

a b
5 5

バージョン:0.17.3

chimuichimuchimuichimu

型変換時に精度外の数値がある時の挙動

Pandasでは数値属性のカラムを型変換する時、精度外の数値があった場合でもエラーを吐かない(ただし値が壊れる)。一方でPolarsではエラーが発生する。

Pandas

df = pd.DataFrame(
    {
        "a": [1, 2, 1, 3, 99999]
    }
)
df["a"] = df["a"].astype(np.int8)
df

出力結果

index a
0 1
1 2
2 1
3 3
4 -97

バージョン:1.5.3

Polars

df = pl.DataFrame(
    {
        "a": [1, 2, 1, 3, 99999]
    }
)
df.with_columns(pl.col("a").cast(pl.Int8))

出力結果
以下のエラーが出力される

---------------------------------------------------------------------------
ComputeError                              Traceback (most recent call last)
<ipython-input-129-3e17b524f8c7> in <cell line: 6>()
      4     }
      5 )
----> 6 df.with_columns(pl.col("a").cast(pl.Int8))

1 frames
/usr/local/lib/python3.9/dist-packages/polars/lazyframe/frame.py in collect(self, type_coercion, predicate_pushdown, projection_pushdown, simplify_expression, no_optimization, slice_pushdown, common_subplan_elimination, streaming)
   1604             streaming,
   1605         )
-> 1606         return wrap_df(ldf.collect())
   1607 
   1608     def sink_parquet(

ComputeError: strict conversion from `i64` to `i8` failed for value(s) [99999]; if you were trying to cast Utf8 to temporal dtypes, consider using `strptime`

バージョン:0.17.3

chimuichimuchimuichimu

同じ名前のカラムを持つDataFrameを結合したときのカラム名

同じ名前のカラムを持つDataFrameを結合したとき、Pandasでは{カラム名}_x, {カラム名}_yとなるのに対して、Polarsでは{カラム名}, {カラム名}_right のようになる。

Pandas

df = pd.DataFrame(
    {
        "id": ["a", "b", "c"],
        "col": [1, 2, 3],
    }
)
df.merge(df, on="id")

出力結果

index id col_x col_y
0 a 1 1
1 b 2 2
2 c 3 3

バージョン:1.5.3

Polars

df = pl.DataFrame(
    {
        "id": ["a", "b", "c"],
        "col": [1, 2, 3],
    }
)
df.join(df, on="id")

出力結果

id col col_right
a 1 1
b 2 2
c 3 3

バージョン:0.17.3

chimuichimuchimuichimu

型が異なるカラムをキーにDataFrameを結合した時の挙動

型が異なるカラムをキーにDataFrameを結合したとき、Pandasでは結合元のDataFrameのカラムの型にキャストされるのに対し、Polarsではエラーになる。

Pandas

# pandas merge
df1 = pd.DataFrame(
    {
        "key": [1, 2, 3],
    },
    dtype = np.int8
)
df2 = pd.DataFrame(
    {
        "key": [1, 2],
    },
    dtype = np.int16
)

df = df1.merge(df2, on="key")
display(df)
print(df["key"].dtypes)

出力結果

index key
0 1
1 2

int8

バージョン:1.5.3

Polars

# polars join
df1 = pl.DataFrame(
    {
        "key": [1, 2, 3],
    },
    schema={
        "key": pl.Int8
    }
)
df2 = pl.DataFrame(
    {
        "key": [1, 2],
    },
    schema={
        "key": pl.Int16
    }
)

df = df1.join(df2, on="key")
display(df)
print(df.dtypes)

出力結果

---------------------------------------------------------------------------
ComputeError                              Traceback (most recent call last)
<ipython-input-25-d3fee5d54aeb> in <cell line: 19>()
     17 )
     18 
---> 19 df = df1.join(df2, on="key")
     20 display(df)
     21 print(df.dtypes)

1 frames
/usr/local/lib/python3.10/dist-packages/polars/lazyframe/frame.py in collect(self, type_coercion, predicate_pushdown, projection_pushdown, simplify_expression, slice_pushdown, comm_subplan_elim, comm_subexpr_elim, no_optimization, streaming, _eager)
   1704             _eager,
   1705         )
-> 1706         return wrap_df(ldf.collect())
   1707 
   1708     @overload

ComputeError: datatypes of join keys don't match - `key`: i8 on left does not match `key`: i16 on right

バージョン:0.20.2