diff --git a/DESCRIPTION b/DESCRIPTION index 9cffdac1..3ee2aba0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: migraph Title: Many Network Measures, Motifs, Members, and Models -Version: 1.3.2 -Date: 2024-01-25 +Version: 1.3.3 +Date: 2024-03-06 Description: A set of tools for analysing multimodal networks. It includes functions for measuring centrality, centralization, cohesion, closure, constraint and diversity, @@ -18,7 +18,7 @@ License: MIT + file LICENSE Language: en-GB Encoding: UTF-8 LazyData: true -RoxygenNote: 7.3.0 +RoxygenNote: 7.3.1 Depends: R (>= 3.6.0), manynet diff --git a/NAMESPACE b/NAMESPACE index 961dbde2..35ee04aa 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -136,6 +136,9 @@ export(node_automorphic_equivalence) export(node_betweenness) export(node_bridges) export(node_brokerage_census) +export(node_brokering) +export(node_brokering_activity) +export(node_brokering_exclusivity) export(node_closeness) export(node_components) export(node_constraint) diff --git a/NEWS.md b/NEWS.md index 32a37d65..4daadfbf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,25 @@ +# migraph 1.3.3 + +2024-01-25 + +## Measures + +- Added `node_brokering_activity()` and `node_brokering_exclusivity()` from Hamilton et al (2020) +- `node_degree()` now returns strength centrality (alpha = 1) for weighted networks by default +- `node_redundancy()` now works for weighted onemode and twomode networks (closed #292) + - Matrix operations bring an approximately sixfold speed increase compared to vapply +- `node_effsize()` now works for weighted onemode and twomode networks + - Matrix operations bring an approximately sixfold speed increase compared to vapply +- `network_equivalency()` now normalises weighted twomode networks (closed #291) + +## Members + +- Added `node_brokering()` for identifying brokering roles from brokering activity and exclusivity +- `node_roulette()` now optimises group diversity based on historical interactions (Lai and Hao 2016) + - Added more documentation about maximally diverse grouping problem + - Matrix operations bring an approximately threefold speed increase compared to vapply + - Perturbation helpers can be used also for blockmodelling (closed #38) + # migraph 1.3.2 2024-01-25 diff --git a/R/measure_centrality.R b/R/measure_centrality.R index 9ccd418b..d6cfcdf1 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -9,7 +9,7 @@ #' or weighted degree/strength of nodes in a weighted network; #' there are several related shortcut functions: #' - `node_deg()` returns the unnormalised results. -#' - `node_indegree()` returns the `direction = 'out'` results. +#' - `node_indegree()` returns the `direction = 'in'` results. #' - `node_outdegree()` returns the `direction = 'out'` results. #' - `node_multidegree()` measures the ratio between types of ties in a multiplex network. #' - `node_posneg()` measures the PN (positive-negative) centrality of a signed network. @@ -90,7 +90,7 @@ NULL #' @rdname degree_centrality #' @importFrom manynet as_igraph #' @export -node_degree <- function (.data, normalized = TRUE, alpha = 0, +node_degree <- function (.data, normalized = TRUE, alpha = 1, direction = c("all","out","in")){ if(missing(.data)) {expect_nodes(); .data <- .G()} diff --git a/R/measure_closure.R b/R/measure_closure.R index 8768c567..25da08b8 100644 --- a/R/measure_closure.R +++ b/R/measure_closure.R @@ -80,7 +80,11 @@ node_transitivity <- function(.data) { .data) } -#' @rdname closure +#' @rdname closure +#' @section Equivalency: +#' The `network_equivalency()` function calculates the Robins and Alexander (2004) +#' clustering coefficient for two-mode networks. +#' Note that for weighted two-mode networks, the result is divided by the average tie weight. #' @examples #' network_equivalency(ison_southern_women) #' @export @@ -96,6 +100,7 @@ network_equivalency <- function(.data) { sum(twopaths * (matrix(indegrees, c, c) - twopaths))) if (is.nan(output)) output <- 1 + if(manynet::is_weighted(.data)) output <- output / mean(mat[mat>0]) } else stop("This function expects a two-mode network") make_network_measure(output, .data) } diff --git a/R/measure_holes.R b/R/measure_holes.R index 3d678d70..6848fc8c 100644 --- a/R/measure_holes.R +++ b/R/measure_holes.R @@ -62,39 +62,73 @@ node_bridges <- function(.data){ #' Borgatti, Steven. 1997. #' “\href{http://www.analytictech.com/connections/v20(1)/holes.htm}{Structural Holes: Unpacking Burt’s Redundancy Measures}” #' _Connections_ 20(1):35-38. +#' +#' Burchard, Jake, and Benjamin Cornwell. 2018. +#' “Structural Holes and Bridging in Two-Mode Networks.” +#' _Social Networks_ 55:11–20. +#' \doi{10.1016/j.socnet.2018.04.001.} #' @examples #' node_redundancy(ison_adolescents) #' node_redundancy(ison_southern_women) #' @export node_redundancy <- function(.data){ - g <- manynet::as_igraph(.data) - .inc <- NULL - out <- vapply(igraph::V(g), function(ego){ - n = igraph::neighbors(g, ego) - t = length(igraph::E(g)[.inc(n) & !.inc(ego)]) - n = length(n) - 2 * t / n - }, FUN.VALUE = numeric(1)) + if(manynet::is_twomode(.data)){ + mat <- manynet::as_matrix(.data) + out <- c(.redund2(mat), .redund2(t(mat))) + } else { + out <- .redund(manynet::as_matrix(.data)) + } make_node_measure(out, .data) } +.redund <- function(.mat){ + n <- nrow(.mat) + qs <- .twopath_matrix(.mat > 0) + piq <- .mat/rowSums(.mat) + mjq <- .mat/matrix(do.call("pmax",data.frame(.mat)),n,n) + out <- rowSums(qs * piq * mjq) + out +} + +.redund2 <- function(.mat){ + sigi <- .mat %*% t(.mat) + diag(sigi) <- 0 + vapply(seq.int(nrow(sigi)), + function(x){ + xvec <- sigi[x,] #> 0 + if(manynet::is_weighted(.mat)){ + wt <- colMeans((.mat[x,] > 0 * t(.mat[xvec > 0,])) * t(.mat[xvec > 0,]) + .mat[x,]) * 2 + } else wt <- 1 + sum(colSums(xvec > 0 & t(sigi[xvec > 0,])) * xvec[xvec > 0] / + (sum(xvec) * wt)) + }, FUN.VALUE = numeric(1)) +} + #' @rdname holes #' @examples #' node_effsize(ison_adolescents) #' node_effsize(ison_southern_women) #' @export node_effsize <- function(.data){ - g <- manynet::as_igraph(.data) - .inc <- NULL - out <- vapply(igraph::V(g), function(ego){ - n = igraph::neighbors(g, ego) - t = length(igraph::E(g)[.inc(n) & !.inc(ego)]) - n = length(n) - n - 2 * t / n - }, FUN.VALUE = numeric(1)) + if(manynet::is_twomode(.data)){ + mat <- manynet::as_matrix(.data) + out <- c(rowSums(manynet::as_matrix(manynet::to_mode1(.data))>0), + rowSums(manynet::as_matrix(manynet::to_mode2(.data))>0)) - node_redundancy(.data) + } else { + mat <- manynet::as_matrix(.data) + out <- rowSums(mat>0) - .redund(mat) + } make_node_measure(out, .data) } +.twopath_matrix <- function(.data){ + .data <- manynet::as_matrix(.data) + qs <- .data %*% t(.data) + diag(qs) <- 0 + qs +} + + #' @rdname holes #' @examples #' node_efficiency(ison_adolescents) diff --git a/R/member_cliques.R b/R/member_cliques.R new file mode 100644 index 00000000..7c99ced4 --- /dev/null +++ b/R/member_cliques.R @@ -0,0 +1,128 @@ +#' Clique partitioning algorithms +#' +#' @description +#' These functions create a vector of nodes' memberships in +#' cliques: +#' +#' - `node_roulette()` assigns nodes to maximally diverse groups. +#' +#' @section Maximally diverse grouping problem: +#' This well known computational problem is a NP-hard problem +#' with a number of relevant applications, +#' including the formation of groups of students that have encountered +#' each other least or least recently. +#' Essentially, the aim is to return a membership of nodes in cliques +#' that minimises the sum of their previous (weighted) ties: +#' +#' \deqn{\sum_{g=1}^{m} \sum_{i=1}^{n-1} \sum_{j=i+1}^{n} x_{ij} y_{ig} y_{jg}} +#' +#' where \eqn{y_{ig} = 1} if node \eqn{i} is in group \eqn{g}, and 0 otherwise. +#' +#' \eqn{x_{ij}} is the existing network data. +#' If this is an empty network, the function will just return cliques. +#' To run this repeatedly, one can join a clique network of the membership result +#' with the original network, using this as the network data for the next round. +#' +#' A form of the Lai and Hao (2016) iterated maxima search (IMS) is used here. +#' This performs well for small and moderately sized networks. +#' It includes both weak and strong perturbations to an initial solution +#' to ensure that a robust solution from the broader state space is identified. +#' The user is referred to Lai and Hao (2016) and Lai et al (2021) for more details. +#' @inheritParams cohesion +#' @name cliques +#' @family memberships +NULL + +#' @rdname cliques +#' @param num_groups An integer indicating the number of groups desired. +#' @param group_size An integer indicating the desired size of most of the groups. +#' Note that if the number of nodes is not divisible into groups of equal size, +#' there may be some larger or smaller groups. +#' @param times An integer of the number of search iterations the algorithm should complete. +#' By default this is the number of nodes in the network multiplied by the number of groups. +#' This heuristic may be insufficient for small networks and numbers of groups, +#' and burdensome for large networks and numbers of groups, but can be overwritten. +#' At every 10th iteration, a stronger perturbation of a number of successive changes, +#' approximately the number of nodes divided by the number of groups, +#' will take place irrespective of whether it improves the objective function. +#' @references +#' Lai, Xiangjing, and Jin-Kao Hao. 2016. +#' “Iterated Maxima Search for the Maximally Diverse Grouping Problem.” +#' _European Journal of Operational Research_ 254(3):780–800. +#' \doi{10.1016/j.ejor.2016.05.018}. +#' +#' Lai, Xiangjing, Jin-Kao Hao, Zhang-Hua Fu, and Dong Yue. 2021. +#' “Neighborhood Decomposition Based Variable Neighborhood Search and Tabu Search for Maximally Diverse Grouping.” +#' _European Journal of Operational Research_ 289(3):1067–86. +#' \doi{10.1016/j.ejor.2020.07.048}. +#' @export +node_roulette <- function(.data, num_groups, group_size, times = NULL){ + if(missing(num_groups) & missing(group_size)){ + stop(paste("Either `num_groups` must indicate number of groups desired", + "or `group_size` must indicate the desired average size of groups.")) + } + n <- manynet::network_nodes(.data) + my_vec <- sample(seq.int(n)) + # Initial partition + if(!missing(num_groups)){ + out <- cut(seq_along(my_vec), num_groups, labels = FALSE)[my_vec] + } else { + out <- ceiling(seq_along(my_vec) / group_size)[my_vec] + } + if(is.null(times)) times <- n * max(out) + # Get fitness + mat <- manynet::as_matrix(.data) + fit <- sum(.to_cliques(out) * mat) + soln <- out + for(t in seq.int(times)){ + soln <- .weakPerturb(soln) + new_fit <- sum(.to_cliques(soln) * mat) + if(new_fit < fit){ + out <- soln + fit <- new_fit + } + if(t %% 10) soln <- .strongPerturb(soln) + } + make_node_member(out, .data) +} + +.to_cliques <- function(member){ + (member == t(matrix(member, length(member), length(member))))*1 +} + +.weakPerturb <- function(soln){ + gsizes <- table(soln) + evens <- all(gsizes == max(gsizes)) + if(evens){ + soln <- .swapMove(soln) + } else { + if(stats::runif(1)<0.5) soln <- .swapMove(soln) else + soln <- .oneMove(soln) + } + soln +} + +.swapMove <- function(soln){ + from <- sample(seq.int(length(soln)), 1) + to <- sample(which(soln != soln[from]), 1) + soln[c(to,from)] <- soln[c(from,to)] + soln +} + +.oneMove <- function(soln){ + gsizes <- table(soln) + maxg <- which(gsizes == max(gsizes)) + from <- sample(which(soln %in% maxg), 1) + soln[from] <- sample(which(gsizes != max(gsizes)), 1) + soln +} + +.strongPerturb <- function(soln, strength = 1){ + times <- ceiling(strength * length(soln)/max(soln)) + for (t in seq.int(times)){ + soln <- .weakPerturb(soln) + } + soln +} + + diff --git a/R/member_components.R b/R/member_components.R index e4c958fe..3190646d 100644 --- a/R/member_components.R +++ b/R/member_components.R @@ -62,23 +62,5 @@ node_strong_components <- function(.data){ .data) } -#' @rdname components -#' @param num_groups An integer indicating the number of groups desired -#' @param group_size An integer indicating the desired size of most of the groups -#' @export -node_roulette <- function(.data, num_groups, group_size){ - if(missing(num_groups) & missing(group_size)){ - stop(paste("Either `num_groups` must indicate number of groups desired", - "or `group_size` must indicate the desired average size of groups.")) - } - n <- manynet::network_nodes(.data) - my_vec <- sample(seq.int(n)) - if(!missing(num_groups)){ - out <- cut(seq_along(my_vec), num_groups, labels = FALSE)[my_vec] - } else { - out <- ceiling(seq_along(my_vec) / group_size)[my_vec] - } - make_node_member(out, .data) -} diff --git a/R/motif_census.R b/R/motif_census.R index bd5b5fee..527fd644 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -375,3 +375,90 @@ network_brokerage_census <- function(.data, membership, standardized = FALSE){ } make_network_motif(out, .data) } + +#' @rdname brokerage_census +#' @references +#' 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" +#' _Social Networks_ 61: 1-10. +#' \doi{10.1016/j.socnet.2019.08.002} +#' @export +node_brokering_activity <- function(.data, membership){ + 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), + match(twopaths$from, manynet::node_names(.data)), + twopaths$from)] + twopaths$to_memb <- manynet::node_attribute(.data, membership)[`if`(manynet::is_labelled(.data), + match(twopaths$to.y, manynet::node_names(.data)), + twopaths$to.y)] + twopaths <- dplyr::filter(twopaths, from_memb != to_memb) + } + # 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))] + # missings should be none + out[is.na(out)] <- 0 + make_node_measure(out, .data) +} + +#' @rdname brokerage_census +#' @examples +#' node_brokering_exclusivity(ison_networkers, "Discipline") +#' @export +node_brokering_exclusivity <- function(.data, membership){ + 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), + match(twopaths$from, manynet::node_names(.data)), + twopaths$from)] + twopaths$to_memb <- manynet::node_attribute(.data, membership)[`if`(manynet::is_labelled(.data), + match(twopaths$to.y, manynet::node_names(.data)), + twopaths$to.y)] + twopaths <- dplyr::filter(twopaths, from_memb != to_memb) + } + # get only exclusive paths + out <- twopaths %>% dplyr::group_by(from, to.y) %>% dplyr::filter(dplyr::n()==1) + # 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))] + # missings should be none + out[is.na(out)] <- 0 + make_node_measure(out, .data) +} + +#' @rdname brokerage_census +#' @export +node_brokering <- function(.data, membership){ + activ <- node_brokering_activity(.data, membership) + exclusiv <- node_brokering_exclusivity(.data, membership) + activ <- activ - mean(activ) + exclusiv <- exclusiv - mean(exclusiv) + out <- dplyr::case_when(activ > 0 & exclusiv > 0 ~ "Powerhouse", + activ > 0 & exclusiv < 0 ~ "Connectors", + activ < 0 & exclusiv > 0 ~ "Linchpins", + activ < 0 & exclusiv < 0 ~ "Sideliners") + make_node_member(out, .data) +} + +.to_twopaths <- function(.data){ + to <- from <- to.y <- NULL + if(!manynet::is_directed(.data)){ + el <- manynet::as_edgelist(manynet::to_reciprocated(.data)) + } else el <- manynet::as_edgelist(.data) + twopaths <- dplyr::full_join(el, el, + by = dplyr::join_by(to == from), + relationship = "many-to-many") + # remove non two-paths + twopaths <- dplyr::filter(twopaths, !(is.na(from) | is.na(to.y))) + # remove reciprocated paths + twopaths <- dplyr::filter(twopaths, from != to.y) + # remove triads + twopaths <- dplyr::filter(twopaths, !paste(from, to.y) %in% paste(from, to)) + twopaths +} diff --git a/cran-comments.md b/cran-comments.md index 8e0d1f8d..ae763253 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -8,5 +8,3 @@ ## R CMD check results 0 errors | 0 warnings | 0 notes - -- This submission resolves reverse dependencies from igraph v2.0.0 \ No newline at end of file diff --git a/man/brokerage_census.Rd b/man/brokerage_census.Rd index ca8d0976..75daea70 100644 --- a/man/brokerage_census.Rd +++ b/man/brokerage_census.Rd @@ -4,11 +4,20 @@ \alias{brokerage_census} \alias{node_brokerage_census} \alias{network_brokerage_census} +\alias{node_brokering_activity} +\alias{node_brokering_exclusivity} +\alias{node_brokering} \title{Censuses of brokerage motifs} \usage{ node_brokerage_census(.data, membership, standardized = FALSE) network_brokerage_census(.data, membership, standardized = FALSE) + +node_brokering_activity(.data, membership) + +node_brokering_exclusivity(.data, membership) + +node_brokering(.data, membership) } \arguments{ \item{.data}{An object of a \code{{manynet}}-consistent class: @@ -39,6 +48,7 @@ roles in a network. \examples{ node_brokerage_census(manynet::ison_networkers, "Discipline") network_brokerage_census(manynet::ison_networkers, "Discipline") +node_brokering_exclusivity(ison_networkers, "Discipline") } \references{ Gould, R.V. and Fernandez, R.M. 1989. @@ -49,6 +59,12 @@ Jasny, Lorien, and Mark Lubell. 2015. “Two-Mode Brokerage in Policy Networks.” \emph{Social Networks} 41:36–47. \doi{10.1016/j.socnet.2014.11.005}. + +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: diff --git a/man/cliques.Rd b/man/cliques.Rd new file mode 100644 index 00000000..cf1021c2 --- /dev/null +++ b/man/cliques.Rd @@ -0,0 +1,84 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/member_cliques.R +\name{cliques} +\alias{cliques} +\alias{node_roulette} +\title{Clique partitioning algorithms} +\usage{ +node_roulette(.data, num_groups, group_size, times = NULL) +} +\arguments{ +\item{.data}{An object of a \code{{manynet}}-consistent class: +\itemize{ +\item matrix (adjacency or incidence) from \code{{base}} R +\item edgelist, a data frame from \code{{base}} R or tibble from \code{{tibble}} +\item igraph, from the \code{{igraph}} package +\item network, from the \code{{network}} package +\item tbl_graph, from the \code{{tidygraph}} package +}} + +\item{num_groups}{An integer indicating the number of groups desired.} + +\item{group_size}{An integer indicating the desired size of most of the groups. +Note that if the number of nodes is not divisible into groups of equal size, +there may be some larger or smaller groups.} + +\item{times}{An integer of the number of search iterations the algorithm should complete. +By default this is the number of nodes in the network multiplied by the number of groups. +This heuristic may be insufficient for small networks and numbers of groups, +and burdensome for large networks and numbers of groups, but can be overwritten. +At every 10th iteration, a stronger perturbation of a number of successive changes, +approximately the number of nodes divided by the number of groups, +will take place irrespective of whether it improves the objective function.} +} +\description{ +These functions create a vector of nodes' memberships in +cliques: +\itemize{ +\item \code{node_roulette()} assigns nodes to maximally diverse groups. +} +} +\section{Maximally diverse grouping problem}{ + +This well known computational problem is a NP-hard problem +with a number of relevant applications, +including the formation of groups of students that have encountered +each other least or least recently. +Essentially, the aim is to return a membership of nodes in cliques +that minimises the sum of their previous (weighted) ties: + +\deqn{\sum_{g=1}^{m} \sum_{i=1}^{n-1} \sum_{j=i+1}^{n} x_{ij} y_{ig} y_{jg}} + +where \eqn{y_{ig} = 1} if node \eqn{i} is in group \eqn{g}, and 0 otherwise. + +\eqn{x_{ij}} is the existing network data. +If this is an empty network, the function will just return cliques. +To run this repeatedly, one can join a clique network of the membership result +with the original network, using this as the network data for the next round. + +A form of the Lai and Hao (2016) iterated maxima search (IMS) is used here. +This performs well for small and moderately sized networks. +It includes both weak and strong perturbations to an initial solution +to ensure that a robust solution from the broader state space is identified. +The user is referred to Lai and Hao (2016) and Lai et al (2021) for more details. +} + +\references{ +Lai, Xiangjing, and Jin-Kao Hao. 2016. +“Iterated Maxima Search for the Maximally Diverse Grouping Problem.” +\emph{European Journal of Operational Research} 254(3):780–800. +\doi{10.1016/j.ejor.2016.05.018}. + +Lai, Xiangjing, Jin-Kao Hao, Zhang-Hua Fu, and Dong Yue. 2021. +“Neighborhood Decomposition Based Variable Neighborhood Search and Tabu Search for Maximally Diverse Grouping.” +\emph{European Journal of Operational Research} 289(3):1067–86. +\doi{10.1016/j.ejor.2020.07.048}. +} +\seealso{ +Other memberships: +\code{\link{community}}, +\code{\link{components}()}, +\code{\link{core}}, +\code{\link{equivalence}} +} +\concept{memberships} diff --git a/man/closure.Rd b/man/closure.Rd index e362e6ab..18502e58 100644 --- a/man/closure.Rd +++ b/man/closure.Rd @@ -61,6 +61,13 @@ For three-mode networks, \code{network_congruency} calculates the proportion of spanning two two-mode networks that are closed by a fourth tie to establish a "congruent four-cycle" structure. } +\section{Equivalency}{ + +The \code{network_equivalency()} function calculates the Robins and Alexander (2004) +clustering coefficient for two-mode networks. +Note that for weighted two-mode networks, the result is divided by the average tie weight. +} + \examples{ network_reciprocity(ison_southern_women) node_reciprocity(to_unweighted(ison_networkers)) diff --git a/man/community.Rd b/man/community.Rd index 3ecded83..74c2d841 100644 --- a/man/community.Rd +++ b/man/community.Rd @@ -265,6 +265,7 @@ Traag, V. A., L Waltman, and NJ van Eck. 2019. } \seealso{ Other memberships: +\code{\link{cliques}}, \code{\link{components}()}, \code{\link{core}}, \code{\link{equivalence}} diff --git a/man/components.Rd b/man/components.Rd index 03198e42..3d604ad6 100644 --- a/man/components.Rd +++ b/man/components.Rd @@ -5,7 +5,6 @@ \alias{node_components} \alias{node_weak_components} \alias{node_strong_components} -\alias{node_roulette} \title{Component partitioning algorithms} \usage{ node_components(.data) @@ -13,8 +12,6 @@ node_components(.data) node_weak_components(.data) node_strong_components(.data) - -node_roulette(.data, num_groups, group_size) } \arguments{ \item{.data}{An object of a \code{{manynet}}-consistent class: @@ -25,10 +22,6 @@ node_roulette(.data, num_groups, group_size) \item network, from the \code{{network}} package \item tbl_graph, from the \code{{tidygraph}} package }} - -\item{num_groups}{An integer indicating the number of groups desired} - -\item{group_size}{An integer indicating the desired size of most of the groups} } \description{ These functions create a vector of nodes' memberships in @@ -65,6 +58,7 @@ node_components(mpn_bristol) } \seealso{ Other memberships: +\code{\link{cliques}}, \code{\link{community}}, \code{\link{core}}, \code{\link{equivalence}} diff --git a/man/core.Rd b/man/core.Rd index c4eaf90f..b19c0921 100644 --- a/man/core.Rd +++ b/man/core.Rd @@ -67,6 +67,7 @@ Lip, Sean Z. W. 2011. } \seealso{ Other memberships: +\code{\link{cliques}}, \code{\link{community}}, \code{\link{components}()}, \code{\link{equivalence}} diff --git a/man/degree_centrality.Rd b/man/degree_centrality.Rd index 7c7bd2ee..043ea40d 100644 --- a/man/degree_centrality.Rd +++ b/man/degree_centrality.Rd @@ -17,7 +17,7 @@ node_degree( .data, normalized = TRUE, - alpha = 0, + alpha = 1, direction = c("all", "out", "in") ) @@ -94,7 +94,7 @@ or weighted degree/strength of nodes in a weighted network; there are several related shortcut functions: \itemize{ \item \code{node_deg()} returns the unnormalised results. -\item \code{node_indegree()} returns the \code{direction = 'out'} results. +\item \code{node_indegree()} returns the \code{direction = 'in'} results. \item \code{node_outdegree()} returns the \code{direction = 'out'} results. } \item \code{node_multidegree()} measures the ratio between types of ties in a multiplex network. diff --git a/man/equivalence.Rd b/man/equivalence.Rd index 66a0dfef..44949237 100644 --- a/man/equivalence.Rd +++ b/man/equivalence.Rd @@ -121,6 +121,7 @@ plot(nae) } \seealso{ Other memberships: +\code{\link{cliques}}, \code{\link{community}}, \code{\link{components}()}, \code{\link{core}} diff --git a/man/holes.Rd b/man/holes.Rd index f9d25f56..ddddd455 100644 --- a/man/holes.Rd +++ b/man/holes.Rd @@ -100,6 +100,11 @@ Borgatti, Steven. 1997. “\href{http://www.analytictech.com/connections/v20(1)/holes.htm}{Structural Holes: Unpacking Burt’s Redundancy Measures}” \emph{Connections} 20(1):35-38. +Burchard, Jake, and Benjamin Cornwell. 2018. +“Structural Holes and Bridging in Two-Mode Networks.” +\emph{Social Networks} 55:11–20. +\doi{10.1016/j.socnet.2018.04.001.} + Hollway, James, Jean-Frédéric Morin, and Joost Pauwelyn. 2020. "Structural conditions for novelty: the introduction of new environmental clauses to the trade regime complex." \emph{International Environmental Agreements: Politics, Law and Economics} 20 (1): 61–83. diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 9923e938..882048db 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -66,6 +66,7 @@ reference: - components - equivalence - core + - cliques - title: "Models" desc: "Functions for modelling multimodal networks:" contents: diff --git a/tests/testthat/helper-functions.R b/tests/testthat/helper-functions.R index 224077dd..5abaf2d2 100644 --- a/tests/testthat/helper-functions.R +++ b/tests/testthat/helper-functions.R @@ -1,17 +1,25 @@ top3 <- function(res, dec = 4){ - unname(round(res, dec))[1:3] + if(is.numeric(res)){ + unname(round(res, dec))[1:3] + } else unname(res)[1:3] } bot3 <- function(res, dec = 4){ lr <- length(res) - unname(round(res, dec))[(lr-2):lr] + if(is.numeric(res)){ + unname(round(res, dec))[(lr-2):lr] + } else unname(res)[(lr-2):lr] } top5 <- function(res, dec = 4){ - unname(round(res, dec))[1:5] + if(is.numeric(res)){ + unname(round(res, dec))[1:5] + } else unname(res)[1:3] } bot5 <- function(res, dec = 4){ lr <- length(res) - unname(round(res, dec))[(lr-4):lr] + if(is.numeric(res)){ + unname(round(res, dec))[(lr-4):lr] + } else unname(res)[(lr-2):lr] } diff --git a/tests/testthat/test-measure_holes.R b/tests/testthat/test-measure_holes.R index 0e2b2549..2b940eda 100644 --- a/tests/testthat/test-measure_holes.R +++ b/tests/testthat/test-measure_holes.R @@ -14,8 +14,7 @@ test_that("effective size is calculated and reported correctly", { expect_equal(length(node_effsize(mpn_elite_usa_advice)), network_nodes(mpn_elite_usa_advice)) expect_named(node_effsize(mpn_elite_usa_advice)) - x <- node_degree(ison_southern_women, normalized = FALSE)[1] - node_redundancy(ison_southern_women)[1] - expect_equal(node_effsize(ison_southern_women)[1], x) + expect_equal(top5(node_effsize(ison_southern_women)), c(2.5,1.3778,2.4561,1.4565,1)) }) test_that("efficiency is reported correctly", { diff --git a/tests/testthat/test-motif_census.R b/tests/testthat/test-motif_census.R index 79d59985..d2e9713e 100644 --- a/tests/testthat/test-motif_census.R +++ b/tests/testthat/test-motif_census.R @@ -64,3 +64,24 @@ test_that("node path census works", { expect_true(nrow(node_path_census(manynet::ison_southern_women)) == ncol(node_path_census(manynet::ison_southern_women))) }) + +test <- node_brokering_activity(manynet::ison_networkers, "Discipline") +test_that("node activity works", { + expect_s3_class(test, "node_measure") + expect_equal(manynet::network_nodes(manynet::ison_networkers), length(test)) + expect_equal(top3(test), c(333,207,3)) +}) + +test <- node_brokering_exclusivity(manynet::ison_networkers, "Discipline") +test_that("node exclusivity works", { + expect_s3_class(test, "node_measure") + expect_equal(manynet::network_nodes(manynet::ison_networkers), length(test)) + expect_equal(top3(test), c(1,0,0)) +}) + +test <- node_brokering(manynet::ison_networkers, "Discipline") +test_that("node brokering works", { + expect_s3_class(test, "node_member") + expect_equal(manynet::network_nodes(manynet::ison_networkers), length(test)) + expect_equal(top3(test), c("Powerhouse","Connectors","Sideliners")) +})