【问题标题】:Python Pandas: Count quarterly occurrence from start and end date rangePython Pandas:计算开始和结束日期范围内的季度出现次数
【发布时间】:2017-05-04 21:26:35
【问题描述】:

我有一个不同人的工作数据框,每个工作都有星级和结束时间。我想每四个月计算一次,每个人负责多少工作。我想办法做到这一点,但我确信它的效率非常低(我是熊猫的新手)。当我在完整的数据集(数百人和工作)上运行代码时,计算需要相当长的时间。

这是我目前所拥有的。

#create a data frame
import pandas as pd
import numpy as np

df = pd.DataFrame({'job': pd.Categorical(['job1','job2','job3','job4']),
               'person': pd.Categorical(['p1', 'p1', 'p2','p2']),
               'start': ['2015-01-01', '2015-06-01', '2015-01-01', '2016- 01- 01'],
               'end': ['2015-07-01', '2015- 12-31', '2016-03-01', '2016-12-31']})
df['start'] = pd.to_datetime(df['start'])
df['end'] = pd.to_datetime(df['end'])

这给了我

然后我创建一个新的数据集

bdate = min(df['start'])
edate = max(df['end'])
dates = pd.date_range(bdate, edate, freq='4MS')

people = sorted(set(list(df['person'])))

df2 = pd.DataFrame(np.zeros((len(dates), len(people))), index=dates, columns=people)

for d in pd.date_range(bdate, edate, freq='MS'):
    for p in people:
        contagem = df[(df['person'] == p) &
           (df['start'] <= d) &
           (df['end'] >= d)]
        pos = np.argmin(np.abs(dates - d))
        df2.iloc[pos][p] = len(contagem.index)

df2

我得到了

我确信必须有更好的方法来执行此操作,而不必遍历所有日期和人员。但是怎么做呢?

【问题讨论】:

  • 您的问题表明您希望每季度输出一次,但您的示例代码和输出每三年完成一次。你要哪一个?
  • 意味着每 4 个月一次。修复了问题

标签: python pandas


【解决方案1】:

此答案假设每个工作人员组合都是唯一的。它为每一行创建一个系列,其值等于作业一个扩展日期的索引。然后它每 4 个月重新采样一次(不是季度,而是您的解决方案所描述的)并计算唯一的非 na 出现次数。

def make_date_range(x):
    return pd.Series(index=pd.date_range(x.start.values[0], x.end.values[0], freq='M'), data=x.job.values[0])

# Iterate through each job person combo and make an entry for each month with the job as the value
df1 = df.groupby(['job', 'person']).apply(make_date_range).unstack('person')

# remove outer level from index
df1.index = df1.index.droplevel('job')

# resample each month counting only unique values
df1.resample('4MS').agg(lambda x: len(x[x.notnull()].unique()))

输出

person      p1  p2
2015-01-01   1   1
2015-05-01   2   1
2015-09-01   1   1
2016-01-01   0   2
2016-05-01   0   1
2016-09-01   0   1

这是一个很长的单行解决方案,它遍历每一行并创建一个新的数据帧,并通过pd.concat 将它们全部堆叠在一起,然后重新采样。

pd.concat([pd.DataFrame(index = pd.date_range(tup.start, tup.end, freq='4MS'), 
                        data=[[tup.job]], 
                        columns=[tup.person])  for tup in df.itertuples()])\
  .resample('4MS').count()

还有一个更快的

df1 = pd.melt(df, id_vars=['job', 'person'], value_name='date').set_index('date')

g = df1.groupby([pd.TimeGrouper('4MS'), 'person'])['job']

g.agg('nunique').unstack('person', fill_value=0)

【讨论】:

  • 谢谢!我会坚持第一个解决方案,这对我来说更容易阅读。我需要一段时间才能理解每一步都在做什么,但最终我会弄清楚的。同时,第三种解决方案给出的答案与前两种不同。
  • 我不认为第三个版本是正确的。 date 不是列之一 - 它需要计算索引月份是否介于 startend 之间。
猜你喜欢
  • 1970-01-01
  • 2019-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多