Python pandas dataframe で、実現できるのか教えてください。
下記のようなデータフレームで、列a だけがある状態から、列b の値を算出し追加したいです。
列b は、列aの値が、1行前からn行前のaよりも大きければnが入るとします。
(列aが前の行のaと同じまたは小さければ0が、
1行前だけのaよりも大きければ1が、
1行前・2行前のaよりも大きければ2が入ります。)
各行についてforループで、さらに何行前と比較するかをforループにというように
forループを2重にすれば書けることはわかるのですが、
そうではなく簡素に(各行についてループすることなく)書くことは可能でしょうか。
(whereなどをうまく使うのかなと思いましたがどうすれば良いのかわかりませんでした)
a | b |
---|---|
48 | 0 |
20 | 0 |
3 | 0 |
15 | 1 |
13 | 0 |
32 | 4 |
7 | 0 |
27 | 1 |
85 | 8 |
48 | 0 |
簡素になるならば、最大で5行前までを比較の対象とする、などとしてしまうことは可能です。
よろしくお願い致します。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
下記のような質問は推奨されていません。
- 質問になっていない投稿
- スパムや攻撃的な表現を用いた投稿
適切な質問に修正を依頼しましょう。
また依頼した内容が修正された場合は、修正依頼を取り消すようにしましょう。

回答4件
1
ベストアンサー
pandasでやるなら、expandingを使うのはどうでしょう。
(先頭からその行まで計算します)
applyの中のlambdaでやっていることは、Seriesを逆向きに並べ替えてcummaxをとって、先頭より値が小さいものの数を数えています。
python
1df['b'] = ( 2 df['a'].expanding() 3 .apply( 4 lambda s: (s.iloc[-2::-1].cummax() < s.iloc[-1]).sum() 5 ).astype(int))
投稿2022/09/12 23:55
編集2022/09/12 23:56総合スコア4369
下記のような回答は推奨されていません。
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
このような回答には修正を依頼しましょう。
また依頼した内容が修正された場合は、修正依頼を取り消すようにしましょう。
回答へのコメント

1
生のpythonでの実装例です。
a列のデータ数が多くなければcalc_b0
のようにベタに実装すれば十分だと思います。この方式の問題点はデータが[0, 1, 2, 3, ...]のように昇順に並んでいる場合、処理時間が長くなる点です。それでもデータ数500件くらいまでであれば気にならない範囲だと思います。
データ数が多い場合、
- データは整数のみ
- データの範囲は0〜10,000などある程度の範囲に絞られている
という条件を満たせる場合、Range Update Query(RUQ)というデータ構造を使用すると高速に処理できます。
calc_b1
が実装例です。(RUQ実装はここからお借りしました)
処理したいデータが [0, 1, 2, ...,9999]のようにcalc_b0が苦手なデータの場合、私の環境ではcalc_b1の方が60倍以上高速です。
python
1from timeit import timeit 2from random import randint 3 4def calc_b0(lst): 5 res = [] 6 stack = [float('inf')] 7 for l in lst: 8 x = -1 9 while stack[x] < l: 10 x -= 1 11 stack.append(l) 12 res.append(-x-1) 13 return res 14 15def calc_b1(lst): 16 N = 10001 # 想定する数字は0~10,000まで 17 N0 = 2**(N-1).bit_length() 18 data = [None]*(2*N0) 19 INF = (-1, 2**31-1) 20 # 区間[l, r+1)の値をvに書き換える 21 # vは(t, value)という値にする (新しい値ほどtは大きくなる) 22 def update(l, r, v): 23 L = l + N0; R = r + N0 24 while L < R: 25 if R & 1: 26 R -= 1 27 data[R-1] = v 28 29 if L & 1: 30 data[L-1] = v 31 L += 1 32 L >>= 1; R >>= 1 33 34 # a_iの現在の値を取得 35 def _query(k): 36 k += N0-1 37 s = INF 38 while k >= 0: 39 if data[k]: 40 s = max(s, data[k]) 41 k = (k - 1) // 2 42 return s 43 # これを呼び出す 44 def query(k): 45 return _query(k)[1] 46 47 res = [] 48 update(0, 10001, (0, 0)) # 0~10000を0で初期化 49 for i, l in enumerate(lst): 50 q = query(l) 51 res.append(i - q) 52 update(0, l+1, (0, i+1)) # 0~今回の数字までを塗り潰す 53 return res 54 55lst = [48, 20, 3, 15, 13, 32, 7, 27, 85, 48] # サンプルデータ 56res0 = calc_b0(lst) 57res1 = calc_b1(lst) 58assert res0 == res1 # 結果が同じかチェック 59print(res1) # [0, 0, 0, 1, 0, 4, 0, 1, 8, 0] 60 61lst = [randint(0, 1000) for _ in range(10000)] # データ点数の多いランダムデータ 62assert calc_b0(lst) == calc_b1(lst) # 結果が同じかチェック 63 64lst = list(range(10000)) # 昇順にソートされているデータ 65print(timeit('calc_b0(lst)', globals=globals(), number=10)) # 25.5021925秒 66print(timeit('calc_b1(lst)', globals=globals(), number=10)) # 0.39431319999999914秒
投稿2022/09/12 16:18

