Bobとpython拡張ノードで祝日フラグを作る
以下の記事を題材に、Bobを使って拡張ノードのPythonコードを効率よく作成する方法をご紹介します。
1.想定される利用目的
・顧客マスタや取引先マスタデータのクレンジング
・対象データの精査・名寄せ
2.サンプルストリームのダウンロード
3.サンプルストリームの説明
a.入力するデータは以下の通りです。
b.[拡張の変換]ノードを編集します。pythonスクリプトをどのようにBobに書かせたかは後述します。
[テーブル]を表示します。
Bobによるスクリプトの作成手順
1.Bobを起動して、作業フォルダを指定します。
2.SPSS Modeler[拡張のエクスポート]ノードからサンプル構文をコピーします。
3.プロンプトを作成します。サンプル構文をペーストします。
プロンプト
日付情報を受け取ると日本の祝日かどうかをフラグで表す列を新規作成する
SPSS Modelerの拡張の変換ノードのネイティブPython APIスクリプトを書いてください。
前方からはユーザー入力ノードで
日付
2020-01-01
2020-01-02
2020-01-03
2020-01-04
2020-01-05
2020-01-06
2020-01-07
2020-01-08
2020-01-09
2020-01-10
このようなデータがやってきます。
- 拡張の変換ノードのスクリプトの解説として
@https://www.ibm.com/docs/ja/spss-modeler/18.6.0?topic=spark-native-python-apis を参考にしてください。
- シバンは不要です。
シンタックスは以下のサンプルを参考にしてください
###Use this code to transfer data between Modeler and Python
#import modelerpy
#if modelerpy.isComputeDataModelOnly():
# modelerDataModel = modelerpy.getDataModel()
# outputDataModel = None
### compute your output data model
# modelerpy.setOutputDataModel(outputDataModel)
#else:
# modelerData = modelerpy.readPandasDataframe()
# outputData = None
### compute your output data
# modelerpy.writePandasDataframe(outputData)
4.プロンプトを実行します。
5.Bobの以下の出力を[拡張の変換]にそのまま利用します。
Pythonスクリプト
###Use this code to transfer data between Modeler and Python
# 日付フィールドから「日本の祝日かどうか」を表すフラグ列を新規作成する
# (外部ライブラリ不要。pandas と標準ライブラリのみ使用 / 対象: 1948年〜2099年)
import datetime
import modelerpy
import pandas as pd
DATE_FIELD = "日付" # 入力の日付フィールド名
FLAG_FIELD = "祝日フラグ" # 新規作成するフラグフィールド名 (1=祝日, 0=祝日以外)
def nth_monday(year, month, n):
"""year年month月の第n月曜日"""
first = datetime.date(year, month, 1)
offset = (0 - first.weekday()) % 7
return first + datetime.timedelta(days=offset + 7 * (n - 1))
def vernal_equinox(year):
"""春分の日 (簡易計算式)"""
if year >= 1980:
d = int(20.8431 + 0.242194 * (year - 1980) - int((year - 1980) / 4))
else:
d = int(20.8357 + 0.242194 * (year - 1980) - int((year - 1983) / 4))
return datetime.date(year, 3, d)
def autumnal_equinox(year):
"""秋分の日 (簡易計算式)"""
if year >= 1980:
d = int(23.2488 + 0.242194 * (year - 1980) - int((year - 1980) / 4))
else:
d = int(23.2588 + 0.242194 * (year - 1980) - int((year - 1983) / 4))
return datetime.date(year, 9, d)
def base_holidays(year):
"""国民の祝日(振替休日・国民の休日を除く)の日付集合"""
h = set()
D = datetime.date
# 元日
h.add(D(year, 1, 1))
# 成人の日
h.add(D(year, 1, 15) if year < 2000 else nth_monday(year, 1, 2))
# 建国記念の日
if year >= 1967:
h.add(D(year, 2, 11))
# 天皇誕生日
if 1949 <= year <= 1988:
h.add(D(year, 4, 29))
elif 1989 <= year <= 2018:
h.add(D(year, 12, 23))
elif year >= 2020:
h.add(D(year, 2, 23))
# 春分の日
h.add(vernal_equinox(year))
# 昭和の日 / みどりの日
if year >= 1989:
h.add(D(year, 4, 29))
# 憲法記念日・こどもの日
h.add(D(year, 5, 3))
h.add(D(year, 5, 5))
# みどりの日(2007年〜は5/4)
if year >= 2007:
h.add(D(year, 5, 4))
# 海の日
if year == 2020:
h.add(D(2020, 7, 23))
elif year == 2021:
h.add(D(2021, 7, 22))
elif year >= 2003:
h.add(nth_monday(year, 7, 3))
elif year >= 1996:
h.add(D(year, 7, 20))
# 山の日
if year == 2020:
h.add(D(2020, 8, 10))
elif year == 2021:
h.add(D(2021, 8, 8))
elif year >= 2016:
h.add(D(year, 8, 11))
# 敬老の日
if year >= 2003:
h.add(nth_monday(year, 9, 3))
elif year >= 1966:
h.add(D(year, 9, 15))
# 秋分の日
h.add(autumnal_equinox(year))
# 体育の日 / スポーツの日
if year == 2020:
h.add(D(2020, 7, 24))
elif year == 2021:
h.add(D(2021, 7, 23))
elif year >= 2000:
h.add(nth_monday(year, 10, 2))
elif year >= 1966:
h.add(D(year, 10, 10))
# 文化の日・勤労感謝の日
h.add(D(year, 11, 3))
h.add(D(year, 11, 23))
# 皇室行事等による臨時の祝日
special = {
D(1959, 4, 10), D(1989, 2, 24), D(1990, 11, 12), D(1993, 6, 9),
D(2019, 5, 1), D(2019, 10, 22),
}
h |= {d for d in special if d.year == year}
return {d for d in h if d.year == year}
def all_holidays(year):
"""振替休日・国民の休日を含めた、year年の祝日の日付集合"""
one = datetime.timedelta(days=1)
# 前後の年の祝日も考慮する(年またぎの振替休日などのため)
base = base_holidays(year - 1) | base_holidays(year) | base_holidays(year + 1)
result = set(base)
# 国民の休日: 前日と翌日が祝日に挟まれた平日(日曜除く) ※1986年〜
for d in range(1, 367):
try:
day = datetime.date(year, 1, 1) + datetime.timedelta(days=d - 1)
except OverflowError:
break
if day.year != year or day in base:
continue
if (day.year >= 1986 and day.weekday() != 6
and (day - one) in base and (day + one) in base):
result.add(day)
# 振替休日: 祝日が日曜と重なった場合、翌日以降の最初の祝日でない日 ※1973-04-12〜
for hd in sorted(base):
if hd.weekday() != 6 or hd < datetime.date(1973, 4, 12):
continue
sub = hd + one
if hd.year >= 2007:
while sub in base or sub in result:
sub += one
elif sub in base:
continue # 2006年以前は月曜が祝日なら振替なし
result.add(sub)
return {d for d in result if d.year == year}
_cache = {}
def is_holiday(day):
"""datetime.date が日本の祝日なら 1、そうでなければ 0"""
if day.year not in _cache:
_cache[day.year] = all_holidays(day.year)
return 1 if day in _cache[day.year] else 0
if modelerpy.isComputeDataModelOnly():
modelerDataModel = modelerpy.getDataModel()
outputDataModel = modelerDataModel
# 祝日フラグ列(整数, 測定の尺度=フラグ)を追加
outputDataModel.addField(modelerpy.Field(FLAG_FIELD, "integer", "flag"))
modelerpy.setOutputDataModel(outputDataModel)
else:
modelerData = modelerpy.readPandasDataframe()
outputData = modelerData.copy()
# 文字列(YYYY-MM-DD)・日付型・数値(エポック値)のいずれでも受け取れるよう変換
col = outputData[DATE_FIELD]
if pd.api.types.is_numeric_dtype(col) and not pd.api.types.is_bool_dtype(col):
# 数値の場合、pd.to_datetime は既定でナノ秒と解釈し全て1970-01-01になるため、
# 値の大きさから単位(日/秒/ミリ秒/マイクロ秒/ナノ秒)を判定する
peak = col.abs().max()
if peak < 1e6:
unit = "D"
elif peak < 1e11:
unit = "s"
elif peak < 1e14:
unit = "ms"
elif peak < 1e17:
unit = "us"
else:
unit = "ns"
dates = pd.to_datetime(col, unit=unit, errors="coerce")
else:
dates = pd.to_datetime(col, errors="coerce")
outputData[FLAG_FIELD] = [
0 if pd.isna(d) else is_holiday(d.date()) for d in dates
]
modelerpy.writePandasDataframe(outputData)
注意事項
このストリームでは日付という列名を固定していますので別ストリームに転用する時には直前にフィルターノードを置き列名を変更するなどが必要です。
4.参考情報
祝日マスタを日付で割り当て休日フラグを作成する
SPSS Modeler ノードリファレンス目次
SPSS Modeler 逆引きストリーム集(データ加工)







