Pandas - 自上次值更改以来的连续计数

Pandas - count streak since last value change

我想获取特定列中值更改后的连续记录:

datetime                 val1          val2  val1-streak   val2-streak
2018-04-01 00:00:00        4           1        0             0
2018-05-01 00:00:00        5           2        0             0
2018-06-01 00:00:00        5           2        1             1
2018-07-01 00:00:00        6           2        0             2
2018-08-01 00:00:00        7           2        0             3
2018-09-01 00:00:00        7           3        1             0
2018-10-01 00:00:00        7           3        2             1
2018-11-01 00:00:00        5           2        0             0

现在可以假设 dattime 列是等间距的。这样我就可以计算“周期”,因为目标列中的值发生了变化。理想情况下,该函数还可以计算自上次更改值以来 datetinme 的差异。

我在这个网站上找到了一些解决方案,但当我仔细查看时,它们并没有真正描述我的问题:

https://joshdevlin.com/blog/calculate-streaks-in-pandas/

.....至少我无法将给定的答案转移到我的问题上。

让我们从如何使用单列开始。首先我们需要找到值变化的每个点:

diffs = df['val1'].diff(1)
change_points = diffs != 0

由于以前遇到的值可以再次出现,我们需要找到一种正确的方法来区分相同值的不同条纹。我们将通过更改点数组的累加和来实现。为方便起见,我们将创建一个临时 DataFrame 来保存这些结果(您可以在 DataFrame 中创建一个新列,但这有点混乱)

change_points_cumsum = change_points.cumsum()
tmp_merged = pd.concat([serie, change_points_cumsum], axis=1, keys=['val1', 'change_points_cumsum'])

每个连胜都分配了不同的 change_points_cumsum 值,因此现在可以应用简单的分组依据和累积计数来获得最终结果

tmp_merged["val1-streak"] = tmp_merged.groupby(change_points_cumsum).cumcount()
print(tmp_merged)

   val1  change_points_cumsum  val1-streak 
0     4                     1             0
1     5                     2             0
2     5                     2             1
3     6                     3             0
4     7                     4             0
5     7                     4             1
6     7                     4             2
7     5                     5             0

如果您正在寻找更短、更紧凑的解决方案

change_points_cumsum = df['val1'].diff(1).ne(0).cumsum()
change_points_cumsum.groupby(change_points_cumsum).cumcount()

0    0
1    0
2    1
3    0
4    0
5    1
6    2
7    0

让我们尝试以下操作:

(i) 使用 set_index + unstack 创建堆叠系列(这是为了不在两列上分别重复相同的功能)。

(ii) 使用groupby + diff 找出连续元素之间的差异:tmp

(iii) 我们对差异为 0 的位置感兴趣;我们再做一次 groupby + cumsum 以获得预期的结果。

(iv) 使用 pivot 将输出恢复为 df.

的形状
tmp = df.set_index('datetime')[['val1','val2']].unstack().groupby(level=0).diff()
df[['val1-streak','val2-streak']] = pd.pivot(tmp.eq(0)
                                             .groupby([tmp.index.get_level_values(0), 
                                                       tmp.ne(0).cumsum()])
                                             .cumsum()
                                             .reset_index(), 
                                             'datetime', 'level_0', 0).to_numpy()

输出:

              datetime  val1  val2  val1-streak  val2-streak
0  2018-04-01 00:00:00     4     1            0            0
1  2018-05-01 00:00:00     5     2            0            0
2  2018-06-01 00:00:00     5     2            1            1
3  2018-07-01 00:00:00     6     2            0            2
4  2018-08-01 00:00:00     7     2            0            3
5  2018-09-01 00:00:00     7     3            1            0
6  2018-10-01 00:00:00     7     3            2            1
7  2018-11-01 00:00:00     5     2            0            0

通过比较 Series.diff, compare for not equal by Series.ne with cumulative sum, last pass to GroupBy.cumcount 列表中指定的每列的差异,使用自定义函数按连续值生成计数器:

vals = ['val1','val2']

def f(x):
    x = x.diff().ne(0).cumsum()
    return x.groupby(x).cumcount()

df = df.join(df[vals].apply(f).add_suffix('_streak'))