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

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

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

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python 3.x

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

pandas

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

Q&A

3回答

766閲覧

ログデータのセッション開始時間とセッション終了時間の差を計算できるようなデータ加工をしたい。

nrnrdsa

総合スコア19

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

Python 3.x

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

pandas

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

0グッド

2クリップ

投稿2023/01/06 16:41

編集2023/01/12 11:15

前提

Pandasでログデータの分析のためのデータ加工をしています。
ログデータは元データ(添付左側)のように時間、IN/OUT、USER単位の行で格納されており、
基本的にはINと対になるOUTがあるはずですが、まれに対になるものがない状態となっています。

実現したいこと

  • 加工後データ(添付右側)のように、INと対になるOUTのデータを同じ行で持ち、列同士の計算で滞在時間(列:diff)を出したいです。
  • 1行ごとにループして総当たりをかければ実現できるのですが、データ量が膨大で全行ループでの処理は現実問題実現不可です。
  • Pythonでループ処理以外(Pnadas,Numpy等)の計算が早い方法で実現したいと思っています。<br>

  →最悪ループ処理でもよいのですが極力計算コストが少なくなる処理(コード)にしたいです。

  • 加工後データ(添付右側)のように黄色ハッチング部分は処理の中で変数として渡せるようにしておきたいです。

イメージ説明

元データ

Nodatetimein_outuserservice
12023/1/1 16:33:19INAα
22023/1/1 16:33:05INAα
32023/1/1 16:24:43INAα
42023/1/1 16:23:42INAα
52023/1/1 16:22:59INAα
62023/1/1 15:48:54INAα
82023/1/1 17:17:36OUTAα
92023/1/1 16:31:21OUTAα
102023/1/1 16:00:28OUTAα

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

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

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

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

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

can110

2023/01/07 04:22

同一userにおけるINとOUTの対応関係(ルール)がよく分かりません。 以下、元データを日付順に並び替えて左端に行番号列を追加して例にて 11 2023-02-03 15:40:00 IN B 12 2023-02-04 16:00:00 IN B 16 2023-02-04 17:20:00 OUT B において、16のOUTに対応するのが12ではなく11である理由 13 2023-02-05 16:20:00 IN B 15 2023-02-07 17:00:00 OUT B 17 2023-02-10 18:00:00 OUT B において、13のINに対応するのが15ではなく17である理由 とくに2つ目、13のINに対応するのは15であるのが自然な気がしますが。
nrnrdsa

2023/01/07 15:39

>とくに2つ目、13のINに対応するのは15であるのが自然な気がしますが。 サンプルデータの誤りでした。更新したうえで情報の追記をしました。 >同一userにおけるINとOUTの対応関係(ルール)がよく分かりません。 明確はルールは存在せず、もっともらしさで自分で定義する必要があります。 考えられる定義としてはIN,OUTのuser,service,時間の誤差が最小になるレコードで取得しもっともらしいIN-OUTのペアを作るということです。 この時、ループしてすべての組み合わせとの最小時間を利用するという方法が考えられますが、総当たりで時間がかかりすぎるためより良い方法はないかということで本投稿をしております。
guest

回答3

0

質問が変更されて、「誤差が最大となるように」に合わせてみました。
(INとOUTのペアリングのところ以外は修正前と同じです)

下記は、直前がINでないINと、直後がOUTでないOUTをマッチさせるようにしたものです。
これで、だいたい最大っぽい感じになると思いますが、もし合わないケースがあれば、ご自身で条件を変えてみてください。

python

1import pandas as pd 2import numpy as np 3 4# df = ... 5 6# 日付ソート 7df = df.sort_values(['datetime']) 8 9# INとOUTのペアリング 10def pair_number(x): 11 valid_in = (x == 'IN') & (x.shift() != 'IN') 12 valid_out = (x == 'OUT') & (x.shift(-1) != 'OUT') 13 num = np.arange(len(x)) 14 num_in = np.maximum.accumulate(np.where(valid_in, num, -1)) 15 return np.where(valid_out, num_in, num) 16 17df['key'] = df.groupby(['user', 'service'])['in_out'].transform(pair_number) 18 19# ピボット 20df2 = pd.pivot( 21 df, 22 index=['user', 'service', 'key'], 23 columns='in_out', 24 values=['No', 'datetime']) 25 26# カラム名の整理 27df2.columns = [c2 + '_' + c1 for (c1, c2) in df2.columns] 28df2 = df2.reset_index().drop(columns='key') 29 30print(df2)

