在熊猫中找到间隔的交集

时间:2018-04-24 18:21:18

标签: python pandas merge

我有两个数据帧

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
    [...]

4 个答案:

答案 0 :(得分:1)

不确定这是否是执行此操作的最佳方式,但您可以reindexjoingroupbyagg获取您的间隔,例如:

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

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没有标准的方法只有连续的值(àlaitertools.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

这假定两个数据帧的范围相同,或者您需要处理NaN所带来的join()

答案 1 :(得分:1)

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

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.]})

首先,我要创建Start的{​​{1}}和Stop列:

df_out

然后在名为df_out = pd.DataFrame({'Start': sorted(set(dfa['Start'])|set(dfb['Start'])), 'Stop': sorted(set(dfa['Stop'])|set(dfb['Stop']))}) (和dfa)的列中获取与正确范围(开始,停止)关联的dfb(和Value_dfA)的值,我愿意:

Value_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']]

答案 2 :(得分:1)

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

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

df = df_a.append(df_b)

根据df列对start进行排序。

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

答案 3 :(得分:0)

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

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