【问题标题】:Groupby and get values from another columnGroupby并从另一列获取值
【发布时间】:2020-12-19 08:18:34
【问题描述】:

我有一个像这样排列的庞大数据集

Serial     Val1      Val2      Val3     
1          21.10                         
1          43.06                         
1          32.12                         
2                    11.20               
2                    22.20               
3          45.10                         
3          14.16                         
4                              34.90     
4                              12.12     
4                              18.09

我想对每个唯一序列进行分组并将其对应的值(从 Val1 到 Val3)合并到一列 ['All'] 并放置一个 ['Source'] 列。

Serial     Val1      Val2      Val3      All       Source
1          21.10                         21.10     Val1
1          43.06                         43.06
1          32.12                         32.12
2                    11.20               11.20     Val2
2                    22.20               22.20
3          45.10                         45.10     Val1
3          14.16                         14.16
4                              34.90     34.90     Val3
4                              12.12     12.12
4                              18.09     18.09 

我试着做这样的事情, df['All'] = df['Serial'].map(df.groupby('Serial').apply(lambda x: x['Val2'] if pd.isnull(x['Val1']) else x['Val3'])

【问题讨论】:

    标签: python python-3.x pandas


    【解决方案1】:

    如果只有一个值填充了非缺失值,则首先选择列进行处理,例如这里首先通过在DataFrame.iloc 中进行索引来省略,然后通过DataFrame.notnaDataFrame.idxmax 比较非缺失值来获取mean 列名的第一个Trues,最后添加Series.mask 仅用于每组的第一个值Series.duplicated:

    df1 = df.iloc[:, 1:]
    df = df.assign(All = df1.mean(axis=1),
                   Source = df1.notna().idxmax(axis=1).mask(df['Serial'].duplicated()))
    print (df)
       Serial   Val1  Val2   Val3    All Source
    0       1  21.10   NaN    NaN  21.10   Val1
    1       1  43.06   NaN    NaN  43.06    NaN
    2       1  32.12   NaN    NaN  32.12    NaN
    3       2    NaN  11.2    NaN  11.20   Val2
    4       2    NaN  22.2    NaN  22.20    NaN
    5       3  45.10   NaN    NaN  45.10   Val1
    6       3  14.16   NaN    NaN  14.16    NaN
    7       4    NaN   NaN  34.90  34.90   Val3
    8       4    NaN   NaN  12.12  12.12    NaN
    9       4    NaN   NaN  18.09  18.09    NaN
    

    如果可能,只需要为每行添加另一个掩码以防止匹配第一个 NaN 列:

    df1 = df.iloc[:, 1:]
    
    mask = df1.isna().all(axis=1)
    
    df = df.assign(All = df1.mean(axis=1),
                   Source = df1.notna().idxmax(axis=1).mask(df['Serial'].duplicated() | mask))
    print (df)
       Serial   Val1  Val2   Val3    All Source
    0       1  21.10   NaN    NaN  21.10   Val1
    1       1  43.06   NaN    NaN  43.06    NaN
    2       1  32.12   NaN    NaN  32.12    NaN
    3       2    NaN  11.2    NaN  11.20   Val2
    4       2    NaN  22.2    NaN  22.20    NaN
    5       3  45.10   NaN    NaN  45.10   Val1
    6       3  14.16   NaN    NaN  14.16    NaN
    7       4    NaN   NaN  34.90  34.90   Val3
    8       4    NaN   NaN  12.12  12.12    NaN
    9       5    NaN   NaN    NaN    NaN    NaN
    

    小型 DataFrame 中的性能 - 使用示例数据,上述解决方案的速度提高了 329 倍:

    #10k rows
    df = pd.concat([df] * 1000, ignore_index=True)
    
    
    In [109]: %%timeit
         ...: def lastVal(row):
         ...:     lvi = row.last_valid_index()
         ...:     return pd.Series({'All': row.loc[lvi], 'Source': lvi})
         ...: 
         ...: result = df.join(df.apply(lastVal, axis=1))
         ...: result.Source = np.where(result.Source != result.Source.shift(), result.Source, '')
         ...: 
    4.97 s ± 254 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    In [110]: %%timeit
         ...: df1 = df.iloc[:, 1:]
         ...: df.assign(All = df1.mean(axis=1), Source = df1.notna().idxmax(axis=1).mask(df['Serial'].duplicated()))
         ...: 
    15.1 ms ± 549 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    【讨论】:

    • 谢谢!它正在工作。跟进问题。如果数据框中有其他列但我只想合并特定列(Val1、Val2、Val3)怎么办?
    • @kiyaserin - 然后使用df1 = df[['Val1','Val2','Val3']] 而不是df1 = df.iloc[:, 1:]
    • 谢谢!我还有其他问题。我不确定是否应该为此创建另一个问题,但是如果有些行为多个列分配了值怎么办。例如,序列 1 具有 Val1 和 Val2 的值,但我仍然希望从 Val1 中为 All 列选择值?
    • @kiyaserin - 我认为最好的方法应该是接受答案并创建新答案。
    【解决方案2】:

    要为每一行获取两个新列的值:

    • 检索最后一个有效索引,
    • 检索它所指示的值,
    • 系列的形式返回值和索引,并带有正确的列名。

    为此,定义以下函数:

    def lastVal(row):
        lvi = row.last_valid_index()
        return pd.Series({'All': row.loc[lvi], 'Source': lvi})
    

    然后应用它并将结果与​​原始 DataFrame 连接起来:

    result = df.join(df.apply(lastVal, axis=1))
    

    结果是:

       Serial   Val1  Val2   Val3    All Source
    0       1  21.10   NaN    NaN  21.10   Val1
    1       1  43.06   NaN    NaN  43.06   Val1
    2       1  32.12   NaN    NaN  32.12   Val1
    3       2    NaN  11.2    NaN  11.20   Val2
    4       2    NaN  22.2    NaN  22.20   Val2
    5       3  45.10   NaN    NaN  45.10   Val1
    6       3  14.16   NaN    NaN  14.16   Val1
    7       4    NaN   NaN  34.90  34.90   Val3
    8       4    NaN   NaN  12.12  12.12   Val3
    9       4    NaN   NaN  18.09  18.09   Val3
    

    如果您对 Source 列中的“重复”值不满意, 运行:

    result.Source.mask(result.Source == result.Source.shift(), '', inplace=True)
    

    【讨论】:

      【解决方案3】:

      解决此类问题的最佳方法是“取消透视列”。取消透视对于像您这样的问题很有用,随着时间的推移,列的大小会增加数据的可见性和分析难度。

      在 pandas 中取消透视列的方法是使用 melt 函数。

      解决方案:

      # return new dataframe after unpivoting columns 
      df_unpivoted=df.melt(id_vars=['Series'],var_name='Source',value_name='All')
      #remove null for dataframe as you have them for all value columns
      df_unpivoted.dropna(inplace=True)
      #try
      df_unpivoted.head()
      

      此外,您现在可以将旧数据框中的值列添加到新数据框中!!

      【讨论】:

        猜你喜欢
        • 2021-01-18
        • 2021-05-10
        • 2014-09-06
        • 2018-12-09
        • 1970-01-01
        • 2022-06-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多