【问题标题】:Create flag based on cumsum and timediff根据 cumsum 和 timediff 创建标志
【发布时间】:2017-09-07 12:48:18
【问题描述】:

考虑以下数据框,

import pandas as pd
import numpy as np

np.random.seed(666)
dd=pd.DataFrame({'v1': np.random.choice(range(30), 20),
                 'v2': np.random.choice(pd.date_range(
                       '5/3/2016', periods=365, freq='D'),
                     20, replace=False)
                 })
dd=dd.sort_values('v2')

#    v1         v2
#5    4 2016-05-03
#11  14 2016-05-26
#19  12 2016-06-26
#15   8 2016-07-06
#7   27 2016-08-04
#4    9 2016-08-28
#17   5 2016-09-08
#13  16 2016-10-04
#14  14 2016-10-10
#18  18 2016-11-25
#3    6 2016-12-03
#8   19 2016-12-04
#12   1 2016-12-12
#10  28 2017-01-14
#1    2 2017-02-12
#0   12 2017-02-15
#9   28 2017-03-11
#6   29 2017-03-18
#16   7 2017-03-21
#2   13 2017-04-29

我想创建基于以下两个条件的组:

  1. v1 <= 40的累计和
  2. v2 <= 61天的时差

换句话说,每个组必须有 40 个v1 的总和或 2 个月的时间。因此,如果 61 天过去了,但 40 天还没有完成,那么无论如何都要关闭该组。如果 40 在 1 天内完成,请再次关闭该组

最终的标志是,

dd['expected_flag']=[1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9]

我在 R here 中提出了一个非常相似的问题,但是现在(日期)有一个新要求,我无法完全理解它。

注意我将在庞大的数据集中运行它,所以效率越高越好

编辑:我发现this question 基本上处理第一个条件,但不处理日期条件

编辑2:61天的时差只是为了说明时间限制。实际上,约束将在几分钟内完成

编辑 3:使用@Maarten 提供的函数,我得到以下信息(前 40 行),其中第 1 组还应包括第 2 组中的前 2 行(即 v1=6 和 v1 =6)。

Out[330]: 
    index                  v2  v1  max_limit       group
0       2 2017-04-01 00:00:02  14      335.0        1
1       3 2017-04-01 00:00:03   8      335.0        1
2      13 2017-04-01 00:00:13  11      335.0        1
3      14 2017-04-01 00:00:14  11      335.0        1
4      29 2017-04-01 00:00:29   4      335.0        1
5      44 2017-04-01 00:00:44  16      335.0        1
6      52 2017-04-01 00:00:52  10      335.0        1
7      58 2017-04-01 00:00:58  11      335.0        1
8      65 2017-04-01 00:01:05  15      335.0        1
9      68 2017-04-01 00:01:08   8      335.0        1
10     81 2017-04-01 00:01:21  12      335.0        1
11     98 2017-04-01 00:01:38   9      335.0        1
12    102 2017-04-01 00:01:42   7      335.0        1
13    107 2017-04-01 00:01:47  12      335.0        1
14    113 2017-04-01 00:01:53   6      335.0        1
15    116 2017-04-01 00:01:56   6      335.0        1
16    121 2017-04-01 00:02:01   4      335.0        1
17    128 2017-04-01 00:02:08  16      335.0        1
18    143 2017-04-01 00:02:23   7      335.0        1
19    149 2017-04-01 00:02:29  11      335.0        1
20    163 2017-04-01 00:02:43   4      335.0        1
21    185 2017-04-01 00:03:05   9      335.0        1
22    239 2017-04-01 00:03:59   6      335.0        1
23    242 2017-04-01 00:04:02  13      335.0        1
24    272 2017-04-01 00:04:32   4      335.0        1
25    293 2017-04-01 00:04:53   8      335.0        1
26    301 2017-04-01 00:05:01  10      335.0        1
27    302 2017-04-01 00:05:02   7      335.0        1
28    305 2017-04-01 00:05:05  12      335.0        1
29    323 2017-04-01 00:05:23   5      335.0        1
30    326 2017-04-01 00:05:26  13      335.0        1
31    329 2017-04-01 00:05:29  10      335.0        1
32    365 2017-04-01 00:06:05  10      335.0        1
33    368 2017-04-01 00:06:08  11      335.0        1
34    411 2017-04-01 00:06:51   6      335.0        2
35    439 2017-04-01 00:07:19   6      335.0        2
36    440 2017-04-01 00:07:20   8      335.0        2
37    466 2017-04-01 00:07:46   7      335.0        2
38    475 2017-04-01 00:07:55   4      335.0        2
39    489 2017-04-01 00:08:09   4      335.0        2 

所以说清楚,当我求和并计算得到的 timediff 时,

dd.groupby('group', as_index=False).agg({'v1': 'sum', 'v2': lambda x: max(x)-min(x)})
Out[332]: 
#      group   v1       v2
#0         1  320 00:06:06
#1         2  326 00:07:34
#2         3  330 00:06:53
#...

【问题讨论】:

  • 我不明白 - 仍然存在时差 v1 中的所有值均
  • 正是如此。每组必须有 40 个 v1 或 2 个月的时间。因此,如果 2 个月过去了,但 40 还没有完成,那么无论如何都要关闭该组。如果 40 在 1 天内完成,请再次关闭组
  • 所以时间间隔也是累积的。不仅仅是连续行之间的差异?该组中最后一个元素和第一个元素之间的区别?
  • @ayhan 完全正确。 (最大 - 最小)
  • @IanS 我宁愿不要。效率越高越好。其实我需要在我的问题中提到这一点

