【问题标题】:DataFrame GroupBy with Multiple Column Output具有多列输出的 DataFrame GroupBy
【发布时间】:2018-06-10 03:25:18
【问题描述】:

我的目标是获取一个 DataFrame 对象并向其附加多个列,其中这些列是按组计算的,但这些计算不能直观地向量化(它们涉及 if 语句的累积和)。

我来自 R data.table 背景,我将在其中运行如下代码:

DT[,c('newcol1','newcol2'):=f(.SD),by=groupvar]

其中 groupvar 是分组变量,函数 f 接受 sub-data.table(按组拆分)并返回一个列表,其中两个数组的长度等于组的长度。在这种情况下,赋值的副作用 := 将两个新列 newcol1 和 newcol2 附加到原始 data.table DT。

我尝试使用 pandas 文档,但我仍然不清楚如何复制此操作(例如,我的函数 f 应该返回 DataFrames 还是只是带有 Series 的 dict?)。

这是我最初的 df:

import pandas as pd
df=pd.DataFrame({'id': [1, 1, 1, 1, 2, 2, 2, 2],'time':[1,2,3,4,1,2,3,4],'choice':['a','a','b','a','b','b','b','b']})

我想在“a”和“b”两列上添加,以便它们在该时间段的选择之前按该 ID 计算选择“a”或“b”的累积数量。我想要的输出是这样的:

dffinal=pd.DataFrame({'id': [1, 1, 1, 1, 2, 2, 2, 2],'time' [1,2,3,4,1,2,3,4],'choice':['a','a','b','a','b','b','b','b'],'a':[0,1,2,2,0,0,0,0],'b'=[0,0,0,1,0,1,2,3]})

我写了一个函数,大致按组进行正确的操作(假设它已经按时间排序):

def cumulativechoice(df):
    length=df.shape[0]
    cols=['a','b']
    for x in cols:
        df[x]=0
    for x in cols:
        counter=0
        for y in range(length):
            df.loc[y,x]=counter
            if df.loc[y,'choice']==x:
                counter=counter+1
    return df[cols]

如果我运行累积选择(subdf),其中 subdf 是一个 id 的子数据帧,该函数工作得很好,如果我尝试 df.groupby('id').apply(cumulativechoice) 并显示错误消息“无法重新索引”,它会中断从一个重复的轴'。我在这里做错了什么?

编辑: 更一般地说,我的问题不是关于我的函数累积选择的细节,而是关于我想要的“正确”拆分-应用-组合公式是什么 1) 按组拆分,2) 应用生成多个 dicts/ 的函数一个 DataFrame 等和 3) 组合回来,因此最终结果是我在输出中添加了多个列,在特殊情况下它不像“转换”那么简单。

