【问题标题】:extract attributes from pandas columns that satisfy a condition [sorted by frequency]从满足条件的熊猫列中提取属性[按频率排序]
【发布时间】:2021-04-12 20:39:15
【问题描述】:

所以这是关于 a previous question 我不久前在 SO 上发布的。

回到开头,这是最简单的例子:

我有一个如下所示的数据框:

tupl = [(0.1, 0.2, 0.7), (0,0,1), (0.2,0.6,0.2), (0.6,0.4,0)]

df_test = pd.DataFrame(tupl, columns = ["M1", "M2", "M3"], index =["P1", "P2", "P3", "P4"])

所以它是 3 个不同变量的频率数据框:M1、M2 和 M3,在不同的实例上:P1、... P4。

现在对于每一行,我希望能够将每个变量的出现提取为字符串,但是,与上一个问题相反,我希望它按频率排序,例如最终输出将类似于:

output = pd.DataFrame([("M3+M2+M1"), ("M3"), ("M2+M1+M3"), ("M1+M2")], 
                      columns = ["label"], index = ["P1", "P2", "P3", "P4"])

以前的答案是做一些事情,例如:

(
    df_test.gt(0).apply(lambda x: np.where(x, x.name, None))
    .apply(lambda x: '+'.join(x.dropna()), axis=1)
    .to_frame('label') 
)

这对我以前的目的非常有用,但现在我需要按频率顺序将标签粘贴在新表上。我试图通过将它传递给一个 numpy 来解决这个问题,然后在每一行上循环,但还没有取得太大的成功。我想知道是否有更流行或更有效的方法来解决它。谢谢!

【问题讨论】:

    标签: python pandas subset


    【解决方案1】:

    我希望通过join 生成器理解的循环应该使用另一个解决方案中的简化和提高性能:

    f = lambda x: '+'.join(i for i,  j 
                          in sorted(x.items(), key=lambda y: y[-1], reverse=True) if j != 0)
    df = df_test.apply(f, axis=1).to_frame('label')
    
    print (df)
           label
    P1  M3+M2+M1
    P2        M3
    P3  M2+M1+M3
    P4     M1+M2
    

    另一个想法是使用带有 np.argsort 的 numpy 通过排除 0 值的排序值进行索引,然后还排序掩码并在列表理解中获取输出:

    m = df_test.ne(0)
    a = df_test[m].to_numpy()
    c = df_test.columns.to_numpy()
    ind = np.argsort(np.argsort(-a))
    
    vals = c[ind]
    mask = m.to_numpy()[np.arange(len(df_test))[:, None], ind]
    
    out = ['+'.join(i[j]) for i, j in zip(vals, mask)]
    
    df = pd.DataFrame({'label':out}, index=df_test.index)
    print (df)
           label
    P1  M3+M2+M1
    P2        M3
    P3  M2+M1+M3
    P4     M1+M2
    

    或仅使用 pandas - 将 0 替换为 NaNs,重塑以删除它们,使用 join 进行升序排序和分组:

    df = (df_test[df_test.ne(0)]
             .stack()
             .sort_values(ascending=False)
             .reset_index(level=1)
             .groupby(level=0)['level_1']
             .agg('+'.join)
             .to_frame('label')
             .reindex(df_test.index, fill_value=''))
    
    print (df)
           label
    P1  M3+M2+M1
    P2        M3
    P3  M2+M3+M1
    P4     M1+M2
    

    表现在40k rows;

    tupl = [(0.1, 0.2, 0.7), (0,0,1), (0.2,0.6,0.2), (0.6,0.4,0)]
    
    df_test = pd.DataFrame(tupl, columns = ["M1", "M2", "M3"], index =["P1", "P2", "P3", "P4"])
    
    #40 k rows
    df_test = pd.concat([df_test] * 10000, ignore_index=True).rename(lambda x: f'P{x+1}')
    print (df_test)
    

    #Ferris solution 
    In [59]: %%timeit
        ...: obj1 = df_test.apply(lambda x: 
        ...:               sorted(x.items(), key=lambda y: y[-1], reverse=True)
        ...:               , axis=1)
        ...: obj1.map(lambda x: [i[0] for i in x if i[-1] !=0]).str.join('+')
        ...: 
    680 ms ± 6.85 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    In [63]: %%timeit
        ...: m = df_test.ne(0)
        ...: a = df_test[m].to_numpy()
        ...: c = df_test.columns.to_numpy()
        ...: ind = np.argsort(np.argsort(-a))
        ...: 
        ...: vals = c[ind]
        ...: mask = m.to_numpy()[np.arange(len(df_test))[:, None], ind]
        ...: 
        ...: out = ['+'.join(i[j]) for i, j in zip(vals, mask)]
        ...: 
        ...: pd.DataFrame({'label':out}, index=df_test.index)
        ...: 
        ...: 
    149 ms ± 1.53 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    In [60]: %%timeit
    
        ...: f = lambda x: '+'.join(i for i,  j 
                          in sorted(x.items(), key=lambda y: y[-1], reverse=True) if j != 0)
        ...: df_test.apply(f, axis=1).to_frame('label')
        ...: 
    610 ms ± 8.91 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    
    In [66]: %%timeit
        ...: (df_test[df_test.ne(0)]
        ...:          .stack()
        ...:          .sort_values(ascending=False)
        ...:          .reset_index(level=1)
        ...:          .groupby(level=0)['level_1']
        ...:          .agg('+'.join)
        ...:          .to_frame('label')
        ...:          .reindex(df_test.index, fill_value=''))
        ...:          
    477 ms ± 8.78 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    【讨论】:

      【解决方案2】:
      # sort values each row
      obj1 = df_test.apply(lambda x: 
                    sorted(x.items(), key=lambda y: y[-1], reverse=True)
                    , axis=1)
      # P1    [(M3, 0.7), (M2, 0.2), (M1, 0.1)]
      # P2    [(M3, 1.0), (M1, 0.0), (M2, 0.0)]
      # P3    [(M2, 0.6), (M1, 0.2), (M3, 0.2)]
      # P4    [(M1, 0.6), (M2, 0.4), (M3, 0.0)]
      
      # filter 0.0 & join with '+'
      obj1.map(lambda x: [i[0] for i in x if i[-1] !=0]).str.join('+')
      # P1    M3+M2+M1
      # P2          M3
      # P3    M2+M1+M3
      # P4       M1+M2
      # dtype: object
      

      【讨论】:

      • 我已经尝试了这两种解决方案,它们都能完美运行!谢谢!我给了 Ferris 接受的答案,因为它比 jezrael 的更快(分别为 278 毫秒和 4.2 秒)。
      • @jezrael 第一个。
      • @LaCordillera - 我尝试了测试解决方案,它真的很慢,所以被删除了。我添加了时间,并且在示例数据中,所有 3 个解决方案都比 Ferris 解决方案更快,是否可以使用真实数据进行测试?
      猜你喜欢
      • 1970-01-01
      • 2022-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-22
      相关资源
      最近更新 更多