Python & Data Science
Inferencia Causal En revisión

Estimación y validación de efectos causales con DoWhy

La última vez en la política de 90 días

Maya — la nueva científica de datos en Blue Harbor Hotels — dibujó su primer DAG (Grafo Dirigido Acíclico: una imagen de qué causa qué) para el problema de cancelaciones del hotel. Mapeó la pregunta de la política de 90 días — ¿reservar con mucha antelación causa cancelaciones? — y aprendió que una variable oculta llamada confusor puede falsificar toda la conexión.

Ella tiene la imagen. Ahora Mr. Delgado, el VP de Ingresos, quiere el número: ¿en cuánto un lead_time largo aumenta la probabilidad de que un huésped cancele? Maya tiene que demostrar que puede confiar en él.

Este tutorial es el núcleo de toda la serie: cómo pasar de “tengo un DAG” a “aquí hay un número, y aquí está por qué deberías creerlo”. También es donde DoWhy demuestra su valor — no solo te da una respuesta. Te obliga a comprobar si la pregunta era respondible en primer lugar.

Lo que aprenderás aquí:

  • Qué es un estimand y por qué DoWhy lo separa de la estimación
  • Cómo usar identify_effect() para determinar si tu pregunta causal es respondible
  • Los principales métodos de estimación en DoWhy y cuándo usar cada uno
  • Cómo interpretar una estimación causal
  • Cómo funciona la refutación y qué está probando realmente cada refutador
  • Cómo interpretar los resultados de la refutación y decidir si confiar en tu estimación

Requisitos previos: Haber completado los Tutoriales 1 y 2 (o estar familiarizado con CausalModel de DoWhy y la construcción de DAGs). Tener DoWhy instalado. Estar familiarizado con conceptos básicos de regresión.


1. Repaso rápido: el flujo de trabajo de cuatro pasos

Antes de que Maya ejecute cualquier cosa, Priya le pide que anote cuatro pasos a los que regresará:

# The four steps of DoWhy's CausalModel API:
# 1. model  = CausalModel(data, treatment, outcome, graph)   → encode assumptions
# 2. estimand = model.identify_effect()                      → prove it's answerable
# 3. estimate = model.estimate_effect(estimand, method_name) → compute the number
# 4. refute  = model.refute_estimate(estimand, estimate, ...) → try to break it

El paso 1 provino del Tutorial 2. Este tutorial cubre los pasos 2, 3 y 4. Maya comienza reconstruyendo el mismo conjunto de datos de hoteles y el DAG de la vez anterior, manteniendo una configuración familiar:

import pandas as pd
import numpy as np
from dowhy import CausalModel
import networkx as nx

np.random.seed(0)
n = 5000

lead_time = np.random.exponential(scale=60, size=n).astype(int)
is_repeat_guest = np.random.binomial(1, 0.15, n)
market_segment = np.random.choice(
    ["Direct", "Corporate", "Online TA", "Groups"], n,
    p=[0.2, 0.25, 0.45, 0.1]
)
special_requests = np.random.poisson(0.7, n)
cancel_prob = (
    0.3
    + 0.003 * lead_time
    - 0.15 * is_repeat_guest
    - 0.01 * special_requests
    + np.random.normal(0, 0.1, n)
)
is_cancelled = np.random.binomial(1, np.clip(cancel_prob, 0, 1))

df = pd.DataFrame({
    "lead_time": lead_time,
    "is_cancelled": is_cancelled,
    "is_repeat_guest": is_repeat_guest,
    "market_segment": market_segment,
    "special_requests": special_requests,
})

G = nx.DiGraph()
G.add_edges_from([
    ("lead_time", "is_cancelled"),
    ("is_repeat_guest", "lead_time"),
    ("is_repeat_guest", "is_cancelled"),
    ("market_segment", "lead_time"),
    ("market_segment", "special_requests"),
    ("special_requests", "is_cancelled"),
])

model = CausalModel(data=df, treatment="lead_time", outcome="is_cancelled", graph=G)

