按每组平均值填充缺失值
- 2024-12-04 08:55:00
- admin 原创
- 174
问题描述:
这应该很简单,但我发现最接近的是这篇文章:
pandas:填充组内缺失的值,但我仍然无法解决我的问题......
假设我有以下数据框
df = pd.DataFrame({'value': [1, np.nan, np.nan, 2, 3, 1, 3, np.nan, 3], 'name': ['A','A', 'B','B','B','B', 'C','C','C']})
name value
0 A 1
1 A NaN
2 B NaN
3 B 2
4 B 3
5 B 1
6 C 3
7 C NaN
8 C 3
我想在每个“名称”组中用平均值填充“NaN”,即
name value
0 A 1
1 A 1
2 B 2
3 B 2
4 B 3
5 B 1
6 C 3
7 C 3
8 C 3
我不知道接下来该去哪里:
grouped = df.groupby('name').mean()
解决方案 1:
一种方法是使用transform
:
>>> df
name value
0 A 1
1 A NaN
2 B NaN
3 B 2
4 B 3
5 B 1
6 C 3
7 C NaN
8 C 3
>>> df["value"] = df.groupby("name").transform(lambda x: x.fillna(x.mean()))
>>> df
name value
0 A 1
1 A 1
2 B 2
3 B 2
4 B 3
5 B 1
6 C 3
7 C 3
8 C 3
解决方案 2:
fillna
+ groupby
+ transform
+mean
这看起来很直观:
df['value'] = df['value'].fillna(df.groupby('name')['value'].transform('mean'))
groupby
+语法transform
将分组平均值映射到原始数据框的索引。这大致相当于@DSM的解决方案,但避免了定义匿名函数的需要lambda
。
解决方案 3:
@DSM 在我看来是正确答案,但我想分享我对这个问题的概括和优化:多列分组并具有多个值列:
df = pd.DataFrame(
{
'category': ['X', 'X', 'X', 'X', 'X', 'X', 'Y', 'Y', 'Y'],
'name': ['A','A', 'B','B','B','B', 'C','C','C'],
'other_value': [10, np.nan, np.nan, 20, 30, 10, 30, np.nan, 30],
'value': [1, np.nan, np.nan, 2, 3, 1, 3, np.nan, 3],
}
)
... 给出 ...
category name other_value value
0 X A 10.0 1.0
1 X A NaN NaN
2 X B NaN NaN
3 X B 20.0 2.0
4 X B 30.0 3.0
5 X B 10.0 1.0
6 Y C 30.0 3.0
7 Y C NaN NaN
8 Y C 30.0 3.0
在这种普遍情况下,我们希望按category
和进行分组name
,并仅对进行归纳value
。
可以通过如下方法解决:
df['value'] = df.groupby(['category', 'name'])['value']\n .transform(lambda x: x.fillna(x.mean()))
请注意 group-by 子句中的列列表,我们value
在 group-by 之后立即选择该列。这使得转换仅在该特定列上运行。您可以将其添加到末尾,但随后您将只对所有列运行它,以便在末尾丢弃除一个度量列之外的所有列。标准 SQL 查询规划器可能已经能够优化这一点,但 pandas (0.19.2) 似乎没有这样做。
通过增加数据集进行性能测试......
big_df = None
for _ in range(10000):
if big_df is None:
big_df = df.copy()
else:
big_df = pd.concat([big_df, df])
df = big_df
...确认这会提高速度,并且速度与您不必插补的列数成比例:
import pandas as pd
from datetime import datetime
def generate_data():
...
t = datetime.now()
df = generate_data()
df['value'] = df.groupby(['category', 'name'])['value']\n .transform(lambda x: x.fillna(x.mean()))
print(datetime.now()-t)
# 0:00:00.016012
t = datetime.now()
df = generate_data()
df["value"] = df.groupby(['category', 'name'])\n .transform(lambda x: x.fillna(x.mean()))['value']
print(datetime.now()-t)
# 0:00:00.030022
最后要注意的是,如果您想要估算多个列(但不是全部),您可以进一步概括:
df[['value', 'other_value']] = df.groupby(['category', 'name'])['value', 'other_value']\n .transform(lambda x: x.fillna(x.mean()))
解决方案 4:
捷径:
Groupby + 应用 + Lambda + Fillna + Mean
>>> df['value1']=df.groupby('name')['value'].apply(lambda x:x.fillna(x.mean()))
>>> df.isnull().sum().sum()
0
如果您想按多列分组来替换缺失值,此解决方案仍然有效。
>>> df = pd.DataFrame({'value': [1, np.nan, np.nan, 2, 3, np.nan,np.nan, 4, 3],
'name': ['A','A', 'B','B','B','B', 'C','C','C'],'class':list('ppqqrrsss')})
>>> df['value']=df.groupby(['name','class'])['value'].apply(lambda x:x.fillna(x.mean()))
>>> df
value name class
0 1.0 A p
1 1.0 A p
2 2.0 B q
3 2.0 B q
4 3.0 B r
5 3.0 B r
6 3.5 C s
7 4.0 C s
8 3.0 C s
解决方案 5:
我会这样做
df.loc[df.value.isnull(), 'value'] = df.groupby('group').value.transform('mean')
解决方案 6:
精选的高排名答案仅适用于只有两列的 pandas Dataframe。如果您有更多列的情况,请改用:
df['Crude_Birth_rate'] = df.groupby("continent").Crude_Birth_rate.transform(
lambda x: x.fillna(x.mean()))
解决方案 7:
总结以上关于可能解决方案的效率,我有一个包含 97 906 行和 48 列的数据集。我想用每组的中位数填充 4 列。我想要分组的列有 26 200 个组。
第一个解决方案
start = time.time()
x = df_merged[continuous_variables].fillna(df_merged.groupby('domain_userid')[continuous_variables].transform('median'))
print(time.time() - start)
0.10429811477661133 seconds
第二种解决方案
start = time.time()
for col in continuous_variables:
df_merged.loc[df_merged[col].isnull(), col] = df_merged.groupby('domain_userid')[col].transform('median')
print(time.time() - start)
0.5098445415496826 seconds
由于运行时间太长,我只对下一个解决方案的子集执行了该解决方案。
start = time.time()
for col in continuous_variables:
x = df_merged.head(10000).groupby('domain_userid')[col].transform(lambda x: x.fillna(x.median()))
print(time.time() - start)
11.685635566711426 seconds
以下解决方案遵循与上述相同的逻辑。
start = time.time()
x = df_merged.head(10000).groupby('domain_userid')[continuous_variables].transform(lambda x: x.fillna(x.median()))
print(time.time() - start)
42.630549907684326 seconds
因此,选择正确的方法非常重要。请记住,我注意到,一旦列不是数字,时间就会呈指数增长(这很有意义,因为我正在计算中位数)。
解决方案 8:
apply
我知道这是一个老问题。但我对这里答案的一致性感到非常惊讶lambda
。
一般来说,从时间角度来看,这是继迭代行之后第二糟糕的事情。
我在这里要做的是
df.loc[df['value'].isna(), 'value'] = df.groupby('name')['value'].transform('mean')
或者使用 fillna
df['value'] = df['value'].fillna(df.groupby('name')['value'].transform('mean'))
我已经用 timeit 进行了检查(因为,再次,基于 apply/lambda 的解决方案的一致意见让我怀疑我的直觉)。这确实比获得最多支持的方案快 2.5 倍。
解决方案 9:
def groupMeanValue(group):
group['value'] = group['value'].fillna(group['value'].mean())
return group
dft = df.groupby("name").transform(groupMeanValue)
解决方案 10:
用按“名称”分组的平均值填充所有数字空值
num_cols = df.select_dtypes(exclude='object').columns
df[num_cols] = df.groupby("name").transform(lambda x: x.fillna(x.mean()))
解决方案 11:
这是一种容易理解的方法。
使用groupby
++set_index
fillna
import pandas as pd
import numpy as np
df = pd.DataFrame({'value': [1, np.nan, np.nan, 2, 3, 1, 3, np.nan, 3], 'name': ['A','A', 'B','B','B','B', 'C','C','C']})
print(df)
name_mean = df.groupby('name').mean()
df.set_index('name', inplace=True)
df = df.fillna(name_mean)
df.reset_index(inplace=True)
print(df)
结果
value name
0 1.0 A
1 NaN A
2 NaN B
3 2.0 B
4 3.0 B
5 1.0 B
6 3.0 C
7 NaN C
8 3.0 C
name value
0 A 1.0
1 A 1.0
2 B 2.0
3 B 2.0
4 B 3.0
5 B 1.0
6 C 3.0
7 C 3.0
8 C 3.0
解决方案 12:
df.fillna(df.groupby(['name'], as_index=False).mean(), inplace=True)
解决方案 13:
您也可以使用"dataframe or table_name".apply(lambda x: x.fillna(x.mean()))
。