Pandas Dataframe:用行平均值替换 NaN
Pandas Dataframe: Replacing NaN with row average
我正在努力学习pandas,但我一直对以下内容感到困惑。我想用行平均值替换 DataFrame 中的 NaN。因此 df.fillna(df.mean(axis=1))
之类的东西应该可以工作,但由于某种原因它对我来说失败了。我错过了什么,我在做什么有问题吗?是因为没有实施吗?参见
import pandas as pd
import numpy as np
pd.__version__
Out[44]:
'0.15.2'
In [45]:
df = pd.DataFrame()
df['c1'] = [1, 2, 3]
df['c2'] = [4, 5, 6]
df['c3'] = [7, np.nan, 9]
df
Out[45]:
c1 c2 c3
0 1 4 7
1 2 5 NaN
2 3 6 9
In [46]:
df.fillna(df.mean(axis=1))
Out[46]:
c1 c2 c3
0 1 4 7
1 2 5 NaN
2 3 6 9
不过这样的东西看起来工作正常
df.fillna(df.mean(axis=0))
Out[47]:
c1 c2 c3
0 1 4 7
1 2 5 8
2 3 6 9
如评论所述,fillna 的轴参数是 NotImplemented。
df.fillna(df.mean(axis=1), axis=1)
注意:这在这里很重要,因为您不想用第 n 行平均值填充第 n 列。
现在您需要迭代:
m = df.mean(axis=1)
for i, col in enumerate(df):
# using i allows for duplicate columns
# inplace *may* not always work here, so IMO the next line is preferred
# df.iloc[:, i].fillna(m, inplace=True)
df.iloc[:, i] = df.iloc[:, i].fillna(m)
print(df)
c1 c2 c3
0 1 4 7.0
1 2 5 3.5
2 3 6 9.0
另一种方法是填充转置然后转置,这可能更有效...
df.T.fillna(df.mean(axis=1)).T
作为替代方案,您还可以使用带有 lambda
表达式的 apply
,如下所示:
df.apply(lambda row: row.fillna(row.mean()), axis=1)
也屈服
c1 c2 c3
0 1.0 4.0 7.0
1 2.0 5.0 3.5
2 3.0 6.0 9.0
我将提出一个涉及转换为 numpy 数组的替代方案。在性能方面,我认为这比目前提出的其他解决方案更有效,并且可能扩展性更好。
想法是使用指示矩阵(df.isna().values
如果元素为 N/A,则为 1,否则为 0)并将其广播乘以行平均值。
因此,我们最终得到一个矩阵(与原始 df 的形状完全相同),如果原始元素为 N/A,则该矩阵包含行平均值,否则为 0。
我们将此矩阵添加到原始 df,确保用 0 填充 na,这样实际上,我们用相应的行平均值填充了 N/A。
# setup code
df = pd.DataFrame()
df['c1'] = [1, 2, 3]
df['c2'] = [4, 5, 6]
df['c3'] = [7, np.nan, 9]
# fillna row-wise
row_avgs = df.mean(axis=1).values.reshape(-1,1)
df = df.fillna(0) + df.isna().values * row_avgs
df
给予
c1 c2 c3
0 1.0 4.0 7.0
1 2.0 5.0 3.5
2 3.0 6.0 9.0
刚遇到同样的问题。我发现此解决方法有效:
df.transpose().fillna(df.mean(axis=1)).transpose()
虽然我不确定这个解决方案的效率。
您可以将平均值广播到与原始索引具有相同索引的 DataFrame,然后使用 update
和 overwrite=False
来获得 .fillna
的行为。与 .fillna
不同,update
允许在索引具有重复标签时进行填充。对于小于 50,000 行左右的情况,应该比循环 .fillna 更快。
fill = pd.DataFrame(np.broadcast_to(df.mean(1).to_numpy()[:, None], df.shape),
columns=df.columns,
index=df.index)
df.update(fill, overwrite=False)
print(df)
1 1 1
0 1.0 4.0 7.0
0 2.0 5.0 3.5
0 3.0 6.0 9.0
要获得有效的解决方案,请使用 DataFrame.where
:
我们可以在 axis=0
上使用 where
:
df.where(df.notna(), df.mean(axis=1), axis=0)
或 mask
在 axis=0
:
df.mask(df.isna(), df.mean(axis=1), axis=0)
通过使用axis=0
,我们可以用行平均值填充每列中的缺失值。
这些方法的执行非常相似(where
在大型 DataFrames (300_000, 20) 上表现稍好)并且比此处发布的 numpy 方法快 35-50%,快 110 倍比双转置方法。
一些基准:
df = creator()
>>> %timeit df.where(df.notna(), df.mean(axis=1), axis=0)
542 ms ± 3.36 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit df.mask(df.isna(), df.mean(axis=1), axis=0)
555 ms ± 21.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit df.fillna(0) + df.isna().values * df.mean(axis=1).values.reshape(-1,1)
751 ms ± 22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit fill = pd.DataFrame(np.broadcast_to(df.mean(1).to_numpy()[:, None], df.shape), columns=df.columns, index=df.index); df.update(fill, overwrite=False)
848 ms ± 22.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit df.apply(lambda row: row.fillna(row.mean()), axis=1)
1min 4s ± 5.32 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit df.T.fillna(df.mean(axis=1)).T
1min 5s ± 2.4 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
def creator():
A = np.random.rand(300_000, 20)
A.ravel()[np.random.choice(A.size, 300_000, replace=False)] = np.nan
return pd.DataFrame(A)
我正在努力学习pandas,但我一直对以下内容感到困惑。我想用行平均值替换 DataFrame 中的 NaN。因此 df.fillna(df.mean(axis=1))
之类的东西应该可以工作,但由于某种原因它对我来说失败了。我错过了什么,我在做什么有问题吗?是因为没有实施吗?参见
import pandas as pd
import numpy as np
pd.__version__
Out[44]:
'0.15.2'
In [45]:
df = pd.DataFrame()
df['c1'] = [1, 2, 3]
df['c2'] = [4, 5, 6]
df['c3'] = [7, np.nan, 9]
df
Out[45]:
c1 c2 c3
0 1 4 7
1 2 5 NaN
2 3 6 9
In [46]:
df.fillna(df.mean(axis=1))
Out[46]:
c1 c2 c3
0 1 4 7
1 2 5 NaN
2 3 6 9
不过这样的东西看起来工作正常
df.fillna(df.mean(axis=0))
Out[47]:
c1 c2 c3
0 1 4 7
1 2 5 8
2 3 6 9
如评论所述,fillna 的轴参数是 NotImplemented。
df.fillna(df.mean(axis=1), axis=1)
注意:这在这里很重要,因为您不想用第 n 行平均值填充第 n 列。
现在您需要迭代:
m = df.mean(axis=1)
for i, col in enumerate(df):
# using i allows for duplicate columns
# inplace *may* not always work here, so IMO the next line is preferred
# df.iloc[:, i].fillna(m, inplace=True)
df.iloc[:, i] = df.iloc[:, i].fillna(m)
print(df)
c1 c2 c3
0 1 4 7.0
1 2 5 3.5
2 3 6 9.0
另一种方法是填充转置然后转置,这可能更有效...
df.T.fillna(df.mean(axis=1)).T
作为替代方案,您还可以使用带有 lambda
表达式的 apply
,如下所示:
df.apply(lambda row: row.fillna(row.mean()), axis=1)
也屈服
c1 c2 c3
0 1.0 4.0 7.0
1 2.0 5.0 3.5
2 3.0 6.0 9.0
我将提出一个涉及转换为 numpy 数组的替代方案。在性能方面,我认为这比目前提出的其他解决方案更有效,并且可能扩展性更好。
想法是使用指示矩阵(df.isna().values
如果元素为 N/A,则为 1,否则为 0)并将其广播乘以行平均值。
因此,我们最终得到一个矩阵(与原始 df 的形状完全相同),如果原始元素为 N/A,则该矩阵包含行平均值,否则为 0。
我们将此矩阵添加到原始 df,确保用 0 填充 na,这样实际上,我们用相应的行平均值填充了 N/A。
# setup code
df = pd.DataFrame()
df['c1'] = [1, 2, 3]
df['c2'] = [4, 5, 6]
df['c3'] = [7, np.nan, 9]
# fillna row-wise
row_avgs = df.mean(axis=1).values.reshape(-1,1)
df = df.fillna(0) + df.isna().values * row_avgs
df
给予
c1 c2 c3
0 1.0 4.0 7.0
1 2.0 5.0 3.5
2 3.0 6.0 9.0
刚遇到同样的问题。我发现此解决方法有效:
df.transpose().fillna(df.mean(axis=1)).transpose()
虽然我不确定这个解决方案的效率。
您可以将平均值广播到与原始索引具有相同索引的 DataFrame,然后使用 update
和 overwrite=False
来获得 .fillna
的行为。与 .fillna
不同,update
允许在索引具有重复标签时进行填充。对于小于 50,000 行左右的情况,应该比循环 .fillna 更快。
fill = pd.DataFrame(np.broadcast_to(df.mean(1).to_numpy()[:, None], df.shape),
columns=df.columns,
index=df.index)
df.update(fill, overwrite=False)
print(df)
1 1 1
0 1.0 4.0 7.0
0 2.0 5.0 3.5
0 3.0 6.0 9.0
要获得有效的解决方案,请使用 DataFrame.where
:
我们可以在 axis=0
上使用 where
:
df.where(df.notna(), df.mean(axis=1), axis=0)
或 mask
在 axis=0
:
df.mask(df.isna(), df.mean(axis=1), axis=0)
通过使用axis=0
,我们可以用行平均值填充每列中的缺失值。
这些方法的执行非常相似(where
在大型 DataFrames (300_000, 20) 上表现稍好)并且比此处发布的 numpy 方法快 35-50%,快 110 倍比双转置方法。
一些基准:
df = creator()
>>> %timeit df.where(df.notna(), df.mean(axis=1), axis=0)
542 ms ± 3.36 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit df.mask(df.isna(), df.mean(axis=1), axis=0)
555 ms ± 21.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit df.fillna(0) + df.isna().values * df.mean(axis=1).values.reshape(-1,1)
751 ms ± 22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit fill = pd.DataFrame(np.broadcast_to(df.mean(1).to_numpy()[:, None], df.shape), columns=df.columns, index=df.index); df.update(fill, overwrite=False)
848 ms ± 22.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
>>> %timeit df.apply(lambda row: row.fillna(row.mean()), axis=1)
1min 4s ± 5.32 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit df.T.fillna(df.mean(axis=1)).T
1min 5s ± 2.4 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
def creator():
A = np.random.rand(300_000, 20)
A.ravel()[np.random.choice(A.size, 300_000, replace=False)] = np.nan
return pd.DataFrame(A)