【问题标题】:Update column values in a group based on one row in that group根据组中的一行更新组中的列值
【发布时间】:2021-11-05 11:04:41
【问题描述】:

我有一个来自源数据的数据框,类似于以下内容:

In[1]: df = pd.DataFrame({'test_group': [1, 1, 1, 2, 2, 2, 3, 3, 3],
         'test_type': [np.nan,'memory', np.nan, np.nan, 'visual', np.nan, np.nan,
         'auditory', np.nan]}
Out[1]:
   test_group test_type
0           1       NaN
1           1    memory
2           1       NaN
3           2       NaN
4           2    visual
5           2       NaN
6           3       NaN
7           3  auditory
8           3       NaN

test_group 代表行的分组,代表一个测试。我需要将每个test_grouptest_type 列中的NaN 替换为不是NaN 的行的值,例如记忆力、视觉力等。

我尝试了多种方法,包括隔离test_type 中的“真实”值,例如

In [4]: df.groupby('test_group')['test_type'].unique()
Out[4]:
test_group
1      [nan, memory]
2      [nan, visual]
3    [nan, auditory]

很简单,我可以索引每一行并提取我想要的值。这似乎朝着正确的方向发展:

In [6]: df.groupby('test_group')['test_type'].unique().apply(lambda x: x[1])
Out[6]:
test_group
1      memory
2      visual
3    auditory

我尝试了很多其他方法,但效果不佳(注意:apply 和 transform 给出相同的结果):

In [15]: grp = df.groupby('test_group')
In [16]: df['test_type'] = grp['test_type'].unique().transform(lambda x: x[1])

In [17]: df
Out[17]:
   test_group test_type
0           1       NaN
1           1    memory
2           1    visual
3           2  auditory
4           2       NaN
5           2       NaN
6           3       NaN
7           3       NaN
8           3       NaN

我敢肯定,如果我循环它,我会做的事情,但循环太慢,因为数据集是每个文件数百万条记录。

【问题讨论】:

  • df.groupby('test_group')['test_type'].bfill().ffill()?您的预期输出是什么?
  • 是否总是一开始,每个组只有一个有效值,而每个组中的其余值都是 NaN?
  • @It_is_Chris - 是的,这就完成了。谢谢!
  • @Ch3steR - 正确。
  • @schwim 感谢您的澄清。该不变量帮助我找到了比双填充更快的解决方案。

标签: python pandas dataframe group-by pandas-groupby


【解决方案1】:

您可以使用GroupBy.size 获取每个组的大小。然后boolean index 使用Series.isna。现在,将Index.repeatdf.reindex 一起使用

repeats = df.groupby('test_group').size()
out = df[~df['test_type'].isna()]
out.reindex(out.index.repeat(repeats)).reset_index(drop=True)

   test_group test_type
0           1    memory
1           1    memory
2           1    memory
3           2    visual
4           2    visual
5           2    visual
6           3  auditory
7           3  auditory
8           3  auditory

timeit 分析:

基准数据框

df = pd.DataFrame({'test_group': [1]*10_001 + [2]*10_001 + [3]*10_001, 
                            'test_type' : [np.nan]*10_000 + ['memory'] +
                                          [np.nan]*10_000 + ['visual'] +
                                          [np.nan]*10_000 + ['auditory']})   
df.shape
# (30003, 2) 

结果:

# Ch3steR's answer
In [54]: %%timeit 
    ...: repeats = df.groupby('test_group').size() 
    ...: out = df[~df['test_type'].isna()] 
    ...: out.reindex(out.index.repeat(repeats)).reset_index(drop=True) 
    ...:  
    ...:                                                                        
2.56 ms ± 73.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# timgeb's answer
In [55]: %%timeit 
    ...: df['test_type'] = df.groupby('test_group')['test_type'].fillna(method='ffill').fillna(method='bfill') 
    ...:  
    ...:                                                                                                                 
10.1 ms ± 724 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

几乎快 4 倍。我相信这是因为布尔索引非常快。与双填充相比,reindex + repeat 更轻。

【讨论】:

  • 注意:如果您正在运行 timeit 测试,请先运行我的答案,因为它不会改变原始数据帧,而 Tim 的 soln 会改变原始数据帧。 Tim 的非变异版本将使用 df.assign df.assign(test_type = df.groupby('test_group')['test_type'].fillna(method='ffill').fillna(method='bfill'))
  • 我按照他们的节奏运行了这两种方法。我注意到您的 df 创建与我在这里的模式不太匹配。无论如何,这两种方法似乎都可以正常工作。我将 df 修改为与我的数据匹配的大约 300k 行模式,并且确实您的方法要快得多。奇怪的是,在我的实际数据集上它并没有更快。我需要弄清楚为什么 - 感兴趣的模式和列是相同的,我的产品数据只是有更多的列。嗯。
  • 当我实现 tim 的方法时,我最终将其修改为使用 .ffill().bfill() 而不是 .fillna(method=...)。这种方法似乎仍然更快。对于 299997 行:@Ch3steR 方法:每个循环 46.3 ms ± 953 µs ffill().bfill() 方法:每个循环 34.3 ms ± 2.04 ms Tim 方法:每个循环 22.9 s ± 430 ms!我重复了几次以确定。
  • @timgeb 谢谢,没有特别的原因,但我经常使用 reindex 和重复可能只是一种习惯的力量。我已经测量了两者的性能,两者几乎没有区别。但是是的,.loc 打字少。
  • 我做了一个快速比较here 值得注意的是,如果我删除额外的列,@Ch3steR 方法会稍微快一些。
【解决方案2】:

假设每个组都有一个唯一的非 nan 值,以下应该满足您的要求。

>>> df['test_type'] = df.groupby('test_group')['test_type'].ffill().bfill() 
>>> df
   test_group test_type
0           1    memory
1           1    memory
2           1    memory
3           2    visual
4           2    visual
5           2    visual
6           3  auditory
7           3  auditory
8           3  auditory

编辑:

使用的原始答案

df.groupby('test_group')['test_type'].fillna(method='ffill').fillna(method='bfill') 

但根据schwim 的时间安排,ffill/bfill 似乎要快得多(出于某种原因)。

【讨论】:

  • 根据之前的评论this 比较了 3 种方法的性能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多