Pandas:在每组中平均填充缺失值比变换更快

Posted

技术标签:

【中文标题】Pandas:在每组中平均填充缺失值比变换更快【英文标题】:Pandas: Fill missing values by mean in each group faster than transform 【发布时间】:2017-04-02 14:34:34 【问题描述】:

我需要用每组中的平均值填充 pandas DataFrame 中的缺失值。根据this questiontransform可以实现这个。

但是,transform 对我来说太慢了。

例如,对具有 100 个不同组和 70% NaN 值的大型 DataFrame 进行以下设置:

import pandas as pd
import numpy as np

size = 10000000  # DataFrame length
ngroups = 100  # Number of Groups

randgroups = np.random.randint(ngroups, size=size)  # Creation of groups
randvals = np.random.rand(size) * randgroups * 2    # Random values with mean like group number
nan_indices = np.random.permutation(range(size))    # NaN indices
nanfrac = 0.7                                       # Fraction of NaN values
nan_indices = nan_indices[:int(nanfrac*size)]       # Take fraction of NaN indices
randvals[nan_indices] = np.NaN                      # Set NaN values

df = pd.DataFrame('value': randvals, 'group': randgroups)  # Create data frame

通过transform 使用

df.groupby("group").transform(lambda x: x.fillna(x.mean())) # Takes too long

在我的计算机上已经花费了 3 秒以上。我需要快一个数量级的东西(购买更大的机器不是一种选择:-D)。

那么我怎样才能更快地填充缺失值呢?

【问题讨论】:

是否可以在将丢失的数据读入帧之前对其进行处理? 嗯,我不确定。我不希望这样做,因为真正的 DataFrame 来自 SQL 查询(实际上是几 GB 大小)。 我会考虑在那里做。如果 SQL 能够比 Pandas 更快地计算平均值,我不会感到惊讶。 【参考方案1】:

你做错了。它很慢,因为您使用的是lambda

df[['value']].fillna(df.groupby('group').transform('mean'))

【讨论】:

啊,我明白了,也许您也应该将其发布为原始问题的答案,他们建议使用lambda 不过,您的解决方案“仅”快了 20%,但这并没有快一个数量级 :-) @SmCaterpillar 使用 pandas 解决方案,我怀疑您能否在这方面得到显着改进。大部分时间都花在计算平均值上。 df['value'].fillna(df.groupby('group', sort=False)['value'].transform('mean')) 快一点。 在我的机器上,piRSquared 的答案大约快 6 倍。如果您使用像 mean(cythonized)这样的本机函数而不是 lambda(非 cythonized),transform 通常会更快 @SmCaterpillar 确保您使用的是最新版本的熊猫——它可能会影响转换速度。 github.com/pandas-dev/pandas/issues/12737【参考方案2】:

使用排序索引 + fillna()

您是对的 - 您的代码需要 3.18 秒才能运行。 @piRSquared 提供的代码需要 2.78 秒才能运行。

    示例代码 %%timeit df2 = df1.groupby("group").transform(lambda x: x.fillna(x.mean())) Output: 1 loop, best of 3: 3.18 s per loop`

    piRSquared 的改进 %%timeit df[['value']].fillna(df.groupby('group').transform('mean')) Output: 1 loop, best of 3: 2.78 s per loop

    更高效的方式(使用排序索引和fillna

您可以将group列设置为数据框的索引,并对其进行排序。

df = df.set_index('group').sort_index()

现在您有了一个排序索引,使用df.loc[x,:] 按组号访问数据帧的子集非常便宜

由于您需要对每个组进行平均估算,因此您需要所有唯一的组 ID。对于此示例,您可以使用 range(因为组是从 0 到 99),但更一般地 - 您可以使用:

groups = np.unique(set(df.index))

之后,您可以遍历组并使用fillna() 进行插补: %%timeit for x in groups: df.loc[x,'value'] = df.loc[x,'value'].fillna(np.mean(df.loc[x,'value'])) Output: 1 loop, best of 3: 231 ms per loop

注意:set_indexsort_indexnp.unique 操作是一次性成本。公平地说,在我的机器上总时间(包括这些操作)是 2.26 秒,但插补片只用了 231 毫秒。

【讨论】:

【参考方案3】:

这是一种使用 np.bincount 的 NumPy 方法,对于此类基于 bin 的求和/平均操作非常有效 -

ids = df.group.values                    # Extract 2 columns as two arrays
vals = df.value.values

m = np.isnan(vals)                             # Mask of NaNs
grp_sums = np.bincount(ids,np.where(m,0,vals)) # Group sums with NaNs as 0s
avg_vals = grp_sums*(1.0/np.bincount(ids,~m))        # Group averages
vals[m] = avg_vals[ids[m]]              # Set avg values into NaN positions

请注意,这将更新 value 列。

运行时测试

数据大小:

size = 1000000  # DataFrame length
ngroups = 10  # Number of Groups

时间:

In [17]: %timeit df.groupby("group").transform(lambda x: x.fillna(x.mean()))
1 loops, best of 3: 276 ms per loop

In [18]: %timeit bincount_based(df)
100 loops, best of 3: 13.6 ms per loop

In [19]: 276.0/13.6  # Speedup
Out[19]: 20.294117647058822

20x+ 加速!

【讨论】:

以上是关于Pandas:在每组中平均填充缺失值比变换更快的主要内容,如果未能解决你的问题,请参考以下文章

熊猫:在每组中按平均值填充缺失值

R语言-均值填充缺失值

技巧 Pandas 数据填充

pandas 处理缺失值[dropna、drop、fillna]

在pyspark中填充每组的缺失值?

pandas如何实现缺失的行数据按上一行数据进行填充?