SPSS Modelerのタイムスタンプ関連clem関数をPythonで書き換える。

以下の記事も参考にして、SPSS Modelerでタイムスタンプ関連のclem関数を使って、よく行う特徴量抽出を行ってみます。そしてその処理をPythonのpandasで書き換えてみます。

https://qiita.com/Makimaki2020/items/72e3b774039327fa4ac4

日付型については以下の記事で紹介しています。

https://qiita.com/kawada2017/items/c41f443e5246d331bea1

代表的なものとして以下の加工処理を行っていきます。

1. タイムスタンプデータの読み込み
1. 文字列や数値からのタイムスタンプデータ生成
1. 現在タイムスタンプの取得
1. タイムスタンプの年、月、日、時、分、秒の分解
1. タイムスタンプの差の計算
1. タイムスタンプの大小比較
1. タイムスタンプの加算減算


以下の、タイムスタンプ(TS),電力(Power),温度(Temperature),圧力(Pressure),起動時間(Uptime),状態(Status),エラーコード(Outcome)が記録された時系列のセンターデータを使います。毎分のセンサーの値が記録されています。今回はこの中のTSのみを使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/59840a58-0896-227e-fb86-f127ea04b7b6.png)


# 1m．① 文字列や数値からのタイムスタンプデータ生成　Modeler版

文字列や数値からタイムスタンプ型データを生成してみます。タイムスタンプ演算をするために文字列データから型変換をすることはよくあります。また年、月、日、時、分、秒が別列に分かれて保存されている場合に、それらの列からタイムスタンプ型のデータを作成することもよくあります。

まずModelerのタイムスタンプや時間のフォーマットを確認しておきます。ストリームのオプションの「日付/時刻」の中にあります。YYYY-MM-DDとHH:MM:SSがデフォルトです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/a8eb965d-56c3-166d-4093-990a381c230e.png)

CSVデータを読み込んだ際に、デフォルトでは「自動的に日付と時間を認識します」のオプションが有効になっており、ストリームオプションで指定されていたYYYY-MM-DD HH:MM:SSで解釈できる文字列データはタイムスタンプデータとして読み込みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0a9276de-790b-7767-650b-b1a3e491fe23.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/42e7e291-6f58-310a-8496-61a82abe4494.png)


# 1p．① 文字列や数値からのタイムスタンプデータ生成　pandas版

pythonには以下のような複数のTimestamp型があります。

* datetime.datetime
* numpy.datetime64
* pandas._libs.tslibs.timestamps.Timestamp

```python:データ型確認
#datetime.datetime
dtdt=datetime.datetime(2021, 6, 10, 3, 11, 0)
print(dtdt)
print(type(dtdt))

#numpy.datetime64
npdt=np.datetime64('2021-06-10 03:11:00')
print(npdt)
print(type(npdt))

#pandas._libs.tslibs.timestamps.Timestamp
pddt=pd.to_datetime('2021-06-10 03:11:00')
print(pddt)
print(type(pddt))
```

表示上はnumpy.datetime64は日付と時間の間に「T」が入っていました。

```ouput:結果
結果
2021-06-10 03:11:00
<class 'datetime.datetime'>
2021-06-10T03:11:00
<class 'numpy.datetime64'>
2021-06-10 03:11:00
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
```

この記事ではpandasのデータフレームを使うので、pandasのTimestamp型を使用していきます。
しかし、datetime.datetimeとnumpy.datetime64をpandasの列にいれると自動的にpandas._libs.tslibs.timestamps.Timestampに変換されていました。
dfdt.dtypesでみるといずれもdatetime64[ns]として表示されます。互換性があるようです。

