【问题标题】:Filtering Pandas dataframe on thousands of conditions在数千个条件下过滤 Pandas 数据框
【发布时间】:2021-09-26 15:06:21
【问题描述】:

我目前有一个如下所示的元组列表:

time_constraints = [
    ('001', '01/01/2020 10:00 AM', '01/01/2020 11:00 AM'),
    ('001', '01/03/2020 05:00 AM', '01/03/2020 06:00 AM'),
    ...
    ('999', '01/07/2020 07:00 AM', '01/07/2020 08:00 AM')
]

地点:

  • 每个元组包含一个id、lower_bound和upper_bound
  • 对于给定的id,没有任何时间范围重叠
  • len(time_constraints) 可以是 10^4 到 10^5 的数量级。

我的目标是快速有效地过滤相对较大(数百万行)的 Pandas 数据帧 (df),以仅包含与 id 列匹配且介于指定 lower_bound 和 @987654333 之间的行@次(含)。

我目前的计划是这样做:

import pandas as pd

output = []
for i, lower, upper in time_constraints:
    indices = list(df.loc[(df['id'] == i) & (df['timestamp'] >= lower) & (df['timestamp'] <= upper), ].index)
    output.extend(indices)

output_df = df.loc[df.index.isin(output), ].copy()

但是,使用 for 循环并不理想。我想知道是否有更好的解决方案(最好是矢量化的)使用 Pandas 或 NumPy 数组更快。

已编辑:

这是df的一些示例行:

id timestamp
1 01/01/2020 9:56 AM
1 01/01/2020 10:32 AM
1 01/01/2020 10:36 AM
2 01/01/2020 9:42 AM
2 01/01/2020 9:57 AM
2 01/01/2020 10:02 AM

【问题讨论】:

  • 非常有趣的问题!我昨天在here 上问过类似的问题,但还没有得到答案。
  • 为什么 df 包含与 time_constraints 相同的字段?我猜 df 只有一个日期时间字段?请使用 df 示例更新您的帖子。
  • @Corralien 更新了帖子,现在应该更有意义了
  • @qualitaetsmuell。不要犹豫,在 SO 上发布您的问题并在此处发表评论以通知我。
  • 嘿@Co​​rralien,感谢您的帮助!由于 CR 上的优秀人员,我设法将运行时间减少了 5 倍。可以在 here 找到代码的更新版本。您认为还有进一步改进的空间吗?如果是这样,我很乐意接受您的帮助并在 SO 上发布问题。

标签: python pandas numpy for-loop vectorization


【解决方案1】:

我已经回答了类似的case。

为了测试,我使用了 100,000 个约束 (tc) 和 5,000,000 条记录 (df)。 是否如你所愿

>>> df
          id           timestamp
0        565 2020-08-16 05:40:55
1        477 2020-04-05 22:21:40
2        299 2020-02-22 04:54:34
3        108 2020-08-17 23:54:02
4        041 2020-09-10 10:01:31
...      ...                 ...
4999995  892 2020-12-27 16:16:35
4999996  373 2020-08-29 05:44:34
4999997  659 2020-05-23 20:48:15
4999998  858 2020-09-08 22:58:20
4999999  710 2020-04-10 08:03:14

[5000000 rows x 2 columns]


>>> tc
        id         lower_bound         upper_bound
0      000 2020-01-01 00:00:00 2020-01-04 14:00:00
1      000 2020-01-04 15:00:00 2020-01-08 05:00:00
2      000 2020-01-08 06:00:00 2020-01-11 20:00:00
3      000 2020-01-11 21:00:00 2020-01-15 11:00:00
4      000 2020-01-15 12:00:00 2020-01-19 02:00:00
...    ...                 ...                 ...
99995  999 2020-12-10 09:00:00 2020-12-13 23:00:00
99996  999 2020-12-14 00:00:00 2020-12-17 14:00:00
99997  999 2020-12-17 15:00:00 2020-12-21 05:00:00
99998  999 2020-12-21 06:00:00 2020-12-24 20:00:00
99999  999 2020-12-24 21:00:00 2020-12-28 11:00:00

[100000 rows x 3 columns]
# from tqdm import tqdm
from itertools import chain

# df = pd.DataFrame(data, columns=['id', 'timestamp'])
tc = pd.DataFrame(time_constraints, columns=['id', 'lower_bound', 'upper_bound'])
g1 = df.groupby('id')
g2 = tc.groupby('id')

indexes = []
# for id_ in tqdm(tc['id'].unique()):
for id_ in tc['id'].unique():
    df1 = g1.get_group(id_)
    df2 = g2.get_group(id_)

    ii = pd.IntervalIndex.from_tuples(list(zip(df2['lower_bound'], 
                                               df2['upper_bound'])),
                                      closed='both')
    indexes.append(pd.cut(df1['timestamp'], bins=ii).dropna().index)

out = df.loc[chain.from_iterable(indexes)]

性能:

100%|█████████████████████████████████████████████████| 1000/1000 [00:17<00:00, 58.40it/s]

输出结果:

>>> out
          id           timestamp
1326     000 2020-11-10 05:51:00
1685     000 2020-10-07 03:12:48
2151     000 2020-05-08 11:11:18
2246     000 2020-07-06 07:36:57
3995     000 2020-02-02 04:39:11
...      ...                 ...
4996406  999 2020-02-19 15:27:06
4996684  999 2020-02-05 11:13:56
4997408  999 2020-07-09 09:31:31
4997896  999 2020-04-10 03:26:13
4999674  999 2020-04-21 22:57:04

[4942976 rows x 2 columns]  # 57024 records filtered

【讨论】:

  • 自己测试了一下,很好的解决问题
【解决方案2】:

您也可以使用布尔索引,同样:

output_df = df[pd.Series(list(zip(df['id'], 
               df['lower_bound'],
               df['upper_bound']))).isin(time_constraints)]

zip 函数从每一列创建tuples,然后将其与您的元组列表进行比较。 pd.Series 用于创建布尔系列。

【讨论】:

  • 感谢您的回答。我稍微更新了这个问题,所以我认为.isin() 解决方案不会起作用。我正在寻找time_constraints 中的id 与df 匹配但df 的时间戳介于lower_bound 和upper_bound(含)之间的情况。
猜你喜欢
  • 2020-08-29
  • 1970-01-01
  • 1970-01-01
  • 2017-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-07
  • 1970-01-01
相关资源
最近更新 更多