R/mark_nodes.R

Defines functions node_is_mean node_is_min node_is_max node_is_random node_is_exposed node_is_recovered node_is_infected node_is_latent node_is_neighbor node_is_mentor node_is_fold node_is_cutpoint node_is_independent node_is_pendant node_is_isolate

Documented in 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

# 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_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).
#' @inheritParams mark_is
#' @family marks
#' @name mark_nodes
NULL

#' @rdname mark_nodes
#' @examples 
#' node_is_isolate(ison_brandes)
#' @export
node_is_isolate <- function(.data){
  if(missing(.data)) {expect_nodes(); .data <- .G()}
  mat <- as_matrix(.data)
  if(is_twomode(.data)){
    out <- c(rowSums(mat)==0, colSums(mat)==0)
  } else {
    out <- rowSums(mat)==0 & colSums(mat)==0
  }
  names(out) <- node_names(.data)
  make_node_mark(out, .data)
}

#' @rdname mark_nodes
#' @export
node_is_pendant <- function(.data){
  if(missing(.data)) {expect_nodes(); .data <- .G()}
  mat <- as_matrix(.data)
  if(is_twomode(.data)){
    out <- c(rowSums(mat)==1, colSums(mat)==1)
  } else {
    out <- rowSums(mat)==1 & colSums(mat)==1
  }
  make_node_mark(out, .data)
}

#' @rdname mark_nodes
#' @importFrom igraph largest_ivs
#' @references
#' ## On independent sets
#' Tsukiyama, Shuji, Mikio Ide, Hiromu Ariyoshi, and Isao Shirawaka. 1977.
#' "A new algorithm for generating all the maximal independent sets". 
#' _SIAM Journal on Computing_, 6(3):505–517.
#' \doi{10.1137/0206036}
#' @examples
#' node_is_independent(ison_adolescents)
#' @export
node_is_independent <- function(.data){
  if(missing(.data)) {expect_nodes(); .data <- .G()}
  if(is_twomode(.data)){
    samp <- igraph::largest_ivs(to_mode1(.data))
    if(is_labelled(.data)){
      out <- node_names(.data) %in% 
        attr(samp[[sample(1:length(samp), 1)]], 
             "names")
      names(out) <- node_names(.data)
    } else {
      out <- 1:net_nodes(.data) %in% 
        samp[[sample(1:length(samp), 1)]]
    }
  } else {
    samp <- igraph::largest_ivs(to_undirected(as_igraph(.data)))
    if(is_labelled(.data)){
      out <- node_names(.data) %in% 
        attr(samp[[sample(1:length(samp), 1)]], 
             "names")
      names(out) <- node_names(.data)
    } else {
      out <- 1:net_nodes(.data) %in% 
        samp[[sample(1:length(samp), 1)]]
    }
  }
  make_node_mark(out, .data)
}

#' @rdname mark_nodes
#' @importFrom igraph articulation_points
#' @references
#' ## On articulation or cut-points
#' Tarjan, Robert E. and Uzi Vishkin. 1985.
#' "An Efficient Parallel Biconnectivity Algorithm",
#' _SIAM Journal on Computing_ 14(4): 862-874.
#' \doi{10.1137/0214061}
#' @examples 
#' node_is_cutpoint(ison_brandes)
#' @export
node_is_cutpoint <- function(.data){
  if(missing(.data)) {expect_nodes(); .data <- .G()}
  if(is_labelled(.data)){
    out <- node_names(.data) %in% 
      attr(igraph::articulation_points(as_igraph(.data)), 
           "names")
    names(out) <- node_names(.data)
  } else {
    out <- 1:net_nodes(.data) %in% 
      igraph::articulation_points(as_igraph(.data))
  }
  make_node_mark(out, .data)
}

