如何在 Pandas 中加快每个 groupby 分组的缺失值替换?

3
我有一个非常大的熊猫数据集,其数据长这样:
df = pd.DataFrame({'group1' : ['A', 'A', 'A', 'A',
                         'B', 'B', 'B', 'B'],
                   'group2' : ['C', 'C', 'C', 'D',
                         'E', 'E', 'F', 'F'],
                   'B' : ['one', np.NaN, np.NaN, np.NaN,
                        np.NaN, 'two', np.NaN, np.NaN],
                   'C' : [np.NaN, 1, np.NaN, np.NaN,
                        np.NaN, np.NaN, np.NaN, 4]})     




df
Out[64]: 
     B   C group1 group2
0  one NaN      A      C
1  NaN   1      A      C
2  NaN NaN      A      C
3  NaN NaN      A      D
4  NaN NaN      B      E
5  two NaN      B      E
6  NaN NaN      B      F
7  NaN   4      B      F

在这里,您可以看到对于每个唯一的 group1group2 的组合,列 BC 最多包含一个非缺失变量。

在每个 groupby(['group1','group2']) 组中,如果该值存在,则使用该组中唯一的非缺失值替换所有缺失值。

为此,我使用了 groupby 后可用的 first 函数,该函数将B或C中的第一个非缺失值传播到该组中其余缺失值的位置:
df[['B','C']]=df.groupby(['group1','group2']).transform('first')     



df
Out[62]: 
     B   C group1 group2
0  one   1      A      C
1  one   1      A      C
2  one   1      A      C
3  NaN NaN      A      D
4  two NaN      B      E
5  two NaN      B      E
6  NaN   4      B      F
7  NaN   4      B      F

很不幸,这对于我非常大的数据集来说速度非常慢。你有什么方法可以提高这里的速度吗?我在考虑使用 `fillna`,但似乎需要应用两次 (`ffill` 和 `bfill`) ... 有什么想法吗?
更新:下面由 `ajcr` 提出的非常有效的解决方案是否适用于由几列定义的组?在这种情况下,`map` 不起作用。也许是 `merge`?
3个回答

3
在我的计算机上,使用groupby然后再使用map可以使速度快近100倍:
g = df.groupby('group', sort=False).first()

df['B'] = df['group'].map(g['B'])
df['C'] = df['group'].map(g['C'])

这是一个包含1000个组和10000行的测试DataFrame:

df = pd.DataFrame({'group': np.repeat(np.arange(1000), 10),
                    'B': np.nan,
                    'C': np.nan})

df.ix[4::10, 'B':'C'] = 5 # every 4th row of a group is non-null

同时也需要考虑时间因素:

%%timeit

df2 = df.copy()

g = df2.groupby('group', sort=False).first()

df2['B'] = df2['group'].map(g['B'])
df2['C'] = df2['group'].map(g['C'])

这返回100次循环,3次中最佳:每个循环2.29毫秒transform方法几乎慢了100倍:
%%timeit

df3 = df.copy()

df3[['B','C']] = df3.groupby('group').transform('first')

这将返回1个循环,3个中的最佳结果:每个循环205毫秒


关于您更新的有关使用多个组的问题,在下面的评论中@jeff建议使用:

df['B'] = df.groupby(['group1','group2']).B.transform('first')
df['C'] = df.groupby(['group1','group2']).C.transform('first')

将两列分别转换比同时转换快大约50倍。这是因为当前 transform 对于 Series 的性能更高,尽管存在一个新创建的问题来提高在 DataFrames 上的操作速度。


1
我曾经尝试使用类似于a[['B','C']] = a.apply(lambda x: g.loc[x.group, ['B','C']], axis=1)的方法,但速度要慢得多。因此,我赞同您的解决方案。 +1票 - MaxU - stand with Ukraine
1
@Noobie:map 函数沿着列中的值进行查找,并在 Series 或字典中查找它们(或应用函数),并将该值替换为返回的值。在这种情况下,我们使用 df['group'].map(g['B']) 来创建一个新列,通过在 groupby DataFrame g 中替换组值为相应的值。 - Alex Riley
1
感谢@Jeff - 我很惊讶transform在这里对DataFrame的速度明显较慢。@Noobie:我认为Jeff建议的解决方案最好地回答了您更新的问题。 - Alex Riley
1
@ajcr 是的,快速转换仅针对Series groupby实现,DataFrame groupby可以用类似的方式完成。 - Jeff
1
@Noobie:将Jeff的建议添加到答案中。对于您的小示例DataFrame,我发现这样做速度快了两倍。 - Alex Riley
显示剩余9条评论

1
让我们加快一点速度:
In [130]: a = df.copy()

In [131]: %timeit a['B'],a['C'] = a.B.fillna(method='ffill'), a.C.fillna(method='bfill')
1000 loops, best of 3: 538 µs per loop

In [132]: a = df.copy()

In [133]: %timeit a[['B','C']]=a.groupby('A').transform('first')
100 loops, best of 3: 3 ms per loop

谢谢你的建议,maxU。但我们不确定每个组中缺失值的确切位置。换句话说,在B上只进行“ffill”可能行不通。这就是为什么我提到在每列上进行双重“ffill”和“bfill”的原因... - ℕʘʘḆḽḘ
1
@Noobie,请您相应地更新您的示例DF,好吗? - MaxU - stand with Ukraine
实际上你的代码在这个例子中是有效的,但我不明白为什么它有效。例如,如果你对B列进行了向前填充,难道我们不应该有一个残留的缺失值吗?事实上,在B组的groupby中,B列的第一个非缺失值在第二行(df中的第5行)... - ℕʘʘḆḽḘ
实际上,我现在注意到你的代码没有考虑到分组级别的聚合。缺失值必须在每个组内进行替换。你有其他解决方案吗?谢谢帮忙! - ℕʘʘḆḽḘ
1
确实,你的代码不起作用。我正在查看我的输出。你自己试试吧。再次感谢! - ℕʘʘḆḽḘ
1
@Noobie,我找不到比ajcr的解决方案更快的东西了。 - MaxU - stand with Ukraine

1
如果您采用不同的方法,不再尝试填充,而是重新构建,会怎样呢?这样更容易理解。请保留HTML标签。
unique_df = df.drop_duplicates()

bVal = unique_df.drop(['B'],axis = 1).dropna().set_index(['A'])
cVal = unique_df.drop(['C'],axis = 1).dropna().set_index(['A'])

colVals = pd.merge(bVal,cVal, how = 'outer',left_index = True, right_index = True)
output = pd.merge(df[['A']],colVals, how = 'left',left_on = 'A',right_index = True) 

将df缩小到唯一部分,找到较小数据框中的唯一行,然后合并回来重新构建较大的数据框 - 更快?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接