Skip to content

DIDMultiplegtStat — class API

The scikit-learn style class is the recommended entry point.

from did_multiplegt_stat import DIDMultiplegtStat

Constructor

DIDMultiplegtStat

DIDMultiplegtStat(estimator: str | Sequence[str] | None = None, order: int | list[int] = 1, noextrapolation: bool = False, placebo: int = 0, switchers: str | None = None, disaggregate: bool = False, as_vs_was: bool = False, exact_match: bool = False, by: Sequence[str] | None = None, by_fd: int | None = None, by_baseline: int | None = None, other_treatments: Sequence[str] | None = None, cluster: str | None = None, weight: str | None = None, controls: Sequence[str] | None = None, cross_fitting: int = 0, trimming: float = 0, on_placebo_sample: bool = False, bootstrap: int = 0, twfe: bool | dict[str, Any] = False, seed: int = 0, cross_validation: dict[str, Any] | None = None, iv_method: str = 'manual', asinstata: bool = False, model_deltay: Any | None = None, model_stayer: Any | None = None, **legacy_options: Any)

Difference-in-Differences estimator following de Chaisemartin & D'Haultfeuille (2024).

Implements AS (Average Slope), WAS (Weighted Average Slope), and IV-WAS estimators. Doubly robust estimation is used by default; exact matching activates regression adjustment internally.

Parameters:

Name Type Description Default
estimator str or list of str

Estimator type(s): 'as', 'was', or 'iv-was'. Default: ['as', 'was'], or ['iv-was'] if Z is provided in fit().

None
order int or list of int

Polynomial order. Can be single int or list of 4 (reg, logit_bis, logit_Plus, logit_Minus) or 8 (4 for first-stage + 4 for reduced-form for IV-WAS).

1
noextrapolation bool

Restrict to common support without extrapolation.

False
placebo int

Number of placebo tests.

0
switchers str

Restrict to 'up' (increasing treatment) or 'down' (decreasing treatment).

None
disaggregate bool

Report period-specific estimates.

False
as_vs_was bool

Test equality between AS and WAS.

False
exact_match bool

Use exact matching on baseline treatment.

False
by list of str

Stratification variables.

None
by_fd int

Number of bins for first-difference quantiles.

None
by_baseline int

Number of bins for baseline treatment quantiles.

None
other_treatments list of str

Additional treatment variables to control for.

None
cluster str

Cluster variable for standard errors.

None
weight str

Observation weights variable.

None
controls list of str

Control variables.

None
cross_fitting int

Number of cross-fitting folds.

0
trimming float

Propensity score trimming threshold.

0
on_placebo_sample bool

Estimate only on stayer sample.

False
bootstrap int

Number of bootstrap replications.

0
twfe bool or dict

Compare with TWFE regression.

False
seed int

Random seed for reproducibility.

0
cross_validation dict

Cross-validation options for polynomial order selection.

None

Attributes:

Name Type Description
results_ dict

Full results dictionary after fitting.

table_ DataFrame

Main results table with Estimate, SE, LB CI, UB CI, Switchers, Stayers.

placebo_tables_ dict

Placebo test results by index.

n_obs_ int

Number of observations.

n_clusters_ int or None

Number of clusters (if clustered).

by_levels_ list or None

Levels of by-group analysis.

first_stage_ DIDMultiplegtStat or None

First-stage results for IV-WAS.

is_fitted_ bool

Whether the model has been fitted.

Examples:

>>> import pandas as pd
>>> from did_multiplegt_stat import DIDMultiplegtStat
>>>
>>> # Basic usage
>>> model = DIDMultiplegtStat(estimator=['as', 'was'])
>>> model.fit(df, Y='outcome', ID='unit_id', Time='time', D='treatment')
>>> model.summary()
>>>
>>> # With IV
>>> model_iv = DIDMultiplegtStat(estimator='iv-was')
>>> model_iv.fit(df, Y='outcome', ID='unit_id', Time='time', D='treatment', Z='instrument')
>>> model_iv.plot()

Initialize the estimator with configuration parameters.

