gpt4 book ai didi

python - 如何避免 Pandas DataFrame 中过多的 lambda 函数分配和应用方法链

转载 作者:行者123 更新时间:2023-12-03 15:42:36 28 4
gpt4 key购买 nike

我正在尝试将 R 中数据帧的操作管道转换为其 Python 等效项。管道的一个基本示例如下,包含几个mutatefilter调用:

library(tidyverse)

calc_circle_area <- function(diam) pi / 4 * diam^2
calc_cylinder_vol <- function(area, length) area * length

raw_data <- tibble(cylinder_name=c('a', 'b', 'c'), length=c(3, 5, 9), diam=c(1, 2, 4))

new_table <- raw_data %>%
mutate(area = calc_circle_area(diam)) %>%
mutate(vol = calc_cylinder_vol(area, length)) %>%
mutate(is_small_vol = vol < 100) %>%
filter(is_small_vol)

我可以毫不费力地在 Pandas 中复制它,但发现它涉及一些嵌套的 lambda使用时调用 assign做一个 apply (首先数据帧调用者是一个参数,然后是数据帧行作为参数)。这往往会掩盖赋值调用的含义,如果可能的话,我想在其中指定更切题的内容(如 R 版本)。
import pandas as pd
import math

calc_circle_area = lambda diam: math.pi / 4 * diam**2
calc_cylinder_vol = lambda area, length: area * length

raw_data = pd.DataFrame({'cylinder_name': ['a', 'b', 'c'], 'length': [3, 5, 9], 'diam': [1, 2, 4]})

new_table = (
raw_data
.assign(area=lambda df: df.diam.apply(lambda r: calc_circle_area(r.diam), axis=1))
.assign(vol=lambda df: df.apply(lambda r: calc_cylinder_vol(r.area, r.length), axis=1))
.assign(is_small_vol=lambda df: df.vol < 100)
.loc[lambda df: df.is_small_vol]
)

我知道 .assign(area=lambda df: df.diam.apply(calc_circle_area))可以写成 .assign(area=raw_data.diam.apply(calc_circle_area))但只是因为 diam列已存在于原始数据框中,但情况并非总是如此。

我也意识到 calc_...这里的函数是可向量化的,这意味着我也可以做类似的事情
.assign(area=lambda df: calc_circle_area(df.diam))
.assign(vol=lambda df: calc_cylinder_vol(df.area, df.length))

但同样,由于大多数函数不可矢量化,因此在大多数情况下这不起作用。

TL;DR 我想知道是否有一种更简洁的方法来“变异”数据帧上的列而不涉及双重嵌套 lambda语句,例如:
.assign(vol=lambda df: df.apply(lambda r: calc_cylinder_vol(r.area, r.length), axis=1))

这种类型的应用程序是否有最佳实践,或者这是在方法链上下文中可以做的最好的实践吗?

最佳答案

最佳实践是向量化操作。

这样做的原因是性能,因为 apply很慢。您已经在 R 代码中利用了矢量化,您应该继续在 Python 中这样做。您会发现,由于这种性能考虑,您需要的大部分功能实际上都是可矢量化的。

这将摆脱你内心的 lambdas。对于 df 上的外部 lambda ,我认为你拥有的是最干净的模式。另一种方法是反复重新分配给 raw_data变量或其他一些中间变量,但这不符合您要求的方法链接样式。

还有 Python 包,如 dfply旨在模仿 dplyr感觉在 Python 中。这些没有得到与核心相同级别的支持 pandas会,所以如果你想走这条路,请记住这一点。

或者,如果您只想节省一点打字的时间,并且所有函数都将只在列上,您可以创建一个粘合函数,为您解包并传递它们。

def df_apply(col_fn, *col_names):
def inner_fn(df):
cols = [df[col] for col in col_names]
return col_fn(*cols)
return inner_fn

然后使用最终看起来像这样:

new_table = (
raw_data
.assign(area=df_apply(calc_circle_area, 'diam'))
.assign(vol=df_apply(calc_cylinder_vol, 'area', 'length'))
.assign(is_small_vol=lambda df: df.vol < 100)
.loc[lambda df: df.is_small_vol]
)

也可以在不利用矢量化的情况下编写它,以防万一。

def df_apply_unvec(fn, *col_names):
def inner_fn(df):
def row_fn(row):
vals = [row[col] for col in col_names]
return fn(*vals)
return df.apply(row_fn, axis=1)
return inner_fn

为了更加清晰,我使用了命名函数。但是它可以用 lambda 压缩成看起来很像你的原始格式的东西,只是通用的。

关于python - 如何避免 Pandas DataFrame 中过多的 lambda 函数分配和应用方法链,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61243071/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com