```python
dfdt =pd.DataFrame({'datetime':[dtdt],
                   'npdatetime':[npdt],
                   'pddatetime':[pddt]})
print(dfdt.dtypes)
print(type(dfdt['datetime'][0]))
print(type(dfdt['npdatetime'][0]))
print(type(dfdt['pddatetime'][0]))
```
```output:結果
datetime      datetime64[ns]
npdatetime    datetime64[ns]
pddatetime    datetime64[ns]
dtype: object
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/392c49b5-b7bf-66c4-5a2e-0bd0847e75a0.png)

csvファイルからpandasに読むこむときにはデフォルトでは文字列型で読み込まれてしまいますが、パースしてタイムスタンプ型として読み込むこともできます。
parse_datesに列を指定すると、その列がタイムスタンプとして解釈できればタイムスタンプ型データとして読み込まれます。

```python:数値型データからタイムスタンプ型データ生成
#現在のタイムスタンプ
df = pd.read_csv('COND2nts.csv', parse_dates=['TS'])
print(df.dtypes)
print(type(df['TS'][0])
```

以下ではTS列が、文字列としてではなく、datetime64[ns]型で読み込まれています。

```output:結果
TS             datetime64[ns]
Power                   int64
Temperature             int64
Pressure                int64
Uptime                  int64
Status                  int64
Outcome                 int64
dtype: object
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
```

* 参考

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html?highlight=parse_dates


# 2m．② 文字列や数値からのタイムスタンプデータ生成　Modeler版

フィールド作成ノードで文字列からタイムスタンプ型データを生成してみます。datetime_dateという関数でストリームオプションで指定されていたYYYY-MM-DD形式で文字列を指定し、datetime_timeという関数でHH:MM:SS形式で文字列を指定します。そしてその日付型データと時間型データをdatetime_timestampでタイムスタンプ型として統合します。

datetime_timestamp(datetime_date('2021-04-01'),datetime_time('03:11:00'))

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/34de417c-b214-9c47-15aa-cbc5e0d89689.png)

続いて、数値型データからタイムスタンプ型データを生成してみます。やはりdatetime_dateという関数を使いますが、年、月、日、時、分、秒の数値をカンマ区切りで指定します。
datetime_timestamp(2021,4,1,3,11,00)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/e5b11c2d-183e-b02e-7630-a98029855bda.png)


データ型ノードをつかって確認してみると、アイコンがカレンダーと時計になり、尺度が連続型になっています。正しくタイムスタンプ型が生成されていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/64f7ecd2-bb46-7f1d-0d2e-741c69822dc8.png)

# 2p．② 文字列や数値からのタイムスタンプデータ生成　pandas版

あらためて文字列からタイムスタンプ型データを生成してみます。pd.to_datetimeという関数でYYYY-MM-DD HH:MM:SS形式で文字列を指定します。YYYY-MM-DD HH:MM:SS形式以外のフォーマットの場合はformatstrのオプションでフォーマットを指定します。

```python:文字列からタイムスタンプ型データ生成
df1['REF_TS']=pd.to_datetime('2021-04-01 03:11:00')
```

続いて、数値型データからタイムスタンプ型データを生成してみます。いったんdatetime.dateで年、月、日、時、分、秒を数値で指定してdatetime.datetime型で生成します。pd.to_datetimeで変換せずとも自動的にpandas._libs.tslibs.timestamps.Timestampに変換されます。

```python:数値からタイムスタンプ型データ生成
df1['REF_TS2']=datetime.datetime(2021,4,1,3,11,0)
print(df1.dtypes)
```

```output:結果
TS             datetime64[ns]
Power                   int64
Temperature             int64
Pressure                int64
Uptime                  int64
Status                  int64
Outcome                 int64
REF_TS         datetime64[ns]
REF_TS2        datetime64[ns]
dtype: object
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/60e3edff-0606-31bf-8480-b66422ed3f1d.png)

* 参考

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html

# 3m．③現在のタイムスタンプの取得　Modeler版

「現在のタイムスタンプ」を取得してみます。現在のタイムスタンプはデータ生成日時の記録などによく使います。

フィールド作成ノードで「datetime_now」という関数を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/3f70309c-7f22-caab-369c-0e831de39f81.png)

現在のタイムスタンプが入ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/dad2c7df-2a29-8361-56e3-da99813f697e.png)

# 3p．③ 現在のタイムスタンプの取得　pandas版
datetime.date.now()で現在のタイムスタンプを取得します。
なお、np.datetime64('now')でも取得はできましたが、マニュアルにこの記述方法がなかったので、datetimeを使う方法の方が堅いかと思います。

```python:数値型データからタイムスタンプ型データ生成
#現在のタイムスタンプ
df2['TODAYTS']=datetime.datetime.now()
#以下でも動作するがマニュアルに見当たらなかった。
#df2['TODAYTS']=np.datetime64('now')
print(df2.dtypes)
```

