

pandasの縦方向の計算が苦手で、教えていただければと思います。

シミュレーション用のデータを作成したいと考えているのですが、顧客ごとに、5つの行動をします。その5つの行動によって、顧客の認知レベルが上がるということを想定します。行動1から行動５まであった場合に、顧客ステージを0～5の6段階に分けて、初期状態では、まず顧客ステージが0を入れます。表は時系列になっています。

・顧客ステージが0の時、行動1を実施するとステージが1に上がる  
・顧客ステージが1の時、行動2を実施するとステージが2に上がる  
・顧客ステージが2の時、行動3を実施するとステージが3に上がる  
・顧客ステージが3の時、行動4を実施するとステージが4に上がる  
・顧客ステージが4の時、行動5を実施するとステージが5に上がる

5まで上がり切ったあとは顧客ステージは5のままと考えます。  
また顧客ステージが1の状態で、何かステージが上がる行動ではなくても、  
顧客ステージは保持されると考えます。

つまり、以下のような初期の表 (ファイル) があった場合に、

顧客 | 行動1 | 行動2 | 行動3 | 行動4 | 行動5 | 顧客ステージ  
---|---|---|---|---|---|---  
A | 0 | 0 | 0 | 0 | 0 | 0  
A | 1 | 0 | 0 | 0 | 0 | 0  
A | 1 | 0 | 0 | 0 | 0 | 0  
A | 0 | 1 | 0 | 0 | 0 | 0  
A | 0 | 0 | 0 | 0 | 1 | 0  
A | 0 | 0 | 1 | 0 | 0 | 0  
A | 0 | 0 | 0 | 0 | 1 | 0  
A | 0 | 0 | 0 | 1 | 0 | 0  
A | 0 | 0 | 0 | 0 | 1 | 0  
A | 1 |  | 0 | 0 | 0 | 0  
B | 0 | 0 | 0 | 0 | 0 | 0  
B | 0 | 0 | 0 | 0 | 1 | 0  
B | 1 | 0 | 0 | 0 | 0 | 0  
B | 0 | 1 | 0 | 0 | 0 | 0  
B | 0 | 0 | 1 | 0 | 0 | 0  
B | 0 | 0 | 1 | 0 | 0 | 0  
B | 0 | 0 | 0 | 1 | 0 | 0  
B | 0 | 0 | 0 | 0 | 1 | 0  
B | 0 | 0 | 0 | 0 | 0 | 0  
  
というファイルがあった時に、

顧客 | 行動1 | 行動2 | 行動3 | 行動4 | 行動5 | 顧客ステージ  
---|---|---|---|---|---|---  
A | 0 | 0 | 0 | 0 | 0 | 0  
A | 1 | 0 | 0 | 0 | 0 | 1  
A | 1 | 0 | 0 | 0 | 0 | 1  
A | 0 | 1 | 0 | 0 | 0 | 2  
A | 0 | 0 | 0 | 0 | 1 | 2  
A | 0 | 0 | 1 | 0 | 0 | 3  
A | 0 | 0 | 0 | 0 | 1 | 3  
A | 0 | 0 | 0 | 1 | 0 | 4  
A | 0 | 0 | 0 | 0 | 1 | 5  
A | 1 | 0 | 0 | 0 | 0 | 5  
B | 0 | 0 | 0 | 0 | 0 | 0  
B | 0 | 0 | 0 | 0 | 1 | 0  
B | 1 | 0 | 0 | 0 | 0 | 1  
B | 0 | 1 | 0 | 0 | 0 | 2  
B | 0 | 0 | 1 | 0 | 0 | 2  
B | 0 | 0 | 1 | 0 | 0 | 3  
B | 0 | 0 | 0 | 1 | 0 | 4  
B | 0 | 0 | 0 | 0 | 1 | 5  
B | 0 | 0 | 1 | 0 | 0 | 5  
  
自分で思いついたのは、以下の様なコードですが、これだと、顧客のIDを無視していることと、顧客ステージが1のあと、また0に戻ったりすることが分かっています。  
恐れ入りますが、何卒、よろしくお願い申し上げます。

[code]

    df["顧客ステージ2"]=df["顧客ステージ"].shift(1).fillna(0)
    df.loc[(df["1"]==1),"顧客ステージ2"]=1
    df["顧客ステージ3"]=df["顧客ステージ2"].shift(1).fillna(0)
    df.loc[(df["2"]==1)&(df["顧客ステージ3"]==1),"顧客ステージ3"]=2
    df["顧客ステージ4"]=df["顧客ステージ3"].shift(1).fillna(0)
    df.loc[(df["3"]==1)&(df["顧客ステージ4"]==2),"顧客ステージ4"]=3
    df["顧客ステージ5"]=df["顧客ステージ4"].shift(1).fillna(0)
    df.loc[(df["4"]==1)&(df["顧客ステージ5"]==3),"顧客ステージ5"]=4
    df["顧客ステージ6"]=df["顧客ステージ5"].shift(1).fillna(0)
    df.loc[(df["5"]==1)&(df["顧客ステージ6"]==4),"顧客ステージ6"]=5
    df.loc[df["顧客ステージ2"]==1,"顧客ステージ"]=1
    df.loc[df["顧客ステージ3"]==2,"顧客ステージ"]=2
    df.loc[df["顧客ステージ4"]==3,"顧客ステージ"]=3
    df.loc[df["顧客ステージ5"]==4,"顧客ステージ"]=4
    df.loc[df["顧客ステージ6"]==4,"顧客ステージ"]=5
    df= df.顧客op(['顧客ステージ2',"顧客ステージ3","顧客ステージ4","顧客ステージ5","顧客ステージ6"],axis=1)
    
