【问题标题】:How to get column name for second largest row value in pandas DataFrame [duplicate]如何获取pandas DataFrame中第二大行值的列名
【发布时间】:2019-02-27 02:47:18
【问题描述】:

我有一个非常简单的问题——我想——但我似乎无法解决这个问题。我是 Python 和 Pandas 的初学者。我搜索了论坛,但找不到适合我需要的(最近的)答案。

我有一个这样的数据框:

df = pd.DataFrame({'A': [1.1, 2.7, 5.3], 'B': [2, 10, 9], 'C': [3.3, 5.4, 1.5], 'D': [4, 7, 15]}, index = ['a1', 'a2', 'a3'])

这给出了:

          A   B    C   D
    a1  1.1   2  3.3   4
    a2  2.7  10  5.4   7
    a3  5.3   9  1.5  15

我的问题很简单:我想添加一列,给出每行的最大值的列名。

我写了一个简单的函数,它返回每行的第二个最大值

def get_second_best(x):
    return sorted(x)[-2]

df['value'] = df.apply(lambda row: get_second_best(row), axis=1)

这给出了:

      A   B    C   D  value
a1  1.1   2  3.3   4    3.3
a2  2.7  10  5.4   7    7.0
a3  5.3   9  1.5  15    9.0

但我找不到如何在“值”列中显示列名,而不是值...我正在考虑布尔索引(将“值”列值与每一行进行比较),但是我还没想好怎么做。

为了更清楚,我希望它是:

      A   B    C   D  value
a1  1.1   2  3.3   4    C
a2  2.7  10  5.4   7    D
a3  5.3   9  1.5  15    B

感谢任何帮助(和解释)!

【问题讨论】:

    标签: python pandas sorting numpy dataframe


    【解决方案1】:

    一种方法是使用Series.nlargest 找出每行中最大的两个元素,然后使用Series.idxmin 找到与最小元素相对应的列:

    In [45]: df['value'] = df.T.apply(lambda x: x.nlargest(2).idxmin())
    
    In [46]: df
    Out[46]:
          A   B    C   D value
    a1  1.1   2  3.3   4     C
    a2  2.7  10  5.4   7     D
    a3  5.3   9  1.5  15     B
    

    值得注意的是,选择 Series.idxmin 而不是 DataFrame.idxmin 可以在性能方面有所不同:

    df = pd.DataFrame(np.random.normal(size=(100, 4)), columns=['A', 'B', 'C', 'D'])
    %timeit df.T.apply(lambda x: x.nlargest(2).idxmin()) # 39.8 ms ± 2.66 ms
    %timeit df.T.apply(lambda x: x.nlargest(2)).idxmin() # 53.6 ms ± 362 µs
    

    编辑:添加到@jpp 的答案,如果性能很重要,您可以通过使用Numba 来获得显着的加速,将代码编写为 C 并编译它:

    from numba import njit, prange
    
    @njit
    def arg_second_largest(arr):
        args = np.empty(len(arr), dtype=np.int_)
        for k in range(len(arr)):
            a = arr[k]
            second = np.NINF
            arg_second = 0
            first = np.NINF
            arg_first = 0
            for i in range(len(a)):
                x = a[i]
                if x >= first:
                    second = first
                    first = x
                    arg_second = arg_first
                    arg_first = i
                elif x >= second:
                    second = x
                    arg_second = i
            args[k] = arg_second
        return args
    

    让我们分别在(1000, 4)(1000, 1000)形状的两组数据上比较不同的解决方案:

    df = pd.DataFrame(np.random.normal(size=(1000, 4)))
    %timeit df.T.apply(lambda x: x.nlargest(2).idxmin())     # 429 ms ± 5.1 ms
    %timeit df.columns[df.values.argsort(1)[:, -2]]          # 94.7 µs ± 2.15 µs
    %timeit df.columns[np.argpartition(df.values, -2)[:,-2]] # 101 µs ± 1.07 µs
    %timeit df.columns[arg_second_largest(df.values)]        # 74.1 µs ± 775 ns
    
    df = pd.DataFrame(np.random.normal(size=(1000, 1000)))
    %timeit df.T.apply(lambda x: x.nlargest(2).idxmin())     # 1.8 s ± 49.7 ms
    %timeit df.columns[df.values.argsort(1)[:, -2]]          # 52.1 ms ± 1.44 ms
    %timeit df.columns[np.argpartition(df.values, -2)[:,-2]] # 14.6 ms ± 145 µs
    %timeit df.columns[arg_second_largest(df.values)]        # 1.11 ms ± 22.6 µs
    

    在最后一种情况下,通过使用 @njit(parallel=True) 并将外部循环替换为 for k in prange(len(arr)),我能够挤出更多时间并将基准测试降低到 852 µs。

    【讨论】:

    • 太棒了,就像一个魅力!希望我自己考虑一下。非常感谢!
    【解决方案2】:

    这是使用 NumPy 的一种解决方案。这个想法是argsort你数据框中的值,选择倒数第二列,最后用它来索引df.column

    df['value'] = df.columns[df.values.argsort(1)[:, -2]]
    
    print(df)
    
          A   B    C   D value
    a1  1.1   2  3.3   4     C
    a2  2.7  10  5.4   7     D
    a3  5.3   9  1.5  15     B
    

    您应该会发现这比基于 Pandas 的解决方案更有效:

    # Python 3.6, NumPy 1.14.3, Pandas 0.23.0
    
    np.random.seed(0)
    
    df = pd.DataFrame(np.random.normal(size=(100, 4)), columns=['A', 'B', 'C', 'D'])
    
    %timeit df.T.apply(lambda x: x.nlargest(2).idxmin())  # 49.6 ms
    %timeit df.T.apply(lambda x: x.nlargest(2)).idxmin()  # 73.2 ms
    %timeit df.columns[df.values.argsort(1)[:, -2]]       # 36.3 µs
    

    【讨论】:

    • +1;拥有 NumPy 等价物总是很高兴。值得注意的是,如果数据框更宽,那么df.columns[np.argpartition(df.values, -2)[:,-2]] 将成为一个可能的替代方案。对于size=(100, 100) 数据帧,基于argsort 的解决方案需要364 µs,而argpartition 将其缩短到168 µs。
    猜你喜欢
    • 2022-11-29
    • 2018-06-09
    • 1970-01-01
    • 1970-01-01
    • 2017-09-27
    • 2022-08-19
    • 2021-10-08
    • 1970-01-01
    • 2020-08-27
    相关资源
    最近更新 更多