Hasta ahora, esto es puro Tutorial 2 — mismos datos, misma imagen. Ahora viene la parte nueva.

2. Paso 2 en profundidad: identificación y el estimando

Aquí hay una pregunta que la mayoría de los principiantes nunca piensa en hacer, y es la más importante de todo el flujo de trabajo: dados mi grafo causal y los datos observacionales, ¿acaso es matemáticamente posible estimar el efecto causal?

Esa pregunta es la identificación. A veces la respuesta es un rotundo no. Un solo factor de confusión no medido, sin solución alternativa, y ninguna cantidad de datos te salvará — un millón de filas y sigues atascado. DoWhy te hace enfrentar esto antes de calcular nada.

Si la respuesta es sí, DoWhy produce un estimando: la fórmula matemática precisa que, al calcularse correctamente, te da el efecto causal. Maya escribe la distinción clave con letras grandes en su cuaderno:

  • Un estimando es una ecuación — “aquí está la matemática que me daría la verdad.”
  • Una estimación es un número — “aquí está el valor real que calculé a partir de mis datos.”

El estimando es la receta. La estimación es el plato.

estimand = model.identify_effect(proceed_when_unidentifiable=True)
print(estimand)

La salida le indica a Maya tres cosas:

  • Tipo de estimando — ¿está DoWhy usando una ruta de puerta trasera (bloqueando los factores de confusión), una ruta de puerta delantera (un mediador) o una variable instrumental (una puerta lateral, cubierta en el Tutorial 5)? Para nuestros datos del hotel, es del tipo puerta trasera.
  • La expresión del estimando — la fórmula real.
  • Variables de puerta trasera — la lista exacta de variables que DoWhy dice que debemos controlar para aislar el efecto del tiempo de anticipación.
print("Backdoor adjustment set:")
print(estimand.backdoor_variables)

Nota: DoWhy encuentra automáticamente el conjunto óptimo de ajuste de puerta trasera a partir de tu grafo. También puedes especificarlo manualmente si tienes conocimiento del dominio que reemplace la selección automática.

Maya ya puede ver el beneficio del Tutorial 2: dibujó el DAG, y DoWhy convirtió esa imagen en “aquí está el conjunto exacto de variables a mantener constantes.”

3. El criterio de puerta trasera (sin matemáticas)

Un camino de puerta trasera es cualquier camino desde el tratamiento hasta el resultado que va en contra de una flecha causal antes de ir a favor de una. Estos caminos crean una correlación espuria sin representar una verdadera causalidad.

Aquí está el camino de puerta trasera en el modelo de hotel de Maya:

Example backdoor path in our hotel model:
lead_time ← is_repeat_guest → is_cancelled

This path makes lead_time and is_cancelled correlated even if lead_time
had ZERO causal effect on cancellation.

Léalo de izquierda a derecha. El estado de huésped recurrente causa el tiempo de anticipación, y también causa la cancelación. Por lo tanto, incluso si el tiempo de anticipación no tuviera un efecto real, ambos seguirían pareciendo correlacionados. Comparten una causa común.

El criterio de puerta trasera es lo que le saca de este problema. Si puede bloquear cada camino de puerta trasera condicionando por un conjunto de variables — sin abrir nuevos a través de colisionadores — puede identificar el efecto causal. El “conjunto de ajuste de puerta trasera” del estimando hace exactamente eso.

from dowhy.causal_identifier import BackdoorAdjustment
print(f"Variables in backdoor adjustment set: {estimand.backdoor_variables}")

“Condicionar por” simplemente significa mantener constante. Cuando controlamos por is_repeat_guest, estamos diciendo: compare los huéspedes con tiempo de anticipación largo y los huéspedes con tiempo de anticipación corto que tienen la misma probabilidad de ser huéspedes recurrentes. Eso cierra el camino espurio.

4. Paso 3 en profundidad: métodos de estimación

