有效地检查任何给定范围中是否存在值

时间:2016-07-05 10:27:10

标签: python datetime pandas dataframe

我有两个pandas DataFrame对象:

  • A包含'start''finish'

  • B'date'

目标是有效地创建一个布尔掩码,指示date是否在[start, finish]区间

天真迭代需要花费太多时间,我想有一种方法可以更快地完成

更新: AB不同的行数

UPDATE2: 样品:

A
    | start     | finish    |
    |-------    |--------   |
    | 1         | 3         |
    | 50        | 83        |
    | 30        | 42        |

B
    | date      | 
    |-------    |
    | 31        | 
    | 20        | 
    | 2.5       |
    | 84        |
    | 1000      |

Output:
            | in_interval | 
            |-------    |
            | True      | 
            | False     | 
            | True      |
            | False     |
            | False     |

P.S。我的数据采用日期时间格式,但我想解决方案与数字

的解决方案没有区别

2 个答案:

答案 0 :(得分:4)

你可以用O(n)复杂度来做到这一点。我们的想法是改变表现形式。在A中,每个间隔存储一行。我建议一个数据帧,每个转换存储一行(即输入一个间隔,留一个间隔)。

A = pd.DataFrame(
    data={
        'start': [1, 50, 30],
        'finish': [3, 83, 42]    
    }
)

starts = pd.DataFrame(data={'start': 1}, index=A.start.tolist())
finishs = pd.DataFrame(data={'finish': -1}, index=A.finish.tolist())
transitions = pd.merge(starts, finishs, how='outer', left_index=True, right_index=True).fillna(0)
transitions

    start  finish
1       1       0
3       0      -1
30      1       0
42      0      -1
50      1       0
83      0      -1

此数据框按日期存储转换类型。现在,我们需要知道每个日期是否在一个区间内。看起来像开头和开头一样右括号。你可以这样做:

transitions['transition'] = (transitions.pop('finish') + transitions.pop('start')).cumsum()
transitions

    transition
1            1
3            0
30           1
42           0
50           1
83           0

这里说:

  • 1,我在间隔
  • 3岁时,我
  • 一般情况下,如果该值严格大于0,则表示间隔时间。
  • 请注意,这会处理重叠间隔

现在您与B数据框合并:

B = pd.DataFrame(
    index=[31, 20, 2.5, 84, 1000]
)

pd.merge(transitions, B, how='outer', left_index=True, right_index=True).fillna(method='ffill').loc[B.index].astype(bool)

       transition
31.0         True
20.0        False
2.5          True
84.0        False
1000.0      False

答案 1 :(得分:1)

IIUC如果日期至少有一个时间间隔,您希望输出为True吗?

apply(lambda)有效吗? (对于大型数据帧,它可能有点长,因为它遍历B行。如果是,你可以试试这个:

def in_range(date,start,finish):
    return (True in ((start < date) & (date < finish)).unique())

B.date.apply(lambda x: in_range(x,A.start,A.finish))

输出:

0     True
1    False
2     True
3    False
4    False
编辑:MaxU的答案实际上更好。以下是10 000行数据帧(A和B)的计时器:

%timeit B2.date.apply(lambda x: in_range(x,A2.start,A2.finish))
1 loop, best of 3: 9.82 s per loop

%timeit B2.date.apply(lambda x: ((x >= A2.start) & (x <= A2.finish)).any())
1 loop, best of 3: 7.31 s per loop