Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 0 additions & 54 deletions .github/workflows/interrogate-badge.yml

This file was deleted.

24 changes: 10 additions & 14 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,31 +42,27 @@ repos:
# Run the formatter
- id: ruff-format
types_or: [ python, pyi, jupyter ]
- repo: https://github.com/econchick/interrogate
rev: 1.7.0
hooks:
- id: interrogate
# needed to make excludes in pyproject.toml work
# see here https://github.com/econchick/interrogate/issues/60#issuecomment-735436566
pass_filenames: false
- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
hooks:
- id: codespell
additional_dependencies:
# Support pyproject.toml configuration
- tomli
# Validate that every public ``plot()`` override on a BaseExperiment subclass
# keeps its numpydoc Parameters block in sync with the function signature
# (issue #886). Configuration lives under [tool.numpydoc_validation] in
# pyproject.toml and is intentionally narrow: only ``.plot`` methods are
# checked, only PR01/PR02 are enforced, and the base ``BaseExperiment.plot``
# is excluded because it uses ``*args, **kwargs`` for dispatch.
# Validate numpydoc-style docstrings across the package. The check set and
# exclude regex are configured under [tool.numpydoc_validation] in
# pyproject.toml. Originally introduced under #886 as a narrow check on
# public ``.plot`` overrides, the scope was widened under #898 to cover the
# whole ``causalpy`` package. The ``files`` pattern below excludes the test
# suite and the simulated-data helpers in ``causalpy/data/`` (those use the
# Sphinx ``:param:`` style for didactic clarity and are not part of the
# public API surface). Private members are excluded via the config-level
# regex in ``pyproject.toml``.
- repo: https://github.com/numpy/numpydoc
rev: v1.10.0
hooks:
- id: numpydoc-validation
files: ^causalpy/experiments/.*\.py$
files: ^causalpy/(?!tests/|data/).*\.py$
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.20.1
hooks:
Expand Down
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ lint: ## Run ruff linter and formatter
check_lint: ## Check code formatting and linting without making changes
ruff check .
ruff format --diff --check .
interrogate .