[/code]


CC BY-SA 4.0
2022-04-29T08:11:00.640
88565
2022-05-02T03:48:17.150
2022-04-30T03:55:11.500
41289
41289
question
python python3 pandas
pandasの整形加工：顧客に実施した行動により顧客の認知ステージを変化させるプログラムを作成したい

[code]

    from functools import reduce
    
    df['顧客ステージ'] = (
      df.groupby('顧客', sort=False).apply(lambda x:
        pd.DataFrame(
          reduce(lambda a, b: a + [a[-1] + ((b - a[-1]) == 1)],
                 x.filter(regex=r'^行動')
                  .multiply(range(1, 6), axis=1).sum(axis=1).values,
                 [0])[1:], index=x.index)))
    
    print(df.to_markdown(index=False))
    
[/code]

顧客 | 行動1 | 行動2 | 行動3 | 行動4 | 行動5 | 顧客ステージ  
---|---|---|---|---|---|---  
A | 0 | 0 | 0 | 0 | 0 | 0  
A | 1 | 0 | 0 | 0 | 0 | 1  
A | 1 | 0 | 0 | 0 | 0 | 1  
A | 0 | 1 | 0 | 0 | 0 | 2  
A | 0 | 0 | 0 | 0 | 1 | 2  
A | 0 | 0 | 1 | 0 | 0 | 3  
A | 0 | 0 | 0 | 0 | 1 | 3  
A | 0 | 0 | 0 | 1 | 0 | 4  
A | 0 | 0 | 0 | 0 | 1 | 5  
A | 1 | 0 | 0 | 0 | 0 | 5  
B | 0 | 0 | 0 | 0 | 0 | 0  
B | 0 | 0 | 0 | 0 | 1 | 0  
B | 1 | 0 | 0 | 0 | 0 | 1  
B | 0 | 1 | 0 | 0 | 0 | 2  
B | 0 | 0 | 1 | 0 | 0 | 3  
B | 0 | 0 | 1 | 0 | 0 | 3  
B | 0 | 0 | 0 | 1 | 0 | 4  
B | 0 | 0 | 0 | 0 | 1 | 5  
B | 0 | 0 | 0 | 0 | 0 | 5


CC BY-SA 4.0
2022-04-29T13:16:04.910
88568
2022-05-01T03:24:54.570
2022-05-01T03:24:54.570
47127
47127
88565
answer


pandasらしくない, ほぼ Python版

[code]

    import pandas as pd
    df = pd.DataFrame([
           ['A', 0, 0, 0, 0, 0],
           ['A', 1, 0, 0, 0, 0],
           ['A', 0, 0, 0, 1, 0],
           ['B', 0, 1, 0, 0, 0],
           ['B', 1, 0, 0, 0, 0],
           ['B', 0, 1, 0, 0, 0],
           ['A', 0, 1, 0, 0, 0],
           ['A', 0, 0, 1, 0, 0],
           ['A', 0, 0, 0, 1, 0],
           ['A', 0, 0, 0, 0, 1],
           ['A', 0, 0, 0, 0, 0],
        ],
        columns=['顧客', '行動1', '行動2', '行動3', '行動4', '行動5'])
    
    cli = dict.fromkeys(df['顧客'].unique(), 0)
    for k, *a in df.itertuples(index=False, name=None):
        s = cli[k]
        if s < 5 and a[s] >= 1:   # while あるいは if
            s += 1
            cli[k] = s
        print(k, *a, s)
    
[/code]

こちらは pandasぽく記述できた(と思う)コード

  * colabで動かしてるため, `(row > 0)` 2回記述してるが, 比較的新しい Pythonならば セイウチ演算子利用可能かもしれない
  * `selectors`は [itertools.compress](https://docs.python.org/ja/3/library/itertools.html#itertools.compress) の逆, dataからフィルター作る関数。pandas/NumPy に同様の機能があれば置き換えたいが, 見つからないようなため

[code]

    def selectors(data, iterable):
        it = iter(iterable)
        n = next(it)
        for d in data:
            b = d == n
            yield b and 1 or 0
            if b: n = next(it, None)
    
    # (`df.v` は「行動n」を示すワーク, 不要なので処理後は削除してよい)
    df['v'] = df.loc[:, '行動1':'行動5'].apply(lambda row:
            (row > 0).argmax() if (row > 0).any() else -1, axis=1)
    df['ステージ'] = df.groupby('顧客').apply(lambda sdf:
            pd.Series(selectors(sdf.v, range(5)), index=sdf.index).cumsum()).droplevel(0)
    
    display(df.drop(columns='v'))
    
[/code]


CC BY-SA 4.0
2022-04-30T11:38:45.387
88585
2022-05-02T03:48:17.150
2022-05-02T03:48:17.150
43025
43025
88565
answer