Skip to content

Restore the classical-fit notebook, ported to the 0.4 syntax - #45

Merged
MArpogaus merged 4 commits into
dev-marcelfrom
feat/classical-fit-notebook
Aug 31, 2026
Merged

Restore the classical-fit notebook, ported to the 0.4 syntax#45
MArpogaus merged 4 commits into
dev-marcelfrom
feat/classical-fit-notebook

Conversation

@oduerr

@oduerr oduerr commented Aug 28, 2026

Copy link
Copy Markdown
Member

Restores notebooks/classical_fit_tram_dag.py, deleted with notebooks/stale/ in 2a7327d, ported to the 0.4 term syntax and extended.

Merge note

This change needs the 0.4 API and the new data paths. It uses SI and flow.design_matrix, and it reads experiments/misc/data/. None of these exist on main. Therefore do not merge it into main on its own. It goes into main with dev-marcel.

What it contains

Four worked examples, each checked against classical software:

section model reference agreement
0 logistic regression on MASS::birthwt statsmodels.Logit, R glm ~1e-8
1 the same predictors on continuous bwt R tram::Colr ~0.002
1b the bimodal VACA triangle Colr, binned OrderedModel ~0.002
2 the all-ls stroke DAG statsmodels.OrderedModel ~1e-3

Section 0 makes the point that a two-level OrdinalNode with LS terms is logistic regression. Section 1 adds bwt to birthwt.csv and shows the proportional-odds property directly: the smoke log-odds ratio is +0.664 from the flow, +0.669 from Colr and +0.671 from the dichotomized glm.

Sections 1 and 2 also prototype standard errors from the observed Fisher information. The information matrix is singular in both, so the helper uses a pseudo-inverse and reports how far each contrast leaks into the flat subspace. This is deliberately a notebook prototype and not API: a flow.conf_int with one row per parameter would be mostly nonsense, because 13 of 24 rows on bwt have no curvature behind them.

Section 2 corrects a claim the old notebook made. The treatment effect T is not weakly identified: it sits 6.6 standard errors from zero. The weak coefficient is mRS_pre level 5, carried by 7 of 1275 rows, which is what experiments/misc/validate_ls.py already recorded in its docstring.

Two things reviewers should look at

--math in the docs build. pdoc renders formulae only with --math, which defaults to false, so every $...$ in the notebooks and guides has been publishing as literal LaTeX -- around 110 of them before this branch. The one-word fix in .github/workflows/docs.yaml changes how existing pages render, not only the new one. This is intended.

statsmodels joins the notebooks dependency group. The docs job runs --group notebooks --group docs, and the new notebook needs it.

R companion

notebooks/classical_fit_tram_dag.R fits every classical reference the notebook pins, so the numbers can be re-checked instead of trusted. It needs tram, which CI does not install, so it is run by hand. birthwt ships with MASS, so Sections 0 and 1 read no file at all.

Verification

  • all pre-commit hooks pass
  • the notebook runs top to bottom in ~19 s
  • every pinned R constant matches the output of the R companion exactly
  • tests/test_flow.py passes; no test reads notebooks/

🤖 Generated with Claude Code

https://claude.ai/code/session_01Pqkmo7dKBep1cvkHUfEH6i

oduerr and others added 2 commits August 27, 2026 11:56
notebooks/classical_fit_tram_dag.py was deleted with notebooks/stale/ in
2a7327d. Its ordinal half went to experiments/misc/validate_ls.py and its
warm-start lesson to docs/fitting.md, but nothing else walks fit_classical
through end to end, so it comes back rather than staying scattered.

The port drops the tramdag.simulations import (the VACA triangle is written
inline, as the other notebooks do), reads the stroke cohort from its new home
under experiments/misc/data/, uses positional terms and CausalFlowDAG(seed=),
and replaces a hand-rolled one-hot design with flow.design_matrix(drop_first=)
-- which did not exist when the notebook was written.

Two sections are new:

- Section 0 opens on plain logistic regression, because a two-level OrdinalNode
  with LS terms *is* one: logit P(Y=1) = -theta_0 + w_0 + sum_p w_p x_p. It runs
  on MASS::birthwt so the reader can re-fit it in R, and agrees with
  statsmodels.Logit and R glm to ~1e-8 on every coefficient, on the
  log-likelihood, and on the fitted probabilities to 9e-8. It makes two
  conventions concrete on the simplest possible model: an ordinal node
  subtracts its shift, and an ordinal parent's one-hot level-0 column is part
  of the intercept, so R's (Intercept) is -theta_0 + w_0.

- Section 1 reproduces the continuous fit outside the flow, in R (tram::Colr,
  shown with its real output) and in Python. statsmodels has no continuous
  transformation model, but it does not need one: such a model is the limit of
  an ordered logit, so binning the outcome into K quantile bins and fitting
  OrderedModel converges to the flow's shift coefficients. Both are labelled
  consistency checks, not identities -- they compare two sieve approximations
  of h and agree to ~0.1%, not to 1e-8. The cell also shows the sign flip that
  Sections 0 and 2 do not need: a continuous node adds its shift.

notebooks/data/ is new, with a README recording provenance. birthwt.csv is four
columns exported verbatim from MASS and is an input. vaca.csv is an output the
notebook rewrites each run, tracked so the R snippet reads the identical rows
the flow was fitted on, and so a changed n or seed shows up as a diff that
flags the pinned Colr coefficients as stale.

The notebook joins the docs workflow's NOTEBOOKS list and the pdoc nav, per the
rule in notebooks/README.md that a notebook not executed by CI does not belong
in that directory. statsmodels joins the notebooks dependency group, since that
job runs --group notebooks --group docs. codespell learns "lik", which R prints
in "'log Lik.'".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pqkmo7dKBep1cvkHUfEH6i
MArpogaus and others added 2 commits August 31, 2026 10:16
Both CHANGELOG sides are 0.4.0 Added bullets — kept both; .gitignore
keeps scratch/, .DS_Store and site/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three cells still used the pre-0.4 arguments: fit_classical(verbose=),
fit(schedule="plateau", plateau_patience=, freeze_patience=, verbose=)
— now callbacks.PerNodePlateau over per_node_adam — and
fit(restore_best=False), which is the default behavior. Executed end to
end under the merged 0.4 API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MArpogaus
MArpogaus merged commit bd1f52a into dev-marcel Aug 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants