【问题标题】:Find (only) the first row satisfying a given condition in pandas DataFrame在 pandas DataFrame 中查找(仅)满足给定条件的第一行
【发布时间】:2018-05-16 00:21:50
【问题描述】:

我有一个数据框df,其中有一列很长的随机正整数:

df = pd.DataFrame({'n': np.random.randint(1, 10, size = 10000)})

我想确定列中第一个偶数的索引。一种方法是:

df[df.n % 2 == 0].iloc[0]

但这涉及很多操作(生成索引f.n % 2 == 0,在这些索引上评估df,最后取第一项)并且非常慢。这样的循环要快得多:

for j in range(len(df)):
    if df.n.iloc[j] % 2 == 0:
        break

还因为第一个结果可能在前几行。有没有类似性能的熊猫方法?谢谢。

注意:此条件(为偶数)只是一个示例。 我正在寻找一种适用于值的任何类型条件的解决方案,即,一种快速的单行替代方案:

df[ conditions on df.n ].iloc[0]

【问题讨论】:

  • 你为什么不直接使用那个循环呢?
  • 列是否已排序?如果是这样,您可以尝试np.searchsorted。如果没有,除了预排序,我认为没有任何矢量化解决方案。
  • @RNar:我正在学习熊猫,我想知道如何在熊猫中做到这一点@ayhan:是的,该列已排序。但是如何使用np.searchsorted 指定复杂条件?例如,如何找到第一个偶数?
  • 如果你说的前几行通常满足条件,那么你可以做df.iloc[:x,df.A > 3.5].iloc[0]只搜索前X行。如果错过了,请搜索下 X 行等。根据您的数据和应该快速的 X 选择。否则我可能会在 ayhan 链接的答案之一中尝试 numba 函数
  • 归根结底,conditions on df.n 是一个非常 广泛的问题,并且根据具体情况有不同的操作。无论如何,要摆脱与系列/列的元素比较是很困难的。 .iloc[0] 或其他任何你在最后添加的东西都不是昂贵的部分。

标签: python pandas


【解决方案1】:

TLDR:您可以使用next(j for j in range(len(df)) if df.at[j, "n"] % 2 == 0)


我认为完全有可能在 oneliner 中编写代码。让我们定义一个 DataFrame 来证明这一点:

df = pd.DataFrame({'n': np.random.randint(1, 10, size = 100000)})

首先,您的代码给出:

for j in range(len(df)):
    if df.n.iloc[j] % 2 == 0:
        break
% 22.1 µs ± 1.5 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

将其转换为 oneliner 给出:

next(j for j in range(len(df)) if df["n"].iloc[j] % 2 == 0)
% 20.6 µs ± 1.26 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

为了进一步加快计算速度,我们可以使用at而不是iloc,因为这在访问单个值时更快:

next(j for j in range(len(df)) if df.at[j, "n"] % 2 == 0)
% 8.88 µs ± 617 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

