我有一个下面的数据框
id action
================
10 CREATED
10 111
10 222
10 333
10 DONE
10 222
10 UPDATED
777 CREATED
10 333
10 DONE
我想创建一个新的“检查”列,该列将基于数据帧中前几行中的数据:
输出:
id action check
================
10 CREATED
10 111
10 222
10 333
10 DONE C
10 222
10 UPDATED
777 CREATED
10 333
10 DONE U
我尝试使用多个if条件,但对我不起作用。您能帮忙吗?
答案 0 :(得分:1)
考虑一个更复杂的示例数据框进行说明:
# print(df)
id action
10 CREATED
10 111
10 222
10 333
10 DONE
10 222
10 UPDATED
777 CREATED
10 333
10 DONE
777 DONE
10 CREATED
10 DONE
11 UPDATED
11 DONE
使用:
transformer = lambda s: s[(s.eq('CREATED') | s.eq('UPDATED')).cumsum().idxmax()]
grouper = (
lambda g: g.groupby(
g['action'].eq('DONE').cumsum().shift().fillna(0))['action']
.transform(transformer)
)
df['check'] = df.groupby('id').apply(grouper).droplevel(0).str[0]
df.loc[df['action'].ne('DONE'), 'check'] = ''
说明:
首先,我们在id
上对数据帧进行分组,并应用grouper
函数,然后对于每个分组的数据帧,我们通过在操作列中首次出现DONE
对该分组的数据帧进行分组,因此从本质上讲,我们将分组的数据帧分为多个部分,每个部分之间的距离由操作列中的DONE
值分开。然后我们使用transformer
lambda函数根据操作列中CREATED
值之前的第一个值(UPDATED
或DONE
)来变换每个分散的数据帧。>
结果:
# print(df)
id action check
0 10 CREATED
1 10 111
2 10 222
3 10 333
4 10 DONE C
5 10 222
6 10 UPDATED
7 777 CREATED
8 10 333
9 10 DONE U
10 777 DONE C
11 10 CREATED
12 10 DONE C
13 11 UPDATED
14 11 DONE U
答案 1 :(得分:0)
我不知道这是否是最好的答案,但我尝试创建自己的逻辑来解决此问题。
1)获取执行操作的行的索引:
m = df.groupby(['id'])['action'].transform(list).eq('DONE')
idx = df[m].index.values.tolist()
df [m]:
id action
4 10 DONE
9 10 DONE
idx:
[4, 9]
2)动作已创建或已更新的所有行的分组ID和索引
n = df.groupby(['id'])['action'].transform(list).str.contains('CREATED|UPDATED', case=False)
n_idx = df[n].index
df [n]:
id action
0 10 CREATED
6 10 UPDATED
7 777 CREATED
n_idx:
Int64Index([0, 6, 7], dtype='int64')
3)用空字符串填充新列“ check”:
df['check'] = ''
4)现在您有2个索引,一个用于DONE,另一个用于CREATED / UPDATED。 现在,您必须检查以前的行是否具有任何CREATED / UPDATED,并记住它们应该具有相同的ID。
ix = [0] + idx # <-- [0, 4, 9]
for a in list(zip(ix, ix[1:])): # <--- will create range (0,4), (4,9)
for j in (n_idx):
if j in range(a[0], a[1]): # <--- compare if CREATED/UPDATED indexes fall in this range. (checking previous row) and break if get any of them
if (df.iloc[a[1]].id==df.iloc[j].id): # <-- check for id
df.loc[a[1],'check'] = df.loc[j,'action'][0] # <--- assign Action
break
最终输出:
df:
id action check
0 10 CREATED
1 10 111
2 10 222
3 10 333
4 10 DONE C
5 10 222
6 10 UPDATED
7 777 CREATED
8 10 333
9 10 DONE U
完整代码:
m = df.groupby(['id'])['action'].transform(list).eq('DONE')
idx = df[m].index.values.tolist()
n = df.groupby(['id'])['action'].transform(list).str.contains('CREATED|UPDATED', case=False)
n_idx = df[n].index
ix = [0] + idx
df['check'] = ''
for a in list(zip(ix, ix[1:])):
for j in (n_idx):
if (j in range(a[0], a[1]+1)) and (df.iloc[a[1]].id==df.iloc[j].id):
df.loc[a[1],'check'] = df.loc[j,'action'][0]
break
id action check
0 10 CREATED
1 10 111
2 10 DONE C
3 10 333
4 10 DONE
5 10 222
6 10 UPDATED
7 777 CREATED
8 777 DONE C
9 10 DONE
id action check
0 10 CREATED
1 10 111
2 10 DONE C
3 10 333
4 777 UPDATED
5 10 222
6 10 UPDATED
7 777 CREATED
8 777 DONE U
9 10 DONE
答案 2 :(得分:0)
一个有环的解决方案,不是最佳解决方案,但是可以完成工作。
这假定数据框中的行是按时间排序的,并且您有一个包含2列['id', 'action']
和整数索引= range(N)
的数据框,其中N
是列数。然后:
df['check'] = ''
for i, action in zip(df.index, df['action']):
if action == 'DONE':
action_id = df.loc[i, 'id']
prev_action = df.iloc[:i].loc[(df['id'] == action_id) &
(df['action'].isin(['CREATED', 'UPDATED'])), 'action'].iloc[-1]
if prev_action == 'CREATED':
df.loc[i, 'check'] = 'C'
elif prev_action == 'UPDATED':
df.loc[i, 'check'] = 'U'
基本上,我们遍历动作,查找df['action'] == 'DONE'
时的情况,然后获取与该动作相关联的ID,然后通过调用{查看当前'DONE'
事件之前该ID的动作历史{1}}。然后,我们将该列表缩小为属于df.iloc[:i]
的动作,然后查看该列表中的最后一个动作,并根据该动作将值分配给['CREATED', 'UPDATED']
列。