Help us understand the problem. What is going on with this article?

Pandasでの日付・時間周りのちょっとしたチートシート

More than 1 year has passed since last update.

Apacheのログやアプリケーションサーバのログ分析をする機会がありまして、その際に出てきた定番の処理を自分用に再編成してまとめていきます。

まずは日付・時間列の分離と結合

apacheログのサンプルです(生成ツールがあるんですね。。。)

108.81.70.158 - - [16/Jun/2015:13:59:34 +0000] "GET /item/electronics/3717 HTTP/1.1" 200 86 "-" "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"

176.33.96.225 - - [16/Jun/2015:13:59:35 +0000] "GET /category/cameras HTTP/1.1" 200 88 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"

32.129.29.109 - - [16/Jun/2015:13:59:35 +0000] "GET /item/toys/2278 HTTP/1.1" 200 129 "/search/?c=Toys" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"

で、ここからapacheログをparseするのですが、区切り文字を正規表現で表して項目を分離してread_csvをやります。
詳細は先人たちのお知恵を借ります。

http://blog.mmast.net/read-apache-access-log-pandas

前提はここまで。
以下は純粋にpandasの話になります。
読み込んだapacheログはdfに読み込まれているものとします(pandas.DataFrame)。

日付と時間列 分離・結合

parserの書き方次第ですが「日付 時間」列になっていたり「日付」「時間」と分離されたりします。ものによっては最初から希望通りの列を得られない場合がありますので分離や結合をやってみます。

「日付 時間」列を「日付」列と「時間」列に分離する

df[['date', 'time']] = df.loc['date_time'].apply(lambda x: pd.Series(str(x).split()))

「日付」「時間」列を「日付 時間」列に結合する(ついでにDatetimeIndex型にする)

df[['date_time']] = df.DatetimeIndex(df[['date', 'time']].apply(lambda x: '{} {}'.format(x[0], x[1]), axis=1)

項目をインデックス化

DatetimeIndexを使わずにインデックス指定もできます。

df.set_index['date_time']

続けてデータ加工処理をやっていきます。

キーワード処理

キーワード置換

特定の加工をするために正規表現などを用いてレコードの加工を行います。この部分はpythonに渡す前にsed などを用いて加工しても良いと思います(むしろそっちで)。
replaceメソッドを正規表現を使って実施します。メソッドは続けて実行が可能です。

df_sel = df.replace("POST\s", "", regex=True).replace("GET\s", "", regex=True) 

キーワード抽出

特定のキーワードを含むデータのみ選択します。同様に正規表現を使用できます。

df_sel = df[df['request'].str.contains('AAAAA')]

そもそもの入力レコードの件数チェック

対象データの件数が0件だったりすると後々面倒なことになります(型エラーだったりいろいろと)。
なので最小に対象レコード0件判定を行いバイパスするほうがシンプルな制御になります(たぶん)。

import os
BASEDIR="/home/user/xxxxxxxxxxx"
FILE=os.path.join(BASEDIR, "apache.log")
len_FILE = sum(1 for line in open(FILE))

# 空 判定
if len_FILE > 0:
  xxxx
else:
  pass

他ファイルとの連携

Apacheログだけでなく内部処理を行うWebApplicationServerとのログ結合もしたいです。結合Key項目がありますので(ID)でMergeします。

他ファイルとの結合

pd.merge(Apache, Wacs, on='ID')

#さらにこれをCSVファイルに出力します、インデックスはいらない
pd.merge(Apache, Wacs, on='ID').to_csv('BASEDIR + 'merged.csv', index=None)

インデックス・カラム操作

インデックスを再度割り振り

抽出したりマージするといろいろとインデックスが乱れます。
作り直したDataFrameのインデックスを割り当て直します。

df.reset_index(drop=True, inplace=True)

カラム名を変更する

df.columns['datetime','id','requestId','requestime','userId']

データ分析

いよいよデータ分析(といってもちょっとした集計)になります。

日付単位でリサンプル、ユニークユーザ数を日毎にカウントする

date_timeにインデックス設定状態でgroupbyを行っているので、ユーザ毎のカウント(列で)を行うためにinstack()してindexからcolumnにuserIDを戻してやります。

df.groupby('userID').resample('D').mean().unstack().count()

3秒以上、reqtimeかかっているトランザクションの日別発生比率

対象と全量、それぞれ必要になりますね。
日付(インデックス)と比率がきれいに並びます。

df_sel = df[df['reqtime'] > 3000].resample('D').['req'].count()
df_all = df.resample('D').['req'].count()
pd.concat([df_sel / df_all], axis=1)

指定時間あたりのトランザクション数を算出する

# 対象時間を選定
START_TIME='2017-12-24 09:00:00'
END_TIME='2017-12-24 11:00:00'

# 対象時間データを抽出(2時間)
df_sel = df[(df['date_time'] >= START_TIME) & \
           [(df['date_time'] < END_TIME)] 

# 該当時間での日別件数と日別TPSを算出
pd.concat([df_sel.resamle('D').['req'].count(), df_sel.resamle('D').['req'].count() / (3600 * 2)], axis=1)

抽出条件と反転条件

ある条件に当てはまるユーザIDによるトランザクションを抽出する。処理時間が長い順に。

# 当てはまる人たち
df_sel = df[df['userID'].str.contains('^A')].sort_values('reqtime', ascending=False)

# 当てはまらない人たち
df_sel = df[~df['userID'].str.contains('^A')].sort_values('reqtime', ascending=False)

インデックスのちょっと繊細な操作

ここから先はデータにインデックスは設定してありません(日付、時間もデータの一部になります)。

データ抽出するとインデックス番号が飛びます。

# 対象時間データを抽出(2時間)
df_sel = df[(df['date_time'] >= START_TIME) & \
           [(df['date_time'] < END_TIME)] 

# 抽出後DataFrameのインデックス番号を振り直します
df_sel.reset_index()

同じインデックス番号でマージしたいときがあります。
何も指定せずにmergeすると(インデックスでなく別の何かのデータ列でマージ)、見事に期待するマージになりません。。。

# 抽出処理などしたであろうDataFrameのインデックスを振り直します
df_1.reset_index()
df_2.reset_index()

# インデックス番号でマージ(お互いのDataFrameともに)
df.merge(df_1, df_2, left_index=Trune, ringht_index=True)

それってどんなとき?

例えばトランザックションの開始・終了を把握したいとしたとき、4つのトランザクションを1つの塊として把握する必要があるとします。
この場合1つ目のトランザクション開始時間と4つ目のトランザクション終了時間を抽出することになります。そして4つの塊がその後延々とログに出現する、、、状況で、かつrequestID毎にシリアライズされていると仮定します(かなりご都合の仮定ですが)。

やること

0番目と3番目のサイクルでDataFrameから明細を取り出す。
つまり4で割って余りで分類することで一定パターンの処理を行う。

抽出条件
index % 4 = 0 →開始
index % 4 = 3 →終了

抽出したのでindexのリセット
index番号でmergeして開始時間・終了時間を横に並べる

以上の処理はこんな感じに。

df_start = df[df.index % 4 == 0]
df_start.reset_index(drop=True, inplace=True]

df_end = df[df.index % 4 == 3]
df_end.reset_index(drop=True, inplace=True]

pd.merge(df_start, df_end, left_index=True, right_index=True)

レポートは以上です。自分用のチートシートとして書きました。
もうちょっとこう書くといいよってあればぜひご指摘をお願いします。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした