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

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

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

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

pandas

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

Q&A

解決済

3回答

1697閲覧

datetimeオブジェクトの差をとる処理

nouken

総合スコア369

Python

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

pandas

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

0グッド

0クリップ

投稿2018/12/24 01:41

編集2018/12/24 03:14

以下のようなpd.DataFrameがあるとします。

pd.DataFrameの例

行いたい処理は各id毎にdateの差をとっていくことです。すでにid毎にdateは
sortされているものとします。

以下のコードで処理しようとしましたが、実際のデータは3000万行程uniqueなidが30万ほど)あり、時間がかかりすぎだったので、途中で止めました。効率よくこの処理を行うにはどうすればよいでしょうか?

python

1#dateの差をとっていく、ただしidが変わるときは0とする(違うcard_id間で差を取らないように気をつける) 2for i in tqdm(range(len(df))): 3 if i == 0: 4 pass 5 elif df['id'][i] == df['id'][i-1]: 6 df['delta'][i] = df['date'][i] - df['date'][i-1] 7 else: 8 pass

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

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

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

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

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

guest

回答3

0

ベストアンサー

can110さんのから改良。気持ちマシになると思いますが、そもそも遅い処理なのでどうだろうな面はあります。

python

1import pandas as pd 2 3df = pd.DataFrame({'id':['a','a','a','b','b','b'],'date':['2018/01/23','2018/01/24','2018/01/26','2018/01/23','2018/01/26','2018/01/30']}) 4df.loc[:,'date'] = pd.to_datetime(df['date']) 5 6grp = df.groupby('id') 7for grp_name, grp_idx in grp.groups.items(): 8 df.loc[grp_idx,'delta'] = df.loc[grp_idx,'date'].diff().fillna(0) 9 10print(df) 11""" 12 date id delta 130 2018-01-23 a 0 days 141 2018-01-24 a 1 days 152 2018-01-26 a 2 days 163 2018-01-23 b 0 days 174 2018-01-26 b 3 days 185 2018-01-30 b 4 days 19""" 20
毎回fillnaするオーバーヘッドを削減()したもの

python

1import pandas as pd 2 3df = pd.DataFrame({'id':['a','a','a','b','b','b'],'date':['2018/01/23','2018/01/24','2018/01/26','2018/01/23','2018/01/26','2018/01/30']}) 4df.loc[:,'date'] = pd.to_datetime(df['date']) 5 6grp = df.groupby('id') 7for grp_name, grp_idx in grp.groups.items(): 8 df.loc[grp_idx,'delta'] = df.loc[grp_idx,'date'].diff() 9 10df.fillna(0, inplace=True) 11print(df) 12""" 13 date id delta 140 2018-01-23 a 0 days 151 2018-01-24 a 1 days 162 2018-01-26 a 2 days 173 2018-01-23 b 0 days 184 2018-01-26 b 3 days 195 2018-01-30 b 4 days 20""" 21

別のアイデア

これでも良い気がします。速くはなるはずなんだけど、idごとに計測するのは無理かも。あと、上のループでどれくらい時間を食うかが懸念。

python

1import pandas as pd 2 3df = pd.DataFrame({'id':['a','a','a','b','b','b'],'date':['2018/01/23','2018/01/24','2018/01/26','2018/01/23','2018/01/26','2018/01/30']}) 4df.loc[:,'date'] = pd.to_datetime(df['date']) 5 6before_id = None 7zero_points = [] 8for i, id_ in df["id"].iteritems(): 9 if id_ != before_id: 10 zero_points.append(i) 11 before_id = id_ 12 13df.loc[:,"delta"] = df.loc[:,"date"].diff() 14df.loc[zero_points,"delta"] = pd.Timedelta(0) 15print(df) 16 17""" 18 date id delta 190 2018-01-23 a 0 days 201 2018-01-24 a 1 days 212 2018-01-26 a 2 days 223 2018-01-23 b 0 days 234 2018-01-26 b 3 days 245 2018-01-30 b 4 days 25"""

投稿2018/12/24 03:54

編集2018/12/24 05:37
hayataka2049

総合スコア30933

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

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

nouken

2018/12/24 04:03

