LoginSignup
2
0

More than 1 year has passed since last update.

SPSS Modelerの日付関連clem関数をPythonで書き換える。

Last updated at Posted at 2021-06-21

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

タイムスタンプ型については以下の記事で紹介しています。
https://qiita.com/kawada2017/items/0546580314f5b67408b1

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

  1. 日付データの読み込み
  2. 文字列や数値からの日付データ生成
  3. 今日の日付の取得
  4. 日付の年、月、日の分解
  5. 曜日判定
  6. 日付の差の計算
  7. 年と月の差の計算
  8. 日付の大小比較
  9. 日付の加算減算

以下のID付POSデータを対象に行います。
誰(CUSTID)がいつ(SDATE)何(PRODUCTID、L_CLASS商品大分類、M_CLASS商品中分類)をいくら(SUBTOTAL)購入したかが記録されたID付POSデータを使います。今回はこの中のSDATEのみを使います。

image.png

1m.① 日付データの読み込み Modeler版

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

image.png

CSVデータを読み込んだ際に、デフォルトでは「自動的に日付と時間を認識します」のオプションが有効になっており、ストリームオプションで指定されていたYYYY-MM-DD形式で解釈できる文字列データは日付データとして読み込みます。
image.png

image.png

1p.① 日付データの読み込み pandas版

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

  • datetime.date
  • datetime.datetime
  • numpy.datetime64
  • pandas._libs.tslibs.timestamps.Timestamp
データ型確認
#datetime.date
dtdt=datetime.date(2021, 6, 10)
print(dtdt)
print(type(dtdt))
#datetime.datetime
dtdtt=datetime.datetime(2021, 6, 10)
print(dtdtt)
print(type(dtdtt))

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

#pandas._libs.tslibs.timestamps.Timestamp
pddt=pd.to_datetime('2021-06-10')
print(pddt)
print(type(pddt))
結果
2021-06-10
<class 'datetime.date'>
2021-06-10 00:00:00
<class 'datetime.datetime'>
2021-06-10
<class 'numpy.datetime64'>
2021-06-10 00:00:00
<class 'pandas._libs.tslibs.timestamps.Timestamp'>

この記事ではpandasのデータフレームを使うので、pandasのTimestamp型を使用していきます。ただし、加工方法によってはdatetimeを使う方が便利な場合もあるので組み合わせて使用していきます。
しかし、datetime.datetimeとnumpy.datetime64をいれると自動的にpandas._libs.tslibs.timestamps.Timestampに変換されていました。
dfdt.dtypesでみるといずれもdatetime64[ns]として表示されます。
datetime.dateはdatetime.dateのままでobjectとして認識されます。pandasにはdate型がなくTimestamp型しかないのでdate型は自動変換しないようです。

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

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

数値型データから日付型データ生成
#現在の日付
df = pd.read_csv('sampletranDEPT4en2019S.csv', parse_dates=['SDATE'])
print(df.dtypes)
print(type(df['SDATE'][0]))

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

結果
CUSTID                int64
SDATE        datetime64[ns]
PRODUCTID             int64
L_CLASS              object
M_CLASS              object
SUBTOTAL              int64
dtype: object
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
  • 参考

2m.② 文字列や数値からの日付データ生成 Modeler版

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

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

datetime_date('2019-07-01')

image.png

続いて、数値型データから日付型データを生成してみます。やはりdatetime_dateという関数を使いますが、年、月、日の数値をカンマ区切りで指定します。
datetime_date(2018,7,1)

image.png

データ型ノードをつかって確認してみると、アイコンがカレンダーになり、尺度が連続型になっています。正しく日付型が生成されていることが確認できました。
image.png

2p.② 文字列や数値からの日付データ生成 pandas版

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

文字列から日付型データ生成
df1['REF_DATE']=pd.to_datetime('2019-07-01')

続いて、数値型データから日付型データを生成してみます。時間情報のない日付データなのですが、datetime.dateだとobjectとして認識されてしまうので、それを避けるためにdatetime.dateではなくdatetime.datetimeで年、月、日を数値で指定してTimestamp型で生成します。

数値から日付型データ生成
df1['REF_DATE2']=datetime.datetime(2018, 7, 1)
print(df1.dtypes)

