Python Pandas:计算从开始和结束日期范围开始的季度发生

时间:2016-12-19 18:55:05

标签: python pandas

我为不同的人提供了一份工作数据框,每个工作都有明星和结束时间。我想每四个月计算一个人每个人要负责多少工作。我想出去做,但我确定它非常低效(我是熊猫新手)。当我在完整的数据集上运行代码(数百人和工作)时,计算需要很长时间。

这是我到目前为止所拥有的。

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

哪个给了我

enter image description here

然后我用

创建一个新的数据集
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

我得到了

enter image description here

我确信必须有一个更好的方法来做到这一点,而不必遍历所有日期和人。但是如何?

1 个答案:

答案 0 :(得分:0)

这个答案假设每个工作人员组合都是独一无二的。它为每一行创建一个系列,其值等于作业和扩展日期的索引。然后每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)