0x01. DoWhy ケース分析
このケースはまだ Microsoft の公式のオープン ソース ドキュメントに基づいています. 詳細を知りたい場合は、Microsoft の公式 Web サイトにアクセスしてください.
背景:
ホテルの予約をキャンセルする理由はさまざまです。顧客は、提供できないもの (駐車場など) を要求したり、後でホテルが要求を満たしていないことに気付いたり、単に旅行全体をキャンセルしたりする場合があります。駐車場など、これらの問題にはホテルが対処できるものもあれば、旅行のキャンセルなど、ホテルの管理外のものもあります。いずれにせよ、どのような要因がキャンセルにつながるのかをよりよく理解したいと考えています。
この例では、私たちの研究課題は、消費者がホテルを予約しているときに、以前に予約した部屋とは異なる部屋を割り当てた場合に、現在の予約がキャンセルされた場合の影響を推定することです。このような問題を分析するための標準は、各消費者が2つの介入のいずれかにランダムに割り当てられる「ランダム化比較試験」です。以前に予約した部屋と同じ部屋または異なる部屋です。
ホテルの予約がキャンセルされる要因を検討します。単純な経験的思考を図に示します。
0x02.実験開始
0x02_1.ガイドパケット読み出しデータ
データの詳細な説明については、ポータルを参照してください。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import dowhy
dataset = pd.read_csv('https://raw.githubusercontent.com/Sid-darthvader/DoWhy-The-Causal-Story-Behind-Hotel-Booking-Cancellations/master/hotel_bookings.csv')
dataset.columns
データセットには次の列が含まれています。
Index(['hotel', 'is_canceled', 'lead_time', 'arrival_date_year',
'arrival_date_month', 'arrival_date_week_number',
'arrival_date_day_of_month', 'stays_in_weekend_nights',
'stays_in_week_nights', 'adults', 'children', 'babies', 'meal',
'country', 'market_segment', 'distribution_channel',
'is_repeated_guest', 'previous_cancellations',
'previous_bookings_not_canceled', 'reserved_room_type',
'assigned_room_type', 'booking_changes', 'deposit_type', 'agent',
'company', 'days_in_waiting_list', 'customer_type', 'adr',
'required_car_parking_spaces', 'total_of_special_requests',
'reservation_status', 'reservation_status_date'],
dtype='object')
0x02_2. データ処理
機能のマージにより、元の機能の次元が削減され、データの価値が高まります. 具体的に作成されるデータは次のとおりです。
- 滞在合計=
stays_in_weekend_nights
+stays_in_week_nights
- ゲスト=
adults
+children
+babies
- Different_room_assigned
reserved_room_type
= & がassigned_room_type
異なる場合は 1 、そうでない場合は 0。
操作は基本的な pandas 操作です。練習に従ってください。
# Total stay in nights
dataset['total_stay'] = dataset['stays_in_week_nights']+dataset['stays_in_weekend_nights']
# Total number of guests
dataset['guests'] = dataset['adults']+dataset['children'] +dataset['babies']
# Creating the different_room_assigned feature
dataset['different_room_assigned']=0
slice_indices =dataset['reserved_room_type']!=dataset['assigned_room_type']
dataset.loc[slice_indices,'different_room_assigned']=1
# Deleting older features
dataset = dataset.drop(['stays_in_week_nights','stays_in_weekend_nights','adults','children','babies'
,'reserved_room_type','assigned_room_type'],axis=1)
dataset.columns
データの列が処理されると、次のようになります。
Index(['hotel', 'is_canceled', 'lead_time', 'arrival_date_year',
'arrival_date_month', 'arrival_date_week_number',
'arrival_date_day_of_month', 'meal', 'country', 'market_segment',
'distribution_channel', 'is_repeated_guest', 'previous_cancellations',
'previous_bookings_not_canceled', 'booking_changes', 'deposit_type',
'agent', 'company', 'days_in_waiting_list', 'customer_type', 'adr',
'required_car_parking_spaces', 'total_of_special_requests',
'reservation_status', 'reservation_status_date', 'total_stay', 'guests',
'different_room_assigned'],
dtype='object')
次に、null 値などの問題に対処します。
dataset.columns
#%%
dataset.isnull().sum() # Country,Agent,Company contain 488,16340,112593 missing entries
dataset = dataset.drop(['agent','company'],axis=1)
# Replacing missing countries with most freqently occuring countries
dataset['country']= dataset['country'].fillna(dataset['country'].mode()[0])
dataset = dataset.drop(['reservation_status','reservation_status_date','arrival_date_day_of_month'],axis=1)
dataset = dataset.drop(['arrival_date_year'],axis=1)
dataset = dataset.drop(['distribution_channel'], axis=1)
# Replacing 1 by True and 0 by False for the experiment and outcome variables
dataset['different_room_assigned']= dataset['different_room_assigned'].replace(1,True)
dataset['different_room_assigned']= dataset['different_room_assigned'].replace(0,False)
dataset['is_canceled']= dataset['is_canceled'].replace(1,True)
dataset['is_canceled']= dataset['is_canceled'].replace(0,False)
dataset.dropna(inplace=True)
print(dataset.columns)
dataset.iloc[:, 5:20].head(100)
データ出力結果は次のとおりです。
Index(['hotel', 'is_canceled', 'lead_time', 'arrival_date_month',
'arrival_date_week_number', 'meal', 'country', 'market_segment',
'is_repeated_guest', 'previous_cancellations',
'previous_bookings_not_canceled', 'booking_changes', 'deposit_type',
'days_in_waiting_list', 'customer_type', 'adr',
'required_car_parking_spaces', 'total_of_special_requests',
'total_stay', 'guests', 'different_room_assigned'],
dtype='object')
デポジットを使用しなかった人のデータを統計分析して、注文をキャンセルしたかどうかを観察します
dataset = dataset[dataset.deposit_type=="No Deposit"]
dataset.groupby(['deposit_type','is_canceled']).count()
結果は次のとおりです。
データのディープ コピーを作成します。
dataset_copy = dataset.copy(deep=True)
0x02_3. 簡易分析仮説
データの前処理が完了したら、まずデータを分析して変数間の関係を調べます。対象変数is_cancelledとdifferent_room_assignedについて、ランダムに 1000 個の観測を選択して、上記の 2 つの変数が同じ値を持つ回数 (つまり、因果関係がある可能性がある) を調べ、上記のプロセスを 10000 回繰り返して平均を取ります。コードは次のとおりです。
counts_sum=0
for i in range(1,10000):
counts_i = 0
rdf = dataset.sample(1000)
counts_i = rdf[rdf["is_canceled"]== rdf["different_room_assigned"]].shape[0]
counts_sum+= counts_i
counts_sum/10000
結果として期待される頻度は、518
2 つの変数が約 50% の確率で異なるということであり、因果関係をまだ判断できていません。予約プロセス中に調整が行われない (つまり、変数 booking_changes が 0 である) 場合に、2 つの変数が等しいと予想される頻度をさらに分析してみましょう。
counts_sum=0
for i in range(1,10000):
counts_i = 0
rdf = dataset[dataset["booking_changes"]==0].sample(1000)
counts_i = rdf[rdf["is_canceled"]== rdf["different_room_assigned"]].shape[0]
counts_sum+= counts_i
counts_sum/10000
得られた結果は です492
。次に、予約プロセス中に調整が発生した場合に予想される頻度を分析します。
counts_sum=0
for i in range(1,10000):
counts_i = 0
rdf = dataset[dataset["booking_changes"]>0].sample(1000)
counts_i = rdf[rdf["is_canceled"]== rdf["different_room_assigned"]].shape[0]
counts_sum+= counts_i
counts_sum/10000
結果は、663
以前とは大きく異なることが判明しました。予約調整変数は「交絡因子」であると大まかに考えることができます。同様に、他の変数について分析を実行し、因果関係の推論のための事前知識として機能するいくつかの仮定を行います。DoWhy は完全な事前知識を必要とせず、特定されていない変数が潜在的な交絡因子として推測されます。
0x03. DoWhy を使用して因果関係を推定する
次のように、因果分析の 4 つの手順を使用して結果を分析します。
0x03_1. 因果グラフの作成
この段階で完全な因果関係図を作成する必要はありません。部分的な図でも十分です。残りは DoWhy で計算できます。考えられるいくつかの仮定を因果関係図に変換します。
- market_segmentには 2 つの値があります。「TA」は「旅行代理店」を表し、「TO」は「ツアー オペレーター」を表すため、lead_time (予約から到着までの日数) に影響します。
- 国は、人が早期に予約するかどうか (つまり、リードタイムに影響を与える) と、好きな食べ物を予約するかどうか (つまり、食事に影響を与える) を決定します。
- Lead_time は、予約の待機時間 ( days_in_waiting_list ) に影響します。
- 予約の待ち時間days_in_waiting_list、合計滞在時間total_stay 、およびゲストの数は、予約がキャンセルされるかどうかに影響します。
- 以前の予約のキャンセルprevious_bookings_not_canceledは、ゲストがis_repeated_guestかどうかに影響します; これらの 2 つの変数は、予約がキャンセルされるかどうかにも影響します。
- booking_changes は、顧客が別の部屋に割り当てられるかどうかに影響し、予約のキャンセルにも影響します。booking_changes
の交絡因子に加えて、介入と結果の両方に影響を与える他の交絡因子が存在する必要があります。
import pygraphviz
causal_graph = """digraph {
different_room_assigned[label="Different Room Assigned"];
is_canceled[label="Booking Cancelled"];
booking_changes[label="Booking Changes"];
previous_bookings_not_canceled[label="Previous Booking Retentions"];
days_in_waiting_list[label="Days in Waitlist"];
lead_time[label="Lead Time"];
market_segment[label="Market Segment"];
country[label="Country"];
U[label="Unobserved Confounders",observed="no"];
is_repeated_guest;
total_stay;
guests;
meal;
hotel;
U->{different_room_assigned,required_car_parking_spaces,guests,total_stay,total_of_special_requests};
market_segment -> lead_time;
lead_time->is_canceled; country -> lead_time;
different_room_assigned -> is_canceled;
country->meal;
lead_time -> days_in_waiting_list;
days_in_waiting_list ->{is_canceled,different_room_assigned};
previous_bookings_not_canceled -> is_canceled;
previous_bookings_not_canceled -> is_repeated_guest;
is_repeated_guest -> {different_room_assigned,is_canceled};
total_stay -> is_canceled;
guests -> is_canceled;
booking_changes -> different_room_assigned; booking_changes -> is_canceled;
hotel -> {different_room_assigned,is_canceled};
required_car_parking_spaces -> is_canceled;
total_of_special_requests -> {booking_changes,is_canceled};
country->{hotel, required_car_parking_spaces,total_of_special_requests};
market_segment->{hotel, required_car_parking_spaces,total_of_special_requests};
}"""
上記の因果図に基づいて、モデルを構築します。
model= dowhy.CausalModel(
data = dataset,
graph=causal_graph.replace("\n", " "),
treatment="different_room_assigned",
outcome='is_canceled')
model.view_model()
from IPython.display import Image, display
display(Image(filename="causal_model.png"))
構築の結果は
次の図のようになります。
0x03_2. 因果関係の特定
介入(治療) が結果 (結果)につながると言うのは、他のすべてが等しい場合に、介入の変化が結果の変化を引き起こす場合に限られます。因果効果とは、介入が 1 単位変化したときに結果が変化する度合いです。以下では、因果グラフのプロパティを使用して、因果効果の推定量を特定します。
identified_estimand = model.identify_effect()
print(identified_estimand)
出力は次のとおりです。
Estimand type: nonparametric-ate
### Estimand : 1
Estimand name: backdoor
Estimand expression:
d
──────────────────────────(E[is_canceled|guests,booking_changes,days_in_waitin
d[different_room_assigned]
g_list,is_repeated_guest,hotel,lead_time,total_of_special_requests,required_car_parking_spaces,total_stay])
Estimand assumption 1, Unconfoundedness: If U→{different_room_assigned} and U→is_canceled then P(is_canceled|different_room_assigned,guests,booking_changes,days_in_waiting_list,is_repeated_guest,hotel,lead_time,total_of_special_requests,required_car_parking_spaces,total_stay,U) = P(is_canceled|different_room_assigned,guests,booking_changes,days_in_waiting_list,is_repeated_guest,hotel,lead_time,total_of_special_requests,required_car_parking_spaces,total_stay)
### Estimand : 2
Estimand name: iv
No such variable(s) found!
### Estimand : 3
Estimand name: frontdoor
No such variable(s) found!
0x03_3. 因果効果推定
推定量に基づいて、実際のデータに基づいて因果効果を推定できます。先に述べたように、因果効果とは、介入が単位変化の結果を変化させる度合いです。DoWhy は、因果効果推定量を計算するためのさまざまな方法をサポートし、最終的に単一の平均を返します。コードは次のようになります。
estimate = model.estimate_effect(identified_estimand,
method_name="backdoor.propensity_score_stratification",target_units="ate")
# ATE = Average Treatment Effect
# ATT = Average Treatment Effect on Treated (i.e. those who were assigned a different room)
# ATC = Average Treatment Effect on Control (i.e. those who were not assigned a different room)
print(estimate)
出力は次のとおりです。
*** Causal Estimate ***
## Identified estimand
Estimand type: nonparametric-ate
### Estimand : 1
Estimand name: backdoor
Estimand expression:
d
──────────────────────────(E[is_canceled|guests,booking_changes,days_in_waitin
d[different_room_assigned]
g_list,is_repeated_guest,hotel,lead_time,total_of_special_requests,required_car_parking_spaces,total_stay])
Estimand assumption 1, Unconfoundedness: If U→{different_room_assigned} and U→is_canceled then P(is_canceled|different_room_assigned,guests,booking_changes,days_in_waiting_list,is_repeated_guest,hotel,lead_time,total_of_special_requests,required_car_parking_spaces,total_stay,U) = P(is_canceled|different_room_assigned,guests,booking_changes,days_in_waiting_list,is_repeated_guest,hotel,lead_time,total_of_special_requests,required_car_parking_spaces,total_stay)
## Realized estimand
b: is_canceled~different_room_assigned+guests+booking_changes+days_in_waiting_list+is_repeated_guest+hotel+lead_time+total_of_special_requests+required_car_parking_spaces+total_stay
Target units: ate
## Estimate
Mean value: -0.23589558877628428
0x03_4. 反論結果
実際、上記の因果関係はデータに基づくものではなく、私たちが行った仮定 (つまり、提供された因果関係図) に基づいており、データは統計的推定にのみ使用されます。したがって、仮定の正しさを検証する必要があります。DoWhy は、仮定の有効性をテストするためのさまざまな堅牢性チェックをサポートしています。これらのテストのいくつかを次に示します。
- ランダムな交絡因子を追加します。仮定が正しければ、ランダムな交絡因子を追加しても因果効果はあまり変化しません。
refute1_results=model.refute_estimate(identified_estimand, estimate,
method_name="random_common_cause")
print(refute1_results)
結果は次のとおりです。
Refute: Add a random common cause
Estimated effect:-0.23589558877628428
New effect:-0.2387463245344759
p value:0.19999999999999996
- プラセボ介入。介入を確率変数に置き換えると、仮定が正しければ、因果効果は 0 に近くなるはずです。
refute2_results=model.refute_estimate(identified_estimand, estimate,
method_name="placebo_treatment_refuter")
print(refute2_results)
結果は次のとおりです。
Refute: Use a Placebo Treatment
Estimated effect:-0.23589558877628428
New effect:8.214081490552981e-05
p value:0.98
- データ サブセットの検証。因果効果はデータのサブセットで推定され、仮定が正しければほとんど変化しないはずです。
refute3_results=model.refute_estimate(identified_estimand, estimate,
method_name="data_subset_refuter")
print(refute3_results)
結果は次のとおりです。
Refute: Use a subset of data
Estimated effect:-0.23589558877628428
New effect:-0.23518131741789491
p value:0.8
私たちの因果モデルは、基本的に上記のテストに合格できる (つまり、期待される結果を達成できる) ことがわかります。したがって、推定フェーズの結果に基づいて、消費者が部屋を予約する場合、以前に予約した部屋を割り当てる (different_room_assigned = 0) 方が、別の room_assigned 部屋を割り当てる ( different_room_assigned ) よりも平均キャンセル確率 ( is_canceled ) が高くなると結論付けます。 = 1) は低いです23%
。