質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

pandas

Pandasは、PythonでRにおけるデータフレームに似た型を持たせることができるライブラリです。 行列計算の負担が大幅に軽減されるため、Rで行っていた集計作業をPythonでも比較的簡単に行えます。 データ構造を変更したりデータ分析したりするときにも便利です。

Q&A

解決済

4回答

700閲覧

Python Pandas データフレームでの計算処理について

echizenkurage

総合スコア1

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

pandas

Pandasは、PythonでRにおけるデータフレームに似た型を持たせることができるライブラリです。 行列計算の負担が大幅に軽減されるため、Rで行っていた集計作業をPythonでも比較的簡単に行えます。 データ構造を変更したりデータ分析したりするときにも便利です。

0グッド

1クリップ

投稿2022/09/12 07:58

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などをうまく使うのかなと思いましたがどうすれば良いのかわかりませんでした)

ab
480
200
30
151
130
324
70
271
858
480

簡素になるならば、最大で5行前までを比較の対象とする、などとしてしまうことは可能です。
よろしくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

meg_

2022/09/12 10:17

> forループを2重にすれば書けることはわかるのですが、 そのコードはありますか? ループ処理ですと何か問題があるのですか?
guest

回答4

0

ベストアンサー

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
bsdfan

総合スコア4567

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

echizenkurage

2022/09/13 01:36

とてもシンプルなコード、どうもありがとうございました!!
guest

0

生の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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

echizenkurage

2022/09/13 01:46

シンプルなのと爆速なのの2通りご回答頂き、どうもありがとうございました。勉強になります!
guest

0

いわば勝ち残り戦での防衛回数を求める問題と考え、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
can110

総合スコア38266

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2022/09/12 12:33

「列aが前の行のaと同じまたは小さければ0」なので、 p = np.searchsorted(vals, val, side='right') では。
can110

2022/09/12 12:52 編集

> (列aが前の行のaと同じまたは小さければ0が~ なので、同値なら条件を満たさないと解釈してデフォルトのleftとしていますが、どうなんでしょうね。
can110

2022/09/12 14:48

dljfab さん > (列aが前の行のaと同じまたは小さければ0が~ にて、同値のあるデータで確認しました。 right(=同値含む左側は条件を満たさない)のが正しい結果と思いますので回答修正しました。 ご指摘感謝します。
echizenkurage

2022/09/13 01:51

ご回答ありがとうございました!
guest

0

簡素ではない方法です。

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

melian

総合スコア19796

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

echizenkurage

2022/09/13 01:49

ご回答ありがとうございました。 他のデータでやってみたところ、マイナスの数字になるところがありました。 例) a b 0 56 0 1 28 0 2 45 1 3 58 -4 4 10 0 5 55 1 6 78 -1 7 86 7 8 78 0 9 56 0 しかしながら3行でのコーディング、勉強になります。ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問