Source code in src/did_multiplegt_stat/estimator.py
def __init__(
    self,
    estimator: str | Sequence[str] | None = None,
    order: int | list[int] = 1,
    noextrapolation: bool = False,
    placebo: int = 0,
    switchers: str | None = None,
    disaggregate: bool = False,
    as_vs_was: bool = False,
    exact_match: bool = False,
    by: Sequence[str] | None = None,
    by_fd: int | None = None,
    by_baseline: int | None = None,
    other_treatments: Sequence[str] | None = None,
    cluster: str | None = None,
    weight: str | None = None,
    controls: Sequence[str] | None = None,
    cross_fitting: int = 0,
    trimming: float = 0,
    on_placebo_sample: bool = False,
    bootstrap: int = 0,
    twfe: bool | dict[str, Any] = False,
    seed: int = 0,
    cross_validation: dict[str, Any] | None = None,
    iv_method: str = "manual",
    asinstata: bool = False,
    model_deltay: Any | None = None,
    model_stayer: Any | None = None,
    **legacy_options: Any,
) -> None:
    """Initialize the estimator with configuration parameters."""
    as_vs_was = resolve_legacy_options(
        as_vs_was=as_vs_was,
        legacy_options=legacy_options,
    )
    if estimator is None:
        self.estimator = None
    elif isinstance(estimator, str):
        self.estimator = to_public_estimator(estimator)
    else:
        public_estimators, _ = normalize_estimators(estimator)
        self.estimator = public_estimators
    self.order = order
    self.noextrapolation = noextrapolation
    self.placebo = placebo
    self.switchers = switchers
    self.disaggregate = disaggregate
    self.as_vs_was = as_vs_was
    self.exact_match = exact_match
    self.by = by
    self.by_fd = by_fd
    self.by_baseline = by_baseline
    self.other_treatments = other_treatments
    self.cluster = cluster
    self.weight = weight
    self.controls = controls
    self.cross_fitting = cross_fitting
    self.trimming = trimming
    self.on_placebo_sample = on_placebo_sample
    self.bootstrap = bootstrap
    self.twfe = twfe
    self.seed = seed
    self.cross_validation = cross_validation
    self.iv_method = iv_method
    self.asinstata = asinstata
    self.model_deltay = model_deltay
    self.model_stayer = model_stayer

    # Fitted attributes (set after fit())
    self.results_: dict[str, Any] | None = None
    self.table_: pd.DataFrame | None = None
    self.placebo_tables_: dict[int, pd.DataFrame] | None = None
    self.n_obs_: int | None = None
    self.n_clusters_: int | None = None
    self.by_levels_: list | None = None
    self.first_stage_: DIDMultiplegtStat | None = None
    self.is_fitted_: bool = False

    # Data column names (set after fit())
    self._Y: str | None = None
    self._ID: str | None = None
    self._Time: str | None = None
    self._D: str | None = None
    self._Z: str | None = None

Fitting

fit

fit(df: DataFrame, Y: str, ID: str, Time: str, D: str, Z: str | None = None) -> DIDMultiplegtStat

Fit the DiD estimator.

Parameters:

Name Type Description Default
df DataFrame

Panel data in long format.

required
Y str

Column name for outcome variable.

required
ID str

Column name for unit identifier.

required
Time str

Column name for time variable.

required
D str

Column name for treatment variable.

required
Z str

Column name for instrument variable (required for IV-WAS).

None

Returns:

Name Type Description
self DIDMultiplegtStat

Fitted estimator (scikit-learn convention).

Raises:

Type Description
ValueError

If invalid parameter combinations are specified.

Source code in src/did_multiplegt_stat/estimator.py
def fit(
    self,
    df: pd.DataFrame,
    Y: str,
    ID: str,
    Time: str,
    D: str,
    Z: str | None = None,
) -> DIDMultiplegtStat:
    """
    Fit the DiD estimator.

    Parameters
    ----------
    df : pd.DataFrame
        Panel data in long format.
    Y : str
        Column name for outcome variable.
    ID : str
        Column name for unit identifier.
    Time : str
        Column name for time variable.
    D : str
        Column name for treatment variable.
    Z : str, optional
        Column name for instrument variable (required for IV-WAS).

    Returns
    -------
    self : DIDMultiplegtStat
        Fitted estimator (scikit-learn convention).

    Raises
    ------
    ValueError
        If invalid parameter combinations are specified.
    """
    # Store column names
    self._Y = Y
    self._ID = ID
    self._Time = Time
    self._D = D
    self._Z = Z

    # Call the internal function
    self.results_ = _did_multiplegt_stat(
        df=df,
        Y=Y,
        ID=ID,
        Time=Time,
        D=D,
        Z=Z,
        estimator=self.estimator,
        order=self.order,
        noextrapolation=self.noextrapolation,
        placebo=self.placebo,
        switchers=self.switchers,
        disaggregate=self.disaggregate,
        as_vs_was=self.as_vs_was,
        exact_match=self.exact_match,
        by=self.by,
        by_fd=self.by_fd,
        by_baseline=self.by_baseline,
        other_treatments=self.other_treatments,
        cluster=self.cluster,
        weight=self.weight,
        controls=self.controls,
        cross_fitting=self.cross_fitting,
        trimming=self.trimming,
        on_placebo_sample=self.on_placebo_sample,
        bootstrap=self.bootstrap,
        twfe=self.twfe,
        seed=self.seed,
        cross_validation=self.cross_validation,
        iv_method=self.iv_method,
        asinstata=self.asinstata,
        model_deltay=self.model_deltay,
        model_stayer=self.model_stayer,
    )

    # Extract key results
    self._extract_results()
    self.is_fitted_ = True

    return self

