【问题标题】:Rolling window count for a date interval in pandas熊猫中日期间隔的滚动窗口计数
【发布时间】:2018-01-14 04:18:51
【问题描述】:

我有项目历史及其相关的计划开始和结束时间:

id   planned_start planned_end
1    2017-09-12    2017-09-13
2    2017-09-12    2017-09-14
3    2017-09-12    2017-09-13
4    2017-09-13    2017-09-13
5    2017-09-12    2017-09-12
6    2017-09-12    2017-09-20
7    2017-09-14    2017-09-15
8    2017-09-14    2017-09-20

我想计算上述项目的每个开始日期的并发项目数。这是我的逻辑:

for project_id in df['id']:
    start_date = df[df['id'] == project_id]['planned_start'].values[0]
    concurrent_projects = df[(df['planned_start'] <= start_date) & (df['planned_end'] >= start_date)]
    df.ix[df['id'] == project_id, 'concurrent_projects'] = concurrent_projects.shape[0]

产生这个:

   id planned_start planned_end  concurrent_projects
0   1    2017-09-12  2017-09-13                  5.0
1   2    2017-09-12  2017-09-14                  5.0
2   3    2017-09-12  2017-09-13                  5.0
3   4    2017-09-13  2017-09-13                  5.0
4   5    2017-09-12  2017-09-12                  5.0
5   6    2017-09-12  2017-09-20                  5.0
6   7    2017-09-14  2017-09-15                  4.0
7   8    2017-09-14  2017-09-20                  4.0

但是,我知道上面的for 循环在时间上是多么不理想。实际上,我有超过 500,000 个项目需要计算。有人可以就如何加快速度提供一些建议吗?我知道必须有一个纯熊猫甚至 numpy 的解决方案来杀死我上面的内容。

【问题讨论】:

  • 如果AB 之后但在B 结束之前开始,您当前的逻辑不会考虑一个项目(A) 与另一个项目(B) 并发。当您说“我想要......每个开始日期的并发项目”时,您的意思是您只想知道在新项目开始时已经开始但尚未完成的项目数量?
  • 是的,只有在给定项目 A 开始时已经开始并且可能已经完成也可能尚未完成的项目。基本上,我想知道项目团队在开始一个新项目时已经完成了多少工作。
  • “已经开始,可能已经完成,也可能没有完成”:为了并发,为什么要计算一个可能已经完成的项目?
  • 如果你只想要已经开始的项目,那么你想要df['planned_start'] &lt; start_date而不是&lt;=吗?
  • 漫长的一天……是的,应该是“以前开始但尚未完成的项目”。 &lt;= 过滤器很好......我会将同时开始的项目算作“之前”的一部分。

标签: python pandas numpy optimization


【解决方案1】:

矢量化方式...但会炸毁内存。仍在研究更好的矢量化方式。我有概念,只是在我吃晚饭的时候处理细节。

s = df.planned_start.values
e = df.planned_end.values

s_ = s >= s[:, None]
e_ = s <= e[:, None]

df.assign(concurrent_projects=(e_ & s_).sum(0))

   id planned_start planned_end  concurrent_projects
0   1    2017-09-12  2017-09-13                    5
1   2    2017-09-12  2017-09-14                    5
2   3    2017-09-12  2017-09-13                    5
3   4    2017-09-13  2017-09-13                    5
4   5    2017-09-12  2017-09-12                    5
5   6    2017-09-12  2017-09-20                    5
6   7    2017-09-14  2017-09-15                    4
7   8    2017-09-14  2017-09-20                    4

抱歉,我没有时间解释。但我不想让你挂着

k = len(df)

d = np.column_stack([df.planned_start.values, df.planned_end.values + 1]).ravel()

i = np.tile([1, -1], k)
a = d.argsort()

f = np.arange(k).repeat(2)
r = np.zeros(k, int)
z = np.zeros(k, int)
m = np.zeros(k, int)

cumsum = 0

for j in range(f.size):
    x = f[a[j]]
    y = i[a[j]]
    r[x] = cumsum
    z[x] = (y + 1) // 2
    r += y * z
    m = np.column_stack([m, r]).max(1)
    cumsum += y

m

array([5, 5, 5, 5, 5, 5, 4, 4])

【讨论】:

  • 我希望你能找到这个问题 :) ...而且,是的,这肯定会扼杀我的记忆。实际上,我有超过 500,000 个项目。干杯。
  • 很好的解决方案~!
  • 我将在这里坐一会儿,尝试解析所有这些,但真诚地感谢您的跟进。
【解决方案2】:

这是我的解决方案,通过使用crosstab,基本上是使用evens的martix进行计算(输入Dataframedf2):

df=pd.crosstab(df2.planned_end,df2.planned_start,margins=True)
df=pd.concat([df,pd.DataFrame(columns=list(set(df.index)- set(df.columns)))]).fillna(0)
df2['concurrent_projects']=df2.planned_start.map(df.loc['All',:].cumsum()-df.All.cumsum().shift().fillna(0))



df2
Out[112]: 
   id planned_start planned_end  concurrent_projects
0   1    2017-09-12  2017-09-13                  5.0
1   2    2017-09-12  2017-09-14                  5.0
2   3    2017-09-12  2017-09-13                  5.0
3   4    2017-09-13  2017-09-13                  5.0
4   5    2017-09-12  2017-09-12                  5.0
5   6    2017-09-12  2017-09-20                  5.0
6   7    2017-09-14  2017-09-15                  4.0
7   8    2017-09-14  2017-09-20                  4.0

【讨论】:

    【解决方案3】:

    使用 apply 可以将速度提高大约 3 倍。

    目前的做法:

    %%timeit
    def concurrent_count_using_loop():
        for project_id in df['id']:
            start_date = df[df['id'] == project_id]['planned_start'].values[0]
            concurrent_projects = df[(df['planned_start'] <= start_date) & (df['planned_end'] >= start_date)]
            df.ix[df['id'] == project_id, 'concurrent_projects'] = concurrent_projects.shape[0]
    
    concurrent_count_using_loop()
    
    # 10 loops, best of 3: 21.4 ms per loop
    

    apply():

    %%timeit
    def concurrent_count(project):
        valid_start = df.planned_start <= project["planned_start"]
        valid_end = df.planned_end >= project["planned_start"]
        return (valid_start & valid_end).sum()
    
    df["concurrent_projects"] = df.apply(concurrent_count, axis=1)
    
    # 100 loops, best of 3: 6.94 ms per loop
    

    【讨论】:

      猜你喜欢
      • 2016-09-10
      • 2018-07-10
      • 2017-03-30
      • 2019-10-21
      • 1970-01-01
      • 1970-01-01
      • 2020-04-21
      • 2021-03-30
      • 1970-01-01
      相关资源
      最近更新 更多