metrics: Time-varying graph-level structure

View source: R/graph.R

metricsR Documentation

Time-varying graph-level structure

Description

Graph properties measured on each time bin, returned as a time series. This is where a temporal network earns its keep: a single density for the whole course tells you nothing about a group that was dense in week two and silent in week five.

Usage

metrics(
  dn,
  measure = "density",
  sessions = c("bounded", "collapse", "separate"),
  sample = NULL,
  start = NULL,
  end = NULL,
  step = NULL,
  window = NULL,
  plot = FALSE
)

Arguments

dn

A temporal network from dynet().

measure

One or more measure names, "density" by default: "density", "edges", "active_nodes", "isolates", "transitivity", "reciprocity", "components", "components_strong", "largest_component", "mean_distance", "diameter", "mutual", "asymmetric", "null", "assortativity", "centralization_degree", "centralization_betweenness", "centralization_closeness", "triads", "connectedness", "efficiency", "hierarchy", "lubness". "triads" expands to the sixteen triad classes; those four are Krackhardt's indices of how far a directed network departs from a pure out-tree, only one of which is hierarchy itself. Lightweight structural summaries are "degree_mean", "degree_variance", "degree_min", "degree_max", "mean_degree", "indegree_1_5", "outdegree_1_5", "triangles", "concurrent_nodes", "concurrent_share", "in_2stars", "out_2stars", and "two_paths". Exact window-integrated quantities are "temporal_density", "observed_pair_density", "onset_intensity", and "observed_pair_onset_intensity". Any other name raises an error of class dynet_unknown_measure. Eight of these read direction and need a directed network, raising dynet_needs_directed on an undirected one: "reciprocity", "mutual", "asymmetric", "null", "in_2stars", "out_2stars", "indegree_1_5" and "outdegree_1_5".

sessions

How to treat sessions, as in centrality_series(): "bounded" (the default) keeps each session apart while pooling the reported rows, "collapse" ignores session labels, and "separate" reports each session on its own rows and needs a network built with a session column, raising dynet_no_sessions otherwise.

sample

Deprecated. "instant" is equivalent to window = 0; "window" uses the current positive/default window.

start, end

First and last time at which to measure. Default to the observed range. A network built from dates may be addressed with dates.

step

How often to measure. Defaults to the interval the network was built with.

window

How much time each measurement covers. Defaults to step, which tiles the period into disjoint bins. A larger value slides an overlapping window; 0 samples the network at each point in time. "all" measures the whole observed period as one window, closed on the right so an event at the final instant is inside it; it cannot be combined with step, and under sessions = "separate" or discontinuous observation it gives one window per session or observed component.

plot

Whether to draw the result as well as return it. Drawing is a side effect in the manner of graphics::hist(): the verb still returns its tidy table, invisibly when it has drawn, so plot = TRUE saves the wrapping plot() call without changing what comes back. Use plot() on the result when the figure needs arguments of its own.

Details

"density" counts the any-time union of realised edges against eligible possible edges in each bin. The four temporal selectors instead integrate exact state over positive observed time inside every reporting window. "temporal_density" is binary occupied pair-time divided by all eligible nonloop ordered-pair time (directed) or dyad time (undirected). "observed_pair_density" uses the same numerator but restricts opportunity to pairs having endpoint-valid evidence anywhere in the complete stored history. That cohort is not reset by reporting windows or observation gaps. summary() reports the first quantity over the pooled full history.

If Y[r](t) is exact simultaneous endpoint eligibility, E[r](t) is binary edge presence, and H is the ever-observed pair set, the exposure ledgers are ⁠R = sum(r) integral(Y[r](t) dt)⁠, ⁠O = sum(r) integral(Y[r](t) E[r](t) dt)⁠, and ⁠R_H = sum(r in H) integral(Y[r](t) dt)⁠. The two occupancies are O/R and O/R_H and lie in ⁠[0, 1]⁠. Loops, weights, duplicates, and censor flags cannot multiply occupancy. Integration stops at the observation period, which defaults to the span of the data, so a last window reaching past the final spell is measured over its observed part only, and tiled windows pool to the whole-period value. tsna::tEdgeDensity() uses the same observation-period rule.

"onset_intensity" and "observed_pair_onset_intensity" divide the number of known raw spell starts by R and R_H. Each nonloop raw row, including a point contact, contributes once when its start is observed, not onset-censored, and has exactly eligible endpoints. Termini are not events; overlaps and duplicates remain distinct onsets. Intensities are nonnegative, unbounded, and measured in inverse network time. A zero denominator gives NA for every temporal selector, even when a point event exists. A positive denominator with a zero numerator gives zero. Therefore window = 0 makes all four temporal selectors undefined while snapshot measures retain their exact point-state meanings.

step and window are separate on purpose: step is how often you look, window is how much of the timeline each look takes in. A seven-day window stepped one day at a time smooths a noisy series without giving up daily resolution. They match time.interval and aggregate.dur in tsna::tSnaStats().

When vertex activity was declared in dynet(), every measure is computed on the endpoint-induced eligible vertex set for the window. Positive windows independently use the any-time vertex and edge unions before induction; window = 0 evaluates the exact state. Density and census opportunities, components, isolate counts, largest-component shares, and Freeman denominators therefore use eligible rather than fixed order.

"assortativity" is Newman's degree assortativity computed with the total degree (in plus out) at both ends of every arc, on directed and undirected snapshots alike. It is not the directed out-degree-to-in-degree variant igraph::assortativity_degree(directed = TRUE) reports, and the two disagree on directed data.

