Nothing
# Closeness-like centralities ####
#' Measuring nodes closeness-like centrality
#' @name measure_central_close
#' @description
#' These functions calculate common closeness-related centrality measures
#' that rely on path-length for one- and two-mode networks:
#'
#' - `node_by_closeness()` measures the closeness centrality of nodes in a
#' network.
#' - `node_by_harmonic()` measures nodes' harmonic centrality or valued
#' centrality, which is thought to behave better than reach centrality
#' for disconnected networks.
#' - `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_by_eccentricity()` measures nodes' eccentricity or maximum distance
#' from another node in the network.
#' - `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.
#'
#' All measures attempt to use as much information as they are offered,
#' including whether the networks are directed, weighted, or multimodal.
#' If this would produce unintended results,
#' first transform the salient properties using e.g. [to_undirected()] functions.
#' All centrality and centralization measures return normalized measures by default,
#' including for two-mode networks.
#' @template param_data
#' @template param_norm
#' @template param_dir
#' @family closeness
#' @family centrality
#' @template node_measure
NULL
#' @rdname measure_central_close
#' @param cutoff Maximum path length to use during calculations.
#' @section Closeness centrality:
#' Closeness centrality, status centrality, or barycenter centrality is
#' defined as the reciprocal of the farness or distance, \eqn{d},
#' from a node to all other nodes in the network:
#' \deqn{C_C(i) = \frac{1}{\sum_j d(i,j)}}
#' When (more commonly) normalised, the numerator is instead \eqn{N-1}.
#' @references
#' ## On closeness centrality
#' Bavelas, Alex. 1950.
#' "Communication Patterns in Task‐Oriented Groups".
#' _The Journal of the Acoustical Society of America_, 22(6): 725–730.
#' \doi{10.1121/1.1906679}
#'
#' Harary, Frank. 1959.
#' "Status and Contrastatus".
#' _Sociometry_, 22(1): 23–43.
#' \doi{10.2307/2785610}
#' @examples
#' node_by_closeness(ison_southern_women)
#' @export
node_by_closeness <- function(.data, normalized = TRUE,
direction = "out", cutoff = NULL){
.data <- manynet::expect_nodes(.data)
weights <- `if`(manynet::is_weighted(.data),
manynet::tie_weights(.data), NA)
graph <- manynet::as_igraph(.data)
# Do the calculations
if (manynet::is_twomode(graph) & normalized){
# farness <- rowSums(igraph::distances(graph = graph))
closeness <- igraph::closeness(graph = graph, vids = igraph::V(graph), mode = direction)
other_set_size <- ifelse(igraph::V(graph)$type, sum(!igraph::V(graph)$type), sum(igraph::V(graph)$type))
set_size <- ifelse(igraph::V(graph)$type, sum(igraph::V(graph)$type), sum(!igraph::V(graph)$type))
out <- closeness/(1/(other_set_size+2*set_size-2))
} else {
cutoff <- if (is.null(cutoff)) -1 else cutoff
out <- igraph::closeness(graph = graph, vids = igraph::V(graph), mode = direction,
cutoff = cutoff, weights = weights, normalized = normalized)
}
out <- make_node_measure(out, .data)
out
}
#' @rdname measure_central_close
#' @section Harmonic centrality:
#' Harmonic centrality or valued centrality reverses the sum and reciprocal
#' operations compared to closeness centrality:
#' \deqn{C_H(i) = \sum_{i, i \neq j} \frac{1}{d(i,j)}}
#' where \eqn{\frac{1}{d(i,j)} = 0} where there is no path between \eqn{i} and
#' \eqn{j}. Normalization is by \eqn{N-1}.
#' Since the harmonic mean performs better than the arithmetic mean on
#' unconnected networks, i.e. networks with infinite distances,
#' harmonic centrality is to be preferred in these cases.
#' @references
#' ## On harmonic centrality
#' Marchiori, Massimo, and Vito Latora. 2000.
#' "Harmony in the small-world".
#' _Physica A_ 285: 539-546.
#' \doi{10.1016/S0378-4371(00)00311-3}
#'
#' Dekker, Anthony. 2005.
#' "Conceptual distance in social network analysis".
#' _Journal of Social Structure_ 6(3).
#' @export
node_by_harmonic <- function(.data, normalized = TRUE, cutoff = -1){
.data <- manynet::expect_nodes(.data)
out <- igraph::harmonic_centrality(as_igraph(.data), # weighted if present
normalized = normalized, cutoff = cutoff)
out <- make_node_measure(out, .data)
out
}
#' @rdname measure_central_close
#' @section Reach centrality:
#' In some cases, longer path lengths are irrelevant and 'closeness' should
#' be defined as how many others are in a local neighbourhood.
#' How many steps out this neighbourhood should be defined as is given by
#' the 'cutoff' parameter.
#' This is usually termed \eqn{k} or \eqn{m} in equations,
#' which is why this is sometimes called (\eqn{m}- or)
#' \eqn{k}-step reach centrality:
#' \deqn{C_R(i) = \sum_j d(i,j) \leq k}
#' The maximum reach score is \eqn{N-1}, achieved when the node can reach all
#' other nodes in the network in \eqn{k} steps or less,
#' but the normalised version, \eqn{\frac{C_R}{N-1}}, is more common.
#' Note that if \eqn{k = 1} (i.e. cutoff = 1), then this returns the node's degree.
#' At higher cutoff reach centrality returns the size of the node's component.
#' @references
#' ## On reach centrality
#' Borgatti, Stephen P., Martin G. Everett, and J.C. Johnson. 2013.
#' _Analyzing social networks_.
#' London: SAGE Publications Limited.
#' @examples
#' node_by_reach(ison_adolescents)
#' @export
node_by_reach <- function(.data, normalized = TRUE, cutoff = 2){
.data <- manynet::expect_nodes(.data)
if(manynet::is_weighted(.data)){
tore <- manynet::as_matrix(.data)/mean(manynet::as_matrix(.data))
out <- 1/tore
} else out <- igraph::distances(manynet::as_igraph(.data))
diag(out) <- 0
out <- rowSums(out <= cutoff)
if(normalized) out <- out/(manynet::net_nodes(.data)-1)
out <- make_node_measure(out, .data)
out
}
#' @rdname measure_central_close
#' @section Information centrality:
#' Information centrality, also known as current-flow centrality,
#' is a hybrid measure relating to both path-length and walk-based measures.
#' The information centrality of a node is the harmonic average of the
#' “bandwidth” or inverse path-length for all paths originating from the node.
#'
#' As described in the `{sna}` package,
#' information centrality works on an undirected but potentially weighted
#' network excluding isolates (which take scores of zero).
#' It is defined as:
#' \deqn{C_I = \frac{1}{T + \frac{\sum T - 2 \sum C_1}{|N|}}}
#' where \eqn{C = B^-1} with \eqn{B} is a pseudo-adjacency matrix replacing
#' the diagonal of \eqn{1-A} with \eqn{1+k},
#' and \eqn{T} is the trace of \eqn{C} and \eqn{S_R} an arbitrary row sum
#' (all rows in \eqn{C} have the same sum).
#'
#' Nodes with higher information centrality have a large number of short paths
#' to many others in the network, and are thus considered to have greater
#' control of the flow of information.
#' @references
#' ## On information centrality
#' Stephenson, Karen, and Marvin Zelen. 1989.
#' "Rethinking centrality: Methods and examples".
#' _Social Networks_ 11(1):1-37.
#' \doi{10.1016/0378-8733(89)90016-6}
#'
#' Brandes, Ulrik, and Daniel Fleischer. 2005.
#' "Centrality Measures Based on Current Flow".
#' _Proc. 22nd Symp. Theoretical Aspects of Computer Science_ LNCS 3404: 533-544.
#' \doi{10.1007/978-3-540-31856-9_44}
#' @export
node_by_information <- function(.data, normalized = TRUE){
.data <- manynet::expect_nodes(.data)
thisRequires("sna")
out <- sna::infocent(manynet::as_network(.data),
gmode = ifelse(manynet::is_directed(.data), "digraph", "graph"),
diag = manynet::is_complex(.data),
rescale = normalized)
make_node_measure(out, .data)
}
#' @rdname measure_central_close
#' @section Eccentricity centrality:
#' Eccentricity centrality, graph centrality, or the Koenig number,
#' is the (if normalized, inverse of) the distance to the furthest node:
#' \deqn{C_E(i) = \frac{1}{max_{j \in N} d(i,j)}}
#' where the distance from \eqn{i} to \eqn{j} is \eqn{\infty} if unconnected.
#' As such it is only well defined for connected networks.
#' @references
#' ## On eccentricity centrality
#' Hage, Per, and Frank Harary. 1995.
#' "Eccentricity and centrality in networks".
#' _Social Networks_, 17(1): 57-63.
#' \doi{10.1016/0378-8733(94)00248-9}
#' @export
node_by_eccentricity <- function(.data, normalized = TRUE){
.data <- manynet::expect_nodes(.data)
if(!manynet::is_connected(.data))
manynet::snet_unavailable("Eccentricity centrality is only available for connected networks.")
disties <- igraph::distances(as_igraph(.data))
out <- apply(disties, 1, max)
if(normalized) out <- 1/out
make_node_measure(out, .data)
}
# - `node_eccentricity()` measures nodes' eccentricity or Koenig number,
# a measure of farness based on number of links needed to reach
# most distant node in the network.
# #' @rdname measure_holes
# #' @importFrom igraph eccentricity
# #' @export
# cnode_eccentricity <- function(.data){
# if(missing(.data)) {expect_nodes(); .data <- .G()}
# out <- igraph::eccentricity(manynet::as_igraph(.data),
# mode = "out")
# make_node_measure(out, .data)
# }
#' @rdname measure_central_close
#' @param from,to Index or name of a node to calculate distances from or to.
#' @export
node_by_distance <- function(.data, from, to, normalized = TRUE){
.data <- manynet::expect_nodes(.data)
if(missing(from) && missing(to)) manynet::snet_abort("Either 'from' or 'to' must be specified.")
if(!missing(from)) out <- igraph::distances(manynet::as_igraph(.data), v = from) else
if(!missing(to)) out <- igraph::distances(manynet::as_igraph(.data), to = to)
if(normalized) out <- out/max(out)
make_node_measure(out, .data)
}
#' @rdname measure_central_close
#' @section Closeness vitality centrality:
#' The closeness vitality of a node is the change in the sum of all distances
#' in a network, also known as the Wiener Index, when that node is removed.
#' Note that the closeness vitality may be negative infinity if
#' removing that node would disconnect the network.
#' Formally:
#' \deqn{C_V(i) = \sum_{j,k} d(j,k) - \sum_{j,k} d(j,k,G\ i)}
#' where \eqn{d(j,k,G\ i)} is the distance between nodes \eqn{j} and \eqn{k}
#' in the network with node \eqn{i} removed.
#' @references
#' ## On closeness vitality centrality
#' Koschuetzki, Dirk, Katharina Lehmann, Leon Peeters, Stefan Richter,
#' Dagmar Tenfelde-Podehl, and Oliver Zlotowski. 2005.
#' "Centrality Indices", in
#' Brandes, Ulrik, and Thomas Erlebach (eds.).
#' _Network Analysis: Methodological Foundations_.
#' Springer: Berlin, pp. 16-61.
#' @export
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)))
}, FUN.VALUE = numeric(1))
if(normalized) out <- out/max(out)
make_node_measure(out, .data)
}
#' @rdname measure_central_close
#' @section Random walk closeness centrality:
#' Random walk closeness centrality is based on the average length of
#' random walks starting at all other nodes to reach a given node.
#' It is defined as the inverse of the average hitting time to a node.
#' This means that higher values are given to nodes that can be reached
#' more quickly on average by random walks starting at other nodes.
#' Formally:
#' \deqn{C_{RW}(i) = \frac{1}{\frac{1}{N-1} \sum_{j \neq i} H_{ji}}}
#' where \eqn{H_{ji}} is the hitting time from node \eqn{j} to node \eqn{i}.
#' @references
#' ## On random walk closeness centrality
#' Noh, J.D. and R. Rieger. 2004.
#' "Random Walks on Complex Networks".
#' _Physical Review Letters_, 92(11): 118701.
#' \doi{10.1103/PhysRevLett.92.118701}
#' @export
node_by_randomwalk <- function(.data, normalized = TRUE){
.data <- manynet::expect_nodes(.data)
# adjacency and degree matrices
A <- manynet::as_matrix(manynet::to_multilevel(.data))
degs <- node_by_deg(.data)
D <- diag(degs)
# Laplacian
L <- D - A
# pseudoinverse of Laplacian
Lplus <- .ginv(L)
volG <- sum(degs) # total degree (2 * edges)
n <- manynet::net_nodes(.data)
out <- numeric(n)
for (i in 1:n) {
# hitting time from j to i
hitting_times <- sapply(1:n, function(j) {
if (i == j) return(0)
volG * (Lplus[j, j] + Lplus[i, i] - 2 * Lplus[i, j])
})
# average hitting time to node i
avg_ht <- mean(hitting_times[-i])
out[i] <- 1 / avg_ht
}
make_node_measure(out, .data)
}
# This is a helper function to compute the Moore-Penrose generalized inverse
# of a matrix using its singular value decomposition (SVD).
.ginv <- function (X, tol = sqrt(.Machine$double.eps)){
if (length(dim(X)) > 2L || !(is.numeric(X) || is.complex(X)))
stop("'X' must be a numeric or complex matrix")
if (!is.matrix(X))
X <- as.matrix(X)
Xsvd <- svd(X)
if (is.complex(X))
Xsvd$u <- Conj(Xsvd$u)
Positive <- Xsvd$d > max(tol * Xsvd$d[1L], 0)
if (all(Positive))
Xsvd$v %*% (1/Xsvd$d * t(Xsvd$u))
else if (!any(Positive))
array(0, dim(X)[2L:1L])
else Xsvd$v[, Positive, drop = FALSE] %*% ((1/Xsvd$d[Positive]) *
t(Xsvd$u[, Positive, drop = FALSE]))
}
# Tie closeness centrality ####
#' Measuring ties closeness-like centrality
#' @name measure_centralities_close
#' @description
#' `tie_by_closeness()` measures the closeness of each tie to other ties
#' in the network.
#'
#' All measures attempt to use as much information as they are offered,
#' including whether the networks are directed, weighted, or multimodal.
#' If this would produce unintended results,
#' first transform the salient properties using e.g. [to_undirected()] functions.
#' All centrality and centralization measures return normalized measures by default,
#' including for two-mode networks.
#' @template param_data
#' @template param_norm
#' @family closeness
#' @family centrality
#' @template tie_measure
NULL
#' @rdname measure_centralities_close
#' @examples
#' (ec <- tie_by_closeness(ison_adolescents))
#' ison_adolescents |> mutate_ties(weight = ec)
#' @export
tie_by_closeness <- function(.data, normalized = TRUE){
.data <- manynet::expect_ties(.data)
edge_adj <- manynet::to_ties(.data)
out <- node_by_closeness(edge_adj, normalized = normalized)
class(out) <- "numeric"
make_tie_measure(out, .data)
}
# Closeness centralisation ####
#' Measuring networks closeness-like centralisation
#' @name measure_centralisation_close
#' @description
#' - `net_by_closeness()` measures a network's closeness centralization as a
#' single score.
#' - `mode_by_closeness()` measures closeness centralization separately for each
#' mode of a two-mode network, returning one score per mode
#' (following Borgatti and Everett, 1997).
#' - `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.
#' If this would produce unintended results,
#' first transform the salient properties using e.g. [to_undirected()] functions.
#' All centrality and centralization measures return normalized measures by default,
#' including for two-mode networks.
#'
#' For two-mode networks the two modes have different theoretical maxima, so
#' `net_by_closeness()` reports a single network-level score by applying
#' Freeman's general centralization index over the normalized node closeness
#' scores, whereas `mode_by_closeness()` reports the per-mode scores directly.
#' @template param_data
#' @template param_norm
#' @template param_dir
#' @family closeness
#' @family centrality
#' @references
#' Borgatti, Stephen P., and Martin G. Everett. 1997.
#' "Network analysis of 2-mode data."
#' _Social Networks_ 19(3): 243-269.
#' \doi{10.1016/S0378-8733(96)00301-2}
#' @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.
#' @returns
#' `net_by_*()` functions return a `network_measure` scalar;
#' `mode_by_closeness()` returns a `mode_measure` numeric vector of length two,
#' giving one centralization score per mode.
NULL
#' @rdname measure_centralisation_close
#' @examples
#' net_by_closeness(ison_southern_women, direction = "in")
#' @export
net_by_closeness <- function(.data, normalized = TRUE,
direction = c("all", "out", "in")){
.data <- manynet::expect_nodes(.data)
direction <- match.arg(direction)
graph <- manynet::as_igraph(.data)
if (manynet::is_twomode(.data)) {
# Two-mode closeness centralization is intrinsically per mode
# (see `mode_by_closeness()`, following Borgatti and Everett, 1997).
# For a single network-level score we apply Freeman's general
# centralization index over the whole node set, using the normalized node
# closeness scores (each in [0, 1]); the numerator's maximum is (n - 1).
nc <- node_by_closeness(graph, normalized = TRUE, direction = direction)
out <- sum(max(nc) - nc) / (length(nc) - 1)
} else {
out <- igraph::centr_clo(graph = graph,
mode = direction,
normalized = normalized)$centralization
}
out <- make_network_measure(out, .data, call = deparse(sys.call()))
out
}
#' @rdname measure_centralisation_close
#' @examples
#' mode_by_closeness(ison_southern_women, direction = "in")
#' @export
mode_by_closeness <- function(.data, normalized = TRUE,
direction = c("all", "out", "in")){
.data <- manynet::expect_nodes(.data)
direction <- match.arg(direction)
graph <- manynet::as_igraph(.data)
if (!manynet::is_twomode(.data))
manynet::snet_abort("`mode_by_closeness()` is only defined for two-mode networks; use `net_by_closeness()` for one-mode networks.")
{
clcent <- node_by_closeness(graph, normalized = TRUE, direction = direction)
mode <- igraph::V(graph)$type
mode1 <- length(mode) - sum(mode)
mode2 <- sum(mode)
out <- list()
if (direction == "in") {
out$nodes1 <- sum(max(clcent[!mode]) - clcent[!mode])/(((mode1 - 2)*(mode1 - 1))/(2 * mode1 - 3))
out$nodes2 <- sum(max(clcent[mode]) - clcent[mode])/(((mode2 - 2)*(mode2 - 1))/(2 * mode2 - 3))
if (mode1 > mode2) { #28.43
lhs <- ((mode2 - 1)*(mode1 - 2) / (2 * mode1 - 3))
rhs <- ((mode2 - 1)*(mode1 - mode2) / (mode1 + mode2 - 2))
out$nodes1 <- sum(max(clcent[!mode]) - clcent[!mode])/( lhs + rhs) # 0.2135
}
if (mode2 > mode1) {
lhs <- ((mode1 - 1)*(mode2 - 2) / (2 * mode2 - 3))
rhs <- ((mode1 - 1)*(mode2 - mode1) / (mode2 + mode1 - 2))
out$nodes2 <- sum(max(clcent[mode]) - clcent[mode])/( lhs + rhs)
}
} else {
term1 <- 2*(mode1 - 1) * (mode2 + mode1 - 4)/(3*mode2 + 4*mode1 - 8)
term2 <- 2*(mode1 - 1) * (mode1 - 2)/(2*mode2 + 3*mode1 - 6)
term3 <- 2*(mode1 - 1) * (mode2 - mode1 + 1)/(2*mode2 + 3*mode1 - 4)
out$nodes1 <- sum(max(clcent[!mode]) - clcent) / sum(term1, term2, term3)
term1 <- 2*(mode2 - 1) * (mode1 + mode2 - 4)/(3*mode1 + 4*mode2 - 8)
term2 <- 2*(mode2 - 1) * (mode2 - 2)/(2*mode1 + 3*mode2 - 6)
term3 <- 2*(mode2 - 1) * (mode1 - mode2 + 1)/(2*mode1 + 3*mode2 - 4)
out$nodes2 <- sum(max(clcent[mode]) - clcent) / sum(term1, term2, term3)
if (mode1 > mode2) {
term1 <- 2*(mode2 - 1) * (mode2 + mode1 - 2) / (3 * mode2 + 4 * mode1 - 8)
term2 <- 2*(mode1 - mode2) * (2 * mode2 - 1) / (5 * mode2 + 2 * mode1 - 6)
term3 <- 2*(mode2 - 1) * (mode1 - 2) / (2 * mode2 + 3 * mode1 - 6)
term4 <- 2 * (mode2 - 1) / (mode1 + 4 * mode2 - 4)
out$nodes1 <- sum(max(clcent[!mode]) - clcent) / sum(term1, term2, term3, term4)
}
if (mode2 > mode1) {
term1 <- 2*(mode1 - 1) * (mode1 + mode2 - 2) / (3 * mode1 + 4 * mode2 - 8)
term2 <- 2*(mode2 - mode1) * (2 * mode1 - 1) / (5 * mode1 + 2 * mode2 - 6)
term3 <- 2*(mode1 - 1) * (mode2 - 2) / (2 * mode1 + 3 * mode2 - 6)
term4 <- 2 * (mode1 - 1) / (mode2 + 4 * mode1 - 4)
out$nodes2 <- sum(max(clcent[mode]) - clcent) / sum(term1, term2, term3, term4)
}
}
out <- c("Mode 1" = out$nodes1, "Mode 2" = out$nodes2)
}
out <- make_mode_measure(out, .data, call = deparse(sys.call()))
out
}
#' @rdname measure_centralisation_close
#' @export
net_by_reach <- function(.data, normalized = TRUE, cutoff = 2){
.data <- manynet::expect_nodes(.data)
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, call = deparse(sys.call()))
}
#' @rdname measure_centralisation_close
#' @export
net_by_harmonic <- function(.data, normalized = TRUE, cutoff = 2){
.data <- manynet::expect_nodes(.data)
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, call = deparse(sys.call()))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.