```output:結果
TS             datetime64[ns]
Power                   int64
Temperature             int64
Pressure                int64
Uptime                  int64
Status                  int64
Outcome                 int64
TODAYTS        datetime64[ns]
dtype: object
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/fccbb46f-dc32-34cd-48ee-a1e213c391d6.png)
* 参考

https://note.nkmk.me/python-datetime-now-today/

https://stackoverflow.com/questions/19461258/python-get-current-time-in-numpy-datetime64-format/44602127

https://numpy.org/doc/stable/reference/arrays.datetime.html

# 4m．④ タイムスタンプの年、月、日、時、分、秒の分解　Modeler版

タイムスタンプから年、月、日、時、分、秒を取得してみます。時間毎のグループ化集計などのために抜き出すことがよくあります。
フィールド作成ノードでdatetime_year,datetime_month、datetime_day、datetime_hour、datetime_minute、datetime_secondという関数をつかって抜き出すことができます。ここではdatetime_minuteで分を抜き出してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0e387c23-f251-645b-f35d-7c0bc3911bbc.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/febd98f7-04e5-ed8c-b91b-fee9da84f017.png)

# 4p．④ タイムスタンプの年、月、日、時、分、秒の分解　pandas版
dtアクセサをつかって年year、月month、日day、時hour、分minute、秒secondの分解が可能です。dtアクセサはdayofyear（年初からの日数）やquarter(四半期)なども抜き出すことができます。
ここではdt.minuteで月を抜き出します。結果はintで返ります。

```python:数値型データからタイムスタンプ型データ生成
df3['MIN']=df3['TS'].dt.minute
```

```output:結果
TS             datetime64[ns]
Power                   int64
Temperature             int64
Pressure                int64
Uptime                  int64
Status                  int64
Outcome                 int64
MIN                     int64
dtype: object
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/8dc9b193-9591-1556-c844-54b27e2f2f59.png)

*参考

https://pandas.pydata.org/pandas-docs/stable/reference/series.html#datetimelike-properties


# 5m．⑤ タイムスタンプの差の計算　Modeler版

タイムスタンプ同士の差を計算してみます。あるシグナルとシグナルの時間間隔や傾きをとる場合など、時間差は重要な特徴量になりえます。

フィールド作成ノードでdatetime_in_secondsで秒に変換してから差を取ります。
以下の例だとタイムスタンプ(TS)- 基準タイムスタンプ(REF_TS)の秒数を返します。基準タイムスタンプ(REF_TS)＞タイムスタンプ(TS)であればマイナスの値が返ります。

datetime_in_seconds(TS)  - datetime_in_seconds(REF_TS)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/c01b01af-6dac-b675-6f9c-562af22e5fce.png)
さらに、分の単位で差をだしたい場合は60秒で割ることで、
( datetime_in_seconds(TS)  - datetime_in_seconds(REF_TS))/60
時間の単位で差をだしたい場合は60秒*60分で割ることで求めることができます。
( datetime_in_seconds(TS)  - datetime_in_seconds(REF_TS))/(60*60)

2021-04-01 03:11:00という基準日とタイムスタンプ(TS)の差がTS_DIFF秒、TS_DIFF分、TS_DIFF、に入っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/246690e5-09b1-56fe-1e48-4a9775fd0e63.png)

# 5p．⑤タイムスタンプの差の計算　pandas版
まず、以下のように単純にタイムスタンプの入った列の引算を行います。
df4["TS_DIFF_TD"]=(df4["TS"]-df4["REF_TS"])
これは整数ではなく、pandas._libs.tslibs.timedeltas.Timedeltaという特殊な型（df5.dtypesの結果だとtimedelta64[ns]）で値を返します。
ややこしいのは、Timedeltaはマイナスの時間を表さない仕様であることです。-1分の場合、-1 days +23:59:00というようなわかりにくい値が返ります（合計するとマイナス1分）。
これを.dt.secondsで秒数に変換してしまうと23:59:00を秒変換するので、86340秒（24*60*60-60）という期待しない値が返ってしまいます。
単なる秒数がほしいので、dtアクセサとをtotal_seconds()メソッドつかって.dt.total_seconds()で秒数に変換します。プロパティではなくメソッドなので（）が付くことに注意してください。

```python:タイムスタンプの差の計算
df4["TS_DIFF_TD"]=(df4["TS"]-df4["REF_TS"])
print(type((df4["TS_DIFF_TD"])[0]))

#以下は見た目も分かりやすい
df4["TS_DIFF秒"]=(df4["TS"]-df4["REF_TS"]).dt.total_seconds().astype('int')
df4["TS_DIFF分"]=(df4["TS"]-df4["REF_TS"]).dt.total_seconds()/60
df4["TS_DIFF時"]=(df4["TS"]-df4["REF_TS"]).dt.total_seconds()/(60*60)

#以下だとマイナスの時に間違った値が戻る
#df4["TS_DIFF"]=(df4["TS"]-df4["REF_TS"]).dt.seconds

#以下でも計算はできる
#df4["TS_DIFF秒"]=((df4["TS"]-df4["REF_TS"]) /np.timedelta64(1,'s')).astype('int')
#df4["TS_DIFF分"]=((df4["TS"]-df4["REF_TS"]) /np.timedelta64(1,'m'))
#df4["TS_DIFF時"]=((df4["TS"]-df4["REF_TS"]) /np.timedelta64(1,'h'))

print(df4.dtypes)
```

```output:結果
<class 'pandas._libs.tslibs.timedeltas.Timedelta'>
TS              datetime64[ns]
Power                    int64
Temperature              int64
Pressure                 int64
Uptime                   int64
                    ...       
REF_TS          datetime64[ns]
TS_DIFF_TD     timedelta64[ns]
TS_DIFF秒                 int32
TS_DIFF分               float64
TS_DIFF時               float64
```

