用另一个数据框的列特定标量除以Polars数据框的每一列

4 投票
2 回答
80 浏览
提问于 2025-04-14 15:55

我是Polars的新手,给定一个 m x n 的Polars数据框 df 和一个 1 x n 的标量数据框,我想把 df 中的每一列都除以另一个数据框中对应的标量。

import numpy as np
import polars as pl

cols = list('abc')
df = pl.DataFrame(np.linspace(1, 9, 9).reshape(3, 3),
                  schema=cols)
scalars = pl.DataFrame(np.linspace(1, 3, 3)[:, None],
                       schema=cols)
In [13]: df
Out[13]: 
shape: (3, 3)
┌─────┬─────┬─────┐
│ a   ┆ b   ┆ c   │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═════╪═════╪═════╡
│ 1.0 ┆ 2.0 ┆ 3.0 │
│ 4.0 ┆ 5.0 ┆ 6.0 │
│ 7.0 ┆ 8.0 ┆ 9.0 │
└─────┴─────┴─────┘

In [14]: scalars
Out[14]: 
shape: (1, 3)
┌─────┬─────┬─────┐
│ a   ┆ b   ┆ c   │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═════╪═════╪═════╡
│ 1.0 ┆ 2.0 ┆ 3.0 │
└─────┴─────┴─────┘

在Pandas中,我可以很简单地做到这一点,方法是利用NumPy的广播功能,但我在想有没有更好的方法来处理这个问题,而不需要在Polars和Pandas之间来回切换。

In [16]: df.to_pandas() / scalars.to_numpy()
Out[16]: 
     a    b    c
0  1.0  1.0  1.0
1  4.0  2.5  2.0
2  7.0  4.0  3.0

我找到过一个类似的问题,在那个问题中,标量常量已经是原始数据框中的一行,但我不知道如何利用来自另一个数据框的行。

到目前为止,我想到的最好办法是把这两个数据框合并,然后做一些看起来很复杂的操作 :D

In [31]: (pl.concat([df, scalars])
    ...:    .with_columns(pl.all() / pl.all().tail(1))
    ...:    .head(-1))
Out[31]: 
shape: (3, 3)
┌─────┬─────┬─────┐
│ a   ┆ b   ┆ c   │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═════╪═════╪═════╡
│ 1.0 ┆ 1.0 ┆ 1.0 │
│ 4.0 ┆ 2.5 ┆ 2.0 │
│ 7.0 ┆ 4.0 ┆ 3.0 │
└─────┴─────┴─────┘

2 个回答

2

我之前不确定这是否不可能,但我试了一下,简单的数学运算实际上是可以的,只要把数据框调整成相同的形状。首先,使用 Expr.repeat_by(),然后用 Expr.list.explode() 把列表展开成行:

python

df / scalars.select(pl.all().repeat_by(len(df)).explode())

┌─────┬─────┬─────┐
│ a   ┆ b   ┆ c   │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═════╪═════╪═════╡
│ 1.0 ┆ 1.0 ┆ 1.0 │
│ 4.0 ┆ 2.5 ┆ 2.0 │
│ 7.0 ┆ 4.0 ┆ 3.0 │
└─────┴─────┴─────┘

另外,显然你可以在 DataFrame()Series() 之间进行数学运算,但似乎你需要先用 transpose() 转置你的 DataFrame,因为运算是按列进行的(数据框的第一列会被系列的第一个值除,以此类推):

df.transpose()

┌──────────┬──────────┬──────────┐
│ column_0 ┆ column_1 ┆ column_2 │
│ ---      ┆ ---      ┆ ---      │
│ f64      ┆ f64      ┆ f64      │
╞══════════╪══════════╪══════════╡
│ 1.0      ┆ 4.0      ┆ 7.0      │
│ 2.0      ┆ 5.0      ┆ 8.0      │
│ 3.0      ┆ 6.0      ┆ 9.0      │
└──────────┴──────────┴──────────┘

scalars.transpose().to_series()

[
    1.0
    2.0
    3.0
]


(df.transpose() / scalars.transpose().to_series())
.transpose(column_names=df.columns)

┌─────┬─────┬─────┐
│ a   ┆ b   ┆ c   │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═════╪═════╪═════╡
│ 1.0 ┆ 1.0 ┆ 1.0 │
│ 4.0 ┆ 2.5 ┆ 2.0 │
│ 7.0 ┆ 4.0 ┆ 3.0 │
└─────┴─────┴─────┘

我快速测试了一下在稍微大一点的数据框上的性能,所有提出的解决方案在我的机器上速度似乎都差不多:

%timeit 'df.transpose() / scalars.transpose().to_series()).transpose(column_names=df.columns))'
11.4 ns ± 0.892 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)

%timeit 'df / scalars.select(pl.all().repeat_by(len(df)).explode())'
11.7 ns ± 0.992 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)

%timeit 'pl.concat([df, scalars]).with_columns(pl.all() / pl.all().tail(1)).head(-1))'
11.5 ns ± 0.369 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)

%timeit 'df.select(column / scalars[column.name] for column in df.iter_columns())'
11.2 ns ± 0.522 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)
3

我觉得你找到了一个非常独特、有趣且聪明的解决方案。你也可以考虑直接遍历列:

df.select(column / scalars[column.name] for column in df.iter_columns())

或者

df.select(pl.col(k) / scalars[k] for k in df.columns)

或者

df.with_columns(pl.col(k).truediv(scalars[k]) for k in df.columns)

撰写回答