Maya tiene la receta (el estimando). Ahora la pregunta es cómo prepararla — y hay varias cocinas para elegir. Lo clave que hay que asimilar: el estimando permanece igual independientemente del estimador que elijas. La receta no cambia; solo lo hace la técnica de preparación.

4a. Regresión Lineal

estimate_lr = model.estimate_effect(
    estimand,
    method_name="backdoor.linear_regression",
    control_value=0,
    treatment_value=1,
)
print(f"Linear regression estimate: {estimate_lr.value:.4f}")
print(estimate_lr)

Qué hace esto: Ejecuta una regresión de is_cancelled sobre lead_time y todas las variables de ajuste de puerta trasera. El coeficiente de lead_time es tu estimación. (Una regresión es simplemente una línea matemática ajustada a los datos para describir cómo cambia una variable con otra.)

4b. Propensity Score Matching

estimate_psm = model.estimate_effect(
    estimand,
    method_name="backdoor.propensity_score_matching",
    target_units="ate",
)
print(f"Propensity score matching estimate: {estimate_psm.value:.4f}")

Cuándo usarlo: cuando sospechas que la relación entre los confusores y el tratamiento es no lineal, o tus tamaños de muestra son lo suficientemente grandes para encontrar buenos emparejamientos. (El emparejamiento tiene su propio tratamiento a fondo en el Tutorial 6.)

4c. Inverse Probability Weighting (IPW)

estimate_ipw = model.estimate_effect(
    estimand,
    method_name="backdoor.propensity_score_weighting",
    method_params={"weighting_scheme": "ips_weight"},
)
print(f"IPW estimate: {estimate_ipw.value:.4f}")

4d. Elegir un Método

MétodoUsar CuandoSupone
linear_regressionLas relaciones son aproximadamente lineales; línea base rápidaLinealidad, sin efectos de interacción
propensity_score_matchingConfusión no lineal; N grandeBuen solapamiento entre tratados/control
propensity_score_weightingQuieres que todos los puntos de datos contribuyanPositividad: todas las unidades tienen cierta probabilidad de tratamiento
econml.* (EconML)Quieres efectos de tratamiento heterogéneosVaría según el método

Consejo: prueba múltiples estimadores. Si dan respuestas muy diferentes, probablemente necesites revisar los supuestos de tu modelo — no porque un estimador esté bien y el otro esté mal.

Este consejo guía los siguientes tutoriales. Si tres estimadores coinciden, puedes respirar. Si discrepan drásticamente, el problema no son los estimadores. Es la imagen — el DAG — que dibujaste.

5. Interpretación de tu estimación

Maya ejecuta su regresión lineal y obtiene un número. ¿Qué significa en realidad?

Una estimación de 0.003 significa: por cada día extra de lead_time, la probabilidad de que un huésped cancele aumenta en aproximadamente 0.3 puntos porcentuales. Por lo tanto, un huésped que reserva con 90 días de anticipación frente a uno que reserva con 1 día tendría una probabilidad de cancelación aproximadamente 0.27 mayor (89 días extra × 0.003) solo debido al tiempo de anticipación — asumiendo que el DAG es correcto.

Tres cosas que siempre debes revisar al leer una estimación:

  1. El signo. Positivo significa que el tratamiento aumenta el resultado; negativo significa que lo disminuye.
  2. El tamaño. ¿Es lo suficientemente grande como para importar en la decisión de negocio? Un efecto de 0.003 por día se acumula mucho durante 90 días.
  3. La incertidumbre. Cualquier estimación a partir de los datos conlleva cierto margen de error. DoWhy reporta intervalos de confianza — un rango en el que probablemente se encuentra el efecto real. Un rango amplio significa que no estás seguro; uno estrecho significa que sí lo estás.

6. Paso 4 en profundidad: por qué importa la refutación

Aquí es donde DoWhy se diferencia de cualquier otra biblioteca que Maya haya usado. Después de obtener un número, la filosofía de DoWhy es: no confíes en él — intenta romperlo.