Inspection

summary

summary(show_header: bool = True, show_placebo: bool = True, show_warnings: bool = True) -> None

Print formatted summary mimicking Stata ADO output.

Parameters:

Name Type Description Default
show_header bool

Display summary statistics header.

True
show_placebo bool

Display placebo results if available.

True
show_warnings bool

Display warnings about quasi-stayers, common support violations.

True
Source code in src/did_multiplegt_stat/estimator.py
def summary(
    self,
    show_header: bool = True,
    show_placebo: bool = True,
    show_warnings: bool = True,
) -> None:
    """
    Print formatted summary mimicking Stata ADO output.

    Parameters
    ----------
    show_header : bool, default=True
        Display summary statistics header.
    show_placebo : bool, default=True
        Display placebo results if available.
    show_warnings : bool, default=True
        Display warnings about quasi-stayers, common support violations.
    """
    self._check_is_fitted()

    args = self.results_.get("args", {})
    _, estim_list = normalize_estimators(
        args.get("estimator", ["as", "was"]),
        has_instrument=args.get("Z") is not None,
        warn_legacy=False,
    )

    by_var = args.get("by")
    by_fd = args.get("by_fd")
    by_baseline = args.get("by_baseline")

    if by_var is None and by_fd is None and by_baseline is None:
        by_levs = ["_no_by"]
        by_obj = ["results"]
    else:
        by_levs = list(self.results_.get("by_levels", []))
        by_obj = [f"results_by_{j + 1}" for j in range(len(by_levs))]

    estims_map = ESTIMATOR_POSITIONS

    for idx, key in enumerate(by_obj):
        print_obj = self.results_.get(key)
        if print_obj is None:
            continue

        by_level = by_levs[idx] if by_levs[idx] != "_no_by" else None

        if show_header:
            print_header(
                N=print_obj.get("N", 0),
                estimation_method=args.get(
                    "_estimation_method",
                    "ra" if args.get("exact_match") else "dr",
                ),
                estimator_list=estim_list,
                order=args.get("order"),
                exact_match=args.get("exact_match", False),
                noextrapolation=args.get("noextrapolation", False),
                controls=args.get("controls"),
                cross_fitting=args.get("cross_fitting", 0),
                trimming=args.get("trimming", 0),
                n_clusters=print_obj.get("n_clusters"),
                cluster=args.get("cluster"),
                by_level=by_level,
            )

        table = print_obj.get("table")
        pairs = int(print_obj.get("pairs", 1))

        for est in estim_list:
            print_estimator_section(
                estimator=est,
                table=table,
                estims_map=estims_map,
                pairs=pairs,
                disaggregate=args.get("disaggregate", False),
            )

            # Placebo results
            placebo_n = args.get("placebo", 0)
            if show_placebo and placebo_n > 0:
                placebo_tables = {}
                for pl_idx in range(1, placebo_n + 1):
                    table_p = print_obj.get(f"table_placebo_{pl_idx}", print_obj.get("table_placebo"))
                    if isinstance(table_p, pd.DataFrame):
                        placebo_tables[pl_idx] = table_p
                if placebo_tables:
                    print_placebo_section(
                        estimator=est,
                        placebo_tables=placebo_tables,
                        estims_map=estims_map,
                        placebo_n=placebo_n,
                    )

        # AS vs WAS test
        if args.get("as_vs_was"):
            diff_tab = print_obj.get("as_vs_was")
            if diff_tab is not None:
                print_as_vs_was_section(diff_tab)

    # First-stage results (IV-WAS)
    if self.first_stage_ is not None:
        print_first_stage_section(self.first_stage_.results_)
        self.first_stage_.summary(show_header=show_header, show_placebo=show_placebo)
        print(f"{'=' * 80}")
        print(f"{' ' * 30}Reduced form estimation (above)")
        print(f"{'=' * 80}")

    # TWFE comparison
    twfe_tab = self.results_.get("twfe_comparison")
    if twfe_tab is not None:
        print_twfe_comparison(twfe_tab)

