【问题标题】:Create a Pandas daily aggregate time series from a DataFrame with date ranges从具有日期范围的 DataFrame 创建 Pandas 每日聚合时间序列
【发布时间】:2018-02-23 23:30:15
【问题描述】:

我有一个订阅的 Pandas DataFrame,每个订阅都有一个开始日期时间(时间戳)和一个可选的结束日期时间(如果它们被取消的话)。

为简单起见,我根据开始和结束日期时间(时间戳)为日期创建了字符串列(例如“20170901”)。它看起来像这样:

df = pd.DataFrame([('20170511', None), ('20170514', '20170613'), ('20170901', None), ...], columns=["sd", "ed"])

最终结果应该是在一个范围内的任何给定日期有多少订阅处于活动状态的时间序列。

为此,我为某个范围内的所有日期创建了一个索引:

days = df.groupby(["sd"])["sd"].count()

我能够通过循环创建我感兴趣的内容,每个循环对整个 DataFrame df 执行一个查询。

count_by_day = pd.DataFrame([ len(df.loc[(df.sd <= i) & (df.ed.isnull() | (df.ed > i))]) for i in days.index], index=days.index)

请注意,我在原始数据集中每天都有值,因此没有间隙。我确信可以改进日期范围。

实际的问题是:对于具有数千行的大型初始数据集 df,是否有一种有效的方法来计算它?看来我使用的方法在复杂性上是二次方的。我也试过 df.query() 但它比 Pythonic 过滤器慢 66% 并且不会改变复杂性。

我尝试在 Pandas 文档中搜索示例,但我似乎使用了错误的关键字。有什么想法吗?

【问题讨论】:

    标签: python pandas datetime filter aggregate


    【解决方案1】:

    这是一个有趣的问题,我会这样做。不确定性能

    编辑:我的第一个答案不正确,我没有完整阅读问题

    # Initial data, columns as Timestamps
    df = pd.DataFrame([('20170511', None), ('20170514', '20170613'), ('20170901', None)], columns=["sd", "ed"])
    df['sd'] = pd.DatetimeIndex(df.sd)
    df['ed'] = pd.DatetimeIndex(df.ed)
    
    # Range input and related index
    beg = pd.Timestamp('2017-05-15')
    end = pd.Timestamp('2017-09-15')
    idx = pd.DatetimeIndex(start=beg, end=end, freq='D')
    
    # We filter data for records out of the range and then clip the 
    # the subscriptions start/end to the range bounds.
    fdf = df[(df.sd <= beg) | ((df.ed >= end) | (pd.isnull(df.ed)))]
    fdf['ed'].fillna(end, inplace=True)
    fdf['ps'] = fdf.sd.apply(lambda x: max(x, beg))
    fdf['pe'] = fdf.ed.apply(lambda x: min(x, end))
    
    # We run a conditional count
    idx.to_series().apply(lambda x: len(fdf[(fdf.ps<=x) & (fdf.pe >=x)]))
    

    【讨论】:

    • 谢谢!一些代码模式比我的问题中的代码好得多。
    【解决方案2】:

    好的,经过大量研究、摆弄和尝试,我正在回答我自己的问题。我可能仍然缺少一个明显的解决方案,但它可能会有所帮助。

    迄今为止我能找到的最快的解决方案是(感谢 Alex 提供的一些不错的代码模式):

    # Start with test data from question
    df = pd.DataFrame([('20170511', None), ('20170514', '20170613'),
                       ('20170901', None), ...], columns=['sd', 'ed'])
    
    # Convert to datetime columns
    df['sd'] = pd.DatetimeIndex(df['sd'])
    df['ed'] = pd.DatetimeIndex(df['ed'])
    df.ed.fillna(df.sd.max(), inplace=True)
    
    # Note: In my real data I have timestamps - I convert them like this:
    #df['sd'] = pd.to_datetime(df['start_date'], unit='s').apply(lambda x: x.date())
    
    # Set and sort multi-index to enable slices
    df = df.set_index(['sd', 'ed'], drop=False)
    df.sort_index(inplace=True)
    
    # Compute the active counts by day in range
    di = pd.DatetimeIndex(start=df.sd.min(), end=df.sd.max(), freq='D')
    count_by_day = di.to_series().apply(lambda i: len(df.loc[
               (slice(None, i.date()), slice(i.date(), None)), :]))
    

    在我的真实数据集中(df 有 >10K 行,日期范围约为一年),这比问题中的代码快两倍,大约 1.5 秒。

    以下是我学到的一些经验:

    • 使用日期范围的计数器创建一个系列并使用df.applydf.itertuples 迭代数据集df,并且递增计数器要慢得多。奇怪的是,applyitertuples 慢。想都别想iterrows
    • 我的数据集的每一行都有一个 product_id,因此过滤每个产品的数据集并对过滤结果(每个产品)运行计算比将 product_id 添加到多索引并在该级别上切片的速度快两倍太
    • 构建活动天数的中间系列(从遍历 df 中的每一行并将活动范围内的每个日期添加到系列中)然后按日期分组要慢得多。
    • 在具有多索引的 df 上运行问题中的代码不会改变性能。
    • 在具有有限列集(我的真实数据集有 22 列)的 df 上运行问题中的代码并没有改变性能。
    • 我正在查看 pd.crosstabpd.Period,但我无法得到任何工作
    • Pandas 非常棒,想要超越它真的很难(尤其是 Python 中的非矢量化)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-13
      • 2018-09-16
      • 1970-01-01
      • 2019-05-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多