DataFrame GroupBy具有多列输出

时间:2017-12-29 18:26:37

标签: python pandas pandas-groupby

我的目标是获取一个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'这样他们就可以计算累积的选择数量' a'或者' b'在该时间段之前的那个id的选择。我想要的输出是:

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的子数据框架,那么该函数运行正常,如果我尝试df.groupby(' id'),它会中断.app(cumulativechoice)with错误消息'无法从重复的轴'重新索引。我在这里做错了什么?

编辑: 更一般地说,我的问题不是关于我的函数累积选择的具体细节,而是什么是正确的' split-apply-combine公式用于我想要的地方1)按组拆分,2)应用生成多个dicts / a DataFrame等的函数,3)组合回来,以便最终结果是我添加的我的输出有多列,在特殊情况下,它并不像变换那样简单。

2 个答案:

答案 0 :(得分: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

答案 1 :(得分: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