Saltar al contenido

# inferencia-causal-practica-con-dowhy-y-grafos-dirigidos-dags

Inferencia Causal Práctica con DoWhy y Grafos Dirigidos (DAGs)

Volver a la portada del curso
Notebook

Configuración del Entorno con DoWhy

Sección 3 · Uso de DoWhy para Modelado Causal

Configuración del entorno con DoWhy

En este notebook dejas listo tu entorno y recorres por primera vez los cuatro pasos de DoWhy: modelar, identificar, estimar y refutar. El caso es una tienda en línea que envió cupones de descuento y quiere saber cuánto aumentan el gasto. Descarga el notebook adjunto y ejecútalo celda por celda.

Instalación

Crea un entorno virtual e instala la biblioteca:

# En la terminal:
# python -m venv .venv
# .venv\Scripts\activate          (Windows)
# source .venv/bin/activate       (macOS o Linux)
# pip install dowhy matplotlib

import dowhy, networkx, pandas, sklearn
print(dowhy.__version__, networkx.__version__, pandas.__version__, sklearn.__version__)

El material se probó con DoWhy 0.14, NetworkX 3.6, pandas 3.0, scikit-learn 1.8 y statsmodels 0.14 (estas dependencias llegan con DoWhy). No hace falta instalar Graphviz: si no está, DoWhy dibuja el grafo con NetworkX y Matplotlib.

Los datos: cupones y gasto

Generamos 4,000 clientes con semilla fija. Los clientes con más antigüedad y más visitas reciben más cupones (la tienda los prioriza) y además gastan más. Un sorteo decide parte de los envíos. El efecto verdadero del cupón es de 8 pesos:

import numpy as np
import pandas as pd

def generar_cupones(n=4000, semilla=42):
    rng = np.random.default_rng(semilla)
    antiguedad = rng.uniform(0, 10, n)
    visitas = rng.poisson(5, n)
    sorteo = rng.binomial(1, 0.5, n)
    p = 1 / (1 + np.exp(-(-2.5 + 0.25 * antiguedad + 0.2 * visitas + 2.0 * sorteo)))
    cupon = rng.binomial(1, p)
    gasto = 50 + 8 * cupon + 4 * antiguedad + 3 * visitas + rng.normal(0, 10, n)
    return pd.DataFrame(dict(antiguedad=antiguedad.round(2), visitas=visitas,
                             sorteo=sorteo, cupon=cupon, gasto=gasto.round(2)))

La comparación ingenua (gasto medio con cupón menos gasto medio sin cupón) da 17.02 pesos: más del doble del efecto real, porque mezcla el efecto del cupón con el hecho de que los cupones fueron a los mejores clientes.

Paso 1: modelar con un grafo

CausalModel recibe los datos, el tratamiento, el resultado y el grafo. El grafo puede ser un texto en formato DOT o GML, o un networkx.DiGraph:

from dowhy import CausalModel

df = generar_cupones()
grafo = """digraph {
  antiguedad -> cupon; antiguedad -> gasto;
  visitas -> cupon; visitas -> gasto;
  sorteo -> cupon; cupon -> gasto
}"""
modelo = CausalModel(data=df, treatment="cupon", outcome="gasto", graph=grafo)
print(modelo.get_common_causes(), modelo.get_instruments())
modelo.view_model()

DoWhy lee el grafo y reconoce los confusores (antiguedad, visitas) y el instrumento (sorteo).

Paso 2: identificar

identify_effect() aplica los criterios del grafo y devuelve las expresiones (estimandos) que permiten calcular el efecto. Aquí encuentra un estimando de puerta trasera, que ajusta por los dos confusores, y uno de variable instrumental con el sorteo. Este paso no usa los datos: es puro razonamiento sobre el grafo.

Paso 3: estimar

estimando = modelo.identify_effect()
estimacion = modelo.estimate_effect(estimando, method_name="backdoor.linear_regression",
                                    confidence_intervals=True)
print(round(estimacion.value, 3), estimacion.get_confidence_intervals().round(2))

La regresión con ajuste da 7.899, con intervalo del 95% de [7.21, 8.59], que contiene el 8 verdadero. El nombre del método tiene la forma estrategia.estimador: backdoor., iv. o frontdoor., seguido del estimador.

Paso 4: refutar

Un refutador cambia los datos de una forma cuyo resultado conoces y revisa si la estimación se comporta como debe. Con un tratamiento placebo (el cupón permutado al azar), el efecto debe desaparecer:

placebo = modelo.refute_estimate(estimando, estimacion,
                                 method_name="placebo_treatment_refuter",
                                 placebo_type="permute", random_seed=1)
print(placebo)

El nuevo efecto es 0.052, prácticamente cero: la estimación no es un artefacto del modelo.

Trampas comunes

  • Guarda el tratamiento binario como 0/1 (entero), no como bool: con pandas 3, el refutador add_unobserved_common_cause falla al escribir enteros en una columna booleana.
  • Los nombres de nodos del grafo deben coincidir exactamente con las columnas del DataFrame.
  • Una variable del grafo que no está en los datos se trata como no observada; si no lo esperabas, revisa la ortografía.
  • Fija random_seed en los refutadores para que los resultados se puedan reproducir.

Cierre

Con estos cuatro pasos ya tienes el esqueleto de cualquier análisis causal del curso. En la siguiente sección los usarás con varios estimadores y con grafos donde el ajuste simple no funciona.

Recursos