如何计算数据框中“开始”和“结束”列包含特定日期的行数?

2024-10-01 00:28:42 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个看起来像

        Category   Start       End
0          a     2014-12-01 2015-06-01
1          a     2015-10-02 2015-10-16
2          b     2015-10-01 2016-04-01
3          b     2015-10-01 2015-12-01
4          c     2015-06-01 2015-08-01

对于日期范围d中的每个日期,我想查找开始<;=日期<;=结束,然后我想数一数有多少不同的类别

最有效的方法是什么

import pandas as pd
import datetime

d = pd.date_range(start='2015-01-01', end='2015-12-31', freq='D')
s = {'Start':[datetime.date(2014,12,1), datetime.date(2015,10,2), datetime.date(2015,10,1), datetime.date(2015,10,1), datetime.date(2015,6,1)]}
e = {'End':[datetime.date(2015,6,1), datetime.date(2015,10,16), datetime.date(2016,4,1), datetime.date(2015,12,1), datetime.date(2015,8,1)]}
c = {'Category': ['a', 'a', 'b', 'b', 'c']}
c.update(s)
c.update(e)
df = pd.DataFrame(c)
df_count = pd.DataFrame(index=d, col['count']

for date in d:
   count_occourances = len(set(df.loc[(df['Start'] <= date) & (df['End'] >= date), 'Category']))
   # Some saving to keep track on count for this particular date e.g.
   df_count.loc[date, 'count'] = count_occourances

然后,预期的输出将 df_计数:

        Category   Count      
2015-01-01          1    
2015-01-02          1    
2015-01-03          1    
2015-01-04          1     
2015-01-05          1     
    .
    .
    .
2015-05-31          1
2015-06-01          2
2015-06-02          1
2015-06-03          1
    .
    .
    .
2015-07-31          1
2015-08-01          1
2015-08-02          0
    .
    .
    .
2015-09-30          0
2015-10-01          2
2015-10-02          3
2015-10-03          3
    .
    .
    .
2015-10-15          3
2015-10-16          3
2015-10-17          2
    .
    .
    .
2015-12-01          2
2015-12-02          1
    .
    .
    .
2015-12-31          1

Tags: importltdataframedffordatetimedatecount
3条回答

您可以添加一个helper列来计算每行上日期范围d和日期范围StartEnd之间的重叠天数。然后,在这些重叠的日子对行进行筛选>;0最后,计算筛选行上不同的Category数:

  1. 将日期转换为日期时间格式(如果尚未使用日期时间格式):
df['Start'] = pd.to_datetime(df['Start'])
df['End'] = pd.to_datetime(df['End'])
  1. 在每一行上,通过pd.date_range()创建StartEnd日期之间的日期范围。然后,通过numpy函数^{}获得日期范围d的重叠日期范围(以获得两个日期范围之间的交集)。通过获取交叉点的长度来获取重叠天数
df['overlap_days'] = df.apply(lambda x: len(np.intersect1d(pd.date_range(start=x['Start'], end=x['End'], freq='D'), d)), axis=1)

结果:

print(df)

  Category      Start        End  overlap_days
0        a 2014-12-01 2015-06-01           152
1        a 2015-10-02 2015-10-16            15
2        b 2015-10-01 2016-04-01            92
3        b 2015-10-01 2015-12-01            62
4        c 2015-06-01 2015-08-01            62
  1. 对日期重叠的行进行筛选>;按.loc计算0,并按^{}计算筛选行上不同的Category数,如下所示:
df.loc[df['overlap_days'] > 0, 'Category'].nunique()

输出:

3

性能考虑因素

此解决方案在引擎盖下使用快速矢量化Numpy操作。尽管它使用apply()循环,但与使用Python循环和/或列表理解的逻辑相比,它仍然运行得更快

基准测试表明,该解决方案的运行速度为2.57ms,而其他解决方案的运行速度为41.7ms、142ms和288ms

你是说:

print([((df['Start'] <= date) & (date <= df['End'])).sum() for date in d])

输出:

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

如果我理解正确,您可以通过以下方式实现:

将日期转换为日期时间数据类型:

df = df.assign(Start = df.Start.transform(pd.to_datetime), 
               End = df.End.transform(pd.to_datetime))

创建intervalindex:

intervals = pd.IntervalIndex.from_arrays(df.Start, df.End, closed='both')

创建字典,将唯一计数与各个日期配对:

  counter = {}
  category = df.Category.array
  for dates in d:
      booleans = intervals.contains(dates)
      count = category[booleans].unique().size
      counter[dates] = count

然后可以创建一个系列:

series = pd.Series(counter)
series.index.name = 'Category'
series.name = 'counter'

series.head()

Category
2015-01-01    1
2015-01-02    1
2015-01-03    1
2015-01-04    1
2015-01-05    1
Name: counter, dtype: int64

既然这些都是间隔,假设我的问题是对的,你应该得到显著的加速

相关问题 更多 >