From 5d81763b9253ab59d22667b51cdc886925333099 Mon Sep 17 00:00:00 2001 From: cynthiahqy <29718979+cynthiahqy@users.noreply.github.com> Date: Mon, 1 May 2023 01:00:20 +1000 Subject: [PATCH] :tada: add v0.0.1 files from https://github.com/cynthiahqy/conformr-xmap-project/pull/122 --- .Rbuildignore | 6 + DESCRIPTION | 41 +++ LICENSE | 2 + LICENSE.md | 21 ++ NAMESPACE | 38 ++ R/abort.R | 120 +++++++ R/add_weights_equal.R | 28 ++ R/add_weights_unit.R | 24 ++ R/as_xmap.R | 80 +++++ R/calc.R | 12 + R/data.R | 20 ++ R/get-helpers.R | 9 + R/is_xmap.R | 17 + R/new_xmap.R | 12 + R/pairs_named.R | 69 ++++ R/print.R | 44 +++ R/sysdata.rda | Bin 0 -> 237 bytes R/utils.R | 6 + R/validate_xmap_df.R | 25 ++ R/verify_links_as_xmap.R | 31 ++ R/verify_named.R | 202 +++++++++++ R/verify_pairs.R | 40 +++ R/vhas.R | 89 +++++ R/xmap_df.R | 31 ++ R/xmap_drop_extra.R | 20 ++ R/xmap_reverse.R | 42 +++ R/xmap_to.R | 127 +++++++ README.Rmd | 45 +++ README.md | 63 ++++ _pkgdown.yml | 61 ++++ data-raw/mock.R | 21 ++ data/mock.rda | Bin 0 -> 3739 bytes man/abort.Rd | 74 ++++ man/add_weights_equal.Rd | 35 ++ man/add_weights_unit.Rd | 31 ++ man/as_pairs.Rd | 65 ++++ man/as_xmap.Rd | 77 ++++ man/mock.Rd | 30 ++ man/new_xmap.Rd | 29 ++ man/op-null-default.Rd | 13 + man/print.xmap.Rd | 17 + man/validate_xmap_df.Rd | 13 + man/verify_links_as_xmap.Rd | 30 ++ man/verify_named.Rd | 66 ++++ man/verify_named_matchset.Rd | 56 +++ man/verify_pairs.Rd | 35 ++ man/vhas.Rd | 55 +++ man/xmap_drop_extra.Rd | 14 + man/xmap_reverse.Rd | 27 ++ man/xmap_to_matrix.Rd | 47 +++ man/xmap_to_named.Rd | 49 +++ tests/testthat.R | 12 + tests/testthat/tests.R | 422 ++++++++++++++++++++++ vignettes/making-xmaps.Rmd | 466 +++++++++++++++++++++++++ vignettes/plot-anzsco-isco-bigraph.png | Bin 0 -> 95249 bytes vignettes/plot-weight-sum-matrix.png | Bin 0 -> 83688 bytes vignettes/vis-xmaps.Rmd | 255 ++++++++++++++ vignettes/xmap.Rmd | 135 +++++++ 58 files changed, 3399 insertions(+) create mode 100644 .Rbuildignore create mode 100644 DESCRIPTION create mode 100644 LICENSE create mode 100644 LICENSE.md create mode 100644 NAMESPACE create mode 100644 R/abort.R create mode 100644 R/add_weights_equal.R create mode 100644 R/add_weights_unit.R create mode 100644 R/as_xmap.R create mode 100644 R/calc.R create mode 100644 R/data.R create mode 100644 R/get-helpers.R create mode 100644 R/is_xmap.R create mode 100644 R/new_xmap.R create mode 100644 R/pairs_named.R create mode 100644 R/print.R create mode 100644 R/sysdata.rda create mode 100644 R/utils.R create mode 100644 R/validate_xmap_df.R create mode 100644 R/verify_links_as_xmap.R create mode 100644 R/verify_named.R create mode 100644 R/verify_pairs.R create mode 100644 R/vhas.R create mode 100644 R/xmap_df.R create mode 100644 R/xmap_drop_extra.R create mode 100644 R/xmap_reverse.R create mode 100644 R/xmap_to.R create mode 100644 README.Rmd create mode 100644 README.md create mode 100644 _pkgdown.yml create mode 100644 data-raw/mock.R create mode 100644 data/mock.rda create mode 100644 man/abort.Rd create mode 100644 man/add_weights_equal.Rd create mode 100644 man/add_weights_unit.Rd create mode 100644 man/as_pairs.Rd create mode 100644 man/as_xmap.Rd create mode 100644 man/mock.Rd create mode 100644 man/new_xmap.Rd create mode 100644 man/op-null-default.Rd create mode 100644 man/print.xmap.Rd create mode 100644 man/validate_xmap_df.Rd create mode 100644 man/verify_links_as_xmap.Rd create mode 100644 man/verify_named.Rd create mode 100644 man/verify_named_matchset.Rd create mode 100644 man/verify_pairs.Rd create mode 100644 man/vhas.Rd create mode 100644 man/xmap_drop_extra.Rd create mode 100644 man/xmap_reverse.Rd create mode 100644 man/xmap_to_matrix.Rd create mode 100644 man/xmap_to_named.Rd create mode 100644 tests/testthat.R create mode 100644 tests/testthat/tests.R create mode 100644 vignettes/making-xmaps.Rmd create mode 100644 vignettes/plot-anzsco-isco-bigraph.png create mode 100644 vignettes/plot-weight-sum-matrix.png create mode 100644 vignettes/vis-xmaps.Rmd create mode 100644 vignettes/xmap.Rmd diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000..d130929 --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,6 @@ +^LICENSE\.md$ +^data-raw$ +^README\.Rmd$ +^_pkgdown\.yml$ +^docs$ +^pkgdown$ diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..08ea0cd --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,41 @@ +Package: xmap +Title: A principled approach to recoding and redistributing data between + nomenclature +Version: 0.0.1 +Authors@R: c( + person("Cynthia", "Huang", , "cynthia@gmail.com", role = c("aut", "cre")), + person("Laura", "Puzzello", role = c("aut", "fnd")) + ) +Description: Provides tools for creating and verifying classification, + category and/or nomenclature mapping objects. +License: MIT + file LICENSE +URL: https://github.com/cynthiahqy/xmap +Config/testthat/edition: 3 +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.2.2 +Depends: + R (>= 4.1) +Imports: + cli, + dplyr, + glue, + rlang (>= 1.0.0), + tibble, + tidyr +Suggests: + forcats, + ggbump, + ggplot2, + knitr, + matlib, + Matrix, + patchwork, + rmarkdown, + stats, + stringr, + testthat (>= 3.0.0) +LazyData: true +VignetteBuilder: knitr +LitrVersionUsed: 0.7.0 +LitrId: 63fe8913732cbd8d745656e34cc88010 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..355e013 --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2023 +COPYRIGHT HOLDER: C. Huang diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9e0db0d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2023 C. Huang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..58ff08a --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,38 @@ +# Generated by roxygen2: do not edit by hand + +S3method(as_xmap_df,data.frame) +S3method(print,xmap_df) +S3method(xmap_reverse,xmap_df) +S3method(xmap_to_matrix,xmap_df) +export(add_weights_equal) +export(add_weights_unit) +export(as_pairs_from_named) +export(as_xmap_df) +export(is_xmap) +export(is_xmap_df) +export(msg_abort_frac_weights) +export(msg_abort_named_matchset) +export(pairs_to_named_list) +export(pairs_to_named_vector) +export(verify_links_as_xmap) +export(verify_named_all_1to1) +export(verify_named_all_names_unique) +export(verify_named_all_unique) +export(verify_named_all_values_unique) +export(verify_named_as_recode_unique) +export(verify_named_matchset_names_contain) +export(verify_named_matchset_names_exact) +export(verify_named_matchset_names_within) +export(verify_named_matchset_values_contain) +export(verify_named_matchset_values_exact) +export(verify_named_matchset_values_within) +export(verify_named_no_dup_names) +export(verify_named_no_dup_values) +export(verify_pairs_all_1to1) +export(verify_pairs_all_unique) +export(verify_pairs_as_recode_unique) +export(xmap_drop_extra) +export(xmap_reverse) +export(xmap_to_matrix) +export(xmap_to_named_list) +export(xmap_to_named_vector) diff --git a/R/abort.R b/R/abort.R new file mode 100644 index 0000000..02b80cc --- /dev/null +++ b/R/abort.R @@ -0,0 +1,120 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' @describeIn abort Abort if named columns can't be found in df +#' +abort_missing_cols <- function(df, cols) { + missing_cols <- setdiff(cols, names(df)) + if (length(missing_cols) != 0) { + cli::cli_abort( + message = "The column{?s} {.var {missing_cols}} {?was/were} not found.", + class = "abort_missing_cols" + ) + } + invisible(df) +} + +#' @describeIn abort Abort if xmap_df has missing values +#' +abort_any_na <- function(df) { + if (base::anyNA(df)) { + cli::cli_abort( + message = "NA values found. Please enter missing `from` or `to` node labels and/or convert NA weights", + class = "abort_na" + ) + } + invisible(df) +} + +#' Validation functions and messages for xmap or candidate links (Internal) +#' +#' @description +#' Checks issues with data.frame like objects containing validated `xmap` or candidate links. +#' +#' @param df a data.frame-like object containing links +#' @param col_from,col_to,col_weights character vector or values naming columns from `df` +#' +#' @returns An error if the validation condition fails, +#' and invisibly returns `df` otherwise. +#' +#' @name abort +NULL + +#' @describeIn abort Abort if xmap_df has wrong column types +#' +abort_weights_col_type <- function(df, col_weights) { + if (!is.numeric(df[[col_weights]])) { + cli::cli_abort( + message = "The column `{col_weights}` should be of type numeric", + class = "abort_col_type" + ) + } + invisible(df) +} + +#' @describeIn abort Abort if duplicate source-target pairs are found +#' +abort_dup_pairs <- function(df, col_from, col_to) { + if (!vhas_no_dup_pairs(df[[col_from]], df[[col_to]])) { + cli::cli_abort( + message = "Duplicate `from`-`to` links were found. + Please remove or collapse duplicates.", + class = "abort_dup_pairs" + ) + } + invisible(df) +} + +#' @describeIn abort Abort for invalid mapping weights +#' +abort_bad_weights <- function(col_weights, call = rlang::caller_env()) { + cli::cli_abort( + message = c( + "Incomplete mapping weights found", + "x" = "{.var {col_weights}} does not sum to 1", + "i" = "Modify weights or adjust `tol` and try again."), + class = "abort_bad_weights", + call = call + ) +} + +#' @describeIn abort Abort if xmap_df columns are not in order +abort_col_order <- function(df, col_from, col_to, col_weights){ + correct_order <- c(col_from, col_to, col_weights) + first_three <- names(df[1:3]) + if(!identical(first_three, correct_order)){ + rlang::abort( + message = "columns are not sorted in order `from`, `to`, `weights`", + class = "abort_col_order" + ) + } + invisible(df) +} + +#' @describeIn abort Abort if from_set attribute doesn't match xmap_df values +#' +abort_from_set <- function(df, col_from, from_set) { + col_from_set <- as.character(unique(df[[col_from]])) + stopifnot(identical(col_from_set, from_set)) + + invisible(df) +} + +#' @describeIn abort Abort message for fractional weights +#' @export +msg_abort_frac_weights <- function(impact){ + cli::format_error(c( + "`x` contains fractional weights. {impact}", + "x" = "You've supplied a xmap with weights not equal to 1") + ) +} + +#' @describeIn abort Abort if xmap_df is not reversible without new weights +#' +abort_not_reversible <- function(df, col_to) { + x_to <- df[[col_to]] + if (vhas_collapse(x_to)){ + cli::cli_abort("Collapse links in {.var xmap_df} cannot be reversed. Please supply new weights and create a new xmap.") + } + invisible(df) +} + diff --git a/R/add_weights_equal.R b/R/add_weights_equal.R new file mode 100644 index 0000000..01ac4b5 --- /dev/null +++ b/R/add_weights_equal.R @@ -0,0 +1,28 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Add equal fractional weights to groups of source-target node pairs +#' +#' Attaches equal weights to every source-target link from a given source node. +#' The resultant weighted links can be verified or coerced into an `xmap`. +#' +#' @inheritParams verify_pairs +#' @inheritParams add_weights_unit +#' +#' @return `pairs` with additional column of weights +#' @family {Helpers for adding weights to pairs} +#' @export +#' +#' @examples +#' animal_pairs <- list(MAMM = c("elephant", "whale", "monkey"), +#' REPT = c("lizard", "turtle"), +#' CRUS = c("crab")) |> +#' as_pairs_from_named("class", "animal") +#' animal_pairs |> +#' add_weights_equal(from = class, to = animal) +add_weights_equal <- function(df, from, to, weights_into = "weights"){ + ## TODO: validate_pairs_unique() + df |> + dplyr::group_by({{from}}) |> + dplyr::mutate("{weights_into}" := 1/dplyr::n_distinct({{to}})) |> + dplyr::ungroup() +} diff --git a/R/add_weights_unit.R b/R/add_weights_unit.R new file mode 100644 index 0000000..d4128f7 --- /dev/null +++ b/R/add_weights_unit.R @@ -0,0 +1,24 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Add unit weights to node pairs table +#' +#' Attaches column of unit weights to pairs of source-target nodes. +#' The resultant weighted links can be verified or coerced into `xmap`. +#' +#' @inheritParams verify_pairs +#' @param weights_into character string naming new column to store link weights in +#' +#' @return `pairs` with additional column of ones +#' @family {Helpers for adding weights to pairs} +#' @export +#' +#' @examples +#' AUS_pairs <- list(AUS = c("NSW", "QLD", "SA", "TAS", "VIC", "WA", "ACT", "NT")) |> +#' as_pairs_from_named(names_to = "ctr", values_to = "state") +#' AUS_pairs |> +#' add_weights_unit(weights_into = "weights") +add_weights_unit <- function(df, weights_into = "weights"){ + ## TODO: validate_pairs_unique() + df[,weights_into] <- 1 + return(df) +} diff --git a/R/as_xmap.R b/R/as_xmap.R new file mode 100644 index 0000000..339c1a2 --- /dev/null +++ b/R/as_xmap.R @@ -0,0 +1,80 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Coerce objects to xmap_df +#' +#' Validates and creates a valid crossmap `xmap_df` object. +#' +#' @param x +#' * For `as_xmap_df()`: An object to coerce +#' * For `is_xmap_df()`: An object to test. +#' @param from,to Columns in `x` specifying the source and target nodes +#' @param weights Column in `x` specifying the weight applied to data passed along the directed link between source and target node +#' @inheritParams vhas_complete_weights +#' @param subclass Which xmap subclass to return. Defaults to `xmap_df` for `data.frame` and `tibble` +#' @param .drop_extra Drop columns other than `from`, `to` and `weights`. Defaults to `TRUE` +#' +#' @return A validated `xmap` object. +#' @name as_xmap +#' @export +as_xmap_df <- function(x, from, to, weights, tol = .Machine$double.eps^0.5, subclass = c("xmap_df"), ...) { + UseMethod("as_xmap_df") +} + +#' @describeIn as_xmap Coerce a `data.frame` to `xmap` +#' +#' @export +#' @examples +#' # For a well formed crossmap: +#' links <- data.frame( +#' a = "AUS", +#' b = c("VIC", "NSW", "WA", "OTHER"), +#' w = c(0.1, 0.15, 0.25, 0.5) +#' ) +#' as_xmap_df(links, from = a, to = b, weights = w) +#' +#' # extra columns are dropped, +#' links$extra <- c(2, 4, 5, 6) +#' as_xmap_df(links, from = a, to = b, weights = w) +as_xmap_df.data.frame <- function(x, from, to, weights, tol = .Machine$double.eps^0.5, subclass = "xmap_df", .drop_extra = TRUE) { + ## coercion & checks + stopifnot(is.data.frame(x)) + + # get string names for columns + col_from <- deparse(substitute(from)) + col_to <- deparse(substitute(to)) + col_weights <- deparse(substitute(weights)) + col_strings <- c(col_from, col_to, col_weights) + ## check columns exist + abort_missing_cols(x, col_strings) + + ## drop additional columns + if (.drop_extra) { + df <- x[col_strings] + } else { + df <- x + } + if (ncol(df) < ncol(x)) { + cli::cli_inform("Dropped additional columns in {.arg {deparse(substitute(x))}}") + } + + ## rearrange columns + col_order <- c(col_strings, setdiff(names(df), col_strings)) + df <- df[col_order] + + ## construction + xmap <- switch(subclass, + xmap_df = new_xmap_df(df, col_from, col_to, col_weights), + stop("Unknown xmap subclass")) + + ## validation + ## ---- xmap graph properties ---- + abort_weights_col_type(df, col_weights) + abort_dup_pairs(df, col_from, col_to) + stop_bad_weights <- !vhas_complete_weights(df[[col_from]], df[[col_weights]], tol) + if (stop_bad_weights) { abort_bad_weights(col_weights) } + + ## ---- xmap_df attributes ---- + validate_xmap_df(xmap) + + return(xmap) +} diff --git a/R/calc.R b/R/calc.R new file mode 100644 index 0000000..10b12dc --- /dev/null +++ b/R/calc.R @@ -0,0 +1,12 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' +.calc_unique_sets.xmap_df <- function(x){ + stopifnot(is_xmap_df(x)) + df <- data.frame(x) + x_attrs <- attributes(x) + uniq_sets <- list() + uniq_sets$from_set <- as.character(unique(df[[x_attrs$col_from]])) + uniq_sets$to_set <- as.character(unique(df[[x_attrs$col_to]])) + return(uniq_sets) +} diff --git a/R/data.R b/R/data.R new file mode 100644 index 0000000..061058f --- /dev/null +++ b/R/data.R @@ -0,0 +1,20 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Mock input objects for the `xmap` package +#' +#' A collection of mock inputs for experimenting with functions +#' in the `xmap` package. +#' `named_` objects are either named vectors or nested lists. +#' `df_` objects may contain source-target *pairs* (no weights), +#' or weighted source-target *links*. +#' +#' @format ## `mock` +#' A list with: +#' \describe{ +#' \item{named_ctr_iso3c}{named vector. Names are ISO-3 country codes, values are ISO English country names. Retrieved from `countrycode` package: +#' \url{https://github.com/vincentarelbundock/countrycode}} +#' \item{df_anzsco21}{4-column tibble. Contains major and submajor occupation codes and descriptions for ANZSCO21. Retrieved from `strayr` package: +#' \url{https://github.com/runapp-aus/strayr}} +#' \item{df_mixed}{3-column data.frame. } +#' } +"mock" diff --git a/R/get-helpers.R b/R/get-helpers.R new file mode 100644 index 0000000..6a47ac7 --- /dev/null +++ b/R/get-helpers.R @@ -0,0 +1,9 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' +.get_col_attrs.xmap_df <- function(x){ + stopifnot(is_xmap_df(x)) + x_attrs <- attributes(x) + col_attrs <- x_attrs[startsWith(names(x_attrs), "col")] + return(col_attrs) +} diff --git a/R/is_xmap.R b/R/is_xmap.R new file mode 100644 index 0000000..b0203aa --- /dev/null +++ b/R/is_xmap.R @@ -0,0 +1,17 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Test if object is a crossmap +#' +#' This function returns `TRUE` for crossmaps `xmap` or subclasses thereof (`xmap_df`), and `FALSE` for all other objects, including regular data.frames or tibbles. +#' @export +#' @rdname as_xmap +is_xmap <- function(x) { + base::inherits(x, "xmap") +} + +#' Test if object is `xmap_df` +#' @export +#' @rdname as_xmap +is_xmap_df <- function(x) { + rlang::inherits_all(x, c("xmap_df", "xmap")) +} diff --git a/R/new_xmap.R b/R/new_xmap.R new file mode 100644 index 0000000..ec4fd24 --- /dev/null +++ b/R/new_xmap.R @@ -0,0 +1,12 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' +.calc_xmap_subclass_attr <- function(subclass = c("xmap_df")){ + subclass <- rlang::arg_match(subclass) + + class_attr <- switch(subclass, + xmap_df = c("xmap_df", "xmap", "data.frame"), + stop("Unknown xmap subclass")) + + return(class_attr) +} diff --git a/R/pairs_named.R b/R/pairs_named.R new file mode 100644 index 0000000..abefd24 --- /dev/null +++ b/R/pairs_named.R @@ -0,0 +1,69 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Convert between column pairs and named vector or lists +#' +#' @description +#' Convert named vectors or nested lists into a two-column table of node pairs and vice versa. +#' `as_pairs_from_named` extracts the vector or list element names and the values, unnesting where necessary. +#' `pairs_to_named_vector` extracts name-value pairs from column pairs as is, +#' whilst `pairs_to_named_list()` nests the values first. +#' +#' @inheritParams verify_named +#' @inheritParams verify_pairs +#' @param names_to,values_to character vector specify the new columns to pass the information in `x` into. +#' @param names_from,values_from two columns in `x` to convert to names and values +#' +#' @return +#' * For `as_pairs_from_named()`: a two-column tibble +#' * For `pairs_to_named` fncs: named vector or list +#' +#' @name as_pairs +#' @examples +#' # Coerce named vectors and list to column pairs +#' +#' veg_vec <- c(eggplant = "aubergine", zucchini = "courgette") +#' as_pairs_from_named(veg_vec, "au_eng", "uk_eng") +#' +#' animal_list <- list(MAMM = c("elephant", "whale", "monkey"), +#' REPT = c("lizard", "turtle"), +#' CRUS = c("crab")) +#' as_pairs_from_named(animal_list, "class", "animal") +#' +#' # Convert pairs back to named vector and lists +#' veg_from_pairs <- as_pairs_from_named(veg_vec) |> +#' pairs_to_named_vector(names_from = name, values_from = value) +#' identical(veg_vec, veg_from_pairs) +#' +#' animal_from_pairs <- as_pairs_from_named(animal_list, "class", "animal") |> +#' pairs_to_named_list(names_from = class, values_from = animal) +#' identical(animal_list, animal_from_pairs) +NULL + +#' @describeIn as_pairs Convert named vector or nested list into column pairs +#' @export +as_pairs_from_named <- function(x, names_to = "name", values_to = "value"){ + stopifnot(is.vector(x)) + node_pairs <- x |> + tibble::enframe(name = names_to, value = values_to) |> + tidyr::unnest_longer(col=tidyr::all_of(values_to)) + return(node_pairs) +} + +#' @describeIn as_pairs Convert column pairs to named vector +#' @export +pairs_to_named_vector <- function(df, names_from = name, values_from = value){ + ordered_cols <- dplyr::select(df, {{names_from}}, {{values_from}}) + tibble::deframe(ordered_cols) +} + +#' @describeIn as_pairs Convert column pairs to nested named list +#' @export +pairs_to_named_list <- function(df, names_from = name, values_from = value){ + nested_cols <- dplyr::select(df, {{names_from}}, {{values_from}}) |> + tidyr::nest(values = {{values_from}}) + ordered_cols <- dplyr::select(nested_cols, {{names_from}}, values) + tibble::deframe(ordered_cols) |> + sapply(as.matrix) |> + sapply(as.vector) |> + as.list() +} diff --git a/R/print.R b/R/print.R new file mode 100644 index 0000000..904421b --- /dev/null +++ b/R/print.R @@ -0,0 +1,44 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' +.calc_link_types.xmap_df <- function(xmap_df){ + x_attrs <- attributes(xmap_df) + x_weights <- xmap_df[[x_attrs$col_weights]] + x_to <- xmap_df[[x_attrs$col_to]] + + flags <- c( + "recode" = vhas_recode(x_weights), + "split" = vhas_split(x_weights), + "collapse" = vhas_collapse(x_to) + ) + types <- names(flags[flags == TRUE]) + type <- cli::pluralize("{types} {? }") + + return(type) +} + +#' +.calc_link_direction.xmap_df <- function(xmap_df){ + x_attrs <- attributes(xmap_df) + direction <- paste0("(", x_attrs$col_from, " -> ", x_attrs$col_to, ") ", + "BY " ,x_attrs$col_weights) + return(direction) +} + +#' Print an `xmap` object +#' +#' @name print.xmap +NULL + +#' @describeIn print.xmap Print an `xmap_df` +#' +#' @export +print.xmap_df <- function(x){ + x_direction <- .calc_link_direction.xmap_df(x) + x_type <- .calc_link_types.xmap_df(x) + x_links <- as.data.frame(x) + + ## print headers and links + cat(paste0("xmap_df:\n", x_type, "\n", x_direction, "\n")) + print(x_links) +} diff --git a/R/sysdata.rda b/R/sysdata.rda new file mode 100644 index 0000000000000000000000000000000000000000..5e4b328729b212a6d903af59811fbe77c3c28cfe GIT binary patch literal 237 zcmV +#' verify_named_matchset_names_within(ref_set = fruit_set) +NULL + +#' @describeIn verify_named_matchset Names of `x` **exactly** match `ref_set` +#' @export +verify_named_matchset_names_exact <- function(x, ref_set){ + stopifnot(is.vector(x)) + unique_names <- unique(names(x)) + stop <- !setequal(ref_set, unique_names) + if (stop) { + cli::cli_abort(msg_abort_named_matchset("names", "exact"), + class = "abort_matchset") + } + invisible(x) +} + +#' @describeIn verify_named_matchset Values of `x` **exactly** match `ref_set` +#' @export +verify_named_matchset_values_exact <- function(x, ref_set){ + stopifnot(is.vector(x)) + unique_values <- unique(unlist(unname(x))) + stop <- !setequal(ref_set, unique_values) + if (stop) { + cli::cli_abort(msg_abort_named_matchset("values", "exact"), + class = "abort_matchset") + } + invisible(x) +} + +#' @describeIn verify_named_matchset Names of `x` **contain** all of `ref_set` +#' @export +verify_named_matchset_names_contain <- function(x, ref_set){ + stopifnot(is.vector(x)) + unique_names <- unique(names(x)) + stop <- !all(ref_set %in% unique_names) + if (stop){ + cli::cli_abort(msg_abort_named_matchset("names", "contain"), + class = "abort_matchset") + } + invisible(x) +} + +#' @describeIn verify_named_matchset Values of `x` **contain** all of `ref_set` +#' @export +verify_named_matchset_values_contain <- function(x, ref_set){ + stopifnot(is.vector(x)) + unique_values <- unique(unlist(unname(x))) + stop <- !all(ref_set %in% unique_values) + if (stop){ + cli::cli_abort(msg_abort_named_matchset("values", "contain"), + class = "abort_matchset") + } + invisible(x) +} + +#' @describeIn verify_named_matchset Names of `x` are all **within** `ref_set` +#' @export +verify_named_matchset_names_within <- function(x, ref_set){ + stopifnot(is.vector(x)) + unique_x <- unique(names(x)) + stop <- !all(unique_x %in% ref_set) + if (stop){ + cli::cli_abort(msg_abort_named_matchset("names", "within"), + class = "abort_matchset") + } + invisible(x) +} + +#' @describeIn verify_named_matchset Values of `x` are all **within** `ref_set` +#' @export +verify_named_matchset_values_within <- function(x, ref_set){ + stopifnot(is.vector(x)) + unique_x <- unique(unlist(unname(x))) + stop <- !all(unique_x %in% ref_set) + if (stop){ + cli::cli_abort(msg_abort_named_matchset("values", "within"), + class = "abort_matchset") + } + invisible(x) +} + +#' @describeIn verify_named Alias of `verify_named_all_1to1()` +#' @export +verify_named_as_recode_unique <- verify_named_all_1to1 + +#' @describeIn verify_named Alias of `verify_named_all_values_unique()` +#' @export +verify_named_no_dup_values <- verify_named_all_values_unique + +#' @describeIn verify_named Alias of `verify_named_all_names_unique()` +#' @export +verify_named_no_dup_names <- verify_named_all_names_unique diff --git a/R/verify_pairs.R b/R/verify_pairs.R new file mode 100644 index 0000000..3bcf0c7 --- /dev/null +++ b/R/verify_pairs.R @@ -0,0 +1,40 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Verify crossmap properties of column pairs +#' +#' @param df a data frame-like object with at least two columns +#' @inheritParams verify_links_as_xmap +#' +#' @return `df` or error +#' @name verify_pairs +NULL + +#' @describeIn verify_pairs Verify column pairs have only one-to-one relations +#' @export +verify_pairs_all_1to1 <- function(df, from, to){ + stopifnot(is.data.frame(df)) + set_from <- unique(df[[rlang::englue("{{from}}")]]) + set_to <- unique(df[[rlang::englue("{{to}}")]]) + stopifnot(length(set_from) == length(set_to)) + invisible(df) +} + +#' @describeIn verify_pairs Verify column pairs are all unique +#' @export +verify_pairs_all_unique <- function(df, from, to){ + stopifnot(is.data.frame(df)) + pairs <- dplyr::select(df, {{ from }}, {{to}}) + stopifnot(!as.logical(anyDuplicated(pairs))) + invisible(df) +} + +#' @describeIn verify_pairs Alias of `verify_pairs_all_1to1` +#' @export +verify_pairs_as_recode_unique <- verify_pairs_all_1to1 + +# @describeIn verify_pairs Verify column pairs outgoing link degree +verify_pairs_out <- function(x, from, to, max_out, min_out){ + # TODO: FINISH THIS! + dplyr::group_by(x, {{from}}) |> + dplyr::summarise() +} diff --git a/R/vhas.R b/R/vhas.R new file mode 100644 index 0000000..8a60a83 --- /dev/null +++ b/R/vhas.R @@ -0,0 +1,89 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Boolean flags for properties of candidate and validated xmap links (internal) +#' +#' @description +#' `vhas_*()` functions check properties of xmap links and/or candidate links. +#' The functions only accepts equal length vector inputs to support multiple link formats, +#' but does not check if the inputs are from the same xmap. +#' @param v_from,v_to,v_weights equal length vectors containing the source-target node pairs +#' +#' @return TRUE or FALSE +#' +#' @name vhas +NULL + +#' @describeIn vhas Returns TRUE if xmap does not have +#' duplicate pairs of source-target nodes (irrespective of weights) +#' +vhas_no_dup_pairs <- function(v_from, v_to) { + stopifnot(is.vector(v_from)) + stopifnot(is.vector(v_to)) + stopifnot(identical(length(v_from), length(v_to))) + links <- data.frame(v_from, v_to) + dup_idx <- anyDuplicated(links) + !as.logical(dup_idx) +} + +#' @describeIn vhas Returns TRUE if all weights for a given `from` label +#' sum to one (approximately) +#' @param tol numeric \eqn{\ge 0}. Ignore differences smaller than `tol`. +#' Passed through to the `tolerance` arg of `base::all.equal()`. +vhas_complete_weights <- function(v_from, v_weights, tol = .Machine$double.eps^0.5) { + stopifnot(is.vector(v_from)) + stopifnot(is.vector(v_weights)) + stopifnot(identical(length(v_from), length(v_weights))) + sum_w <- tapply( + X = v_weights, + INDEX = v_from, + FUN = sum, + simplify = TRUE + ) |> as.vector() + names(sum_w) <- NULL + ones <- rep(1, length(sum_w)) + all(isTRUE(all.equal(sum_w, ones, tolerance = tol))) +} + +.calc_vector_lens <- function(...){ + v_list <- list(...) + v_lens <- sapply(v_list, length) + return(v_lens) +} + +#' @describeIn vhas Returns TRUE if links have no duplicate pairs and complete weights +vhas_xmap_props <- function(v_from, v_to, v_weights){ + ## check vectors are equal length + v_lengths <- .calc_vector_lens(v_from, v_to, v_weights) + stopifnot(length(unique(v_lengths)) == 1) + + ## check properties + v_props <- c( + pairs = vhas_no_dup_pairs(v_from, v_to), + weights = vhas_complete_weights(v_from, v_weights) + ) + all(v_props) +} + +#' @describeIn vhas Return TRUE if xmap recodes labels between `from` and `to` +vhas_1to1 <- function(v_weights) { + stopifnot(is.vector(v_weights)) + any(v_weights == 1) +} +#' +vhas_recode <- vhas_1to1 + +#' @describeIn vhas Return TRUE if xmap has splitting links between `from` and `to` +vhas_1toM <- function(v_weights) { + stopifnot(is.vector(v_weights)) + any(v_weights < 1) +} +#' +vhas_split <- vhas_1toM + +#' @describeIn vhas Return TRUE if xmap has collapsing links between `from` and `to` +vhas_1fromM <- function(v_to){ + stopifnot(is.vector(v_to)) + as.logical(anyDuplicated(v_to)) +} +#' +vhas_collapse <- vhas_1fromM diff --git a/R/xmap_df.R b/R/xmap_df.R new file mode 100644 index 0000000..b64d64a --- /dev/null +++ b/R/xmap_df.R @@ -0,0 +1,31 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Low Level Constructors for xmap subclasses +#' @param x data-frame object containing candidate links. +#' @param col_from,col_to,col_weights character strings naming columns containing source nodes, target nodes and numeric weights. +#' @return xmap_df object. Note that this function unclasses tibbles. +#' @name new_xmap +NULL + +#' @describeIn new_xmap Construct xmap_df from data.frame +new_xmap_df <- function(x, col_from, col_to, col_weights, from_set = NULL) { + #' checks argument types + stopifnot(is.data.frame(x)) + stopifnot(length(col_from) == 1 && is.character(col_from)) + stopifnot(length(col_to) == 1 && is.character(col_to)) + stopifnot(length(col_weights) == 1 && is.character(col_weights)) + + #' naively generates `from_set` if it is missing + from_set <- from_set %||% as.character(unique(x[[col_from]])) + stopifnot(is.vector(from_set, mode = "character")) + + #' @return `x` with additional subclasses `xmap_df` and `xmap` + + class(x) <- .calc_xmap_subclass_attr("xmap_df") + structure(x, + col_from = col_from, + col_to = col_to, + col_weights = col_weights, + from_set = from_set + ) +} diff --git a/R/xmap_drop_extra.R b/R/xmap_drop_extra.R new file mode 100644 index 0000000..8b7f626 --- /dev/null +++ b/R/xmap_drop_extra.R @@ -0,0 +1,20 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Drop extra columns from `xmap` objects +#' +#' @param .xmap an xmap object +#' +#' @export +xmap_drop_extra <- function(.xmap){ + stopifnot(is_xmap_df(.xmap)) + # get col names + col_strings <- simplify2array(.get_col_attrs.xmap_df(.xmap)) + # construct new xmap with only necessary columns + z_attrs <- attributes(.xmap) + z_attrs$names <- unname(col_strings) + z <- as.data.frame(.xmap) + z <- z[,col_strings] + attributes(z) <- z_attrs + + return(z) +} diff --git a/R/xmap_reverse.R b/R/xmap_reverse.R new file mode 100644 index 0000000..e68fec5 --- /dev/null +++ b/R/xmap_reverse.R @@ -0,0 +1,42 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Reverse xmap direction +#' +#' @param .xmap xmap object to be reversed +#' @param weights_into A string specifying the name of a new or existing column to store reverse weights in. +#' +#' @return xmap object of same class as `x`, or throws an error if `x` is not reversible +#' @export +xmap_reverse <- function(.xmap, weights_into){ + UseMethod("xmap_reverse") +} + +#' @describeIn xmap_reverse Reverse a `xmap_df` +#' +#' @export +xmap_reverse.xmap_df <- function(.xmap, weights_into = "r_weights"){ + stopifnot(inherits(.xmap, "xmap_df")) + x_attrs <- attributes(.xmap) + df <- as.data.frame(.xmap) + + ## check xmap can be reversed + abort_not_reversible(df, x_attrs$col_to) + + ## make new xmap + df[[weights_into]] <- 1 + new_from <- x_attrs$col_to + new_to <- x_attrs$col_from + new_weights <- weights_into + new_cols <- c(new_from, new_to, new_weights) + ## rearrange columns + #col_order <- c(new_cols, setdiff(names(df), new_cols)) + df <- df[new_cols] + + ## construction + xmap <- new_xmap_df(df, new_from, new_to, new_weights) + + ## validation + validate_xmap_df(xmap) + + return(xmap) +} diff --git a/R/xmap_to.R b/R/xmap_to.R new file mode 100644 index 0000000..0b48eaa --- /dev/null +++ b/R/xmap_to.R @@ -0,0 +1,127 @@ +# Generated from create-xmap.Rmd: do not edit by hand + +#' Extract incidence matrix from xmap objects +#' +#' Transforms `xmap` objects into incidence matrix where the rows are indexed by the `from` values +#' and the columns are indexed by `to` values. Drops any additional variables. +#' +#' @param .xmap an xmap object +#' @param sparse logical specifying if the result should be a sparse matrix. Defaults to TRUE. +#' @param ... Reversed for passing arguments to `stats::xtabs` +#' +#' @return A matrix or sparse matrix object +#' @family {xmap coercion} +#' +#' @export +xmap_to_matrix <- function(.xmap, sparse, ...) { + UseMethod("xmap_to_matrix") +} + +#' @describeIn xmap_to_matrix Coerce a `xmap_df` to a Matrix +#' +#' @export +#' @examples +#' abc_xmap <- data.frame( +#' stringsAsFactors = FALSE, +#' origin = c("a","b","c","d","e", +#' "f","g","h","i","i","j","j","j"), +#' dest = c("AA","AA","AA","AA", +#' "BB","BB","CC","DD","EE","FF","GG","HH","II"), +#' link = c(1, 1, 1, 1, 1, 1, 1, 1, 0.5, 0.5, 0.3, 0.3, 0.4) +#' ) |> +#' as_xmap_df(origin, dest, link) +#' xmap_to_matrix(abc_xmap) +xmap_to_matrix.xmap_df <- function(.xmap, sparse = TRUE, ...){ + x_attrs <- attributes(.xmap) + df <- .xmap |> as.data.frame(stringsAsFactors = TRUE) + fm <- paste(x_attrs$col_weights, "~", x_attrs$col_from, "+", x_attrs$col_to, + collapse = "") + + if(sparse){ + x_mtx <- stats::xtabs(stats::as.formula(fm), df, sparse = TRUE) + } else { + x_mtx <- stats::xtabs(stats::as.formula(fm), df, sparse = FALSE) + attr(x_mtx, "call") <- NULL + unclass(x_mtx) + } + return(x_mtx) +} + +#' Coerce a unit weight `xmap_df` to a named vector or list +#' +#' Checks that an `xmap` has unit weights, and converts the +#' `from` values into: +#' * a vector for `xmap_to_named_vector()` +#' * a nested list for `xmap_to_named_list()` +#' +#' Names are the unique target nodes in `to`, +#' and each element contains the source node(s) in `from`. +#' +#' @param .xmap xmap with only unit weights +#' +#' @return Named vector or list. +#' @export +#' @rdname xmap_to_named +#' @family {xmap coercion} +#' +#' @examples +#' iso_vector <- c(AF = "004", AL = "008", DZ = "012", AS = "016", AD = "020") +#' iso_xmap <- iso_vector |> +#' as_pairs_from_named(names_to = "iso2c", values_to = "iso3n") |> +#' add_weights_unit() |> +#' as_xmap_df(from = iso3n, to = iso2c, weights) +#' identical(iso_vector, xmap_to_named_vector(iso_xmap)) +xmap_to_named_vector <- function(.xmap){ + stopifnot(is_xmap(.xmap)) + x_attrs <- attributes(.xmap) + df <- as.data.frame(.xmap) + # check only unit weights + w <- df[[x_attrs$col_weights]] + stop <- !all(w == 1) + if (stop) { + cli::cli_abort(msg_abort_frac_weights("Cannot convert to named vector"), + class = "abort_frac_weights") + } + + # convert + df |> + subset(select = c(x_attrs$col_to, x_attrs$col_from)) |> + tibble::deframe() |> + sapply(as.matrix) |> + sapply(as.vector) +} + +#' @rdname xmap_to_named +#' @export +#' +#' @examples +#' animal_list <- list(MAMM = c("elephant", "whale", "monkey"), +#' REPT = c("lizard", "turtle"), +#' CRUS = c("crab")) +#' animal_xmap <- animal_list |> +#' as_pairs_from_named(names_to = "class", values_to = "animals") |> +#' add_weights_unit() |> +#' as_xmap_df(from = animals, to = class, weights = weights) +#' identical(xmap_to_named_list(animal_xmap), animal_list) +xmap_to_named_list <- function(.xmap) { + stopifnot(is_xmap(.xmap)) + x_attrs <- attributes(.xmap) + df <- as.data.frame(.xmap) + # check only unit weights + w <- df[[x_attrs$col_weights]] + + stop <- !all(w == 1) + if (stop) { + cli::cli_abort(msg_abort_frac_weights("Cannot convert to named list"), + class = "abort_frac_weights") + } + + # convert + df |> + subset(select = c(x_attrs$col_to, x_attrs$col_from)) |> + tidyr::nest(source = c(x_attrs$col_from)) |> + tibble::deframe() |> + sapply(as.matrix) |> + sapply(as.vector) |> + as.list() +} diff --git a/README.Rmd b/README.Rmd new file mode 100644 index 0000000..4390df6 --- /dev/null +++ b/README.Rmd @@ -0,0 +1,45 @@ +--- +output: github_document +--- + + + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +## Overview + +There are lots of ways in R to modify categorical variables and to redistribution numeric values between categories. If you are performing a simple recoding of category labels, or collapsing multiple categories, you might use the [`forcats`](https://github.com/tidyverse/forcats) package. However, if you are doing more complex transformations you might find yourself writing custom functions or scripts including mutating joins, grouped summary operations, or case-wise transformation of numeric values. Verifying these data wrangling scripts and pipelines becomes more difficult as the number of categories and the complexity of the mappings increases. Current solutions to this issue mostly involve ad hoc data validation of the data before and after they are transformed ([`assertr`](https://github.com/ropensci/assertr), [`validate`](https://data-cleaning.github.io/validate/), [`pointblank`](https://rich-iannone.github.io/pointblank/)). + +The `xmap` package offers an alternative approach to ensuring your code performs the intended transformations. Instead of inspecting the data, the package provides tools for validating the mapping objects which are used to transform the data. Examples of mapping objects and available verification functions include: + +- **Named vectors or lists** + - Commonly used as reference inputs for recoding or collapsing categories. + - Use `verify_named()` for checking properties such as uniqueness or 1-to-1 relations, + - and `verify_named_matchset()` for checking the set of names or values matches expectations. +- **Lookup tables**: + - Also known as crosswalks and concordance tables + - Use `verify_pairs()` for checking uniqueness and 1-to-1 relations. +- **Crossmaps**: + - a new graph-based extension of Crosswalk tables that also store redistribution weights for ambiguous 1-to-many relations. + - Use `verify_links_as_xmap()` to check aggregation or disaggregation weights and other desirable properties. + +See `vignette("xmap")` to get started using verification functions in your existing workflows. The functions are based on results obtained by representing and analysing recoding or redistribution transformations as directed, weighted bipartite graphs (i.e. "Crossmaps"). For more information about this underlying graph structure, and the experimental `xmap_df` class, see `vignette("making-xmaps")` and `vignette("vis-xmaps")`. + +## Installation + +To install the latest release of `xmap`: + +``` r +remotes::install_github("cynthiahqy/xmap") +``` + +To install the latest development version of `xmap`: + +``` r +remotes::install_github("cynthiahqy/conformr-xmap-project", subdir = "xmap") +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..15f31f3 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ + + + +## Overview + +There are lots of ways in R to modify categorical variables and to +redistribution numeric values between categories. If you are performing +a simple recoding of category labels, or collapsing multiple categories, +you might use the [`forcats`](https://github.com/tidyverse/forcats) +package. However, if you are doing more complex transformations you +might find yourself writing custom functions or scripts including +mutating joins, grouped summary operations, or case-wise transformation +of numeric values. Verifying these data wrangling scripts and pipelines +becomes more difficult as the number of categories and the complexity of +the mappings increases. Current solutions to this issue mostly involve +ad hoc data validation of the data before and after they are transformed +([`assertr`](https://github.com/ropensci/assertr), +[`validate`](https://data-cleaning.github.io/validate/), +[`pointblank`](https://rich-iannone.github.io/pointblank/)). + +The `xmap` package offers an alternative approach to ensuring your code +performs the intended transformations. Instead of inspecting the data, +the package provides tools for validating the mapping objects which are +used to transform the data. Examples of mapping objects and available +verification functions include: + +- **Named vectors or lists** + - Commonly used as reference inputs for recoding or collapsing + categories. + - Use `verify_named()` for checking properties such as uniqueness or + 1-to-1 relations, + - and `verify_named_matchset()` for checking the set of names or + values matches expectations. +- **Lookup tables**: + - Also known as crosswalks and concordance tables + - Use `verify_pairs()` for checking uniqueness and 1-to-1 relations. +- **Crossmaps**: + - a new graph-based extension of Crosswalk tables that also store + redistribution weights for ambiguous 1-to-many relations. + - Use `verify_links_as_xmap()` to check aggregation or disaggregation + weights and other desirable properties. + +See `vignette("xmap")` to get started using verification functions in +your existing workflows. The functions are based on results obtained by +representing and analysing recoding or redistribution transformations as +directed, weighted bipartite graphs (i.e. “Crossmaps”). For more +information about this underlying graph structure, and the experimental +`xmap_df` class, see `vignette("making-xmaps")` and +`vignette("vis-xmaps")`. + +## Installation + +To install the latest release of `xmap`: + +``` r +remotes::install_github("cynthiahqy/xmap") +``` + +To install the latest development version of `xmap`: + +``` r +remotes::install_github("cynthiahqy/conformr-xmap-project", subdir = "xmap") +``` diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..60d1e44 --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,61 @@ +destination: ./docs/ + +url: ~ +template: + bootstrap: 5 + +repo: + url: + home: https://github.com/cynthiahqy/xmap/ + source: https://github.com/cynthiahqy/xmap/ + issue: https://github.com/cynthiahqy/conformr-xmap-project/issues + user: https://github.com/cynthiahqy + +authors: + Cynthia Huang: + href: https://www.cynthiahqy.com + Laura Puzzello: + href: https://sites.google.com/site/laurapuzzello + +navbar: + structure: + left: [intro, reference, articles] + right: [github] + components: + github: + icon: fa-github + href: https://github.com/cynthiahqy/xmap/ + +reference: +- title: Verify existing mapping objects + contents: + - starts_with("verify_named") + - starts_with("verify_pairs") + - starts_with("verify_links") + +- title: Creating `xmap` objects + contents: + - starts_with("pairs") + - starts_with("links") + - starts_with("add_weights") + - starts_with("as") + - as_xmap_df + - mock + +- title: Using and Modifying `xmap` objects + contents: + - starts_with("xmap_to") + - starts_with("xmap") + - is_xmap + - print.xmap + +- title: internal + contents: + - starts_with("vhas") + - starts_with("abort") + - starts_with("msg") + - starts_with("validate") + - starts_with(".get") + - starts_with(".calc") + - new_xmap_df + - validate_xmap_df diff --git a/data-raw/mock.R b/data-raw/mock.R new file mode 100644 index 0000000..f0b9976 --- /dev/null +++ b/data-raw/mock.R @@ -0,0 +1,21 @@ +## code to prepare `mock` dataset goes here + +usethis::use_data(mock, overwrite = TRUE) + +mock <- list() + +mock$named_ctr_iso3c <- countrycode::codelist |> + dplyr::select(iso3c, iso.name.en) |> + tidyr::drop_na() |> + tibble::deframe() + +# mock_named$collapse_list <- list(MAMM = c("elephant", "whale", "monkey"), +# REPT = c("lizard", "turtle"), +# CRUS = c("crab")) + +mock$df_anzsco21 <- strayr::anzsco2021 |> + dplyr::select(tidyselect::starts_with(c("anzsco_major", "anzsco_submajor"))) |> + dplyr::distinct() |> + dplyr::select(tidyselect::ends_with("_code"), tidyselect::everything()) + +usethis::use_data(mock) diff --git a/data/mock.rda b/data/mock.rda new file mode 100644 index 0000000000000000000000000000000000000000..23f8ce3ae63189ca2197370a5d5f24330172b14c GIT binary patch literal 3739 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 + as_pairs_from_named("class", "animal") +animal_pairs |> + add_weights_equal(from = class, to = animal) +} +\seealso{ +Other {Helpers for adding weights to pairs}: +\code{\link{add_weights_unit}()} +} +\concept{{Helpers for adding weights to pairs}} diff --git a/man/add_weights_unit.Rd b/man/add_weights_unit.Rd new file mode 100644 index 0000000..f15a964 --- /dev/null +++ b/man/add_weights_unit.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_weights_unit.R +\name{add_weights_unit} +\alias{add_weights_unit} +\title{Add unit weights to node pairs table} +\usage{ +add_weights_unit(df, weights_into = "weights") +} +\arguments{ +\item{df}{a data frame-like object with at least two columns} + +\item{weights_into}{character string naming new column to store link weights in} +} +\value{ +\code{pairs} with additional column of ones +} +\description{ +Attaches column of unit weights to pairs of source-target nodes. +The resultant weighted links can be verified or coerced into \code{xmap}. +} +\examples{ +AUS_pairs <- list(AUS = c("NSW", "QLD", "SA", "TAS", "VIC", "WA", "ACT", "NT")) |> + as_pairs_from_named(names_to = "ctr", values_to = "state") +AUS_pairs |> + add_weights_unit(weights_into = "weights") +} +\seealso{ +Other {Helpers for adding weights to pairs}: +\code{\link{add_weights_equal}()} +} +\concept{{Helpers for adding weights to pairs}} diff --git a/man/as_pairs.Rd b/man/as_pairs.Rd new file mode 100644 index 0000000..9822ef4 --- /dev/null +++ b/man/as_pairs.Rd @@ -0,0 +1,65 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/pairs_named.R +\name{as_pairs} +\alias{as_pairs} +\alias{as_pairs_from_named} +\alias{pairs_to_named_vector} +\alias{pairs_to_named_list} +\title{Convert between column pairs and named vector or lists} +\usage{ +as_pairs_from_named(x, names_to = "name", values_to = "value") + +pairs_to_named_vector(df, names_from = name, values_from = value) + +pairs_to_named_list(df, names_from = name, values_from = value) +} +\arguments{ +\item{x}{a Named vector or list. Lists values are flattened via \code{unlist()}.} + +\item{names_to, values_to}{character vector specify the new columns to pass the information in \code{x} into.} + +\item{df}{a data frame-like object with at least two columns} + +\item{names_from, values_from}{two columns in \code{x} to convert to names and values} +} +\value{ +\itemize{ +\item For \code{as_pairs_from_named()}: a two-column tibble +\item For \code{pairs_to_named} fncs: named vector or list +} +} +\description{ +Convert named vectors or nested lists into a two-column table of node pairs and vice versa. +\code{as_pairs_from_named} extracts the vector or list element names and the values, unnesting where necessary. +\code{pairs_to_named_vector} extracts name-value pairs from column pairs as is, +whilst \code{pairs_to_named_list()} nests the values first. +} +\section{Functions}{ +\itemize{ +\item \code{as_pairs_from_named()}: Convert named vector or nested list into column pairs + +\item \code{pairs_to_named_vector()}: Convert column pairs to named vector + +\item \code{pairs_to_named_list()}: Convert column pairs to nested named list + +}} +\examples{ +# Coerce named vectors and list to column pairs + +veg_vec <- c(eggplant = "aubergine", zucchini = "courgette") +as_pairs_from_named(veg_vec, "au_eng", "uk_eng") + +animal_list <- list(MAMM = c("elephant", "whale", "monkey"), + REPT = c("lizard", "turtle"), + CRUS = c("crab")) +as_pairs_from_named(animal_list, "class", "animal") + +# Convert pairs back to named vector and lists +veg_from_pairs <- as_pairs_from_named(veg_vec) |> + pairs_to_named_vector(names_from = name, values_from = value) +identical(veg_vec, veg_from_pairs) + +animal_from_pairs <- as_pairs_from_named(animal_list, "class", "animal") |> + pairs_to_named_list(names_from = class, values_from = animal) +identical(animal_list, animal_from_pairs) +} diff --git a/man/as_xmap.Rd b/man/as_xmap.Rd new file mode 100644 index 0000000..def2f0b --- /dev/null +++ b/man/as_xmap.Rd @@ -0,0 +1,77 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/as_xmap.R, R/is_xmap.R +\name{as_xmap} +\alias{as_xmap} +\alias{as_xmap_df} +\alias{as_xmap_df.data.frame} +\alias{is_xmap} +\alias{is_xmap_df} +\title{Coerce objects to xmap_df} +\usage{ +as_xmap_df( + x, + from, + to, + weights, + tol = .Machine$double.eps^0.5, + subclass = c("xmap_df"), + ... +) + +\method{as_xmap_df}{data.frame}( + x, + from, + to, + weights, + tol = .Machine$double.eps^0.5, + subclass = "xmap_df", + .drop_extra = TRUE +) + +is_xmap(x) + +is_xmap_df(x) +} +\arguments{ +\item{x}{\itemize{ +\item For \code{as_xmap_df()}: An object to coerce +\item For \code{is_xmap_df()}: An object to test. +}} + +\item{from, to}{Columns in \code{x} specifying the source and target nodes} + +\item{weights}{Column in \code{x} specifying the weight applied to data passed along the directed link between source and target node} + +\item{tol}{numeric \eqn{\ge 0}. Ignore differences smaller than \code{tol}. +Passed through to the \code{tolerance} arg of \code{base::all.equal()}.} + +\item{subclass}{Which xmap subclass to return. Defaults to \code{xmap_df} for \code{data.frame} and \code{tibble}} + +\item{.drop_extra}{Drop columns other than \code{from}, \code{to} and \code{weights}. Defaults to \code{TRUE}} +} +\value{ +A validated \code{xmap} object. +} +\description{ +Validates and creates a valid crossmap \code{xmap_df} object. + +This function returns \code{TRUE} for crossmaps \code{xmap} or subclasses thereof (\code{xmap_df}), and \code{FALSE} for all other objects, including regular data.frames or tibbles. +} +\section{Functions}{ +\itemize{ +\item \code{as_xmap_df(data.frame)}: Coerce a \code{data.frame} to \code{xmap} + +}} +\examples{ +# For a well formed crossmap: +links <- data.frame( + a = "AUS", + b = c("VIC", "NSW", "WA", "OTHER"), + w = c(0.1, 0.15, 0.25, 0.5) +) +as_xmap_df(links, from = a, to = b, weights = w) + +# extra columns are dropped, +links$extra <- c(2, 4, 5, 6) +as_xmap_df(links, from = a, to = b, weights = w) +} diff --git a/man/mock.Rd b/man/mock.Rd new file mode 100644 index 0000000..9d590e7 --- /dev/null +++ b/man/mock.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{mock} +\alias{mock} +\title{Mock input objects for the \code{xmap} package} +\format{ +\subsection{\code{mock}}{ + +A list with: +\describe{ +\item{named_ctr_iso3c}{named vector. Names are ISO-3 country codes, values are ISO English country names. Retrieved from \code{countrycode} package: +\url{https://github.com/vincentarelbundock/countrycode}} +\item{df_anzsco21}{4-column tibble. Contains major and submajor occupation codes and descriptions for ANZSCO21. Retrieved from \code{strayr} package: +\url{https://github.com/runapp-aus/strayr}} +\item{df_mixed}{3-column data.frame. } +} +} +} +\usage{ +mock +} +\description{ +A collection of mock inputs for experimenting with functions +in the \code{xmap} package. +\code{named_} objects are either named vectors or nested lists. +\code{df_} objects may contain source-target \emph{pairs} (no weights), +or weighted source-target \emph{links}. +} +\keyword{datasets} diff --git a/man/new_xmap.Rd b/man/new_xmap.Rd new file mode 100644 index 0000000..add7bca --- /dev/null +++ b/man/new_xmap.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/xmap_df.R +\name{new_xmap} +\alias{new_xmap} +\alias{new_xmap_df} +\title{Low Level Constructors for xmap subclasses} +\usage{ +new_xmap_df(x, col_from, col_to, col_weights, from_set = NULL) +} +\arguments{ +\item{x}{data-frame object containing candidate links.} + +\item{col_from, col_to, col_weights}{character strings naming columns containing source nodes, target nodes and numeric weights.} +} +\value{ +xmap_df object. Note that this function unclasses tibbles. + +\code{x} with additional subclasses \code{xmap_df} and \code{xmap} +} +\description{ +Low Level Constructors for xmap subclasses +} +\section{Functions}{ +\itemize{ +\item \code{new_xmap_df()}: Construct xmap_df from data.frame +checks argument types +naively generates \code{from_set} if it is missing + +}} diff --git a/man/op-null-default.Rd b/man/op-null-default.Rd new file mode 100644 index 0000000..a13048e --- /dev/null +++ b/man/op-null-default.Rd @@ -0,0 +1,13 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{op-null-default} +\alias{op-null-default} +\alias{\%||\%} +\title{Defaults for NULL values} +\usage{ +x \%||\% y +} +\description{ +Defaults for NULL values +} +\keyword{internal} diff --git a/man/print.xmap.Rd b/man/print.xmap.Rd new file mode 100644 index 0000000..064f33d --- /dev/null +++ b/man/print.xmap.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/print.R +\name{print.xmap} +\alias{print.xmap} +\alias{print.xmap_df} +\title{Print an \code{xmap} object} +\usage{ +\method{print}{xmap_df}(x) +} +\description{ +Print an \code{xmap} object +} +\section{Functions}{ +\itemize{ +\item \code{print(xmap_df)}: Print an \code{xmap_df} + +}} diff --git a/man/validate_xmap_df.Rd b/man/validate_xmap_df.Rd new file mode 100644 index 0000000..5e553cd --- /dev/null +++ b/man/validate_xmap_df.Rd @@ -0,0 +1,13 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/validate_xmap_df.R +\name{validate_xmap_df} +\alias{validate_xmap_df} +\title{Validator for \code{xmap_df} class (INTERNAL)} +\usage{ +validate_xmap_df(x) +} +\description{ +Only checks class attributes, not crossmap graph properties. +Use \code{verify_links_as_xmap()} or \code{as_xmap()} to verify graph +properties +} diff --git a/man/verify_links_as_xmap.Rd b/man/verify_links_as_xmap.Rd new file mode 100644 index 0000000..4222c80 --- /dev/null +++ b/man/verify_links_as_xmap.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/verify_links_as_xmap.R +\name{verify_links_as_xmap} +\alias{verify_links_as_xmap} +\title{Check if candidate links meet crossmap properties} +\usage{ +verify_links_as_xmap(df, from, to, weights, tol = .Machine$double.eps^0.5) +} +\arguments{ +\item{df}{data.frame like object containing candidate links} + +\item{from, to}{Columns in \code{x} specifying the source and target nodes} + +\item{weights}{Column in \code{x} specifying the weight applied to data passed along the directed link between source and target node} + +\item{tol}{numeric \eqn{\ge 0}. Ignore differences smaller than \code{tol}. +Passed through to the \code{tolerance} arg of \code{base::all.equal()}.} +} +\description{ +Check if candidate links meet crossmap properties +} +\examples{ +# For a well formed crossmap: +links <- data.frame( + a = "AUS", + b = c("VIC", "NSW", "WA", "OTHER"), + w = c(0.1, 0.15, 0.25, 0.5) +) +verify_links_as_xmap(links, from = a, to = b, weights = w) +} diff --git a/man/verify_named.Rd b/man/verify_named.Rd new file mode 100644 index 0000000..3a3e5a1 --- /dev/null +++ b/man/verify_named.Rd @@ -0,0 +1,66 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/verify_named.R +\name{verify_named} +\alias{verify_named} +\alias{verify_named_all_1to1} +\alias{verify_named_all_unique} +\alias{verify_named_all_names_unique} +\alias{verify_named_all_values_unique} +\alias{verify_named_as_recode_unique} +\alias{verify_named_no_dup_values} +\alias{verify_named_no_dup_names} +\title{Verify crossmap properties of named vectors or lists} +\usage{ +verify_named_all_1to1(x) + +verify_named_all_unique(x) + +verify_named_all_names_unique(x) + +verify_named_all_values_unique(x) + +verify_named_as_recode_unique(x) + +verify_named_no_dup_values(x) + +verify_named_no_dup_names(x) +} +\arguments{ +\item{x}{a Named vector or list. Lists values are flattened via \code{unlist()}.} +} +\value{ +\code{x} or throws an error +} +\description{ +Verify crossmap properties of named vectors or lists +} +\section{Functions}{ +\itemize{ +\item \code{verify_named_all_1to1()}: Verify named vector or list has only one-to-one relations + +\item \code{verify_named_all_unique()}: Verify name-value pairs of named vector or list are not duplicated + +\item \code{verify_named_all_names_unique()}: Verify names of named vector or list are not duplicated + +\item \code{verify_named_all_values_unique()}: Verify values in named vector or list are not duplicated (after unnesting) + +\item \code{verify_named_as_recode_unique()}: Alias of \code{verify_named_all_1to1()} + +\item \code{verify_named_no_dup_values()}: Alias of \code{verify_named_all_values_unique()} + +\item \code{verify_named_no_dup_names()}: Alias of \code{verify_named_all_names_unique()} + +}} +\examples{ +## check each fruit has a unique color +fruit_color <- c(apple = "green", strawberry = "red", banana = "yellow") +verify_named_all_1to1(fruit_color) + +## check no student is assigned to multiple groups +student_groups <- list(GRP1 = c("kate", "jane", "peter"), + GRP2 = c("terry", "ben", "grace"), + GRP3 = c("cindy", "lucy", "alex" )) +verify_named_no_dup_names(student_groups) + +## check +} diff --git a/man/verify_named_matchset.Rd b/man/verify_named_matchset.Rd new file mode 100644 index 0000000..4fad30f --- /dev/null +++ b/man/verify_named_matchset.Rd @@ -0,0 +1,56 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/verify_named.R +\name{verify_named_matchset} +\alias{verify_named_matchset} +\alias{verify_named_matchset_names_exact} +\alias{verify_named_matchset_values_exact} +\alias{verify_named_matchset_names_contain} +\alias{verify_named_matchset_values_contain} +\alias{verify_named_matchset_names_within} +\alias{verify_named_matchset_values_within} +\title{Verify unique names or values of named vector or list match expected set} +\usage{ +verify_named_matchset_names_exact(x, ref_set) + +verify_named_matchset_values_exact(x, ref_set) + +verify_named_matchset_names_contain(x, ref_set) + +verify_named_matchset_values_contain(x, ref_set) + +verify_named_matchset_names_within(x, ref_set) + +verify_named_matchset_values_within(x, ref_set) +} +\arguments{ +\item{x}{a Named vector or list. Lists values are flattened via \code{unlist()}.} + +\item{ref_set}{a vector of character strings} +} +\value{ +\code{x} or throw an error +} +\description{ +Verify unique names or values of named vector or list match expected set +} +\section{Functions}{ +\itemize{ +\item \code{verify_named_matchset_names_exact()}: Names of \code{x} \strong{exactly} match \code{ref_set} + +\item \code{verify_named_matchset_values_exact()}: Values of \code{x} \strong{exactly} match \code{ref_set} + +\item \code{verify_named_matchset_names_contain()}: Names of \code{x} \strong{contain} all of \code{ref_set} + +\item \code{verify_named_matchset_values_contain()}: Values of \code{x} \strong{contain} all of \code{ref_set} + +\item \code{verify_named_matchset_names_within()}: Names of \code{x} are all \strong{within} \code{ref_set} + +\item \code{verify_named_matchset_values_within()}: Values of \code{x} are all \strong{within} \code{ref_set} + +}} +\examples{ +fruit_color <- c(apple = "green", strawberry = "red", banana = "yellow") +fruit_set <- c("apple", "strawberry", "banana", "pear") +fruit_color |> + verify_named_matchset_names_within(ref_set = fruit_set) +} diff --git a/man/verify_pairs.Rd b/man/verify_pairs.Rd new file mode 100644 index 0000000..b79c47e --- /dev/null +++ b/man/verify_pairs.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/verify_pairs.R +\name{verify_pairs} +\alias{verify_pairs} +\alias{verify_pairs_all_1to1} +\alias{verify_pairs_all_unique} +\alias{verify_pairs_as_recode_unique} +\title{Verify crossmap properties of column pairs} +\usage{ +verify_pairs_all_1to1(df, from, to) + +verify_pairs_all_unique(df, from, to) + +verify_pairs_as_recode_unique(df, from, to) +} +\arguments{ +\item{df}{a data frame-like object with at least two columns} + +\item{from, to}{Columns in \code{x} specifying the source and target nodes} +} +\value{ +\code{df} or error +} +\description{ +Verify crossmap properties of column pairs +} +\section{Functions}{ +\itemize{ +\item \code{verify_pairs_all_1to1()}: Verify column pairs have only one-to-one relations + +\item \code{verify_pairs_all_unique()}: Verify column pairs are all unique + +\item \code{verify_pairs_as_recode_unique()}: Alias of \code{verify_pairs_all_1to1} + +}} diff --git a/man/vhas.Rd b/man/vhas.Rd new file mode 100644 index 0000000..8095ae0 --- /dev/null +++ b/man/vhas.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vhas.R +\name{vhas} +\alias{vhas} +\alias{vhas_no_dup_pairs} +\alias{vhas_complete_weights} +\alias{vhas_xmap_props} +\alias{vhas_1to1} +\alias{vhas_1toM} +\alias{vhas_1fromM} +\title{Boolean flags for properties of candidate and validated xmap links (internal)} +\usage{ +vhas_no_dup_pairs(v_from, v_to) + +vhas_complete_weights(v_from, v_weights, tol = .Machine$double.eps^0.5) + +vhas_xmap_props(v_from, v_to, v_weights) + +vhas_1to1(v_weights) + +vhas_1toM(v_weights) + +vhas_1fromM(v_to) +} +\arguments{ +\item{v_from, v_to, v_weights}{equal length vectors containing the source-target node pairs} + +\item{tol}{numeric \eqn{\ge 0}. Ignore differences smaller than \code{tol}. +Passed through to the \code{tolerance} arg of \code{base::all.equal()}.} +} +\value{ +TRUE or FALSE +} +\description{ +\verb{vhas_*()} functions check properties of xmap links and/or candidate links. +The functions only accepts equal length vector inputs to support multiple link formats, +but does not check if the inputs are from the same xmap. +} +\section{Functions}{ +\itemize{ +\item \code{vhas_no_dup_pairs()}: Returns TRUE if xmap does not have +duplicate pairs of source-target nodes (irrespective of weights) + +\item \code{vhas_complete_weights()}: Returns TRUE if all weights for a given \code{from} label +sum to one (approximately) + +\item \code{vhas_xmap_props()}: Returns TRUE if links have no duplicate pairs and complete weights + +\item \code{vhas_1to1()}: Return TRUE if xmap recodes labels between \code{from} and \code{to} + +\item \code{vhas_1toM()}: Return TRUE if xmap has splitting links between \code{from} and \code{to} + +\item \code{vhas_1fromM()}: Return TRUE if xmap has collapsing links between \code{from} and \code{to} + +}} diff --git a/man/xmap_drop_extra.Rd b/man/xmap_drop_extra.Rd new file mode 100644 index 0000000..f357cd1 --- /dev/null +++ b/man/xmap_drop_extra.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/xmap_drop_extra.R +\name{xmap_drop_extra} +\alias{xmap_drop_extra} +\title{Drop extra columns from \code{xmap} objects} +\usage{ +xmap_drop_extra(.xmap) +} +\arguments{ +\item{.xmap}{an xmap object} +} +\description{ +Drop extra columns from \code{xmap} objects +} diff --git a/man/xmap_reverse.Rd b/man/xmap_reverse.Rd new file mode 100644 index 0000000..4d7c277 --- /dev/null +++ b/man/xmap_reverse.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/xmap_reverse.R +\name{xmap_reverse} +\alias{xmap_reverse} +\alias{xmap_reverse.xmap_df} +\title{Reverse xmap direction} +\usage{ +xmap_reverse(.xmap, weights_into) + +\method{xmap_reverse}{xmap_df}(.xmap, weights_into = "r_weights") +} +\arguments{ +\item{.xmap}{xmap object to be reversed} + +\item{weights_into}{A string specifying the name of a new or existing column to store reverse weights in.} +} +\value{ +xmap object of same class as \code{x}, or throws an error if \code{x} is not reversible +} +\description{ +Reverse xmap direction +} +\section{Methods (by class)}{ +\itemize{ +\item \code{xmap_reverse(xmap_df)}: Reverse a \code{xmap_df} + +}} diff --git a/man/xmap_to_matrix.Rd b/man/xmap_to_matrix.Rd new file mode 100644 index 0000000..dfdcb76 --- /dev/null +++ b/man/xmap_to_matrix.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/xmap_to.R +\name{xmap_to_matrix} +\alias{xmap_to_matrix} +\alias{xmap_to_matrix.xmap_df} +\title{Extract incidence matrix from xmap objects} +\usage{ +xmap_to_matrix(.xmap, sparse, ...) + +\method{xmap_to_matrix}{xmap_df}(.xmap, sparse = TRUE, ...) +} +\arguments{ +\item{.xmap}{an xmap object} + +\item{sparse}{logical specifying if the result should be a sparse matrix. Defaults to TRUE.} + +\item{...}{Reversed for passing arguments to \code{stats::xtabs}} +} +\value{ +A matrix or sparse matrix object +} +\description{ +Transforms \code{xmap} objects into incidence matrix where the rows are indexed by the \code{from} values +and the columns are indexed by \code{to} values. Drops any additional variables. +} +\section{Methods (by class)}{ +\itemize{ +\item \code{xmap_to_matrix(xmap_df)}: Coerce a \code{xmap_df} to a Matrix + +}} +\examples{ +abc_xmap <- data.frame( + stringsAsFactors = FALSE, + origin = c("a","b","c","d","e", + "f","g","h","i","i","j","j","j"), + dest = c("AA","AA","AA","AA", + "BB","BB","CC","DD","EE","FF","GG","HH","II"), + link = c(1, 1, 1, 1, 1, 1, 1, 1, 0.5, 0.5, 0.3, 0.3, 0.4) + ) |> +as_xmap_df(origin, dest, link) +xmap_to_matrix(abc_xmap) +} +\seealso{ +Other {xmap coercion}: +\code{\link{xmap_to_named_vector}()} +} +\concept{{xmap coercion}} diff --git a/man/xmap_to_named.Rd b/man/xmap_to_named.Rd new file mode 100644 index 0000000..f388f7c --- /dev/null +++ b/man/xmap_to_named.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/xmap_to.R +\name{xmap_to_named_vector} +\alias{xmap_to_named_vector} +\alias{xmap_to_named_list} +\title{Coerce a unit weight \code{xmap_df} to a named vector or list} +\usage{ +xmap_to_named_vector(.xmap) + +xmap_to_named_list(.xmap) +} +\arguments{ +\item{.xmap}{xmap with only unit weights} +} +\value{ +Named vector or list. +} +\description{ +Checks that an \code{xmap} has unit weights, and converts the +\code{from} values into: +\itemize{ +\item a vector for \code{xmap_to_named_vector()} +\item a nested list for \code{xmap_to_named_list()} +} + +Names are the unique target nodes in \code{to}, +and each element contains the source node(s) in \code{from}. +} +\examples{ +iso_vector <- c(AF = "004", AL = "008", DZ = "012", AS = "016", AD = "020") +iso_xmap <- iso_vector |> + as_pairs_from_named(names_to = "iso2c", values_to = "iso3n") |> + add_weights_unit() |> + as_xmap_df(from = iso3n, to = iso2c, weights) +identical(iso_vector, xmap_to_named_vector(iso_xmap)) +animal_list <- list(MAMM = c("elephant", "whale", "monkey"), + REPT = c("lizard", "turtle"), + CRUS = c("crab")) +animal_xmap <- animal_list |> + as_pairs_from_named(names_to = "class", values_to = "animals") |> + add_weights_unit() |> + as_xmap_df(from = animals, to = class, weights = weights) +identical(xmap_to_named_list(animal_xmap), animal_list) +} +\seealso{ +Other {xmap coercion}: +\code{\link{xmap_to_matrix}()} +} +\concept{{xmap coercion}} diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..96fd256 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,12 @@ +# This file is part of the standard setup for testthat. +# It is recommended that you do not modify it. +# +# Where should you do additional test configuration? +# Learn more about the roles of various files in: +# * https://r-pkgs.org/tests.html +# * https://testthat.r-lib.org/reference/test_package.html#special-files + +library(testthat) +library(xmap) + +test_check("xmap") diff --git a/tests/testthat/tests.R b/tests/testthat/tests.R new file mode 100644 index 0000000..3f0c374 --- /dev/null +++ b/tests/testthat/tests.R @@ -0,0 +1,422 @@ +# Generated from create-xmap.Rmd: do not edit by hand +testthat::test_that("add_weights_*() work as expected",{ + abc_pairs <- data.frame(lower = letters[1:5], upper = LETTERS[1:5]) + abc_links <- data.frame(lower = letters[1:5], upper = LETTERS[1:5], weights = 1) + testthat::expect_equal(add_weights_unit(abc_pairs), abc_links) + + animal_pairs <- list(MAMM = c("elephant", "whale", "monkey"), + REPT = c("lizard", "turtle"), + CRUS = c("crab")) |> + as_pairs_from_named("class", "animal") + animal_links <- animal_pairs |> + dplyr::group_by(class) |> + dplyr::mutate(weights = 1/dplyr::n_distinct(animal)) |> + dplyr::ungroup() + add_links <- animal_pairs |> + add_weights_equal(from = class, to = animal, weights_into = "weights") + + testthat::expect_equal(animal_links, add_links) + } + ) + +testthat::test_that( + "vhas_* xmap validation helpers work as expected on valid df", + { + df <- tibble::tribble( + ~from, ~to, ~weights, + "A1", "B01", 1, + "A2", "B02", 1, + "A3", "B02", 1, + "A4", "B03", 0.67, + "A4", "B04", 0.33 + ) + testthat::expect_true(vhas_no_dup_pairs(df$from, df$to)) + testthat::expect_true(vhas_complete_weights(df$from, df$weights)) + testthat::expect_true(vhas_xmap_props(df$from, df$to, df$weights)) + } +) + +testthat::test_that( + "vhas_* xmap validation helpers catch invalid df", + { + df <- tibble::tribble( + ~from, ~to, ~weights, + "A1", "B01", 1, + "A2", "B02", 0.3, + "A2", "B02", 0.5 + ) + testthat::expect_false(vhas_complete_weights(df$from, df$weights)) + testthat::expect_false(vhas_no_dup_pairs(df$from, df$to)) + } +) + +testthat::test_that( + "vhas_complete_weights() works on recurring fractional weights", + { + df <- data.frame(key1 = rep("A1", 3), + key2 = c("B01", "B02", "B03"), + share = rep(1/3, 3)) + + testthat::expect_true(vhas_complete_weights(df$key1, df$share)) + } +) + +testthat::test_that( + "vhas_* relation type flag functions work as expected", + { + w_1to1 <- rep(1, 10) + w_1toM <- rep(1/6, 6) + to_1fromM <- rep("country", 4) + testthat::expect_true(vhas_recode(w_1to1)) + testthat::expect_false(vhas_recode(w_1toM)) + testthat::expect_true(vhas_split(w_1toM)) + testthat::expect_false(vhas_split(w_1to1)) + testthat::expect_true(vhas_collapse(to_1fromM)) + } +) + +testthat::test_that("verify_named_all_1to1() works as expected", { + v1toM <- c(fruit = "apple", fruit = "banana") + v1to1 <- c(A = 1, B = 2, C = 3) + l1toM <- list(fruit = c("apple", "banana")) + testthat::expect_error(verify_named_all_1to1(v1toM), class = "abort_not_1to1") + testthat::expect_error(verify_named_all_1to1(l1toM), class = "abort_not_1to1") + testthat::expect_equal(verify_named_all_1to1(v1to1), v1to1) + }) + +testthat::test_that("verify_named_*_unique() work as expected", { + vdup_pairs <- c(fruit = "apple", fruit = "apple") + ldup_pairs <- list(fruit = c("apple", "apple")) + testthat::expect_error(verify_named_all_unique(vdup_pairs), class = "abort_not_unique") + testthat::expect_error(verify_named_all_unique(ldup_pairs), class = "abort_not_unique") + vdup_names <- c(fruit = "apple", fruit = "banana") + ldup_names <- list(fruit = c("apple", "banana"), fruit = "pear") + testthat::expect_error(verify_named_all_names_unique(vdup_names), class = "abort_not_unique") + testthat::expect_error(verify_named_all_names_unique(ldup_names), class = "abort_not_unique") + vdup_values <- c(fruit = "apple", veg = "apple") + ldup_values <- list(fruit = c("apple", "banana"), veg = "apple") + testthat::expect_error(verify_named_all_values_unique(vdup_values), class = "abort_not_unique") + testthat::expect_error(verify_named_all_values_unique(ldup_values), class = "abort_not_unique") +}) + +testthat::test_that("verify_named_matchset fncs work as expected", { + v_1to1 <- c(x1 = 1, x2 = 2, x3 = 3) + refn_exact_1to1 <- c("x1", "x2", "x3") + refn_subset_1to1 <- c("x1", "x2") + refn_superset_1to1 <- c("x1", "x2", "x3", "x4") + testthat::expect_equal(verify_named_matchset_names_exact(v_1to1, refn_exact_1to1), v_1to1) + testthat::expect_error(verify_named_matchset_names_exact(v_1to1, c("not", "right")), + class = "abort_matchset") + testthat::expect_equal(verify_named_matchset_names_contain(v_1to1, refn_subset_1to1), v_1to1) + testthat::expect_equal(verify_named_matchset_names_contain(v_1to1, refn_exact_1to1), v_1to1) + testthat::expect_error(verify_named_matchset_names_contain(v_1to1, refn_superset_1to1), + class = "abort_matchset") + testthat::expect_equal(verify_named_matchset_names_within(v_1to1, refn_superset_1to1), v_1to1) + testthat::expect_equal(verify_named_matchset_names_within(v_1to1, refn_exact_1to1), v_1to1) + testthat::expect_error(verify_named_matchset_names_within(v_1to1, refn_subset_1to1), + class = "abort_matchset") + refv_exact_1to1 <- c(1, 2, 3) + refv_subset_1to1 <- c(1, 2) + refv_superset_1to1 <- c(1, 2, 3, 4) + testthat::expect_equal(verify_named_matchset_values_exact(v_1to1, refv_exact_1to1), v_1to1) + testthat::expect_error(verify_named_matchset_values_exact(v_1to1, c("not", "right")), + class = "abort_matchset") + testthat::expect_equal(verify_named_matchset_values_contain(v_1to1, refv_subset_1to1), v_1to1) + testthat::expect_equal(verify_named_matchset_values_contain(v_1to1, refv_exact_1to1), v_1to1) + testthat::expect_error(verify_named_matchset_values_contain(v_1to1, refv_superset_1to1), + class = "abort_matchset") + testthat::expect_equal(verify_named_matchset_values_within(v_1to1, refv_superset_1to1), v_1to1) + testthat::expect_equal(verify_named_matchset_values_within(v_1to1, refv_exact_1to1), v_1to1) + testthat::expect_error(verify_named_matchset_values_within(v_1to1, refv_subset_1to1), + class = "abort_matchset") +}) + +testthat::test_that("verify_pairs_* work as expected", { + v_1to1 <- c(x1 = 1, x2 = 2, x3 = 3) + pairs_1to1 <- tibble::enframe(v_1to1, "f", "t") + testthat::expect_identical(verify_pairs_all_1to1(pairs_1to1, f, t), pairs_1to1) + testthat::expect_identical(verify_pairs_all_unique(pairs_1to1, f, t), pairs_1to1) +} +) + +testthat::test_that(".calc_xmap_subclass_attr() rejects unknown subclass", + { + testthat::expect_error(.calc_xmap_subclass_attr("unknown")) + }) + +testthat::test_that( + "new_xmap_df() accepts arbitrary data.frames with correct from argument", + { + df <- data.frame( + x = letters[1:5], + y = 1:5, + z = runif(5) + ) + xmap <- new_xmap_df(x = df, "x", "y", "z") + xmap_attrs <- attributes(xmap) + testthat::expect_s3_class(xmap, .calc_xmap_subclass_attr("xmap_df")) + testthat::expect_identical(xmap_attrs$col_from, "x") + testthat::expect_identical(xmap_attrs$col_to, "y") + testthat::expect_identical(xmap_attrs$col_weights, "z") + testthat::expect_identical(xmap_attrs$from_set, unique(df$x)) + } +) + +testthat::test_that("abort_col_order() works as expected", + { + df <- data.frame(a = 1, b = 2, c = 3) + testthat::expect_invisible(abort_col_order(df, "a", "b", "c")) + testthat::expect_identical(abort_col_order(df, "a", "b", "c"), df) + testthat::expect_error(abort_col_order(df, "b", "a", "c"), + class = "abort_col_order") + }) + +testthat::test_that( + "validate & verify xmap fncs accept well-formed xmaps", + { + df <- tibble::tribble( + ~node_A, ~node_B, ~w_AB, + "A1", "B01", 1, + "A2", "B02", 1, + "A3", "B02", 1, + "A4", "B03", 0.25, + "A4", "B04", 0.75 + ) + x <- new_xmap_df(df, "node_A", "node_B", "w_AB") + out <- testthat::expect_invisible(validate_xmap_df(x)) + testthat::expect_identical(out, x) + testthat::expect_identical(df, verify_links_as_xmap(df, node_A, node_B, w_AB)) + } +) + +## columns present +testthat::test_that( + "validate & verify fncs reject missing columns", + { + df <- tibble::tribble( + ~from, ~to, ~weights, + "A1", "B01", 1 + ) + x <- new_xmap_df(df, "from", "missing_col", "weights") + testthat::expect_error(abort_missing_cols(df, c("from", "missing_col", "weights")), + class = "abort_missing_cols" + ) + testthat::expect_error(validate_xmap_df(x), + class = "abort_missing_cols" + ) + testthat::expect_error(verify_links_as_xmap(df, node_A, node_B, w_AB), + class = "abort_missing_cols") + } +) + +## any NA values +testthat::test_that( + "validate & verify xmap fncs reject missing values", + { + df <- tibble::tribble( + ~from, ~to, ~weights, + "A1", "B2", NA, + NA, "B2", NA, + "A3", "B1", 1 + ) + x <- new_xmap_df(df, "from", "to", "weights") + testthat::expect_error(validate_xmap_df(x), class = "abort_na") + testthat::expect_error(verify_links_as_xmap(df, from, to, weights), + class = "abort_na") + } +) + + +## column type +testthat::test_that( + "validate & verify xmap fncs reject non-numeric weight columns", + { + df <- tibble::tribble( + ~f, ~t, ~w, + "A1", "B01", 1, + "A4", "B03", 0.25, + "A4", "B04", 0.75 + ) |> + dplyr::mutate(w = as.character(w)) + testthat::expect_error(abort_weights_col_type(df, "weights"), + class = "abort_col_type" + ) + x <- new_xmap_df(df, "f", "t", "w") + testthat::expect_error(verify_links_as_xmap(df, f, t, w), + class = "abort_col_type") + } +) + +## from set check +testthat::test_that( + "validate_xmap_df() rejects mismatching from_set", + { + df <- tibble::tribble( + ~from, ~to, ~weights, + "A1", "B01", 1, + "A4", "B03", 0.25, + "A4", "B04", 0.75 + ) + bad_set <- c("bad set", "of", "nodes") + testthat::expect_error(abort_from_set(df, "from", bad_set)) + x <- new_xmap_df(df, "from", "to", "weights", from_set = bad_set) + testthat::expect_error(validate_xmap_df(x)) + } +) + +## duplicate links +testthat::test_that( + "validate and verify xmap fncs reject duplicate from-to links", + { + df <- tibble::tribble( + ~f, ~t, ~w, + "A1", "B02", 0.3, + "A1", "B02", 1 + ) + testthat::expect_error(abort_dup_pairs(df, "f", "t"), class = "abort_dup_pairs") + x <- new_xmap_df(df, "f", "t", "w") + testthat::expect_error(verify_links_as_xmap(df, f, t, w), + class = "abort_dup_pairs") + } +) + +## complete weights +testthat::test_that( + "validate & verify xmap fncs rejects invalid weights", + { + df <- tibble::tribble( + ~f, ~t, ~w, + "A1", "B01", 0.4, + "A1", "B02", 0.59 + ) + x <- new_xmap_df(df, "f", "t", "w") + testthat::expect_error(verify_links_as_xmap(df, f, t, w), + class = "abort_bad_weights") + } +) + +testthat::test_that( + "as_xmap() is returns expected xmap subclasses", + { + tbl_links <- tibble::tribble( + ~f, ~t, ~w, + "A1", "B01", 1, + "A2", "B02", 1, + "A3", "B02", 1, + "A4", "B03", 0.67, + "A4", "B04", 0.33 + ) + df_links <- as.data.frame(tbl_links) + + ## default subclasses work as expected + testthat::expect_s3_class(as_xmap_df(df_links, f, t, w), + .calc_xmap_subclass_attr("xmap_df")) + + ## override subclass works as well + testthat::expect_s3_class(as_xmap_df(tbl_links, f, t, w, subclass = "xmap_df"), + .calc_xmap_subclass_attr("xmap_df")) + } +) + +testthat::test_that("xmap_to_matrix handles xmaps with different column counts",{ + links <- tibble::tribble( + ~f, ~t, ~w, + "A1", "B01", 1, + "A2", "B02", 1, + "A3", "B02", 1, + "A4", "B03", 0.25, + "A4", "B04", 0.75 + ) + xmap_small <- new_xmap_df(links, "f", "t", "w") + + links_extra <- links |> + dplyr::mutate(ex = "extra") + xmap_extra <- new_xmap_df(links_extra, "f", "t", "w") + + xmap_matrix_small <- xmap_small |> xmap_to_matrix() + xmap_matrix_extra <- xmap_extra |> xmap_to_matrix() + + testthat::expect_identical(xmap_matrix_small, xmap_matrix_extra) + } + ) + +testthat::test_that("xmap_to_named works as expected", { + links <- tibble::tribble( + ~f, ~t, ~w, + "A1", "B01", 1, + "A2", "B02", 1, + "A3", "B02", 1, + "A4", "B03", 0.25, + "A4", "B04", 0.75 + ) + ## works for collapse relations + xmap_unit <- new_xmap_df(links[1:3,], "f", "t", "w") + unit_list <- list(B01 = c("A1"), B02 = c("A2", "A3")) + unit_vector <- tibble::deframe(xmap_unit[,c("t", "f")]) + testthat::expect_identical(unit_list, xmap_to_named_list(xmap_unit)) + testthat::expect_identical(unit_vector, xmap_to_named_vector(xmap_unit)) + ## rejects split relations + xmap_mixed <- new_xmap_df(links, "f", "t", "w") + testthat::expect_error(xmap_to_named_list(xmap_mixed), + class = "abort_frac_weights") +}) + +testthat::test_that("xmap_to_named_list() reverses as_pairs_from_named()", { + link_list <- list(AA = c("x3", "x4", "x6"), + BB = c("x1", "x5"), + CC = c("x2") + ) + link_xmap <- + as_pairs_from_named(link_list, + "capital", "xvars") |> + add_weights_unit(weights_into = "w") |> + new_xmap_df("xvars", "capital", "w") + testthat::expect_identical(xmap_to_named_list(link_xmap), link_list) +}) + +testthat::test_that("xmap_reverse.xmap_df() works as expected", { + df_x <- tibble::tribble( + ~from, ~to, ~weights, + "A1", "B01", 1, + "A4", "B03", 0.25, + "A4", "B04", 0.75 + ) |> as.data.frame() |> + new_xmap_df("from", "to", "weights") + + df_x_rev <- data.frame( + to = df_x$to, + from = df_x$from, + r_weights = 1 + ) |> + new_xmap_df("to", "from", "r_weights") + + # class checks + testthat::expect_s3_class(xmap_reverse.xmap_df(df_x), class(df_x_rev)) + testthat::expect_s3_class(xmap_reverse(df_x), class(df_x_rev)) + + # output checks + testthat::expect_identical(xmap_reverse.xmap_df(df_x), df_x_rev) + testthat::expect_identical(abort_not_reversible(df_x,"to"), df_x) +} +) + +testthat::test_that('xmap_drop_extra works as expected', { + links <- tibble::tribble( + ~f, ~t, ~w, + "A1", "B01", 1, + "A2", "B02", 1, + "A3", "B02", 1, + "A4", "B03", 0.25, + "A4", "B04", 0.75 + ) + xmap_small <- new_xmap_df(links, "f", "t", "w") + + links_extra <- links |> + dplyr::mutate(ex = "extra") + xmap_extra <- new_xmap_df(links_extra, "f", "t", "w") + + xmap_drop <- xmap_extra |> xmap_drop_extra() + + testthat::expect_identical(xmap_small, xmap_drop) +}) + diff --git a/vignettes/making-xmaps.Rmd b/vignettes/making-xmaps.Rmd new file mode 100644 index 0000000..ce9e6b5 --- /dev/null +++ b/vignettes/making-xmaps.Rmd @@ -0,0 +1,466 @@ +--- +title: "Making and Verifying Crossmaps" +output: + rmarkdown::html_vignette: + toc: yes +vignette: > + %\VignetteIndexEntry{Making and Verifying Crossmaps} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r setup} +library(xmap) +``` + +## Crossmaps + +A (Nomenclature) Crossmap encodes a complete redistribution of values between a source and target classifications as a directed, bipartite, weighted graph. Numeric values transformed using a valid Crossmap will sum to the same total in both the source and target classifications. A valid Crossmap satisfies the following conditions: + +1. There is at most one link between each distinct source and target node +2. For each source node, the sum of weights attached to all outgoing links sums to one. + +For example, this mapping between occupation codes in ANZSCO22 and ISCO8 is a valid crossmap: + +![A crossmap for converting Australian occupation categories (ANZSCO22) to an international standard (ISCO8).](plot-anzsco-isco-bigraph.png) + +### Unit and Fractional Weight Links + +The weights associated with each source-target link encode the transformation to be applied to values associated with a given source node. Let the weight between source node $i$ and target node $j$ be denoted $w_{ij}$. Weights can range from $[0,1]$. Note that $w_{ij} = 0$ is the trivial case where there is no link between the source and target nodes. The non-trivial cases are unit weights, $w_{ij} = 1$ and fractional weights, $w_{ij} \in (0,1)$. + +Unit weights indicate that source values will be unmodified when passed into the target nomenclature. Therefore, crosswalks for recoding node labels, or collapsing multiple sub-category source nodes into parent target nodes, can both be represented as crossmaps with unit weights. + +Fractional weights on the other hand indicate how a source value will be modified when passed to the target node. For example a link with a weight of $0.7$ indicates that 70% of the numeric value associated with a source node should be "passed over" to the target node. + +Now, consider two links from the same source node $i$ to two different target nodes $j$ and $k$, with weights $w_{ij}$ and $w_{ik}$. Assume that the source node $i$ has no other outgoing links into the target nomenclature. Then, it follows that the weights on the two given links should sum to one if we want to preserve the total value between the source and target nomenclature. For instance, if $w_{ij} = 0.7$, then $w_{ik}$ should be $0.3$, such that 70% of the value associated with source node $i$ goes to the target node $j$, and the remaining 30% is distributed to target node $k$. + +### Crossmap tables + +Crossmap tables are extensions of crosswalk or lookup tables, where each row represents a weighted link between the source and target classifications. You can pass a data.frame or tibble of weighted edges to `verify_links_as_xmap()` to check if the input satisfies crossmap properties. + +Your input table `x` needs to have at least 3 complete (i.e. no `NA`) columns: + +- `from`: source classification labels. +- `to`: target classification labels +- `weights`: applied to values in the source classification + +Additional columns (e.g. for label descriptions etc.) can be retained by setting the `.drop_extra` argument to `FALSE`. + +`verify_links_as_xmap` and `as_xmap_df()` will validate that: + +1. There are no duplicates `from`-`to` pairs, +2. Every group of `weights` associated with a distinct `from` value sums to 1 (subject to a floating point tolerance). + +The package also offers a experimental `xmap_df` class facilitate additional functionality such as graph property calculation and printing (i.e. relation types and crossmap direction) via a custom `print()` method, coercion to other useful classes (e.g. `xmap_to_matrix()`), visualisation (see `vignette("vis-xmaps")` for prototypes), and multi-step transformations (i.e. from nomenclature A to B to C). + +Please note that the `xmap_df` class and related functions are still in active development and subject to breaking changes in future releases. + +## Special Cases of Crossmaps + +The conditions for valid nomenclature crossmaps defined above are sufficient to ensure that the total sum of value is retained between the source and target nomenclature However, there are a number of other transformation properties which crossmaps can also encode and validate. Such special cases include "degenerate" crossmaps. We consider a crossmap "degenerate" if it has only binary weights on all possible source-target links. In such cases the weights are implied by the presence or absence of a link. + +### Types of Mapping Relations + +It is helpful when thinking about special cases to define some familiar types of relations: + +- **one-to-one**: a set of links where pairs of source and target nodes are uniquely linked to each other and no other nodes. Any links in this type of relation will have unit weights. +- **one-to-many**: a set of links where a single source node is linked to multiple target nodes. Links in this "splitting" relation will have fractional weights. +- **one-from-many**: a set of links where a single target node is linked to multiple source nodes. This "collapse" relation is more commonly known as a **many-to-one**. Links can have either unit or fractional weights depending on whether the source nodes are part of one-to-one or one-to-many relations. +- **many-to-many**: refers to a combination of the above relations and is often used to signal that a correspondence between sets has both **one-to-many** splitting relations and **one-from-many** collapsing relations. + +Note that links in a crossmap can be partitioned into subgraphs according to the number of outgoing links (i.e. one-to-one and one-to-many) + +### Recode maps + +Crossmaps with only one-to-one relations recode source labels to target labels, leaving any attached values untouched. This implies that the weights on links are always 1. Notice that Schema Crosswalks, lookup tables and category recoding functions are all Recode Maps. + +A common way of implementing recodings in R is named vectors or pairwise arguments such as in `forcats::fct_recode()`. Named vectors are a convenient way to store and use one-to-one and many-to-one mappings. In general it is not necessary to convert such mappings into crossmaps unless you want to combine them with fractional weight links. However, when necessary, named vectors can be converted into `xmap` by first transforming vector into two-column table of node pairs, and then attaching the implied unit weights. + +``` r +# example using the named vector included in `mock` objects: +mock$recode_vect + +recode_xmap <- + xmap::as_pairs_from_named(mock$recode_vect, names_to = "iso3", values = "ctr_name") |> + xmap::add_weights_unit(weights_into = "w") |> + xmap::as_xmap_df(from = iso3, to = ctr_name, weights = w) +``` + +For a crossmap to be a recode map it must have binary weights, and the cardinality of the source and target node sets must be equal. In other words, weights can only be 0 (i.e. no link) or 1, and the number of unique source and target labels should be the same. The function `verify_named_all_1to1()` or its alias `verify_named_as_recode_unique()` uses the cardinality condition to verify whether or not a named vector has only one-to-one relation: + +```{r} +fruit_color <- c(apple = "green", strawberry = "red", banana = "yellow") +fruit_color |> + verify_named_all_1to1() |> + print() +``` + +We can also identify mistakes such as accidentally assigning a value twice: + +```{r error=TRUE} +fruit_color_mistake <- c(fruit_color, pear = "green") + +fruit_color_mistake |> + verify_named_as_recode_unique() +``` + +### Collapse or Aggregation maps + +Collapse crossmaps are similar to Recode crossmaps in that source values are unmodified when passed into the target nomenclature. However, as the name suggests, multiple source nodes can be "collapsed" into the same target node, such that at least some of the source values will be aggregated in the target nomenclature. Similar to a Recode crossmap, a Collapse crossmap must only have unit weights, but the cardinality of the source node set will be larger than the target node set. This means that at least two source nodes must be linked to the same target node since there are fewer target nodes than source nodes. + +Similar to the recode maps, collapse maps only have unit weights. Hence, we can use named lists to store such mappings. Consider an example such as assigning students to groups. Assume no two students share the same name: + +```{r} +student_groups <- list(GRP1 = c("kate", "jane", "peter"), + GRP2 = c("terry", "ben", "grace"), + GRP3 = c("cindy", "lucy", "alex" )) +``` + +We can check that students are not assigned to multiple groups: + +```{r} +student_groups |> + verify_named_all_values_unique() +``` + +Or that the every student has been assigned a group: + +```{r error=TRUE} +## mistakenly assign kate to another group +student_group_mistake <- list(GRP1 = c("kate", "jane", "peter"), + GRP2 = c("terry", "ben", "grace"), + GRP2 = c("cindy", "lucy", "kate" )) + +student_list <- c("kate", "jane", "peter", "terry", "ben", "grace", "cindy", "lucy", "alex") + +student_group_mistake |> + verify_named_matchset_values_exact(student_list) +``` + +and each group has a unique name: + +```{r error=TRUE} +student_group_mistake |> + verify_named_all_names_unique() +``` + +Coercion to `xmap` requires adding weights and is directional. We can choose to verify conditions and return the same object via `verify_links_*` functions, or coerce to an `xmap` for use with other functions in the package. + +```{r error=TRUE} +group_links <- student_groups |> + as_pairs_from_named(names_to = "group", values_to = "student") |> + add_weights_unit() + +## collapse xmap from students to groups +group_links |> + verify_links_as_xmap(from = student, to = group, weights = weights) + +## reverse doesn't work without adjusting the weights +group_links |> + verify_links_as_xmap(from = group, to = student, weights = weights) +``` + +Aggregation transformations can implemented as Collapse maps. Additionally, aggregation size requirements can be implemented as conditions on the number of incoming links for each target node. For instance, if you know that each target node should aggregate values from at least two source nodes, then the minimum number of non-zero incoming links should be 2 for every target node. Notice that this requirement precludes one-to-one links since it prevents any source node from being recoded into an "unshared" node in the target nomenclature. + +### Split or Redistribution maps (with Equal Weights) + +Any crossmap with at least one set of fractional weight (one-to-many) links involve some redistribution of source value. However, a useful special case of maps with fractional weights are split maps, which encode the disaggregation of source values into a target nomenclature. Such maps which will have mostly one-to-many relations, and do not contain one-from-many relations. This can be ensured by restricting each target node to only have one incoming link. Furthermore, we can preclude one-to-one links with the condition that all links must have fractional weights. + +Notice that the same set of node pairs can generate either a split or collapse map depending on the weights added. For example, recall that in the student group example from above, swapping the `to` and `from` variables no longer formed a valid crossmap. Now consider allocating some budget of prizes equally between students in each group. This requires adding fractional weights to `group_links`, which can then be verified as a crossmap. + +```{r} +group_prize_links <- student_groups |> + as_pairs_from_named("group", "student") |> + dplyr::group_by(group) |> + dplyr::mutate(prize_share = 1 / dplyr::n_distinct(student)) + +group_prize_links |> + verify_links_as_xmap(from = group, to = student, weights = prize_share) +``` + +Note that if the first crossmap condition is met, i.e. there is only one link between each unique source-node pair, then `dplyr::n()` and `dplyr::n_distinct()` are interchangeable when generating equal share weights. + +## Creating `xmap_df` from Pairs and Links + +The following examples demonstrate how to coerce various inputs into the experimental `xmap_df` class. + +### Row-wise Links + +The following example shows how to create a mixed crossmap which encodes one-to-one, many-to-one and one-to-many relations and coerce them into a `xmap_df`. This method is most suitable for simple crossmaps. + +```{r} +simple_links <- tibble::tribble( + ~source, ~target, ~share, + "equal", "EQUAL", 1, # one-to-one + "member_1", "GROUP", 1, # one-FROM-many + "member_2", "GROUP", 1, + "whole", "PART_1", 0.3, # one-to-many + "whole", "PART_2", 0.6, + "whole", "PART_3", 0.1 +) + +simple_xmap <- simple_links |> + as_xmap_df(from = source, to = target, weights = share) + +simple_xmap +``` + +### Crosswalk/Lookup Tables + +It is more common that you will want to convert an existing correspondence into a crossmap. Such conversions require attaching appropriate weights to the existing crosswalk table. + +#### Recode Pairs + +Consider the first five country codes in the ISO 3166 international standard and the one-to-one correspondence between the 2-digit, 2-digit and numeric codes. + +```{r} +iso_codes <- tibble::tribble( + ~country, ~ISO2, ~ISO3, ~ISONumeric, + "Afghanistan", "AF", "AFG", "004", + "Albania", "AL", "ALB", "008", + "Algeria", "DZ", "DZA", "012", + "American Samoa", "AS", "ASM", "016", + "Andorra", "AD", "AND", "020" + ) +``` + +We can verify any pairwise combination of columns as one-to-one recode maps using the `verify_pairs_*` functions: + +```{r} +iso_codes |> + verify_pairs_as_recode_unique(from = country, to = ISO2) |> + print() +``` + +To create a crossmap between `ISONumeric` and `ISO2`, we simply add a weights columns and coerce to `xmap_df`: + +```{r} +iso_xmap <- iso_codes |> + add_weights_unit(weights_into = "weight") |> + as_xmap_df(from = ISONumeric, to = ISO2, weights = weight) +``` + +Notice that `as_xmap_df()` always place the `from`, `to` and `weights` columns in order and drops any additional columns passed to it. + +```{r} +print(iso_xmap) +``` + +To convert the validated xmap into a named vector: + +```{r} +iso_xmap |> + xmap_to_named_vector() +``` + +We can also easily generate validated crossmaps for the other nomenclature in the table, and keep additional columns: + +```{r} +iso_codes |> + add_weights_unit(weights_into = "weight") |> + as_xmap_df(from = ISO2, to = ISO3, weights = weight, .drop_extra = FALSE) +``` + +#### Nested Lists + +Now consider aggregating data which were collected using the ISO 3166-2 Subdivisions of [Australia](https://en.wikipedia.org/w/index.php?title=ISO_3166-2:AU&oldid=1110907059) and [Canada](https://en.wikipedia.org/w/index.php?title=ISO_3166-2:CA&oldid=1110906706): + +```{r} +adm1_list <- tibble::tribble( + ~ctr, ~adm1, + "AU", "AU-NSW, AU-QLD, AU-SA, AU-TAS, AU-VIC, AU-WA, AU-ACT, AU-NT", + "CA", "CA-AB, CA-BC, CA-MB, CA-NB, CA-NL, CA-NS, CA-ON, CA-PE, CA-QC, CA-SK, CA-NT, CA-NU, CA-YT" +) +``` + +Recall that we need one row per relation between the source (`adm1`) and target (`ctr`) nomenclature. Thus we split the string list into a vector, and then unnest the values by country. + +```{r} +agg_x <- adm1_list |> + dplyr::mutate(adm1 = stringr::str_split(adm1, ", ")) |> + tidyr::unnest(cols = c(adm1)) + +agg_x +``` + +Since aggregation involves the one-to-one transfer of values between `adm1` and `ctr` prior to the collapsing the `ctr` groups, we simple add unit weights to form a valid crossmap: + +```{r} +agg_xmap <- agg_x |> + add_weights_unit(weights_into = "link") |> + as_xmap_df(from = adm1, to = ctr, weights = link) + +agg_xmap +``` + +#### References for Custom Weights + +Conversely, we might have aggregate level data which want to disaggregate. Continuing the above example, this could involve incorporating country level data into analysis at the 3166-2 Subdivisions level. + +For example, imagine that we have population figures for Australia at the 3166-2 level for 9 out of 10 years, but only country level figures for the missing year. In this simple example, a reasonable harmonisation design could involve splitting the country level figure by the subdivision level population proportions from the year preceding or following the missing year. + +Alternatively, we might want to compare aggregate and disaggregate statistics to identify discrepancies. + +Using Australian population statistics from 30 Jun 2022[^1] + +[^1]: Source: [Australian Bureau of Statistics](https://www.abs.gov.au/statistics/people/population/national-state-and-territory-population/jun-2022) + +```{r} +state_data <- tibble::tribble( + ~state, ~adm1, ~Pop, + "New South Wales", "AU-NSW", 8153600, + "Victoria", "AU-VIC", 6613700, + "Queensland", "AU-QLD", 5322100, + "South Australia", "AU-SA", 1820500, + "Western Australia", "AU-WA", 2785300, + "Tasmania", "AU-TAS", 571500, + "Northern Territory", "AU-NT", 250600, + "Australian Capital Territory", "AU-ACT", 456700 +) + +state_xmap <- state_data |> + dplyr::mutate(ctr = "AU", + adm1, + share = Pop / sum(Pop)) |> + as_xmap_df(from = ctr, to = adm1, weights = share) + +state_xmap +``` + +### Piecewise Construction + +Now consider the following mixed transformation using selected correspondences between NAICS Canada 1997 and ISIC Revision 3. Imagine that we have some numeric data (e.g. gross output in CAD) collected in the NAICS Canada nomenclature that we want to harmonise into ISIC Revision 3. The correspondence between the two nomenclature contains a mixture of one-to-one, one-to-many, and one-from-many relations. + +Luckily, we can split the source nomenclature into two groups: + +1. source nodes with only one outgoing link (one-to-one relations with unit weights) +2. source nodes with multiple outgoing links (one-to-many relations with fractional weights) + +Let's first define somed example correspondences.[^2] + +[^2]: based on examples provided by Statistics Canada on the page [How to Read a Concordance Table](https://www.statcan.gc.ca/en/subjects/standard/concordances/concordanc_tabl3). + +In the first example, one NAICS Canada class relates to exactly one ISIC class, forming a one-to-one relation. + +```{r} +canada_recode <- tibble::tibble( + NAICS1997 = "212210", + NAICS1997_desc = "Iron Ore Mining", + ISIC3 = "C1310", + ISIC3_desc = "Mining of iron ores" +) +``` + +```{r echo=FALSE} +knitr::kable(canada_recode) +``` + +In the second example, the ISIC target class `D1543` is equivalent to more than one NAICS Canada source class, forming a one-from-many relation. The asterisk (Partial Flag) indicates that part of ISIC D1543 is equivalent to each NAICS Canada class. The ISIC activities corresponding to each NAICS Canada class are listed in the column labelled "Link". + +```{r} +canada_agg <- tibble::tribble( + ~NAICS1997, ~NAICS1997_desc, ~ISIC3, ~ISIC3_desc, ~Link, + "311320", "Chocolate and Confectionery Manufacturing from Cacao Beans", "D1543 *", "Manufacture of cocoa, chocolate and sugar confectionery", "Chocolate and confectionery, made from cacao beans", + "311330", "Confectionery Manufacturing from Purchased Chocolate", "D1543 *", "Manufacture of cocoa, chocolate and sugar confectionery", "Confectionery, made from purchased chocolate", + "311340", "Non-Chocolate Confectionery Manufacturing", "D1543 *", "Manufacture of cocoa, chocolate and sugar confectionery", "Non-chocolate confectionery, manufacturing" +) +``` + +```{r echo=FALSE} +knitr::kable(canada_agg) +``` + +Notice that for both one-to-one and one-from-many relations, values attached to each source category are not directly modified during the "transfer" between source and target nomenclature. Instead, the source values are either retained, or summarised when the category collapse (and value aggregation) is performed. Thus, as shown above, all links in the above examples will take unit weights. + +In this third example, one souce NAICS Canada class is equivalent to more than one target ISIC class, forming a one-to-many relation. + +```{r} +canada_split <- tibble::tribble( + ~NAICS1997, ~NAICS1997_desc, ~ISIC3, ~ISIC3_desc, ~Link, + "483213", "Inland Water Transportation (except by Ferries)", "I6110 *", "Sea and coastal water transport", "Intracoastal water transportation", + "483213", "Inland Water Transportation (except by Ferries)", "I6120 *", "Inland water transport", "Inland water transportation (except ferries)" +) +``` + +```{r echo=FALSE} +knitr::kable(canada_split) +``` + +Let's clean up the recode and collapse relations shown above: + +```{r} +canada_unit <- canada_agg |> + # remove the partial flag (*) + dplyr::mutate(ISIC3 = stringr::str_remove(ISIC3, " \\*")) |> + dplyr::select(-Link) |> + # bind the links together and add weights + dplyr::bind_rows(canada_recode) |> + dplyr::mutate(share = 1) +``` + +```{r echo=FALSE} +knitr::kable(canada_unit) +``` + +Now all that remains is to prepare the split links. Similar to the disaggregation example above, we need to design weights to allocate the "pool" of numeric value associated with the NAICS class `483213` into the corresponding ISIC classes `I6110` and `I6120`. + +Assume for illustration purposes that we have reference data to suggest the Canadian "Inland water transport" industry (`I6120`) is twice as big as the "Sea and coastal water transport" industry (`I6110`). This suggests that the weight between `483213` and `I6120` should be twice that of `I6110`. + +```{r} +canada_frac <- canada_split |> + dplyr::mutate(ISIC3 = stringr::str_remove(ISIC3, " \\*")) |> + dplyr::select(-Link) |> + dplyr::mutate(share = dplyr::case_when(ISIC3 == "I6110" ~ 0.33, + ISIC3 == "I6120" ~ 0.67, + T ~ NA_real_)) +``` + +Now let's combine the unit and fractional links into a crossmap: + +```{r} +canada_xmap <- dplyr::bind_rows(canada_unit, canada_frac) |> + as_xmap_df(from = NAICS1997, to = ISIC3, weights = share) + +print(canada_xmap) +``` + +## Reversing Crossmaps + +### One-Way Maps + +Except in the case of recoding, crossmaps are generally lateral (one-way). Weights on collapse and split links are no longer valid if you reverse the direct of the link. Notice that `as_xmap_df()` throws an error if you try to naively swap the `from` and `to` arguments: + +```{r error=TRUE} +dplyr::bind_rows(canada_unit, canada_frac) |> + as_xmap_df(from = ISIC3, to = NAICS1997, weights = share) +``` + +### Reversible Maps + +However, we **can** swap the arguments on a recode map. Recall the ISO country code crossmap we created above: + +```{r} +print(iso_xmap) +``` + +Imagine that instead of converting country codes from ISO Numeric to ISO-2 digit, we wanted to convert from ISO-2 digit to ISO Numeric. We can take the existing crossmap and invert it without editing any weights: + +```{r} +iso_xmap |> + xmap_reverse() +``` + +A less trivial reversal is creating an aggregation map from a disaggregation map. Recall the country to state level disaggregation map we created above: + +```{r} +state_xmap |> + xmap_drop_extra() +``` + +Now imagine we wanted to re-aggregate the data, say after some state-level adjustments: + +```{r} +state_xmap |> + xmap_reverse(weights_into = "agg_w") |> + xmap_drop_extra() +``` diff --git a/vignettes/plot-anzsco-isco-bigraph.png b/vignettes/plot-anzsco-isco-bigraph.png new file mode 100644 index 0000000000000000000000000000000000000000..8e0d578c2c8c6e1c9f710914115261d4c5795e09 GIT binary patch literal 95249 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 literal 0 HcmV?d00001 diff --git a/vignettes/plot-weight-sum-matrix.png b/vignettes/plot-weight-sum-matrix.png new file mode 100644 index 0000000000000000000000000000000000000000..29f8dcee6d147c0a2373e49aaa14f0a40a2da0b2 GIT binary patch literal 83688 zcmeEuc{tQ-__s=^gi4Z~CQTYk60%0NktO>YkzLucuVoD(BOLod2r(i17Glz*NkjH^ zNV0F)dGG1Gzw7$_|Ni^Vb*^*Gb+?bG5J!k36-yx(DMF4skKv*S-W5=N zZRg`-HuZTehO{-Ek5MhOOwLeE#FvV@p2Z2&=a^{B>tdoVC9@|#GWV&z?3k<)&uH+H zGBV7NIwa+Y%~*Bj=Lg+NyiAH})K?sUfmw~TuP84DyI&NUy*4c9)w zU-FP+u(7d;<&gH>o$#wP-j_X`IaJfoDD~bS8aw!ta>G`ic+h;f+Z^}e#c(|93G!z@ zrOsS5^BOO`$Ec{qh(UNrWaQhk;V#lrVG!0v`g(d=uhi2-FUEA-L#~rJ5BujfJUu+x zNh{miKAAHu&1@`|zB<2% zZ?U=4>bpn`qSLogMoesGbGNvcPo9R;AWz$8v36#4d#@>A_lr#hGAfvhE0mV%m^$$q zosnLzZ{yds2DqFQHHi$mEVIm+<8tFZftHq&pW-ih-_>Hg6$}5%0Bg4J>zADr@lCG- zqvFLFrKlYJN4I2T;4M9wz5bpu!x-uto-^W$pA)YwEGz^zb4C<#?u}C~c=`#aq-SM~ zy|+*3vbjVv3Cmxo`x{>6RBfWyYg{5KEG#S`G5Gy^x$~Gz9F9mcc2oUs0Z~Usr{%%7 zxedD0rzLGB7B_blwbD~l#| zqEM(8FJAOGRK~`}Hu`PWjVzuYNkYt4_L=$p%8p>8Qx;8YbesM>KK|hD-MfXlEF2u8 z^FUY zTH7WTXhz6q!=t68#a%-B)~!z0;i)MrFE1~|*^3u1S{QlallVtQMwD{Px;fL`Zb(Tr zynp|(d_Mb5#9pARqSnBpKV4#DD8`ck0RiHR$=0=oD6=~EtIV{2HaGhmZl=fS@+HDo z=H=x@G2t_%{Zdm??NYzatX&W^qm5__2?=4t$<5T<=urZNAdtTnRF}^VqDngPGJR!HL4f6f+UX&}yO%4}O989!SIhskH_^7T60lw*kHls(Ah+<#+S61Y)p zzQ5?zjCVE7mCKxI-kTPLf;x7UDH<2czPnx^n?abdHahqz*1XMmxWYN@w%uR$Fl&F_ zHfS4f{==aXKDHRvk0#r#c5l9dAhS9A1cPW?|9 zyJ3R<6BnAd6f=r#D+W~aw2_Q(@^^Z%fj1LXVJQgyKX1U2>grW?XQ?1o+XVwx!Y-J> z=4)$f%i8{83+ule-F&ct)zjPW3)(l3LjSAFC%w_5W4=4s#~@FxFz^y9qf-51+1&wx z%;7fS37gl-WP9@-zL|}alQ8XexSe&l9pmhON3WOsG$kda_K(!xiuQEQR|q#E&7pbs zF229tu+muC<9E-uZ{MH>!QSSGQIeF9cqGay|2%(o6V{`mq9RUa2g#wZ^!xV?+zYhU zt1g?g^z<*&Zcq%wynlX4F+}~z6AZB5{j;#*y*FomxDyw&M{0|p&yw<4jW*w#d>AKP zRa5g1e%k5#W*+ra9(ra;V zct$3IAvHZ+%J0v7AKWfD`$_oe?rtr0BJY(e=04Mjg0NNTW%u6Y+naB-UKsUX>v9p7 z5*7V3m7wV~QdgREuzj8kqapV$`T4E5Dw=F#>OVHdlU3QQo{;O{qgvtYOV_@OUF z_-<1%aj{I?*Gcz2g61fpTY=_kls2|6Mk%+b=x|l=P#!Zyij#E`O~c`ESw25g=Kf?C z6i{=+^5r2vw}en%ivPsw++ZQZEXAQIk>TXvurqZCCMxI2mask?FbL?jDl|gOB-kzRy*h=p1?p&e5DKBuOmSYP! zNYScLf3Lq=PtR9qZRCu+x;h|=$2*;xf#t3wCAcjXZQ*_9L5+ENdDppO7;=zB`o+_3 zw@kyQd3Sbqt&NxzyJyynP)W(=ffqGz!5M>c47-r=WQLT_S?<@w*>&FlaW=Y$Ht2NX zehONz;FLN;%WQ5QTkwpFi!-klK*%wm@QWk$o3%5U6~-mdm-hGf$$MjSVF*|Lh>HQv zns~6YgM)<6sukjFi>oLa!_Uu;!b9JwfVaJ(nc1M#tI}{?j$z=DXfnQWx%CuOQFsY8 zJ;(gq+^LAR7!_mUdldvmqrhuvl)Tf7OFCTHgC3%9QXpDeLg-*Ds+I zH7Gd*D`BYiaI#-n*TNzf7GBNJ*xe7T9tzl5pM?`FB=j$F7W!R=YF>CmL_t9T_ZT~Q z9Umle^t*ah48RMPzuX{5?Jc)4DoX$9e^_St7K z-M-*STCq>-vZ)wInsHm-+~kyS|CMN9b|Rw9JKPagpw^UpHV#@(30hUamozEvr3wBJ ziJbYGfuC8Mb{kmQtTz=%%^y2F*fekOKEthxVmeI~GU8;Q*GpRYr|akWb|gKg%-+v5 zXHPG$>Z&RzvdggCesBg1lO8)c1;Ue9c1e9~-*LHrQd4*OaWXbS%;_2hy$&0@`&oNa zSAw8b$Hc}?Zs#&8UZ)KSii*R}x;v!Wh_JvZGYi@$!{N%@T#`ZaPVe{cn3e}BdD@)& zzm!=sxM_0g>+9ivwO=rI7iUXINPtZSr@+qc7vMeeB}asZL$ze5ix_z1)`wq_YQVgO;huz(J?5XL8b#i{YIi zs;`TSOb;n2t})QNbjWAlQ$d_H!}iU(=W7=lm;7`QPuH+5(T`T(d3O6YwvRfb#j@P!?OP2VK$9l&i3{bJ~GL80Q(7@jM425 zm8mE^qhbJwb&OoO4wmT;g_7}bC)UQ>Cf;cjtob%>_y)MU_gI#>dw5LLrQ{&tR&W0K zBd~M==epwU@9#fsQ?aqRIpcOKqHSVr8yHS+vB9~>k3ULGd@^yf8(Vv7>gpaI9`T5Q z;o*7D`r~rX+bsbTi>TAn(TU!?37ktPx}ne-~p9Hp~w<09%f3Xa2oSPwS_i zb*1=3T&E=xi6OX#B{a3@qyFUqLf{{uMxMM0a94H!4K_X=s`C4O?(g5YB!8&c&B!h) zLPix8vCxM5VH#s3@09>4>Ya7xO^CE${&eZ@#d*K{UrhTP<&nRCZOF;x`QN2K2zc5b za9TvY9;^LgvHE~3R9COD@BWtb=Fru;v7L}bya4$x#+A4{&5y}3P|DTxHEyvhY5~^U zKPTXr+Tl7J(95X>{H&pmeVZMP@y=`xvb5IIyA`X$IBn+jmqLORu*-ssax+)FRaC?m z<#tFJoSKiuxSMZKXXW^f#E)s|!Da9MX6!8YKV1KNU1HQ5Bk;k)KxvEn=4ws9S_vDK z{FuheUJCWj#P&L*=HFtAO?&%Jt3-cpLqeVCS--C|^r_sGFOh{{a3c7faS2t3#R&i2 zu=j5j%+x+HNKs2cYsx1x2br3GF}&8`S`Nj#QaQ2&v2+qP<2_`JsX%V`8>Fge+GX_j zX}1yiJzat1=yo3YPe7VVewZv`ii)HNbM6uu8eR!o+Qj#dL?wapZTMaczn<2G4>B(9 zWx88rKHqu0G0b&nH(X&2Cd#4(vRxYSVs=`y#{`=Hxt!FiKL?(q-^T z3)*t|{Bc2W>b1S`{Z}VqoA+fudcD5uJ#~Ux{)f{h#y(YvE7WP1F%t8dFY4`?YQL(R zukHC)__*M#z+=3dzBp;cyF0)N# zydGbomGE}eIX9YME*g2QR}PiND5e0JvC5T|Mjo_wp>C7{Pq&>e<~5wXWPIgbTx7m| zFqcAZcS_1dqa^!O*!4-)*gi=ip$C=5OTFS=dD=#XhEVlH)DsX_@xp;rXK5%!ordko z3>VQ@3d4fY<7M^vmrwR$*-uT+eTT1A#KObiGjt?f2fI5zs$bnDKWw&K${i|_T3oz^ zH*r?hV>Ify9zJA0Rn2dY3L4s%qd^}n$TbRX=nA@#ny*D;|8Zfo%03E#K`7p1tn0d) ze}+a&PqB8vQ`d;m?oX_#aOyb``I-@(kJ_J#bGba5ohLq?#UNI;9Lo%K^?L0@Mq8hk z=OCxuYPAc$iDY!yIM?3wE?Nh2!o?uOOe+>Sg2p^@b`}E!pJ3nq?{0>*5Hl4u^%;3U zn=CPAH7*m23yvj>is%C3xpU{jqG&KjALA0jL<XBO z=Gm-6^+g2BmH66uSAZgWh(6(r_8Dykw6@n|+wE*gZEGbtgxo!^{JYgbQ6jIoj4mND zJtGI-cG-W?%cmCcsr;)l@>r(Meto7&Gd`c^e15|y;rB)z7Y#5e>bX2^xUmVH?x)lf z@zcg7`9(!sG~51WjEbrG&W9}{UELxJYun#4yya^+I?JIu!`{QM`P9%2Ase8{7MVjb_fGX;-77a3ttf3^afO{M?RaXcV+f+;*1!;Zi@sn*a;~h?A+n z=GRT?ryi%NI-%0F;9S$WXhhRa2bu6Cx=W0dv@dS^XOhH|tkroFkWndWKQjt{hPE|% zJy71{Hf_hxZ7dE{*gkkbfor)oKEpigK4>L;i;b?)Op_rL2`dVlU5=rA0EKg1@}D^) zPbv2jhzp`Am{N|JmMXVfMXM)3?_Mp)A*AU+WKXjx_@JnLC+&=XjDNmmb85}x`WX^uGJ z>}&g-x*^h4fkIurEW%xk6^JlqOO+O-MMcomGc-uERWS;&Ccn(`v^%HWVDFh>S!t=V z2s7>ySoJ#?>swjS(66WF1HdUwTXcKEs8~DV1n^5Zg*m)-aezs&OqUOhnOgB?4(Fzk z*Xy+`t9keCLph4D7AF6URHHZgZsKTtQIXk@iGRWa5fT1fu2f9E@|9!wy&)mTz!W`QS0*_M@Vh|! zYUYe z?k*xGh81U(B3w!692hVPxA2$A(kL*QOVJl>(6d~n070%Z=!7CyvGS417#N9x(5YT#l0%3SMLTyZsuhvq06Bkq}uxSFVH zEqpN<-#awKN*Cc;lBrk43$pVqM#bYtkEA~faefatRf@x_exy%*r1lIN^LbkM>${>} zheBP@oZvXe;hx-$IQ60a0@+MhevpIwytGLsy>@xpWyU2L85!j$9LY5qf1ZZ&hw=VB zJv}KV$LgWSqG>3+Q?(>Z%+EC+sVi=Y3;(F)S5=8t58W>TU|lrg1VGcC@udYbRovSI zgnjkUf+y@*+BT59(%u^t_rg_~m<)Bqu&VQ(!?^pwm#Q_77yYN41CI$!78-@kufoT$XuUdc7rq#)U(C2yF@VP328-nezE(4NKYj1&V6V1{0=6BJMX0vq}ox7rsBtZo4VCz zW@egvTK*vd=N@1FsAAxY%?-ku8CHxXCZA}J?W^BW`D!OJxsiP`taW|;eXqmg$B*w5 zxfUytQRM@V?%cT}z6dYXFHYoouA29qd8Q*|67*VlY@ILeg?3Gx6){WkNF*&56-`D^ z0pE3~oE%6J&$vBcO2r^K*|{rU5! z$WKYzOXQ;Lj2 zpuNRG1;O6*#I+1(q&C$`Z6#P=q<=QF@Mju}rD3=gOA?RGpH-UP?G<8)0pLv(f%-8x zST|YV$UfSnvutS+l>_QDP4GL=YOsCaoHYHau6j|LgA{ijeIPWR4~MYRsxB;VciiTQa`ssLFPigs0Ek@Ya_;S3? z&`tFlX5{TX&1pP_uV#P&n?&0{0FmncvvHT3ifi1s2Z24h?T z{P6eCOq7+W@#WqMS%^*my-C*2Yqt^Tvy>Bq)Vvf=(?b61o!&L~F^K4PAQ*4;9ZU3e z^eSs>2cW+c4o2o6H+&o6g!wmtSEBS{6xm|Nm-s_u_Q?cOS9VUaPm4~_X~uD}y7pFQqb54d5qA3m!g1qDFF$KD>z*nXSK zCHH)=g4WxM*W&DCLegX(G;o zwvKt-Yim8;KSEmhNLtCuyBx~UCIovUivZG{wNIw+oianf$N*BXeO{iP0*6|SW`It_ z1(6vV7fv3pA87)KO=%V#`m(49#6mW@8?-vby>OQRk~%uh&Cb?3C7mk0l55YRp$uyk z!_b_Ydu++o(vpXPrvH&>dMc*VYTk1fgo$LYOj3;sLiqZ1IjvW16QF}RsuyYL!PNtJ zH6>CR)IwjKaQTOe#tNyc@;_OCs2p&u*yyNb_(3&IR0WuT#z2imoGt8i0Hgy8xUpN( z>i}41#k--nxES0_+dDpuW*Hh{qN1V_5>b8e_ot^dcoS08s3KrnpP4aN@TAnJmF%@`h2@`+g$0 zzbQacQ7c{rLAEzkFrbBru{6ViM=Aj8gA>E!3H24kMjGx64Zxsa!j#_Mq^6g*6}}kC zz!9-HKmW+yzE+F(MCp6xNCgIYXo*l$WBTu7`#|R@M?o?5gYKc^suYJK0ft@hCncJk z(smuiPveu5u_4g}D*5LY|11nuOR~|0TY#&1Q=Jt&LkAC8q z@S9YjWKROubnGi#n<0>TtqcZO%)&k0+|F_F4>4B#*HO5f8VUzA{y>$!z=O@s;zOd~b0Ye{J+Jk3sKs-_^yp56W#= zFkuqtS(OaB5zam}9lc2Gb*fW!f^{J;^UFeiSCoqc1eppiGf7z|SR1A0yM8||PlJuG z&G%gPyNni2QR@po5br*Ibm)8bcYP(}pV-aXksvcLqS_C>O z=To^_b6)&p@I+CpH>>GorOhNq>Md8L&#SjD5>6W)G|21e86}a8=qBTmaul#Z=5YJB z{FVNAP~HeL7sFS)rK<16C=*5euR5;_nqd3H77JZ|YZU6XZYzqWb=irOZsd^8g)!85 zDut4z5|U%%XVH~&PoY2->M|;}7z|hmPZ%}Bfqcii9LEIE->_zf`+!CBWJ4ujs3sy}bmVVDWwA`DD`n`B)y>=kuZArNx=H7tG)X)4vI9wy%=}r3 zxgKdl+5ePPivJJ?SxkEnP^0PK1_yR| zd70fj!TGY0Ql!uC9>SaPwX^qQ~xXPKeZLl%eFRUoiKtMul)cexeND-xJ*7w%m7UDjiy6G@w{n6kh zYUJ4u3U3Pg=FPig74B*M_-J2BON(!u5jICQ>^9Mnfls-1um*Z5N4 z%fU~3tIN05D5AbJR4tgHfweoA+}fV_R8Kv@dfM~h3HB1HRWu{VB~nFo9|HwFuO1PB z?s-3!G-jq{%u4<)ULPAdy=;xDa&sU0`tHmRR4B)?!JUIl1NOc$Luj|%JY*p#j{xDF z+Ysezr8@d-t~4#?A*gH66&2A*)~83zmcO#Z55)fRDs8<$F&AA_^j$dSraSq~U8qNP zUA@TFcuRJ_vQV%(9MRb^Nr)<%-HNbUo*evq1-QGxD(Yq;f(#l0_c-M7PHBVSp zR#r&poh>1AZ11O7FxPOrO{Dc=vIpsi_scW?YmMUSZ_pC^`fx%o%&?ojjo$W+Aa!?8 zQOY%dzu0ahLc?7@a?l+Y_Wwqwo{GZ_)IS}U+J~FeEZa* zB1*a86_22$v+3z-T0Q(;-XrX*`=n9aA_~9bFSF;NsY!p(N?JjUZ^SAR3*1v@*dsX$-j;+Yyn3<50A&d$!SU%$fD zgtWL|{nkaTj3BGv+_2J-#Bbv3>-$29WNdOz?`DeH*RHOcVq(>ml_Q4{{^qrtWxKwX z9^8~cY-Dxzf`XP*z>cm?LZ3~=7!+cnh#Ui8u7O8Y1CMAczMd2XMHa<$EB1*vTTH>` zCbv-%kl3<^%Aoy9WNZd;K@|aOaCzBBUB|@4#EILpPlP{FHA5!AVeZFcr|R9u^tJbp zx_swDTHaYc+8jBNsYAY!A5Zz88d<8r4E5bzE~23 z1HjLz1M?QU>jnzlid0so6^Pb#$nWh!YQVO?X4LQO?5yvY)Dk~bhk;9>UtIF@B|y_r zZ&E)z_~++Zj*)S6m65YMO-GHd0joH`-=+EXjIk@aSgUg(dC35xAN*1o|&-DJ%+R02=pq zW7D>Ye9pE@{n4*R6akbT4hJBneM?vGJSFIPdmT!oa@3j>mMki@kX*`9r-HfgCAVzq{cfd}{;$9$jR{FbGtz)e&D<#D_0OMqv;)zqVESnU1o^fXi}F%9I0o zJvotOZS(`ePbY%67PQ9qp~I|6p)ocUMi_836|{62;X~oZ`Oh;236T%!nLciii3*4p zX~U|7)LN=4^OS=6sJa&z1dF zEL6wAo7X-R7kyrnl2Hm}I5=)Zy#8a(!zgJbLLGY2dBY@Y=5PdBPY>(>GzNvAG{3xO z)Xshh9cQ4I7aX+A?n0Tpj+ z_|tY+Wo6V6^QI{3r4k0d=N1-sO!NUg`BpxaL?@A5xrew|I6fdLU=SltUJxU>L(e|H zu<%&HJ_kt^(n7+G{gah-fwE=R-D$L8`&)J!M;tY^1c*ZZ{`0?kJ0m!J?MNLlG0Vt= z&UA?l8@|MyvBO=>68&rP03pG_yv`K8-_2R{dxMDK`Dx#UyHHQ8lMEglDNQX z<1FCx17JCVcPW~v3MN?=p&E37t~9aDjmHj@hAC<`R#V?07$wB~-nax(RAcpCORHyX z9WjKNQqzmlK?i%^zXKtW$-?I4S=-q`Xv$>ybV$BEXjc~mO#ewq5lDm#P;o&4IRW!! z#?guGCg>%t6U#k4x=0boCV;Umki51#l;oUiKL}15SpT-RwyJrUv8A!*!w+C@8UArP zkJe#?{{Q8Rnx>|vG8sdH+1GA4`|FqYlHXVa)*Hzv5p>Wn@MtGQb}J~i``4#WpFV!{ z=1o}fZH%Dj9Bn?leMuh-AV`e?ngkiJ*C8?AKFQj|WIs-7)rITZXumygLaqDk_nsc| zR~>jXRK3(s{;EapUg}d@$V62$Y&G1uX7XcfZUeM^vWNz*Z`wcqe3~v7 zQ3oV14R$s7?|qqjz#o7l=jTI(TnS(&tl`D(=_QhC019fVsVVSSgx(7ZI*h-x1mDDP z|8alVskRmGoPq*dcs~$8zkW@Z39zxVvvYCz=1`fWns@GshGEGMc1cfw_o4T)@72H+ z_ZFXnxSf^o>C>kdM(VLz#~8TX|IF7$MxGef%h0HPGV>MezWZJrLNcr%91@8-AU>t$ zPYgYM_N;|`Q}FaqH|#{*Ae~ZHc0DQw#FN1B56^BtcpS2mnh#w*;fkh)x;hB!dcELB z5oRP^#T@St8dU=LTdwA$f9=|C_hgBWb*#9RC7%Y zVD#k&2>ItTbKt&udh#Z!N+0f>TJeV1)b{=X*vYDSEH@^^W7Ed3Vzwc5$gpXC`TCVZHqhT~)d8wDr1 z`h{dU_hvPNw2OQHz?$t8eXghgq?HZ=DZpp#!t`5DY=9_G|0}^hy|yj$r|dAyB*EIp z!eRm^8wgp8s=p7OP?v4la0`|z&87koZKtS0T;1GOA?b3FaLNpuY(1dMr-x!aQ-3#&|9a_Wl}OUtfA(vyMUXk`&L zzuln*r$kvvseE7_j^afSTZcf}O6yW3)@c{Qwe&$?Ve6);g{5V~O6Su2ytbaT{Z*zo z<#Y`LQr|_{&tRLiPEF~_F;G!aU5Z~$xlwRl@s|6qmwj_F?Uv-Mg_Tj~%oEsZhNv%u z2$uSO(DVRRz}4pn)Mc`M#=(j?T%(DE}VPN_fDsj4Ns~wRB^j-jbXv@=|Lw zqYsl`@t&WE;2drAtL~e-L~~R&teWgkpMk(J7;d**Cqco7$euDVQhQ^ALS0DRnt4xb zC!6`MoXgsJ7>6sdXZd=~FrFzm>)ol~Z#3?@dI{E`Mv#Me-{wv)1szf!eO3kc$gOsm z1|_0?Dop;0Mmm%{NC+Mhhw8g6%icia^gv@EgG@IJ5sT&J?e_LxP%-E^{6ARc~)-v5~D^}2}rum75Z+fat%m;+;d=tZ2S)aGyn1|e9lbQ0$ZMjad`BLjoJ zj+g+BI9u>?IO4=gogsaEvDSTi_4(M?pUlk$+=R<7_Z}#MK$=d5jkRw=t7&VSfi;3V zO~)qwKs3#3ad;a-v*B$Mk&!Eli%uBCeE~0JW##M#?Rv|w8cl^CBC38QT8OAi2JUVB z`gOj!`7tybTBgRv{)@G?idw!~6Ux4ReqcNh`d~{~mKi=2k$|L@l#eBx4=8jOSq3NF zYP~Z#IXQ#iFUBBT85g*oTl``K0{$pG0A#Y!4ryja#mII`K;;z`It-z@eD)OsAd>OK z!~T~SVjeEH#zS5GVsi@DtC22#3m(G4<@QA0y-rAw>u@oQZ~H%3 zRmc(C{@NNPVGR_;`ugG(vt)dj-{mMvBqLy2$N)i#6yjFE=_$F3dv~6muDe85lqrUd z#N(1k;+WT)MOHyO{5Uj-KyQ2*pgzwzP7Wq^xU?twc%6#}YPwHFC4alvzU70EQCcPW&Ki7B|{-+@P?%|Su7wio0- z&20$q^TR*wq%!ToDR5WEii-`fz=nV-gizDNpaDN&1845SCMN85HC|OA^_ z+V?b70I&&sva)cBJ9HVoyRP-7rJzH3dgz^t+yeG7Hdggy2Auk61|AwvP5q6oC4L?r zHbpWHHu#ViJYC}gho(BOPTweE+}$d-yT)&G@y{Pny0i;l4K0YWsBe794p2YQvJsy1TF(9QGujRbw^d5>$Aj)iYMSQ$e2IL)2{Vi`QF;v8X>zEf4|<-M~VZ3xD{&> z*danTy&)uS$!s(8^*$IuACgTluL0G(h>2-}m^!e^;@O4~r-w#KBnLp55EvlNmX*+Y8`fD6dhMD&Lq)Sw72XL~#y9tr9mxzI$9&o)u&&|`$M}D@c zV3+c~M-&Nt?p8asxyu>272n~<&vcixw)Z0|LqcwEbdDbJ-#)(X+t^gn#Yyu%LY_O^ zf;g-DMS4r@#Cj^9ZQC5t*IGSkUZ&gk!;Gtxhc}+ z3kgYcmGAc0{N%ckl48Ht4hcOOmspZZzX6ibC)3Uw5=mkfPVyKEpj3C`5&2lhsf ztMCfp6p*2g9SecQbLB8hT3F&_=HM_heZh26k*yx$wnLAqfFjk8EY8VLfpvcmiAXq6 zKulCYfYD51hNOWM2hMujV2!5TsT#_0EJ?f2s@Bwk*#BoMTA%w7Wev&%t4ZWYzYj101WH1<@l-YrSi&=!ezP@VLhvrY7z|jLYYbMO0vQ-+Jb4Jb zo~7hVzKcO<_Y5A~ncWCKM$I9d@@31UdWhtC=yak3+<{_OG4o_CBG~XBTuov8#%u8r z$wP|~jd?e;Af~#|7_VIjapt%B_!!~GjEY?|5C^%H5j(8~11&OH7i@KU9Bu}4F6v}g*;#z*RI~c2j!H;K#AzsxfVu_sUM34lxVY@hPgbJH<4WV| zM+4vc%S%d5E8Ea;lWiYpo8t5bN-^UK%f5}7`01~i`;CsF5%yRU?-|eWl+(JB}qz#wLA;2&9E1u710=m z?_aZI`?lCy&Od}WM^RB!&Rbs3YU9TMI1Ev|XCwWazJb3!G6D?nN$q@VYcR;@X{T|Y z0sZP1Ls|-$+0D^d$J@6u=oGa?)J>+@2Gn@L;7LuD+LG&nJjUn6%n{_n_ zfhaUQ?7uwT29fF)3a#1S{DI1v!(%KT^0d+It4|e||5Jq$)_wQkm}CM|gd%spN$Tv$ zFo^#^1fKI?0$2Tb$VN16CHP;O?DO5XXi$a9liKyAJ>SEM<=Uj?%<& z2x>k9FS?WU;DP}}EA<-9#s&sz19tzMm%W+t#nCv99VY>*4+0(fUP*taQW7Gglx4zn zxnxgPo#+yHI5`Qy3e&)jWy|y!7gM5$qShIC&w5Zj*!zC!j{6n#ZgTp~@tMlL1ivp# zzI)=G7%weXD5GM$_3U8vxK$eK{eS=J{wkQ(1RfTb$d-<35aH8vpoah$EVcXiNYvcf z+t5UZ%bS1LI*P~nEM!Te7+|_VpN%WF;ikHx$~j7ymjZ<+y1j1i$#ZkQ?Ik7G4+5u|>dUd=d z1i}xH*}ils*~Zp3q-7kG#`UteeJr%965)2c#{ZafhHdo_r|+-qk$Nx4Oqt%Af^_+5 z%A*iyw-Xs$_@lschW0C@SUcYF;Ck<5zs$GSC^(m#%ZonA{dn8X6$0I+FTmk{TZthz zBZ>Q+R>B-{8yg$*dHqm=FdvkrW>vyqS;%k8!*Gh9}=QHmO6KA7@3h!Gooqgzz-l@^VG`MAnnbH4Mt zJEHf~jBhi1G%hw38|A^Tdf?{CV-2u{hJA+1Kr!j;L}$(2Mqa#0NPvbwpSyhZdauVqnvu<@G8nWrU2txsQ#7U~z$VpvS0jGxI-Cjp37Ml1)* zVDOUI*xezA0)6BoA2$r>W)7W#}m| zIQHivPZkxqn8uK5I-()8WMd=25m#AR@}pXErA(X-Fj|$}Fk3Ks_O-D;h`*RJNuV7R#40-GhQOf<3 znR%vt>~tZ_4)irTGJ-? zS+K|IoDixElpN_A2uzLCo*Vw|Rtc3H!{f(Ca=XRF#QZjX_s%)pA-V?z$>Pglkm4S4 z{DSLm4bikpLlj3`gtbvr4scQ~niREsGnm8UOZ*ImC$Isll0Wq-7i6!$(T4(js@Gv7 z^#IFHf27ev_BIAK#`yM5CF{7w{n%nb0f9*!=%#|;Nl8h)U@#7J9ZPDrv@oi=_sLdx zR!NqSB}>M+Sp?EMwnh(SD-o7+kya?C`t6&RMoAWiT8yjP!>M%sx4^jFtTsj^fmeUe z?nu_5BjMPh9+GiVytJEIi_zA`MnQ{~@+kB>Ya`RG%WP&Jnt5rmy`=)@`@7+sJpZ{u zd8~R!`o@g}!pzF6kZD}aj!-HWjsMWO7$wNE-8gM*Yz)Js5*cR3#tsgvI-6=6olxWsco)3pT6 z_JbrV2^$FJWQOZJOiJ|e)2r7UX0Tp8$uLpjHxf|~ZM&*<-=8n}%#usy7{8>68=f?Ow*Py3v2WHRh=Bpl6@&+Hi~L#h<_+RL+jb_NgTyQyW|EZ28?*wzJV! z*4o5hKDG(*a%tc74LcD&yfBCLTm9m0yYR`(Rgp{HTCr6lRGLQx$6l#;04T3wl68CxXMEv=5lKr;}5uE}v0}ero<> z(Oq!oB{F!>tq+8Xir|KjZIt=>c|bfspJ8H%Q4x~c5c<+Dz8aO<`5JZ_4EI&p>zMPz zyVOG{0RpSa1;mstm`-7f!|Cbe+8gUZK&7FESsJ`W)HNUuiIvB%ODzUYe@=WP>Im?~ zrhZStHXGRA2R&)^$6E(SMg1^n$VGs|?^1P*DFWiQS0A0SY zwh09uIZSA~*c$fQCDy1&F#+0}rHnZl-n7oC$0W zf+4+2moCMOT&Jx7Q)d&#R6$*aqzcT#Ddiq9Fh8T%J>zILS0gE~?z*)+k#J>`L1(-~P>|Ng+I~yB_Ni3Y4q?**6H@=O5c6JqJ&S0H}$y;pP02h{xnHkhS zFiDi-nnpoNDAa{XHK2Tebl?{VF8y(<9|4|;vG#FtT7+5t${}`eQvE@ct*+V;a~j?{ ztneCZakZL#Hf}!nd3BVPQA&k@>qzjwUL%NtE_cG40^G<#=f_`*^4}Lb!u0(dzrJ-6 z6L6~ZD)^>4m~tx2Gq3lH#xENYi48S^yI6f%ewzBXBkL42&Yvl)EBtr7f*KZCXeb5{ ztcc;={WeSPYjb)w`qRmR&6|xo685Q1j~@MA81b9=y5KeHgLCrgap+aeBcpSWDXt9C zH8wV45Ti{2wsyaMLZlVE7Ln(CE5PcoeYRzWpzgu!!}_|r4B6Nqd*W9k+Mo}W`Fe0g zw7JgrufW*$g*$gbK)?}Ehu@fh0nJ|xZoLX;cydX zV2X-%6t3RpP3U|i`h>0ey;~lR~Pt`M5E4ou^>(Tn004omjqk-n=P1j($bCXmJkXP&LY5IjU2<~(wI4lsr$p+mCw?5 zf?K`;Xu+B{|AAc(h*QMgQJhUQ?Y=iRZd8C(##T^Zud4ORRKUDgZ+CZdcmG*L zBLCp)%-)D+46Bu<`Iyi8+EIVjeQeCrsV0t^(=lH-;Twa?l&yRkoBEmG<}yiXwyjrJ zT?_fgNIiL>4!d{nC?0+je^_LWux^n;xoS&NSB+Nx!T~Yr*-}4tJW+ag-0y?9di=b zsCG0k0HY9QXr(1vm*^W{k`+~ajnO;Ga%f1huFeyDPDQN%nV9|tozNC$X(N;wU+7qAvc#%>#hO|chB5B>Zi5S`FL z;}P9+9`CEY-d9&&i)vllc9-Z9$_l^u*P)&14zUL7H2V%UOh8IxSTg5>wu*H&uQi3E zUx0M5zts((q31A)j}@ZDLciK3R7_0f%6*bS@&i-rmPWD~FDx7~3ihhqly#mqR2rX; zy+V0K{Wp+3(Rn-tA#TOnwUYK>?feca0c!9M8%mdb%7HDRvbP4MX}y^mD8LX(rt?K|J^3cvXBBDRXJ?#2V*l(UiIs$LV~ zjbog4FuowDFU!Y9hXVsb)-U*t=If^ibx>3(K79J5QlbyP9TF201I8}=h(NE1A!qt; zw^}%5fEmBH)KJR7j7LgWKJ=<}o&HBvnVB3(*2+rZpqu5}+f=}4a#|NeZb5ykul}ox zrO0P<(cGlTHp9yLZ+(KFW0B$@Jaj7Vn(Qp!oJp}cMGm5AiUARt-4Cnz7)>#V zFq?W)5757~6dxyaespDv^3&DZAKh$%nB(rdFZTk)U6!r^RTLv_BjLGd$S~1lQs3Q6x=u%xmpJL{!eeBQw$JGkgJtg}ShA z39;N}b!T-eOfj!j^IBacLRx4jm|0l%#zW{SxB}P2g3fh4Fph|#gAi=E#l5<{wpAXk zP#7Z0B2PrVf1QJbpBvIVT-W^VGjq+u-9260)oq}QN%u*`z40!aQ!KRaR>KlikE_>4 zsTN=dIWlL^7zpxarL{vYB$XRHaaqN@e+&LC;;ZYn5MhC%oL~d-dTl{JL7L!R2Ww`f zr0MEWUq=@gm(9fy@((a%mRcZ9B4>Q`^yWQrMY1#y!!{MTL!-`1JZD|=n8y*sK7rBw zmYBl`?DP+p4Ce61Pk1x7)1=2{Pw-<+!z~D$p)T(3@3pTw4*s^){2Mnl(0CXsZ45qD zG9E@~tc0_>6W-ALgb~~dh=48*_HK}W$jI@Za$0VH?YX&^-n{!+^j1HbFoPXKwQ}eP zr&uvloi2UT$4s8ho}fR3pSh8NpA~@vChK*%|I%CQc^RQ66A>XnRX^FcnZqq8p7wbK z%&FlFe$=05reai-v+gt4zs53F+x|1yhYr&@HMT!;nB4Uf66$+_P(r4+_dvRYlm#4h zykPTp5Ok`Rp8p@t-ou~j|Ns9#m2@&nQiO_9Nm5G4rc@M>l@VEGi)4>7UmYO|A%yHr z_NpXFc2-uB?46bMy&do0=O6gFyf5!d?_Td0$2rgE<9Q#q+x1pFad^+W=h5n2i#+S^{6n2GBpgKBspJr=#)ud8qwwv3GPU??D z2YuWD!otj~a_pMQSH-Q|dzjtnR86I1*ahk@c)c~rU6wMeWEkdPfBS~(1&a}fkB7Th z?$e&L9@iPvF=}-FsD3E=Adye{B~y>1Yi@`;Adn42>Mg_i``N5mDqqgWRU{Ih&T}P{5rs zM_StF@bchdpRj#V(~Ym*-pP5#f{rguLzd=xcIv_Fi;El8Hyrk?wd>EWdAL-x{x)0r zWxD>0&C~1Fg)>SlhHtqDv1#4%Z)czPvZ?K5_|B9>{7sp%=YZb3{h=N$=Fg5xzWMJ$ zP^?UNFQ>lB1-boPX}zUZpHoJOb@!WEtK-vy?R6(t+ACNF?l-x~3Ds`=i{D@wD%bC5 z^SB~rK%SCskAYDURDy`pwk17!*&T}4T=JcuDLU>FUyYp#ViDE~~U3Gps;u|Sm&JvoTgX!rPI>PDR2OMvRt)^gG zk0|f^_(a%S*VHSfL#Jy|m|=NDeRcNlMRk|2VasP}x)>VAUyb!pnbE%KKRnh$O20?T zKdi?amHxJIfdA(G+|;$ET74#O(LK?}I`#@ZKGgnu)W*WhrOQJX7w?!Mt=^*7t=M(@ zM9W1+9V|0C&Iy-QteUHHA3v1M;f?=~xvnW)No|UAI$dR<5y9^^4Miqj#i4Z z_FTB*{=U?{{E&l`;Rz>6v%{7Go^)%(@6j>Fj!tbyKUO@(x9>RqykY26Fsak*%CYy^ zbwnvnnOo<=U>Crl2p^1{Z`Py7yF510Flnl%YI9(Adr^Zu6?B?e546bELpU(j4!}Q_ zeLKH@ADcf)@yaZ@yUxxc!LoVz`M)|`=5DE6cu}KO;aZlRot>AruX?iU#ilF(oGq+= z)6>%lk1^Q*MhBBZ5JG&aCPf+o{QS`SF#J;w2V_hV%XBmcYw z^}=7Qz6{Q#q`=q%6>x9ehqfK0o(ppDUb0rhZKaoeO%Zem$IH8(RrU{~3MqP+$Xy$H zMh68Xs(dH;)prNQ&y(NerxHYJFLzE7N>T{4?H*<&`uyuL4#%dBrZNrxbN-2*9Vr%K zr`jBRUu7;{8hy+$_xR>=!jJO>R5KVk7oRM%R0?6swcw} zME_77c9Suql_Z9mJMf9X3y(RH|G;SSF1nA2n(X;9FzVJ0Doo4KU54RKHFdwQmoAfy zvIJ4>5M+%C4BOecgJn}vV%c0p!zLf{T`qIdtF4%Zz@|-@~Xz<9*s$`De#L94Ais zd~%nD#%~v;6tm=8-X%cCx!)JVW*~DE zpW49_ZCe6a<6+;bZVNk6+nb3kcyA2O;hvF=*W+VGN6bcy<&5Pib{5;f$^f(i@&j1N z@5<5oVpr#+_xYTpJPq?r7ojAv$<-6y;fpr8gTD~#9#!lvzv z@E#Au$ol1F$ohk83^39BaQ87CDHa~@iPG z39lXGlQYGkX<{P9yN2bm>^F()vCOdN;bRuy9Y-U5M=;O#Cd|MM@UJf)!>{Wg9=Y*Q&}O%GT1s|9~dbQ`2tjJ(EB7SyVJE*M5O>a z1(ljYcf~ti3P}nNNl7~FxF)-c04i%oec0G2(ek%VnCroKdDz>b`{%ONY`b zi8A(-J$XUksPb0=A+BoFkH2MnuCOhQVRr85qF`K2Uw6X!)tn`dgk;~A7xB*f2@<(x zN#-YN`!((|`Bvp%JOiy4?vdNv{6JDHu0Su~ zB6fIJ-{BZQsmSb39Lo6AuqWU81YOtFCxL84yq8d+Bq#5b{(Y+b@vjxB7%VWLui>DV zjAK-o-a(K!c9lSg<%A%c@VL`#5^5tycv0fGeI?i+Nx%tdEX3>wD&^%PGx*UkdO^Jn zq>qxWEMnNVv!mlbU|Hzpii*<)SmhdE4f|T4-i87Q^H|6rR##UsDf;>IC))n2SA80W zy7UfCv+%$VKLRx%eyt}GZy<6@7{Sx~dJu3nes%BO=-&5?j>C9yy9ljZLoqL%N$ahd zA-9FAyL8?>*ZzAme?Dsc+de)E;Fn9YdU5>(e2ppZJ~{u z41gv{I!L>noJwj3VI^j*j+ql8NCdsb<*AY0*4U_U(?&Zvs+Uy1b=WiASR?!POT$il zwzuWT(|T^Alprg{a#&U-w^&=Kr{H*&V`k_ZMYr@{O|IS7zHEsMDBd= z#%n>4Y-|clFxaadl*;oMjq}?=fT%H|?uQxw_hr8*2I)P9Uj!rNftOK4qX)^_ME5^E zqbx`?Axj+T_w=VF#loEV_0A1C3b~g3zVVxuTa=G@hoFzfbM6|)uX)E%eoH0I4G&GS z0uMtdbtrP-guKn&23^iI4xkUK$c%>m2TLBi#RMH@f8zwM9_Zf`7nEVVj!8vN?tLKs z4lps<&(#SOSZD6^^tkW#$I@g>@B!c6h_xk4*G7lm+e!ay7WbVyd8PWG!`$KL990ik zrN!s|%BiS+&bX6s;ND_QD3|m4!m`~Q^-i~kEdNX|MtdoAZ43%bloL{0SiY&U7V4Y2 z$E*9dGRCjkFRVKjY$p(&Jv({VcxiPoe=kutT^0Ixi8~fNp{E$Auwo9rR1M4wNGQR} zx?Vb?4$Rm^SQNAiHz?eIU4(gnNJt9s`0H4PH^x;PV)Cd8#j8Oaql2w{wDA_#gFack z#w6v(rS2#qv!S4nP&l5i+D`a1>h5W_c_kZT+lMtd2BUR6ZA9?H;Fr!VOtCAwz#h`prC;W4LJXsJJ&5cgjourW~E;H0V_mvZ8@!~Q z7a_SPFF)ei$L{d7Ygh)%XRl`qdp1nHM zp2FGAg8&eu-1=&$tR7YBa7QV4IOEX4`)tux?SUy@vYVU&{dsbGY7WRh8+dsJMk4mmtI!{SVCI6-UP0oa3C1h_BBCKu80Yu3SNvpT%1| zl(5G~^?*qPhK(pnWOwbO@)~Q;gcAf!3<@iGfDIEDQGS>eW^;+wp)Q9eHKIuTobA9@wT&5Z0pX~Ls$-s6swb_k z?3GdEt87loP~~dd;A%@D5L7Lb7^vI(mKXc}F>d&rr#te1@k>a8v4&jI$*+mxCt0h_ zU(C4q26OoLgi0kYpvnoYy&x`Lk(GswHrewUNaWg=!=2I0Cl_L<%wRqEgF}Xkfr{|h za3q~GkaGTI+`d?L2+cp-JuAZf=TdTOQn|tL7Z^S#dB1BIdRU~TIr6>yxKTLfzN)GF zj>Q|C3qvnU-XK$&^LOQp3@dJVe)U_V=byf09aHuoj+hVs&9C_e#cUyf-KaZr!10U? z@ADc1`zW{-^s+sr*zvx~?$vAfPd*V6blZ}Htq*~rZWtm{Z}5zmeDDqx4f-IU*^KjP zWcBb$Tr0r)*+x(}eeI>3&;CmV0}7Jt$~_h9Z9yC=T>EU^r8%0tp?NJ8gLb}&W}9j% z>?g7EYw0#(8XFx4d(NWx2Vi2vMv4f5bcRj^^w%>IY^YzwmVR!N395g6QJ+5o8*uCd zLC(P}PH|@pd6L*y?gC=W_4cQeubkVacJWD&zW(XGPa)lw_|k*UBt`Z-_rGfla#K#r zsVmy+r24y~BC{V4Z?qLunY51Ef1Y3`hU+q@UrBG~3s#?f*xg)GQMPfmcI0}|jf>F- z4;SmrxdtCExE6IFp9|;GahJ9G;cWlipSh%-G;0{MdaGJ(7k`x2T(EfZRNw0Eu^vy_ zI%rVM@+H{tv7X06Qa*9p6A2}yy|SJlig1wYDXw5ZOv29wvl0KkaQ9A*k$irtMRV-Y zFHqK-)x$urU~VroaZ)f|#uVxr2YO?w`Txn8Wm^;qBL7bdaAN+#RYiuczW-GZ;0K`= z#)JyiA=X`Sr+E3gj-_;=$Qu>7tq|$@T_(WshM~H)YHCTj+reEsihoJ%xmbA?8}8O6 znn&M&bjGz(caPl@mlplgH@uAv_an?6LIZOn0YxmpCcM17Fwa6qURc=C*oZPuaXo^} z-isl{263Du1lsu;EDOfE==i2qyChw`hpH%dN{maJ>OD z=+q3=Q$kGR^(yOo<|}rUuh{i|ZVTykCA>-bLX&!we|e!=jB$&?*#)zy3?r1J^p{4t zg;s{ntUWt$KuMY>)9w^MpF10xGC+yU%&?u3lw5_Z8~a!rp@X$KaK?ZVZ*LDN0+31m z0}7+Q7;g=RDURkstM{YU*M!JLa{s!0D*>lKXl+W0nAx|~1Y=lduuse3hHMGCb;mO8 z*X(mkOx{ng#KB1fVZrX*yJ=|w?*kN?Am?P9eH(2Mrp%U5K|HXbT_0q>>H#bj7^ozP+a)EGlG;oz@cBR&h0oaNT+hgeacXNZ z=I>DQjT0oQDN}B$q_zSb5KxT)J+%iE`FM&|^i`O)^8k(WZ@wUfc25bU8v=dKATY=1 z>Ecb3$UQ6^DeqfFzsu+T&>v6@pd7Nt7|+n!I!N~w+EHwLq!o`RCN#>Go$j`iBIqqr zXLfJ=Uj6mgfH$JoYFJQOX)bQ@-egbrqu<+;jHfnQqw}VO`NSP9t7G<3*3LdrH}%(j zbf>z0b$ItWBdy7*6qGh@N}B1)tDSZm4ecBK;&T`B?zpU2-ka*XrIPntTVyS@Li*SE zC7Lc^a4<sPOm?ial&&xXMQMgx?^P>^J72>OqWj>Z}@NB+SiD(A1NB102{ zxY3alS#PboE3G3@rjw;&<^a6nJ^9-`l(!{+*UK|xtulG2lVW)Zf|rLw^2+o!W@a?= zkxhJlJjDvEI?2knL+<>T((bFcUJZ|>icCHvz)N7#|;V(aM}(6IwLcCj)}JswO+l_TTXlx1YXiVnSu80H?V0k-+#l-qsk` zMMMn<3jX(Ry~&`4Gc+M4W=qeNu3cBF{<(>SCqNh7bQ6eHfs`%Jn(tf-0$4geJ#M=e zkD4g_$gkqA2E-nVZhBux8*e`4n%Yt(cz%yb-vN^;ipOV#waP#5_o&Jr>o|Fp?X}Le zaWSjo%|#LCMdr-J-8sw;^_fBF>kcr8Qow9I=f?G z!0`M(wCI`bSbH_ip*iUJ;C8#B1aP{`M~v14P25b3LYw&N&eCv&1w@^YX*T0Wml`)> zrlMj9lY(G)0tjwjpJ8es;Q7E^cbO^WH(!JO0xsq?*GKSWI zyQJ`*k^llE%$eGmYC#+xAKtW&p*BfL8MY5=Bs1k~zI;73{_pYYh5Bk)@g8h4;f7lX z`$N0N9|STapJxhR99J@Y3hknUyzwB1%)f}Pp8hL1x5JNL3%b{F=c+c`+V?snOnnNn zz1Z+9EU0AMS((*Pb@p41&3(dS3_O_qcKKXVQTeW3@|SU&pXi+hdb0XwtodsIO(phy zHb>k#GzSzc)9EN;+Q&i%IRFE)-NNi}qvdb2DFh8@knu;yl z09nhXI*`LSPeYMUCv$x#;kIMPn=+llwLZnBhiCTh7UmBro43vK)9X9^IM&#tW{^te z)?=-;n+-c;g2scQ_1)PoOc>oYGU$?kQNgNpYNW%M%S=fEfRJEYXV_V%)r#2pBNCd7(5rAaGIOLvZom1 zMfeaFQ0I>71#w_iPP==zX?R;pZX(>CttuD+qOpZe673&YR5)%IqD@Qz9)gPqdRt6> zz-CgPPkL_X;OuN>Z+~0y<>Vw;IOM!O@rnN^QCBF^b^7OrZ{OZE8Lc*lYC};t)|2FC zL91t1^5Hf$;QW_k8E|HQK8cOD5R0JP)iAj!P~X&aV7P>IiQu5Y-GKquroD zofE+>pved_(gq$|A3CUQlarJ2`g%(p&IijL6m@)8;%wM3+1X*x}!kUCmDVg?9nxwnbk#a2Z3^MR3wR)n9DLBNHHg zR^_X0{2GmZQ!QRTAIk2;cXwxIO*k)2*Pq$eZ@V+xGS~YUHFO+yMSkd_^*jrpV#^4; zOvcR8iVgn`LX(P@>_v@H6cZ3kkru607}!ovuR(eAnT+RDyK|%E9n7D2{j@Y%ATsDO zg=-IfhqDPNnD_7BqqxDmg4r!9{d&tgD3+{?(l`O>!V~tozMWhP=92Sc?L2piJ^f#+ z2#9&7zfBn(9{v)4Jru@419hy5#(HAq^vyq~EO_tEFSLDAeWB=|&(L~nQFgSc!krhv z`b_zvlEgDNqc=QuZx~AcY&yTibjfILWZt|rz>a5zCx(QUE#)zCm4Lq>;TYR ztGCeMAv)2r?c&-R2*Ma1;btcM7jlR z-zuj99sD(VvuB5Nh%pIY80suudJv^ZkFSIzB;{QsF{sEh{25vvAt`5w*EARFOl@3c zcVz_3!(tsO+y3vDDn}y%=j77z|Te}4wAwl zA)BO_81Q5t2(C_8Zm2=RII5yea)0BNFv)v8h6kV!BcZ41QenTnO!cD$Z z=PjD<<%Edd_N#Ama~ou=(W)r6!X&ZPrr8dC7MrCd=}7oP1iq?~Q*o8s*_x^w;fB${n3J7?siU!R5}AvclQ%1PStrcyz2wn3orlX;3=*oWkUE6fCFFY~Xf?NY;8n7iT&kjR5YuQ(h?M_omOKxv^W!A)4n1R2o zeTh)%9m3;qt<&GFN%>KttaKl(RXDkRe-4}3XA*wo=`;BX$=E$|Di_pai)i^iIT?tp zh1w|{4fbmm$gh=HZ5cRid$d4t7CY=fiYxa!*nH?7KR@=SrO?qOAQ8i5zg(8vE#Yaj zPEpXIU#<2BF5@*c?{KBVVG-Q`djb#;rC>v~7LRv)=BTyi35D8qV&WiwL>)$`&g(1! zWLSrR`aLE}HRq36k7mrSx{Ms~a4owpfn8e?*9j$-V>A>5lW^{~+x3N#ThGX;D9af9 zWl4%1WxcLuaQZ0MSkLpDDhEDh>@Je?_S;)hE9KQ1A@GHrg6;dB@P=4@CXwe1+W^1$ z^z;R=c>yvoU&cLT7qqo0k+u))Reh@39KC_)!?lB2nSb(s<%DUbGS|~s0z1}+=jTr?)N`uAe@A!mSD- zy>a+{X)8RFgFek$uz8y+EqODN>ywa8b1gLT!G`g<53#Im&+H@m9EJJb^j00E(=JSD zj=z8kDkWtd*%uz8;sE!PXAzJkyR&hh?g+qE+GDepF{_gx)($ocPnGTssNQv0thucX zo0UhC-9A(0D;X^#*a`_c_rW0IEE0v=EKxoZxTg702{`wE^Q;5h3Ur2j(M$_j=i2Lxd-o<}o+S8D(qnoFM5XD+783 zydeQQB3eq!i;^Y<|8WjlgDV9+sBaYkophm=#mfxb%HT~C6cm-E6)9ri6Vl_QmLz!V zA{A{?Is;OT8T;=32dtuw-}@i54c+-_(2U(D5+@uTq1buIijAm!)DEcFugBxW6N$dc z63s@$-Xn**H>*Q7JON%?nIOVrkd7I)lMz`4c^caZc(tjmr0ZWZs5Yz0E=1pr*k~v+ z5FWD;bGWw=@YyPUF~nAxziZ(2<)+e zoT)w>^B^lRu^>75sqQN*IbvcYzkAz@fVBk%0*p-*) z&cQi{6&Hha=Y&647CU`j{g;xmxoh#1!WLDIu7Z^rCz7Uy#-dFwl91?Z_JPuE|87YKFAWwR(+aa_* zM>9)jzm`mu@Aa+H(CmUZ09%iUh}xlt!(M0$31`K{1J+rd^1T`JG07vF$>$vYmZC*G zb0x??UmvO;2I+lNtH9%Ge7=ibjZ!_N={yE44AR6Hj;EM-mfsd+r6gc;H3#m#s~hc2 zYK_DcNIt;oY0s#Se6N z@#RhxPnY7_i@W!0^{uhz8@eo3u&&0Dv?gaKg!ivCW8*{aMtC0hUE6G9@MqTl|80+1 zoVx|Om@d-W|DgR75#Kv>UvJe+E+fs(m)r^+2>IjQcCBMS?T=V?O^+z`jgi>RpOxQA zw4SKUE)ZwkD0=(fSkHcPJ9YYVgI44kksuNHJupG(%!GYqT5g)Y6TjZL|M6vDROD{@ ztzQrN-CPm7!J|oExqgU9`2ILyYqC~p%UBb`KXHZc(BVTIg zGTB_j$+dMbB_C%;*-hF@Wk>ger-wFMW$>(^Lm z#wZYdraG*{xBl^|NfmD`Ulz}_Q#w0-vFJd_ETh`SWse`tClEa{oJNzbnW;+d)oziv z7`@ynTmPL`NuE8Nx!TljyD?hR7TO*!gC%fuWMrA+ya@+=SVV-Wg$00HNRynfjTUvv z2T97rL^g;cu4xH%EV)#jDCk3O7VgsP@wZTe-I>bQXOTI1v%BJ2ZwY~DQWoK^+aU8S zdO3BhFLlQxhvd8f>j>>ZBil#AMiwqz|({ajt2uu`&w0M_B8RwbXy5q-XEOn+F2)2pS0lS#p-BT7LjvWg24WLv=11y@_h@h zYf@IpZ<^|{zdDIcb1W+xir<@NFQ01S1J3QGFIJ|a>N5wP(BVy}J?Dw-QHY_jV{k6L zC$d1L6%UoYN=XUg;3B~`NhaJWe4o8;M~oq-!hv0^vrkO5LQq8oW-ErlJuHVSRpX73 z9+bmq?&x;Q&q`8gD zlc5V6&_L5rR)UZ+ZYwMk1Re#9M|ecLRsb=A`c9mV*vN<2KWJV6i+y(GXysY7%H#?m zDylbIzqZ*U)}12E}g2AkMo#-HiPn^hN6x1CVTcA{~RRp@Mpzl0J)kw&@- z*PxeI)vaVm5nD|jmh=GWQa-~wu1g^aeLGJ1&|3K83%am>IpFOO8+}l&g(BuSsrkOo z*e&2Z4b*MrG{!FMa9#W~wb8%qw(rchEd*J;nTZwgf2vbLKD213(v@O<0e4y5IlPMguP4 z9ByR)>yNtV-@IukqBc2xg)bOKf*gr&t?f86XvA0iC)z3(N+|isOlH1L9=JP|CKA6NXO3p-d^V zhJWpECRMbS%{|q|#GI3ax*wS*HbO+*WMvI4ts99gi2550Ni&D4!}`b`+?H-iO1WfQ z1sNz^V9~?M%KCSC6-Yt;up5NOP|wLHlKC#*zmGo;CtF-Qkg>z@l-cOBXTA4Pk^{apkg%rCwb9e*0Z@dpcQLP{!(5_lXQzd?=@@0fqcjey#sQ%dGfq=5h2iH-JL9FqQsl^u@D(P*vlB-avO0eN{ zsvfv#Kh5UZhy@mpXlA=wdK;COZ;inb1R6SvIae4RAjQ6xAHqqEPK85e_n~L5vo$9$ zUU_<1gx>hBx+;=9ZN{gUS9wBP49>Oi9i*W&4o_A!ov}B&cklDmZVY-gcS@0?U3-d; z)mrQHm0$AN1pt8pXK7)fprF0Jy?w{j;?+36o9`eh0=*8>&{DHE{(VzAbD0ZG2}(m^ zC9M}j$}1-H>_Thj^D!BJpqWfTZ!Ei?b%?6}w!^%v?&M3}`lKtoGzmho}s;~cdG9+O{f~~Qm<1#t0Xx{E!=eTOB`H0?!yPSxYDkW0%w}49o$*^lh zMY(luC&Z=Op1l7kl@s*^%MtGVwarR}mHjy4(3ImW3~XOLZ?T0#1}ZFsN@d?B5E!J7 zy`YAmNrr>|i3A<3DYSXOj;_-q-UyX$%+J-;*POBqzkYtmC#a}k^@s+D??)qi9yUT- z2%MuxWwEwS$;+FN(YGT=a59*fnmV<4jBg=WylD8?N!q%ba4nQEdcNy)?mGJ!NxFG0 z#_xHRXTLSe+xV{-8lN|jkvdIyjNI@%{Qy1N$K~@UD@eKxFVdVfw69!!NR!9!@|&~b zweG9xjW87kbl<1BxwE(-%N;8yJthP3EA$0JbJ%tB9WqwGiK-)qyM=FX-f?WIa8!?U zB<}g1$X%)vijepyGK3i3;SmrD%+x~kSqhaWT!_%7L)%gWF-qONOUpZl!>*b+ljqL6 zGx$ByWmQ_i6q1c+B~qZ>BuIn|+KY(sFJK-5vF{Cp8|%HgP(D!LeM9Ixspr_d!%ZE( zmYrFr{bHX??)+SGx8t7l_l-Lq1cqRXZ$ongg2#vHrJd(Vv4C4G_Bu_a7xbc9+$+6D zG)z8G3}DmT`}F~Ar~gk2(5HF(c9M_F_2d*#01k^VwT{ukqqn>Ep^D0d{l0zy0obCV z9}(eJfnmL~Gt6G(k983j% zMs%yWmz@_H=E}#bH`go+i*H;rwRj->zj3@}z( zM-Ud=ym=-?Ki?W#0!m7rzA~rkv3bNrivQWsZP7?^5`Y-8Y#&o5U@{Sq33GZS|N8gu z-~CuTz#hxA%p?!gI;lJEQLrOj9CsWyY!FaHpvbqMk%El&X2YS-T9l-D8V=2mTvunV zBGk5vk>_>A!y#M#i1fs7=DdRaKIYq(_OnxjuN16sKRE zx%xFzB(258)pZ@a9^{2$`UzuL{^&WnkEhfqu>$}e6@T%gyLnTKUxyqB{`0*aH8X>T~S&z~Fhp$8^U8}FC@&7xkaqyC@UAyJ{JSD7om zeGJdJ(MN5E4@)cV@)@WTs95(n?7T7Bdg{fCy?Cakw+Zwvg}0jA>7+&+EcpUQ&E~LC0{hNXE;h*CVn{8D%>o=dqsxryuAo)vm875On&jIVKJ)Xx*Uw<_D*-GlyW-1 zVNeGwK=ola8g@Wte>Z23YZCsqX(WhIcO&JkHV1X55uezdkI0C`ZthI!^skkvXxyxR zmydvw58;3o|DSz^t-#hrIg=U+<zHNGrwSMq085o+G&x@!v&nzJUdF>j5d%Xxh0!JMv7>l9{%c8 zA>_Ew7fnqK31-|%Cc59vzJ18;4VPRC)EomdM&TeZsA1z-hCgdy_A&^5E=R8J6Mq?+3B`yL;9ie&l!ajT8RJhBq=Z8=vmKw6eJ9R3`TN zkH6?va?u0aJTe3>UJPy+f_2YfmLr8BtYHXop0I8;dm_R?<`(z$?6i6a{nPVz?7o6q zA@as?jKlJ8jr&*JAuofWz9FwKe_9un-*x0f?~4Jij10Xwe+eg5W#!`eFd8nQr*N?V zN#U!r3S-vci`H-R^2}S$r?P#=_7hDAcR0II>-&QQDCnlTi(KHjjEaf^E5;sL-t4#p z2!6)@&5z;zz+@J8fxxcr=>In37KX>h0A@2XBJSI>6~imH#mPwMZ4j<|@S^qip}B$g z2UoQ4dqvh;7x4+&10;jD7yvGhn?7%cO!LCEEtifgeuC1Ji3Wsg?%Fn!)_?uQ7k-{0 z@4;N93MQ)1#fvA2TbInV2Ed#A*#F?trI!POON*^)!Ph>4 zX^6i;p5BN}Di65Scw_9v`dV9|1|hoNew2-U_son{T+_Sz4Tq-H=PM0s@9y@@( zo23mVvvXn4AOXKTNYzBsIDZLcWfXZGgpe?@b78OGPiOLc-kes&>Iq#6h{CHm#bylB zz)n@)6hT8qUhLY7tRoZ}KLA$7wzhnPj;yWpZL9-ZA4gW&nVOi;levpXqoMnRPE&0^ zhvd#SczF~!$+2*wI0#GAI1wO#g9(T-WIs@CmsZ4%8V~nSZ6^f=T(89?b}f8$bIvge zFUd)1+=17)@9e9#dLqHb?DtD-z)+!K5Vjok5OCq3d_zmBnmP|Y`0#TKCPW(ItoZ=A zzpat&!y%K~`MsxXWoBsMWBzW0CSm3q9!|E^VExtH49WrUrQ~M?piK_5UU;?`o+(1I zQ)liJX3#*buj__t?Z8KJCDV~l z$$mL(IH|NU=LL>+&WC@kRPit44R#r8OGiEP2wI^nnfG!X07wHF;iB7mH-2pL6&g0^E?sW z`y@f8{_lf(c8<1Ja;+Yo7q%;5Of4i<$!N&fQB{pHoM6O%8%TwB5GRaoOqum{^o~SL z-h1(8%fV~*bRh(y8t0VUk3{ht77uUJ=a=ZEf39rzc(PU7u#6j__shMDMr07nPeJ(* zNSqK{<>s~lJJk<g?~^A_qR0Yl&rCM69SI46VEm1Y zfSNre=#K?VjKc8m8YS#-KYzaAz!Qq~8;~xyMjnOQFi7vDaL36Y1RJ1`iVCm}95S%V zirJ1A;vDuWndjQf$6jG3XIFHN&07?E3d*$z-z*-R|2I233pmirkdX8?y0@IN5yl#~ zhyOVAn(^myYwGBbA^euUb@1|ev0V!>#I;kt(lcezYHsfNtaec7(WuRxJ3XKI8KhAP zp4QdT)I_LK^N5_P`hE3FR#v9zw~ogzM_UkTkE6z*jVI*y=@80Iafbo)7CD)z`$UZwoUWS9haG3 z@u-o31e?qhY0xQrz|QRVNpFUn()zYa?~zbsaBy&h#pt7g_62OC-k%3%b7-0I)}Pnl z1BEl2507eS)O^7y+uHXs*rE1oX_qD7p=!#?s>*2>K0<^Q8$_tQ!Fr%V!2M8x-KgVdMZ~iKf)Sw9a0ra-n|3y`!_j$J=;R` z0yGMpoynZCn+`(`nOAioqNO(5He;BOsbqH+PGn)g9Tw^kX&8YY?GQnUhBZL$de8SL z=28sg@b&)C{{^fNGIwkucJY_?qVMg&UblB8an+`jc9lsG+1=TARdLnhA8Kc~(3j{x zQ}LWlSZDkkyxwx8+R(Rdq83X z|FKxynoXHdUZ`vB;9MY#;GUm@KTc$?BvK1a*`5&d%FegW3_tTEH06j&mB}H)aiZq| zdi6Q|YoY!UG|6RN%ARw-oVV)l8z^+|N820$h&tk*$?olehzk?$QDL2aNuD@ zMk%5$)FbX3a~#<>?%sILA$d!u`>)CeiZB%gvlm`^w%eZMP)S9LNjuC0xh(Ae8XZcr z_i|LJMrM8J=(>T>xr8K#>;ZuUH^zDj6{hps2n44dv8O$Iq%KEM+##}i<`K_Y7GF+V z7PtJZxZiH#%S#c5kOxs^CQO5JYT5@q{4H(mCT-7%&DShh9<7SLkSD*2zjJa_f7EiL8J+8Dgw!B;$ud@!|vGMV=z76B^ zuoOF%RraSPB_Y4OUC%;V(fz~wZ{N%~8ITYN;W^|;L!wdSh`pd7dUGWZ^M>Xi9P^Qi zF*tXU=&s#>OBn|T?kJF5KQ7KZD#*!c?(548e!lrGk%t6o4n?7cv>Ggm451+*fR=+Z z2kETQ`e zI2_<8;$>Mjzb`xi>lq^BzI}N}2M%_)=ub_t3X)S1#`ma7$H-qSM&bJ@0(JkQS3zed zEq%!IKPc@{*8=x9JYRtLUjhHhEyxvM^`9F$)5v!)pc*t_tld36C^$F(&=gNry@l3d z0gw#D6ZnZYj>EA8M>1-ExFa#7(58Cz-WL(H(D-4E@bzmc-dvWTN5`?j-XL2t!+Qvjz{Uy!2kND;IltgnDr)<9qY{Br zo8t*EkoQQTvYV>d=sIyT>RW^OphHwFXIxVUe&&xKDS1PJ^%J(#?q`u(mXibh2lswz ztwsV%atc5W=ms#H#f!+#%VYOUQym*=xev3@+RV((%!1OQqUErB8M;qi)Rz1L0#nNc z-G}r3OTW+QE-ZsSfWJ&VM;W zily1T8$si!-e6US81QyTd{a~4ZSM8IBi-0I@!0VONcNI*dxq{lGgL?S4yg-G?%dlF zG6&Na`3WKo(7%2+IA>~wy9(+_b!}}6OH1^&pn?pz?0x!acJ`dK;=XXMtmB| z$`ltay;nu;kMJaebG9XySXf@v{Xypj36xRz*Lx39jYrB?+6>8UM(q;VJ(Y2)VMZ!R z7u%G?Ns>>T3nQ&zpBd!YH|6%vMm|v=S37(KfN4fW{aYFc(8_T4bFh))355e1Lj$`a zD!^a!zq-SF1QBb0|DJ-b9!%0z{yR*hz%~Fm-H#s0;Z#xWANc+I(?E5=fh&FAZSJ;U zWEzr!h2~uZ0`)(5i6GF(JxOKv-^m7|{lVSJ?DyGYhj4VVGOV~ic(x6ADONm34|xH zN*6L6;U!Te1;}k%Mud>CgG8&F4d!u8x7E~!7FPgwL;`wAalH_$zh4c+M!2OR)p;o! zFCQhMe(P3_@i#aJz+Qz&kCO@)I2X({fBDz~uYqMQcjHE(RtDh5mXDaTP=i6Ye2t^I zqhl$p(qwVBips;H*9`@%zE$u+4G;gvM#O*!!iLn$qn`zt{m|a4DgIXG5)~E(Oc`i7 ze)j{9{Zuo@XCO_v5}t4=22)gw<696wIYx?o+k%-L^*y%o}JtCVW8{KC?oIUhA)4P$LuF(^G|KK>t+=y@`pV58gNPUkWyV~C4k%gI? zu+R@$@A*6}TtCs#n1A{=42=yB3*s0{=(Lem#I6@d7(UVaLoDP$6jP!8Gnn}B;wpX0 zAi~2V1fqiu8zsyxF!6%M=)#3OP(H0osH2S!FYcCBjBOf*7@O#hNB-050c`8>Pc@vj zdh^O3-&d3BU`5N)Wza61&9ZK4@^dc z4jv`x0W94Q#>yQT85sn+3q^()USy)FtFMReDKjDA&#$WMI1ruENDTjbKP2hAyu9Fn zAOjc~1FE+@k$@2T^y$1#GvUK|u#Pv(kr!L2EU>X1sE)%>yR7$&+j4Cx^}n=_Okn zrJ@H?IcVWauY$+JQAE0L zZE}%95;)r*ufh;`*^bWH?O&ps2_G}N^Num2om)Mhf4GZGr=dCc10dY8L}(t?KTyx` zOS=v|jC8=HdHv(^93zL!H-j9Ao6N?oFrapGtBB=vTdmf~OiB51ed^L?o$jo0@)nD( zJFjh#mWIos0w-uS@93Wh_q}W~DzzR2W&fVNJqoblk6r=-7F+Z|TEbfJCP)N4ZpgJyZcCLFfa;JNI_uxV36bD)+`IQ@2g2m2sHifuiqMs3jofIv&Fu*A=En5l zUg!q96ztg8 zt?Zt>V)oNNzbkjOrXGf8?oP#I(Sv@ur2cJfF`T3!1OZAb;yA|_&J^TW05T6d_34&B z))xh|6IP^z4(ey0-E1)N57$@RHY%XH#ReLw_Y2MM)Ls!#wEcYe?m)anfhInja|a~p zqV!=~)F4Yfjs^~okAEw!(0hhIAxP^5RPF+OrS?nHfpSN!pINcdk#lzPOG{VVG<7c8 zXFY8BRGNR}9~5oaYmgVkC|2dJe!~IP+v|fhHl|U(u|&-)%5BItUD-hvk!+Ukhz-Gn z4eaYP$6h?e;M^cL%nzHJx11?B0F#r|={%o4ed=fWecNNYBV=VU(Uv;(Nf-%D&@?uXSZj(*JO5-P3916VX;)dApx;FrSlJE0Cr zNSvt=H3sGXS~FFYl`X=DgULI?*vu(KNy!hGGEq}e<)7r;$&dZ8NuHy~A#Yt2-Io=A zq{M4yB2|q+8Xrt(wjiNn+qEEW?!Tz_`uh=9N?x;C2>i1deaI17i$>!`&EM{>c6vG# z&Gjc1Z>Kkl=A9>`ow&|fl31U=#s0$C&l;`b;R(^kiYm{nAf|ZeAAi};enz3w+pA4ZYFI|nRL47Yp~xtZglB)t z7#d0|DYd8`dXvO|qn3<_5juNl;YZr}<{xDSUEnkAlMvjA?xJNTGXp4lRvc7&C z|A{>G==jW>l;Fp^#-Ya_fopzjl)Tlr#sS|j|CoC^y0nP8`i6#6B)tORXP?W?-=!R! zu(vaJEk<#{^9H^8#1fKc$WkjshIp4b*qJ%0WP+xpX~cFMeS3|w^uN!OvFH1GCwd9f z)&RxnaTwN`4XeIWk0Qll**u}CM%`W6pT%9vA2AWem4?7qTs7fyyPz?c_#PekQ-L>;>6y)dqpfk&GMq3x~s$>ec{3d zQRj}5oSct2lp%(BN2Fa`0x+X>Ab%|v@2$YAnwiPA5qgNJAj-GA`?Ei6KuZ5IcLfbp z3Qo|-@bK5+;auE*u?=eAwOOaGAtkL%kNWfu8kY*=yYuo|&HR$~A2|Ct+Idz6-&IbI zw({)L%fAK!#IX=bNl%aGXGR?n!eLNBE9GfzVcNBvj z;8Whc8^=x%<+oa3nNPubwMVuB`$PQm6z8l3`UIZ*)=!!tK zIlJm|F#XDJj~$CANmP{Nhb{E72?+~d0*`9EJw&8{n1=qTy_}q1K7V@O)L~|3ba_T5 zLKqzt)zKH-6+bGIgU3XCymX1h!f?}3u-I~bVROV>c~(xYj>V%G*Be5}tJQAK*ir4XH=1o%f5Fnw(~P|M}nM2+lp{SP)lTfGTy!NHu%rd?GB$iUS8J4W$WKu)U?QA z*YbF{+)?2+F4q~I*rl0Za{oLxMz$frqBt|qVV1&UUO!HAGr1$q0Cxt|KiK4+zuiu( z(@=fNsVYD3cGV8R9=Il{w z1j$^iMbvTHXDWH}!%f^*Y|1jm24%nE3h(K9F`pmxFF_G)Uo#Ek0<7&fc}q0*l7ux=q11r;7eH9=Qz z?hfXSR4CZEv&y6fdGLgwRKX-2r+_yb2GZVwzOUItG zpnEI|(L*Lnw>+d-fy_A+sDOGxp&wpb8=)3UDSG#i!&7SppMsu-#tTY9+j%8_LN#w9 zi_l})LzCv;_i2z7J_r%RCwoYbJ{BClvw7e0;_tCb8|ayDYd_}Y6;?4= z&0k1dU11z)*neeVk&r++-|y(APD!W-1)CjZ)?p&(mCZ9=;^H4UCo?bmrR zw|CIqpk_d*L_Usj?z$?$UW21}+m9cam=M_Eht1#(nt7P;0z-(owjUP^RotqoB5|%L z$ZGzTI6mmZMRm(Gnb3}=>E)a(^bmiNvx%^sTI>k&u&w|?x?U%wQx?_J{8+zB{z$~_ z&Fd@iv?y-Jt%$Ug^uTbO&9e41@3c1qR=hUeuY|4>F7*8#-@er8`g5)>u`7{VA;1{* z+G-kYv;Eg-4RkDXL;Vdd^0&UA3>>c_8M5+QZM1DPN#Sj4ookbc;I4Ii zP-%oZ*pia-?IU#wo95fUCg*J$laDFT(QLfv@i1q^XCzEo;*)h{aYH|%iJKiAaZ7p+yuyv`D z7fkCv0^4Xx-U(mN4w-C|jr;#~4<421Uz)sOPmow~vp#Z_uJldex5$Z00_!D&*9lQ3;6Q%w6fzlGnUh*4s2|HfuVo zC(!RK4o-ZnnYP7A1~TKKBGi*uD?I%8@d7miEK#HtU4@9;w>G`+mW1RC|26}oYtOeQ zB$jnI2LyJxaysS-)bv)7k<{0$!78$*RGLIupSKg z$|-3`-CJYljVrpDjJg<5p{bx#XX0`S(gwQoKVIWm76@+)pyClTTpCvf zGD|zi*R<6hYl*Pe`oC+?_`8XQ=Gkd+wJSaBBA+WS8D84bAAMu(cMI)HyS8Vme)ZeV zF*@o?rfHT0b%3w#X`27(um{Dm#wQrL8ihcLPDU6F)xX){d`m7QpIFxh{06P^(5U+p z=Hrw^(~uv<+ip*vT{shsbo=)AgmpB6yVsUu$_;8>a&y0~GS6kuga|AcrJ(UTA0sas zF$SH3%(p=oU{Ppm!vl%LE?onIYF6ZpA`d|%q2+xqYY$dx`ddprGIc|sojS+5#m0Ka z`jhU~^{xdzqTPLzB9W*p*1yiEc#Yiaz6J&v32R)i(2yk?Dnhu2fk;xs{Yw7juPh{t&@MZ#=h64C$b~Y$IO!JA&)GBL_e^1L6J@}%T zS9W9ni+qgVi@)BJZQN354;6R${Z8Svv|;A8>yrM* z4D|G{h*n~;eQz?qGFxGvuSF@lJimQyuc|=E90pTK4#RzZlR}VV9UB`E8*oCOBxY$0 zwN+K8VOL^d0a!EYFRRKN$<4?y0!D%MhL>~~v`F3E*NGSLV%cl3pz+dIXF?I>_V;1E z1#BmfoZ-0+{2JPF0Nl-277txwCIe5_(*t_GVcja!G;iMm`)98~L~{9KDeoL8<8YQE zZ}BM~rr1f3*R-;z1O|R)t4axR6pGW+x{qXRk%VY2AK&=RJrQqqqks*nWV}_FsK9t2 zUZB^zf8-Dqj;5Krm%@AJ%mMc86(a|qf?@St@dBqIQgDbPT$k89HZ~069YAPPQh0 zR10AL=|`8ewITFu9G*UO=unm|!jo#v;**m(_wB=C2-eD6+y8F8MRDs$Rz3Un!!;8S zIOFp~3wrP9;4uHLU_d&8#81Tl@+lNxc2{?w)`~%Ls5MWZnosI8-x+8S}>Cp*0&eM=9~s2FTpMORmQds(}bhwa)u56Bt9ssjn49UJZ{Xmb!#5QWzEgG+g`Mm93hkY`}$&HW8ISk z`T4!W5ffp^&?zYo zAD&{X^dqcgk|TOvkq2rO=-u$Hw-)x>h%C05l{PCfzc&*)iA{5LMJgx-hLhKnS107o zM?O~PgK!<&#@sVkBcq2YDZhb#T9;8f5rfu@JP3ed6MYG)Qe0dd1WWSp$@V(%fs#p= z$UuTE6RdY5p-M_j3|ZRs7GNIGvo*X4F--<|daa`|s;FXx%pcG=S*0bikxHeMb>gIr zgT!TKTFR;P0c#Hc0m$_3-Ma^Q8G5Rlu1j!)S1gXn%z2d~53ANpEKCCWEvToO<%1yg z*j+JqtD%?}WP*o(oFesL^_u(J_68wf7@#>P=E#%C26v*z9+C!2T*{t%oV|r4jr^2LFD3cx zM}m1gcST97c8-R5W!~Q}&{XRFdV8-z?eP$@c8c?Yr2E$W{+8?6rvJ^Aiozv-?w^*&)!Cq*NGFQuU>Vuwdoib z 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 literal 0 HcmV?d00001 diff --git a/vignettes/vis-xmaps.Rmd b/vignettes/vis-xmaps.Rmd new file mode 100644 index 0000000..4635e54 --- /dev/null +++ b/vignettes/vis-xmaps.Rmd @@ -0,0 +1,255 @@ +--- +title: "Visualising Crossmap Transformations" +output: + rmarkdown::html_vignette: + toc: yes +vignette: > + %\VignetteIndexEntry{Visualising Crossmap Transformations} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +## Alternative representations of Crossmaps + +Crossmaps aim to encode dataset integration and harmonisation choices separately to the code used to apply those such designs to data. It follows that visualisations and plots of the candidate crossmaps could be useful during the design process. For instance, Sankey diagrams are sometimes used to visualise schema crosswalks. + +This article provides a few ggplot2 code examples for visualising crossmaps. The package will offer functions for generating these visualisations from `xmap` objects in future releases. + +```{r message=FALSE} +library(ggplot2) +library(dplyr) +library(stringr) +library(patchwork) +library(ggbump) +library(xmap) +``` + +### Table + +Let's start with visualising a section of the ANZSCO22 to ISCO8 crosswalk published by the Australian Bureau of Statistics: + +```{r} +anzsco_cw <- tibble::tribble( + ~anzsco22, ~anzsco22_descr, ~isco8, ~partial, ~isco8_descr, + "111111", "Chief Executive or Managing Director", "1112", "p", "Senior government officials", + "111111", "Chief Executive or Managing Director", "1114", "p", "Senior officials of special-interest organizations", + "111111", "Chief Executive or Managing Director", "1120", "p", "Managing directors and chief executives", + "111211", "Corporate General Manager", "1112", "p", "Senior government officials", + "111211", "Corporate General Manager", "1114", "p", "Senior officials of special-interest organizations", + "111211", "Corporate General Manager", "1120", "p", "Managing directors and chief executives", + "111212", "Defence Force Senior Officer", "0110", "p", "Commissioned armed forces officers", + "111311", "Local Government Legislator", "1111", "p", "Legislators", + "111312", "Member of Parliament", "1111", "p", "Legislators", + "111399", "Legislators nec", "1111", "p", "Legislators" + ) + +links <- anzsco_cw |> + dplyr::group_by(anzsco22) |> + dplyr::summarise(n_dest = dplyr::n_distinct(isco8)) |> + dplyr::ungroup() |> + dplyr::transmute(anzsco22, weight = 1/n_dest) |> + dplyr::left_join(anzsco_cw, by = "anzsco22") + +## get code tables +table_anzsco <- anzsco_cw |> + dplyr::distinct(anzsco22, anzsco22_descr) +table_isco8 <- anzsco_cw |> + dplyr::distinct(isco8, isco8_descr) + +## make xmap +anzsco_xmap <- links |> + as_xmap_df(anzsco22, isco8, weight) +``` + +The included `print()` method for `xmap_df` objects: + +```{r} +print(anzsco_xmap) +``` + +### Bigraph + +Visualisation as a bigraph is particularly useful for seeing the relations between the two nomenclature. + +```{r message=FALSE} +.bigraph_add_link_style <- function(edges, x_attrs, ...) { + ## generate out link type + style_out_case <- tibble::tribble( + ~out_case, ~line_type, ~font_type, + "unit_out", "solid", "bold", + "frac_out", "dashed", "italic") + edges |> + dplyr::mutate(out_case = dplyr::case_when(.data[[x_attrs$col_weights]] == 1 ~ "unit_out", + .data[[x_attrs$col_weights]] < 1 ~ "frac_out")) |> + dplyr::left_join(style_out_case, + by = "out_case") |> + dplyr::ungroup() +} + +.bigraph_add_node_positions <- function(edges, x_attrs, pos_from, pos_to, ...) { + ## attach node positions + edges |> + dplyr::left_join(pos_from, by = setNames("from_set", x_attrs$col_from)) |> + dplyr::left_join(pos_to, by = setNames("to_set", x_attrs$col_to)) |> + dplyr::mutate(from_x = 0, + to_x = 5) |> + dplyr::mutate(idx = dplyr::row_number()) +} + +plt_xmap_bigraph <- function(x, ...) { + stopifnot(is_xmap_df(x)) + x_attrs <- attributes(x) + edges_short <- tibble::as_tibble(x) + + df_out_style <- .bigraph_add_link_style(edges_short, x_attrs) + + ## generate node positions + from_nodes <- tibble::tibble(from_set = x_attrs$from_set) |> + dplyr::mutate(from_y = dplyr::row_number()) + to_nodes <- tibble::tibble(to_set = unique(x[[x_attrs$col_to]])) |> + dplyr::mutate(to_y = dplyr::row_number() - 1 + 0.5) + + df_gg <- .bigraph_add_node_positions(df_out_style, x_attrs, + from_nodes, to_nodes) + ## build ggplot + ggplot2::ggplot(data = df_gg, + aes(x = from_x, xend = to_x, + y = from_y, yend = to_y, + group = idx)) + + ## edges + ggbump::geom_sigmoid(aes(linetype = I(line_type))) + + ggplot2::geom_label(data = dplyr::filter(df_gg, out_case == "unit_out"), + aes(x = (from_x + to_x) / 4, + y = from_y, + label = round(.data[[x_attrs$col_weights]], 2))) + + ggplot2::geom_label(data = dplyr::filter(df_gg, out_case == "frac_out"), + aes(x = (((from_x + to_x) / 2) + to_x) / 2, + y = to_y, + label = round(.data[[x_attrs$col_weights]], 2))) + + ## from nodes + ggplot2::geom_text(aes(x = from_x - 0.5, y = from_y, + label = .data[[x_attrs$col_from]], + fontface=I(font_type)), + ## drop idx groups to avoid duplicate labels + stat = "unique", inherit.aes = FALSE) + + ## to nodes + ggplot2::geom_label(aes(x = to_x + 0.5, y = to_y, + label = .data[[x_attrs$col_to]]), + fill = "black", + alpha = 0.1) + + ggplot2::scale_y_reverse() + + ggplot2::theme_minimal() + + theme(legend.position = "bottom", + panel.grid.major = element_blank(), + panel.grid.minor = element_blank(), + axis.text.y = element_blank(), + axis.text.x = element_blank(), + plot.background = element_rect(fill = "white")) + + labs(x = NULL, y = NULL) +} +``` + +```{r message=FALSE, echo=FALSE} +gg_bigraph <- anzsco_xmap |> + plt_xmap_bigraph() +``` + +```{r message=FALSE, echo=FALSE, out.width="100%"} +# print bigraph and code tables +gg_bigraph +``` + +```{r message=FALSE, echo=FALSE} +knitr::kable(list(table_anzsco, table_isco8)) +``` + +This visualisation also has benefits over the traditionally used Sankey diagram. Sankey diagrams are often used to illustrated "flows" between nodes. However, variable link widths can actually clutter the visualisation of crosswalks. Consider this simple crossmap that might be used to harmonise national accounts data (e.g. GDP) across two time periods. + +```{r} +edges <- tribble(~ctr, ~ctr2, ~split, + "BLX", "BEL", 0.5, + "BLX", "LUX", 0.5, + "E.GER", "DEU", 1, + "W.GER", "DEU", 1) +``` + +![](https://raw.githubusercontent.com/cynthiahqy/viz-panel-maps/57272c61692ece2d2b94874d12fe7f0619c6e864/docs/plots/viz-country-concord/ggsankey.png){width="100%"} + +On the other hand, the bigraph visualisation shows more clearly how data is modified (or not) when harmonising between nomenclature. The solid lines show when a link does not modify the source values, whilst the dotted line style indicates that data will be split up. Furthermore, by using fixed width links, there is room to place labels on-top of each curve indicated the transformation weights. + +![](https://raw.githubusercontent.com/cynthiahqy/viz-panel-maps/workflowr/docs/plots/ggbump-sigmoid-graph-edges.jpg){width="100%"} + +### Matrix + +Another useful visualisation or representation of a crossmap is as an incidence matrix with the source nomenclature indexed along the rows and the target nomenclature indexed on the columns: + +```{r xmap-as-matrix} +plt_xmap_ggmatrix <- function(x, ...){ + stopifnot(is_xmap_df(x)) + x_attrs <- attributes(x) + edges_complete <- tibble::as_tibble(x) |> + tidyr::complete(.data[[x_attrs$col_from]], .data[[x_attrs$col_to]]) + + ## add link-out type + gg_df <- edges_complete |> + dplyr::mutate(out_case = dplyr::case_when(.data[[x_attrs$col_weights]] == 1 ~ "one-to-one", + .data[[x_attrs$col_weights]] < 1 ~ "one-to-many", + is.na(.data[[x_attrs$col_weights]]) ~ "none") + ) + + ## make plot + gg_df |> ggplot(aes(x=.data[[x_attrs$col_to]], + y=.data[[x_attrs$col_from]])) + + geom_tile(aes(fill=out_case), col="grey") + + scale_y_discrete(limits=rev) + + scale_x_discrete(position='top') + + scale_fill_brewer() + + coord_fixed() + + labs(x = x_attrs$col_to, y = x_attrs$col_from, fill="Outgoing Link Type") + + theme_minimal() + + geom_text(data = dplyr::filter(gg_df, !is.na(.data[[x_attrs$col_weights]])), aes(label=round(.data[[x_attrs$col_weights]], 2))) + + theme(legend.position = "bottom", + panel.grid.major = element_blank(), + panel.grid.minor = element_blank() + ) +} +``` + +```{r} +plt_xmap_ggmatrix(anzsco_xmap) +``` + +Notice that the requirement that a valid crossmap has outgoing weights which sum to 1 for each source node is equivalent to a requirement that the total of weights across each row sums to 1. + +![](plot-weight-sum-matrix.png){width="100%"} + +## Visualising Types of Mapping Relations + +```{r, echo=FALSE} +veg_1a <- c("eggplant", "capsicum", "zucchini") +veg_1b <- c("aubergine", "pepper", "courgette") +veg_2 <- c("vegetables") +fruit_1a <- c("peach", "raspberry", "kumquat") +fruit_2 <- c("fruits") +pb_1a <- c("salt", "sugar", "peanuts") +pb_2 <- c("peanut butter") +pb_1a_w <- c(0.02, 0.05, 0.93) + +recode <- data.frame(au = veg_1a, uk = veg_1b, link = 1) |> + as_xmap_df(au, uk, link) +gg_recode <- recode |> plt_xmap_bigraph() + +agg <- data.frame(item = fruit_1a, group = fruit_2, link = 1) |> + as_xmap_df(item, group, link) +gg_agg <- agg |> plt_xmap_bigraph() + +disagg <- data.frame(group = pb_2, item = pb_1a, link = pb_1a_w) |> + as_xmap_df(group, item, link) +gg_disagg <- disagg |> plt_xmap_bigraph() +``` + +```{r} +library(patchwork) + +gg_recode + gg_agg + gg_disagg +``` diff --git a/vignettes/xmap.Rmd b/vignettes/xmap.Rmd new file mode 100644 index 0000000..b75044e --- /dev/null +++ b/vignettes/xmap.Rmd @@ -0,0 +1,135 @@ +--- +title: "Getting Started with Crossmaps" +output: + rmarkdown::html_vignette: + toc: yes +vignette: > + %\VignetteIndexEntry{Getting Started with Crossmaps} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +## Checking existing pipelines + +`xmap` offers verification functions for mappings encoded in named vectors or lists as well as data frames. Many of these checks will seem trivial in short mappings, but can be useful checks for longer or more complex mappings. + +For example, if you are using a named list to manually recode categorical variables (e.g. using `forcats::fct_recode`), then you might want to verify some properties of the level mappings -- e.g. each existing level is only mapped once: + +```{r} +library(forcats) +library(xmap) + +x <- factor(c("apple", "bear", "banana", "dear")) +levels <- ## new = "old" levels + c(fruit = "apple", fruit = "banana") |> + verify_named_all_values_unique() +forcats::fct_recode(x, !!!levels) +``` + +```{r error=TRUE} +mistake_levels <- c(fruit = "apple", fruit = "banana", veg = "banana") |> + verify_named_all_values_unique() +``` + +Similarly, if you are using a named list to encode group members, you could check the members against a reference list. + +```{r error=TRUE} +## mistakenly assign kate to another group +student_group_mistake <- list(GRP1 = c("kate", "jane", "peter"), + GRP2 = c("terry", "ben", "grace"), + GRP2 = c("cindy", "lucy", "kate" )) + +student_list <- c("kate", "jane", "peter", "terry", "ben", + "grace", "cindy", "lucy", "alex") + +student_group_mistake |> + verify_named_matchset_values_exact(student_list) +``` + +If you are redistributing numeric values between categories, see `vignette("making-xmaps")` for details on how to check your transformation weights redistribute exactly 100% of your original data. + +## Crossmap transformations + +All crossmaps transformations can be decomposed into multiple "standard" data manipulation steps. + +1. **Rename original categories into target categories** +2. **Mutate source node values by link weight.** +3. **Summarise mutated values by target node.** + +To implement these steps use the following `dplyr` pipeline with verified links: + +1. `dplyr::left_join` the target categories (`to`) to the original data via the source labels (`from`). +2. `dplyr::mutate` the source values by multiplying them with the link weights (`weights`) to transform the original values into redistributed values. +3. `dplyr::group_by` and `dplyr::summarise` values by target groups (`to`) to complete any many-to-1 mappings. + +For example given some original data (`v19_data`) with source categories (`v19`), and a valid `xmap` with source categories (`from = version19`), target categories (`to = version18`) and link weights (`weights = w19to18`): + +```{r message=FALSE} +library(xmap) +library(tibble) +library(dplyr) +``` + +```{r} +# original data +v19_data <- tibble::tribble( + ~v19, ~count, + "1120", 300, + "1121", 400, + "1130", 200, + "1200", 600 +) + +# valid crossmap +xmap_19to18 <- tibble::tribble( + ~version19, ~version18, ~w19to18, + # many-to-1 collapsing + "1120", "A2", 1, + "1121", "A2", 1, + # 1-to-1 recoding + "1130", "A3", 1, + # 1-to-many redistribution + "1200", "A4", 0.6, + "1200", "A5", 0.4 +) |> + verify_links_as_xmap(from = version19, to = version18, weights = w19to18) + +# transformed data +(v18_data <- dplyr::left_join(x = v19_data, + y = xmap_19to18, + by = c(v19 = "version19")) |> + dplyr::mutate(new_count = count * w19to18) |> + dplyr::group_by(version18) |> + dplyr::summarise(v20_count = sum(new_count)) +) +``` + +Note that we expect multiple matches for 1-to-many relations so the `dplyr` warning can safely be ignored. + +## Creating an `xmap_df` object (EXPERIMENTAL) + +The `xmap_df` class aims to facilitates additional functionality such as graph property calculation and printing (i.e. relation types and crossmap direction) via a custom `print()` method, coercion to other useful classes (e.g. `xmap_to_matrix()`), visualisation (see `vignette("vis-xmaps")` for prototypes), and multi-step transformations (i.e. from nomenclature A to B to C). + +Please note that the `xmap_df` class and related functions are still in active development and subject to breaking changes in future releases. + +If you already have a data frame of candidate links, turn them into a valid `xmap` object by specifying the source (`from`) nodes, target (`to`) nodes, and weights (`weights`): + +```{r} +uk_shares <- tibble::tribble( + ~key1, ~key2, ~shares, + "UK, Channel Islands, Isle of Man", "Scotland", 0.1102047, + "UK, Channel Islands, Isle of Man", "Wales", 0.02720333, + "UK, Channel Islands, Isle of Man", "England", 0.862592 + ) + +uk_shares |> + as_xmap_df(from = key1, to = key2, weights = shares, tol = 3e-08) |> + print() +``` + +Note that both verification and coercion will fail without adjusting the tolerance `tol`, which specifies differences to ignore (i.e. what counts as close enough to 1). + +```{r error=TRUE} +uk_shares |> + verify_links_as_xmap(key1, key2, shares) +```