These fixes change the numbers that some functions returned. Code written for version 1.0-3 runs without errors, but gives different results:
count_geodesics() counted only some of the geodesics
between two nodes. A node that had already been reached did not add the
geodesics of the other nodes at the same distance, so the counts were
too low. The counts now agree with sna::geodist().
wall_distances() returned a single path for each
node instead of one path for each pair of nodes. It now returns
fromTo[[i]][[j]] and toFrom[[i]][[j]] for
every pair.
wlocal_distances() and wall_distances()
now use Dijkstra’s algorithm, as the documentation said. Before they
enumerated every simple path, which did not finish on networks of a
dozen nodes. Between paths of the same length, the path returned might
differ from the one returned before.
triad_uman(): the covariance between the counts of
201 and 102 was positive, and should have been negative. The term of the
two triads that share a dyad used m + n + 4 where the
combinatorics give m + n - 4. The covariance matrix now
adds up to zero, as it must because the number of triads is
fixed.
triad_uman() no longer returns the element
z_test by default. It tested the sum of the sixteen counts,
which is always choose(g, 3), so it was NaN
for every network. The new argument l tests a linear
combination of the census (Wasserman and Faust, 1994: 583); with
ztest = TRUE and no l, the function returns
the data frame with the columns Z and P. The
covariance matrix is now returned symmetric instead of
triangular.
eb_constraint() failed for egos with a single alter,
and for egos whose alters are not tied to each other. It now returns the
constraint in both cases, and gives an informative error for
isolates.
components_id() did not return the components of a
directed network. It grouped the nodes that reach the same nodes, so a
chain of citations gave as many components as nodes. It now returns the
weak components, which for undirected networks are the ones it returned
before, and takes the arguments mode = "strong" and
bipartite.
edgelist_to_matrix() placed some ties in the wrong
direction, depending on the order in which the nodes appeared in the
edge list, and dropped the ties of a node with itself. The names are now
matched one by one, and loops = TRUE keeps the diagonal. It
also returned a vector instead of a matrix when the result had a single
row or column.
mix_matrix() counted only the ties of the upper
triangle of the matrix, so for a directed network with 15 arcs it
returned a table adding up to 5, and it dropped the row or the column of
a group that never sent or received a tie, which made the table lose its
shape. The mixing matrix is now built from the ties between every pair
of groups.
ei_index() ignored the attribute unless
mixed = FALSE was given, and with its default arguments it
returned one for any network without loops. The attribute now takes
precedence, and the index agrees with netseg::ei() for
directed and undirected networks.
trans_matrix() failed with “the condition has length
> 1” whenever a node belonged to more than one transitive triple,
which is the usual case in an undirected network with a triangle. It now
marks the nodes of every transitive triple.
triad_uman() failed on a network without ties,
because the matrix was stored in a class that cannot be
modified.
multilevel_degree() returned wrong values for the
nodes of the third level when B2 was given without
B3: the rows k1, k2, ... of the column
multilevel held the degrees of the nodes of the second
level. They now count the ties of each node within the third level and
with the second level, which is the value of the same nodes in
high_multilevel, and the documentation describes what every
column counts.
gen_degree() with weighted = TRUE
removed the loops from the degree but not from the strength when
loops = FALSE, and symmetrized the ties but not the weights
when digraph = FALSE. The weights now follow the ties in
both cases.
gen_density() removed the diagonal of two-mode
matrices, which holds real ties, so with the default
loops = FALSE the density of an incidence matrix was too
low (its own example gave 0.33 instead of 0.5). For a directed matrix in
a list (multilayer = TRUE) it used only the lower triangle,
and with directed = FALSE it kept the ties of the upper
triangle instead of the underlying graph. With loops = TRUE
the diagonal is now counted among the possible ties. The results agree
with igraph::edge_density().
eb_constraint() chose the maximum constraint by the
size of the ego network including ego, so an ego with seven alters got
the maximum of the shadow ego network (0.486) instead of the complete
one (0.493, Everett and Borgatti, 2020: Table 1), and a normalization
that was too high. The maximum is now the larger of the two. An ego with
a single alter has a normalization of one, as in Everett and Borgatti
(2020: Eq. 5), instead of NaN.
q_analysis() reported only the dimensions q that
some simplex had exactly, so the levels at which components merge were
missing: two 4-simplices that share a face of dimension 2 were never
reported as 2-connected. It now reports every q from the largest
dimension down to 0. A network was turned into a complex of its
triangles and all its edges, so a clique of four nodes became four
triangles; the simplices are now the maximal cliques. A square incidence
matrix, which stopped the function, is now accepted. The function
returns a list with the simplices, the table of the structure vectors,
the components at each q and the eccentricities; the argument
dimensions is kept but no longer needed. The values agree
with Freeman (1980) and with the Python package of Smirnov et
al. (2025).
simplicial_complexes() built the complex of a
network from its triangles and all its edges, so a clique of four nodes
became four triangles and six edges, and it stopped with an error on
networks without triangles. The simplices are now the maximal cliques,
named after their nodes (a-b-c) instead of numbered.
zero_simplex = TRUE, now the default, adds the isolated
nodes as simplices of dimension 0; before it added columns that did not
correspond to the nodes. The rows are still the nodes and the columns
the simplices.
k_core() of a binary network counted the loops even
when loops = FALSE, which is the default. The loops are now
counted only with loops = TRUE. Without loops the values
are the same as before.
zone_sample() returns adjacency matrices instead of
igraph objects. With core = TRUE, the indicator of the
actors at distance one is the attribute core of each
matrix. The nodes and the ties are the same as before.
An audit of every exported function found further errors,
recorded in dev/audit/audit.md with how each was
verified:
multiplex_census() added counts of the two networks
instead of counting the triples of each joint configuration (two empty
networks gave 5 of 10 triples). It now returns the classes of Figure 12
of Espinosa-Rada (2021), named by the type of the directed triad and the
position of the undirected edges (e.g. 021U_102ac);
merge = "overlap" merges the classes that give the same
overlapped triad.mixed_census(quad = TRUE): the class “201” multiplied
two counts instead of adding them.kp_reciprocity() used the number of arcs minus twice
the mutual dyads instead of the number of arcs for free choices.posneg_index(): select = "all" returned
the out-index and "out" the all-index. They now agree with
signnet::pn_index().k_core() with weighted = TRUE or
multilevel = TRUE returned the round in which a node was
removed instead of its core value (a path gave 1 0 1).digraph = FALSE, gen_degree(),
eb_constraint(), redundancy(),
clique_table(), dyad_triad_table(),
struc_balance() and the signed
eigenvector_centrality() copied the upper triangle of the
matrix over the lower one, so the ties present only in the lower
triangle were lost and the results depended on the order of the nodes.
They now use the underlying graph.edgelist_to_matrix(digraph = FALSE) dropped the edges
listed as (b, a); matrix_to_edgelist(valued = TRUE) dropped
ties with non-integer values and listed undirected ties twice;
matrix_adjlist() dropped ties below 1.adj_to_matrix(type = "adjacency") returned one row per
line of the list instead of one per node.meta_matrix() placed the ties between the second and
third levels only above the diagonal, and dropped them when
B3 was given.extract_component() returned wrong matrices when
several components had the same size; position now counts
the distinct sizes.dist_sim_matrix() computed the Hamming distance of
two-mode matrices over the wrong nodes, failed with rows without ties,
and now keeps the names of the nodes.fractional_approach() did not compute the networks
of Batagelj (2020): its “citation” network was a product of the two
incidence matrices, and the fractional co-citation was normalised on one
side only. It now takes the citation network between works
(A1) and, for the citations between authors, the authorship
matrix (A2), with full or fractional counting
(fractional); the fractional bibliographic coupling can be
made symmetric with the six measures of Batagelj (2020)
(symmetric). Calls with two incidence matrices, as in
version 1.0-3, now stop.
dyad_triad_table() called “Triad201” every pair of
neighbours of a node, closed or not, and min and
max limited how often a triad was repeated (which selected
the closed triads). It now returns the type of each triad
(201 for the forbidden triad of Granovetter, 300 when closed, 102 for a
dyad and 003 for an isolated node) and its members, and
min and max limit the number of forbidden
triads of which a node is the centre.
structural_na() warned whenever the labels had more
nodes than the matrix, which is its purpose. It now warns only when a
node of the matrix is not in the labels and is dropped.
eb_constraint(), redundancy() and
ego_net() without ego failed with “argument is
of length zero”; they now ask for the name of ego.
trans_coef(method = "mean") always failed with
could not find function "local_trans".
short_path() failed when there was no path between
the two nodes. It now warns and returns NULL.
percolation_clique() failed when every node belonged
to a clique.
power_function() reached the limit of nested
expressions for large powers, which made its own example fail. It now
uses a loop instead of recursion.
matrix_to_edgelist() failed on a network without
ties. It now returns an empty edge list.
percolation_clique() failed when the network had a
single clique, and when some nodes did not belong to any
clique.
redundancy() failed with an obscure message for an
isolated ego, and clique_table() stopped without a message
when there were no cliques.
Several functions failed or never ended on common inputs:
k_core() looped forever on a matrix with NA,
and ind_rand_matrix(type = "edges") for undirected networks
whenever l was larger than the number of nodes.
bfs_ugraph(), count_geodesics(),
gen_degree(), short_path(),
wall_distances(), wlocal_distances(),
matrix_to_edgelist(), multiplex_census() and
ego_net() now treat NA as an absent tie;
adj_to_incidence() and edgelist_to_matrix()
accept networks without ties; minmax_overlap() accepts a
single row; co_occurrence(occurrence = FALSE) no longer
fails; ego_net() returns a matrix for an ego with one
alter, and redundancy() no longer calls that ego an
isolate.
wall_distances() and wlocal_distances()
accept binary matrices, in which every tie has length one.
ind_rand_matrix() takes sparse, which
draws the ties among the cells that the model allows and returns a
sparse matrix of the Matrix package, without building the
dense one. A network of 10,000 nodes takes 0.2 MB instead of 800 MB. It
is available for one-mode and two-mode networks with
trials = 1.
edgelist_to_matrix() orders the nodes as in
label (and label2 for two-mode networks), with
the nodes that are not in the labels after them in alphabetical order.
Before, the nodes were always in alphabetical order, so a matrix could
not be recovered from its edge list in its own order.
components_id() takes mode (weak or
strong) and bipartite, which covers the nodes of both modes
(issue #4).
eigenvector_centrality() takes signed,
for networks with negative ties (Bonacich and Lloyd, 2004) (issue
#9).
trans_coef() takes method = "barrat",
the weighted transitivity of Barrat et al. (2004) (issue #5).
gen_density() computes the density of weighted
networks, which is the average strength of the possible ties (issue
#12).
edgelist_to_matrix() takes valued,
which reads the value of the ties from a third column, and
loops (issue #2).
dist_sim_matrix() takes a list of matrices, and
compares the nodes across all the relations at once (issue #7).
q_analysis() returns the second and third structure
vectors (Raj et al., 2024), the obstruction vector and the eccentricity
of each simplex, with the definition of Atkin (1974) or of Johnson
(eccentricity). A network can be analysed through its
clique complex or its neighbourhood complex (complex), with
open or closed neighbourhoods (closed). It is also about a
hundred times faster.
simplicial_complexes() takes
complex = "neighbourhood" and closed, as
q_analysis(), which now builds its complex with it, and
valued for projections that count the shared nodes and
simplices.
eb_constraint() takes digraph = TRUE
and weighted = TRUE, which used to stop. A directed network
has the constraint of the valued network A + t(A), and the
maximum used in the normalization is that of Everett and Borgatti (2020:
Eq. 6 to 9). The results agree with their Tables 1 and 2 and with
igraph::constraint() on the ego network.
edgelist_to_matrix() takes rule for
undirected networks: a tie listed in either order (weak,
default) or only in both orders (strong), as
sna::symmetrize().
gen_degree() and gen_density() no
longer warn that a symmetric matrix is undirected when
digraph = TRUE (or directed = TRUE), as the
result is the same.
supra_adjacency() arranges the layers of a multiplex
network in a single matrix of actor-layer pairs, with categorical,
ordinal or no coupling between the layers (De Domenico et al., 2013;
Kivela et al., 2014), and aggregate_layers() reduces the
layers to a single matrix by sum, binary or mean (Battiston et al.,
2014). Centrality:
closeness_centrality(),
betweenness_centrality() (Brandes’ algorithm),
eigenvector_centrality(), katz_centrality(),
bonacich_power(), page_rank_centrality() and
centrality_centralization().
geo_distances() and geo_summary() for
the distances, the diameter and the average distance.
Positions and dominance:
neigh_inclusion(), set_inclusion(),
pareto_dominance(), dominance_pairs(),
preserved_order() and dominance_layers().hyperevent_dominance() for the dominance of authors
through the chain author, citing paper, cited paper, author
(Espinosa-Rada, 2026). With the data of the article, it gives the same
dominance matrices as the analysis scripts, the maximal and dominant
authors of Section 6 and the values of Table 1. The arguments
strict and closure_papers give the
alternatives of the text of Section 3.5 where it differs from the
analysis scripts.dir_inclusion() with the nine directed
neighbourhood-inclusion criteria of Marmulla and Brandes (2026).pos_dominance(), indirect_rel() and
dominance_ranks().Roles, positions and macro structure:
block_density(), concor() and
rege().krackhardt_index() with the four dimensions of
Krackhardt (1994) and the condition recommended by Everett and
Krackhardt (2012).core_periphery() and clique_max() (maximal
cliques of any size).Communities:
modularity_score() (Newman and Girvan, Arenas et
al. for directed networks, and the LinkRank of Kim, Son and Jeong).leiden() (Traag et al., 2019), which with
refine = FALSE is the algorithm of Louvain,
leading_eigen(), community_greedy(),
community_label() and
community_betweenness().Inference:
cug_test() for conditional uniform graphs, and
qap_cor() and qap_lm() for the QAP correlation
and the MRQAP regressions, linear and logistic.Dynamics and generators:
social_influence() with the rules of assimilation,
bounded confidence, repulsion and Friedkin-Johnsen, and
threshold_diffusion().small_world() and pref_attachment().Segregation:
segregation() with the five measures reviewed by
Bojanowski and Corten (2014).Overlapping categories (Everett and Borgatti, 2026):
alter_composition(),
alter_heterogeneity(), alter_homophily() (E-I
index and Yule’s Q), brokerage_roles() (Gould and
Fernandez) and partition_centrality() for nodes that belong
to several categories, given as a membership matrix that is made
row-stochastic, or as a vector when the categories are a partition.
alter_homophily() takes similarity for the
three definitions of the similarity of two memberships (product, minimum
and cosine).structural_holes() with the effective size, efficiency
and constraint of Burt for every node of a valued or directed network,
computed within the ego networks (as UCINET) or the whole network (as
igraph), and with the option of treating the alters of the same category
as redundant (B and beta).sna::brokerage() and
igraph::constraint(). Tables 8 and 9 are reproduced within
rounding when Chuck spends 12 of 51 hours on the third task instead of
11 of 44 as printed in Table 2, which suggests that they were computed
with those hours.campnet, the Camp 92 network with the
gender and role of each person.Citation networks:
traversal_weights() with the search path count (SPC),
the search path link count (SPLC) and the search path node pair (SPNP)
of each arc, and the weighted in-degree and out-degree of each paper
(Kuan, 2020). The weights agree with the published values of Liu and Lu
(2012, Fig. 1) and Kuan (2020, Tables 3 and 4).main_path() with the global, local and key-route main
paths (Liu and Lu, 2012), main_path_diag(),
citation_decay(), and dag_check() and
dag_sort() to remove the cycles of a citation network and
to order it.dev/audit/03_documentation.R: that each one says what it
returns, that the value is not copied from another function, that the
arguments of the documentation and of the function are the same, that
the cross-references point to topics that exist, and that every DOI
resolves and belongs to the reference that cites it. It corrected the
value of ind_rand_matrix(), which said that it returned a
dyad census, and made the value of neigh_inclusion(),
dir_inclusion() and pos_dominance() say what
their matrices mean.multiplex_census() and corrects
two references.k_core() and
zone_sample() were the only functions that used it. igraph
is in Suggests, for the plots of the vignette.dev/validation, which
is not part of the package.leiden(), community_label() and
core_periphery() start from random partitions, so they need
a seed to be reproducible.clique_max(), pos_dominance(),
indirect_rel(), dominance_ranks(),
segregation() and centrality_centralization()
(sna::centralization()).