idあたり4~5秒になりましたので7時間ほどに短縮されました! とりあえずこれで計算回してみたいと思います。 ありがとうございました!
hayataka2049

2018/12/24 04:05

速くはなったけど微妙・・・とりあえずfillna外して、最後にまとめてfillnaする実装にするとどうなりますか?(追記します)
can110

2018/12/24 04:07

あ。fillnaは最後でやったほうが絶対いいですね。
hayataka2049

2018/12/24 04:07

まあ、やるに越したことはないけどすごく効く高速化ではないか・・・
nouken

2018/12/24 04:10 編集

2~3s/itになりました!3.5時間ほどで終わりそうです!!勉強になります!
hayataka2049

2018/12/24 04:11

思ったより効いた。ちょっと意外
hayataka2049

2018/12/24 04:14 編集

素直にfillna(0)すると新しいSeriesを返すからですかね >>> import pandas as pd >>> import numpy as np >>> a = pd.Series([0, np.nan]) >>> id(a) 140342098511240 >>> id(a.fillna(0)) 140342472549880
nouken

2018/12/24 04:34

すみません、今気づいたんですが単位が分じゃなく時間だったので、200時間ぐらいかかってしまいそうです、上の別解を試してみます。。。
hayataka2049

2018/12/24 04:36

そうですね・・・今なげた別解ですごく速くなるとも思えないし、最初にint64にして計算して最後にTimedeltaに直す方針じゃないときついかも
nouken

2018/12/24 05:11

上の別解のゼロになるところをあらかじめ出しておいて、とりあえず全体で計算し、あとからゼロに置き換える方法、マジックのように30秒ほどで計算終わりました!  これほど高速化できるのは驚きです。。。
hayataka2049

2018/12/24 05:16 編集

えっ、本当ですか? ちゃんと正しい値になっているのでしょうか diffが異様に速いのかー
nouken

2018/12/24 05:23

確認しましたが、しっかり計算できていました。 本当に助かりました、ありがとうございました!
can110

2018/12/24 05:28

う~ん。大熊猫魔術(パンダマジック)ですね…
hayataka2049

2018/12/24 05:36 編集

200時間が30秒になるとは思わなんだ・・・誰よりも投稿した私が驚いているのですが、解決したなら何よりです。 https://github.com/pandas-dev/pandas/blob/master/pandas/core/series.py#L2061 ちゃんと追ってないけど、高速処理できる内部表現(intとか)にしてdiffして戻す処理をちゃんと作ってるみたいですね。これは脱帽しました(pandasに)。 というか、30万のidごとにちびちび取り出して書き込むオーバーヘッドが主だったてことか・・・まあ考えてみればそれはそうなんですが
can110

2018/12/24 05:44

diffって思ってた以上に速いんですね、私も驚きです。 今回のデータではid毎に取り出す処理だと厳しいですね。 実測することの意義にも改めて気づきました。
guest

0

以下のような感じでできそうです。
idでグループ化してid毎にdiffを適用します。
先頭行はNaNになるのでfillna(0)で0で埋めています。

Python

1import pandas as pd 2 3df = pd.DataFrame({'id':['a','a','a','b','b','b'],'date':['2018/01/23','2018/01/24','2018/01/26','2018/01/23','2018/01/26','2018/01/30']}) 4df.loc[:,'date'] = pd.to_datetime(df['date']) 5 6grp = df.groupby('id') 7for id in grp.groups: 8 df.loc[df['id'] == id,'delta'] = df.loc[df['id'] == id,'date'].diff().fillna(0) 9 10print(df) 11""" 12 date id delta 130 2018-01-23 a 0 days 141 2018-01-24 a 1 days 152 2018-01-26 a 2 days 163 2018-01-23 b 0 days 174 2018-01-26 b 3 days 185 2018-01-30 b 4 days 19"""

別解:先頭から舐める版

ユニークidが多く、各id毎の行数が少ない場合は、以下のように先頭から舐めて計算する方が速いかもしれません。

Python

