【问题标题】:finding intersection of intervals in pandas在熊猫中找到区间的交点
【发布时间】:2018-10-05 02:46:09
【问题描述】:

我有两个数据框

df_a=

     Start Stop Value
    0  0     100  0.0
    1  101   200  1.0
    2  201  1000  0.0

df_b=
       Start Stop Value
    0  0     50 0.0
    1  51   300 1.0
    2  301 1000  0.0

我想生成一个DataFrame,其中包含由StartStop 标识的区间,其中df_a 和df_b 中的值相同。对于我想存储的每个间隔:如果Value 相同,那么df_adf_b 中的值是哪个。 期望的输出:

df_out=
  Start Stop SameValue Value_dfA Value_dfB
      0    50    1          0       0
      51   100   0          0       1
      101  200   1          1       1
      201  300   0          0       1
    [...]

【问题讨论】:

  • 您是如何开始使用102 的?
  • 这是101之后的下一个值(上一个区间的上限)。
  • 在给定 2 个df_adf_b 的情况下,上一个区间的上限不只是100
  • 也许pandas.merge_asof 可能对这个问题感兴趣? (pandas.pydata.org/pandas-docs/stable/generated/…)。
  • @Guybrush,这没有意义,但不要担心 OP 已经澄清了。

标签: python pandas merge


【解决方案1】:

不确定这是否是最好的方法,但您可以通过reindexjoingroupbyagg 获取您的间隔,例如:

使用reindex()padding 值扩展每个df,以便索引是范围内的每个值(StartStop):

In []:
df_a_expanded = df_a.set_index('Start').reindex(range(max(df_a['Stop'])+1)).fillna(method='pad')
df_a_expanded

Out[]:
         Stop  Value
Start               
0       100.0    0.0
1       100.0    0.0
2       100.0    0.0
3       100.0    0.0
4       100.0    0.0
...
997    1000.0    0.0
998    1000.0    0.0
999    1000.0    0.0
1000   1000.0    0.0

[1001 rows x 2 columns]

In []:
df_b_expanded = df_b.set_index('Start').reindex(range(max(df_b['Stop'])+1)).fillna(method='pad')

加入两个扩展dfs

In []:
df = df_a_expanded.join(df_b_expanded, lsuffix='_dfA', rsuffix='_dfB').reset_index()
df

Out[]:
      Start  Stop_dfA  Value_dfA  Stop_dfB  Value_dfB
0         0     100.0        0.0      50.0        0.0
1         1     100.0        0.0      50.0        0.0
2         2     100.0        0.0      50.0        0.0
3         3     100.0        0.0      50.0        0.0
4         4     100.0        0.0      50.0        0.0
...

注意:您可以忽略 Stop 列,并且可以在上一步中删除它们。

没有标准的方法来groupby 只有连续值(à la itertools.groupby),所以诉诸cumsum() hack:

In []:
groups = (df[['Value_dfA', 'Value_dfB']] != df[['Value_dfA', 'Value_dfB']].shift()).any(axis=1).cumsum()
g = df.groupby([groups, 'Value_dfA', 'Value_dfB'], as_index=False)

现在您可以通过使用minmax 聚合组来获得所需的结果:

In []:
df_out = g['Start'].agg({'Start': 'min', 'Stop': 'max'})
df_out

Out[]:
   Value_dfA  Value_dfB  Start  Stop
0        0.0        0.0      0    50
1        0.0        1.0     51   100
2        1.0        1.0    101   200
3        0.0        1.0    201   300
4        0.0        0.0    301  1000

现在您只需添加 SameValue 列,如果需要,对列进行排序以获得您想要的确切输出:

In []:
df_out['SameValue'] = (df_out['Value_dfA'] == df_out['Value_dfB'])*1
df_out[['Start', 'Stop', 'SameValue', 'Value_dfA', 'Value_dfB']]

Out[]:
   Start  Stop  SameValue  Value_dfA  Value_dfB
0      0    50          1        0.0        0.0
1     51   100          0        0.0        1.0
2    101   200          1        1.0        1.0
3    201   300          0        0.0        1.0
4    301  1000          1        0.0        0.0

这假设两个数据帧的范围是相同的,否则您将需要处理NaNs,您将获得join()

