当前位置: 首页 > 知识库问答 >
问题:

同时(“同时”)获取'min'和'idxmin'(或'max'和'idxmax')?

晏鸿畅
2023-03-14

我想知道是否有可能同时调用idxminmin(在同一个调用/循环中)。

假设以下数据帧:

    id  option_1    option_2    option_3    option_4
0   0   10.0        NaN         NaN         110.0
1   1   NaN         20.0        200.0       NaN
2   2   NaN         300.0       30.0        NaN
3   3   400.0       NaN         NaN         40.0
4   4   600.0       700.0       50.0        50.0

我想计算选项系列的最小值(min)和包含它的列(idxmin):

    id  option_1    option_2    option_3    option_4    min_column  min_value
0   0   10.0        NaN         NaN         110.0       option_1        10.0
1   1   NaN         20.0        200.0       NaN         option_2        20.0
2   2   NaN         300.0       30.0        NaN         option_3        30.0
3   3   400.0       NaN         NaN         40.0        option_4        40.0
4   4   600.0       700.0       50.0        50.0        option_3        50.0

显然,我可以分别调用idxminmin(一个接一个,请参见下面的示例),但是有没有一种方法可以在不搜索矩阵两次(一次搜索值,另一次搜索索引)的情况下提高效率?

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'id': [0,1,2,3,4], 
    'option_1': [10,     np.nan, np.nan, 400,    600], 
    'option_2': [np.nan, 20,     300,    np.nan, 700], 
    'option_3': [np.nan, 200,    30,     np.nan, 50],
    'option_4': [110,    np.nan, np.nan, 40,     50], 
})

df['min_column'] = df.filter(like='option').idxmin(1)
df['min_value'] = df.filter(like='option').min(1)

(我预计这将是次优的,因为搜索执行了两次。)


共有3个答案

吕自明
2023-03-14

@piRSquared的Numpy解决方案是我认为最常见情况的赢家。以下是他的答案,只做了最小的修改,将列分配给原始数据框(我在所有测试中都这样做了,以便与原始问题的示例保持一致)

col_mask = df.columns.str.startswith('option')
options = df.columns[col_mask]
v = np.column_stack([*map(df.get, options)])
df.assign(min_value = np.nanmin(v, axis=1),
          min_column = options[np.nanargmin(v, axis=1)])

如果有很多列(超过10000列),您应该小心,因为在这些极端情况下,结果可能会开始发生显著变化。

根据我的测试,根据所有建议的答案,分别调用minidxmin是最快的。

虽然不是同时进行的(请参见下面的直接答案),但最好在列索引(minu columncolumn)上使用DataFrame.lookup),以避免搜索值(minu values)。

因此,您不需要遍历整个矩阵,即O(n*m),而只遍历生成的min_列series,即O(n):

df = pd.DataFrame({
    'id': [0,1,2,3,4], 
    'option_1': [10,     np.nan, np.nan, 400,    600], 
    'option_2': [np.nan, 20,     300,    np.nan, 700], 
    'option_3': [np.nan, 200,    30,     np.nan, 50],
    'option_4': [110,    np.nan, np.nan, 40,     50], 
})

df['min_column'] = df.filter(like='option').idxmin(1)
df['min_value'] = df.lookup(df.index, df['min_column'])

既然你问了如何计算“在同一个调用中”的值(假设因为你简化了这个问题的例子),你可以尝试一个lambda表达式:

def min_idxmin(x):
    _idx = x.idxmin()
    return _idx, x[_idx]

df['min_column'], df['min_value'] = zip(*df.filter(like='option').apply(
    lambda x: min_idxmin(x), axis=1))

需要说明的是,尽管第二次搜索被删除(替换为x[\u idx]中的直接访问),但这很可能需要更长的时间,因为您没有利用pandas/numpy的矢量化属性。

底线是熊猫/Numpy矢量化操作非常快。

使用df.lookup似乎没有任何优势,单独调用minidxmin比使用查找更好,因为查找本身令人兴奋,值得一提。

我测试了一个包含10000行和10列的数据帧(在最初的示例中为option\uusequence)。因为,我得到了一些意想不到的结果,然后我还测试了1000x1000和100x1000。根据调查结果:

>

