From e9653023b7f9eeb0c4911d7247ad7ce1c958a323 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:36:03 +0000 Subject: [PATCH 01/98] Initial plan From 08c9ca4b49c5ff549bf923a33aad738a62ae2db0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:42:17 +0000 Subject: [PATCH 02/98] Fix documentation: replace manynet/migraph references with netrics, create _pkgdown.yml, update DESCRIPTION Co-authored-by: jhollway <5595229+jhollway@users.noreply.github.com> --- DESCRIPTION | 2 +- R/mark_nodes.R | 4 +- R/mark_ties.R | 4 +- R/measure_features.R | 2 +- R/motif_census.R | 2 +- R/zzz.R | 2 +- README.Rmd | 80 +++++++++++------------------- _pkgdown.yml | 114 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 151 insertions(+), 59 deletions(-) create mode 100644 _pkgdown.yml diff --git a/DESCRIPTION b/DESCRIPTION index ced8554..f3fdc5c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -4,7 +4,7 @@ Version: 0.0.1 Date: 2025-12-05 Description: Many tools for marking, measuring, motifs and memberships of many different types of networks. - All functions operate with matrices, edge lists, and 'igraph', 'network', and 'tidygraph' objects, + All functions operate with matrices, edge lists, and 'igraph', 'network', 'tidygraph', and 'mnet' objects, on directed, multiplex, multimodal, signed, and other networks. URL: https://stocnet.github.io/netrics/ BugReports: https://github.com/stocnet/netrics/issues diff --git a/R/mark_nodes.R b/R/mark_nodes.R index d9c0d65..b65d3c7 100644 --- a/R/mark_nodes.R +++ b/R/mark_nodes.R @@ -403,7 +403,7 @@ node_is_random <- function(.data, size = 1){ #' three scores. #' By default, `ranks = 1`. #' @examples -#' #node_is_max(migraph::node_degree(ison_brandes)) +#' #node_is_max(node_by_degree(manynet::ison_brandes)) #' @export node_is_max <- function(node_measure, ranks = 1){ if(!inherits(node_measure, "node_measure")) @@ -429,7 +429,7 @@ node_is_max <- function(node_measure, ranks = 1){ #' @rdname mark_select #' @examples -#' #node_is_min(migraph::node_degree(ison_brandes)) +#' #node_is_min(node_by_degree(manynet::ison_brandes)) #' @export node_is_min <- function(node_measure, ranks = 1){ if(!inherits(node_measure, "node_measure")) diff --git a/R/mark_ties.R b/R/mark_ties.R index 80db090..947a026 100644 --- a/R/mark_ties.R +++ b/R/mark_ties.R @@ -341,7 +341,7 @@ tie_is_random <- function(.data, size = 1){ #' @rdname mark_tie_select #' @param tie_measure An object created by a `tie_` measure. #' @examples -#' # tie_is_max(migraph::tie_betweenness(ison_brandes)) +#' # tie_is_max(tie_by_betweenness(manynet::ison_brandes)) #' @export tie_is_max <- function(tie_measure){ out <- as.numeric(tie_measure) == max(as.numeric(tie_measure)) @@ -351,7 +351,7 @@ tie_is_max <- function(tie_measure){ #' @rdname mark_tie_select #' @examples -#' #tie_is_min(migraph::tie_betweenness(ison_brandes)) +#' #tie_is_min(tie_by_betweenness(manynet::ison_brandes)) #' @export tie_is_min <- function(tie_measure){ out <- as.numeric(tie_measure) == min(as.numeric(tie_measure)) diff --git a/R/measure_features.R b/R/measure_features.R index 7990b4e..b29120e 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -248,7 +248,7 @@ net_by_modularity <- function(.data, #' with the same dimensions. #' \eqn{SWI} also ranges between 0 and 1 with the same interpretation, #' but where there may not be a network for which \eqn{SWI = 1}. -#' @seealso [net_transitivity()] and [net_equivalency()] +#' @seealso [net_by_transitivity()] and [net_by_equivalency()] #' for how clustering is calculated #' @references #' ## On small-worldliness diff --git a/R/motif_census.R b/R/motif_census.R index 9bdd64d..c31862b 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -301,7 +301,7 @@ node_x_path <- function(.data){ #' @name motif_net #' @family motifs #' @inheritParams motif_node -#' @param object2 A second, two-mode migraph-consistent object. +#' @param object2 A second, two-mode network object. NULL #' @rdname motif_net diff --git a/R/zzz.R b/R/zzz.R index 8d3f887..6d5b5b0 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -18,7 +18,7 @@ greet_startup_cli <- function() { tips <- c( - "i" = "There are lots of ways to contribute to {.pkg manynet} at {.url https://github.com/stocnet/netrics/}.", + "i" = "There are lots of ways to contribute to {.pkg netrics} at {.url https://github.com/stocnet/netrics/}.", "i" = "Please share bugs, issues, or feature requests at {.url https://github.com/stocnet/netrics/issues}. It's really helpful!", # "i" = "To suppress package startup messages, use: `suppressPackageStartupMessages(library({.pkg netrics}))`.", # "i" = "Changing the theme of all your graphs is straightforward with `set_manynet_theme()`", diff --git a/README.Rmd b/README.Rmd index cd41700..fafe669 100644 --- a/README.Rmd +++ b/README.Rmd @@ -29,11 +29,6 @@ list_data <- function(string){ ![GitHub release (latest by date)](https://img.shields.io/github/v/release/stocnet/netrics) ![GitHub Release Date](https://img.shields.io/github/release-date/stocnet/netrics) [![Codecov test coverage](https://codecov.io/gh/stocnet/netrics/branch/main/graph/badge.svg)](https://app.codecov.io/gh/stocnet/netrics?branch=main) - - - - - ## About the package @@ -51,7 +46,8 @@ which can mess up your pretty presentation or paper. This can make learning and using network analysis tools in R challenging. By contrast, `{netrics}` offers _many_ analytic tools that work on _many_ (if not most) types and kinds of networks. -It helps researchers make, modify, mark, measure, and identify nodes' motifs and memberships in networks. +It helps researchers mark, measure, and identify nodes' motifs and memberships in networks. +All functions operate on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects. For graph drawing see [`{autograph}`](https://stocnet.github.io/autograph/), and for further testing and modelling capabilities see [`{migraph}`](https://stocnet.github.io/migraph/) and the other [stocnet](https://github.com/stocnet) packages. @@ -60,7 +56,6 @@ see [`{migraph}`](https://stocnet.github.io/migraph/) and the other [stocnet](ht - [Motifs](#motifs) - [Memberships](#memberships) - [Measuring](#measuring) -- [Tutorials](#tutorials) - [Installation](#installation) - [Stable](#stable) - [Development](#development) @@ -75,7 +70,7 @@ marks, measures, motifs, and memberships. Marks are logical scalars or vectors, measures are numeric, memberships categorical, and motifs result in tabular outputs. -`{manynet}`'s `*is_*()` functions offer fast logical tests of various properties. +`{netrics}`'s `*is_*()` functions offer fast logical tests of various properties. Whereas `is_*()` returns a single logical value for the network, `node_is_*()` returns a logical vector the length of the number of nodes in the network, and `tie_is_*()` returns a logical vector the length of the number of ties in the network. @@ -88,14 +83,14 @@ the maximum or minimum, respectively, node or tie according to some measure (see ## Motifs -`{manynet}`'s `*by_*()` functions tabulate nodes' frequency in various motifs. +`{netrics}`'s `*by_*()` functions tabulate nodes' frequency in various motifs. These include: - `r list_functions("_by_")` ## Memberships -`{manynet}`'s `*in_*()` functions identify nodes' membership in some grouping, +`{netrics}`'s `*in_*()` functions identify nodes' membership in some grouping, such as a community or component. These functions always return a character vector, indicating e.g. that the first node is a member of group "A", the second in group "B", etc. @@ -108,18 +103,18 @@ Gould-Fernandez brokerage roles for a one-mode network, and the Jasny-Lubell brokerage roles for a two-mode network. These can be analysed alone, or used as a profile for establishing equivalence. -`{manynet}` offers both HCA and CONCOR algorithms, +`{netrics}` offers both HCA and CONCOR algorithms, as well as elbow, silhouette, and strict methods for _k_-cluster selection. Plot of a dendrogram of structural equivalence -`{manynet}` also includes functions for establishing membership on other bases, +`{netrics}` also includes functions for establishing membership on other bases, such as typical community detection algorithms, as well as component and core-periphery partitioning algorithms. ## Measuring -`{manynet}` also offers a large and growing smorgasbord of measures that +`{netrics}` also offers a large and growing smorgasbord of measures that can be used at the node, tie, and network level to measure some feature, property, or quantity of the network. Each recognises whether the network is directed or undirected, @@ -140,31 +135,18 @@ Here are some examples: - _Diffusion_: e.g. `net_reproduction()`, `net_immunity()`, `node_thresholds()` There is a lot here, -so we recommend you explore [the list of functions](https://stocnet.github.io/migraph/reference/index.html) to find out more. - -## Tutorials - -This package includes tutorials to help new and experienced users -learn how they can conduct social network analysis using the package. -These tutorials leverage the additional package `{learnr}` (see [here](https://rstudio.github.io/learnr/)), -but we have made it easy to use `{manynet}` or `{migraph}` tutorials -right out of the box: - -```{r learnr-tutes} -run_tute() -# run_tute("tutorial1") -``` +so we recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. ## Installation ### Stable -The easiest way to install the latest stable version of `{manynet}` is via CRAN. +The easiest way to install the latest stable version of `{netrics}` is via CRAN. Simply open the R console and enter: -`install.packages('manynet')` +`install.packages('netrics')` -`library(manynet)` will then load the package and make the data and tutorials (see below) contained within the package available. +`library(netrics)` will then load the package and make the functions contained within the package available. ### Development @@ -173,46 +155,42 @@ for slightly earlier access to new features or for testing, you may wish to download and install the binaries from Github or install from source locally. The latest binary releases for all major OSes -- Windows, Mac, and Linux -- -can be found [here](https://github.com/stocnet/manynet/releases/latest). +can be found [here](https://github.com/stocnet/netrics/releases/latest). Download the appropriate binary for your operating system, and install using an adapted version of the following commands: -- For Windows: `install.packages("~/Downloads/manynet_winOS.zip", repos = NULL)` -- For Mac: `install.packages("~/Downloads/manynet_macOS.tgz", repos = NULL)` -- For Unix: `install.packages("~/Downloads/manynet_linuxOS.tar.gz", repos = NULL)` +- For Windows: `install.packages("~/Downloads/netrics_winOS.zip", repos = NULL)` +- For Mac: `install.packages("~/Downloads/netrics_macOS.tgz", repos = NULL)` +- For Unix: `install.packages("~/Downloads/netrics_linuxOS.tar.gz", repos = NULL)` -To install from source the latest main version of `{manynet}` from Github, +To install from source the latest main version of `{netrics}` from Github, please install the `{remotes}` package from CRAN and then: - For latest stable version: -`remotes::install_github("stocnet/manynet")` +`remotes::install_github("stocnet/netrics")` - For latest development version: -`remotes::install_github("stocnet/manynet@develop")` +`remotes::install_github("stocnet/netrics@develop")` ### Other sources Those using Mac computers may also install using Macports: -`sudo port install R-manynet` +`sudo port install R-netrics` ## Relationship to other packages This package stands on the shoulders of several incredible packages. In terms of the objects it works with, -this package aims to provide an updated, more comprehensive replacement for `{intergraph}`. -As such it works with objects in `{igraph}` and `{network}` formats, -but also equally well with base matrices and edgelists (data frames), -and formats from several other packages. - -The user interface is inspired in some ways by Thomas Lin Pedersen's excellent `{tidygraph}` package, -though makes some different decisions, -and uses the quickest `{igraph}` or `{network}` routines where available. - -`{manynet}` has inherited most of its core functionality from its maternal package, `{migraph}`. -`{migraph}` continues to offer more analytic and modelling functions that builds upon -the architecture provided by `{manynet}`. -For more, please check out `{migraph}` directly. +`{netrics}` operates on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects. +It uses the quickest `{igraph}` or `{network}` routines where available. + +`{netrics}` contains the analytic functions (measures, memberships, and motifs) +from `{manynet}`, allowing `{manynet}` to concentrate on working with +different network classes and modifying them. +`{netrics}` is also designed to work well with +[`{autograph}`](https://stocnet.github.io/autograph/) for graph drawing, +and [`{migraph}`](https://stocnet.github.io/migraph/) for further testing and modelling capabilities. ## Funding details diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..911b989 --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,114 @@ +url: https://stocnet.github.io/netrics/ + +template: + bootstrap: 5 + +reference: +- title: Marking + desc: > + Logical tests of properties for networks, nodes, and ties. + contents: + - mark_nodes + - mark_ties + - mark_select + - mark_tie_select + - mark_core + - mark_diff + - mark_triangles + +- title: Measuring + desc: > + Numeric measures of properties for networks, nodes, and ties. + +- subtitle: Centrality + desc: > + Measures of node and network centrality. + contents: + - measure_central_degree + - measure_central_close + - measure_central_between + - measure_central_eigen + +- subtitle: Closure + desc: > + Measures of network closure and clustering. + contents: + - measure_closure + +- subtitle: Cohesion + desc: > + Measures of network cohesion. + contents: + - measure_cohesion + - measure_fragmentation + - measure_breadth + +- subtitle: Features + desc: > + Measures of topological features such as core-periphery, + modularity, small-worldness, scale-freeness, and balance. + contents: + - measure_features + - measure_periods + +- subtitle: Heterogeneity + desc: > + Measures of network diversity and heterogeneity. + contents: + - measure_heterogeneity + +- subtitle: Hierarchy + desc: > + Measures of network hierarchy. + contents: + - measure_hierarchy + +- subtitle: Holes + desc: > + Measures of structural holes and brokerage opportunity. + contents: + - measure_holes + +- subtitle: Diffusion + desc: > + Measures of diffusion properties for networks and nodes. + contents: + - measure_diffusion_net + - measure_diffusion_node + - measure_diffusion_infection + +- title: Memberships + desc: > + Categorical classifications of nodes into groups. + contents: + - member_community_hier + - member_community_non + - member_components + - member_core + - member_equivalence + - member_cliques + - member_brokerage + - member_diffusion + +- title: Motifs + desc: > + Tabulations of nodes' and networks' participation in + network substructures and motifs. + contents: + - motif_net + - motif_node + - motif_brokerage + - motif_diffusion + +- title: Models + desc: > + Clustering algorithms and methods for selecting the + number of clusters. + contents: + - model_cluster + - model_kselect + +- title: Other + contents: + - defunct + - reexports From 174f25892ab8b78c9a0fcc5f3068b4ffd273df99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:44:29 +0000 Subject: [PATCH 03/98] Update README.md to match README.Rmd fixes (replace manynet/migraph refs, remove tutorials section, update installation) Co-authored-by: jhollway <5595229+jhollway@users.noreply.github.com> --- README.md | 100 ++++++++++++++++-------------------------------------- 1 file changed, 30 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index b7b0180..d4893a7 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,6 @@ Release Date](https://img.shields.io/github/release-date/stocnet/netrics) [![Codecov test coverage](https://codecov.io/gh/stocnet/netrics/branch/main/graph/badge.svg)](https://app.codecov.io/gh/stocnet/netrics?branch=main) - - - - - ## About the package @@ -38,7 +33,7 @@ make learning and using network analysis tools in R challenging. By contrast, `{netrics}` offers *many* analytic tools that work on *many* (if not most) types and kinds of networks. It helps researchers -make, modify, mark, measure, and identify nodes’ motifs and memberships +mark, measure, and identify nodes’ motifs and memberships in networks. For graph drawing see [`{autograph}`](https://stocnet.github.io/autograph/), and for further testing and modelling capabilities see @@ -49,7 +44,6 @@ testing and modelling capabilities see - [Motifs](#motifs) - [Memberships](#memberships) - [Measuring](#measuring) -- [Tutorials](#tutorials) - [Installation](#installation) - [Stable](#stable) - [Development](#development) @@ -63,7 +57,7 @@ own pretty `print()` and `plot()` methods: marks, measures, motifs, and memberships. Marks are logical scalars or vectors, measures are numeric, memberships categorical, and motifs result in tabular outputs. -`{manynet}`’s `*is_*()` functions offer fast logical tests of various +`{netrics}`’s `*is_*()` functions offer fast logical tests of various properties. Whereas `is_*()` returns a single logical value for the network, `node_is_*()` returns a logical vector the length of the number of nodes in the network, and `tie_is_*()` returns a logical vector the @@ -87,7 +81,7 @@ maximum or minimum, respectively, node or tie according to some measure ## Motifs -`{manynet}`‘s `*by_*()` functions tabulate nodes’ frequency in various +`{netrics}`‘s `*by_*()` functions tabulate nodes’ frequency in various motifs. These include: - `net_by_adhesion()`, `net_by_assortativity()`, `net_by_balance()`, @@ -130,7 +124,7 @@ motifs. These include: ## Memberships -`{manynet}`‘s `*in_*()` functions identify nodes’ membership in some +`{netrics}`‘s `*in_*()` functions identify nodes’ membership in some grouping, such as a community or component. These functions always return a character vector, indicating e.g. that the first node is a member of group “A”, the second in group “B”, etc. @@ -149,18 +143,18 @@ participation in Gould-Fernandez brokerage roles for a one-mode network, and the Jasny-Lubell brokerage roles for a two-mode network. These can be analysed alone, or used as a profile for establishing -equivalence. `{manynet}` offers both HCA and CONCOR algorithms, as well +equivalence. `{netrics}` offers both HCA and CONCOR algorithms, as well as elbow, silhouette, and strict methods for *k*-cluster selection. Plot of a dendrogram of structural equivalence -`{manynet}` also includes functions for establishing membership on other +`{netrics}` also includes functions for establishing membership on other bases, such as typical community detection algorithms, as well as component and core-periphery partitioning algorithms. ## Measuring -`{manynet}` also offers a large and growing smorgasbord of measures that +`{netrics}` also offers a large and growing smorgasbord of measures that can be used at the node, tie, and network level to measure some feature, property, or quantity of the network. Each recognises whether the network is directed or undirected, weighted or unweighted, one-mode or @@ -187,48 +181,20 @@ can be overrided. Here are some examples: `node_thresholds()` There is a lot here, so we recommend you explore [the list of -functions](https://stocnet.github.io/migraph/reference/index.html) to +functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. -## Tutorials - -This package includes tutorials to help new and experienced users learn -how they can conduct social network analysis using the package. These -tutorials leverage the additional package `{learnr}` (see -[here](https://rstudio.github.io/learnr/)), but we have made it easy to -use `{manynet}` or `{migraph}` tutorials right out of the box: - -``` r -run_tute() -#> Checking tutorials in stocnet packages ■■■■■■■■■■■ 33% | … -#> # A tibble: 10 × 3 -#> package name title -#> -#> 1 manynet tutorial0 Intro to R -#> 2 manynet tutorial1 Data -#> 3 autograph tutorial2 Visualisation -#> 4 manynet tutorial3 Centrality -#> 5 manynet tutorial4 Cohesion and Community -#> 6 manynet tutorial5 Position and Equivalence -#> 7 manynet tutorial6 Topology and Resilience -#> 8 migraph tutorial7 Diffusion and Learning -#> 9 migraph tutorial8 Diversity and Regression -#> 10 migraph tutorial9 Modelling with ERGMs -#> ℹ You can run a tutorial by typing e.g `run_tute('tutorial1')` or `run_tute('Data')` into the console. -# run_tute("tutorial1") -``` - ## Installation ### Stable -The easiest way to install the latest stable version of `{manynet}` is +The easiest way to install the latest stable version of `{netrics}` is via CRAN. Simply open the R console and enter: -`install.packages('manynet')` +`install.packages('netrics')` -`library(manynet)` will then load the package and make the data and -tutorials (see below) contained within the package available. +`library(netrics)` will then load the package and make the functions +contained within the package available. ### Development @@ -236,51 +202,45 @@ For the latest development version, for slightly earlier access to new features or for testing, you may wish to download and install the binaries from Github or install from source locally. The latest binary releases for all major OSes – Windows, Mac, and Linux – can be found -[here](https://github.com/stocnet/manynet/releases/latest). Download the +[here](https://github.com/stocnet/netrics/releases/latest). Download the appropriate binary for your operating system, and install using an adapted version of the following commands: - For Windows: - `install.packages("~/Downloads/manynet_winOS.zip", repos = NULL)` + `install.packages("~/Downloads/netrics_winOS.zip", repos = NULL)` - For Mac: - `install.packages("~/Downloads/manynet_macOS.tgz", repos = NULL)` + `install.packages("~/Downloads/netrics_macOS.tgz", repos = NULL)` - For Unix: - `install.packages("~/Downloads/manynet_linuxOS.tar.gz", repos = NULL)` + `install.packages("~/Downloads/netrics_linuxOS.tar.gz", repos = NULL)` -To install from source the latest main version of `{manynet}` from +To install from source the latest main version of `{netrics}` from Github, please install the `{remotes}` package from CRAN and then: - For latest stable version: - `remotes::install_github("stocnet/manynet")` + `remotes::install_github("stocnet/netrics")` - For latest development version: - `remotes::install_github("stocnet/manynet@develop")` + `remotes::install_github("stocnet/netrics@develop")` ### Other sources Those using Mac computers may also install using Macports: -`sudo port install R-manynet` +`sudo port install R-netrics` ## Relationship to other packages This package stands on the shoulders of several incredible packages. -In terms of the objects it works with, this package aims to provide an -updated, more comprehensive replacement for `{intergraph}`. As such it -works with objects in `{igraph}` and `{network}` formats, but also -equally well with base matrices and edgelists (data frames), and formats -from several other packages. - -The user interface is inspired in some ways by Thomas Lin Pedersen’s -excellent `{tidygraph}` package, though makes some different decisions, -and uses the quickest `{igraph}` or `{network}` routines where -available. - -`{manynet}` has inherited most of its core functionality from its -maternal package, `{migraph}`. `{migraph}` continues to offer more -analytic and modelling functions that builds upon the architecture -provided by `{manynet}`. For more, please check out `{migraph}` -directly. +In terms of the objects it works with, +`{netrics}` operates on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects. +It uses the quickest `{igraph}` or `{network}` routines where available. + +`{netrics}` contains the analytic functions (measures, memberships, and motifs) +from `{manynet}`, allowing `{manynet}` to concentrate on working with +different network classes and modifying them. +`{netrics}` is also designed to work well with +[`{autograph}`](https://stocnet.github.io/autograph/) for graph drawing, +and [`{migraph}`](https://stocnet.github.io/migraph/) for further testing and modelling capabilities. ## Funding details From 0badd415f94dfbf146301598f293f42bc43afea6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:46:33 +0000 Subject: [PATCH 04/98] Fix roxygen examples: remove manynet:: prefix from ison_brandes to match package conventions Co-authored-by: jhollway <5595229+jhollway@users.noreply.github.com> --- R/mark_nodes.R | 4 ++-- R/mark_ties.R | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/mark_nodes.R b/R/mark_nodes.R index b65d3c7..98f4d72 100644 --- a/R/mark_nodes.R +++ b/R/mark_nodes.R @@ -403,7 +403,7 @@ node_is_random <- function(.data, size = 1){ #' three scores. #' By default, `ranks = 1`. #' @examples -#' #node_is_max(node_by_degree(manynet::ison_brandes)) +#' #node_is_max(node_by_degree(ison_brandes)) #' @export node_is_max <- function(node_measure, ranks = 1){ if(!inherits(node_measure, "node_measure")) @@ -429,7 +429,7 @@ node_is_max <- function(node_measure, ranks = 1){ #' @rdname mark_select #' @examples -#' #node_is_min(node_by_degree(manynet::ison_brandes)) +#' #node_is_min(node_by_degree(ison_brandes)) #' @export node_is_min <- function(node_measure, ranks = 1){ if(!inherits(node_measure, "node_measure")) diff --git a/R/mark_ties.R b/R/mark_ties.R index 947a026..10c7fd1 100644 --- a/R/mark_ties.R +++ b/R/mark_ties.R @@ -341,7 +341,7 @@ tie_is_random <- function(.data, size = 1){ #' @rdname mark_tie_select #' @param tie_measure An object created by a `tie_` measure. #' @examples -#' # tie_is_max(tie_by_betweenness(manynet::ison_brandes)) +#' # tie_is_max(tie_by_betweenness(ison_brandes)) #' @export tie_is_max <- function(tie_measure){ out <- as.numeric(tie_measure) == max(as.numeric(tie_measure)) @@ -351,7 +351,7 @@ tie_is_max <- function(tie_measure){ #' @rdname mark_tie_select #' @examples -#' #tie_is_min(tie_by_betweenness(manynet::ison_brandes)) +#' #tie_is_min(tie_by_betweenness(ison_brandes)) #' @export tie_is_min <- function(tie_measure){ out <- as.numeric(tie_measure) == min(as.numeric(tie_measure)) From 9d7494e463dee552d893ea5730759c0658ee69c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 06:29:41 +0000 Subject: [PATCH 05/98] Clarify package architecture: marks in netrics, is_*() in manynet, autograph depends on both Co-authored-by: jhollway <5595229+jhollway@users.noreply.github.com> --- DESCRIPTION | 4 ++ README.Rmd | 98 ++++++++++++++++++++++++------------------- README.md | 117 ++++++++++++++++++++++++++++------------------------ 3 files changed, 122 insertions(+), 97 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f3fdc5c..eb433af 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -4,6 +4,10 @@ Version: 0.0.1 Date: 2025-12-05 Description: Many tools for marking, measuring, motifs and memberships of many different types of networks. + Marks ('node_is_*()' and 'tie_is_*()') identify structural positions, + measures ('*_by_*()') quantify network properties, + memberships ('*_in_*()') classify nodes into groups, + and motifs ('*_x_*()') tabulate substructure participation. All functions operate with matrices, edge lists, and 'igraph', 'network', 'tidygraph', and 'mnet' objects, on directed, multiplex, multimodal, signed, and other networks. URL: https://stocnet.github.io/netrics/ diff --git a/README.Rmd b/README.Rmd index fafe669..4b9a46f 100644 --- a/README.Rmd +++ b/README.Rmd @@ -33,24 +33,19 @@ list_data <- function(string){ ## About the package -While many awesome packages for network analysis exist for R, -all with their own offerings and advantages, -they also all have their own vocabulary, syntax, -and expected formats for data inputs and analytic outputs. -Many of these packages only work on _some_ types of networks -(usually one-mode, simple, directed or undirected networks) for _some_ types of analysis; -if you want to analyse a different type of network or try a different analysis, -a different package is needed. -And they can rely on a very different visual language (and sometimes plotting engine), -which can mess up your pretty presentation or paper. -This can make learning and using network analysis tools in R challenging. - -By contrast, `{netrics}` offers _many_ analytic tools that work on _many_ (if not most) types and kinds of networks. -It helps researchers mark, measure, and identify nodes' motifs and memberships in networks. -All functions operate on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects. -For graph drawing see [`{autograph}`](https://stocnet.github.io/autograph/), +`{netrics}` is the analytic engine of the [stocnet](https://github.com/stocnet) ecosystem. +It provides _many_ tools for marking, measuring, and identifying nodes' motifs and memberships +in _many_ (if not most) types and kinds of networks. +All functions operate on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects, +and recognise directed, weighted, multiplex, multimodal, signed, and other types of networks. + +`{netrics}` depends on [`{manynet}`](https://stocnet.github.io/manynet/), +which handles working with different network classes, converting between them, and modifying them. +Network-level logical tests (e.g. `is_directed()`, `is_twomode()`) remain in `{manynet}`, +while node- and tie-level analytic marks (e.g. `node_is_*()`, `tie_is_*()`) are in `{netrics}`. +For graph drawing, see [`{autograph}`](https://stocnet.github.io/autograph/), and for further testing and modelling capabilities -see [`{migraph}`](https://stocnet.github.io/migraph/) and the other [stocnet](https://github.com/stocnet) packages. +see [`{migraph}`](https://stocnet.github.io/migraph/). - [Marking](#marking) - [Motifs](#motifs) @@ -70,10 +65,11 @@ marks, measures, motifs, and memberships. Marks are logical scalars or vectors, measures are numeric, memberships categorical, and motifs result in tabular outputs. -`{netrics}`'s `*is_*()` functions offer fast logical tests of various properties. -Whereas `is_*()` returns a single logical value for the network, +`{netrics}`'s `node_is_*()` and `tie_is_*()` functions offer fast logical tests +of node- and tie-level properties. `node_is_*()` returns a logical vector the length of the number of nodes in the network, and `tie_is_*()` returns a logical vector the length of the number of ties in the network. +Note that network-level tests such as `is_directed()` and `is_twomode()` are in `{manynet}`. - `r list_functions("^node_is_")` - `r list_functions("^tie_is_")` @@ -83,14 +79,15 @@ the maximum or minimum, respectively, node or tie according to some measure (see ## Motifs -`{netrics}`'s `*by_*()` functions tabulate nodes' frequency in various motifs. +`{netrics}`'s `*_by_*()` functions offer numeric measures +as well as tabulations of nodes' frequency in various motifs. These include: - `r list_functions("_by_")` ## Memberships -`{netrics}`'s `*in_*()` functions identify nodes' membership in some grouping, +`{netrics}`'s `*_in_*()` functions identify nodes' membership in some grouping, such as a community or component. These functions always return a character vector, indicating e.g. that the first node is a member of group "A", the second in group "B", etc. @@ -123,16 +120,16 @@ All return normalized values wherever possible, though this can be overrided. Here are some examples: -- _Centrality_: `node_degree()`, `node_closeness()`, `node_betweenness()`, and `node_eigenvector()`, - `net_degree()`, `net_closeness()`, `net_betweenness()`, and `net_eigenvector()` -- _Cohesion_: `net_density()`, `net_reciprocity()`, `net_transitivity()`, `net_equivalency()`, and `net_congruency()` -- _Hierarchy_: `net_connectedness()`, `net_efficiency()`, `net_upperbound()` -- _Resilience_: `net_components()`, `net_cohesion()`, `net_adhesion()`, `net_diameter()`, `net_length()` -- _Innovation_: e.g. `node_redundancy()`, `node_effsize()`, `node_efficiency()`, `node_constraint()`, `node_hierarchy()` -- _Diversity_: `net_richness()`, `net_diversity()`, `net_heterophily()`, `net_assortativity()`, - `node_richness()`, `node_diversity()`, `node_heterophily()`, `node_assortativity()` -- _Topology_: e.g. `net_core()`, `net_factions()`, `net_modularity()`, `net_smallworld()`, `net_balance()` -- _Diffusion_: e.g. `net_reproduction()`, `net_immunity()`, `node_thresholds()` +- _Centrality_: `node_by_degree()`, `node_by_closeness()`, `node_by_betweenness()`, and `node_by_eigenvector()`, + `net_by_degree()`, `net_by_closeness()`, `net_by_betweenness()`, and `net_by_eigenvector()` +- _Cohesion_: `net_by_density()`, `net_by_reciprocity()`, `net_by_transitivity()`, `net_by_equivalency()`, and `net_by_congruency()` +- _Hierarchy_: `net_by_connectedness()`, `net_by_efficiency()`, `net_by_upperbound()` +- _Resilience_: `net_by_components()`, `net_by_cohesion()`, `net_by_adhesion()`, `net_by_diameter()`, `net_by_length()` +- _Innovation_: e.g. `node_by_redundancy()`, `node_by_effsize()`, `node_by_efficiency()`, `node_by_constraint()`, `node_by_hierarchy()` +- _Diversity_: `net_by_richness()`, `net_by_diversity()`, `net_by_heterophily()`, `net_by_assortativity()`, + `node_by_richness()`, `node_by_diversity()`, `node_by_heterophily()`, `node_by_homophily()` +- _Topology_: e.g. `net_by_core()`, `net_by_factions()`, `net_by_modularity()`, `net_by_smallworld()`, `net_by_balance()` +- _Diffusion_: e.g. `net_by_reproduction()`, `net_by_immunity()`, `node_by_thresholds()` There is a lot here, so we recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. @@ -179,18 +176,33 @@ Those using Mac computers may also install using Macports: ## Relationship to other packages -This package stands on the shoulders of several incredible packages. - -In terms of the objects it works with, -`{netrics}` operates on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects. -It uses the quickest `{igraph}` or `{network}` routines where available. - -`{netrics}` contains the analytic functions (measures, memberships, and motifs) -from `{manynet}`, allowing `{manynet}` to concentrate on working with -different network classes and modifying them. -`{netrics}` is also designed to work well with -[`{autograph}`](https://stocnet.github.io/autograph/) for graph drawing, -and [`{migraph}`](https://stocnet.github.io/migraph/) for further testing and modelling capabilities. +`{netrics}` is part of the [stocnet](https://github.com/stocnet) ecosystem of R packages +for network analysis. +The packages are designed to be modular, with clear roles and dependencies: + +- [`{manynet}`](https://stocnet.github.io/manynet/): + The foundation package for working with network data. + It handles network classes (matrices, `{igraph}`, `{network}`, `{tidygraph}`, `mnet`), + coercion between them, modification, and network-level logical tests (`is_*()` functions). +- **`{netrics}`**: + The analytic package containing all measures (`*_by_*()` functions), + memberships (`*_in_*()` functions), motifs (`*_x_*()` functions), + and node- and tie-level marks (`node_is_*()`, `tie_is_*()` functions). + `{netrics}` depends on `{manynet}`. +- [`{autograph}`](https://stocnet.github.io/autograph/): + The graph drawing package. + `{autograph}` depends on both `{manynet}` (for network classes) + and `{netrics}` (for analytic results to visualise), + since it would typically be used with both. +- [`{migraph}`](https://stocnet.github.io/migraph/): + The modelling and testing package, + building on both `{manynet}` and `{netrics}`. + +Node- and tie-level marks such as `node_is_cutpoint()` and `tie_is_bridge()` are kept +in `{netrics}` rather than `{manynet}` because they are analytic functions that identify +structural positions in the network. +Network-level property tests like `is_directed()` remain in `{manynet}` +because they describe the type of data rather than an analytic result. ## Funding details diff --git a/README.md b/README.md index d4893a7..3ff1ac9 100644 --- a/README.md +++ b/README.md @@ -20,25 +20,19 @@ coverage](https://codecov.io/gh/stocnet/netrics/branch/main/graph/badge.svg)](ht ## About the package -While many awesome packages for network analysis exist for R, all with -their own offerings and advantages, they also all have their own -vocabulary, syntax, and expected formats for data inputs and analytic -outputs. Many of these packages only work on *some* types of networks -(usually one-mode, simple, directed or undirected networks) for *some* -types of analysis; if you want to analyse a different type of network or -try a different analysis, a different package is needed. And they can -rely on a very different visual language (and sometimes plotting -engine), which can mess up your pretty presentation or paper. This can -make learning and using network analysis tools in R challenging. - -By contrast, `{netrics}` offers *many* analytic tools that work on -*many* (if not most) types and kinds of networks. It helps researchers -mark, measure, and identify nodes’ motifs and memberships -in networks. For graph drawing see -[`{autograph}`](https://stocnet.github.io/autograph/), and for further -testing and modelling capabilities see -[`{migraph}`](https://stocnet.github.io/migraph/) and the other -[stocnet](https://github.com/stocnet) packages. +`{netrics}` is the analytic engine of the [stocnet](https://github.com/stocnet) ecosystem. +It provides *many* tools for marking, measuring, and identifying nodes’ motifs and memberships +in *many* (if not most) types and kinds of networks. +All functions operate on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects, +and recognise directed, weighted, multiplex, multimodal, signed, and other types of networks. + +`{netrics}` depends on [`{manynet}`](https://stocnet.github.io/manynet/), +which handles working with different network classes, converting between them, and modifying them. +Network-level logical tests (e.g. `is_directed()`, `is_twomode()`) remain in `{manynet}`, +while node- and tie-level analytic marks (e.g. `node_is_*()`, `tie_is_*()`) are in `{netrics}`. +For graph drawing, see [`{autograph}`](https://stocnet.github.io/autograph/), +and for further testing and modelling capabilities +see [`{migraph}`](https://stocnet.github.io/migraph/). - [Marking](#marking) - [Motifs](#motifs) @@ -57,11 +51,11 @@ own pretty `print()` and `plot()` methods: marks, measures, motifs, and memberships. Marks are logical scalars or vectors, measures are numeric, memberships categorical, and motifs result in tabular outputs. -`{netrics}`’s `*is_*()` functions offer fast logical tests of various -properties. Whereas `is_*()` returns a single logical value for the -network, `node_is_*()` returns a logical vector the length of the number -of nodes in the network, and `tie_is_*()` returns a logical vector the -length of the number of ties in the network. +`{netrics}`’s `node_is_*()` and `tie_is_*()` functions offer fast logical tests +of node- and tie-level properties. +`node_is_*()` returns a logical vector the length of the number of nodes in the network, +and `tie_is_*()` returns a logical vector the length of the number of ties in the network. +Note that network-level tests such as `is_directed()` and `is_twomode()` are in `{manynet}`. - `node_is_core()`, `node_is_cutpoint()`, `node_is_exposed()`, `node_is_fold()`, `node_is_independent()`, `node_is_infected()`, @@ -161,24 +155,24 @@ network is directed or undirected, weighted or unweighted, one-mode or two-mode. All return normalized values wherever possible, though this can be overrided. Here are some examples: -- *Centrality*: `node_degree()`, `node_closeness()`, - `node_betweenness()`, and `node_eigenvector()`, `net_degree()`, - `net_closeness()`, `net_betweenness()`, and `net_eigenvector()` -- *Cohesion*: `net_density()`, `net_reciprocity()`, - `net_transitivity()`, `net_equivalency()`, and `net_congruency()` -- *Hierarchy*: `net_connectedness()`, `net_efficiency()`, - `net_upperbound()` -- *Resilience*: `net_components()`, `net_cohesion()`, `net_adhesion()`, - `net_diameter()`, `net_length()` -- *Innovation*: e.g. `node_redundancy()`, `node_effsize()`, - `node_efficiency()`, `node_constraint()`, `node_hierarchy()` -- *Diversity*: `net_richness()`, `net_diversity()`, `net_heterophily()`, - `net_assortativity()`, `node_richness()`, `node_diversity()`, - `node_heterophily()`, `node_assortativity()` -- *Topology*: e.g. `net_core()`, `net_factions()`, `net_modularity()`, - `net_smallworld()`, `net_balance()` -- *Diffusion*: e.g. `net_reproduction()`, `net_immunity()`, - `node_thresholds()` +- *Centrality*: `node_by_degree()`, `node_by_closeness()`, + `node_by_betweenness()`, and `node_by_eigenvector()`, `net_by_degree()`, + `net_by_closeness()`, `net_by_betweenness()`, and `net_by_eigenvector()` +- *Cohesion*: `net_by_density()`, `net_by_reciprocity()`, + `net_by_transitivity()`, `net_by_equivalency()`, and `net_by_congruency()` +- *Hierarchy*: `net_by_connectedness()`, `net_by_efficiency()`, + `net_by_upperbound()` +- *Resilience*: `net_by_components()`, `net_by_cohesion()`, `net_by_adhesion()`, + `net_by_diameter()`, `net_by_length()` +- *Innovation*: e.g. `node_by_redundancy()`, `node_by_effsize()`, + `node_by_efficiency()`, `node_by_constraint()`, `node_by_hierarchy()` +- *Diversity*: `net_by_richness()`, `net_by_diversity()`, `net_by_heterophily()`, + `net_by_assortativity()`, `node_by_richness()`, `node_by_diversity()`, + `node_by_heterophily()`, `node_by_homophily()` +- *Topology*: e.g. `net_by_core()`, `net_by_factions()`, `net_by_modularity()`, + `net_by_smallworld()`, `net_by_balance()` +- *Diffusion*: e.g. `net_by_reproduction()`, `net_by_immunity()`, + `node_by_thresholds()` There is a lot here, so we recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to @@ -229,18 +223,33 @@ Those using Mac computers may also install using Macports: ## Relationship to other packages -This package stands on the shoulders of several incredible packages. - -In terms of the objects it works with, -`{netrics}` operates on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects. -It uses the quickest `{igraph}` or `{network}` routines where available. - -`{netrics}` contains the analytic functions (measures, memberships, and motifs) -from `{manynet}`, allowing `{manynet}` to concentrate on working with -different network classes and modifying them. -`{netrics}` is also designed to work well with -[`{autograph}`](https://stocnet.github.io/autograph/) for graph drawing, -and [`{migraph}`](https://stocnet.github.io/migraph/) for further testing and modelling capabilities. +`{netrics}` is part of the [stocnet](https://github.com/stocnet) ecosystem of R packages +for network analysis. +The packages are designed to be modular, with clear roles and dependencies: + +- [`{manynet}`](https://stocnet.github.io/manynet/): + The foundation package for working with network data. + It handles network classes (matrices, `{igraph}`, `{network}`, `{tidygraph}`, `mnet`), + coercion between them, modification, and network-level logical tests (`is_*()` functions). +- **`{netrics}`**: + The analytic package containing all measures (`*_by_*()` functions), + memberships (`*_in_*()` functions), motifs (`*_x_*()` functions), + and node- and tie-level marks (`node_is_*()`, `tie_is_*()` functions). + `{netrics}` depends on `{manynet}`. +- [`{autograph}`](https://stocnet.github.io/autograph/): + The graph drawing package. + `{autograph}` depends on both `{manynet}` (for network classes) + and `{netrics}` (for analytic results to visualise), + since it would typically be used with both. +- [`{migraph}`](https://stocnet.github.io/migraph/): + The modelling and testing package, + building on both `{manynet}` and `{netrics}`. + +Node- and tie-level marks such as `node_is_cutpoint()` and `tie_is_bridge()` are kept +in `{netrics}` rather than `{manynet}` because they are analytic functions that identify +structural positions in the network. +Network-level property tests like `is_directed()` remain in `{manynet}` +because they describe the type of data rather than an analytic result. ## Funding details From ae17369a39b72c469ac4b035869988349a138558 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 06:33:08 +0000 Subject: [PATCH 06/98] Reorganize README: separate Measures (_by_) from Motifs (_x_), fix naming conventions Co-authored-by: jhollway <5595229+jhollway@users.noreply.github.com> --- README.Rmd | 45 +++++++++++++++------------------------- README.md | 58 ++++++++++++++++++++-------------------------------- _pkgdown.yml | 10 ++++----- 3 files changed, 44 insertions(+), 69 deletions(-) diff --git a/README.Rmd b/README.Rmd index 4b9a46f..6382dd1 100644 --- a/README.Rmd +++ b/README.Rmd @@ -48,9 +48,9 @@ and for further testing and modelling capabilities see [`{migraph}`](https://stocnet.github.io/migraph/). - [Marking](#marking) +- [Measures](#measures) - [Motifs](#motifs) - [Memberships](#memberships) -- [Measuring](#measuring) - [Installation](#installation) - [Stable](#stable) - [Development](#development) @@ -77,14 +77,20 @@ Note that network-level tests such as `is_directed()` and `is_twomode()` are in The `*is_max()` and `*is_min()` functions are used to identify the maximum or minimum, respectively, node or tie according to some measure (see below). -## Motifs +## Measures -`{netrics}`'s `*_by_*()` functions offer numeric measures -as well as tabulations of nodes' frequency in various motifs. +`{netrics}`'s `*_by_*()` functions offer numeric measures at the network, node, and tie level. These include: - `r list_functions("_by_")` +## Motifs + +`{netrics}`'s `*_x_*()` functions tabulate nodes' and networks' frequency in various motifs. +These include: + +- `r list_functions("_x_")` + ## Memberships `{netrics}`'s `*_in_*()` functions identify nodes' membership in some grouping, @@ -109,30 +115,13 @@ as well as elbow, silhouette, and strict methods for _k_-cluster selection. such as typical community detection algorithms, as well as component and core-periphery partitioning algorithms. -## Measuring - -`{netrics}` also offers a large and growing smorgasbord of measures that -can be used at the node, tie, and network level -to measure some feature, property, or quantity of the network. -Each recognises whether the network is directed or undirected, -weighted or unweighted, one-mode or two-mode. -All return normalized values wherever possible, -though this can be overrided. -Here are some examples: - -- _Centrality_: `node_by_degree()`, `node_by_closeness()`, `node_by_betweenness()`, and `node_by_eigenvector()`, - `net_by_degree()`, `net_by_closeness()`, `net_by_betweenness()`, and `net_by_eigenvector()` -- _Cohesion_: `net_by_density()`, `net_by_reciprocity()`, `net_by_transitivity()`, `net_by_equivalency()`, and `net_by_congruency()` -- _Hierarchy_: `net_by_connectedness()`, `net_by_efficiency()`, `net_by_upperbound()` -- _Resilience_: `net_by_components()`, `net_by_cohesion()`, `net_by_adhesion()`, `net_by_diameter()`, `net_by_length()` -- _Innovation_: e.g. `node_by_redundancy()`, `node_by_effsize()`, `node_by_efficiency()`, `node_by_constraint()`, `node_by_hierarchy()` -- _Diversity_: `net_by_richness()`, `net_by_diversity()`, `net_by_heterophily()`, `net_by_assortativity()`, - `node_by_richness()`, `node_by_diversity()`, `node_by_heterophily()`, `node_by_homophily()` -- _Topology_: e.g. `net_by_core()`, `net_by_factions()`, `net_by_modularity()`, `net_by_smallworld()`, `net_by_balance()` -- _Diffusion_: e.g. `net_by_reproduction()`, `net_by_immunity()`, `node_by_thresholds()` - -There is a lot here, -so we recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. +The measures are organised into several broad categories, including: +_Centrality_, _Cohesion_, _Hierarchy_, _Innovation_ (structural holes), +_Diversity_ (heterogeneity), _Topology_ (features), and _Diffusion_. +Each measure recognises whether the network is directed or undirected, +weighted or unweighted, one-mode or two-mode, +and returns normalized values wherever possible. +We recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. ## Installation diff --git a/README.md b/README.md index 3ff1ac9..0db54bf 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ and for further testing and modelling capabilities see [`{migraph}`](https://stocnet.github.io/migraph/). - [Marking](#marking) +- [Measures](#measures) - [Motifs](#motifs) - [Memberships](#memberships) -- [Measuring](#measuring) - [Installation](#installation) - [Stable](#stable) - [Development](#development) @@ -73,10 +73,10 @@ The `*is_max()` and `*is_min()` functions are used to identify the maximum or minimum, respectively, node or tie according to some measure (see below). -## Motifs +## Measures -`{netrics}`‘s `*by_*()` functions tabulate nodes’ frequency in various -motifs. These include: +`{netrics}`‘s `*_by_*()` functions offer numeric measures at the network, node, and tie level. +These include: - `net_by_adhesion()`, `net_by_assortativity()`, `net_by_balance()`, `net_by_betweenness()`, `net_by_change()`, `net_by_closeness()`, @@ -116,9 +116,19 @@ motifs. These include: `tie_by_betweenness()`, `tie_by_closeness()`, `tie_by_cohesion()`, `tie_by_degree()`, `tie_by_eigenvector()` +## Motifs + +`{netrics}`’s `*_x_*()` functions tabulate nodes’ and networks’ frequency in various motifs. +These include: + +- `net_x_brokerage()`, `net_x_dyad()`, `net_x_hazard()`, + `net_x_mixed()`, `net_x_tetrad()`, `net_x_triad()`, + `node_x_brokerage()`, `node_x_dyad()`, `node_x_exposure()`, + `node_x_path()`, `node_x_tetrad()`, `node_x_tie()`, `node_x_triad()` + ## Memberships -`{netrics}`‘s `*in_*()` functions identify nodes’ membership in some +`{netrics}`‘s `*_in_*()` functions identify nodes’ membership in some grouping, such as a community or component. These functions always return a character vector, indicating e.g. that the first node is a member of group “A”, the second in group “B”, etc. @@ -146,37 +156,13 @@ as elbow, silhouette, and strict methods for *k*-cluster selection. bases, such as typical community detection algorithms, as well as component and core-periphery partitioning algorithms. -## Measuring - -`{netrics}` also offers a large and growing smorgasbord of measures that -can be used at the node, tie, and network level to measure some feature, -property, or quantity of the network. Each recognises whether the -network is directed or undirected, weighted or unweighted, one-mode or -two-mode. All return normalized values wherever possible, though this -can be overrided. Here are some examples: - -- *Centrality*: `node_by_degree()`, `node_by_closeness()`, - `node_by_betweenness()`, and `node_by_eigenvector()`, `net_by_degree()`, - `net_by_closeness()`, `net_by_betweenness()`, and `net_by_eigenvector()` -- *Cohesion*: `net_by_density()`, `net_by_reciprocity()`, - `net_by_transitivity()`, `net_by_equivalency()`, and `net_by_congruency()` -- *Hierarchy*: `net_by_connectedness()`, `net_by_efficiency()`, - `net_by_upperbound()` -- *Resilience*: `net_by_components()`, `net_by_cohesion()`, `net_by_adhesion()`, - `net_by_diameter()`, `net_by_length()` -- *Innovation*: e.g. `node_by_redundancy()`, `node_by_effsize()`, - `node_by_efficiency()`, `node_by_constraint()`, `node_by_hierarchy()` -- *Diversity*: `net_by_richness()`, `net_by_diversity()`, `net_by_heterophily()`, - `net_by_assortativity()`, `node_by_richness()`, `node_by_diversity()`, - `node_by_heterophily()`, `node_by_homophily()` -- *Topology*: e.g. `net_by_core()`, `net_by_factions()`, `net_by_modularity()`, - `net_by_smallworld()`, `net_by_balance()` -- *Diffusion*: e.g. `net_by_reproduction()`, `net_by_immunity()`, - `node_by_thresholds()` - -There is a lot here, so we recommend you explore [the list of -functions](https://stocnet.github.io/netrics/reference/index.html) to -find out more. +The measures are organised into several broad categories, including: +*Centrality*, *Cohesion*, *Hierarchy*, *Innovation* (structural holes), +*Diversity* (heterogeneity), *Topology* (features), and *Diffusion*. +Each measure recognises whether the network is directed or undirected, +weighted or unweighted, one-mode or two-mode, +and returns normalized values wherever possible. +We recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. ## Installation diff --git a/_pkgdown.yml b/_pkgdown.yml index 911b989..4339e82 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -6,7 +6,7 @@ template: reference: - title: Marking desc: > - Logical tests of properties for networks, nodes, and ties. + Logical tests (`node_is_*()`, `tie_is_*()`) of node- and tie-level properties. contents: - mark_nodes - mark_ties @@ -16,9 +16,9 @@ reference: - mark_diff - mark_triangles -- title: Measuring +- title: Measures desc: > - Numeric measures of properties for networks, nodes, and ties. + Numeric measures (`*_by_*()`) of properties for networks, nodes, and ties. - subtitle: Centrality desc: > @@ -79,7 +79,7 @@ reference: - title: Memberships desc: > - Categorical classifications of nodes into groups. + Categorical classifications (`*_in_*()`) of nodes into groups. contents: - member_community_hier - member_community_non @@ -92,7 +92,7 @@ reference: - title: Motifs desc: > - Tabulations of nodes' and networks' participation in + Tabulations (`*_x_*()`) of nodes' and networks' participation in network substructures and motifs. contents: - motif_net From 766da815a3d0f0f70724c7139e037a7381a487c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Mar 2026 06:34:04 +0000 Subject: [PATCH 07/98] Fix trailing whitespace in README.Rmd Co-authored-by: jhollway <5595229+jhollway@users.noreply.github.com> --- README.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.Rmd b/README.Rmd index 6382dd1..2370fb3 100644 --- a/README.Rmd +++ b/README.Rmd @@ -116,7 +116,7 @@ such as typical community detection algorithms, as well as component and core-periphery partitioning algorithms. The measures are organised into several broad categories, including: -_Centrality_, _Cohesion_, _Hierarchy_, _Innovation_ (structural holes), +_Centrality_, _Cohesion_, _Hierarchy_, _Innovation_ (structural holes), _Diversity_ (heterogeneity), _Topology_ (features), and _Diffusion_. Each measure recognises whether the network is directed or undirected, weighted or unweighted, one-mode or two-mode, From 5afdf7d0a59a9410a5ef8810629c80f5d38db177 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Tue, 3 Mar 2026 19:41:34 +0100 Subject: [PATCH 08/98] Set up data_objs of different types --- tests/testthat/helper-netrics.R | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/testthat/helper-netrics.R b/tests/testthat/helper-netrics.R index 0353258..be73dc1 100644 --- a/tests/testthat/helper-netrics.R +++ b/tests/testthat/helper-netrics.R @@ -66,3 +66,19 @@ bot5 <- function(res, dec = 4){ unname(round(res, dec))[(lr-4):lr] } else unname(res)[(lr-2):lr] } + +# data_objs <- mget(ls("package:manynet"), inherits = TRUE) +# # Filter to relevant objects +# # data_objs <- data_objs[grepl("ison_|fict_|irps_|mpn_", names(data_objs))] +# # data_objs <- data_objs[!grepl("starwars|physicians|potter", names(data_objs))] +# objs <- table_data() %>% dplyr::filter(!grepl("starwars|physicians|potter", dataset)) %>% +# dplyr::distinct(directed, weighted, twomode, labelled, signed, multiplex, longitudinal, dynamic, changing, .keep_all = TRUE) %>% +# dplyr::pull(dataset) %>% as.character() +# data_objs <- data_objs[objs] + +data_objs <- list(directed = generate_random(12, directed = TRUE), + undirected = generate_random(12, directed = FALSE), + twomode = generate_random(c(6,6)), + labelled = add_node_attribute(generate_random(12, directed = TRUE), "name", paste0("Node", 1:12)), + signed = to_signed(generate_random(12, directed = TRUE))) + From 11b6a5a764f335bc5a7c145838ef90af33fbb089 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Tue, 3 Mar 2026 19:41:58 +0100 Subject: [PATCH 09/98] Added node_marks tests --- tests/testthat/test-marks.R | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/testthat/test-marks.R diff --git a/tests/testthat/test-marks.R b/tests/testthat/test-marks.R new file mode 100644 index 0000000..9217e58 --- /dev/null +++ b/tests/testthat/test-marks.R @@ -0,0 +1,16 @@ +funs_objs <- mget(ls("package:netrics"), inherits = TRUE) +# Filter to relevant objects + +node_marks <- funs_objs[grepl("node_is_", names(funs_objs))] +for(fn in names(node_marks)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("node_is_recovered|neighbor|min|max|mean|latent|infected|core", fn)) + if(fn == "node_is_exposed"){ + expect_s3_class(node_marks[[fn]](data_objs[[ob]], mark = c(1,3)), "node_mark") + } else { + expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") + } + }) + } +} From d2a15025468a47d256087327c33c840d92c2d620 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Tue, 3 Mar 2026 19:42:07 +0100 Subject: [PATCH 10/98] Added tie_marks tests --- tests/testthat/test-marks.R | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/testthat/test-marks.R b/tests/testthat/test-marks.R index 9217e58..7567590 100644 --- a/tests/testthat/test-marks.R +++ b/tests/testthat/test-marks.R @@ -14,3 +14,22 @@ for(fn in names(node_marks)) { }) } } + +tie_marks <- funs_objs[grepl("tie_is_", names(funs_objs))] +for(fn in names(tie_marks)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("tie_is_max|tie_is_min|tie_is_recovered", fn)) + skip_if(grepl("tie_is_imbalanced", fn) && ob == "twomode") + if(fn == "tie_is_path"){ + expect_s3_class(tie_marks[[fn]](data_objs[[ob]], 1, 2), "tie_mark") + } else if(fn == "tie_is_max" || fn == "tie_is_min"){ + expect_s3_class(tie_marks[[fn]](tie_by_degree(data_objs[[ob]])), "tie_mark") + } else if(fn == "tie_is_infected"){ + expect_s3_class(tie_marks[[fn]](play_diffusion(data_objs[[ob]])), "tie_mark") + } else { + expect_s3_class(tie_marks[[fn]](data_objs[[ob]]), "tie_mark") + } + }) + } +} From ec5ec3366e9bdbb650d2072d97a27a50834e4111 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Tue, 3 Mar 2026 19:42:26 +0100 Subject: [PATCH 11/98] Using node_by_ and tie_by_ in examples --- man/mark_select.Rd | 4 ++-- man/mark_tie_select.Rd | 4 ++-- man/measure_features.Rd | 2 +- man/motif_net.Rd | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/man/mark_select.Rd b/man/mark_select.Rd index 9e89019..52689bf 100644 --- a/man/mark_select.Rd +++ b/man/mark_select.Rd @@ -44,8 +44,8 @@ are key because they minimise or, more often, maximise some measure. } \examples{ node_is_random(ison_brandes, 2) -#node_is_max(migraph::node_degree(ison_brandes)) -#node_is_min(migraph::node_degree(ison_brandes)) +#node_is_max(node_by_degree(ison_brandes)) +#node_is_min(node_by_degree(ison_brandes)) #node_is_mean(node_degree(ison_brandes)) } \seealso{ diff --git a/man/mark_tie_select.Rd b/man/mark_tie_select.Rd index 87e471e..8329e82 100644 --- a/man/mark_tie_select.Rd +++ b/man/mark_tie_select.Rd @@ -33,8 +33,8 @@ are key because they minimise or, more often, maximise some measure. } } \examples{ -# tie_is_max(migraph::tie_betweenness(ison_brandes)) -#tie_is_min(migraph::tie_betweenness(ison_brandes)) +# tie_is_max(tie_by_betweenness(ison_brandes)) +#tie_is_min(tie_by_betweenness(ison_brandes)) } \seealso{ Other marks: diff --git a/man/measure_features.Rd b/man/measure_features.Rd index 26fa8d2..cf14bdf 100644 --- a/man/measure_features.Rd +++ b/man/measure_features.Rd @@ -230,7 +230,7 @@ Cartwright, D., and Frank Harary. 1956. } } \seealso{ -\code{\link[manynet:measure_closure]{manynet::net_transitivity()}} and \code{\link[manynet:measure_closure]{manynet::net_equivalency()}} +\code{\link[=net_by_transitivity]{net_by_transitivity()}} and \code{\link[=net_by_equivalency]{net_by_equivalency()}} for how clustering is calculated Other measures: diff --git a/man/motif_net.Rd b/man/motif_net.Rd index 3223627..419f9f7 100644 --- a/man/motif_net.Rd +++ b/man/motif_net.Rd @@ -24,7 +24,7 @@ net_x_mixed(.data, object2) For more information on the standard coercion possible, see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} -\item{object2}{A second, two-mode migraph-consistent object.} +\item{object2}{A second, two-mode network object.} } \description{ These functions include ways to take a census of the graphlets From 5ffd21fd2392e0aabbe3930a0b8004e26223df36 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Tue, 3 Mar 2026 19:42:53 +0100 Subject: [PATCH 12/98] Fixed tie_is_random to return tie marks --- R/mark_ties.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/mark_ties.R b/R/mark_ties.R index 10c7fd1..3f6ca4f 100644 --- a/R/mark_ties.R +++ b/R/mark_ties.R @@ -335,7 +335,7 @@ tie_is_random <- function(.data, size = 1){ n <- manynet::net_ties(.data) out <- rep(FALSE, n) out[sample.int(n, size)] <- TRUE - make_node_mark(out, .data) + make_tie_mark(out, .data) } #' @rdname mark_tie_select From 19412635af467a009fdbe446c0cfda652aa5b538 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:09:52 +0100 Subject: [PATCH 13/98] Added .github files --- .github/.gitignore | 1 + .github/CODE_OF_CONDUCT.md | 25 +++ .github/CONTRIBUTING.md | 123 ++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 31 ++++ .../ISSUE_TEMPLATE/documentation_request.md | 12 ++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++ .github/ISSUE_TEMPLATE/test_request.md | 10 ++ .github/pull_request_template.md | 13 ++ .github/workflows/prchecks.yml | 74 +++++++++ .github/workflows/pushrelease.yml | 157 ++++++++++++++++++ 10 files changed, 466 insertions(+) create mode 100644 .github/.gitignore create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/documentation_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/test_request.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/prchecks.yml create mode 100644 .github/workflows/pushrelease.yml diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..2d19fc7 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..24aa0a3 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,25 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for +everyone, regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. + +Examples of unacceptable behavior by participants include the use of sexual language or +imagery, derogatory comments or personal attacks, trolling, public or private harassment, +insults, or other unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed +from the project team. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by +opening an issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the Contributor Covenant +(http://contributor-covenant.org), version 1.0.0, available at +http://contributor-covenant.org/version/1/0/0/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..dbbe01f --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,123 @@ +# Contributing + +Contributions to `netrics`, +whether in the form of issue identification, bug fixes, new code or documentation +are encouraged and welcome. + +## Aims + +Here is some things that Guy Kawasaki, Silicon Valley venture capitalist, +learned from Steve Jobs: + +- "Experts" are clueless. Especially self-declared ones. +- Customers cannot tell you what they need. They can help with evolution, but not revolution. +- Biggest challenges beget the best work. +- Design counts. Users will see the skin/UI of your product, not the great algorithms. +- Big graphics, big fonts. +- Jump curves---do things 10 times better, not 10 percent. +- All that truly matters is whether something works or doesn't work. Open or close, iPhone or Android, car or train, doesn't matter---make +it work. +- "Value" is different from "price". There is a class of people who do care about value. Ease of use -> less support costs. You have to create a unique and valuable product as an engineer. +- Real CEOs can demo. If you can't demo your own product, then quit. +- Real entrepreneurs ship, not slip. +- Some things need to be believed to be seen. + +## Git and Bitbucket + +`stocnet` projects are maintained using the git version control system. +A plain-English introduction to git can be found [here](https://blog.red-badger.com/2016/11/29/gitgithub-in-plain-english). +I recommend you read this before continuing. +A more recent motivation can be found [here](https://www.r-bloggers.com/2024/04/git-gud-version-control-best-practices/). +It will explain the basics of git version control, committing and repos, pulling and pushing, +branching and merging. + +Using git from the command line on your lap- or desktop can be intimidating, +but I recommend [Fork](https://git-fork.com) software for Mac and Windows. +This allows mostly visual management of commits, diffs, branches, etc. +There are various other git software packages available, but this one is fairly fully featured. + +The Github page allows to access the issues assigned to you and check the commits. +You can also access the documents in the repository, +although this won't be necessary after you have cloned it on your computer via Fork. + +## Style + +In terms of style, we are aiming for pleasant predictability in terms of user experience. +To that end, we have a regular syntax that users can rely on producing expected effects. + +## Fork + +### Cloning +Once you have downloaded Fork, the first thing you have to do is to +clone the remote repository on your computer. +Before cloning, you will be able to choose on which `branch` you want to work: +develop or main. + +### Pull +This command allows you to `pull` changes from the remote repository to your local repository on Sourcetree. +Make sure you do that before starting working on your files so you have the newest versions. +When pulling, make sure you choose master or develop, +depending on the branch you decided to work with. +Once you pulled, you have now all the new commits and files and +you can start working on your assigned tasks. +Note that you can access and open the files either from the Finder or from Fork. +Some documents might be stored using Large File Storage (LFS) to save space on the repository. + +### Commit and Push + +Once you have made modifications on a file and saved them, it will appear in your `commit` window. +Here you can control one last time your file, write the commit message with the +issue reference (see below) and commit. +Once your commit is ready, you can `push` them to the origin/main repository. +Note that you can click the "push immediately" box in the commit window +if you don't want to do it in two steps. +If you are working on a separate branch, +it is important to select this branch when pushing to origin/main. + +## Issues and tests + +Please use the issues tracker on Github to identify any function-related issues. +You can use these issues to track progress on the issue and +to comment or continue a conversation on that issue. +Currently issue tracking is only open to those involved in the project. + +The most useful issues are ones that precisely identify an error, +or propose a test that should pass but instead fails. +This package uses the `testthat` package for testing functions. +Please see the [testthat website](https://testthat.r-lib.org) for more details. + +## Bug fixing or adding new code + +Independent or assigned code contributions are most welcome. +When writing new code, please follow +[standard R guidelines](https://www.r-bloggers.com/🖊-r-coding-style-guide/). +It can help to use packages such as `lintr`, `goodpractice` and `formatR` +to ensure these are followed. + +Currently, commits can only be pushed to Bitbucket where they reference an existing issue. +If no issue exists for the code you have developed, please add an issue first before pushing. +Once the issue exists, you will need to mention the issue number (preceded by a hash symbol: #) +in the commit description: + +` Resolved #31 by adding a new function that does things, also updated documentation ` + +Where the issue hash (i.e. #31) is preceded by +`resolve`, `resolves`, `resolved`, `close`, `closes`, `closed`, `fix`, `fixes`, or `fixed` +(capitalised or not), +Github will automatically updated the status of the issue(s) mentioned. + +Our current syntactical standard is to mention the issue first and then +provide a short description of what the committed changes do +in relation to that issue. +Any ancillary changes can be mentioned after a comma. + +## Documentation + +A final way of contributing to the package is in developing the +vignettes/articles that illustrate the value added in the package. +Please contact me with any proposals here. + +Please note that the `netrics` project is released with a +[Contributor Code of Conduct](CODE_OF_CONDUCT.md). +By contributing to this project, you agree to abide by its terms. + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..cfd4a70 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Provide system information** + - OS: [e.g. MacOS Mojave 10.14.6] + - R version: [e.g. 3.5.3] + - migraph version: [e.g. 1.4.0] + +**To Reproduce** +Please consider providing a [reprex](https://reprex.tidyverse.org) (a reproducible example), +or outline the steps taken to reproduce the behaviour, e.g.: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error +If applicable, add screenshots to help explain your problem. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/documentation_request.md b/.github/ISSUE_TEMPLATE/documentation_request.md new file mode 100644 index 0000000..8c4e661 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_request.md @@ -0,0 +1,12 @@ +--- +name: Documentation request +about: Create a report to help us improve +title: '' +labels: documentation +assignees: '' + +--- + +**Name the function or functions that require further documentation here and in the title** + +Please describe what is not clear in the current documentation that can be expanded upon. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..cb310f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: feature_request +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/test_request.md b/.github/ISSUE_TEMPLATE/test_request.md new file mode 100644 index 0000000..35bf9ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/test_request.md @@ -0,0 +1,10 @@ +--- +name: Test request +about: Create a report to help us improve +title: '' +labels: tests +assignees: '' + +--- + +**Name the function or functions that require the tests here and in the title** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..4e13534 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +# Description + +# Checklist: + +- Documentation + - [ ] DESCRIPTION file version is bumped by the appropriate increment (major, minor, patch) + - [ ] Date in DESCRIPTION is correct + - [ ] Longer functions are commented inline or broken down into helper functions to help debugging +- PR form + - [ ] Title indicates expected version number + - [ ] PR description above and the NEWS.md file are aligned + - [ ] Description above itemizes changes under subsection titles, e.g. "## Data"" + - [ ] Closed, fixed, or related issues are referenced and explained in the description above, e.g. "Fixed #0 by adding A" diff --git a/.github/workflows/prchecks.yml b/.github/workflows/prchecks.yml new file mode 100644 index 0000000..7ad888f --- /dev/null +++ b/.github/workflows/prchecks.yml @@ -0,0 +1,74 @@ +on: + pull_request: + branches: + - main + +name: Binary checks + +jobs: + + build: + name: Build for ${{ matrix.config.os }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - {os: macOS-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: macOS} + - {os: windows-latest, r: 'release', artifact_name: '*.zip', asset_name: winOS} + - {os: ubuntu-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: linuxOS} + + env: + R_REMOTES_NO_ERRORS_FROM_WARNINGS: true + RSPM: ${{ matrix.config.rspm }} + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + cache-version: 2 + needs: check, build + extra-packages: | + any::rcmdcheck + + - uses: r-lib/actions/check-r-package@v2 + env: + _R_CHECK_FORCE_SUGGESTS_: false + with: + upload-snapshots: true + + - name: Binary + run: | + pkgbuild::clean_dll() + binary <- pkgbuild::build(binary = TRUE, needs_compilation = TRUE, compile_attributes = TRUE) + dir.create("build") + file.copy(binary, "build") + shell: Rscript {0} + + - name: Save binary artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.config.asset_name }} + path: build/ + + - name: Calculate code coverage + if: runner.os == 'macOS' + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: Rscript -e 'covr::codecov(token = Sys.getenv("CODECOV_TOKEN"))' + + - name: Lint + run: lintr::lint_package() + shell: Rscript {0} + + - name: Spell check + run: spelling::spell_check_package() + shell: Rscript {0} diff --git a/.github/workflows/pushrelease.yml b/.github/workflows/pushrelease.yml new file mode 100644 index 0000000..bd4a22b --- /dev/null +++ b/.github/workflows/pushrelease.yml @@ -0,0 +1,157 @@ +on: + push: + branches: + - main + +name: Check and release + +jobs: + + build: + name: Build for ${{ matrix.config.os }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - {os: macOS-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: macOS} + - {os: windows-latest, r: 'release', artifact_name: '*.zip', asset_name: winOS} + - {os: ubuntu-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: linuxOS} + + env: + R_REMOTES_NO_ERRORS_FROM_WARNINGS: true + RSPM: ${{ matrix.config.rspm }} + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + cache-version: 2 + needs: check, build + extra-packages: | + any::rcmdcheck + any::remotes + + - uses: r-lib/actions/check-r-package@v2 + env: + _R_CHECK_FORCE_SUGGESTS_: false + with: + upload-snapshots: true + + - name: Binary + run: | + pkgbuild::clean_dll() + binary <- pkgbuild::build(binary = TRUE, needs_compilation = TRUE, compile_attributes = TRUE) + dir.create("build") + file.copy(binary, "build") + shell: Rscript {0} + + - name: Save binary artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.config.asset_name }} + path: build/ + + - name: Calculate code coverage + if: runner.os == 'macOS' + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: Rscript -e 'covr::codecov(token = Sys.getenv("CODECOV_TOKEN"))' + + release: + name: Bump version and release + if: ${{ always() }} + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout one + uses: actions/checkout@v4 + with: + fetch-depth: '0' + - name: Bump version and push tag + id: newtag + uses: anothrNick/github-tag-action@1.39.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WITH_V: true + DEFAULT_BUMP: patch + RELEASE_BRANCHES: main + - name: Checkout two + uses: actions/checkout@v4 + + - name: Extract version + run: | + echo "PACKAGE_VERSION=$(grep '^Version' DESCRIPTION | sed 's/.*: *//')" >> $GITHUB_ENV + echo "PACKAGE_NAME=$(grep '^Package' DESCRIPTION | sed 's/.*: *//')" >> $GITHUB_ENV + + - name: Download binaries + uses: actions/download-artifact@v4 + + - name: Rename binaries release + shell: bash + run: | + ls -R + cp ./macOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.tgz . + cp ./linuxOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.tar.gz . + cp ./winOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.zip . + echo "Renamed files" + ls netrics_* + + - name: Create Release and Upload Assets + id: create_release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.newtag.outputs.tag }} + name: Release ${{ steps.newtag.outputs.tag }} + draft: false + prerelease: false + fail_on_unmatched_files: true + # Specify the assets you want to upload + files: | + netrics_*.tgz + netrics_*.tar.gz + netrics_*.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + pkgdown: + name: Build and deploy website + if: ${{ always() }} + needs: release + runs-on: macOS-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v2 + + - uses: r-lib/actions/setup-r@v2 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + cache-version: 2 + extra-packages: | + any::rcmdcheck + any::pkgdown + any::rsconnect + needs: check + + - name: Install package + run: R CMD INSTALL . + + - name: Deploy package + run: | + git config --local user.email "actions@github.com" + git config --local user.name "GitHub Actions" + Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' From 0cffc7bc30b5ec24b1686e52f9072650ad1c0716 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:12:59 +0100 Subject: [PATCH 14/98] Moved node_mark tests back to mark_nodes test to assist with parallelisation --- tests/testthat/test-mark_nodes.R | 17 ++++++++++++++++ tests/testthat/test-marks.R | 35 -------------------------------- 2 files changed, 17 insertions(+), 35 deletions(-) delete mode 100644 tests/testthat/test-marks.R diff --git a/tests/testthat/test-mark_nodes.R b/tests/testthat/test-mark_nodes.R index c20adde..f497c1f 100644 --- a/tests/testthat/test-mark_nodes.R +++ b/tests/testthat/test-mark_nodes.R @@ -1,5 +1,22 @@ set.seed(1234) +node_marks <- funs_objs[grepl("node_is_", names(funs_objs))] +for(fn in names(node_marks)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("node_is_recovered|neighbor|min|max|mean|latent|infected", fn)) + if(fn == "node_is_exposed"){ + expect_s3_class(node_marks[[fn]](data_objs[[ob]], mark = c(1,3)), "node_mark") + } else if(fn == "node_is_core"){ + skip_if(manynet::is_directed(data_objs[[ob]])) + expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") + } else { + expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") + } + }) + } +} + test_that("node_is_cutpoint", { expect_true(exists("node_is_cutpoint")) test_that("returns correct type", { diff --git a/tests/testthat/test-marks.R b/tests/testthat/test-marks.R deleted file mode 100644 index 7567590..0000000 --- a/tests/testthat/test-marks.R +++ /dev/null @@ -1,35 +0,0 @@ -funs_objs <- mget(ls("package:netrics"), inherits = TRUE) -# Filter to relevant objects - -node_marks <- funs_objs[grepl("node_is_", names(funs_objs))] -for(fn in names(node_marks)) { - for (ob in names(data_objs)) { - test_that(paste(fn, "works on", ob), { - skip_if(grepl("node_is_recovered|neighbor|min|max|mean|latent|infected|core", fn)) - if(fn == "node_is_exposed"){ - expect_s3_class(node_marks[[fn]](data_objs[[ob]], mark = c(1,3)), "node_mark") - } else { - expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") - } - }) - } -} - -tie_marks <- funs_objs[grepl("tie_is_", names(funs_objs))] -for(fn in names(tie_marks)) { - for (ob in names(data_objs)) { - test_that(paste(fn, "works on", ob), { - skip_if(grepl("tie_is_max|tie_is_min|tie_is_recovered", fn)) - skip_if(grepl("tie_is_imbalanced", fn) && ob == "twomode") - if(fn == "tie_is_path"){ - expect_s3_class(tie_marks[[fn]](data_objs[[ob]], 1, 2), "tie_mark") - } else if(fn == "tie_is_max" || fn == "tie_is_min"){ - expect_s3_class(tie_marks[[fn]](tie_by_degree(data_objs[[ob]])), "tie_mark") - } else if(fn == "tie_is_infected"){ - expect_s3_class(tie_marks[[fn]](play_diffusion(data_objs[[ob]])), "tie_mark") - } else { - expect_s3_class(tie_marks[[fn]](data_objs[[ob]]), "tie_mark") - } - }) - } -} From 252ba8eac0feee938c6f87e2a5d2e762f82e2d85 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:13:19 +0100 Subject: [PATCH 15/98] mark_ties tests back alone to assist with parallelisation too --- tests/testthat/test-mark_ties.R | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/testthat/test-mark_ties.R b/tests/testthat/test-mark_ties.R index a921e65..848ffe1 100644 --- a/tests/testthat/test-mark_ties.R +++ b/tests/testthat/test-mark_ties.R @@ -1,3 +1,22 @@ +tie_marks <- funs_objs[grepl("tie_is_", names(funs_objs))] +for(fn in names(tie_marks)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("tie_is_max|tie_is_min|tie_is_recovered", fn)) + skip_if(grepl("tie_is_imbalanced", fn) && ob == "twomode") + if(fn == "tie_is_path"){ + expect_s3_class(tie_marks[[fn]](data_objs[[ob]], 1, 2), "tie_mark") + } else if(fn == "tie_is_max" || fn == "tie_is_min"){ + expect_s3_class(tie_marks[[fn]](tie_by_degree(data_objs[[ob]])), "tie_mark") + } else if(fn == "tie_is_infected"){ + expect_s3_class(tie_marks[[fn]](play_diffusion(data_objs[[ob]])), "tie_mark") + } else { + expect_s3_class(tie_marks[[fn]](data_objs[[ob]]), "tie_mark") + } + }) + } +} + graph1 <- igraph::make_directed_graph(c(1,2,1,5,2,3,2,4,3,5,4,5,5,1)) graph2 <- igraph::make_undirected_graph(c(1,1,1,2,2,4,3,4,3,4)) From c9b3e74b281e93c8dd523613e67bbe3023be1a87 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:13:27 +0100 Subject: [PATCH 16/98] Dropped model_play tests --- tests/testthat/test-model_play.R | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 tests/testthat/test-model_play.R diff --git a/tests/testthat/test-model_play.R b/tests/testthat/test-model_play.R deleted file mode 100644 index d6cc692..0000000 --- a/tests/testthat/test-model_play.R +++ /dev/null @@ -1,14 +0,0 @@ - -test_that("play_diffusion works for named networks", { - expect_warning(named_SI <- play_diffusion(ison_adolescents, old_version = TRUE)) - expect_equal(named_SI$S + named_SI$I, named_SI$n) - expect_equal(summary(named_SI)$t[1], 0) - expect_equal(summary(named_SI)$nodes[1:4], c(1,2,3,5)) -}) - -test_that("play_diffusion works for named networks", { - expect_warning(named_SEI <- play_diffusion(ison_adolescents, latency = 1, old_version = TRUE)) - expect_equal(named_SEI$S + named_SEI$E + named_SEI$I, named_SEI$n) - expect_equal(summary(named_SEI)$t[1], 0) - expect_equal(summary(named_SEI)$nodes[1:4], c(1,2,NA,NA)) -}) From 438718b55e8ad0a08b954e1d034805c09e26dd1c Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:13:44 +0100 Subject: [PATCH 17/98] Fixed package description --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index eb433af..7437c6d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,8 +2,8 @@ Package: netrics Title: Many Ways to Measure and Classify Membership for Networks, Nodes, and Ties Version: 0.0.1 Date: 2025-12-05 -Description: Many tools for marking, measuring, motifs and memberships of - many different types of networks. +Description: Many tools for calculating network, node, or tie + marks, measures, motifs and memberships of many different types of networks. Marks ('node_is_*()' and 'tie_is_*()') identify structural positions, measures ('*_by_*()') quantify network properties, memberships ('*_in_*()') classify nodes into groups, From 126d29e2d032869f8532863f42f4e8bae447ae3a Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:20:41 +0100 Subject: [PATCH 18/98] Moved funs_objs to helper --- tests/testthat/helper-netrics.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/testthat/helper-netrics.R b/tests/testthat/helper-netrics.R index be73dc1..71b65f7 100644 --- a/tests/testthat/helper-netrics.R +++ b/tests/testthat/helper-netrics.R @@ -67,6 +67,8 @@ bot5 <- function(res, dec = 4){ } else unname(res)[(lr-2):lr] } +funs_objs <- mget(ls("package:netrics"), inherits = TRUE) + # data_objs <- mget(ls("package:manynet"), inherits = TRUE) # # Filter to relevant objects # # data_objs <- data_objs[grepl("ison_|fict_|irps_|mpn_", names(data_objs))] From 1bdd22a06da3b9e254d9b83db04bcccd57da7416 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:30:35 +0100 Subject: [PATCH 19/98] Fixed documentation errors in .github --- .github/CONTRIBUTING.md | 8 ++++---- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/pull_request_template.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index dbbe01f..703c456 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -22,7 +22,7 @@ it work. - Real entrepreneurs ship, not slip. - Some things need to be believed to be seen. -## Git and Bitbucket +## Git `stocnet` projects are maintained using the git version control system. A plain-English introduction to git can be found [here](https://blog.red-badger.com/2016/11/29/gitgithub-in-plain-english). @@ -36,7 +36,7 @@ but I recommend [Fork](https://git-fork.com) software for Mac and Windows. This allows mostly visual management of commits, diffs, branches, etc. There are various other git software packages available, but this one is fairly fully featured. -The Github page allows to access the issues assigned to you and check the commits. +The GitHub page allows to access the issues assigned to you and check the commits. You can also access the documents in the repository, although this won't be necessary after you have cloned it on your computer via Fork. @@ -76,7 +76,7 @@ it is important to select this branch when pushing to origin/main. ## Issues and tests -Please use the issues tracker on Github to identify any function-related issues. +Please use the issues tracker on GitHub to identify any function-related issues. You can use these issues to track progress on the issue and to comment or continue a conversation on that issue. Currently issue tracking is only open to those involved in the project. @@ -94,7 +94,7 @@ When writing new code, please follow It can help to use packages such as `lintr`, `goodpractice` and `formatR` to ensure these are followed. -Currently, commits can only be pushed to Bitbucket where they reference an existing issue. +Currently, commits can only be pushed to GitHub where they reference an existing issue. If no issue exists for the code you have developed, please add an issue first before pushing. Once the issue exists, you will need to mention the issue number (preceded by a hash symbol: #) in the commit description: diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index cfd4a70..d5d8f18 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -13,7 +13,7 @@ A clear and concise description of what the bug is. **Provide system information** - OS: [e.g. MacOS Mojave 10.14.6] - R version: [e.g. 3.5.3] - - migraph version: [e.g. 1.4.0] + - netrics version: [e.g. 1.4.0] **To Reproduce** Please consider providing a [reprex](https://reprex.tidyverse.org) (a reproducible example), diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4e13534..74c60e3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,5 +9,5 @@ - PR form - [ ] Title indicates expected version number - [ ] PR description above and the NEWS.md file are aligned - - [ ] Description above itemizes changes under subsection titles, e.g. "## Data"" + - [ ] Description above itemizes changes under subsection titles, e.g. "## Data" - [ ] Closed, fixed, or related issues are referenced and explained in the description above, e.g. "Fixed #0 by adding A" From e64f0068029cdd2c70ad9b5f4901a3557866be2c Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:30:59 +0100 Subject: [PATCH 20/98] Set seed for generate_random data_objs --- tests/testthat/helper-netrics.R | 1 + tests/testthat/test-mark_ties.R | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/testthat/helper-netrics.R b/tests/testthat/helper-netrics.R index 71b65f7..2b56964 100644 --- a/tests/testthat/helper-netrics.R +++ b/tests/testthat/helper-netrics.R @@ -78,6 +78,7 @@ funs_objs <- mget(ls("package:netrics"), inherits = TRUE) # dplyr::pull(dataset) %>% as.character() # data_objs <- data_objs[objs] +set.seed(1234) data_objs <- list(directed = generate_random(12, directed = TRUE), undirected = generate_random(12, directed = FALSE), twomode = generate_random(c(6,6)), diff --git a/tests/testthat/test-mark_ties.R b/tests/testthat/test-mark_ties.R index 848ffe1..28e874a 100644 --- a/tests/testthat/test-mark_ties.R +++ b/tests/testthat/test-mark_ties.R @@ -6,8 +6,6 @@ for(fn in names(tie_marks)) { skip_if(grepl("tie_is_imbalanced", fn) && ob == "twomode") if(fn == "tie_is_path"){ expect_s3_class(tie_marks[[fn]](data_objs[[ob]], 1, 2), "tie_mark") - } else if(fn == "tie_is_max" || fn == "tie_is_min"){ - expect_s3_class(tie_marks[[fn]](tie_by_degree(data_objs[[ob]])), "tie_mark") } else if(fn == "tie_is_infected"){ expect_s3_class(tie_marks[[fn]](play_diffusion(data_objs[[ob]])), "tie_mark") } else { From 6708453d9d5ea1dc00d81826f31264ffd3532ad2 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:31:09 +0100 Subject: [PATCH 21/98] #minor bump --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7437c6d..feee643 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: netrics Title: Many Ways to Measure and Classify Membership for Networks, Nodes, and Ties -Version: 0.0.1 -Date: 2025-12-05 +Version: 0.1.0 +Date: 2026-03-04 Description: Many tools for calculating network, node, or tie marks, measures, motifs and memberships of many different types of networks. Marks ('node_is_*()' and 'tie_is_*()') identify structural positions, From 46b719b0352d5bf762d73a2d195e834165decb75 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:43:42 +0100 Subject: [PATCH 22/98] Updated ignores --- .Rbuildignore | 18 +++++++++++++++ .gitignore | 64 +++++++++++++++------------------------------------ 2 files changed, 36 insertions(+), 46 deletions(-) diff --git a/.Rbuildignore b/.Rbuildignore index 91114bf..b80fbe7 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,2 +1,20 @@ +^CODE_OF_CONDUCT\.md$ +^LICENSE\.md$ ^.*\.Rproj$ ^\.Rproj\.user$ +^working$ +^\.github$ +^data-raw$ +data.csv +vignettes/*\.Rmd\.orig +vignettes/precompile\.R +^docs$ +^cache$ +^pkgdown$ +^cran-comments\.md$ +^CRAN-RELEASE$ +^doc$ +^Meta$ +^CRAN-SUBMISSION$ +^README\.Rmd$ +^.mailmap$ \ No newline at end of file diff --git a/.gitignore b/.gitignore index d915b79..d1b11a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,22 @@ -# History files +inst/doc +.Rproj.user .Rhistory -.Rapp.history - -# Session Data files .RData -.RDataTmp - -# User-specific files .Ruserdata - -# Example code in package build process -*-Ex.R - -# Output files from R CMD build -/*.tar.gz - -# Output files from R CMD check -/*.Rcheck/ - -# RStudio files -.Rproj.user/ - -# produced vignettes -vignettes/*.html -vignettes/*.pdf - -# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 -.httr-oauth - -# knitr and R markdown default cache directories -*_cache/ -/cache/ - -# Temporary files created by R markdown -*.utf8.md -*.knit.md - -# R Environment Variables -.Renviron - -# pkgdown site +.DS_Store +data-raw/* docs/ - -# translation temp files -po/*~ - -# RStudio Connect folder -rsconnect/ -*.DS_Store +working/* +/doc/ +/Meta/ +tests/testthat/Rplots.pdf +.DS_Store +CRAN-SUBMISSION +man/figures/unnamed-chunk-1-1.png +inst/tutorials/community/community_data/* +inst/tutorials/equivalence/equivalence_data/* +inst/tutorials/tutorial4/community_data/* +inst/tutorials/tutorial5/equivalence_data/* +toadd/* +.data.csv +cache/* From 6e1969382e8a5e70fd8e68b7867e47c5a984655a Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 13:45:47 +0100 Subject: [PATCH 23/98] Fixed random tidygraph dep --- tests/testthat/test-measure_features.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/testthat/test-measure_features.R b/tests/testthat/test-measure_features.R index 410530c..0033c63 100644 --- a/tests/testthat/test-measure_features.R +++ b/tests/testthat/test-measure_features.R @@ -56,8 +56,7 @@ test_that("net_balance works", { }) wavenet <- ison_adolescents %>% - tidygraph::activate(edges) %>% - mutate(wave = c(1, 1, 1, 1, 2, 2, 2, 3, 3, 3)) + mutate_ties(wave = c(1, 1, 1, 1, 2, 2, 2, 3, 3, 3)) test_that("net_waves works", { expect_equal(net_waves(ison_adolescents), 1) From 5a171bd1b25b3eae6bcbb1684647b52f6e9d118a Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 14:02:09 +0100 Subject: [PATCH 24/98] Added node_motifs tests --- R/measure_closure.R | 2 +- tests/testthat/test-motif_census.R | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/R/measure_closure.R b/R/measure_closure.R index 54fe819..deabaf4 100644 --- a/R/measure_closure.R +++ b/R/measure_closure.R @@ -120,7 +120,7 @@ net_by_equivalency <- function(.data) { #' @rdname measure_closure #' @examples -#' node_by_equivalency(ison_southern_women) +#' # node_by_equivalency(ison_southern_women) #' @export node_by_equivalency <- function(.data) { .data <- manynet::expect_nodes(.data) diff --git a/tests/testthat/test-motif_census.R b/tests/testthat/test-motif_census.R index f211806..ee758fb 100644 --- a/tests/testthat/test-motif_census.R +++ b/tests/testthat/test-motif_census.R @@ -1,3 +1,17 @@ +node_motifs <- funs_objs[grepl("node_x_", names(funs_objs))] +for(fn in names(node_motifs)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("exposure|brokerage", fn)) + skip_if(grepl("triad|dyad", fn) && is_twomode(data_objs[[ob]])) + if(fn == "x"){ + } else { + expect_s3_class(node_motifs[[fn]](data_objs[[ob]]), "node_motif") + } + }) + } +} + # # Census function family tests set.seed(123) task_eg <- to_named(to_uniplex(ison_algebra, "tasks")) From f9cf27cc5d1be8cdf3b25e060737010ff0086b43 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 14:07:54 +0100 Subject: [PATCH 25/98] Dropped node_by_equivalency measure from examples --- man/measure_closure.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/measure_closure.Rd b/man/measure_closure.Rd index 9befbd5..526ab26 100644 --- a/man/measure_closure.Rd +++ b/man/measure_closure.Rd @@ -72,7 +72,7 @@ node_by_reciprocity(to_unweighted(ison_networkers)) net_by_transitivity(ison_adolescents) node_by_transitivity(ison_adolescents) net_by_equivalency(ison_southern_women) -node_by_equivalency(ison_southern_women) +# node_by_equivalency(ison_southern_women) } \references{ \subsection{On equivalency or four-cycles}{ From 25769762b51c90951fed6c0ad0ec1a31ff4dc50c Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 14:15:55 +0100 Subject: [PATCH 26/98] Fixed features tests and examples --- R/measure_features.R | 2 +- man/measure_features.Rd | 2 +- tests/testthat/test-measure_features.R | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/measure_features.R b/R/measure_features.R index b29120e..5c50659 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -366,7 +366,7 @@ net_by_scalefree <- function(.data){ #' _Psychological Review_, 63(5): 277-293. #' \doi{10.1037/h0046049} #' @examples -#' net_by_balance(fict_marvel) +#' net_by_balance(to_uniplex(fict_marvel, "relationship")) #' @export net_by_balance <- function(.data) { diff --git a/man/measure_features.Rd b/man/measure_features.Rd index cf14bdf..acb9f90 100644 --- a/man/measure_features.Rd +++ b/man/measure_features.Rd @@ -141,7 +141,7 @@ net_by_smallworld(ison_southern_women) net_by_scalefree(ison_adolescents) net_by_scalefree(generate_scalefree(50, 1.5)) net_by_scalefree(create_lattice(100)) -net_by_balance(fict_marvel) +net_by_balance(to_uniplex(fict_marvel, "relationship")) } \references{ \subsection{On core-periphery}{ diff --git a/tests/testthat/test-measure_features.R b/tests/testthat/test-measure_features.R index 0033c63..fd87eee 100644 --- a/tests/testthat/test-measure_features.R +++ b/tests/testthat/test-measure_features.R @@ -59,7 +59,7 @@ wavenet <- ison_adolescents %>% mutate_ties(wave = c(1, 1, 1, 1, 2, 2, 2, 3, 3, 3)) test_that("net_waves works", { - expect_equal(net_waves(ison_adolescents), 1) + # expect_equal(net_waves(ison_adolescents), 1) expect_equal(net_waves(wavenet), 3) }) From 88b5fd41f98d18ce579356e404abe10b7842b474 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 14:22:53 +0100 Subject: [PATCH 27/98] Using fict_marvel correctly --- R/measure_heterogeneity.R | 2 +- man/measure_heterogeneity.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/measure_heterogeneity.R b/R/measure_heterogeneity.R index c8614a8..3ec9477 100644 --- a/R/measure_heterogeneity.R +++ b/R/measure_heterogeneity.R @@ -136,7 +136,7 @@ node_by_richness <- function(.data, attribute){ #' Princeton: Princeton University Press. #' \doi{10.1515/9781400835140} #' @examples -#' marvel_friends <- to_unsigned(fict_marvel, "positive") +#' marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") #' net_by_diversity(marvel_friends, "Gender") #' net_by_diversity(marvel_friends, "Appearances") #' @export diff --git a/man/measure_heterogeneity.Rd b/man/measure_heterogeneity.Rd index 05a456c..e6fec3a 100644 --- a/man/measure_heterogeneity.Rd +++ b/man/measure_heterogeneity.Rd @@ -156,7 +156,7 @@ where 1 indicates ties only between categories/groups and -1 ties only within ca \examples{ net_by_richness(ison_networkers) node_by_richness(ison_networkers, "Discipline") -marvel_friends <- to_unsigned(fict_marvel, "positive") +marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") net_by_diversity(marvel_friends, "Gender") net_by_diversity(marvel_friends, "Appearances") node_by_diversity(marvel_friends, "Gender") From a50e8f56a8ddb97c8db2fbe42412b141db52f99f Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 14:30:31 +0100 Subject: [PATCH 28/98] Improved net_by_mixed() to use mixed information --- R/motif_census.R | 7 +++---- man/motif_net.Rd | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/R/motif_census.R b/R/motif_census.R index c31862b..aa09fe5 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -442,14 +442,13 @@ net_x_tetrad <- function(.data){ #' _Network Science_ 5(2): 187–212. #' \doi{10.1017/nws.2017.8} #' @examples -#' marvel_friends <- to_unsigned(fict_marvel, "positive") -#' (mixed_cen <- net_x_mixed(marvel_friends, ison_marvel_teams)) +#' net_by_mixed(fict_marvel) #' @export net_x_mixed <- function (.data, object2) { .data <- manynet::expect_nodes(.data) if(missing(object2) && manynet::is_multiplex(.data)) { - # object2 <- to_uniplex(.data, ) - + object2 <- to_uniplex(.data, unique(manynet::tie_attribute(.data, "type"))[2]) + .data <- to_uniplex(.data, unique(manynet::tie_attribute(.data, "type"))[1]) } if(manynet::is_twomode(.data)) manynet::snet_abort("First object should be a one-mode network") diff --git a/man/motif_net.Rd b/man/motif_net.Rd index 419f9f7..2e49152 100644 --- a/man/motif_net.Rd +++ b/man/motif_net.Rd @@ -70,8 +70,7 @@ Graphs of these motifs can be shown using net_x_dyad(manynet::ison_algebra) net_x_triad(manynet::ison_adolescents) net_x_tetrad(ison_southern_women) -marvel_friends <- to_unsigned(fict_marvel, "positive") -(mixed_cen <- net_x_mixed(marvel_friends, ison_marvel_teams)) +net_by_mixed(fict_marvel) } \references{ \subsection{On the dyad census}{ From c247901f81d8c38c0ab494008629db2043e8f6de Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 14:30:42 +0100 Subject: [PATCH 29/98] Moved pkgdown yaml to own folder --- _pkgdown.yml | 114 ------------------------------------------- pkgdown/_pkgdown.yml | 99 +++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 114 deletions(-) delete mode 100644 _pkgdown.yml create mode 100644 pkgdown/_pkgdown.yml diff --git a/_pkgdown.yml b/_pkgdown.yml deleted file mode 100644 index 4339e82..0000000 --- a/_pkgdown.yml +++ /dev/null @@ -1,114 +0,0 @@ -url: https://stocnet.github.io/netrics/ - -template: - bootstrap: 5 - -reference: -- title: Marking - desc: > - Logical tests (`node_is_*()`, `tie_is_*()`) of node- and tie-level properties. - contents: - - mark_nodes - - mark_ties - - mark_select - - mark_tie_select - - mark_core - - mark_diff - - mark_triangles - -- title: Measures - desc: > - Numeric measures (`*_by_*()`) of properties for networks, nodes, and ties. - -- subtitle: Centrality - desc: > - Measures of node and network centrality. - contents: - - measure_central_degree - - measure_central_close - - measure_central_between - - measure_central_eigen - -- subtitle: Closure - desc: > - Measures of network closure and clustering. - contents: - - measure_closure - -- subtitle: Cohesion - desc: > - Measures of network cohesion. - contents: - - measure_cohesion - - measure_fragmentation - - measure_breadth - -- subtitle: Features - desc: > - Measures of topological features such as core-periphery, - modularity, small-worldness, scale-freeness, and balance. - contents: - - measure_features - - measure_periods - -- subtitle: Heterogeneity - desc: > - Measures of network diversity and heterogeneity. - contents: - - measure_heterogeneity - -- subtitle: Hierarchy - desc: > - Measures of network hierarchy. - contents: - - measure_hierarchy - -- subtitle: Holes - desc: > - Measures of structural holes and brokerage opportunity. - contents: - - measure_holes - -- subtitle: Diffusion - desc: > - Measures of diffusion properties for networks and nodes. - contents: - - measure_diffusion_net - - measure_diffusion_node - - measure_diffusion_infection - -- title: Memberships - desc: > - Categorical classifications (`*_in_*()`) of nodes into groups. - contents: - - member_community_hier - - member_community_non - - member_components - - member_core - - member_equivalence - - member_cliques - - member_brokerage - - member_diffusion - -- title: Motifs - desc: > - Tabulations (`*_x_*()`) of nodes' and networks' participation in - network substructures and motifs. - contents: - - motif_net - - motif_node - - motif_brokerage - - motif_diffusion - -- title: Models - desc: > - Clustering algorithms and methods for selecting the - number of clusters. - contents: - - model_cluster - - model_kselect - -- title: Other - contents: - - defunct - - reexports diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml new file mode 100644 index 0000000..7cf0103 --- /dev/null +++ b/pkgdown/_pkgdown.yml @@ -0,0 +1,99 @@ +url: https://stocnet.github.io/netrics/ +development: + mode: auto +template: + bootswatch: superhero +authors: + James Hollway: + href: https://jameshollway.com +navbar: + structure: + left: + - home + - intro + - reference + - news + right: + - search + - github + - cran + components: + home: + icon: fa-home fa-lg + href: index.html + aria-label: Go to home + reference: + text: Function Overview + href: reference/index.html + news: + text: News + href: news/index.html + github: + icon: "fab fa-github fa-lg" + href: https://github.com/stocnet/netrics + aria-label: View on Github + # cran: + # icon: "fab fa-r-project" + # href: https://cloud.r-project.org/package=netrics + # aria-label: View on CRAN +reference: + - title: "Marking" + desc: | + Functions for identifying properties of nodes or ties, + all returning logical scalars or vectors. + - subtitle: "Nodal marks" + desc: | + `node_is_*()` functions return a vector of logical values the length + of the nodes in the network. + contents: + - starts_with("node_is_") + - subtitle: "Tie marks" + desc: | + `tie_is_*()` functions return a vector of logical values the length + of the ties in the network. + contents: + - starts_with("tie_is_") + + - title: "Measuring" + desc: | + Functions for measuring networks and returning a numeric vector or value. + `net_` measures return one or, in some cases of two-mode measures, + two values. + All `node_` and `tie_` measures return a single vector, + the length of the nodes or ties in the network, respectively. + - subtitle: "Centrality" + contents: + - starts_with("measure_central") + - measure_holes + - measure_hierarchy + - subtitle: "Cohesion" + contents: + - measure_cohesion + - measure_closure + - measure_features + - measure_heterogeneity + - subtitle: "Dynamics" + contents: + - measure_periods + - starts_with("measure_diffusion") + + - title: "Memberships" + desc: | + Motifs are functions for calculating network subgraphs, + always return a matrix or table of nodes as rows and motif or other property as columns, + and can be recognised by the `_by_` in the function name. + Memberships are functions for identifying community, cluster, or class memberships, + always return a string vector the length of the nodes in the network, + and can be recognised by the `_in_` in the function name. + - subtitle: "Motifs" + contents: + - contains("_x_") + - subtitle: "Members" + contents: + - contains("_in_") + + - title: "Methods" + desc: "Methods used in other functions but documented here:" + contents: + - starts_with("model_") + From c9cb886f823b0acf961fde9dd02546074389f1df Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 14:47:48 +0100 Subject: [PATCH 30/98] Split motif testing into nodes and net --- tests/testthat/test-motif_net.R | 14 ++++++++++++++ .../{test-motif_census.R => test-motif_nodes.R} | 0 2 files changed, 14 insertions(+) create mode 100644 tests/testthat/test-motif_net.R rename tests/testthat/{test-motif_census.R => test-motif_nodes.R} (100%) diff --git a/tests/testthat/test-motif_net.R b/tests/testthat/test-motif_net.R new file mode 100644 index 0000000..b761b61 --- /dev/null +++ b/tests/testthat/test-motif_net.R @@ -0,0 +1,14 @@ +net_motifs <- funs_objs[grepl("net_x_", names(funs_objs))] +for(fn in names(net_motifs)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("exposure|brokerage|mixed|hazard", fn)) + skip_if(grepl("triad|dyad", fn) && is_twomode(data_objs[[ob]])) + if(fn == "x"){ + } else { + expect_s3_class(net_motifs[[fn]](data_objs[[ob]]), "network_motif") + } + }) + } +} + diff --git a/tests/testthat/test-motif_census.R b/tests/testthat/test-motif_nodes.R similarity index 100% rename from tests/testthat/test-motif_census.R rename to tests/testthat/test-motif_nodes.R From f1cab4699bbd06bdf12018de847b846d651a4f35 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 15:01:36 +0100 Subject: [PATCH 31/98] Added rotation tests for node_membs --- R/motif_census.R | 4 ++-- tests/testthat/test-member_nodes.R | 12 ++++++++++++ tests/testthat/test-motif_brokerage.R | 17 +++-------------- tests/testthat/test-motif_nodes.R | 11 +++++++++++ tests/testthat/test-motif_ties.R | 13 +++++++++++++ 5 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 tests/testthat/test-member_nodes.R create mode 100644 tests/testthat/test-motif_ties.R diff --git a/R/motif_census.R b/R/motif_census.R index aa09fe5..a730cec 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -447,8 +447,8 @@ net_x_tetrad <- function(.data){ net_x_mixed <- function (.data, object2) { .data <- manynet::expect_nodes(.data) if(missing(object2) && manynet::is_multiplex(.data)) { - object2 <- to_uniplex(.data, unique(manynet::tie_attribute(.data, "type"))[2]) - .data <- to_uniplex(.data, unique(manynet::tie_attribute(.data, "type"))[1]) + object2 <- manynet::to_uniplex(.data, unique(manynet::tie_attribute(.data, "type"))[2]) + .data <- manynet::to_uniplex(.data, unique(manynet::tie_attribute(.data, "type"))[1]) } if(manynet::is_twomode(.data)) manynet::snet_abort("First object should be a one-mode network") diff --git a/tests/testthat/test-member_nodes.R b/tests/testthat/test-member_nodes.R new file mode 100644 index 0000000..e1c8059 --- /dev/null +++ b/tests/testthat/test-member_nodes.R @@ -0,0 +1,12 @@ +node_membs <- funs_objs[grepl("node_in_", names(funs_objs))] +for(fn in names(node_membs)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("roulette|equivalence|adopter|brokering", fn)) + if(fn == "x"){ + } else { + expect_s3_class(node_membs[[fn]](data_objs[[ob]]), "node_member") + } + }) + } +} \ No newline at end of file diff --git a/tests/testthat/test-motif_brokerage.R b/tests/testthat/test-motif_brokerage.R index f482e03..5021c28 100644 --- a/tests/testthat/test-motif_brokerage.R +++ b/tests/testthat/test-motif_brokerage.R @@ -1,12 +1,12 @@ -test_that("node_brokering_activity works", { - test <- node_brokering_activity(ison_networkers, "Discipline") +test_that("node_by_brokering_activity works", { + test <- node_by_brokering_activity(ison_networkers, "Discipline") expect_s3_class(test, "node_measure") expect_equal(c(net_nodes(ison_networkers)), length(test)) expect_equal(top3(test), c(333,207,3)) }) test_that("node_brokering_exclusivity works", { - test <- node_brokering_exclusivity(ison_networkers, "Discipline") + test <- node_by_brokering_exclusivity(ison_networkers, "Discipline") expect_s3_class(test, "node_measure") expect_equal(c(net_nodes(ison_networkers)), length(test)) expect_equal(top3(test), c(1,0,0)) @@ -21,14 +21,3 @@ test_that("node_in_brokering works", { expect_output(print(summary(node_in_brokering(to_uniplex(fict_marvel, "affiliation")))), "Connectors") }) -test_that("node_by_brokerage works", { - test <- node_by_brokerage(ison_networkers, "Discipline") - expect_s3_class(test, "node_motif") - expect_equal(dim(test), c(32,6)) -}) - -test_that("net_by_brokerage works", { - test <- net_by_brokerage(ison_networkers, "Discipline") - expect_s3_class(test, "network_motif") - expect_equal(top3(names(test)), c("Coordinator","Itinerant","Gatekeeper")) -}) diff --git a/tests/testthat/test-motif_nodes.R b/tests/testthat/test-motif_nodes.R index ee758fb..110fdd5 100644 --- a/tests/testthat/test-motif_nodes.R +++ b/tests/testthat/test-motif_nodes.R @@ -94,3 +94,14 @@ test_that("node path census works", { ncol(node_by_path(ison_southern_women))) }) +test_that("node_x_brokerage works", { + test <- node_x_brokerage(ison_networkers, "Discipline") + expect_s3_class(test, "node_motif") + expect_equal(dim(test), c(32,6)) +}) + +test_that("net_x_brokerage works", { + test <- net_x_brokerage(ison_networkers, "Discipline") + expect_s3_class(test, "network_motif") + expect_equal(top3(names(test)), c("Coordinator","Itinerant","Gatekeeper")) +}) diff --git a/tests/testthat/test-motif_ties.R b/tests/testthat/test-motif_ties.R new file mode 100644 index 0000000..e39de75 --- /dev/null +++ b/tests/testthat/test-motif_ties.R @@ -0,0 +1,13 @@ +tie_motifs <- funs_objs[grepl("tie_x_", names(funs_objs))] +for(fn in names(tie_motifs)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("exposure|brokerage", fn)) + skip_if(grepl("triad|dyad", fn) && is_twomode(data_objs[[ob]])) + if(fn == "x"){ + } else { + expect_s3_class(tie_motifs[[fn]](data_objs[[ob]]), "tie_motif") + } + }) + } +} From d6bf7a0882071536d40b1e3a6a67a540d4c1635f Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 18:32:58 +0100 Subject: [PATCH 32/98] Fixed net_x_hierarchy() as it produces a motif --- R/measure_hierarchy.R | 2 +- man/measure_hierarchy.Rd | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/measure_hierarchy.R b/R/measure_hierarchy.R index 25e5e6f..baa91f6 100644 --- a/R/measure_hierarchy.R +++ b/R/measure_hierarchy.R @@ -33,7 +33,7 @@ NULL #' @rdname measure_hierarchy #' @export -net_by_hierarchy <- function(.data){ +net_x_hierarchy <- function(.data){ .data <- manynet::expect_nodes(.data) out <- data.frame(Connectedness = net_by_connectedness(.data), InvReciprocity = 1 - net_by_reciprocity(.data), diff --git a/man/measure_hierarchy.Rd b/man/measure_hierarchy.Rd index 6a03126..02c3ee5 100644 --- a/man/measure_hierarchy.Rd +++ b/man/measure_hierarchy.Rd @@ -2,13 +2,13 @@ % Please edit documentation in R/measure_hierarchy.R \name{measure_hierarchy} \alias{measure_hierarchy} -\alias{net_by_hierarchy} +\alias{net_x_hierarchy} \alias{net_by_connectedness} \alias{net_by_efficiency} \alias{net_by_upperbound} \title{Graph theoretic dimensions of hierarchy} \usage{ -net_by_hierarchy(.data) +net_x_hierarchy(.data) net_by_connectedness(.data) From 9657e8b2211b8f9193c3f925bac3a81c44804a51 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 18:33:09 +0100 Subject: [PATCH 33/98] Added net_measure tests --- tests/testthat/test-measure_net.R | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/testthat/test-measure_net.R diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R new file mode 100644 index 0000000..a5b7940 --- /dev/null +++ b/tests/testthat/test-measure_net.R @@ -0,0 +1,13 @@ +net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] +for(fn in names(net_meas)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("congruency|correlation|degree|diversity|core|change|heterophily|infection|reach|immunity|recovery|reproduction|richclub|homophily|stability|spatial|balance|strength|waves|transmiss", fn)) + skip_if(grepl("net_by_factions", fn) && ob == "twomode") + if(fn == "x"){ + } else { + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") + } + }) + } +} From 58c29457a4517c37076f38f1b7c195b4ae9030ec Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 18:33:38 +0100 Subject: [PATCH 34/98] Made sure net_by_reach() and net_by_harmonic() include call --- R/measure_centrality.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index 6fa51d1..2f148a1 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -930,7 +930,7 @@ net_by_reach <- function(.data, normalized = TRUE, cutoff = 2){ reaches <- node_by_reach(.data, normalized = FALSE, cutoff = cutoff) out <- sum(max(reaches) - reaches) if(normalized) out <- out / sum(manynet::net_nodes(.data) - reaches) - make_network_measure(out, .data) + make_network_measure(out, .data, call = deparse(sys.call())) } #' @rdname measure_central_close @@ -940,7 +940,7 @@ net_by_harmonic <- function(.data, normalized = TRUE, cutoff = 2){ harm <- node_by_harmonic(.data, normalized = FALSE, cutoff = cutoff) out <- sum(max(harm) - harm) if(normalized) out <- out / sum(manynet::net_nodes(.data) - harm) - make_network_measure(out, .data) + make_network_measure(out, .data, call = deparse(sys.call())) } # Eigenvector-like centralities #### From 74c6afd07c812e85e20a8bb9ce1bc3a6bd4eff71 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 18:33:49 +0100 Subject: [PATCH 35/98] Fixed namespace --- NAMESPACE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NAMESPACE b/NAMESPACE index d9ddd65..3b14718 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -29,7 +29,6 @@ export(net_by_equivalency) export(net_by_factions) export(net_by_harmonic) export(net_by_heterophily) -export(net_by_hierarchy) export(net_by_homophily) export(net_by_immunity) export(net_by_indegree) @@ -59,6 +58,7 @@ export(net_by_waves) export(net_x_brokerage) export(net_x_dyad) export(net_x_hazard) +export(net_x_hierarchy) export(net_x_mixed) export(net_x_tetrad) export(net_x_triad) From 0f4ede2b7b10af78ecc425afe1b39474f9f73141 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 20:35:23 +0100 Subject: [PATCH 36/98] Added tie measure tests --- tests/testthat/helper-netrics.R | 2 +- tests/testthat/test-measure_ties.R | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/testthat/test-measure_ties.R diff --git a/tests/testthat/helper-netrics.R b/tests/testthat/helper-netrics.R index 2b56964..c56911b 100644 --- a/tests/testthat/helper-netrics.R +++ b/tests/testthat/helper-netrics.R @@ -82,6 +82,6 @@ set.seed(1234) data_objs <- list(directed = generate_random(12, directed = TRUE), undirected = generate_random(12, directed = FALSE), twomode = generate_random(c(6,6)), - labelled = add_node_attribute(generate_random(12, directed = TRUE), "name", paste0("Node", 1:12)), + labelled = add_node_attribute(generate_random(12), "name", LETTERS[1:12]), signed = to_signed(generate_random(12, directed = TRUE))) diff --git a/tests/testthat/test-measure_ties.R b/tests/testthat/test-measure_ties.R new file mode 100644 index 0000000..3acbd94 --- /dev/null +++ b/tests/testthat/test-measure_ties.R @@ -0,0 +1,12 @@ +tie_meas <- funs_objs[grepl("tie_by_", names(funs_objs))] +for(fn in names(tie_meas)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("eigenvector|degree|cohesion|closeness", fn)) + if(fn == "x"){ + } else { + expect_s3_class(tie_meas[[fn]](data_objs[[ob]]), "tie_measure") + } + }) + } +} \ No newline at end of file From 0cd87ee3c148eefa778dff4ef5597dee3102a2ff Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 20:36:04 +0100 Subject: [PATCH 37/98] Using snet_progress_seq() instead of snet_progress_along(seq_nodes()) in node_by_equivalency() --- R/measure_closure.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/measure_closure.R b/R/measure_closure.R index deabaf4..a190f7a 100644 --- a/R/measure_closure.R +++ b/R/measure_closure.R @@ -126,7 +126,7 @@ node_by_equivalency <- function(.data) { .data <- manynet::expect_nodes(.data) # if(is_weighted(.data)) # snet_info("Using unweighted form of the network.") - out <- vapply(manynet::snet_progress_along(seq_nodes(.data)), function(i){ + out <- vapply(manynet::snet_progress_seq(.data), function(i){ threepaths <- igraph::all_simple_paths(.data, i, cutoff = 3, mode = "all") onepaths <- threepaths[vapply(threepaths, length, From aefd520b3d43a976230441cb4fd475ccb6845fdc Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 20:36:31 +0100 Subject: [PATCH 38/98] Fixed node_by_randomwalk() to work with two-mode networks --- R/measure_centrality.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index 2f148a1..37872df 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -797,7 +797,7 @@ node_by_vitality <- function(.data, normalized = TRUE){ node_by_randomwalk <- function(.data, normalized = TRUE){ .data <- manynet::expect_nodes(.data) # adjacency and degree matrices - A <- manynet::as_matrix(.data) + A <- manynet::as_matrix(manynet::to_multilevel(.data)) degs <- node_by_deg(.data) D <- diag(degs) From 25c5f7517cbbf31a40bb60cc3ef1d7bcbde17e2b Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 20:36:55 +0100 Subject: [PATCH 39/98] Fixed node_by_pagerank() to use igraph vector output --- R/measure_centrality.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index 37872df..de8d53e 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -1144,7 +1144,7 @@ node_by_alpha <- function(.data, alpha = 0.85){ #' @export node_by_pagerank <- function(.data){ .data <- manynet::expect_nodes(.data) - make_node_measure(igraph::page_rank(manynet::as_igraph(.data)), + make_node_measure(igraph::page_rank(manynet::as_igraph(.data))$vector, .data) } From 7f33c6a2601302f6eac656bbce038544e4baa6e9 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Wed, 4 Mar 2026 20:37:07 +0100 Subject: [PATCH 40/98] Added node_measure tests --- tests/testthat/test-measure_nodes.R | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/testthat/test-measure_nodes.R diff --git a/tests/testthat/test-measure_nodes.R b/tests/testthat/test-measure_nodes.R new file mode 100644 index 0000000..b9f572f --- /dev/null +++ b/tests/testthat/test-measure_nodes.R @@ -0,0 +1,12 @@ +node_meas <- funs_objs[grepl("node_by_", names(funs_objs))] +for(fn in names(node_meas)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("vitality|threshold|richness|recovery|posneg|multideg|hub|homophily|hetero|exposure|diversity|distance|authority|adoption|equivalency", fn)) + if(fn == "x"){ + } else { + expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") + } + }) + } +} \ No newline at end of file From 41e88e24f378c50bd9c26c299900c38c552927ae Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 5 Mar 2026 17:32:12 +0100 Subject: [PATCH 41/98] Removing some unnecessary tests --- tests/testthat/test-measure_centrality.R | 36 --------------------- tests/testthat/test-measure_closure.R | 26 --------------- tests/testthat/test-measure_cohesion.R | 8 ----- tests/testthat/test-measure_features.R | 19 ----------- tests/testthat/test-measure_heterogeneity.R | 3 -- tests/testthat/test-measure_hierarchy.R | 4 --- tests/testthat/test-measure_holes.R | 2 -- tests/testthat/test-member_community.R | 1 - 8 files changed, 99 deletions(-) diff --git a/tests/testthat/test-measure_centrality.R b/tests/testthat/test-measure_centrality.R index 30e9183..3be02a4 100644 --- a/tests/testthat/test-measure_centrality.R +++ b/tests/testthat/test-measure_centrality.R @@ -78,16 +78,6 @@ test_that("two mode eigenvector centrality calculated correctly",{ expect_equal(bot3(node_eigenvector(test_igr)), c(0.4764, 0.2907, 0.2907)) }) -test_that("node measure class works", { - expect_s3_class(node_degree(ison_adolescents), "node_measure") - expect_s3_class(node_betweenness(ison_adolescents), "node_measure") - expect_s3_class(node_closeness(ison_adolescents), "node_measure") - expect_s3_class(node_eigenvector(ison_adolescents), "node_measure") - expect_s3_class(node_reach(ison_adolescents), "node_measure") - expect_s3_class(node_randomwalk(ison_adolescents), "node_measure") - expect_s3_class(node_harmonic(ison_adolescents), "node_measure") -}) - test_that("summary node measure works", { expect_equal(names(summary(node_degree(ison_adolescents))), c("Minimum","Maximum","Mean","StdDev","Missing")) @@ -124,42 +114,16 @@ test_that("two mode betweenness centralisation calculated correctly", { }) test_that("net_measure class works", { - expect_s3_class(net_degree(ison_algebra), "network_measure") - expect_s3_class(net_betweenness(ison_southern_women), "network_measure") - expect_s3_class(net_closeness(ison_southern_women), "network_measure") expect_output(print(net_degree(ison_algebra))) }) # ####### Edge centrality -test_that("tie_degree works", { - res <- tie_degree(ison_adolescents) - expect_s3_class(res, "tie_measure") - expect_length(res, manynet::net_ties(ison_adolescents)) - expect_output(print(res), "Betty-Sue") -}) - test_that("tie_betweenness works", { - expect_s3_class(tie_betweenness(ison_adolescents), - "tie_measure") - expect_length(tie_betweenness(ison_adolescents), - manynet::net_ties(ison_adolescents)) expect_equal(unname(tie_betweenness(ison_adolescents)[1:3]), c(7,3,5), tolerance = 0.001) }) test_that("tie_closeness works", { - expect_s3_class(tie_closeness(ison_adolescents), - "tie_measure") - expect_length(tie_closeness(ison_adolescents), - manynet::net_ties(ison_adolescents)) expect_equal(unname(tie_closeness(ison_adolescents)[1:3]), c(0.562,0.692,0.600), tolerance = 0.001) }) - -test_that("tie_eigenvector works", { - expect_s3_class(tie_eigenvector(ison_southern_women), - "tie_measure") - expect_length(tie_eigenvector(ison_southern_women), - manynet::net_ties(ison_southern_women)) -}) - diff --git a/tests/testthat/test-measure_closure.R b/tests/testthat/test-measure_closure.R index f001179..ff838c1 100644 --- a/tests/testthat/test-measure_closure.R +++ b/tests/testthat/test-measure_closure.R @@ -1,5 +1,4 @@ test_that("network density works", { - expect_s3_class(net_density(ison_southern_women), "network_measure") expect_equal(as.numeric(net_density(create_empty(10))), 0) expect_equal(as.numeric(net_density(create_empty(c(10,6)))), 0) expect_equal(as.numeric(net_density(create_filled(10))), 1) @@ -7,33 +6,20 @@ test_that("network density works", { expect_output(print(net_density(create_filled(10)))) }) -test_that("network reciprocity works", { - expect_s3_class(net_reciprocity(ison_networkers), "network_measure") - expect_output(print(net_reciprocity(ison_networkers))) - expect_length(net_reciprocity(ison_networkers), 1) - expect_equal(as.numeric(net_reciprocity(ison_networkers)), - igraph::reciprocity(as_igraph(ison_networkers))) -}) - test_that("one-mode object clustering is reported correctly",{ expect_equal(as.numeric(net_transitivity(ison_algebra)), 0.69787, tolerance = 0.001) - expect_s3_class(net_transitivity(ison_algebra), "network_measure") - expect_output(print(net_transitivity(ison_algebra))) }) test_that("two-mode object clustering is reported correctly",{ expect_equal(as.numeric(net_equivalency(ison_southern_women)), 0.4677, tolerance = 0.001) - expect_s3_class(net_equivalency(ison_southern_women), "network_measure") - expect_output(print(net_equivalency(ison_southern_women))) expect_values(net_equivalency(ison_adolescents), 0.258) }) test_that("node_equivalency works correctly",{ expect_equal(as.numeric(node_equivalency(ison_laterals$ison_mm)), c(0,1,1,0,0.5,0.5), tolerance = 0.001) - expect_s3_class(node_equivalency(ison_southern_women), "node_measure") }) test_that("three-mode clustering calculated correctly",{ @@ -41,16 +27,4 @@ test_that("three-mode clustering calculated correctly",{ mat2 <- manynet::create_ring(c(5,8)) expect_equal(as.numeric(net_congruency(mat1, mat2)), 0.3684, tolerance = 0.001) - expect_s3_class(net_congruency(mat1, mat2), "network_measure") - expect_output(print(net_congruency(mat1, mat2))) -}) - -test_that("node_transitivity is reported correctly",{ - expect_length(node_transitivity(ison_algebra), net_nodes(ison_algebra)) - expect_s3_class(node_transitivity(ison_algebra), "node_measure") -}) - -test_that("node_reciprocity works",{ - expect_length(node_reciprocity(ison_networkers), net_nodes(ison_networkers)) - expect_s3_class(node_reciprocity(ison_networkers), "node_measure") }) diff --git a/tests/testthat/test-measure_cohesion.R b/tests/testthat/test-measure_cohesion.R index ec37d11..fd84d26 100644 --- a/tests/testthat/test-measure_cohesion.R +++ b/tests/testthat/test-measure_cohesion.R @@ -1,40 +1,32 @@ test_that("network components works", { - expect_s3_class(net_components(ison_adolescents), "network_measure") expect_equal(as.numeric(net_components(ison_adolescents)), 1) }) test_that("network cohesion works", { - expect_s3_class(net_cohesion(ison_southern_women), "network_measure") expect_equal(as.numeric(net_cohesion(ison_southern_women)), 2) }) test_that("network adhesion works", { - expect_s3_class(net_adhesion(ison_southern_women), "network_measure") expect_equal(as.numeric(net_adhesion(ison_southern_women)), 2) }) test_that("network diameter works", { - expect_s3_class(net_diameter(ison_southern_women), "network_measure") expect_equal(as.numeric(net_diameter(ison_southern_women)), 4) }) test_that("network length works", { - expect_s3_class(net_length(ison_southern_women), "network_measure") expect_equal(as.numeric(net_length(ison_southern_women)), 2.306, tolerance = 0.001) }) test_that("net_independence works", { - expect_s3_class(net_independence(ison_adolescents), "network_measure") expect_values(net_independence(ison_adolescents), 4) }) test_that("net_strength works", { - expect_s3_class(net_strength(ison_adolescents), "network_measure") expect_values(net_strength(ison_adolescents), 0.5) }) test_that("net_toughness works", { - expect_s3_class(net_toughness(ison_adolescents), "network_measure") expect_values(net_toughness(ison_adolescents), 0.5) }) \ No newline at end of file diff --git a/tests/testthat/test-measure_features.R b/tests/testthat/test-measure_features.R index fd87eee..670265c 100644 --- a/tests/testthat/test-measure_features.R +++ b/tests/testthat/test-measure_features.R @@ -1,11 +1,5 @@ set.seed(123) -test_that("small-world metrics for two mode networks are calculated and displayed correctly", { - expect_s3_class(net_smallworld(ison_southern_women), "network_measure") - expect_equal(as.numeric(net_smallworld(ison_southern_women)), -0.94, tolerance = 0.02) - expect_equal(c(net_smallworld(ison_adolescents, method = "SWI")), -0.25, tolerance = 0.05) -}) - # test_that("net_balance works", { # out <- net_balance(ison_marvel_relationships) # expect_s3_class(out, "network_measure") @@ -17,14 +11,11 @@ test_that("small-world metrics for two mode networks are calculated and displaye test_that("net_modularity works for two mode networks", { out <- net_modularity(ison_southern_women, node_in_partition(ison_southern_women)) - expect_s3_class(out, "network_measure") expect_length(out, 1) }) test_that("net_core works", { out <- net_core(ison_adolescents) - expect_s3_class(out, "network_measure") - expect_length(out, 1) expect_values(out, -0.133) expect_values(net_core(ison_adolescents, method = "ident"), 6.481) expect_values(net_core(ison_adolescents, method = "diff"), 6.094) @@ -32,26 +23,16 @@ test_that("net_core works", { test_that("net_richclub works", { out <- net_richclub(ison_adolescents) - expect_s3_class(out, "network_measure") - expect_length(out, 1) expect_values(out, 0.833) }) -test_that("net_factions works", { - out <- net_factions(ison_adolescents) - expect_s3_class(out, "network_measure") - expect_length(out,1) -}) - test_that("net_scalefree works", { out <- net_scalefree(ison_adolescents) - expect_s3_class(out, "network_measure") expect_values(out,3.689) }) test_that("net_balance works", { out <- net_balance(irps_wwi) - expect_s3_class(out, "network_measure") expect_values(out,1) }) diff --git a/tests/testthat/test-measure_heterogeneity.R b/tests/testthat/test-measure_heterogeneity.R index 8f35da6..3d1705e 100644 --- a/tests/testthat/test-measure_heterogeneity.R +++ b/tests/testthat/test-measure_heterogeneity.R @@ -16,15 +16,12 @@ test_that("heterophily function works", { test_that("assortativity function works", { expect_length(net_assortativity(ison_networkers), 1) - expect_s3_class(net_assortativity(ison_networkers), "network_measure") }) test_that("richness function works", { expect_length(net_richness(ison_networkers), 1) expect_equal(as.numeric(net_richness(ison_networkers)), 3) - expect_s3_class(net_richness(ison_networkers), "network_measure") expect_length(node_richness(ison_networkers, "type"), 32) - expect_s3_class(node_richness(ison_networkers, "type"), "node_measure") }) test_that("net_spatial works", { diff --git a/tests/testthat/test-measure_hierarchy.R b/tests/testthat/test-measure_hierarchy.R index ce085ba..75e3090 100644 --- a/tests/testthat/test-measure_hierarchy.R +++ b/tests/testthat/test-measure_hierarchy.R @@ -3,7 +3,6 @@ test_that("net_connectedness works correctly", { connect_judo <- net_connectedness(ison_judo_moves) # Basic functionality tests - expect_s3_class(connect_judo, "network_measure") # undirected # Return type and range tests expect_true(is.numeric(as.numeric(connect_judo))) @@ -21,7 +20,6 @@ test_that("net_connectedness works correctly", { test_that("net_efficiency works correctly", { # Basic functionality tests effic_judo <- net_efficiency(ison_judo_moves) - expect_s3_class(effic_judo, "network_measure") # Return type tests expect_true(is.numeric(as.numeric(effic_judo))) @@ -31,7 +29,6 @@ test_that("net_efficiency works correctly", { test_that("net_upperbound works correctly", { # Basic functionality tests upper_judo <- net_efficiency(ison_judo_moves) - expect_s3_class(upper_judo, "network_measure") # Return type and range tests expect_true(is.numeric(as.numeric(upper_judo))) @@ -47,7 +44,6 @@ test_that("net_upperbound works correctly", { test_that("net_by_hierarchy works correctly", { result <- net_by_hierarchy(ison_judo_moves) # Basic functionality tests - expect_s3_class(result, "network_motif") # Check that it returns a data frame with correct columns expect_true(is.data.frame(result)) diff --git a/tests/testthat/test-measure_holes.R b/tests/testthat/test-measure_holes.R index 264dbae..d3b3e81 100644 --- a/tests/testthat/test-measure_holes.R +++ b/tests/testthat/test-measure_holes.R @@ -1,6 +1,4 @@ test_that("redundancy is reported correctly", { - expect_s3_class(node_redundancy(ison_brandes), "node_measure") - expect_s3_class(node_redundancy(ison_southern_women), "node_measure") expect_equal(as.numeric(length(node_redundancy(ison_brandes))), as.numeric(net_nodes(ison_brandes))) expect_equal(as.numeric(length(node_redundancy(ison_southern_women))), diff --git a/tests/testthat/test-member_community.R b/tests/testthat/test-member_community.R index c7634e3..e1074d7 100644 --- a/tests/testthat/test-member_community.R +++ b/tests/testthat/test-member_community.R @@ -28,7 +28,6 @@ test_that("node_in_community uses node_in_optimal on small networks", { options(snet_verbosity = "verbose") expect_message(node_in_community(manynet::create_ring(10)), "optimal") expect_message(node_in_community(manynet::create_ring(200)), "xcluding") - expect_message(node_in_community(fict_thrones), "xcluding") options(manynet_verbosity = "quiet") options(snet_verbosity = "quiet") }) \ No newline at end of file From c1a27b28c6af1e077752d2af8f1891b9d4031b8b Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 5 Mar 2026 17:32:25 +0100 Subject: [PATCH 42/98] Fixed website pages --- pkgdown/_pkgdown.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 7cf0103..6834198 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -72,6 +72,8 @@ reference: - measure_closure - measure_features - measure_heterogeneity + - measure_fragmentation + - measure_breadth - subtitle: "Dynamics" contents: - measure_periods From e5e4d29402be82a376991d1deedb09643d58c316 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 5 Mar 2026 17:32:37 +0100 Subject: [PATCH 43/98] Split triangular properties off --- R/mark_ties.R | 2 +- man/mark_triangles.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/mark_ties.R b/R/mark_ties.R index 3f6ca4f..22e3f6a 100644 --- a/R/mark_ties.R +++ b/R/mark_ties.R @@ -101,7 +101,7 @@ tie_is_path <- function(.data, from, to, all_paths = FALSE){ # Triangular properties #### -#' Marking ties based on structural properties +#' Marking ties based on triangular properties #' #' @description #' These functions return logical vectors the length of the ties diff --git a/man/mark_triangles.Rd b/man/mark_triangles.Rd index abe50d1..1039ea2 100644 --- a/man/mark_triangles.Rd +++ b/man/mark_triangles.Rd @@ -9,7 +9,7 @@ \alias{tie_is_simmelian} \alias{tie_is_forbidden} \alias{tie_is_imbalanced} -\title{Marking ties based on structural properties} +\title{Marking ties based on triangular properties} \usage{ tie_is_triangular(.data) From 3b2c0bb5640d989ee6433957380b72da5b9be01c Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 5 Mar 2026 17:32:44 +0100 Subject: [PATCH 44/98] Added favicons --- pkgdown/favicon/apple-touch-icon.png | Bin 0 -> 7055 bytes pkgdown/favicon/favicon-96x96.png | Bin 0 -> 2889 bytes pkgdown/favicon/favicon.ico | Bin 0 -> 15086 bytes pkgdown/favicon/favicon.svg | 1 + pkgdown/favicon/site.webmanifest | 21 +++++++++++++++++++ pkgdown/favicon/web-app-manifest-192x192.png | Bin 0 -> 7497 bytes pkgdown/favicon/web-app-manifest-512x512.png | Bin 0 -> 23364 bytes 7 files changed, 22 insertions(+) create mode 100644 pkgdown/favicon/apple-touch-icon.png create mode 100644 pkgdown/favicon/favicon-96x96.png create mode 100644 pkgdown/favicon/favicon.ico create mode 100644 pkgdown/favicon/favicon.svg create mode 100644 pkgdown/favicon/site.webmanifest create mode 100644 pkgdown/favicon/web-app-manifest-192x192.png create mode 100644 pkgdown/favicon/web-app-manifest-512x512.png diff --git a/pkgdown/favicon/apple-touch-icon.png b/pkgdown/favicon/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3f1f39a6571d79b8995df6f894e41d99a0dc49c3 GIT binary patch literal 7055 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 literal 0 HcmV?d00001 diff --git a/pkgdown/favicon/favicon.ico b/pkgdown/favicon/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..379d961b0a5ad92c27cc2a4087c0fd63e2243582 GIT binary patch literal 15086 zcmdU0Yiv}<6~2ZbDpeJTA5}$*OjWg1YLW7zjf4Q1<_C!W08}X;dDn(IK&YTnRfKHs z8rGo%k|u$)Ng#Odu5D~$V-f;^n0LJfOaO;RdBwcU0>+Mc81t}M+iOqHne~~>y}q-1 zy$>fHX*{np=ljmwy>n;Ij5RGy8?L?hq6X=Bt@4+e_L8P)i%wj);!|8 z_Ty!&udk0i?R?DgeOba5caihwspc_Q{hs3aKgrb{y~O?DlY8*4t^|cKh~icJt;M_I4g+{@9Mct2aJgD(r930v2bsgC>zqN zSFf_BrY0UQUc6{%qr$@{t-=P-8TM>h^5bN1w9=8gdGRQ~=pydT4{s3-%QV{fn>&1Y z`Jk{PUw2%w!cU|Q=8x0FyXM?3GrVOv7z~PfIWH}e`WEXSyxXexkMG(o<4ob-*;@j`yeQ6 z04|-&w>W`5g zd)WT3s#xv$JuLXm6{7-E>HhuuEF6x^3y05s_N^UWM{P74^1PYnkoy-Wx0*CYFJd3W zxfJ}0eYATuD?7A-V;(xQ1Ln$J`2ITCTigD|mEiop;nUpc3Zb>9r-!w*weh(Yg+JJ} zlI&2fr-*+{F{0H*md%?`l6D+gVp^kdL?!`NQ`iQT~-RA1e93zq7!=4ZpW?c<|tXTpW-`Zn<8^ ze^lcSb8A4wf8Dxu1Aaea#*E<@^7wakhm`yuJT|^3*m>5hS$v;!_Uu`LdNx+ z%s&takQh6D`2ah1VK3{2HAFH(EdDV5)rs)OceA%QIb5ymwZVUPy!^YnyBV$CWxZ#=pG0 zT+E+y=gwuzmM!CS<@tyById|I>vp@TI-wM~Mf-li{8^#mzj5P6!H3olW5;bZK=FCZmJ9du6AN(ZcD*nXToT6Uy_?u;@jIxOY)i6a=dU{s!gyE) z@C%o!_@}3*5BN=h5;L_hxN(D1 zGV{mv1==P@^O_?bTaX)!v2yc+K0s>1f#kBB#>rO44vfb%N;E}$)+ zA1_T6J*D2uw@%BvTRa}mFgv_unX0N+h6DeH^gNpGH!AUx)zI11!Q8OdC(OLktY68T z;>oY{FZey_TV3`V_X#G8udL_apSc8YI1BuHJY1vo$L6e?!=w(uK-%Fe~1@aAx*; z*>x@{PUIT#bj`0EbM_f5f;Ls^hg>U8A%Ia!Hf0%F|27Bm~Yp#QJ4la4ZZ2C z5bhU2+J|I3nij$|UDJY?j_OZG{{N<6F1G{B=XQ~uWH$@;j{}VhjT4RykAdNll*X0D zxzB_3cu#9Q#Ay&yaZfAHpY=z42k?#!Knz)c_1+8g?uELai@9$o2mo#fv76xxTMpu$ zgwNTK3LdcksYKjuflUGy@1fxR2l)=oJKLA@@yPdO|0NkbJf%6mL)?Pm1p=2cc#j14 zS5|jmun)!+dd`pHK2LZnt|Aueg^AEPF^i^f|uC6X|wu?CN;>!S!JKA{B?Vm4K$IRQo$GZi17A;3S zYfnb7gHO+l{6$q&RR(B$-l&J-VD9nQ#UC+Z1U@GbWdejNr*b>xH*NZMzM?w2_%b%; zlbdGVlY=Rd?yDJ62&hnDiUyog@qz-Hse6tJkoJESUn zum}BDydCEp^ry>_I9aH*RfXSa)L)=GCIFT)ky* ziw3%Xk9RNQ3E#Bc;r4663we*Y$`n z{KfvNVAbC&E905lQqf \ No newline at end of file diff --git a/pkgdown/favicon/site.webmanifest b/pkgdown/favicon/site.webmanifest new file mode 100644 index 0000000..4ebda26 --- /dev/null +++ b/pkgdown/favicon/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/pkgdown/favicon/web-app-manifest-192x192.png b/pkgdown/favicon/web-app-manifest-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..d23f1c7cc7dfc3381b63edd27bb21fa56b0064d9 GIT binary patch literal 7497 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 literal 0 HcmV?d00001 From cb4c784e9370cd6305e66f990a6625b319fc0815 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 5 Mar 2026 19:58:33 +0100 Subject: [PATCH 45/98] Fixed node_is_isolate() and node_is_pendant() to work with signed networks --- R/mark_nodes.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/mark_nodes.R b/R/mark_nodes.R index 98f4d72..d4c020c 100644 --- a/R/mark_nodes.R +++ b/R/mark_nodes.R @@ -29,7 +29,7 @@ NULL #' @export node_is_isolate <- function(.data){ .data <- manynet::expect_nodes(.data) - mat <- manynet::as_matrix(.data) + mat <- abs(manynet::as_matrix(.data)) if(manynet::is_twomode(.data)){ out <- c(rowSums(mat)==0, colSums(mat)==0) } else { @@ -43,7 +43,7 @@ node_is_isolate <- function(.data){ #' @export node_is_pendant <- function(.data){ .data <- manynet::expect_nodes(.data) - mat <- manynet::as_matrix(.data) + mat <- abs(manynet::as_matrix(.data)) if(manynet::is_twomode(.data)){ out <- c(rowSums(mat)==1, colSums(mat)==1) } else { From ae6d0722e8a02ee2b8e1549d025d145d7c76d56e Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 5 Mar 2026 19:59:16 +0100 Subject: [PATCH 46/98] Moved degree-related marks to new section --- R/mark_nodes.R | 65 +++++++++++++++++++++++++++++++++--------- man/mark_core.Rd | 17 ----------- man/mark_degree.Rd | 61 +++++++++++++++++++++++++++++++++++++++ man/mark_diff.Rd | 9 ++++++ man/mark_nodes.Rd | 12 ++------ man/mark_select.Rd | 1 + man/mark_tie_select.Rd | 1 + man/mark_ties.Rd | 1 + man/mark_triangles.Rd | 1 + 9 files changed, 128 insertions(+), 40 deletions(-) create mode 100644 man/mark_degree.Rd diff --git a/R/mark_nodes.R b/R/mark_nodes.R index d4c020c..6153a55 100644 --- a/R/mark_nodes.R +++ b/R/mark_nodes.R @@ -1,6 +1,6 @@ -# Structural properties #### +# Degree properties #### -#' Marking nodes based on structural properties +#' Marking nodes based on degree properties #' #' @description #' These functions return logical vectors the length of the @@ -8,22 +8,19 @@ #' #' - `node_is_isolate()` marks nodes that are isolates, #' with neither incoming nor outgoing ties. -#' - `node_is_independent()` marks nodes that are members of the largest independent set, -#' aka largest internally stable set. -#' - `node_is_cutpoint()` marks nodes that cut or act as articulation points in a network, -#' increasing the number of connected components when removed. -#' - `node_is_core()` marks nodes that are members of the network's core. -#' - `node_is_fold()` marks nodes that are in a structural fold between two or more -#' triangles that are only connected by that node. -#' - `node_is_mentor()` marks a proportion of high indegree nodes as 'mentors' (see details). +#' - `node_is_pendant()` marks nodes that are pendants, +#' with exactly one incoming or outgoing tie. +#' - `node_is_universal()` identifies whether nodes are adjacent to all other +#' nodes in the network. #' @param .data A network object of class `mnet`, `igraph`, `tbl_graph`, `network`, or similar. #' For more information on the standard coercion possible, #' see [manynet::as_tidygraph()]. #' @family marks -#' @name mark_nodes +#' @family degree +#' @name mark_degree NULL -#' @rdname mark_nodes +#' @rdname mark_degree #' @examples #' node_is_isolate(ison_brandes) #' @export @@ -39,7 +36,7 @@ node_is_isolate <- function(.data){ make_node_mark(out, .data) } -#' @rdname mark_nodes +#' @rdname mark_degree #' @export node_is_pendant <- function(.data){ .data <- manynet::expect_nodes(.data) @@ -52,6 +49,47 @@ node_is_pendant <- function(.data){ make_node_mark(out, .data) } +#' @rdname mark_degree +#' @section Universal/dominating node: +#' A universal node is adjacent to all other nodes in the network. +#' It is also sometimes called the dominating vertex because it represents +#' a one-element dominating set. +#' A network with a universal node is called a cone, and its universal node +#' is called the apex of the cone. +#' A classic example of a cone is a star graph, +#' but friendship, wheel, and threshold graphs are also cones. +#' @examples +#' node_is_universal(create_star(11)) +#' @export +node_is_universal <- function(.data){ + .data <- manynet::expect_nodes(.data) + net <- manynet::to_undirected(manynet::to_unweighted(.data)) + make_node_mark(node_by_deg(net)==(manynet::net_nodes(net)-1), .data) +} + +# Structural properties #### + +#' Marking nodes based on structural properties +#' +#' @description +#' These functions return logical vectors the length of the +#' nodes in a network identifying which hold certain properties or positions in the network. +#' +#' - `node_is_independent()` marks nodes that are members of the largest independent set, +#' aka largest internally stable set. +#' - `node_is_cutpoint()` marks nodes that cut or act as articulation points in a network, +#' increasing the number of connected components when removed. +#' - `node_is_fold()` marks nodes that are in a structural fold between two or more +#' triangles that are only connected by that node. +#' - `node_is_mentor()` marks a proportion of high indegree nodes as 'mentors' (see details). +#' - `node_is_neighbor()` marks nodes that are neighbours of a given node. +#' @param .data A network object of class `mnet`, `igraph`, `tbl_graph`, `network`, or similar. +#' For more information on the standard coercion possible, +#' see [manynet::as_tidygraph()]. +#' @family marks +#' @name mark_nodes +NULL + #' @rdname mark_nodes #' @importFrom igraph largest_ivs #' @references @@ -195,6 +233,7 @@ node_is_neighbor <- function(.data, node){ #' - `node_is_recovered()` marks nodes that are recovered at a particular time point. #' @inheritParams mark_nodes #' @family marks +#' @family diffusion #' @name mark_diff NULL diff --git a/man/mark_core.Rd b/man/mark_core.Rd index 3ef024c..857f2fd 100644 --- a/man/mark_core.Rd +++ b/man/mark_core.Rd @@ -2,15 +2,12 @@ % Please edit documentation in R/member_core.R \name{mark_core} \alias{mark_core} -\alias{node_is_universal} \alias{node_is_core} \alias{node_by_kcoreness} \alias{node_by_coreness} \alias{node_in_core} \title{Core-periphery clustering algorithms} \usage{ -node_is_universal(.data) - node_is_core(.data, method = c("degree", "eigenvector")) node_by_kcoreness(.data) @@ -41,8 +38,6 @@ or "kmeans" (k-means clustering). Default is "bins".} \description{ These functions identify nodes belonging to (some level of) the core of a network: \itemize{ -\item \code{node_is_universal()} identifies whether nodes are adjacent to all other -nodes in the network. \item \code{node_is_core()} identifies whether nodes belong to the core of the network, as opposed to the periphery. \item \code{node_in_core()} categorizes nodes into two or more core/periphery @@ -52,17 +47,6 @@ resembles a typical core node. \item \code{node_kcoreness()} assigns nodes to their level of k-coreness. } } -\section{Universal/dominating node}{ - -A universal node is adjacent to all other nodes in the network. -It is also sometimes called the dominating vertex because it represents -a one-element dominating set. -A network with a universal node is called a cone, and its universal node -is called the apex of the cone. -A classic example of a cone is a star graph, -but friendship, wheel, and threshold graphs are also cones. -} - \section{Core-periphery}{ This function is used to identify which nodes should belong to the core, @@ -97,7 +81,6 @@ quantile-based bins, or k-means clustering. } \examples{ -node_is_universal(create_star(11)) node_is_core(ison_adolescents) #ison_adolescents \%>\% # mutate(corep = node_is_core()) \%>\% diff --git a/man/mark_degree.Rd b/man/mark_degree.Rd new file mode 100644 index 0000000..d19c5a3 --- /dev/null +++ b/man/mark_degree.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mark_nodes.R +\name{mark_degree} +\alias{mark_degree} +\alias{node_is_isolate} +\alias{node_is_pendant} +\alias{node_is_universal} +\title{Marking nodes based on degree properties} +\usage{ +node_is_isolate(.data) + +node_is_pendant(.data) + +node_is_universal(.data) +} +\arguments{ +\item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. +For more information on the standard coercion possible, +see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} +} +\description{ +These functions return logical vectors the length of the +nodes in a network identifying which hold certain properties or positions in the network. +\itemize{ +\item \code{node_is_isolate()} marks nodes that are isolates, +with neither incoming nor outgoing ties. +\item \code{node_is_pendant()} marks nodes that are pendants, +with exactly one incoming or outgoing tie. +\item \code{node_is_universal()} identifies whether nodes are adjacent to all other +nodes in the network. +} +} +\section{Universal/dominating node}{ + +A universal node is adjacent to all other nodes in the network. +It is also sometimes called the dominating vertex because it represents +a one-element dominating set. +A network with a universal node is called a cone, and its universal node +is called the apex of the cone. +A classic example of a cone is a star graph, +but friendship, wheel, and threshold graphs are also cones. +} + +\examples{ +node_is_isolate(ison_brandes) +node_is_universal(create_star(11)) +} +\seealso{ +Other marks: +\code{\link{mark_diff}}, +\code{\link{mark_nodes}}, +\code{\link{mark_select}}, +\code{\link{mark_tie_select}}, +\code{\link{mark_ties}}, +\code{\link{mark_triangles}} + +Other degree: +\code{\link{measure_central_degree}} +} +\concept{degree} +\concept{marks} diff --git a/man/mark_diff.Rd b/man/mark_diff.Rd index 140d404..2f18f47 100644 --- a/man/mark_diff.Rd +++ b/man/mark_diff.Rd @@ -60,10 +60,19 @@ in that diffusion. } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_nodes}}, \code{\link{mark_select}}, \code{\link{mark_tie_select}}, \code{\link{mark_ties}}, \code{\link{mark_triangles}} + +Other diffusion: +\code{\link{measure_diffusion_infection}}, +\code{\link{measure_diffusion_net}}, +\code{\link{measure_diffusion_node}}, +\code{\link{member_diffusion}}, +\code{\link{motif_diffusion}} } +\concept{diffusion} \concept{marks} diff --git a/man/mark_nodes.Rd b/man/mark_nodes.Rd index b4a09e9..e878af7 100644 --- a/man/mark_nodes.Rd +++ b/man/mark_nodes.Rd @@ -2,8 +2,6 @@ % Please edit documentation in R/mark_nodes.R \name{mark_nodes} \alias{mark_nodes} -\alias{node_is_isolate} -\alias{node_is_pendant} \alias{node_is_independent} \alias{node_is_cutpoint} \alias{node_is_fold} @@ -11,10 +9,6 @@ \alias{node_is_neighbor} \title{Marking nodes based on structural properties} \usage{ -node_is_isolate(.data) - -node_is_pendant(.data) - node_is_independent(.data) node_is_cutpoint(.data) @@ -50,20 +44,17 @@ described in Valente and Davis (1999).} These functions return logical vectors the length of the nodes in a network identifying which hold certain properties or positions in the network. \itemize{ -\item \code{node_is_isolate()} marks nodes that are isolates, -with neither incoming nor outgoing ties. \item \code{node_is_independent()} marks nodes that are members of the largest independent set, aka largest internally stable set. \item \code{node_is_cutpoint()} marks nodes that cut or act as articulation points in a network, increasing the number of connected components when removed. -\item \code{node_is_core()} marks nodes that are members of the network's core. \item \code{node_is_fold()} marks nodes that are in a structural fold between two or more triangles that are only connected by that node. \item \code{node_is_mentor()} marks a proportion of high indegree nodes as 'mentors' (see details). +\item \code{node_is_neighbor()} marks nodes that are neighbours of a given node. } } \examples{ -node_is_isolate(ison_brandes) node_is_independent(ison_adolescents) node_is_cutpoint(ison_brandes) node_is_fold(create_explicit(A-B, B-C, A-C, C-D, C-E, D-E)) @@ -102,6 +93,7 @@ Valente, Thomas, and Rebecca Davis. 1999. } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_select}}, \code{\link{mark_tie_select}}, diff --git a/man/mark_select.Rd b/man/mark_select.Rd index 52689bf..93138aa 100644 --- a/man/mark_select.Rd +++ b/man/mark_select.Rd @@ -50,6 +50,7 @@ node_is_random(ison_brandes, 2) } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_nodes}}, \code{\link{mark_tie_select}}, diff --git a/man/mark_tie_select.Rd b/man/mark_tie_select.Rd index 8329e82..c1a7fc9 100644 --- a/man/mark_tie_select.Rd +++ b/man/mark_tie_select.Rd @@ -38,6 +38,7 @@ are key because they minimise or, more often, maximise some measure. } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_nodes}}, \code{\link{mark_select}}, diff --git a/man/mark_ties.Rd b/man/mark_ties.Rd index 20f5853..c064e58 100644 --- a/man/mark_ties.Rd +++ b/man/mark_ties.Rd @@ -61,6 +61,7 @@ ison_adolescents \%>\% } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_nodes}}, \code{\link{mark_select}}, diff --git a/man/mark_triangles.Rd b/man/mark_triangles.Rd index 1039ea2..999eef5 100644 --- a/man/mark_triangles.Rd +++ b/man/mark_triangles.Rd @@ -70,6 +70,7 @@ fict_marvel \%>\% to_uniplex("relationship") \%>\% tie_is_imbalanced() } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_nodes}}, \code{\link{mark_select}}, From 8aa9a1aa5298847b61893be580688082206c1ac0 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 5 Mar 2026 20:00:06 +0100 Subject: [PATCH 47/98] Separated out brokerage functions by measure, motif, member --- R/motif_brokerage.R | 27 ++++++++++++++++++++++++--- R/motif_census.R | 1 + man/motif_brokerage.Rd | 21 +++++---------------- man/motif_diffusion.Rd | 8 ++++++++ pkgdown/_pkgdown.yml | 1 + 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/R/motif_brokerage.R b/R/motif_brokerage.R index 4fa6980..435a1b5 100644 --- a/R/motif_brokerage.R +++ b/R/motif_brokerage.R @@ -1,4 +1,4 @@ -# Brokerage #### +# Motifs #### #' Motifs of brokerage #' @@ -15,6 +15,7 @@ #' #' @name motif_brokerage #' @family motifs +#' @family brokerage #' @inheritParams motif_node #' @param membership A vector of partition membership as integers. #' @param standardized Whether the score should be standardized @@ -80,7 +81,24 @@ net_x_brokerage <- function(.data, membership, standardized = FALSE){ make_network_motif(out, .data) } -#' @rdname motif_brokerage +# Measures #### + +#' Measures of brokerage +#' +#' @description +#' These functions include ways to measure nodes' brokerage activity and +#' exclusivity in a network: +#' +#' - `node_brokering_activity()` measures nodes' brokerage activity. +#' - `node_brokering_exclusivity()` measures nodes' brokerage exclusivity. +#' +#' @name measure_brokerage +#' @family measures +#' @family brokerage +#' @inheritParams motif_brokerage +NULL + +#' @rdname measure_brokerage #' @references #' ## On brokerage activity and exclusivity #' Hamilton, Matthew, Jacob Hileman, and Orjan Bodin. 2020. @@ -111,7 +129,7 @@ node_by_brokering_activity <- function(.data, membership){ make_node_measure(out, .data) } -#' @rdname motif_brokerage +#' @rdname measure_brokerage #' @examples #' node_by_brokering_exclusivity(ison_networkers, "Discipline") #' @export @@ -139,6 +157,8 @@ node_by_brokering_exclusivity <- function(.data, membership){ make_node_measure(out, .data) } +# Memberships #### + #' Memberships of brokerage #' #' @description @@ -150,6 +170,7 @@ node_by_brokering_exclusivity <- function(.data, membership){ #' #' @name member_brokerage #' @family memberships +#' @family brokerage #' @inheritParams motif_brokerage NULL diff --git a/R/motif_census.R b/R/motif_census.R index a730cec..12f017f 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -498,6 +498,7 @@ net_x_mixed <- function (.data, object2) { #' infection/adoption by time step. #' #' @family motifs +#' @family diffusion #' @inheritParams motif_node #' @inheritParams measure_diffusion_net #' @name motif_diffusion diff --git a/man/motif_brokerage.Rd b/man/motif_brokerage.Rd index 93693e9..55bca31 100644 --- a/man/motif_brokerage.Rd +++ b/man/motif_brokerage.Rd @@ -4,17 +4,11 @@ \alias{motif_brokerage} \alias{node_x_brokerage} \alias{net_x_brokerage} -\alias{node_by_brokering_activity} -\alias{node_by_brokering_exclusivity} \title{Motifs of brokerage} \usage{ node_x_brokerage(.data, membership, standardized = FALSE) net_x_brokerage(.data, membership, standardized = FALSE) - -node_by_brokering_activity(.data, membership) - -node_by_brokering_exclusivity(.data, membership) } \arguments{ \item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. @@ -42,7 +36,6 @@ roles in a network. \examples{ # node_x_brokerage(ison_networkers, "Discipline") # net_x_brokerage(ison_networkers, "Discipline") -node_by_brokering_exclusivity(ison_networkers, "Discipline") } \references{ \subsection{On brokerage motifs}{ @@ -57,20 +50,16 @@ Jasny, Lorien, and Mark Lubell. 2015. \emph{Social Networks} 41:36–47. \doi{10.1016/j.socnet.2014.11.005} } - -\subsection{On brokerage activity and exclusivity}{ - -Hamilton, Matthew, Jacob Hileman, and Orjan Bodin. 2020. -"Evaluating heterogeneous brokerage: New conceptual and methodological approaches -and their application to multi-level environmental governance networks" -\emph{Social Networks} 61: 1-10. -\doi{10.1016/j.socnet.2019.08.002} -} } \seealso{ Other motifs: \code{\link{motif_diffusion}}, \code{\link{motif_net}}, \code{\link{motif_node}} + +Other brokerage: +\code{\link{measure_brokerage}}, +\code{\link{member_brokerage}} } +\concept{brokerage} \concept{motifs} diff --git a/man/motif_diffusion.Rd b/man/motif_diffusion.Rd index d99ce11..254fd6a 100644 --- a/man/motif_diffusion.Rd +++ b/man/motif_diffusion.Rd @@ -92,5 +92,13 @@ Other motifs: \code{\link{motif_brokerage}}, \code{\link{motif_net}}, \code{\link{motif_node}} + +Other diffusion: +\code{\link{mark_diff}}, +\code{\link{measure_diffusion_infection}}, +\code{\link{measure_diffusion_net}}, +\code{\link{measure_diffusion_node}}, +\code{\link{member_diffusion}} } +\concept{diffusion} \concept{motifs} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 6834198..0dd4901 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -65,6 +65,7 @@ reference: contents: - starts_with("measure_central") - measure_holes + - measure_brokerage - measure_hierarchy - subtitle: "Cohesion" contents: From c6a92bc009eb13cb7a983d85549e301e3febb1a0 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 5 Mar 2026 21:07:42 +0100 Subject: [PATCH 48/98] Further documentation links --- R/measure_centrality.R | 2 + R/member_community.R | 10 +++-- R/member_components.R | 2 +- R/member_core.R | 20 --------- man/measure_breadth.Rd | 1 + man/measure_brokerage.Rd | 66 ++++++++++++++++++++++++++++++ man/measure_central_between.Rd | 2 + man/measure_central_close.Rd | 1 + man/measure_central_degree.Rd | 5 +++ man/measure_central_eigen.Rd | 1 + man/measure_closure.Rd | 1 + man/measure_cohesion.Rd | 1 + man/measure_diffusion_infection.Rd | 5 ++- man/measure_diffusion_net.Rd | 5 ++- man/measure_diffusion_node.Rd | 5 ++- man/measure_features.Rd | 1 + man/measure_fragmentation.Rd | 1 + man/measure_heterogeneity.Rd | 1 + man/measure_hierarchy.Rd | 1 + man/measure_holes.Rd | 1 + man/measure_periods.Rd | 1 + man/member_brokerage.Rd | 5 +++ man/member_community_hier.Rd | 6 ++- man/member_community_non.Rd | 6 ++- man/member_components.Rd | 2 +- man/member_diffusion.Rd | 5 ++- 26 files changed, 125 insertions(+), 32 deletions(-) create mode 100644 man/measure_brokerage.Rd diff --git a/R/measure_centrality.R b/R/measure_centrality.R index de8d53e..d805d3f 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -29,6 +29,7 @@ #' @name measure_central_degree #' @family centrality #' @family measures +#' @family degree #' @inheritParams mark_nodes #' @param normalized Logical scalar, whether the centrality scores are normalized. #' Different denominators are used depending on whether the object is one-mode or two-mode, @@ -303,6 +304,7 @@ net_by_indegree <- function(.data, normalized = TRUE){ #' @name measure_central_between #' @family centrality #' @family measures +#' @family betweenness #' @inheritParams measure_central_degree #' @param cutoff The maximum path length to consider when calculating betweenness. #' If negative or NULL (the default), there's no limit to the path lengths considered. diff --git a/R/member_community.R b/R/member_community.R index 065f465..7edc9b6 100644 --- a/R/member_community.R +++ b/R/member_community.R @@ -1,6 +1,6 @@ -# Non-hierarchical community partitioning #### +# Non-hierarchical community clustering #### -#' Non-hierarchical community partitioning algorithms +#' Non-hierarchical community clustering algorithms #' #' @description #' These functions offer algorithms for partitioning @@ -30,6 +30,7 @@ #' @inheritParams mark_nodes #' @name member_community_non #' @family memberships +#' @family community NULL #' @rdname member_community_non @@ -355,9 +356,9 @@ node_in_leiden <- function(.data, resolution = 1){ make_node_member(out, .data) } -# Hierarchical community partitioning #### +# Hierarchical community clustering #### -#' Hierarchical community partitioning algorithms +#' Hierarchical community clustering algorithms #' #' @description #' These functions offer algorithms for hierarchically clustering @@ -379,6 +380,7 @@ node_in_leiden <- function(.data, resolution = 1){ #' @inheritParams member_community_non #' @name member_community_hier #' @family memberships +#' @family community NULL #' @rdname member_community_hier diff --git a/R/member_components.R b/R/member_components.R index 556bbe2..dfad1a8 100644 --- a/R/member_components.R +++ b/R/member_components.R @@ -1,4 +1,4 @@ -#' Component partitioning algorithms +#' Component clustering algorithms #' #' @description #' These functions create a vector of nodes' memberships in components: diff --git a/R/member_core.R b/R/member_core.R index 66c7356..f3dfae8 100644 --- a/R/member_core.R +++ b/R/member_core.R @@ -2,8 +2,6 @@ #' @description #' These functions identify nodes belonging to (some level of) the core of a network: #' -#' - `node_is_universal()` identifies whether nodes are adjacent to all other -#' nodes in the network. #' - `node_is_core()` identifies whether nodes belong to the core of the #' network, as opposed to the periphery. #' - `node_in_core()` categorizes nodes into two or more core/periphery @@ -23,24 +21,6 @@ #' @family memberships NULL -#' @rdname mark_core -#' @section Universal/dominating node: -#' A universal node is adjacent to all other nodes in the network. -#' It is also sometimes called the dominating vertex because it represents -#' a one-element dominating set. -#' A network with a universal node is called a cone, and its universal node -#' is called the apex of the cone. -#' A classic example of a cone is a star graph, -#' but friendship, wheel, and threshold graphs are also cones. -#' @examples -#' node_is_universal(create_star(11)) -#' @export -node_is_universal <- function(.data){ - .data <- manynet::expect_nodes(.data) - net <- manynet::to_undirected(manynet::to_unweighted(.data)) - make_node_mark(node_by_deg(net)==(manynet::net_nodes(net)-1), .data) -} - #' @rdname mark_core #' @section Core-periphery: #' This function is used to identify which nodes should belong to the core, diff --git a/man/measure_breadth.Rd b/man/measure_breadth.Rd index c86798d..4304bd6 100644 --- a/man/measure_breadth.Rd +++ b/man/measure_breadth.Rd @@ -30,6 +30,7 @@ net_by_length(to_giant(fict_marvel)) } \seealso{ Other measures: +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_brokerage.Rd b/man/measure_brokerage.Rd new file mode 100644 index 0000000..e44390c --- /dev/null +++ b/man/measure_brokerage.Rd @@ -0,0 +1,66 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/motif_brokerage.R +\name{measure_brokerage} +\alias{measure_brokerage} +\alias{node_by_brokering_activity} +\alias{node_by_brokering_exclusivity} +\title{Measures of brokerage} +\usage{ +node_by_brokering_activity(.data, membership) + +node_by_brokering_exclusivity(.data, membership) +} +\arguments{ +\item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. +For more information on the standard coercion possible, +see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} + +\item{membership}{A vector of partition membership as integers.} +} +\description{ +These functions include ways to measure nodes' brokerage activity and +exclusivity in a network: +\itemize{ +\item \code{node_brokering_activity()} measures nodes' brokerage activity. +\item \code{node_brokering_exclusivity()} measures nodes' brokerage exclusivity. +} +} +\examples{ +node_by_brokering_exclusivity(ison_networkers, "Discipline") +} +\references{ +\subsection{On brokerage activity and exclusivity}{ + +Hamilton, Matthew, Jacob Hileman, and Orjan Bodin. 2020. +"Evaluating heterogeneous brokerage: New conceptual and methodological approaches +and their application to multi-level environmental governance networks" +\emph{Social Networks} 61: 1-10. +\doi{10.1016/j.socnet.2019.08.002} +} +} +\seealso{ +Other measures: +\code{\link{measure_breadth}}, +\code{\link{measure_central_between}}, +\code{\link{measure_central_close}}, +\code{\link{measure_central_degree}}, +\code{\link{measure_central_eigen}}, +\code{\link{measure_closure}}, +\code{\link{measure_cohesion}}, +\code{\link{measure_diffusion_infection}}, +\code{\link{measure_diffusion_net}}, +\code{\link{measure_diffusion_node}}, +\code{\link{measure_features}}, +\code{\link{measure_fragmentation}}, +\code{\link{measure_heterogeneity}}, +\code{\link{measure_hierarchy}}, +\code{\link{measure_holes}}, +\code{\link{measure_periods}}, +\code{\link{member_diffusion}} + +Other brokerage: +\code{\link{member_brokerage}}, +\code{\link{motif_brokerage}} +} +\concept{brokerage} +\concept{measures} diff --git a/man/measure_central_between.Rd b/man/measure_central_between.Rd index dc1c8de..ea22911 100644 --- a/man/measure_central_between.Rd +++ b/man/measure_central_between.Rd @@ -150,6 +150,7 @@ Other centrality: Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, \code{\link{measure_central_eigen}}, @@ -166,5 +167,6 @@ Other measures: \code{\link{measure_periods}}, \code{\link{member_diffusion}} } +\concept{betweenness} \concept{centrality} \concept{measures} diff --git a/man/measure_central_close.Rd b/man/measure_central_close.Rd index 2d33137..b2c069f 100644 --- a/man/measure_central_close.Rd +++ b/man/measure_central_close.Rd @@ -273,6 +273,7 @@ Other centrality: Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_degree}}, \code{\link{measure_central_eigen}}, diff --git a/man/measure_central_degree.Rd b/man/measure_central_degree.Rd index e94065f..919ad7c 100644 --- a/man/measure_central_degree.Rd +++ b/man/measure_central_degree.Rd @@ -193,6 +193,7 @@ Other centrality: Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_eigen}}, @@ -208,6 +209,10 @@ Other measures: \code{\link{measure_holes}}, \code{\link{measure_periods}}, \code{\link{member_diffusion}} + +Other degree: +\code{\link{mark_degree}} } \concept{centrality} +\concept{degree} \concept{measures} diff --git a/man/measure_central_eigen.Rd b/man/measure_central_eigen.Rd index 5219735..29a3723 100644 --- a/man/measure_central_eigen.Rd +++ b/man/measure_central_eigen.Rd @@ -224,6 +224,7 @@ Other centrality: Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_closure.Rd b/man/measure_closure.Rd index 526ab26..f32652d 100644 --- a/man/measure_closure.Rd +++ b/man/measure_closure.Rd @@ -94,6 +94,7 @@ Cambridge University Press. Cambridge University Press. \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_cohesion.Rd b/man/measure_cohesion.Rd index fa56dae..ef93932 100644 --- a/man/measure_cohesion.Rd +++ b/man/measure_cohesion.Rd @@ -55,6 +55,7 @@ net_by_independence(ison_adolescents) \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_diffusion_infection.Rd b/man/measure_diffusion_infection.Rd index 048f7d3..3561c06 100644 --- a/man/measure_diffusion_infection.Rd +++ b/man/measure_diffusion_infection.Rd @@ -50,6 +50,7 @@ highest infection rate is observed. \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, @@ -67,9 +68,11 @@ Other measures: \code{\link{member_diffusion}} Other diffusion: +\code{\link{mark_diff}}, \code{\link{measure_diffusion_net}}, \code{\link{measure_diffusion_node}}, -\code{\link{member_diffusion}} +\code{\link{member_diffusion}}, +\code{\link{motif_diffusion}} } \concept{diffusion} \concept{measures} diff --git a/man/measure_diffusion_net.Rd b/man/measure_diffusion_net.Rd index 14e1977..03861ef 100644 --- a/man/measure_diffusion_net.Rd +++ b/man/measure_diffusion_net.Rd @@ -166,6 +166,7 @@ Garnett, G.P. 2005. \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, @@ -183,9 +184,11 @@ Other measures: \code{\link{member_diffusion}} Other diffusion: +\code{\link{mark_diff}}, \code{\link{measure_diffusion_infection}}, \code{\link{measure_diffusion_node}}, -\code{\link{member_diffusion}} +\code{\link{member_diffusion}}, +\code{\link{motif_diffusion}} } \concept{diffusion} \concept{measures} diff --git a/man/measure_diffusion_node.Rd b/man/measure_diffusion_node.Rd index eb2469c..5205712 100644 --- a/man/measure_diffusion_node.Rd +++ b/man/measure_diffusion_node.Rd @@ -116,6 +116,7 @@ Valente, Tom W. 1995. \emph{Network models of the diffusion of innovations} \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, @@ -133,9 +134,11 @@ Other measures: \code{\link{member_diffusion}} Other diffusion: +\code{\link{mark_diff}}, \code{\link{measure_diffusion_infection}}, \code{\link{measure_diffusion_net}}, -\code{\link{member_diffusion}} +\code{\link{member_diffusion}}, +\code{\link{motif_diffusion}} } \concept{diffusion} \concept{measures} diff --git a/man/measure_features.Rd b/man/measure_features.Rd index acb9f90..6046686 100644 --- a/man/measure_features.Rd +++ b/man/measure_features.Rd @@ -235,6 +235,7 @@ for how clustering is calculated Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_fragmentation.Rd b/man/measure_fragmentation.Rd index 0774062..7ccb2f5 100644 --- a/man/measure_fragmentation.Rd +++ b/man/measure_fragmentation.Rd @@ -55,6 +55,7 @@ White, Douglas R and Frank Harary. 2001. \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_heterogeneity.Rd b/man/measure_heterogeneity.Rd index e6fec3a..30b4d26 100644 --- a/man/measure_heterogeneity.Rd +++ b/man/measure_heterogeneity.Rd @@ -227,6 +227,7 @@ Moran, Patrick Alfred Pierce. 1950. \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_hierarchy.Rd b/man/measure_hierarchy.Rd index 02c3ee5..5fd1c55 100644 --- a/man/measure_hierarchy.Rd +++ b/man/measure_hierarchy.Rd @@ -55,6 +55,7 @@ Everett, Martin, and David Krackhardt. 2012. \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_holes.Rd b/man/measure_holes.Rd index f8580d3..ff37776 100644 --- a/man/measure_holes.Rd +++ b/man/measure_holes.Rd @@ -112,6 +112,7 @@ Barrat, Alain, Marc Barthelemy, Romualdo Pastor-Satorras, and Alessandro Vespign \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_periods.Rd b/man/measure_periods.Rd index b687d2e..d453680 100644 --- a/man/measure_periods.Rd +++ b/man/measure_periods.Rd @@ -38,6 +38,7 @@ of networks minus one. E.g., the periods between waves. \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/member_brokerage.Rd b/man/member_brokerage.Rd index 2aa0a39..5dceab8 100644 --- a/man/member_brokerage.Rd +++ b/man/member_brokerage.Rd @@ -30,5 +30,10 @@ Other memberships: \code{\link{member_community_non}}, \code{\link{member_components}}, \code{\link{member_equivalence}} + +Other brokerage: +\code{\link{measure_brokerage}}, +\code{\link{motif_brokerage}} } +\concept{brokerage} \concept{memberships} diff --git a/man/member_community_hier.Rd b/man/member_community_hier.Rd index 546516d..f2232b7 100644 --- a/man/member_community_hier.Rd +++ b/man/member_community_hier.Rd @@ -6,7 +6,7 @@ \alias{node_in_greedy} \alias{node_in_eigen} \alias{node_in_walktrap} -\title{Hierarchical community partitioning algorithms} +\title{Hierarchical community clustering algorithms} \usage{ node_in_betweenness(.data) @@ -130,5 +130,9 @@ Other memberships: \code{\link{member_community_non}}, \code{\link{member_components}}, \code{\link{member_equivalence}} + +Other community: +\code{\link{member_community_non}} } +\concept{community} \concept{memberships} diff --git a/man/member_community_non.Rd b/man/member_community_non.Rd index 2265ad1..e1ad097 100644 --- a/man/member_community_non.Rd +++ b/man/member_community_non.Rd @@ -10,7 +10,7 @@ \alias{node_in_fluid} \alias{node_in_louvain} \alias{node_in_leiden} -\title{Non-hierarchical community partitioning algorithms} +\title{Non-hierarchical community clustering algorithms} \usage{ node_in_community(.data) @@ -229,5 +229,9 @@ Other memberships: \code{\link{member_community_hier}}, \code{\link{member_components}}, \code{\link{member_equivalence}} + +Other community: +\code{\link{member_community_hier}} } +\concept{community} \concept{memberships} diff --git a/man/member_components.Rd b/man/member_components.Rd index 19890af..1f0572a 100644 --- a/man/member_components.Rd +++ b/man/member_components.Rd @@ -5,7 +5,7 @@ \alias{node_in_component} \alias{node_in_weak} \alias{node_in_strong} -\title{Component partitioning algorithms} +\title{Component clustering algorithms} \usage{ node_in_component(.data) diff --git a/man/member_diffusion.Rd b/man/member_diffusion.Rd index e22b14c..595f3ee 100644 --- a/man/member_diffusion.Rd +++ b/man/member_diffusion.Rd @@ -49,6 +49,7 @@ Valente, Tom W. 1995. \seealso{ Other measures: \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, @@ -66,9 +67,11 @@ Other measures: \code{\link{measure_periods}} Other diffusion: +\code{\link{mark_diff}}, \code{\link{measure_diffusion_infection}}, \code{\link{measure_diffusion_net}}, -\code{\link{measure_diffusion_node}} +\code{\link{measure_diffusion_node}}, +\code{\link{motif_diffusion}} } \concept{diffusion} \concept{measures} From 23a8d28b0007cb4d49294aa9ce7b3379d9eea041 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 06:55:15 +0100 Subject: [PATCH 49/98] Added diffusion data_obj for testing diffusion functions --- tests/testthat/helper-netrics.R | 3 ++- tests/testthat/test-measure_nodes.R | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/testthat/helper-netrics.R b/tests/testthat/helper-netrics.R index c56911b..a087d3a 100644 --- a/tests/testthat/helper-netrics.R +++ b/tests/testthat/helper-netrics.R @@ -83,5 +83,6 @@ data_objs <- list(directed = generate_random(12, directed = TRUE), undirected = generate_random(12, directed = FALSE), twomode = generate_random(c(6,6)), labelled = add_node_attribute(generate_random(12), "name", LETTERS[1:12]), - signed = to_signed(generate_random(12, directed = TRUE))) + signed = to_signed(generate_random(12, directed = TRUE)), + diffusion = play_diffusion(create_ring(12), seeds = 1, steps = 5, latency = 0.75, recovery = 0.25)) diff --git a/tests/testthat/test-measure_nodes.R b/tests/testthat/test-measure_nodes.R index b9f572f..950a29d 100644 --- a/tests/testthat/test-measure_nodes.R +++ b/tests/testthat/test-measure_nodes.R @@ -2,9 +2,12 @@ node_meas <- funs_objs[grepl("node_by_", names(funs_objs))] for(fn in names(node_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("vitality|threshold|richness|recovery|posneg|multideg|hub|homophily|hetero|exposure|diversity|distance|authority|adoption|equivalency", fn)) - if(fn == "x"){ - } else { + skip_if(grepl("vitality|threshold|richness|recovery|posneg|multideg|hub|homophily|hetero|exposure|diversity|distance|authority|equivalency", fn)) + if(grepl("adoption",fn)){ + if(ob == "diffusion"){ + expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") + } + } else if (ob %in% c("directed","undirected","weighted","twomode","signed","labelled")){ expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") } }) From e335431a3860030a04b734abe253cee9d707758d Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 13:56:34 +0100 Subject: [PATCH 50/98] Fixed node_by_power() to revert to lower the exponent (bringing it closer to degree centrality) where there is no degree variation --- R/measure_centrality.R | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index d805d3f..12f5bf8 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -1073,6 +1073,11 @@ node_by_power <- function(.data, normalized = TRUE, scale = FALSE, exponent = 1) manynet::tie_weights(.data), NA) graph <- manynet::as_igraph(.data) + if(var(node_by_deg(graph))==0){ + snet_minor_info("All nodes have the same degree, so power centrality is the same as degree centrality.") + exponent <- 0 + } + # Do the calculations if (!manynet::is_twomode(graph)){ out <- igraph::power_centrality(graph = graph, From 667f138dd7d6a3c8cd34188652c4ae150fcc3571 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 13:57:06 +0100 Subject: [PATCH 51/98] Split heterogeneity functions into diversity and assortativity --- R/measure_heterogeneity.R | 39 +++++++++-- man/measure_assortativity.Rd | 126 +++++++++++++++++++++++++++++++++++ man/measure_heterogeneity.Rd | 66 +----------------- 3 files changed, 160 insertions(+), 71 deletions(-) create mode 100644 man/measure_assortativity.Rd diff --git a/R/measure_heterogeneity.R b/R/measure_heterogeneity.R index 3ec9477..2fcbb5f 100644 --- a/R/measure_heterogeneity.R +++ b/R/measure_heterogeneity.R @@ -1,3 +1,5 @@ +# Diversity #### + #' Measures of network diversity #' #' @description @@ -209,7 +211,31 @@ node_by_diversity <- function(.data, attribute, make_node_measure(out, .data) } -#' @rdname measure_heterogeneity +# Assortativity #### + +#' Measures of network assortativity +#' +#' @description +#' These functions offer ways to measure the distribution or assortativity +#' of ties in a network: +#' +#' - `net_heterophily()` measures how embedded nodes in the network +#' are within groups of nodes with the same attribute. +#' - `node_heterophily()` measures each node's embeddedness within groups +#' of nodes with the same attribute. +#' - `net_assortativity()` measures the degree assortativity in a network. +#' - `net_spatial()` measures the spatial association/autocorrelation +#' (global Moran's I) in a network. +#' +#' @inheritParams mark_nodes +#' @inheritParams measure_heterogeneity +#' @param attribute Name of a nodal attribute or membership vector +#' to use as categories for the diversity measure. +#' @name measure_assortativity +#' @family measures +NULL + +#' @rdname measure_assortativity #' @section Homophily: #' Given a partition of a network into a number of mutually exclusive groups then #' The E-I index is the number of ties between (or _external_) nodes @@ -230,6 +256,7 @@ node_by_diversity <- function(.data, attribute, #' _Annual Review of Sociology_, 27(1): 415-444. #' \doi{10.1146/annurev.soc.27.1.415} #' @examples +#' marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") #' net_by_heterophily(marvel_friends, "Gender") #' net_by_heterophily(marvel_friends, "Attractive") #' @export @@ -249,7 +276,7 @@ net_by_heterophily <- function(.data, attribute){ make_network_measure(ei, .data, call = deparse(sys.call())) } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @examples #' node_by_heterophily(marvel_friends, "Gender") #' node_by_heterophily(marvel_friends, "Attractive") @@ -275,7 +302,7 @@ node_by_heterophily <- function(.data, attribute){ make_node_measure(ei, .data) } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @examples #' net_by_homophily(marvel_friends, "Gender") #' @export @@ -373,7 +400,7 @@ attr_mode <- function(.data, attribute){ } else NULL } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @export node_by_homophily <- function(.data, attribute, method = c("ie","ei","yule","geary")){ @@ -400,7 +427,7 @@ node_by_homophily <- function(.data, attribute, make_node_measure(out, .data) } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @importFrom igraph assortativity_degree #' @references #' ## On assortativity @@ -418,7 +445,7 @@ net_by_assortativity <- function(.data){ .data, call = deparse(sys.call())) } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @references #' ## On spatial autocorrelation #' Moran, Patrick Alfred Pierce. 1950. diff --git a/man/measure_assortativity.Rd b/man/measure_assortativity.Rd new file mode 100644 index 0000000..5b96a06 --- /dev/null +++ b/man/measure_assortativity.Rd @@ -0,0 +1,126 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/measure_heterogeneity.R +\name{measure_assortativity} +\alias{measure_assortativity} +\alias{net_by_heterophily} +\alias{node_by_heterophily} +\alias{net_by_homophily} +\alias{node_by_homophily} +\alias{net_by_assortativity} +\alias{net_by_spatial} +\title{Measures of network assortativity} +\usage{ +net_by_heterophily(.data, attribute) + +node_by_heterophily(.data, attribute) + +net_by_homophily(.data, attribute, method = c("ie", "ei", "yule", "geary")) + +node_by_homophily(.data, attribute, method = c("ie", "ei", "yule", "geary")) + +net_by_assortativity(.data) + +net_by_spatial(.data, attribute) +} +\arguments{ +\item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. +For more information on the standard coercion possible, +see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} + +\item{attribute}{Name of a nodal attribute or membership vector +to use as categories for the diversity measure.} + +\item{method}{Which method to use for \code{net_diversity()}. +Either "blau" (Blau's index) or "teachman" (Teachman's index) for +categorical attributes, or "variation" (coefficient of variation) +or "gini" (Gini coefficient) for numeric attributes. +Default is "blau". +If an incompatible method is chosen for the attribute type, +a suitable alternative will be used instead with a message.} +} +\description{ +These functions offer ways to measure the distribution or assortativity +of ties in a network: +\itemize{ +\item \code{net_heterophily()} measures how embedded nodes in the network +are within groups of nodes with the same attribute. +\item \code{node_heterophily()} measures each node's embeddedness within groups +of nodes with the same attribute. +\item \code{net_assortativity()} measures the degree assortativity in a network. +\item \code{net_spatial()} measures the spatial association/autocorrelation +(global Moran's I) in a network. +} +} +\section{Homophily}{ + +Given a partition of a network into a number of mutually exclusive groups then +The E-I index is the number of ties between (or \emph{external}) nodes +grouped in some mutually exclusive categories +minus the number of ties within (or \emph{internal}) these groups +divided by the total number of ties. +This value can range from 1 to -1, +where 1 indicates ties only between categories/groups and -1 ties only within categories/groups. +} + +\examples{ +marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") +net_by_heterophily(marvel_friends, "Gender") +net_by_heterophily(marvel_friends, "Attractive") +node_by_heterophily(marvel_friends, "Gender") +node_by_heterophily(marvel_friends, "Attractive") +net_by_homophily(marvel_friends, "Gender") +net_by_assortativity(ison_networkers) +net_by_spatial(ison_lawfirm, "age") +} +\references{ +\subsection{On heterophily}{ + +Krackhardt, David, and Robert N. Stern. 1988. +Informal networks and organizational crises: an experimental simulation. +\emph{Social Psychology Quarterly} 51(2): 123-140. +\doi{10.2307/2786835} + +McPherson, Miller, Lynn Smith-Lovin, and James M. Cook. 2001. +"Birds of a Feather: Homophily in Social Networks". +\emph{Annual Review of Sociology}, 27(1): 415-444. +\doi{10.1146/annurev.soc.27.1.415} +} + +\subsection{On assortativity}{ + +Newman, Mark E.J. 2002. +"Assortative mixing in networks". +\emph{Physical Review Letters}, 89(20): 208701. +\doi{10.1103/physrevlett.89.208701} +} + +\subsection{On spatial autocorrelation}{ + +Moran, Patrick Alfred Pierce. 1950. +"Notes on continuous stochastic phenomena". +\emph{Biometrika} 37(1): 17-23. +\doi{10.2307/2332142} +} +} +\seealso{ +Other measures: +\code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, +\code{\link{measure_central_between}}, +\code{\link{measure_central_close}}, +\code{\link{measure_central_degree}}, +\code{\link{measure_central_eigen}}, +\code{\link{measure_closure}}, +\code{\link{measure_cohesion}}, +\code{\link{measure_diffusion_infection}}, +\code{\link{measure_diffusion_net}}, +\code{\link{measure_diffusion_node}}, +\code{\link{measure_features}}, +\code{\link{measure_fragmentation}}, +\code{\link{measure_heterogeneity}}, +\code{\link{measure_hierarchy}}, +\code{\link{measure_holes}}, +\code{\link{measure_periods}}, +\code{\link{member_diffusion}} +} +\concept{measures} diff --git a/man/measure_heterogeneity.Rd b/man/measure_heterogeneity.Rd index 30b4d26..ac38934 100644 --- a/man/measure_heterogeneity.Rd +++ b/man/measure_heterogeneity.Rd @@ -6,12 +6,6 @@ \alias{node_by_richness} \alias{net_by_diversity} \alias{node_by_diversity} -\alias{net_by_heterophily} -\alias{node_by_heterophily} -\alias{net_by_homophily} -\alias{node_by_homophily} -\alias{net_by_assortativity} -\alias{net_by_spatial} \title{Measures of network diversity} \usage{ net_by_richness(.data, attribute) @@ -29,18 +23,6 @@ node_by_diversity( attribute, method = c("blau", "teachman", "variation", "gini") ) - -net_by_heterophily(.data, attribute) - -node_by_heterophily(.data, attribute) - -net_by_homophily(.data, attribute, method = c("ie", "ei", "yule", "geary")) - -node_by_homophily(.data, attribute, method = c("ie", "ei", "yule", "geary")) - -net_by_assortativity(.data) - -net_by_spatial(.data, attribute) } \arguments{ \item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. @@ -142,17 +124,6 @@ and the Lorenz curve, divided by the total area under the line of equality. } -\section{Homophily}{ - -Given a partition of a network into a number of mutually exclusive groups then -The E-I index is the number of ties between (or \emph{external}) nodes -grouped in some mutually exclusive categories -minus the number of ties within (or \emph{internal}) these groups -divided by the total number of ties. -This value can range from 1 to -1, -where 1 indicates ties only between categories/groups and -1 ties only within categories/groups. -} - \examples{ net_by_richness(ison_networkers) node_by_richness(ison_networkers, "Discipline") @@ -161,13 +132,6 @@ net_by_diversity(marvel_friends, "Gender") net_by_diversity(marvel_friends, "Appearances") node_by_diversity(marvel_friends, "Gender") node_by_diversity(marvel_friends, "Attractive") -net_by_heterophily(marvel_friends, "Gender") -net_by_heterophily(marvel_friends, "Attractive") -node_by_heterophily(marvel_friends, "Gender") -node_by_heterophily(marvel_friends, "Attractive") -net_by_homophily(marvel_friends, "Gender") -net_by_assortativity(ison_networkers) -net_by_spatial(ison_lawfirm, "age") } \references{ \subsection{On richness}{ @@ -194,38 +158,10 @@ Page, Scott E. 2010. Princeton: Princeton University Press. \doi{10.1515/9781400835140} } - -\subsection{On heterophily}{ - -Krackhardt, David, and Robert N. Stern. 1988. -Informal networks and organizational crises: an experimental simulation. -\emph{Social Psychology Quarterly} 51(2): 123-140. -\doi{10.2307/2786835} - -McPherson, Miller, Lynn Smith-Lovin, and James M. Cook. 2001. -"Birds of a Feather: Homophily in Social Networks". -\emph{Annual Review of Sociology}, 27(1): 415-444. -\doi{10.1146/annurev.soc.27.1.415} -} - -\subsection{On assortativity}{ - -Newman, Mark E.J. 2002. -"Assortative mixing in networks". -\emph{Physical Review Letters}, 89(20): 208701. -\doi{10.1103/physrevlett.89.208701} -} - -\subsection{On spatial autocorrelation}{ - -Moran, Patrick Alfred Pierce. 1950. -"Notes on continuous stochastic phenomena". -\emph{Biometrika} 37(1): 17-23. -\doi{10.2307/2332142} -} } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, From a2bfdb733c966cf7aa41d30dc88d9fbb83d4d65a Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 13:58:14 +0100 Subject: [PATCH 52/98] Split hierarchy into measures and motif --- R/measure_hierarchy.R | 64 ++++++++++++++++++++++++------ man/measure_breadth.Rd | 1 + man/measure_brokerage.Rd | 1 + man/measure_central_between.Rd | 1 + man/measure_central_close.Rd | 1 + man/measure_central_degree.Rd | 1 + man/measure_central_eigen.Rd | 1 + man/measure_closure.Rd | 1 + man/measure_cohesion.Rd | 1 + man/measure_diffusion_infection.Rd | 1 + man/measure_diffusion_net.Rd | 1 + man/measure_diffusion_node.Rd | 1 + man/measure_features.Rd | 1 + man/measure_fragmentation.Rd | 1 + man/measure_hierarchy.Rd | 10 +++-- man/measure_holes.Rd | 1 + man/measure_periods.Rd | 1 + man/member_diffusion.Rd | 1 + man/motif_brokerage.Rd | 1 + man/motif_diffusion.Rd | 1 + man/motif_hierarchy.Rd | 59 +++++++++++++++++++++++++++ man/motif_net.Rd | 1 + man/motif_node.Rd | 1 + pkgdown/_pkgdown.yml | 1 + 24 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 man/motif_hierarchy.Rd diff --git a/R/measure_hierarchy.R b/R/measure_hierarchy.R index baa91f6..9d8c7d6 100644 --- a/R/measure_hierarchy.R +++ b/R/measure_hierarchy.R @@ -1,4 +1,54 @@ -#' Graph theoretic dimensions of hierarchy +# Motifs #### + +#' Motifs of hierarchy +#' +#' @description +#' This function collects the measures of hierarchy into a single motif, +#' which can be used to compare the relative hierarchy of different networks. +#' The measures of hierarchy are: +#' - `net_connectedness()` measures the proportion of dyads in the network +#' that are reachable to one another, +#' or the degree to which network is a single component. +#' - `net_efficiency()` measures the Krackhardt efficiency score. +#' - `net_upperbound()` measures the Krackhardt (least) upper bound +#' score. +#' - `net_reciprocity()` measures the proportion of ties in the network that +#' are reciprocated, +#' which is a measure of the degree to which the network is non-hierarchical. +#' +#' @inheritParams mark_nodes +#' @name motif_hierarchy +#' @family motifs +#' @family hierarchy +#' @references +#' ## On hierarchy +#' Krackhardt, David. 1994. +#' Graph theoretical dimensions of informal organizations. +#' In Carley and Prietula (eds) _Computational Organizational Theory_, +#' Hillsdale, NJ: Lawrence Erlbaum Associates. Pp. 89-111. +#' +#' Everett, Martin, and David Krackhardt. 2012. +#' “A second look at Krackhardt's graph theoretical dimensions of informal organizations.” +#' _Social Networks_, 34: 159-163. +#' \doi{10.1016/j.socnet.2011.10.006} +#' @examples +#' net_x_hierarchy(ison_networkers) +NULL + +#' @rdname motif_hierarchy +#' @export +net_x_hierarchy <- function(.data){ + .data <- manynet::expect_nodes(.data) + out <- data.frame(Connectedness = net_by_connectedness(.data), + InvReciprocity = 1 - net_by_reciprocity(.data), + Efficiency = net_by_efficiency(.data), + LeastUpperBound = net_by_upperbound(.data)) + make_network_motif(out, .data) +} + +# Measures #### + +#' Measures of hierarchy #' #' @description #' These functions, together with `net_reciprocity()`, are used jointly to @@ -13,6 +63,7 @@ #' @inheritParams mark_nodes #' @name measure_hierarchy #' @family measures +#' @family hierarchy #' @references #' ## On hierarchy #' Krackhardt, David. 1994. @@ -31,17 +82,6 @@ #' net_upperbound(ison_networkers) NULL -#' @rdname measure_hierarchy -#' @export -net_x_hierarchy <- function(.data){ - .data <- manynet::expect_nodes(.data) - out <- data.frame(Connectedness = net_by_connectedness(.data), - InvReciprocity = 1 - net_by_reciprocity(.data), - Efficiency = net_by_efficiency(.data), - LeastUpperBound = net_by_upperbound(.data)) - make_network_motif(out, .data) -} - #' @rdname measure_hierarchy #' @export net_by_connectedness <- function(.data){ diff --git a/man/measure_breadth.Rd b/man/measure_breadth.Rd index 4304bd6..17c9608 100644 --- a/man/measure_breadth.Rd +++ b/man/measure_breadth.Rd @@ -30,6 +30,7 @@ net_by_length(to_giant(fict_marvel)) } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, diff --git a/man/measure_brokerage.Rd b/man/measure_brokerage.Rd index e44390c..a3f71b5 100644 --- a/man/measure_brokerage.Rd +++ b/man/measure_brokerage.Rd @@ -40,6 +40,7 @@ and their application to multi-level environmental governance networks" } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, diff --git a/man/measure_central_between.Rd b/man/measure_central_between.Rd index ea22911..9ee90d4 100644 --- a/man/measure_central_between.Rd +++ b/man/measure_central_between.Rd @@ -149,6 +149,7 @@ Other centrality: \code{\link{measure_central_eigen}} Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_close}}, diff --git a/man/measure_central_close.Rd b/man/measure_central_close.Rd index b2c069f..15ce16a 100644 --- a/man/measure_central_close.Rd +++ b/man/measure_central_close.Rd @@ -272,6 +272,7 @@ Other centrality: \code{\link{measure_central_eigen}} Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_central_degree.Rd b/man/measure_central_degree.Rd index 919ad7c..b2625e6 100644 --- a/man/measure_central_degree.Rd +++ b/man/measure_central_degree.Rd @@ -192,6 +192,7 @@ Other centrality: \code{\link{measure_central_eigen}} Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_central_eigen.Rd b/man/measure_central_eigen.Rd index 29a3723..3261673 100644 --- a/man/measure_central_eigen.Rd +++ b/man/measure_central_eigen.Rd @@ -223,6 +223,7 @@ Other centrality: \code{\link{measure_central_degree}} Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_closure.Rd b/man/measure_closure.Rd index f32652d..d36e57b 100644 --- a/man/measure_closure.Rd +++ b/man/measure_closure.Rd @@ -93,6 +93,7 @@ Cambridge University Press. Cambridge University Press. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_cohesion.Rd b/man/measure_cohesion.Rd index ef93932..1818a85 100644 --- a/man/measure_cohesion.Rd +++ b/man/measure_cohesion.Rd @@ -54,6 +54,7 @@ net_by_independence(ison_adolescents) } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_diffusion_infection.Rd b/man/measure_diffusion_infection.Rd index 3561c06..54beb20 100644 --- a/man/measure_diffusion_infection.Rd +++ b/man/measure_diffusion_infection.Rd @@ -49,6 +49,7 @@ highest infection rate is observed. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_diffusion_net.Rd b/man/measure_diffusion_net.Rd index 03861ef..27e9039 100644 --- a/man/measure_diffusion_net.Rd +++ b/man/measure_diffusion_net.Rd @@ -165,6 +165,7 @@ Garnett, G.P. 2005. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_diffusion_node.Rd b/man/measure_diffusion_node.Rd index 5205712..a4d5380 100644 --- a/man/measure_diffusion_node.Rd +++ b/man/measure_diffusion_node.Rd @@ -115,6 +115,7 @@ Valente, Tom W. 1995. \emph{Network models of the diffusion of innovations} } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_features.Rd b/man/measure_features.Rd index 6046686..5f07e02 100644 --- a/man/measure_features.Rd +++ b/man/measure_features.Rd @@ -234,6 +234,7 @@ Cartwright, D., and Frank Harary. 1956. for how clustering is calculated Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_fragmentation.Rd b/man/measure_fragmentation.Rd index 7ccb2f5..29b1427 100644 --- a/man/measure_fragmentation.Rd +++ b/man/measure_fragmentation.Rd @@ -54,6 +54,7 @@ White, Douglas R and Frank Harary. 2001. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_hierarchy.Rd b/man/measure_hierarchy.Rd index 5fd1c55..428d6cb 100644 --- a/man/measure_hierarchy.Rd +++ b/man/measure_hierarchy.Rd @@ -2,14 +2,11 @@ % Please edit documentation in R/measure_hierarchy.R \name{measure_hierarchy} \alias{measure_hierarchy} -\alias{net_x_hierarchy} \alias{net_by_connectedness} \alias{net_by_efficiency} \alias{net_by_upperbound} -\title{Graph theoretic dimensions of hierarchy} +\title{Measures of hierarchy} \usage{ -net_x_hierarchy(.data) - net_by_connectedness(.data) net_by_efficiency(.data) @@ -54,6 +51,7 @@ Everett, Martin, and David Krackhardt. 2012. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, @@ -71,5 +69,9 @@ Other measures: \code{\link{measure_holes}}, \code{\link{measure_periods}}, \code{\link{member_diffusion}} + +Other hierarchy: +\code{\link{motif_hierarchy}} } +\concept{hierarchy} \concept{measures} diff --git a/man/measure_holes.Rd b/man/measure_holes.Rd index ff37776..4c1230d 100644 --- a/man/measure_holes.Rd +++ b/man/measure_holes.Rd @@ -111,6 +111,7 @@ Barrat, Alain, Marc Barthelemy, Romualdo Pastor-Satorras, and Alessandro Vespign } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/measure_periods.Rd b/man/measure_periods.Rd index d453680..4f6af7d 100644 --- a/man/measure_periods.Rd +++ b/man/measure_periods.Rd @@ -37,6 +37,7 @@ of networks minus one. E.g., the periods between waves. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/member_diffusion.Rd b/man/member_diffusion.Rd index 595f3ee..d067505 100644 --- a/man/member_diffusion.Rd +++ b/man/member_diffusion.Rd @@ -48,6 +48,7 @@ Valente, Tom W. 1995. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, \code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, diff --git a/man/motif_brokerage.Rd b/man/motif_brokerage.Rd index 55bca31..bcf5502 100644 --- a/man/motif_brokerage.Rd +++ b/man/motif_brokerage.Rd @@ -54,6 +54,7 @@ Jasny, Lorien, and Mark Lubell. 2015. \seealso{ Other motifs: \code{\link{motif_diffusion}}, +\code{\link{motif_hierarchy}}, \code{\link{motif_net}}, \code{\link{motif_node}} diff --git a/man/motif_diffusion.Rd b/man/motif_diffusion.Rd index 254fd6a..4a7541c 100644 --- a/man/motif_diffusion.Rd +++ b/man/motif_diffusion.Rd @@ -90,6 +90,7 @@ Cambridge: MIT Press. \seealso{ Other motifs: \code{\link{motif_brokerage}}, +\code{\link{motif_hierarchy}}, \code{\link{motif_net}}, \code{\link{motif_node}} diff --git a/man/motif_hierarchy.Rd b/man/motif_hierarchy.Rd new file mode 100644 index 0000000..ac38c62 --- /dev/null +++ b/man/motif_hierarchy.Rd @@ -0,0 +1,59 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/measure_hierarchy.R +\name{motif_hierarchy} +\alias{motif_hierarchy} +\alias{net_x_hierarchy} +\title{Motifs of hierarchy} +\usage{ +net_x_hierarchy(.data) +} +\arguments{ +\item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. +For more information on the standard coercion possible, +see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} +} +\description{ +This function collects the measures of hierarchy into a single motif, +which can be used to compare the relative hierarchy of different networks. +The measures of hierarchy are: +\itemize{ +\item \code{net_connectedness()} measures the proportion of dyads in the network +that are reachable to one another, +or the degree to which network is a single component. +\item \code{net_efficiency()} measures the Krackhardt efficiency score. +\item \code{net_upperbound()} measures the Krackhardt (least) upper bound +score. +\item \code{net_reciprocity()} measures the proportion of ties in the network that +are reciprocated, +which is a measure of the degree to which the network is non-hierarchical. +} +} +\examples{ +net_x_hierarchy(ison_networkers) +} +\references{ +\subsection{On hierarchy}{ + +Krackhardt, David. 1994. +Graph theoretical dimensions of informal organizations. +In Carley and Prietula (eds) \emph{Computational Organizational Theory}, +Hillsdale, NJ: Lawrence Erlbaum Associates. Pp. 89-111. + +Everett, Martin, and David Krackhardt. 2012. +“A second look at Krackhardt's graph theoretical dimensions of informal organizations.” +\emph{Social Networks}, 34: 159-163. +\doi{10.1016/j.socnet.2011.10.006} +} +} +\seealso{ +Other motifs: +\code{\link{motif_brokerage}}, +\code{\link{motif_diffusion}}, +\code{\link{motif_net}}, +\code{\link{motif_node}} + +Other hierarchy: +\code{\link{measure_hierarchy}} +} +\concept{hierarchy} +\concept{motifs} diff --git a/man/motif_net.Rd b/man/motif_net.Rd index 2e49152..6c6fc3f 100644 --- a/man/motif_net.Rd +++ b/man/motif_net.Rd @@ -116,6 +116,7 @@ Hollway, James, Alessandro Lomi, Francesca Pallotti, and Christoph Stadtfeld. 20 Other motifs: \code{\link{motif_brokerage}}, \code{\link{motif_diffusion}}, +\code{\link{motif_hierarchy}}, \code{\link{motif_node}} } \concept{motifs} diff --git a/man/motif_node.Rd b/man/motif_node.Rd index 0140269..353f4c7 100644 --- a/man/motif_node.Rd +++ b/man/motif_node.Rd @@ -121,6 +121,7 @@ Opsahl, Tore, Filip Agneessens, and John Skvoretz. 2010. Other motifs: \code{\link{motif_brokerage}}, \code{\link{motif_diffusion}}, +\code{\link{motif_hierarchy}}, \code{\link{motif_net}} } \concept{motifs} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 0dd4901..ec08648 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -73,6 +73,7 @@ reference: - measure_closure - measure_features - measure_heterogeneity + - measure_assortativity - measure_fragmentation - measure_breadth - subtitle: "Dynamics" From 2d406784d1c66b1fd94f2f28f875ec77ff8352e9 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 13:58:36 +0100 Subject: [PATCH 53/98] Added cosine cluster testing --- tests/testthat/test-model_cluster.R | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-model_cluster.R b/tests/testthat/test-model_cluster.R index f7f1e0d..ed5a03e 100644 --- a/tests/testthat/test-model_cluster.R +++ b/tests/testthat/test-model_cluster.R @@ -1,3 +1,8 @@ test_that("cluster_cosine works", { - expect_s3_class(cluster_cosine(node_by_triad(ison_monks), distance = "euclidean"), "hclust") + expect_s3_class(cluster_cosine(node_x_triad(ison_monks), distance = "euclidean"), "hclust") +}) + +test_that("cluster_cosine works", { + unlab_2mode <- generate_random(c(6,6)) + expect_s3_class(cluster_concor(unlab_2mode, node_x_tetrad(unlab_2mode)), "hclust") }) From 435a845ede84cc61eece1caf55961a2cb00bd9a2 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 13:58:55 +0100 Subject: [PATCH 54/98] Fixed motif testing to use new names --- tests/testthat/test-motif_nodes.R | 57 +++++++++++++------------------ 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/tests/testthat/test-motif_nodes.R b/tests/testthat/test-motif_nodes.R index 110fdd5..14f82b2 100644 --- a/tests/testthat/test-motif_nodes.R +++ b/tests/testthat/test-motif_nodes.R @@ -16,92 +16,81 @@ for(fn in names(node_motifs)) { set.seed(123) task_eg <- to_named(to_uniplex(ison_algebra, "tasks")) -test_that("node_by_tie census works", { - test <- node_by_tie(task_eg) +test_that("node_x_tie census works", { + test <- node_x_tie(task_eg) expect_equal(test[1:4], rep(0, 4)) - expect_s3_class(test, "node_motif") expect_output(print(test), "fromA") expect_equal(nrow(summary(test, membership = node_in_roulette(task_eg, 3))), 3) }) -test_that("node_by_dyad census works", { - test <- node_by_dyad(ison_adolescents) - expect_s3_class(test, "node_motif") +test_that("node_x_dyad census works", { + test <- node_x_dyad(ison_adolescents) expect_equal(colnames(test)[1:2], c("Mutual", "Null")) }) -test_that("node_by_triad census works", { - test <- node_by_triad(task_eg) +test_that("node_x_triad census works", { + test <- node_x_triad(task_eg) expect_equal(top3(test[,16]), c(7,8,6)) - expect_s3_class(test, "node_motif") expect_equal(colnames(test)[1:3], c("003", "012", "102")) }) -test_that("net_by_dyad census works", { - test <- net_by_dyad(ison_adolescents) +test_that("net_x_dyad census works", { + test <- net_x_dyad(ison_adolescents) expect_equal(test[[1]], 10) expect_equal(test[[2]], 18) expect_equal(names(test), c("Mutual", "Null")) - expect_s3_class(test, "network_motif") expect_output(print(test), "Mutual") }) -test_that("net_by_triad census works", { - test <- net_by_triad(ison_adolescents) +test_that("net_x_triad census works", { + test <- net_x_triad(ison_adolescents) expect_equal(test[[1]], 13) expect_equal(test[[3]], 29) expect_equal(names(test), c("003", "012", "102", "201", "210", "300")) - expect_s3_class(test, "network_motif") expect_equal(names(summary(test)), c("003", "012", "102", "201", "210", "300")) # Error - expect_error(net_by_triad(ison_southern_women)) + expect_error(net_x_triad(ison_southern_women)) }) -test_that("net_by_tetrad census works", { - test <- net_by_tetrad(ison_southern_women) - expect_s3_class(test, "network_motif") +test_that("net_x_tetrad census works", { + test <- net_x_tetrad(ison_southern_women) expect_values(c(test)[1], 12388) }) -test_that("node_by_tetrad census works", { - test <- node_by_tetrad(ison_southern_women) - expect_s3_class(test, "node_motif") +test_that("node_x_tetrad census works", { + test <- node_x_tetrad(ison_southern_women) expect_equal(test[1,1], 1241) }) test_that("net_mixed census works", { marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") - test <- net_by_mixed(marvel_friends, to_uniplex(fict_marvel, "affiliation")) - expect_s3_class(test, "network_motif") + test <- net_x_mixed(marvel_friends, to_uniplex(fict_marvel, "affiliation")) expect_equal(unname(test[1]), 1137) expect_equal(names(test[1]), "22") # Errors - expect_error(net_by_mixed(ison_southern_women, + expect_error(net_x_mixed(ison_southern_women, to_uniplex(fict_marvel, "affiliation"))) - expect_error(net_by_mixed(to_uniplex(fict_marvel, "affiliation"), + expect_error(net_x_mixed(to_uniplex(fict_marvel, "affiliation"), ison_southern_women)) - expect_error(net_by_mixed(ison_karateka, + expect_error(net_x_mixed(ison_karateka, to_uniplex(fict_marvel, "affiliation"))) }) -test <- node_by_path(ison_southern_women) +test <- node_x_path(ison_southern_women) test_that("node path census works", { expect_equal(c(net_nodes(ison_adolescents)), - nrow(node_by_path(ison_adolescents))) - expect_s3_class(test, "node_motif") - expect_true(nrow(node_by_path(ison_southern_women)) == - ncol(node_by_path(ison_southern_women))) + nrow(node_x_path(ison_adolescents))) + expect_true(nrow(node_x_path(ison_southern_women)) == + ncol(node_x_path(ison_southern_women))) }) test_that("node_x_brokerage works", { test <- node_x_brokerage(ison_networkers, "Discipline") - expect_s3_class(test, "node_motif") expect_equal(dim(test), c(32,6)) }) test_that("net_x_brokerage works", { test <- net_x_brokerage(ison_networkers, "Discipline") - expect_s3_class(test, "network_motif") expect_equal(top3(names(test)), c("Coordinator","Itinerant","Gatekeeper")) }) From e1516331181b5853faa3add456fefbe9ce6a7286 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 13:59:21 +0100 Subject: [PATCH 55/98] Improved documentation titles --- R/mark_nodes.R | 2 +- R/mark_ties.R | 2 +- R/member_components.R | 2 +- man/mark_select.Rd | 2 +- man/mark_tie_select.Rd | 2 +- man/member_components.Rd | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/mark_nodes.R b/R/mark_nodes.R index 6153a55..730647e 100644 --- a/R/mark_nodes.R +++ b/R/mark_nodes.R @@ -405,7 +405,7 @@ node_is_exposed <- function(.data, mark, time = 0){ # Selection properties #### -#' Marking nodes for selection based on measures +#' Marking nodes based on measures #' #' @description #' These functions return logical vectors the length of the diff --git a/R/mark_ties.R b/R/mark_ties.R index 22e3f6a..3db2c86 100644 --- a/R/mark_ties.R +++ b/R/mark_ties.R @@ -313,7 +313,7 @@ tie_is_imbalanced <- function(.data){ # Selection properties #### -#' Marking ties for selection based on measures +#' Marking ties based on measures #' #' @description #' These functions return logical vectors the length of the ties in a network: diff --git a/R/member_components.R b/R/member_components.R index dfad1a8..17e2cfb 100644 --- a/R/member_components.R +++ b/R/member_components.R @@ -1,4 +1,4 @@ -#' Component clustering algorithms +#' Membership in components #' #' @description #' These functions create a vector of nodes' memberships in components: diff --git a/man/mark_select.Rd b/man/mark_select.Rd index 93138aa..c629763 100644 --- a/man/mark_select.Rd +++ b/man/mark_select.Rd @@ -6,7 +6,7 @@ \alias{node_is_max} \alias{node_is_min} \alias{node_is_mean} -\title{Marking nodes for selection based on measures} +\title{Marking nodes based on measures} \usage{ node_is_random(.data, size = 1) diff --git a/man/mark_tie_select.Rd b/man/mark_tie_select.Rd index c1a7fc9..debb95e 100644 --- a/man/mark_tie_select.Rd +++ b/man/mark_tie_select.Rd @@ -5,7 +5,7 @@ \alias{tie_is_random} \alias{tie_is_max} \alias{tie_is_min} -\title{Marking ties for selection based on measures} +\title{Marking ties based on measures} \usage{ tie_is_random(.data, size = 1) diff --git a/man/member_components.Rd b/man/member_components.Rd index 1f0572a..156926a 100644 --- a/man/member_components.Rd +++ b/man/member_components.Rd @@ -5,7 +5,7 @@ \alias{node_in_component} \alias{node_in_weak} \alias{node_in_strong} -\title{Component clustering algorithms} +\title{Membership in components} \usage{ node_in_component(.data) From efe1682bbff095b177604391cdb3fad5b9ca2fe8 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 16:05:29 +0100 Subject: [PATCH 56/98] Drop tie_is_forbidden for now --- NAMESPACE | 1 - R/mark_ties.R | 66 ++++++++++++++++++++++--------------------- man/mark_triangles.Rd | 7 ----- 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 3b14718..04cd795 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -165,7 +165,6 @@ export(tie_by_eigenvector) export(tie_is_bridge) export(tie_is_cyclical) export(tie_is_feedback) -export(tie_is_forbidden) export(tie_is_imbalanced) export(tie_is_loop) export(tie_is_max) diff --git a/R/mark_ties.R b/R/mark_ties.R index 3db2c86..d237656 100644 --- a/R/mark_ties.R +++ b/R/mark_ties.R @@ -114,7 +114,7 @@ tie_is_path <- function(.data, from, to, all_paths = FALSE){ #' and fully reciprocated. #' - `tie_is_imbalanced()` marks ties that are part of imbalanced triads. #' - `tie_is_transitive()` marks ties that complete transitive closure. -#' - `tie_is_forbidden()` marks ties that complete forbidden triads. +# #' - `tie_is_forbidden()` marks ties that complete forbidden triads. #' #' They are most useful in highlighting parts of the network that #' are cohesively connected. @@ -218,37 +218,39 @@ tie_is_simmelian <- function(.data){ make_tie_mark(out, .data) } -#' @rdname mark_triangles -#' @examples -#' generate_random(8, directed = TRUE) %>% -#' mutate_ties(forbid = tie_is_forbidden()) -#' #graphr(edge_color = "forbid") -#' @export -tie_is_forbidden <- function(.data){ - .data <- manynet::expect_ties(.data) - dists <- igraph::distances(.data, mode = "out")==2 - ends <- which(dists * t(dists)==1, arr.ind = TRUE) - ends <- t(apply(ends, 1, function(x) sort(x))) - ends <- ends[!duplicated(ends),] - tris <- apply(ends, 1, function(x){ - y <- unlist(igraph::all_shortest_paths(.data, x[1], x[2], mode = "out")$res) - y <- matrix(y, ncol = 3, byrow = TRUE) - y <- do.call("paste", c(as.data.frame(y)[, , drop = FALSE], sep = "-")) - z <- unlist(igraph::all_shortest_paths(.data, x[1], x[2], mode = "in")$res) - z <- matrix(z, ncol = 3, byrow = TRUE) - z <- do.call("paste", c(as.data.frame(z)[, , drop = FALSE], sep = "-")) - as.numeric(unlist(strsplit(y[y %in% z], "-"))) - }) - out <- matrix(unlist(tris), ncol = 3, byrow = TRUE) - out <- unique(c(apply(out, 1, function(x){ - c(paste0(x[1],"->",x[2]), - paste0(x[2],"->",x[1]), - paste0(x[2],"->",x[3]), - paste0(x[3],"->",x[2])) - } ))) - out <- names(tie_is_reciprocated(.data)) %in% out - make_tie_mark(out, .data) -} +# #' @rdname mark_triangles +# #' @examples +# #' generate_random(8, directed = TRUE) %>% +# #' mutate_ties(forbid = tie_is_forbidden()) +# #' #graphr(edge_color = "forbid") +# #' @export +# tie_is_forbidden <- function(.data){ +# .data <- manynet::expect_ties(.data) +# if(!manynet::is_weighted(.data)) +# snet_abort("This function only works with weighted networks.") +# dists <- igraph::distances(.data, mode = "out")==2 +# ends <- which(dists * t(dists)==1, arr.ind = TRUE) +# ends <- t(apply(ends, 1, function(x) sort(x))) +# ends <- ends[!duplicated(ends),] +# tris <- apply(ends, 1, function(x){ +# y <- unlist(igraph::all_shortest_paths(.data, x[1], x[2], mode = "out")$res) +# y <- matrix(y, ncol = 3, byrow = TRUE) +# y <- do.call("paste", c(as.data.frame(y)[, , drop = FALSE], sep = "-")) +# z <- unlist(igraph::all_shortest_paths(.data, x[1], x[2], mode = "in")$res) +# z <- matrix(z, ncol = 3, byrow = TRUE) +# z <- do.call("paste", c(as.data.frame(z)[, , drop = FALSE], sep = "-")) +# as.numeric(unlist(strsplit(y[y %in% z], "-"))) +# }) +# out <- matrix(unlist(tris), ncol = 3, byrow = TRUE) +# out <- unique(c(apply(out, 1, function(x){ +# c(paste0(x[1],"->",x[2]), +# paste0(x[2],"->",x[1]), +# paste0(x[2],"->",x[3]), +# paste0(x[3],"->",x[2])) +# } ))) +# out <- names(tie_is_reciprocated(.data)) %in% out +# make_tie_mark(out, .data) +# } #' @rdname mark_triangles #' @examples diff --git a/man/mark_triangles.Rd b/man/mark_triangles.Rd index 999eef5..8a1ef60 100644 --- a/man/mark_triangles.Rd +++ b/man/mark_triangles.Rd @@ -7,7 +7,6 @@ \alias{tie_is_triplet} \alias{tie_is_cyclical} \alias{tie_is_simmelian} -\alias{tie_is_forbidden} \alias{tie_is_imbalanced} \title{Marking ties based on triangular properties} \usage{ @@ -21,8 +20,6 @@ tie_is_cyclical(.data) tie_is_simmelian(.data) -tie_is_forbidden(.data) - tie_is_imbalanced(.data) } \arguments{ @@ -41,7 +38,6 @@ in a network identifying which hold certain properties or positions in the netwo and fully reciprocated. \item \code{tie_is_imbalanced()} marks ties that are part of imbalanced triads. \item \code{tie_is_transitive()} marks ties that complete transitive closure. -\item \code{tie_is_forbidden()} marks ties that complete forbidden triads. } They are most useful in highlighting parts of the network that @@ -63,9 +59,6 @@ ison_adolescents \%>\% to_directed() \%>\% ison_monks \%>\% to_uniplex("like") \%>\% mutate_ties(simmel = tie_is_simmelian()) #graphr(edge_color = "simmel") -generate_random(8, directed = TRUE) \%>\% - mutate_ties(forbid = tie_is_forbidden()) - #graphr(edge_color = "forbid") fict_marvel \%>\% to_uniplex("relationship") \%>\% tie_is_imbalanced() } \seealso{ From c84aafaf75550d5c60488b41c0299f58a23a00bd Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 18:19:28 +0100 Subject: [PATCH 57/98] Testing diversity and assortativity network measures --- tests/testthat/helper-netrics.R | 11 ++++++----- tests/testthat/test-measure_net.R | 6 ++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/testthat/helper-netrics.R b/tests/testthat/helper-netrics.R index a087d3a..9a05c8e 100644 --- a/tests/testthat/helper-netrics.R +++ b/tests/testthat/helper-netrics.R @@ -1,10 +1,6 @@ options(manynet_verbosity = "quiet") options(snet_verbosity = "quiet") -collect_functions <- function(pattern, package = "netrics"){ - getNamespaceExports(package)[grepl(pattern, getNamespaceExports(package))] -} - expect_values <- function(object, ref, toler = 3) { # 1. Capture object and label # act <- quasi_label(rlang::enquo(object), arg = "object") @@ -67,6 +63,9 @@ bot5 <- function(res, dec = 4){ } else unname(res)[(lr-2):lr] } +collect_functions <- function(pattern, package = "netrics"){ + getNamespaceExports(package)[grepl(pattern, getNamespaceExports(package))] +} funs_objs <- mget(ls("package:netrics"), inherits = TRUE) # data_objs <- mget(ls("package:manynet"), inherits = TRUE) @@ -82,7 +81,9 @@ set.seed(1234) data_objs <- list(directed = generate_random(12, directed = TRUE), undirected = generate_random(12, directed = FALSE), twomode = generate_random(c(6,6)), - labelled = add_node_attribute(generate_random(12), "name", LETTERS[1:12]), + labelled = add_node_attribute(create_ring(12), "name", LETTERS[1:12]), + attribute = add_node_attribute(create_ring(12), "group", rep(c("A","B"), each = 6)), + weighted = add_tie_attribute(create_ring(12), "weight", rep(c(1,2), each = 6)), signed = to_signed(generate_random(12, directed = TRUE)), diffusion = play_diffusion(create_ring(12), seeds = 1, steps = 5, latency = 0.75, recovery = 0.25)) diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R index a5b7940..65658d6 100644 --- a/tests/testthat/test-measure_net.R +++ b/tests/testthat/test-measure_net.R @@ -2,9 +2,11 @@ net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] for(fn in names(net_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("congruency|correlation|degree|diversity|core|change|heterophily|infection|reach|immunity|recovery|reproduction|richclub|homophily|stability|spatial|balance|strength|waves|transmiss", fn)) + skip_if(grepl("congruency|correlation|degree|core|change|infection|reach|immunity|recovery|reproduction|richclub|stability|balance|strength|waves|transmiss|spatial", fn)) skip_if(grepl("net_by_factions", fn) && ob == "twomode") - if(fn == "x"){ + if(grepl("diversity|heterophily|homophily", fn)){ + if(ob == "attribute") + expect_s3_class(net_meas[[fn]](data_objs[[ob]], "group"), "network_measure") } else { expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") } From 91348d3d4242e0a466d52fe5954b935d94898256 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 18:19:53 +0100 Subject: [PATCH 58/98] Testing diversity and assortativity node measures --- tests/testthat/test-measure_nodes.R | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-measure_nodes.R b/tests/testthat/test-measure_nodes.R index 950a29d..a8e4e02 100644 --- a/tests/testthat/test-measure_nodes.R +++ b/tests/testthat/test-measure_nodes.R @@ -2,11 +2,13 @@ node_meas <- funs_objs[grepl("node_by_", names(funs_objs))] for(fn in names(node_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("vitality|threshold|richness|recovery|posneg|multideg|hub|homophily|hetero|exposure|diversity|distance|authority|equivalency", fn)) - if(grepl("adoption",fn)){ - if(ob == "diffusion"){ + skip_if(grepl("vitality|threshold|recovery|posneg|multideg|hub|exposure|distance|authority|equivalency", fn)) + if(grepl("diversity|richness|heterophily|homophily", fn)){ + if(ob == "attribute") + expect_s3_class(node_meas[[fn]](data_objs[[ob]], "group"), "node_measure") + }else if(grepl("adoption",fn)){ + if(ob == "diffusion") expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") - } } else if (ob %in% c("directed","undirected","weighted","twomode","signed","labelled")){ expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") } From fe4a3d0bb9f740826a33298a0035ede4c220eac7 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 18:20:16 +0100 Subject: [PATCH 59/98] Don't bother with community printing testing after 9am --- tests/testthat/test-member_community.R | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testthat/test-member_community.R b/tests/testthat/test-member_community.R index e1074d7..1f27e16 100644 --- a/tests/testthat/test-member_community.R +++ b/tests/testthat/test-member_community.R @@ -24,6 +24,7 @@ test_that("node_walktrap algorithm works", { }) test_that("node_in_community uses node_in_optimal on small networks", { + skip_if(format(Sys.time(), "%H") >= "09") options(manynet_verbosity = "verbose") options(snet_verbosity = "verbose") expect_message(node_in_community(manynet::create_ring(10)), "optimal") From 17020591f1bbd29ab47cb9efe944d18a42449990 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 18:20:55 +0100 Subject: [PATCH 60/98] Fixed equivalence k-assignment in the case that every node is in the same cluster --- R/member_equivalence.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/member_equivalence.R b/R/member_equivalence.R index f619d59..d357218 100644 --- a/R/member_equivalence.R +++ b/R/member_equivalence.R @@ -68,6 +68,7 @@ node_in_equivalence <- function(.data, census, strict = k_strict(hc, .data), elbow = k_elbow(hc, .data, census, range), silhouette = k_silhouette(hc, .data, range)) + if(length(k)==0) k <- 1 # in the case of all nodes being in the same cluster out <- make_node_member(stats::cutree(hc, k), .data) attr(out, "hc") <- hc @@ -108,12 +109,12 @@ node_in_regular <- function(.data, .data <- manynet::expect_nodes(.data) if(manynet::is_twomode(.data)){ manynet::snet_info("Since this is a two-mode network,", - "using {.fn node_by_tetrad} to", + "using {.fn node_x_tetrad} to", "profile nodes' embedding in local structures.") mat <- as.matrix(node_x_tetrad(.data)) } else { manynet::snet_info("Since this is a one-mode network,", - "using {.fn node_by_triad} to", + "using {.fn node_x_triad} to", "profile nodes' embedding in local structures.") mat <- node_x_triad(.data) } From cafb0d731b64d01b1a945c6336d5157658680ade Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 18:21:39 +0100 Subject: [PATCH 61/98] Specifying which network types node_membs should operate on --- tests/testthat/test-member_nodes.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-member_nodes.R b/tests/testthat/test-member_nodes.R index e1c8059..e17ca7a 100644 --- a/tests/testthat/test-member_nodes.R +++ b/tests/testthat/test-member_nodes.R @@ -4,7 +4,7 @@ for(fn in names(node_membs)) { test_that(paste(fn, "works on", ob), { skip_if(grepl("roulette|equivalence|adopter|brokering", fn)) if(fn == "x"){ - } else { + } else if (ob %in% c("directed","undirected","weighted","twomode","signed","labelled")){ expect_s3_class(node_membs[[fn]](data_objs[[ob]]), "node_member") } }) From b5860107f797a6a7ee50cdc707ea4b14d89d9aca Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 6 Mar 2026 20:31:46 +0100 Subject: [PATCH 62/98] Fixed early resolution of attribute into vector in node_by_homophily() --- R/measure_heterogeneity.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/measure_heterogeneity.R b/R/measure_heterogeneity.R index 2fcbb5f..3f3280c 100644 --- a/R/measure_heterogeneity.R +++ b/R/measure_heterogeneity.R @@ -405,9 +405,9 @@ attr_mode <- function(.data, attribute){ node_by_homophily <- function(.data, attribute, method = c("ie","ei","yule","geary")){ .data <- manynet::expect_nodes(.data) - if (length(attribute) == 1 && is.character(attribute)) { - attribute <- manynet::node_attribute(.data, attribute) - } + # if (length(attribute) == 1 && is.character(attribute)) { + # attribute <- manynet::node_attribute(.data, attribute) + # } method <- match.arg(method) if(is.numeric(attribute) && method %in% c("ie","ei","yule")){ manynet::snet_info("{.val {method}} index is not appropriate for numeric attributes.") From f9777b779ae3431809e9ff3131e712e59d058283 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 7 Mar 2026 21:17:18 +0100 Subject: [PATCH 63/98] Fixed net_by_richclub() to return 0 in the case of all equivalent degrees --- R/measure_features.R | 9 +++++---- tests/testthat/test-measure_net.R | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/R/measure_features.R b/R/measure_features.R index 5c50659..c520abe 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -106,8 +106,8 @@ net_by_richclub <- function(.data){ .data <- manynet::expect_nodes(.data) coefs <- vector() temp <- .data - for(k in seq_len(max(node_by_degree(temp, normalized = FALSE)))){ - richclub <- manynet::to_subgraph(temp, node_by_degree(temp, normalized = FALSE) >= k) + for(k in seq_len(max(node_by_deg(temp)))){ + richclub <- manynet::to_subgraph(temp, node_by_deg(temp) >= k) nk <- manynet::net_nodes(richclub) ek <- ifelse(manynet::is_directed(temp), manynet::net_ties(richclub), @@ -138,8 +138,9 @@ net_by_richclub <- function(.data){ } coefs[is.nan(coefs)] <- 1 - out <- coefs[.elbow_finder(seq_along(coefs), coefs)] - # max(coefs, na.rm = TRUE) + if(length(which(coefs == 1)) == 0) out <- 0 else + out <- coefs[.elbow_finder(seq_along(coefs), coefs)] + # max(coefs, na.rm = TRUE) make_network_measure(out, .data, call = deparse(sys.call())) } diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R index 65658d6..0490365 100644 --- a/tests/testthat/test-measure_net.R +++ b/tests/testthat/test-measure_net.R @@ -2,7 +2,7 @@ net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] for(fn in names(net_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("congruency|correlation|degree|core|change|infection|reach|immunity|recovery|reproduction|richclub|stability|balance|strength|waves|transmiss|spatial", fn)) + skip_if(grepl("congruency|correlation|degree|core|change|infection|reach|immunity|recovery|reproduction|stability|balance|strength|waves|transmiss|spatial", fn)) skip_if(grepl("net_by_factions", fn) && ob == "twomode") if(grepl("diversity|heterophily|homophily", fn)){ if(ob == "attribute") From 6b92823e6a4ff51a0da97293710829a6a2ac4e6e Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 7 Mar 2026 21:17:39 +0100 Subject: [PATCH 64/98] Corrected by_ names in documentation --- R/measure_features.R | 22 +++++++++++----------- man/measure_features.Rd | 14 +++++++------- man/measure_periods.Rd | 8 ++++---- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/R/measure_features.R b/R/measure_features.R index c520abe..5b8205a 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -4,22 +4,22 @@ #' @description #' These functions measure certain topological features of networks: #' -#' - `net_core()` measures the correlation between a network +#' - `net_by_core()` measures the correlation between a network #' and a core-periphery model with the same dimensions. -#' - `net_richclub()` measures the rich-club coefficient of a network. -#' - `net_factions()` measures the correlation between a network +#' - `net_by_richclub()` measures the rich-club coefficient of a network. +#' - `net_by_factions()` measures the correlation between a network #' and a component model with the same dimensions. #' If no 'membership' vector is given for the data, #' `node_partition()` is used to partition nodes into two groups. -#' - `net_modularity()` measures the modularity of a network +#' - `net_by_modularity()` measures the modularity of a network #' based on nodes' membership in defined clusters. -#' - `net_smallworld()` measures the small-world coefficient for one- or +#' - `net_by_smallworld()` measures the small-world coefficient for one- or #' two-mode networks. Small-world networks can be highly clustered and yet #' have short path lengths. -#' - `net_scalefree()` measures the exponent of a fitted +#' - `net_by_scalefree()` measures the exponent of a fitted #' power-law distribution. An exponent between 2 and 3 usually indicates #' a power-law distribution. -#' - `net_balance()` measures the structural balance index on +#' - `net_by_balance()` measures the structural balance index on #' the proportion of balanced triangles, #' ranging between `0` if all triangles are imbalanced and #' `1` if all triangles are balanced. @@ -439,10 +439,10 @@ net_by_balance <- function(.data) { #' @description #' These functions measure certain topological features of networks: #' -#' - `net_waves()` measures the number of waves in longitudinal network data. -#' - `net_change()` measures the Hamming distance between two or more networks. -#' - `net_stability()` measures the Jaccard index of stability between two or more networks. -#' - `net_correlation()` measures the product-moment correlation between two networks. +#' - `net_by_waves()` measures the number of waves in longitudinal network data. +#' - `net_by_change()` measures the Hamming distance between two or more networks. +#' - `net_by_stability()` measures the Jaccard index of stability between two or more networks. +#' - `net_by_correlation()` measures the product-moment correlation between two networks. #' #' These `net_*()` functions return a numeric vector the length of the number #' of networks minus one. E.g., the periods between waves. diff --git a/man/measure_features.Rd b/man/measure_features.Rd index 5f07e02..a1614c9 100644 --- a/man/measure_features.Rd +++ b/man/measure_features.Rd @@ -75,22 +75,22 @@ The lower this parameter, the fewer larger communities are likely to be found.} \description{ These functions measure certain topological features of networks: \itemize{ -\item \code{net_core()} measures the correlation between a network +\item \code{net_by_core()} measures the correlation between a network and a core-periphery model with the same dimensions. -\item \code{net_richclub()} measures the rich-club coefficient of a network. -\item \code{net_factions()} measures the correlation between a network +\item \code{net_by_richclub()} measures the rich-club coefficient of a network. +\item \code{net_by_factions()} measures the correlation between a network and a component model with the same dimensions. If no 'membership' vector is given for the data, \code{node_partition()} is used to partition nodes into two groups. -\item \code{net_modularity()} measures the modularity of a network +\item \code{net_by_modularity()} measures the modularity of a network based on nodes' membership in defined clusters. -\item \code{net_smallworld()} measures the small-world coefficient for one- or +\item \code{net_by_smallworld()} measures the small-world coefficient for one- or two-mode networks. Small-world networks can be highly clustered and yet have short path lengths. -\item \code{net_scalefree()} measures the exponent of a fitted +\item \code{net_by_scalefree()} measures the exponent of a fitted power-law distribution. An exponent between 2 and 3 usually indicates a power-law distribution. -\item \code{net_balance()} measures the structural balance index on +\item \code{net_by_balance()} measures the structural balance index on the proportion of balanced triangles, ranging between \code{0} if all triangles are imbalanced and \code{1} if all triangles are balanced. diff --git a/man/measure_periods.Rd b/man/measure_periods.Rd index 4f6af7d..4b1d299 100644 --- a/man/measure_periods.Rd +++ b/man/measure_periods.Rd @@ -26,10 +26,10 @@ see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} \description{ These functions measure certain topological features of networks: \itemize{ -\item \code{net_waves()} measures the number of waves in longitudinal network data. -\item \code{net_change()} measures the Hamming distance between two or more networks. -\item \code{net_stability()} measures the Jaccard index of stability between two or more networks. -\item \code{net_correlation()} measures the product-moment correlation between two networks. +\item \code{net_by_waves()} measures the number of waves in longitudinal network data. +\item \code{net_by_change()} measures the Hamming distance between two or more networks. +\item \code{net_by_stability()} measures the Jaccard index of stability between two or more networks. +\item \code{net_by_correlation()} measures the product-moment correlation between two networks. } These \verb{net_*()} functions return a numeric vector the length of the number From 99c6cdab563517199769a25eedf391d94da3081b Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 7 Mar 2026 22:22:33 +0100 Subject: [PATCH 65/98] Fixed net_by_degree() testing --- R/measure_centrality.R | 4 ++-- tests/testthat/test-measure_net.R | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index 12f5bf8..bcb2a6a 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -249,11 +249,11 @@ net_by_degree <- function(.data, normalized = TRUE, out$nodes1 <- sum(max(allcent[!mode]) - allcent)/((nrow(mat) + ncol(mat))*ncol(mat) - 2*(ncol(mat) + nrow(mat) - 1)) out$nodes2 <- sum(max(allcent[mode]) - allcent)/((nrow(mat) + ncol(mat))*nrow(mat) - 2*(ncol(mat) + nrow(mat) - 1)) } else if (normalized) { - allcent <- node_by_degree(mat, normalized = TRUE) + allcent <- node_by_degree(.data, normalized = TRUE) out$nodes1 <- sum(max(allcent[!mode]) - allcent)/((nrow(mat) + ncol(mat) - 1) - (ncol(mat) - 1) / nrow(mat) - (ncol(mat) + nrow(mat) - 1)/nrow(mat)) out$nodes2 <- sum(max(allcent[mode]) - allcent)/((ncol(mat) + nrow(mat) - 1) - (nrow(mat) - 1) / ncol(mat) - (nrow(mat) + ncol(mat) - 1)/ncol(mat)) } - } else if (direction == "in") { + } else if (direction == "in" | direction == "out") { out$nodes1 <- sum(max(rowSums(mat)) - rowSums(mat))/((ncol(mat) - 1)*(nrow(mat) - 1)) out$nodes2 <- sum(max(colSums(mat)) - colSums(mat))/((ncol(mat) - 1)*(nrow(mat) - 1)) } diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R index 0490365..e4c8a69 100644 --- a/tests/testthat/test-measure_net.R +++ b/tests/testthat/test-measure_net.R @@ -2,7 +2,7 @@ net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] for(fn in names(net_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("congruency|correlation|degree|core|change|infection|reach|immunity|recovery|reproduction|stability|balance|strength|waves|transmiss|spatial", fn)) + skip_if(grepl("congruency|correlation|core|change|infection|reach|immunity|recovery|reproduction|stability|balance|strength|waves|transmiss|spatial", fn)) skip_if(grepl("net_by_factions", fn) && ob == "twomode") if(grepl("diversity|heterophily|homophily", fn)){ if(ob == "attribute") From dddf66a48eb7e2622fed134c9ea7ece70d8c2795 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sun, 8 Mar 2026 09:43:42 +0100 Subject: [PATCH 66/98] Testing node_by_vitality() --- R/measure_centrality.R | 28 ++++++++++++++++------------ man/measure_central_close.Rd | 25 ++++++++++++++----------- tests/testthat/test-mark_nodes.R | 6 ------ tests/testthat/test-measure_nodes.R | 7 +++++-- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index bcb2a6a..08353e0 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -517,24 +517,27 @@ net_by_betweenness <- function(.data, normalized = TRUE, #' These functions calculate common closeness-related centrality measures #' that rely on path-length for one- and two-mode networks: #' -#' - `node_closeness()` measures the closeness centrality of nodes in a +#' - `node_by_closeness()` measures the closeness centrality of nodes in a #' network. -#' - `node_reach()` measures nodes' reach centrality, -#' or how many nodes they can reach within _k_ steps. -#' - `node_harmonic()` measures nodes' harmonic centrality or valued +#' - `node_by_harmonic()` measures nodes' harmonic centrality or valued #' centrality, which is thought to behave better than reach centrality #' for disconnected networks. -#' - `node_information()` measures nodes' information centrality or +#' - `node_by_reach()` measures nodes' reach centrality, +#' or how many nodes they can reach within _k_ steps. +#' - `node_by_information()` measures nodes' information centrality or #' current-flow closeness centrality. -#' - `node_eccentricity()` measures nodes' eccentricity or maximum distance +#' - `node_by_eccentricity()` measures nodes' eccentricity or maximum distance #' from another node in the network. -#' - `node_distance()` measures nodes' geodesic distance from or to a +#' - `node_by_distance()` measures nodes' geodesic distance from or to a +#' given node. +#' - `node_by_vitality()` measures a network's closeness vitality centrality, +#' or the change in closeness centrality between networks with and without a #' given node. -#' - `tie_closeness()` measures the closeness of each tie to other ties +#' - `tie_by_closeness()` measures the closeness of each tie to other ties #' in the network. -#' - `net_closeness()` measures a network's closeness centralization. -#' - `net_reach()` measures a network's reach centralization. -#' - `net_harmonic()` measures a network's harmonic centralization. +#' - `net_by_closeness()` measures a network's closeness centralization. +#' - `net_by_reach()` measures a network's reach centralization. +#' - `net_by_harmonic()` measures a network's harmonic centralization. #' #' All measures attempt to use as much information as they are offered, #' including whether the networks are directed, weighted, or multimodal. @@ -773,7 +776,8 @@ node_by_vitality <- function(.data, normalized = TRUE){ .data <- manynet::expect_nodes(.data) .data <- manynet::as_igraph(.data) out <- vapply(manynet::snet_progress_nodes(.data), function(x){ - sum(igraph::distances(.data)) - sum(igraph::distances(manynet::delete_nodes(.data, x))) + sum(igraph::distances(.data)) - + sum(igraph::distances(manynet::delete_nodes(.data, x))) }, FUN.VALUE = numeric(1)) if(normalized) out <- out/max(out) make_node_measure(out, .data) diff --git a/man/measure_central_close.Rd b/man/measure_central_close.Rd index 15ce16a..a38e1dd 100644 --- a/man/measure_central_close.Rd +++ b/man/measure_central_close.Rd @@ -66,24 +66,27 @@ against only the centrality scores of the other nodes in that mode.} These functions calculate common closeness-related centrality measures that rely on path-length for one- and two-mode networks: \itemize{ -\item \code{node_closeness()} measures the closeness centrality of nodes in a +\item \code{node_by_closeness()} measures the closeness centrality of nodes in a network. -\item \code{node_reach()} measures nodes' reach centrality, -or how many nodes they can reach within \emph{k} steps. -\item \code{node_harmonic()} measures nodes' harmonic centrality or valued +\item \code{node_by_harmonic()} measures nodes' harmonic centrality or valued centrality, which is thought to behave better than reach centrality for disconnected networks. -\item \code{node_information()} measures nodes' information centrality or +\item \code{node_by_reach()} measures nodes' reach centrality, +or how many nodes they can reach within \emph{k} steps. +\item \code{node_by_information()} measures nodes' information centrality or current-flow closeness centrality. -\item \code{node_eccentricity()} measures nodes' eccentricity or maximum distance +\item \code{node_by_eccentricity()} measures nodes' eccentricity or maximum distance from another node in the network. -\item \code{node_distance()} measures nodes' geodesic distance from or to a +\item \code{node_by_distance()} measures nodes' geodesic distance from or to a +given node. +\item \code{node_by_vitality()} measures a network's closeness vitality centrality, +or the change in closeness centrality between networks with and without a given node. -\item \code{tie_closeness()} measures the closeness of each tie to other ties +\item \code{tie_by_closeness()} measures the closeness of each tie to other ties in the network. -\item \code{net_closeness()} measures a network's closeness centralization. -\item \code{net_reach()} measures a network's reach centralization. -\item \code{net_harmonic()} measures a network's harmonic centralization. +\item \code{net_by_closeness()} measures a network's closeness centralization. +\item \code{net_by_reach()} measures a network's reach centralization. +\item \code{net_by_harmonic()} measures a network's harmonic centralization. } All measures attempt to use as much information as they are offered, diff --git a/tests/testthat/test-mark_nodes.R b/tests/testthat/test-mark_nodes.R index f497c1f..02d66c2 100644 --- a/tests/testthat/test-mark_nodes.R +++ b/tests/testthat/test-mark_nodes.R @@ -18,17 +18,12 @@ for(fn in names(node_marks)) { } test_that("node_is_cutpoint", { - expect_true(exists("node_is_cutpoint")) - test_that("returns correct type", { - expect_s3_class(node_is_cutpoint(ison_algebra), "node_mark") - }) expect_length(node_is_cutpoint(ison_southern_women), c(net_nodes(ison_southern_women))) }) test_that("node_is_isolate", { f <- node_is_isolate - expect_true(is.function(f)) test <- f(ison_brandes) test_that("returns correct values", { expect_equal(length(test), c(net_nodes(ison_brandes))) @@ -41,7 +36,6 @@ test_that("node_is_isolate", { test_that("node_is_fold works", { test <- node_is_fold(create_explicit(A-B, B-C, A-C, C-D, C-E, D-E)) expect_equal(as.logical(test), c(F,F,T,F,F)) - expect_s3_class(test, "node_mark") }) test_that("node_is_neighbor works", { diff --git a/tests/testthat/test-measure_nodes.R b/tests/testthat/test-measure_nodes.R index a8e4e02..356c144 100644 --- a/tests/testthat/test-measure_nodes.R +++ b/tests/testthat/test-measure_nodes.R @@ -2,13 +2,16 @@ node_meas <- funs_objs[grepl("node_by_", names(funs_objs))] for(fn in names(node_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("vitality|threshold|recovery|posneg|multideg|hub|exposure|distance|authority|equivalency", fn)) + skip_if(grepl("threshold|recovery|multideg|exposure|distance|equivalency", fn)) if(grepl("diversity|richness|heterophily|homophily", fn)){ if(ob == "attribute") expect_s3_class(node_meas[[fn]](data_objs[[ob]], "group"), "node_measure") - }else if(grepl("adoption",fn)){ + } else if(grepl("adoption",fn)){ if(ob == "diffusion") expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") + } else if(grepl("posneg",fn)){ + if(ob == "signed") + expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") } else if (ob %in% c("directed","undirected","weighted","twomode","signed","labelled")){ expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") } From f934ccee84fbc8959e6d476fec7aba14fa9d06de Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sun, 8 Mar 2026 09:44:24 +0100 Subject: [PATCH 67/98] Fixed node_by_authority() and node_by_hub() to use updated igraph functions --- R/measure_centrality.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index 08353e0..aa0c6e9 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -1169,7 +1169,7 @@ node_by_pagerank <- function(.data){ #' @export node_by_authority <- function(.data){ .data <- manynet::expect_nodes(.data) - make_node_measure(igraph::authority_score(manynet::as_igraph(.data))$vector, + make_node_measure(igraph::hits_scores(manynet::as_igraph(.data))$authority, .data) } @@ -1177,7 +1177,7 @@ node_by_authority <- function(.data){ #' @export node_by_hub <- function(.data){ .data <- manynet::expect_nodes(.data) - make_node_measure(igraph::hub_score(manynet::as_igraph(.data))$vector, + make_node_measure(igraph::hits_scores(manynet::as_igraph(.data))$hub, .data) } From a908f6fc8717d3fb4730ff80b025b0c4b5354a03 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Mon, 9 Mar 2026 09:41:14 +0100 Subject: [PATCH 68/98] Testing distance and equivalency --- tests/testthat/test-measure_nodes.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-measure_nodes.R b/tests/testthat/test-measure_nodes.R index 356c144..0e5eddf 100644 --- a/tests/testthat/test-measure_nodes.R +++ b/tests/testthat/test-measure_nodes.R @@ -2,7 +2,8 @@ node_meas <- funs_objs[grepl("node_by_", names(funs_objs))] for(fn in names(node_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("threshold|recovery|multideg|exposure|distance|equivalency", fn)) + skip_if(grepl("threshold|recovery|multideg|exposure", fn)) + skip_if(grepl("equivalency", fn) && ob == "labelled") if(grepl("diversity|richness|heterophily|homophily", fn)){ if(ob == "attribute") expect_s3_class(node_meas[[fn]](data_objs[[ob]], "group"), "node_measure") @@ -12,6 +13,8 @@ for(fn in names(node_meas)) { } else if(grepl("posneg",fn)){ if(ob == "signed") expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") + } else if(grepl("distance",fn)){ + expect_s3_class(node_meas[[fn]](data_objs[[ob]], 1, 2), "node_measure") } else if (ob %in% c("directed","undirected","weighted","twomode","signed","labelled")){ expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") } From 5e1f1081a1681393b9945544c74c73dc5720a805 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Mon, 9 Mar 2026 16:51:22 +0100 Subject: [PATCH 69/98] Testing reach --- tests/testthat/test-mark_nodes.R | 2 +- tests/testthat/test-measure_net.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-mark_nodes.R b/tests/testthat/test-mark_nodes.R index 02d66c2..ebe30e2 100644 --- a/tests/testthat/test-mark_nodes.R +++ b/tests/testthat/test-mark_nodes.R @@ -8,7 +8,7 @@ for(fn in names(node_marks)) { if(fn == "node_is_exposed"){ expect_s3_class(node_marks[[fn]](data_objs[[ob]], mark = c(1,3)), "node_mark") } else if(fn == "node_is_core"){ - skip_if(manynet::is_directed(data_objs[[ob]])) + skip_if(fn == "node_is_core" && manynet::is_directed(data_objs[[ob]])) expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") } else { expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R index e4c8a69..158d625 100644 --- a/tests/testthat/test-measure_net.R +++ b/tests/testthat/test-measure_net.R @@ -2,7 +2,7 @@ net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] for(fn in names(net_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("congruency|correlation|core|change|infection|reach|immunity|recovery|reproduction|stability|balance|strength|waves|transmiss|spatial", fn)) + skip_if(grepl("congruency|correlation|core|change|infection|immunity|recovery|reproduction|stability|balance|strength|waves|transmiss|spatial", fn)) skip_if(grepl("net_by_factions", fn) && ob == "twomode") if(grepl("diversity|heterophily|homophily", fn)){ if(ob == "attribute") From f52aa390c76ec0914700d21ff37000898edad95f Mon Sep 17 00:00:00 2001 From: James Hollway Date: Tue, 10 Mar 2026 18:21:26 +0100 Subject: [PATCH 70/98] Fixed dyad census to work with two-mode networks --- R/motif_census.R | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/R/motif_census.R b/R/motif_census.R index 12f017f..ac7a609 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -305,6 +305,17 @@ node_x_path <- function(.data){ NULL #' @rdname motif_net +#' @section Dyad census: +#' The dyad census counts the number of mutual, asymmetric, and null dyads +#' in a network. +#' For directed networks, +#' - Mutual dyads have ties in both directions +#' - Asymmetric dyads have a tie in one direction only +#' - Null dyads have no ties +#' +#' Note that for undirected and two-mode networks, +#' only mutual and null dyads are possible, +#' as the concept of an asymmetric dyad does not apply. #' @references #' ## On the dyad census #' Holland, Paul W., and Samuel Leinhardt. 1970. @@ -320,15 +331,11 @@ NULL #' @export net_x_dyad <- function(.data) { .data <- manynet::expect_nodes(.data) - if (manynet::is_twomode(.data)) { - manynet::snet_unavailable("A twomode or multilevel option for a dyad census is not yet implemented.") - } else { - out <- suppressWarnings(igraph::dyad_census(manynet::as_igraph(.data))) - out <- unlist(out) - names(out) <- c("Mutual", "Asymmetric", "Null") - if (!manynet::is_directed(.data)) out <- out[c(1, 3)] - make_network_motif(out, .data) - } + out <- suppressWarnings(igraph::dyad_census(manynet::as_igraph(.data))) + out <- unlist(out) + names(out) <- c("Mutual", "Asymmetric", "Null") + if (!manynet::is_directed(.data)) out <- out[c(1, 3)] + make_network_motif(out, .data) } #' @rdname motif_net From 0240481cd6d7ce7c65e1720ea3f866d8d92bad1c Mon Sep 17 00:00:00 2001 From: James Hollway Date: Tue, 10 Mar 2026 18:21:47 +0100 Subject: [PATCH 71/98] Added details about the dyad and triad motifs --- R/motif_census.R | 34 ++++++++++++++++++++- man/motif_net.Rd | 53 +++++++++++++++++++++++++++++++++ tests/testthat/test-motif_net.R | 2 +- 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/R/motif_census.R b/R/motif_census.R index ac7a609..299541b 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -338,7 +338,39 @@ net_x_dyad <- function(.data) { make_network_motif(out, .data) } -#' @rdname motif_net +#' @rdname motif_net +#' @section Triad census: +#' The triad census counts the number of three-node configurations in the network. +#' The function returns a matrix with a special naming convention: +#' - 003: This is an empty triad; no ties +#' - 012: This triad includes one tie +#' - 102: This triad includes two ties, but they are not reciproc +#' - 021D: This triad includes two ties, one of which is reciprocated, and the other is directed towards the reciprocated tie +#' - 021U: This triad includes two ties, one of which is +#' reciprocated, and the other is directed away from the reciprocated tie +#' - 021C: This triad includes two ties, one of which is reciprocated, and the other is directed between the two non-reciprocated nodes +#' - 111D: This triad includes three ties, two of which are +#' reciprocated, and the other is directed towards the reciprocated ties +#' - 111U: This triad includes three ties, two of which are +#' reciprocated, and the other is directed away from the reciprocated ties +#' - 030T: This triad includes three ties, all of which are +#' directed in a transitive manner (i.e. A->B, B->C, A->C) +#' - 030C: This triad includes three ties, all of which are +#' directed in a cyclic manner (i.e. A->B, B->C +#' A->C) +#' - 201: This triad includes three ties, all of which are reciproc +#' ated (i.e. A<->B, B<->C, A<->C) +#' - 120D: This triad includes four ties, three of which are +#' reciprocated, and the other is directed towards the reciprocated ties +#' - 120U: This triad includes four ties, three of which are +#' reciprocated, and the other is directed away from the reciprocated ties +#' - 120C: This triad includes four ties, three of which are +#' reciprocated, and the other is directed between the two non-reciprocated +#' - 210: This triad includes five ties, four of which are reciprocated, and the other is directed between the two non-reciprocated +#' - 300: This triad includes six ties, all of which are reciprocated +#' +#' Note that for undirected and two-mode networks, only 003, 102, and 201 are possible, +#' as the other configurations rely on the concept of directionality. #' @references #' ## On the triad census #' Davis, James A., and Samuel Leinhardt. 1967. diff --git a/man/motif_net.Rd b/man/motif_net.Rd index 6c6fc3f..0f47366 100644 --- a/man/motif_net.Rd +++ b/man/motif_net.Rd @@ -39,6 +39,59 @@ a one-mode and a two-mode network. See also \href{https://www.graphclasses.org/smallgraphs.html}{graph classes}. } +\section{Dyad census}{ + +The dyad census counts the number of mutual, asymmetric, and null dyads +in a network. +For directed networks, +\itemize{ +\item Mutual dyads have ties in both directions +\item Asymmetric dyads have a tie in one direction only +\item Null dyads have no ties +} + +Note that for undirected and two-mode networks, +only mutual and null dyads are possible, +as the concept of an asymmetric dyad does not apply. +} + +\section{Triad census}{ + +The triad census counts the number of three-node configurations in the network. +The function returns a matrix with a special naming convention: +\itemize{ +\item 003: This is an empty triad; no ties +\item 012: This triad includes one tie +\item 102: This triad includes two ties, but they are not reciproc +\item 021D: This triad includes two ties, one of which is reciprocated, and the other is directed towards the reciprocated tie +\item 021U: This triad includes two ties, one of which is +reciprocated, and the other is directed away from the reciprocated tie +\item 021C: This triad includes two ties, one of which is reciprocated, and the other is directed between the two non-reciprocated nodes +\item 111D: This triad includes three ties, two of which are +reciprocated, and the other is directed towards the reciprocated ties +\item 111U: This triad includes three ties, two of which are +reciprocated, and the other is directed away from the reciprocated ties +\item 030T: This triad includes three ties, all of which are +directed in a transitive manner (i.e. A->B, B->C, A->C) +\item 030C: This triad includes three ties, all of which are +directed in a cyclic manner (i.e. A->B, B->C +A->C) +\item 201: This triad includes three ties, all of which are reciproc +ated (i.e. A<->B, B<->C, A<->C) +\item 120D: This triad includes four ties, three of which are +reciprocated, and the other is directed towards the reciprocated ties +\item 120U: This triad includes four ties, three of which are +reciprocated, and the other is directed away from the reciprocated ties +\item 120C: This triad includes four ties, three of which are +reciprocated, and the other is directed between the two non-reciprocated +\item 210: This triad includes five ties, four of which are reciprocated, and the other is directed between the two non-reciprocated +\item 300: This triad includes six ties, all of which are reciprocated +} + +Note that for undirected and two-mode networks, only 003, 102, and 201 are possible, +as the other configurations rely on the concept of directionality. +} + \section{Tetrad census}{ The tetrad census counts the number of four-node configurations in the network. diff --git a/tests/testthat/test-motif_net.R b/tests/testthat/test-motif_net.R index b761b61..3eaedd1 100644 --- a/tests/testthat/test-motif_net.R +++ b/tests/testthat/test-motif_net.R @@ -3,7 +3,7 @@ for(fn in names(net_motifs)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { skip_if(grepl("exposure|brokerage|mixed|hazard", fn)) - skip_if(grepl("triad|dyad", fn) && is_twomode(data_objs[[ob]])) + skip_if(grepl("triad", fn) && is_twomode(data_objs[[ob]])) if(fn == "x"){ } else { expect_s3_class(net_motifs[[fn]](data_objs[[ob]]), "network_motif") From 50acf9a3f3fbd987a45324c843d02622c88b26c6 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 10:28:23 +0100 Subject: [PATCH 72/98] Testing roulette and neighbor --- R/motif_census.R | 2 +- tests/testthat/test-mark_nodes.R | 8 ++++++-- tests/testthat/test-member_nodes.R | 7 +++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/R/motif_census.R b/R/motif_census.R index 299541b..9c1442e 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -344,7 +344,7 @@ net_x_dyad <- function(.data) { #' The function returns a matrix with a special naming convention: #' - 003: This is an empty triad; no ties #' - 012: This triad includes one tie -#' - 102: This triad includes two ties, but they are not reciproc +#' - 102: This triad includes two ties, but they are not reciprocated #' - 021D: This triad includes two ties, one of which is reciprocated, and the other is directed towards the reciprocated tie #' - 021U: This triad includes two ties, one of which is #' reciprocated, and the other is directed away from the reciprocated tie diff --git a/tests/testthat/test-mark_nodes.R b/tests/testthat/test-mark_nodes.R index ebe30e2..e5428c6 100644 --- a/tests/testthat/test-mark_nodes.R +++ b/tests/testthat/test-mark_nodes.R @@ -4,12 +4,16 @@ node_marks <- funs_objs[grepl("node_is_", names(funs_objs))] for(fn in names(node_marks)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("node_is_recovered|neighbor|min|max|mean|latent|infected", fn)) + skip_if(grepl("node_is_recovered|min|max|mean|latent|infected", fn)) if(fn == "node_is_exposed"){ - expect_s3_class(node_marks[[fn]](data_objs[[ob]], mark = c(1,3)), "node_mark") + expect_s3_class(node_marks[[fn]](data_objs[[ob]], mark = c(1,3)), + "node_mark") } else if(fn == "node_is_core"){ skip_if(fn == "node_is_core" && manynet::is_directed(data_objs[[ob]])) expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") + } else if(fn == "node_is_neighbor"){ + expect_s3_class(node_marks[[fn]](data_objs[[ob]], node = 1), + "node_mark") } else { expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") } diff --git a/tests/testthat/test-member_nodes.R b/tests/testthat/test-member_nodes.R index e17ca7a..8414c6c 100644 --- a/tests/testthat/test-member_nodes.R +++ b/tests/testthat/test-member_nodes.R @@ -2,8 +2,11 @@ node_membs <- funs_objs[grepl("node_in_", names(funs_objs))] for(fn in names(node_membs)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("roulette|equivalence|adopter|brokering", fn)) - if(fn == "x"){ + skip_if(grepl("equivalence|adopter|brokering", fn)) + if(grepl("roulette", fn)){ + if(ob != "twomode") + expect_s3_class(node_membs[[fn]](data_objs[[ob]], num_groups = 3), + "node_member") } else if (ob %in% c("directed","undirected","weighted","twomode","signed","labelled")){ expect_s3_class(node_membs[[fn]](data_objs[[ob]]), "node_member") } From e78ef20025dadda16e8770bc46e63630abfcdbe7 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 11:14:30 +0100 Subject: [PATCH 73/98] Fixed net_by_waves() to return 1 for cross-sectional networks and return a network measure --- R/measure_features.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/measure_features.R b/R/measure_features.R index 5b8205a..d95e3b4 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -459,8 +459,9 @@ net_by_waves <- function(.data){ if(manynet::is_changing(.data)){ chltime <- manynet::as_changelist(.data)$time chg_waves <- (max(chltime)+1) - max(min(chltime)-1, 0) - } else chg_waves <- 0 - max(tie_waves, chg_waves) + } else chg_waves <- 1 + make_network_measure(max(tie_waves, chg_waves), + .data, call = deparse(sys.call())) } #' @rdname measure_periods From c2b8677ecbb847048d8774aea53708a11888c957 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 11:15:15 +0100 Subject: [PATCH 74/98] Reduce number of different types of network data objects tested --- man/motif_net.Rd | 2 +- tests/testthat/helper-netrics.R | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/man/motif_net.Rd b/man/motif_net.Rd index 0f47366..b5f17f1 100644 --- a/man/motif_net.Rd +++ b/man/motif_net.Rd @@ -62,7 +62,7 @@ The function returns a matrix with a special naming convention: \itemize{ \item 003: This is an empty triad; no ties \item 012: This triad includes one tie -\item 102: This triad includes two ties, but they are not reciproc +\item 102: This triad includes two ties, but they are not reciprocated \item 021D: This triad includes two ties, one of which is reciprocated, and the other is directed towards the reciprocated tie \item 021U: This triad includes two ties, one of which is reciprocated, and the other is directed away from the reciprocated tie diff --git a/tests/testthat/helper-netrics.R b/tests/testthat/helper-netrics.R index 9a05c8e..cc9a3c5 100644 --- a/tests/testthat/helper-netrics.R +++ b/tests/testthat/helper-netrics.R @@ -79,11 +79,14 @@ funs_objs <- mget(ls("package:netrics"), inherits = TRUE) set.seed(1234) data_objs <- list(directed = generate_random(12, directed = TRUE), - undirected = generate_random(12, directed = FALSE), twomode = generate_random(c(6,6)), - labelled = add_node_attribute(create_ring(12), "name", LETTERS[1:12]), - attribute = add_node_attribute(create_ring(12), "group", rep(c("A","B"), each = 6)), - weighted = add_tie_attribute(create_ring(12), "weight", rep(c(1,2), each = 6)), - signed = to_signed(generate_random(12, directed = TRUE)), - diffusion = play_diffusion(create_ring(12), seeds = 1, steps = 5, latency = 0.75, recovery = 0.25)) + labelled = to_signed(add_node_attribute(create_wheel(12), "name", + LETTERS[1:12])), + attribute = add_node_attribute(create_ring(12), "group", + rep(c("A","B"), each = 6)), + weighted = add_tie_attribute(create_ring(12), "weight", + rep(c(1,2), each = 6)), + diffusion = play_diffusion(create_ring(12), seeds = 1, + steps = 5, latency = 0.75, + recovery = 0.25)) From ea6379be1c8ef0649dce41284330644e7725870c Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 11:15:43 +0100 Subject: [PATCH 75/98] Testing balance and waves --- tests/testthat/test-measure_net.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R index 158d625..79509fa 100644 --- a/tests/testthat/test-measure_net.R +++ b/tests/testthat/test-measure_net.R @@ -2,11 +2,14 @@ net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] for(fn in names(net_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("congruency|correlation|core|change|infection|immunity|recovery|reproduction|stability|balance|strength|waves|transmiss|spatial", fn)) + skip_if(grepl("congruency|correlation|core|infection|immunity|recovery|reproduction|change|stability|strength|transmiss|spatial", fn)) skip_if(grepl("net_by_factions", fn) && ob == "twomode") if(grepl("diversity|heterophily|homophily", fn)){ if(ob == "attribute") expect_s3_class(net_meas[[fn]](data_objs[[ob]], "group"), "network_measure") + } else if(grepl("balance", fn)){ + if(ob == "labelled") + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") } else { expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") } From 1dd54eb3cdde2d085912e7e936b466dd79c758ab Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 13:02:55 +0100 Subject: [PATCH 76/98] Testing net_by_correlation, _change, and _stability() --- R/measure_features.R | 46 +++++++++++++++++-------------- tests/testthat/test-measure_net.R | 8 +++++- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/R/measure_features.R b/R/measure_features.R index d95e3b4..7fb9a1c 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -468,38 +468,44 @@ net_by_waves <- function(.data){ #' @param object2 A network object. #' @export net_by_change <- function(.data, object2){ - .data <- manynet::expect_nodes(.data) - if(manynet::is_list(.data)){ - - } else if(!missing(object2)){ - .data <- list(.data, object2) - } else manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") - periods <- length(.data)-1 - vapply(seq.int(periods), function(x){ - net1 <- manynet::as_matrix(.data[[x]]) - net2 <- manynet::as_matrix(.data[[x+1]]) + net <- manynet::expect_nodes(.data) + if(!missing(object2)){ + net <- list(net, object2) + } else if(manynet::is_longitudinal(net)){ + net <- manynet::to_waves(net) + } + if(!manynet::is_list(net)) + manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") + periods <- length(net)-1 + out <- vapply(seq.int(periods), function(x){ + net1 <- manynet::as_matrix(net[[x]]) + net2 <- manynet::as_matrix(net[[x+1]]) sum(net1 != net2) }, FUN.VALUE = numeric(1)) + make_network_measure(out, .data, call = deparse(sys.call())) } #' @rdname measure_periods #' @export net_by_stability <- function(.data, object2){ - .data <- manynet::expect_nodes(.data) - if(manynet::is_list(.data)){ - - } else if(!missing(object2)){ - .data <- list(.data, object2) - } else manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") - periods <- length(.data)-1 - vapply(seq.int(periods), function(x){ - net1 <- manynet::as_matrix(.data[[x]]) - net2 <- manynet::as_matrix(.data[[x+1]]) + net <- manynet::expect_nodes(.data) + if(!missing(object2)){ + net <- list(net, object2) + } else if(manynet::is_longitudinal(net)){ + net <- manynet::to_waves(net) + } + if(!manynet::is_list(net)) + manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") + periods <- length(net)-1 + out <- vapply(seq.int(periods), function(x){ + net1 <- manynet::as_matrix(net[[x]]) + net2 <- manynet::as_matrix(net[[x+1]]) n11 <- sum(net1 * net2) n01 <- sum(net1==0 * net2) n10 <- sum(net1 * net2==0) n11 / (n01 + n10 + n11) }, FUN.VALUE = numeric(1)) + make_network_measure(out, .data, call = deparse(sys.call())) } #' @rdname measure_periods diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R index 79509fa..989303a 100644 --- a/tests/testthat/test-measure_net.R +++ b/tests/testthat/test-measure_net.R @@ -2,7 +2,7 @@ net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] for(fn in names(net_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("congruency|correlation|core|infection|immunity|recovery|reproduction|change|stability|strength|transmiss|spatial", fn)) + skip_if(grepl("core|infection|immunity|recovery|reproduction|strength|transmiss|spatial", fn)) skip_if(grepl("net_by_factions", fn) && ob == "twomode") if(grepl("diversity|heterophily|homophily", fn)){ if(ob == "attribute") @@ -10,6 +10,12 @@ for(fn in names(net_meas)) { } else if(grepl("balance", fn)){ if(ob == "labelled") expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") + } else if(grepl("congruency", fn)){ + if(ob == "twomode") + expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") + } else if(grepl("correlation|change|stability", fn)){ + if(ob == "labelled") + expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") } else { expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") } From d250aca09166ac450f9dc76d17df60046d2816de Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 13:29:22 +0100 Subject: [PATCH 77/98] Testing tie measures and fixed tie_by_cohesion() to return tie_measure class --- R/measure_change.R | 90 +++++++++++++++++++++++++++++ R/measure_features.R | 91 ------------------------------ R/measure_holes.R | 2 +- tests/testthat/test-measure_ties.R | 7 +-- 4 files changed, 93 insertions(+), 97 deletions(-) create mode 100644 R/measure_change.R diff --git a/R/measure_change.R b/R/measure_change.R new file mode 100644 index 0000000..a8471ee --- /dev/null +++ b/R/measure_change.R @@ -0,0 +1,90 @@ +# Change #### + +#' Measures of network change +#' @description +#' These functions measure certain topological features of networks: +#' +#' - `net_by_waves()` measures the number of waves in longitudinal network data. +#' - `net_by_change()` measures the Hamming distance between two or more networks. +#' - `net_by_stability()` measures the Jaccard index of stability between two or more networks. +#' - `net_by_correlation()` measures the product-moment correlation between two networks. +#' +#' These `net_*()` functions return a numeric vector the length of the number +#' of networks minus one. E.g., the periods between waves. +#' @inheritParams mark_nodes +#' @name measure_periods +#' @family measures +NULL + +#' @rdname measure_periods +#' @export +net_by_waves <- function(.data){ + .data <- manynet::expect_nodes(.data) + tie_waves <- length(unique(manynet::tie_attribute(.data, "wave"))) + if(manynet::is_changing(.data)){ + chltime <- manynet::as_changelist(.data)$time + chg_waves <- (max(chltime)+1) - max(min(chltime)-1, 0) + } else chg_waves <- 1 + make_network_measure(max(tie_waves, chg_waves), + .data, call = deparse(sys.call())) +} + +#' @rdname measure_periods +#' @param object2 A network object. +#' @export +net_by_change <- function(.data, object2){ + net <- manynet::expect_nodes(.data) + if(!missing(object2)){ + net <- list(net, object2) + } else if(manynet::is_longitudinal(net)){ + net <- manynet::to_waves(net) + } + if(!manynet::is_list(net)) + manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") + periods <- length(net)-1 + out <- vapply(seq.int(periods), function(x){ + net1 <- manynet::as_matrix(net[[x]]) + net2 <- manynet::as_matrix(net[[x+1]]) + sum(net1 != net2) + }, FUN.VALUE = numeric(1)) + make_network_measure(out, .data, call = deparse(sys.call())) +} + +#' @rdname measure_periods +#' @export +net_by_stability <- function(.data, object2){ + net <- manynet::expect_nodes(.data) + if(!missing(object2)){ + net <- list(net, object2) + } else if(manynet::is_longitudinal(net)){ + net <- manynet::to_waves(net) + } + if(!manynet::is_list(net)) + manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") + periods <- length(net)-1 + out <- vapply(seq.int(periods), function(x){ + net1 <- manynet::as_matrix(net[[x]]) + net2 <- manynet::as_matrix(net[[x+1]]) + n11 <- sum(net1 * net2) + n01 <- sum(net1==0 * net2) + n10 <- sum(net1 * net2==0) + n11 / (n01 + n10 + n11) + }, FUN.VALUE = numeric(1)) + make_network_measure(out, .data, call = deparse(sys.call())) +} + +#' @rdname measure_periods +#' @export +net_by_correlation <- function(.data, object2){ + .data <- manynet::expect_nodes(.data) + comp1 <- manynet::as_matrix(.data) + comp2 <- manynet::as_matrix(object2) + if(!manynet::is_complex(.data)){ + diag(comp1) <- NA + } + if(!manynet::is_directed(.data)){ + comp1[upper.tri(comp1)] <- NA + } + out <- cor(c(comp1), c(comp2), use = "complete.obs") + make_network_measure(out, .data, call = deparse(sys.call())) +} \ No newline at end of file diff --git a/R/measure_features.R b/R/measure_features.R index 7fb9a1c..8cf3c1a 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -432,94 +432,3 @@ net_by_balance <- function(.data) { call = deparse(sys.call())) } - -# Change #### - -#' Measures of network change -#' @description -#' These functions measure certain topological features of networks: -#' -#' - `net_by_waves()` measures the number of waves in longitudinal network data. -#' - `net_by_change()` measures the Hamming distance between two or more networks. -#' - `net_by_stability()` measures the Jaccard index of stability between two or more networks. -#' - `net_by_correlation()` measures the product-moment correlation between two networks. -#' -#' These `net_*()` functions return a numeric vector the length of the number -#' of networks minus one. E.g., the periods between waves. -#' @inheritParams mark_nodes -#' @name measure_periods -#' @family measures -NULL - -#' @rdname measure_periods -#' @export -net_by_waves <- function(.data){ - .data <- manynet::expect_nodes(.data) - tie_waves <- length(unique(manynet::tie_attribute(.data, "wave"))) - if(manynet::is_changing(.data)){ - chltime <- manynet::as_changelist(.data)$time - chg_waves <- (max(chltime)+1) - max(min(chltime)-1, 0) - } else chg_waves <- 1 - make_network_measure(max(tie_waves, chg_waves), - .data, call = deparse(sys.call())) -} - -#' @rdname measure_periods -#' @param object2 A network object. -#' @export -net_by_change <- function(.data, object2){ - net <- manynet::expect_nodes(.data) - if(!missing(object2)){ - net <- list(net, object2) - } else if(manynet::is_longitudinal(net)){ - net <- manynet::to_waves(net) - } - if(!manynet::is_list(net)) - manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") - periods <- length(net)-1 - out <- vapply(seq.int(periods), function(x){ - net1 <- manynet::as_matrix(net[[x]]) - net2 <- manynet::as_matrix(net[[x+1]]) - sum(net1 != net2) - }, FUN.VALUE = numeric(1)) - make_network_measure(out, .data, call = deparse(sys.call())) -} - -#' @rdname measure_periods -#' @export -net_by_stability <- function(.data, object2){ - net <- manynet::expect_nodes(.data) - if(!missing(object2)){ - net <- list(net, object2) - } else if(manynet::is_longitudinal(net)){ - net <- manynet::to_waves(net) - } - if(!manynet::is_list(net)) - manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") - periods <- length(net)-1 - out <- vapply(seq.int(periods), function(x){ - net1 <- manynet::as_matrix(net[[x]]) - net2 <- manynet::as_matrix(net[[x+1]]) - n11 <- sum(net1 * net2) - n01 <- sum(net1==0 * net2) - n10 <- sum(net1 * net2==0) - n11 / (n01 + n10 + n11) - }, FUN.VALUE = numeric(1)) - make_network_measure(out, .data, call = deparse(sys.call())) -} - -#' @rdname measure_periods -#' @export -net_by_correlation <- function(.data, object2){ - .data <- manynet::expect_nodes(.data) - comp1 <- manynet::as_matrix(.data) - comp2 <- manynet::as_matrix(object2) - if(!manynet::is_complex(.data)){ - diag(comp1) <- NA - } - if(!manynet::is_directed(.data)){ - comp1[upper.tri(comp1)] <- NA - } - out <- cor(c(comp1), c(comp2), use = "complete.obs") - make_network_measure(out, .data, call = deparse(sys.call())) -} \ No newline at end of file diff --git a/R/measure_holes.R b/R/measure_holes.R index 8225019..b6d4c6d 100644 --- a/R/measure_holes.R +++ b/R/measure_holes.R @@ -246,5 +246,5 @@ tie_by_cohesion <- function(.data){ neigh_nodes <- length(unique(c(neigh1, neigh2)))-2 shared_nodes / neigh_nodes } ) - make_node_measure(out, .data) + make_tie_measure(out, .data) } diff --git a/tests/testthat/test-measure_ties.R b/tests/testthat/test-measure_ties.R index 3acbd94..f08f7b2 100644 --- a/tests/testthat/test-measure_ties.R +++ b/tests/testthat/test-measure_ties.R @@ -2,11 +2,8 @@ tie_meas <- funs_objs[grepl("tie_by_", names(funs_objs))] for(fn in names(tie_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("eigenvector|degree|cohesion|closeness", fn)) - if(fn == "x"){ - } else { - expect_s3_class(tie_meas[[fn]](data_objs[[ob]]), "tie_measure") - } + skip_if_not(packageVersion("manynet") >= "1.7.3") + expect_s3_class(tie_meas[[fn]](data_objs[[ob]]), "tie_measure") }) } } \ No newline at end of file From 437e6013ab56041e77586299313a71f964815629 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 13:34:29 +0100 Subject: [PATCH 78/98] Testing net_by_strength and _toughness only once because slow --- DESCRIPTION | 4 ++-- man/measure_periods.Rd | 2 +- tests/testthat/test-measure_net.R | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index feee643..f6fc487 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: netrics Title: Many Ways to Measure and Classify Membership for Networks, Nodes, and Ties Version: 0.1.0 -Date: 2026-03-04 +Date: 2026-03-13 Description: Many tools for calculating network, node, or tie marks, measures, motifs and memberships of many different types of networks. Marks ('node_is_*()' and 'tie_is_*()') identify structural positions, @@ -46,4 +46,4 @@ Config/Needs/website: pkgdown Config/testthat/parallel: true Config/testthat/edition: 3 -Config/testthat/start-first: member_community +Config/testthat/start-first: measure_net diff --git a/man/measure_periods.Rd b/man/measure_periods.Rd index 4b1d299..067a754 100644 --- a/man/measure_periods.Rd +++ b/man/measure_periods.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/measure_features.R +% Please edit documentation in R/measure_change.R \name{measure_periods} \alias{measure_periods} \alias{net_by_waves} diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R index 989303a..853dbbb 100644 --- a/tests/testthat/test-measure_net.R +++ b/tests/testthat/test-measure_net.R @@ -2,7 +2,7 @@ net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] for(fn in names(net_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("core|infection|immunity|recovery|reproduction|strength|transmiss|spatial", fn)) + skip_if(grepl("core|infection|immunity|recovery|reproduction|transmiss|spatial", fn)) skip_if(grepl("net_by_factions", fn) && ob == "twomode") if(grepl("diversity|heterophily|homophily", fn)){ if(ob == "attribute") @@ -13,6 +13,9 @@ for(fn in names(net_meas)) { } else if(grepl("congruency", fn)){ if(ob == "twomode") expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") + } else if(grepl("strength|toughness", fn)){ # why is this so slow?? + if(ob == "weighted") + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") } else if(grepl("correlation|change|stability", fn)){ if(ob == "labelled") expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") From 8e7973c0db9d983077ae9c9992dd289418c13157 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:19:17 +0100 Subject: [PATCH 79/98] Fixed how node_by_bokering_activity() and node_by_brokering_exclusivity() treat unlabelled data --- R/motif_brokerage.R | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/R/motif_brokerage.R b/R/motif_brokerage.R index 435a1b5..cc97f35 100644 --- a/R/motif_brokerage.R +++ b/R/motif_brokerage.R @@ -109,7 +109,6 @@ NULL #' @export node_by_brokering_activity <- function(.data, membership){ .data <- manynet::expect_nodes(.data) - from <- to.y <- to_memb <- from_memb <- NULL twopaths <- .to_twopaths(.data) if(!missing(membership)){ twopaths$from_memb <- manynet::node_attribute(.data, membership)[`if`(manynet::is_labelled(.data), @@ -122,8 +121,12 @@ node_by_brokering_activity <- function(.data, membership){ } # tabulate brokerage out <- c(table(twopaths$to)) - # correct ordering for named data - if(manynet::is_labelled(.data)) out <- out[match(manynet::node_names(.data), names(out))] + # correct ordering + if(manynet::is_labelled(.data)) out <- out[match(manynet::node_names(.data), names(out))] else { + temp <- rep(0, manynet::net_nodes(.data)) + temp[as.numeric(names(out))] <- out + out <- temp + } # missings should be none out[is.na(out)] <- 0 make_node_measure(out, .data) @@ -135,7 +138,6 @@ node_by_brokering_activity <- function(.data, membership){ #' @export node_by_brokering_exclusivity <- function(.data, membership){ .data <- manynet::expect_nodes(.data) - from <- to.y <- to_memb <- from_memb <- NULL twopaths <- .to_twopaths(.data) if(!missing(membership)){ twopaths$from_memb <- manynet::node_attribute(.data, membership)[`if`(manynet::is_labelled(.data), @@ -151,7 +153,11 @@ node_by_brokering_exclusivity <- function(.data, membership){ # tabulate brokerage out <- c(table(out$to)) # correct ordering for named data - if(manynet::is_labelled(.data)) out <- out[match(manynet::node_names(.data), names(out))] + if(manynet::is_labelled(.data)) out <- out[match(manynet::node_names(.data), names(out))] else { + temp <- rep(0, manynet::net_nodes(.data)) + temp[as.numeric(names(out))] <- out + out <- temp + } # missings should be none out[is.na(out)] <- 0 make_node_measure(out, .data) From 008a709cfcff396b5ab6051d7ec4277c4821840b Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:19:32 +0100 Subject: [PATCH 80/98] Minor documentation issues --- R/measure_centrality.R | 2 +- R/motif_census.R | 2 +- man/motif_net.Rd | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index aa0c6e9..8c71945 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -1078,7 +1078,7 @@ node_by_power <- function(.data, normalized = TRUE, scale = FALSE, exponent = 1) graph <- manynet::as_igraph(.data) if(var(node_by_deg(graph))==0){ - snet_minor_info("All nodes have the same degree, so power centrality is the same as degree centrality.") + manynet::snet_minor_info("All nodes have the same degree, so power centrality equals degree centrality.") exponent <- 0 } diff --git a/R/motif_census.R b/R/motif_census.R index 9c1442e..b712dc1 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -481,7 +481,7 @@ net_x_tetrad <- function(.data){ #' _Network Science_ 5(2): 187–212. #' \doi{10.1017/nws.2017.8} #' @examples -#' net_by_mixed(fict_marvel) +#' net_x_mixed(fict_marvel) #' @export net_x_mixed <- function (.data, object2) { .data <- manynet::expect_nodes(.data) diff --git a/man/motif_net.Rd b/man/motif_net.Rd index b5f17f1..ea267f0 100644 --- a/man/motif_net.Rd +++ b/man/motif_net.Rd @@ -123,7 +123,7 @@ Graphs of these motifs can be shown using net_x_dyad(manynet::ison_algebra) net_x_triad(manynet::ison_adolescents) net_x_tetrad(ison_southern_women) -net_by_mixed(fict_marvel) +net_x_mixed(fict_marvel) } \references{ \subsection{On the dyad census}{ From 9f4a5d93ddb8d17e7c6d03d3d85e6ff8192e2594 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:20:53 +0100 Subject: [PATCH 81/98] Testing all node_members --- tests/testthat/test-member_nodes.R | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-member_nodes.R b/tests/testthat/test-member_nodes.R index 8414c6c..56b6bc4 100644 --- a/tests/testthat/test-member_nodes.R +++ b/tests/testthat/test-member_nodes.R @@ -2,12 +2,20 @@ node_membs <- funs_objs[grepl("node_in_", names(funs_objs))] for(fn in names(node_membs)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("equivalence|adopter|brokering", fn)) if(grepl("roulette", fn)){ if(ob != "twomode") expect_s3_class(node_membs[[fn]](data_objs[[ob]], num_groups = 3), + "node_member") else + succeed("Roulette doesn't work on two-mode objects") + } else if(grepl("adopter", fn)){ + if(ob == "diffusion") + expect_s3_class(node_membs[[fn]](data_objs[[ob]]), "node_member") else + succeed("Only works on diffusion objects") + } else if(grepl("equivalence", fn)){ + expect_s3_class(node_membs[[fn]](data_objs[[ob]], + node_x_tie(data_objs[[ob]])), "node_member") - } else if (ob %in% c("directed","undirected","weighted","twomode","signed","labelled")){ + } else { expect_s3_class(node_membs[[fn]](data_objs[[ob]]), "node_member") } }) From fa6367382a3971cef21df87cf4e12e70ecbed7c8 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:21:20 +0100 Subject: [PATCH 82/98] Testing all node marks --- tests/testthat/test-mark_nodes.R | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-mark_nodes.R b/tests/testthat/test-mark_nodes.R index e5428c6..c9ee0cb 100644 --- a/tests/testthat/test-mark_nodes.R +++ b/tests/testthat/test-mark_nodes.R @@ -4,7 +4,6 @@ node_marks <- funs_objs[grepl("node_is_", names(funs_objs))] for(fn in names(node_marks)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("node_is_recovered|min|max|mean|latent|infected", fn)) if(fn == "node_is_exposed"){ expect_s3_class(node_marks[[fn]](data_objs[[ob]], mark = c(1,3)), "node_mark") @@ -14,6 +13,13 @@ for(fn in names(node_marks)) { } else if(fn == "node_is_neighbor"){ expect_s3_class(node_marks[[fn]](data_objs[[ob]], node = 1), "node_mark") + } else if(grepl("recovered|latent|infected", fn)){ + if(ob == "diffusion") + expect_s3_class(node_marks[[fn]](data_objs[[ob]]), + "node_mark") else succeed("Only used for diffusion objects") + } else if(grepl("min|max|mean", fn)){ + expect_s3_class(node_marks[[fn]](node_by_deg(data_objs[[ob]])), + "node_mark") } else { expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") } From cf2cce800f23ddc408caafd51d97626f3eca2b79 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:35:15 +0100 Subject: [PATCH 83/98] Testing ties --- tests/testthat/test-mark_ties.R | 9 ++++++--- tests/testthat/test-motif_ties.R | 25 ++++++++++++------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/testthat/test-mark_ties.R b/tests/testthat/test-mark_ties.R index 28e874a..a27d808 100644 --- a/tests/testthat/test-mark_ties.R +++ b/tests/testthat/test-mark_ties.R @@ -2,12 +2,15 @@ tie_marks <- funs_objs[grepl("tie_is_", names(funs_objs))] for(fn in names(tie_marks)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("tie_is_max|tie_is_min|tie_is_recovered", fn)) skip_if(grepl("tie_is_imbalanced", fn) && ob == "twomode") if(fn == "tie_is_path"){ expect_s3_class(tie_marks[[fn]](data_objs[[ob]], 1, 2), "tie_mark") - } else if(fn == "tie_is_infected"){ - expect_s3_class(tie_marks[[fn]](play_diffusion(data_objs[[ob]])), "tie_mark") + } else if(grepl("infected|recovered", fn)){ + if(ob == "diffusion") + expect_s3_class(tie_marks[[fn]](play_diffusion(data_objs[[ob]])), "tie_mark") else + success("Only used for diffusion objects") + } else if(grepl("max|min", fn)){ + expect_s3_class(tie_marks[[fn]](tie_by_degree(data_objs[[ob]])), "tie_mark") } else { expect_s3_class(tie_marks[[fn]](data_objs[[ob]]), "tie_mark") } diff --git a/tests/testthat/test-motif_ties.R b/tests/testthat/test-motif_ties.R index e39de75..043dbb7 100644 --- a/tests/testthat/test-motif_ties.R +++ b/tests/testthat/test-motif_ties.R @@ -1,13 +1,12 @@ -tie_motifs <- funs_objs[grepl("tie_x_", names(funs_objs))] -for(fn in names(tie_motifs)) { - for (ob in names(data_objs)) { - test_that(paste(fn, "works on", ob), { - skip_if(grepl("exposure|brokerage", fn)) - skip_if(grepl("triad|dyad", fn) && is_twomode(data_objs[[ob]])) - if(fn == "x"){ - } else { - expect_s3_class(tie_motifs[[fn]](data_objs[[ob]]), "tie_motif") - } - }) - } -} +# tie_motifs <- funs_objs[grepl("tie_x_", names(funs_objs))] +# for(fn in names(tie_motifs)) { +# for (ob in names(data_objs)) { +# test_that(paste(fn, "works on", ob), { +# if(fn == "x"){ +# } else { +# expect_s3_class(tie_motifs[[fn]](data_objs[[ob]]), "tie_motif") +# } +# }) +# } +# } + From 18a0542f854cf9400e4dde7e1c9fa612b2bb8d86 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:35:24 +0100 Subject: [PATCH 84/98] Testing more measures --- tests/testthat/test-measure_net.R | 15 ++++++++++----- tests/testthat/test-measure_nodes.R | 15 +++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R index 853dbbb..3515fe9 100644 --- a/tests/testthat/test-measure_net.R +++ b/tests/testthat/test-measure_net.R @@ -6,19 +6,24 @@ for(fn in names(net_meas)) { skip_if(grepl("net_by_factions", fn) && ob == "twomode") if(grepl("diversity|heterophily|homophily", fn)){ if(ob == "attribute") - expect_s3_class(net_meas[[fn]](data_objs[[ob]], "group"), "network_measure") + expect_s3_class(net_meas[[fn]](data_objs[[ob]], "group"), "network_measure") else + succeed("Only used for attribute objects") } else if(grepl("balance", fn)){ if(ob == "labelled") - expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") else + succeed("Only used for signed objects") } else if(grepl("congruency", fn)){ if(ob == "twomode") - expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") + expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") else + succeed("Only used for multiple two-mode objects") } else if(grepl("strength|toughness", fn)){ # why is this so slow?? if(ob == "weighted") - expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") else + succeed("Testing only once because slow") } else if(grepl("correlation|change|stability", fn)){ if(ob == "labelled") - expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") + expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") else + succeed("Only used for multi objects") } else { expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") } diff --git a/tests/testthat/test-measure_nodes.R b/tests/testthat/test-measure_nodes.R index 0e5eddf..f9b4082 100644 --- a/tests/testthat/test-measure_nodes.R +++ b/tests/testthat/test-measure_nodes.R @@ -2,20 +2,23 @@ node_meas <- funs_objs[grepl("node_by_", names(funs_objs))] for(fn in names(node_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("threshold|recovery|multideg|exposure", fn)) + skip_if(grepl("multideg", fn)) skip_if(grepl("equivalency", fn) && ob == "labelled") if(grepl("diversity|richness|heterophily|homophily", fn)){ if(ob == "attribute") - expect_s3_class(node_meas[[fn]](data_objs[[ob]], "group"), "node_measure") - } else if(grepl("adoption",fn)){ + expect_s3_class(node_meas[[fn]](data_objs[[ob]], "group"), "node_measure") else + succeed("Only used for attribute objects") + } else if(grepl("adoption|threshold|recovery|exposure",fn)){ if(ob == "diffusion") - expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") + expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") else + succeed("Only used for diffusion objects") } else if(grepl("posneg",fn)){ if(ob == "signed") - expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") + expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") else + succeed("Only used for signed objects") } else if(grepl("distance",fn)){ expect_s3_class(node_meas[[fn]](data_objs[[ob]], 1, 2), "node_measure") - } else if (ob %in% c("directed","undirected","weighted","twomode","signed","labelled")){ + } else { expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") } }) From efd08f7d4b7211faad11c17ad3c89d2c848f7ad3 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:35:38 +0100 Subject: [PATCH 85/98] Testing brokerage net motifs --- tests/testthat/test-motif_net.R | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-motif_net.R b/tests/testthat/test-motif_net.R index 3eaedd1..715ffc3 100644 --- a/tests/testthat/test-motif_net.R +++ b/tests/testthat/test-motif_net.R @@ -2,9 +2,12 @@ net_motifs <- funs_objs[grepl("net_x_", names(funs_objs))] for(fn in names(net_motifs)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("exposure|brokerage|mixed|hazard", fn)) + skip_if(grepl("exposure|mixed|hazard", fn)) skip_if(grepl("triad", fn) && is_twomode(data_objs[[ob]])) - if(fn == "x"){ + if(grepl("brokerage", fn)){ + if(ob == "attribute") + expect_s3_class(net_motifs[[fn]](data_objs[[ob]], "group"), "network_motif") else + succeed("Only used for attribute objects") } else { expect_s3_class(net_motifs[[fn]](data_objs[[ob]]), "network_motif") } From 15cbad3abbe22ad572a88cae7c32f88d45748be2 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:48:37 +0100 Subject: [PATCH 86/98] Testing net_by diffusion functions --- tests/testthat/test-measure_net.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R index 3515fe9..8ddb97c 100644 --- a/tests/testthat/test-measure_net.R +++ b/tests/testthat/test-measure_net.R @@ -2,7 +2,7 @@ net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] for(fn in names(net_meas)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("core|infection|immunity|recovery|reproduction|transmiss|spatial", fn)) + skip_if(grepl("core|spatial", fn)) skip_if(grepl("net_by_factions", fn) && ob == "twomode") if(grepl("diversity|heterophily|homophily", fn)){ if(ob == "attribute") @@ -20,6 +20,10 @@ for(fn in names(net_meas)) { if(ob == "weighted") expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") else succeed("Testing only once because slow") + } else if(grepl("infection|immunity|recovery|reproduction|transmiss", fn)){ + if(ob == "diffusion") + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") else + succeed("Only used for diffusion objects") } else if(grepl("correlation|change|stability", fn)){ if(ob == "labelled") expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") else From 6e41f24287174dd61be7a9e25692f4684a6f687f Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:48:52 +0100 Subject: [PATCH 87/98] Testing brokerage and exposure node motifs --- tests/testthat/test-motif_nodes.R | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-motif_nodes.R b/tests/testthat/test-motif_nodes.R index 14f82b2..51315c2 100644 --- a/tests/testthat/test-motif_nodes.R +++ b/tests/testthat/test-motif_nodes.R @@ -2,9 +2,15 @@ node_motifs <- funs_objs[grepl("node_x_", names(funs_objs))] for(fn in names(node_motifs)) { for (ob in names(data_objs)) { test_that(paste(fn, "works on", ob), { - skip_if(grepl("exposure|brokerage", fn)) skip_if(grepl("triad|dyad", fn) && is_twomode(data_objs[[ob]])) - if(fn == "x"){ + if(grepl("brokerage", fn)){ + if(ob == "attribute") + expect_s3_class(node_motifs[[fn]](data_objs[[ob]], "group"), "node_motif") else + succeed("Only used for attribute objects") + } else if(grepl("exposure", fn)){ + if(ob == "diffusion") + expect_s3_class(node_motifs[[fn]](data_objs[[ob]]), "node_motif") else + succeed("Only used for diffusion objects") } else { expect_s3_class(node_motifs[[fn]](data_objs[[ob]]), "node_motif") } From 67eff63a7c7e464b3ba2d557bd3149e6201517cc Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 13 Mar 2026 18:57:13 +0100 Subject: [PATCH 88/98] Skip if tie_by_degree() not available in correct form --- R/netrics-utils.R | 1 + tests/testthat/test-mark_ties.R | 1 + 2 files changed, 2 insertions(+) diff --git a/R/netrics-utils.R b/R/netrics-utils.R index 74bdbac..a97ab23 100644 --- a/R/netrics-utils.R +++ b/R/netrics-utils.R @@ -3,6 +3,7 @@ # defining global variables more centrally utils::globalVariables(c(".data", "obs", "from", "to", "name", "weight","sign","wave", + "from_memb","to_memb","to.y", "nodes","event","exposure", "student","students","colleges", "node","value","var","active","time", diff --git a/tests/testthat/test-mark_ties.R b/tests/testthat/test-mark_ties.R index a27d808..67896f1 100644 --- a/tests/testthat/test-mark_ties.R +++ b/tests/testthat/test-mark_ties.R @@ -10,6 +10,7 @@ for(fn in names(tie_marks)) { expect_s3_class(tie_marks[[fn]](play_diffusion(data_objs[[ob]])), "tie_mark") else success("Only used for diffusion objects") } else if(grepl("max|min", fn)){ + skip_if_not(packageVersion("manynet") >= "1.7.3") expect_s3_class(tie_marks[[fn]](tie_by_degree(data_objs[[ob]])), "tie_mark") } else { expect_s3_class(tie_marks[[fn]](data_objs[[ob]]), "tie_mark") From 3dabf11057a56d583d1c83cbeec82537b0e51324 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 14 Mar 2026 11:32:54 +0100 Subject: [PATCH 89/98] Changed older tests to point to netrics analytic functions instead of manynet functions --- tests/testthat/test-mark_nodes.R | 14 +-- tests/testthat/test-mark_ties.R | 14 +-- tests/testthat/test-measure_centrality.R | 126 ++++++++++---------- tests/testthat/test-measure_closure.R | 23 ++-- tests/testthat/test-measure_cohesion.R | 16 +-- tests/testthat/test-measure_diffusion.R | 4 +- tests/testthat/test-measure_features.R | 16 +-- tests/testthat/test-measure_heterogeneity.R | 20 ++-- tests/testthat/test-measure_hierarchy.R | 18 +-- tests/testthat/test-measure_holes.R | 40 +++---- tests/testthat/test-member_components.R | 2 +- tests/testthat/test-member_core.R | 2 +- 12 files changed, 143 insertions(+), 152 deletions(-) diff --git a/tests/testthat/test-mark_nodes.R b/tests/testthat/test-mark_nodes.R index c9ee0cb..03ddee5 100644 --- a/tests/testthat/test-mark_nodes.R +++ b/tests/testthat/test-mark_nodes.R @@ -55,23 +55,23 @@ test_that("node_is_neighbor works", { test_that("node_is_max works", { # skip_on_cran() # skip_on_ci() - expect_equal(length(node_is_max(node_betweenness(ison_brandes))), + expect_equal(length(node_is_max(node_by_betweenness(ison_brandes))), c(net_nodes(ison_brandes))) - expect_equal(sum(node_is_max(node_betweenness(ison_brandes)) == TRUE), 1) - expect_s3_class(node_is_max(node_betweenness(ison_brandes)), "logical") + expect_equal(sum(node_is_max(node_by_betweenness(ison_brandes)) == TRUE), 1) + expect_s3_class(node_is_max(node_by_betweenness(ison_brandes)), "logical") }) test_that("node_is_min works", { # skip_on_cran() # skip_on_ci() - expect_equal(length(node_is_min(node_betweenness(ison_brandes))), + expect_equal(length(node_is_min(node_by_betweenness(ison_brandes))), c(net_nodes(ison_brandes))) - expect_equal(sum(node_is_min(node_betweenness(ison_brandes)) == TRUE), 4) - expect_s3_class(node_is_min(node_betweenness(ison_brandes)), "logical") + expect_equal(sum(node_is_min(node_by_betweenness(ison_brandes)) == TRUE), 4) + expect_s3_class(node_is_min(node_by_betweenness(ison_brandes)), "logical") }) test_that("node_is_mean works", { - expect_s3_class(node_is_mean(node_betweenness(ison_brandes)), "logical") + expect_s3_class(node_is_mean(node_by_betweenness(ison_brandes)), "logical") }) test_that("additional node mark functions work", { diff --git a/tests/testthat/test-mark_ties.R b/tests/testthat/test-mark_ties.R index 67896f1..535c4b6 100644 --- a/tests/testthat/test-mark_ties.R +++ b/tests/testthat/test-mark_ties.R @@ -7,7 +7,7 @@ for(fn in names(tie_marks)) { expect_s3_class(tie_marks[[fn]](data_objs[[ob]], 1, 2), "tie_mark") } else if(grepl("infected|recovered", fn)){ if(ob == "diffusion") - expect_s3_class(tie_marks[[fn]](play_diffusion(data_objs[[ob]])), "tie_mark") else + expect_s3_class(tie_marks[[fn]](data_objs[[ob]]), "tie_mark") else success("Only used for diffusion objects") } else if(grepl("max|min", fn)){ skip_if_not(packageVersion("manynet") >= "1.7.3") @@ -60,19 +60,19 @@ test_that("directed triangle tie marks work", { test_that("tie_is_max works", { skip_on_ci() skip_on_cran() - expect_equal(length(tie_is_max(tie_betweenness(graph1))), + expect_equal(length(tie_is_max(tie_by_betweenness(graph1))), c(net_ties(graph1))) - expect_equal(sum(tie_is_max(tie_betweenness(graph1)) == TRUE), 1) - expect_s3_class(tie_is_max(tie_betweenness(graph1)), "logical") + expect_equal(sum(tie_is_max(tie_by_betweenness(graph1)) == TRUE), 1) + expect_s3_class(tie_is_max(tie_by_betweenness(graph1)), "logical") }) test_that("tie_is_min works", { skip_on_ci() skip_on_cran() - expect_equal(length(tie_is_min(tie_betweenness(ison_brandes))), + expect_equal(length(tie_is_min(tie_by_betweenness(ison_brandes))), c(net_ties(ison_brandes))) - expect_equal(sum(tie_is_min(tie_betweenness(ison_brandes)) == TRUE), 1) - expect_s3_class(tie_is_min(tie_betweenness(ison_brandes)), "logical") + expect_equal(sum(tie_is_min(tie_by_betweenness(ison_brandes)) == TRUE), 1) + expect_s3_class(tie_is_min(tie_by_betweenness(ison_brandes)), "logical") }) test_that("tie_is_feedback() mark functions work", { diff --git a/tests/testthat/test-measure_centrality.R b/tests/testthat/test-measure_centrality.R index 3be02a4..df85781 100644 --- a/tests/testthat/test-measure_centrality.R +++ b/tests/testthat/test-measure_centrality.R @@ -3,127 +3,127 @@ test_igr <- ison_southern_women test_mat <- as_matrix(ison_southern_women) test_that("one mode degree centrality calculated correctly",{ - expect_equal(top5(node_degree(ison_adolescents, normalized = FALSE)), c(1,4,4,2,3)) + expect_equal(top5(node_by_degree(ison_adolescents, normalized = FALSE)), c(1,4,4,2,3)) }) test_that("one mode strength centrality calculated correctly",{ - expect_equal(top5(node_degree(to_unweighted(ison_networkers), direction = "in", normalized = FALSE)), + expect_equal(top5(node_by_degree(to_unweighted(ison_networkers), direction = "in", normalized = FALSE)), c(29, 24, 11, 18, 8)) - expect_equal(top5(node_degree(ison_networkers, direction = "in", normalized = FALSE, alpha = 1)), + expect_equal(top5(node_by_degree(ison_networkers, direction = "in", normalized = FALSE, alpha = 1)), c(2495, 1212, 101, 322, 89)) }) test_that("two mode degree centrality calculated correctly",{ - expect_equal(top5(node_degree(test_mat, normalized = FALSE)), c(8,7,8,7,4)) - expect_equal(top5(node_degree(test_igr, normalized = FALSE)), c(8,7,8,7,4)) - expect_equal(top5(with_graph(test_tbl, node_degree(normalized = FALSE))), c(8,7,8,7,4)) - expect_equal(bot3(node_degree(test_mat, normalized = FALSE)), c(6,3,3)) - expect_equal(bot3(node_degree(test_igr, normalized = FALSE)), c(6,3,3)) - expect_equal(bot3(with_graph(test_tbl, node_degree(normalized = FALSE))), c(6,3,3)) - expect_equal(top5(node_degree(test_mat, normalized = TRUE)), c(0.5714, .5, .5714, .5, .2857)) - expect_equal(top5(node_degree(test_igr, normalized = TRUE)), c(0.5714, .5, .5714, .5, .2857)) - expect_equal(top5(with_graph(test_tbl, node_degree(normalized = TRUE))), c(0.5714, .5, .5714, .5, .2857)) - expect_equal(bot3(node_degree(test_mat, normalized = TRUE)), c(.3333, .1667, .1667)) - expect_equal(bot3(node_degree(test_igr, normalized = TRUE)), c(.3333, .1667, .1667)) - expect_equal(bot3(with_graph(test_tbl, node_degree(normalized = TRUE))), c(.3333, .1667, .1667)) + expect_equal(top5(node_by_degree(test_mat, normalized = FALSE)), c(8,7,8,7,4)) + expect_equal(top5(node_by_degree(test_igr, normalized = FALSE)), c(8,7,8,7,4)) + expect_equal(top5(with_graph(test_tbl, node_by_degree(normalized = FALSE))), c(8,7,8,7,4)) + expect_equal(bot3(node_by_degree(test_mat, normalized = FALSE)), c(6,3,3)) + expect_equal(bot3(node_by_degree(test_igr, normalized = FALSE)), c(6,3,3)) + expect_equal(bot3(with_graph(test_tbl, node_by_degree(normalized = FALSE))), c(6,3,3)) + expect_equal(top5(node_by_degree(test_mat, normalized = TRUE)), c(0.5714, .5, .5714, .5, .2857)) + expect_equal(top5(node_by_degree(test_igr, normalized = TRUE)), c(0.5714, .5, .5714, .5, .2857)) + expect_equal(top5(with_graph(test_tbl, node_by_degree(normalized = TRUE))), c(0.5714, .5, .5714, .5, .2857)) + expect_equal(bot3(node_by_degree(test_mat, normalized = TRUE)), c(.3333, .1667, .1667)) + expect_equal(bot3(node_by_degree(test_igr, normalized = TRUE)), c(.3333, .1667, .1667)) + expect_equal(bot3(with_graph(test_tbl, node_by_degree(normalized = TRUE))), c(.3333, .1667, .1667)) }) test_that("one mode closeness centrality calculated correctly",{ - expect_equal(top3(node_closeness(ison_adolescents, normalized = FALSE)), c(0.059, 0.091, 0.091), tolerance = 0.01) + expect_equal(top3(node_by_closeness(ison_adolescents, normalized = FALSE)), c(0.059, 0.091, 0.091), tolerance = 0.01) }) test_that("two mode closeness centrality calculated correctly",{ - expect_equal(top5(node_closeness(test_mat, normalized = FALSE)), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) - expect_equal(top5(node_closeness(test_igr, normalized = FALSE)), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) - expect_equal(top5(with_graph(test_tbl, node_closeness(normalized = FALSE))), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) - expect_equal(bot3(node_closeness(test_mat, normalized = FALSE)), c(0.0128, 0.0119, 0.0119)) - expect_equal(bot3(node_closeness(test_igr, normalized = FALSE)), c(0.0128, 0.0119, 0.0119)) - expect_equal(bot3(with_graph(test_tbl, node_closeness(normalized = FALSE))), c(0.0128, 0.0119, 0.0119)) - expect_equal(top5(node_closeness(test_mat, normalized = TRUE)), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) - expect_equal(top5(node_closeness(test_igr, normalized = TRUE)), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) - expect_equal(top5(with_graph(test_tbl, node_closeness(normalized = TRUE))), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) - expect_equal(bot3(node_closeness(test_mat, normalized = TRUE)), c(0.5641, 0.5238, 0.5238)) - expect_equal(bot3(node_closeness(test_igr, normalized = TRUE)), c(0.5641, 0.5238, 0.5238)) - expect_equal(bot3(with_graph(test_tbl, node_closeness(normalized = TRUE))), c(0.5641, 0.5238, 0.5238)) + expect_equal(top5(node_by_closeness(test_mat, normalized = FALSE)), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) + expect_equal(top5(node_by_closeness(test_igr, normalized = FALSE)), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) + expect_equal(top5(with_graph(test_tbl, node_by_closeness(normalized = FALSE))), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) + expect_equal(bot3(node_by_closeness(test_mat, normalized = FALSE)), c(0.0128, 0.0119, 0.0119)) + expect_equal(bot3(node_by_closeness(test_igr, normalized = FALSE)), c(0.0128, 0.0119, 0.0119)) + expect_equal(bot3(with_graph(test_tbl, node_by_closeness(normalized = FALSE))), c(0.0128, 0.0119, 0.0119)) + expect_equal(top5(node_by_closeness(test_mat, normalized = TRUE)), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) + expect_equal(top5(node_by_closeness(test_igr, normalized = TRUE)), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) + expect_equal(top5(with_graph(test_tbl, node_by_closeness(normalized = TRUE))), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) + expect_equal(bot3(node_by_closeness(test_mat, normalized = TRUE)), c(0.5641, 0.5238, 0.5238)) + expect_equal(bot3(node_by_closeness(test_igr, normalized = TRUE)), c(0.5641, 0.5238, 0.5238)) + expect_equal(bot3(with_graph(test_tbl, node_by_closeness(normalized = TRUE))), c(0.5641, 0.5238, 0.5238)) }) test_that("one mode betweenness centrality calculated correctly",{ - expect_equal(top3(node_betweenness(ison_adolescents, normalized = FALSE)), c(0, 7.5, 5.5), tolerance = 0.001) + expect_equal(top3(node_by_betweenness(ison_adolescents, normalized = FALSE)), c(0, 7.5, 5.5), tolerance = 0.001) }) test_that("two mode betweenness centrality calculated correctly",{ - expect_equal(top5(node_betweenness(test_mat, normalized = FALSE)), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) - expect_equal(top5(node_betweenness(test_igr, normalized = FALSE)), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) - expect_equal(top5(with_graph(test_tbl, node_betweenness(normalized = FALSE))), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) - expect_equal(bot3(node_betweenness(test_mat, normalized = FALSE)), c(8.1786, 1.0128, 1.0128)) - expect_equal(bot3(node_betweenness(test_igr, normalized = FALSE)), c(8.1786, 1.0128, 1.0128)) - expect_equal(bot3(with_graph(test_tbl, node_betweenness(normalized = FALSE))), c(8.1786, 1.0128, 1.0128)) - expect_equal(top3(node_betweenness(test_mat, normalized = TRUE),4), c(0.0972, 0.0517, 0.0882)) - expect_equal(top3(node_betweenness(test_igr, normalized = TRUE),4), c(0.0972, 0.0517, 0.0882)) - expect_equal(top3(with_graph(test_tbl, node_betweenness(normalized = TRUE)),4), c(0.0972, 0.0517, 0.0882)) - expect_equal(bot3(node_betweenness(test_mat, normalized = TRUE),4), c(0.0181, 0.0022, 0.0022)) - expect_equal(bot3(node_betweenness(test_igr, normalized = TRUE),4), c(0.0181, 0.0022, 0.0022)) - expect_equal(bot3(with_graph(test_tbl, node_betweenness(normalized = TRUE)),4), c(0.0181, 0.0022, 0.0022)) + expect_equal(top5(node_by_betweenness(test_mat, normalized = FALSE)), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) + expect_equal(top5(node_by_betweenness(test_igr, normalized = FALSE)), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) + expect_equal(top5(with_graph(test_tbl, node_by_betweenness(normalized = FALSE))), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) + expect_equal(bot3(node_by_betweenness(test_mat, normalized = FALSE)), c(8.1786, 1.0128, 1.0128)) + expect_equal(bot3(node_by_betweenness(test_igr, normalized = FALSE)), c(8.1786, 1.0128, 1.0128)) + expect_equal(bot3(with_graph(test_tbl, node_by_betweenness(normalized = FALSE))), c(8.1786, 1.0128, 1.0128)) + expect_equal(top3(node_by_betweenness(test_mat, normalized = TRUE),4), c(0.0972, 0.0517, 0.0882)) + expect_equal(top3(node_by_betweenness(test_igr, normalized = TRUE),4), c(0.0972, 0.0517, 0.0882)) + expect_equal(top3(with_graph(test_tbl, node_by_betweenness(normalized = TRUE)),4), c(0.0972, 0.0517, 0.0882)) + expect_equal(bot3(node_by_betweenness(test_mat, normalized = TRUE),4), c(0.0181, 0.0022, 0.0022)) + expect_equal(bot3(node_by_betweenness(test_igr, normalized = TRUE),4), c(0.0181, 0.0022, 0.0022)) + expect_equal(bot3(with_graph(test_tbl, node_by_betweenness(normalized = TRUE)),4), c(0.0181, 0.0022, 0.0022)) }) test_that("one mode eigenvector centrality calculated correctly",{ # expect_equal(top3(node_eigenvector(ison_adolescents, normalized = FALSE)), c(0.16, 0.491, 0.529), tolerance = 0.001) - expect_equal(top3(node_eigenvector(ison_adolescents)), c(0.303, 0.928, 1), tolerance = 0.001) + expect_equal(top3(node_by_eigenvector(ison_adolescents)), c(0.303, 0.928, 1), tolerance = 0.001) }) test_that("two mode eigenvector centrality calculated correctly",{ - expect_equal(top3(node_eigenvector(test_mat)), c(0.9009, 0.8497, 1)) - expect_equal(bot3(node_eigenvector(test_mat)), c(0.4764, 0.2907, 0.2907)) - expect_equal(top3(node_eigenvector(test_igr)), c(0.9009, 0.8497, 1)) - expect_equal(bot3(node_eigenvector(test_igr)), c(0.4764, 0.2907, 0.2907)) + expect_equal(top3(node_by_eigenvector(test_mat)), c(0.9009, 0.8497, 1)) + expect_equal(bot3(node_by_eigenvector(test_mat)), c(0.4764, 0.2907, 0.2907)) + expect_equal(top3(node_by_eigenvector(test_igr)), c(0.9009, 0.8497, 1)) + expect_equal(bot3(node_by_eigenvector(test_igr)), c(0.4764, 0.2907, 0.2907)) }) test_that("summary node measure works", { - expect_equal(names(summary(node_degree(ison_adolescents))), + expect_equal(names(summary(node_by_degree(ison_adolescents))), c("Minimum","Maximum","Mean","StdDev","Missing")) }) test_that("summary net measure works", { - expect_match(summary(net_degree(ison_adolescents)), "z =") + expect_match(summary(net_by_degree(ison_adolescents)), "z =") }) # ####### Centralization test_that("one-mode centralisation is calculated correctly", { - expect_equal(as.numeric(net_degree(ison_adolescents)), 0.2142, tolerance = 0.001) - expect_equal(as.numeric(net_closeness(ison_adolescents)), 0.3195, tolerance = 0.001) - expect_equal(as.numeric(net_betweenness(ison_adolescents)), 0.3401, tolerance = 0.001) - expect_equal(as.numeric(net_eigenvector(ison_adolescents)), 0.5479, tolerance = 0.001) + expect_equal(as.numeric(net_by_degree(ison_adolescents)), 0.2142, tolerance = 0.001) + expect_equal(as.numeric(net_by_closeness(ison_adolescents)), 0.3195, tolerance = 0.001) + expect_equal(as.numeric(net_by_betweenness(ison_adolescents)), 0.3401, tolerance = 0.001) + expect_equal(as.numeric(net_by_eigenvector(ison_adolescents)), 0.5479, tolerance = 0.001) }) test_that("two mode degree centralisation calculated correctly", { - expect_equal(as.numeric(net_degree(ison_southern_women, normalized = FALSE)), c(0.2021, 0.5253), tolerance = 0.001) - expect_equal(as.numeric(net_degree(ison_southern_women, direction = "in")), c(0.249, 0.484), tolerance = 0.001) - expect_equal(as.numeric(net_degree(ison_southern_women, normalized = TRUE)), c(0.245, 0.493), tolerance = 0.001) + expect_equal(as.numeric(net_by_degree(ison_southern_women, normalized = FALSE)), c(0.2021, 0.5253), tolerance = 0.001) + expect_equal(as.numeric(net_by_degree(ison_southern_women, direction = "in")), c(0.249, 0.484), tolerance = 0.001) + expect_equal(as.numeric(net_by_degree(ison_southern_women, normalized = TRUE)), c(0.245, 0.493), tolerance = 0.001) }) test_that("two mode closeness centralisation calculated correctly", { - expect_equal(as.numeric(net_closeness(ison_southern_women, normalized = TRUE)), c(0.293, 0.452), tolerance = 0.001) - expect_equal(as.numeric(net_closeness(ison_southern_women, direction = "in")), c(0.224, 0.537), tolerance = 0.001) + expect_equal(as.numeric(net_by_closeness(ison_southern_women, normalized = TRUE)), c(0.293, 0.452), tolerance = 0.001) + expect_equal(as.numeric(net_by_closeness(ison_southern_women, direction = "in")), c(0.224, 0.537), tolerance = 0.001) }) test_that("two mode betweenness centralisation calculated correctly", { - expect_equal(as.numeric(net_betweenness(ison_southern_women, normalized = FALSE)), c(0.0733, 0.2113), tolerance = 0.001) - expect_equal(as.numeric(net_betweenness(ison_southern_women, direction = "in")), c(0.082, 0.202), tolerance = 0.001) - expect_equal(as.numeric(net_betweenness(ison_southern_women, normalized = TRUE)), c(0.0739, 0.2113), tolerance = 0.001) + expect_equal(as.numeric(net_by_betweenness(ison_southern_women, normalized = FALSE)), c(0.0733, 0.2113), tolerance = 0.001) + expect_equal(as.numeric(net_by_betweenness(ison_southern_women, direction = "in")), c(0.082, 0.202), tolerance = 0.001) + expect_equal(as.numeric(net_by_betweenness(ison_southern_women, normalized = TRUE)), c(0.0739, 0.2113), tolerance = 0.001) }) test_that("net_measure class works", { - expect_output(print(net_degree(ison_algebra))) + expect_output(print(net_by_degree(ison_algebra))) }) # ####### Edge centrality test_that("tie_betweenness works", { - expect_equal(unname(tie_betweenness(ison_adolescents)[1:3]), + expect_equal(unname(tie_by_betweenness(ison_adolescents)[1:3]), c(7,3,5), tolerance = 0.001) }) test_that("tie_closeness works", { - expect_equal(unname(tie_closeness(ison_adolescents)[1:3]), + expect_equal(unname(tie_by_closeness(ison_adolescents)[1:3]), c(0.562,0.692,0.600), tolerance = 0.001) }) diff --git a/tests/testthat/test-measure_closure.R b/tests/testthat/test-measure_closure.R index ff838c1..9efe669 100644 --- a/tests/testthat/test-measure_closure.R +++ b/tests/testthat/test-measure_closure.R @@ -1,30 +1,25 @@ test_that("network density works", { - expect_equal(as.numeric(net_density(create_empty(10))), 0) - expect_equal(as.numeric(net_density(create_empty(c(10,6)))), 0) - expect_equal(as.numeric(net_density(create_filled(10))), 1) - expect_equal(as.numeric(net_density(create_filled(c(10,6)))), 1) - expect_output(print(net_density(create_filled(10)))) + expect_equal(as.numeric(net_by_density(create_empty(10))), 0) + expect_equal(as.numeric(net_by_density(create_empty(c(10,6)))), 0) + expect_equal(as.numeric(net_by_density(create_filled(10))), 1) + expect_equal(as.numeric(net_by_density(create_filled(c(10,6)))), 1) + expect_output(print(net_by_density(create_filled(10)))) }) test_that("one-mode object clustering is reported correctly",{ - expect_equal(as.numeric(net_transitivity(ison_algebra)), + expect_equal(as.numeric(net_by_transitivity(ison_algebra)), 0.69787, tolerance = 0.001) }) test_that("two-mode object clustering is reported correctly",{ - expect_equal(as.numeric(net_equivalency(ison_southern_women)), + expect_equal(as.numeric(net_by_equivalency(ison_southern_women)), 0.4677, tolerance = 0.001) - expect_values(net_equivalency(ison_adolescents), 0.258) -}) - -test_that("node_equivalency works correctly",{ - expect_equal(as.numeric(node_equivalency(ison_laterals$ison_mm)), - c(0,1,1,0,0.5,0.5), tolerance = 0.001) + expect_values(net_by_equivalency(ison_adolescents), 0.258) }) test_that("three-mode clustering calculated correctly",{ mat1 <- manynet::create_ring(c(10,5)) mat2 <- manynet::create_ring(c(5,8)) - expect_equal(as.numeric(net_congruency(mat1, mat2)), + expect_equal(as.numeric(net_by_congruency(mat1, mat2)), 0.3684, tolerance = 0.001) }) diff --git a/tests/testthat/test-measure_cohesion.R b/tests/testthat/test-measure_cohesion.R index fd84d26..ddbf321 100644 --- a/tests/testthat/test-measure_cohesion.R +++ b/tests/testthat/test-measure_cohesion.R @@ -1,32 +1,32 @@ test_that("network components works", { - expect_equal(as.numeric(net_components(ison_adolescents)), 1) + expect_equal(as.numeric(net_by_components(ison_adolescents)), 1) }) test_that("network cohesion works", { - expect_equal(as.numeric(net_cohesion(ison_southern_women)), 2) + expect_equal(as.numeric(net_by_cohesion(ison_southern_women)), 2) }) test_that("network adhesion works", { - expect_equal(as.numeric(net_adhesion(ison_southern_women)), 2) + expect_equal(as.numeric(net_by_adhesion(ison_southern_women)), 2) }) test_that("network diameter works", { - expect_equal(as.numeric(net_diameter(ison_southern_women)), 4) + expect_equal(as.numeric(net_by_diameter(ison_southern_women)), 4) }) test_that("network length works", { - expect_equal(as.numeric(net_length(ison_southern_women)), 2.306, + expect_equal(as.numeric(net_by_length(ison_southern_women)), 2.306, tolerance = 0.001) }) test_that("net_independence works", { - expect_values(net_independence(ison_adolescents), 4) + expect_values(net_by_independence(ison_adolescents), 4) }) test_that("net_strength works", { - expect_values(net_strength(ison_adolescents), 0.5) + expect_values(net_by_strength(ison_adolescents), 0.5) }) test_that("net_toughness works", { - expect_values(net_toughness(ison_adolescents), 0.5) + expect_values(net_by_toughness(ison_adolescents), 0.5) }) \ No newline at end of file diff --git a/tests/testthat/test-measure_diffusion.R b/tests/testthat/test-measure_diffusion.R index 91df04d..5dbbf69 100644 --- a/tests/testthat/test-measure_diffusion.R +++ b/tests/testthat/test-measure_diffusion.R @@ -1,9 +1,9 @@ test <- play_diffusion(create_tree(12), steps = 2) test_that("infection total works", { - expect_values(net_infection_total(test), 0.583) + expect_values(net_by_infection_total(test), 0.583) }) test_that("infection complete works", { - expect_values(net_infection_complete(test), Inf) + expect_values(net_by_infection_complete(test), Inf) }) diff --git a/tests/testthat/test-measure_features.R b/tests/testthat/test-measure_features.R index 670265c..5fcd274 100644 --- a/tests/testthat/test-measure_features.R +++ b/tests/testthat/test-measure_features.R @@ -9,30 +9,30 @@ set.seed(123) # }) test_that("net_modularity works for two mode networks", { - out <- net_modularity(ison_southern_women, + out <- net_by_modularity(ison_southern_women, node_in_partition(ison_southern_women)) expect_length(out, 1) }) test_that("net_core works", { - out <- net_core(ison_adolescents) + out <- net_by_core(ison_adolescents) expect_values(out, -0.133) - expect_values(net_core(ison_adolescents, method = "ident"), 6.481) - expect_values(net_core(ison_adolescents, method = "diff"), 6.094) + expect_values(net_by_core(ison_adolescents, method = "ident"), 6.481) + expect_values(net_by_core(ison_adolescents, method = "diff"), 6.094) }) test_that("net_richclub works", { - out <- net_richclub(ison_adolescents) + out <- net_by_richclub(ison_adolescents) expect_values(out, 0.833) }) test_that("net_scalefree works", { - out <- net_scalefree(ison_adolescents) + out <- net_by_scalefree(ison_adolescents) expect_values(out,3.689) }) test_that("net_balance works", { - out <- net_balance(irps_wwi) + out <- net_by_balance(irps_wwi) expect_values(out,1) }) @@ -41,6 +41,6 @@ wavenet <- ison_adolescents %>% test_that("net_waves works", { # expect_equal(net_waves(ison_adolescents), 1) - expect_equal(net_waves(wavenet), 3) + expect_values(net_by_waves(wavenet), 3) }) diff --git a/tests/testthat/test-measure_heterogeneity.R b/tests/testthat/test-measure_heterogeneity.R index 3d1705e..d3aab50 100644 --- a/tests/testthat/test-measure_heterogeneity.R +++ b/tests/testthat/test-measure_heterogeneity.R @@ -1,29 +1,29 @@ #*************** Test the heterogeneity family of functions ******************# test_that("diversity functions works", { - expect_equal(as.numeric(net_diversity(to_uniplex(fict_marvel,"relationship"), "Gender")), + expect_equal(as.numeric(net_by_diversity(to_uniplex(fict_marvel,"relationship"), "Gender")), 0.306, tolerance = 0.001) - expect_equal(top3(node_diversity(ison_lawfirm, "gender")), + expect_equal(top3(node_by_diversity(ison_lawfirm, "gender")), c(0.285, 0.375,0), tolerance = 0.01) }) test_that("heterophily function works", { - expect_equal(as.numeric(net_heterophily(ison_networkers, "Discipline")), .1704, tolerance = 0.001) - expect_length(node_heterophily(ison_networkers, "Discipline"), + expect_equal(as.numeric(net_by_heterophily(ison_networkers, "Discipline")), .1704, tolerance = 0.001) + expect_length(node_by_heterophily(ison_networkers, "Discipline"), net_nodes(ison_networkers)) - expect_s3_class(node_heterophily(ison_networkers, "Discipline"), "node_measure") + expect_s3_class(node_by_heterophily(ison_networkers, "Discipline"), "node_measure") }) test_that("assortativity function works", { - expect_length(net_assortativity(ison_networkers), 1) + expect_length(net_by_assortativity(ison_networkers), 1) }) test_that("richness function works", { - expect_length(net_richness(ison_networkers), 1) - expect_equal(as.numeric(net_richness(ison_networkers)), 3) - expect_length(node_richness(ison_networkers, "type"), 32) + expect_length(net_by_richness(ison_networkers), 1) + expect_equal(as.numeric(net_by_richness(ison_networkers)), 3) + expect_length(node_by_richness(ison_networkers, "type"), 32) }) test_that("net_spatial works", { - expect_values(net_spatial(ison_lawfirm, "age"), 0.126) + expect_values(net_by_spatial(ison_lawfirm, "age"), 0.126) }) \ No newline at end of file diff --git a/tests/testthat/test-measure_hierarchy.R b/tests/testthat/test-measure_hierarchy.R index 75e3090..ea5bdc2 100644 --- a/tests/testthat/test-measure_hierarchy.R +++ b/tests/testthat/test-measure_hierarchy.R @@ -1,7 +1,7 @@ # Test hierarchy measures test_that("net_connectedness works correctly", { - connect_judo <- net_connectedness(ison_judo_moves) + connect_judo <- net_by_connectedness(ison_judo_moves) # Basic functionality tests # Return type and range tests @@ -10,16 +10,16 @@ test_that("net_connectedness works correctly", { expect_true(as.numeric(connect_judo) <= 1) # Test with complete graph (should be 1) - expect_equal(round(as.numeric(net_connectedness(create_filled(5))), 4), 1) - expect_equal(round(as.numeric(net_connectedness(create_empty(5))), 4), 0) + expect_equal(round(as.numeric(net_by_connectedness(create_filled(5))), 4), 1) + expect_equal(round(as.numeric(net_by_connectedness(create_empty(5))), 4), 0) # Test edge case: single node - expect_false(is.finite(as.numeric(net_connectedness(create_empty(1))))) + expect_false(is.finite(as.numeric(net_by_connectedness(create_empty(1))))) }) test_that("net_efficiency works correctly", { # Basic functionality tests - effic_judo <- net_efficiency(ison_judo_moves) + effic_judo <- net_by_efficiency(ison_judo_moves) # Return type tests expect_true(is.numeric(as.numeric(effic_judo))) @@ -28,7 +28,7 @@ test_that("net_efficiency works correctly", { test_that("net_upperbound works correctly", { # Basic functionality tests - upper_judo <- net_efficiency(ison_judo_moves) + upper_judo <- net_by_efficiency(ison_judo_moves) # Return type and range tests expect_true(is.numeric(as.numeric(upper_judo))) @@ -38,11 +38,11 @@ test_that("net_upperbound works correctly", { # Test with perfect hierarchy (should approach 1) # Create a tournament-like structure perfect_hierarchy <- create_tree(5) - expect_equal(as.numeric(net_upperbound(perfect_hierarchy)), 1) + expect_equal(as.numeric(net_by_upperbound(perfect_hierarchy)), 1) }) -test_that("net_by_hierarchy works correctly", { - result <- net_by_hierarchy(ison_judo_moves) +test_that("net_x_hierarchy works correctly", { + result <- net_x_hierarchy(ison_judo_moves) # Basic functionality tests # Check that it returns a data frame with correct columns diff --git a/tests/testthat/test-measure_holes.R b/tests/testthat/test-measure_holes.R index d3b3e81..e22cba8 100644 --- a/tests/testthat/test-measure_holes.R +++ b/tests/testthat/test-measure_holes.R @@ -1,56 +1,52 @@ test_that("redundancy is reported correctly", { - expect_equal(as.numeric(length(node_redundancy(ison_brandes))), + expect_equal(as.numeric(length(node_by_redundancy(ison_brandes))), as.numeric(net_nodes(ison_brandes))) - expect_equal(as.numeric(length(node_redundancy(ison_southern_women))), + expect_equal(as.numeric(length(node_by_redundancy(ison_southern_women))), as.numeric(net_nodes(ison_southern_women))) - expect_named(node_redundancy(ison_southern_women)) + expect_named(node_by_redundancy(ison_southern_women)) }) test_that("effective size is calculated and reported correctly", { - expect_s3_class(node_effsize(ison_brandes), "node_measure") - expect_s3_class(node_effsize(ison_southern_women), "node_measure") - expect_equal(as.numeric(length(node_effsize(ison_brandes))), + expect_equal(as.numeric(length(node_by_effsize(ison_brandes))), as.numeric(net_nodes(ison_brandes))) - expect_equal(length(node_effsize(ison_southern_women)), + expect_equal(length(node_by_effsize(ison_southern_women)), c(net_nodes(ison_southern_women))) - expect_named(node_effsize(ison_southern_women)) - expect_equal(unname(node_effsize(ison_southern_women)[1:3]), c(2.5,1.38,2.46), tolerance = 0.01) + expect_named(node_by_effsize(ison_southern_women)) + expect_equal(unname(node_by_effsize(ison_southern_women)[1:3]), c(2.5,1.38,2.46), tolerance = 0.01) }) test_that("efficiency is reported correctly", { - expect_s3_class(node_efficiency(ison_brandes), "node_measure") - expect_s3_class(node_efficiency(ison_southern_women), "node_measure") - expect_equal(length(node_efficiency(ison_brandes)), c(net_nodes(ison_brandes))) - expect_equal(length(node_efficiency(ison_southern_women)), + expect_equal(length(node_by_efficiency(ison_brandes)), c(net_nodes(ison_brandes))) + expect_equal(length(node_by_efficiency(ison_southern_women)), c(net_nodes(ison_southern_women))) }) test_that("constraint scores are reported correctly for two-mode networks",{ - res <- node_constraint(ison_southern_women) + res <- node_by_constraint(ison_southern_women) expect_equal(top3(res), c(0.2782, 0.3071, 0.2965)) expect_output(print(res), "Evelyn") # expect_named(node_constraint(ison_southern_women)[1:3], c("Evelyn", "Laura", "Theresa")) }) test_that("constraint scores are reported correctly for one-mode notworks",{ - res <- node_constraint(ison_adolescents) + res <- node_by_constraint(ison_adolescents) expect_equal(round(unname(res[1:3]),2), c(1, .43, .57)) expect_output(print(res), "Alice") }) test_that("hierarchy is reported correctly", { - expect_s3_class(node_hierarchy(ison_brandes), "node_measure") - expect_s3_class(node_hierarchy(ison_southern_women), "node_measure") - expect_equal(length(node_hierarchy(ison_brandes)), c(net_nodes(ison_brandes))) - expect_equal(length(node_hierarchy(ison_southern_women)), + expect_s3_class(node_by_hierarchy(ison_brandes), "node_measure") + expect_s3_class(node_by_hierarchy(ison_southern_women), "node_measure") + expect_equal(length(node_by_hierarchy(ison_brandes)), c(net_nodes(ison_brandes))) + expect_equal(length(node_by_hierarchy(ison_southern_women)), c(net_nodes(ison_southern_women))) - expect_named(node_hierarchy(ison_southern_women)) + expect_named(node_by_hierarchy(ison_southern_women)) }) test_that("node_neighbours_degree works", { - expect_equal(top3(node_neighbours_degree(ison_adolescents)), c(4,2.75,3)) + expect_equal(top3(node_by_neighbours_degree(ison_adolescents)), c(4,2.75,3)) }) test_that("tie_cohesion works", { - expect_equal(top3(tie_cohesion(ison_adolescents)), c(0,0.5,0.3333)) + expect_equal(top3(tie_by_cohesion(ison_adolescents)), c(0,0.5,0.3333)) }) diff --git a/tests/testthat/test-member_components.R b/tests/testthat/test-member_components.R index ad795ad..877c331 100644 --- a/tests/testthat/test-member_components.R +++ b/tests/testthat/test-member_components.R @@ -3,7 +3,7 @@ test_that("node_in_component works", { node_in_component() expect_s3_class(comp, "node_member") expect_equal(length(unique(comp)), - c(net_components(to_uniplex(ison_monks, "esteem")))) + c(net_by_components(to_uniplex(ison_monks, "esteem")))) expect_equal(length(unique(comp)), length(unique(node_in_strong(to_uniplex(ison_monks, "esteem"))))) comp <- ison_monks %>% to_uniplex("esteem") %>% diff --git a/tests/testthat/test-member_core.R b/tests/testthat/test-member_core.R index 88a3942..8a57ca7 100644 --- a/tests/testthat/test-member_core.R +++ b/tests/testthat/test-member_core.R @@ -3,7 +3,7 @@ test_that("node_is_universal works", { }) test_that("node_kcoreness works", { - expect_equal(top3(node_kcoreness(ison_adolescents)), c(1,2,2)) + expect_equal(top3(node_by_kcoreness(ison_adolescents)), c(1,2,2)) }) test_that("node_in_core works", { From 86a7a82d2cadea51155205c91566ee81c36536a7 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 14 Mar 2026 11:33:16 +0100 Subject: [PATCH 90/98] net_by_smallworld() now uses netrics functions --- R/measure_features.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/measure_features.R b/R/measure_features.R index 8cf3c1a..49b8372 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -296,12 +296,12 @@ net_by_smallworld <- function(.data, } } - lo <- manynet::net_length(.data) + lo <- net_by_length(.data) lr <- mean(vapply(1:times, - function(x) manynet::net_length(manynet::generate_random(.data)), + function(x) net_by_length(manynet::generate_random(.data)), FUN.VALUE = numeric(1))) if(method == "SWI"){ - ll <- manynet::net_length(manynet::create_ring(.data)) + ll <- net_by_length(manynet::create_ring(.data)) } out <- switch(method, From 36b2a539a3a293b646e8842bb5958bec01e00d92 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 14 Mar 2026 11:33:36 +0100 Subject: [PATCH 91/98] node_by_bridges() now uses netrics functions --- R/measure_holes.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/measure_holes.R b/R/measure_holes.R index b6d4c6d..de02b3c 100644 --- a/R/measure_holes.R +++ b/R/measure_holes.R @@ -51,7 +51,7 @@ node_by_bridges <- function(.data){ g <- manynet::as_igraph(.data) .inc <- NULL out <- vapply(igraph::V(g), function(ego){ - length(igraph::E(g)[.inc(ego) & manynet::tie_is_bridge(g)==1]) + length(igraph::E(g)[.inc(ego) & tie_is_bridge(g)==1]) }, FUN.VALUE = numeric(1)) make_node_measure(out, .data) } From fd74c3b940774c9e6756c6a76cc93871add16890 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 14 Mar 2026 11:33:52 +0100 Subject: [PATCH 92/98] node_in_community() now uses netrics functions --- R/member_community.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/member_community.R b/R/member_community.R index 7edc9b6..feb08d8 100644 --- a/R/member_community.R +++ b/R/member_community.R @@ -48,7 +48,7 @@ node_in_community <- function(.data){ # don't use node_in_betweenness because slow and poorer quality to optimal manynet::snet_success("{.fn node_in_optimal} available and", "will return the highest modularity partition.") - manynet::node_in_optimal(.data) + netrics::node_in_optimal(.data) } else { manynet::snet_info("Excluding {.fn node_in_optimal} because network rather large.") poss_algs <- c("node_in_infomap", From 40c30f81b521fa42b9effd444f6bca72d8b159e7 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 14 Mar 2026 12:02:34 +0100 Subject: [PATCH 93/98] Fixed documentation issues --- R/measure_centrality.R | 8 ++++---- R/measure_diffusion.R | 2 +- R/measure_features.R | 4 ++-- R/measure_hierarchy.R | 8 ++++---- man/measure_central_between.Rd | 4 ++-- man/measure_central_close.Rd | 2 +- man/measure_central_degree.Rd | 2 +- man/measure_diffusion_infection.Rd | 2 +- man/measure_features.Rd | 4 ++-- man/measure_hierarchy.Rd | 8 ++++---- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index 8c71945..60b5db4 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -82,7 +82,7 @@ #' _Social Networks_ 32, 245-251. #' \doi{10.1016/j.socnet.2010.03.006} #' @examples -#' node_degree(ison_southern_women) +#' node_by_degree(ison_southern_women) #' @return Depending on how and what kind of an object is passed to the function, #' the function will return a `tidygraph` object where the nodes have been updated NULL @@ -441,7 +441,7 @@ node_by_stress <- function(.data, normalized = TRUE){ #' @rdname measure_central_between #' @importFrom igraph edge_betweenness #' @examples -#' (tb <- tie_betweenness(ison_adolescents)) +#' (tb <- tie_by_betweenness(ison_adolescents)) #' ison_adolescents %>% mutate_ties(weight = tb) #' @export tie_by_betweenness <- function(.data, normalized = TRUE){ @@ -456,7 +456,7 @@ tie_by_betweenness <- function(.data, normalized = TRUE){ #' @rdname measure_central_between #' @examples -#' net_betweenness(ison_southern_women, direction = "in") +#' net_by_betweenness(ison_southern_women, direction = "in") #' @export net_by_betweenness <- function(.data, normalized = TRUE, direction = c("all", "out", "in")) { @@ -853,7 +853,7 @@ node_by_randomwalk <- function(.data, normalized = TRUE){ #' @rdname measure_central_close #' @examples -#' (ec <- tie_closeness(ison_adolescents)) +#' (ec <- tie_by_closeness(ison_adolescents)) #' ison_adolescents %>% mutate_ties(weight = ec) #' @export tie_by_closeness <- function(.data, normalized = TRUE){ diff --git a/R/measure_diffusion.R b/R/measure_diffusion.R index 75442a8..0c55e4a 100644 --- a/R/measure_diffusion.R +++ b/R/measure_diffusion.R @@ -216,7 +216,7 @@ net_by_immunity <- function(.data, normalized = TRUE){ #' @examples #' smeg <- generate_smallworld(15, 0.025) #' smeg_diff <- play_diffusion(smeg) -#' net_infection_complete(smeg_diff) +#' net_by_infection_complete(smeg_diff) #' @export net_by_infection_complete <- function(.data){ diff_model <- manynet::as_diffusion(.data) diff --git a/R/measure_features.R b/R/measure_features.R index 49b8372..703531d 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -186,9 +186,9 @@ net_by_factions <- function(.data, #' The higher this parameter, the more smaller communities will be privileged. #' The lower this parameter, the fewer larger communities are likely to be found. #' @examples -#' net_modularity(ison_adolescents, +#' net_by_modularity(ison_adolescents, #' node_in_partition(ison_adolescents)) -#' net_modularity(ison_southern_women, +#' net_by_modularity(ison_southern_women, #' node_in_partition(ison_southern_women)) #' @references #' ## On modularity diff --git a/R/measure_hierarchy.R b/R/measure_hierarchy.R index 9d8c7d6..fd4fc4e 100644 --- a/R/measure_hierarchy.R +++ b/R/measure_hierarchy.R @@ -76,10 +76,10 @@ net_x_hierarchy <- function(.data){ #' _Social Networks_, 34: 159-163. #' \doi{10.1016/j.socnet.2011.10.006} #' @examples -#' net_connectedness(ison_networkers) -#' 1 - net_reciprocity(ison_networkers) -#' net_efficiency(ison_networkers) -#' net_upperbound(ison_networkers) +#' net_by_connectedness(ison_networkers) +#' 1 - net_by_reciprocity(ison_networkers) +#' net_by_efficiency(ison_networkers) +#' net_by_upperbound(ison_networkers) NULL #' @rdname measure_hierarchy diff --git a/man/measure_central_between.Rd b/man/measure_central_between.Rd index 9ee90d4..4178777 100644 --- a/man/measure_central_between.Rd +++ b/man/measure_central_between.Rd @@ -101,9 +101,9 @@ nodes, and thus associated with bridging or spanning boundaries. \examples{ node_by_betweenness(ison_southern_women) node_by_induced(ison_adolescents) -(tb <- tie_betweenness(ison_adolescents)) +(tb <- tie_by_betweenness(ison_adolescents)) ison_adolescents \%>\% mutate_ties(weight = tb) -net_betweenness(ison_southern_women, direction = "in") +net_by_betweenness(ison_southern_women, direction = "in") } \references{ \subsection{On betweenness centrality}{ diff --git a/man/measure_central_close.Rd b/man/measure_central_close.Rd index a38e1dd..19d93fc 100644 --- a/man/measure_central_close.Rd +++ b/man/measure_central_close.Rd @@ -192,7 +192,7 @@ where \eqn{H_{ji}} is the hitting time from node \eqn{j} to node \eqn{i}. \examples{ node_by_closeness(ison_southern_women) node_by_reach(ison_adolescents) -(ec <- tie_closeness(ison_adolescents)) +(ec <- tie_by_closeness(ison_adolescents)) ison_adolescents \%>\% mutate_ties(weight = ec) net_by_closeness(ison_southern_women, direction = "in") } diff --git a/man/measure_central_degree.Rd b/man/measure_central_degree.Rd index b2625e6..c458233 100644 --- a/man/measure_central_degree.Rd +++ b/man/measure_central_degree.Rd @@ -136,7 +136,7 @@ neighbours, \eqn{J}: } \examples{ -node_degree(ison_southern_women) +node_by_degree(ison_southern_women) tie_by_degree(ison_adolescents) net_by_degree(ison_southern_women, direction = "in") } diff --git a/man/measure_diffusion_infection.Rd b/man/measure_diffusion_infection.Rd index 54beb20..43db7fb 100644 --- a/man/measure_diffusion_infection.Rd +++ b/man/measure_diffusion_infection.Rd @@ -43,7 +43,7 @@ highest infection rate is observed. \examples{ smeg <- generate_smallworld(15, 0.025) smeg_diff <- play_diffusion(smeg) - net_infection_complete(smeg_diff) + net_by_infection_complete(smeg_diff) net_by_infection_total(smeg_diff) net_by_infection_peak(smeg_diff) } diff --git a/man/measure_features.Rd b/man/measure_features.Rd index a1614c9..4dc1322 100644 --- a/man/measure_features.Rd +++ b/man/measure_features.Rd @@ -132,9 +132,9 @@ net_by_core(ison_adolescents) net_by_core(ison_southern_women) net_by_richclub(ison_adolescents) net_by_factions(ison_southern_women) -net_modularity(ison_adolescents, +net_by_modularity(ison_adolescents, node_in_partition(ison_adolescents)) -net_modularity(ison_southern_women, +net_by_modularity(ison_southern_women, node_in_partition(ison_southern_women)) net_by_smallworld(ison_brandes) net_by_smallworld(ison_southern_women) diff --git a/man/measure_hierarchy.Rd b/man/measure_hierarchy.Rd index 428d6cb..3f9ca79 100644 --- a/man/measure_hierarchy.Rd +++ b/man/measure_hierarchy.Rd @@ -30,10 +30,10 @@ or the degree to which network is a single component. } } \examples{ -net_connectedness(ison_networkers) -1 - net_reciprocity(ison_networkers) -net_efficiency(ison_networkers) -net_upperbound(ison_networkers) +net_by_connectedness(ison_networkers) +1 - net_by_reciprocity(ison_networkers) +net_by_efficiency(ison_networkers) +net_by_upperbound(ison_networkers) } \references{ \subsection{On hierarchy}{ From 3d46c4f2c3ada12926c8dd3511ebc4644d5644be Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sun, 15 Mar 2026 10:32:39 +0100 Subject: [PATCH 94/98] Added some thematic structure in analysis section in README --- R/measure_holes.R | 3 +- README.Rmd | 46 ++++++++---- README.md | 185 +++++++++++++++++++++++++++++----------------- 3 files changed, 149 insertions(+), 85 deletions(-) diff --git a/R/measure_holes.R b/R/measure_holes.R index de02b3c..1ca28ac 100644 --- a/R/measure_holes.R +++ b/R/measure_holes.R @@ -190,8 +190,7 @@ node_by_constraint <- function(.data) { nodes = igraph::V(.data), weights = NULL) } - res <- make_node_measure(res, .data) - res + make_node_measure(res, .data) } #' @rdname measure_holes diff --git a/README.Rmd b/README.Rmd index 2370fb3..8134e15 100644 --- a/README.Rmd +++ b/README.Rmd @@ -49,8 +49,9 @@ see [`{migraph}`](https://stocnet.github.io/migraph/). - [Marking](#marking) - [Measures](#measures) -- [Motifs](#motifs) - [Memberships](#memberships) +- [Motifs](#motifs) +- [Analysis](#analysis) - [Installation](#installation) - [Stable](#stable) - [Development](#development) @@ -84,12 +85,13 @@ These include: - `r list_functions("_by_")` -## Motifs - -`{netrics}`'s `*_x_*()` functions tabulate nodes' and networks' frequency in various motifs. -These include: - -- `r list_functions("_x_")` +The measures are organised into several broad categories, including: +_Centrality_, _Cohesion_, _Hierarchy_, _Innovation_ (structural holes), +_Diversity_ (heterogeneity), _Topology_ (features), and _Diffusion_. +Each measure recognises whether the network is directed or undirected, +weighted or unweighted, one-mode or two-mode, +and returns normalized values wherever possible. +We recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. ## Memberships @@ -100,7 +102,7 @@ indicating e.g. that the first node is a member of group "A", the second in grou - `r list_functions("_in_")` -For example `node_brokerage_census()` returns +For example `node_in_brokering()` returns the frequency of nodes' participation in Gould-Fernandez brokerage roles for a one-mode network, and the Jasny-Lubell brokerage roles for a two-mode network. @@ -115,13 +117,27 @@ as well as elbow, silhouette, and strict methods for _k_-cluster selection. such as typical community detection algorithms, as well as component and core-periphery partitioning algorithms. -The measures are organised into several broad categories, including: -_Centrality_, _Cohesion_, _Hierarchy_, _Innovation_ (structural holes), -_Diversity_ (heterogeneity), _Topology_ (features), and _Diffusion_. -Each measure recognises whether the network is directed or undirected, -weighted or unweighted, one-mode or two-mode, -and returns normalized values wherever possible. -We recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. +## Motifs + +`{netrics}`'s `*_x_*()` functions tabulate nodes' and networks' frequency in various motifs. +These include: + +- `r list_functions("_x_")` + +## Analysis + +The functions in `{netrics}` are designed to answer a wide variety of analytic questions about networks. +For example, you might want to know about: + +- _Centrality_: `r list_functions("degree|betweenness|closeness|eigenvector")` +- _Cohesion_: `r list_functions("density|reciprocity|transitivity|equivalency|congruency")` +- _Hierarchy_: `r list_functions("hierarchy|connectedness|upper|efficiency|reciprocity")` +- _Innovation_: `r list_functions("hole|redundancy|constraint|effsize")` +- _Diversity_: `r list_functions("diversity|phily|richness|assort")` +- _Topology_: `r list_functions("core|factions|modularity|smallworld|balance|richclub")` +- _Resilience_: `r list_functions("cutpoint|bridge|hesion|articul")` +- _Brokerage_: `r list_functions("broke")` +- _Diffusion_: `r list_functions("adopt|infect|expos")` ## Installation diff --git a/README.md b/README.md index 0db54bf..d33818f 100644 --- a/README.md +++ b/README.md @@ -20,24 +20,29 @@ coverage](https://codecov.io/gh/stocnet/netrics/branch/main/graph/badge.svg)](ht ## About the package -`{netrics}` is the analytic engine of the [stocnet](https://github.com/stocnet) ecosystem. -It provides *many* tools for marking, measuring, and identifying nodes’ motifs and memberships -in *many* (if not most) types and kinds of networks. -All functions operate on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects, -and recognise directed, weighted, multiplex, multimodal, signed, and other types of networks. - -`{netrics}` depends on [`{manynet}`](https://stocnet.github.io/manynet/), -which handles working with different network classes, converting between them, and modifying them. -Network-level logical tests (e.g. `is_directed()`, `is_twomode()`) remain in `{manynet}`, -while node- and tie-level analytic marks (e.g. `node_is_*()`, `tie_is_*()`) are in `{netrics}`. -For graph drawing, see [`{autograph}`](https://stocnet.github.io/autograph/), -and for further testing and modelling capabilities -see [`{migraph}`](https://stocnet.github.io/migraph/). +`{netrics}` is the analytic engine of the +[stocnet](https://github.com/stocnet) ecosystem. It provides *many* +tools for marking, measuring, and identifying nodes’ motifs and +memberships in *many* (if not most) types and kinds of networks. All +functions operate on matrices, `{igraph}`, `{network}`, `{tidygraph}`, +and `mnet` class objects, and recognise directed, weighted, multiplex, +multimodal, signed, and other types of networks. + +`{netrics}` depends on +[`{manynet}`](https://stocnet.github.io/manynet/), which handles working +with different network classes, converting between them, and modifying +them. Network-level logical tests (e.g. `is_directed()`, `is_twomode()`) +remain in `{manynet}`, while node- and tie-level analytic marks +(e.g. `node_is_*()`, `tie_is_*()`) are in `{netrics}`. For graph +drawing, see [`{autograph}`](https://stocnet.github.io/autograph/), and +for further testing and modelling capabilities see +[`{migraph}`](https://stocnet.github.io/migraph/). - [Marking](#marking) - [Measures](#measures) -- [Motifs](#motifs) - [Memberships](#memberships) +- [Motifs](#motifs) +- [Analysis](#analysis) - [Installation](#installation) - [Stable](#stable) - [Development](#development) @@ -51,11 +56,12 @@ own pretty `print()` and `plot()` methods: marks, measures, motifs, and memberships. Marks are logical scalars or vectors, measures are numeric, memberships categorical, and motifs result in tabular outputs. -`{netrics}`’s `node_is_*()` and `tie_is_*()` functions offer fast logical tests -of node- and tie-level properties. -`node_is_*()` returns a logical vector the length of the number of nodes in the network, -and `tie_is_*()` returns a logical vector the length of the number of ties in the network. -Note that network-level tests such as `is_directed()` and `is_twomode()` are in `{manynet}`. +`{netrics}`’s `node_is_*()` and `tie_is_*()` functions offer fast +logical tests of node- and tie-level properties. `node_is_*()` returns a +logical vector the length of the number of nodes in the network, and +`tie_is_*()` returns a logical vector the length of the number of ties +in the network. Note that network-level tests such as `is_directed()` +and `is_twomode()` are in `{manynet}`. - `node_is_core()`, `node_is_cutpoint()`, `node_is_exposed()`, `node_is_fold()`, `node_is_independent()`, `node_is_infected()`, @@ -64,8 +70,8 @@ Note that network-level tests such as `is_directed()` and `is_twomode()` are in `node_is_neighbor()`, `node_is_pendant()`, `node_is_random()`, `node_is_recovered()`, `node_is_universal()` - `tie_is_bridge()`, `tie_is_cyclical()`, `tie_is_feedback()`, - `tie_is_forbidden()`, `tie_is_imbalanced()`, `tie_is_loop()`, - `tie_is_max()`, `tie_is_min()`, `tie_is_multiple()`, `tie_is_path()`, + `tie_is_imbalanced()`, `tie_is_loop()`, `tie_is_max()`, + `tie_is_min()`, `tie_is_multiple()`, `tie_is_path()`, `tie_is_random()`, `tie_is_reciprocated()`, `tie_is_simmelian()`, `tie_is_transitive()`, `tie_is_triangular()`, `tie_is_triplet()` @@ -75,8 +81,8 @@ maximum or minimum, respectively, node or tie according to some measure ## Measures -`{netrics}`‘s `*_by_*()` functions offer numeric measures at the network, node, and tie level. -These include: +`{netrics}`’s `*_by_*()` functions offer numeric measures at the +network, node, and tie level. These include: - `net_by_adhesion()`, `net_by_assortativity()`, `net_by_balance()`, `net_by_betweenness()`, `net_by_change()`, `net_by_closeness()`, @@ -85,8 +91,8 @@ These include: `net_by_degree()`, `net_by_density()`, `net_by_diameter()`, `net_by_diversity()`, `net_by_efficiency()`, `net_by_eigenvector()`, `net_by_equivalency()`, `net_by_factions()`, `net_by_harmonic()`, - `net_by_heterophily()`, `net_by_hierarchy()`, `net_by_homophily()`, - `net_by_immunity()`, `net_by_indegree()`, `net_by_independence()`, + `net_by_heterophily()`, `net_by_homophily()`, `net_by_immunity()`, + `net_by_indegree()`, `net_by_independence()`, `net_by_infection_complete()`, `net_by_infection_peak()`, `net_by_infection_total()`, `net_by_length()`, `net_by_modularity()`, `net_by_outdegree()`, `net_by_reach()`, `net_by_reciprocity()`, @@ -116,15 +122,14 @@ These include: `tie_by_betweenness()`, `tie_by_closeness()`, `tie_by_cohesion()`, `tie_by_degree()`, `tie_by_eigenvector()` -## Motifs - -`{netrics}`’s `*_x_*()` functions tabulate nodes’ and networks’ frequency in various motifs. -These include: - -- `net_x_brokerage()`, `net_x_dyad()`, `net_x_hazard()`, - `net_x_mixed()`, `net_x_tetrad()`, `net_x_triad()`, - `node_x_brokerage()`, `node_x_dyad()`, `node_x_exposure()`, - `node_x_path()`, `node_x_tetrad()`, `node_x_tie()`, `node_x_triad()` +The measures are organised into several broad categories, including: +*Centrality*, *Cohesion*, *Hierarchy*, *Innovation* (structural holes), +*Diversity* (heterogeneity), *Topology* (features), and *Diffusion*. +Each measure recognises whether the network is directed or undirected, +weighted or unweighted, one-mode or two-mode, and returns normalized +values wherever possible. We recommend you explore [the list of +functions](https://stocnet.github.io/netrics/reference/index.html) to +find out more. ## Memberships @@ -142,7 +147,7 @@ member of group “A”, the second in group “B”, etc. `node_in_spinglass()`, `node_in_strong()`, `node_in_structural()`, `node_in_walktrap()`, `node_in_weak()` -For example `node_brokerage_census()` returns the frequency of nodes’ +For example `node_in_brokering()` returns the frequency of nodes’ participation in Gould-Fernandez brokerage roles for a one-mode network, and the Jasny-Lubell brokerage roles for a two-mode network. @@ -156,13 +161,58 @@ as elbow, silhouette, and strict methods for *k*-cluster selection. bases, such as typical community detection algorithms, as well as component and core-periphery partitioning algorithms. -The measures are organised into several broad categories, including: -*Centrality*, *Cohesion*, *Hierarchy*, *Innovation* (structural holes), -*Diversity* (heterogeneity), *Topology* (features), and *Diffusion*. -Each measure recognises whether the network is directed or undirected, -weighted or unweighted, one-mode or two-mode, -and returns normalized values wherever possible. -We recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. +## Motifs + +`{netrics}`‘s `*_x_*()` functions tabulate nodes’ and networks’ +frequency in various motifs. These include: + +- `net_x_brokerage()`, `net_x_dyad()`, `net_x_hazard()`, + `net_x_hierarchy()`, `net_x_mixed()`, `net_x_tetrad()`, + `net_x_triad()`, `node_x_brokerage()`, `node_x_dyad()`, + `node_x_exposure()`, `node_x_path()`, `node_x_tetrad()`, + `node_x_tie()`, `node_x_triad()` + +## Analysis + +The functions in `{netrics}` are designed to answer a wide variety of +analytic questions about networks. For example, you might want to know +about: + +- *Centrality*: `net_by_betweenness()`, `net_by_closeness()`, + `net_by_degree()`, `net_by_eigenvector()`, `net_by_indegree()`, + `net_by_outdegree()`, `node_by_betweenness()`, `node_by_closeness()`, + `node_by_degree()`, `node_by_eigenvector()`, `node_by_indegree()`, + `node_by_multidegree()`, `node_by_neighbours_degree()`, + `node_by_outdegree()`, `node_in_betweenness()`, + `tie_by_betweenness()`, `tie_by_closeness()`, `tie_by_degree()`, + `tie_by_eigenvector()` +- *Cohesion*: `net_by_congruency()`, `net_by_density()`, + `net_by_equivalency()`, `net_by_reciprocity()`, + `net_by_transitivity()`, `node_by_equivalency()`, + `node_by_reciprocity()`, `node_by_transitivity()` +- *Hierarchy*: `net_by_connectedness()`, `net_by_efficiency()`, + `net_by_reciprocity()`, `net_by_upperbound()`, `net_x_hierarchy()`, + `node_by_efficiency()`, `node_by_hierarchy()`, `node_by_reciprocity()` +- *Innovation*: `node_by_constraint()`, `node_by_effsize()`, + `node_by_redundancy()` +- *Diversity*: `net_by_assortativity()`, `net_by_diversity()`, + `net_by_heterophily()`, `net_by_homophily()`, `net_by_richness()`, + `node_by_diversity()`, `node_by_heterophily()`, `node_by_homophily()`, + `node_by_richness()` +- *Topology*: `net_by_balance()`, `net_by_core()`, `net_by_factions()`, + `net_by_modularity()`, `net_by_richclub()`, `net_by_smallworld()`, + `node_by_coreness()`, `node_by_kcoreness()`, `node_in_core()`, + `node_is_core()`, `tie_is_imbalanced()` +- *Resilience*: `net_by_adhesion()`, `net_by_cohesion()`, + `node_by_bridges()`, `node_is_cutpoint()`, `tie_by_cohesion()`, + `tie_is_bridge()` +- *Brokerage*: `net_x_brokerage()`, `node_by_brokering_activity()`, + `node_by_brokering_exclusivity()`, `node_in_brokering()`, + `node_x_brokerage()` +- *Diffusion*: `net_by_infection_complete()`, `net_by_infection_peak()`, + `net_by_infection_total()`, `node_by_adoption_time()`, + `node_by_exposure()`, `node_in_adopter()`, `node_is_exposed()`, + `node_is_infected()`, `node_x_exposure()` ## Installation @@ -209,33 +259,32 @@ Those using Mac computers may also install using Macports: ## Relationship to other packages -`{netrics}` is part of the [stocnet](https://github.com/stocnet) ecosystem of R packages -for network analysis. -The packages are designed to be modular, with clear roles and dependencies: - -- [`{manynet}`](https://stocnet.github.io/manynet/): - The foundation package for working with network data. - It handles network classes (matrices, `{igraph}`, `{network}`, `{tidygraph}`, `mnet`), - coercion between them, modification, and network-level logical tests (`is_*()` functions). -- **`{netrics}`**: - The analytic package containing all measures (`*_by_*()` functions), - memberships (`*_in_*()` functions), motifs (`*_x_*()` functions), - and node- and tie-level marks (`node_is_*()`, `tie_is_*()` functions). - `{netrics}` depends on `{manynet}`. -- [`{autograph}`](https://stocnet.github.io/autograph/): - The graph drawing package. - `{autograph}` depends on both `{manynet}` (for network classes) - and `{netrics}` (for analytic results to visualise), +`{netrics}` is part of the [stocnet](https://github.com/stocnet) +ecosystem of R packages for network analysis. The packages are designed +to be modular, with clear roles and dependencies: + +- [`{manynet}`](https://stocnet.github.io/manynet/): The foundation + package for working with network data. It handles network classes + (matrices, `{igraph}`, `{network}`, `{tidygraph}`, `mnet`), coercion + between them, modification, and network-level logical tests (`is_*()` + functions). +- **`{netrics}`**: The analytic package containing all measures + (`*_by_*()` functions), memberships (`*_in_*()` functions), motifs + (`*_x_*()` functions), and node- and tie-level marks (`node_is_*()`, + `tie_is_*()` functions). `{netrics}` depends on `{manynet}`. +- [`{autograph}`](https://stocnet.github.io/autograph/): The graph + drawing package. `{autograph}` depends on both `{manynet}` (for + network classes) and `{netrics}` (for analytic results to visualise), since it would typically be used with both. -- [`{migraph}`](https://stocnet.github.io/migraph/): - The modelling and testing package, - building on both `{manynet}` and `{netrics}`. - -Node- and tie-level marks such as `node_is_cutpoint()` and `tie_is_bridge()` are kept -in `{netrics}` rather than `{manynet}` because they are analytic functions that identify -structural positions in the network. -Network-level property tests like `is_directed()` remain in `{manynet}` -because they describe the type of data rather than an analytic result. +- [`{migraph}`](https://stocnet.github.io/migraph/): The modelling and + testing package, building on both `{manynet}` and `{netrics}`. + +Node- and tie-level marks such as `node_is_cutpoint()` and +`tie_is_bridge()` are kept in `{netrics}` rather than `{manynet}` +because they are analytic functions that identify structural positions +in the network. Network-level property tests like `is_directed()` remain +in `{manynet}` because they describe the type of data rather than an +analytic result. ## Funding details From c7a677747f8706a017c8788e2dd8a299a5da0297 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Mar 2026 16:40:57 +0000 Subject: [PATCH 95/98] Initial plan From 90e00b62649beb210838aee8560cd0f2e2669679 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Mar 2026 16:43:58 +0000 Subject: [PATCH 96/98] Add NEWS.md for v0.1.0 release Co-authored-by: jhollway <5595229+jhollway@users.noreply.github.com> --- NEWS.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 NEWS.md diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..5c38977 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,121 @@ +# netrics 0.1.0 + +## Release notes + +`{netrics}` 0.1.0 is the first formal release of the package as a standalone +analytic engine for the [stocnet](https://github.com/stocnet) ecosystem. +The analytic functions — marks, measures, motifs, and memberships — have been +extracted from `{manynet}` and `{migraph}` into this dedicated package, with +consistent naming conventions and a range of bug fixes. + +## New naming conventions + +All functions now follow a consistent verb–object–qualifier naming scheme: + +- **Marks** (`node_is_*()`, `tie_is_*()`): logical vectors identifying which nodes + or ties hold a particular structural property. +- **Measures** (`*_by_*()`): numeric vectors at the network (`net_by_*()`), + node (`node_by_*()`), or tie (`tie_by_*()`) level. +- **Motifs** (`*_x_*()`): tabular counts of nodes' or networks' participation + in structural sub-patterns. +- **Memberships** (`*_in_*()`): categorical vectors assigning nodes to groups + (components, communities, equivalence classes, etc.). + +Functions previously named with other prefixes (e.g. `node_centrality_*`, +`net_cohesion_*`, `node_equivalency_*`) have been renamed to follow the +`*_by_*()` / `*_x_*()` / `*_in_*()` convention. +`tie_by_cohesion()` now correctly returns a `tie_measure` class object. + +## Functions moved from `{manynet}` / `{migraph}` + +The following groups of functions have been moved into `{netrics}`: + +### Marks +- `node_is_core()`, `node_is_cutpoint()`, `node_is_exposed()`, + `node_is_fold()`, `node_is_independent()`, `node_is_infected()`, + `node_is_isolate()`, `node_is_latent()`, `node_is_max()`, + `node_is_mean()`, `node_is_mentor()`, `node_is_min()`, + `node_is_neighbor()`, `node_is_pendant()`, `node_is_random()`, + `node_is_recovered()`, `node_is_universal()` +- `tie_is_bridge()`, `tie_is_cyclical()`, `tie_is_feedback()`, + `tie_is_imbalanced()`, `tie_is_loop()`, `tie_is_max()`, + `tie_is_min()`, `tie_is_multiple()`, `tie_is_path()`, + `tie_is_random()`, `tie_is_reciprocated()`, `tie_is_simmelian()`, + `tie_is_transitive()`, `tie_is_triangular()`, `tie_is_triplet()` + +### Measures +- Network-level: `net_by_adhesion()`, `net_by_assortativity()`, + `net_by_balance()`, `net_by_betweenness()`, `net_by_change()`, + `net_by_closeness()`, `net_by_cohesion()`, `net_by_components()`, + `net_by_congruency()`, `net_by_connectedness()`, `net_by_core()`, + `net_by_correlation()`, `net_by_degree()`, `net_by_density()`, + `net_by_diameter()`, `net_by_diversity()`, `net_by_efficiency()`, + `net_by_eigenvector()`, `net_by_equivalency()`, `net_by_factions()`, + `net_by_harmonic()`, `net_by_heterophily()`, `net_by_hierarchy()`, + `net_by_homophily()`, `net_by_immunity()`, `net_by_indegree()`, + `net_by_independence()`, `net_by_infection_complete()`, + `net_by_infection_peak()`, `net_by_infection_total()`, + `net_by_length()`, `net_by_modularity()`, `net_by_outdegree()`, + `net_by_reach()`, `net_by_reciprocity()`, `net_by_recovery()`, + `net_by_reproduction()`, `net_by_richclub()`, `net_by_richness()`, + `net_by_scalefree()`, `net_by_smallworld()`, `net_by_spatial()`, + `net_by_stability()`, `net_by_strength()`, `net_by_toughness()`, + `net_by_transitivity()`, `net_by_transmissibility()`, + `net_by_upperbound()`, `net_by_waves()` +- Node-level: `node_by_adoption_time()`, `node_by_alpha()`, + `node_by_authority()`, `node_by_betweenness()`, `node_by_bridges()`, + `node_by_brokering_activity()`, `node_by_brokering_exclusivity()`, + `node_by_closeness()`, `node_by_constraint()`, `node_by_coreness()`, + `node_by_deg()`, `node_by_degree()`, `node_by_distance()`, + `node_by_diversity()`, `node_by_eccentricity()`, + `node_by_efficiency()`, `node_by_effsize()`, `node_by_eigenvector()`, + `node_by_equivalency()`, `node_by_exposure()`, `node_by_flow()`, + `node_by_harmonic()`, `node_by_heterophily()`, `node_by_hierarchy()`, + `node_by_homophily()`, `node_by_hub()`, `node_by_indegree()`, + `node_by_induced()`, `node_by_information()`, `node_by_kcoreness()`, + `node_by_leverage()`, `node_by_multidegree()`, + `node_by_neighbours_degree()`, `node_by_outdegree()`, + `node_by_pagerank()`, `node_by_posneg()`, `node_by_power()`, + `node_by_randomwalk()`, `node_by_reach()`, `node_by_reciprocity()`, + `node_by_recovery()`, `node_by_redundancy()`, `node_by_richness()`, + `node_by_stress()`, `node_by_subgraph()`, `node_by_thresholds()`, + `node_by_transitivity()`, `node_by_vitality()` +- Tie-level: `tie_by_betweenness()`, `tie_by_closeness()`, + `tie_by_cohesion()`, `tie_by_degree()`, `tie_by_eigenvector()` + +### Motifs +- `net_x_brokerage()`, `net_x_dyad()`, `net_x_hazard()`, + `net_x_mixed()`, `net_x_tetrad()`, `net_x_triad()` +- `node_x_brokerage()`, `node_x_dyad()`, `node_x_exposure()`, + `node_x_path()`, `node_x_tetrad()`, `node_x_tie()`, `node_x_triad()` + +### Memberships +- `node_in_adopter()`, `node_in_automorphic()`, + `node_in_betweenness()`, `node_in_brokering()`, + `node_in_community()`, `node_in_component()`, `node_in_core()`, + `node_in_eigen()`, `node_in_equivalence()`, `node_in_fluid()`, + `node_in_greedy()`, `node_in_infomap()`, `node_in_leiden()`, + `node_in_louvain()`, `node_in_optimal()`, `node_in_partition()`, + `node_in_regular()`, `node_in_roulette()`, `node_in_spinglass()`, + `node_in_strong()`, `node_in_structural()`, `node_in_walktrap()`, + `node_in_weak()` + +## Bug fixes + +- `node_is_isolate()` and `node_is_pendant()` now work correctly with signed networks. +- `tie_is_random()` now correctly returns a `tie_mark` class object (previously returned a node mark). +- `node_by_authority()` and `node_by_hub()` updated to use current `{igraph}` API. +- `node_by_brokering_activity()` and `node_by_brokering_exclusivity()` now handle unlabelled networks correctly. +- `node_by_homophily()` no longer resolves the attribute to a vector prematurely. +- `node_by_pagerank()` updated to correctly extract the vector output from `{igraph}`. +- `node_by_power()` reverts to a lower exponent (closer to degree centrality) when there is no degree variation. +- `node_by_randomwalk()` now works with two-mode networks. +- `net_by_degree()`, `net_by_harmonic()`, and `net_by_reach()` now consistently include the function call in the returned object. +- `net_by_richclub()` returns 0 (rather than erroring) when all nodes have equivalent degree. +- `net_by_smallworld()` and `node_by_bridges()` now use internal `{netrics}` functions rather than `{manynet}` equivalents. +- `net_by_waves()` returns 1 for cross-sectional networks and correctly returns a network measure class. +- `net_x_hierarchy()` correctly classified as a motif function. +- `node_in_community()` now delegates to `{netrics}` membership functions internally. +- Dyad census fixed to handle two-mode networks. +- Equivalence *k*-assignment fixed for the degenerate case where every node is placed in the same cluster. +- `tie_by_cohesion()` now correctly returns a `tie_measure` class object. From 526429c5baf347fe12ba2296564108c8e6556503 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sun, 15 Mar 2026 17:48:25 +0100 Subject: [PATCH 97/98] Updated date --- DESCRIPTION | 2 +- cran-comments.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 cran-comments.md diff --git a/DESCRIPTION b/DESCRIPTION index f6fc487..7ec05df 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: netrics Title: Many Ways to Measure and Classify Membership for Networks, Nodes, and Ties Version: 0.1.0 -Date: 2026-03-13 +Date: 2026-03-15 Description: Many tools for calculating network, node, or tie marks, measures, motifs and memberships of many different types of networks. Marks ('node_is_*()' and 'tie_is_*()') identify structural positions, diff --git a/cran-comments.md b/cran-comments.md new file mode 100644 index 0000000..d90f044 --- /dev/null +++ b/cran-comments.md @@ -0,0 +1,16 @@ +## Test environments + +* local R installation, aarch64-apple-darwin20, R 4.5.2 +* macOS 15.7.4 (on Github), R 4.5.2 +* Microsoft Windows Server 2025 10.0.26100 (on Github), R 4.5.2 +* Ubuntu 24.04.3 (on Github), R 4.5.2 + +## R CMD check results + +0 errors | 0 warnings | 0 notes + +- This package is ready for submission to CRAN. +The check results are clean, with no errors, warnings, or notes across all tested environments. +Note that there are some conflicts with manynet functions, +but these are not causing any issues with the package itself and +will be resolved by removing them from manynet once netrics is available on CRAN. From 38226b8c8dbef7006950cafeb00103d8744d37b9 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sun, 15 Mar 2026 17:51:45 +0100 Subject: [PATCH 98/98] Simplified description --- DESCRIPTION | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7ec05df..5c8f140 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -4,12 +4,10 @@ Version: 0.1.0 Date: 2026-03-15 Description: Many tools for calculating network, node, or tie marks, measures, motifs and memberships of many different types of networks. - Marks ('node_is_*()' and 'tie_is_*()') identify structural positions, - measures ('*_by_*()') quantify network properties, - memberships ('*_in_*()') classify nodes into groups, - and motifs ('*_x_*()') tabulate substructure participation. - All functions operate with matrices, edge lists, and 'igraph', 'network', 'tidygraph', and 'mnet' objects, - on directed, multiplex, multimodal, signed, and other networks. + Marks identify structural positions, measures quantify network properties, + memberships classify nodes into groups, and motifs tabulate substructure participation. + All functions operate with all classes of network data covered in 'manynet', + and on directed, undirected, multiplex, multimodal, signed, and other networks. URL: https://stocnet.github.io/netrics/ BugReports: https://github.com/stocnet/netrics/issues License: MIT + file LICENSE