diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4e13534ef..d94118716 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,7 +4,6 @@ - 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 diff --git a/DESCRIPTION b/DESCRIPTION index 886afecaa..75c4c9661 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: manynet Title: Many Ways to Make, Manipulate, and Modify Myriad Networks -Version: 2.1.2 +Version: 2.1.3 Description: Many tools for making, manipulating, and modifying many different types of networks. All functions operate with matrices, edge lists, and 'igraph', 'network', and 'tidygraph' objects, on directed, multiplex, multimodal, signed, and other networks. diff --git a/NEWS.md b/NEWS.md index 63d6f4fce..03b122f53 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,20 @@ +# manynet 2.1.3 + +## Class + +- Fixed stocnet validation test issue + +## Describing + +- Fixed `mode_names.stocnet()` and `layer_names.stocnet()` to look up correct info names + +## Manipulating + +- Improved `add_info()` + - `add_info(.data)` (so no further arguments) now checks to see whether metadata may be added and prompts the user to add it if so + - `add_info(.data, optional = TRUE)` extends this to further, optional metadata + - This includes prompting the user by layer for multiplex networks + # manynet 2.1.2 ## Package diff --git a/R/manip_info.R b/R/manip_info.R index 1619090a2..6663c94e2 100644 --- a/R/manip_info.R +++ b/R/manip_info.R @@ -5,8 +5,6 @@ #' @description #' These functions allow users to add and edit information about the network #' itself. -#' This includes the name, year, and mode of collection of the network, -#' as well as definitions of the nodes and ties in the network. #' Where available, this information is printed for tidygraph-class objects, #' and can be used for printing a grand table in the `{grand}` package. #' @@ -18,11 +16,28 @@ #' @eval detail_avail(".*_info") #' @template fam_manip #' @param ... Named attributes. The following are currently recognised: -#' "name", "year", and "doi" of the network, -#' "collection" or "mode" of the network -#' ("survey", "interview","sensor","observation","archival", or "simulation"), -#' "nodes" (a vector of the names of the nodes) or "vertex1"/"vertex2", -#' "ties" or "edge.pos"/"edge.neg" for defining the ties. +#' - "name" is the name of the network +#' - "modes" is the name(s) of the nodeset(s) +#' - "layers" is the name(s) of the tie type(s) +#' - "directed" is a logical vector indicating whether each layer is directed +#' - "source" is the source of the network ("empirical" or "synthetic") +#' - "method" is the method of data collection or model used +#' (e.g. "survey", "interview","sensor","observation","archival", or "simulation") +#' - "location" is the geographic, institutional, or digital location of the network +#' - "date" is the date of data collection or model run +#' - "boundary" is the boundary specification of the network ("ego", "roster", or "snowball") +#' - "observation" is the observation type of the network ("cross-sectional", +#' "panel", or "event") +#' - "update" is the update type of the network ("increment" or "replacement") +#' - "max_degree" is the maximum degree of the network +#' - "min_degree" is the minimum degree of the network +#' - "doi" is the DOI or URL of the network +#' +#' If no arguments are used, +#' the function will check for missing information and prompt the user to add it. +#' If `optional = TRUE` is specified, the function will also prompt for optional information. +#' @seealso \href{https://grand-statement.org}{GRAND statement} for more +#' information on the Guidelines for Reporting About Network Data (GRAND). #' @examples #' add_info(ison_algebra, name = "Algebra") #' @export @@ -42,7 +57,9 @@ add_info.igraph <- function(.data, ...){ } info <- list(...) - if(length(info)==0) return(.check_info(.data)) + optional <- info$optional %||% FALSE + info$optional <- NULL + if(length(info)==0) return(.check_info(.data, optional = optional)) unrecog <- setdiff(names(info), c("name", "nodes", "ties", "doi", "source", "method", "location", "date", "system", @@ -85,6 +102,12 @@ add_info.igraph <- function(.data, ...){ #' @export add_info.stocnet <- function(.data, ...){ dots <- list(...) + # if dots contains optional = FALSE/TRUE + optional <- dots$optional %||% FALSE + dots$optional <- NULL + if(length(dots) == 0){ + return(.check_info(.data, optional = optional)) + } for(item in names(dots)){ .data$info[[item]] <- dots[[item]] } @@ -130,48 +153,104 @@ net_attributes <- function(.data){ .check_info <- function(.data, optional = FALSE){ out <- .data + read_optional <- function(prompt) { + x <- readline(prompt) + if (x == "") NULL else x + } # Names - if(is.null(net_name(.data)) || net_name(.data) == ""){ + if(is.null(net_name(out)) || net_name(out) == ""){ snet_prompt("This network does not have a name. Please add one.") - out <- add_info(out, name = readline(prompt = "Network name: ")) - } else snet_success("Network name: {net_name(out)}") + out <- add_info(out, name = read_optional(prompt = "Network name: ")) + } + if(!is.null(net_name(out))) snet_success("Network name: {net_name(out)}") - # Nodes - if(is_twomode(.data) && is.null(mode_names(.data))){ + # Nodes #### + if(is.null(mode_names(out))){ + if(is_twomode(out)){ snet_prompt("This two-mode network does not have names for the nodesets. Please add one.") - out$nodes <- c(readline(prompt = "Nodeset 1 name: "), - readline(prompt = "Nodeset 2 name: ")) - } else if(is_twomode(.data)){ - snet_success("Nodesets: {mode_names(out)}") - } else if(!is_twomode(.data) && is.null(mode_names(.data))){ - snet_prompt("This network does not have a name for the nodes. Please add one.") - out <- add_info(out, nodes = readline(prompt = "Nodeset name: ")) - } else snet_success("Nodeset: {mode_names(out)}") + out$modes <- c(read_optional(prompt = "Nodeset 1 name: "), + read_optional(prompt = "Nodeset 2 name: ")) + } else { + snet_prompt("This network does not have a name for the nodeset. Please add one.") + out <- add_info(out, modes = read_optional(prompt = "Nodeset name: ")) + }} + if(!is.null(mode_names(out))) snet_success("Modes: {mode_names(out)}") + + # Ties #### + if(is.null(layer_names(out))){ + snet_prompt("This network does not have a name for the layer/type of tie. Please add one.") + out <- add_info(out, layers = read_optional(prompt = "Layer name: ")) + } + if(!is.null(layer_names(out))) snet_success("Layers: {layer_names(out)}") + if(!"directed" %in% net_attributes(out)){ + if(net_layers(out) > 1){ + snet_prompt("This network has multiple layers. Please specify whether they are directed or undirected.") + for (layer in layer_names(out)) { + directed <- utils::menu(choices = c("Directed", "Undirected"), + title = paste0("Is the layer '", layer, "' directed or undirected?")) + out$info$directed[match(layer, layer_names(out))] <- stats::setNames(directed == 1, layer) + } + } else out <- add_info(out, directed = is_directed(.data)) + } + if("directed" %in% net_attributes(out)) snet_success("Directed: {as_infolist(out)$directed}") - # Source & method + # Optionals #### if(optional){ - if(!"source" %in% net_attributes(.data)){ + if(!"source" %in% net_attributes(out)){ snet_prompt("This network does not have a source. You may add one.") - source_options <- c("Observed", "Synthetic") - source <- utils::menu(choices = source_options, title = "Is this network observed or synthetic?") + source_options <- c("Empirical", "Synthetic") + source <- utils::menu(choices = source_options, title = "Is this network empirical or synthetic?") if(source == 1){ method_options <- c("Survey", "Interview", "Sensor", "Archival", "Trace", "Ethnography") out <- add_info(out, source = source_options[source], - method = utils::menu(choices = source_options, title = "Method: ")) + method = utils::menu(choices = method_options, title = "Method: ")) + out <- add_info(out, location = read_optional(prompt = "Location: ")) + out <- add_info(out, date = read_optional(prompt = "Date: ")) + bound_options <- c("Ego", "Roster", "Snowball") + out <- add_info(out, boundary = utils::menu(choices = bound_options, title = "Boundary: ")) } else if(source == 2){ out <- add_info(out, source = source_options[source], - method = readline(prompt = "Model: ")) + method = read_optional(prompt = "Model: ")) + } + if(!"doi" %in% net_attributes(out)){ + out <- add_info(out, doi = read_optional(prompt = "DOI/URL: ")) + } + if(!"max_degree" %in% net_attributes(out)){ + out <- add_info(out, max_degree = read_optional(prompt = "Maximum degree: ")) + } + if(!"min_degree" %in% net_attributes(out)){ + out <- add_info(out, min_degree = read_optional(prompt = "Minimum degree: ")) } - } else snet_success("Source: {net_info(out)$source}") + if(!"observation" %in% net_attributes(out)){ + obs_options <- c("Cross-sectional", "Panel", "Event") + for (layer in layer_names(out)) { + observation <- utils::menu(choices = obs_options, + title = paste0("The layer '", layer, "' is observed as: ")) + out$info$observation[match(layer, layer_names(out))] <- stats::setNames(obs_options[observation], layer) + } + } + if(is_weighted(out) && !"update" %in% net_attributes(out)){ + upd_options <- c("Increment", "Replacement") + for (layer in layer_names(out)) { + update <- utils::menu(choices = upd_options, + title = paste0("The layer '", layer, "' is updated by: ")) + out$info$update[match(layer, layer_names(out))] <- stats::setNames(upd_options[update], layer) + } + } + } + if("source" %in% net_attributes(out)) snet_success("Source: {as_infolist(out)$source}") + if("method" %in% net_attributes(out)) snet_success("Method/Model: {as_infolist(out)$method}") + if("boundary" %in% net_attributes(out)) snet_success("Boundary: {as_infolist(out)$boundary}") + if("location" %in% net_attributes(out)) snet_success("Location: {as_infolist(out)$location}") + if("observation" %in% net_attributes(out)) snet_success("Observation: {as_infolist(out)$observation}") + if("update" %in% net_attributes(out)) snet_success("Update: {as_infolist(out)$update}") + if("max_degree" %in% net_attributes(out)) snet_success("Max degree: {as_infolist(out)$max_degree}") + if("min_degree" %in% net_attributes(out)) snet_success("Min degree: {as_infolist(out)$min_degree}") + if("date" %in% net_attributes(out)) snet_success("Date: {as_infolist(out)$date}") + if("doi" %in% net_attributes(out)) snet_success("DOI/URL: {as_infolist(out)$doi}") } - # Ties - if(is.null(layer_names(.data))){ - snet_prompt("This network does not have a name for the ties. Please add one.") - out <- add_info(out, ties = readline(prompt = "Ties name: ")) - } else snet_success("Ties: {layer_names(out)}") - out } diff --git a/R/measure_properties.R b/R/measure_properties.R index 10b1d1a1b..971d38553 100644 --- a/R/measure_properties.R +++ b/R/measure_properties.R @@ -284,7 +284,7 @@ mode_names.igraph <- function(.data){ #' @export mode_names.stocnet <- function(.data){ - .data$info$nodes + .data$info$modes } #' @rdname member_names @@ -335,7 +335,7 @@ layer_names.igraph <- function(.data){ #' @export layer_names.stocnet <- function(.data){ - .data$info$ties + .data$info$layers %||% unique(.data$ties$layer) } #' @rdname member_names diff --git a/cran-comments.md b/cran-comments.md index 062b48694..368e61c23 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -9,5 +9,4 @@ 0 errors | 0 warnings | 0 notes -- Fixed README link issue -- Together with parallel submissions of 'netrics' and 'migraph', should pass all reverse dependency checks now too \ No newline at end of file +- Though less than 10 days, this version fixes intermittent validation test error on CRAN \ No newline at end of file diff --git a/man/manip_info.Rd b/man/manip_info.Rd index 0ef955e4d..716d62ea8 100644 --- a/man/manip_info.Rd +++ b/man/manip_info.Rd @@ -25,11 +25,28 @@ net_attributes(.data) }} \item{...}{Named attributes. The following are currently recognised: -"name", "year", and "doi" of the network, -"collection" or "mode" of the network -("survey", "interview","sensor","observation","archival", or "simulation"), -"nodes" (a vector of the names of the nodes) or "vertex1"/"vertex2", -"ties" or "edge.pos"/"edge.neg" for defining the ties.} +\itemize{ +\item "name" is the name of the network +\item "modes" is the name(s) of the nodeset(s) +\item "layers" is the name(s) of the tie type(s) +\item "directed" is a logical vector indicating whether each layer is directed +\item "source" is the source of the network ("empirical" or "synthetic") +\item "method" is the method of data collection or model used +(e.g. "survey", "interview","sensor","observation","archival", or "simulation") +\item "location" is the geographic, institutional, or digital location of the network +\item "date" is the date of data collection or model run +\item "boundary" is the boundary specification of the network ("ego", "roster", or "snowball") +\item "observation" is the observation type of the network ("cross-sectional", +"panel", or "event") +\item "update" is the update type of the network ("increment" or "replacement") +\item "max_degree" is the maximum degree of the network +\item "min_degree" is the minimum degree of the network +\item "doi" is the DOI or URL of the network +} + +If no arguments are used, +the function will check for missing information and prompt the user to add it. +If \code{optional = TRUE} is specified, the function will also prompt for optional information.} } \value{ A data object of the same class as the function was given. @@ -37,8 +54,6 @@ A data object of the same class as the function was given. \description{ These functions allow users to add and edit information about the network itself. -This includes the name, year, and mode of collection of the network, -as well as definitions of the nodes and ties in the network. Where available, this information is printed for tidygraph-class objects, and can be used for printing a grand table in the \code{{grand}} package. \itemize{ @@ -66,6 +81,9 @@ If no method is available for any class, an error will be thrown. add_info(ison_algebra, name = "Algebra") } \seealso{ +\href{https://grand-statement.org}{GRAND statement} for more +information on the Guidelines for Reporting About Network Data (GRAND). + Other manipulations: \code{\link{manip_changes}}, \code{\link{manip_global}}, diff --git a/tests/testthat/test-class_stocnet.R b/tests/testthat/test-class_stocnet.R index a3a7906a4..c21469207 100644 --- a/tests/testthat/test-class_stocnet.R +++ b/tests/testthat/test-class_stocnet.R @@ -7,7 +7,7 @@ test_that("stocnet validation", { reserved_cols( .data = list(nodes = tibble::tibble(id = 1)), component = "nodes", - reserved_cols = "label", + column = "label", class = "character", aka = c("name", "id") ), @@ -17,7 +17,7 @@ test_that("stocnet validation", { reserved_cols( .data = list(nodes = tibble::tibble(mode = 1)), component = "nodes", - reserved_cols = "mode", + column = "mode", class = "character" ), "'mode' must be of class 'character'." @@ -26,7 +26,7 @@ test_that("stocnet validation", { required_cols( .data = list(nodes = tibble::tibble(nabel = 1)), component = "nodes", - required_cols = "label" + column = "label" ), "The 'nodes' component of a stocnet object must have the following columns: label." )