退会済みユーザー
総合スコア0
下記のような回答は推奨されていません。
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
このような回答には修正を依頼しましょう。
また依頼した内容が修正された場合は、修正依頼を取り消すようにしましょう。
回答へのコメント

1
いわば勝ち残り戦での防衛回数を求める問題と考え、numpyにて実装しました。
Python
1import numpy as np 2 3def calc(lst): 4 lst = np.array(lst) 5 L = len(lst) 6 7 # リング上の選手の「位置」と「値(強さ)」 8 # 強さの昇順で並びをキープする 9 # 配列サイズは固定で最大分を用意 10 idxs = np.zeros(L,dtype=int) 11 vals = np.zeros(L,dtype=int) 12 13 ret = np.zeros(L,dtype=int) # 各選手の防衛回数 14 15 # 防衛回数の算出 16 def calc_ret(idxs, idx): 17 if idxs.shape[0] > 0: 18 ret[idxs] = idxs - idx - 1 # 各選手の位置 - 挑戦者の位置 - 1 19 20 # 逆順にリングに上がる 21 count = 0 # リング上にいる選手の数 22 for i in range(L): 23 24 # 挑戦者 25 idx = L-i-1 26 val = lst[idx] 27 28 st = L-count # リング上にいる先頭(最弱)選手の位置 29 30 # リング上にいる選手内での挑戦者のランク(位置)を決定 31 # searchsortedはバイナリサーチなので速いはず 32 p = 0 33 if count > 0: 34 p = np.searchsorted(vals[-count:], val, side='right') 35 36 # pより手前にいる選手は挑戦者以下なのでリングから脱落 37 # 脱落者の防衛回数を算出 38 calc_ret(idxs[st:st+p], idx) 39 40 # この時点で挑戦者は最弱なので先頭に追加 41 count = count - p + 1 42 st = L-count 43 idxs[st] = idx 44 vals[st] = val 45 46 # 最後に生き残った選手の防衛回数を算出 47 calc_ret(idxs[-count:], -1) 48 49 return ret.tolist() 50 51 52N = 100000 53for lst in [np.random.randint(1,N,N), 54 np.arange(1,N), 55 np.array([48, 20, 3, 15, 13, 32, 7, 27, 85, 48]), 56 np.array([1,1,2,3])]: 57 58 lst = lst.tolist() 59 print('-----') 60 print(lst) 61 ret1 = calc(lst) 62 print(ret1) 63 64""" 65----- 66[48, 20, 3, 15, 13, 32, 7, 27, 85, 48] 67[0, 0, 0, 1, 0, 4, 0, 1, 8, 0] 68----- 69[1, 1, 2, 3] 70[0, 0, 2, 3] 71"""
投稿2022/09/12 11:42
編集2022/09/13 06:14総合スコア38144
下記のような回答は推奨されていません。
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
このような回答には修正を依頼しましょう。
また依頼した内容が修正された場合は、修正依頼を取り消すようにしましょう。
回答へのコメント

退会済みユーザー
2022/09/12 12:33

1
簡素ではない方法です。
python
1import pandas as pd 2 3df = pd.DataFrame({ 4 'a': [48, 20, 3, 15, 13, 32, 7, 27, 85, 48], 5}) 6 7# 8dfx = df[df['a'].diff().gt(0)].apply(lambda x: df.loc[:(x.name-1),'a'].gt(df.loc[x.name,'a']), axis=1) 9dfx = dfx.index - dfx[dfx.columns[::-1]].fillna(False).idxmax(axis=1) - 1 10df = df.assign(b = dfx.mask(dfx == 0, dfx.index)).fillna(0, downcast='infer') 11print(df)
投稿2022/09/12 11:41
総合スコア18774
下記のような回答は推奨されていません。
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
このような回答には修正を依頼しましょう。
また依頼した内容が修正された場合は、修正依頼を取り消すようにしましょう。
回答へのコメント

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
同じタグがついた質問を見る
Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。
Pandasは、PythonでRにおけるデータフレームに似た型を持たせることができるライブラリです。 行列計算の負担が大幅に軽減されるため、Rで行っていた集計作業をPythonでも比較的簡単に行えます。 データ構造を変更したりデータ分析したりするときにも便利です。