Esto se llama refutación. La idea es simple y brutal: si nuestro análisis causal es correcto, entonces ciertos “trucos baratos” no deberían poder reproducir el resultado. Un refutador ejecuta uno de esos trucos. Si el truco tiene éxito — produciendo un gran efecto donde no debería haber ninguno — nuestro análisis es sospechoso. Si falla, produciendo aproximadamente cero como debería, nuestro análisis gana un poco más de confianza.

refutation = model.refute_estimate(
    estimand,
    estimate_lr,
    method_name="random_common_cause",
)
print(refutation)

Observa la forma aquí: esto es jugar deliberadamente al escéptico. Maya ya no está intentando demostrar que tiene razón — está intentando demostrar que está equivocada, a propósito, antes de que Delgado lo haga por ella.

7. Los cuatro refutadores principales

Maya mantiene cuatro refutadores en rotación regular. Cada uno se enfoca en un punto débil diferente.

Refutador 1: Causa Común Aleatoria. Agrega una variable completamente aleatoria al modelo y lo reestima. Si una variable verdaderamente aleatoria cambia tu respuesta, el modelo está captando ruido, no señal. Apenas debería moverse.

Refutador 2: Tratamiento Placebo. Cambia el tratamiento real (lead_time) por ruido aleatorio y lo reestima. Si el ruido puro produce un “efecto” grande, tu modelo está encontrando patrones donde no existen. Debería ser ~0.

refute_placebo = model.refute_estimate(
    estimand,
    estimate_lr,
    method_name="placebo_treatment_refuter",
    placebo_type="permute",
)
print(refute_placebo)

Refutador 3: Subconjunto de Datos. Elimina una porción aleatoria de los datos —digamos 20%— y lo reestima. Si la respuesta varía drásticamente, el resultado fue una casualidad de una muestra en particular. Debería mantenerse más o menos igual.

refute_subset = model.refute_estimate(
    estimand,
    estimate_lr,
    method_name="data_subset_refuter",
    subset_fraction=0.8,
)
print(refute_subset)

Refutador 4: Causa Común No Observada. Simula un confusor oculto (una variable “fantasma”) de fuerza creciente y rastrea qué tanto se mueve la estimación. Este es el refutador de análisis de sensibilidad. Tendrá su propio tutorial en la Parte 4.

8. Integrando todo: un análisis completo

Aquí está el flujo de trabajo completo en una sola ejecución — cómo Maya planea presentárselo a Delgado:

# Step 1: encode assumptions
model = CausalModel(data=df, treatment="lead_time", outcome="is_cancelled", graph=G)

# Step 2: prove it's answerable
estimand = model.identify_effect(proceed_when_unidentifiable=True)

# Step 3: compute the number (try a few kitchens)
estimate_1 = model.estimate_effect(estimand, method_name="backdoor.linear_regression")
estimate_2 = model.estimate_effect(estimand, method_name="backdoor.propensity_score_matching")
print(f"Linear regression:  {estimate_1.value:.4f}")
print(f"Propensity matching: {estimate_2.value:.4f}")

# Step 4: try to break it
refute_placebo = model.refute_estimate(estimand, estimate_1, method_name="placebo_treatment_refuter")
refute_subset = model.refute_estimate(estimand, estimate_1, method_name="data_subset_refuter")
print("Placebo refutation:", refute_placebo)
print("Subset refutation:", refute_subset)

Si ambos estimadores coinciden aproximadamente y las refutaciones se comportan bien (placebo ~0, subconjunto estable), Maya tiene una historia que puede defender.

9. Lo que la refutación puede y no puede decirte

La refutación tiene un valor real, pero no es una varita mágica. Maya tiene que ser sincera sobre sus límites:

Lo que SÍ puede decirte: que tu modelo es consistente — no está captando ruido puro, no es una casualidad de una sola muestra y no está siendo impulsado silenciosamente por una variable oculta fuerte.