to_dataframe

to_dataframe() -> pd.DataFrame

Return main results as a clean DataFrame.

Returns:

Name Type Description
df DataFrame

Results table with Estimate, SE, LB CI, UB CI, Switchers, Stayers.

Source code in src/did_multiplegt_stat/estimator.py
def to_dataframe(self) -> pd.DataFrame:
    """
    Return main results as a clean DataFrame.

    Returns
    -------
    df : pd.DataFrame
        Results table with Estimate, SE, LB CI, UB CI, Switchers, Stayers.
    """
    self._check_is_fitted()
    if self.table_ is None:
        return pd.DataFrame()
    return self.table_.copy()

get_coefficients

get_coefficients(estimator: str | None = None) -> pd.Series

Get coefficient estimates for specified estimator.

Parameters:

Name Type Description Default
estimator str

Which estimator ('as', 'was', or 'iv-was'). Default: first available.

None

Returns:

Name Type Description
coeffs Series

Coefficient estimates.

Source code in src/did_multiplegt_stat/estimator.py
def get_coefficients(
    self,
    estimator: str | None = None,
) -> pd.Series:
    """
    Get coefficient estimates for specified estimator.

    Parameters
    ----------
    estimator : str, optional
        Which estimator ('as', 'was', or 'iv-was'). Default: first available.

    Returns
    -------
    coeffs : pd.Series
        Coefficient estimates.
    """
    self._check_is_fitted()

    if self.table_ is None:
        return pd.Series()

    args = self.results_.get("args", {})
    _, estim_list = normalize_estimators(
        args.get("estimator", ["as", "was"]),
        has_instrument=args.get("Z") is not None,
        warn_legacy=False,
    )

    if estimator is None:
        estimator = estim_list[0]
    else:
        estimator = to_internal_estimator(estimator)

    estims_map = ESTIMATOR_POSITIONS
    pairs = int(self.results_.get("results", self.results_).get("pairs", 1))

    l_bound = estims_map.get(estimator, 0) * pairs
    u_bound = l_bound + pairs

    if l_bound >= len(self.table_):
        return pd.Series()

    return self.table_.iloc[l_bound:u_bound]["Estimate"]

get_confidence_intervals

get_confidence_intervals(estimator: str | None = None, level: float = 0.95) -> pd.DataFrame

Get confidence intervals at specified level.

Parameters:

Name Type Description Default
estimator str

Which estimator. Default: first available.

None
level float

Confidence level (e.g., 0.95 for 95% CI).

0.95

Returns:

Name Type Description
ci DataFrame

DataFrame with columns 'LB CI' and 'UB CI'.

Source code in src/did_multiplegt_stat/estimator.py
def get_confidence_intervals(
    self,
    estimator: str | None = None,
    level: float = 0.95,
) -> pd.DataFrame:
    """
    Get confidence intervals at specified level.

    Parameters
    ----------
    estimator : str, optional
        Which estimator. Default: first available.
    level : float, default=0.95
        Confidence level (e.g., 0.95 for 95% CI).

    Returns
    -------
    ci : pd.DataFrame
        DataFrame with columns 'LB CI' and 'UB CI'.
    """
    self._check_is_fitted()

    if self.table_ is None:
        return pd.DataFrame()

    args = self.results_.get("args", {})
    _, estim_list = normalize_estimators(
        args.get("estimator", ["as", "was"]),
        has_instrument=args.get("Z") is not None,
        warn_legacy=False,
    )

    if estimator is None:
        estimator = estim_list[0]
    else:
        estimator = to_internal_estimator(estimator)

    estims_map = ESTIMATOR_POSITIONS
    pairs = int(self.results_.get("results", self.results_).get("pairs", 1))

    l_bound = estims_map.get(estimator, 0) * pairs
    u_bound = l_bound + pairs

    if l_bound >= len(self.table_):
        return pd.DataFrame()

    # Note: Currently returns stored 95% CI; for different levels,
    # would need to recompute from SE
    return self.table_.iloc[l_bound:u_bound][["LB CI", "UB CI"]]

Plotting

plot

