# `causalis.scenarios.gate.model`

Utilities for group-level IRM estimands such as GATE and GATET.

The public entry points in this module estimate subgroup effects from a fitted binary-treatment IRM using pre-defined groups aligned to the fit-time sample.

## Module Contents

### Functions

`estimate_gate_from_irm` | Estimate Group Average Treatment Effects (GATEs) from a fitted IRM.
`estimate_gatet_from_irm` | Estimate Group Average Treatment Effects on the Treated (GATETs) from a fitted IRM.

### API

causalis.scenarios.gate.model.estimate_gate_from_irm(irm_model: Any, groups: Optional[pandas.DataFrame | pandas.Series | numpy.ndarray], alpha: float = 0.05, cov_type: str = 'HC3', cov_kwds: Optional[Dict[str, Any]] = None) → causalis.data_contracts.gate_estimate.GateEstimate

  Estimate Group Average Treatment Effects (GATEs) from a fitted IRM.

  Parameters

  irm_model : fitted IRM Interactive Regression Model with stored cross-fitted nuisance predictions :math:`\hat g_0(X)`, :math:`\hat g_1(X)`, and :math:`\hat m(X)`. The model must already be fitted. groups : Optional[pd.DataFrame, pd.Series, or np.ndarray] Pre-specified subgroup definition.

      Accepted forms are:
      - A single Series / one-column DataFrame of group labels.
      - A multi-column dummy basis with binary indicators.
      - A numpy array of group labels (assumed aligned with fit-time rows).

      For strict GATE, groups must define a mutually exclusive and exhaustive
      partition of the fitted sample. Each group must contain at least one
      treated and one control observation.

      GATE requires the fitted ``CausalData`` to define ``user_id``.
      Group rows are aligned to the IRM fit-time sample using those fit-time
      observation ids. In practice, ``groups.index`` must either exactly match
      those ids or be a permutation of them. As a convenience, when the model
      was fitted with ``user_id``, GATE also accepts groups indexed by the
      original fit-time row index if the current row-to-``user_id`` mapping is
      still unchanged.

      If None, falls back to ``irm_model.data.gate_groups`` when present.

  alpha : float, default 0.05 Significance level for two-sided confidence intervals. cov_type : {“HC0”, “HC1”, “HC2”, “HC3”}, default “HC3” Heteroskedasticity-robust covariance estimator used for subgroup inference. The implementation uses a closed-form saturated dummy-model covariance, equivalent to a no-intercept OLS regression on the GATE basis. cov_kwds : Optional[Dict[str, Any]], default None Additional covariance options requested by the caller. These are currently ignored because GATE uses the closed-form HCx covariance formulas implemented in this module.

  Returns

  GateEstimate Result contract containing subgroup effects, standard errors, Wald statistics, confidence intervals, covariance matrix, and group-level diagnostics. The returned object also supports `contrast(...)` and `pairwise_summary(...)` for formal post-estimation group comparisons.

  Examples

  > > > import pandas as pd from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor from causalis.dgp import obs_linear_26_dataset from causalis.scenarios.unconfoundedness.model import IRM data = obs_linear_26_dataset( … n=1000, … seed=3141, … include_oracle=False, … return_causal_data=True, … ) groups = pd.Series( … (data.df[“x1”] >= 0).map({True: “high_x1”, False: “low_x1”}), … index=data.df[“user_id”], … name=”segment”, … ) irm = IRM( … data=data, … ml_g=RandomForestRegressor(n_estimators=200, max_depth=6, random_state=3141), … ml_m=RandomForestClassifier(n_estimators=200, max_depth=6, random_state=3141), … n_folds=3, … random_state=3141, … ) irm.fit() gate = estimate_gate_from_irm(irm, groups=groups) gate.summary() # doctest: +SKIP

  Notes

  This implementation targets strict subgroup effects under the same unconfoundedness and overlap assumptions used by IRM, with an additional requirement that subgroup membership is pre-treatment.

  Let :math:`G` denote a finite partition of the sample space and let :math:`\phi(W; \hat\eta)` be the cross-fitted doubly robust signal

  .. math::

      \phi =
      \hat g_1(X) - \hat g_0(X)
      + (Y - \hat g_1(X)) \frac{D}{\hat m(X)}
      - (Y - \hat g_0(X)) \frac{1-D}{1-\hat m(X)}.

  For a subgroup :math:`g`, the target estimand is

  .. math::

      \theta_g = \mathbb{E}[\phi(W; \eta_0) \mid G=g].

  With a mutually exclusive and exhaustive dummy basis, the estimator reduces to the groupwise sample mean of the orthogonal score:

  .. math::

      \hat\theta_g = \frac{1}{n_g} \sum_{i : G_i = g} \hat\phi_i.

  Inference is computed from the equivalent saturated no-intercept linear regression of :math:`\hat\phi` on the subgroup dummies. Because the design is block-diagonal, the covariance matrix is diagonal and each subgroup variance is available in closed form under HC0/HC1/HC2/HC3.

  `normalize_ipw=True` on the IRM is intentionally ignored for GATE so the estimator uses the canonical unnormalized orthogonal signal above.

