【问题标题】:How to make a slice of DataFrame and "fillna" in specific slice using Python Pandas?如何使用 Python Pandas 在特定切片中制作一个 DataFrame 和“fillna”?
【发布时间】:2018-05-07 13:27:06
【问题描述】:

问题:让我们从 Kaggle 获取 Titanic 数据集。 我有包含“Pclass”、“Sex”和“Age”列的数据框。 我需要在“年龄”列中用特定组的中位数填充 NaN。 如果是一等女性,我想用一等女性的中位数填充她的年龄,而不是整个年龄列的中位数。

问题是如何在某个切片中做出这种改变?

我试过了:

data['Age'][(data['Sex'] == 'female')&(data['Pclass'] == 1)&(data['Age'].isnull())].fillna(median)

“中位数”是我的值,但没有任何变化“inplace=True”没有帮助。

非常感谢!

【问题讨论】:

  • 我添加了按每组中位数填充NaNs 的解决方案,仅需要groupby。检查我的答案中的编辑。

标签: python pandas dataframe data-analysis


【解决方案1】:

我相信您需要按掩码过滤并分配回:

data = pd.DataFrame({'a':list('aaaddd'),
                     'Sex':['female','female','male','female','female','male'],
                     'Pclass':[1,2,1,2,1,1],
                     'Age':[40,20,30,20,np.nan,np.nan]})

print (data)
    Age  Pclass     Sex  a
0  40.0       1  female  a
1  20.0       2  female  a
2  30.0       1    male  a
3  20.0       2  female  d
4   NaN       1  female  d
5   NaN       1    male  d

#boolean mask
mask1 = (data['Sex'] == 'female')&(data['Pclass'] == 1)

#get median by mask without NaNs
med = data.loc[mask1, 'Age'].median()
print (med)
40.0

#repalce NaNs
data.loc[mask1, 'Age'] = data.loc[mask1, 'Age'].fillna(med)
print (data)
    Age  Pclass     Sex  a
0  40.0       1  female  a
1  20.0       2  female  a
2  30.0       1    male  a
3  20.0       2  female  d
4  40.0       1  female  d
5   NaN       1    male  d

什么是相同的:

mask2 = mask1 &(data['Age'].isnull())

data.loc[mask2, 'Age'] = med
print (data)
    Age  Pclass     Sex  a
0  40.0       1  female  a
1  20.0       2  female  a
2  30.0       1    male  a
3  20.0       2  female  d
4  40.0       1  female  d
5   NaN       1    male  d

编辑:

如果需要用中位数替换所有组NaNs:

data['Age'] = data.groupby(["Sex","Pclass"])["Age"].apply(lambda x: x.fillna(x.median()))
print (data)

    Age  Pclass     Sex  a
0  40.0       1  female  a
1  20.0       2  female  a
2  30.0       1    male  a
3  20.0       2  female  d
4  40.0       1  female  d
5  30.0       1    male  d

【讨论】:

  • 是的,完全正确。因为 eklse 得到 NaN。
  • 不客气!也许对未来的小建议 - how to provide a great pandas example ;)
  • 我在评论中看到您的问题并尝试回答。 for 循环在 pandas 中是可能的,但速度很慢。 Jeff,目前 pandas 的主要开发者之一,发布了非常好的答案 - 检查 here
【解决方案2】:

如果你想对每个组都做同样的事情,你可以使用这个技巧

data = pd.DataFrame({'a':list('aaaddd'),
                    'Sex':['female','female','male','female','female','male'],
                    'Pclass':[1,2,1,2,1,1],
                    'Age':[40,20,30,20, np.nan, np.nan]})
df = data.groupby(["Sex","Pclass"])["Age"].median().to_frame().reset_index()
df.rename(columns={"Age":"Med"}, inplace=True)
data = pd.merge(left=data,right=df, how='left', on=["Sex", "Pclass"])
data["Age"] = np.where(data["Age"].isnull(), data["Med"], data["Age"])

更新:

# dummy dataframe
n = int(1e7)
data = pd.DataFrame({"Age":np.random.choice([10,20,20,30,30,40,np.nan], n),
                     "Pclass":np.random.choice([1,2,3], n),
                     "Sex":np.random.choice(["male","female"], n),
                     "a":np.random.choice(["a","b","c","d"], n)})

在我的机器上运行这个(和以前一样,没有重命名)

df = data.groupby(["Sex","Pclass"])["Age"].agg(['median']).reset_index()
data = pd.merge(left=data,right=df, how='left', on=["Sex", "Pclass"])
data["Age"] = np.where(data["Age"].isnull(), data["median"], data["Age"])

CPU times: user 1.98 s, sys: 216 ms, total: 2.2 s
Wall time: 2.2 s

虽然面具解决方案采取:

for sex in ["male", "female"]:
    for pclass in range(1,4):
        mask1 =(data['Sex'] == sex)&(data['Pclass'] == pclass)
        med = data.loc[mask1, 'Age'].median()
        data.loc[mask1, 'Age'] = data.loc[mask1, 'Age'].fillna(med)

CPU times: user 5.13 s, sys: 60 ms, total: 5.19 s
Wall time: 5.19 s

@jezrael 解决方案更快

data['Age'] = data.groupby(["Sex","Pclass"])["Age"].apply(lambda x: x.fillna(x.median()))

CPU times: user 1.34 s, sys: 92 ms, total: 1.44 s
Wall time: 1.44 s

【讨论】:

  • 感谢您的回答!我还有一个问题。我使用了另一种使用 for 循环的方式。但我认为你的方法可能会更好,因为它使用 C++ 的 numpy 和 pandas,如果用于大型数据集必须更快,对吗?
  • 我对一个带有1e7 行的虚拟数据库进行了一些测试,结果比@jezrael 的要好(如果您不需要将它应用于所有类别,我发现它很好)。如果你有一个庞大的数据框,你可以考虑dask.pydata.org/en/latest BTW,如果你喜欢这个答案,你可以投票。同样,特定问题的公认答案应该是@jezrael 的答案
【解决方案3】:

我想在这里添加一个更有效的答案,因为它涉及的代码更少。本质上,如果您使用布尔条件对数据框进行切片并在这些特定条件下使用 .fillna,只需使用赋值:

我将使用另一个 Kaggle 比赛的示例:

# Use a mask as suggested by jesrael. It's just neater:
mask1 = (test_df.Neighborhood == 'IDOTRR') & (test_df.MSZoning.isna())
mask2 = (test_df.Neighborhood == 'Mitchel') & (test_df.MSZoning.isna())

# Use the mask and assign the desired value 
test_df.loc[mask1, 'MSZoning'] = 'RM'
test_df.loc[mask2, 'MSZoning'] = 'RL'

这与 jesrael 的回答不同,因为他/她使用 .fillna() 分配回屏蔽数据帧。如果您要使用掩码,并且考虑到特定值,则无需使用 '.fillna()'

【讨论】:

    猜你喜欢
    • 2019-09-03
    • 2017-12-15
    • 2021-06-28
    • 1970-01-01
    • 2020-12-08
    • 2021-09-03
    • 1970-01-01
    • 2013-09-01
    • 2017-03-28
    相关资源
    最近更新 更多