Coming from R#

Important

Last updated on 1 October 2026, against ergm 4.12.0, the version on CRAN then, and its development version on GitHub (4.13.0-8214, commit a85e6a9), ergm.multi 0.3.0 and tergm 4.2.2. The differences below describe these packages at that point; later versions may have changed them.

ergmx follows R’s ergm closely: the same formulas, term names, statistics, defaults and output, so a model fitted in R can be refitted in Python and the tables compared line by line.

Functions#

R (ergm, statnet)

ergmx

ergm(net ~ edges + triangle)

ergmx.ergm(g, "edges + triangle")

summary(fit)

fit.summary()

coef(fit), vcov(fit)

fit.coef, fit.cov

logLik(fit), AIC(fit), BIC(fit)

fit.loglik, fit.aic, fit.bic

AIC(fit1, fit2), anova(fit1, fit2)

ergmx.compare(fit1, fit2)

summary(net ~ edges + triangle)

ergmx.summary_stats(g, "edges + triangle")

simulate(fit, nsim = 10)

fit.simulate(10)

simulate(net ~ edges, coef = -2)

ergmx.simulate(g, "edges", [-2])

gof(fit), plot(gof(fit))

fit.gof(), fit.gof().plot()

mcmc.diagnostics(fit)

fit.mcmc_diagnostics(), .plot()

ergm(..., estimate = "MPLE")

ergmx.ergm(..., estimate="MPLE")

ergm(..., estimate = "CD")

ergmx.ergm(..., estimate="CD")

control.ergm(init.method = "CD")

ergmx.ergm(..., init="CD")

control.ergm(init = c(-2, 0.1))

ergmx.ergm(..., init=[-2, 0.1])

control.ergm(seed = 1)

ergmx.ergm(..., seed=1)

control.ergm(MCMC.samplesize = 2048)

ergmx.ergm(..., samplesize=2048)

control.ergm(MCMC.interval = 4096)

ergmx.ergm(..., interval=4096)

control.ergm(MCMC.burnin = 65536)

ergmx.ergm(..., burnin=65536)

control.ergm(parallel = 8)

ergmx.ergm(..., n_chains=8)

ergm(..., eval.loglik = FALSE)

ergmx.ergm(..., eval_loglik=False)

ergm(net ~ ..., constraints = ~bd(maxout = 5))

ergmx.ergm(g, ..., constraints="bd(maxout=5)")

ergm(net ~ offset(edges) + ..., offset.coef = -3)

ergmx.ergm(g, "offset(edges) + ...", offset_coef=[-3])

net[i, j] <- NA

g.add_edge(i, j, na=True) (an edge marked missing)

simulate(net ~ ..., constraints = ~degrees)

ergmx.simulate(g, ..., constraints="degrees")

data(faux.mesa.high)

ergmx.datasets.load("faux.mesa.high")

ergm(Networks(g1, g2) ~ N(~edges, lm = ~log(n)))

ergmx.ergm(ergmx.Networks(g1, g2), "N(~edges, lm=~log(n))")

tergm(list(g1, g2, g3) ~ Form(~edges) + Persist(~edges), estimate = "CMLE")

ergmx.tergm([g1, g2, g3], "Form(~edges) + Persist(~edges)")

ergm(NetSeries(g1, g2) ~ Form(~edges) + Diss(~edges))

ergmx.ergm(ergmx.NetSeries(g1, g2), "Form(~edges) + Diss(~edges)")

simulate(fit, nw.start = "last", time.slices = 10)

fit.simulate(time_slices=10)

simulate(g ~ Form(...) + Persist(...), coef = c(...), time.slices = 10, dynamic = TRUE)

ergmx.simulate_dynamic(g, "Form(...) + Persist(...)", [...], 10)

tergm(g ~ Form(~edges) + Persist(~edges), targets = ~edges + mean.age, target.stats = c(20, 10), estimate = "EGMME")

ergmx.tergm(g, "Form(~edges) + Persist(~edges)", estimate="EGMME", targets="edges + mean.age", target_stats=[20, 10])

tergm(..., control = control.tergm(CMLE.NA.impute = "next")), NetSeries(..., NA.impute = "next")

ergmx.tergm(..., na_impute="next"), ergmx.NetSeries(..., na_impute="next")

gofN(fit, GOF = ~edges + triangle) (ergm.multi)