REF_DATEもREF_DATE2もobjectとしてではなく、datetime64[ns]型で生成されていることがわかります。

結果
SDATE             datetime64[ns]
Power                   int64
Temperature             int64
Pressure                int64
Uptime                  int64
Status                  int64
Outcome                 int64
REF_DATE         datetime64[ns]
REF_DATE2        datetime64[ns]
dtype: object

image.png

  • 参考

3m.③ 現在の日付の取得 Modeler版

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

フィールド作成ノードで「@TODAY」という関数を設定します。
image.png

現在の日付が入ります。
image.png

3p.③ 現在の日付の取得 pandas版

datetime.date.today()で現在の日付を取得し、pd.to_datetimeで型変換をします。
なお、np.datetime64('today')だと型変換不要で取得はできましたが、マニュアルにこの記述方法がなかったので、datetime.date.today()の方が堅いかと思います。

数値型データから日付型データ生成
#現在の日付
df2['TODAY']=pd.to_datetime(datetime.date.today())
#以下だとobject型になる。
#df2['TODAY']=datetime.date.today()
#以下でも可能だったが、マニュアルにこの記述方法は見当たらなかった
#df2['TODAY']=np.datetime64('today')
print(df2.dtypes)
結果
CUSTID                int64
SDATE        datetime64[ns]
PRODUCTID             int64
L_CLASS              object
M_CLASS              object
SUBTOTAL              int64
TODAY        datetime64[ns]
dtype: object

image.png

  • 参考

4m.④日付の年、月、日の分解 Modeler版

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

image.png

image.png

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

日付型から月の分解
df3['MONTH']=df3['SDATE'].dt.month
結果
CUSTID                int64
SDATE        datetime64[ns]
PRODUCTID             int64
L_CLASS              object
M_CLASS              object
SUBTOTAL              int64
MONTH                 int64
dtype: object

image.png

  • 参考

5m.⑤ 曜日判定 Modeler版

曜日によって購買傾向が変わることはよくありますので、曜日は重要な特徴量になります。

フィールド作成ノードでdatetime_weekdayという関数をつかって抜き出すことができます。1(日曜)~7(土曜) の範囲の整数として戻ります。

image.png
image.png

5p.⑤ 曜日判定 pandas版

dtアクセサをつかってweekdayでをつかって抜き出すことができます。0(月曜)~6(日曜)の範囲の整数として戻ります。Modelerと範囲が異なりますので、Modelerと同じ範囲にしたい場合は以下のようにシフトさせる必要があります。

日付型から曜日の分解
#曜日判定。0(月曜)~6(日曜)  
df4['WEEKDAY']=df2['SDATE'].dt.weekday
# Modelerにあわせて1(日曜)~7(土曜) の範囲の整数に変換。日曜は6+2=8になるので7.1で割った余り0.9を四捨五入して1にする
df4['WEEKDAYSPSS']=round((df2['SDATE'].dt.weekday+2)%7.1).astype(int)
print(df4.dtypes)
結果
CUSTID                  int64
SDATE          datetime64[ns]
PRODUCTID               int64
L_CLASS                object
M_CLASS                object
SUBTOTAL                int64
WEEKDAY                 int64
WEEKDAYSPSS             int32
dtype: object

image.png

6m.⑥ 日付の差の計算 Modeler版

日付同士の差を計算してみます。購買間隔やイベント日からの日数など、日付差は重要な特徴量になりえます。

フィールド作成ノードでdate_days_differenceで日数差を取ります。
以下の例だと売上日(SDATE)- 基準日付(REF_DATE)の日数を返します。基準日付(REF_DATE)>売上日(SDATE)であればマイナスの値が返ります。

date_days_difference(REF_DATE,SDATE)

image.png

2019-07-01という基準日と売上日(SDATE)の差がDAY_DIFFに入っています。
image.png

6p.⑥ 日付の差の計算 pandas版

まず、以下のように単純に日付の入った列の引算を行います。
df5["DAY_DIFF_TD"]=(df5["SDATE"]-df5["REF_DATE"])
これは整数ではなく、pandas._libs.tslibs.timedeltas.Timedeltaという特殊な型(df5.dtypesの結果だとtimedelta64[ns])で値を返します。
日付演算をする場合はこのままでよいのですが、整数として扱う場合にはdt.daysで変換します。

