diff --git a/.github/workflows/pushrelease.yml b/.github/workflows/pushrelease.yml index 9e92356b..cee4b41a 100644 --- a/.github/workflows/pushrelease.yml +++ b/.github/workflows/pushrelease.yml @@ -61,18 +61,20 @@ jobs: path: build/ - name: Calculate code coverage + if: runner.os == 'macOS-latest' run: Rscript -e "covr::codecov()" release: name: Bump version and release if: ${{ always() }} needs: build + if: github.event.pull_request.merged == true runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout one - uses: actions/checkout@master + uses: actions/checkout@v4 with: fetch-depth: '0' - name: Bump version and push tag @@ -84,24 +86,16 @@ jobs: DEFAULT_BUMP: patch RELEASE_BRANCHES: main - name: Checkout two - uses: actions/checkout@v2 - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.newtag.outputs.tag }} - release_name: Release ${{ steps.newtag.outputs.tag }} - draft: false - prerelease: false + uses: actions/checkout@v4 + + - name: Extract version + run: | + echo "PACKAGE_VERSION=$(grep '^Version' DESCRIPTION | sed 's/.*: *//')" >> $GITHUB_ENV + echo "PACKAGE_NAME=$(grep '^Package' DESCRIPTION | sed 's/.*: *//')" >> $GITHUB_ENV - name: Download binaries uses: actions/download-artifact@v4 - - name: Display structure of downloaded files - run: ls -R - - name: Rename binaries release shell: bash run: | @@ -111,9 +105,9 @@ jobs: cp ./winOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.zip . echo "Renamed files" ls manynet_* - + - name: Create Release and Upload Assets - id: upload_release + id: create_release uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.newtag.outputs.tag }} @@ -127,7 +121,7 @@ jobs: manynet_*.tar.gz manynet_*.zip env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pkgdown: name: Build and deploy website diff --git a/DESCRIPTION b/DESCRIPTION index 73d420e7..b17a0e9f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: manynet Title: Many Ways to Make, Modify, Map, Mark, and Measure Myriad Networks -Version: 1.1.0 -Date: 2024-09-12 +Version: 1.2.0 +Date: 2024-09-28 Description: Many tools for making, modifying, mapping, marking, measuring, and motifs and memberships of many different types of networks. All functions operate with matrices, edge lists, and 'igraph', 'network', and 'tidygraph' objects, @@ -41,7 +41,6 @@ Suggests: methods, multiplex, netdiffuseR, - oaqc, patchwork, readxl, rmarkdown, diff --git a/NAMESPACE b/NAMESPACE index 9671e0bb..bb376eaf 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -138,8 +138,10 @@ S3method(plot,diffs_model) S3method(plot,learn_model) S3method(plot,matrix) S3method(plot,network_measures) +S3method(plot,network_motif) S3method(plot,node_measure) S3method(plot,node_member) +S3method(plot,node_motif) S3method(plot,tie_measure) S3method(print,diff_model) S3method(print,diffs_model) @@ -296,6 +298,7 @@ export("%>%") export(.E) export(.G) export(.N) +export(add_info) export(add_node_attribute) export(add_nodes) export(add_tie_attribute) @@ -321,10 +324,12 @@ export(cluster_hierarchical) export(create_components) export(create_core) export(create_degree) +export(create_ego) export(create_empty) export(create_explicit) export(create_filled) export(create_lattice) +export(create_motifs) export(create_ring) export(create_star) export(create_tree) @@ -406,6 +411,7 @@ export(net_betweenness) export(net_by_brokerage) export(net_by_dyad) export(net_by_mixed) +export(net_by_quad) export(net_by_triad) export(net_change) export(net_closeness) @@ -509,6 +515,7 @@ export(network_upperbound) export(node_adoption_time) export(node_alpha) export(node_attribute) +export(node_authority) export(node_automorphic_equivalence) export(node_betweenness) export(node_bridges) @@ -517,6 +524,7 @@ export(node_brokering) export(node_brokering_activity) export(node_brokering_exclusivity) export(node_by_brokerage) +export(node_by_dyad) export(node_by_exposure) export(node_by_path) export(node_by_quad) @@ -537,6 +545,7 @@ export(node_efficiency) export(node_effsize) export(node_eigenvector) export(node_equivalence) +export(node_equivalency) export(node_exposure) export(node_fast_greedy) export(node_flow) @@ -544,6 +553,7 @@ export(node_fluid) export(node_harmonic) export(node_heterophily) export(node_hierarchy) +export(node_hub) export(node_in_adopter) export(node_in_automorphic) export(node_in_betweenness) @@ -665,6 +675,7 @@ export(scale_fill_sdgs) export(scale_fill_uzh) export(scale_size) export(select_ties) +export(set_manynet_theme) export(summarise_ties) export(table_data) export(theme_ethz) @@ -909,7 +920,6 @@ importFrom(stats,cor) importFrom(stats,cutree) importFrom(stats,hclust) importFrom(stats,median) -importFrom(tidygraph,"%E>%") importFrom(tidygraph,.E) importFrom(tidygraph,.G) importFrom(tidygraph,.N) diff --git a/NEWS.md b/NEWS.md index 063489ec..89554636 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,61 @@ +# manynet 1.2.0 + +## Package + +- Added progress updates, information, and unavailability errors to several functions + - These can be silenced by setting `options(manynet_verbosity ="quiet")` + - Where a feature is unavailable, users are directed to the Github issues page +- Added console theme to color the startup and various warning or info messages + +## Making + +- Added `create_ego()` for collecting ego networks through interviews, including arguments for: + - Indicating whether a roster should be used, otherwise follows a name generator approach + - Indicating whether nodes should be interpreted, i.e. nodal attributes collected + - Indicating whether ties between alters should be requested +- Added `create_motifs()` for creating networks that correspond to the isomorphic subgraphs of certain size and format + +## Modifying + +- Improved `print.mnet()` + - Prints multiplex types if available + - Prints both nodesets for two-mode networks +- Added `add_info()` for adding grand info to tidygraph objects + - This includes the name of the network, node sets and ties, DOI, year and mode of collection +- Fixed `to_unweighted()` so that it passes through unweighted networks correctly + +## Mapping + +- Added `set_manynet_theme()` to set theme (re #60), but not yet fully implemented + +## Marking + +- Improved `is_multiplex()` to ignore "name" tie attributes + +## Measuring + +- Added `node_authority()` and `node_hub()` centrality measures +- Added `node_equivalency()` for calculating four-cycle closure by node +- Extended `net_equivalency()` to one-mode networks + +## Members + +- Fixed `node_in_equivalence()` to use census directly + +## Motifs + +- Added plot methods for network_motif and node_motif classes that use `create_motifs()` +- Added `node_by_dyad()` for node level dyad census +- Added `net_by_quad()` for network level quad census +- Fixed `node_by_quad()` to avoid `{oaqc}` dependency (#89), more flexible but slower +- Fixed `print.node_motif()` to convert to tibble and add modes and names where available only upon print + - The underlying object is still a matrix, used for equivalence and blockmodelling + +## Data + +- Updated `ison_southern_women` with grand info +- Updated `ison_laterals` with reordered nodes + # manynet 1.1.0 ## Package @@ -98,7 +156,7 @@ - `table_data()` can now report on data from multiple packages - `{manynet}` and `{migraph}` are included by default, and if any are not installed they are just ignored -- `tabe_data()` can now filter by any reported formats, +- `table_data()` can now filter by any reported formats, such as 'directed' or 'twomode' ## Website @@ -198,7 +256,7 @@ including force-directed, layered, circular, spectral, and grid layouts existing network was incorrect - Added `generate_configuration()` for generating configuration models (including for two-mode networks) -- `play_diffuson()` now includes an explicit contact argument to control the +- `play_diffusion()` now includes an explicit contact argument to control the basis of exposure ## Marking @@ -277,7 +335,7 @@ and `node_is_infected()` (closes #71) `net_balance()`, `net_change()`, and `net_stability()` - Updated properties from mapping to measuring - Updated attributes from mapping to measuring - - Renamed `node_mode()` (deprecated) to node_is_mode() since it returns a + - Renamed `node_mode()` (deprecated) to `node_is_mode()` since it returns a logical vector - Updated `node_attribute()` and `tie_attribute()` to return measures when the output is numeric diff --git a/R/class_motifs.R b/R/class_motifs.R index d20faf6a..12e68465 100644 --- a/R/class_motifs.R +++ b/R/class_motifs.R @@ -1,6 +1,7 @@ make_node_motif <- function(out, .data) { class(out) <- c("node_motif", class(out)) - attr(out, "mode") <- node_is_mode(.data) + if(is_twomode(.data)) attr(out, "mode") <- node_is_mode(.data) + if(is_labelled(.data)) attr(out, "dimnames")[[1]] <- node_names(.data) out } @@ -14,17 +15,55 @@ make_network_motif <- function(out, .data) { print.node_motif <- function(x, ..., n = 6, digits = 3) { - if(!is.null(attr(x, "dimnames")[[1]])){ - x <- data.frame(names = attr(x, "dimnames")[[1]], x) - } if (any(attr(x, "mode"))) { - print(dplyr::tibble(as.data.frame(x)[!attr(x, "mode")]), n = n) - print(dplyr::tibble(as.data.frame(x)[attr(x, "mode")]), n = n) + y <- as.data.frame(x[!attr(x, "mode"),]) + z <- as.data.frame(x[attr(x, "mode"),]) + if(!is.null(attr(x, "dimnames")[[1]])){ + y <- data.frame(names = attr(x, "dimnames")[[1]][!attr(x, "mode")], y) + z <- data.frame(names = attr(x, "dimnames")[[1]][attr(x, "mode")], z) + } + print(dplyr::tibble(y), n = n) + print(dplyr::tibble(z), n = n) } else { - print(dplyr::tibble(as.data.frame(x)), n = n) + if(!is.null(attr(x, "dimnames")[[1]])){ + x <- data.frame(names = attr(x, "dimnames")[[1]], x) + } else x <- as.data.frame(x) + print(dplyr::tibble(x), n = n) } } +#' @export +plot.node_motif <- function(x, ...) { + motifs <- dimnames(x)[[2]] + if("X4" %in% motifs){ + graphs(create_motifs(4), waves = 1:11) + } else if("021D" %in% motifs){ + graphs(create_motifs(3, directed = TRUE), waves = 1:16) + } else if("102" %in% motifs){ + graphs(create_motifs(3), waves = 1:4) + } else if("Asymmetric" %in% motifs){ + graphs(create_motifs(2, directed = TRUE), waves = 1:3) + } else if("Mutual" %in% motifs){ + graphs(create_motifs(2), waves = 1:2) + } else mnet_unavailable("Cannot plot these motifs yet, sorry.") +} + +#' @export +plot.network_motif <- function(x, ...) { + motifs <- dimnames(x)[[2]] + if("X4" %in% motifs){ + graphs(create_motifs(4), waves = 1:11) + } else if("021D" %in% motifs){ + graphs(create_motifs(3, directed = TRUE), waves = 1:16) + } else if("102" %in% motifs){ + graphs(create_motifs(3), waves = 1:4) + } else if("Asymmetric" %in% motifs){ + graphs(create_motifs(2, directed = TRUE), waves = 1:3) + } else if("Mutual" %in% motifs){ + graphs(create_motifs(2), waves = 1:2) + } else mnet_unavailable("Cannot plot these motifs yet, sorry.") +} + # summary(node_by_triad(mpn_elite_mex), # membership = node_regular_equivalence(mpn_elite_mex, "elbow")) #' @export diff --git a/R/class_networks.R b/R/class_networks.R index c4947365..885256d0 100644 --- a/R/class_networks.R +++ b/R/class_networks.R @@ -6,22 +6,23 @@ print.mnet <- function(x, ..., n = 6) { arg_list <- list(...) arg_list[['useS4']] <- NULL + if(is_grand(x) && !is.null(igraph::graph_attr(x, "grand")$name)) + cli::cli_text("# {igraph::graph_attr(x, 'grand')$name}") graph_desc <- describe_graph(x) + tie_desc <- describe_ties(x) + node_desc <- describe_nodes(x) + cli::cli_text("# {graph_desc} network of {node_desc} and {tie_desc}") top <- dplyr::as_tibble(tidygraph::activate(x, "nodes")) bottom <- dplyr::as_tibble(tidygraph::activate(x, "edges")) - if(is.null(igraph::graph_attr(x, "grand"))) node_name <- "nodes" else - node_name <- igraph::graph_attr(x, "grand")$vertex1 - if(is.null(igraph::graph_attr(x, "grand"))) - tie_name <- ifelse(is_directed(x), "arcs", "ties") else - tie_name <- paste(igraph::graph_attr(x, "grand")$edge.pos, - ifelse(is_directed(x), "arcs", "ties")) - cat('#', graph_desc, 'network of', igraph::gorder(x), node_name, 'and', - igraph::gsize(x), tie_name, '\n', sep = ' ') if (ncol(top)>0) print(top, n = n) if (ncol(bottom)>0) print(bottom, n = n, max_footer_lines = 1) invisible(x) } +is_grand <- function(.data){ + !is.null(igraph::graph_attr(.data, "grand")) +} + describe_graph <- function(x) { paste0("A ", ifelse(is_dynamic(x), "dynamic, ", ""), @@ -35,3 +36,26 @@ describe_graph <- function(x) { ifelse(is_directed(x), "directed", "undirected")) ) } + +describe_nodes <- function(x){ + nd <- net_dims(x) + if(!is.null(igraph::graph_attr(x, "grand")$vertex1)){ + node_name <- paste(nd[1], igraph::graph_attr(x, "grand")$vertex1) + if(length(nd)==2 && !is.null(igraph::graph_attr(x, "grand")$vertex2)) + node_name <- c(node_name, paste(nd[2], igraph::graph_attr(x, "grand")$vertex2)) + } else node_name <- paste(sum(nd), "nodes") + node_name +} + +describe_ties <- function(x){ + nt <- net_ties(x) + tie_name <- ifelse(is_directed(x), "arcs", "ties") + if(!is.null(igraph::graph_attr(x, "grand")$edge.pos)){ + tie_name <- paste(igraph::graph_attr(x, "grand")$edge.pos, + tie_name) + } else if(!is.null(tie_attribute(x, "type"))){ + tie_name <- paste(cli::ansi_collapse(unique(tie_attribute(x, "type"))), + tie_name) + } + paste(nt, tie_name) +} diff --git a/R/make_create.R b/R/make_create.R index 3bc7b436..fe7dbfd8 100644 --- a/R/make_create.R +++ b/R/make_create.R @@ -2,7 +2,6 @@ #' Making networks with explicit ties #' -#' #' @description #' This function creates a network from a vector of explicitly named nodes #' and ties between them. @@ -116,6 +115,175 @@ create_explicit <- function(...){ as_tidygraph(res) } +# Collections #### + +#' Making ego networks through interviewing +#' +#' @description +#' This function creates an ego network through interactive interview questions. +#' Note that it only creates a simplex, directed network. +#' @param max_alters The maximum number of alters to collect. +#' By default infinity, but many name generators will expect a maximum of +#' e.g. 5 alters to be named. +#' @param roster A vector of node names to offer as potential alters for ego. +#' @param interpreter Logical. If TRUE, then it will ask for which attributes +#' to collect and give prompts for each attribute for each node in the network. +#' By default FALSE. +#' @param interrelater Logical. If TRUE, then it will ask for the contacts from +#' each of the alters perspectives too. +#' @name make_ego +#' @family makes +#' @export +create_ego <- function(max_alters = Inf, + roster = NULL, + interpreter = FALSE, + interrelater = FALSE){ + cli::cli_text("What is ego's name?") + ego <- readline() + cli::cli_text("What is the relationship you are collecting? Name it in the singular, e.g. 'friendship'") + ties <- readline() + # cli::cli_text("Is this a weighted network?") + # weighted <- q_yes() + alters <- vector() + if(!is.null(roster)){ + for (alt in roster){ + cli::cli_text("Is {ego} connected by {ties} to {alt}?") + alters <- c(alters, q_yes()) + } + alters <- roster[alters] + } else { + repeat{ + cli::cli_text("Please name a contact:") + alters <- c(alters, readline()) + if(length(alters) == max_alters){ + cli::cli_alert_info("{.code max_alters} reached.") + break + } + if (q_yes("Are these all the contacts?")) break + } + } + out <- as_tidygraph(as.data.frame(cbind(ego, alters))) + if(interpreter){ + attr <- vector() + repeat{ + cli::cli_text("Please name an attribute you are collecting, or press [Enter] to continue.") + attr <- c(attr, readline()) + if (attr[length(attr)]==""){ + attr <- attr[-length(attr)] + break + } + } + if(length(attr)>0){ + for(att in attr){ + values <- vector() + for (alt in c(ego, alters)){ + cli::cli_text("What value does {alt} have for {att}:") + values <- c(values, readline()) + } + out <- add_node_attribute(out, att, values) + } + } + } + if(interrelater){ + for(alt in alters){ + others <- setdiff(c(ego,alters), alt) + extra <- vector() + for(oth in others){ + cli::cli_text("Is {alt} connected by {ties} to {oth}?") + extra <- c(extra, q_yes()) + } + # cat(c(rbind(alt, others[extra]))) + out <- add_ties(out, c(rbind(alt, others[extra]))) + } + } + if(!is.null(roster) && any(!roster %in% node_names(out))){ + isolates <- roster[!roster %in% node_names(out)] + out <- add_nodes(out, length(isolates), list(name = isolates)) + } + out <- add_info(out, ties = ties, + collection = "Interview", + year = format(as.Date(Sys.Date(), format="%d/%m/%Y"),"%Y")) + out +} + +q_yes <- function(msg = NULL){ + if(!is.null(msg)) cli::cli_text(msg) + out <- readline() + if(is.logical(out)) return(out) + if(out=="") return(FALSE) + choices <- c("yes","no","true","false") + out <- c(TRUE,FALSE,TRUE,FALSE)[pmatch(tolower(out), tolower(choices))] + out +} + +# Collections #### + +#' Making motifs +#' +#' @description +#' `create_motifs()` is used to create a list of networks that represent the +#' subgraphs or motifs corresponding to a certain number of nodes and direction. +#' Note that currently only `n==2` to `n==4` is implemented, +#' and the latter only for undirected networks. +#' +#' @inheritParams make_create +#' @name make_motifs +#' @family makes +#' @export +create_motifs <- function(n, directed = FALSE){ + directed <- infer_directed(n, directed) + n <- infer_n(n) + if(!directed & n==2){ + return(list(Null = mutate_nodes(create_empty(2), + name = c("A","B")), + M = create_explicit(A--B))) + } else if(directed & n==2){ + return(list(Null = mutate_nodes(create_empty(2, directed = TRUE), + name = c("A","B")), + Asymmetric = create_explicit(A-+B), + Mutual = create_explicit(A++B))) + } else if(!directed & n==3){ + return(list(Empty = mutate_nodes(create_empty(3), + names = c("A","B","C")), + Edge = create_explicit(A--B, C), + Path = create_explicit(A--B--C), + Triangle = create_explicit(A--B--C--A))) + } else if(directed & n==3){ + return(list(`003` = mutate_nodes(create_empty(3, directed = TRUE), + names = c("A","B","C")), + `012` = create_explicit(A-+B, C), + `102` = create_explicit(A++B, C), + `021D` = create_explicit(A-+B, A-+C), + `021U` = create_explicit(A+-B, A+-C), + `021C` = create_explicit(A-+B, B-+C), + `111D` = create_explicit(A++B, C-+B), + `111U` = create_explicit(A++B, B-+C), + `030T` = create_explicit(A-+B, A-+C, B-+C), + `030C` = create_explicit(A-+B, B-+C, C-+A), + `201` = create_explicit(A++B, B++C), + `120D` = create_explicit(A++B, C-+A:B), + `120U` = create_explicit(A++B, A:B-+C), + `120C` = create_explicit(A++B, A-+C-+B), + `210` = create_explicit(A++B, B++C, A-+C), + `300` = create_explicit(A++B++C++A))) + } else if(!directed & n==4){ + return(list(E4 = mutate_nodes(create_empty(4), + name = c("A","B","C","D")), + I4 = create_explicit(A--B, C, D), + H4 = create_explicit(A--B, C--D), + L4 = create_explicit(A--B--C, D), + D4 = create_explicit(A--B--C--A, D), + U4 = create_explicit(A--B--C--D), + Y4 = create_explicit(A--B--C, B--D), + P4 = create_explicit(A--B--C, B--D--C), + C4 = create_explicit(A--B--C--D--A), + Z4 = create_explicit(A--B--C--D--A--C), + X4 = create_explicit(A--B--C--D--A--C, B--D))) + } else + cli::cli_alert_warning("Motifs not yet available for that kind of network.") +} + + # Defined #### #' Making networks with defined structures diff --git a/R/make_read.R b/R/make_read.R index 729475a9..dde1f4bc 100644 --- a/R/make_read.R +++ b/R/make_read.R @@ -684,6 +684,7 @@ write_graphml <- function(.data, #' # edge_color = "type", node_color = "Compilation") #' @export read_cran <- function(pkg = "all"){ + mnet_progress_step("Downloading data about available packages from CRAN") cranInfoDF <- as.data.frame(utils::available.packages( utils::contrib.url(getOption("repos"), type = "source"))) if(pkg=="all") new <- cranInfoDF$Package else diff --git a/R/manip_nodes.R b/R/manip_nodes.R index 988beda4..9562b528 100644 --- a/R/manip_nodes.R +++ b/R/manip_nodes.R @@ -173,3 +173,59 @@ rename <- tidygraph::rename filter_nodes <- function(.data, ..., .by){ tidygraph::filter(.data, ..., .by = .by) } + +# Network information #### + +#' Modifying network data +#' +#' @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. +#' @name manip_net +#' @inheritParams mark_is +#' @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. +#' @examples +#' add_info(ison_algebra, name = "Algebra") +#' @export +add_info <- function(.data, ...){ + if(!is.null(igraph::graph_attr(.data)$grand)){ + cli::cli_abort("Hmm, I don't know how to do that yet.") + } else { + info <- list(...) + unrecog <- setdiff(names(info), c("name", "nodes", "ties", "doi", + "collection", "year", "mode", "vertex1", + "vertex1.total", "vertex2", + "vertex2.total", "edge.pos", "edge.neg")) + if(length(unrecog)>0) + cli::cli_alert_warning("{unrecog} are not recognised fields.") + if("nodes" %in% names(info)){ + info$vertex1 <- info$nodes[1] + if(is_twomode(.data) && length(info$nodes)==2) + info$vertex2 <- info$nodes[2] + info$nodes <- NULL + } + if("ties" %in% names(info)){ + info$edge.pos <- info$ties + info$ties <- NULL + } + if("collection" %in% names(info)){ + info$mode <- info$collection + info$collection <- NULL + } + # return(str(info)) # for debugging + out <- .data + igraph::graph_attr(out)$grand <- info + } + as_tidygraph(out) +} + + diff --git a/R/manip_reformat.R b/R/manip_reformat.R index 6d636739..64a6f29b 100644 --- a/R/manip_reformat.R +++ b/R/manip_reformat.R @@ -264,10 +264,12 @@ to_unweighted <- function(.data, threshold = 1) UseMethod("to_unweighted") #' @export to_unweighted.tbl_graph <- function(.data, threshold = 1) { - edges <- weight <- NULL - .data %>% activate(edges) %>% - dplyr::filter(weight >= threshold) %>% - dplyr::select(-c(weight)) + if(is_weighted(.data)){ + edges <- weight <- NULL + .data %>% activate(edges) %>% + dplyr::filter(weight >= threshold) %>% + dplyr::select(-c(weight)) + } else .data } #' @export diff --git a/R/manip_split.R b/R/manip_split.R index 15668789..8f17ae9f 100644 --- a/R/manip_split.R +++ b/R/manip_split.R @@ -32,6 +32,7 @@ to_egos.igraph <- function(.data, max_dist = 1, min_dist = 0){ if(is_twomode(.data)) max_dist <- max_dist*2 + mnet_progress_step("Obtaining neighbourhoods") out <- igraph::make_ego_graph(.data, order = max_dist, mindist = min_dist) diff --git a/R/manynet-utils.R b/R/manynet-utils.R index 51cdcedb..bd146c06 100644 --- a/R/manynet-utils.R +++ b/R/manynet-utils.R @@ -1,5 +1,5 @@ # defining global variables more centrally -utils::globalVariables(c(".data", "obs", "from", "to", "name")) +utils::globalVariables(c(".data", "obs", "from", "to", "name", "A","B","C","D")) # Helper function for declaring available methods available_methods <- function(fun_vctr) { @@ -41,6 +41,10 @@ thisRequiresBio <- function(pkgname) { patchwork::wrap_plots(e1, e2, ...) } +seq_nodes <- function(.data){ + seq.int(net_nodes(.data)) +} + # #' @export # `%||%` <- function(x, y) { # if (is_null(x)) y else x diff --git a/R/map_autograph.R b/R/map_autograph.R index 0821fcb6..2fadaee7 100644 --- a/R/map_autograph.R +++ b/R/map_autograph.R @@ -388,7 +388,7 @@ graphr <- function(.data, layout, labels = TRUE, } } if (is_complex(g)) { - p <- p + ggraph::geom_edge_loop0(edge_width = esize, edge_alpha = 0.4) + p <- p + ggraph::geom_edge_loop0(edge_alpha = 0.4) } if (length(unique(esize)) == 1) { p <- p + ggplot2::guides(edge_width = "none") diff --git a/R/map_theme.R b/R/map_theme.R index 594893f4..0b60e769 100644 --- a/R/map_theme.R +++ b/R/map_theme.R @@ -15,6 +15,20 @@ #' theme_iheid() NULL +#' @rdname map_themes +#' @param theme String naming a theme. +#' By default "default". +#' @export +set_manynet_theme <- function(theme = "default"){ + theme_opts <- c("default", "iheid", "ethz", "uzh", "rug", "crisp") + if(theme %in% theme_opts){ + options(mnet_theme = theme) + cli::cli_alert_success("Theme set to {theme}.") + } else { + cli::cli_alert_danger("Please choose one of the available themes: {.emph {theme_opts}}.") + } +} + #' @rdname map_themes #' @export theme_iheid <- function(base_size = 12, base_family = "serif") { diff --git a/R/mark_net.R b/R/mark_net.R index b5b3d927..b8d236d5 100644 --- a/R/mark_net.R +++ b/R/mark_net.R @@ -391,7 +391,7 @@ is_multiplex.matrix <- function(.data) { FALSE } -reserved_tie_attr <- c("wave","panel","sign","weight","date","begin","end") +reserved_tie_attr <- c("wave","panel","sign","weight","date","begin","end","name") #' @export is_multiplex.tbl_graph <- function(.data) { diff --git a/R/measure_centrality.R b/R/measure_centrality.R index 7cf2af32..c1874791 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -657,6 +657,8 @@ net_harmonic <- function(.data, normalized = TRUE, k = 2){ #' - `node_power()` measures the Bonacich, beta, or power centrality of nodes in a network. #' - `node_alpha()` measures the alpha or Katz centrality of nodes in a network. #' - `node_pagerank()` measures the pagerank centrality of nodes in a network. +#' - `node_hub()` measures how well nodes in a network serve as hubs pointing to many authorities. +#' - `node_authority()` measures how well nodes in a network serve as authorities from many hubs. #' - `tie_eigenvector()` measures the eigenvector centrality of ties in a network. #' - `net_eigenvector()` measures the eigenvector centralization for a network. #' @@ -825,6 +827,27 @@ node_pagerank <- function(.data){ .data) } +#' @rdname measure_central_eigen +#' @references +#' Kleinberg, Jon. 1999. +#' "Authoritative sources in a hyperlinked environment". +#' _Journal of the ACM_ 46(5): 604–632. +#' \doi{110.1145/324133.324140}. +#' @export +node_authority <- function(.data){ + if(missing(.data)) {expect_nodes(); .data <- .G()} + make_node_measure(igraph::authority_score(manynet::as_igraph(.data))$vector, + .data) +} + +#' @rdname measure_central_eigen +#' @export +node_hub <- function(.data){ + if(missing(.data)) {expect_nodes(); .data <- .G()} + make_node_measure(igraph::hub_score(manynet::as_igraph(.data))$vector, + .data) +} + #' @rdname measure_central_eigen #' @examples #' tie_eigenvector(ison_adolescents) diff --git a/R/measure_closure.R b/R/measure_closure.R index 844fb9d1..6b8300f0 100644 --- a/R/measure_closure.R +++ b/R/measure_closure.R @@ -94,20 +94,53 @@ node_transitivity <- function(.data) { #' @export net_equivalency <- function(.data) { if(missing(.data)) {expect_nodes(); .data <- .G()} - if (manynet::is_twomode(.data)) { + if(is_twomode(.data)){ mat <- manynet::as_matrix(.data) c <- ncol(mat) indegrees <- colSums(mat) twopaths <- crossprod(mat) diag(twopaths) <- 0 - output <- sum(twopaths * (twopaths - 1)) / + out <- sum(twopaths * (twopaths - 1)) / (sum(twopaths * (twopaths - 1)) + sum(twopaths * - (matrix(indegrees, c, c) - twopaths))) - if (is.nan(output)) output <- 1 - if(manynet::is_weighted(.data)) output <- output / mean(mat[mat>0]) - } else cli::cli_abort("This function expects a two-mode network") - make_network_measure(output, .data) + (matrix(indegrees, c, c) - twopaths))) + if (is.nan(out)) out <- 1 + if(manynet::is_weighted(.data)) out <- out / mean(mat[mat>0]) + } else { + out <- rowSums(vapply(cli::cli_progress_along(1:net_nodes(.data)), function(i){ + threepaths <- igraph::all_simple_paths(.data, i, cutoff = 3, + mode = "all") + onepaths <- threepaths[vapply(threepaths, length, + FUN.VALUE = numeric(1))==2] + threepaths <- threepaths[vapply(threepaths, length, + FUN.VALUE = numeric(1))==4] + c(sum(sapply(threepaths,"[[",4) %in% sapply(onepaths,"[[",2)), + length(threepaths)) + }, FUN.VALUE = numeric(2))) + out <- out[1]/out[2] + } + make_network_measure(out, .data) +} + +#' @rdname measure_closure +#' @examples +#' node_equivalency(ison_southern_women) +#' @export +node_equivalency <- function(.data) { + if(missing(.data)) {expect_nodes(); .data <- .G()} + # if(is_weighted(.data)) + # mnet_info("Using unweighted form of the network.") + out <- vapply(cli::cli_progress_along(1:net_nodes(.data)), function(i){ + threepaths <- igraph::all_simple_paths(.data, i, cutoff = 3, + mode = "all") + onepaths <- threepaths[vapply(threepaths, length, + FUN.VALUE = numeric(1))==2] + threepaths <- threepaths[vapply(threepaths, length, + FUN.VALUE = numeric(1))==4] + mean(sapply(threepaths,"[[",4) %in% sapply(onepaths,"[[",2)) + }, FUN.VALUE = numeric(1)) + if (any(is.nan(out))) out[is.nan(out)] <- 0 + make_node_measure(out, .data) } #' @rdname measure_closure diff --git a/R/member_equivalence.R b/R/member_equivalence.R index 6e99e5f3..b94d5a75 100644 --- a/R/member_equivalence.R +++ b/R/member_equivalence.R @@ -57,8 +57,7 @@ node_in_equivalence <- function(.data, census, range = 8L){ if(missing(.data)) {expect_nodes(); .data <- .G()} hc <- switch(match.arg(cluster), - hierarchical = cluster_hierarchical(`if`(manynet::is_twomode(.data), - manynet::to_onemode(census), census), + hierarchical = cluster_hierarchical(census, match.arg(distance)), concor = cluster_concor(.data, census)) @@ -105,13 +104,13 @@ node_in_structural <- function(.data, #' } #' @export node_in_regular <- function(.data, - k = c("silhouette", "elbow", "strict"), - cluster = c("hierarchical", "concor"), - distance = c("euclidean", "maximum", "manhattan", - "canberra", "binary", "minkowski"), - range = 8L){ + k = c("silhouette", "elbow", "strict"), + cluster = c("hierarchical", "concor"), + distance = c("euclidean", "maximum", "manhattan", + "canberra", "binary", "minkowski"), + range = 8L){ if(missing(.data)) {expect_nodes(); .data <- .G()} - if(manynet::is_twomode(.data)){ + if(is_twomode(.data)){ mat <- as.matrix(node_by_quad(.data)) } else { mat <- node_by_triad(.data) diff --git a/R/motif_census.R b/R/motif_census.R index 6bb9545c..57789bad 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -84,6 +84,26 @@ node_by_tie <- function(.data){ make_node_motif(t(mat), object) } +#' @rdname motif_node +#' @examples +#' node_by_dyad(ison_networkers) +#' @export +node_by_dyad <- function(.data) { + if(missing(.data)) {expect_nodes(); .data <- .G()} + if(is_weighted(.data)){ + .data <- to_unweighted(.data) + mnet_info("Ignoring tie weights.") + } + mat <- as_matrix(.data) + out <- t(vapply(seq_nodes(.data), function(x){ + vec <- mat[x,] + mat[,x] + c(sum(vec==2), sum(vec==1), sum(vec==0)) + }, FUN.VALUE = numeric(3))) + colnames(out) <- c("Mutual", "Asymmetric", "Null") + if (!is_directed(.data)) out <- out[,c(1, 3)] + make_node_motif(out, .data) +} + #' @rdname motif_node #' @references #' Davis, James A., and Samuel Leinhardt. 1967. @@ -95,83 +115,117 @@ node_by_triad <- function(.data){ if(missing(.data)) {expect_nodes(); .data <- .G()} out <- t(sapply(seq.int(manynet::net_nodes(.data)), function(x) net_by_triad(.data) - net_by_triad(manynet::delete_nodes(.data, x)))) - rownames(out) <- manynet::node_names(.data) make_node_motif(out, .data) } -#' @rdname motif_node -#' @section Quad census: -#' The quad census uses the `{oaqc}` package to do -#' the heavy lifting of counting the number of each orbits. -#' See `vignette('oaqc')`. -#' However, our function relabels some of the motifs -#' to avoid conflicts and improve some consistency with -#' other census-labelling practices. -#' The letter-number pairing of these labels indicate -#' the number and configuration of ties. -#' For now, we offer a rough translation: -#' -#' | migraph | Ortmann and Brandes -#' | ------------- |------------- | -#' | E4 | co-K4 -#' | I40, I41 | co-diamond -#' | H4 | co-C4 -#' | L42, L41, L40 | co-paw -#' | D42, D40 | co-claw -#' | U42, U41 | P4 -#' | Y43, Y41 | claw -#' | P43, P42, P41 | paw -#' | 04 | C4 -#' | Z42, Z43 | diamond -#' | X4 | K4 -#' -#' See also [this list of graph classes](https://www.graphclasses.org/smallgraphs.html#nodes4). -#' @importFrom tidygraph %E>% -#' @references -#' Ortmann, Mark, and Ulrik Brandes. 2017. -#' “Efficient Orbit-Aware Triad and Quad Census in Directed and Undirected Graphs.” -#' \emph{Applied Network Science} 2(1):13. -#' \doi{10.1007/s41109-017-0027-2}. +# #' @rdname motif_node +# #' @section Quad census: +# #' The quad census uses the `{oaqc}` package to do +# #' the heavy lifting of counting the number of each orbits. +# #' See `vignette('oaqc')`. +# #' However, our function relabels some of the motifs +# #' to avoid conflicts and improve some consistency with +# #' other census-labelling practices. +# #' The letter-number pairing of these labels indicate +# #' the number and configuration of ties. +# #' For now, we offer a rough translation: +# #' +# #' | migraph | Ortmann and Brandes +# #' | ------------- |------------- | +# #' | E4 | co-K4 +# #' | I40, I41 | co-diamond +# #' | H4 | co-C4 +# #' | L42, L41, L40 | co-paw +# #' | D42, D40 | co-claw +# #' | U42, U41 | P4 +# #' | Y43, Y41 | claw +# #' | P43, P42, P41 | paw +# #' | 04 | C4 +# #' | Z42, Z43 | diamond +# #' | X4 | K4 +# #' +# #' See also [this list of graph classes](https://www.graphclasses.org/smallgraphs.html#nodes4). +# #' @importFrom tidygraph %E>% +# #' @references +# #' Ortmann, Mark, and Ulrik Brandes. 2017. +# #' “Efficient Orbit-Aware Triad and Quad Census in Directed and Undirected Graphs.” +# #' \emph{Applied Network Science} 2(1):13. +# #' \doi{10.1007/s41109-017-0027-2}. +# #' @examples +# #' node_by_quad(ison_southern_women) +# #' @export +# node_by_quad <- function(.data){ +# if(missing(.data)) {expect_nodes(); .data <- .G()} +# thisRequires("oaqc") +# graph <- .data %>% manynet::as_tidygraph() %E>% +# as.data.frame() +# if(ncol(graph)>2) graph <- graph[,1:2] +# out <- oaqc::oaqc(graph)[[1]] +# out <- out[-1,] +# rownames(out) <- manynet::node_names(.data) +# colnames(out) <- c("E4", # co-K4 +# "I41","I40", # co-diamond +# "H4", # co-C4 +# "L42","L41","L40", # co-paw +# "D42","D40", # co-claw +# "U42","U41", # P4 +# "Y43","Y41", # claw +# "P43","P42","P41", # paw +# "04", # C4 +# "Z42","Z43", # diamond +# "X4") # K4 +# if(manynet::is_twomode(.data)) out <- out[,-c(8,9,14,15,16,18,19,20)] +# make_node_motif(out, .data) +# } + +#' @rdname motif_node #' @examples -#' node_by_quad(manynet::ison_southern_women) +#' node_by_quad(ison_southern_women) #' @export node_by_quad <- function(.data){ - if(missing(.data)) {expect_nodes(); .data <- .G()} - thisRequires("oaqc") - graph <- .data %>% manynet::as_tidygraph() %E>% - as.data.frame() - if(ncol(graph)>2) graph <- graph[,1:2] - out <- oaqc::oaqc(graph)[[1]] - out <- out[-1,] - rownames(out) <- manynet::node_names(.data) - colnames(out) <- c("E4", # co-K4 - "I41","I40", # co-diamond - "H4", # co-C4 - "L42","L41","L40", # co-paw - "D42","D40", # co-claw - "U42","U41", # P4 - "Y43","Y41", # claw - "P43","P42","P41", # paw - "04", # C4 - "Z42","Z43", # diamond - "X4") # K4 - if(manynet::is_twomode(.data)) out <- out[,-c(8,9,14,15,16,18,19,20)] + cmbs <- utils::combn(1:net_nodes(.data), 4) + mat <- as_matrix(to_onemode(.data)) + dd <- apply(cmbs, 2, function(x) c(sum(mat[x,x]), + max(rowSums(mat[x,x])))) + + types <- rep(NA, ncol(cmbs)) + types[dd[1,] == 0] <- "E4" + types[dd[1,] == 2] <- "I4" + types[dd[1,] == 4 & dd[2,] == 1] <- "H4" + types[dd[1,] == 4 & dd[2,] == 2] <- "L4" + types[dd[1,] == 6 & dd[2,] == 2] <- "D4" + types[dd[1,] == 6 & dd[2,] == 1] <- "U4" + types[dd[1,] == 6 & dd[2,] == 3] <- "Y4" + types[dd[1,] == 8 & dd[2,] == 3] <- "P4" + types[dd[1,] == 8 & dd[2,] == 2] <- "C4" + types[dd[1,] == 10] <- "Z4" + types[dd[1,] == 12] <- "X4" + + appears <- sapply(seq.int(net_nodes(.data)), + function(x) types[which(cmbs == x, arr.ind = TRUE)[,2]]) + out <- apply(appears, 2, table) + + if(is.list(out)){ + out <- as.matrix(dplyr::bind_rows(out)) + } else out <- as.matrix(as.data.frame(t(out))) + out.order <- c("E4","I4","H4","L4","D4","U4","Y4","P4","C4","Z4","X4") + out <- out[,match(out.order, colnames(out))] + colnames(out) <- out.order + out[is.na(out)] <- 0 + make_node_motif(out, .data) } -# #' @export -# node_bmotif_census <- function(.data, normalized = FALSE){ -# if (!("bmotif" %in% rownames(utils::installed.packages()))) { -# message("Please install package `{bmotif}`.") -# out <- bmotif::node_positions(manynet::as_matrix(.data), -# weights_method = ifelse(manynet::is_weighted(.data), -# 'mean_motifweights', 'none'), -# normalisation = ifelse(normalized, -# 'levelsize_NAzero', 'none')) -# make_node_motif(out, .data) -# } +# https://stackoverflow.com/questions/26828301/faster-version-of-combn#26828486 +# comb2.int <- function(n, choose = 2){ +# # e.g. n=3 => (1,2), (1,3), (2,3) +# x <- rep(1:n,(n:1)-1) +# i <- seq_along(x)+1 +# o <- c(0,cumsum((n-2):1)) +# y <- i-o[x] +# return(cbind(x,y)) # } -# + # #' @export # node_igraph_census <- function(.data, normalized = FALSE){ # out <- igraph::motifs(manynet::as_igraph(.data), 4) @@ -204,8 +258,8 @@ node_by_quad <- function(.data){ #' _Social Networks_ 32(3): 245-51. #' \doi{10.1016/j.socnet.2010.03.006}. #' @examples -#' node_by_path(manynet::ison_adolescents) -#' node_by_path(manynet::ison_southern_women) +#' node_by_path(ison_adolescents) +#' node_by_path(ison_southern_women) #' @export node_by_path <- function(.data){ if(missing(.data)) {expect_nodes(); .data <- .G()} @@ -243,7 +297,7 @@ NULL net_by_dyad <- function(.data) { if(missing(.data)) {expect_nodes(); .data <- .G()} if (manynet::is_twomode(.data)) { - cli::cli_abort("A twomode or multilevel option for a dyad census is not yet implemented.") + mnet_unavailable("A twomode or multilevel option for a dyad census is not yet implemented.") } else { out <- suppressWarnings(igraph::dyad_census(manynet::as_igraph(.data))) out <- unlist(out) @@ -275,6 +329,53 @@ net_by_triad <- function(.data) { } } +#' @rdname motif_net +#' @examples +#' net_by_quad(ison_southern_women) +#' @export +net_by_quad <- function(.data){ + if(missing(.data)) {expect_nodes(); .data <- .G()} + cmbs <- utils::combn(1:net_nodes(.data), 4) + mat <- as_matrix(to_onemode(.data)) + dens <- apply(cmbs, 2, function(x) sum(mat[x,x])) + + E4 <- sum(dens == 0) + I4 <- sum(dens == 1) + + if(any(dens==2)){ + if(sum(dens==2)>1){ + twosies <- apply(cmbs[,dens==2], 2, function(x) max(rowSums(mat[x,x]))) + } else twosies <- max(rowSums(mat[cmbs[,dens==2], cmbs[,dens==2]])) + H4 <- sum(twosies==1) + L4 <- sum(twosies==2) + } else H4 <- L4 <- 0 + + if(any(dens==3)){ + if(sum(dens==3)>1){ + threesies <- apply(cmbs[,dens==3], 2, function(x) max(rowSums(mat[x,x]))) + } else threesies <- max(rowSums(mat[cmbs[,dens==3], cmbs[,dens==3]])) + D4 <- sum(threesies==2) + U4 <- sum(threesies==1) + Y4 <- sum(threesies==3) + } else D4 <- U4 <- Y4 <- 0 + + if(any(dens==4)){ + if(sum(dens==4)>1){ + foursies <- apply(cmbs[,dens==4], 2, function(x) max(rowSums(mat[x,x]))) + } else foursies <- max(rowSums(mat[cmbs[,dens==4], cmbs[,dens==4]])) + P4 <- sum(foursies==3) + C4 <- sum(foursies==2) + } else P4 <- C4 <- 0 + + Z4 <- sum(dens == 5) + X4 <- sum(dens == 6) + + out <- c(E4 = E4, I4 = I4, H4 = H4, L4 = L4, D4 = D4, U4 = U4, Y4 = Y4, + P4 = P4, C4 = C4, Z4 = Z4, X4 = X4) + + make_network_motif(out, .data) +} + #' @rdname motif_net #' @source Alejandro Espinosa 'netmem' #' @references diff --git a/R/zzz.R b/R/zzz.R index 1549c505..9a048c47 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -3,15 +3,18 @@ # suppressMessages(suppressPackageStartupMessages(library("manynet", warn.conflicts = FALSE))) if (!interactive()) return() - + + options(manynet_verbosity = getOption("manynet_verbosity", "verbose")) + # options(manynet_theme = getOption("manynet_theme", "default")) + options(cli.theme = manynet_console_theme()) + # pkgs <- as.data.frame(utils::available.packages(utils::contrib.url(getOption("repos")))) # # cran_version <- pkgs[pkgs$Package == "manynet","Version"] local_version <- utils::packageVersion("manynet") - cli::cli_div(theme = list(span.emph = list(color = "red"))) - cli::cli_inform("This is {.pkg manynet} version {.emph {local_version}}", class = "packageStartupMessage") - cli::cli_end() + cli::cli_inform("You are using {.pkg manynet} version {.version {local_version}}.", + class = "packageStartupMessage") old.list <- as.data.frame(utils::old.packages()) behind_cran <- "manynet" %in% old.list$Package @@ -20,6 +23,8 @@ "i" = "There are lots of ways to contribute to {.pkg manynet} at {.url https://github.com/stocnet/manynet/}.", "i" = "Please let us know any issues or features requests at {.url https://github.com/stocnet/manynet/issues}. It's really helpful!", "i" = "To eliminate package startup messages, use: `suppressPackageStartupMessages(library({.pkg manynet}))`.", + # "i" = "Changing the theme of all your graphs is straightforward with `set_manynet_theme()`", + "i" = "If there are too many messages in the console, run `options(manynet_verbosity = 'quiet')`", "i" = "Visit the website to learn more: {.url https://stocnet.github.io/manynet/}.", "i" = "We recommend the 'Function Overview' page online to discover new analytic opportunities: {.url https://stocnet.github.io/manynet/reference/index.html}.", "i" = "Discover all the {.emph stocnet} R packages at {.url https://github.com/stocnet/}.", @@ -43,3 +48,72 @@ } } + +mnet_progress_step <- function(...){ + if(getOption("manynet_verbosity", default = "quiet")!="quiet") + cli::cli_progress_step(...) +} + +mnet_info <- function(...){ + if(getOption("manynet_verbosity", default = "quiet")!="quiet") + cli::cli_alert_info(...) +} + +mnet_unavailable <- function(...){ + if(getOption("manynet_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}.")) +} + +manynet_console_theme <- function(){ + # dark <- detect_dark_theme(dark) + list(h1 = list(`margin-top` = 1, `margin-bottom` = 0, color = "#199D77", + fmt = function(x) cli::rule(x, line_col = "#199D77")), + h2 = list(`margin-top` = 1, `margin-bottom` = 0, color = "#199D77", + fmt = function(x) paste0(cli::symbol$line, " ", x, " ", cli::symbol$line, cli::symbol$line)), + h3 = list(`margin-top` = 1, `margin-bottom` = 0, color = "#199D77"), + par = list(`margin-top` = 0, `margin-bottom` = 1), + `.alert-danger` = list(`background-color` = "#D83127", color = "white", + before = function() paste0(cli::symbol$cross, " ")), + `.alert-warning` = list(color = "#E6AB02", `font-weight` = "bold", before = paste0("!", " ")), + `.alert-success` = list(before = function() paste0(col_mnet_green(cli::symbol$tick), " ")), + `.alert-info` = list(before = function() paste0(col_mnet_blue(cli::symbol$info), " ")), + `.alert-start` = list(before = function() paste0(cli::symbol$arrow_right, " ")), + span.pkg = list(color = "#199D77", `font-weight` = "bold"), + span.version = list(color = "#D83127"), + span.emph = list(color = "#D83127"), + span.strong = list(`font-weight` = "bold", `font-style` = "italic"), + span.fun = utils::modifyList(simple_theme_code(), + list(after = "()")), + span.fn = utils::modifyList(simple_theme_code(), + list(after = "")), + span.arg = simple_theme_code(), + span.kbd = utils::modifyList(simple_theme_code(), + list(before = "<", after = ">")), + span.key = utils::modifyList(simple_theme_code(), + list(before = "<", after = ">")), + span.file = list(color = "#4576B5"), + span.path = list(color = "#4576B5"), + span.email = list(color = "#4576B5"), + span.url = utils::modifyList(list(color = "#4576B5"), list(before = "<", + after = ">")), + span.var = simple_theme_code(), + span.envvar = simple_theme_code(), + span.timestamp = list(before = "[", after = "]", color = "grey")) +} + +simple_theme_code <- function(){ + # if (dark) { + # list(`background-color` = "#232323", color = "#f0f0f0") + # } + # else { + list(`background-color` = "#f8f8f8", color = "#202020") + # } +} + +col_mnet_green <- cli::make_ansi_style("#199D77") + +col_mnet_blue <- cli::make_ansi_style("#4576B5") + diff --git a/cran-comments.md b/cran-comments.md index 732539ec..2e6e0b1f 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -8,3 +8,5 @@ ## R CMD check results 0 errors | 0 warnings | 0 notes + +* This version fixes errors on Linux versions for which the {oaqc} package is not available \ No newline at end of file diff --git a/data/ison_laterals.rda b/data/ison_laterals.rda index fc51fbc3..7b4c4e4e 100644 Binary files a/data/ison_laterals.rda and b/data/ison_laterals.rda differ diff --git a/data/ison_southern_women.rda b/data/ison_southern_women.rda index 0ed513bf..dc1fd4fc 100644 Binary files a/data/ison_southern_women.rda and b/data/ison_southern_women.rda differ diff --git a/inst/tutorials/tutorial0/tutorial0.Rmd b/inst/tutorials/tutorial0/tutorial0.Rmd index 3cb9e704..d8d02cbf 100644 --- a/inst/tutorials/tutorial0/tutorial0.Rmd +++ b/inst/tutorials/tutorial0/tutorial0.Rmd @@ -36,6 +36,7 @@ Recommended elsewhere are the following: - http://data.princeton.edu/R/gettingStarted.html - http://www.ats.ucla.edu/stat/R/sk/ - http://www.statmethods.net/ +- https://www.r-bloggers.com/2022/05/best-books-to-learn-r-programming-2/ These sites will help you learn or refresh your memory. But you can also expect to return to Google often as you go and type "R ..." as a query. diff --git a/inst/tutorials/tutorial1/data.Rmd b/inst/tutorials/tutorial1/data.Rmd index 7605b6c3..a8973e0a 100644 --- a/inst/tutorials/tutorial1/data.Rmd +++ b/inst/tutorials/tutorial1/data.Rmd @@ -514,6 +514,8 @@ Reimport the `data/adols.csv` file, make it an igraph-class object, and then mak read_edgelist("data/adols.csv") %>% as_igraph() %>% to_undirected() ``` +### Free play + Try this out with other compatible classes of objects, and reformatting other aspects of the network. For example: @@ -650,6 +652,8 @@ is just one (albeit the default) option for how ties in the projection are weigh Other options included in `{manynet}` include the Jaccard index, Rand simple matching coefficient, Pearson coefficient, and Yule's Q. These may be of interest if, for example, overlap should be weighted by participation. +### Other transformations + Other transforming functions include: - `to_giant()` identifies and returns only the main component of a network. @@ -658,11 +662,20 @@ Other transforming functions include: - `to_ties()` returns a network where the ties in the original network become the nodes, and the ties are shared adjacencies to nodes. - `to_matching()` returns a network in which each node is only tied to one of its previously existing ties such that the network's cardinality is maximised. +In other words, the algorithm tries to match nodes as best as possible so that each +node has a partner. +Note that this is not always possible. Remember, all these `to_*()` functions work on any compatible class; the `to_*()` functions will also attempt to return that same class of object, making it even easier to manipulate networks into shape for analysis. +### Free play + +```{r transform-free, exercise = TRUE, purl=FALSE} + +``` + ## Modifying data After choosing and/or importing some network data, diff --git a/inst/tutorials/tutorial1/data.html b/inst/tutorials/tutorial1/data.html index 044ee9d8..f1e9419d 100644 --- a/inst/tutorials/tutorial1/data.html +++ b/inst/tutorials/tutorial1/data.html @@ -247,6 +247,18 @@
See whether you can call up other datasets now too. You won’t need to
+load the {manynet}
package again (it’ll stay loaded), but
+identify a network that interests you from table_data()
and
+then call/print it.
data(package = "network")
-data(flo)
+data(flo, package = "network")
flo
This data uses quite a different class to what we encountered above. @@ -650,6 +662,8 @@
read_edgelist("data/adols.csv") %>% as_igraph() %>% to_undirected()
+Try this out with other compatible classes of objects, and reformatting other aspects of the network. For example:
These functions are similar to the reformatting functions, and are @@ -793,6 +808,9 @@
{manynet}
include the Jaccard index, Rand simple matching
coefficient, Pearson coefficient, and Yule’s Q. These may be of interest
if, for example, overlap should be weighted by participation.
+Other transforming functions include:
to_giant()
identifies and returns only the main
@@ -806,13 +824,23 @@ to_matching()
returns a network in which each node is
only tied to one of its previously existing ties such that the network’s
-cardinality is maximised.Remember, all these to_*()
functions work on any
compatible class; the to_*()
functions will also attempt to
return that same class of object, making it even easier to manipulate
networks into shape for analysis.