Fit joint Poisson GLMs per layer in build_netparams (#61)#67
Merged
Conversation
Adds a `method = c("existing", "joint")` argument to `build_netparams()`.
Under `method = "joint"`, one joint Poisson GLM is fit per network layer
(main / casl / inst) predicting degree (or one-off partner count) from
age.grp + sqrt(age.grp), the concurrent-layer degree, hiv2, race (when
enabled), and geogYN (when geography is stratified). Candidate
interactions (`age.grp:race.cat.num` when race = TRUE, and
`age.grp:<cross_deg>`) are evaluated via AIC and retained only when they
improve fit; the selected and candidate sets are stored as attributes on
the fitted object.
The fitted GLMs are exposed at `netparams$<layer>$joint_model` as an
additive output. Default `method = "existing"` is byte-identical to the
pre-refactor behavior — verified against the backward-compat snapshot
harness on all three parameter sets (Atlanta+race, national no-geog,
Atlanta no-race).
Closes #61.
Validation (Atlanta + race):
- All three joint models converge without warnings (nobs 4863/4863/4855).
- Marginal recovery exact: mean(predict) = mean(observed) at 0.000% diff
for all layers (expected for Poisson GLM with intercept fit on the
training data).
- Coefficient signs as expected: age.grp -1.19, sqrt(age.grp) +3.48
(peaked-then-declining profile); race levels 2 and 3 above Black (ref);
deg.casl main -0.55 with age.grp:deg.casl +0.10 interaction.
- AIC-based selection picked age.grp:deg.casl for main, age.grp:deg.main
for casl, and both age.grp:race and age.grp:deg.tot3 for inst. Age×race
was not selected for main or casl (substantive finding worth PI review).
New unit tests: tests/testthat/test-joint-model.R (7 test blocks, 69
assertions). Seeds RNG to 20260419 to handle the stochastic partner-race
NA imputation. Require ARTnetData and skip silently otherwise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
method = c("existing", "joint")tobuild_netparams(). Undermethod = "joint", fits one joint Poisson GLM per layer (main / casl / inst) that predicts degree (or one-off count) from all available attributes simultaneously —age.grp + sqrt(age.grp), the concurrent-layer degree,hiv2, race (when enabled), andgeogYN(when geography-stratified) — with AIC-based interaction selection overage.grp:race.cat.numandage.grp:<cross_deg>. Fitted models are exposed atnetparams$<layer>$joint_modelas an additive output.Default
method = "existing"is byte-identical to pre-refactor behavior.Closes #61. Unblocks #62 and #65.
What changed
R/NetParams.R— new private helperfit_joint_poisson()and joint-fit call sites at the end of each layer block. Helper records selected vs. candidate interactions onattr().R/ARTnet-package.R—@importFrom statsextended withAIC,formula,reformulate,update.tests/testthat/test-joint-model.R— new; 7test_that()blocks, 69 assertions. Skips whenARTnetDatais not installed. Seeds RNG to 20260419 becausebuild_netparams()imputes missing partner races viasample().man/build_netparams.RdandNAMESPACEregenerated viadevtools::document().Validation
Pre/post regression against the
inst/validation/snapshot harness (3 parameter sets):compare_to_snapshot()(default)compare_to_snapshot(method = "existing")Joint-method checks (Atlanta + race):
converged = TRUE, no warnings, nobs 4863/4863/4855mean(predict) = mean(obs)at 0.000% for all layersage.grp = -1.19,sqrt(age.grp) = +3.48(peaked-then-declining)Finding for PI review
AIC-based selection disagrees with the CLAUDE.md prior on which interactions to keep:
age.grp:deg.caslonlyage.grp:deg.mainonlyage.grp:race.cat.numandage.grp:deg.tot3CLAUDE.md flagged
age.grp × raceas "most plausible to matter given AMIS compositional shifts." The data (2017-18 ARTnet, Atlanta + race = TRUE) says otherwise for main/casl. Thecandidate_interactions/selected_interactionsattributes on each fitted model make this decision transparent, and a future iteration can add aforce_interactionsparameter if we want to override AIC.Test plan
method = "existing"match snapshots byte-for-byte on all 3 param setsinst/validation/snapshots/is not required — joint fields are additive and are stripped before comparison.Ref: CLAUDE.md §4.5 (#61 acceptance criteria), §4.7 (validation harness).