【问题标题】:Assign involving both reducing & non-reducing operations in Pandas分配涉及 Pandas 中的减少和非减少操作
【发布时间】:2022-11-23 02:11:33
【问题描述】:

我是一个 R/Tidyverse 的人,在 python/pandas 中弄湿了我的脚,并且无法辨别是否有一种方法可以在 pandas 中像 tidyverse 一样优雅地执行以下操作:

(
    dat
    %>% group_by(grp)
    %>% mutate(
        value = value/max(value)
    )
)

因此,有一个涉及非归约运算(除法)的分组变异,该运算又涉及归约运算的结果(最大值)。我知道以下是可能的:

import pandas as pd
import numpy as np

df = pd.DataFrame({'grp': np.random.randint(0,5, 10), 'value': np.random.randn(10)}).sort_values('grp')

tmp = (
    df
    .groupby('grp')
    .agg('max')
)

(
    df
    .merge(tmp,on='grp')
    .assign(
        value = lambda x: x.value_x / x.value_y
    )
)

但我觉得必须有一种方法来避免创建临时变量tmp,以便在一个表达式中实现这一点,就像我在 tidyverse 中可以实现的那样。我错了吗?

更新:我将@PaulS 的回答标记为正确,因为它确实解决了提出的问题。在使用我的最小示例以外的其他东西时,我意识到在 tidyverse 中还有我没有考虑到的其他隐式行为;具体来说,不涉及一系列指定操作的列保留在 tidyverse 案例中,并丢弃在@PaulS 的答案中。所以这里是一个更接近地模拟 tidyverse 的示例和解决方案:

df = (
    pd.DataFrame({
        'grp': np.random.randint(0,5, 10) #to be used for grouping
        , 'time': np.random.normal(0,1,10) #extra column not involved in computation
        , 'value': np.random.randn(10) #to be used for calculations
    })
    .sort_values(['grp','time'])
    .reset_index()
)

#computing a grouped non-reduced-divided-by-reduced:
(
    df
    .groupby('grp', group_keys=False)
    .apply(
        lambda x: (
            x.assign(
                value = (
                    x.value
                    / x.value.max()
                )
            )
        )
    )
    .reset_index()
    .drop(['index','level_0'],axis=1)
)

我还发现,如果我想在分配期间索引到一列,我必须稍微调整一下,例如:

#this time the reduced compute involves getting the value at the time closest to zero:
(
    df
    .groupby('grp', group_keys=False)
    .apply(
        lambda x: (
            x.assign(
                value = (
                    x.value
                    / x.value.values[np.argmin(np.abs(x.time))] #note use of .values[]
                )
            )
        )
    )
    .reset_index()
    .drop(['index','level_0'],axis=1)
)

【问题讨论】:

    标签: python pandas tidyverse


    【解决方案1】:

    一个可能的解决方案:

    (df.groupby('grp')
     .apply(lambda g: g['value'].div(g['value'].max()))
     .droplevel(1)
     .reset_index())
    

    输出:

        grp      value
    0    0   1.000000
    1    1   1.000000
    2    1   1.052922
    3    2   1.000000
    4    2   5.873499
    5    3  10.009542
    6    3   1.000000
    7    4   1.000000
    8    4  -0.842420
    9    4   0.410153
    

    【讨论】:

    • 谢谢!您的解决方案与我在 tidyverse 中熟悉的内容不匹配的一个方面是,在 tidyverse 操作结束时,所有初始列都会保留,而在您的实现中,仅保留定义 groupby 结构的列和 apply 的输出。我看到 pd.DataFrame.apply 有一个 result_type arg,其中 broadcast 会实现我想要的行为,但我是否正确说不能做到 df.groupby('grp').apply(...,result_type='broadcast')?至少对我产生错误。
    • 欢迎,@MikeLawrence!我也来自tidyverse。你想坚持哪一列? value 一个?如果我没记错的话,mutate,当使用相同的colname 时,只会更新这样一个列——它不会创建一个新列。
    • 啊,抱歉,我的最小示例没有包含额外的列,因为我后来才意识到会更现实。想象一下另一个只有 np.random.normal() 内容的列 var。我想我已经解决了;如果我这样做 .apply( lambda g: g.assign(...) ) 将保留所有旧列并创建一个新列。
    • 没错,@MikeLawrence!只是一个不相关的评论:仅需要 .droplevel(1) 部分,因为您的数据帧的索引不是 0、1、2...(因为 sort 操作)。
    • 请注意,我更新了我的 Q,以包括在您的帮助下我得到的东西以及我在此过程中学到的一些额外知识。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-11
    • 2017-03-21
    • 2023-03-24
    • 1970-01-01
    • 2021-06-08
    • 2017-04-05
    • 1970-01-01
    相关资源
    最近更新 更多