plot(estimator: str | None = None, show_ci: bool = True, ci_alpha: float = 0.2, figsize: tuple[float, float] = (10, 6), colors: dict[str, str] | None = None, title: str | None = None, xlabel: str = 'Relative Time', ylabel: str = 'Effect Estimate', show_zero_line: bool = True, separate_panels: bool = False, save_path: str | None = None, dpi: int = 150) -> plt.Figure | dict[str, plt.Figure]

Generate event-study style plots.

Parameters:

Name Type Description Default
estimator str

Which estimator to plot ('as', 'was', or 'iv-was'). If None, plots all.

None
show_ci bool

Display confidence interval bands.

True
ci_alpha float

Transparency for CI bands.

0.2
figsize tuple

Figure size in inches.

(10, 6)
colors dict

Custom colors for estimators {'as': 'blue', 'was': 'red', ...}.

None
title str

Custom title. Default: auto-generated based on estimator.

None
xlabel str

X-axis label.

"Relative Time"
ylabel str

Y-axis label.

"Effect Estimate"
show_zero_line bool

Show horizontal line at y=0.

True
separate_panels bool

Create separate subplots for AS/WAS/IV-WAS.

False
save_path str

Path to save figure.

None
dpi int

Resolution for saved figure.

150

Returns:

Name Type Description
fig Figure or dict

Single figure or dict of figures if separate_panels=True.

Source code in src/did_multiplegt_stat/estimator.py
def plot(
    self,
    estimator: str | None = None,
    show_ci: bool = True,
    ci_alpha: float = 0.2,
    figsize: tuple[float, float] = (10, 6),
    colors: dict[str, str] | None = None,
    title: str | None = None,
    xlabel: str = "Relative Time",
    ylabel: str = "Effect Estimate",
    show_zero_line: bool = True,
    separate_panels: bool = False,
    save_path: str | None = None,
    dpi: int = 150,
) -> plt.Figure | dict[str, plt.Figure]:
    """
    Generate event-study style plots.

    Parameters
    ----------
    estimator : str, optional
        Which estimator to plot ('as', 'was', or 'iv-was'). If None, plots all.
    show_ci : bool, default=True
        Display confidence interval bands.
    ci_alpha : float, default=0.2
        Transparency for CI bands.
    figsize : tuple, default=(10, 6)
        Figure size in inches.
    colors : dict, optional
        Custom colors for estimators {'as': 'blue', 'was': 'red', ...}.
    title : str, optional
        Custom title. Default: auto-generated based on estimator.
    xlabel : str, default="Relative Time"
        X-axis label.
    ylabel : str, default="Effect Estimate"
        Y-axis label.
    show_zero_line : bool, default=True
        Show horizontal line at y=0.
    separate_panels : bool, default=False
        Create separate subplots for AS/WAS/IV-WAS.
    save_path : str, optional
        Path to save figure.
    dpi : int, default=150
        Resolution for saved figure.

    Returns
    -------
    fig : matplotlib.figure.Figure or dict
        Single figure or dict of figures if separate_panels=True.
    """
    self._check_is_fitted()

    # Check for by-group analysis
    if self.by_levels_ is not None and len(self.by_levels_) > 1:
        return plot_by_groups(
            results=self.results_,
            estimator=estimator or "as",
            show_ci=show_ci,
            ci_alpha=ci_alpha,
            figsize=figsize,
            title=title,
            xlabel=xlabel,
            ylabel=ylabel,
            save_path=save_path,
            dpi=dpi,
        )

    return plot_event_study(
        results=self.results_,
        estimator=estimator,
        show_ci=show_ci,
        ci_alpha=ci_alpha,
        figsize=figsize,
        colors=colors,
        title=title,
        xlabel=xlabel,
        ylabel=ylabel,
        show_zero_line=show_zero_line,
        separate_panels=separate_panels,
        save_path=save_path,
        dpi=dpi,
    )

plot_comparison

plot_comparison(estimators: list[str] | None = None, figsize: tuple[float, float] = (10, 6), title: str = 'Estimator Comparison', save_path: str | None = None, dpi: int = 150) -> plt.Figure

Generate a side-by-side comparison plot of different estimators.

Parameters:

Name Type Description Default
estimators list

Which estimators to compare. Default: all available.

None
figsize tuple

Figure size.

(10, 6)
title str

Plot title.

"Estimator Comparison"
save_path str

Path to save figure.

None
dpi int

Resolution.

150

Returns:

Name Type Description
fig Figure

Matplotlib figure.

