一组条纹(扭曲)

Group streaks of ones (with a twist)

我已阅读相关问题,例如 and this blog post

很遗憾,我无法根据需要修改解决方案。

考虑一个带有 DatetimeIndex 的系列,它可能看起来像这样:

实例化示例的代码:

s = pd.Series([0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0], index=pd.date_range(start=0, freq='1d', periods=12, name='A')

最终,我想得到结果

  (t4 - t1)
+ (t8 - t5)
+ (t10 - t8)

这意味着 我需要识别每边用 0 填充的 1 的条纹 。之后我可以自己做所有事情,即按连胜分组(可能使用 cumcount)并区分每组中的第一个和最后一个时间戳。

有一些特殊情况,当系列 starts/ends 带有 1 时。在这种情况下,我想将其视为 preceded/followed 由 0在同一时间戳,例如


目前尝试次数:

我将合并一些子解决方案以便于可视化。

  1. 在每一端用零填充系列,以避免出现特殊情况。

    s = pd.Series([0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0], index=pd.date_range(start=0, freq='1d', periods=12, name='A')
    s = pd.concat([pd.Series([0], index=s.index[:1]), s, pd.Series([0], index=s.index[-1:])])
    
  2. 获得一连串之前的最后一个 0 和之后的第一个 0

     >>> tmp = pd.concat([s, s.diff(-1).eq(-1).astype(int).rename('starter'), s.diff(1).eq(-1).astype(int).rename('ender')], axis=1)
     >>> tmp 
                 A  starter  ender
     1970-01-01  0        0      0
     1970-01-02  0        1      0
     1970-01-03  1        0      0
     1970-01-04  1        0      0
     1970-01-05  0        0      1
     1970-01-06  0        1      0
     1970-01-07  1        0      0
     1970-01-08  1        0      0
     1970-01-09  0        1      1
     1970-01-10  1        0      0
     1970-01-11  0        0      1
     1970-01-12  0        0      0
    
  3. 1 填充 'A' 列中的单个零间隙,因为它们不会改变所需的结果。 (此步骤可能不是必需的,但有助于可视化。)

     >>> tmp.loc[(both := tmp['starter'].eq(1) & tmp['ender'].eq(1)), 'A'] = 1
     >>> tmp
                 A  starter  ender
     1970-01-01  0        0      0
     1970-01-02  0        1      0
     1970-01-03  1        0      0
     1970-01-04  1        0      0
     1970-01-05  0        0      1
     1970-01-06  0        1      0
     1970-01-07  1        0      0
     1970-01-08  1        0      0
     1970-01-09  1        1      1
     1970-01-10  1        0      0
     1970-01-11  0        0      1
     1970-01-12  0        0      0
    
  4. 调整 'starter''ender' 列。

     >>> tmp.loc[both, ['starter', 'ender']] = 0
     >>> tmp 
                 A  starter  ender
     1970-01-01  0        0      0
     1970-01-02  0        1      0
     1970-01-03  1        0      0
     1970-01-04  1        0      0
     1970-01-05  0        0      1
     1970-01-06  0        1      0
     1970-01-07  1        0      0
     1970-01-08  1        0      0
     1970-01-09  1        0      0
     1970-01-10  1        0      0
     1970-01-11  0        0      1
     1970-01-12  0        0      0
    

这就是我卡住的地方。

这是您的问题的解决方案:

>>> s = pd.Series([0, 1, 1, 0, 1, 0], index=pd.date_range(start=0, freq='1d', periods=6))

1970-01-01    0
1970-01-02    1
1970-01-03    1
1970-01-04    0
1970-01-05    1
1970-01-06    0
>>> s2 = s.diff(1)

1970-01-01    NaN
1970-01-02    1.0
1970-01-03    0.0
1970-01-04   -1.0
1970-01-05    1.0
1970-01-06   -1.0
>>> s3 = s2.loc[s == 0].reset_index(name="d0")

    index       d0
0   1970-01-01  NaN
1   1970-01-04  -1.0
2   1970-01-06  -1.0
>>> s4 = s3["index"].diff(1).loc[s3["d0"] == -1]

1   3 days
2   2 days

>>> s4.sum().days
5

综合起来,

s.diff(1) \
    .loc[s == 0] \
    .reset_index(name="val_diff") \
    .assign(date_diff=lambda x: x["index"].diff(1)) \
    .loc[lambda x: x.val_diff == -1] \
    .date_diff.sum()

对于特殊情况,如果它们以值 = 1 开始或结束,则将额外的行添加到数据框的顶部和底部。类似于:

if s.iloc[0] == 1:
    s = pd.concat([
            pd.Series([0], index=s.index[:1]),
            s
    ])

我几乎完成了问题的尝试,这是完成:

result = (s.index[s.diff(1).eq(-1)] - s.index[s.diff(-1).eq(-1)]).sum()

假设 s 以零开始和结束。否则先用零填充。