causalis.scenarios.gate.model.estimate_gatet_from_irm(irm_model: Any, groups: Optional[pandas.DataFrame | pandas.Series | numpy.ndarray], alpha: float = 0.05, cov_type: str = 'HC3', cov_kwds: Optional[Dict[str, Any]] = None) → causalis.data_contracts.gate_estimate.GateEstimate

  Estimate Group Average Treatment Effects on the Treated (GATETs) from a fitted IRM.

  Parameters

  irm_model : fitted IRM Interactive Regression Model with stored cross-fitted nuisance predictions :math:`\hat g_0(X)`, :math:`\hat g_1(X)`, and :math:`\hat m(X)`. The model must already be fitted. groups : Optional[pd.DataFrame, pd.Series, or np.ndarray] Pre-specified subgroup definition.

      Accepted forms are:
      - A single Series / one-column DataFrame of group labels.
      - A multi-column dummy basis with binary indicators.
      - A numpy array of group labels (assumed aligned with fit-time rows).

      For strict GATET, groups must define a mutually exclusive and exhaustive
      partition of the fitted sample. Each group must contain at least one
      treated observation. Groups with no controls are still accepted for
      GATET, but a warning is emitted because within-group overlap is
      degenerate and identification relies on :math:`\hat g_0(X)` and
      :math:`\hat m(X)` learned outside the subgroup.

      GATET requires the fitted ``CausalData`` to define ``user_id``.
      Group rows are aligned to the IRM fit-time sample using those fit-time
      observation ids. In practice, ``groups.index`` must either exactly match
      those ids or be a permutation of them. As a convenience, when the model
      was fitted with ``user_id``, GATET also accepts groups indexed by the
      original fit-time row index if the current row-to-``user_id`` mapping is
      still unchanged.

      If None, falls back to ``irm_model.data.gate_groups`` when present.

  alpha : float, default 0.05 Significance level for two-sided confidence intervals. cov_type : {“HC0”, “HC1”, “HC2”, “HC3”}, default “HC3” Heteroskedasticity-robust covariance estimator used for subgroup inference. The implementation uses a closed-form subgroupwise sandwich covariance equivalent to a no-intercept regression on subgroup dummies scaled by within-group treated shares. cov_kwds : Optional[Dict[str, Any]], default None Additional covariance options requested by the caller. These are currently ignored because GATET uses the closed-form HCx covariance formulas implemented in this module.

  Returns

  GateEstimate Result contract containing subgroup-treated effects, standard errors, confidence intervals, covariance matrix, and group-level diagnostics. The returned object also supports `contrast(...)` and `pairwise_summary(...)` for formal post-estimation group comparisons. Per-group `summary()["is_significant"]` tests each subgroup effect against zero; use formal contrasts to test whether subgroup effects differ from one another.

  Examples

  > > > import pandas as pd from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor from causalis.dgp import obs_linear_26_dataset from causalis.scenarios.unconfoundedness.model import IRM data = obs_linear_26_dataset( … n=1000, … seed=3141, … include_oracle=False, … return_causal_data=True, … ) groups = pd.Series( … (data.df[“x1”] >= 0).map({True: “high_x1”, False: “low_x1”}), … index=data.df[“user_id”], … name=”segment”, … ) irm = IRM( … data=data, … ml_g=RandomForestRegressor(n_estimators=200, max_depth=6, random_state=3141), … ml_m=RandomForestClassifier(n_estimators=200, max_depth=6, random_state=3141), … n_folds=3, … random_state=3141, … ) irm.fit() gatet = estimate_gatet_from_irm(irm, groups=groups) gatet.summary() # doctest: +SKIP

  Notes

  This implementation targets subgroup ATT under the same unconfoundedness and overlap assumptions used by IRM, with the additional requirement that subgroup membership is pre-treatment.

  Let :math:`G` denote a finite partition of the sample space. For subgroup :math:`g`, the target estimand is

  .. math::

      \theta_g^{\mathrm{GATET}}
      = \mathbb{E}[Y(1)-Y(0)\mid G=g, D=1].

  Equivalently, for a strict subgroup partition,

  .. math::

      \theta_g^{\mathrm{GATET}}
      = \mathbb{E}\left[
          \frac{D \mathbf{1}\{G=g\}}{\mathbb{P}(D=1, G=g)}
          (Y(1)-Y(0))
        \right].

  Using the fitted nuisances :math:`\hat g_0(X)` and :math:`\hat m(X)`, define the ATT-style orthogonal signal

  .. math::

      z =
      D \cdot (Y - \hat g_0(X))
      - \hat m(X)(1-D)\frac{Y-\hat g_0(X)}{1-\hat m(X)}.

  Then the subgroup ATT can be written as

  .. math::

      \hat\theta_g^{\mathrm{GATET}}
      = \frac{1}{n_{\mathrm{treated}, g}}
        \sum_{i:G_i=g} z_i,

  where :math:`n_{\mathrm{treated}, g}` is the number of treated observations in subgroup :math:`g`. In particular, the implementation changes the score itself to the subgroup-ATT signal above; it is not computed by first building ordinary GATE scores and then averaging them only over treated units in subgroup :math:`g`.

  Inference is based on the subgroup moment residual

  .. math::

      u_{ig} = \mathbf{1}\{G_i=g\}\,(z_i - D_i \hat\theta_g),

  with diagonal HC0/HC1/HC2/HC3 covariance formulas computed in closed form.

  As a sanity check, when the groups form an exhaustive partition,

  .. math::

      \mathrm{ATTE}
      = \sum_g \mathbb{P}(G=g \mid D=1)\,
        \theta_g^{\mathrm{GATET}}.

  `normalize_ipw=True` on the IRM is intentionally ignored for GATET so the estimator uses the canonical unnormalized subgroup-ATT signal above.