特定の値のギャップを数える – 任意の要素間のカウント

Pandas: count gaps for specific values – StackOverflow

各列で求めるインターバルが違うので,indexをリネームしようと思うとループを回すしか無い.
値が欲しいだけなら,numpyを用いてベクトル処理が可能.

import io
import pandas as pd        
import numpy as np


strings = """1 A
2 A
3 B
4 A
5 C
6 B
7 C
8 A
9 B"""
s = pd.read_csv(io.StringIO(strings), sep='\s+', 
                header=None, index_col=0, squeeze=True)
print(s)

for k in s.unique():
    idx = s.index[s.eq(k)] - 1
    s_ = s[idx.min():idx.max()]
    g = s_.eq(k).cumsum()
    ind = s_.index.groupby(g)
    res = s_.ne(k).groupby(g).apply(np.count_nonzero)
    res.index = [f'{x[0]}-{x[-1]+1}' for x in ind.values()]
    print(res)
0
1    A
2    A
3    B
4    A
5    C
6    B
7    C
8    A
9    B
Name: 1, dtype: object
1-2    0
2-4    1
4-8    3
Name: 1, dtype: int64
3-6    2
6-9    2
Name: 1, dtype: int64
5-7    1
Name: 1, dtype: int64

numpyベクトライズなソリューションを考えるなら,
任意の範囲で累積和(specified_cumsum)をとり,
0の間の数字だけをピックアップすれば良い.
最適化していないので,ぐちゃぐちゃだけど,こういう感じで.

def seq_cumsum_1d(a):
    arr = a.copy()
    ind = np.concatenate(([0], np.flatnonzero(np.diff(arr))+1))
    arr[ind[1:]] -= np.add.reduceat(arr, ind)[:-1]
    return arr.cumsum(0)


def func(s):
    a, b = s.factorize()
    u = np.arange(len(b))
    arr = (a != u[:, None]).astype(int)
    return arr


arr = func(s)
print(arr)
res = np.apply_along_axis(seq_cumsum_1d, 1, arr)
print(res)

a = (res==0).cumsum(1)
cond1 = a[:, :-1] != 0
cond2 = a[:, :-1] != a[:, 1:]
cond = np.logical_and(cond1, cond2)
b = np.column_stack((np.zeros(3, dtype=int), cond.cumsum(1)))
ind = np.diff(b).astype(bool)
print(res[:, :-1][ind])
[[0 0 1 0 1 1 1 0 1]
 [1 1 0 1 1 0 1 1 0]
 [1 1 1 1 0 1 0 1 1]]
[[0 0 1 0 1 2 3 0 1]
 [1 2 0 1 2 0 1 2 0]
 [1 2 3 4 0 1 0 1 2]]
[0 1 3 2 2 1]
カテゴリー: 未分類 パーマリンク