Source code in src/did_multiplegt_stat/estimator.py
def plot_comparison(
    self,
    estimators: list[str] | None = None,
    figsize: tuple[float, float] = (10, 6),
    title: str = "Estimator Comparison",
    save_path: str | None = None,
    dpi: int = 150,
) -> plt.Figure:
    """
    Generate a side-by-side comparison plot of different estimators.

    Parameters
    ----------
    estimators : list, optional
        Which estimators to compare. Default: all available.
    figsize : tuple, default=(10, 6)
        Figure size.
    title : str, default="Estimator Comparison"
        Plot title.
    save_path : str, optional
        Path to save figure.
    dpi : int, default=150
        Resolution.

    Returns
    -------
    fig : Figure
        Matplotlib figure.
    """
    self._check_is_fitted()
    return plot_comparison(
        results=self.results_,
        estimators=estimators,
        figsize=figsize,
        title=title,
        save_path=save_path,
        dpi=dpi,
    )

Parameter management (sklearn-style)

get_params

get_params(deep: bool = True) -> dict[str, Any]

Get parameters for this estimator.

Parameters:

Name Type Description Default
deep bool

If True, return parameters for sub-objects.

True

Returns:

Name Type Description
params dict

Parameter names mapped to their values.

Source code in src/did_multiplegt_stat/estimator.py
def get_params(self, deep: bool = True) -> dict[str, Any]:
    """
    Get parameters for this estimator.

    Parameters
    ----------
    deep : bool, default=True
        If True, return parameters for sub-objects.

    Returns
    -------
    params : dict
        Parameter names mapped to their values.
    """
    return {
        "estimator": self.estimator,
        "order": self.order,
        "noextrapolation": self.noextrapolation,
        "placebo": self.placebo,
        "switchers": self.switchers,
        "disaggregate": self.disaggregate,
        "as_vs_was": self.as_vs_was,
        "exact_match": self.exact_match,
        "by": self.by,
        "by_fd": self.by_fd,
        "by_baseline": self.by_baseline,
        "other_treatments": self.other_treatments,
        "cluster": self.cluster,
        "weight": self.weight,
        "controls": self.controls,
        "cross_fitting": self.cross_fitting,
        "trimming": self.trimming,
        "on_placebo_sample": self.on_placebo_sample,
        "bootstrap": self.bootstrap,
        "twfe": self.twfe,
        "seed": self.seed,
        "cross_validation": self.cross_validation,
        "iv_method": self.iv_method,
        "asinstata": self.asinstata,
        "model_deltay": self.model_deltay,
        "model_stayer": self.model_stayer,
    }

set_params

set_params(**params) -> DIDMultiplegtStat

Set the parameters of this estimator.

Parameters:

Name Type Description Default
**params dict

Estimator parameters.

{}

Returns:

Name Type Description
self DIDMultiplegtStat

Estimator instance.

Source code in src/did_multiplegt_stat/estimator.py
def set_params(self, **params) -> DIDMultiplegtStat:
    """
    Set the parameters of this estimator.

    Parameters
    ----------
    **params : dict
        Estimator parameters.

    Returns
    -------
    self : DIDMultiplegtStat
        Estimator instance.
    """
    legacy = {
        key: params.pop(key)
        for key in ("aoss_vs_waoss", "estimation_method")
        if key in params
    }
    if legacy:
        self.as_vs_was = resolve_legacy_options(
            as_vs_was=self.as_vs_was,
            legacy_options=legacy,
        )

    if "estimator" in params:
        estimator = params["estimator"]
        if estimator is None:
            params["estimator"] = None
        elif isinstance(estimator, str):
            params["estimator"] = to_public_estimator(estimator)
        else:
            params["estimator"], _ = normalize_estimators(estimator)

    for key, value in params.items():
        if hasattr(self, key):
            setattr(self, key, value)
        else:
            raise ValueError(f"Invalid parameter: {key}")
    return self

Attributes (set after fit)

Attribute Type Description
results_ dict Full results dictionary (source of truth).
table_ pd.DataFrame Main results table: Estimate, SE, LB CI, UB CI, Switchers, Stayers.
placebo_tables_ dict[int, pd.DataFrame] \| None Placebo tables keyed by placebo index.
n_obs_ int Number of observations.
n_clusters_ int \| None Number of clusters, when cluster= is set.
by_levels_ list \| None Levels of by-group analysis.
first_stage_ DIDMultiplegtStat \| None Nested fitted model for the first stage of IV-WAS.
is_fitted_ bool Whether .fit() has been called.