Pandas:在每组中用平均值填充缺失值比变换更快
Pandas: Fill missing values by mean in each group faster than transform
我需要用每个组中的平均值填充 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)。
那么如何才能更快地填充缺失值?
你做错了。它很慢,因为您使用的是 lambda
df[['value']].fillna(df.groupby('group').transform('mean'))
使用排序索引 + fillna()
你是对的 - 你的代码需要 3.18 秒才能 运行。 @piRSquared 提供的代码需要 2.78s 到 运行.
示例代码:
%%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
列设置为dataframe的索引,并对其进行排序。
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_index
、sort_index
和 np.unique
操作是一次性成本。平心而论,在我的机器上总时间(包括这些操作)是2.26s,但是插补部分只用了231毫秒。
这是一个使用 np.bincount
的 NumPy 方法,对于这种基于 bin 的 summing/averaging 操作非常有效 -
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 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)。
那么如何才能更快地填充缺失值?
你做错了。它很慢,因为您使用的是 lambda
df[['value']].fillna(df.groupby('group').transform('mean'))
使用排序索引 + fillna()
你是对的 - 你的代码需要 3.18 秒才能 运行。 @piRSquared 提供的代码需要 2.78s 到 运行.
示例代码:
%%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
列设置为dataframe的索引,并对其进行排序。
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_index
、sort_index
和 np.unique
操作是一次性成本。平心而论,在我的机器上总时间(包括这些操作)是2.26s,但是插补部分只用了231毫秒。
这是一个使用 np.bincount
的 NumPy 方法,对于这种基于 bin 的 summing/averaging 操作非常有效 -
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+
那里加速了!