事例と実装で学ぶ因果推論〜ダイエットアプリの効果を検証するには?(第1回:問題設定)
こんにちは、Ubie Discoveryのmatsu-ryuです。
2018年からヘルステック企業(DeSCヘルスケア→UbieDiscovery)でデータアナリスト/アナリティクスエンジニアとして働いており、実際にプロダクトの効果を検証することがありました。その際に利用してきた因果推論の手法を、仮想のストーリーに乗せて分かりやすく説明していきます。
(全部で、5〜10回のシリーズ記事になる予定です。本記事はその1回目)
仮想のストーリとして「ダイエットアプリ」を例にとります。
効果検証の方法として、アプリ利用者と非利用者の単純比較を行った場合に生じる問題点について説明し、因果推論の必要性を述べ、ポテンシャルアウトカムフレームワークの概要や傾向スコアマッチングなどの因果推論の手法について、R言語による実装例を交えて解説していきます。
また、効果の平均処置効果(ATE)だけでなく、利用者属性によって効果が異なる条件付き平均処置効果(CATE)についても言及します。例えば、年齢、BMI、性別、運動習慣、食生活などの属性によって、アプリ利用による効果が異なる可能性があります。Pythonのパッケージであるdowhy/econMLを使用して、利用者の異質性を考慮したダイエットアプリの効果検証の実装例を説明します。
それでは、因果推論について学んでいきましょう!
問題設定
さて、本記事で取り上げる問題は、あるダイエットアプリの効果を検証することです。
一般的にダイエットアプリは、多くの人が利用する健康アプリの一つで、自分自身の体型や健康状態を改善するために利用されます。しかし、このようなアプリの効果を正確に測定することは容易ではありません。
実際には、多くのユーザーが同じダイエットアプリを使っても、利用者の年齢・性別やもともとの体重、生活習慣の違いにより、その効果は異なります。例えば、元々体重が軽い人は、同じ運動や食事制限をしても、体重の減り方に限界があることが知られています。また、同じ運動や食事制限をしているにも関わらず、ユーザーによっては意識が高まらず、効果を得られない場合もあります。
ダイエットアプリを利用する人は、どういう人が想像できるでしょうか?
- 若い女性は健康意識が高く、ダイエットに関心がありそうだな。
- 中年の男性は、健康診断の結果が年々悪化した結果、ダイエットしようと考えている。
- 健康意識が高い人が、ダイエットに関心がありそう。
- 体重が多い人のほうが、ダイエットに関心がありそう。
というように、
アプリ利用者とアプリ非利用者の背景情報は、一致しないのが一般的です。
そのため、ダイエットアプリ利用の効果検証には慎重さが求められます。
以下では、上記のような状況の疑似データを生成し、効果検証の仮想体験を通して、
効果検証の難しさについて述べていきます。
ダイエットアプリの疑似データの生成
ダイエットアプリを評価する為の疑似データ生成について、説明していきます。
ダイエットアプリの概要
アプリは、ユーザーの情報を元に体重の減少を促すアプリであり、その効果を検証するために、以下の変数を扱います。
扱う変数
- 体重の増減(利用の1年後の体重の増減):outcom
- アプリ利用有無:treat
- 年齢:age
- 性別:gender
- 体重:weight
- 食習慣(0:良い, 1:普通、2:悪い):diet
「体重の増減」の生成方法
「体重の増減」は、年齢、性別、体重、食習慣に基づいて、適当な重みをかけた線形和に、正規分布に従う残差を加えたものとします。
その後、全体の平均が+1となるように標準化を行います。
これにより、「体重の増減」は、平均が+1キロであり、標準偏差が1となるようになります。
最後に、アプリを利用した場合はアプリ利用の効果として-2キロとしました。 ダイエットアプリの効果検証では、アプリを利用したグループと、利用していないグループの体重増減を比較して効果を測定します。この場合、-2キロが真のダイエットアプリの効果となります。効果検証においては、この-2キロが正しく推定できるかどうかが重要なポイントとなります。
※重み付けは以下の通り
- 年齢は高くなるほど、痩せづらい
- 性別は、男性のほうが痩せやすい
- 体重は多いほうが、痩せやすい
- 食習慣は良いほうが、痩せやすい
「アプリ利用有無」の生成方法
「アプリ利用有無」は、年齢、性別、体重、食習慣に基づいて、適当な重みをかけた線形和に、ロジット関数を使ってアプリの利用確率を算出し、その確率に従ってアプリ利用有無をランダムに割り当てています。
※重み付けは以下の通り
- 年齢:若い方が、アプリ利用しやすい
- 性別:男性の方がアプリを利用しやすい
- 体重:多い方が、アプリ利用しやすい
- 食習慣: 悪い方がアプリを利用しやす。
以上が、ダイエットアプリの疑似データを生成するための手順になります。
以下がデータ生成のコードです。
library(tibble)
set.seed(123)
# 年齢、性別、体重、食習慣を生成する関数
generate_covariate <- function(n) {
age <- sample(20:69, n, replace = TRUE)
gender <- sample(c("Male", "Female"), n, prob = c(0.5, 0.5), replace = TRUE)
weight <- rnorm(n, 50, 10) + 0.1 * age + 10 * (gender == "Male") + 0.3 * rnorm(n)
diet <- sample(0:2, n, prob = c(0.4, 0.3, 0.3), replace = TRUE)
tibble(age, gender, weight, diet)
}
# アプリ利用有無を生成する関数
generate_treatment <- function(covariate, a) {
logit <- a[1] * covariate$age + a[2] * (covariate$gender == "Male") + a[3] * covariate$weight + a[4] * covariate$diet
prob <- exp(logit) / (1 + exp(logit))
treatment <- rbinom(nrow(covariate), 1, prob)
tibble(treatment)
}
# アウトカムを生成する関数
generate_outcome <- function(covariate, treatment, b) {
outcome <- b[1] * covariate$age + b[2] * (covariate$gender == "Male") + b[3] * covariate$weight + b[4] * covariate$diet + rnorm(nrow(covariate))
outcome_0 <- scale(outcome, center = TRUE, scale = sd(outcome)) + 1 # アプリ利用していない場合
outcome_1 <- outcome_0 - 2 # アプリ利用している場合
tibble(outcome_0, outcome_1)
}
# 疑似データを生成する関数
generate_data <- function(n, a, b) {
covariate <- generate_covariate(n)
treatment <- generate_treatment(covariate, a)
outcome <- generate_outcome(covariate, treatment$treatment, b)
cbind(covariate, treatment, outcome)
}
# 1000件の疑似データを生成する
n <- 1000
a <- c(-0.1, 1, 0.02, 3)
b <- c(0.1, -2, -0.2, -4)
data <- generate_data(n, a, b)
# 実際の観測値
data <-data %>% mutate(
outcome=case_when(
treatment == 0 ~ outcome_0,
treatment == 1 ~ outcome_1,
))
データのイメージは以下の通りです。(一部を掲載)
age | gender | weight | diet | treatment | outcome_0 | outcome_1 | outcome |
---|---|---|---|---|---|---|---|
50 | Female | 77.55406 | 2 | 1 | 3.9669227 | 1.96692271 | 1.96692271 |
34 | Male | 57.59310 | 2 | 1 | 2.4898854 | 0.48988538 | 0.48988538 |
33 | Female | 58.68436 | 2 | 1 | 2.0998251 | 0.09982511 | 0.09982511 |
22 | Male | 43.65681 | 2 | 1 | 0.5569581 | -1.44304191 | -1.44304191 |
61 | Female | 83.06569 | 2 | 1 | 5.3667989 | 3.36679889 | 3.36679889 |
69 | Male | 65.82998 | 2 | 1 | 3.8853907 | 1.88539070 | 1.88539070 |
体重増減は、
- outcome_0がアプリ利用がない場合の体重増減、
- outcome_1がアプリ利用がある場合の体重増減、
- outcomeが実際の観測値です。
- アプリ利用がない場合は、outcome_0
- アプリ利用がある場合は、outcome_1
を設定しています。
※ 疑似データなので、outcome_0、outcome_1が生成できています。実データにおいて、これは観測できません。(次回のテーマ「ポテンシャルアウトカムフレームワーク」でポイントとなります。)
単純比較の問題点
集計のコード例
tableoneによる比較
library(tableone)
# tableoneを利用して生成されたデータの傾向を比較する
vars <- c("age", "gender", "weight", "diet", "outcome")
CreateTableOne(vars = vars, data = data, factorVars = c("gender", "diet"),
strata = c("treatment"))
比較結果
Stratified by treatment
0 1 p test
n 528 472
age (mean (SD)) 47.35 (13.99) 40.86 (13.37) <0.001
gender = Male (%) 230 (43.6) 270 (57.2) <0.001
weight (mean (SD)) 58.25 (11.19) 60.35 (11.02) 0.003
diet (%) <0.001
0 367 (69.5) 36 ( 7.6)
1 137 (25.9) 153 (32.4)
2 24 ( 4.5) 283 (60.0)
outcome (mean (SD)) 1.62 (0.75) -1.69 (0.77) <0.001
上記の疑似データについて、単純にアプリ利用集団と非利用集団を比較すると、アプリ利用グループが-1.69キロ、アプリ非利用グループが+1.62キロとなる結果が出ました。
アプリ利用者グループの方が、非利用者グループと比較すると3.3キロ体重が減るという結果になりました。
しかし、真のダイエットアプリの効果は-2キロです。
このような結果になるのは、アプリ利用者には、もともと体重が減る要素が多い人が、含まれているためです。
アプリ利用者の方が
- 年齢層が若い
- 男性の方が利用が多い
- 元々体重が重い
- 健康意識が高く
という傾向がある場合、アプリを使わなくても、体重が落ちやすい可能性が高くなります。
(※疑似データの作り方は、痩せやすい人がアプリ利用するシナリオを意識してデータを作成しています。)
この例で言えば、単純比較でダイエットアプリの効果を推定する場合、
ダイエットアプリの効果を、過大評価する結果になってしまいます。
実際、ヘルスケアアプリの効果検証の分析結果を伝える場合、
「もともと健康になりそうな人が使っているだけではないの?」
のツッコミは常にあります。
さて真の効果を推定するためには、どのようにすればよいでしょうか?
次回
今回の記事では、単純比較では真の効果が推定できず、アプリ利用者とアプリ利用者非利用者の背景の違いの影響を排除した手法が必要不可欠であることが示されました。
次回「ポテンシャルアウトカムフレームワーク」について説明してきます!
一緒に働く仲間を探しています!
Ubie Discoveryは、「テクノロジーで適切な医療に導く」ことをミッションとしています。
このミッションをデータドリブンで実現することを目指しています。
そのためには、データアナリスト、アナリティクスエンジニア、データエンジニアの力が必要です。
そこで、私たちは一緒に働く仲間を募集しています!
興味のある方は下記JDから直接応募していただいても良いですし、一度カジュアルにお話したい方はそれぞれのメンバーと話せるカジュアル面談ページがあるのでぜひお気軽にご連絡ください。
私の入社ブログは
で書いてます。
Discussion