Skip to content
Merged
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
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ jobs:
cache-dependency-glob: pyproject.toml
- run: uvx ruff check .

docs:
# Catches broken links and API-docs errors before they reach the published site.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.2.0
with:
python-version: "3.12"
activate-environment: true
cache-dependency-glob: pyproject.toml
- run: uv pip install -e ".[docs]"
- run: mkdocs build --strict

test:
runs-on: ubuntu-latest
strategy:
Expand Down
50 changes: 50 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Docs

# Builds the documentation site and publishes it to GitHub Pages whenever main changes.

on:
push:
branches: [main]
paths:
- "docs/**"
- "mkdocs.yml"
- "src/**"
- "CHANGELOG.md"
- "pyproject.toml"
- ".github/workflows/docs.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.2.0
with:
python-version: "3.12"
activate-environment: true
cache-dependency-glob: pyproject.toml
- run: uv pip install -e ".[docs]"
- run: mkdocs build --strict
- uses: actions/upload-pages-artifact@v5
with:
path: site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ dist/
.ipynb_checkpoints/
benchmarks/results/*.log
private/
site/
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ All notable changes to graphdml are listed here. Versions follow

## [Unreleased]

## [0.2.0] - 2026-10-09

### Added
- Documentation site (https://samrdreyfuss.github.io/graphdml/): guides, methodology,
validation, example datasets and an API reference generated from the docstrings.
- `graphdml.selftest()` and `make_signal_check()`: a built-in check, on synthetic data with
planted effects and a null twin that has none, that the estimator finds real signals and
does not invent false ones. Reports bias, coverage, power and false-alarm rates against
Expand Down Expand Up @@ -53,5 +57,6 @@ First release.
- Neo4j integration: load graphs, write results back, GDS FastRP features, demo.
- Validation suite and benchmarks.

[Unreleased]: https://github.com/Samrdreyfuss/graphdml/compare/v0.1.0...HEAD
[Unreleased]: https://github.com/Samrdreyfuss/graphdml/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/Samrdreyfuss/graphdml/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/Samrdreyfuss/graphdml/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors:
- family-names: Dreyfuss
given-names: Sam
license: MIT
version: 0.1.0
version: 0.2.0
repository-code: "https://github.com/Samrdreyfuss/graphdml"
references:
- type: conference-paper
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

**Causal effects on networks, with honest confidence intervals.**

**[Documentation](https://samrdreyfuss.github.io/graphdml/)** · [PyPI](https://pypi.org/project/graphdml/) · [Changelog](https://github.com/Samrdreyfuss/graphdml/blob/main/CHANGELOG.md)

When a treatment reaches one person, it often reaches their friends too: a vaccine protects
the neighbors, a coupon gets shared, a training spreads by word of mouth. graphdml estimates:

Expand Down
2 changes: 1 addition & 1 deletion benchmarks/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def lastfm_section() -> str:
"# Validation results\n\n"
f"Generated by `benchmarks/report.py` on {dt.date.today()} for graphdml "
f"{graphdml.__version__}. The protocol and pass criteria are in "
"[the validation protocol](../docs/validation.md); raw per-seed results are in "
"[the validation protocol](validation.md); raw per-seed results are in "
"`benchmarks/results/*.csv`.\n"
)
DOC.write_text("\n\n".join([header, coverage_section(), design_section(),
Expand Down
65 changes: 65 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# API reference

## Estimator

::: graphdml.GraphDML

## Data

::: graphdml.GraphData

## Exposure maps

::: graphdml.SumExposure

::: graphdml.MeanExposure

::: graphdml.WeightedExposure

::: graphdml.TwoHopExposure

::: graphdml.MatrixExposure

## Features

::: graphdml.NeighborhoodFeatures

::: graphdml.OwnFeatures

::: graphdml.PrecomputedFeatures

## Diagnostics and checks

::: graphdml.placebo_test

::: graphdml.negative_control_test

::: graphdml.two_hop_test

::: graphdml.selftest

## Datasets

::: graphdml.datasets.GraphDataset

::: graphdml.datasets.make_flu_town

::: graphdml.datasets.make_referral_app

::: graphdml.datasets.make_classroom_tutoring

::: graphdml.datasets.make_homophily_trap

::: graphdml.datasets.make_signal_check

::: graphdml.datasets.make_lastfm_promo

::: graphdml.datasets.load_insurance_experiment

## Neo4j

::: graphdml.io.neo4j.Neo4jGraphSource

::: graphdml.io.neo4j.Neo4jResultWriter

::: graphdml.io.neo4j.GDSEmbeddings
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--8<-- "CHANGELOG.md"
36 changes: 36 additions & 0 deletions docs/datasets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Example datasets
Every dataset is a `GraphDataset` with the data, the true effects (where known) and a story
card: `print(ds.DESCR)`. Five generate their data offline; `make_lastfm_promo` downloads a
public network on first use; `load_insurance_experiment` needs a free download.

---

--8<-- "src/graphdml/datasets/descr/flu_town.md"

---

--8<-- "src/graphdml/datasets/descr/referral_app.md"

---

--8<-- "src/graphdml/datasets/descr/classroom_tutoring.md"

---

--8<-- "src/graphdml/datasets/descr/homophily_trap.md"

---

--8<-- "src/graphdml/datasets/descr/signal_check.md"

---

--8<-- "src/graphdml/datasets/descr/lastfm_promo.md"

---

--8<-- "src/graphdml/datasets/descr/insurance_experiment.md"

---

--8<-- "src/graphdml/datasets/descr/toy_graph.md"
64 changes: 64 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# graphdml

**Causal effects on networks, with honest confidence intervals.**

When a treatment reaches one person, it often reaches their friends too: a vaccine protects
the neighbors, a coupon gets shared, a training spreads by word of mouth. graphdml estimates

* the **ATE**: the average effect of treating everyone vs no one, including the ripple
* the **direct effect** (ADE): what the treatment does for the person who receives it
* the **peer effect** (APE): what it does for the people connected to them

using double machine learning with any scikit-learn model, adjusting for confounding that
runs through the network, with confidence intervals that account for neighbors being
dependent. Graphs can come from pandas, networkx or Neo4j.

## Install

```bash
pip install graphdml # core
pip install "graphdml[neo4j]" # with Neo4j support
```

Requires Python 3.10 or newer.

## Quickstart

```python
from graphdml import GraphDML
from graphdml.datasets import make_flu_town

ds = make_flu_town() # 5,000 residents; true effects: -2.0 and -0.5 sick days
model = GraphDML(exposure="sum", random_state=0).fit(ds.data)
print(model.summary())
```

## Where to go next

| | |
|---|---|
| [Example datasets](datasets.md) | Eight datasets with known truth, and the story behind each |
| [Methodology](methodology.md) | Exactly what `fit` computes, and why |
| [Design choices](design.md) | Each default, with the evidence behind it |
| [Validation](validation.md) | The protocol, and the [results](validation-results.md) |
| [Neo4j integration](neo4j.md) | Load graphs, write results back, GDS embeddings |
| [API reference](api.md) | Every public class and function |

## Does it find real signals?

```python
import graphdml
print(graphdml.selftest())
```

Simulates a world with planted effects and a null twin with none, and checks that graphdml
finds the first and stays quiet on the second, while a method that ignores the network
does not. See the [README](https://github.com/Samrdreyfuss/graphdml#does-it-find-real-signals-run-the-self-test)
for sample output.

## Citing

The estimator builds on Khatami, Parikh, Chen, Roy and Salimi,
[*Graph Machine Learning based Doubly Robust Estimator for Network Causal Effects*](https://proceedings.mlr.press/v258/khatami25a.html)
(AISTATS 2025). Please cite their paper along with graphdml; see
[CITATION.cff](https://github.com/Samrdreyfuss/graphdml/blob/main/CITATION.cff).
9 changes: 9 additions & 0 deletions docs/javascripts/mathjax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
window.MathJax = {
tex: {
inlineMath: [["\\(", "\\)"]],
displayMath: [["\\[", "\\]"]],
processEscapes: true,
processEnvironments: true,
},
options: { ignoreHtmlClass: ".*|", processHtmlClass: "arithmatex" },
};
2 changes: 1 addition & 1 deletion docs/validation-results.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Validation results

Generated by `benchmarks/report.py` on 2026-10-06 for graphdml 0.1.0. The protocol and pass criteria are in [the validation protocol](../docs/validation.md); raw per-seed results are in `benchmarks/results/*.csv`.
Generated by `benchmarks/report.py` on 2026-10-09 for graphdml 0.1.0. The protocol and pass criteria are in [the validation protocol](validation.md); raw per-seed results are in `benchmarks/results/*.csv`.


## Coverage
Expand Down
63 changes: 63 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
site_name: graphdml
site_description: Causal effects on networks with double machine learning
site_url: https://samrdreyfuss.github.io/graphdml/
repo_url: https://github.com/Samrdreyfuss/graphdml
repo_name: Samrdreyfuss/graphdml
edit_uri: edit/main/docs/

theme:
name: material
features:
- navigation.sections
- navigation.top
- content.code.copy
- search.suggest
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
toggle: {icon: material/weather-night, name: Switch to dark mode}
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle: {icon: material/weather-sunny, name: Switch to light mode}

nav:
- Home: index.md
- Guides:
- Example datasets: datasets.md
- Neo4j integration: neo4j.md
- Method:
- Methodology: methodology.md
- Design choices: design.md
- Validation:
- Protocol: validation.md
- Results: validation-results.md
- API reference: api.md
- Changelog: changelog.md

markdown_extensions:
- admonition
- tables
- toc:
permalink: true
- pymdownx.arithmatex:
generic: true
- pymdownx.highlight
- pymdownx.superfences
- pymdownx.snippets:
check_paths: true

extra_javascript:
- javascripts/mathjax.js
- https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js

plugins:
- search
- mkdocstrings:
handlers:
python:
options:
docstring_style: numpy
show_source: false
show_root_heading: true
heading_level: 3
members_order: source
Loading
Loading