#' @rdname mark_nodes
#' @references
#' ## On structural folds
#' Vedres, Balazs, and David Stark. 2010.
#' "Structural folds: Generative disruption in overlapping groups",
#' _American Journal of Sociology_ 115(4): 1150-1190.
#' \doi{10.1086/649497}
#' @examples
#' node_is_fold(create_explicit(A-B, B-C, A-C, C-D, C-E, D-E))
#' @export
node_is_fold <- function(.data){
  if(missing(.data)) {expect_nodes(); .data <- .G()}
  mult_tri <- igraph::count_triangles(.data)>1
  tris <- igraph::triangles(.data)
  tris <- matrix(tris, length(tris)/3, 3, byrow = TRUE)
  out <- vapply(seq_along(mult_tri), function(x){
    if(!mult_tri[x]) FALSE else {
      tri_neigh <- unique(c(tris[apply(tris, 1, function(r) any(x %in% r)),] ))
      tri_neigh <- tri_neigh[tri_neigh != x]
      all(rowSums(igraph::distances(.data, tri_neigh, tri_neigh)==2)>=2)
    }
  }, FUN.VALUE = logical(1) )
  make_node_mark(out, .data)
}

#' @rdname mark_nodes
#' @param elites The proportion of nodes to be selected as mentors.
#'   By default this is set at 0.1.
#'   This means that the top 10% of nodes in terms of degree,
#'   or those equal to the highest rank degree in the network,
#'   whichever is the higher, will be used to select the mentors.
#'   
#'   Note that if nodes are equidistant from two mentors,
#'   they will choose one at random.
#'   If a node is without a path to a mentor,
#'   for example because they are an isolate,
#'   a tie to themselves (a loop) will be created instead.
#'   Note that this is a different default behaviour than that
#'   described in Valente and Davis (1999).
#' @references
#' ## On mentoring
#' Valente, Thomas, and Rebecca Davis. 1999.
#' "Accelerating the Diffusion of Innovations Using Opinion Leaders",
#' _Annals of the American Academy of Political and Social Science_ 566: 56-67.
#' @export
node_is_mentor <- function(.data, elites = 0.1){
  if(missing(.data)) {expect_nodes(); .data <- .G()}
  indegs <- colSums(manynet::as_matrix(.data)) # get rank order of indegrees
  out <- indegs == max(indegs)
  if(sum(out) < length(indegs)*elites){
    out <- indegs %in% unique(sort(indegs, decreasing=TRUE)[seq_len(length(indegs)*elites)])
  }
  make_node_mark(out, .data)
}

#' @rdname mark_nodes
#' @inheritParams manip_scope
#' @export
node_is_neighbor <- function(.data, node){
  if(missing(.data)) {expect_nodes(); .data <- .G()}
  .data <- as_igraph(.data)
  out <- igraph::V(.data) %in% igraph::neighbors(.data, v = node)
  make_node_mark(out, .data)
}

# Diffusion properties ####

#' Marking nodes based on diffusion 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_infected()` marks nodes that are infected by a particular time point. 
#'   - `node_is_exposed()` marks nodes that are exposed to a given (other) mark.
#'   - `node_is_latent()` marks nodes that are latent at a particular time point.
#'   - `node_is_recovered()` marks nodes that are recovered at a particular time point.
#' @inheritParams mark_is
#' @family marks
#' @name mark_diff
NULL

#' @rdname mark_diff 
#' @examples
#'   # To mark nodes that are latent by a particular time point
#'   node_is_latent(play_diffusion(create_tree(6), latency = 1), time = 1)
#' @export
node_is_latent <- function(.data, time = 0){
  if(inherits(.data, "diff_model")){
    event <- nodes <- n <- NULL
    latent <- summary(.data) %>%
      dplyr::filter(t <= time & event %in% c("E", "I")) %>%
      group_by(nodes) %>%
      mutate(n = dplyr::n()) %>%
      filter(n == 1 & event == "E")
    net <- attr(.data, "network")
    if (is_labelled(net)) {
      out <- seq_len(net_nodes(net)) %in% latent$nodes
      names(out) <- node_names(net)
    } else {
      out <- seq_len(net_nodes(net)) %in% latent$nodes
    }
    make_node_mark(out, net)
  } else {
    out <- to_time(.data, time)
    out <- node_attribute(out, "diffusion") == "E"
    make_node_mark(out, .data)
  }
}

#' @rdname mark_diff 
#' @param time A time step at which nodes are identified.
#' @examples
#'   # To mark nodes that are infected by a particular time point
#'   node_is_infected(play_diffusion(create_tree(6)), time = 1)
#' @export
node_is_infected <- function(.data, time = 0) {
  if(inherits(.data, "diff_model")){
    event <- nodes <- n <- NULL
    infected <- summary(.data) %>% 
      dplyr::filter(t <= time & event %in% c("I", "R")) %>%
      group_by(nodes) %>%
      mutate(n = dplyr::n()) %>%
      filter(n == 1 & event == "I")
    net <- attr(.data, "network")
    if (is_labelled(net)) {
      out <- seq_len(net_nodes(net)) %in% infected$nodes
      names(out) <- node_names(net)
    } else {
      out <- seq_len(net_nodes(net)) %in% infected$nodes
    }
    make_node_mark(out, net)
  } else {
    out <- to_time(.data, time)
    out <- node_attribute(out, "diffusion") == "I"
    make_node_mark(out, .data)
  }
}

#' @rdname mark_diff 
#' @examples
#'   # To mark nodes that are recovered by a particular time point
#'   node_is_recovered(play_diffusion(create_tree(6), recovery = 0.5), time = 3)
#' @export
node_is_recovered <- function(.data, time = 0){
  if(inherits(.data, "diff_model")){
    event <- nodes <- n <- NULL
    recovered <- summary(.data) %>% 
      dplyr::filter(t <= time & event == "R") %>%
      group_by(nodes) %>%
      mutate(n = dplyr::n()) %>%
      filter(n == 1)
    net <- attr(.data, "network")
    if (is_labelled(net)) {
      out <- seq_len(net_nodes(net)) %in% recovered$nodes
      names(out) <- node_names(net)
    } else {
      out <- seq_len(net_nodes(net)) %in% recovered$nodes
    }
    make_node_mark(out, net)
  } else {
    out <- to_time(.data, time)
    out <- node_attribute(out, "diffusion") == "R"
    make_node_mark(out, .data)
  }
}

#' @rdname mark_diff 
#' @param mark A valid 'node_mark' object or
#'   logical vector (TRUE/FALSE) of length equal to 
#'   the number of nodes in the network.
#' @section Exposed:
#'   `node_is_exposed()` is similar to `node_exposure()`,
#'   but returns a mark (TRUE/FALSE) vector indicating which nodes
#'   are currently exposed to the diffusion content.
#'   This diffusion content can be expressed in the 'mark' argument.
#'   If no 'mark' argument is provided,
#'   and '.data' is a diff_model object,
#'   then the function will return nodes exposure to the seed nodes
#'   in that diffusion.
#' @param mark vector denoting which nodes are infected
#' @examples
#'   # To mark which nodes are currently exposed
#'   (expos <- node_is_exposed(manynet::create_tree(14), mark = c(1,3)))
#'   which(expos)
#' @export
node_is_exposed <- function(.data, mark){
  event <- nodes <- NULL
  if (missing(mark) && inherits(.data, "diff_model")){
    mark <- summary(.data) %>% 
      dplyr::filter(t == 0 & event == "I") %>% 
      dplyr::select(nodes) %>% unlist()
    .data <- attr(.data, "network")
  }
  if(is.logical(mark)) mark <- which(mark)
  out <- rep(F, manynet::net_nodes(.data))
  out[unique(setdiff(unlist(igraph::neighborhood(.data, nodes = mark)),
                     mark))] <- TRUE
  make_node_mark(out, .data)
}

# Selection properties ####

#' Marking nodes for selection based on measures
#' 
#' @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_random()` marks one or more nodes at random.
#'   - `node_is_max()` and `node_is_min()` are more generally useful
#'   for converting the results from some node measure into a mark-class object.
#'   They can be particularly useful for highlighting which node or nodes
#'   are key because they minimise or, more often, maximise some measure.
#' @inheritParams mark_is
#' @family marks
#' @name mark_select
NULL

