根据 Pandas DataFrame 中每个项目的开始和结束日期计算每月活动的项目数

Posted

技术标签:

【中文标题】根据 Pandas DataFrame 中每个项目的开始和结束日期计算每月活动的项目数【英文标题】:Counting number of items active in each month, calculated from per-item start and end dates in a Pandas DataFrame 【发布时间】:2018-11-12 15:57:47 【问题描述】:

假设我们有以下DataFrame,它详细说明了错误跟踪系统中的错误:

import pandas as pd

bugs = pd.DataFrame([
    'key': 'ABC-1', 'priority': 'high', 'start': pd.Timestamp(2018, 1, 1), 'end': pd.Timestamp(2018,3,20),
    'key': 'ABC-2', 'priority': 'med',  'start': pd.Timestamp(2018, 1, 2), 'end': pd.Timestamp(2018,1,20),
    'key': 'ABC-3', 'priority': 'high', 'start': pd.Timestamp(2018, 2, 3), 'end': pd.Timestamp(2018,3,20),
    'key': 'ABC-4', 'priority': 'med',  'start': pd.Timestamp(2018, 1, 4), 'end': pd.Timestamp(2018,3,20),
    'key': 'ABC-5', 'priority': 'high', 'start': pd.Timestamp(2018, 2, 5), 'end': pd.Timestamp(2018,2,20),
    'key': 'ABC-6', 'priority': 'med',  'start': pd.Timestamp(2018, 3, 6), 'end': pd.Timestamp(2018,3,20)
], columns=['key', 'priority', 'start', 'end'])

这里,startend 表示第一次发现错误的日期和关闭日期。

我们如何按月计算“打开”错误的数量,按优先级细分?也就是说,输出如下所示:

           High   Med
Month
January       1   2
February      3   1
March         2   2

挑战在于同时考虑“开始”和“结束”日期。因此,在 1 月 5 日打开并在 2 月 3 日关闭的优先级“高”错误应计入 1 月和 2 月的“高”优先级错误,而不是 3 月。以此类推。

【问题讨论】:

两个具有相同优先级、发生在相同月份但不同年份的错误最终会被视为相同吗? 不,这个想法是“每个日历月”,而不是“任何一月” 【参考方案1】:

简单而简短:) 这个想法是每月选择其错误重叠的行。

months = ['January', 'February', 'March', 
          'April']  # of course  you can complete this list

bugs[months] = pd.concat([((bugs['start'].dt.month <= i) & 
                           (i <= bugs['end'].dt.month)).astype(int) 
                          for i in range(1, len(months) + 1)], axis=1)

bugs.groupby('priority')[months].sum()

结果:

          January  February  March  April
priority                                 
high            1         3      2      0
med             2         1      2      0

【讨论】:

这需要我预先定义月份,并将两个不同年份的同一月份计为同一类别。也许我没有很好地解释这个问题,但这并不完全正确。这个想法是为了显示历史趋势,例如“上个月开放了 10 个高优先级缺陷,高于前一个月开放的 5 个”。【参考方案2】:

我正在使用stackresample

from pandas.tseries.offsets import MonthEnd

s=bugs.set_index(['key','priority']).stack() # faltten your dataframe , make start and end in the same row , since we do need a range of date
s=pd.to_datetime(s)+MonthEnd(1) # change the date to same scale , month end , since you need monthly data only 
s=s.reset_index().drop_duplicates(['key',0]) # if the start is same with end , we only need keep one of them. 

s=s.groupby('key').apply(lambda x : x.set_index(0).resample('M').ffill()).reset_index(level=1)    # groupby the key then we resample , adding the value between the start and end prepare for the frequency count  

pd.crosstab(s[0].dt.month,s['priority'])# count the frequency 
Out[149]: 
priority  high  med
0                  
1            1    2
2            3    1
3            2    2

【讨论】:

希望能解释一下这里发生了什么。即使通过它,它也会做很多......的事情。 :-) 似乎在我的测试中有效! @optilude 逐行添加解释:-)【参考方案3】:
from pandas.tseries.offsets import MonthEnd

# Last day of the previous month
bugs['start1'] = bugs.start + MonthEnd(-1)

# The first days of the months that > start1 and <= end
bugs['months']  = bugs[['start1', 'end']].apply(lambda x: tuple(pd.date_range(x[0], x[1], freq='MS')), axis=1, raw=True)

# Create dummy columns
dummies = bugs.months.apply(lambda x: pd.Series(k:1 for k in x)).fillna(0)
bugs = bugs.join(dummies)

# Aggregation
bugs.groupby('priority')[dummies.columns].sum().T


priority    high  med
2018-01-01   1.0  2.0
2018-02-01   3.0  1.0
2018-03-01   2.0  2.0

【讨论】:

【参考方案4】:

基于this answer,我设法完成了以下工作:

def strip_day(timestamp):
    return pd.Timestamp(timestamp.year, timestamp.month, 1)

monthly_bugs = pd.concat([
    pd.DataFrame(index=pd.date_range(strip_day(b.start), strip_day(b.end), freq='MS'), data=[[b.key]], columns=[b.priority])
    for b in bugs.itertuples()
]).resample('MS').count()

不过,我不确定它是否比此处发布的其他一些答案更好(谢谢!)。

【讨论】:

以上是关于根据 Pandas DataFrame 中每个项目的开始和结束日期计算每月活动的项目数的主要内容,如果未能解决你的问题,请参考以下文章

Pandas Dataframe to pivot table - 根据前两列的添加创建新列

Pandas Dataframe 根据列值将值展平到单元格

根据条件在Pandas DataFrame中选择行

Pandas Dataframe - 按照Col A分组并对每个组进行求和[C]重复

如何根据字典中的键/值增加 Python Pandas DataFrame

Pandas DataFrame:根据条件替换列中的所有值