Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Pandas 行ループ 累積和

PandasのDataFrameで行ループで判定し、累積和を算出する

df["A"]==0ならばdf["time"]==0とし、df["A"]>0ならばdf["time"]に+5秒づつ累積和を行いたいです。
途中、df["A"]==0となればdf["time"]の累積和を0にしたいです。

いろいろ調べましたがdfを使ってのif文の参考が少なく
解決ができませんでした。

ご教示よろしくお願いいたします。
Q1.JPG

発生している問題・エラー

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
### 該当するソースコード
```Python 3.9
import pandas as pd
df=pd.read_excel("for 文 練習.xlsx")
for index, item in df.iterrows():
    if df["A"]>0:
        df["time"]=df["time"]+5
    else:
        df["time"]=0
0 likes

4Answer

この件で調べてみたら、超絶のワンライナーと出会いました!!

df['time'] = df.A.groupby((df.A == 0).cumsum()).cumcount()*5

これは何をしているかというと、

(df.A == 0)
0      True
1      True
2      True
3      True
4     False
5     False
6     False
7     False
8     False
9      True
10     True
11    False
12    False
13    False
14     True
Name: A, dtype: bool

A列が0であるかを判定して、bool値を累積和を取得します。


(df.A == 0).cumsum()
0     1
1     2
2     3
3     4
4     4
5     4
6     4
7     4
8     4
9     5
10    6
11    6
12    6
13    6
14    7
Name: A, dtype: int32

これをグループ化して累積カウントを取得します。

df.A.groupby((df.A == 0).cumsum()).cumcount()
0     0
1     0
2     0
3     0
4     1
5     2
6     3
7     4
8     5
9     0
10    0
11    1
12    2
13    3
14    0
dtype: int64

この結果に対して係数の5を掛けています

df['time'] = df.A.groupby((df.A == 0).cumsum()).cumcount()*5

 	A 	time
0 	0 	0
1 	0 	0
2 	0 	0
3 	0 	0
4 	1 	5
5 	1 	10
6 	1 	15
7 	1 	20
8 	1 	25
9 	0 	0
10 	0 	0
11 	1 	5
12 	1 	10
13 	1 	15
14 	0 	0
2Like

Comments

  1. こんな短いコードで書けたんですね!
    更に丁寧な解説ありがとうございます!
    groupby()の使い方を勉強します!

r_beginnersさんのワンライナーで既に解決しているのかも知れませんが、
実直にループで実現する場合について書かせていただきます。

元のコード
import pandas as pd

df = pd.read_excel("for 文 練習.xlsx")
for index, item in df.iterrows():
    if df["A"] > 0:
        df["time"] = df["time"] + 5
    else:
        df["time"] = 0
  • forでは、行の番号(index)と内容の複製(item)を行毎に順に取り出して繰り返すようになっているのですが、それらは使われていません。
    • for index, item in df.iterrows(): print(f"{index}, {item['A']}, {item['time']}")を実行してみると、どのような繰り返しか解ると思います。
  • ifの条件df["A"] > 0は、列全体に対して比較演算子を使用したことになります。
    • そのため、「『全て(の行)』なのか『一つでも』なのか、はっきりしなさい」というエラーになりました。
    • item["A"] > 0と書けば、繰り返しで処理中の1行に限定して検査できます。
  • 代入についても同様で、列全体を操作しようとしています。
修正案A
import pandas as pd

df = pd.read_excel("for 文 練習.xlsx")
df['time'] = '' # 念のため
sum = 0
for index, item in df.iterrows():
    sum = df.loc[index, 'time'] = sum + 5 if item['A'] > 0 else 0
  • df.loc[index, 'time']は、item['time']と同じセルの値を参照しますが、後者はコピーなので書き換えても反映されません。
修正案B
import pandas as pd

df = pd.read_excel("for 文 練習.xlsx")
df['time'] = '' # 念のため
sum = 0
for index in range(len(df)):
    sum = df.loc[index, 'time'] = sum + 5 if df['A'][index] > 0 else 0
  • 行番号だけで繰り返すものです。

蛇足

r_beginnersさんのワンライナーですと、最初の行のA列が1の場合、そこだけ上手くいかないような気がするのですが、大丈夫でしょうか…

1Like

Comments

  1. めちゃくちゃありがとうございます!
    dfを使うとfor文が全然うまく実行できなかった理由がわかりました。

    私が実行したかったコードをご教示頂きありがとうございます!!
  2. ごめんなさい、何度か修正を入れました。

自分でも色々調べてみて少し回りくどいコードで解決することも
わかりました。自分にとってのベストアンサーではありませんが、後世の初心者の為に記載しておきます。
一応、下記コードでも可能でした。

import pandas as pd
df=pd.read_excel("for 分 練習.xlsx")
df.loc[df["A"]==0,"time"]=0
df.loc[df["A"]>0,"time"]=1
df["time"]=df["time"].cumsum()
df["time"]=df["time"].sub(df["time"].mask(df["A"] !=0).ffill(), fill_value=0).astype(int)*5

print(df)

1Like
counter = 0

def func(i):
    global counter
    if i:
        counter += 5
    else:
        counter = 0
    return counter

df['A'].apply(func)
0Like

Your answer might help someone💌