【讨论】:

    【解决方案2】:

    我找到了一种方法,但不确定它是否最有效。你有输入数据:

    import pandas as pd
    dfa = pd.DataFrame({'Start': [0, 101, 201], 'Stop': [100, 200, 1000], 'Value': [0., 1., 0.]})
    dfb = pd.DataFrame({'Start': [0, 51, 301], 'Stop': [50, 300, 1000], 'Value': [0., 1., 0.]})
    

    首先,我将创建 StartStop 的列 df_out

    df_out = pd.DataFrame({'Start': sorted(set(dfa['Start'])|set(dfb['Start'])), 
                           'Stop': sorted(set(dfa['Stop'])|set(dfb['Stop']))})
    

    然后要在名为Value_dfA(和Value_dfB)的列中获取与正确范围(开始,停止)相关联的dfa(和dfb)的值,我会这样做:

    df_out['Value_dfA'] = df_out['Start'].apply(lambda x: dfa['Value'][dfa['Start'] <= x].iloc[-1])
    df_out['Value_dfB'] = df_out['Start'].apply(lambda x: dfb['Value'][dfb['Start'] <= x].iloc[-1])
    

    要获取SameValue 列,请执行以下操作:

    df_out['SameValue'] = df_out.apply(lambda x: 1 if x['Value_dfA'] == x['Value_dfB'] else 0,axis=1)
    

    如果重要,您可以使用以下命令重新排序列:

    df_out = df_out[['Start', 'Stop', 'SameValue', 'Value_dfA', 'Value_dfB']]
    

    然后你的输出是

       Start  Stop  SameValue  Value_dfA  Value_dfB
    0      0    50          1        0.0        0.0
    1     51   100          0        0.0        1.0
    2    101   200          1        1.0        1.0
    3    201   300          0        0.0        1.0
    4    301  1000          1        0.0        0.0
    

    【讨论】:

    • @AChampion 如果您只是在dfb['Stop'] 中将 300 替换为 200,那么确实存在问题,但是您对于 dfb 中的 201 到 300 的间隔没有任何价值,因为下一个开始是在 301 . 在这种情况下,是的,它不起作用。但是如果间隔是连续的,那么您还必须在 dfb['Start'] 中将 301 替换为 201,在这种情况下,它可以工作:)
    • 你说得对,我很抱歉,忽略。已删除评论。
    • 实际上,我认为您在我的回答中指出这一点是正确的,以防其他人的数据缺少间隔。我想我们可以添加一些行来填充缺失的间隔以防止中断
    【解决方案3】:

    我有O(nlog(n)) 解决方案,其中ndf_adf_b 的行之和。事情是这样的:

    将两个数据框的value 列分别重命名为value_avalue_b。接下来将df_b 附加到df_a

    df = df_a.append(df_b)
    

    根据start 列对df 进行排序。

    df = df.sort_values('start')
    

    生成的数据框如下所示:

     start  stop    value_a value_b
    0   0   100     0.0      NaN
    0   0   50      NaN      0.0
    1   51  300     NaN      1.0
    1   101 200     1.0      NaN
    2   201 1000    0.0      NaN
    2   301 1000    NaN      0.0
    

    正向填充缺失值:

    df = df.fillna(method='ffill')
    

    计算same_value 列:

    df['same_value'] = df['value_a'] == df['value_b']
    

    重新计算stop 列:

    df.stop = df.start.shift(-1)
    

    你会得到你想要的数据框(除了很容易修复的第一行和最后一行):

     start   stop value_a value_b   same_value
    0   0     0.0   0.0   NaN     False
    0   0     51.0  0.0   0.0     True
    1   51    101.0 0.0   1.0     False
    1   101   201.0 1.0   1.0     True
    2   201   301.0 0.0   1.0     False
    2   301   NaN   0.0   0.0     True
    

    【讨论】:

      【解决方案4】:

      这是一个非常快速地计算重叠间隔的答案(它回答了标题中的问题):

      from io import StringIO    
      import pandas as pd    
      from ncls import NCLS    
      
      c1 = StringIO("""Start Stop Value
      0     100  0.0
      101   200  1.0
      201  1000  0.0""")
      
      c2 = StringIO("""Start Stop Value
      0     50 0.0
      51   300 1.0
      301 1000  0.0""")
      
      df1 = pd.read_table(c1, sep="\s+")
      df2 = pd.read_table(c2, sep="\s+")
      
      ncls = NCLS(df1.Start.values, df1.Stop.values, df1.index.values)
      
      x1, x2 = ncls.all_overlaps_both(df2.Start.values, df2.Stop.values, df2.index.values)
      
      df1 = df1.reindex(x2).reset_index(drop=True)
      df2 = df2.reindex(x1).reset_index(drop=True)
      
      # print(df1)
      # print(df2)
      
      df = df1.join(df2, rsuffix="2")
      
      print(df)
      #    Start  Stop  Value  Start2  Stop2  Value2
      # 0      0   100    0.0       0     50     0.0
      # 1      0   100    0.0      51    300     1.0
      # 2    101   200    1.0      51    300     1.0
      # 3    201  1000    0.0      51    300     1.0
      # 4    201  1000    0.0     301   1000     0.0
      

      有了这个最终的 df,应该很容易得到你需要的结果(但它留给读者作为练习)。

      区间重叠数据结构见NCLS

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-06
        • 2016-02-27
        • 2021-07-04
        相关资源
        最近更新 更多