Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,13 @@ they need.

## Style

In terms of style, we are aiming for pleasant predictability in terms of user experience.
In terms of style, we are aiming for:

- "declarative simplicity". Functions should not have many arguments,
as this requires that the user read the documentation carefully to understand
what all of the options imply.
Functions should be named after what they do, not their (often insider) reference to their progenitor.
- "pleasant predictability" in terms of user experience.
To that end, we have a regular syntax that users can rely on producing expected effects.
Functions in the same family (`as_*()`, `is_*()`, `create_*()`, etc.) should share
argument order and naming, so that behaviour is guessable across the family.
Expand Down Expand Up @@ -362,6 +368,12 @@ Start each bullet with a verb matching the change type:

Any of these verbs can also lead a sub-bullet.

Name a function by the generic, e.g. `net_modes()`, where the change reaches
every class it dispatches on.
Where it reaches only one method, spell that method out in full,
e.g. `net_modes.igraph()`,
so that a reader knows which classes the change applies to.

Keep every bullet to one line of fewer than 81 characters ideally (a few more or less is fine).
If a bullet wraps, it holds too much:
shorten it, or split it into a lead bullet and sub-bullets.
Expand Down
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: manynet
Title: Many Ways to Make, Manipulate, and Modify Myriad Networks
Version: 2.3.1
Version: 2.3.2
Comment thread
jhollway marked this conversation as resolved.
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.
Expand Down
7 changes: 7 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ S3method(delete_node_attribute,tbl_graph)
S3method(delete_nodes,default)
S3method(delete_nodes,igraph)
S3method(delete_nodes,network)
S3method(delete_nodes,stocnet)
S3method(delete_nodes,tbl_graph)
S3method(delete_tie_attribute,data.frame)
S3method(delete_tie_attribute,default)
Expand Down Expand Up @@ -301,6 +302,9 @@ S3method(is_weighted,tbl_graph)
S3method(join_nodes,default)
S3method(join_nodes,igraph)
S3method(join_nodes,stocnet)
S3method(layer_is_directed,default)
S3method(layer_is_directed,igraph)
S3method(layer_is_directed,stocnet)
S3method(layer_names,default)
S3method(layer_names,igraph)
S3method(layer_names,stocnet)
Expand Down Expand Up @@ -517,12 +521,14 @@ S3method(to_mode1,default)
S3method(to_mode1,igraph)
S3method(to_mode1,matrix)
S3method(to_mode1,network)
S3method(to_mode1,stocnet)
S3method(to_mode1,tbl_graph)
S3method(to_mode2,data.frame)
S3method(to_mode2,default)
S3method(to_mode2,igraph)
S3method(to_mode2,matrix)
S3method(to_mode2,network)
S3method(to_mode2,stocnet)
S3method(to_mode2,tbl_graph)
S3method(to_multilevel,default)
S3method(to_multilevel,igraph)
Expand Down Expand Up @@ -751,6 +757,7 @@ export(is_uniplex)
export(is_weighted)
export(join_nodes)
export(join_ties)
export(layer_is_directed)
export(layer_names)
export(layer_ties)
export(make_stocnet)
Expand Down
54 changes: 54 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,57 @@
# manynet 2.3.2

## Package