修正前の回答

datetime でソートして、user, service で groupby した上で、
in_out に連番をつけて、'OUT'のところは -1 すると、
IN → OUT と連続するところだけが同じ数字になります。
あとは、それを使って pivot すれば、だいたい望みの形になると思います。

python

1import pandas as pd 2import numpy as np 3 4#df = ... 5 6# 日付ソート 7df = df.sort_values(['datetime']) 8 9# INとOUTのペアリング 10df['key'] = df.groupby(['user', 'service'])['in_out'].apply( 11 lambda x: np.arange(len(x)) - (x == 'OUT')) 12 13# ピボット 14df2 = pd.pivot( 15 df, 16 index=['user', 'service', 'key'], 17 columns='in_out', 18 values=['No', 'datetime']) 19 20# カラム名の整理 21df2.columns = [c2 + '_' + c1 for (c1, c2) in df2.columns] 22df2 = df2.reset_index().drop(columns='key') 23 24print(df2) 25# user service IN_No OUT_No IN_datetime OUT_datetime 26#0 A α 1 7 2023-01-01 09:00:00 2023-01-01 09:10:00 27#1 A α NaN 9 NaT 2023-01-01 15:20:00 28#2 A β 2 8 2023-01-01 09:25:00 2023-01-01 12:00:00 29#3 A γ 3 NaN 2023-01-01 12:00:00 NaT 30#4 A δ 4 NaN 2023-01-01 12:30:00 NaT 31#5 A ε 5 10 2023-01-01 15:10:00 2023-01-01 15:20:00 32#6 A ζ 6 11 2023-01-01 12:12:00 2023-01-01 15:20:00

投稿2023/01/08 04:40

編集2023/01/14 12:10
bsdfan

総合スコア4567

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

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

nrnrdsa

2023/01/12 11:22

こちらのコードでほとんどやりたいことは実現可能でした。 ですが、仕様の漏れがありこのコードだとIN-OUTが正確に取れない場合は誤差が最小になるようにしていますが、正確には誤差が最大となるようにする必要がありました。 修正版のデータと加工後データのキャプチャを差し替えましたのでこちらに対して処理できるようにできないでしょうか?
bsdfan

2023/01/14 12:12

回答を更新しましたが、とりあえず参考までにということで。 別の回答者さんの指摘にもあるように、「誤差が最大」と書くのは簡単ですが、まじめに考えると、いろんなケースを考えて、こういうときはこうするというのがいくつか出てくるはずなので、そのあたりをちゃんと考えてみてください。 そうすると、条件が明確になって、おのずとコードが書けるようになると思います。
guest

0

まずはIN行毎に対応するOUT行番号を取得します。
これは全レコード1回の走査で得ることができます。

ルールとしては最も近くに出現した行を対応させるものとします。
すなわち

  • 1(IN), 2(IN), 3(OUT) なら 2と3が対応します。
  • 1(IN), 2(OUT), 3(OUT) なら1と2が対応します。

次にこの番号をもとにしたmergeにて

  • IN行と対応するOUT行を結合したテーブル
  • IN行に対応しないOUT行を抽出したテーブル

を求め、この2つのテーブルを行方向に結合します。

最後に適当に並び替えます。

Python