日付の差の計算
df5["DAY_DIFF_TD"]=(df5["SDATE"]-df5["REF_DATE"])
print(type((df5["DAY_DIFF_TD"])[0]))
df5["DAY_DIFF"]=(df5["SDATE"]-df5["REF_DATE"]).dt.days
#以下でも整数化は可能。
#df5["DAY_DIFF"]=((df5["SDATE"]-df5["REF_DATE"]) /np.timedelta64(1,'D')).astype('int')
print(df5.dtypes)
結果
<class 'pandas._libs.tslibs.timedeltas.Timedelta'>
CUSTID                   int64
SDATE           datetime64[ns]
PRODUCTID                int64
L_CLASS                 object
M_CLASS                 object
SUBTOTAL                 int64
REF_DATE        datetime64[ns]
DAY_DIFF_TD    timedelta64[ns]
DAY_DIFF                 int64
dtype: object

DAY_DIFF_TDとDAY_DIFF秒の結果は同じですが、DAY_DIFF_TDにはtimedelta64[ns]型で格納され、DAY_DIFF秒は整数型になっています。
image.png

pandas.Series.dt.days — pandas 1.2.4 documentation
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.days.html

7m.⑦ 年月の差の計算 Modeler版

年と月の差を計算してみます。イベントからの年数、月数や年齢など、年や月の差は重要な特徴量になりえます。
date_years_differenceやdate_months_differenceで差をとることはできるのですが、浮動小数点誤差が発生しうるので、整数化してから計算することがお勧めです。
2019-07-01であれば、年に10000をかけ、月に100をかけて20190701のような10進の整数にしてから差を計算して、最後に10000で割った商で年の位のみを得ます。
以下の例だと売上日(SDATE)- 基準日付(REF_DATE)の年数を返します。

(
(datetime_year(SDATE)*10000+datetime_month(SDATE)*100+datetime_day(SDATE))
-(datetime_year(REF_DATE2)*10000+datetime_month(REF_DATE2)*100+datetime_day(REF_DATE2))
)div 10000

image.png

月の計算も同じように行えます。
まず月の単位にするために年に12をかけ、それから100をかけます。2019-07-01であれば、(2019*12+07)*100+01で10進の整数にしてから差を計算して、最後に100で割った商で月の位のみを得ます。

(
((datetime_year(SDATE)*12+datetime_month(SDATE))*100+datetime_day(SDATE))
-((datetime_year(REF_DATE2)*12+datetime_month(REF_DATE2))*100+datetime_day(REF_DATE2))
)div 100

image.png

2019-07-01という基準日と売上日(SDATE)の年差がYEAR_DIFF、月差がMONTHS_DIFFに入っています。
image.png

7p.⑦ 年月の差の計算 pandas版

Modeler同様に浮動小数点誤差を避けるために10進の整数として計算します。
年の差については、まず以下で10進の整数に変換します。
df6["REF_DATE2"].dt.strftime('%Y%m%d').astype('int')
とすると、2019-07-01であれば、20190701のような10進の整数になります。この後、差をとってから//10000で年の位の商を得ます。