- Added three `snet_verbosity` levels, 'quiet', 'normal', and 'verbose'
- `snet_info()` and `snet_success()` report from 'normal'
- `snet_minor_info()` and the `snet_progress_*()` functions report from 'verbose'
- Improved `snet_warn()` and `snet_unavailable()` so that neither is silenced (closed #169)

## Classes

- Improved `describe_nodes()` on networks of three or more modes (closed #174)
- Fixed `keep_nodes()` to drop and reindex `$missings` (closed #173)
- `reserved_cols()` now names every out-of-range id, instead of erroring
- Improved `validate_stocnet()` to read a node 'id' as an id and not a label
- Improved `validate_stocnet()` to read a tie 'date' as another name for a time

## Coercion

- Fixed `as_igraph.stocnet()` on multimodal and multilevel networks (closed #170)

## Manipulating

- Added `delete_nodes.stocnet()`, which reindexes every component it keeps
- Deleting nodes used to leave the changes naming nodes that were gone

## Modifying

- Added `to_mode1.stocnet()` and `to_mode2.stocnet()`, which prune the changes
- A projection discards a mode, so a change about it describes no node
- Added `keep = "both"` to `to_unsigned()`, which keeps every tie but not its sign
- Fixed `to_unsigned.data.frame()` erroring on a signed edgelist
- now drops the ties of the other sign, as the other methods do
- now reads signs held as negative weights as well as in a 'sign' column
- Fixed `to_unsigned.network()` ignoring its `keep` argument
- Fixed `to_layer()` pointing arcs at the wrong nodes (closed #170)

## Marking

- Improved `is_directed()` on a network tied within a level as well as between
- Fixed `is_multilevel.igraph()` looping through `tie_is_twomode()`
- Added `layer_is_directed()`, reporting each layer of a network (closed #171)

## Measuring

- Fixed `mode_nodes()` to count the nodes in each of three or more modes
Comment thread
jhollway marked this conversation as resolved.
- Fixed `net_modes.igraph()` to count the levels an igraph 'lvl' attribute records

## Data

- Improved `ison_southern_women` to record when each of its events is held
- `is_changing()` now marks it TRUE, as each event enters on its own date
- The dates move from a tie attribute to the changes, where they describe the event

# manynet 2.3.1

## Marking
Expand Down
12 changes: 10 additions & 2 deletions R/class_describe.R
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,16 @@ describe_network <- function(.data) {
describe_nodes <- function(.data){
nd <- mode_nodes(.data)
nn <- mode_names(.data)
if(is.null(nn)) nn <- "nodes"
nn <- ifelse(nd==1, singularize(nn), pluralize(nn))
# A network that names its modes gives one name for each count. Where it
# names fewer or more than it counts, no name can be matched to a count
# with confidence, so every mode is described by the general word instead.
if(is.null(nn) || length(nn) != length(nd)) nn <- rep("nodes", length(nd))
# `ifelse()` returns as many values as its first argument holds, so it
# would report only the first name where there are three or more modes.
Comment thread
jhollway marked this conversation as resolved.
nn <- vapply(seq_along(nd),
function(i) if(nd[i] == 1) singularize(nn[i]) else
pluralize(nn[i]),
character(1))
node_name <- paste(nd, nn)
phrase(node_name)
}
Expand Down
93 changes: 64 additions & 29 deletions R/class_interface.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,36 @@
#' @description
#' These functions wrap `{cli}` functions and elements
#' to build an attractive command line interface (CLI).
#' They divide into those that change what a function does
#' and those that only report what it did.
#'
#' - `snet_info()` for general information messages.
#' - `snet_minor_info()` for minor information messages.
#' - `snet_warn()` for warning messages.
#' - `snet_abort()` for error messages.
#' - `snet_success()` for success messages.
#' - `snet_prompt()` for prompts to the user.
#' - `snet_unavailable()` for features that are not yet available.
#' A call that changes control flow always fires,
#' whatever the verbosity, because silencing it would let the code it guards
#' run on and return a wrong answer instead of an explanation:
#'
#' If you wish to receive fewer messages in the console,
#' run `options(snet_verbosity = 'quiet')`.
#' - `snet_abort()` for an error the user has to fix.
#' - `snet_unavailable()` for a feature that is not yet available.
#' - `snet_warn()` for a result the user should not trust without reading,
#' such as a value that is dropped or a name that does not match.
#'
#' A call that only reports is silenced under the default verbosity:
#'
#' - `snet_info()` for what a function chose on the user's behalf.
#' - `snet_success()` for the completion of a long task.
#' - `snet_minor_info()` for detail that is useful only while debugging.
#' - `snet_progress_step()` and the other `snet_progress_*()` functions
#' for a progress bar.
#'
#' `snet_prompt()` asks the user something, so it always shows.
#' @section Verbosity:
#' The `snet_verbosity` option takes three levels:
#'
#' - `'quiet'`, the default, reports nothing that is not an error,
#' a warning, or a prompt.
#' - `'normal'` adds `snet_info()` and `snet_success()`.
#' - `'verbose'` adds `snet_minor_info()` and the progress bars.
#'
#' Set one with, for example, `options(snet_verbosity = 'verbose')`.
#' @param ... One or more character strings.
#' For most of these functions, if multiple strings are passed these will be
#' pasted together.
Expand All @@ -23,25 +42,37 @@
#' @name interface
NULL

# The three verbosity levels, in order, so that a level can be compared with
# the level a message needs. An unrecognised value reads as 'normal', which is
# what every value other than 'quiet' meant before the levels were named.
.snet_levels <- c("quiet", "normal", "verbose")

.snet_verbose <- function(level = "normal"){
set <- match(getOption("snet_verbosity", default = "quiet"), .snet_levels)
if(is.na(set)) set <- 2L
set >= match(level, .snet_levels)
}

#' @rdname interface
#' @export
snet_info <- function(..., .envir = parent.frame()){
if(getOption("snet_verbosity", default = "quiet")!="quiet")
if(.snet_verbose("normal"))
cli::cli_alert_info(paste(...), .envir = .envir)
}

#' @rdname interface
#' @export
snet_minor_info <- function(..., .envir = parent.frame()){
if(getOption("snet_verbosity", default = "quiet")!="quiet")
if(.snet_verbose("verbose"))
cli::cli_alert_info(cli::col_grey(paste(...)), .envir = .envir)
}

#' @rdname interface
#' @export
snet_warn <- function(..., .envir = parent.frame()){
if(getOption("snet_verbosity", default = "quiet")!="quiet")
cli::cli_alert_warning(paste(...), .envir = .envir)
# A warning tells the user not to trust a result, so it raises a condition
# they can catch or escalate, and it is not silenced by the verbosity.
cli::cli_warn(paste(...), .envir = .envir)
}

#' @rdname interface
Expand All @@ -54,27 +85,30 @@ snet_abort <- function(..., .envir = parent.frame()){
#' @rdname interface
#' @export
snet_success <- function(..., .envir = parent.frame()){
if(getOption("snet_verbosity", default = "quiet")!="quiet")
if(.snet_verbose("normal"))
cli::cli_alert_success(paste(...), .envir = .envir)
}

#' @rdname interface
#' @export
snet_prompt <- function(..., .envir = parent.frame()){
# if(getOption("snet_verbosity", default = "quiet")!="quiet")
cli::cli_text(cli::style_italic(paste(...)),
.envir = .envir)
cli::cli_text(cli::style_italic(paste(...)),
.envir = .envir)
}

#' @rdname interface
#' @export
snet_unavailable <- function(..., .envir = parent.frame()){
if(getOption("snet_verbosity", default = "quiet")!="quiet")
cli::cli_abort(paste(...,
"If you are interested in this feature,",
"please vote for it or raise it as an issue at",
"{.url https://github.com/stocnet/manynet/issues}."),
.envir = .envir)
# The guard has to abort whatever the verbosity, or the code it guards runs
# on and returns a wrong answer. Only the invitation depends on the level.
msg <- paste(...)
if(!nzchar(msg)) msg <- "That is not yet available."
if(.snet_verbose("normal"))
msg <- paste(msg,
"If you are interested in this feature,",
"please vote for it or raise it as an issue at",
"{.url https://github.com/stocnet/manynet/issues}.")
cli::cli_abort(msg, .envir = .envir)
}

# Progress ####
Expand All @@ -89,8 +123,9 @@ snet_unavailable <- function(..., .envir = parent.frame()){
#' - `snet_progress_seq()` for progress along a sequence.
#' - `snet_progress_nodes()` for progress along the nodes of a network.
#'
#' If you wish to receive fewer messages in the console,
#' run `options(snet_verbosity = 'quiet')`.
#' A progress bar reports what a function did and not what it decided,
#' so it shows only where `options(snet_verbosity = 'verbose')`.
#' See the verbosity section of [interface].
#' @inheritParams interface
#' @template param_data
#' @name progress
Expand All @@ -99,29 +134,29 @@ NULL
#' @rdname progress
#' @export
snet_progress_step <- function(..., .envir = parent.frame()){
if(getOption("snet_verbosity", default = "quiet")!="quiet")
if(.snet_verbose("verbose"))
cli::cli_progress_step(..., .envir = .envir)
}

#' @rdname progress
#' @export
snet_progress_along <- function(..., .envir = parent.frame()){
if(getOption("snet_verbosity", default = "quiet")!="quiet")
if(.snet_verbose("verbose"))
cli::cli_progress_along(..., .envir = .envir)
}

#' @rdname progress
#' @export
snet_progress_seq <- function(..., .envir = parent.frame()){
if(getOption("snet_verbosity", default = "quiet")!="quiet")
if(.snet_verbose("verbose"))
cli::cli_progress_along(seq.int(...), .envir = .envir,
total = ..., clear = TRUE)
}

#' @rdname progress
#' @export
snet_progress_nodes <- function(..., .envir = parent.frame()){
if(getOption("snet_verbosity", default = "quiet")!="quiet" && interactive()){
if(.snet_verbose("verbose") && interactive()){
cli::cli_progress_along(seq.int(net_nodes(...)), .envir = .envir,
total = ..., clear = TRUE)
} else seq.int(net_nodes(...))
Expand Down
13 changes: 3 additions & 10 deletions R/class_missing.R
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,6 @@
alters
}

# Whether each layer is directed, named by layer.
.layer_directed <- function(.data, layer){
directed <- .data$info$directed
if(!is.null(directed) && !is.null(names(directed)) && !is.na(layer) &&
layer %in% names(directed)) unname(directed[layer]) else is_directed(.data)
}

# The ties a network records as missing, derived from its nonresponse records.
.expand_missing <- function(.data){
empty <- dplyr::tibble(from = integer(0), to = integer(0),
Expand All @@ -103,7 +96,7 @@
absent <- which(na_state[, at] & act_state[, at])
absent <- .layer_absent(.data, absent, layer, time)
if(!length(absent)) return(NULL)
directed <- .layer_directed(.data, layer)
directed <- layer_is_directed(.data, layer)
pairs <- lapply(absent, function(node){
alters <- .stocnet_alters(.data, node, act_state[, at])
if(!length(alters)) return(NULL)
Expand Down Expand Up @@ -179,7 +172,7 @@
at <- if(is.na(time)) 1L else match(time, times)
if(is.na(at)) at <- 1L
sub <- missing[.same_occasion(missing, layer, time), , drop = FALSE]
directed <- .layer_directed(x, layer)
directed <- layer_is_directed(x, layer)
for(node in unique(c(sub$from, if(!directed) sub$to))){
alters <- .stocnet_alters(x, node, act_state[, at])
held <- if(directed) sub$to[sub$from == node] else
Expand Down Expand Up @@ -209,7 +202,7 @@
covered <- rep(FALSE, nrow(missing))
for(r in seq_len(nrow(found))){
same <- .same_occasion(missing, found$layer[[r]], found$time[[r]])
directed <- .layer_directed(x, found$layer[[r]])
directed <- layer_is_directed(x, found$layer[[r]])
covered <- covered | (same & (missing$from == found$node[[r]] |
(!directed & missing$to == found$node[[r]])))
}
Expand Down
Loading
Loading