Repository navigation
Balance race mixing to the target population, check feasibility, seed the imputation, fit every city with MCMLE (#9 to #12) - #13
Merged
Conversation
… seed the partner-race imputation Under method = "joint", nodematch_race was standardized on the ego side only, so the partner side was still ARTnet's and the race targets came out infeasible in mostly-White cities. Partner race is now modeled with a multinomial on ego attributes, the reported mixing matrix is built at the synthetic population and symmetrically scaled with nodefactor_race held fixed, and nodematch_race and nodematch_race_diffF are read from the balanced matrix (#9). The opt-in nodematch.race.floor covers the legacy method (#9). build_netstats() now stops when a layer's race targets cannot be realized by any network, and check_race_feasibility() is exported (#10). build_netparams() gains a seed argument for the partner-race imputation (#11).
fit_comparison_ergms() builds each scenario's targets under three arms (existing, existing with the random-mixing floor, joint), runs check_race_feasibility() on each, fits the three template ERGMs with MCMLE for the feasible ones, and records convergence and static netdx recovery. render_fit_report() writes method_comparison_fits.md. The comparison report is regenerated for the balanced race targets (#12).
…edited by-age duration Under duration.method = "joint_lm", build_netstats(method = "joint") recomputes the by-age durations at the synthetic population and used the netparams values only as a fallback, so a value edited in netparams$<layer>$durs.<layer>.byage$mean.dur.adj after build_netparams() was dropped without a word. build_netstats() now takes duration.cap, applied with pmin() to every main and casual duration passed to dissolution_coefs() under both methods, and build_netparams() records the by-age durations it produced under joint_lm so build_netstats() can warn when the column has been changed.
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.
Closes #9, closes #10, closes #11, closes #12. They touch the same code, so one PR.
#9, race mixing balanced to the target population. Under
method = "joint", partner race is now modeled with a multinomial on ego attributes (netparams$<layer>$joint_prace_model).build_netstats()forms the reported endpoint matrix at the synthetic population (rows = ego race, row sums =nodefactor_race) and balances it by symmetric scaling,B = D S DwithSthe symmetrized reports andDchosen so the row sums stay atnodefactor_race.nodematch_raceandnodematch_race_diffFare read fromB. The matrices are stored asmixmat_raceandmixmat_race_reported.On the design question: nodefactor holds and nodematch gives. Degree is the ego's own count and anchors edges and every other nodefactor; letting it give would move Seattle's Black main degree by +48%. Plain averaging of the two reports of each cross-race cell is not used because, with the margins held, it drives the Hispanic same-race cell negative in Seattle casual and one-off and in Atlanta casual and one-off. The scaling keeps the homophily odds ratios of the reported matrix and refits the margins, so the targets are always feasible.
Seattle main at 10,000 nodes, default race composition. White egos report 231 Black partners; Black egos hold 174 partnership endpoints in total.
Worst slack by layer (
nodefactor_race - edges - nodematch_race_diffF, feasible iff <= 0): main +157.5 -> -115.6, casual +240.2 -> -207.0, one-off +61.5 -> -16.3. At the four-city project's Seattlerace.prop, where only the one-off layer was infeasible, all three layers are feasible too.The switch to the multinomial also moves joint targets where they were already feasible, because the binomial same-race model read geography as one shared shift in sameness. Atlanta
nodematch_race_diffFmoves +4.6% (main), +19% (casual), +2% (one-off); the casual shift is the Black-to-Hispanic cell that Atlanta's 4.6% Hispanic population cannot supply. The comparison report is regenerated (253 of 384 cells now shift more than 5%, up from 232).The random-mixing floor is in as
nodematch.race.floor = FALSEonbuild_netstats(), opt-in, touchingnodematch_race_diffFonly. It is the stopgap formethod = "existing"; undermethod = "joint"it does not bind.#10.
build_netstats()stops by default when a layer's race targets are infeasible, naming the layer, race and excess;check.feasibility = FALSEreturns them anyway.check_race_feasibility()is exported. This is the one legacy-visible behavior change: amethod = "existing"Seattle call that used to return unfittable targets now errors.#11.
build_netparams()gainsseed. On the pooling question, partner-race missingness by layer inARTnet.longis 0.7% (main, 18 of 2,618), 2.9% (casual, 175 of 5,978) and 9.9% (one-off, 754 of 7,602). One-off partnerships are the largest group in the pooled table, so the pull on one-off mixing is at most 0.2 percentage points of same-race share; the seed sensitivity you saw is the 10% missingness itself. I left the pooling alone, since stratifying changes legacy draws and would need a snapshot recapture.#12.
fit_comparison_ergms()fits the three template ERGMs with MCMLE for every scenario city under three arms (existing,existing_floor,joint) after running the #10 check on each target set, andrender_fit_report()writesinst/validation/method_comparison_fits.md. On the four scenario cities at 5,000 nodes: the three Seattle legacy target sets are skipped by the check, and the other 33 fit and converge, including Seattle under the floor and the joint arm in every city. Static netdx over 1,000 sims gives max |Z| of 3.7 / 4.5 / 4.4 and max |% diff| of 6.7% / 6.7% / 6.3% for existing / floor / joint, the largest in the one-off layer under every arm. One thing to know: ergm opens a one-worker cluster atparallel = 1, so concurrent jobs clash on the port; the harness usesparallel = 0.Durations under
joint_lm.build_netstats(method = "joint")withduration.method = "joint_lm"recomputes the by-age durations at the synthetic population and usednetparams$<layer>$durs.<layer>.byage$mean.dur.adjonly as a fallback, so a value edited there afterbuild_netparams()was dropped without a word, while the same edit worked under the legacy method. Two changes:build_netstats()gainsduration.cap, a single number in time units applied withpmin()to every main and casual duration passed todissolution_coefs(), under both methods, with a message naming the strata it binds on; andbuild_netparams()now records the by-age durations it produced underjoint_lm, sobuild_netstats()warns when the column has been changed and points atduration.cap. A cap just below the departure ceiling1 / (2 * expect.mort)is the intended use (the near-ceiling warning in EpiModel/EpiModel#1138 is the other side of this). DefaultNULL, no change to legacy output.Checks: snapshot harness ALL MATCH 3/3 under default args and
method = "existing"; 804 test assertions pass; R CMD check 0/0/0.nnetis a new import (a recommended package). Version 2.10.0.Not done here: stratifying the imputation by layer, and a
diff = TRUEfeasibility condition for thenodematch_racevector.