Polars自定义函数 #
现在你应该相信,polar表达式是如此的强大和灵活,以至于对自定义python函数的需求比你在其他库中可能需要的要少得多。
尽管如此,你仍然需要有能力将表达式传递给第三方库,或者将你的黑匣子函数应用于polar数据。
为此,我们提供了以下几种表达式:
mapapply
map #
在操作方式上和最终向用户传递的数据上,map和apply函数有重要的区别。
map函数将表达式所支持的Series数据原封不动的传递。
map函数在select和groupby中遵循相同的规则,这将意味着Series代表DataFrame中的一个列。注意,在groupby情况下,该列还没有被分组!
map函数的用法是将表达式中的Series传递给第三方库。下面我们展示了如何使用map将一个表达式列传递给神经网络模型。
df.with_column([
pl.col("features").map(lambda s: MyNeuralNetwork.forward(s.to_numpy())).alias("activations")
])
在groupby中,map的使用情况很有限。它们只用于性能方面,但很容易导致不正确的结果。让我们来解释一下原因。
df = pl.DataFrame(
{
"keys": ["a", "a", "b"],
"values": [10, 7, 1],
}
)
out = df.groupby("keys", maintain_order=True).agg(
[
pl.col("values").map(lambda s: s.shift()).alias("shift_map"),
pl.col("values").shift().alias("shift_expression"),
]
)
print(df)
shape: (3, 2)
┌──────┬────────┐
│ keys ┆ values │
│ --- ┆ --- │
│ str ┆ i64 │
╞══════╪════════╡
│ a ┆ 10 │
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ a ┆ 7 │
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
│ b ┆ 1 │
└──────┴────────┘
在上面的片段中,我们按"keys"列分组。这意味着我们有以下几个组。
"a" -> [10, 7]
"b" -> [1]
如果我们再向右应用一个shift操作,我们就会发现。
"a" -> [null, 10]
"b" -> [null]
现在,让我们打印一下得到的结果:
print(out)
shape: (2, 3)
┌──────┬────────────┬──────────────────┐
│ keys ┆ shift_map ┆ shift_expression │
│ --- ┆ --- ┆ --- │
│ str ┆ list[i64] ┆ list[i64] │
╞══════╪════════════╪══════════════════╡
│ a ┆ [null, 10] ┆ [null, 10] │
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ b ┆ [7] ┆ [null] │
└──────┴────────────┴──────────────────┘
😯.. 很明显,我们得到了一个错误答案。"b"组甚至从"a"组拿到了一个值7😵.
这是一个可怕的错误,因为map在我们聚合之前就应用了这个函数!这意味着整个列[10, 7, 1]先向右移向到了[null, 10, 7],然后再被聚合。
所以我们的建议是,除非你知道你需要使用map并且知道你在做什么,否则永远不要在groupby时使用map。
apply #
幸运的是,我们可以用apply来解决之前的例子。apply可以对该操作的最小的逻辑元素起作用。
这就意味着:
select-> 单个元素groupby-> 单个分组
因此,我们可以用apply来解决我们上述的问题:
out = df.groupby("keys", maintain_order=True).agg(
[
pl.col("values").apply(lambda s: s.shift()).alias("shift_map"),
pl.col("values").shift().alias("shift_expression"),
]
)
print(out)
shape: (2, 3)
┌──────┬────────────┬──────────────────┐
│ keys ┆ shift_map ┆ shift_expression │
│ --- ┆ --- ┆ --- │
│ str ┆ list[i64] ┆ list[i64] │
╞══════╪════════════╪══════════════════╡
│ a ┆ [null, 10] ┆ [null, 10] │
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ b ┆ [null] ┆ [null] │
└──────┴────────────┴──────────────────┘
可以看到,我们得到了正确的结果! 🎉
select中的apply
#
在select中,apply表达式将列的元素传递给python函数。
注意,你现在正在运行Python,这将会很慢。
让我们通过一些例子来看看会发生什么。我们将继续使用我们在本节开始时定义的DataFrame,并展示一个使用apply函数的例子和一个使用表达式API实现相同目标的反例。
添加一个计数器 #
在这个例子中,我们创建了一个全局的 counter (计数器),然后在每处理一个元素时将整数 1 添加到全局状态中。每个迭代的增量结果将被添加到元素值中。
counter = 0
def add_counter(val: int) -> int:
global counter
counter += 1
return counter + val
out = df.select(
[
pl.col("values").apply(add_counter).alias("solution_apply"),
(pl.col("values") + pl.arange(1, pl.count() + 1)).alias("solution_expr"),
]
)
print(out)
shape: (3, 2)
┌────────────────┬───────────────┐
│ solution_apply ┆ solution_expr │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞════════════════╪═══════════════╡
│ 11 ┆ 11 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 9 ┆ 9 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 4 ┆ 4 │
└────────────────┴───────────────┘
合并多列值 #
如果我们想在一次apply函数调用中访问不同列的值,我们可以创建struct数据类型。这种数据类型将这些列作为字段收集在struct中。因此,如果我们从列"keys"和"values"中创建一个struct,我们会得到以下结构元素。
[
{"keys": "a", "values": 10},
{"keys": "a", "values": 7},
{"keys": "b", "values": 1},
]
这些将作为dict传递给调用的Python函数,因此可以通过field: str进行索引。
out = df.select(
[
pl.struct(["keys", "values"]).apply(lambda x: len(x["keys"]) + x["values"]).alias("solution_apply"),
(pl.col("keys").str.lengths() + pl.col("values")).alias("solution_expr"),
]
)
print(out)
shape: (3, 2)
┌────────────────┬───────────────┐
│ solution_apply ┆ solution_expr │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞════════════════╪═══════════════╡
│ 11 ┆ 11 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 8 ┆ 8 │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2 ┆ 2 │
└────────────────┴───────────────┘
返回类型 #
自定义Python函数对polar而言是黑箱。我们真的不知道你在做什么黑科技,所以我们不得不推断并尽力去理解你的意思。
数据类型是自动推断出来的。我们通过等待第一个非空值来做到这一点。这个值将被用来确定Series的类型。
python类型与polars数据类型的映射如下:
int->Int64float->Float64bool->Booleanstr->Utf8list[tp]->List[tp](其中内部类型的推断规则相同)dict[str, [tp]]->structAny->object(在任何时候都要防止这种情况)
作为一个用户,我们希望您能了解我们的工作,以便能更好地利用自定义函数。