doctest: ## Run doctests for the causalpy module
python -m pytest --doctest-modules --ignore=causalpy/tests/ causalpy/ --config-file=causalpy/tests/conftest.py
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
![Build Status](https://github.com/pymc-labs/CausalPy/actions/workflows/ci.yml/badge.svg?branch=main)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
![Interrogate](https://raw.githubusercontent.com/pymc-labs/CausalPy/interrogate-badges/interrogate_badge.svg)
[![codecov](https://codecov.io/gh/pymc-labs/CausalPy/branch/main/graph/badge.svg?token=FDKNAY5CZ9)](https://codecov.io/gh/pymc-labs/CausalPy)

![GitHub Repo stars](https://img.shields.io/github/stars/pymc-labs/causalpy?style=flat)
Expand Down
1 change: 1 addition & 0 deletions causalpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""CausalPy: causal inference for quasi-experiments in Python."""

import causalpy.checks as checks # noqa: E402
import causalpy.pymc_models as pymc_models
Expand Down
18 changes: 16 additions & 2 deletions causalpy/checks/bandwidth.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ def __init__(self, bandwidths: list[float] | None = None) -> None:
self.bandwidths = bandwidths or [0.25, 0.5, 1.0, 2.0, np.inf]

def validate(self, experiment: BaseExperiment) -> None:
"""Verify the experiment is an RD or RKink instance."""
"""Verify the experiment is an RD or RKink instance.

Parameters
----------
experiment : BaseExperiment
Candidate experiment to validate.
"""
if not isinstance(experiment, (RegressionDiscontinuity, RegressionKink)):
raise TypeError(
"BandwidthSensitivity requires a RegressionDiscontinuity "
Expand All @@ -72,7 +78,15 @@ def run(
experiment: BaseExperiment,
context: PipelineContext,
) -> CheckResult:
"""Re-fit the experiment at multiple bandwidths and compare estimates."""
"""Re-fit the experiment at multiple bandwidths and compare estimates.

Parameters
----------
experiment : BaseExperiment
The fitted RD or RKink experiment.
context : PipelineContext
Pipeline context providing ``experiment_config`` for re-fits.
"""
if context.experiment_config is None:
raise RuntimeError(
"No experiment_config in context. Use EstimateEffect "
Expand Down
19 changes: 19 additions & 0 deletions causalpy/checks/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ def clone_model(model: Any) -> Any:
PyMC models cannot survive ``copy.deepcopy`` (the class identity is
lost), so we use their ``_clone()`` method instead. For all other
model types we fall back to ``copy.deepcopy``.

Parameters
----------
model : Any
The model instance to clone. PyMC models must expose a ``_clone()``
method; everything else falls back to :func:`copy.deepcopy`.

Returns
-------
Any
A fresh, unfitted copy of ``model``.
"""
if hasattr(model, "_clone"):
return model._clone()
Expand Down Expand Up @@ -88,6 +99,12 @@ class Check(Protocol):
def validate(self, experiment: BaseExperiment) -> None:
"""Verify the check is applicable to the given experiment.

Parameters
----------
experiment : BaseExperiment
The experiment instance whose type is checked against
``applicable_methods``.

Raises
------
TypeError
Expand All @@ -112,5 +129,7 @@ def run(
Returns
-------
CheckResult
Outcome of the check, including pass/fail status and any
diagnostic payload produced by the implementation.
"""
...
18 changes: 16 additions & 2 deletions causalpy/checks/convex_hull.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ class ConvexHullCheck:
applicable_methods: set[type[BaseExperiment]] = {SyntheticControl}

def validate(self, experiment: BaseExperiment) -> None:
"""Verify the experiment is a SyntheticControl instance."""
"""Verify the experiment is a SyntheticControl instance.

Parameters
----------
experiment : BaseExperiment
Candidate experiment to validate.
"""
if not isinstance(experiment, SyntheticControl):
raise TypeError("ConvexHullCheck requires a SyntheticControl experiment.")

Expand All @@ -48,7 +54,15 @@ def run(
experiment: BaseExperiment,
context: PipelineContext,
) -> CheckResult:
"""Run the convex hull violation check on pre-treatment data."""
"""Run the convex hull violation check on pre-treatment data.

Parameters
----------
experiment : BaseExperiment
The fitted SyntheticControl experiment.
context : PipelineContext
Pipeline context (unused; required by the check protocol).
"""
sc = experiment
datapre_control = sc.datapre_control # type: ignore[attr-defined]
datapre_treated = sc.datapre_treated # type: ignore[attr-defined]
Expand Down
18 changes: 16 additions & 2 deletions causalpy/checks/leave_one_out.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ class LeaveOneOut:
applicable_methods: set[type[BaseExperiment]] = {SyntheticControl}

def validate(self, experiment: BaseExperiment) -> None:
"""Verify the experiment is a SyntheticControl instance."""
"""Verify the experiment is a SyntheticControl instance.

Parameters
----------
experiment : BaseExperiment
Candidate experiment to validate.
"""
if not isinstance(experiment, SyntheticControl):
raise TypeError("LeaveOneOut requires a SyntheticControl experiment.")

Expand All @@ -57,7 +63,15 @@ def run(
experiment: BaseExperiment,
context: PipelineContext,
) -> CheckResult:
"""Drop each control unit in turn and compare effect estimates."""
"""Drop each control unit in turn and compare effect estimates.

Parameters
----------
experiment : BaseExperiment
The fitted SyntheticControl experiment.
context : PipelineContext
Pipeline context providing ``experiment_config`` for re-fits.
"""
if context.experiment_config is None:
raise RuntimeError(
"No experiment_config in context. Use EstimateEffect "
Expand Down
18 changes: 16 additions & 2 deletions causalpy/checks/mccrary.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ def __init__(self, n_bins: int = 20, alpha: float = 0.05) -> None:
self.alpha = alpha

def validate(self, experiment: BaseExperiment) -> None:
"""Verify the experiment is a RegressionDiscontinuity instance."""
"""Verify the experiment is a RegressionDiscontinuity instance.

Parameters
----------
experiment : BaseExperiment
Candidate experiment to validate.
"""
if not isinstance(experiment, RegressionDiscontinuity):
raise TypeError(
"McCraryDensityTest requires a RegressionDiscontinuity experiment."
Expand All @@ -73,7 +79,15 @@ def run(
experiment: BaseExperiment,
context: PipelineContext,
) -> CheckResult:
"""Test for manipulation of the running variable at the threshold."""
"""Test for manipulation of the running variable at the threshold.

Parameters
----------
experiment : BaseExperiment
The fitted RegressionDiscontinuity experiment.
context : PipelineContext
Pipeline context (unused; required by the check protocol).
"""
rd = experiment
threshold = rd.treatment_threshold # type: ignore[attr-defined]
running_var = rd.running_variable_name # type: ignore[attr-defined]
Expand Down
19 changes: 17 additions & 2 deletions causalpy/checks/persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ def __init__(
self.direction = direction

def validate(self, experiment: BaseExperiment) -> None:
"""Verify the experiment is a three-period ITS with treatment_end_time."""
"""Verify the experiment is a three-period ITS with treatment_end_time.

Parameters
----------
experiment : BaseExperiment
Candidate experiment to validate.
"""
if not isinstance(experiment, InterruptedTimeSeries):
raise TypeError(
"PersistenceCheck requires an InterruptedTimeSeries experiment."
Expand All @@ -77,7 +83,16 @@ def run(
experiment: BaseExperiment,
context: PipelineContext,
) -> CheckResult:
"""Run persistence analysis and report whether the effect decays."""
"""Run persistence analysis and report whether the effect decays.

Parameters
----------
experiment : BaseExperiment
The fitted three-period ITS experiment.
context : PipelineContext
Pipeline context (unused by this check; required by the
:class:`~causalpy.checks.base.Check` protocol).
"""
its: Any = experiment
persistence = its.analyze_persistence(
hdi_prob=self.hdi_prob,
Expand Down
18 changes: 16 additions & 2 deletions causalpy/checks/placebo_in_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ class PlaceboInSpace:
applicable_methods: set[type[BaseExperiment]] = {SyntheticControl}

def validate(self, experiment: BaseExperiment) -> None:
"""Verify the experiment is a SyntheticControl instance."""
"""Verify the experiment is a SyntheticControl instance.

Parameters
----------
experiment : BaseExperiment
Candidate experiment to validate.
"""
if not isinstance(experiment, SyntheticControl):
raise TypeError("PlaceboInSpace requires a SyntheticControl experiment.")

Expand All @@ -59,7 +65,15 @@ def run(
experiment: BaseExperiment,
context: PipelineContext,
) -> CheckResult:
"""Treat each control unit as treated and compare effect magnitudes."""
"""Treat each control unit as treated and compare effect magnitudes.

Parameters
----------
experiment : BaseExperiment
The fitted SyntheticControl experiment.
context : PipelineContext
Pipeline context providing ``experiment_config`` for re-fits.
"""
if context.experiment_config is None:
raise RuntimeError(
"No experiment_config in context. Use EstimateEffect "
Expand Down
15 changes: 15 additions & 0 deletions causalpy/checks/placebo_in_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ def __init__(
def validate(self, experiment: BaseExperiment) -> None:
"""Check the experiment is compatible with PlaceboInTime.

Parameters
----------
experiment : BaseExperiment
Candidate experiment to validate.

Raises
------
TypeError
Expand Down Expand Up @@ -553,6 +558,16 @@ def run(
Can be used standalone (``context=None``) when
``experiment_factory`` was provided, or within a pipeline.

Parameters
----------
experiment : BaseExperiment
The fitted experiment whose treatment time will be shifted to
generate placebo folds.
context : PipelineContext or None, default None
Pipeline context providing ``experiment_config`` for re-fits.
If ``None``, an explicit ``experiment_factory`` must have been
supplied at construction time.

Returns
-------
CheckResult
Expand Down
Loading
Loading