Interpreting and reporting results#
An ERGM’s coefficients are changes in the log-odds of a tie, given the rest of the network, per unit of each term’s statistic. This page turns them into quantities that are easier to read and to report: odds ratios, tie probabilities and marginal effects, and tables for papers.
import ergmx
from ergmx import datasets
mesa = datasets.load("faux.mesa.high")
homophily = ergmx.ergm(mesa, "edges + nodematch('Grade') + nodematch('Race') + nodefactor('Sex')")
closure = ergmx.ergm(
mesa, "edges + nodematch('Grade') + nodematch('Race') + nodefactor('Sex') + gwesp(0.5, fixed=TRUE)",
seed=1,
)
Odds ratios and confidence intervals#
odds_ratios() exponentiates the coefficients: the factor
by which one more unit of a statistic multiplies the conditional odds of a
tie. confint() gives Wald intervals of the coefficients,
as R’s confint():
closure.odds_ratios()
Odds ratios, with 95% confidence intervals:
Odds ratio 2.5 % 97.5 %
edges 0.0021 0.0015 0.0029
nodematch.Grade 7.1781 5.0857 10.1314
nodematch.Race 1.3043 1.0333 1.6464
nodefactor.Sex.M 0.8820 0.7619 1.0210
gwesp.fixed.0.5 3.3757 2.8562 3.9896
The factor by which a unit increase in the term's statistic multiplies the conditional odds of a tie.
Students of the same grade have about 7 times the odds of a friendship of other students with the same friends, race and sex. A first shared friend adds one unit to gwesp (later ones add less, with this decay), which multiplies the odds by about 3.4.
Tie probabilities#
predict() gives every dyad’s probability of a tie, as
R’s predict(fit). By default it is conditional: given the rest of the
observed network, computed exactly from the dyad’s change statistics, so it
accounts for the friends two students already share. With
conditional=False it is the share of networks simulated from the model in
which the dyad is a tie:
probabilities = closure.predict()
probabilities
<TiePredictions: conditional tie probabilities of 20910 dyads, mean 0.0090>
.matrix() arranges them in an n x n array, .to_frame() in a pandas
DataFrame, and .mean_by(attribute) averages them by pairs of levels:
probabilities.mean_by("Grade")
Mean conditional tie probability by Grade:
7 8 9 10 11 12
7 0.0410 0.0020 0.0021 0.0021 0.0022 0.0028
8 0.0020 0.0369 0.0024 0.0031 0.0022 0.0027
9 0.0021 0.0024 0.0229 0.0030 0.0028 0.0030
10 0.0021 0.0031 0.0030 0.0210 0.0024 0.0057
11 0.0022 0.0022 0.0028 0.0024 0.0368 0.0043
12 0.0028 0.0027 0.0030 0.0057 0.0043 0.0524
As in ergm, conditional probabilities ignore the sample space constraints; unlike ergm’s formula method, they include dyads whose value is missing, which is how to predict them.
Average marginal effects#
Odds ratios are multiplicative, and depend on the baseline odds. The average marginal effect of a term (Duxbury 2023, R’s ergMargins) is the change in a dyad’s conditional tie probability per unit of the term’s statistic, theta * p (1 - p), averaged over the dyads:
closure.marginal_effects()
Average marginal effects on the tie probability:
AME Delta SE Z P
edges -0.0449 0.0016 -28.0258 7.883e-173
nodematch.Grade 0.0143 0.0013 10.7460 6.188e-27
nodematch.Race 0.0019 0.0009 2.2435 0.02486
nodefactor.Sex.M -0.0009 0.0005 -1.6746 0.09401
gwesp.fixed.0.5 0.0088 0.0005 18.1118 2.571e-73
Averages over the 20910 dyads of the change in each dyad's conditional tie probability per unit of the term's statistic, theta * p (1 - p); delta-method standard errors.
A shared grade adds about 1.4 percentage points to the probability of a friendship, against an average probability of 0.9%. The standard errors use the delta method; ergMargins holds the tie probabilities fixed in it, which ergmx doesn’t, so ergmx’s standard errors differ somewhat from ergMargins’ (the effects are the same).
Tables for papers#
ergmx.table() puts models side by side, as R’s texreg: printed, it is
screenreg()’s table, character for character.
results = ergmx.table(homophily, closure, names=["Homophily", "Closure"])
results
| Homophily | Closure | |
|---|---|---|
| edges | -5.89*** | -6.18*** |
| (0.20) | (0.17) | |
| nodematch.Grade | 2.81*** | 1.97*** |
| (0.18) | (0.18) | |
| nodematch.Race | 0.43** | 0.27* |
| (0.14) | (0.12) | |
| nodefactor.Sex.M | -0.35*** | -0.13 |
| (0.10) | (0.07) | |
| gwesp.fixed.0.5 | 1.22*** | |
| (0.09) | ||
| AIC | 1926.95 | 1744.31 |
| BIC | 1958.75 | 1784.05 |
| Log Likelihood | -959.48 | -867.15 |
| ***p < 0.001; **p < 0.01; *p < 0.05 | ||
In a notebook it shows as HTML. .to_latex(), .to_html() and
.to_markdown() give the table in each format (the first two as texreg’s
texreg() and htmlreg() write them):
print(results.to_markdown())
| | Homophily | Closure |
|---|---:|---:|
| edges | -5.89\*\*\* | -6.18\*\*\* |
| | (0.20) | (0.17) |
| nodematch.Grade | 2.81\*\*\* | 1.97\*\*\* |
| | (0.18) | (0.18) |
| nodematch.Race | 0.43\*\* | 0.27\* |
| | (0.14) | (0.12) |
| nodefactor.Sex.M | -0.35\*\*\* | -0.13 |
| | (0.10) | (0.07) |
| gwesp.fixed.0.5 | | 1.22\*\*\* |
| | | (0.09) |
| AIC | 1926.95 | 1744.31 |
| BIC | 1958.75 | 1784.05 |
| Log Likelihood | -959.48 | -867.15 |
\*\*\* p < 0.001; \*\* p < 0.01; \* p < 0.05
rename= relabels coefficients ({"nodematch.Grade": "Same grade"}),
omit= leaves some out, digits= and stars= change the numbers and the
significance thresholds. ErgmFit.to_frame()
returns the coefficient table as a pandas DataFrame, with R’s columns.