Volvamos a nuestro ejemplo tonto, dónde habíamos visto que el T-learner cuando el modelo base es un modelo lineal equivale a tener un modelo saturado (con interacciones).
En estos de los “metalearners” tenemos entre otros, los T-learners vistos en el post anterior , los S-learner y los X-learners.
Los S-learners no es más que usar un solo modelo “Single” para estimar el Conditional Average Treatment Effect , CATE.
El S-learner sería estimar un sólo modelo y ver la diferencia (en esperanzas) en lo que estima el modelo para cuando W=1 versus lo que estima cuando W=0.
\(E[Y=y | W=1, X=x] - E[Y=y | W=0, X=x]\)
Si hacemos un modelo lineal en este ejemplo, cabe plantearse dos, uno con la interacción
Code
mod_saturado <-lm(Y ~ W *X , data = df)summary(mod_saturado)#> #> Call:#> lm(formula = Y ~ W * X, data = df)#> #> Residuals:#> Min 1Q Median 3Q Max #> -4.6786 -1.2138 0.1903 1.5419 4.6289 #> #> Coefficients:#> Estimate Std. Error t value Pr(>|t|) #> (Intercept) 6.9118 3.1354 2.204 0.0299 * #> W -1.8395 4.0511 -0.454 0.6508 #> X 1.6981 0.3085 5.504 3.08e-07 ***#> W:X 2.4096 0.4016 6.000 3.49e-08 ***#> ---#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1#> #> Residual standard error: 2.011 on 96 degrees of freedom#> Multiple R-squared: 0.9689, Adjusted R-squared: 0.9679 #> F-statistic: 995.9 on 3 and 96 DF, p-value: < 2.2e-16
Que es lo mismo que haber considerado solo los coeficientes cuando W = 1
Code
(cate1 <-coef(mod_saturado)[2] +coef(mod_saturado)[4] *14 )#> W #> 31.89504
Esto ya lo habíamos visto en el post anterior. El tema es que hemos elegido como modelo base el modelo saturado pero podríamos haber elegido otro.
Code
mod_efectos_ppal <-lm(Y ~ W + X , data = df)predict(mod_efectos_ppal, df_w1) -predict(mod_efectos_ppal, df_w0)#> 1 #> 22.33547
Y el CATE en este caso está subestimado ya que no hemos tenido en cuenta la interacción (que existe por construcción del efecto).
Podríamos haber elegido otro modelo, y obtener otra estimación del CATE. Usando un árbol por ejemplo, o en caso de tener más variables, cualquier modelo que se os ocurra.
Code
library(rpart)mod_arbol <-rpart(Y ~ W + X , data = df)predict(mod_arbol, df_w1) -predict(mod_arbol, df_w0)#> 1 #> 27.88051
Total, que el S-learner es eso, usar un sólo modelo y obtener la diferencia entre lo que estima para cuando W = 1 y cuando W = 0.
X-learner
Los X-learner es una forma un poco más inteligente de usar los T-learners. Básicamente se trata de.
Estimamos dos modelos, uno con los datos cuando W=0 y otro cuando W=1. Los notamos por
\[\hat{\mu}_{0} = M_{1}(Y^0 \sim X^0)\] y por \[\hat{\mu}_{1} = M_{2}(Y^1 \sim X^1)\]
Ahora usamos esos modelos de la siguiente forma, para las observaciones que tengan W=0 utilizamos el modelo \(\hat{\mu}_{1}\), y para las observaciones con W=1 usamos el modelo que se ha estimado usando la otra parte de los datos \(\hat{\mu}_{0}\).
Calculamos para cada observación con W=0 la diferencia entre lo observado y lo estimado por el modelo \(\hat{\mu}_{1}\) y lo mismo para las observaciones con W=1. Así tenemos.
\[\hat{\tau}(x) = ps(x)\hat{\tau}_0(x) + (1- ps(x))\hat{\tau}_1(x) \] Dónde \(ps(x) \in [0,1]\) es una función de pesos con ciertas propiedades, normalmente se suele usar el propensity score, que básicamente es la estimación de la probabilidad de que cada observación pertenezca al tratamiento vs al control.
Y en nuestro ejemplo como sería.
Modelos 1 y 2 usando como modelos base un árbol por ejemplo.
Code
m1 <-rpart(Y ~ X , data = df, subset = (W==0))m2 <-rpart(Y ~ X , data = df, subset = (W==1))
Diferencias
Usamos modelo 1 para estimar cuando W=1 y el modelo 2 para estimar cuando W = 0
Code
# Con el viejo R-base sería df$Difer[df$W==1] <- df$Y[df$W==1] -predict(m1, df[df$W==1, ])head(df)#> Y W X Difer#> 1 48.78438 1 10.800067 22.14350#> 2 25.28644 0 10.707605 NA#> 3 28.39538 0 9.925625 NA#> 4 47.60225 1 10.652555 22.79507#> 5 46.72225 1 9.992698 21.91507#> 6 55.15008 1 11.514759 28.50920
Este ejemplo es muy sencillo, y supongo que habría que verlo con muchas más variables y utilizando modelos base más complejos.
No obstante, todo esto de los metalearners no tiene mucho sentido si el grado de solape entre la distribución de las X en el tratamiento y el control no es suficiente, cosa que se intenta arreglar un poco utilizando los propensity scores en el X-learner.
Extra, uso de causalml
En la librería causalml de Uber vienen implmentandos los metalearner entre otras cosas. Usando el mismo ejemplo veamos como se calcularía el CATE.
Nota: He sido incapaz de ver como predecir para mi nueva x, no hay o no he encontrado que funcione un método predict para aplicar el X learner a unos nuevos datos.
Code
from causalml.inference.meta import BaseXRegressor#> The sklearn.utils.testing module is deprecated in version 0.22 and will be removed in version 0.24. The corresponding classes / functions should instead be imported from sklearn.utils. Anything that cannot be imported from sklearn.utils is now part of the private API.from sklearn.linear_model import LinearRegression# llamamos al df que está en Rdf_python = r.df[['Y','W','X','pesos']]df_python#> Y W X pesos#> 0 48.784384 1.0 10.800067 0.608752#> 1 25.286438 0.0 10.707605 0.612492#> 2 28.395375 0.0 9.925625 0.643553#> 3 47.602247 1.0 10.652555 0.614712#> 4 46.722247 1.0 9.992698 0.640932#> .. ... ... ... ...#> 95 47.309873 1.0 10.771928 0.609891#> 96 20.741797 0.0 9.829798 0.647284#> 97 24.393540 0.0 10.412418 0.624340#> 98 53.716540 1.0 11.506078 0.579804#> 99 51.222453 1.0 10.711256 0.612344#> #> [100 rows x 4 columns]