#' @rdname mark_select
#' @param size The number of nodes to select (as TRUE).
#' @examples 
#' node_is_random(ison_brandes, 2)
#' @export
node_is_random <- function(.data, size = 1){
  n <- manynet::net_nodes(.data)
  out <- rep(FALSE, n)
  out[sample.int(n, size)] <- TRUE
  make_node_mark(out, .data)
}

#' @rdname mark_select
#' @param node_measure An object created by a `node_` measure.
#' @param ranks The number of ranks of max or min to return.
#'   For example, `ranks = 3` will return TRUE for nodes with
#'   scores equal to any of the top (or, for `node_is_min()`, bottom)
#'   three scores.
#'   By default, `ranks = 1`.
#' @examples 
#' #node_is_max(migraph::node_degree(ison_brandes))
#' @export
node_is_max <- function(node_measure, ranks = 1){
  if(!inherits(node_measure, "node_measure"))
    snet_abort("This function expects an object of class `node_measure`")
  if(any(attr(node_measure, "mode"))){
    mode1 <- as.numeric(node_measure)[!as.logical(attr(node_measure, "mode"))]
    max1 <- mode1[order(mode1, decreasing = TRUE)[1:ranks]]
    mode2 <- as.numeric(node_measure)[as.logical(attr(node_measure, "mode"))]
    max2 <- mode2[order(mode2, decreasing = TRUE)[1:ranks]]
    out <- ((as.numeric(node_measure) %in% max1 & 
               !as.logical(attr(node_measure, "mode"))) | 
              (as.numeric(node_measure) %in% max2 & 
                 as.logical(attr(node_measure, "mode"))))
    attr(out, "mode") <- attr(node_measure, "mode")
  } else {
    out <- node_measure %in% node_measure[order(node_measure,
                                                decreasing = TRUE)[1:ranks]]
  }
  names(out) <- attr(node_measure, "names")
  class(out) <- c("node_mark", class(out))
  out
}

#' @rdname mark_select
#' @examples 
#' #node_is_min(migraph::node_degree(ison_brandes))
#' @export
node_is_min <- function(node_measure, ranks = 1){
  if(!inherits(node_measure, "node_measure"))
    snet_abort("This function expects an object of class `node_measure`")
  if(any(attr(node_measure, "mode"))){
    mode1 <- as.numeric(node_measure)[!as.logical(attr(node_measure, "mode"))]
    max1 <- mode1[order(mode1, decreasing = FALSE)[1:ranks]]
    mode2 <- as.numeric(node_measure)[as.logical(attr(node_measure, "mode"))]
    max2 <- mode2[order(mode2, decreasing = FALSE)[1:ranks]]
    out <- ((as.numeric(node_measure) %in% max1 & 
               !as.logical(attr(node_measure, "mode"))) | 
              (as.numeric(node_measure) %in% max2 & 
                 as.logical(attr(node_measure, "mode"))))
    attr(out, "mode") <- attr(node_measure, "mode")
  } else {
    out <- node_measure %in% node_measure[order(node_measure,
                                                decreasing = FALSE)[1:ranks]]
  }
  names(out) <- attr(node_measure, "names")
  class(out) <- c("node_mark", class(out))
  out
}

#' @rdname mark_select
#' @examples 
#' #node_is_mean(node_degree(ison_brandes))
#' @export
node_is_mean <- function(node_measure, ranks = 1){
  if(!inherits(node_measure, "node_measure"))
    snet_abort("This function expects an object of class `node_measure`")
  if(any(attr(node_measure, "mode"))){
    mode1 <- node_measure[!as.logical(attr(node_measure, "mode"))]
    out <- mode1 == sort(abs(mode1 - mean(mode1)))[ranks]
    mode2 <- node_measure[as.logical(attr(node_measure, "mode"))]
    out <- c(out, mode2 == sort(abs(mode2 - mean(mode2)))[ranks])
    attr(out, "mode") <- as.logical(attr(node_measure, "mode"))
  } else {
    out <- node_measure == sort(abs(node_measure - mean(node_measure)))[ranks]
  }
  class(out) <- c("node_mark", class(out))
  out
}

Try the manynet package in your browser

Any scripts or data that you put into this service are public.

manynet documentation built on June 23, 2025, 9:07 a.m.