"centralization_closeness" is a Freeman centralisation of this package's own closeness (reachable vertices divided by reachable distance, so it is defined on a disconnected snapshot), with the theoretical maximum of that score: n - 1 for a directed snapshot (a single arc from the centre) and n - 2 for an undirected one (one isolated dyad). It matches sna::centralization(closeness) on connected snapshots and not on disconnected ones, where sna's closeness is zero everywhere.

Krackhardt's four indices – "connectedness", "efficiency", "hierarchy" and "lubness" – describe how far a directed network departs from a pure out-tree. "hierarchy" and "lubness" are undefined on some graphs (no connected pair, no component of three) and report NaN rather than a number that would mislead.

Triad census cost grows with the cube of the vertex count. On a network of a few hundred vertices it is the slowest measure here by a wide margin.

The lightweight structural selectors use the binary, loop-free induced snapshot. Directed total degree is in-degree plus out-degree; "degree_variance" is the sample variance across eligible vertices. "mean_degree" is the mean out-degree for a directed graph (identically, the mean in-degree) and the ordinary mean degree for an undirected graph; this matches ERGM's meandeg statistic. "indegree_1_5" and "outdegree_1_5" sum the corresponding vertex degrees raised to 1.5. Directed "triangles" is the sum of cyclic and transitive triples, while an undirected triangle is counted once. A concurrent vertex has relations to at least two distinct neighbours active at the same instant, so a reciprocal dyad still supplies only one neighbour. Unlike the other structural selectors, concurrency is not read from the window's union snapshot: with a positive window, two ties that fall in the same window without overlapping in time do not make their shared vertex concurrent. A vertex counts in a window when it is concurrent at any instant inside it; "concurrent_nodes" is the number of such vertices and "concurrent_share" divides it by the window's eligible vertices. With window = 0 the snapshot is itself an instant, so both readings coincide. Half-open spells that only meet at a boundary do not overlap, and a point contact is concurrent with every relation active at its timestamp. mixing() answers a different question – which groups are connected somewhere in the window – and requires no simultaneity. "in_2stars" and "out_2stars" sum choose(degree, 2) over directed in- and out-degrees. Directed "two_paths" counts ordered i -> j -> k paths with i != k; undirected two-paths count each unordered wedge once. Empty eligible snapshots return zero for all selectors.

Value

A dynet_metric at graph level: one row per time point and measure, with columns session (only under sessions = "separate", the one mode that keeps session labels apart), time, measure and value. "triads" contributes sixteen rows per time point, whose measure entries are triad_003, triad_012, ..., triad_300. Print it, summary() it, plot() it, or take the plain frame with as.data.frame().

Conditions

Errors: dynet_unknown_measure (a name outside the forty above), dynet_needs_directed (one of the eight direction-reading selectors on an undirected network), dynet_no_sessions (sessions = "separate" without a session column), dynet_outside_observation (the requested range misses observed support; it also carries dynet_bad_input), and dynet_bad_input for every other broken contract – dn not a dynet, a non-character measure, an empty measure, an out-of-range start, end, step or window, end before start, and step combined with window = "all".

Warning: dynet_deprecated for the retired sample argument.

References

Freeman, L. C. (1979). Centrality in social networks: conceptual clarification. Social Networks, 1, 215-239. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1016/0378-8733(78)90021-7")}

Butts, C. T., Leslie-Cook, A., Krivitsky, P. N., & Bender-deMoll, S. (2024). networkDynamic: Dynamic Extensions for Network Objects, version 0.11.5. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.32614/CRAN.package.networkDynamic")}

Holme, P., & Saramaki, J. (2012). Temporal networks. Physics Reports, 519(3), 97-125. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1016/j.physrep.2012.03.001")}

Latapy, M., Viard, T., & Magnien, C. (2018). Stream graphs and link streams for the modeling of interactions over time. Social Network Analysis and Mining, 8, 61. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1007/s13278-018-0537-7")}

Andersen, P. K., & Gill, R. D. (1982). Cox's regression model for counting processes: a large sample study. Annals of Statistics, 10, 1100-1120. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1214/aos/1176345976")}

Krackhardt, D. (1994). Graph theoretical dimensions of informal organizations. In Computational Organization Theory (pp. 89-111). Lawrence Erlbaum.

Newman, M. E. J. (2002). Assortative mixing in networks. Physical Review Letters, 89, 208701. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1103/PhysRevLett.89.208701")}

Morris, M., & Kretzschmar, M. (1997). Concurrent partnerships and the spread of HIV. AIDS, 11(5), 641-648. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.1097/00002030-199705000-00012")}

Holland, P. W., & Leinhardt, S. (1976). Local structure in social networks. Sociological Methodology, 7, 1-45. \Sexpr[results=rd]{tools:::Rd_expr_doi("10.2307/270703")}

Wasserman, S., & Faust, K. (1994). Social Network Analysis: Methods and Applications. Cambridge University Press.

Examples

dn <- dynet(school_contacts)
metrics(dn, measure = "density")
metrics(dn, measure = c("density", "reciprocity", "transitivity"))
metrics(dn, measure = "density", step = 1, window = 3)
dyads <- metrics(dn, measure = c("mutual", "asymmetric"))
plot(dyads)


Dynet documentation built on Oct. 7, 2026, 5:08 p.m.