用另一个数据框的列特定标量除以Polars数据框的每一列
我是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)