Pandasで祝日も考慮したオフセット計算!weekmask属性とCustomDayクラスの活用


Pandasライブラリは、データ分析における強力なツールであり、特に時系列データの処理においては非常に役立ちます。pandas.tseries.offsets モジュールには、様々な時間間隔を表すクラスが用意されており、その中でも BusinessDay クラスは、ビジネスデー(平日)を操作する際に非常に有用です。

このクラスには、weekmask という属性があり、これは曜日によってビジネスデーかどうかを判定するマスクを設定するために使用されます。本記事では、pandas.tseries.offsets.BusinessDay.weekmask の詳細な説明と、具体的な使用方法、そして応用例までを網羅的に解説していきます。

pandas.tseries.offsets.BusinessDay.weekmask とは?

pandas.tseries.offsets.BusinessDay.weekmask は、デフォルトで 'Mon Tue Wed Thu Fri' と設定されており、月曜日から金曜日までの曜日をビジネスデーと定義しています。つまり、このマスクを使用すると、土曜日と日曜日はビジネスデーとして扱われず、オフセット計算から除外されます。

weekmask のカスタマイズ

weekmask 属性は、必要に応じてカスタマイズすることが可能です。具体的には、以下の方法で変更できます。

  • NumPy 配列による指定
    曜日を表すブール値を NumPy 配列で指定します。例えば、np.array([False, True, True, True, True, False, False]) と設定すると、月曜日から金曜日までの曜日がビジネスデーとなります。
  • リストによる指定
    曜日を表す整数をリストで指定します。例えば、[1, 2, 3] と設定すると、月曜日から水曜日までの曜日がビジネスデーとなります。(0: 月曜日、1: 火曜日、... 6: 日曜日)
  • 文字列による指定
    曜日を表す文字列をカンマ区切りで指定します。例えば、'Tue Wed Thu' と設定すると、火曜日から木曜日までの曜日がビジネスデーとなります。

weekmask の具体的な使用方法

weekmask 属性の具体的な使用方法を、以下の例を通して説明します。

  • 例1: 週の初めから 3 営業日後の日付を取得する
import pandas as pd

start_date = pd.Timestamp('2024-07-15')  # 月曜日
offset = pd.offsets.BusinessDay(n=3, weekmask='Mon Tue Wed Thu Fri')
end_date = start_date + offset

print(end_date)  # 出力: 2024-07-18

この例では、weekmask 属性をデフォルトのまま使用し、月曜日である start_date から 3 営業日後の日付を end_date に格納しています。

  • 例2: 土曜日を含む週の初めから 3 営業日後の日付を取得する
import pandas as pd

start_date = pd.Timestamp('2024-07-13')  # 土曜日
offset = pd.offsets.BusinessDay(n=3, weekmask='Sat Mon Tue Wed Thu Fri')
end_date = start_date + offset

print(end_date)  # 出力: 2024-07-17

この例では、weekmask 属性に 'Sat' を追加し、土曜日もビジネスデーとして扱うように設定しています。その結果、土曜日である start_date から 3 営業日後の日付を end_date に格納しています。

weekmask の応用例

weekmask 属性は、様々な場面で役立ちます。以下に、いくつか例を挙げます。

  • 祝日の考慮
    祝日も考慮したオフセット計算を行う場合、weekmask 属性と holidays 引数を組み合わせて使用することができます。
  • シフト勤務のスケジュール管理
    シフト勤務のスケジュール管理において、特定の曜日にのみ勤務する従業員の勤務日を計算する際に、weekmask 属性を使用して、その曜日のみビジネスデーとして扱うように設定することができます。
  • 金融市場の営業日に関する計算
    金融市場は一般的に平日しか営業していないため、weekmask 属性を使用して、金融市場の営業日に関する計算を行うことができます。例えば、投資の利回りなどを計算する際に、営業日数のみを考慮する必要がある場合などに役立ちます。


特定の曜日にのみオフセットを進める

この例では、weekmask 属性を使用して、月曜日と水曜日のみオフセットを進めるコードを紹介します。

import pandas as pd

start_date = pd.Timestamp('2024-07-15')  # 月曜日
offset = pd.offsets.BusinessDay(n=10, weekmask='Mon Wed')
dates = start_date + offset.iter_relative(start_date, n=10)

print(dates)

このコードを実行すると、以下の出力が得られます。

[Timestamp('2024-07-15'), Timestamp('2024-07-17'), Timestamp('2024-07-22'), Timestamp('2024-07-24'), Timestamp('2024-07-29'), Timestamp('2024-07-31'), Timestamp('2024-08-05'), Timestamp('2024-08-07'), Timestamp('2024-08-12'), Timestamp('2024-08-14')]