【讨论】:

    【解决方案2】:

    让您迭代行并在满意时停止的选项是使用 DataFrame.iterrows,是 pandas 的行迭代器。

    在这种情况下,您可以像这样实现它:

    def get_first_row_with(condition, df):
        for index, row in df.iterrows():
            if condition(row):
                return index, row
        return None # Condition not met on any row in entire DataFrame
    

    然后,给定一个DataFrame,例如:

    df = pd.DataFrame({
                        'cats': [1,2,3,4], 
                        'dogs': [2,4,6,8]
                      }, 
                      index=['Alice', 'Bob', 'Charlie', 'Eve'])
    

    你可以用作:

    def some_condition(row):
        return row.cats + row.dogs >= 7
    
    index, row = get_first_row_with(some_condition, df)
    
    # Use results however you like, e.g.:
    print('{} is the first person to have at least 7 pets.'.format(index))
    print('They have {} cats and {} dogs!'.format(row.cats, row.dogs))
    

    哪个会输出:

    Charlie is the first person to have at least 7 pets.
    They have 3 cats and 6 dogs!
    

    【讨论】:

    • 谢谢托马斯,从风格的角度来看,我喜欢这个解决方案。如果我找不到 for 循环的替代品,我会尽快接受您的回答
    • 我已经针对原始 pandas 版本测试了这个 for 循环,如果在数组的开头满足条件,它似乎具有相似的性能,然后效率降低(我的答案中的图表)
    【解决方案3】:

    Zip 索引和列,然后循环它们以获得更快的循环速度。 Zip 提供最快的循环性能,比iterrows()itertuples() 更快。

    for j in zip(df.index,df.n):
            if j[1] % 2 == 0:
                    index_position = j[0]
                    break
    

    【讨论】:

      【解决方案4】:

      为了好玩,我决定尝试几种可能性。我拿了一个数据框:

      MAX = 10**7
      df = pd.DataFrame({'n': range(MAX)})
      

      (这次不是随机的。)我想找到n >= N 的第一行,其中某个值为N。我已经计时了以下四个版本:

      def getfirst_pandas(condition, df):
          return df[condition(df)].iloc[0]
      
      def getfirst_iterrows_loop(condition, df):
          for index, row in df.iterrows():
              if condition(row):
                  return index, row
          return None
      
      def getfirst_for_loop(condition, df):
          for j in range(len(df)):
              if condition(df.iloc[j]):
                  break
          return j
      
      def getfirst_numpy_argmax(condition, df):
          array = df.as_matrix()
          imax  = np.argmax(condition(array))
          return df.index[imax]
      

      N = 十的幂。当然,numpy(优化的 C)代码预计会比 python 中的for 循环更快,但我想看看N python 循环的哪些值仍然可以。

      我计时了:

      getfirst_pandas(lambda x: x.n >= N, df)
      getfirst_iterrows_loop(lambda x: x.n >= N, df)
      getfirst_for_loop(lambda x: x.n >= N, df)
      getfirst_numpy_argmax(lambda x: x >= N, df.n)
      

      N = 1, 10, 100, 1000, ...。这是性能的对数图:

      PICTURE

      简单的for 循环是可以的,只要“第一个真实位置”预计在开头,但随后就变坏了。 np.argmax 是最安全的解决方案。

      从图中可以看出,pandasargmax 的时间保持(几乎)不变,因为它们总是扫描整个数组。最好有一个 nppandas 方法,但没有。

      【讨论】:

      • 至少有人提到 for 循环的复杂性将取决于预期结果的位置......是的,OP 的解决方案通常并不是最受好评的答案所声称的最快的......
      【解决方案5】:

      做了一些时间安排,是的,使用生成器通常会给你更快的结果

      df = pd.DataFrame({'n': np.random.randint(1, 10, size = 10000)})
      
      %timeit df[df.n % 2 == 0].iloc[0]
      %timeit df.iloc[next(k for k,v in df.iterrows() if v.n % 2 == 0)]
      %timeit df.iloc[next(t[0] for t in df.itertuples() if t.n % 2 == 0)]
      

      我明白了:

      1000 loops, best of 3: 1.09 ms per loop
      1000 loops, best of 3: 619 µs per loop # <-- iterrows generator
      1000 loops, best of 3: 1.1 ms per loop
      10000 loops, best of 3: 25 µs per loop # <--- your solution
      

      但是当你放大它时:

      df = pd.DataFrame({'n': np.random.randint(1, 10, size = 1000000)})
      

      差异消失:

      10 loops, best of 3: 40.5 ms per loop 
      10 loops, best of 3: 40.7 ms per loop # <--- iterrows
      10 loops, best of 3: 56.9 ms per loop
      

      您的解决方案是最快的,为什么不使用它呢?

      for j in range(len(df)):
          if df.n.iloc[j] % 2 == 0:
              break
      

      【讨论】:

      • 我同意。我希望在击中目标行时跳出循环,从而跳过下面的行,这将比找到迭代 all 行的最快方法节省更多时间。 (特别是在大型数据帧上)
      • 谢谢 Anton,我想我最终会接受在我的代码中编写一个循环,你证明这是最快的选择
      • 我认为你的比较是不公平的,因为使用你的 oneliners,你正在访问 n % 2 == 0 的数据框行,而对于 for 循环,你没有这样做。为了公平比较,您可以将df.iloc[j] 添加到三行代码中,或者删除next 语句周围的df.iloc
      猜你喜欢
      • 2021-12-04
      • 1970-01-01
      • 2020-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-16
      • 1970-01-01
      • 2013-04-08
      相关资源
      最近更新 更多