np.datetime64の浮動小数としての扱いについて、本家サイトを見てもよくわからなかったので、挙動を確かめた。
結論
- 場合によって数値のオーダーが勝手に変化するので危険
- 第二引数でオーダーを強制的に指定するべし
np.datetime64とは?
pythonでの時刻を取り扱う規則?の一種で、numpy
パッケージに組み込まれているもの。他には組み込みモジュールのdatetime
という組み込みモジュールやpandas
のpd.Timestamp
などがある。
64は64bitなのだと思う。
Numba
ではpandasはサポートされてないが、np.datetime64はサポートされている(ので使いたい)。
pandas
ほど受け入れる文字列は柔軟ではなく、ハイフン"-"で年月日、コロン":"で時分秒ミリ秒...を区切ることで、時刻を指定する。
import numpy as np
# numpyのバージョンは1.24.1
np.datetime64('1970-01-01 00')
# -> numpy.datetime64('1970-01-01T00','h')
np.datetime64('1970-01-01 00:00')
# -> numpy.datetime64('1970-01-01T00:00')
上記の2例、分を書くか時で止めるかで出力結果が少し異なる。
Unix時刻
上述の1970年1月1日0時UTC(世界標準時)はUnix時刻と呼ばれ、この時刻を基準に0と定義し、時間を相対的に表現する方法がある。この数値はdatetime
やpandas
ではTimestampと呼ばれる。故に、Unix時刻を浮動小数型へ変換すると0になる。
型の変換はnumpy
においてはastype
メソッドが便利。
np.datetime64('1970-01-01 00').astype('float')
# -> 0.0
np.datetime64('1970-01-01 00:00').astype('float')
# -> 0.0
分まで書いても時まででも同じ結果となる。
このことにより、時刻を数列として扱いたいとき等に、このようにUnix秒へ変換すると、閏年の考慮や10進数/24進数/60進数の混在するプログラムを作らずに済むので便利。(追記:これは別にUnix秒へ変換せずともdatetime
やpd.Timestamp
等をそのまま利用すれば良い。)
浮動小数(や整数)へ変換することで割り算など柔軟に使えるようになり、例えば時間内挿の計算に役立つ。
問題の処理
例えば、上記例より10時間ずらした値を見てみると、分まで書くか時までとするかで結果が異なる。(この仕様を知らず、泣かされた。。)
np.datetime64('1970-01-01 10').astype('float')
# -> 10.0
np.datetime64('1970-01-01 10:00').astype('float')
# -> 600.0
つまり、時間だけを指定すると単位は「時間」になり、分まで指定すると単位は「分」になる。10時間=600分。
1970年から遠ざかるほど、単位に比例してその差は増える。
np.datetime64('2024-01-01 00:00').astype('float')
# -> 28401120.0
np.datetime64('2024-01-01 00').astype('float')
# -> 473352.0
28401120.0/473352.0
# -> 60.0
特にpandas
で生成したcsvファイルでは、フォーマットを指定しなかった場合、0時や0分など、最小単位が0となると省略されがちなので恐ろしい。
解決
一番最初の出力がヒントで、第二引数にD
(日)やh
(時)やm
(分)やs
(秒)を与えると、狙った時間単位を強制できる。それぞれUnix時間からの差分量を示す。
np.datetime64('2024-01-01 00', 's').astype('float')
# -> 1704067200.0
np.datetime64('2024-01-01 00', 'm').astype('float')
# -> 28401120.0
np.datetime64('2024-01-01 00', 'h').astype('float')
# -> 473352.0
np.datetime64('2024-01-01 00', 'D').astype('float')
# -> 19723.0
np.datetime64('2024-01-01 00', 'M').astype('float')
# -> 648.0
np.datetime64('2024-01-01 00', 'Y').astype('float')
# -> 54.0
大きい単位を選んだ場合、細かい時間の情報は失われるので注意。
np.datetime64('2024-01-01 00', 'h').astype('float')
# -> 473352.0
np.datetime64('2024-01-01 00:00:01', 'h').astype('float')
# -> 473352.0
np.datetime64('2024-01-01 00:59:59', 'h').astype('float')
# -> 473352.0