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

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

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

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

3回答

694閲覧

データ集計の高速化を行いたいです

auto_chiri7

総合スコア1

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

1クリップ

投稿2023/03/03 14:02

編集2023/03/03 14:12

実現したい

日付ID付番カウント率
2021-12-31141
2022-01-01103
2022-01-01112
2022-01-01122
2022-01-02153
2022-01-02122
2022-01-02181
2022-01-03111
2022-12-31113

上記のようなデータフレームにおいて、各行の"カウント率"列に下記④の値を入力したいです。
①各行の日付の前日を基準日として、その180日以内の日付が含まれる行をデータフレームから抽出し、
②上記①で抽出した行のうち、IDが一致する行を抽出したときの行数をAとし、
③上記②で抽出した行のうち、付番が1または2である行を抽出したときの行数をBとしたときの、
④B/A×100の値
(上記データフレームの一番下の行を例にすると、2022.6.30~2022.12.30の間で、IDが11である行数をAとし、Aのなかで付番が1または2である行数をBとし、B/A×100の値を"カウント率"列に入力したいです)

前提

jupyter labを使用してpythonでのデータ集計を行おうとしています。
自身で下記コードを書いたのですが、実際はデータフレームの行数が200万近くあり、処理時間がとても長く実用に耐えませんでした。
初歩的な質問で恐縮ですが、高速化を図れる術をご教示いただきたいです。宜しくお願いいたします。

発生している問題・エラーメッセージ

エラーメッセージ

該当のソースコード

python

1def sample(row): 2 all = len(df[(row["日付"] >df["日付"])& 3 (df["日付"]> row["日付"] - timedelta(days=180))& 4 (df["ID"] == row["ID"]) 5 ] ) 6 count = len(df[((row["日付"] > df["日付"])& 7 (df["日付"]> row["日付"] - timedelta(days=180))& 8 (df["ID"] == row["ID"]))& 9 (df["付番"].isin([1,2])) 10 ] ) 11 try: 12 count_rate = count/all * 100 13 except ZeroDivisionError: 14 count_rate = np.nan 15 row["カウント率"] = count_rate 16 return row 17 18df_new = df.apply(sample,axis=1)

試したこと

swifterを使用しましたが処理時間がほとんど変わらず、またnp.vectorizeを試みたのですが知識不足により実装することができませんでした。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

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

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

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

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

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

Orlofsky

2023/03/03 14:59

>行数が200万近くあり、処理時間がとても長く実用に耐えませんでした。 それでだけデータがあったら、通常データをデータベースに登録してSQL言語でSELECT文を発行して集計します。200万行程度なら数秒で集計できるでしょう。
auto_chiri7

2023/03/04 04:00

ご回答ありがとうございました。SQLも視野に入れてみます。
guest

回答3

0

ベストアンサー

  • isin([1, 2])は、ループの中で何度も計算するのは無駄なので、最初に計算してしまうのがいいです。
  • ID毎の処理になっているようなので、IDでgroupbyするといいと思います。
  • 期間に対しての計算はrollingを使ったらできます。closedにleftを指定することで当日を除去します。
  • B/A は、boolの列に対して平均(mean)をとることで計算できます。

python

1count_ratio = ( 2 df.assign(x=df['付番'].isin([1, 2])) 3 .groupby('ID') 4 .rolling('181D', on='日付', closed='left')['x'] 5 .mean() * 100).rename('カウント率') 6 7# 同じID, 日付のデータが複数ある場合 8count_ratio = count_ratio[~count_ratio.index.duplicated()] 9 10df = df.join(count_ratio, on=['ID', '日付'])

投稿2023/03/03 23:53

編集2023/03/04 06:04
bsdfan

総合スコア4560

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

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

auto_chiri7

2023/03/04 04:02

ご回答ありがとうございました。処理時間が大幅に短縮され、大変助かりました。 高速化でき、処理時間が一番短縮されましたのでベストアンサーにさせていただきます。
can110

2023/03/04 04:57

詳細確認できていませんが、結果の行数が元の行数よりも増えるような気がします。
bsdfan

2023/03/04 06:05

