给出以下两个表示范围的数据框:
df1 =
start end
0 200 300
1 600 900
2 950 1050
df2 =
start end
0 350 550
1 650 800
2 900 1100
它们可以这样表示:
df1 [200 300] [600 900] [950 1050]
df2 [350 550] [650 800] [900 1100]
我的任务是确定df1
和df2
范围之间的四种不同类型的关系:
df2
的df1
子集
df2 [650 800]
的df1 [600 900]
子集df2
的df1
超集
df2 [900 1100]
的{{1}}超集df1 [950 1050]
之后的df2
(最近的邻居,不包括子集/超集)
df1
在df2 [350 550]
之后df1 [200 300]
在df2 [900 1100]
之后df1 [600 900]
在df2
之前(最近的邻居,不包括子集/超集)
df1
之前df2 [350 550]
df1 [600 900]
之前df2 [650 800]
我正在尝试使用从this answer中学到的df1 [950 1050]
,但是由于超集/子集关系增加了复杂性,因此它无法正常工作,例如:
merge_asof()
输出:
# Create "before" condition
df_before = pd.merge_asof(
df2.rename(columns={col:f'before_{col}' for col in df2.columns}).sort_values('before_end'),
df1.assign(before_end=lambda x: x['end']).sort_values('before_end'),
on='before_end',
direction='forward'
).query('end > before_end')
print(df_before)
目标输出:
before_start before_end start end
0 350 550 600.0 900.0
1 650 800 600.0 900.0
问题是
before_start before_end start end
0 350 550 600.0 900.0
1 650 800 950.0 1050.0
在pd.merge_asof(
df2.rename(columns={col:f'before_{col}' for col in df2.columns}).sort_values('before_end'),
df1.assign(before_end=lambda x: x['end']).sort_values('before_end'),
on='before_end',
direction='forward'
)
中找到800之后最接近的df1.end
,即df2 [650 800]
:
df1 [600 900]
是否有可能根据特定条件进行 before_start before_end start end
0 350 550 600.0 900.0
1 650 800 600.0 900.0
2 900 1100 NaN NaN
查找最近的值,例如,只有在该范围内的merge_asof()
大于800时才“找到最近的df1.end
( 950)”?如此复杂,也许还有另一个更适合此任务的功能?
注意:
df1.start
中的范围可以相互重叠,但绝不能完全相同。df1
中的范围可以相互重叠,但绝不能完全相同。df2
和df1
分别有20万多行。df2
和df1
的行数不同。df2
的,因此df1
中的每一行只需要一个匹配项,每行最多有四个关系。鉴于以上提供的数据,合并回df1
后,最终输出将如下所示:df1 =
df1
答案 0 :(得分:1)
可以使用pd.merge_asof
查找之前和之后的选项。
before_df = pd.merge_asof(df1, df2, left_on='start', right_on='end', suffixes=['', '_before'])
before_df
# start end start_before end_before
# 0 200 300 NaN NaN
# 1 600 900 350.0 550.0
# 2 950 1050 650.0 800.0
after_df = pd.merge_asof(df2, df1, left_on='start', right_on='end', suffixes=['_after', ''])
# start_after end_after start end
# 0 350 550 200 300
# 1 650 800 200 300
# 2 900 1100 600 900
但是要使其工作或进行子集和超集计算并不容易。对于那些人,我会尝试一种可以一次通过的算法。
def range_intersect(lh_ranges, rh_ranges):
all_ranges = sorted(
[(b, e, 'lh') for b, e in lh_ranges] +
[(b, e, 'rh') for b, e in rh_ranges]
)
res = []
max_b, max_e = None, None
for b, e, which in all_ranges:
if which == 'rh':
if max_e is None or e > max_e:
max_b, max_e = b, e
elif max_e is not None and e <= max_e:
res.append((b, e, max_b, max_e))
return res
这将找到lh
中元素的子集rh
中的元素。要查找超集,可以反向运行它。为简单起见,它使用范围列表代替DataFrame
。转换很简单。
lh = df1.to_dict('split')['data']
rh = df2.to_dict('split')['data']
lh
# [[200, 300], [600, 900], [950, 1050]]
rh
# [[350, 550], [650, 800], [900, 1100]]
在那之后,您想要的结果DataFrame
仅几处合并。
# Compute supersets, then run in reverse to get the subsets.
superset_df = pd.DataFrame(range_intersect(lh, rh), columns=['start', 'end', 'start_superset', 'end_superset'])
subset_df = pd.DataFrame(range_intersect(rh, lh), columns=['start_subset', 'end_subset', 'start', 'end'])
# Merge all the results together.
result = df1.merge(subset_df, how='left').merge(superset_df, how='left').merge(before_df, how='left').merge(after_df, how='left')
# The reversed operations, after and subset, can have many matches in df1.
result.drop_duplicates(['start', 'end'])
# start end start_subset end_subset start_superset end_superset start_before end_before start_after end_after
# 0 200 300 NaN NaN NaN NaN NaN NaN 350.0 550.0
# 2 600 900 650.0 800.0 NaN NaN 350.0 550.0 900.0 1100.0
# 3 950 1050 NaN NaN 900.0 1100.0 650.0 800.0 NaN NaN