diff --git a/.devcontainer/Dockerfile b/.devcontainer/Containerfile similarity index 64% rename from .devcontainer/Dockerfile rename to .devcontainer/Containerfile index 39a03aa7..f9b37466 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Containerfile @@ -1,16 +1,16 @@ -FROM rocker/tidyverse:4.4.0 +FROM ghcr.io/rocker-org/devcontainer/tidyverse:4.5 # RUN \ # echo 'options(repos=c(CRAN="https://packagemanager.posit.co/cran/__linux__/bookworm/latest"))' >> ~/.Rprofile && \ # Rscript --vanilla -e 'getOption("repos")' -# Adding R packages -RUN install2.r Rcpp sna network networkDynamic Matrix MASS MatchIt SparseM igraph \ - viridisLite covr testthat knitr rmarkdown ape RSiena survival RcppArmadillo - -RUN install2.r languageserver httpgd +RUN install2.r --error languageserver && installGithub.r nx10/httpgd RUN apt-get update && apt-get install --no-install-recommends -y valgrind gdb \ libglpk-dev +# Adding R packages +RUN install2.r --error Rcpp sna network networkDynamic Matrix MASS MatchIt SparseM igraph \ + viridisLite covr testthat knitr rmarkdown ape RSiena survival RcppArmadillo + CMD ["bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8b589118..d07306e4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,9 +1,9 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/cpp { - "name": "epiworldR", + "name": "netdiffuseR", "build": { - "dockerfile": "Dockerfile" + "dockerfile": "Containerfile" }, "customizations": { "vscode": { @@ -11,14 +11,15 @@ "reditorsupport.r", "rdebugger.r-debugger", "quarto.quarto", - "tianyishi.rmarkdown" + "tianyishi.rmarkdown", + "github.vscode-github-actions" ] } }, "mounts": [ // Mount the .vscode configuration into the container "source=${localWorkspaceFolder}/.devcontainer/.vscode,target=/workspaces/${localWorkspaceFolderBasename}/.vscode,type=bind,consistency=cached" - ] + ], // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, @@ -32,5 +33,5 @@ // "customizations": {}, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" + "remoteUser": "root" } diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..cf33a1e1 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,30 @@ +# Copilot Instructions for netdiffuseR + +## Documentation Style + +When writing roxygen2 documentation, prefer markdown syntax over LaTeX: + +- Use `` `code` `` instead of `\code{code}` +- Use `` `[function()]` `` instead of `\code{\link{function}}` +- Use markdown bullet lists instead of `\itemize{}` +- Use markdown formatting for emphasis and structure + +## Code Style + +- Follow existing code patterns and conventions in the package +- Use meaningful variable names and keep functions focused +- Add comments only when they match the existing style or explain complex logic +- Prefer using existing libraries over adding new dependencies + +## Testing + +- Add comprehensive tests for new functions +- Include edge cases and error conditions +- Test with the existing sample datasets when possible +- Validate input parameters and handle errors gracefully + +## Examples + +- Use `\dontrun{}` only when examples take a long time to run +- Prefer examples that can execute quickly for CRAN checks +- Use existing package datasets in examples when available \ No newline at end of file diff --git a/.github/workflows/pkgdown.yml b/.github/workflows/pkgdown.yml index a0d1eba6..53ba6a5b 100644 --- a/.github/workflows/pkgdown.yml +++ b/.github/workflows/pkgdown.yml @@ -24,7 +24,7 @@ jobs: id-token: write pages: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: r-lib/actions/setup-pandoc@v2 with: @@ -35,6 +35,8 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::pkgdown + install-quarto: true + install-pandoc: true - name: Install the package run: R CMD INSTALL . @@ -44,7 +46,7 @@ jobs: shell: Rscript {0} - name: Upload artifact for GH pages deployment - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: path: "docs/" diff --git a/.github/workflows/r.yml b/.github/workflows/r.yml index 700c87fc..e6bd1623 100644 --- a/.github/workflows/r.yml +++ b/.github/workflows/r.yml @@ -15,14 +15,16 @@ jobs: R-CMD-check: runs-on: ${{ matrix.config.os }} - name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) (valgrind:${{ matrix.config.valgrind }}) strategy: fail-fast: false matrix: config: - {os: macOS-latest, r: 'release'} + - {os: macOS-latest, r: 'devel', http-user-agent: 'release'} - {os: windows-latest, r: 'release'} + # - {os: windows-latest, r: 'devel', http-user-agent: 'release'} - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - {os: ubuntu-latest, r: 'release'} - {os: ubuntu-latest, r: 'devel', valgrind: true, http-user-agent: 'release'} @@ -32,9 +34,12 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v4 + # Fix suggested by + # https://msmith.de/2020/03/12/r-cmd-check-github-actions.html + - name: Configure git + run: git config --global core.autocrlf false - - uses: r-lib/actions/setup-pandoc@v2 + - uses: actions/checkout@v5 - uses: r-lib/actions/setup-r@v2 with: @@ -46,6 +51,8 @@ jobs: with: extra-packages: any::rcmdcheck needs: check + install-quarto: true + install-pandoc: true - uses: r-lib/actions/check-r-package@v2 if: ${{ matrix.config.valgrind != true }} @@ -56,36 +63,43 @@ jobs: check-dir: '"check"' error-on: '"error"' - - name: Setup valgrind - if: ${{ matrix.config.valgrind == true }} + - name: Install valgrind + if: ${{ matrix.config.valgrind }} run: | sudo apt-get update - sudo apt-get install -y valgrind --no-install-recommends - shell: bash + sudo apt-get install -y --no-install-recommends valgrind - - name: Check with Valgrind - if: ${{ matrix.config.valgrind == true }} - env: - _R_CHECK_CRAN_INCOMING_REMOTE_: false - run: | - options(crayon.enabled = TRUE) - rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran", "--use-valgrind"), error_on = "error", check_dir = "check") - shell: Rscript {0} + - uses: r-lib/actions/check-r-package@v2 + if: ${{ matrix.config.valgrind }} + with: + args: 'c("--as-cran", "--use-valgrind", "--no-manual")' + upload-results: true + + build-pkg: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 - - name: Upload check results - if: failure() - uses: actions/upload-artifact@main + - uses: r-lib/actions/setup-r@v2 with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check + r-version: 'release' + use-public-rspm: true - - name: Build the package - if: ${{ matrix.config.os == 'ubuntu-latest' && (matrix.config.r == 'release' || matrix.config.r == 'devel') && (matrix.config.valgrind != true) }} + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::devtools + + - name: Build package run: R CMD build . - - uses: actions/upload-artifact@v4 - if: ${{ matrix.config.os == 'ubuntu-latest' && (matrix.config.r == 'release' || matrix.config.r == 'devel') && (matrix.config.valgrind != true) }} + - name: Upload package artifact + uses: actions/upload-artifact@v5 with: - name: netdiffuseR-built-package-${{ matrix.config.os }}-${{ matrix.config.r }} - path: netdiffuseR_*.tar.gz - retention-days: 7 + name: R-package + path: '*.tar.gz' + + + + + + diff --git a/.gitignore b/.gitignore index 8a137dac..2d2dbdb2 100755 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,8 @@ inst/doc *.tex playground/ doc/ + +# Other files +.vscode/ +config.status +src/Makevars diff --git a/DESCRIPTION b/DESCRIPTION index 0d960f0a..2c7e9575 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: netdiffuseR Title: Analysis of Diffusion and Contagion Processes on Networks -Version: 1.23.0 +Version: 1.24.0 Authors@R: c( person("George", "Vega Yon", email="g.vegayon@gmail.com", role=c("aut", "cre"), comment=c(ORCID = "0000-0002-3171-0844", what="Rewrite functions with Rcpp, plus new features") @@ -51,7 +51,7 @@ Suggests: survival VignetteBuilder: knitr LinkingTo: Rcpp, RcppArmadillo -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 Encoding: UTF-8 URL: https://github.com/USCCANA/netdiffuseR, https://USCCANA.github.io/netdiffuseR/ @@ -66,6 +66,7 @@ Collate: 'bootnet.r' 'citer_environment.R' 'data.r' + 'degree_adoption_diagnostic.R' 'diffnet-c.R' 'diffnet-class.r' 'diffnet-indexing.r' diff --git a/Makefile b/Makefile index 832637c0..03fa9d2a 100644 --- a/Makefile +++ b/Makefile @@ -9,16 +9,16 @@ help: @echo " make docs - Generate documentation" install: - R CMD INSTALL . + Rscript --vanilla -e 'devtools::install()' build: R CMD build . -README.md: README.Rmd - Rscript -e 'rmarkdown::render("README.Rmd")' +README.md: README.qmd + quarto render README.qmd -check: netdiffuseR_$(VERSION).tar.gz - R CMD check --as-cran netdiffuseR_$(VERSION).tar.gz +check: + Rscript --vanilla -e 'devtools::check()' checkv: netdiffuseR_$(VERSION).tar.gz R CMD check --as-cran --use-valgrind netdiffuseR_$(VERSION).tar.gz diff --git a/NAMESPACE b/NAMESPACE index 0e4f522d..d3a9d6e9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -66,6 +66,7 @@ S3method(plot_diffnet2,diffnet) S3method(plot_threshold,array) S3method(plot_threshold,default) S3method(plot_threshold,diffnet) +S3method(print,degree_adoption_diagnostic) S3method(print,diffnet) S3method(print,diffnet_bootnet) S3method(print,diffnet_diffmap) @@ -99,6 +100,7 @@ export(classify_adopters) export(classify_graph) export(compare_matrix) export(cumulative_adopt_count) +export(degree_adoption_diagnostic) export(dgr) export(diag_expand) export(diffmap) diff --git a/NEWS.md b/NEWS.md index a8600759..38a56e7e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,11 @@ +# Changes in netdiffuseR version 1.24.0 (DEV) + +* New function `degree_adoption_diagnostic()` analyzes the correlation between network + centrality measures (in-degree and out-degree) and time of adoption to identify + whether opinion leaders were supporters or opposers in diffusion processes. + The function supports multi-diffusion objects, undirected networks, and includes + bootstrap confidence intervals. + # Changes in netdiffuseR version 1.23.0 (2025-06-10) * New methods for simulating multi-diffusion models. diff --git a/R/degree_adoption_diagnostic.R b/R/degree_adoption_diagnostic.R new file mode 100644 index 00000000..1fa9adae --- /dev/null +++ b/R/degree_adoption_diagnostic.R @@ -0,0 +1,744 @@ +#' Degree and Time of Adoption Diagnostic +#' +#' Analyzes the correlation between in-degree, out-degree, and time of adoption +#' to identify whether opinion leaders were early adopters (supporters) or late +#' adopters (opposers). +#' +#' @param graph A `[diffnet()]` object or a graph data structure (classes include +#' `array` (\eqn{n\times n \times T}{n*n*T}), `dgCMatrix` (sparse), +#' `igraph`, etc.; see [netdiffuseR-graphs]). +#' @param toa Integer vector of length \eqn{n} (single behavior) or an \eqn{n\times Q}{n*Q} +#' matrix (multi-behavior) with times of adoption. Required when `graph` is not a `diffnet`. +#' @param t0,t1 Optional integer scalars defining the first and last observed +#' periods. If missing and `toa` is provided, `t0` defaults to 1 +#' and `t1` to `max(toa, na.rm=TRUE)`. +#' @param name Optional character scalars used only when coercing +#' inputs into a `diffnet` object (passed to `new_diffnet`). +#' @param behavior Which behaviors to include when `toa` is a matrix (multi-diffusion). +#' Can be `NULL` (all), a numeric index vector, or a character vector matching `colnames(toa)`. +#' @param combine Character scalar. How to combine multiple behaviors when `toa` is a matrix: +#' - `"none"` (analyze each behavior separately) +#' - `"pooled"` (stack rows across behaviors) +#' - `"average"` (per-actor mean of TOA across selected behaviors) +#' - `"earliest"` (per-actor minimum TOA) +#' Ignored for single-behavior. +#' @param min_adopters Integer scalar. Minimum number of adopters required to compute correlations +#' for any analysis cell (default 3). +#' @param degree_strategy Character scalar. How to aggregate degree measures across +#' time periods: +#' - `"mean"` (default): Average degree across all time periods +#' - `"first"`: Degree in the first time period +#' - `"last"`: Degree in the last time period +#' @param bootstrap Logical scalar. Whether to compute bootstrap confidence intervals. +#' @param R Integer scalar. Number of bootstrap replicates (default 1000). +#' @param conf.level Numeric scalar. Confidence level for bootstrap intervals (default 0.95). +#' @param valued Logical scalar. Whether to use edge weights in degree calculations. +#' @param ... Additional arguments passed on when coercing to `diffnet`. +#' +#' @details +#' This diagnostic function computes correlations between degree centrality measures +#' (in-degree and out-degree) and time of adoption. Positive correlations suggest +#' that central actors (opinion leaders) adopted early, while negative correlations +#' suggest they adopted late. +#' +#' When `bootstrap = TRUE`, the function uses the `boot` package to +#' compute bootstrap confidence intervals for the correlations. +#' +#' When `toa` is a matrix (multi-diffusion), degree vectors are computed once and +#' reused; the time of adoption is combined according to `combine`: +#' - `"none"`: computes separate results per behavior (see Value). +#' - `"pooled"`: stacks (actor, behavior) rows for adopters and runs a single analysis. +#' - `"average"`: one row per actor using the mean TOA of adopted behaviors. +#' - `"earliest"`: one row per actor using the minimum TOA of adopted behaviors. +#' +#' @return When analyzing a single behavior (or when `combine!="none"`), a list with: +#' \item{correlations}{Named numeric vector with correlations between in-degree/out-degree and time of adoption} +#' \item{bootstrap}{List with bootstrap results when `bootstrap = TRUE`, otherwise `NULL`} +#' \item{call}{The matched call} +#' \item{degree_strategy}{The degree aggregation strategy used} +#' \item{sample_size}{Number of rows included in the analysis (adopter rows)} +#' \item{combine}{`NULL` for single-behavior; otherwise the combination rule used.} +#' +#' When `combine="none"` with multiple behaviors, returns the same structure, except: +#' - `correlations` is a \eqn{2\times Q^*}{2 x Q*} matrix with rows `c("indegree_toa","outdegree_toa")` +#' and one column per analyzed behavior. +#' - `bootstrap` is a named list with one entry per behavior (each like the single-behavior case), or `NULL` if `bootstrap=FALSE`. +#' - `sample_size` is an integer vector named by behavior. +#' - `combine` is `"none"`. +#' +#' @examples +#' # Basic usage with Korean Family Planning data +#' data(kfamilyDiffNet) +#' result_basics <- degree_adoption_diagnostic(kfamilyDiffNet, bootstrap = FALSE) +#' print(result_basics) +#' +#' # With bootstrap confidence intervals +#' result_boot <- degree_adoption_diagnostic(kfamilyDiffNet) +#' print(result_boot) +#' +#' # Different degree aggregation strategies +#' result_first <- degree_adoption_diagnostic(kfamilyDiffNet, degree_strategy = "first") +#' result_last <- degree_adoption_diagnostic(kfamilyDiffNet, degree_strategy = "last") +#' +#' # Multi-diffusion (toy) ---------------------------------------------------- +#' set.seed(999) +#' n <- 40; t <- 5; q <- 2 +#' garr <- rgraph_ws(n, t, p=.3) +#' diffnet_multi <- rdiffnet(seed.graph = garr, t = t, seed.p.adopt = rep(list(0.1), q)) +#' +#' # pooled (one combined analysis) +#' degree_adoption_diagnostic(diffnet_multi, combine = "pooled", bootstrap = FALSE) +#' +#' # per-behavior (matrix of correlations; one column per behavior) +#' degree_adoption_diagnostic(diffnet_multi, combine = "none", bootstrap = FALSE) +#' +#' @seealso `[dgr()]`, `[diffreg()]`, `[exposure()]` +#' @family statistics +#' @export +degree_adoption_diagnostic <- function( + graph, + degree_strategy = c("mean", "first", "last"), + bootstrap = TRUE, + R = 1000, + conf.level = 0.95, + toa = NULL, + t0 = NULL, t1 = NULL, + name = NULL, + behavior = NULL, + combine = c("none", "pooled", "average", "earliest"), + min_adopters = 3, + valued = getOption("diffnet.valued", FALSE), + ... +) { + # Check that bootstrap is a logical scalar + if (!is.logical(bootstrap) || length(bootstrap) != 1 || is.na(bootstrap)) { + stop("'bootstrap' must be a logical scalar") + } + + # Check that R is a positive integer + if (!is.numeric(R) || length(R) != 1 || is.na(R) || R < 1 || R != as.integer(R)) { + stop("'R' must be a positive integer") + } + + # Check that conf.level is a numeric scalar between 0 and 1 + if (!is.numeric(conf.level) || length(conf.level) != 1 || is.na(conf.level) || conf.level <= 0 || conf.level >= 1) { + stop("'conf.level' must be between 0 and 1") + } + + # Match and validate arguments + degree_strategy <- match.arg(degree_strategy) + combine <- match.arg(combine) + + # Store the original call + original_call <- match.call() + + # Handle input processing and validation + graph_and_toa <- process_graph_input(graph, toa, t0, t1, name, ...) + graph <- graph_and_toa$graph + toa <- graph_and_toa$toa + + # Get the number of behaviors + if (is.matrix(toa)) { + Q <- ncol(toa) + behavior_names <- if (length(colnames(toa))) colnames(toa) else paste0("B", seq_len(Q)) + } else { + Q <- 1 + behavior_names <- NULL + } + + # Filter behaviors if requested + if (Q > 1 && !is.null(behavior)) { + if (is.character(behavior)) { + if (is.null(colnames(toa))) { + stop("Cannot use character behavior selection without column names in toa") + } + behavior_indices <- match(behavior, colnames(toa)) + if (any(is.na(behavior_indices))) { + stop("Some behavior names not found in colnames(toa): ", + paste(behavior[is.na(behavior_indices)], collapse = ", ")) + } + } else if (is.numeric(behavior)) { + behavior_indices <- behavior + if (any(behavior_indices < 1 | behavior_indices > Q)) { + stop("behavior index out of range: must be between 1 and ", Q) + } + } else { + stop("behavior must be NULL, numeric indices, or character names") + } + + toa <- toa[, behavior_indices, drop = FALSE] + Q <- ncol(toa) + behavior_names <- if (length(colnames(toa))) colnames(toa) else paste0("B", seq_len(Q)) + } + + # Compute degree measures + degrees <- compute_degree_measures(graph, degree_strategy, valued) + + # Handle multi-diffusion analysis: for combine = "none", always analyze per behavior and do not error for too few adopters + if (combine == "none" && is.matrix(toa)) { + return(analyze_multi_behaviors_separately( + degrees, toa, min_adopters, bootstrap, R, conf.level, + behavior_names, degree_strategy, original_call, graph + )) + } + + # Single behavior or combined analysis + combined_data <- prepare_combined_data(degrees, toa, combine, min_adopters, Q) + + if (nrow(combined_data) < min_adopters) { + stop("Insufficient adopters for correlation analysis. (n=", nrow(combined_data), + ", minimum = ", min_adopters, ").") + } + + # Compute correlations + correlations <- compute_correlations(combined_data) + + # Bootstrap if requested + bootstrap_results <- if (bootstrap) { + compute_bootstrap_results(combined_data, R, conf.level) + } else { + NULL + } + + # Determine if undirected (graph is always a diffnet here) + undirected <- isTRUE(is_undirected(graph)) + + # Return results + structure(list( + correlations = correlations, + bootstrap = bootstrap_results, + call = original_call, + degree_strategy = degree_strategy, + sample_size = nrow(combined_data), + combine = if (Q > 1) combine else NULL, + undirected = undirected + ), class = "degree_adoption_diagnostic") +} + +# Helper functions -------------------------------------------------------- + +process_graph_input <- function(graph, toa, t0, t1, name, ...) { + if (inherits(graph, "diffnet")) { + if (!is.null(toa)) { + warning("toa argument ignored when graph is a diffnet object") + } + return(list(graph = graph, toa = graph$toa)) + } + + if (is.null(toa)) { + stop("toa argument is required when graph is not a diffnet object") + } + + # If graph is a list, ensure all elements are dgCMatrix + if (is.list(graph)) { + graph <- lapply(graph, function(g) { + if (inherits(g, "dgCMatrix")) return(g) + if (is.matrix(g)) return(as(Matrix::Matrix(g, sparse = TRUE), "dgCMatrix")) + stop("All elements of the graph list must be matrices or dgCMatrix.") + }) + } + + # If graph is a single static adjacency (matrix/dgCMatrix), expand it to T layers to avoid recycling warnings in new_diffnet + if (inherits(graph, "dgCMatrix") || is.matrix(graph)) { + if (is.null(t0)) t0 <- 1 + if (is.null(t1)) t1 <- max(toa, na.rm = TRUE) + Tlen <- as.integer(t1 - t0 + 1L) + if (is.finite(Tlen) && Tlen > 1L) { + gmat <- if (inherits(graph, "dgCMatrix")) graph else as(Matrix::Matrix(graph, sparse = TRUE), "dgCMatrix") + graph <- replicate(Tlen, gmat, simplify = FALSE) + } + } + + # Detect undirectedness on the raw input before building diffnet + undirected_flag <- check_undirected_graph(graph) + + # Convert to diffnet + if (is.null(t0)) t0 <- 1 + if (is.null(t1)) t1 <- max(toa, na.rm = TRUE) + + graph <- new_diffnet( + graph, toa, + t0 = t0, t1 = t1, + name = name, + undirected = undirected_flag, + ... + ) + return(list(graph = graph, toa = toa)) +} + +compute_degree_measures <- function(graph, degree_strategy, valued) { + if (degree_strategy == "mean") { + indegree <- rowMeans(dgr(graph, cmode = "indegree", valued = valued), na.rm = TRUE) + outdegree <- rowMeans(dgr(graph, cmode = "outdegree", valued = valued), na.rm = TRUE) + } else { + deg_matrix <- dgr(graph, valued = valued) + if (length(dim(deg_matrix)) == 3) { + # Dynamic case + if (degree_strategy == "first") { + indegree <- deg_matrix[, 1, "indegree"] + outdegree <- deg_matrix[, 1, "outdegree"] + } else if (degree_strategy == "last") { + last_time <- dim(deg_matrix)[2] + indegree <- deg_matrix[, last_time, "indegree"] + outdegree <- deg_matrix[, last_time, "outdegree"] + } + } else if (length(dim(deg_matrix)) == 2) { + # Static case: check for column names, else use position + cn <- colnames(deg_matrix) + if (!is.null(cn) && all(c("indegree", "outdegree") %in% cn)) { + indegree <- deg_matrix[, "indegree"] + outdegree <- deg_matrix[, "outdegree"] + } else if (ncol(deg_matrix) >= 2) { + indegree <- deg_matrix[, 1] + outdegree <- deg_matrix[, 2] + } else { + stop("Degree matrix does not have expected columns for static graph.") + } + } else { + stop("Unexpected degree matrix dimensions in compute_degree_measures.") + } + } + + list(indegree = indegree, outdegree = outdegree) +} + +analyze_multi_behaviors_separately <- function(degrees, toa, min_adopters, bootstrap, R, conf.level, behavior_names, degree_strategy, original_call, graph) { + Q <- ncol(toa) + + # Initialize results containers + correlations_matrix <- matrix(NA_real_, nrow = 2, ncol = Q) + rownames(correlations_matrix) <- c("indegree_toa", "outdegree_toa") + colnames(correlations_matrix) <- behavior_names + + sample_sizes <- integer(Q) + names(sample_sizes) <- behavior_names + + bootstrap_results <- if (bootstrap) vector("list", Q) else NULL + if (bootstrap) names(bootstrap_results) <- behavior_names + + # Analyze each behavior + for (q in seq_len(Q)) { + toa_q <- toa[, q] + adopters_q <- which(!is.na(toa_q)) + + if (length(adopters_q) >= min_adopters) { + data_q <- data.frame( + indegree = degrees$indegree[adopters_q], + outdegree = degrees$outdegree[adopters_q], + toa = toa_q[adopters_q] + ) + + correlations_matrix[1, q] <- cor_safe(data_q$indegree, data_q$toa ) + correlations_matrix[2, q] <- cor_safe(data_q$outdegree, data_q$toa ) + sample_sizes[q] <- nrow(data_q) + + if (bootstrap) { + bootstrap_results[[q]] <- compute_bootstrap_results(data_q, R, conf.level) + } + } else { + sample_sizes[q] <- length(adopters_q) + } + } + + # Determine if undirected + undirected <- if (inherits(graph, "diffnet")) { + is_undirected(graph) + } else { + check_undirected_graph(graph) + } + + structure(list( + correlations = correlations_matrix, + bootstrap = bootstrap_results, + call = original_call, + degree_strategy = degree_strategy, + sample_size = sample_sizes, + combine = "none", + undirected = undirected + ), class = "degree_adoption_diagnostic") +} + +prepare_combined_data <- function(degrees, toa, combine, min_adopters, Q) { + if (Q == 1 || combine == "pooled") { + if (Q == 1) { + adopters <- which(!is.na(toa)) + data.frame( + indegree = degrees$indegree[adopters], + outdegree = degrees$outdegree[adopters], + toa = toa[adopters] + ) + } else { + # Pooled: stack all (actor, behavior) rows + adopter_rows <- which(!is.na(as.vector(toa))) + actor_indices <- ((adopter_rows - 1) %% nrow(toa)) + 1 + + data.frame( + indegree = degrees$indegree[actor_indices], + outdegree = degrees$outdegree[actor_indices], + toa = as.vector(toa)[adopter_rows] + ) + } + } else if (combine == "average") { + # Average TOA across behaviors per actor + toa_avg <- rowMeans(toa, na.rm = TRUE) + adopters <- which(!is.na(toa_avg)) + + data.frame( + indegree = degrees$indegree[adopters], + outdegree = degrees$outdegree[adopters], + toa = toa_avg[adopters] + ) + } else if (combine == "earliest") { + # Earliest TOA across behaviors per actor + toa_min <- apply(toa, 1, function(row) { + if (all(is.na(row))) return(NA_real_) + min(row, na.rm = TRUE) + }) + toa_min[is.infinite(toa_min)] <- NA + adopters <- which(!is.na(toa_min)) + + data.frame( + indegree = degrees$indegree[adopters], + outdegree = degrees$outdegree[adopters], + toa = toa_min[adopters] + ) + } +} + +compute_correlations <- function(data) { + c( + indegree_toa = cor_safe(data$indegree, data$toa), + outdegree_toa = cor_safe(data$outdegree, data$toa) + ) +} + +compute_bootstrap_results <- function(combined_data, R, conf.level) { + # Compute baseline correlations + base_corr <- compute_correlations(combined_data) + indeg_corr <- base_corr[["indegree_toa"]] + outdeg_corr <- base_corr[["outdegree_toa"]] + + indeg_boot_list <- NULL + out_boot_list <- NULL + + # Out-degree + if (!is.na(outdeg_corr)) { + # Out-degree bootstrap + safe_bootstrap_out <- function(data, indices) { + d <- data[indices, , drop = FALSE] + suppressWarnings(stats::cor(d$outdegree, d$toa, use = "complete.obs")) + } + boot_obj_out <- boot::boot(combined_data, statistic = safe_bootstrap_out, R = R) + bias_out <- mean(boot_obj_out$t, na.rm = TRUE) - outdeg_corr + se_out <- stats::sd(boot_obj_out$t, na.rm = TRUE) + + ci_out <- tryCatch({ + bci <- boot::boot.ci(boot_obj_out, conf = conf.level, type = "perc") + # Percentile CI vector (low, high) + if (!is.null(bci$percent)) bci$percent[4:5] else NULL + }, error = function(e) NULL) + + out_boot_list <- list( + correlation = outdeg_corr, + bias = bias_out, + std_error = se_out, + conf_int = ci_out, + conf_level = conf.level + ) + } else { + # Degenerate case: correlation is NA (do not include CI/SE/CL) + out_boot_list <- list( + correlation = NA_real_ + ) + boot_obj_out <- NULL + } + + # In-degree + if (!is.na(indeg_corr)) { + # In-degree bootstrap + safe_bootstrap_in <- function(data, indices) { + d <- data[indices, , drop = FALSE] + suppressWarnings(stats::cor(d$indegree, d$toa, use = "complete.obs")) + } + boot_obj_in <- boot::boot(combined_data, statistic = safe_bootstrap_in, R = R) + bias_in <- mean(boot_obj_in$t, na.rm = TRUE) - indeg_corr + se_in <- stats::sd(boot_obj_in$t, na.rm = TRUE) + + ci_in <- tryCatch({ + bci <- boot::boot.ci(boot_obj_in, conf = conf.level, type = "perc") + if (!is.null(bci$percent)) bci$percent[4:5] else NULL + }, error = function(e) NULL) + + indeg_boot_list <- list( + correlation = indeg_corr, + bias = bias_in, + std_error = se_in, + conf_int = ci_in, + conf_level = conf.level + ) + } else { + indeg_boot_list <- list( + correlation = NA_real_ + ) + boot_obj_in <- NULL + } + + list( + indegree = indeg_boot_list, + outdegree = out_boot_list, + R = R, + boot_object = list(indegree = boot_obj_in, outdegree = boot_obj_out) + ) +} + +create_empty_result <- function(degree_strategy, original_call, combine, sample_size) { + structure(list( + correlations = c(indegree_toa = NA_real_, outdegree_toa = NA_real_), + bootstrap = NULL, + call = original_call, + degree_strategy = degree_strategy, + sample_size = sample_size, + combine = combine, + undirected = FALSE + ), class = "degree_adoption_diagnostic") +} + +check_undirected_graph <- function(graph) { + if (is.list(graph)) { + return(all(sapply(graph, function(g) isSymmetric(as.matrix(g))))) + } + if (is.array(graph) && length(dim(graph)) == 3) { + return(all(sapply(seq_len(dim(graph)[3]), function(t) isSymmetric(as.matrix(graph[,,t]))))) + } + if (is.matrix(graph)) { + return(isSymmetric(as.matrix(graph))) + } + FALSE +} + +# Print method ------------------------------------------------------------ + +#' @export +print.degree_adoption_diagnostic <- function(x, ...) { + cat("Degree and Time of Adoption Diagnostic\n") + cat("======================================\n\n") + + # Basic info + cat(sprintf("Degree aggregation strategy: %s \n", x$degree_strategy)) + # Combination mode info (only for multi-diffusion) + if (!is.null(x$combine)) { + mode_label <- switch(x$combine, + "none" = "none (per-behavior)", + "pooled" = "pooled (stacked rows)", + "average" = "average (mean TOA per actor)", + "earliest" = "earliest (min TOA per actor)", + x$combine + ) + cat(sprintf("Behavior combination: %s \n\n", mode_label)) + } + + # Sample size info + if (is.null(names(x$sample_size))) { + cat(sprintf("Sample size (adopters only): %d \n", x$sample_size)) + } else { + cat("Sample sizes (adopters only):\n") + beh_names <- names(x$sample_size) + for (j in seq_along(x$sample_size)) { + cat(sprintf(" - %s: %d\n", if (length(beh_names)) beh_names[j] else paste0("B", j), x$sample_size[j])) + } + } + + undirected <- isTRUE(x$undirected) + + # Single behavior or combined analysis + if (is.vector(x$correlations)) { + print_single_behavior_results(x, undirected) + } else { + print_multi_behavior_results(x, undirected) + } + + invisible(x) +} + +print_single_behavior_results <- function(x, undirected) { + # Extract correlation values + indeg_r <- x$correlations[["indegree_toa"]] + outdeg_r <- x$correlations[["outdegree_toa"]] + + # Print correlations + cat("Correlations:\n") + if (undirected) { + deg_r <- indeg_r # For undirected graphs, in-degree = out-degree = degree + cat(sprintf(" Degree - Time of Adoption: %.3f\n", deg_r)) + } else { + cat(sprintf(" In-degree - Time of Adoption: %.3f\n", indeg_r)) + cat(sprintf(" Out-degree - Time of Adoption: %.3f\n", outdeg_r)) + } + + # Interpretation + cat("\nInterpretation:\n") + + if (!is.null(x$bootstrap)) { + bootstrap_data <- x$bootstrap + deg_ci <- if (undirected && !is.null(bootstrap_data$indegree$conf_int)) { + bootstrap_data$indegree$conf_int + } else NULL + indeg_ci <- if (!is.null(bootstrap_data$indegree$conf_int)) { + bootstrap_data$indegree$conf_int + } else NULL + outdeg_ci <- if (!is.null(bootstrap_data$outdegree$conf_int)) { + bootstrap_data$outdegree$conf_int + } else NULL + lvl <- if (!is.null(bootstrap_data$indegree$conf_level)) { + bootstrap_data$indegree$conf_level * 100 + } else NA_real_ + + if (undirected) { + explain_degree_correlation("Degree", deg_r, deg_ci, lvl_arg = lvl) + } else { + explain_degree_correlation("In-degree", indeg_r, indeg_ci, lvl_arg = lvl) + explain_degree_correlation("Out-degree", outdeg_r, outdeg_ci, lvl_arg = lvl) + } + } else { + if (undirected) { + explain_degree_correlation("Degree", deg_r, NULL) + } else { + explain_degree_correlation("In-degree", indeg_r, NULL) + explain_degree_correlation("Out-degree", outdeg_r, NULL) + } + } +} + +print_multi_behavior_results <- function(x, undirected) { + correlations_matrix <- x$correlations + Q <- ncol(correlations_matrix) + beh_names <- colnames(correlations_matrix) + + # Print correlations matrix + cat("\nCorrelations:\n") + if (undirected) { + cat(" Degree - Time of Adoption:\n") + deg_row <- correlations_matrix["indegree_toa", ] + for (j in seq_len(Q)) { + bname <- if (length(beh_names)) beh_names[j] else paste0("B", j) + cat(sprintf(" [%s]: %.3f\n", bname, deg_row[j])) + } + } else { + cat(" In-degree - Time of Adoption:\n") + indeg_row <- correlations_matrix["indegree_toa", ] + for (j in seq_len(Q)) { + bname <- if (length(beh_names)) beh_names[j] else paste0("B", j) + cat(sprintf(" [%s]: %.3f\n", bname, indeg_row[j])) + } + cat(" Out-degree - Time of Adoption:\n") + outdeg_row <- correlations_matrix["outdegree_toa", ] + for (j in seq_len(Q)) { + bname <- if (length(beh_names)) beh_names[j] else paste0("B", j) + cat(sprintf(" [%s]: %.3f\n", bname, outdeg_row[j])) + } + } + + # Interpretation per behavior + cat("\nInterpretation:\n") + for (j in seq_len(Q)) { + bname <- if (length(beh_names)) beh_names[j] else paste0("B", j) + r_in <- correlations_matrix["indegree_toa", j] + r_out <- correlations_matrix["outdegree_toa", j] + + bootstrap_data <- if (!is.null(x$bootstrap)) x$bootstrap[[j]] else NULL + deg_ci <- if (undirected && !is.null(bootstrap_data) && !is.null(bootstrap_data$indegree$conf_int)) { + bootstrap_data$indegree$conf_int + } else NULL + indeg_ci <- if (!is.null(bootstrap_data) && !is.null(bootstrap_data$indegree$conf_int)) { + bootstrap_data$indegree$conf_int + } else NULL + outdeg_ci <- if (!is.null(bootstrap_data) && !is.null(bootstrap_data$outdegree$conf_int)) { + bootstrap_data$outdegree$conf_int + } else NULL + lvl <- if (!is.null(bootstrap_data) && !is.null(bootstrap_data$indegree$conf_level)) { + bootstrap_data$indegree$conf_level * 100 + } else NA_real_ + + cat(sprintf(" [%s]\n", bname)) + if (undirected) { + explain_degree_correlation("Degree", r_in, deg_ci, lvl_arg = lvl) + } else { + explain_degree_correlation("In-degree", r_in, indeg_ci, lvl_arg = lvl) + explain_degree_correlation("Out-degree", r_out, outdeg_ci, lvl_arg = lvl) + } + } +} + +# Helper function for explaining correlations +explain_degree_correlation <- function(label, r, ci, lvl_arg = NA_real_, thr = 0.10) { + # Handle NA correlations gracefully + if (is.na(r)) { + cat(sprintf( + " %s: Weak relationship between centrality and adoption timing:\n r is NA; no CI.\n", + label + )) + return(invisible()) + } + + abs_big <- abs(r) > thr + degree_term <- switch(label, + "In-degree" = "in-degree", + "Out-degree" = "out-degree", + "degree" + ) + + if (is.null(ci)) { + format_interpretation_no_ci(label, r, abs_big, degree_term, thr) + } else { + format_interpretation_with_ci(label, r, ci, abs_big, degree_term, thr, lvl_arg) + } +} + +format_interpretation_no_ci <- function(label, r, abs_big, degree_term, thr) { + if (!abs_big) { + cat(sprintf(" %s: Weak relationship between %s and adoption timing:\n |r| \u2264 %.1f; no CI.\n", + label, degree_term, thr)) + } else if (r > 0) { + cat(sprintf(" %s: Central actors (high %s) tended to adopt early (supporters):\n |r| > %.1f; no CI.\n", + label, degree_term, thr)) + } else { + cat(sprintf(" %s: Central actors (high %s) tended to adopt late (opposers):\n |r| > %.1f; no CI.\n", + label, degree_term, thr)) + } +} + +format_interpretation_with_ci <- function(label, r, ci, abs_big, degree_term, thr, lvl_arg) { + lvl_local <- if (!is.na(lvl_arg)) lvl_arg else 95 + ci_includes_zero <- (length(ci) >= 2) && is.finite(ci[1]) && is.finite(ci[2]) && (ci[1] <= 0 && ci[2] >= 0) + + if (!abs_big) { + cat(sprintf(" %s: Weak relationship between %s and adoption timing; %s statistically supported:\n |r| \u2264 %.1f; CI (%.1f%%) %s 0.\n", + label, degree_term, + if (ci_includes_zero) "NOT" else "", + thr, lvl_local, + if (ci_includes_zero) "includes" else "excludes")) + } else if (r > 0) { + cat(sprintf(" %s: Central actors (high %s) tended to adopt early (supporters); %s statistically supported:\n |r| > %.1f; CI (%.1f%%) %s 0.\n", + label, degree_term, + if (ci_includes_zero) "NOT" else "", + thr, lvl_local, + if (ci_includes_zero) "includes" else "excludes")) + } else { + cat(sprintf(" %s: Central actors (high %s) tended to adopt late (opposers); %s statistically supported:\n |r| > %.1f; CI (%.1f%%) %s 0.\n", + label, degree_term, + if (ci_includes_zero) "NOT" else "", + thr, lvl_local, + if (ci_includes_zero) "includes" else "excludes")) + } +} + +# Safe correlation: returns NA (no warnings) if zero-variance or too few pairs +cor_safe <- function(x, y) { + x <- as.numeric(x); y <- as.numeric(y) + ok <- is.finite(x) & is.finite(y) + if (!any(ok)) return(NA_real_) + x <- x[ok]; y <- y[ok] + if (length(x) < 2L) return(NA_real_) + if (sd(x) == 0 || sd(y) == 0) return(NA_real_) + stats::cor(x, y) +} diff --git a/README.md b/README.md index b92c0419..6db64b07 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,11 @@ [](https://github.com/USCCANA/netdiffuseR/actions/workflows/r.yml) [](https://app.codecov.io/github/USCCANA/netdiffuseR?branch=master) [](https://cran.r-project.org/package=netdiffuseR) -[](https://cran.r-project.org/package=netdiffuseR) +[](https://cran.r-project.org/package=netdiffuseR) [](https://cran.r-project.org/package=netdiffuseR) [](https://doi.org/10.5281/zenodo.1039317) [](https://cran.r-project.org/package=netdiffuseR) -[](https://keck.usc.edu/cana/) +[](https://keck.usc.edu/cana/)
``` r
out <- plot_infectsuscep(diffnet, bins = 20,K=5, logscale = TRUE,
exclude.zeros = TRUE, h=1)
+#> Warning in plot_infectsuscep.list(graph$graph, graph$toa, t0, normalize, : When
+#> applying logscale some observations are missing.
```
- Warning in plot_infectsuscep.list(graph$graph, graph$toa, t0, normalize, : When
- applying logscale some observations are missing.
-
-
+
### Threshold
@@ -246,25 +213,23 @@ diffnet <- rdiffnet(500, 20,
rgraph.args = list(m=3),
threshold.dist = function(x) runif(1, .3, .7))
diffnet
-```
-
- Dynamic network of class -diffnet-
- Name : A diffusion network
- Behavior : Random contagion
- # of nodes : 500 (1, 2, 3, 4, 5, 6, 7, 8, ...)
- # of time periods : 20 (1 - 20)
- Type : directed
- Num of behaviors : 1
- Final prevalence : 1.00
- Static attributes : real_threshold (1)
- Dynamic attributes : -
+#> Dynamic network of class -diffnet-
+#> Name : A diffusion network
+#> Behavior : Random contagion
+#> # of nodes : 500 (1, 2, 3, 4, 5, 6, 7, 8, ...)
+#> # of time periods : 20 (1 - 20)
+#> Type : directed
+#> Num of behaviors : 1
+#> Final prevalence : 1.00
+#> Static attributes : real_threshold (1)
+#> Dynamic attributes : -
-``` r
# Threshold with fixed vertex size
plot_threshold(diffnet)
```
-
+
Using more features
@@ -278,12 +243,12 @@ plot_threshold(
sub = "Note: Vertices' sizes and shapes given by degree and city respectively",
jitter.factor = c(1,1), jitter.amount = c(.25,.025)
)
+#> Warning in (function (graph, expo, toa, include_censored = FALSE, t0 = min(toa,
+#> : -vertex.sides- will be coerced to integer.
```
- Warning in (function (graph, expo, toa, include_censored = FALSE, t0 = min(toa,
- : -vertex.sides- will be coerced to integer.
-
-
+
### Adoption rate
@@ -291,7 +256,7 @@ plot_threshold(
plot_adopters(diffnet)
```
-
+
### Hazard rate
@@ -299,7 +264,7 @@ plot_adopters(diffnet)
hazard_rate(diffnet)
```
-
+
### Diffusion process
@@ -307,14 +272,15 @@ hazard_rate(diffnet)
plot_diffnet(medInnovationsDiffNet, slices=c(1,9,8))
```
-
+
``` r
diffnet.toa(brfarmersDiffNet)[brfarmersDiffNet$toa >= 1965] <- NA
plot_diffnet2(brfarmersDiffNet, vertex.size = "indegree")
```
-
+
``` r
set.seed(1231)
@@ -344,7 +310,8 @@ mtext("Both networks have the same distribution on times of adoption", 1,
outer = TRUE)
```
-
+
``` r
par(oldpar)
@@ -355,17 +322,14 @@ par(oldpar)
``` r
out <- classify(kfamilyDiffNet, include_censored = TRUE)
ftable(out)
-```
-
- thr Non-Adopters Very Low Thresh. Low Thresh. High Thresh. Very High Thresh.
- toa
- Non-Adopters 0.00 0.00 0.00 0.00 0.00
- Early Adopters 0.00 14.04 8.40 0.57 0.29
- Early Majority 0.00 5.64 11.65 5.54 2.58
- Late Majority 0.00 1.34 5.06 6.21 2.96
- Laggards 0.00 1.53 0.00 0.00 34.19
+#> thr Non-Adopters Very Low Thresh. Low Thresh. High Thresh. Very High Thresh.
+#> toa
+#> Non-Adopters 0.00 0.00 0.00 0.00 0.00
+#> Early Adopters 0.00 14.04 8.40 0.57 0.29
+#> Early Majority 0.00 5.64 11.65 5.54 2.58
+#> Late Majority 0.00 1.34 5.06 6.21 2.96
+#> Laggards 0.00 1.53 0.00 0.00 34.19
-``` r
# Plotting
oldpar <- par(no.readonly = TRUE)
par(xpd=TRUE)
@@ -377,7 +341,7 @@ legend("bottom", legend = levels(out$thr), fill=viridisLite::inferno(5), horiz =
cex=.6, bty="n", inset=c(0,-.1))
```
-
+
``` r
par(oldpar)
@@ -387,44 +351,48 @@ par(oldpar)
``` r
sessionInfo()
+#> R version 4.5.2 (2025-10-31)
+#> Platform: x86_64-pc-linux-gnu
+#> Running under: Ubuntu 24.04.3 LTS
+#>
+#> Matrix products: default
+#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
+#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
+#>
+#> locale:
+#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
+#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
+#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
+#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
+#> [9] LC_ADDRESS=C LC_TELEPHONE=C
+#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
+#>
+#> time zone: Etc/UTC
+#> tzcode source: system (glibc)
+#>
+#> attached base packages:
+#> [1] stats graphics grDevices utils datasets methods base
+#>
+#> other attached packages:
+#> [1] netdiffuseR_1.24.0
+#>
+#> loaded via a namespace (and not attached):
+#> [1] Matrix_1.7-4 jsonlite_2.0.0 dplyr_1.1.4
+#> [4] compiler_4.5.2 tidyselect_1.2.1 Rcpp_1.1.0
+#> [7] networkLite_1.1.0 boot_1.3-32 yaml_2.3.10
+#> [10] fastmap_1.2.0 lattice_0.22-7 coda_0.19-4.1
+#> [13] R6_2.6.1 generics_0.1.4 MatchIt_4.7.2
+#> [16] igraph_2.2.1 knitr_1.50 MASS_7.3-65
+#> [19] backports_1.5.0 tibble_3.3.0 statnet.common_4.12.0
+#> [22] pillar_1.11.1 rlang_1.1.6 xfun_0.53
+#> [25] viridisLite_0.4.2 cli_3.6.5 magrittr_2.0.4
+#> [28] network_1.19.0 digest_0.6.37 grid_4.5.2
+#> [31] lifecycle_1.0.4 vctrs_0.6.5 sna_2.8
+#> [34] evaluate_1.0.5 SparseM_1.84-2 glue_1.8.0
+#> [37] rmarkdown_2.30 tools_4.5.2 pkgconfig_2.0.3
+#> [40] networkDynamic_0.11.5 htmltools_0.5.8.1
```
- R version 4.5.0 (2025-04-11)
- Platform: aarch64-apple-darwin24.2.0
- Running under: macOS Sequoia 15.0.1
-
- Matrix products: default
- BLAS: /opt/homebrew/Cellar/openblas/0.3.29/lib/libopenblasp-r0.3.29.dylib
- LAPACK: /opt/homebrew/Cellar/r/4.5.0/lib/R/lib/libRlapack.dylib; LAPACK version 3.12.1
-
- locale:
- [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
-
- time zone: America/Denver
- tzcode source: internal
-
- attached base packages:
- [1] stats graphics grDevices utils datasets methods base
-
- other attached packages:
- [1] netdiffuseR_1.23.0
-
- loaded via a namespace (and not attached):
- [1] Matrix_1.7-3 jsonlite_2.0.0 dplyr_1.1.4
- [4] compiler_4.5.0 tidyselect_1.2.1 Rcpp_1.0.14
- [7] networkLite_1.1.0 boot_1.3-31 yaml_2.3.10
- [10] fastmap_1.2.0 lattice_0.22-6 coda_0.19-4.1
- [13] R6_2.6.1 generics_0.1.4 MatchIt_4.7.2
- [16] igraph_2.1.4.9046 knitr_1.50 MASS_7.3-65
- [19] backports_1.5.0 tibble_3.3.0 statnet.common_4.12.0
- [22] pillar_1.10.2 rlang_1.1.6 xfun_0.52
- [25] viridisLite_0.4.2 cli_3.6.5 magrittr_2.0.3
- [28] network_1.19.0 digest_0.6.37 grid_4.5.0
- [31] lifecycle_1.0.4 vctrs_0.6.5 sna_2.8
- [34] evaluate_1.0.3 SparseM_1.84-2 glue_1.8.0
- [37] rmarkdown_2.29 tools_4.5.0 pkgconfig_2.0.3
- [40] networkDynamic_0.11.5 htmltools_0.5.8.1
-
## To-do list
- Import/Export functions for interfacing other package’s clases, in
diff --git a/README.qmd b/README.qmd
index f279f82b..efcb6bed 100644
--- a/README.qmd
+++ b/README.qmd
@@ -2,6 +2,18 @@
format: gfm
---
+```{r}
+#| label: setup
+#| echo: false
+knitr::opts_chunk$set(
+ collapse = TRUE,
+ comment = "#>",
+ fig.path = "man/figures/README-",
+ out.width = "100%"
+)
+```
+
+```{=markdown}
[](https://github.com/USCCANA/netdiffuseR/actions/workflows/r.yml)
[](https://app.codecov.io/github/USCCANA/netdiffuseR?branch=master)
[](https://cran.r-project.org/package=netdiffuseR)
@@ -10,6 +22,7 @@ format: gfm
[](https://doi.org/10.5281/zenodo.1039317)
[](https://cran.r-project.org/package=netdiffuseR)
[](https://keck.usc.edu/cana/)
+```