timedelta型のデータをグラフにプロットするのめんどくせえよなぁ!ハム○郎!!お前もそう思うよなァ!!


はじめに

これはNISE (南山大学 青山研究室) Advent Calendar 2019の12日目の記事です。

datetime型の計算、というかtimedelta型の扱いがなかなかしんどかったのでそれを残しておこうかな、と思います。

やりたいこと

datetime型のカラムが2列挿入されているCSVファイルからその差分を計算し、計算結果のsecondmicrosecondの部分をmatplotlibで折れ線グラフにしてプロットします。

グラフの横軸はCSVのindex、縦軸はtimestampの差とします。

CSVの中身はこんな感じです。

send receive topic CPU usage memory usage
16:25:54.492262 16:25:54.545652 normal/DrivePhoto/0 0.1 1140277248
16:25:54.502742 16:25:54.549113 normal/Timestamp/0 25 1140740096
16:25:54.620683 16:25:54.672700 normal/DrivePhoto/1 3.1 1140445184
16:25:54.630237 16:25:54.682169 normal/Timestamp/1 25 1140084736
16:25:54.749052 16:25:54.800217 normal/DrivePhoto/2 3.2 1140187136

sendreceivedatetime型ですね。

何がめんどくさいのか

上記のCSVの例のように、datetime型はstrftimeを利用することで通常のdatetime型から任意の形式に変形することができます。
例えばこのように、

today=datetime.datetime.today().strftime("%H:%M:%S.%f")

こうすれば、上のCSVみたいな表示にできるわけですが…

datetime型を計算するとtimedelta型に強制変換されます。
こいつがクセもんで、上記の変形ができなくなるんですよね。
例えば、
receive-sendの計算結果のsecondとmicrosecondの部分だけ欲しい!yearとかmonthとかどうでもええねんボケェ
といったことをしたくても一筋縄にはいかないのです。
何をしようとも、
0 days 00:00:00.000000
の形から逃れられないのです。

これが分かった途端私は、

「あああああああtimedeltaはク○じゃけえええええええええ」

と発狂したわけです。

あ、そもそもなんでこの変換をしようとしているのかというと、グラフにtimedelta型をそのままプロットすると、グラフが強烈に見にくいものになってしまうからです。

どうやって解決したか

結論から言うと、
計算結果をリスト化→リストを文字列型に変換→文字列から余計な文字を省く→float型にキャスト

といった感じで解決しました。そのソースコードがこちら。

log_graph.py
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import sys
import csv
import datetime

file = input("input filename : ") #ファイル名入力

#読み込んだCSVファイルをListに変換
df = pd.read_csv(file, parse_dates=['send','receive'])

df['difference'] = df['receive'] - df['send'] #差分を計算

date = list(map(str, df['difference'])) #差分をmap関数でリスト化

date_str1 = ",".join(date) #リストを文字列化
date_str2 = date_str1.replace('0 days 00:00:0', '') #余分な文字を取り除く
date = date_str2.split(",") #文字列をリスト化

diff_df = pd.DataFrame(date, columns=['diff']) #プロット用DataFrameの生成

diff_df['diff'] = diff_df['diff'].astype(float) #froat型にキャスト

#matplotlibで折れ線グラフを生成
plt.plot(diff_df.index.values, diff_df['diff'])
plt.title(file)
plt.xlabel('message number')
plt.ylabel('difference time')
plt.show()

以下詳しく説明します

リスト化して文字列型に変換

まず、最終目標として、secondmicrosecondの部分のみ表示したいと上で書きました。
つまり、それ以外の要素を取っ払うということです。
ということは…

「文字列に強制変換してそれ以外の要素を消せばええやん!」

というまるで偏差値3のような考え方に至りました。

そんなわけで、リストに変換するのは文字列に変換するための準備です。
CSVファイルを読み込んでも、そのままではリストになっていません。
そのため、まずは計算結果が格納されているdf[difference]をリストに変換します。

さらに、map関数を使って一気にtimedelta型を文字列型に変換します。

文字列から余計な文字を省く

上でも述べたように、timedelta型は標準で
0 days 00:00:00.000000
のような形になっています。

ここから、secondmicrosecond以外の要素を取り除く…

date_str1.replace('0 days 00:00:0', '')

はい。言うまでもないですね。

float型にキャスト

グラフにプロットするときに、データは原則数値でないといけませんよね。
文字列を要素とするなら、それはよほどレアなケースでしょう。

secondmicrosecondだけが残ったデータ、一体どのような形になっているか考えてみましょう。

send receive difference
16:25:54.492262 16:25:54.545652 0.053390
16:25:54.502742 16:25:54.549113 0.046371
16:25:54.620683 16:25:54.672700 0.052017
16:25:54.630237 16:25:54.682169 0.051932
16:25:54.749052 16:25:54.800217 0.051165

differenceが残ったデータです。

…実質数値じゃん

ということで、float型にキャストして優勝。
timedeltaを見事ぶちのめしました。

ちなみにできたグラフはこちら。

(このグラフはあるtopicを抽出しているので上のCSVとは若干異なるものになってます)

おわりに

今回はtimedelta型を超絶無理やり変形してグラフにプロットする方法を書きました。

ここまでやって言うのもアレですが、自分でもアホなやり方だな~って関心しちゃうくらい無理やりな手法なので、ホントに困ったときに最終奥義くらいのつもりでいてくれると嬉しいです。




ワイ「こんな無理やりなやり方マジで酷いよなぁ!!?!ハム○郎!!お前もそう思うよなァ!!」
ワイの中の全肯定ハム○郎「そうなのだ!!!もっといいやり方が絶対あるはずなのだ!!!!」

ネタが古いとか言わない




そんなわけで、「もっといいやり方知ってるぜ!」という方がいらっしゃいましたら、是非コメントでご教授ください…