ergmx.gofN(fit, "edges + triangle")

saveRDS(fit, "fit.rds"), readRDS("fit.rds")

fit.save("fit.pkl"), ergmx.load_fit("fit.pkl")

data(Goeyvaerts) (ergm.multi)

ergmx.datasets.load("Goeyvaerts"), a list of graphs

predict(fit), predict(fit, conditional = FALSE)

fit.predict(), fit.predict(conditional=False)

predict(net ~ edges + mutual, eta = c(-2, 1.8))

ergmx.predict(g, "edges + mutual", [-2, 1.8])

confint(fit), exp(coef(fit))

fit.confint(), fit.odds_ratios()

ergMargins::ergm.AME(fit, "nodematch.Grade")

fit.marginal_effects() (every term)

ergm(net ~ S(~edges + gwesp(0.5, fixed = TRUE), ~level == "A"))

ergmx.ergm(g, "S(~edges + gwesp(0.5, fixed=TRUE), ~level == 'A')")

MPNet’s multilevel effects (TXAX, C4AXB…)

txax(), c4axb()… (see Multilevel networks)

screenreg(list(fit1, fit2))

print(ergmx.table(fit1, fit2))

texreg(...), htmlreg(...)

ergmx.table(...).to_latex(), .to_html()

Formulas#

Formula strings are R’s, so "edges + gwesp(0.5, fixed=TRUE)", "kstar(2:3)", "nodematch('Grade', diff=TRUE)", "nodemix('Race', levels2=-c(1, 3))" and "F(~gwesp(0.5, fixed=TRUE), ~!nodematch('Grade'))" work as written, and so do constraint strings such as "~bd(maxout=4) + blocks('level', levels2=2)". The left-hand side of net ~ ... is ignored, since the network is the first argument. Strings can use single or double quotes. Only literal arguments are accepted: R expressions such as log(n) must be computed first.

What is different#

Networks are igraph or networkx graphs.

There is no network class. Vertex attributes play the role of net %v% "attr", and graph attributes hold the matrices for edgecov.

Parallel chains by default.

ergmx runs four MCMC chains in threads (ergm runs one, unless parallel is set), which also gives the R-hat diagnostic.

A more accurate log-likelihood.

Both estimate it by path sampling, but ergmx integrates with a higher-order rule and more samples. Its log-likelihoods, and so AICs and BICs, can differ from ergm’s by up to about 2 units on dyad-dependent models, ergm’s being the biased ones (see Validation).

Degeneracy stops the fit with an explanation.

A ergmx.DegeneracyError shows the simulated and observed statistics and suggests what to try, after a density guard like ergm’s or when the estimate stops moving.

Missing dyads are not imputed for the MPLE.

ergm imputes them at random before its MPLE, so its MPLE varies between runs; ergmx treats them as non-ties in the change statistics. Both are only starting values: the MLEs agree.

The goodness of fit of networks with missing dyads uses imputations.

ergmx compares the simulated networks with networks imputed from the model given the observed dyads. ergm computes such imputations but compares with the network with missing dyads as non-ties (in 4.12.0 and the development version).

Bipartite networks are declared, not ordered.

ergm’s bipartite networks have the first mode first (bipartite = n1); ergmx reads each vertex’s mode from an attribute (bipartite="type"), in any order.

Curved terms start from their decay argument.

In ergm, gwesp(0.5) without fixed=TRUE ignores the 0.5 (it warns, and the start comes from init); ergmx starts the decay there. Both estimate it. When a network has more shared partners (or degree) than the cutoff, ergm stops with an error; ergmx counts them in an overflow statistic.

transitive counts transitive triads, as ergm documents.

ergm documents transitive as a count of transitive triads but computes transitive triples, the same as ttriple, in 4.12.0 and the development version. ergmx counts the triads and warns; to reproduce a model fitted in R with transitive, use ttriple, which gives ergm’s statistic and estimates exactly.

intransitive counts intransitive triads, as ergm documents.

The same for intransitive: documented as intransitive triads (types 111D, 201, 111U, 021C and 030C), computed by ergm 4.12.0 as intransitive triples, two-paths without a shortcut. ergmx counts the triads and warns; ergm’s statistic is twopath minus ttriple.

dyadcov’s utri and ltri are as ergm documents.

ergm documents dyadcov’s second and third statistics, in directed networks, as the dyads in the upper- and lower-triangular asymmetric states, but counts a lone tie from the lower- to the higher-numbered vertex, in the upper triangle of the adjacency matrix, as ltri. ergmx follows the documentation and warns: its utri is ergm’s ltri.

smalldiff counts differences up to the cutoff, as ergm’s code.

ergm’s documentation says less than the cutoff, but its code, and so ergmx, counts the ties whose difference is at most the cutoff.

Edgewise RTP statistics are right, as in ergm without its cache.

ergm 4.12.0’s shared-partner cache, used by default, reads the edgewise RTP statistics (esp, gwesp and nsp with type = "RTP") with the wrong key, so their values depend on the order of the vertices. The development version fixes it (statnet/ergm#656). Until the fix reaches CRAN, term.options = list(cache.sp = FALSE) in summary() or ergm() gives the right values in ergm 4.12.0, which match ergmx’s.

Several networks are pooled in goodness of fit.

For models of several networks (Networks(), NetSeries()), ergmx’s gof() sums the distributions over the networks and only counts pairs of vertices in the same network; ergm’s counts the pairs in different networks too, as unreachable (with distance Inf) and without shared partners.

N()’s linear models cover the common cases.

lm formulas take attributes, arithmetic, comparisons, &, |, !, I(), log(), exp(), sqrt(), abs(), factor() and offset(), with R’s column names; not interactions (a:b). subset, offset and label are ergm.multi’s, but label as a function is a Python function of one statistic’s name and one column (R’s is vectorized), and a numeric offset may be a single number (R requires one per network).

gofN() reports degree0 and isolates themselves, from nearly independent draws.

ergm.multi’s gofN() leaves out the empty network’s statistics, so its observed and fitted degree0 and isolates are the counts minus the network size (the residuals are unaffected); ergmx reports the counts and warns. ergmx’s default interval between simulated networks is three times the number of dyads, so that each network’s draws are nearly independent; ergm.multi’s (the fit’s, 1024 by default) leaves those of many small networks autocorrelated. gofN()’s subset selects the networks reported.

Attribute arguments are names.

Terms take vertex attributes by name, and levels= the specifications of ergm’s ?nodal_attributes (values, 1-based indices, negative indices, TRUE, NULL); not functions of attributes (~Grade > 9) or I(). Covariate matrices and reference networks (edgecov, dyadcov, hamming, localtriangle, bd(attribs=)) are graph attributes, arrays or graphs. altkstar needs fixed=TRUE, and gwdegree(attr=) a fixed decay, as in ergm.

Dynamic simulation uses tergm’s stopping rule and discordant proposals.

Each time step’s chain runs until the number of changed dyads stops growing, as tergm’s MCMC.burnin.min, .max, .pval and .add (min_steps, max_steps, pval, add), with tergm’s discordTNT proposal mixed into the TNT and triadic proposals. A fit whose coefficients vary over time (lm=~.Time) continues its trend when simulated forward, at .Time + k .TimeDelta.

The EGMME follows tergm’s algorithm, with its own convergence check.

Starting values from EpiModel’s approximation, a gradient by central differences under common random numbers, stochastic approximation with Polyak averaging, and the delta method’s standard errors, as tergm. The fit is reported as converged when the shift of the coefficients that would bring the final run’s targets to theirs is under half a standard error. Tie ages start at 1, as in tergm.

Predictions include missing dyads; unconditional ones respect the constraints.

ergm’s predict() for a formula leaves out the dyads whose value is missing, and simulates unconditional probabilities without the fit’s constraints. ergmx predicts missing dyads (conditional on the observed network, with missing dyads as non-ties) and simulates with the constraints. ergm’s unconditional predictions also leave out the dyads never tied in the simulations; ergmx lists every dyad.

Marginal effects’ standard errors use the full delta method.

ergMargins’ ergm.AME() holds the tie probabilities fixed when it differentiates the average marginal effect; ergmx also counts how they change with the coefficients. The effects are the same; the standard errors differ, by up to a quarter on the models tested.

Not yet available.

Valued networks; ergm.multi’s lm.gofN() and N()’s weights and contrasts; tergm’s durational model terms (as opposed to EGMME targets and monitors); among ergm’s binary terms, degcor, degcrossprod, tripercent, coincidence and the projection and Sum/Prod/Exp-style operators; and the degreedist and egocentric constraints.