月の差についても、まず以下で10進の整数に変換します。
(df6["REF_DATE2"].dt.year*12*100+df6["REF_DATE2"].dt.strftime('%m%d').astype('int')
とすると、2019-07-01であれば、(2019*12*100)+0701で10進の整数になります。この後、差をとってから//100で月の位の商を得ます。

年と月の差の計算
#10進の整数化してから引算
df6["YEAR_DIFF"]=(df6["SDATE"].dt.strftime('%Y%m%d').astype('int')-df6["REF_DATE2"].dt.strftime('%Y%m%d').astype('int'))//10000
df6["MONTHS_DIFF"]=((df6["SDATE"].dt.year*12*100+df6["SDATE"].dt.strftime('%m%d').astype('int'))
                    -(df6["REF_DATE2"].dt.year*12*100+df6["REF_DATE2"].dt.strftime('%m%d').astype('int')))//100
#以下は1年未満でも1年になってしまう。
#df6["YEAR_DIFF"]=df6["SDATE"].dt.year - df6["REF_DATE2"].dt.year
#以下は浮動小数点誤差が発生する
#df6["YEAR_DIFF"]=(df6["SDATE] - df6["REF_DATE2"]).astype('<m8[Y]')
#df6["YEAR_DIFF"]=((df6["SDATE"] - df6["REF_DATE2"])/np.timedelta64(1,'Y')).astype('int')
#df6["MONTHS_DIFF"]=((df6["SDATE"] - df6["REF_DATE2"])/np.timedelta64(1,'M')).astype('int')
print(df6.dtypes)
結果
CUSTID                  int64
SDATE          datetime64[ns]
PRODUCTID               int64
L_CLASS                object
M_CLASS                object
SUBTOTAL                int64
REF_DATE2      datetime64[ns]
YEAR_DIFF               int32
MONTHS_DIFF             int64
dtype: object

2019-07-01という基準日と売上日(SDATE)の年差がYEAR_DIFF、月差がMONTHS_DIFFに入っています。
image.png

pandas.Series.dt.days — pandas 1.2.4 documentation
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.days.html

8m.⑧日付の大小比較 Modeler版

日付の大小比較をしてみます。イベント日以降の購入かというようなフラグの特徴量が作れます。

フィールド作成ノードで派生:フラグ型に設定し、条件に以下を入力します。基準日より後の購入日ならフラグを立てるという意味です。
SDATE>REF_DATE
image.png
2019-07-01という基準日以降の売上の場合にAFTER_REFにTが立っています。
image.png

8p.⑧ 日付の大小比較 pandas版

以下のように単純に日付の比較式を代入します。結果はbool型で返ります。

日付の差の計算
df7["AFTER_REF"]=(df7["SDATE"]>df7["REF_DATE"])
print(df7.dtypes)```

```output:結果
CUSTID                int64
SDATE        datetime64[ns]
PRODUCTID             int64
L_CLASS              object
M_CLASS              object
SUBTOTAL              int64
REF_DATE     datetime64[ns]
AFTER_REF              bool
dtype: object

2019-07-01という基準日付以降の売上の場合にAFTER_REFにTrueが立っています。
image.png

9m.⑨ 日付の加算減算 Modeler版

日付に加算減算を計算してみます。購入から100日目というような計算です。初回購入から100日以内にリピート購入があった顧客を調べたいときなどに使います。

フィールド作成ノードでdatetime_in_secondsとdatetime_dateという関数をつかって、日付の100日後を生成してみます。
まずdatetime_in_seconds(SDATE)で日付を基準日からの秒数に変換します。

基準日はデフォルトだと1900-01-01です。
image.png

次にこの秒数に100日分の秒数を足します。100日*24時間*60分*60秒です。
そしてこの秒数をdatetime_dateで日付型に変換しなおします。
つまり式としては以下になります。
datetime_date(datetime_in_seconds(SDATE)+100*24*60*60)

image.png

日付(SDATE)の100日後の日付が100D_AFTERに生成されました。
image.png

9p.⑨ 日付の加算減算 pandas版

datetime.timedeltaをつかって量と単位を指定して加算減算をします。以下の例は日付daysを100個分足す、つまり100日分足すという意味になります。'days'の部分を変えると時間'hours'、分'minutes'、秒'seconds'など別の単位でも加算減算が行えます。

日付の加算減算
df8['100D_AFTER']=df8["SDATE"]+ datetime.timedelta(days=100)
#以下でも同じ結果が得られる
#df8['100D_AFTER']=df8["SDATE"]+ np.timedelta64(100,'D')
print(df8.dtypes)
結果
CUSTID                 int64
SDATE         datetime64[ns]
PRODUCTID              int64
L_CLASS               object
M_CLASS               object
SUBTOTAL               int64
100D_AFTER    datetime64[ns]
dtype: object

日付(SDATE)の100日後の日付が100D_AFTERに生成されました。
image.png

10. サンプル

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

ストリーム
https://github.com/hkwd/200611Modeler2Python/raw/master/datetime/date.str
notebook
https://github.com/hkwd/200611Modeler2Python/blob/master/datetime/date.ipynb
データ
https://raw.githubusercontent.com/hkwd/200611Modeler2Python/master/data/sampletranDEPT4en2019S.csv

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0