plot_simplicial() keeps multi-word state names whole when pathways are
given in full arrow notation ("A -> B -> C"), instead of splitting them
on spaces. The HYPA, MOGen and HON pathway extractors emit full arrow
notation, so every state of a path is drawn as its own vertex.plot_mcml() is restored to its 2.7.4 behaviour: on directed input both
modes plot row-normalized cluster weights, and mode changes only the
default edge labels. 2.7.5 had changed the default figure.weighted = FALSE is now honoured by every centrality measure. About twenty
measures, among them closeness, betweenness, harmonic, eigenvector, pagerank,
strength and katz, used the edge weights regardless of this argument.centrality(x, type = "all") runs on the
bundled regulation_net.centrality_gateway() uses the same ties for degree and module links, so the
score lies between 0 and 1 on directed networks; it honours mode and accepts
character or factor membership.centrality_alpha() and centrality_power() honour mode. With the default
mode = "all" a directed network is symmetrized, which changes their default
results on directed input.centrality_expected_influence_1() and _2() no longer count each edge twice
on undirected networks with mode = "all".igraph::transitivity(type = "local"); DMNC no longer double-counts reciprocated neighbors.normalized = TRUE is divided by n - 1 only; a named
personalized vector in PageRank is matched to node names; percolation
honours invert_weights; Katz centrality of a single node is 1.cograph_undefined_measure warning, and invalid arguments raise classed
errors (cograph_bad_parameter, cograph_bad_membership). decay_parameter
and dmnc_epsilon are validated.estrada_index() is correct on directed networks; the efficiency functions
accept tna input with weights = NA; simplify() keeps the weight matrix in
step with the merged edges; is_bipartite() works on named graphs;
split_components() handles an empty network.cluster_significance() compares weighted modularity against null graphs that
carry the observed weights. csum() and summarize_clusters() symmetrize the
input when directed = FALSE, and sequence data with columns V1, V2, ... is
read as sequences. aggregate_layers() binarizes a single layer for union and
intersection; cluster_quality() density ignores self-loops.rich_club() warns (cograph_null_draw_failed) when null graphs cannot be
drawn. nodes() now signals its deprecation.plot_trajectories() handles missing states; plot_network_evolution()
accumulates slices correctly and accepts a coordinate layout;
plot_heatmap() builds a full diverging scale from one colour;
plot_ml_heatmap() labels every layer with its own nodes;
layout_oval() orders by node names; sn_layout() reports an unknown layout
clearly; CographTheme clone and merge keep added parameters.?plot-results. Network accessors, themes, palettes, shapes and the layout
registry each share one page.centrality_*() page follows one layout: definition, the arguments of
centrality() that tune the measure with their defaults, how direction and
weights are read, when the result is NA and which condition is raised,
references and one example on regulation_net.igraph package now check
that it is available. This fixes CRAN check failures on Intel macOS when
igraph is unavailable, while examples and tests using native cograph
functionality continue to run.as_tna() warns when it leaves a cluster out. A cluster becomes a tna
model only when every node in it has a transition to another node of the
same cluster, so a one-node cluster, or a cluster with a node whose
transitions all leave it, was dropped from the result without notice. The
warning, of class cograph_cluster_dropped, names each dropped cluster and
the nodes responsible. The clusters remain in the macro model.
cograph no longer registers a print() method for mcml objects. Nestimate
owns the mcml class and its print method; with both packages registering
one, the printed form, and whether unknown arguments were rejected, depended
on which package was loaded last. mcml objects print through Nestimate's
method (Nestimate is in Suggests).
as.data.frame() method for the results of motifs() and subgraphs().
It returns the main table by default (triad types for a census, node
triples for subgraphs()), and the counts per triad type with
what = "types".network_summary() no longer returns hub_score and authority_score.
Both columns were always NA, because the code read fields that igraph does
not return, and igraph scales HITS scores to a maximum of 1, so the maximum
carried no information. network_summary() now returns 16 statistics by
default, 27 with detailed = TRUE and 35 with extended = TRUE as well.
inst/CITATION lists the package authors as in DESCRIPTION.
network_clique_size(), and with it network_summary(extended = TRUE),
crashed R with a C stack overflow on directed networks such as
student_interactions. The crash is in igraph::clique_num() (igraph
2.3.3) on directed input. Cliques ignore direction, so the count now runs on
the simple undirected network; the result is unchanged for undirected input.
New dataset regulation_net: a synthetic weighted transition network
among ten learning regulation states, used in the examples. Its help page
states that it is synthetic and gives the recipe that generated it.
The introduction vignette is rewritten section by section. Each section
opens with what cograph offers for the task, the tables are corrected (the
higher-order functions are marked as Nestimate's, mlna() is listed as the
plot it is, summarize_network() is listed with the cluster tools, and
plot_difference() replaces plot_compare()), the disparity example uses
the object splot() expects, and the examples use regulation_net.
The README is redesigned: a statement of the package's scope, a quick start
with four examples on regulation_net (plotting, centrality, a hierarchical
multi-cluster plot and higher-order pathways), a summary of each area linked
to its article, and a list of the tutorials and articles. It replaces the
long function tables and the example figures from the June release.
Every citation in the help pages was checked against Crossref, arXiv and
DataCite. Two did not exist and are gone: a 2011 Procedia Engineering
paper cited by centrality_node_contraction() and a 2008 conference paper
cited by group_centrality(), which now cites Puzis, Elovici and Dolev
(2007). centrality_pairwisedis() now cites the paper that introduced the
index (Potapov, Goemann and Wingender, 2008). The brokerage role pages give
the full Gould and Fernandez (1989) reference, and 53 references are reduced
to standard form, with DOIs where they exist.
The centrality catalogue vignette no longer prints a reference paragraph under each of its 201 measures. Those paragraphs carried citations, page numbers and verification claims that could not be vouched for, so they were removed rather than kept unchecked. The 46 "Meaning" paragraphs that had grown into implementation notes (up to 728 words, arguing with source papers and with the Centrality Zoo's transcriptions) are rewritten as short interpretations of what a high value means; the longest is now 62 words.
The "Centrality Zoo lookup" article is now "cograph and the Centrality
Zoo". It compares the number of centralities in cograph with nine other
centrality packages and lists which of the Zoo's 349 measures centrality()
implements, with the closest implemented measure for each of the others.
Every number on the page is computed from the files in docs/zoo/.
plot_mcml() is drawn with a locked aspect ratio from fixed layout numbers, so
its shape never changed: a taller image only added white space (an 8 x 12 in
image used 59% of its height, 8 x 16 in 44%). And the argument documented for
this, layer_spacing, was accepted and then ignored -- 2 and 30 drew the
same figure.
layer_spacing now works, and takes three kinds of value:
NULL (default) -- the automatic layout, exactly as before. Existing figures
are unchanged."fill" -- the gap between the two layers is stretched so the figure uses the
full height of the image it is drawn on. Change the image height and the plot
follows. Shapes stay round; on a wide image, where height is what binds,
nothing changes.spacing. Overrides
inter_layer_gap. A value that overlaps the layers raises a
cograph_layers_overlap warning; anything else invalid raises
cograph_bad_layer_spacing.When a side legend is too large for its band it is scaled down to fit (new in 2.6.10). That scaling made one proportional adjustment, but a legend's size is not proportional to its text size -- symbols and padding scale differently, and font hinting makes text widths step -- so under Cairo fonts (Linux, Windows) the result could still extend 0.5% past the edge of the page, while fitting exactly on macOS. The legend is now re-measured after each adjustment until the measured box fits. Legends that already fit are unaffected.
The "Groups" legend was drawn at a hard-coded cex = 1.4 -- larger than the
node labels it explains -- with no argument to change it. plot_htna() now
takes legend_size (a cex, default 0.8, the same default and meaning as
splot(legend_size = )), and the legend's symbols are sized from it. The
default legend is therefore visibly smaller than in 2.6.9; pass
legend_size = 1.4 for the old text size. Anything other than a single
positive number raises a cograph_bad_legend_size error.
With a side legend (legend_position = "bottom", the default, or "top",
"left", "right"), the "Groups" legend was drawn partly on top of the
network and partly off the page, at every figure size. Two causes:
plot_htna() reserved the legend's margin with par(mar = ), which
splot() replaces with its own margins before drawing. The margin now
travels as splot(margins = ), so the band the legend needs really exists.inset, which is a
fraction of the plot region and moves the box by only a sliver of its own
height. The legend is now measured first and anchored by coordinates in the
centre of its band.The band is sized from the legend itself (rows, title, longest group name,
legend_size, and the device's text scale) rather than a fixed 6.5 lines. If a legend still
cannot fit -- many long group names on a small figure -- it is scaled down
instead of overlapping the plot or leaving the page.
A margin passed explicitly (mar = or margins = ) is left untouched. With
legend_position = "top" and a title, the title is drawn on the outer lines
of the top margin and the legend below it; previously splot() centred the
title in that margin, on top of the legend. Corner positions ("topright"
etc.) are unchanged and still draw inside the plot box.
Every exported and internal function was read against its own roxygen block. Everything below is a documentation correction unless a heading says otherwise.
igraph is in Suggests, but examples throughout the manual failed without
it. 78 roxygen example blocks are now
@examplesIf requireNamespace("igraph", quietly = TRUE).
Most built their example network with igraph::make_ring(),
igraph::make_star() or igraph::make_graph("Zachary") and were found by
reading the examples. The last 21 were not: network_summary(),
network_girth(), network_radius(), degree_distribution(),
dyad_census(), ego_networks(), shortest_paths() and the rest of that
family take a plain matrix and reach igraph internally through to_igraph(),
so nothing in the example text revealed the dependency. They were found by
removing igraph and running the manual.
Verified by running every example in a library where igraph (and therefore
tna) could not be loaded: 403 topics, no failures.
... documented an argument that could not be passed32 verbs -- the community detection family, the network_* summaries,
assortativity(), core_periphery(), dyad_census(), ego_networks(),
shortest_paths(), k_shortest_paths(), rich_club(), rich_club_local(),
robustness() and vulnerability() -- documented ... as "additional
arguments passed to to_igraph()". to_igraph() is
function(x, directed = NULL): it has no ..., so anything passed raised an
"unused argument" error. Each site now says what is true. Where directed is
already an explicit formal the dots are documented as unused; where it is not,
they are documented as carrying directed and nothing else.
plot_mcml()'s title line was missing its leading #, so R parsed it as a
stray top-level string and roxygen took the next ten lines of prose as the
title. man/plot_mcml.Rd now has the title it was meant to have.
A roxygen block in blob-helpers.R had been separated from its function by a
later comment banner, which silently moved .expand_repeated_nodes()'s
@param and @return onto the one-line accessor that followed it.
Among the factual fixes: core_periphery()'s example printed a component that
does not exist; network_global_efficiency() documented the wrong value for
its own example; community_consensus() documented ... as reaching the
detection method when it is discarded; degree_distribution() marked three
always-present components as conditional; binarize(signed = TRUE) was
described as producing -1 rather than +1 or -1; get_edges() did not say
its from/to are integer indices rather than labels; several verbs did not
name the classed condition they raise; and the register_shape() and
register_layout() examples overwrote a built-in shape and layout for the rest
of the session. show_zero_edges in from_tna() and from_qgraph() is
documented as having no effect, which is what it has.
The plot_tna() example that demonstrated custom colours with rainbow(5)
now uses palette_colorblind(5): the reference manual was teaching the one
palette the package's own style rules exclude.
British spellings in roxygen prose were normalized to the en-US that
DESCRIPTION declares (513 words across 79 files; comments only).
scales has been dropped from Suggests. It was used for exactly one call --
scales::squish as the out-of-bounds handler on the centrality heatmap's fill
scale -- and that behaviour is now a nine-line base R helper. Clamping is
unchanged: a finite value outside the limits is pulled to the nearer end, and
NA and the infinities are left alone.
from_tna() and from_qgraph() have accepted and documented a
show_zero_edges argument for several releases without ever reading it. Zero
is how a weight matrix stores "no edge", so an edge whose weight rounds to
zero at weight_digits disappears from the plot, and there was no way to
prevent that. With show_zero_edges = TRUE such an edge is now drawn at the
smallest magnitude weight_digits can express, carrying its sign; every other
weight is untouched. The default, FALSE, behaves exactly as before.
ld_radius, s_shell_a and comm_r were unvalidated, so a value outside
their domain surfaced as an opaque failure from deep inside a kernel --
values must be length 1, but FUN(X[[1]]) result is length 0 for
ld_radius = 0, and an all-NA result with a coercion warning for a
comm_r that is neither "max_intra" nor a number. All three now raise a
classed cograph_bad_parameter error naming the argument and its domain,
matching the other tuning parameters in the centrality surface.
Both were introduced before this release and are found by checking cograph against the eight packages that depend on it. All eight now check identically against this version and against the current CRAN cograph.
centrality() failed with arguments imply differing number of rows on a
network whose node table carries no label column. The node-label fallback
tested only for NULL, and a source with no labels arrives as a zero-length
character vector instead, so the fallback to node indices never fired and the
node column came out empty beside full-length measure columns. The fallback
now checks that there is exactly one label per node, which also rejects a
label vector of the wrong length.
to_df() and to_data_frame() return from, to and weight again. When
these stopped routing through igraph, the round-trip that used to discard
extra edge columns went with it, and the verb silently began returning every
column the edge table carried. They are the narrow conversion verbs; to get
the edge table whole -- including columns mutate_edges() computed -- use
as.data.frame() on the network, which is the accessor and is unchanged.
centrality(measures = "hubbell") refused to report a divergent result only
when the largest real part of the scaled spectrum reached 1. The Neumann
series behind Hubbell diverges once the spectral radius does, so a signed
network carrying a complex pair of modulus above 1 -- or a real eigenvalue
below -1 -- was reported as a confident number instead of NA. The guard now
tests the modulus. For a non-negative weight matrix the Perron root is real,
positive and equal to the spectral radius, so no unsigned network changes.
centrality(measures = "delta_closeness", closeness_delta = 0) scored every
node of a disconnected graph 1. R evaluates Inf^0 as 1 and
is.finite(1) is TRUE, so an unreachable pair passed the filter and counted
as fully close. The distance matrix is now screened as well. Only
closeness_delta = 0 is affected; every positive delta already behaved.
centrality_expected_influence_1() and centrality_expected_influence_2()
read their result column by $ partial matching, because the column is really
expected_influence_1_out. They now index the column by name. Results are
unchanged.
mcml()'s deprecation marker no longer depends on the lifecycle package,
which was not declared anywhere, and no longer points at an SVG that the
package does not ship -- the HTML help image for that topic was broken.
A simplex is a set of vertices, so a blob on its own cannot say which state
came first: A -> B -> B and B -> A -> B drew the identical shape. With
dismantled = TRUE each panel shows one ordered pathway, so plot_simplicial()
can now draw the traversal three ways, selected with direction_cues: a
light-to-dark core ramp along the path ("shade"), a ring whose highlight
peaks on the side facing the next state ("ring"), and an arrowhead just
outside each node aimed at its successor ("arrows").
direction defaults to NULL, which turns the cues on exactly when
dismantled = TRUE. A single combined blob cannot express direction, so
direction = TRUE with dismantled = FALSE raises a classed
cograph_direction_needs_panels error rather than drawing a misleading
figure. legend draws the in-figure legend strip beneath a dismantled grid
and defaults to TRUE whenever the cues are on.
An association-rule itemset and a clique of a simplicial complex are sets:
every member is co-equal and there is no target at all. These are now carried
through the pipeline as genuinely unordered — ordered = FALSE — instead of
having a source/target split imposed on them, and their panels are titled with
a member list rather than a path. Ordered HON/HYPA/MOGen pathways are
unaffected.
A panel-centring clip that could cut a blob off at the panel edge is fixed,
and an NSE-related R CMD check NOTE is resolved.
A permutation replicate that does not come back intact from a parallel worker
now raises an error. Previously only an explicit worker error was caught; a
worker killed by the operating system returns NULL for its whole chunk with
only a warning, and those gaps were silently filled by recycling the surviving
replicates -- part of a permutation null replaced by duplicates, with no
warning and no error.
The serial replicate path no longer leaves the session's random number
generator switched to L'Ecuyer-CMRG. motifs() itself never used that path,
but the package's own test suite did, which changed the random fixtures of
every test file that ran afterwards.
cores validation now raises a classed cograph_bad_cores condition.
igraph is a suggested dependency. Functions that need it -- including
to_igraph(), motif_census(), detect_communities(), robustness(),
vulnerability(), rich_club() and network_summary() -- now raise a
cograph_missing_suggest error naming the function and how to install igraph,
instead of R's bare "there is no package called 'igraph'". Functions that need
no igraph, such as motifs(significance = FALSE), subgraphs(),
extract_triads() and centrality(), keep working without it.
motifs() on individual-level data (a tna model, or an edge list with an
actor column) now runs its permutation null several times faster — on
tna::group_regulation with the default n_perm = 1000, 255s to 39s. The
null builds class counts directly instead of materialising and then
re-counting a row per node triple, and the triple indices are computed once
per state space rather than once per unit per replicate.
Results are unchanged: for a given seed the permutation draws, and every
count, expectation, z score and p value, are identical to previous versions.
The instance-level permutation null now counts (triple, class) pairs directly
instead of building a labelled row per triple per unit and re-aggregating it
by a pasted key. On tna::group_regulation at n_perm = 1000, 428s to 32s.
Results are unchanged for a given seed.
The individual-level permutation null can now run across worker processes.
cores = 1 remains the default and is byte-for-byte the previous behaviour.
cores > 1 gives each replicate its own L'Ecuyer-CMRG stream, so a result
depends on seed alone -- not on the worker count, and not on how replicates
were chunked across workers. Repeated parallel runs of one seed agree exactly,
at any cores. Those are a different set of draws from the serial path, so
p-values from cores > 1 will not match a cores = 1 run of the same seed;
both are valid permutation nulls. Forking is used where available, with a
PSOCK cluster on Windows.
Measured on 10,000 simulated sequences (10 states) at n_perm = 400:
103.0s at cores = 1, 20.5s at cores = 10.
On Windows the null runs through a PSOCK cluster, which is exercised in the test suite on every platform.
A replicate that does not come back intact from a worker -- an error, a NULL
from a killed process, or a wrong-length result -- raises a
cograph_parallel_failure error naming the problem, rather than being folded
into the null matrix. If the
available core count cannot be detected, cores > 1 is used but reported
with a cograph_cores_undetected warning.
The top layer can now be drawn at a finer resolution than the partition: named
clusters appear as their member states while every other cluster stays a
single node, and the bottom layer still shows the partition, so an expanded
state sits inside its cluster's shell and is linked to its own summary node.
expand = "all" (or TRUE) expands every cluster.
The expanded macro is re-counted from the input with a refined partition,
using cograph's own cluster_summary(); a k x k aggregate cannot be
disaggregated after the fact. Only a pre-built cluster_summary or mcml,
which carries no source to re-count from, falls back to
Nestimate::macro_network(), and says so with a
cograph_expand_unavailable error when that is unavailable.
This also fixes a silent defect in the previous layout code: the top layer was indexed positionally against the cluster count, so a macro with more nodes than the partition was truncated to its first k rows and drawn under the cluster names — a confident, wrong figure with no error. It is now matched by name, and a macro wider than the partition that cograph did not build itself is refused.
Fixes for defects an adversarial review found in the 2.6.0 wrangling verbs. All of them were introduced in 2.6.0 except the last, which was older.
Zero is how cograph stores "no edge", so it must never be compared against a
real weight. to_undirected(), symmetrize(), spanning_tree() and
bind_networks() did exactly that, which silently deleted edges in networks
that carry negative weights — correlation and partial-correlation networks
above all:
-2 was deleted by method = "max", because
pmax(-2, 0) is 0;2 was deleted by method = "min";method = "mean" halved every unreciprocated edge against a phantom
reverse arc;spanning_tree() returned an empty network on any all-negative graph: Prim
chose the right edges, then the mirroring step compared each against zero
and erased it;bind_networks(weight = "max") lost an edge only one network had if its
weight was negative.Presence is now carried separately from weight throughout: two values are
combined only where both arcs exist, and an unreciprocated edge keeps its own
weight. Combining to exactly zero raises a cograph_edges_dropped warning
rather than shrinking the edge set in silence.
symmetrize() gains method = "mutual" for the reciprocated-only rule (sna's
"strong"). method = "min" no longer means that: it is a weight combination
that keeps unreciprocated edges, which is a different operation.
contract_nodes() counted an undirected within-group edge twice, because a
symmetric matrix holds every such edge twice. It now aggregates the edge
table, so a single edge of weight 3 becomes a self-loop of 3, not 6.reorder_nodes() checked only the length of order, so a non-permutation
such as c("A", "A", "B") produced duplicate labels or an internal
subscript error. It now requires an exact permutation.add_edges() and set_edges() accepted the same undirected edge twice
(A->B and B->A), leaving the edge table and the weight matrix
disagreeing about how many edges exist. Both now reject it.mutate_nodes() and mutate_edges() could overwrite the columns the
structure is keyed on (label, id, from, to), leaving the node table,
edge table and matrix describing different networks. Those columns are now
reserved. A weight mutated to zero drops the edge with a classed warning
rather than leaving a row the matrix does not have.cograph_isolates_created, as the
documented invariant says: the nodes are kept, so they are newly isolated.split_components() and contract_nodes() no longer fail on a zero-node
network, and the matrix-level verbs raise cograph_bad_selection on
non-finite weights instead of an internal error several frames later.proportion and density reject 0, and top, k and min_size reject
fractional values, as documented. complement_network() rejects
weight = 0, which would have produced an empty complement.bind_networks(directed = FALSE) on directed input returned an
"undirected" network whose matrix was asymmetric and whose edge table was
empty; it now symmetrises the inputs first. It also carries x's metadata,
estimation data and node attributes instead of dropping them.as.data.frame() on one keeps
the extra edge columns.reverse_edges() and normalize_weights() ("max", "sum", "minmax")
keep extra edge columns; they map edges one-to-one, so there was no reason
to lose them. network_to_igraph() carries node columns across as vertex
attributes, so attributes added with mutate_nodes() survive
keep_format = TRUE.parse_matrix() read an undirected matrix from the strict upper triangle,
so a self-loop on the diagonal was dropped from the edge table while
$weights kept it — the two disagreed, and a later matrix-level verb could
resurrect the loop. Undirected self-loops are now edges. This predates
2.6.0.The verbs that reshape a network are now a family with one contract: any
supported input, options as named arguments, a cograph_network back (or the
input format with keep_format = TRUE), and classed conditions. See
?network_wrangling.
as.data.frame() on a cograph_network returns the tidy edge table, with
endpoints as labels rather than internal indices, and
as.data.frame(what = "nodes") returns the node table. No caller needs to
reach into the object with $ any more.
threshold_edges(), binarize(), symmetrize(),
normalize_weights(), invert_weights().to_undirected(), to_directed(), reverse_edges(),
remove_isolates(), contract_nodes(), split_components(),
select_k_core(), spanning_tree(), complement_network(),
reorder_nodes(), rename_nodes().add_nodes(), remove_nodes(), add_edges(), remove_edges(),
mutate_nodes(), mutate_edges(), bind_networks().add_edges() shares its name with igraph::add_edges(); call
cograph::add_edges() when igraph is attached.
Node expressions gain is_isolated, is_source, is_sink, is_leaf,
is_cut, local_transitivity and local_triangles, and any measure
centrality() computes can now be named directly — `select_nodes(x, harmonic
0)
works, as doesselect_top(x, n = 5, by = "leverage"). Edge expressions gainis_loop,is_multiple,is_reciprocal,weight_rank,from_communityandto_community, andselect_edges(by = )` accepts the endpoint metrics.
filter_nodes(),
select_nodes(), select_edges(), to_df(), to_network() or
to_igraph(). network_to_igraph() built the graph from the edge list, so
every node after the last edge endpoint disappeared and the label assignment
then failed.as_cograph() re-detected the result
as directed and every downstream consumer saw half the strength.set_edges() and set_nodes() rebuild the stored weight matrix, so
to_matrix() can no longer return the pre-edit network, and set_edges()
keeps extra edge columns.session, time, ...) survive
as_cograph() and are usable in filter expressions, as documented.keep_format = TRUE returns an empty object of the
input type instead of erroring with "No such edge attribute".keep_format = TRUE on a tna model returns a rebuilt tna model.to_matrix() and to_data_frame() no longer route through igraph.filter_edges(),
select_edges() and friends now keep every node, matching
igraph::delete_edges() and tidygraph, and warn
(cograph_isolates_created) when the filter left a node without edges. Use
remove_isolates(), or keep_isolates = FALSE, for the old behaviour..keep_isolates and .keep_edges are renamed to keep_isolates and
keep_edges. The dotted names still work and warn.cograph_bad_selection rather than
warnings that return something plausible: unknown node names, out-of-range
or fractional indices, a between that is not two node sets, an unknown
measure in by.filter_nodes() computes only the measures its expression names. It used to
compute all twelve, including HITS, on every call.Matrix is no longer imported (nothing used it after the port); the
committed test-network fixtures are now tracked; two tests no longer
need withr.The whole centrality surface (centrality() and its 191 centrality_*
wrappers, edge_centrality(), centralization(), group_centrality(),
dispersion(), estrada_index(), trophic_incoherence(), and the
centrality vocabulary of the wrangling verbs) now computes on cograph's own
kernels. Every input is turned once into a dense, labelled weight matrix
(R/kernels-graph.R); no igraph object is built. igraph stays in Suggests
for input conversion of igraph objects and for community detection and
layouts.
Equivalence was verified measure by measure against the igraph-backed
implementation on a golden corpus of 62 unsigned networks (30 real, 32
synthetic edge cases) under every mode and weighting, at relative tolerance
sqrt(.Machine$double.eps), with the following documented exceptions.
flow_betweenness still needs igraph and raises cograph_needs_igraph
when it is not installed.A'A) is not primitive are not unique; igraph returned a random member
of the eigenspace and sometimes failed to converge. The native kernels
are deterministic and verified by eigen-residual tests instead.any_multiple() had been called on the object, which the old
centrality() always did.alpha now computes on weighted graphs with self-loops; igraph 2.3.3
errored there.1e-18 gives identical betweenness. igraph's
absolute epsilon does not.simplify = FALSE or "none" now sum them (the old adapters
summed them when assembling an adjacency anyway). Only degree on a
multigraph changes, counting a parallel pair once.cograph_directed_unsupported.New internal kernels: edge betweenness, Barrat transitivity, average neighbour degree, articulation points, bridges, ego masks, and a loop-preserving coreness matching igraph's convention.
Test infrastructure: a versioned corpus of test networks under
tests/testthat/networks/ (32 real, 35 degenerate, plus local tiers), a
golden-file comparison harness, a scale-invariance property test, and a CI
job that runs the centrality tests with igraph uninstalled.
centrality_trust_pagerank(), Sheng, Zhu, Wang, Wang and Hou's
trust-PageRank. PageRank's even split of a node's score among its
neighbours is replaced by a trust-value mixing a similarity ratio with
a degree ratio: T(i,j) = (1-k) s(i,j)/sum_{l in N_j} s(j,l) + k d_i/sum_{l in N_j} d_l, fed to
TPR_i = (1-alpha)/n + alpha sum_{j in N_i} T(i,j) TPR_j, with s the
fixed point of SimRank restricted to the lines of the graph. New
parameters tpr_alpha (0.85), tpr_k (0.85), tpr_decay (1),
tpr_tol (1e-14) and tpr_max_iter (1000).doi:10.3390/a13110280, which is what cograph implements.
A reader following the Zoo's reference lands on a different measure.0.1
that initialises the lines. The base case s(a,a) = 1 is then the only
inhomogeneous term, and it reaches a line exactly through the triangles
that line carries, so the recursion contracts even at the source's
C = 1. Pinning non-adjacent pairs at 0.1 instead reproduces neither
published fixture.S_v column is the sum of the paper's own rounded cells rather than
the rounded sum, a convention node 5 alone separates. Table 5 on page
10 prints two top-ten rankings: the karate club's reproduces in order
at all ten positions, and the kite's up to three exact ties forced by
its own automorphism group.NA there rather than inventing one. The recursion is then
homogeneous, its least non-negative fixed point is zero, and the
similarity ratio is 0/0. Every path, tree, star, even cycle and
complete bipartite graph is in that class, and so is the Petersen
graph. Unlike centrality_dil() and centrality_lhc(), the quotient
is not determined by its numerator, so the 1/d_j fallback would
silently turn the measure into a degree-ratio PageRank over the whole
class; this follows centrality_iec() instead. An isolate is not in
the class and keeps the bare (1 - alpha)/n.tpr_max_iter raises
cograph_no_converge.C does not matter is
false for the converged recursion. It holds for a homogeneous
recursion; the base case makes this one affine, so C enters the
resolvent. Moving it from 1 to 0.5 moves the karate club's similarity
ratios by up to 0.141.type = "all" excludes it unless asked for.centrality_dil(), Liu, Xiong, Shi, Shi and Wang's degree and
importance of lines. A node's degree is corrected by the share it can
claim of the importance of the lines touching it:
I_e(m,n) = (k_m - p - 1)(k_n - p - 1) / (p/2 + 1) is the importance of
a line, p being the number of triangles carrying it;
W(i,j) = I_e(i,j) (k_i - 1)/(k_i + k_j - 2) is the endpoint's share of
it; and DIL(i) = k_i + sum over the open neighbourhood of W(i,j). No
new parameter.lambda is p/2 + 1, and a text-layer reading gets it wrong. The
stacked fraction extracts from the published PDF as lambda = 2p + 1,
in the original as much as in the Almasi and Hu (2019) reproduction of
it. The equations were read from 300 dpi page images, and the paper's
own worked example settles it in printed prose: at p = 1 it writes
lambda = 1/2 + 1 = 1.5 and I_e45 = 8/3.I_e45 = 9 and 8/3; figure 2 on page 211
prints four edge importances and then L_v2 = 26/9 and
L_v5 = 52/15; and table 3 on page 217 prints a value for all 21 nodes
of the ARPA network. The figure-1 and figure-2 values match as exact
fractions with no tolerance at all, and all 21 table-3 values match
under the paper's own four-decimal rounding, in the descending order it
prints them. The edge list read off figure 6 is corroborated by the
paper's own degree column at all 21 nodes.k_i + k_j - 2 vanishes only when
both degrees are one, and there the line's importance is exactly zero
as well, so every admissible share of it gives the same contribution.
The share is written as zero with the test taken before the division,
and both nodes score 1. The source is silent on the case; the choice
follows centrality_lhc(), not centrality_iec(), and the help page
says why.p can never
exceed either endpoint's degree minus one, and the network's total
excess over degree is exactly the total importance of its lines — the
two endpoint shares of a line sum to one. Raw scores are
component-local: the measure never reaches past a node's second
neighbours.type = "all" includes it.centrality_iec(), Friedkin's immediate effects centrality. A
node is scored by how quickly the rest of the network's influence
reaches it: c_IEC(j) = (n - 1) / sum_{i != j} m_ij, where
M = (I - Z + E Z_dg) diag(1/c) is the mean first passage time matrix
of the influence chain W = A / rowSums(A), c is W's left
eigenvector at eigenvalue one and Z = (I - W + 1 c')^-1 is the
fundamental matrix. The sum runs down column j, so a high score
marks a node the network reaches fast. No new parameter.a_ii = 1 before row-normalising, a
construction it attributes to French (1956) and states twice on page
1494. Its footnote 10 gives the reason — a strong network with
w_ii > 0 must be regular, meaning aperiodic — and its footnote 9 the
periodic counterexample a zero diagonal admits.centrality_markov(), and the difference is not
a rescaling. The package's own candidate ledger recorded for several
rounds that the two differed only in an n versus n - 1 numerator.
That is wrong: markov also omits the self-loop. The numerator is a
constant factor and cannot reorder anything; the self-loop can and does.
On the five-node star markov gives 1.25, 0.161, ... where iec
gives 0.5, 0.08, ..., and the two rank the nodes differently on 2 of
the 21 connected five-node graphs. markov is unchanged; the two ship
side by side, and each help page now points at the other.c is undetermined and diag(1/c) undefined. Worse,
the closed form does not announce the failure: for i and j in
different classes z_ij = 0, and equation (11) returns the entirely
finite m_ij = z_jj / c_j where the true mean first passage time is
infinite. Rather than publish a finite wrong number, iec tests the
chain by boolean closure before any solve and returns NA at every node
with a cograph_undefined_measure warning. In practice the requirement
is a connected undirected graph or a strongly connected digraph. A
one-node graph is NA too, equation (20) dividing by n - 1 = 0; an
empty graph returns no scores.W is a matrix of directed influence and row
i is what actor i attends to; there is no in/out/all variant, so
mode, cutoff and invert_weights are ignored. Weights are dropped
deliberately — a_ii = 1 is calibrated against a_ij = 1, so a
rescaling of the weights would silently re-weight each actor's
self-reliance against the network — and loops in the input are absorbed
by the mandated diagonal while parallel edges collapse. The measure is
marked costly and is therefore held back from type = "all".0.1875 the paper prints as .187. The
105 printed values of the companion TEC column reproduce as well, three
of them needing the same truncation rule.centrality_lhc(), the Lhc index of Wang, Yang, Liu and Ma. A
node's influence is C(v) = sum_{u in Phi(v)} k_u (1 + TP(u)) / d^2(uv), a sum over the ball Phi(v) of radius lhc_radius in which
each member contributes its degree, inflated by its share of the
network's triangles, discounted by the square of its distance; the index
is Lhc(v) = sum_{w in tau(v)} C(w), that influence summed over the
open neighbourhood. The triangle share is TP(u) = NTS(u) / TNTS with
NTS(u) the number of triangles containing u. New parameter
lhc_radius.TNTS, not the number of triangles, and the paper
settles it rather than the Zoo. Immediately after defining TNTS the
source writes that "the total number of triangle structure exists in the
network are 1/3 * TNTS", so TNTS = 3 * Delta and the share sums to
exactly one over the nodes. Entry 2.221 of the Centrality Zoo
transcribes the structure of both equations correctly but names the
denominator "Delta, the total number of triangular structures in the
network", which read literally is three times too small: on the
Krackhardt kite that reading scores node 1 at 125.45 where the paper
gives 100.15. cograph follows the paper.lhc_radius is the source's own parameter, exposed with the source's
default. The paper writes it d, states on page 4 that it "is set to be
2", and sweeps it in section 3 over eleven real networks, reporting "the
optimal value of d is about 2-3". At lhc_radius = 1 the ball collapses
to the neighbours; at or above the graph's radius the score stops
moving. The domain is a whole number of at least one and anything else
raises cograph_bad_parameter.TNTS = 0, making
TP(u) a 0/0 at every node, and the source never mentions the case.
Since TNTS is a sum of nonnegative counts it vanishes exactly when
every numerator does, so there is no share to distribute: TP is
written as zero and the index reduces to the pure
degree-over-squared-distance sum. The test is made before any division,
so no 0/0 is evaluated.TNTS is a global sum, so
attaching a disconnected component that carries a triangle rescales
every score, while attaching one with no triangle -- an isolate
included -- changes nothing. An isolate scores zero because its
neighbourhood is empty; a singleton and every node of an edgeless graph
score zero for the same reason.mode, cutoff and
invert_weights are ignored, and normalized = TRUE max-scales, the
source stating no normalization.centrality_rsp_betweenness(), the simple randomized shortest
paths betweenness of Kivimaki, Lebichot, Saramaki and Saerens. A
Boltzmann distribution over the absorbing walks from s to t is
tilted by an inverse temperature away from the unbiased random walk and
towards low-cost walks, and a node scores the expected number of visits
it receives summed over every ordered source-target pair,
bet_i = sum_{s,t} (z_si / z_st - z_ti / z_tt) z_it with
Z = (I - W)^-1 and W = (D^-1 A) o exp(-beta C). New parameters
rsp_beta and rsp_cost.Z, and Algorithm 1 takes a strongly connected graph
as its input, but the text below equation (9) settles the general case:
the derivation "holds only if there exists a path from s to t.
Otherwise, naturally, eta_ij(s, t) = 0." cograph evaluates the closed
form masked by reachability, which reproduces equation (15) to machine
precision whenever the graph is strongly connected and applies the
source's own zero rule when it is not.D^-1
is undefined there, so cograph writes that row of P^ref as zero, which
is the paper's own killed random walk read at a node where the walker
dies at once; Z then has z_ii = 1 and the arithmetic gives exactly
1 - 1 = 0. An isolate, a singleton graph and every node of an edgeless
graph score zero for that reason. NetworkToolbox::rspbc() raises an
error on such input and plot-side current_flow_betweenness returns
NA on disconnected input; this measure can answer where those cannot,
because (I - W) stays nonsingular whatever the connectivity.NetworkToolbox::rspbc() masks only the reciprocal half of the term and
leaves the n Diag(Z') half counting every source, so the two agree
exactly on strongly connected input and part company on a disconnected
graph. That function additionally rounds to zero decimals and shifts so
its minimum is one, post-processing that is nowhere in the paper and is
not copied here.rsp_beta defaults to 0.01, which is not the source's number: the
paper fixes no default and treats it as a modelling choice. 0.01 is the
value NetworkToolbox::rspbc() recommends, adopted so the two are
comparable out of the box; it sits near the random-walk end, so raise it
towards 1 and beyond to move the reading towards shortest paths. The
domain is rsp_beta > 0 and values outside it raise
cograph_bad_parameter.rsp_cost chooses how a weight becomes a cost, which the source leaves
free: "inverse" (default) sets C = 1 / w, reading a weight as an
affinity, as the CRAN reference hard-codes; "weight" sets C = w,
reading it as a distance. The two coincide on a binary graph. Negative
and non-finite weights raise cograph_bad_input, Algorithm 1 requiring
a non-negative cost matrix.mode, so a reversed
input generally scores differently. Marked costly: one dense n x n
inverse, which the source itself calls the computational bottleneck at
O(n^3) time and O(n^2) memory.beta approaches zero from above on an
undirected network; it holds, and does so at first order in beta.centrality_hcc() and centrality_ehcc(), the hybrid
characteristic centrality of Liu and Zheng and its extension. HCC adds
two normalised halves: the extended degree
delta * k + (1 - delta) * sum of the neighbours' degrees over its
largest value, and the round in which an E-shell peel removes the node
over the number of rounds. EHCC is the closed-neighbourhood sum of HCC,
the focal node counted once. New parameter hcc_delta, default the
source's 0.5.S_p = arg max while the same sentence calls S_p "the set of
minimum nodes" and the paper's table 2 heads its column "Minimum
extended degree" with the increasing values 2, 2.5, 3, 4.5, 5, 6. The
minimum reading reproduces every printed row; the literal maximum peel
deletes a different set first and finishes in four rounds instead of
six.k^ex and k^ex_max are the
original-graph values. The paper's node d settles it: its original 9.5
gives the printed 1.86, its residual 6 gives 1.55.k^ex_max and pos_max are
single global constants, so a disconnected addition rescales the two
halves independently rather than by one common factor.EHCC(g) = 10.01 for its figure 1, where the exact value 661/66 is
10.015151… and rounds to 10.02. That printed cell is the truncation, but
six other printed cells require rounding, so no single convention
reproduces all twenty table 3 entries. The other 85 printed values
reproduce.hcc_delta in [0, 1], so it
always leaves in the first round; an edgeless graph makes equation (4)'s
first term 0/0, written as zero, so every node of an edgeless graph — a
singleton included — scores exactly 1. hcc_delta outside the source's
stated [0, 1] raises cograph_bad_parameter rather than being
extended.centrality_ked(), the KED method of Chen, Xiao, Zeng and Zhang:
the degree, weighted by one plus the normalised entropy of the
neighbours' degrees, times exp(K / N) for the neighbour-degree sum K
and the whole graph's order N. Two nodes with the same degree and the
same number of second neighbours are separated by how evenly their
neighbours carry the onward paths. It takes no parameters: equation (6)
is a bare product.k outcomes, so the base cancels top and bottom. A full
base-ten reading gives identical scores, which is asserted to 60 digits
on every verification fixture.N is
the vertex count of the whole network, and exp(K / N) shrinks a large
neighbour-degree sum more than a small one, so adding a disconnected
component is not a rescaling: on a seven-node example in the tests, one
extra isolate swaps two nodes' places.1 <= D <= e is not general. It
needs K <= N, which holds on the sparse toy networks of its figure 1 and
on only 1,598 of the 5,532 verification fixtures; every node of the
five-clique has K = 16 against N = 5. cograph implements the formula,
not the range claim, and raises an error rather than returning Inf if
the exponent ever leaves the range of exp().1 + from E, divides K by the largest cluster degree instead of
by N, and adds tunable exponents that appear nowhere in the paper. On
the source's own figure 1 that reading gives 13.5914 and 6.5672 where the
paper prints 25.9187 and 19.2212. cograph implements the paper and offers
no Zoo variant.0/0 for its normalised entropy
and takes zero, and an isolate scores zero.centrality_lnc(), the local neighbor contribution of Dai, Wang,
Sheng, Sun, Khawaja, Ullah, Dejene and Duan: the chance that a node
picking a neighbour at random picks a given one and misses the rest,
scaled by its degree, multiplied by the sum of its neighbours' degrees
weighted by their degree centralities. It takes no parameters, which the
source advertises as one of its contributions.1 / (n - 1) comes
from the source's degree centrality, where n counts every node in the
network rather than in the component, so adding a disconnected component
multiplies every score by (n - 1) / (n' - 1). The ranking is untouched;
the raw values are not.j = 1 to k, and k is
described three incompatible ways: the prose calls it the number of
nearest and next nearest neighbours, Algorithm 1 sets it to the degree,
and equation (5) read literally carries one factor of the degree too many
and returns 6.75 where the paper prints 1.6875. Inverting each of the
eleven printed influences gives k = d^2 in (4) and k = d in (5), the
reading implemented here; the alternative literal split of the two factors
gives the same product, so the measure itself is unambiguous.1 / d, is undefined, and no result is silently NaN.centrality_ira(), the iterative resource allocation of Ren, Zeng,
Chen, Liao and Liu: every node starts with one unit of resource and hands
it repeatedly to its neighbours in proportion to the receiver's centrality
until the amounts stop moving. ira_mass, ira_alpha, ira_tol and
ira_max_iter expose the source's theta, alpha, epsilon and
iteration bound. Every non-empty column of the allocation matrix sums to
one, so a component's scores sum to its vertex count.centrality_iira(), the improved variant of Zhong, Liu and Shang,
which scales each share by 1 - (1 - beta)^k. That factor is strictly
below one, so the resource decays geometrically; the source fixes the step
count instead of a tolerance. iira_beta and iira_steps default to the
source's 0.2 and 50, the raw I(50) is returned so the printed example is
reproducible, and normalized = TRUE max-scales it. Raw IIRA scores from
different connected components are on different exponential scales and must
not be compared.ira reports non-convergence instead of hiding it. The allocation
matrix is a reversible walk, so on a bipartite component it has an
eigenvalue of exactly -1 whose coefficient in the all-ones start is the
difference between the two class sizes. When those differ, the resource
settles into a period-two cycle and no tolerance is ever met: the
three-star alternates for ever between 3, 1/3, 1/3, 1/3 and 1, 1, 1, 1.
centrality_ira() then stops at ira_max_iter, raises a classed
cograph_no_converge warning naming the largest remaining change, and
returns that parity-dependent iterate. Neither source mentions this case.15/8 up to 1.88, while the iteration
approaches that limit from below and returns 1.8749998, which rounds to
1.87. No author software exists for either measure.centrality_neighbor_distance(), the neighborhood centrality of Liu,
Tang, Zhou and Do: a benchmark centrality plus its decayed sums over the
non-backtracking walks that leave the node. nd_order, nd_decay and
nd_mass expose the source's n, a and theta, and the defaults
(degree, two steps, 0.2) are the setting the Centrality Zoo calls neighbor
distance centrality.centrality_relative_entropy(), which turns several indexes into
discrete distributions and returns the unit-sum distribution with the
smallest total relative entropy to all of them: the normalised geometric
mean of the index distributions.re_indexes chooses the constituents from six the source both defines
and gives an evaluating direction (degree, closeness, betweenness,
constraint, and the two post-deletion destructiveness indexes
n_components and largest_component); re_negative overrides which of
them are read as "smaller is more important". The default reproduces the
published Kite study's four-index column, and all three of its printed
integrated columns are reproduced.cograph_undefined_index error rather than
returning zeros.centrality() no longer lets one undefined measure end a whole tier. A
measure named in measures or include still raises, but when
type = "basic", "extended" or "all" supplied it, an undefined result
becomes a cograph_undefined_measure warning and an NA column, matching
what the community-partition measures already do without membership.centrality_dkgm(), whose mass is the degree k-shell index: the
original degree plus a shell number refined by the stage at which the
node left that shell during the k-shell peeling.dkgm_radius = 2 matches the paper's printed nine-node example,
which the implementation reproduces along with its removal stages,
improved shell indices and DK values. "auto" applies the paper's own
half-mean-distance rule with cograph rounding.k versus at most k, the one-shell placement
of isolates, and the single global stage denominator, which makes raw
scores depend on disconnected components.centrality_mixed_gravity() and
centrality_extended_mixed_gravity(), using focal core numbers and partner
degrees, with an optional outer sum over immediate neighbors.centrality_mcgm() with the published adaptive coefficient and
default radius two, plus explicit radius and coefficient overrides.centrality_spectralrank() with optional scalar or node-specific
diagonal priors. Scores use outgoing neighbors in an augmented graph.centrality_controlrank(), the smallest eigenvalue of each grounded
symmetric row-Laplacian, retaining original degrees after node deletion.centrality_map_equation() for fixed one-level or leaf-module
partitions, with recorded node and unrecorded link teleportation.paper includes module-exit flow as
defined in the equations; infomap reproduces the author's visit-only
implementation and published table. Both conventions have independent
numerical verification. Stable arithmetic retains extremely small scores.centrality_ninl() with the full finite iteration family (default
three) and optional radius overrides. The initial score sums degrees
within the ceiling of average path length, then propagates through neighbors.centrality_localized_bridging() and
centrality_extended_local_bridging(): one-hop and two-hop ego betweenness
multiplied by the original-graph bridging coefficient. Uses simple,
undirected, unweighted topology; the two-hop variant is marked costly.local_bridging retains
its existing inverse-degree product, which is a different score.centrality_beta_measure() with positive and negative directed
variants. Successors share one unit equally among their predecessors;
the negative variant reverses the graph. Loops and duplicate arcs are
removed, weights ignored, and isolates score zero.centrality_expected_force() and centrality_modified_expected_force()
from Lawyer's two-event definition, with explicit sequence multiplicity,
boundary-edge counting, direction and exhausted-force conventions.expected computes neighbor-degree
sums and remains available under that definition. ExF now maps to
expected_force; the original ExFm candidate maps to the modified function.centrality_proximal_betweenness() with source, target, sum and
union variants from Brandes. Uses directed unweighted shortest paths,
excludes endpoints, and explicitly preserves ordered-pair raw scaling.centrality_bridging_capital() with a finite walk horizon and optional
source-destination information values. It follows Jackson's single-entry
deletion definition using transmission probabilities between zero and one.centrality_coleman_theil(), measuring concentration of Burt's dyadic
constraints using mutual tie weights. Follows the author's explicit
isolate-zero and single-contact-one conventions, with organizational
multipliers fixed at one.centrality_x_degree(), counting four-edge nonbacktracking walks
centered at each node using original neighbor excess degrees. It uses
the simple undirected unweighted skeleton and supports maximum normalization.centrality_linerank(), using directed line-graph walks or the
ordinary undirected line graph clarified by Kosa et al. (2015).linerank_aggregation choices.
Supports loops and remaining parallel edge states with documented
conventions, uniform dangling redistribution and damping in [0,1).centrality_random_walk_decay() with rwd_decay and optional
rwd_node_weights. Scores sum discounted first-arrival probabilities,
including the starting node. Retains directed flow, weighted transitions
and loops; sinks terminate the walk without restarting it elsewhere.centrality_graph_regularization() with finite nonnegative
grc_gamma, default one. Computes reciprocal diagonal entries of the
inverse regularized weighted Laplacian. Isolates and zero regularization
score one; disconnected components are independent before normalization.centrality_adaptive_leaderrank(), weighting every destination by
its original open-neighborhood H-index and adding a ground node with
H-index one. Retains source total mass N and omits the ground score
without redistribution. Zero H-indices receive zero stationary scores;
an all-zero H-index vector yields NaN.alr_h_mode selects the H-index convention: all (default), out or in.
The paper leaves its directed H-index choice unspecified; these choices
are explicit cograph conventions, while resource flow retains input arcs.
The focal node is excluded from the H-index calculation.centrality_weighted_leaderrank() with finite wlr_alpha, default
one. Original directed arcs retain unit weight and ground-to-node weights
depend on original in-degree. Input edge weights are ignored. Undirected
edges are represented as opposite arcs.centrality_global_structure() (GSM),
centrality_hybrid_global_structure() (H-GSM) and
centrality_improved_global_structure() (IGSM). GSM uses coreness;
H-GSM combines degree and coreness; IGSM uses degree. The latter two
use their published adaptive distance exponents. All three use the simple
undirected skeleton, with explicit zero contributions for unreachable
partners and global size/means that include isolates.centrality_exogenous() with degree, betweenness and adjusted
reverse-closeness bases. Measures the contribution to other nodes'
centrality when the focal node is deleted. Supports directed base
directions and retains negative betweenness contributions.centrality_improved_closeness() with icc_alpha in [0,1],
default 0.2, following Luan et al.'s shortest-path multiplicity formula.
Uses the simple undirected skeleton; alpha zero recovers ordinary
normalized closeness on connected graphs. Disconnected graphs and
singletons score zero under explicit cograph conventions.centrality_cda() with cda_alpha, default 0.5. Returns Wang
et al.'s propagation-capability score using degree, strength, Barrat
clustering and weighted neighbor contributions. The full weighted
calculation retains original weight units and a global maximum weight.centrality_extended_coreness(), the two-step aggregation of
original core numbers, and centrality_extended_gravity(), the sum of
immediate neighbors' raw k-shell gravity scores. Both use the simple
undirected skeleton and assign isolates zero.gravity_radius, default three as in Ma et al.
The radius applies around each neighbor before the outer sum. Independent
checks use NetworkX cores and distances and an exhaustive core-number
oracle on small graphs, covering multiple radii and input projections.centrality_resistance_curvature(), implementing Devriendt and
Lambiotte's conductance-weighted node curvature component by component.
Raw scores can be negative; isolates score one. Directed projection,
zero conductances, normalization and numerical limits are documented.centrality_dynamics_sensitive() with ds_beta, ds_mu and
ds_steps, including the full recovery-rate family from Liu et al.
Recovery rate one recovers the finite-diffusion formula listed by Zoo;
recovery zero supports the paper's SI case. These are linearized scores,
with their interpretation and numerical limits documented explicitly.centrality_malatya(), the static degree-ratio sum. On nonisolated
nodes it is exactly the reciprocal of the bridging coefficient. Both
additions use the simple undirected skeleton and assign isolates zero.centrality_diffusion_centrality() with diffusion_q and
diffusion_steps. It evaluates Banerjee et al.'s finite weighted walk
sum, separate from existing diffusion degree and the TNA power series.
Directed edges follow their outgoing orientation; weights, loops and
repeated walks are supported. Probability interpretation and default
parameter choices are documented explicitly.centrality_dynamical_importance(): relative spectral-radius loss
on node deletion, recomputed directly. It supports nonnegative directed
weighted graphs, removes loops and returns NaN when the original radius
is zero. This costly measure is held back from the default all tier.centrality_volume() with volume_radius and centrality_mcc(),
also available through centrality(measures = ). Both use the simple
undirected skeleton. Volume sums original degrees over closed hop
neighbourhoods; MCC sums factorial contributions from maximal cliques.type = "all" tier
because clique enumeration has exponential worst-case cost.centrality_truss(), centrality_mdd(),
centrality_bridging_coefficient(), centrality_godfather() and
centrality_support(), also available through centrality(measures = ).
They use the simple undirected skeleton. mdd_lambda controls the
exhausted-degree weight; truss numbers use the k-2 triangle convention.semilocal measure on simple
undirected graphs. Corrected parameterized Zoo lookup calls and replaced
unsupported equivalence claims based solely on rank correlation.docs/zoo/parameter_candidate_status.csv.Five measures chosen from the Centrality Zoo correlation study (Shvydun
2025; 349 measures, 648 ICON networks, average Kendall tau) as the ones
with the lowest rank redundancy against what centrality() already
computed (maximum tau with any existing measure in parentheses). All are
implemented in base R on matrices (R/kernels-batch7.R) with thin igraph
glue and one exported verb each (R/centrality-batch7.R).
centrality_distance_entropy() (tau 0.30) — Stella & De Domenico
(2018). Normalised Shannon entropy of a node's hop-distance profile;
closeness is the mean of that profile, this is its spread. The
normaliser is log(M - m + 1) so a uniform profile scores exactly 1
(the printed formula's log(M - m) is undefined for two distances).centrality_local_dimension() (tau 0.50) — Pu et al. (2014). OLS slope
of ln B(r) on ln r, ball including the centre. Reproduces the worked
example of Wen & Deng (2019) exactly (0.9231). Lower = more influential.centrality_local_information_dimension() (tau 0.38) — Wen & Deng
(2020). Entropy-weighted local dimension over boxes up to half the
eccentricity. Higher = more influential. Single-box nodes use the
paper's discretised derivative.centrality_modularity_vitality() (tau 0.40) — Magelinski, Bartulovic &
Carley (2021). Q(G, C) - Q(G - i, C \ i) under a fixed partition;
positive = community hub, negative = bridge. Closed-form vectorised
update (one matrix product for all nodes); matches brute-force
igraph::modularity() after deletion on random directed, undirected and
weighted graphs. Requires membership; wrong-length input raises
cograph_bad_membership.centrality_neighborhood_connectivity() (tau 0.64) — Maslov & Sneppen
(2002). Mean neighbour degree, isolates 0; equals
igraph::knn(weights = NA), with mode support.The three distance-scaling measures are hop-count measures and ignore edge
weights (as gravity and collective_influence already do); they share one
unweighted all-pairs matrix per centrality() call.
The twelve measures the batch 7 lookup listed as "on the way" are now
implemented (thirteen centrality() measures), each verified against an
exact brute-force definition or the source paper's own numbers, plus an
independent Python reference written from the paper (kept in
local_testing_and_equivalence/batch8/, not shipped). Base-R kernels in
R/kernels-batch8.R, verbs in R/centrality-batch8.R.
centrality_shapley_game1(), _game2(), _game3() — Michalak et al.
(2013) closed-form Shapley values of the one-hop, shapley_k-neighbour
and shapley_cutoff-hop coverage games. Equal to exact Shapley values from
full coalition enumeration on random graphs of up to 8 nodes (with
isolates, loops, several components), directed extension included.centrality_access_information(), centrality_hide_information() —
Rosvall et al. (2005) / Sneppen et al. (2005) search information, averaged
from and to each node; shortest-path DAG accumulation, no path
enumeration. Equal to explicit all-shortest-paths enumeration; reproduces
the papers' star and complete-bipartite values. Disconnected graphs
average over each node's reachable set.centrality_rumor() — Shah & Zaman (2011) rumor centrality on each node's
BFS tree, log scale. exp() of it equals brute-force spreading-order
counts on trees; reproduces the paper's Fig. 5 (8, 12, 2, 3, 3).centrality_community_hub_bridge() — Ghalmane, El Hassouni & Cherifi
(2019) raw hub-bridge score (needs membership; cograph_bad_membership
on bad input).centrality_entropy_variation(of = "degree" | "betweenness") — Ai (2017)
signed entropy drop on node deletion; degree variant in closed form.
Equal to the author's own R code path to 1e-15 and to the paper's Table 2
quantiles on its 4234-node network.centrality_s_shell() — Liu, Tang, Do & Hui (2017) strength-based shell
index with asymmetric topological weights (s_shell_a, default 0.5). The
Zoo's "s-shell index" is this measure, not the Eidsaa-Almaas s-core.
Shells verified against the maximal-subgraph definition; a = 0 gives
k-core dense ranks.centrality_degree_discount(), centrality_single_discount() — Chen,
Wang & Yang (2009) greedy seed-selection orders (discount_p), scored
like voterank (first selected = 1).centrality_ncvoterank() — Kumar & Panda (2020) neighbourhood-coreness
VoteRank (ncvote_theta). The original article could not be obtained;
the definition follows the Zoo encyclopedia and three restatements, and
the coreness normalisation (by its maximum) is a documented choice. Its
VoteRank limit reproduces networkx.voterank.The Centrality Zoo lookup article and coverage document were regenerated: 82 Zoo measures are now available in cograph and nothing is "on the way".
centrality() tiers, and a catalogue of the measurestype = "all" no longer runs the measures whose cost grows steeply
with network size. Four are held back: infection, two_way_rw,
node_contraction_improved and entropy_variation_betweenness. On an
81-node graph infection alone took 611 seconds while every other
measure together took about five, so a single type = "all" call could
take minutes by accident. type = "basic" (the default) and
type = "extended" are unchanged.include argument puts them back: include = "costly" for all
four, or name the ones you want. Naming a measure in measures =
always computes it whatever its cost, so nothing became unreachable.list_centralities() (new export) is a tidy table of every measure
with the facts you need before reading a column: orientation (which end
of the scale marks a prominent node), mode_aware, needs_membership,
uses_weights and costly. Twelve measures are oriented so that a low
value marks the more central node, including eccentricity,
constraint, heatmap and the local-dimension family; sorting their
column the usual way puts the periphery on top.
list_centralities(orientation = "lower") lists them. The measure lists
now live in one place that both centrality() and list_centralities()
read, and a test asserts the two agree.katz now warns with a cograph_katz_diverged condition when
katz_alpha is too large for the graph. Katz converges only for
alpha < 1 / rho(A); above it the linear solve still returns numbers,
but they are not Katz scores and can be negative. The default 0.1 is
invalid on any graph whose spectral radius exceeds 10, which includes
many weighted networks. The check costs nothing on the happy path and
the warning names the valid bound.alpha and power now raise a classed cograph_singular_system error
instead of surfacing a bare LU factorization message from igraph when
I - alpha A is singular.?centrality_dmnc gains a "Divergence from centiserve" section.
centiserve::dmnc() counts the largest component's edges in the wrong
index space: its membership vector indexes the neighbourhood subgraph
but is used to subset the original graph. The two disagree on 14 of the
34 karate nodes even at a matched epsilon, and reproducing that indexing
exactly reproduces centiserve's output. cograph counts the edges of the
component it actually found. The catalogue's equivalence claim was
corrected.Twenty more measures (R/kernels-batch9.R, R/centrality-batch9.R),
each researched from its source paper by a dedicated agent and verified
against published tables, brute-force definitions and an independent Python
reference (kept in local_testing_and_equivalence/batch9/, not shipped).
membership): centrality_community_based()
(Zhao et al. 2015; reproduces the paper's Table 1 and Tulu et al.'s
Table 1), centrality_comm_centrality() (Gupta, Singh & Cherifi 2016;
comm_r), centrality_community_mediator() (Tulu, Hou & Younas 2018;
base-2 entropy reproduces its Table 1).centrality_local_dimension_fixed() (Silva & Costa
2013; ld_radius), centrality_fuzzy_local_dimension() (Wen & Jiang
2019; reproduces its kite Table 1 and karate top ten in order),
centrality_local_volume_dimension() (Li & Deng 2021; definition from the
authors' later preprint, flagged).centrality_wvoterank() (Sun et al. 2019; reproduces
all sixty numbers of its Figure 1), centrality_enrenew() (Guo et al.
2020; reproduces its Figure 1; enrenew_depth), centrality_voterank_plus()
(Liu et al. 2021; matches the authors' code; voterank_lambda).centrality_node_contraction() and centrality_node_contraction_improved()
(Tan, Wu & Deng 2006; Wang et al. 2011; reproduce Table 1 and the path
closed forms; contraction_rho). The Zoo entry's "removal" wording is
wrong; the sources contract.centrality_two_way_rw() (Curado et al. 2022; reproduces the paper's toy
example including every fraction; O(n^4)).centrality_heatmap() (Duron 2020; reproduces Table 1;
lower = more central), centrality_flow_coefficient() (Honey et al. 2007,
BCT form; equals one minus clustering on undirected graphs),
centrality_local_entropy() (Nie et al. 2016), centrality_weighted_h_index()
(Gao et al. 2019), centrality_redundancy() (Burt 1992; Borgatti's
worked example).centrality_weighted_kshell() (Garas, Schweitzer & Havlin 2012;
wks_alpha, wks_beta; Figure 1 example and Table 2 core size),
centrality_renewed_coreness() (Liu, Tang, Zhou & Do 2015; Figure 1 and
all twelve percentages of its Table S1; the Zoo's transcription is off by
one), centrality_geodesic_kpath() (Borgatti & Everett 2006; paths counted
with multiplicity; centiserve::geokpath counts nodes instead).Not shipped, with reasons recorded in the coverage document: DegreePunishment, improved WVoteRank and local degree dimension (source articles unobtainable, definitions rest on the Zoo alone) and multi-local dimension (a rescaling of local dimension for every q outside (0, 1)).
New pkgdown article Centrality Zoo lookup answers "is the Zoo measure I
want in cograph?": every Zoo measure listed once under Available, Almost
identical (tau >= 0.99, with the cograph measure to use), Near-duplicate
(0.90 <= tau < 0.99), On the way, or Not available. docs/CENTRALITY-ZOO-COVERAGE.md records the full intersection of
the Zoo matrix with cograph: which Zoo measures are rank-identical to an existing
cograph measure (and therefore not worth adding), which are near-duplicates,
and the remaining ranked candidates.
docs/CENTRALITY-CROSS-COVERAGE.md counted, in both directions, what each
R and Python centrality package reaches of the Zoo. The five node measures
that other packages offered and centrality() did not are now implemented
(R/kernels-batch10.R, R/centrality-batch10.R), each verified against
the package whose gap it closes:
centrality_local_efficiency() — Latora & Marchiori (2001). Global
efficiency of the subgraph induced on a node's neighbours, the node
removed. Matches brainGraph::efficiency(type = "local") and the
networkx induced-subgraph form on 25 random graphs. Note that
igraph::local_efficiency() measures those distances through the rest
of the network and so reports larger values; network_local_efficiency()
keeps its igraph parity and its help page now says so.centrality_s_core() — Eidsaa & Almaas (2013). The weighted k-core: the
largest strength threshold whose core still contains the node. Matches
igraph::coreness() on unweighted graphs and a brute-force reading of
the definition on weighted ones. brainGraph::s_core() returns the
peeling round instead, which is documented as a divergence.centrality_fragmentation() — Borgatti (2006). Distance-weighted
fragmentation after deleting the node; matches keyplayer::fragment().
This is the Zoo's "Distance-weighted fragmentation", taking cograph's Zoo
coverage to 103 of 349.centrality_kpath() — Sade (1989). Simple paths of length at most
kpath_len that the node lies on; matches the per-vertex counts of
sna::kpath.census() for k = 2 and 3, directed and undirected.centrality_epc() — Lin et al. (2008), the cytoHubba edge percolated
component. Recovers the exact bond-percolation mean on small graphs and
matches the normalisation of centiserve::epc(). Monte Carlo: pass
epc_seed for a reproducible value; the caller's random stream is
restored.fragmentation and epc join the costly list, so type = "all" holds
them back (include = "costly" or naming them restores them).
Three of the reported gaps turned out not to be gaps at all, and the
document now says so with the evidence: centiserve::closeness.latora() is
cograph's harmonic exactly, centiserve::communibet() is
communicability_betweenness exactly, and brainGraph::efficiency(type = "nodal") is harmonic over n - 1. Two remain unimplemented and are
listed with the reason: the link-community centrality of Kalinka &
Tomancak (its reference package linkcomm is archived, so no equivalence
check is possible) and keyplayer::kpset() (a set search, not a node
measure).
160 of the Zoo's measures sit at a rank correlation of 0.90 or better with
something centrality() already computes
(docs/zoo/parameter_candidates.csv), which suggests many are the same
family at a different setting rather than different ideas. The first five
investigated bear that out, and four new measures plus two arguments cover
seven more Zoo labels (coverage 103 -> 110 of 349):
centrality_length_scaled_betweenness() — Borgatti & Everett (2006),
Brandes (2008) Algorithm 5. Betweenness with each separated pair weighted
by 1 / d(s,t).centrality_delta_betweenness() — Agneessens, Borgatti & Everett (2017).
Betweenness with the pair weight (d - 1)^-delta
(betweenness_delta, default 1); delta = 0 is ordinary betweenness.centrality_ego_betweenness() — Everett & Borgatti (2005). Betweenness
inside the node's own ego network. Close to effective_size, and a test
pins that it is not a function of it.centrality_delta_closeness() — Agneessens et al. (2017) eq. 2.
sum_j d_ij^-delta / (n-1) (closeness_delta, default 1). One exponent
spans the family: delta = 1 is harmonic over n-1, delta = 2 is
harary over n-1, a large delta approaches degree, delta = 0
counts the reachable set.centrality(x, measures = "betweenness", cutoff = k) already computes
it, verified against a brute-force reading of the definition on directed
and undirected graphs. It is now mapped as covered.All four are exact under a brute-force enumeration of weighted geodesic
pairs (local_testing_and_equivalence/batch11/run_equivalence.R, 6 blocks,
6 PASS).
gravity computed a formula that appears in no paper. It summed
deg(j) * kshell(j) / d(i,j)^2 over every reachable j: the product of
two masses on the partner, none on the focal node, and no truncation. Its
help page cited Li et al. (2019), whose formula is k_i k_j / d^2.
Dropping the focal mass changes the ranking, not just the scale. The
measure now computes m_i m_j / d^exponent and gains two arguments:
gravity_mass ("kshell" default, "degree", or "legacy") and
gravity_radius (a number, default 3, "auto" for half the mean
distance, or NULL). The default is now Ma, Ma, Zhang & Wang (2016);
gravity_mass = "degree", gravity_radius = NULL is Li et al.'s gravity
model and gravity_radius = "auto" their local gravity model, so one
measure covers three Zoo labels. gravity returns different values
than in 2.4.7 and earlier; gravity_mass = "legacy" with
gravity_radius = NULL reproduces the old numbers exactly, and a test
pins that.splot() on a netobject with method = "entropy" (Nestimate's
entropy_network()) now receives TNA styling — oval layout, TNA palette,
initial-probability donuts — instead of falling through to psych styling,
so the entropy re-weighting of a transition network renders comparably
with its source.
splot() now accepts label_abbrev, matching mcml: use an integer for
a fixed maximum label length, "auto" for node-count-aware abbreviation,
or NULL to retain full labels.
Producer-supplied splot metadata (x$meta$splot): packages that create
cograph-plottable objects can now attach a small rendering contract —
renderer (resolved through a cograph-maintained whitelist of existing
renderers; arbitrary function names are never evaluated), weight (which
stored edge quantity to render: an edge column keeps the producer's edge
set, a matrix redefines the drawn network from its nonzero cells, aligned
by dimnames), and defaults (renderer arguments). Precedence is always
user arguments > meta$splot$defaults > cograph defaults; on the regular
network path this includes deprecated argument aliases (a user-supplied
positive_color still beats a metadata edge_positive_color default).
See ?splot, section "Producer-Supplied splot Metadata".
Motif subsystem overhaul following an adversarial review (13 findings, each verified against igraph before fixing):
motif_census() mislabeled 13 of the 16 directed triad classes: it
attached MAN-order names to igraph::motifs() output, which is in
igraph's isomorphism-class order (a pure 021U triad was reported as
102). The directed 3-node census now uses igraph::triad_census(),
whose ordering is MAN order. Counts were internally consistent —
z-scores compared like with like — but carried the wrong names.
motifs(), subgraphs() and triad_census() were never affected.empty/edge/wedge/triangle), and self-loops are stripped before
counting (they are not part of any 3-node class)."configuration" null model now uses exact degree-preserving edge
rewiring. The old stub-matching + simplify() silently changed degrees,
and the undirected "vl" sampler errored on graphs with isolates and
restricted the ensemble to connected graphs (two disconnected triangles
got sd = 0, z = 0, p = 1 for an observation the null could never
produce).z = NA when the observation differs from it —
never a silent z = 0. Zero-variance handling was previously
inconsistent across the three engines (forced 0 / sd := 1 / sd := 0.1).
n_random / n_perm below 2 is now an error.motifs(pattern = "all") now actually includes the 003 class; a full
census sums to choose(n, 3) and matches igraph::triad_census()
class by class.subgraphs()) now tests the null
probability that a triple instantiates the row's own MAN type; the
old null counted "any of the six edges exists" (for ten subjects each
with a 3-cycle, expected was 8.33 instead of the correct 3.33).params$significance = FALSE instead of silently
returning results without the promised z/p columns.empty/wedge/triangle names never
matched a MAN row, yielding all-NA statistics).extract_motifs(level = "aggregate") now actually pools the
per-individual transition matrices (previously only metadata changed),
and min_transitions applies per-triad at aggregate level as
documented.motif_census(x, directed = ...) conflicting with an igraph input's
own directedness is now an error instead of relabeling without
converting.edge_method = "percent" thresholds above 1 are percentages and the
comparison is >= as documented (the old > total * 1.5 default could
never classify anything); fractional edge weights are rounded, not
truncated, when building permutation stubs.plot.cograph_motifs(type = "network") forwards ... to the per-motif
igraph plots as documented, and pattern-panel significance decoration
is suppressed for legacy per-triple results where a per-type lookup
would be ambiguous.igraph::triad_census(),
brute-force triple enumeration, and per-actor reference censuses.extract_motifs() individual-level path now retains one row
per (node triple, MAN type) and tests that exact type under the same
weighted stub-matching null as individual-level subgraphs(). It no longer
collapses mixed-type triples to a dominant label or counts any permuted
type as a match.sample(x) length-one ambiguity.
Unit eligibility is frozen from the original loopless weighted activity,
and every positive edge retains support during integerization.sig, printing, and motif plot colors now consistently follow the
empirical permutation decision (p < .05) instead of mixing it with
|z| > 1.96 or |z| > 2 cutoffs. Parallel edges are simplified before
motif_census() so observed and null graphs use the same simple-graph
projection.node1 / node2 / node3
columns in addition to the existing triad display label, so node names
containing " - " remain unambiguous in statistics and plots.M1–M218 naming
sequence covering all igraph isomorphism slots (199 are connected), and
directed= conflicts are rejected consistently for both igraph and
cograph-network inputs.z = NA with the smallest possible empirical p,
emitted when the observation lies outside a zero-variance null) are now
treated as the strongest findings everywhere: they rank first in sorted
results, survive top = n cuts instead of being silently truncated, and
the significance plots report them with a message instead of silently
dropping them (a z bar cannot be drawn for them).plot(x, type = "triads") no longer errors on fractional weighted
counts (e.g. probability matrices at aggregate level); whole numbers
keep the plain n=3 caption.min_transitions no
longer aborts the whole significance run (malformed cells in eligible
units still error loudly).plot_transitions() with a multi-column data frame (the consecutive
multi-step branch) no longer silently drops styling arguments: value_min,
label_color, label_fontface, label_nudge, title_color,
title_fontface, value_halo, value_fontface, value_nudge, and
total_fontface are now forwarded, so both multi-step input forms (list
of matrices, data frame) respond to the same arguments identically.
Motif pattern plots (plot(motifs(x), type = "network"), triad glyph
panels) drew the wrong structure for five of the sixteen MAN triad classes:
the 021D and 021U glyphs were swapped (transposed matrices), the 120D
and 120U glyphs both drew a 120C-isomorphic triad, and the 210 glyph
drew a 120-class triad — so the 120U and 210 structures were never
drawn at all. Only the drawn glyphs were wrong: motif counts,
significance tests, and triad_census() were always computed from a
separate, correct canonical pattern set (verified against
igraph::triad_census()). All sixteen visual patterns are now verified
against igraph by a regression test that also pins the visual and
canonical sets to each other. Thanks to Mengli Zhang for reporting
(reconstructing the structures from the package source and spotting that
three "distinct" glyphs were isomorphic).
plot_bootstrap_forest(), extract_motifs(), motif_census(),
triad_census() and extract_triads() are now listed in the package index
and the reference site. All five are exported and user-facing, but carried
@keywords internal, which hid them from help(package = "cograph") — you
could only find them if you already knew the name. mcml() remains hidden;
it is a deprecated alias of csum(). The n and ... arguments of
print.cograph_motif_analysis() and print.cograph_motifs() are now
documented (previously exempt from checking by the internal keyword).
plot_difference()'s new difference argument moved to the end of the
signature, after combined. It had been inserted before combined, which
shifted the positional argument order relative to the released 2.3.6
signature. Because plot_compare() is function(x, ...) and forwards to
plot_difference(), a caller passing 14 positional arguments had their 14th
silently rebound from combined to difference — making the function treat
x as an already-subtracted matrix, discard y, and draw the wrong network
with no error. difference was introduced after the last CRAN release, so no
released behaviour changes. Named calls were never affected.
plot_bootstrap_forest() and plot_edge_diff_forest() no longer emit a
geom_errorbarh() deprecation warning under ggplot2 4.0.0. The four
horizontal error-bar layers now use
geom_errorbar(orientation = "y"); the rendered output is unchanged.
DESCRIPTION now declares the ggplot2 (>= 3.4.0) requirement the package
already had (it uses the linewidth aesthetic throughout).
plot_edge_diff_forest(layout = "chord") no longer emits a spurious
"row names were found from a short variable and have been discarded"
warning for every node arc it draws.
aggregate_layers(), supra_adjacency(), layer_similarity_matrix() and
plot_motifs() now ship runnable examples. Their \examples sections were
previously commented out (or entirely \dontrun), so they demonstrated
nothing and were never checked. The remaining \dontrun blocks in
motifs() and extract_motifs() are now \donttest, so they are executed
under R CMD check --run-donttest.
detect_communities() with the "louvain" (the default) or "leiden"
method no longer errors on a directed graph. These igraph algorithms
are undirected-only, so detect_communities(tna_object) — a tna model is
always directed — aborted with "Multi-level community detection works for
undirected graphs only". It now collapses the directed edges to undirected
(mean, as the "fast_greedy" method already did) with a message, so the
package's primary object type works with the default algorithm. This also
fixes plot_htna(x, community = "louvain") and other internal callers that
ran community detection on a directed model.
splot() on a Nestimate netdifference (from subtract_networks() /
as_netdifference()) now routes to plot_difference(). Previously it fell
through to the netobject path, which styles by $method — "difference"
is not a TNA-family method, so the asymmetric difference matrix was drawn
with undirected psych styling: no arrowheads and one triangle of each
asymmetric edge pair silently dropped. splot(d, minimum = 3) is now the
straightforward call for a signed difference network.
The netdifference routing excludes net_permutation-family objects:
net_bayes carries both classes and must keep reaching
splot.net_permutation, whose per-edge CI/star arrays are aligned by
Nestimate::plot.net_bayes to that renderer's edge ordering.
plot_difference() on a netdifference now draws the display matrix
($weights — e.g. only the credible differences when coerced with
as_netdifference(b, significant_only = TRUE)), falling back to
$difference_matrix. For subtract_networks() results the two are
identical, so nothing changes there.
plot_permutation() / splot.net_permutation(): the title and layout
defaults now use exact [[ indexing. args$title on a dots-list holding
title_size (but no title) partially matched title_size, so the
default title was silently skipped and no title was drawn — this is why
Nestimate::plot.net_bayes() output had no title. Same latent hazard
fixed for layout / layout_scale.
Edge label templates gain a {p_diff} placeholder (probability of the
difference, for Bayesian comparisons), fed by the new edge_label_p_diff
argument — a per-edge vector or a full node-by-node matrix (the matrix is
indexed at each drawn edge, so it survives minimum/threshold
filtering, and is aligned by dimnames so it may be supplied in any node
order). Filled automatically from $p_difference by
splot.net_permutation and by plot_difference() on Bayesian
netdifference coercions. Template example:
edge_label_template = "{est} (P={p_diff})".
splot.netobject() styling classifier: "edge_betweenness" networks are
now styled by their directedness. A directed edge-betweenness network
previously fell into psych styling — drawn undirected, silently losing one
direction of each asymmetric pair; it now gets the TNA presets with arrows.
An undirected one (from a correlation-family source — Nestimate preserves
the source's directedness) keeps the psych look.
Nestimate producers now use the meta$splot contract: netdifference
objects carry renderer = "difference" and net_bayes carries
renderer = "permutation", so metadata routing (which runs before class
dispatch) selects the renderer; the netdifference class branch remains
as a fallback for objects built without metadata.
plot_difference() no longer hides small difference edges: it defaults
minimum = 0 (the style presets otherwise injected minimum = 0.01,
silently dropping edges with |x - y| < 0.01). An explicit minimum
still wins.
plot_difference(x, y, difference = TRUE) now warns that y is ignored
and uses x as the difference network, instead of silently computing
x - y.
plot_compare() is no longer deprecated — it is a plain alias of
plot_difference(). tna::plot_compare() delegates to it by name
(cograph::plot_compare(x, y, ...)), so deprecating it wrongly made every
tna::plot_compare() call emit a warning; the warning is removed. Both
names call the same implementation; plot_difference() is the preferred
spelling for new cograph code.
plot_difference() also auto-detects a Nestimate netdifference object
(or any object exposing $difference_matrix), alongside tna_comparison.
plot_difference() can now consume a pre-computed difference network:
a tna_comparison object (from tna::compare()) is detected automatically
and its $difference_matrix is plotted, and difference = TRUE treats x
as an already-subtracted matrix/network (no y needed). The two-network
plot_difference(x, y) path is unchanged.splot(x, layout = "target") / layout = "saqr"):
layout_target() ports qgraph's flow() — places one node of
interest (target =) on the left and every other node in columns by
unweighted BFS distance (hops). Unlike qgraph it handles disconnected
graphs (isolated nodes go to a trailing column) instead of erroring.layout_saqr() ports the Dynalytics Desktop "saqr" transition layout
(Saqr et al., LAK25): Start on top, End on bottom, middle nodes ranked
by outgoing weight from Start and split into 2–3 sine-enveloped rows
with a zig-zag first row (start =, end =, jitter =).plot_difference() now styles the difference network automatically
instead of drawing bare default-blue nodes: an undirected difference
gets the psychometric look (Okabe-Ito node palette, no arrows, thin
edges), a directed difference gets the TNA look (TNA palette, arrows).
Node size uses the calibrated preset (previously nodes could render
near-invisible), and edges stay coloured by the sign of the difference.
Explicit node_* arguments still override the preset.
plot_difference() is added as the preferred name for the
difference-network plotter. plot_compare() remains a first-class alias
of it (tna::plot_compare() delegates to cograph::plot_compare() by
name, so the name must keep working).
plot_difference() (the renamed difference-network plotter) now treats
an S3 cograph_network (which is itself a list — e.g. a psychnet
fit, a Nestimate netobject, or any as_cograph() result) as a single
network. Previously such an object fell into the "plain list of
networks" branch and was misread as a list of sub-networks, failing
with "x must be a matrix, cograph_network, tna, or igraph object".
Comparing two psychnet/netobject networks with
plot_difference(net1, net2) now works.
dyad_census() classifies every dyad of a directed network into
mutual (M), asymmetric (A), or null (N), returning a tidy
one-row-per-type data.frame with counts and proportions and a
dyad-based reciprocity (2M / (2M + A)) attribute. It is the
dyad-level companion to triad_census(). Undirected input counts
every edge as a mutual dyad.
ego_networks() reports tidy per-ego personal-network metrics — size,
ego/alter tie counts and densities, and Burt's structural-hole
measures (effective_size, constraint, order = 1 only) — with one
row per ego. The structural-hole columns reuse the same
implementations as centrality(), so they match
centrality(x, measures = c("effective_size", "constraint")) exactly.
Bootstrap plots of undirected co-occurrence networks
(splot.net_bootstrap) now default to the "oval" layout instead of
the force-directed "spring" layout, matching splot.tna_bootstrap.
Pass layout = "spring" to restore the previous behavior.
Bootstrap plots now auto-suppress the ".00" decimal tail on
integer-valued weight matrices (co-occurrence counts, raw
frequencies): 266.00** renders as 266**. Detection mirrors
splot.netobject — when every nonzero weight is a whole number and the
user has not set weight_digits, both weight_digits and
edge_label_digits default to 0. Applies to both splot.net_bootstrap
and splot.tna_bootstrap. Non-integer (correlation/GLASSO) networks are
unaffected, and an explicit weight_digits always wins.
plot_mcml() gains a theme argument: "classic" (default — the
established pie-node / straight-edge look, now with thinner node and
shell borders and slightly larger detail nodes), "rich" (donut nodes
on both layers plus curved summary edges and splot self-loops), and
"light" ("rich" with no shell outline and a softer fill). Granular
overrides node_donut, node_donut_inner_ratio,
summary_donut_inner_ratio, summary_donut_show_value, curved_edges,
and summary_curve win over the preset.
plot_mcml() now colors edges by weight sign on every layer
(within-cluster, between-cluster, summary, and self-loops) via
edge_color_by: "auto" (default) keeps cluster coloring for
non-negative transition networks but switches to sign coloring when any
negative weight is present (correlation / association networks),
"cluster" and "sign" force either mode. Positive edges use
edge_positive_color ("#2E7D32", green) and negative edges
edge_negative_color ("#C62828", red), matching splot(). Edge
visibility thresholding and width scaling now use the absolute weight,
so negative edges are drawn rather than silently dropped, and a
positive/negative key is added to the legend when sign coloring is
active.
plot_mcml() summary-node labels are now placed "on the clock": each
label sits just outside its node in the cardinal direction the node
points from the arrangement center (top at 12, bottom at 6, left at 9,
right at 3), anchored at the node boundary so it always clears the node
regardless of summary_size. An explicit summary_label_position still
overrides this.
plot_mcml() and splot() accept mcml_pc objects
(Nestimate::build_mcml_pc(), experimental psychometric MCML) and
render them undirected via their meta$directed flag.cluster_summary() and build_mcml() are removed
to end, permanently, the collision with Nestimate::cluster_summary()
and Nestimate::build_mcml() — different functions that silently
masked each other depending on package attach order (the same disease
as the cluster_network() alias removed in 2.3.6, where load order
silently flipped results between raw counts and row-normalized
probabilities). Migration is name-for-name with identical behavior:
cluster_summary(...) → csum(...) (the existing short alias is
now the canonical exported name; same arguments, same
cluster_summary return object).build_mcml(...) → summarize_clusters(...) (same arguments, same
mcml return object).
In sessions where both packages are attached, the bare names
cluster_summary() / build_mcml() now always refer to Nestimate's
data-layer verbs, regardless of attach order. The as_tna() generic
is intentionally exported by both packages: the definitions are
identical (function(x) UseMethod("as_tna")), so masking is harmless
and S3 methods from both packages dispatch correctly.plot_mcml() gains a directed argument (default NULL =
auto-detect). Undirected rendering suppresses arrowheads on all three
edge layers (within-cluster, between-cluster, summary), draws each
symmetric edge pair once instead of twice (previously a symmetric
matrix produced overplotted reciprocal arrows), and moves edge labels
to the edge midpoint. Auto-detection reads $meta$directed from
cluster_summary/mcml input (e.g., co-occurrence aggregations such
as Nestimate::build_mcml(type = "cooccurrence") now render
undirected with no extra flag), the $directed field of network
objects, or matrix symmetry for plain matrices — the same contract as
splot(), which forwards directed when dispatching
mcml/cluster_summary objects.plot_mcml() undirected matrix input is aggregated with
cluster_summary(type = "cooccurrence") (symmetrized counts) instead
of the row-normalized type = "tna", whose output is asymmetric even
for symmetric input and cannot be represented by undirected drawing.
When directed = FALSE is forced on weights that are not symmetric,
plot_mcml() now warns that only the upper triangle is drawn.cluster_summary() and the sequence path of build_mcml() now record
the effective directedness in $meta$directed: FALSE when
type = "cooccurrence" (which symmetrizes the weights), instead of
echoing the directed argument unchanged.cluster_network() alias for summarize_network(). It
collided with Nestimate::cluster_network() — a completely different
function (PAM clustering on sequence data, one network per cluster) —
and the two silently masked each other depending on package attach
order, producing confusing unused arguments (k = ..., cluster_by = ...) errors. Use summarize_network() (or its remaining short form
cnet()) for matrix-to-cluster aggregation in cograph.LICENSE..smooth_blob() (used by plot_simplicial() and
overlay_communities()) now guards grDevices::chull() against
non-finite anchor coordinates. Previously a node lacking layout
coordinates (NA/Inf) aborted the blob with "finite coordinates are
needed"; such anchors are now dropped before the convex-hull step.motifs() / subgraphs() roxygen documentation with the
post-audit behavior shipped in 2.3.2 (census type_summary counts,
min_count handling, and corrected plot legend descriptions).type_summary now holds
real MAN-type counts in census mode, min_count is honored in census
mode, and the swapped source/target color description in
plot.cograph_motif_result() is corrected.motifs() and plot_simplicial() on Nestimate-backed
workflows (HON / HYPA sequence inputs).panel_layout(): tightened dimension validation and made the
restoration claim honest — it now restores only the par() settings
it actually changed.combined argument (default TRUE) on every multi-panel plot
function: splot() group-cascade, plot_netobject_group(),
plot_netobject_ml(), plot_net_bootstrap_group(),
plot_group_permutation(), plot_compare(), splot.net_mlvar(),
plot_network_evolution(), plot.cograph_motifs(),
plot.cograph_motif_result(), plot.cograph_motif_analysis(), and
plot.tna_disparity(). With combined = FALSE these functions draw
panels into the active device without calling graphics::par(mfrow=...),
so callers can drive their own layout (e.g. graphics::layout() or
the new panel_layout() helper). Default TRUE preserves prior
behavior — every existing call site renders identically.panel_layout() helper sets up a custom multi-panel device layout
for use with combined = FALSE. Accepts either a uniform-grid
c(nrow, ncol) or a graphics::layout() matrix for non-uniform
layouts (e.g. one wide panel + two narrow ones). Returns a par()
snapshot for restoration via on.exit().test-coverage-splot-{41,42}.R: bumped n_nodes from 4 to 10 in
seven per-edge attribute tests so the seed=42 sampler does not
produce duplicate (1,2) pairs that trip cograph's
undirected-duplicate-edge detector.test-coverage-class-network-41.R: aligned the
set_layout_coords() mismatched-row-count test with the strict
input validation already enforced by R/class-network.R.test-overlay-communities.R: prefixed two communities() calls
with cograph:: to avoid tna masking when both packages are
loaded in the suite (per CLAUDE.md "namespace masking" gotcha).cr_color #D4820A -> #D4829A in plot-forest.R;
show_value default FALSE -> TRUE in splot-nodes.R), corrected
dataset dimensions in data-hai.R (302 -> 429 x 287), corrected a
reference to the nonexistent igraph::is_bipartite() (now
bipartite_mapping()), expanded centrality() @param measure
lists for mode, cutoff, invert_weights, and membership to
match the implementation, dropped baked-in measure counts that rot
on each addition, and removed nonexistent themes from sn_theme
documentation. No runtime behavior changes from the documentation
pass itself.plot_simplicial() now warns when anomaly is set on an input that
has no anomaly concept (HON, association rules, link prediction,
character pathways, method = "hon" / "rules"). Previously the
argument was silently dropped, so calls like
plot_simplicial(hon, anomaly = "over") and
plot_simplicial(hon, anomaly = "under") produced byte-identical
plots. anomaly is honored only for net_hypa inputs and
method = "hypa" auto-builds.centrality() gains an umbrella argument tna_network (logical or
NULL). When TRUE (or auto-detected from a tna/group_tna/ctna/
ftna/atna input), all measures shared with tna::centralities()
match byte-for-byte: loops = FALSE, invert_weights = TRUE,
diffusion_method = "power_series", transitivity_type = "onnela".
Side-by-side audit confirms zero divergence on OutStrength,
InStrength, ClosenessIn/Out/All, Betweenness, Diffusion,
Clustering (max|diff| = 0). Any per-argument override the user
passes explicitly always wins over the umbrella.centrality() (and centrality_diffusion()) gain a
diffusion_method = c("kandhway_kuri", "power_series") argument. The
default NULL auto-detects: "power_series" for tna inputs (matches
tna::centralities(., measures = "Diffusion") byte-for-byte when
loops = FALSE), "kandhway_kuri" (the existing 1-hop binary-degree
formula, Kandhway & Kuri 2014) for everything else. Previously
cograph's diffusion silently disagreed with tna's because cograph used
an unweighted neighborhood-degree sum while tna uses
rowSums(P + P^2 + ... + P^n) on the diagonal-zeroed weighted matrix
— the same name covered two different statistics. Set explicitly to
override the auto-detect.tests/testthat/test-validate-nestimate-bootstrap-permutation.R
asserting that centrality() on a Nestimate netobject agrees with
centrality() on its $weights matrix when the diagonal is
non-zero. Locks in the upstream Nestimate fix to
.extract_edges_from_matrix() (Nestimate >= 2026-05-02) which now
preserves self-loops in $edges. Without that fix, loop-bearing
netobjects (e.g. Nestimate::build_mcml() |> Nestimate::as_tna())
silently under-counted node degree by 2.edge_label_size is now coupled to the node label cex at a
fixed 0.55 fraction (edge_cex = 0.55 * mean(node_label_cex)) so the
node-to-edge-label ratio stays a stable ~1.82x across canvases. This
replaces the previous EDGE_LABEL_SCALE_CAP-based compensation, which
let the ratio drift from 2.5x at reference to 3.6x at poster canvases
because edge labels were clamped to a tighter 1.6 ceiling while node
labels scaled freely to 2.3. The visible effect: edge weight
annotations are now readable at poster sizes instead of shrinking
relative to node labels. User-explicit edge_label_size still wins
and receives the same (capped) visual-scale compensation as before;
only the default path changed.render_edges_splot()
into splot.R so the final cex is produced in a single place.splot() now applies device-dependent compensation to text, line, and
point sizes so visual ratios (label-to-node, legend-to-plot, edge
thickness) stay consistent when the output device changes. This fixes the
long-standing "labels too big at high DPI" and "legend desynchronised from
the plot" issues when saving PNGs at res = 300 or res = 600 with
pixel-default width/height, and when resizing the RStudio plot pane.
Implementation: a single compute_visual_scale() reads the active
device's canvas size (dev.size("in")) and returns multipliers keyed off
a 5.9-inch reference (matching the default RStudio 7×5" pane so
backward-compatible behaviour at the default canvas is preserved).
Multipliers are clamped to [0.55, 1.9] to keep thumbnails and posters
legible. See the new R/visual-scale.R.scaling = "fixed" mode on splot() — and corresponding global
option options(cograph.visual_scaling = FALSE) — disables device
compensation for reproducibility-sensitive workflows that calibrated
against the previous behaviour.splot() return value now carries two attributes for downstream tooling:
cograph.visual_scale (the multiplier list) and cograph.node_diam_in
(the representative node diameter in inches at the rendered device).render_legend_splot() plus the new shared
.render_legend_base() (R/render-legend-shared.R) replace the ad-hoc
legend cex/pt.cex handling with a single compensated path. plot_htna,
plot_mtna, plot_mlna, plot_mcml still use their historical scale
multiplier arguments; Phase 2 will migrate them to the shared helper.splot.netobject now routes on the Nestimate $method slot rather than
just direction. Undirected sequence-based networks from build_cna() and
wtna(method = "cooccurrence") get oval TNA-family styling (layout,
palette, donuts) with arrows and dotted edge starts automatically dropped
because the matrix is symmetric. Glasso / cor / pcor / ising networks
still get psych_styling = TRUE (spring layout, Okabe-Ito palette).from_tna() auto-detects integer-valued weight matrices (ftna, ctna, raw
counts) and sets weight_digits = edge_label_digits = 0 so edge labels
render as 2304 rather than 2304.00. Fractional weights still format
to two decimals. Explicit user-supplied weight_digits still wins.psych_styling = TRUE is now exported as a first-class styling preset
(undirected counterpart of tna_styling) — Okabe-Ito palette, spring
layout, no arrows — applied by default to splot.netobject on
correlation-family input and to the $contemporaneous / $between
constituents of net_mlvar.splot() dispatch coverage across the tna and Nestimate class
hierarchies, ensuring tna, ftna, ctna, group_tna, tna_bootstrap,
group_tna_bootstrap, tna_permutation, group_tna_permutation,
netobject, netobject_group, netobject_ml, net_mlvar, wtna_mixed,
net_bootstrap, net_permutation, boot_glasso, mcml, net_hon,
net_hypa, and simplicial_complex all reach the correct renderer.detect_duplicate_edges(), aggregate_duplicate_edges(),
simplify.cograph_network(), and the internal check_duplicate_edges()
helper now respect directed vs undirected semantics. Previously the
canonical (min/max) endpoint key collapsed A -> B and B -> A into one
edge even on directed graphs, matching igraph::simplify() ground truth..compute_modularity() replaces a nested for loop with cluster-wise
vectorization (sum(A[idx, idx]) - sum(k_out[idx]) * sum(k_in[idx]) / m),
per the project "no for loops" rule. Results verified bit-exact against
igraph::modularity().is_directed() now recognises CographNetwork R6 objects — previously
only the cograph_network list format dispatched correctly.compute_layout_for_cograph() uses layout$get_type() instead of the
removed $name field on CographLayout.network_small_world() returns 0 (valid: no triangles means
definitively not small-world) instead of NA_real_ when the observed
clustering coefficient is zero but path length is finite.simplify.cograph_network() threads the directed flag through to edge
aggregation so directed multigraphs collapse correctly.simplify() performance refactor for large networks plus a cleaner
title-composition path.motifs(), extract_motifs(), and plot.cograph_motif_analysis
examples reworked to use n_perm = 10L (or significance = FALSE) and
promoted from \dontrun to CRAN-runnable (optional tna branches stay in
\donttest). Retires 320 seconds of latent CRAN timing risk — every
example now runs in under 4 seconds.test-audit-fixes.R — ground-truth regressions for the directed edge
semantics, modularity vectorization, and small-world behaviour changes.test-integer-weight-labels.R — locks from_tna() integer-weight
auto-detect behaviour and precedence of explicit weight_digits.test-equiv-{assortativity, cluster-quality, communities, disparity, edge-centrality, network-summary, robustness, standalone-measures}.R —
numerical equivalence against igraph, sna, centiserve, brainGraph,
influenceR, tidygraph, and NetworkX. Gated by
skip_coverage_tests() + skip_on_cran(), so they do not run on the
CRAN pipeline.These measures don't fit the per-node centrality() data frame, so they live as standalone functions:
estrada_index() — graph-level spectral invariant: \eqn{EE(G) = \sum_i e^{\lambda_i}}, equal to the trace of the matrix exponential of the adjacency. Equivalently, the sum of subgraph_centrality() across all nodes. Matches networkx.estrada_index at machine epsilon (max relative diff ~5e-15 across random test graphs).trophic_incoherence() — graph-level food-web stability measure (Johnson et al. 2014). Defined as the population standard deviation of per-edge trophic differences \eqn{s_v - s_u} where \eqn{s_i} is the trophic level of node \eqn{i}. Zero for perfectly coherent DAGs (e.g., a pure chain). Matches networkx.trophic_incoherence_parameter at machine epsilon. Directed-only; reuses the existing trophic_level calculator.group_centrality(x, nodes, measure = c("betweenness", "closeness", "degree")) — Everett-Borgatti (1999) group centrality for a set of nodes. Returns a scalar. Supports mode = "in"/"out" for directed-degree variants. Group closeness and group degree match networkx.group_*_centrality bit-exact. Group betweenness implements the textbook Everett-Borgatti / Puzis 2008 definition (fraction of shortest paths passing through at least one node in the group), which diverges from networkx.group_betweenness_centrality on some graphs due to a known quirk in NetworkX's Puzis-Yahalom-Elovici iterative algorithm. Verified via an independent Python brute-force: cograph matches the textbook definition; NX produces larger values on graphs with many overlapping shortest paths. Documented in the roxygen "Divergence from NetworkX" section.dispersion(x, u = NULL, v = NULL, normalized = TRUE, alpha = 1, b = 0, c = 0) — Backstrom-Kleinberg (2014 Facebook) pair-level measure of tie strength. Counts the number of "well-dispersed" mutual friends of u and v (pairs of common neighbors that are not directly connected and share no common neighbor inside u's ego network other than u and v). Matches networkx.dispersion bit-exact across all 156 edges on the karate club graph. Returns a scalar, named vector, or data frame depending on which of u, v are specified.Added the five Gould-Fernandez (1989) brokerage role counts, a foundational measure in social network analysis (~1500 citations). Each role is a separate per-node measure requiring a membership argument (following the same pattern as participation, within_module_z, gateway), and counts open directed 2-paths a -> v -> c through broker v:
centrality_brokerage_coordinator() — all three in broker's group (w_I)centrality_brokerage_itinerant() — endpoints same group, broker different (w_O, "consultant")centrality_brokerage_representative() — broker + source same, target different (b_IO)centrality_brokerage_gatekeeper() — broker + target same, source different (b_OI)centrality_brokerage_liaison() — all three in different groups (b_O)Bit-exact match against sna::brokerage$raw.nli for all five roles across 20 random directed graphs. Implemented natively (no runtime dependency on sna). Key implementation detail: the Gould-Fernandez counting rule requires open 2-paths only — triads where a direct edge a -> c already exists are excluded. This matches sna's C implementation exactly and was derived empirically (sna's .C("brokerage_R", ...) has no R-level source).
Directed-only; warns and returns NA on undirected input.
centrality_prestige_domain() — directed-graph prestige measure: for each node \eqn{v}, the number of other nodes that can reach \eqn{v} via a directed path. Classical Wasserman-Faust (1994) measure from sna::prestige(cmode = "domain"). Bit-exact match against sna, implemented natively via igraph::distances(mode = "out") + colSums(is.finite(D)) - 1 (no runtime dependency on sna). Directed-only; returns NA with a warning on undirected input.centrality_prestige_domain_proximity() — distance-weighted variant: R_v^2 / (D_v * (n - 1)) where R_v is the number of reachers and D_v is the sum of their geodesic distances to v. Bit-exact match against sna::prestige(cmode = "domain.proximity") on strongly connected directed graphs. On graphs with any unreachable pair, sna has a known bug (FALSE * Inf = NaN collapses the denominator, producing all-zero output); cograph's is.finite()-masked formula produces mathematically correct values on any directed graph. Directed-only.centrality_katz() — Katz (1953) status index. Bit-exact match against centiserve::katzcent (cograph mirrors centiserve's exact LAPACK call sequence). Also matches igraph::alpha_centrality(exo = 1) and networkx.katz_centrality_numpy at machine epsilon. New katz_alpha parameter (default 0.1).centrality_hubbell() — Hubbell (1965) input-output centrality. Bit-exact match against centiserve::hubbell (cograph mirrors centiserve's full-inverse LAPACK call path). Note: centiserve's default (weights = NULL) silently ignores E(g)$weight; to reproduce cograph's behavior with centiserve on weighted graphs, pass weights = igraph::E(g)$weight explicitly. New hubbell_weight parameter (default 0.5).centrality_information() — Stephenson-Zelen (1989) information centrality. Bit-exact match against sna::infocent on connected undirected graphs (cograph mirrors sna's exact construction and solve() call sequence).centrality_pairwisedis() — Pairwise disconnectivity (Potapov et al. 2008). Directed-only; fraction of reachable ordered pairs that become unreachable when each node is removed. Bit-exact match against centiserve::pairwisedis. Warns and returns NA on undirected input, matching the convention used by salsa, leaderrank, and trophic_level.centrality_reaching_local() / reaching_global() — Local and global reaching centrality (Mones, Vicsek & Vicsek 2012). Bit-exact match against networkx.local_reaching_centrality across the directed unweighted, undirected unweighted, and weighted branches. Undirected unweighted LRC coincides with igraph::harmonic_centrality(normalized = TRUE) (documented). reaching_global() is a graph-level hierarchy statistic in [0, 1].plot_simplicial() now accepts tna, netobject, net_hon, and net_hypa objects directly — higher-order pathways are auto-built and visualized with proper state labels, no manual extraction needed. New parameters: method ("hon" / "hypa"), max_pathways, ncol. Dismantled mode uses gridExtra grid layout with scaled nodesprint.cograph_network() now shows a structured summary: node/edge counts, density, reciprocity, weight range, and top-degree nodes — replacing the minimal R6 default outputmcml S3 class with as_mcml() generic for type-safe handling of Markov Chain Multi-Level models — enables print(), plot(), and method dispatch on MCML objects%||% operator for R 4.1 compatibility (no longer requires R 4.4+)$between → $macro, $within → $clustersas_tna() on MCML objects now returns a flat group_tna list instead of a nested structureplot_mcml() now suppresses zero-weight edges instead of drawing invisible lines, and strips leading zeros from edge labels (.32 instead of 0.32)cluster_summary() are now preserved in the macro diagonal, reflecting intra-cluster retention ratesoverlay_communities() for drawing community blob overlays on any network plot — accepts method names, membership vectors, or pre-computed community objectsplot_simplicial() for higher-order pathway visualization, rendering simplicial complexes as smooth blobs with flexible separators and a dismantled view optionvalue_nudge parameter to plot_transitions() for controlling the distance between flow labels and nodesbundle_legend_size, bundle_legend_color, bundle_legend_fontface, bundle_legend_positionlabel_size, label_color, label_fontface, label_hjust) to plot_transitions(), plot_trajectories(), and plot_alluvial()cluster_summary() for aggregating network weights at the cluster level, producing between-cluster and within-cluster matrices from raw transition databuild_mcml() for constructing Markov Chain Multi-Level models from edge lists or sequence data with automatic cluster detectioncluster_quality() for modularity-based cluster quality metrics and cluster_significance() for permutation-based significance testingas_tna() to convert cluster summaries to TNA objects for bootstrapping, permutation testing, and plotting with splot()simplify() for pruning weak edges from networks, with configurable weight threshold and aggregation methoddisparity_filter() for backbone extraction (Serrano et al. 2009), with methods for matrices, tna, igraph, and cograph_network objectsrobustness() for network robustness analysis with targeted (betweenness, degree) and random attack strategies, plus ggplot_robustness() for faceted ggplot2 outputtemporal_edge_list() for converting sequence data to timestamped edge listssupra_adjacency(), supra_layer(), supra_interlayer() for multilayer supra-adjacency matrix constructionlayer_similarity(), layer_similarity_matrix(), and layer_degree_correlation() for comparing layers in multilayer networksaggregate_weights() and aggregate_layers() for weight aggregation across layersverify_with_igraph() for cross-validating cograph centrality and network metrics against igraphmotifs() / subgraphs() as a unified API for triad census (node-exchangeable counts) and instance extraction (named node triples), with auto-detection of actor/session columns, rolling/tumbling window support, and exact configuration model significance testingplot_mcml() for Markov Chain Multi-Level visualization showing between-cluster summary edges alongside within-cluster detail, with pie charts, self-loops, and 22 customization parametersplot_chord() for native chord diagrams with automatic weight-based arc sizingplot_time_line() for cluster membership timeline visualizationplot_htna() orientations: "facing" (tip-to-tip columns) and "circular" (two semicircles), plus intra_curvature for drawing intra-group edges as dotted bezier arcsthreshold parameter to all plot functions for filtering edges/cells below a minimum absolute weightvalue_fontface, value_fontfamily, and value_halo parameters to plot_heatmap() for text styling controlscale_nodes_by: indegree, outdegree, instrength, outstrength, incloseness, outcloseness, inharmonic, outharmonic, ineccentricity, outeccentricityscale_nodes_scale parameter to splot() for dampening (< 1) or exaggerating (> 1) centrality-based node sizing differencessplot(): when plotting tna objects, qgraph-style parameters (vsize, asize, edge.color, lty, shape) are automatically mapped to cograph equivalentsnode_label_format (e.g., "{state} (n={count})") for showing counts on transition plot nodesbundle_size for aggregating individual trajectories into weighted summary lines in large datasetsshow_values / value_position for displaying transition counts on flow lineslabel_position consistency across ALL columns (first, middle, last) in trajectory plotsgamer_data, group_engagement, srl_dataset_node_groups() / get_node_groups() for managing cluster assignments on cograph_network objects$meta with getter/setter functionsgroup_tna support to splot() for direct plotting of grouped TNA modelscentrality_* wrapper its own focused help pagesplot() viewport calculationsplot()'s signature and silently dropped when dispatchingplot_heatmap() so high values get dark colorsbuild_mcml() density method crash when weight vector had no names.collect_dispatch_args() helper to replace 6 copy-paste dispatch blocks, using match.call() + mget() for reliable argument capturecentrality() with 23 measures and individual wrappers: degree, strength, betweenness, closeness, eigenvector, pagerank, harmonic, authority, hub, alpha, power, kreach, diffusion, percolation, eccentricity, transitivity, constraint, coreness, load, subgraph, leverage, laplacian, current-flow betweenness, current-flow closeness, voterankedge_betweenness() for edge-level centralitydetect_communities() with 11 algorithms: louvain, walktrap, fast_greedy, label_propagation, leading_eigenvector, infomap, spinglass, leiden, optimal, edge_betweenness, multilevel — plus com_* shorthand aliasescluster_significance() for permutation-based validationnetwork_summary() and summarize_network() for computing comprehensive network-level statistics (density, reciprocity, transitivity, diameter, components, degree distribution)plot_transitions() for alluvial/Sankey flow diagrams, with plot_alluvial() and plot_trajectories() wrappersplot_bootstrap() and plot_permutation() for significance-styled visualization of bootstrap and permutation test results — significant edges rendered solid on top, non-significant edges dashed behindplot_mixed_network() for overlaying symmetric (undirected, straight) and asymmetric (directed, curved) edges on the same networkplot_heatmap() for adjacency matrix heatmaps with optional hierarchical clustering and plot_ml_heatmap() for multilayer 3D perspective heatmapsplot_compare() for difference network visualization showing edge-weight changes between two networkssplot() S3 methods for tna_bootstrap and tna_permutation objectsmotif_census(), triad_census(), and extract_motifs() for triad motif analysis with pattern filtering, significance testing, and network diagram visualizationfilter_edges(), subset_edges(), select_nodes(), select_edges() for flexible network subsettingset_groups() for storing cluster assignments on cograph_network objects with automatic dispatch to plot_htna() / plot_mtna()cograph_network objects as input, in addition to matrices, igraph objects, and tna objectslayout_spring and layout_gephi_fr algorithms: vectorized attraction forces, edge aggregation for dense networkspar(pin) error on exit when plot device state was corruptedx across all plotting functions:
plot_tna(): input → xplot_htna(): input → x (was model)plot_mtna(): input → x (was model)splot() already used xtplot() default margins causing tiny plots compared to splot()vignettes/qgraph-to-splot.md)The following parameters have been renamed for consistency. The old names still work but emit deprecation warnings:
| Old Name | New Name | Reason |
|----------|----------|--------|
| esize | edge_size | Add edge_ prefix, expand abbreviation |
| cut | edge_cutoff | Add edge_ prefix, clarify meaning |
| usePCH | use_pch | Fix camelCase to snake_case |
| positive_color | edge_positive_color | Add edge_ prefix (matches theme storage) |
| negative_color | edge_negative_color | Add edge_ prefix (matches theme storage) |
| donut_border_lty | donut_line_type | Expand lty abbreviation |
edge_label_fontface now accepts string values ("plain", "bold", "italic", "bold.italic") in addition to numeric valuesmlna() for multilevel network visualization with 3D perspectivemtna() for multi-cluster network visualization with shape-based cluster containersplot_htna() for hierarchical multi-group network layouts with polygon and circular arrangementstplot() as a qgraph drop-in replacement with automatic parameter translationarrow_angle parameter for customizable arrowhead geometryedge_start_dot_density parameter for TNA-style dotted edge starts indicating directionfrom_tna() — no manual matrix extraction needednetwork and qgraph objects as inputpie_values vector to donut_fill when all values are in [0,1]splot() when other parameters were specifieddonut_shape validation rejecting custom SVG shapesfrom_qgraph() when a layout override was providednormalize_coords()from_qgraph() by using a matrix intermediary for per-edge vector reorderingnrow(el) crash: qgraph's Edgelist is a list, not a data.framedonut_empty parameter for rendering unfilled donut nodesfrom_qgraph() for converting qgraph objects to cograph format, reading resolved graphAttributes for accurate parameter extractionlayout_info guard causing errors on certain device configurationssoplot() for grid/ggplot2-based network plotting — full feature parity with splot() using a different rendering backendlayout_oval() for oval/elliptical node arrangementslayout_scale parameter to expand or contract the network layout, with "auto" mode for node-count-based scalingedge_start_style parameter for visually indicating edge direction via styled start segments (dashed, dotted)soplot() curve direction and edge defaults diverging from splot() behaviorrescale_layout distorting oval aspect ratios by switching to uniform scalingpar(pin) restoration error on plot device exitsplot() — a base R graphics engine for network visualization using polygon(), lines(), and xspline(), providing better performance than grid-based rendering for large networkssn_save() with configurable DPIdonut_color API to accept 1 color (fill), 2 colors (fill + background), or n colors (per-node)