can110さん ご指摘ありがとうございます。IDと日付が同じデータが複数あるケースでは、データが増えてしまうようです。 追記・修正しました。
guest

0

ID毎に、1,2または1,2以外の2種類の日付を昇順にキューで管理することで処理速度はあがると思います。
ただし、このコードでも200万行だとデータ分布にもよりますが数分はかかります。

Python

1import pandas as pd 2from collections import deque 3from io import StringIO 4from itertools import product 5 6s = """date,ID,no 72022-01-01,1,1 82022-01-01,1,2 92022-01-01,1,3 102022-02-01,1,1 112022-02-01,1,2 122022-02-01,1,3 132022-08-01,1,1 14""" 15df = pd.read_csv(StringIO(s), parse_dates=['date']) 16 17# 2192000 rows 18#data = product(pd.date_range('2020-01-01','2022-12-31'), range(100), range(20)) 19#df = pd.DataFrame(data=data, columns=['date','ID','no']) 20 21print(df) 22 23# 古い日付を削除 24def remove(q,dt): 25 while len(q) > 0: 26 if (dt - q[0]).days <= 181: 27 break 28 q.popleft() 29 30# (同日を除く)対象数を取得 31def count(q,dt): 32 cnt = len(q) 33 for i in reversed(q): 34 if (dt - i).days >= 1: 35 break 36 cnt -= 1 37 return cnt 38 39# 行毎の処理 40def func(row): 41 dt, id, no = row['date'], row['ID'], row['no'] 42 43 # ID毎に管理 44 if id not in que: 45 que[id] = [deque(), deque()] # [1,2以外, 1,2のみ] 46 qs = que[id] 47 48 # 古いものは不要なので削除 49 remove(qs[0], dt) 50 remove(qs[1], dt) 51 52 # 対象数(同日を除く)を得る 53 o, b = count(qs[0], dt), count(qs[1], dt) 54 a = o+b 55 qs[no in (1,2)].append(dt) # いずれかに振り分けて追加 56 57 return a,b # とりあえず分かりやすいようにa,bを返す 58 59 60df = df.sort_values('date') 61que = {} # キー=ID, 値=[1,2以外の日付, 1,2の日付] 62df['ab'] = df.apply(func, axis=1) 63print(df) 64""" 65 date ID no ab 660 2022-01-01 1 1 (0, 0) 671 2022-01-01 1 2 (0, 0) 682 2022-01-01 1 3 (0, 0) 693 2022-02-01 1 1 (3, 2) 704 2022-02-01 1 2 (3, 2) 715 2022-02-01 1 3 (3, 2) 726 2022-08-01 1 1 (3, 2) 73"""

投稿2023/03/04 00:08

編集2023/03/04 04:50
can110

総合スコア38262

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

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

auto_chiri7

2023/03/04 04:03 編集

ご回答ありがとうございました。参考にさせていただきます。
guest

0

pandas.DataFrame.rolling() を使う方法。(※ 処理速度に関しては不明)

python

1import pandas as pd 2import io 3 4pd.set_option('display.unicode.east_asian_width', True) 5 6csv_data = ''' 7日付,ID,付番 82021-12-31,14,1 92022-01-01,10,3 102022-01-01,11,2 112022-01-01,12,2 122022-01-02,15,3 132022-01-02,12,2 142022-01-02,18,1 152022-01-03,11,1 162022-12-31,11,3 17''' 18df = pd.read_csv(io.StringIO(csv_data), parse_dates=['日付']) 19 20# 21def aggregate(idx): 22 dfi = df.loc[idx[:-1]] 23 A = dfi['ID'].eq(df.loc[idx[-1], 'ID']) 24 B = dfi[A]['付番'].isin((1, 2)).sum() 25 ratio = (B / A.sum() * 100) 26 return ratio 27 28df['カウント率'] = df.sort_values('日付').reset_index()\ 29 .rolling('181D', on='日付')['index']\ 30 .apply(aggregate) 31print(df)

投稿2023/03/03 16:45

melian

総合スコア19749

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

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

auto_chiri7

2023/03/04 04:01

ご回答ありがとうございました。rolling()を使ったことがなかったため活用させていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問