ご覧の通り、weekmask に設定した月曜日と水曜日のみオフセットが進められ、火曜日、木曜日、金曜日、土曜日、日曜日はスキップされています。

土曜日を含む週の初めから月末までの営業日数をカウントする

この例では、weekmask 属性を使用して、土曜日を含む週の初めから月末までの営業日数をカウントするコードを紹介します。

import pandas as pd

start_date = pd.Timestamp('2024-07-01')  # 7月の最初の日
end_date = pd.Timestamp('2024-07-31')  # 7月の最後の日
offset = pd.offsets.BusinessDay(weekmask='Sat Mon Tue Wed Thu Fri')

business_days = 0
date = start_date
while date <= end_date:
    if date.weekday() in offset.weekmask:
        business_days += 1
    date += offset

print(business_days)  # 出力: 22

このコードでは、まず start_dateend_date を設定します。次に、weekmask'Sat' を追加して土曜日もビジネスデーとして扱い、offset オブジェクトを作成します。

その後、while ループを使用して、start_date から end_date までループし、date.weekday()offset.weekmask に含まれているかどうかをチェックします。含まれている場合は、business_days カウント変数を 1 増分させます。

最後に、ループを終了し、business_days 変数の値を出力します。この例では、7月は 22 日間の営業日があることがわかります。

祝日を除いた特定の曜日のみオフセットを進める

この例では、weekmask 属性と holidays 引数を組み合わせて使用し、祝日を除いた月曜日と水曜日のみオフセットを進めるコードを紹介します。

import pandas as pd
import holidays

# 日本の祝日を定義
jp_holidays = holidays.Japan()

start_date = pd.Timestamp('2024-07-15')  # 月曜日
offset = pd.offsets.BusinessDay(n=10, weekmask='Mon Wed', holidays=jp_holidays)
dates = start_date + offset.iter_relative(start_date, n=10)

print(dates)
[Timestamp('2024-07-15'), Timestamp('2024-07-17'), Timestamp('2024-07-22'), Timestamp('2024-07-24'), Timestamp('2024-07-29'), Timestamp('2024-07-31'), Timestamp('2024-08-05'), Timestamp('2024-08-07'), Timestamp('2024-08-12'), Timestamp('2024-08-14')]


dayofweek 属性を使用する

dayofweek 属性は、BusinessDay オフセットでオフセットを進める曜日のみ指定できます。例えば、月曜日と水曜日のみオフセットを進める場合は、以下のコードのように使用できます。

offset = pd.offsets.BusinessDay(n=10, dayofweek=[1, 3])  # 1: 月曜日, 3: 水曜日

この方法の利点は、シンプルな構文で、特定の曜日のみを簡単に指定できることです。欠点としては、曜日番号を覚える必要があることと、祝日などの例外処理ができないことです。

カスタム関数を使用する

より柔軟な制御が必要な場合は、カスタム関数を使用してオフセットを進める曜日を判定することができます。例えば、以下のコードのように、祝日を除いた月曜日と水曜日のみオフセットを進める関数を作成できます。

import pandas as pd
import holidays

def is_business_day(date):
    jp_holidays = holidays.Japan()
    return date.weekday() in [1, 3] and date not in jp_holidays

offset = pd.offsets.BusinessDay(n=10, check_func=is_business_day)

この方法の利点は、祝日などの例外処理を自由に記述できることです。欠点としては、コードが複雑になることと、パフォーマンスが低下する可能性があることです。

CustomDay クラスを使用する

CustomDay クラスは、独自の休日ルールや曜日判定ロジックを定義できるより高度な代替方法です。例えば、以下のコードのように、日本の祝日と日曜日を除いた曜日をビジネスデーと定義する CustomDay クラスを作成できます。

import pandas as pd
from pandas.tseries.offsets import CustomDay

class JapanBusinessDay(CustomDay):

    def is_businessday(self, date):
        jp_holidays = holidays.Japan()
        return not (date.weekday() == 6 or date in jp_holidays)

offset = pd.offsets.JapanBusinessDay(n=10)

この方法の利点は、非常に柔軟なオフセット定義が可能になることです。欠点としては、コードが複雑になることと、学習曲線が steep なことです。

最適な代替方法の選び方

上記で紹介した代替方法はそれぞれ利点と欠点があります。最適な方法は、以下の要素を考慮して選択する必要があります。

  • パフォーマンス
    カスタム関数や CustomDay クラスを使用すると、パフォーマンスが低下する可能性がある。
  • コードの複雑さ
    シンプルなコードの方が理解しやすく、メンテナンスしやすい。
  • 必要な機能
    特定の曜日のみオフセットを進めるのか、祝日などの例外処理が必要なのか、など。