在Pandas中构建条件时间序列数据透视表

时间:2015-08-02 22:07:41

标签: pandas

我有一个这样的数据框:

df = pd.DataFrame(data, columns=['dev_date', 'test_date', 'done_date'])

列是日期,表示工作项首次被接受到项目中的开发/测试/完成的日期(还有一些其他列,但它们对问题并不重要)。

我想从中构建一个累积流程图,它基本上是一个区域图表,显示每天在项目生命周期中每个阶段接受的项目总数。

最基本的算法相对简单:

  1. 对于从最低dev_date到最高done_date ...
  2. 的每个日期
  3. ...为每列(dev_datetest_datedone_date)...
  4. ...计算记录日期< =循环日期
  5. 的项目数

    输出应该是这样的:

    date        dev test done
    ----------  --- ---- ----
    2015-01-01  1   0     0
    2015-01-02  2   1     0    
    2015-01-03  3   2     1
    2015-01-04  3   2     1
    2015-01-05  4   3     1
    2015-01-06  5   3     1
    

    实际上,我可以编写一些相当天真的代码来实现这一点,循环遍历日期,然后循环遍历列,执行每个系列的切片和计数。

    然而,感觉应该可以使用pivot_table()或其他更优雅的方法(可能在日期重新编制索引然后使用groupBy()执行此操作)。

    我对此感兴趣主要是为了让Pandas更好一点,因为感觉有一些性能和代码质量可以获得。

    有什么建议吗?

1 个答案:

答案 0 :(得分:4)

您可以使用value_counts来计算每列的每个日期的出现次数。例如,

In [385]: df['dev_date']
Out[385]: 
0   2000-01-04
1   2000-01-10
2   2000-01-10
3   2000-01-09
4   2000-01-10
5   2000-01-05
6   2000-01-08
7   2000-01-07
8   2000-01-10
9   2000-01-04
Name: dev_date, dtype: datetime64[ns]

In [386]: df['dev_date'].value_counts()
Out[386]: 
2000-01-10    4
2000-01-04    2
2000-01-08    1
2000-01-09    1
2000-01-07    1
2000-01-05    1
Name: dev_date, dtype: int64

然后您可以使用pd.concat将这些系列连接到一个DataFrame中:

In [387]: result = pd.concat({col:df[col].value_counts() for col in df}, axis=1)

In [388]: result
Out[388]: 
            dev_date  test_date  done_date
2000-01-04         2        NaN        NaN
2000-01-05         1        NaN        NaN
2000-01-06       NaN          1        NaN
2000-01-07         1          2        NaN
2000-01-08         1        NaN        NaN
2000-01-09         1        NaN          1
2000-01-10         4        NaN          1
2000-01-11       NaN          2        NaN
2000-01-12       NaN          1        NaN
2000-01-14       NaN        NaN          1
2000-01-15       NaN        NaN          1
2000-01-16       NaN          2        NaN
2000-01-17       NaN          1        NaN
2000-01-18       NaN          1          1
2000-01-20       NaN        NaN          2
2000-01-21       NaN        NaN          1
2000-01-22       NaN        NaN          1
2000-01-24       NaN        NaN          1

用零替换NaN,然后​​沿行累计总和:

result = result.fillna(0).cumsum(axis=0)

最后使用reindex确保每个日期都有一行:

start, end = result.index.min(), result.index.max()
result = result.reindex(pd.date_range(start, end, freq='D'), method='ffill')
import pandas as pd
import numpy as np

# generate an example df
np.random.seed(2015)
arr = np.random.randint(1, 10, size=(10,3)).astype(float)
arr[arr == 9] = np.nan
data = np.add.accumulate(
    arr, axis=1).astype('<m8[D]')
data = np.array('2000-01-01 12:34:56', dtype='<M8[ns]') + data
df = pd.DataFrame(data, columns=['dev_date', 'test_date', 'done_date'])

# strip times from the dates
df = pd.DataFrame(np.array(df.values, dtype='<M8[ns]')
                  .astype('<M8[D]').astype('<M8[ns]'),
                  columns=df.columns, index=df.index)

result = pd.concat({col:df[col].value_counts() for col in df}, axis=1)
result = result.fillna(0).cumsum(axis=0)
start, end = result.index.min(), result.index.max()
result = result.reindex(pd.date_range(start, end, freq='D'), method='ffill')
print(result)

产量

            dev_date  done_date  test_date
2000-01-04         2          0          0
2000-01-05         3          0          0
2000-01-06         3          0          1
2000-01-07         4          0          3
2000-01-08         5          0          3
2000-01-09         6          1          3
2000-01-10         6          2          3
2000-01-11         6          2          4
2000-01-12         6          2          4
2000-01-13         6          2          4
2000-01-14         6          3          4

在OP的实际问题中,DataFrame包含包含两者的列 datetime.datetime和无值。这些可以 使用

转换为datetime64[ns]的DataFrame
df = pd.DataFrame(np.array(df.values, dtype='<M8[ns]'),
                  columns=df.columns, index=df.index)

要删除日期时间的时间部分,您可以使用:

df = pd.DataFrame(np.array(df.values, dtype='<M8[ns]')
                  .astype('<M8[D]').astype('<M8[ns]'),
                  columns=df.columns, index=df.index)