Lo que NO puede decirte: que tu DAG sea correcto. Cada refutación prueba el modelo dado el grafo. Si el grafo en sí es incorrecto — si olvidaste un confundidor real que no es ruido aleatorio y no está capturado por un subconjunto — la refutación no lo detectará. El refutador placebo puede demostrar “tu modelo no está encontrando patrones en la nada.” No puede demostrar “tu modelo encontró el patrón correcto.”

Piénsalo como una revisión técnica de un auto: puede decirte que los frenos funcionan. No puede decirte que no haya un bache en la próxima curva.

10. Conclusión

Maya finalmente tiene su número: los plazos de anticipación largos sí causan cancelaciones, y el efecto es lo suficientemente real como para importar en una política de 90 días. Ella sabe hasta qué punto confiar en ese resultado. También sabe lo que aún no puede probar.

Pero hay una sombra sobre el resultado. La pregunta más incisiva de Mr. Delgado sigue sin respuesta: “¿Y si existe un rasgo de ‘viajero inestable’ que nunca recopilamos — un tipo de huésped que reserva con anticipación y también cancela? No puedes refutar eso con ruido aleatorio, Maya. Eso no es aleatorio. Es específico.”

Él tiene razón. Y ese fantasma es exactamente lo que el próximo tutorial persigue.

En este tutorial, aprendiste:

  • Qué es un estimando y por qué DoWhy lo separa de la estimación
  • Cómo identify_effect() verifica si tu pregunta tiene respuesta
  • Los principales métodos de estimación y cuándo usar cada uno
  • Cómo interpretar una estimación causal (signo, magnitud, incertidumbre)
  • Por qué importa la refutación y qué ponen a prueba los cuatro refutadores
  • Qué puede y qué no puede decirte la refutación

A continuación: Tutorial 4 — Análisis de sensibilidad, donde Maya persigue la variable de confusión no medida.

Comprueba tu comprensión

Las preguntas a continuación van desde el simple recuerdo hasta el diseño abierto, siguiendo aproximadamente la taxonomía de Bloom.

Recordar ¿Cuál es la diferencia entre un “estimando” y una “estimación” en el flujo de trabajo de DoWhy?

Comprender Con tus propias palabras, explica qué es un “camino de puerta trasera”, usando el ejemplo propio del tutorial (lead_time ← is_repeat_guest → is_cancelled).

Aplicar Usando la tabla de selección de métodos del tutorial, ¿qué método de estimación elegirías si sospecharas que la relación entre tus variables de confusión y el tratamiento es no lineal y tuvieras un tamaño de muestra grande? ¿Qué supuesto requiere ese método que linear_regression no requiere?

Analizar El tutorial señala que “el estimando permanece igual independientemente del estimador que elijas.” Explica paso a paso por qué es cierto: ¿qué especifica exactamente el estimando y por qué cambiar de regresión lineal a emparejamiento por puntaje de propensión no lo cambia?

Evaluar El consejo del tutorial dice que si múltiples estimadores dan respuestas muy diferentes, “generalmente significa que los supuestos de tu modelo necesitan revisión, no que un estimador esté correcto y otro incorrecto.” Critica este consejo: ¿existe alguna vez un caso legítimo en el que confiarías en la respuesta de un estimador sobre la de otro, en lugar de concluir que todo tu modelo está defectuoso?

Crear Diseña un escenario (un tratamiento, un resultado y una restricción de tamaño de datos) en el que deliberadamente elegirías propensity_score_weighting en lugar de propensity_score_matching, basándote en el compromiso mencionado en el tutorial sobre el uso de “todos los puntos de datos.”

Esta traducción fue generada automáticamente y puede contener errores. Si el idioma inglés es tu preferencia, puedes leer el artículo original en inglés .

«Aplica lo que aprendiste» es para suscriptores Supporter e Insider.

Suscríbete para desbloquear los ejercicios de este artículo.

Ver planes

¿Buscas otra cosa?

Busca en todos los artículos por título, resumen o tema.