TS_DIFF_TDとTS_DIFF秒の結果は同じですが、TS_DIFF_TDにはtimedelta64[ns]型で格納され、TS_DIFF秒は整数型になっています。そしてマイナス値になるときにはTS_DIFF秒の方が理解しやすい値として返っています。例では-60秒です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/5fa131f6-afc1-b1a4-8aba-0d3d4956069a.png)

timedelta（Python）のマイナス値表現の謎 - Qiita

https://qiita.com/__Attsun__/items/836bb3954ddcc6413dc4

pandas.Series.dt.total_seconds

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.total_seconds.html#

# 6m．⑥タイムスタンプの大小比較　Modeler版

タイムスタンプの大小比較をしてみます。ある作業工程以降かというようなフラグの特徴量が作れます。

フィールド作成ノードで派生：フラグ型に設定し、条件に以下を入力します。基準日より後の購入日ならフラグを立てるという意味です。
TS>REF_TS
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/a04e3726-de0a-b0c6-ed92-5b4fe1f6a4ea.png)


2021-04-01 03:11:00
という基準タイムスタンプ以降の場合にAFTER_REFにTが立っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/9d98228f-3562-00af-9e59-c3fac7747159.png)

# 6p．⑥ タイムスタンプの大小比較　pandas版
以下のように単純にタイムスタンプの比較式を代入します。結果はbool型で返ります。

```python:タイムスタンプの差の計算
df5["AFTER_REF"]=(df5["TS"]>df5["REF_TS"])
print(df5.dtypes)

```
```output:結果
TS             datetime64[ns]
Power                   int64
Temperature             int64
Pressure                int64
Uptime                  int64
Status                  int64
Outcome                 int64
REF_TS         datetime64[ns]
AFTER_REF                bool
dtype: object
```

2021-04-01 03:11:00という基準タイムスタンプ以降の売上の場合にAFTER_REFにTrueが立っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/41153ded-a2c6-2061-b088-95b6dab35152.png)

# 7m．⑦ タイムスタンプの加算減算　Modeler版

タイムスタンプに加算減算を計算してみます。起動からから3時間後というような計算です。

フィールド作成ノードでdatetime_in_secondsとdatetime_timestampという関数をつかって、タイムスタンプの3時間後を生成してみます。
まずdatetime_in_seconds(TS)でタイムスタンプを基準日からの秒数に変換します。

基準日はデフォルトだと1900-01-01です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/a407901c-99dc-f43a-dce3-059877d52e94.png)


次にこの秒数に3時間分の秒数を足します。3時間\*60分\*60秒です。
そしてこの秒数をdatetime_dateでタイムスタンプ型に変換しなおします。
つまり式としては以下になります。
datetime_timestamp(datetime_in_seconds(TS)+3\*60\*60)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/2f25e542-fdb3-2389-254e-d32b20aff0dd.png)

タイムスタンプ(TS)の3時間後のタイムスタンプが3H_AFTERに生成されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/01c4e96b-b91c-cfb6-18e5-4ffed131cad2.png)


# 7p．⑦ タイムスタンプの加算減算　pandas版
datetime.timedeltaをつかって量と単位を指定して加算減算をします。以下の例は時間hoursを3個分足す、つまり3時間分足すという意味になります。hoursの部分を変えると分minutes、秒secondsなど別の単位でも加算減算が行えます。

```python:タイムスタンプの加算減算
df5['3H_AFTER']=df5["TS"]+ datetime.timedelta(hours=3)
#以下でも実現は可能
#df5['3H_AFTER']=df5["TS"]+ np.timedelta64(3,'h')
print(df5.dtypes)
```

```output:結果
TS             datetime64[ns]
Power                   int64
Temperature             int64
Pressure                int64
Uptime                  int64
Status                  int64
Outcome                 int64
3H_AFTER       datetime64[ns]
dtype: object
```

タイムスタンプ(TS)の3時間後のタイムスタンプが3H_AFTERに生成されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/51f81101-8ac8-725e-5b50-c9ea06983e8e.png)

# 8. サンプル
サンプルは以下に置きました。

ストリーム

https://github.com/hkwd/200611Modeler2Python/raw/master/datetime/ts.str

notebook

https://github.com/hkwd/200611Modeler2Python/blob/master/datetime/ts.ipynb

データ

https://raw.githubusercontent.com/hkwd/200611Modeler2Python/master/data/Cond4n_e.csv

■テスト環境
Modeler 18.2.2
Windows 10 64bit
Python 3.8.5
pandas 1.0.5
numpy 1.19.2