我的目标是获取一个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'这样他们就可以计算累积的选择数量' 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)组合回来,以便最终结果是我添加的我的输出有多列,在特殊情况下,它并不像变换那样简单。
答案 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
-
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
ID
,shift
每个值减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