【问题讨论】:

    标签: python pandas pandas-groupby


    【解决方案1】:

    嗯,这有点复杂,但并不难。您可以利用pd.get_dummies 来简化此操作。

    df = df.set_index('id')
    
    def f(x):
        return x.shift().fillna(0).cumsum().astype(int)
    
    v = pd.get_dummies(df.choice).groupby(level=0).apply(f)
    pd.concat([v, df], 1).reset_index()
    
       id  a  b choice  time
    0   1  0  0      a     1
    1   1  1  0      a     2
    2   1  2  0      b     3
    3   1  2  1      a     4
    4   2  0  0      b     1
    5   2  0  1      b     2
    6   2  0  2      b     3
    7   2  0  3      b     4
    

    详情

    首先,设置索引。

    df = df.set_index('id')
    

    get_dummies 获取 OHE -

    i = pd.get_dummies(df.choice)
    i
    
        a  b
    id      
    1   1  0
    1   1  0
    1   0  1
    1   1  0
    2   0  1
    2   0  1
    2   0  1
    2   0  1
    

    现在,groupbyIDshift 每个值减 1,找到 cumsum 并变换回来。

    v = i.groupby(level=0).apply(lambda x: 
            x.shift().fillna(0).cumsum().astype(int))
    v
    
        a  b
    id      
    1   0  0
    1   1  0
    1   2  0
    1   2  1
    2   0  0
    2   0  1
    2   0  2
    2   0  3
    

    现在,这只是连接结果的问题 -

    pd.concat([v, df], 1)
    
        a  b choice  time
    id                   
    1   0  0      a     1
    1   1  0      a     2
    1   2  0      b     3
    1   2  1      a     4
    2   0  0      b     1
    2   0  1      b     2
    2   0  2      b     3
    2   0  3      b     4
    

    然后重置索引。

    连接的替代方法是切片分配 -

    df[['a', 'b']] = v
    df
    
       choice  time  a  b
    id                   
    1       a     1  0  0
    1       a     2  1  0
    1       b     3  2  0
    1       a     4  2  1
    2       b     1  0  0
    2       b     2  0  1
    2       b     3  0  2
    2       b     4  0  3
    

    【讨论】:

    • 这看起来不错,给了我想要的东西,但是有没有办法做到这一点,而无需制作第二个 DataFrame 并手动将其连接回来(我不知道这是否真的可能,我我只是问)。由于我还在学习熊猫,这种制作新列并连接回“标准”拆分-应用-组合公式的方法吗?
    • @Ray 是的,还有另一种方法,它更便宜。这称为切片分配。确保在执行任何操作之前将索引设置为 ID(请参阅我的答案的编辑)。此外,S-A-C 范式是指将数据帧分成组,对每个组进行操作(希望是矢量化的),然后组合结果。更多信息:pandas.pydata.org/pandas-docs/stable/groupby.html
    • 感谢您的澄清。这是一个有点愚蠢的问题,但在一个我最终想使用分组操作添加 n 个新列的一般问题中(如这个问题),我的函数 f(x) 应该作用于什么类型的对象组输出?它应该是一个 DataFrame、一个带有列表的字典等吗?我知道在 R 中,如果我为每个组输出一个 data.frame 样式对象(或 python 列表的类似物),它会为我将它们“堆叠”在最终输出中的所有组上,但是我的等效输出是什么使用 pandas 时应该针对每个群体?
    • @Ray 取决于功能。例如,apply 将传递一个数据帧并期望一个数据帧作为回报。 transform 将期待一个系列。 agg 将期望单个值或从传递的输入减少的值。只有实践才能帮助您了解什么是去哪里。
    【解决方案2】:

    return df[cols] 更改为return df,可以防止此错误,但不能完全解决您的问题。您在代码中使用的 for-loop 不是迭代数据帧的适当方式。相反,我们可以将其更改为 iterrows() 并消除无用代码

    def cumulativechoice(df):
        cols=['a','b']
        for z in cols:
            df[z]=0
        for x in cols:
            counter=0
            for index,row in df.iterrows(): 
                df.loc[index,x]=counter
                if row['choice']==x:
                    counter=counter+1
        return df[cols] #<- this for just 'a' & 'b' or return df for entire df
    

    但是,也许这样的事情会更容易一些..

    # set location where true == 1
    df.loc[df.choice == 'a','a'] = 1
    df.loc[df.choice == 'b','b'] = 1
    
    #do a cumsum on new columns
    df.fillna(0).groupby('id')['a','b'].cumsum()
    

    为您提供以下值,它们以 1 而不是零开始,但如果有必要,您可以抵消它....

      a  b
    0  1  0
    1  2  0
    2  2  1
    3  3  1
    4  0  1
    5  0  2
    6  0  3
    7  0  4
    

    【讨论】:

    • 输出不是我想要的,但这没什么大不了的。我真正的问题涉及这些涉及添加多个附加列的操作的正确“拆分-应用-组合”语法/框架。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-02-22
    • 2015-10-15
    • 1970-01-01
    • 2018-08-20
    • 2021-07-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多