从日期范围为

时间:2017-09-14 21:10:44

标签: python pandas datetime filter aggregate

我有一个订阅的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文档中的示例,但我似乎使用了错误的关键字。有什么想法吗?

2 个答案:

答案 0 :(得分:2)

这是一个有趣的问题,这就是我将如何做到的。不确定性能

编辑:我的第一个答案是错误的,我没有完全阅读问题

# 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)]))

答案 1 :(得分:0)

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

我能找到的最快的解决方案是(感谢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秒。

我从中学到了一些课程:

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