1import pandas as pd 2 3df = pd.DataFrame({'id':['a','a','a','b','b','b'],'date':['2018/01/23','2018/01/24','2018/01/26','2018/01/23','2018/01/26','2018/01/30']}, 4 columns = ['id','date']) 5df.loc[:,'date'] = pd.to_datetime(df['date']) 6df['delta'] = 0 7 8prev_id,prev_date = df.loc[0,'id'], df.loc[0,'date'] 9for idx,row in df.iterrows(): 10 cur_id = row['id'] 11 cur_date = row['date'] 12 if prev_id != cur_id: 13 pass 14 else: 15 df.loc[idx,'delta'] = cur_date - prev_date 16 prev_id = cur_id 17 prev_date = cur_date 18 19print(df) 20""" 21 id date delta 220 a 2018-01-23 0 days 00:00:00 231 a 2018-01-24 1 days 00:00:00 242 a 2018-01-26 2 days 00:00:00 253 b 2018-01-23 0 264 b 2018-01-26 3 days 00:00:00 275 b 2018-01-30 4 days 00:00:00 28"""

投稿2018/12/24 02:44

編集2018/12/24 04:27
can110

総合スコア38233

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

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

nouken

2018/12/24 03:06

回答ありがとうございます!やってみたのですが、各idの処理に約8秒かかり、全ての処理に12時間ほどかかってしまう計算になります。最初よりはだいぶ早くなったのですが、やはりこれは時間のかかる処理なんでしょうかね…
can110

2018/12/24 03:36

idあたり100行程度に8秒は時間がかかりすぎな感じがしますね… なぜだろう? 8秒に、初回のグループ化の時間も含まれているとかはないですよね?
nouken

2018/12/24 03:44 編集

ids = set(df['id']) for id in ids: df.loc[df['id'] == id,'delta'] = df.loc[df['id'] == id,'date'].diff().fillna(0) のようにやりましたのでそれは大丈夫です。
nouken

2018/12/24 03:42

詳しくないのでよくわからないんですが、idが実際はハッシュ化された15文字の文字列なんですが、それとかは関係ないですよね?
hayataka2049

2018/12/24 03:54 編集

df['id'] == idは3000万の総なめ線形探索になると思うので、普通に8秒かかるんじゃないですかね
can110

2018/12/24 03:48

時間計測については了解しました。 私も大規模データでの実測経験はないので推測ベースでしかコメントできませんが ハッシュ文字列のidをintの連番に振りなおし実際の速度を比較してみて違いが出るかですかね…
can110

2018/12/24 03:53

> df['id'] == idは3000万の総なめ線形探索になると思うので、 となると、下手にpandasでid毎に抽出するより、先頭からの走査で差分を取る手法のほうが速い可能性がありますね…idの切り替わりだけ注意しなければなりませんが。 forループが遅いとはいえ、走査だと1回舐めるだけですみますし。
hayataka2049

2018/12/24 03:57

grp.groupsの値が {'b': Int64Index([3, 4, 5], dtype='int64'), 'a': Int64Index([0, 1, 2], dtype='int64')} なので一応groupbyした時点でインデックスは得られているから、使えばそこの速度は改善されるだろう、ということで回答つけてみました。
can110

2018/12/24 04:02

なるほど、グループ化によってインデックス化されているようですね。 これで実際の速度がどれほど短縮されるか気になるところです。
nouken

2018/12/24 04:06

お二人ともありがとうございます! 多少高速になりました(4~5s/it)!
can110

2018/12/24 04:31

先頭からの走査で差分を取る手法での回答を追記しました。 もしかしたら、こちらのほうが速度アップするかもしれません(希望)。
nouken

2018/12/24 05:23

試してみたのですが、手元ではループがなぜか回らず、エラーも吐かず、動かなくなってしまいました。。 一応解決しましたので、ありがとうございました!
can110

2018/12/24 05:25

あらら? とりあえず解決して何よりです。
guest

0

根本的な解決になっていないかもしれないですが内包表現でしたらforより早くなります。

投稿2018/12/24 02:00

amo

総合スコア33

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

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

quickquip

2018/12/24 03:15

内包表記がforより速いのは、古い話ですよ。
amo

2018/12/24 07:56

申し訳ありませんが初めて聞きました。 私が調べる限りそのような根拠が見つからないので、教えてもらえたらありがたいです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問