标签: python pandas numpy


【解决方案1】:

设置:

dd['days'] = dd['v2'].diff().dt.days.fillna(0).astype(int)
dd = dd[['v1', 'v2', 'days']]  # the order of the columns matters

初始化:

increment = pd.Series(False, index=dd.index)
v1_cum = 0
days_cum = 0

循环:

for row in dd.itertuples(name=None):  # faster than iterrows
    v1_cum += row[1]
    days_cum += row[3]
    if v1_cum > 40 or days_cum > 61:
        increment[row[0]] = True  # first element of tuple is index
        # notice the different re-initialization
        v1_cum = row[1]
        days_cum = 0

分配:

dd['flag'] = increment.cumsum() + 1

输出:

[1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9]

【讨论】:

  • 你可以使用row.v1, row.days, row.Index 代替row[1],...
  • @MaartenFabré 我有一种直觉,使用未命名的元组可能会快一点...
  • 这似乎有效。让我试试,让你知道
  • 这种方法对我来说的一个缺点是它修改了初始 DataFrame,不仅修改了实际结果,还修改了中间列。不这样做应该很容易适应它
  • 如果我的日期类似于2017-04-01 00:00:02diff 可以计算秒或分钟吗?
【解决方案2】:

与@IanS 略有不同的方法。我不知道哪个会更快。 这个实际上计算了几个月的差异

def diff_in_months(date1, date2):
    import itertools
#     print(date1, date2)
    x, y = max(date1, date2), min(date1, date2)
    coefficients = 12, 100, 24, 100, 100, 1
    coefficients = list(reversed([i for i in itertools.accumulate(reversed(coefficients), operator.mul)]))

    return (sum(i * j for i, j in zip(coefficients, x.timetuple())) - sum(i * j for i, j in zip(coefficients, y.timetuple()))) // coefficients[1]

这可以通过只计算一次系数(并使用global 变量)而不是每次调用该方法来稍微加快

def my_grouping(df):
    i = 1
    v1 = 0
    v2 = df['v2'].iloc[0]
    for row in df.itertuples():
#         print(row)
        if diff_in_months(v2, row.v2) >= 2 or (v1 + row.v1 >= 41):
            i += 1
            v1 = row.v1
            v2 = row.v2
        else:
            v1 += row.v1
        yield i

flag_series = pd.Series(my_grouping(dd), index = dd.index))
dd.assign(flag=flag_series, expected_flag = [1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9])
    v1  v2  expected_flag   flag
5   4   2016-05-03  1   1
11  14  2016-05-26  1   1
19  12  2016-06-26  1   1
15  8   2016-07-06  2   2
7   27  2016-08-04  2   2
4   9   2016-08-28  3   3
17  5   2016-09-08  3   3
13  16  2016-10-04  3   3
14  14  2016-10-10  4   4
18  18  2016-11-25  4   4
3   6   2016-12-03  4   4
8   19  2016-12-04  5   5
12  1   2016-12-12  5   5
10  28  2017-01-14  6   6
1   2   2017-02-12  6   6
0   12  2017-02-15  7   7
9   28  2017-03-11  7   7
6   29  2017-03-18  8   8
16  7   2017-03-21  8   8
2   13  2017-04-29  9   9

任意区间

def my_grouping_arbitrary_interval(df, diff_v1 = 41, interval = pd.Timedelta(61, 'D')):
    i = 1
    v1 = 0
    v2 = df['v2'].iloc[0]
    for row in df.itertuples():
#         print(row)
        if max(v2, row.v2) - min(v2, row.v2) >= interval or (v1 + row.v1 >= diff_v1):
            i += 1
            v1 = row.v1
            v2 = row.v2
        else:
            v1 += row.v1
        yield i

问题在于 pd.Timedelta 将unit : string, [D,h,m,s,ms,us,ns] 中的任何一个作为输入,因此没有几个月或几年。对于那些你必须适应我的diff_in_months

【讨论】:

  • 谢谢@Maarten。可以将其缩放为秒而不是几个月吗?最后,带有参数data framesum of v1time interval 的函数将是理想的......类似于f(df, count = 40, time = 10mins)
  • 是的!!!那将是功能。但是,它不返回任何内容。它给了Out[290]: <generator object my_grouping_arbitrary_interval at 0x0000020DA23B93B8>。对不起,如果我听起来有点迟钝。我对python很陌生(我来自R背景)
  • 那是因为它是一个生成器。它不会在 1 个镜头中返回数据转储,而是一次返回一个值。您需要稍微不同地使用它:flag_series = pd.Series(my_grouping_arbitrary_interval(dd), index = dd.index))
  • 似乎工作正常。谢谢!我将在整个数据集上运行它并在接受您的答案之前仔细检查。再次感谢
  • 应该是v1 = 0
猜你喜欢
  • 1970-01-01
  • 2022-11-15
  • 1970-01-01
  • 1970-01-01
  • 2019-10-12
  • 2020-12-15
  • 1970-01-01
  • 2020-12-26
  • 1970-01-01
相关资源
最近更新 更多