Nothing
# Network diversity ####
#' Measures of network diversity
#' @name measure_diverse_net
#' @description
#' These functions offer ways to measure the heterogeneity of an attribute
#' across a network, within groups of a network, or the distribution of ties
#' across this attribute:
#'
#' - `net_by_richness()` measures the number of unique categories
#' in a network attribute.
#' - `net_by_diversity()` measures the heterogeneity of ties across a network.
#'
#' @template param_data
#' @template param_attr
#' @family diversity
#' @template net_measure
#' @param diversity Which method to use for `*_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.
NULL
#' @rdname measure_diverse_net
#' @section Richness:
#' Richness is a simple count of the number of different categories
#' present for a given attribute.
#' @references
#' ## On richness
#' Magurran, Anne E. 1988.
#' _Ecological Diversity and Its Measurement_.
#' Princeton: Princeton University Press.
#' \doi{10.1007/978-94-015-7358-0}
#' @examples
#' net_by_richness(ison_networkers)
#' @export
net_by_richness <- function(.data, attribute){
.data <- manynet::expect_nodes(.data)
make_network_measure(length(unique(manynet::node_attribute(.data, attribute))),
.data, call = deparse(sys.call()))
}
#' @rdname measure_diverse_net
#' @section Diversity:
#' Blau's index (1977) uses a formula known also in other disciplines
#' by other names
#' (Gini-Simpson Index, Gini impurity, Gini's diversity index,
#' Gibbs-Martin index, and probability of interspecific encounter (PIE)):
#' \deqn{1 - \sum\limits_{i = 1}^k {p_i^2 }}
#' where \eqn{p_i} is the proportion of group members in \eqn{i}th category
#' and \eqn{k} is the number of categories for an attribute of interest.
#' This index can be interpreted as the probability that two members
#' randomly selected from a group would be from different categories.
#' This index finds its minimum value (0) when there is no variety,
#' i.e. when all individuals are classified in the same category.
#' The maximum value depends on the number of categories and
#' whether nodes can be evenly distributed across categories.
#'
#' Teachman's index (1980) is based on information theory
#' and is calculated as:
#' \deqn{- \sum\limits_{i = 1}^k {p_i \log(p_i)}}
#' where \eqn{p_i} is the proportion of group members in \eqn{i}th category
#' and \eqn{k} is the number of categories for an attribute of interest.
#' This index finds its minimum value (0) when there is no variety,
#' i.e. when all individuals are classified in the same category.
#' The maximum value depends on the number of categories and
#' whether nodes can be evenly distributed across categories.
#' It thus shares similar properties to Blau's index,
#' but includes also a notion of richness that tends to give more weight to
#' rare categories and thus tends to highlight imbalances more.
#'
#' The coefficient of variation (CV) is a standardised measure of dispersion
#' of a probability distribution or frequency distribution.
#' It is defined as the ratio of the standard deviation \eqn{\sigma}
#' to the mean \eqn{\mu}:
#' \deqn{CV = \frac{\sigma}{\mu}}
#' It is often expressed as a percentage.
#' The CV is useful because the standard deviation of data must always be understood
#' in the context of the mean of the data.
#' The CV is particularly useful when comparing the degree of variation
#' from one data series to another,
#' even if the means are drastically different from each other.
#'
#' The Gini coefficient is a measure of statistical dispersion
#' that is intended to represent the income or wealth distribution
#' of a nation's residents,
#' and is commonly used as a measure of inequality.
#' It is defined as a ratio with values between 0 and 1,
#' where 0 corresponds with perfect equality
#' (everyone has the same income) and 1 corresponds with perfect inequality
#' (one person has all the income, and everyone else has zero income).
#' The Gini coefficient can be calculated from the Lorenz curve,
#' which plots the proportion of the total income of the population
#' that is cumulatively earned by the bottom x% of the population.
#' The Gini coefficient is defined as the area between the line of equality
#' and the Lorenz curve,
#' divided by the total area under the line of equality.
#' @references
#' ## On diversity
#' Blau, Peter M. 1977.
#' _Inequality and heterogeneity_.
#' New York: Free Press.
#'
#' Teachman, Jay D. 1980.
#' Analysis of population diversity: Measures of qualitative variation.
#' _Sociological Methods & Research_, 8:341-362.
#' \doi{10.1177/004912418000800305}
#'
#' Page, Scott E. 2010.
#' _Diversity and Complexity_.
#' Princeton: Princeton University Press.
#' \doi{10.1515/9781400835140}
#' @examples
#' marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive")
#' net_by_diversity(marvel_friends, "Gender")
#' net_by_diversity(marvel_friends, "Appearances")
#' @export
net_by_diversity <- function(.data, attribute,
diversity = c("blau","teachman","variation","gini")){
.data <- manynet::expect_nodes(.data)
blau <- function(features) { 1 - sum((table(features)/length(features))^2) }
teachman <- function(features) {
p <- table(features)/length(features)
-sum(p * log(p))
}
cv <- function(values) {
stats::sd(values, na.rm = TRUE) / mean(values, na.rm = TRUE) }
gini <- function(values) {
x <- sort(values)
n <- length(x)
G <- sum(x * (1:n))
return((2 * G) / (n * sum(x)) - (n + 1) / n)
}
attr <- manynet::node_attribute(.data, attribute)
diversity <- match.arg(diversity)
if(is.numeric(attr) && diversity %in% c("blau","teachman")){
manynet::snet_info("{.val {diversity}} index is not appropriate for numeric attributes.")
manynet::snet_info("Using {.val variation} coefficient instead",
"({.val gini} coefficient also available).")
diversity <- "variation"
}
if(is.character(attr) && diversity %in% c("variation","gini")){
manynet::snet_info("{.val {diversity}} coefficient is not appropriate for categorical attributes.")
manynet::snet_info("Using {.val blau} index instead",
"({.val teachman} index also available).")
diversity <- "blau"
}
out <- switch(diversity,
blau = blau(attr),
teachman = teachman(attr),
variation = cv(attr),
gini = gini(attr))
make_network_measure(out, .data, call = deparse(sys.call()))
}
# Nodal diversity ####
#' Measures of nodes diversity
#' @name measure_diverse_node
#' @description
#' These functions offer ways to measure the heterogeneity of an attribute
#' across a network, within groups of a network, or the distribution of ties
#' across this attribute:
#'
#' - `node_by_richness()` measures the number of unique categories
#' of an attribute to which each node is connected.
#' - `node_by_diversity()` measures the heterogeneity of each node's
#' local neighbourhood.
#'
#' @template param_data
#' @template param_attr
#' @family diversity
#' @template node_measure
#' @inheritParams measure_diverse_net
NULL
#' @rdname measure_diverse_node
#' @examples
#' node_by_richness(ison_networkers, "Discipline")
#' @export
node_by_richness <- function(.data, attribute){
.data <- manynet::expect_nodes(.data)
out <- vapply(manynet::to_egos(.data, min_dist = 1),
function(x) length(unique(manynet::node_attribute(x, attribute))),
FUN.VALUE = numeric(1))
make_node_measure(out, .data)
}
#' @rdname measure_diverse_node
#' @examples
#' marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive")
#' node_by_diversity(marvel_friends, "Gender")
#' node_by_diversity(marvel_friends, "Attractive")
#' @export
node_by_diversity <- function(.data, attribute,
diversity = c("blau","teachman","variation","gini")){
.data <- manynet::expect_nodes(.data)
attr <- manynet::node_attribute(.data, attribute)
diversity <- match.arg(diversity)
if(is.numeric(attr) && diversity %in% c("blau","teachman")){
manynet::snet_info("{.val {method}} index is not appropriate for numeric attributes.")
manynet::snet_info("Using {.val variation} coefficient instead",
"({.val gini} coefficient also available).")
diversity <- "variation"
}
if(is.character(attr) && diversity %in% c("variation","gini")){
manynet::snet_info("{.val {diversity}} coefficient is not appropriate for categorical attributes.")
manynet::snet_info("Using {.val blau} index instead",
"({.val teachman} index also available).")
diversity <- "blau"
}
out <- vapply(igraph::ego(manynet::as_igraph(.data)),
function(x) net_by_diversity(
igraph::induced_subgraph(manynet::as_igraph(.data), x),
attribute, diversity = diversity),
FUN.VALUE = numeric(1))
make_node_measure(out, .data)
}
# Network assortativity ####
#' Measures of network assortativity
#' @name measure_assort_net
#' @description
#' These functions offer ways to measure the distribution or assortativity
#' of ties in a network:
#'
#' - `net_by_heterophily()` measures how embedded nodes in the network
#' are within groups of nodes with the same attribute.
#' - `net_by_homophily()` measures how embedded nodes in the network
#' are within groups of nodes with the same attribute, but with more options for method.
#' - `net_by_assortativity()` measures the degree assortativity in a network.
#' - `net_by_spatial()` measures the spatial association/autocorrelation
#' (global Moran's I) in a network.
#'
#' Note that for two-mode networks,
#' homophily is calculated on the mode with the attribute of interest,
#' and the other mode is ignored.
#' If the attribute is present on both modes,
#' homophily is calculated on the first mode by default,
#' but a message is given and the user can choose to calculate homophily on the
#' other mode instead by subsetting the attribute vector and
#' converting the network to a one-mode network.
#' @template param_data
#' @template param_attr
#' @param assortativity Which method to use for `*_homophily()`.
#' Either "ie" (negative E-I index), "ei" (E-I index),
#' "yule" (Yule's Q), or "geary" (Geary's C).
#' Default is "ie".
#' The E-I index is the number of ties between (or _external_) nodes
#' grouped in some mutually exclusive categories
#' minus the number of ties within (or _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.
#'
#' Yule's Q is a measure of association for two binary variables, calculated as:
#' \deqn{Q = \frac{ad - bc}{ad + bc}}
#' where \eqn{a} is the number of ties between nodes in the same category,
#' \eqn{b} is the number of ties between nodes in different categories,
#' \eqn{c} is the number of non-ties between nodes in the same category,
#' and \eqn{d} is the number of non-ties between nodes in different categories.
#' This value can range from -1 to 1, where 1 indicates perfect association
#' (all ties are between nodes in the same category), -1 indicates perfect disassociation
#' (all ties are between nodes in different categories), and 0 indicates no association.
#'
#' Geary's C is a measure of spatial autocorrelation, calculated as:
#' \deqn{C = \frac{(n - 1) \sum
#' \limits_{i=1}^n \sum\limits_{j=1}^n w_{ij} (x_i - x_j)^2}{2W \sum\limits_{i=1}^n (x_i - \bar{x})^2}}
#' where \eqn{n} is the number of nodes, \eqn{
#' w_{ij}} is the weight of the tie between nodes \eqn{i} and \eqn{j},
#' \eqn{x_i} is the attribute value of node \eqn{i},
#' \eqn{\bar{x}} is the mean attribute value across all nodes, and \eqn{W} is the sum of all tie weights.
#' This value can range from 0 to 2, where values less than 1
#' indicate positive autocorrelation (similar values are more likely to be connected),
#' values greater than 1 indicate negative autocorrelation (dissimilar values are more likely
#' to be connected), and a value of 1 indicates no autocorrelation.
#' If an incompatible method is chosen for the attribute type,
#' a suitable alternative will be used instead with a message.
#' @family diversity
#' @template net_measure
NULL
#' @rdname measure_assort_net
#' @section Heterophily:
#' 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
#' grouped in some mutually exclusive categories
#' minus the number of ties within (or _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.
#' @references
#' ## On heterophily
#' Krackhardt, David, and Robert N. Stern. 1988.
#' Informal networks and organizational crises: an experimental simulation.
#' _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".
#' _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
net_by_heterophily <- function(.data, attribute){
.data <- manynet::expect_nodes(.data)
m <- manynet::as_matrix(.data)
if (length(attribute) == 1 && is.character(attribute)) {
attribute <- manynet::node_attribute(.data, attribute)
}
if (is.character(attribute) | is.numeric(attribute)) {
attribute <- as.factor(attribute)
}
same <- outer(attribute, attribute, "==")
nInternal <- sum(m * same, na.rm = TRUE)
nExternal <- sum(m, na.rm = TRUE) - nInternal
ei <- (nExternal - nInternal) / sum(m, na.rm = TRUE)
make_network_measure(ei, .data, call = deparse(sys.call()))
}
#' @rdname measure_assort_net
#' @examples
#' net_by_homophily(marvel_friends, "Gender")
#' @export
net_by_homophily <- function(.data, attribute,
assortativity = c("ie","ei","yule","geary")){
.data <- manynet::expect_nodes(.data)
# mode <- attr_mode(.data, attribute)
# if(is_twomode(.data) && !is.null(mode)){
# if(mode){
# snet_info("Attribute only present on second mode of two-mode network.")
# snet_info("Calculating homophily on first mode instead.")
# attribute <- manynet::node_attribute(.data, attribute)[
# !manynet::node_is_mode(.data)]
# } else {
# snet_info("Attribute only present on first mode of two-mode network.")
# snet_info("Calculating homophily on second mode instead.")
# attribute <- manynet::node_attribute(.data, attribute)[
# manynet::node_is_mode(.data)]
# }
# .data <- manynet::to_mode(.data, mode = mode)
# }
if (length(attribute) == 1 && is.character(attribute)) {
attribute <- manynet::node_attribute(.data, attribute)
}
assortativity <- match.arg(assortativity)
if(is.numeric(attribute) && assortativity %in% c("ie","ei","yule")){
manynet::snet_info("{.val {assortativity}} index is not appropriate for numeric attributes.")
manynet::snet_info("Using {.val geary}'s C instead.")
assortativity <- "geary"
}
if(!is.numeric(attribute) && assortativity == "geary"){
manynet::snet_info("{.val {assortativity}} index is not appropriate for categorical attributes.")
manynet::snet_info("Using {.val ie} index instead.")
assortativity <- "ie"
}
m <- manynet::as_matrix(manynet::to_unweighted(.data))
ei <- function(m, attribute){
same <- outer(attribute, attribute, "==")
nInternal <- sum(m * same, na.rm = TRUE)
nExternal <- sum(m, na.rm = TRUE) - nInternal
(nExternal - nInternal) / sum(m, na.rm = TRUE)
}
yule <- function(m, attribute){
same <- outer(attribute, attribute, "==")
a <- sum(m * same, na.rm = TRUE)
b <- sum(m * (!same), na.rm = TRUE)
c <- sum((1 - m) * same, na.rm = TRUE)
d <- sum((1 - m) * (!same), na.rm = TRUE)
(a*d - b*c)/(a*d + b*c)
}
geary <- function(m, attribute){
# identify valid nodes
valid <- !is.na(attribute)
attr_valid <- attribute[valid]
m_valid <- m[valid, valid, drop = FALSE]
# recompute n and mean on valid nodes
n <- length(attr_valid)
xbar <- mean(attr_valid, na.rm = TRUE)
# pairwise squared differences
diffsq <- (outer(attr_valid, attr_valid, "-"))^2
# weight sum
W <- sum(m_valid, na.rm = TRUE)
# numerator and denominator
num <- (n - 1) * sum(m_valid * diffsq, na.rm = TRUE)
den <- 2 * W * sum((attr_valid - xbar)^2, na.rm = TRUE)
if (den == 0) return(NA_real_)
num / den
}
res <- switch(match.arg(assortativity),
ie = -ei(m, attribute),
ei = ei(m, attribute),
yule = yule(m, attribute),
geary = geary(m, attribute))
make_network_measure(res, .data, call = deparse(sys.call()))
}
attr_mode <- function(.data, attribute){
if(manynet::is_twomode(.data)){
miss <- is.na(manynet::node_attribute(.data, attribute))
mode <- manynet::node_is_mode(.data)
if(all(miss[mode])) return(FALSE) # attribute only on first (FALSE) mode
if(all(miss[!mode])) return(TRUE) # attribute only on second (TRUE) mode
} else NULL
}
#' @rdname measure_assort_net
#' @importFrom igraph assortativity_degree
#' @references
#' ## On assortativity
#' Newman, Mark E.J. 2002.
#' "Assortative mixing in networks".
#' _Physical Review Letters_, 89(20): 208701.
#' \doi{10.1103/physrevlett.89.208701}
#' @examples
#' net_by_assortativity(ison_networkers)
#' @export
net_by_assortativity <- function(.data){
.data <- manynet::expect_nodes(.data)
make_network_measure(igraph::assortativity_degree(manynet::as_igraph(.data),
directed = manynet::is_directed(.data)),
.data, call = deparse(sys.call()))
}
#' @rdname measure_assort_net
#' @references
#' ## On spatial autocorrelation
#' Moran, Patrick Alfred Pierce. 1950.
#' "Notes on continuous stochastic phenomena".
#' _Biometrika_ 37(1): 17-23.
#' \doi{10.2307/2332142}
#' @examples
#' net_by_spatial(ison_lawfirm, "age")
#' @export
net_by_spatial <- function(.data, attribute){
.data <- manynet::expect_nodes(.data)
N <- manynet::net_nodes(.data)
x <- manynet::node_attribute(.data, attribute)
stopifnot(is.numeric(x))
x_bar <- mean(x, na.rm = TRUE)
w <- manynet::as_matrix(.data)
W <- sum(w, na.rm = TRUE)
I <- (N/W) *
(sum(w * matrix(x - x_bar, N, N) * matrix(x - x_bar, N, N, byrow = TRUE)) /
sum((x - x_bar)^2))
make_network_measure(I, .data,
call = deparse(sys.call()))
}
# Network assortativity ####
#' Measures of nodes assortativity
#' @name measure_assort_node
#' @description
#' These functions offer ways to measure nodes' assortativity in a network:
#'
#' - `node_by_heterophily()` measures each node's embeddedness within groups
#' of nodes with the same attribute.
#' - `node_by_homophily()` measures each node's embeddedness within groups
#' of nodes with the same attribute, using a variety of methods.
#'
#' @template param_data
#' @template param_attr
#' @family diversity
#' @template node_measure
#' @inheritParams measure_assort_net
NULL
#' @rdname measure_assort_node
#' @examples
#' marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive")
#' node_by_heterophily(marvel_friends, "Gender")
#' node_by_heterophily(marvel_friends, "Attractive")
#' @export
node_by_heterophily <- function(.data, attribute){
.data <- manynet::expect_nodes(.data)
m <- manynet::as_matrix(.data)
if (length(attribute) == 1 && is.character(attribute)) {
attribute <- manynet::node_attribute(.data, attribute)
}
if (is.character(attribute) | is.numeric(attribute)) {
attribute <- as.factor(attribute)
}
if(anyNA(attribute)){
m[is.na(attribute),] <- NA
m[,is.na(attribute)] <- NA
}
same <- outer(attribute, attribute, "==")
nInternal <- rowSums(m * same, na.rm = TRUE)
nInternal[is.na(attribute)] <- NA
nExternal <- rowSums(m, na.rm = TRUE) - nInternal
ei <- (nExternal - nInternal) / rowSums(m, na.rm = TRUE)
make_node_measure(ei, .data)
}
#' @rdname measure_assort_node
#' @export
node_by_homophily <- function(.data, attribute,
assortativity = c("ie","ei","yule","geary")){
.data <- manynet::expect_nodes(.data)
# if (length(attribute) == 1 && is.character(attribute)) {
# attribute <- manynet::node_attribute(.data, attribute)
# }
assortativity <- match.arg(assortativity)
if(is.numeric(attribute) && assortativity %in% c("ie","ei","yule")){
manynet::snet_info("{.val {assortativity}} index is not appropriate for numeric attributes.")
manynet::snet_info("Using {.val geary}'s C instead.")
assortativity <- "geary"
}
if(!is.numeric(attribute) && assortativity == "geary"){
manynet::snet_info("{.val {assortativity}} index is not appropriate for categorical attributes.")
manynet::snet_info("Using {.val ie} index instead.")
assortativity <- "ie"
}
idat <- manynet::as_igraph(.data)
out <- vapply(igraph::ego(idat),
function(x) {
subattr <- if (length(attribute) == 1 && is.character(attribute))
attribute else attribute[as.integer(x)]
net_by_homophily(
igraph::induced_subgraph(idat, x),
subattr, assortativity = assortativity)
},
FUN.VALUE = numeric(1))
make_node_measure(out, .data)
}
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.