DataFrame GroupBy具有多列输出

问题描述 投票:0回答:2

我的目标是获取一个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还是只返回带有Seri​​es的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]

如果我运行cumulativechoice(subdf),其中subdf是一个id的子DataFrame,该函数运行正常,如果我尝试df.groupby('id'),它会中断.apply(cumulativechoice)带有错误消息'无法从重复中重新索引轴'。我在这做错了什么?

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

python pandas pandas-groupby
2个回答
0
投票

嗯,这有点牵扯,但并不难。你可以利用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

现在,groupby IDshift每个值减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

0
投票

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
© www.soinside.com 2019 - 2024. All rights reserved.