分别调用minidxmin对于10000x10情况是最好的,甚至比Dataframe.lookup更好(尽管Dataframe.lookup结果在100x10000情况下表现更好)。虽然数据的形状会影响结果,但我认为拥有10000列是不现实的。

@wen提供的解决方案在性能上紧随其后,尽管它并不比分别调用idxminmin或使用Dataframe.lookup要好。我做了一个额外的测试(见test7()),因为我觉得添加操作(reset_indexzip可能会干扰结果。它仍然比test1test2更糟糕,尽管它没有做同化(我不知道如何使用head(1)来做同化)。@文你介意拉我一把吗?

@当有更多的列(1000x1000或100x1000)时,这个解决方案会变得更有效,这是有意义的,因为排序比搜索慢。在本例中,我建议的lambda表达式性能更好。

使用lambda表达式或使用转置(T)的任何其他解决方案都会落后。我建议的lambda表达式花费了大约1秒,比使用@piRSquared和@RafaelC建议的转座T大约11秒要好。

使用以下10000行10列的数据帧:

import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.randint(0,100,size=(10000, 10)), columns=[f'option_{x}' for x in range(1,11)]).reset_index()

>

  • 分别调用两列:

    def test1():
        df['min_column'] = df.filter(like='option').idxmin(1)
        df['min_value'] = df.filter(like='option').min(1)
    %timeit -n 100 test1()
    13 ms ± 580 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    调用查找(在这种情况下速度较慢!):

    def test2():
        df['min_column'] = df.filter(like='option').idxmin(1)
        df['min_value'] = df.lookup(df.index, df['min_column'])    
    %timeit -n 100 test2()
    # 15.7 ms ± 399 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    使用应用min_idxmin(x)

    def min_idxmin(x):
        _idx = x.idxmin()
        return _idx, x[_idx]
    
    def test3():
        df['min_column'], df['min_value'] = zip(*df.filter(like='option').apply(
            lambda x: min_idxmin(x), axis=1))
    %timeit -n 10 test3()
    # 968 ms ± 32.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    通过@piRSquared使用agg['min',idxmin']

    def test4():
        df['min_column'], df['min_value'] = zip(*df.set_index('index').filter(like='option').T.agg(['min', 'idxmin']).T.values)
    
    %timeit -n 1 test4()
    # 11.2 s ± 850 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    @RafaelC使用agg['min',idxmin']

    def test5():
    
        df['min_column'], df['min_value'] = zip(*df.filter(like='option').agg(lambda x: x.agg(['min', 'idxmin']), axis=1).values)
        %timeit -n 1 test5()
        # 11.7 s ± 597 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    按@Wen排序值:

    def test6():
        df['min_column'], df['min_value'] = zip(*df.filter(like='option').stack().sort_values().groupby(level=[0]).head(1).reset_index(level=1).values)
    
    %timeit -n 100 test6()
    # 33.6 ms ± 1.72 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    排序值由@文我修改,使比较更公平由于同化操作过载(我解释了为什么在开头的摘要):

    def test7():
        df.filter(like='option').stack().sort_values().groupby(level=[0]).head(1)
    
    %timeit -n 100 test7()
    # 25 ms ± 937 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    使用Numpy:

    def test8():
        col_mask = df.columns.str.startswith('option')
        options = df.columns[col_mask]
        v = np.column_stack([*map(df.get, options)])
        df.assign(min_value = np.nanmin(v, axis=1),
                  min_column = options[np.nanargmin(v, axis=1)])
    
    %timeit -n 100 test8()
    # 2.76 ms ± 248 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    使用numpy但避免搜索(改为索引):

    def test9():
        col_mask = df.columns.str.startswith('option')
        options = df.columns[col_mask]
        v = np.column_stack([*map(df.get, options)])
        idxmin = np.nanargmin(v, axis=1)
        # instead of looking for the answer, indexes are used
        df.assign(min_value = v[range(v.shape[0]), idxmin],
                  min_column = options[idxmin])
    
    %timeit -n 100 test9()
    # 3.96 ms ± 267 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    我用1000x1000形状执行更多测试:

    df = pd.DataFrame(np.random.randint(0,100,size=(1000, 1000)), columns=[f'option_{x}' for x in range(1,1001)]).reset_index()
    

    尽管结果发生了变化:

    test1    ~27.6ms
    test2    ~29.4ms
    test3    ~135ms
    test4    ~1.18s
    test5    ~1.29s
    test6    ~287ms
    test7    ~290ms
    test8    ~25.7
    test9    ~26.1
    

    我使用100x10000形状执行更多测试:

    df = pd.DataFrame(np.random.randint(0,100,size=(100, 10000)), columns=[f'option_{x}' for x in range(1,10001)]).reset_index()
    

    尽管结果发生了变化:

    test1    ~46.8ms
    test2    ~25.6ms
    test3    ~101ms
    test4    ~289ms
    test5    ~276ms
    test6    ~349ms
    test7    ~301ms
    test8    ~121ms
    test9    ~122ms
    

  • 简宏义
    2023-03-14

    可能使用stackgroupby

    v=df.filter(like='option')
    v.stack().sort_values().groupby(level=[0]).head(1).reset_index(level=1)
    Out[313]:
        level_1     0
    0  option_1  10.0
    1  option_2  20.0
    2  option_3  30.0
    3  option_4  40.0
    4  option_3  50.0
    
    傅志文
    2023-03-14

    谷歌Colab
    GitHub

    df.set_index('id').T.agg(['min', 'idxmin']).T
    
      min    idxmin
    0  10  option_1
    1  20  option_2
    2  30  option_3
    3  40  option_4
    4  50  option_3
    
    d_ = df.set_index('id')
    v = d_.values
    pd.DataFrame(dict(
        Min=np.nanmin(v, axis=1),
        Idxmin=d_.columns[np.nanargmin(v, axis=1)]
    ), d_.index)
    
          Idxmin   Min
    id                
    0   option_1  10.0
    1   option_2  20.0
    2   option_3  30.0
    3   option_4  40.0
    4   option_3  50.0
    
    col_mask = df.columns.str.startswith('option')
    options = df.columns[col_mask]
    v = np.column_stack([*map(df.get, options)])
    pd.DataFrame(dict(
        Min=np.nanmin(v, axis=1),
        IdxMin=options[np.nanargmin(v, axis=1)]
    ))
    

    Numpy解决方案是最快的。

             pir_agg_1  pir_agg_2  pir_agg_3  wen_agg_1  tot_agg_1  tot_agg_2
    10       12.465358   1.272584        1.0   5.978435   2.168994   2.164858
    30       26.538924   1.305721        1.0   5.331755   2.121342   2.193279
    100      80.304708   1.277684        1.0   7.221127   2.215901   2.365835
    300     230.009000   1.338177        1.0   5.869560   2.505447   2.576457
    1000    661.432965   1.249847        1.0   8.931438   2.940030   3.002684
    3000   1757.339186   1.349861        1.0  12.541915   4.656864   4.961188
    10000  3342.701758   1.724972        1.0  15.287138   6.589233   6.782102
    
            pir_agg_1  pir_agg_2  pir_agg_3  wen_agg_1  tot_agg_1  tot_agg_2
    10       8.008895   1.000000   1.977989   5.612195   1.727308   1.769866
    30      18.798077   1.000000   1.855291   4.350982   1.618649   1.699162
    100     56.725786   1.000000   1.877474   6.749006   1.780816   1.850991
    300    132.306699   1.000000   1.535976   7.779359   1.707254   1.721859
    1000   253.771648   1.000000   1.232238  12.224478   1.855549   1.639081
    3000   346.999495   2.246106   1.000000  21.114310   1.893144   1.626650
    10000  431.135940   2.095874   1.000000  32.588886   2.203617   1.793076
    
    def pir_agg_1(df):
      return df.set_index('id').T.agg(['min', 'idxmin']).T
    
    def pir_agg_2(df):
      d_ = df.set_index('id')
      v = d_.values
      return pd.DataFrame(dict(
          Min=np.nanmin(v, axis=1),
          IdxMin=d_.columns[np.nanargmin(v, axis=1)]
      ))
    
    def pir_agg_3(df):
      col_mask = df.columns.str.startswith('option')
      options = df.columns[col_mask]
      v = np.column_stack([*map(df.get, options)])
      return pd.DataFrame(dict(
          Min=np.nanmin(v, axis=1),
          IdxMin=options[np.nanargmin(v, axis=1)]
      ))
    
    def wen_agg_1(df):
      v = df.filter(like='option')
      d = v.stack().sort_values().groupby(level=0).head(1).reset_index(level=1)
      d.columns = ['IdxMin', 'Min']
      return d
    
    def tot_agg_1(df):
      """I combined toto_tico's 2 filter calls into one"""
      d = df.filter(like='option')
      return df.assign(
          IdxMin=d.idxmin(1),
          Min=d.min(1)
      )
    
    def tot_agg_2(df):
      d = df.filter(like='option')
      idxmin = d.idxmin(1)
      return df.assign(
          IdxMin=idxmin,
          Min=d.lookup(d.index, idxmin)
      )
    
    def sim_df(n, m):
      return pd.DataFrame(
          np.random.randint(m, size=(n, m))
      ).rename_axis('id').add_prefix('option').reset_index()
    
    
    fs = 'pir_agg_1 pir_agg_2 pir_agg_3 wen_agg_1 tot_agg_1 tot_agg_2'.split()
    ix = [10, 30, 100, 300, 1000, 3000, 10000]
    
    res_small_col = pd.DataFrame(index=ix, columns=fs, dtype=float)
    res_large_col = pd.DataFrame(index=ix, columns=fs, dtype=float)
    
    for i in ix:
      df = sim_df(i, 10)
      for j in fs:
        stmt = f"{j}(df)"
        setp = f"from __main__ import {j}, df"
        res_small_col.at[i, j] = timeit(stmt, setp, number=10)
    
    for i in ix:
      df = sim_df(i, 100)
      for j in fs:
        stmt = f"{j}(df)"
        setp = f"from __main__ import {j}, df"
        res_large_col.at[i, j] = timeit(stmt, setp, number=10)
    
     类似资料:
    • 问题内容: numpy.amax()将在数组中找到最大值,numpy.amin()对最小值进行相同操作。如果要同时找到max和min,则必须调用两个函数,这需要两次(非常大)数组传递,这似乎很慢。 numpy API中是否存在仅通过一次数据查找即可找到max和min的函数? 问题答案: numpy API中是否存在仅通过一次数据查找即可找到max和min的函数? 否。在撰写本文时,尚无此功能。(是

    • 本文向大家介绍使用Break时在SAP Web Intelligence的表中获取MIN和MAX日期,包括了使用Break时在SAP Web Intelligence的表中获取MIN和MAX日期的使用技巧和注意事项,需要的朋友参考一下 这可以通过根据要查找的条件创建一个指标来实现-POST测试的最小绘制日期时间和PRE-Test的最大绘制日期时间。  创建该指标后,根据条件以黄色突出显示的行将显示

    • 我已经看到了一些关于寻找最小索引的问题。在这个相关的问题上有一个解决方案,它使用2个内置函数,,然后。这种方法的问题是它会对整个列表进行两次检查。是否有任何单个内置函数用于最小/最大索引?

    • 我有以下四个表格,包含学生信息、讲座信息、听课信息和考试。 使用SELECT Student_;我得到了每个学生的平均分数列表。 如何将其与MAX函数相结合,以获得列表中平均数最高的学生的ID,姓名和平均值? 学生id 学生姓名 lecture_id<br>lecture_name<br>ECTS id 学生id 讲座id id 学生id 讲座id 分数

    • 问题内容: 在以下查询中,您认为哪种方法更好?您的原因是什么(代码效率,更好的可维护性,更少的WTFery)… 问题答案: 在最坏的情况下,如果要查看未索引的字段,则使用需要对表进行一次完整的遍历。使用并且需要一个文件排序。如果针对大表运行,则预期的性能可能会存在显着差异。作为无意义的数据点,我的开发服务器上的106,000行表花了0.36秒,花了0.84秒。 但是,如果您正在查看索引列,则很难注

    • 问题内容: 任何人都可以从官方MySQL文档中澄清这一点 使用索引…查找特定索引列key_col的MIN()或MAX()值。这由预处理器优化,该预处理器检查是否在索引中在key_col之前出现的所有关键部分上使用WHERE key_part_N =常量。在这种情况下,MySQL对每个MIN()或MAX()表达式执行一次键查找,并将其替换为常量。如果所有表达式都用常量替换,查询将立即返回。例如: S