1import pandas as pd 2from io import StringIO 3 4# テストデータ 5s = """No,datetime,in_out,user,service 61,2023/1/1 9:00,IN,A,α 72,2023/1/1 9:25,IN,A,β 83,2023/1/1 12:00,IN,A,γ 94,2023/1/1 12:30,IN,A,δ 105,2023/1/1 15:10,IN,A,ε 116,2023/1/1 12:12,IN,A,ζ 127,2023/1/1 9:10,OUT,A,α 138,2023/1/1 12:00,OUT,A,β 149,2023/1/1 15:20,OUT,A,α 1510,2023/1/1 15:20,OUT,A,ε 1611,2023/1/1 15:20,OUT,A,ζ""" 17df = pd.read_csv(StringIO(s), parse_dates=['datetime']) 18 19# 末尾から処理するため、日付とIN/OUTの逆順にソート 20df = df.sort_values(['datetime','in_out'], ascending=False) 21print(df) 22 23# IN行に対応するOUT行番号を取得 24link = {} # キー:userとserviceの組み合わせ, 値:対応するOUT行番号 25def find_out(row): 26 key = (row['user'], row['service']) 27 io = row['in_out'] 28 no = row['No'] 29 out_no = -1 30 if io == 'OUT': 31 link[key] = no 32 else: 33 if key in link: 34 out_no = link[key] 35 link[key] = -1 36 return out_no 37df['out_no'] = df.apply(find_out, axis=1) 38print(df) 39 40# IN行にOUT行をくっつける 41df_i = df[df['in_out']=='IN'].merge(df, left_on='out_no', right_on='No', how='left', suffixes=['', '_o']) 42print(df_i) 43 44# IN行に対応しないOUT行のみを抽出する 45df_o = df[df['in_out']=='IN'].merge(df, left_on='out_no', right_on='No', how='right', suffixes=['', '_o']) 46print(df_o) 47df_o = df_o[(df_o['in_out_o']=='OUT') & (df_o['No'].isna())] 48df_o[['No','user','service']] = df_o[['No_o','user_o','service_o']] # 日付を除き、OUT行のものを採用 49print(df_o) 50 51# 両方を行方向にまとめる 52df_ret = pd.concat([df_i,df_o], axis=0) 53 54# 不要な列の削除と並び替え 55df_ret = df_ret.loc[:,['No','datetime','user','service','datetime_o']].sort_values(['No']) 56print(df_ret) 57""" 58 No datetime user service datetime_o 595 1 2023-01-01 09:00:00 A α 2023-01-01 09:10:00 604 2 2023-01-01 09:25:00 A β 2023-01-01 12:00:00 613 3 2023-01-01 12:00:00 A γ NaT 621 4 2023-01-01 12:30:00 A δ NaT 630 5 2023-01-01 15:10:00 A ε 2023-01-01 15:20:00 642 6 2023-01-01 12:12:00 A ζ 2023-01-01 15:20:00 650 9 NaT A α 2023-01-01 15:20:00 66"""

投稿2023/01/08 02:49

can110

総合スコア38266

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

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

nrnrdsa

2023/01/12 11:17

こちらのコードでほとんどやりたいことは実現可能でした。 ですが、仕様の漏れがありこのコードだとIN-OUTが正確に取れない場合は誤差が最小になるようにしていますが、正確には誤差が最大となるようにする必要がありました。 修正版のデータと加工後データのキャプチャを差し替えましたのでこちらに対して処理できるようにできないでしょうか?
can110

2023/01/12 11:30

私の回答は私が回答に記した(決めた)ルールにしたがっています。 それと異なるのであれば、まずはその仕様(ルール)を明文化して質問に追記ください。 とくに - その「誤差」というのが何を基準として何との差として求められるのか - それを「最大となるようにする」とは具体的にどのような計算(処理)を行うのか といった部分を明確にする必要があるかと思います。
guest

0

1行ごとにループしなくても、全行を1回ずつ処理するだけで実現できませんか

  • 特定時刻のINが現れたら時刻をキーとして連想配列に追加する
  • INに対応するOUTが現れたらそれをOUT時間とする

投稿2023/01/07 04:24

yuma.inaura

総合スコア1453

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

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

nrnrdsa

2023/01/07 15:44

>特定時刻のINが現れたら 特定時刻のINはどのように設定(処理)すればよいのでしょうか? 本件の詰まりどころ(=解決ポイント)はIN-OUTのロジックの設計とそのロジックをPythonでどのように描くべきかということと認識しております。 >連想配列に追加する あまり本質と関係ないことに思いますが、連想配列=辞書型と理解しております。 辞書にする際のKey,valuesはそれぞれ何になるのでしょうか? 連想配列で解決するという意味だと思いますがあまり意味を理解できていません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問