初めて記事を書かせて頂きます。
良い記事を書くためのガイドラインを見ながら書きました
時間を扱い始めて日が浅いので、間違っている点や追加点があればお願いします!
2021/02/01 更新
正規表現を用いてより分かりやすくより簡潔に修正してくださった方がいらっしゃいました!!
コメントをご参照ください!!
きっかけ
あるデータの継続時間(〇〇h〇〇m〇〇s)をdatetime64[ns]に変換して
時間ごとの変化量を可視化したいな~~って思った時に
**1dayに変換されず24時間を超えるデータ(27h〇〇m〇〇s)**があったり、
**まず1時間に到達してなかったり(〇〇m〇〇s)**と、
与データの仕様で色々困ってしまったからです(詳しくは後述データ)。
綺麗にデータが与えてくれるのが普通だと思うけど!!
同じような境遇の人がいればいいな...
以下に、解決した処理を書きます。
バーションとか
import sys
import pandas as pd
import datetime
# Pythonのバージョン
print(sys.version)
# pandasのバージョン
print(pd.__version__)
1.1.3
3.8.5 (default, Sep 3 2020, 21:29:08) [MSC v.1916 64 bit (AMD64)]
原因となったデータとその処理
ここでは簡潔に適当なデータを用意します。(ダミーです)
test_data = pd.DataFrame(['1h2m3s', '10m30s', '25h23m10s'],
columns=['test'])
test_data.head()
test
0 1h2m3s
1 10m30s
2 25h23m10s
見やすくしたもの↓
時間データ(test) | |
---|---|
0 | 1h2m3s |
1 | 10m30s |
2 | 25h23m10s |
データを見ると、 | |
index 1番目で本来は時間(h)があって欲しいのに10m30sになってたり、 | |
index 2番目で時間(h)において24時間を超えてしまっていますよね |
よくある自分が知っている処理では
test_data['test_converted'] = test_data['test'].map(lambda \
x:datetime.datetime.strptime(x, '%Hh%Mm%Ss'))
という処理が有名だと思いますが(mapで1要素ずつ取り出してそれに対して処理をしていく)、結果
time data '10m30s' does not match format '%Hh%Mm%Ss'
と表示されてしまいます。1時間に到達していないデータは同様な結果になります。
まずは与データの仕様に合わせる
まず、与データの仕様に合わせるため、
データに対して処理を変えなければいけません
ここで、.map属性には関数が適用できることを思い出します。
例えば、
test_data['test_converted'] = test_data['test'].map(lambda x:func(x))
という風に、関数func()を適用させることができます!
実際に合わせてみましょう。
適用させる関数をtime_convert()とかにしてみます
ここでは与えられる文字列にhが含まれているかを.findで探してあげます。
見つからなかった場合は、-1が返ります。
そして存在する場合と存在しない場合で.strptimeの処理を分けてあげます。
(ここの処理はデータの型とかによって変えてみてください!128m20sだったら'm'で切ったほうが良いし...)
def time_convert(x):
if x.find('h') == -1:
# ?m?s の場合
result = datetime.datetime.strptime(x, "%Mm%Ss")
else:
# ?h?m?sの場合
result = datetime.datetime.strptime(x, "%Hh%Mm%Ss")
return result
# 処理部分
test_data['test_converted'] = test_data['test'].map(lambda x:time_convert(x))
わーいやったーできましたーーとなりましたが、新しく問題が発生します。
time data '25h23m10s' does not match format '%Hh%Mm%Ss'
そうです、
%Hといったもの書式コードとかディレクティブとか言うらしいのですが、
%Hは00, 01, ..., 23にしか対応していません。
( https://docs.python.org/ja/3/library/datetime.html#strftime-and-strptime-format-codes から参照)
これが次に直面した問題で、
与データの仕様であり仕方がないので、変換する方法を探します。
24時間を超えるデータについて
25hというものを、1day 1h というdatetime型に変換してあげたいと思います
そのためには、まず時間部分とそれ以外の部分で分割してあげます。
これには.split属性を使います。'h'で分割させてあげることにします。
hoursは時間、restは残りの部分とします
hours, rest = x.split('h')
print(hours, rest)
25 23m10s
と出力されました。この何で分割するなどはデータで変えてあげてください
次に行う処理は、25時間を1日1時間に分割させてあげる処理なのですが、
ここではdatetime.timedelta()を使います。
これを使うと、1時間が3600秒に変換され、残りの変換されたdatetime型と足し合わせることができます。
datetime型は1日を超える時間加算が行われると自動的に1日進んだりしてくれます。
( https://docs.python.org/ja/3/library/datetime.html#timedelta-objects )
実際に処理してみます。先ほどの関数をちょっと変えます。
def time_convert(x):
if x.find('h') == -1:
# ?m?s の場合
result = datetime.datetime.strptime(x, "%Mm%Ss")
print(type(result))
else:
# ?h?m?sの場合
hours, rest = x.split('h')
print(hours, rest)
# 以前のやつ
# result = datetime.datetime.strptime(x, "%Hh%Mm%Ss")
# 新しいバージョン
# 時間のみの変換 + それ以外の変換
result = datetime.timedelta(hours=int(hours)) + \
datetime.datetime.strptime(rest, "%Mm%Ss")
return result
# 処理部分
test_data['test_converted'] = test_data['test'].map(lambda x: \
time_convert(x))
( \ を入れるだけでコード内改行が出来るらしいです)
おっしゃー出来た!!ってなったらまた問題が出ます
test test_converted
0 1h2m3s 1900-01-01 01:02:03
1 10m30s 1900-01-01 00:10:30
2 25h23m10s 1900-01-02 01:23:10
いや出来てるけど、1900っていつやねん!?てなります。
変換の仕様上こうなるのですが、私が得たい結果ではないので変換します。
(詳しくは検索を~)
見たい情報に合わせる(ここからは蛇足)
ここからは蛇足で、やらなくてもいいかも!
この処理は簡単で、timedelta()は年や月、日も指定できるので、
逆に減算してあげます。つまり、
result -= datetime.datetime(year=1900,month=1,day=1)
をします。
ここで、得たい情報は
1 days なんたら:なんたら:なんたら
ですので、またまた関数の中身を変更させてあげます。すると、
def time_convert(x):
if x.find('h') == -1:
# ?m?s の場合
result = datetime.datetime.strptime(x, "%Mm%Ss")
print(type(result))
else:
# ?h?m?sの場合
hours, rest = x.split('h')
print(hours, rest)
# 以前のやつ
# result = datetime.datetime.strptime(x, "%Hh%Mm%Ss")
# 新しいバージョン
# 時間のみの変換 + それ以外の変換
result = datetime.timedelta(hours=int(hours)) + \
datetime.datetime.strptime(rest, "%Mm%Ss")
# この部分
result -= datetime.datetime(year=1900,month=1,day=1)
return result
# 処理部分
test_data['test_converted'] = test_data['test'].map(lambda x: \
time_convert(x))
となります、結果は
test test_converted
0 1h2m3s 0 days 01:02:03
1 10m30s 0 days 00:10:30
2 25h23m10s 1 days 01:23:10
と出来ました!!やったー!
終わりに
同じような悩みを持っていた方がこの記事を見て解決したーー!!ってなってくれたら嬉しいです!
datetime型にすることでプロットを簡単にすることが出来るのでいいですよね~
ここで、補足として、時間をx軸に表示させたいな~って時の方法を下に書きます
import matplotlib.pyplot as plt
plt.figure(figsize=(16,8))
# ラベルとして表示させたいので文字列に変換~
time_index = [str(a) for a in test_data['test_converted'].tolist()]
# plt.plotのx軸とxticksのticks部分は一致させないといけないので、秒数に変換したりする
# 変換させないと '<' not supported between instances of 'numpy.float64' and 'Timedelta' ってエラーが出ちゃう...
# ↑ これもよく分からない
plt.plot(test_data['test_converted'].dt.total_seconds(), test_data.index, color = 'blue')
plt.xticks(ticks = test_data['test_converted'].dt.total_seconds(),
labels= time_index,
fontsize = 20,
rotation = 45)
plt.yticks(fontsize = 20)
とすることで、
と変化が見れました!!!ダミーデータなのでの遷移意味わからんけどね!!
(ここも綺麗に表示できる方法があれば教えてください...ここのリファレンス読んで~とか)
日付云々ほんとに苦手過ぎて何回検索して何回沼にハマってるんだっていう感じになってる...
記事を見てくださり、ありがとうございました~!