【问题标题】:pandas dataframe column based on previous rows基于前几行的熊猫数据框列
【发布时间】:2020-06-12 16:35:59
【问题描述】:

我有一个下面的数据框

         id  action   
         ================
         10   CREATED   
         10   111
         10   222
         10   333
         10   DONE      
         10   222
         10   UPDATED   
         777  CREATED    
         10   333
         10   DONE      

我想创建一个新列“检查”,该列将基于数据框中先前行中的数据:

  1. 在操作列中查找单元格 = "DONE"
  2. 在 DONE 之前搜索先前行中具有相同 ID 的第一个 CREATED 或 UPDATED。如果它已创建,则放入 C,如果已更新,则放入 U。

输出:

         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 条件,但它对我不起作用。你能帮忙吗?

【问题讨论】:

  • 每个id可以有多个DONE值吗?
  • 是的,每个 id 可以有多个 DONE,但在每个 DONE 之前,应该为该 id 创建或更新。

标签: python pandas dataframe if-statement


【解决方案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 函数根据操作列中DONE 值之前的第一个值(CREATEDUPDATED)转换每个拆分的数据帧。

结果:

# 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

【讨论】:

  • 这会失败:justpaste.it/2vkql。如果在连续的 Done 上应用逻辑。
  • 我猜不会,值应该是 C,因为它是第 777 组中 DONE 之前的第一个值
  • 而且也不能有两个“DONE”连续完成,就像我按照 OP 一样。
  • 我没听懂。应该考虑第 4 行吗?
  • 我明白了,您从一开始就在考虑第一个值,但我是从底部开始计算第一个值。我想这需要由 OP 澄清。
【解决方案2】:

一个循环的解决方案,不是最佳的,但可以完成工作。

这假设您的数据框中的行是按时间排序的,并且您有一个包含 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,然后通过调用df.iloc[:i]查看当前'DONE'事件之前该id的动作历史。然后我们将该列表缩小到属于['CREATED', 'UPDATED'] 的操作,然后查看该列表中的最后一个操作,根据该操作我们将值分配给'check' 列。

【讨论】:

    【解决方案3】:

    我不知道这是否是最佳答案,但我尝试创建自己的逻辑来解决这个问题。

    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) 操作创建或更新的所有行的 groupby 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。 现在你必须检查之前的行是否有任何创建/更新,记住它们应该有相同的 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    
    

    【讨论】:

      猜你喜欢
      • 2013-02-07
      • 1970-01-01
      • 2019-09-28
      • 2023-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-18
      • 2022-12-13
      相关资源
      最近更新 更多