【问题标题】:Delete rows with overlapping intervals efficiently有效地删除具有重叠间隔的行
【发布时间】:2023-01-26 23:56:11
【问题描述】:

考虑以下数据框

>>> df
   Start  End  Tiebreak
0      1    6  0.376600
1      5    7  0.050042
2     15   20  0.628266
3     10   15  0.984022
4     11   12  0.909033
5      4    8  0.531054

每当两行的 [Start, End] 间隔重叠时,我希望删除具有较低平局值的行。该示例的结果将是

>>> df
   Start  End  Tiebreak
2     15   20  0.628266
3     10   15  0.984022
5      4    8  0.531054

我有一个双循环,它的工作效率很低,我想知道是否存在一种利用内置函数并按列工作的方法。

import pandas as pd
import numpy as np

# initial data
df = pd.DataFrame({
    'Start': [1, 5, 15, 10, 11, 4],
    'End': [6, 7, 20, 15, 12, 8],
    'Tiebreak': np.random.uniform(0, 1, 6)
})

# checking for overlaps
list_idx_drop = []
for i in range(len(df) - 1):
    for j in range(i + 1, len(df)):
        idx_1 = df.index[i]
        idx_2 = df.index[j]

        cond_1 = (df.loc[idx_1, 'Start'] < df.loc[idx_2, 'End'])
        cond_2 = (df.loc[idx_2, 'Start'] < df.loc[idx_1, 'End'])
        
        # if rows overlaps
        if cond_1 & cond_2:
            tie_1 = df.loc[idx_1, 'Tiebreak']
            tie_2 = df.loc[idx_2, 'Tiebreak']

            # delete row with lower tiebreaking value
            if tie_1 < tie_2:  
                df.drop(idx_1, inplace=True)
            else:
                df.drop(idx_2, inplace=True)

【问题讨论】:

    标签: python pandas dataframe for-loop coding-efficiency


    【解决方案1】:

    您可以按 End 排序并检查结尾大于前一个 Start 的情况。使用该 True/False 值,您可以创建要删除重复项的分组。按Tiebreak 再次排序,并将重复项放在组列中。

    import pandas as pd
    
    df = pd.DataFrame({'Start': {0: 1, 1: 5, 2: 15, 3: 10, 4: 11, 5: 4}, 'End': {0: 6, 1: 7, 2: 20, 3: 15, 4: 12, 5: 8}, 'Tiebreak': {0: 0.3766, 1: 0.050042, 2: 0.628266, 3: 0.984022, 4: 0.909033, 5: 0.531054}})
    
    df = df.sort_values(by='End', ascending=False)
    
    df['overlap'] = df['End'].gt(df['Start'].shift(fill_value=0))
    df['group'] = df['overlap'].eq(False).cumsum()
    
    df = df.sort_values(by='Tiebreak', ascending=False)
    df = df.drop_duplicates(subset='group').drop(columns=['overlap','group'])
    
    print(df)
    

    输出

       Start  End  Tiebreak
    2     15   20  0.628266
    3     10   15  0.984022
    5      4    8  0.531054
    

    【讨论】:

      【解决方案2】:

      您可以按 Start 对值进行排序并计算 End 的 cummax,然后按非重叠间隔形成组并使用 groupby.idxmax 获得最大 Tiebreak:

      keep = (df
         .sort_values(by=['Start', 'End'])
         .assign(max_End=lambda d: d['End'].cummax(),
                 group=lambda d: d['Start'].ge(d['max_End'].shift()).cumsum())
         .groupby('group', sort=False)['Tiebreak'].idxmax()
      )
      
      out = df[df.index.isin(keep)]
      

      输出:

         Start  End  Tiebreak
      2     15   20  0.628266
      3     10   15  0.984022
      5      4    8  0.531054
      
      逻辑如图像

      逻辑是从左到右移动并开始一个新组,然后是“跳跃”(无重叠)。硬线表示间隔(粗体表示最大的抢七),虚线表示 cummax 结束。

      中间体:

         Start  End  Tiebreak  max_End  group
      0      1    6  0.376600        6      0
      5      4    8  0.531054        8      0
      1      5    7  0.050042        8      0
      3     10   15  0.984022       15      1  # 10 ≥ 8
      4     11   12  0.909033       15      1
      2     15   20  0.628266       20      2  # 15 ≥ 15
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-17
        • 1970-01-01
        • 2021-07-16
        • 1970-01-01
        相关资源
        最近更新 更多