【问题标题】:Efficiently check if value is present in any of given ranges有效地检查值是否存在于任何给定范围内
【发布时间】:2016-11-07 03:36:39
【问题描述】:

我有两个 pandas DataFrame 对象:

  • A 包含 'start''finish'

  • B 具有列'date'

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

天真的迭代花费了太多时间,我想有一种方法可以更快地做到这一点

更新: AB不同的行数

更新2: 示例:

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     |

附:我有日期时间格式的数据,但我想解决方案不会与数字不同

【问题讨论】:

  • 所以你要df[(df['date'] > df['start']) & (df['date'] < df['finish'])]
  • 你想在元素方面做到这一点(即你需要相同大小的数据框)吗?
  • 不,这是两个独立的数据帧,行数不同,所以我认为这个行不通
  • 那么,如果A有M行,B有N行,输出会有NxM行?
  • 为简单起见,提供了带有数字而不是日期时间的示例输出

标签: python datetime pandas dataframe


【解决方案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

【讨论】:

  • 我觉得你可以稍微简化一下:B.date.apply(lambda x: ((x &gt;= a.start) &amp; (x &lt;= a.finish)).any())
  • 谢谢,我使用了这种方法,这在我的 100mb 数据集上花费了太多时间
【解决方案2】:

你可以用 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

【讨论】:

  • 我真的很喜欢你的解决方案!有没有办法将finish 视为封闭区间界限? IE。如果我们将值 83 添加到 B - 它不会被视为属于 [50, 83] 范围 - 你知道如何处理吗?
  • 是的,在第一次合并中,索引按升序排序。对后代顺序应用相同的逻辑。最后,您有一个针对开闭区间的转换和一个针对开闭区间的转换。封闭封闭的意思是“他们中的任何一个”。
猜你喜欢
  • 1970-01-01
  • 2017-05-28
  • 2014-12-05
  • 1970-01-01
  • 1970-01-01
  • 2011-08-28
  • 1970-01-01
  • 2017-11-01
  • 2018-06-07
相关资源
最近更新 更多