我有一个这样的数据框:
df = pd.DataFrame(data, columns=['dev_date', 'test_date', 'done_date'])
列是日期,表示工作项首次被接受到项目中的开发/测试/完成的日期(还有一些其他列,但它们对问题并不重要)。
我想从中构建一个累积流程图,它基本上是一个区域图表,显示每天在项目生命周期中每个阶段接受的项目总数。
最基本的算法相对简单:
dev_date
到最高done_date
... dev_date
,test_date
,done_date
)... 输出应该是这样的:
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更好一点,因为感觉有一些性能和代码质量可以获得。
有什么建议吗?
答案 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)