【问题标题】:Pandas: filling missing values by mean in each group熊猫:在每组中按平均值填充缺失值
【发布时间】:2021-11-28 15:09:09
【问题描述】:

这应该很简单,但我发现最接近的是这篇文章: pandas: Filling missing values within a group,还是解决不了我的问题....

假设我有以下数据框

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()

非常感谢。

【问题讨论】:

    标签: python pandas pandas-groupby imputation fillna


    【解决方案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
    

    【讨论】:

    • 我发现在开始坐下来阅读文档时很有帮助。这个在groupby 部分中有介绍。要记住的东西太多了,但是您会选择诸如“转换用于您希望像原始帧一样索引的每组操作”之类的规则。
    • 还可以查找 Wes McKinney 的书。我个人认为 groupby 上的文档很糟糕,这本书稍微好一点。
    • 如果你有两个以上的列,请确保指定列名 df["value"] = df.groupby("name").transform(lambda x: x.fillna(x. mean()))['值']
    • @Lauren 好点。我想补充一点,出于性能原因,您可能会考虑将值列规范进一步向左移动到 group-by 子句。这样,lambda 函数只针对该特定列中的值调用,而不是每一列然后选择列。做了一个测试,使用两列时速度是原来的两倍。当然,不需要估算的列越多,性能就会越好:df["value"] = df.groupby("name")["value"].transform(lambda x: x.fillna(x.mean()))
    • 我一直在寻找这个两天..只是一个问题给你。为什么用循环来做这件事太难了?因为在我的情况下,有两个多索引,即StateAge_Group,所以我试图用组平均值填充这些组中的缺失值(从同一年龄组内的相同状态取平均值并填充组中的缺失值)。 .谢谢
    【解决方案2】:

    fillna + groupby + transform + mean

    这看起来很直观:

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

    groupby + transform 语法将分组均值映射到原始数据帧的索引。这大致相当于@DSM's solution,但避免了定义匿名lambda 函数的需要。

    【讨论】:

    • 谢谢!,我发现 lambda 函数有点令人困惑,而你的更容易理解。
    • 不错的解决方案。我的 groupby 返回 73k 组。所以换句话说,它需要找到 73k 组的平均值,以便为每个组填写 NA 值。我主要关心的是时间,因为我想轻松地将其扩展到超过 73k 组。 lambda 解决方案需要 21.39 秒才能完成,而此解决方案需要 0.27 秒。强烈推荐使用这个解决方案!
    • df = df.fillna(df.groupby('name').transform('mean')) 是否对所有列都成功执行此操作?我正在使用它,它看起来不错,但我担心我做错了什么,就像这里的每列都做的那样?
    【解决方案3】:

    @DSM 为 IMO 提供了正确答案,但我想分享我对问题的概括和优化:Multiple columns to group-by and have multiple value columns:

    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
    

    在这种广义情况下,我们希望按categoryname 分组,并仅对value 进行估算。

    可以这样解决:

    df['value'] = df.groupby(['category', 'name'])['value']\
        .transform(lambda x: x.fillna(x.mean()))
    

    注意 group-by 子句中的列列表,我们在 group-by 之后选择了 value 列。这使得转换仅在该特定列上运行。您可以将其添加到末尾,但随后您将对所有列运行它,仅在最后丢弃除一个度量列之外的所有列。标准的 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']\
        .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'])\
        .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']\
        .transform(lambda x: x.fillna(x.mean()))
    

    【讨论】:

    • 感谢您的出色工作。我想知道如何使用for 循环成功完成相同的转换。速度不是我关心的问题,因为我正在尝试寻找手动方法。谢谢@AndréC.Andersen
    【解决方案4】:

    快捷方式:

    Groupby + Apply + 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')
      

      【讨论】:

      • df['value_imputed'] = np.where(df.value.isnull(), df.groupby('group').value.transform('mean'), df.value)略有不同的版本
      【解决方案6】:

      精选的高排名答案仅适用于只有两列的熊猫数据框。如果您有更多列的情况,请改用:

      df['Crude_Birth_rate'] = df.groupby("continent").Crude_Birth_rate.transform(
          lambda x: x.fillna(x.mean()))
      

      【讨论】:

      • 这个答案对我有用,谢谢。同样对于熊猫新手,也可以使用切片符号df.groupby("continent")['Crude_Birth_rate']... 进行索引,我相信这是建议的约定
      【解决方案7】:
      def groupMeanValue(group):
          group['value'] = group['value'].fillna(group['value'].mean())
          return group
      
      dft = df.groupby("name").transform(groupMeanValue)
      

      【讨论】:

        【解决方案8】:

        总结以上所有关于可能解决方案的效率 我有一个包含 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
        

        所以选择正确的方法非常重要。 请记住,我注意到一旦列不是数字,时间就会呈指数级增长(因为我正在计算中位数,所以这是有道理的)。

        【讨论】:

          【解决方案9】:
          df.fillna(df.groupby(['name'], as_index=False).mean(), inplace=True)
          

          【讨论】:

          • 请对你的回答做一些解释。为什么从 google 偶然发现此页面的人会使用您的解决方案而不是其他 6 个答案?
          • @vino 请添加一些解释
          【解决方案10】:

          您也可以使用"dataframe or table_name".apply(lambda x: x.fillna(x.mean()))

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-04-30
            • 2021-12-25
            • 1970-01-01
            • 1970-01-01
            • 2018-01-16
            • 2017-04-02
            相关资源
            最近更新 更多