├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ └── test-coverage.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── RcppExports.R ├── core_periphery.R ├── dyad_census_attr.R ├── fast_cliques.R ├── graph_products.R ├── graph_structures.R ├── graphs.R ├── lfr_benchmark.R ├── netUtils-package.R ├── print_igraph.R ├── qap.R ├── reciprocity.R ├── sample_kcores.R ├── sample_pa_homophilic.R ├── structural_equivalence.R ├── triad_census_attr.R └── utils.R ├── README.Rmd ├── README.html ├── README.md ├── _pkgdown.yml ├── cran-comments.md ├── inst └── CITATION ├── man ├── as_adj_list1.Rd ├── as_adj_weighted.Rd ├── as_multi_adj.Rd ├── bipartite_from_data_frame.Rd ├── clique_vertex_mat.Rd ├── core_periphery.Rd ├── dyad_census_attr.Rd ├── fast_cliques.Rd ├── figures │ └── logo.png ├── graph_cartesian.Rd ├── graph_cor.Rd ├── graph_direct.Rd ├── graph_from_multi_edgelist.Rd ├── graph_kpartite.Rd ├── helpers.Rd ├── reciprocity_cor.Rd ├── sample_coreseq.Rd ├── sample_lfr.Rd ├── sample_pa_homophilic.Rd ├── split_graph.Rd ├── str.igraph.Rd ├── structural_equivalence.Rd └── triad_census_attr.Rd ├── src ├── .gitignore ├── RcppExports.cpp ├── lfr.cpp ├── mse.cpp └── triad_census_col.cpp └── tests ├── testthat.R └── testthat ├── test-core_periphery.R ├── test-dyad_census_attr.R ├── test-graph_products.R ├── test-graph_structures.R ├── test-graphs.R ├── test-lfr_benchmark.R ├── test-reciprocity.R ├── test-sample_kcores.R ├── test-sample_pa_homophilic.R ├── test-structural_equivalence.R ├── test-triad_census_attr.R └── test-utils.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^netUtils\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^\.travis\.yml$ 6 | ^codecov\.yml$ 7 | ^doc$ 8 | ^Meta$ 9 | ^cran-comments\.md$ 10 | ^CRAN-RELEASE$ 11 | ^_pkgdown\.yml$ 12 | ^docs$ 13 | ^pkgdown$ 14 | ^\.github$ 15 | ^CRAN-SUBMISSION$ 16 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: any::rcmdcheck 45 | needs: check 46 | 47 | - uses: r-lib/actions/check-r-package@v2 48 | with: 49 | upload-snapshots: true 50 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | permissions: 23 | contents: write 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | - uses: r-lib/actions/setup-pandoc@v2 28 | 29 | - uses: r-lib/actions/setup-r@v2 30 | with: 31 | use-public-rspm: true 32 | 33 | - uses: r-lib/actions/setup-r-dependencies@v2 34 | with: 35 | extra-packages: any::pkgdown, local::. 36 | needs: website 37 | 38 | - name: Build site 39 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 40 | shell: Rscript {0} 41 | 42 | - name: Deploy to GitHub pages 🚀 43 | if: github.event_name != 'pull_request' 44 | uses: JamesIves/github-pages-deploy-action@v4.4.1 45 | with: 46 | clean: false 47 | branch: gh-pages 48 | folder: docs 49 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | extra-packages: any::covr 27 | needs: coverage 28 | 29 | - name: Test coverage 30 | run: | 31 | covr::codecov( 32 | quiet = FALSE, 33 | clean = FALSE, 34 | install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") 35 | ) 36 | shell: Rscript {0} 37 | 38 | - name: Show testthat output 39 | if: always() 40 | run: | 41 | ## -------------------------------------------------------------------- 42 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true 43 | shell: bash 44 | 45 | - name: Upload test results 46 | if: failure() 47 | uses: actions/upload-artifact@v3 48 | with: 49 | name: coverage-test-failures 50 | path: ${{ runner.temp }}/package 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | *.Rproj 5 | *.xcf 6 | inst/doc 7 | docs 8 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: netUtils 2 | Title: A Collection of Tools for Network Analysis 3 | Version: 0.8.3.9000 4 | Authors@R: 5 | person("David", "Schoch", email = "david@schochastics.net", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0003-2952-4812")) 7 | Description: Provides a collection of network analytic (convenience) functions which are missing in other standard packages. This includes triad census with attributes , core-periphery models , and several graph generators. Most functions are build upon 'igraph'. 8 | URL: https://github.com/schochastics/netUtils/, https://schochastics.github.io/netUtils/ 9 | BugReports: https://github.com/schochastics/netUtils/issues 10 | License: MIT + file LICENSE 11 | Encoding: UTF-8 12 | LazyData: true 13 | Roxygen: list(markdown = TRUE) 14 | RoxygenNote: 7.3.2 15 | LinkingTo: 16 | Rcpp, 17 | RcppArmadillo 18 | Imports: 19 | Rcpp, 20 | igraph (>= 2.0.0), 21 | stats 22 | Suggests: 23 | covr, 24 | GA, 25 | testthat (>= 3.0.0) 26 | Config/testthat/edition: 3 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2019 2 | COPYRIGHT HOLDER: David Schoch 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 David Schoch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(graph_cor,array) 4 | S3method(graph_cor,default) 5 | S3method(graph_cor,igraph) 6 | S3method(graph_cor,matrix) 7 | S3method(str,igraph) 8 | export(as_adj_list1) 9 | export(as_adj_weighted) 10 | export(as_multi_adj) 11 | export(biggest_component) 12 | export(bipartite_from_data_frame) 13 | export(clique_vertex_mat) 14 | export(core_periphery) 15 | export(delete_isolates) 16 | export(dyad_census_attr) 17 | export(graph_cartesian) 18 | export(graph_cor) 19 | export(graph_direct) 20 | export(graph_from_multi_edgelist) 21 | export(graph_kpartite) 22 | export(reciprocity_cor) 23 | export(sample_coreseq) 24 | export(sample_lfr) 25 | export(sample_pa_homophilic) 26 | export(split_graph) 27 | export(structural_equivalence) 28 | export(triad_census_attr) 29 | importFrom(Rcpp,sourceCpp) 30 | useDynLib(netUtils, .registration = TRUE) 31 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # netUtils 0.8.3.9000 2 | 3 | # netUtils 0.8.3 4 | 5 | * added more tests #14 6 | * require igraph 2.0.0 7 | 8 | # netUtils 0.8.2 9 | 10 | * refactored `sample_lfr()` in C++ (#9) 11 | 12 | # netUtils 0.8.1 13 | 14 | * fixed a bug that prevented `str.igraph` from working (#10) 15 | 16 | # netUtils 0.8.0 17 | 18 | * added `reciprocity_cor()` 19 | * fixed wrong str print (#5) 20 | * switched from Simulated Annealing to Genetic Algorithm (#4) 21 | * added more tests 22 | * added `sample_lfr()` (#9) 23 | 24 | # netUtils 0.7.0 25 | 26 | * fixed documentation 27 | * removed unfinished functions 28 | * added examples 29 | 30 | # netUtils 0.6.0.9000 31 | 32 | added `sample_pa_homophilic()` 33 | 34 | # netUtils 0.5.0.9000 35 | 36 | * renamed package to `netUtils` 37 | * added `bipartite_from_data_frame()` 38 | * added `graph_from_multi_edgelist()` and `as_multi_adj()` 39 | * added `structural_equivalence()` 40 | * added `core_periphery()` 41 | * added `sample_coreseq()` 42 | * added tests 43 | * added graph products `graph_cartesian()` and `graph_direct()` 44 | * added fast max clique routine `fast_cliques()` 45 | 46 | # igraphUtils 0.1.0 47 | 48 | * Added `as_adj_list1()` and `as_adj_weighted()` 49 | * Added `clique_vertex_mat()` 50 | -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | benchmark <- function(excess, defect, num_nodes, average_k, max_degree, tau, tau2, mixing_parameter, overlapping_nodes, overlap_membership, nmin, nmax, fixed_range) { 5 | .Call(`_netUtils_benchmark`, excess, defect, num_nodes, average_k, max_degree, tau, tau2, mixing_parameter, overlapping_nodes, overlap_membership, nmin, nmax, fixed_range) 6 | } 7 | 8 | mse <- function(adjList, deg) { 9 | .Call(`_netUtils_mse`, adjList, deg) 10 | } 11 | 12 | sortxy <- function(x, y) { 13 | .Call(`_netUtils_sortxy`, x, y) 14 | } 15 | 16 | triadCensusCol <- function(A, attr, orbitClasses, triads) { 17 | .Call(`_netUtils_triadCensusCol`, A, attr, orbitClasses, triads) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /R/core_periphery.R: -------------------------------------------------------------------------------- 1 | #' Discrete core-periphery model 2 | #' @description Fits a discrete core-periphery model to a given network 3 | #' @param graph igraph object 4 | #' @param method algorithm to use (see details) 5 | #' @param iter number of iterations if `method=GA` 6 | #' @param ... other parameters for GA 7 | #' @details The function fits the data to an optimal pattern matrix with a genetic algorithm (method="GA") or a rank 1 approximation, either with degree centrality (method="rk1_dc") or eigenvector centrality (method="rk1_ec") . The rank 1 approximation is computationally far cheaper but also more experimental. Best is to compare the results from both models. 8 | #' @return list with numeric vector with entries (k1,k2,...ki...) where ki assigns vertex i to either the core (ki=1) or periphery (ki=0), and the maximal correlation with an optimal pattern matrix 9 | #' @references 10 | #' Borgatti, Stephen P., and Martin G. Everett. "Models of core/periphery structures." Social networks 21.4 (2000): 375-395. 11 | #' @author David Schoch 12 | #' @examples 13 | #' set.seed(121) 14 | #' # split graphs have a perfect core-periphery structure 15 | #' sg <- split_graph(n = 20, p = 0.3, core = 0.5) 16 | #' core_periphery(sg) 17 | #' @export 18 | core_periphery <- function(graph, method = "rk1_dc", iter = 500, ...) { 19 | A <- igraph::as_adj(graph, type = "both", sparse = FALSE) 20 | if (method == "SA") { 21 | warning("method='SA' is deprecated, using 'GA' instead") 22 | method <- "GA" 23 | } else if (method == "GA") { 24 | if (!requireNamespace("GA", quietly = TRUE)) { 25 | stop("The package 'GA' is needed for method='GA'") 26 | } 27 | 28 | n <- nrow(A) 29 | 30 | res <- GA::ga("real-valued", 31 | fitness = cp_fct1__0, A = A, 32 | lower = rep(0, n), upper = rep(1, n), 33 | monitor = FALSE, maxiter = iter, maxFitness = 1, ... 34 | ) 35 | return(list( 36 | vec = unname(round(res@solution[nrow(res@solution), ])), 37 | corr = res@fitnessValue 38 | )) 39 | } else if (method == "rk1_dc") { 40 | ev <- igraph::degree(graph, mode = "all", loops = FALSE) 41 | 42 | thresh <- unique(ev) 43 | optcorr <- -2 44 | 45 | for (tr in thresh) { 46 | evabs <- (ev >= tr) + 0 47 | E <- outer(evabs, evabs, "+") 48 | E[E == 1] <- NA 49 | E[E == 2] <- 1 50 | diag(E) <- NA 51 | if (sum(E, na.rm = TRUE) == 0) { 52 | next() 53 | } 54 | tmp <- suppressWarnings(graph_cor(E, A)) 55 | if (is.na(tmp)) { 56 | next() 57 | } 58 | if (tmp > optcorr) { 59 | optperm <- evabs 60 | optcorr <- tmp 61 | } 62 | } 63 | return(list(vec = optperm, corr = optcorr)) 64 | } else if (method == "rk1_ec") { 65 | ev <- round(igraph::eigen_centrality(graph)$vector, 8) 66 | 67 | thresh <- unique(ev) 68 | optcorr <- -2 69 | 70 | for (tr in thresh) { 71 | evabs <- (ev >= tr) + 0 72 | E <- outer(evabs, evabs, "+") 73 | E[E == 1] <- NA 74 | E[E == 2] <- 1 75 | diag(E) <- NA 76 | if (sum(E, na.rm = TRUE) == 0) { 77 | next() 78 | } 79 | tmp <- suppressWarnings(graph_cor(E, A)) 80 | if (is.na(tmp)) { 81 | next() 82 | } 83 | if (tmp > optcorr) { 84 | optperm <- evabs 85 | optcorr <- tmp 86 | } 87 | } 88 | return(list(vec = optperm, corr = optcorr)) 89 | } else { 90 | stop("method must be one of 'SA', 'rk1_dc', or 'rk1_ec'") 91 | } 92 | } 93 | 94 | 95 | # helper functions ---- 96 | cp_fct1__0 <- function(A, cvec) { # core=1 periphery=0 97 | cvec <- round(cvec) 98 | delta <- outer(cvec, cvec, "+") 99 | delta[delta == 1] <- NA 100 | delta[delta == 2] <- 1 101 | diag(delta) <- NA 102 | suppressWarnings(graph_cor(delta, A)) 103 | } 104 | -------------------------------------------------------------------------------- /R/dyad_census_attr.R: -------------------------------------------------------------------------------- 1 | #' dyad census with node attributes 2 | #' 3 | #' @param g igraph object. should be a directed graph. 4 | #' @param vattr name of vertex attribute to be used. 5 | #' @return dyad census as a data.frame. 6 | #' @details The node attribute should be integers from 1 to max(attr) 7 | #' @author David Schoch 8 | #' @examples 9 | #' library(igraph) 10 | #' g <- sample_gnp(10, 0.4, directed = TRUE) 11 | #' V(g)$attr <- c(rep(1, 5), rep(2, 5)) 12 | #' dyad_census_attr(g, "attr") 13 | #' @export 14 | dyad_census_attr <- function(g, vattr) { 15 | if (!igraph::is_directed(g)) { 16 | stop("g must be a directed graph") 17 | } 18 | if (!vattr %in% igraph::vertex_attr_names(g)) { 19 | stop(paste0("there is no vertex attribute called ", vattr)) 20 | } 21 | attr <- igraph::vertex_attr(g, vattr) 22 | if (!all(is.numeric(attr))) { 23 | stop("vertex attribute must be numeric ") 24 | } 25 | ns <- table(attr) 26 | 27 | df <- igraph::as_data_frame(g, "both") 28 | nodes <- df$vertices 29 | edges <- df$edges 30 | 31 | edges["from_attr"] <- nodes[[vattr]][edges[["from"]]] 32 | edges["to_attr"] <- nodes[[vattr]][edges[["to"]]] 33 | edges[["reci"]] <- igraph::which_mutual(g) 34 | edges[["count"]] <- 1 35 | 36 | asym <- tryCatch(stats::aggregate(count ~ from_attr + to_attr, data = edges[!edges[["reci"]], ], FUN = sum), 37 | error = function(e) cbind(expand.grid(from_attr = 1:max(attr), to_attr = 1:max(attr)), count = 0) 38 | ) 39 | sym <- tryCatch(stats::aggregate(count ~ from_attr + to_attr, data = edges[edges[["reci"]], ], FUN = sum), 40 | error = function(e) cbind(expand.grid(from_attr = 1:max(attr), to_attr = 1:max(attr)), count = 0) 41 | ) 42 | 43 | idxx <- sym[["from_attr"]] == sym[["to_attr"]] 44 | sym[["count"]][idxx] <- sym[["count"]][idxx] / 2 45 | sym <- sym[sym[["from_attr"]] <= sym[["to_attr"]], ] 46 | 47 | idyx <- asym[["from_attr"]] > asym[["to_attr"]] 48 | asym2 <- asym[idyx, ] 49 | names(asym2) <- c(names(asym2)[2:1], "count2") 50 | asym <- merge(asym, asym2) 51 | names(asym)[3:4] <- c("asym_ab", "asym_ba") 52 | 53 | dc_df <- expand.grid(from_attr = 1:max(attr), to_attr = 1:max(attr)) 54 | dc_df <- dc_df[dc_df[["from_attr"]] <= dc_df[["to_attr"]], ] 55 | dc_df <- merge(dc_df, asym, all.x = TRUE) 56 | dc_df[["asym_ab"]][is.na(dc_df[["asym_ab"]])] <- 0 57 | dc_df[["asym_ba"]][is.na(dc_df[["asym_ba"]])] <- 0 58 | 59 | dc_df <- merge(dc_df, sym, all.x = TRUE) 60 | names(dc_df)[5] <- "sym" 61 | dc_df[["sym"]][is.na(dc_df[["sym"]])] <- 0 62 | dc_df[["null"]] <- ifelse(dc_df[["from_attr"]] == dc_df[["to_attr"]], 63 | choose(ns[dc_df[["from_attr"]]], 2) - dc_df[["asym_ab"]] - dc_df[["asym_ba"]] - dc_df[["sym"]], 64 | ns[dc_df[["from_attr"]]] * ns[dc_df[["to_attr"]]] - dc_df[["asym_ab"]] - dc_df[["asym_ba"]] - dc_df[["sym"]] 65 | ) 66 | dc_df 67 | } 68 | -------------------------------------------------------------------------------- /R/fast_cliques.R: -------------------------------------------------------------------------------- 1 | #' @title Find Cliques, maximal or not, fast 2 | #' @description Enumerates all (maximal) cliques using MACE. Can be faster than igraph in some circumstances 3 | #' @param g An igraph object 4 | #' @param what either "M" for maximal cliques or "C" for all cliques 5 | #' @param min Numeric constant, lower limit on the size of the cliques to find. NULL means no limit, ie. it is the same as 0 6 | #' @param max Numeric constant, upper limit on the size of the cliques to find. NULL means no limit 7 | #' @param outfile character. If not NA, cliques are written to file 8 | #' @details C Code downloaded from http://research.nii.ac.jp/~uno/codes.htm. Download the code and run make and then point an environment variable called MACE_PATH to the binary. See http://research.nii.ac.jp/~uno/code/mace.html for more details. MACE is faster than igraph for dense graphs. 9 | #' @return a list containing numeric vectors of vertex ids. Each list element is a clique. If outfile!=NA, the output is written to the specified file 10 | #' @author David Schoch 11 | #' @references Kazuhisa Makino, Takeaki Uno, "New Algorithms for Enumerating All Maximal Cliques", Lecture Notes in Computer Science 3111 (Proceedings of SWAT 2004), Springer, pp.260-272, 2004 12 | fast_cliques <- function(g, what = "M", min = NULL, max = NULL, outfile = NA) { 13 | if (!igraph::is.igraph(g)) { 14 | stop("g must be an igraph object") 15 | } 16 | what <- match.arg(what, c("M", "C")) 17 | what <- paste0(what, "_") 18 | ########## 19 | binary <- Sys.getenv("MACE_PATH") 20 | if (binary == "") { 21 | stop("MACE not found. Please set the MACE_PATH as environment variable.") 22 | } 23 | ########## 24 | fin <- tempfile() 25 | if (is.na(outfile)) { 26 | fout <- tempfile() 27 | } else { 28 | fout <- outfile 29 | } 30 | # adj <- igraph::get.adjlist(g) 31 | adj <- as_adj_list1(g) 32 | adj <- lapply(1:length(adj), function(x) { 33 | neigh <- adj[[x]] 34 | neigh[neigh > x] - 1 35 | }) 36 | writeLines(sapply(adj, paste, collapse = ","), fin) 37 | if (!is.null(min)) { 38 | what <- paste(what, "-l", min) 39 | } 40 | if (!is.null(max)) { 41 | what <- paste(what, "-u", max) 42 | } 43 | cmd <- paste(binary, what, fin, fout) 44 | a <- system(cmd, intern = TRUE) 45 | 46 | unlink(fin) 47 | if (is.na(outfile)) { 48 | clix <- readLines(fout) 49 | lst <- sapply(clix, function(x) strsplit(x, split = " ")) 50 | lst <- lapply(lst, function(x) as.integer(x) + 1) 51 | lst <- unname(lst) 52 | unlink(fout) 53 | return(lst) 54 | } else { 55 | invisible(g) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /R/graph_products.R: -------------------------------------------------------------------------------- 1 | #' @title Cartesian product of two graphs 2 | #' @description Compute the Cartesian product of two graphs 3 | #' @param g An igraph object 4 | #' @param h An igraph object 5 | #' @details See https://en.wikipedia.org/wiki/Cartesian_product_of_graphs 6 | #' @return Cartesian product as igraph object 7 | #' @author David Schoch 8 | #' @examples 9 | #' library(igraph) 10 | #' g <- make_ring(4) 11 | #' h <- make_full_graph(2) 12 | #' graph_cartesian(g, h) 13 | #' @export 14 | graph_cartesian <- function(g, h) { 15 | elg <- igraph::as_edgelist(g) 16 | elh <- igraph::as_edgelist(h) 17 | vg <- 1:igraph::vcount(g) 18 | vh <- 1:igraph::vcount(h) 19 | el <- matrix(0, 0, 2) 20 | for (i in seq_len(nrow(elg))) { 21 | el_tmp <- matrix(apply(expand.grid(elg[i, ], vh), 1, function(x) paste(x, collapse = "-")), ncol = 2, byrow = TRUE) 22 | el <- rbind(el, el_tmp) 23 | } 24 | for (i in seq_len(nrow(elh))) { 25 | el_tmp <- matrix(apply(expand.grid(elh[i, ], vg), 1, function(x) paste(rev(x), collapse = "-")), ncol = 2, byrow = TRUE) 26 | el <- rbind(el, el_tmp) 27 | } 28 | igraph::graph_from_edgelist(el, F) 29 | } 30 | 31 | # direct graph product ---- 32 | #' @title Direct product of two graphs 33 | #' @description Compute the direct product of two graphs 34 | #' @param g An igraph object 35 | #' @param h An igraph object 36 | #' @details See https://en.wikipedia.org/wiki/Tensor_product_of_graphs 37 | #' @return Direct product as igraph object 38 | #' @author David Schoch 39 | #' @examples 40 | #' library(igraph) 41 | #' g <- make_ring(4) 42 | #' h <- make_full_graph(2) 43 | #' graph_direct(g, h) 44 | #' @export 45 | graph_direct <- function(g, h) { 46 | elg <- igraph::as_edgelist(g) 47 | elh <- igraph::as_edgelist(h) 48 | el <- matrix(0, 0, 2) 49 | for (i in seq_len(nrow(elg))) { 50 | for (j in seq_len(nrow(elh))) { 51 | edg <- c(paste0(elg[i, 1], "-", elh[j, 1]), paste0(elg[i, 2], "-", elh[j, 2])) 52 | el <- rbind(el, edg) 53 | edg <- c(paste0(elg[i, 2], "-", elh[j, 1]), paste0(elg[i, 1], "-", elh[j, 2])) 54 | el <- rbind(el, edg) 55 | } 56 | } 57 | igraph::graph_from_edgelist(el, directed = FALSE) 58 | } 59 | -------------------------------------------------------------------------------- /R/graph_structures.R: -------------------------------------------------------------------------------- 1 | #' @title Adjacency list 2 | #' @description Create adjacency lists from a graph, either for adjacent edges or for neighboring vertices. This version is faster than the version of igraph but less general. 3 | #' @param g An igraph object 4 | #' @details The function does not have a mode parameter and only returns the adjacency list comparable to as_adj_list(g,mode="all) 5 | #' @return A list of numeric vectors. 6 | #' @author David Schoch 7 | #' @examples 8 | #' library(igraph) 9 | #' g <- make_ring(10) 10 | #' as_adj_list1(g) 11 | #' @export 12 | as_adj_list1 <- function(g) { 13 | n <- igraph::vcount(g) 14 | lapply(1:n, function(i) { 15 | x <- g[[i]][[1]] 16 | attr(x, "env") <- NULL 17 | attr(x, "graph") <- NULL 18 | class(x) <- NULL 19 | x 20 | }) 21 | } 22 | 23 | #' @title weighted dense adjacency matrix 24 | #' @description returns the weighted adjacency matrix in dense format 25 | #' @param g An igraph object 26 | #' @param attr Either NULL or a character string giving an edge attribute name. If NULL a traditional adjacency matrix is returned. If not NULL then the values of the given edge attribute are included in the adjacency matrix. 27 | #' @details This method is faster than as_adj from igraph if you need the weighted adjacency matrix in dense format 28 | #' @return Numeric matrix 29 | #' @author David Schoch 30 | #' @examples 31 | #' library(igraph) 32 | #' g <- sample_gnp(10, 0.2) 33 | #' E(g)$weight <- runif(ecount(g)) 34 | #' as_adj_weighted(g, attr = "weight") 35 | #' @export 36 | as_adj_weighted <- function(g, attr = NULL) { 37 | as.matrix(igraph::as_adj(g, attr = attr, type = "both", sparse = TRUE)) 38 | } 39 | 40 | 41 | #' @title Clique Vertex Matrix 42 | #' @description Creates the clique vertex matrix with entries (i,j) equal to one if node j is in clique i 43 | #' @param g An igraph object 44 | #' @return Numeric matrix 45 | #' @author David Schoch 46 | #' @examples 47 | #' library(igraph) 48 | #' g <- sample_gnp(10, 0.2) 49 | #' clique_vertex_mat(g) 50 | #' @export 51 | clique_vertex_mat <- function(g) { 52 | if (!igraph::is_igraph(g)) { 53 | stop("g must be an igraph object") 54 | } 55 | if (igraph::is_directed(g)) { 56 | warning("g is directed. Underlying undirected graph is used") 57 | g <- igraph::as.undirected(g) 58 | } 59 | mcl <- igraph::max_cliques(g) 60 | M <- matrix(0, length(mcl), igraph::vcount(g)) 61 | for (i in seq_len(length(mcl))) { 62 | M[i, mcl[[i]]] <- 1 63 | } 64 | M 65 | } 66 | 67 | 68 | #' @title Convert a list of graphs to an adjacency matrices 69 | #' @description Convenience function that turns a list of igraph objects into adjacency matrices. 70 | #' @param g_lst A list of igraph object 71 | #' @param attr Either NULL or a character string giving an edge attribute name. If NULL a binary adjacency matrix is returned. 72 | #' @param sparse Logical scalar, whether to create a sparse matrix. The 'Matrix' package must be installed for creating sparse matrices. 73 | #' @return List of numeric matrices 74 | #' @author David Schoch 75 | #' @export 76 | as_multi_adj <- function(g_lst, attr = NULL, sparse = FALSE) { 77 | if (!all(unlist(lapply(g_lst, igraph::is.igraph)))) { 78 | stop("all entries of g_lst must be igraph objects") 79 | } 80 | lapply(g_lst, function(x) igraph::as_adj(x, "both", attr = attr, sparse = sparse)) 81 | } 82 | -------------------------------------------------------------------------------- /R/graphs.R: -------------------------------------------------------------------------------- 1 | #' @title two-mode network from a data.frame 2 | #' @description Create a two-mode network from a data.frame 3 | #' 4 | #' @param d data.frame 5 | #' @param type1 column name of mode 1 6 | #' @param type2 column name of mode 2 7 | #' @param attr named list of edge attributes 8 | #' @param weighted should a weighted graph be created if multiple edges occur 9 | #' @return two mode network as igraph object 10 | #' @author David Schoch 11 | #' @examples 12 | #' library(igraph) 13 | #' edges <- data.frame(mode1 = 1:5, mode2 = letters[1:5]) 14 | #' bipartite_from_data_frame(edges, "mode1", "mode2") 15 | #' @export 16 | 17 | bipartite_from_data_frame <- function(d, type1, type2, attr = NULL, weighted = TRUE) { 18 | if (!type1 %in% names(d)) { 19 | stop(paste0("no column named ", type1, " found in data frame")) 20 | } 21 | if (!type2 %in% names(d)) { 22 | stop(paste0("no column named ", type2, " found in data frame")) 23 | } 24 | 25 | mode1 <- unique(d[[type1]]) 26 | mode2 <- unique(d[[type2]]) 27 | if (any(mode1 %in% mode2)) { 28 | stop("some nodes appear in both modes. Modes in a two-mode network must be distinct.") 29 | } 30 | el <- cbind(d[[type1]], d[[type2]]) 31 | 32 | g <- igraph::graph.empty(directed = FALSE) 33 | g <- igraph::add_vertices(g, nv = length(mode1), attr = list(name = mode1, type = TRUE)) 34 | g <- igraph::add_vertices(g, nv = length(mode2), attr = list(name = mode2, type = FALSE)) 35 | if (!is.null(attr)) { 36 | g <- igraph::add_edges(g, c(t(el)), attr = attr) 37 | } else { 38 | g <- igraph::add_edges(g, c(t(el))) 39 | } 40 | if (igraph::any_multiple(g) && weighted) { 41 | igraph::E(g)$weight <- 1 42 | g <- igraph::simplify(g, 43 | remove.multiple = TRUE, 44 | remove.loops = TRUE, 45 | edge.attr.comb = "sum" 46 | ) 47 | } 48 | g 49 | } 50 | 51 | #' @title Multiple networks from a single edgelist with a typed attribute 52 | #' @description Create a list of igraph objects from an edgelist according to a type attribute 53 | #' 54 | #' @param d data frame. 55 | #' @param from column name of sender. If NULL, defaults to first column. 56 | #' @param to column of receiver. If NULL, defaults to second column. 57 | #' @param type type attribute to split the edgelist. If NULL, defaults to third column. 58 | #' @param weight optional column name of edge weights. Ignored if NULL. 59 | #' @param directed logical scalar, whether or not to create a directed graph. 60 | #' @return list of igraph objects. 61 | #' @author David Schoch 62 | #' @examples 63 | #' library(igraph) 64 | #' d <- data.frame( 65 | #' from = rep(c(1, 2, 3), 3), to = rep(c(2, 3, 1), 3), 66 | #' type = rep(c("a", "b", "c"), each = 3), weight = 1:9 67 | #' ) 68 | #' graph_from_multi_edgelist(d, "from", "to", "type", "weight") 69 | #' @export 70 | 71 | graph_from_multi_edgelist <- function(d, from = NULL, to = NULL, type = NULL, weight = NULL, directed = FALSE) { 72 | d <- as.data.frame(d) 73 | if (ncol(d) < 2) { 74 | stop("the data frame should contain at least three columns") 75 | } 76 | dnames <- names(d) 77 | 78 | if (is.null(from)) { 79 | from <- dnames[1] 80 | } 81 | if (is.null(to)) { 82 | to <- dnames[2] 83 | } 84 | if (is.null(type)) { 85 | type <- dnames[3] 86 | } 87 | if (is.null(weight)) { 88 | d <- d[, c(from, to, type)] 89 | } else { 90 | d <- d[, c(from, to, weight, type)] 91 | } 92 | 93 | if (!from %in% dnames) { 94 | stop(paste0(from, " is not a valid column name")) 95 | } 96 | if (!to %in% dnames) { 97 | stop(paste0(to, " is not a valid column name")) 98 | } 99 | if (!type %in% dnames) { 100 | stop(paste0(type, " is not a valid column name")) 101 | } 102 | 103 | d_lst <- split(d, d[[type]]) 104 | g_lst <- lapply(d_lst, function(x) igraph::graph_from_data_frame(x, directed = directed)) 105 | g_lst 106 | } 107 | 108 | #' @title k partite graphs 109 | #' @description Create a random k-partite graph. 110 | #' 111 | #' @param n number of nodes 112 | #' @param grp vector of partition sizes 113 | #' @return igraph object 114 | #' @author David Schoch 115 | #' @examples 116 | #' # 3-partite graph with equal sized groups 117 | #' graph_kpartite(n = 15, grp = c(5, 5, 5)) 118 | #' @export 119 | 120 | graph_kpartite <- function(n = 10, grp = c(5, 5)) { 121 | g <- igraph::graph.empty(n = n, directed = FALSE) 122 | cur_node <- 1 123 | nodes <- 1:n 124 | for (i in 1:(length(grp) - 1)) { 125 | add_nodes <- cur_node:(cur_node + grp[i] - 1) 126 | add_edges <- c(t(expand.grid(add_nodes, nodes[nodes > max(add_nodes)]))) 127 | g <- igraph::add_edges(g, add_edges) 128 | cur_node <- cur_node + grp[i] 129 | } 130 | return(g) 131 | } 132 | 133 | #' @title split graph 134 | #' @description Create a random split graph with a perfect core-periphery structure. 135 | #' 136 | #' @param n number of nodes 137 | #' @param p probability of peripheral nodes to connect to the core nodes 138 | #' @param core fraction of nodes in the core 139 | #' @return igraph object 140 | #' @author David Schoch 141 | #' @examples 142 | #' # split graph with 20 nodes and a core size of 10 143 | #' split_graph(n = 20, p = 0.4, 0.5) 144 | #' @export 145 | split_graph <- function(n, p, core) { 146 | ncore <- floor(n * core) 147 | nperi <- n - ncore 148 | Acore <- matrix(1, ncore, ncore) 149 | Aperi <- (matrix(stats::runif(ncore * nperi), ncore, nperi) < p) + 0 150 | A <- rbind(cbind(Acore, Aperi), cbind(t(Aperi), matrix(0, nperi, nperi))) 151 | g <- igraph::graph_from_adjacency_matrix(A, "undirected", diag = FALSE) 152 | igraph::V(g)$core <- FALSE 153 | igraph::V(g)$core[1:ncore] <- TRUE 154 | g 155 | } 156 | -------------------------------------------------------------------------------- /R/lfr_benchmark.R: -------------------------------------------------------------------------------- 1 | #' LFR benchmark graphs 2 | #' @description Generates benchmark networks for clustering tasks with a priori known communities. The algorithm accounts for the heterogeneity in the distributions of node degrees and of community sizes. 3 | #' @param n Number of nodes in the created graph. 4 | #' @param tau1 Power law exponent for the degree distribution of the created graph. This value must be strictly greater than one 5 | #' @param tau2 Power law exponent for the community size distribution in the created graph. This value must be strictly greater than one 6 | #' @param mu Fraction of inter-community edges incident to each node. This value must be in the interval 0 to 1. 7 | #' @param average_degree Desired average degree of nodes in the created graph. This value must be in the interval 0 to n. Exactly one of this and `min_degree` must be specified, otherwise an error is raised 8 | #' @param max_degree Maximum degree of nodes in the created graph. If not specified, this is set to n-1. 9 | #' @param min_community Minimum size of communities in the graph. If not specified, this is set to `min_degree` 10 | #' @param max_community Maximum size of communities in the graph. If not specified, this is set to n, the total number of nodes in the graph. 11 | #' @param on number of overlapping nodes 12 | #' @param om number of memberships of the overlapping nodes 13 | #' @return an igraph object 14 | #' @references A. Lancichinetti, S. Fortunato, and F. Radicchi.(2008) Benchmark graphs for testing community detection algorithms. Physical Review E, 78. arXiv:0805.4770 15 | #' @details code adapted from 16 | #' @examples 17 | #' # Simple Girven-Newman benchmark graphs 18 | #' g <- sample_lfr( 19 | #' n = 128, average_degree = 16, 20 | #' max_degree = 16, mu = 0.1, 21 | #' min_community = 32, max_community = 32 22 | #' ) 23 | #' @export 24 | sample_lfr <- function(n, 25 | tau1 = 2, 26 | tau2 = 1, 27 | mu = 0.1, 28 | average_degree, 29 | max_degree, 30 | min_community = NULL, 31 | max_community = NULL, 32 | on = 0, 33 | om = 0) { 34 | if (missing(average_degree)) { 35 | stop("average_degree must be specified") 36 | } 37 | 38 | if (average_degree > n || average_degree <= 0) { 39 | stop("average_degree must be in the interval (0, n]") 40 | } 41 | 42 | if (missing(max_degree)) { 43 | stop("max_degree must be specified") 44 | } 45 | if (max_degree > n || max_degree <= 0) { 46 | stop("max_degree must be in the interval (0, n]") 47 | } 48 | 49 | if (!(tau1 >= 1)) { 50 | stop("tau1 must be greater than one") 51 | } 52 | if (!(tau2 >= 1)) { 53 | stop("tau2 must be greater than one") 54 | } 55 | if (mu < 0 || mu > 1) { 56 | stop("mu must be in the interval [0, 1]") 57 | } 58 | 59 | if (!is.null(min_community) && !is.null(max_community)) { 60 | crange <- TRUE 61 | } 62 | 63 | if (is.null(min_community) && is.null(max_community)) { 64 | min_community <- 0 65 | max_community <- 0 66 | crange <- FALSE 67 | } else if (xor(is.null(min_community), is.null(max_community))) { 68 | stop("both min_community and max_community need to either be specified or not") 69 | } 70 | 71 | res <- benchmark(FALSE, FALSE, 72 | num_nodes = n, 73 | average_k = average_degree, 74 | max_degree = max_degree, 75 | tau = tau1, 76 | tau2 = tau2, 77 | mixing_parameter = mu, 78 | overlapping_nodes = on, 79 | overlap_membership = om, 80 | nmin = min_community, 81 | nmax = max_community, 82 | fixed_range = crange 83 | ) 84 | 85 | g <- igraph::graph_from_adj_list(lapply(res[["edgelist"]], function(x) x + 1), mode = "all") 86 | igraph::V(g)$membership <- unlist(res$membership) + 1 87 | g 88 | } 89 | -------------------------------------------------------------------------------- /R/netUtils-package.R: -------------------------------------------------------------------------------- 1 | ## usethis namespace: start 2 | #' @importFrom Rcpp sourceCpp 3 | #' @useDynLib netUtils, .registration = TRUE 4 | ## usethis namespace: end 5 | NULL 6 | -------------------------------------------------------------------------------- /R/print_igraph.R: -------------------------------------------------------------------------------- 1 | #' @title Print graphs to terminal 2 | #' @description Prints an igraph object to terminal (different than the standard igraph method) 3 | #' @param object An igraph object 4 | #' @param ... additional arguments to print (ignored) 5 | #' @return str does not return anything. The obvious side effect is output to the terminal. 6 | #' @author David Schoch 7 | #' @export 8 | str.igraph <- function(object, ...) { 9 | maxpr <- getOption("width") 10 | gattrs <- igraph::graph.attributes(object) 11 | vattrs <- igraph::vertex.attributes(object) 12 | eattrs <- igraph::edge.attributes(object) 13 | comps <- igraph::components(object) 14 | dens <- igraph::graph.density(object) 15 | 16 | # header 17 | if (!"name" %in% names(gattrs)) { 18 | gname <- "Unnamed Network" 19 | } else { 20 | gname <- gattrs[["name"]] 21 | } 22 | 23 | head <- paste(toupper(gname), " ", 24 | c("(undirected", "(directed")[igraph::is.directed(object) + 1], ", ", 25 | c("unweighted", "weighted")[igraph::is.weighted(object) + 1], ", ", 26 | c("", "signed, ")["sign" %in% names(eattrs) + 1], 27 | c("one-mode", "two-mode")[igraph::is.bipartite(object) + 1], " ", 28 | "network)", 29 | sep = "" 30 | ) 31 | 32 | head <- paste0(head, "\n") 33 | delim <- paste0(rep("-", min(c(nchar(head), maxpr))), collapse = "") 34 | delim <- paste0(delim, "\n") 35 | short_delim <- "---\n" 36 | 37 | # graph details 38 | n <- paste0("Nodes: ", igraph::vcount(object)) 39 | m <- paste0("Edges: ", igraph::ecount(object)) 40 | d <- paste0("Density: ", ifelse(dens < 1e-4, "<1e-4", round(dens, 4))) 41 | cc <- paste0("Components: ", comps$no) 42 | iso <- paste0("Isolates: ", sum(igraph::degree(object) == 0)) 43 | gstats <- paste(n, m, d, cc, iso, sep = ", ") 44 | gstats <- paste0(strwrap(gstats), collapse = "\n") 45 | gstats <- paste0(gstats, "\n") 46 | 47 | # graph attrs 48 | gattr_str <- "" 49 | if (length(gattrs) > 0) { 50 | gattr_str <- apply(cbind(names(gattrs), paste0("(", substr(sapply(gattrs, mode), 1, 1), ")")), 1, paste0, collapse = "") 51 | gattr_str <- paste("-Graph Attributes:\n ", paste0(gattr_str, collapse = ", ")) 52 | gattr_str <- paste0(gattr_str, "\n", short_delim) 53 | } 54 | 55 | # vertex attrs 56 | vattr_str <- "" 57 | for (l in seq_along(vattrs)) { 58 | peek <- head_dot(vattrs[[l]], nchar(names(vattrs)[l]) + 4) 59 | if (l == 1) { 60 | vattr_str <- "-Vertex Attributes:\n " 61 | vattr_str <- paste0(vattr_str, paste0(names(vattrs)[l], "(", substr(mode(vattrs[[l]]), 1, 1), "): ", peek, "\n")) 62 | } else { 63 | vattr_str <- paste0(vattr_str, paste0(" ", names(vattrs)[l], "(", substr(mode(vattrs[[l]]), 1, 1), "): ", peek, "\n")) 64 | } 65 | } 66 | if (length(vattrs) > 0) { 67 | vattr_str <- paste0(vattr_str, short_delim) 68 | } 69 | 70 | eattr_str <- "" 71 | for (l in seq_along(eattrs)) { 72 | peek <- head_dot(eattrs[[l]], nchar(names(eattrs)[l]) + 4) 73 | if (l == 1) { 74 | eattr_str <- "-Edge Attributes:\n " 75 | eattr_str <- paste0(eattr_str, paste0(names(eattrs)[l], "(", substr(mode(eattrs[[l]]), 1, 1), "): ", peek, "\n")) 76 | } else { 77 | eattr_str <- paste0(eattr_str, paste0(" ", names(eattrs)[l], "(", substr(mode(eattrs[[l]]), 1, 1), "): ", peek, "\n")) 78 | } 79 | } 80 | 81 | if (length(eattrs) > 0) { 82 | eattr_str <- paste0(eattr_str, short_delim) 83 | } 84 | if (igraph::ecount(object) > 0) { 85 | edges <- igraph::get.edgelist(object)[1:min(c(10, igraph::ecount(object))), ] 86 | edges <- strwrap(paste0(apply(edges, 1, paste0, collapse = c("--", "->")[igraph::is.directed(object) + 1]), collapse = " ")) 87 | edges <- paste(edges, collapse = "\n") 88 | if (igraph::ecount(object) > 10) { 89 | edges <- c("-Edges (first 10): \n ", edges) 90 | } else { 91 | edges <- c("-Edges: \n ", edges) 92 | } 93 | } else { 94 | edges <- "" 95 | } 96 | cat(delim, head, delim, gstats, gattr_str, vattr_str, eattr_str, edges, sep = "") 97 | } 98 | 99 | 100 | head_dot <- function(x, lname) { 101 | stri <- strwrap(paste0(x, collapse = ", "), width = 0.9 * getOption("width") - lname)[1] 102 | paste0(stri, " ...") 103 | } 104 | -------------------------------------------------------------------------------- /R/qap.R: -------------------------------------------------------------------------------- 1 | #' Graph correlation 2 | #' 3 | #' This function computes the correlation between networks. Implemented methods expect 4 | #' the graph to be an adjacency matrix, an igraph, or a network object. 5 | #' 6 | #' @param object1 igraph object or adjacency matrix 7 | #' @param object2 igraph object or adjacency matrix over the same vertex set as object1 8 | #' @param ... additional arguments 9 | #' 10 | #' @return correlation between graphs 11 | #' 12 | #' @export 13 | graph_cor <- function(object1, object2) UseMethod("graph_cor") 14 | 15 | #' @rdname graph_cor 16 | #' @method graph_cor default 17 | #' @export 18 | graph_cor.default <- function(object1, object2) { 19 | stop("don't know how to handle class ", dQuote(data.class(object1)), " and ", dQuote(data.class(object2))) 20 | } 21 | 22 | #' @rdname graph_cor 23 | #' @method graph_cor igraph 24 | #' @export 25 | graph_cor.igraph <- function(object1, object2, ...) { 26 | A1 <- igraph::as_adj(object1, type = "both", sparse = FALSE, ...) 27 | A2 <- igraph::as_adj(object2, type = "both", sparse = FALSE, ...) 28 | graph_cor.matrix(A1, A2) 29 | } 30 | 31 | #' @rdname graph_cor 32 | #' @method graph_cor matrix 33 | #' @export 34 | graph_cor.matrix <- function(object1, object2) { 35 | stats::cor(c(object1), c(object2), use = "complete.obs") 36 | } 37 | 38 | #' @rdname graph_cor 39 | #' @method graph_cor array 40 | #' @export 41 | graph_cor.array <- function(object1, object2) { 42 | stats::cor(c(object1), c(object2), use = "complete.obs") 43 | } 44 | -------------------------------------------------------------------------------- /R/reciprocity.R: -------------------------------------------------------------------------------- 1 | #' Reciprocity correlation coefficient 2 | #' 3 | #' @param g igraph object. should be a directed graph 4 | #' @return Reciprocity as a correlation 5 | #' @details 6 | #' The usual definition of reciprocity has some defects. It cannot tell the relative difference of reciprocity compared with purely random network with the same number of vertices and edges. 7 | #' The useful information from reciprocity is not the value itself, but whether mutual links occur more or less often than expected by chance. 8 | #' 9 | #' To overcome this issue, reciprocity can be defined as the correlation coefficient between the entries of the adjacency matrix of a directed graph: 10 | #' \deqn{ 11 | #' \frac{\sum_{i\neq j} (a_{ij} - a')((a_{ji} - a')}{\sum_{i\neq j} (a_{ij} - a')^2} 12 | #' } 13 | #' where a' is the density of g. 14 | #' 15 | #' This definition gives an absolute quantity which directly allows one to distinguish between reciprocal (>0) and antireciprocal (< 0) networks, with mutual links occurring more and less often than random respectively. 16 | #' @references Diego Garlaschelli; Loffredo, Maria I. (2004). "Patterns of Link Reciprocity in Directed Networks". Physical Review Letters. American Physical Society. 93 (26): 268701 17 | #' @author David Schoch 18 | #' @examples 19 | #' library(igraph) 20 | #' g <- sample_gnp(20, p = 0.3, directed = TRUE) 21 | #' reciprocity(g) 22 | #' reciprocity_cor(g) 23 | #' @export 24 | reciprocity_cor <- function(g) { 25 | if (!igraph::is_igraph(g)) { 26 | stop("g must be an igraph object") 27 | } 28 | if (!igraph::is_directed(g)) { 29 | stop("g must be directed") 30 | } 31 | r <- igraph::reciprocity(g) 32 | d <- igraph::edge_density(g) 33 | if (d == 1) { 34 | return(0) 35 | } else { 36 | (r - d) / (1 - d) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /R/sample_kcores.R: -------------------------------------------------------------------------------- 1 | #' Generate random graphs with a given coreness sequence 2 | #' @description Similar to \link[igraph]{sample_degseq} just with \link[igraph]{coreness} 3 | #' @param cores coreness sequence 4 | #' @details The code is an adaption of the python code from https://github.com/ktvank/Random-Graphs-with-Prescribed-K-Core-Sequences/ 5 | #' @return igraph object of graph with the same coreness sequence as the input 6 | #' @references 7 | #' Van Koevering, Katherine, Austin R. Benson, and Jon Kleinberg. 2021. ‘Random Graphs with Prescribed K-Core Sequences: A New Null Model for Network Analysis’. ArXiv:2102.12604. https://doi.org/10.1145/3442381.3450001. 8 | #' @author David Schoch 9 | #' @examples 10 | #' library(igraph) 11 | #' g1 <- make_graph("Zachary") 12 | #' kcores1 <- coreness(g1) 13 | #' g2 <- sample_coreseq(kcores1) 14 | #' kcores2 <- coreness(g2) 15 | #' 16 | #' # the sorted arrays are the same 17 | #' all(sort(kcores1) == sort(kcores2)) 18 | #' 19 | #' @export 20 | sample_coreseq <- function(cores) { 21 | cores <- sort(cores, decreasing = TRUE) 22 | 23 | if (!is_kcoreseq(cores)) { 24 | stop("sequence is not a valid kcore sequence") 25 | } 26 | K <- cores[1] 27 | 28 | indices <- lapply(0:K, function(x) which(cores == x)) 29 | 30 | if (K == 0) { 31 | n0 <- length(indices[[1]]) 32 | g <- igraph::make_empty_graph(n = n0, directed = FALSE) 33 | return(g) 34 | } else if (K == 1) { 35 | n0 <- length(indices[[1]]) 36 | n1 <- length(indices[[2]]) 37 | g <- igraph::make_empty_graph(n = n1, directed = FALSE) 38 | g <- igraph::add_edges(g, edges = c(t(cbind(1:(n1 - 1), 2:n1)))) 39 | g <- igraph::add_vertices(g, nv = n0) 40 | return(g) 41 | } else if (K == 2) { 42 | n0 <- length(indices[[1]]) 43 | n1 <- length(indices[[2]]) 44 | n2 <- length(indices[[3]]) 45 | g <- igraph::graph.empty(n = n2, directed = FALSE) 46 | g <- igraph::add_edges(g, edges = c(t(cbind(1:(n2 - 1), 2:n2)))) 47 | g <- igraph::add_edges(g, edges = c(1, n2)) 48 | g <- add_lower_nodes(cores, indices, g, 1) 49 | return(g) 50 | } else { 51 | g <- igraph::graph.empty(directed = FALSE) 52 | g <- generate_k_graph(K, sum(cores == K), g) 53 | g <- add_lower_nodes(cores, indices, g, K - 1) 54 | return(g) 55 | } 56 | } 57 | 58 | is_kcoreseq <- function(cores) { 59 | cores <- sort(cores, decreasing = TRUE) 60 | num_max_core_val <- sum(cores == max(cores)) 61 | return(num_max_core_val >= cores[1]) 62 | } 63 | 64 | add_lower_nodes <- function(cores, indices, g, k) { 65 | K <- cores[1] 66 | higher_nodes <- sort(unname(unlist(indices[(k + 2):(K + 1)]))) 67 | g <- igraph::add_vertices(g, nv = length(indices[[k + 1]])) 68 | if (k == 0) { 69 | return(g) 70 | } 71 | for (v in indices[[k + 1]]) { 72 | randv <- sample(higher_nodes, k, replace = FALSE) 73 | g <- igraph::add_edges(g, c(t(cbind(randv, v)))) 74 | } 75 | return(add_lower_nodes(cores, indices, g, k - 1)) 76 | } 77 | 78 | generate_k_graph <- function(C, N, g) { 79 | if (!((C %% 2) == (N %% 2))) { 80 | g <- igraph::add_vertices(g, nv = N) 81 | g <- igraph::add_edges(g, edges = c(t(cbind(1:(N - 1), 2:N)))) 82 | g <- igraph::add_edges(g, edges = c(1, N)) 83 | z <- ceiling((N - C + 1) / 2) 84 | for (i in 0:(N - 1)) { 85 | start <- (i + z + 1) %% (N) # BUG POTENTIAL!!! 86 | stop <- (i - z) %% (N) # BUG POTENTIAL!!! 87 | 88 | if (stop >= start) { 89 | listv <- start:(stop - 1) # BUG POTENTIAL!!! 90 | edges <- c(t(cbind(listv, i))) 91 | } else { 92 | listv <- c((0:(N - 1))[0:(stop)], (0:(N - 1))[(start + 1):N]) 93 | edges <- c(t(cbind(listv, i))) 94 | } 95 | g <- igraph::add_edges(g, edges + 1) 96 | g <- igraph::simplify(g) 97 | } 98 | } else { 99 | g <- generate_k_graph(C, N - 1, g) 100 | g <- igraph::add_vertices(g, 1) 101 | randv <- sample(1:(N - 1), C) 102 | g <- igraph::add_edges(g, c(t(cbind(randv, N)))) 103 | } 104 | return(g) 105 | } 106 | -------------------------------------------------------------------------------- /R/sample_pa_homophilic.R: -------------------------------------------------------------------------------- 1 | #' Homophilic random graph using BA preferential attachment model 2 | #' @description A graph of n nodes is grown by attaching new nodes each with m 3 | #' edges that are preferentially attached to existing nodes with high 4 | #' degree, depending on the homophily parameters. 5 | #' @param n number of nodes 6 | #' @param m number of edges a new node is connected to 7 | #' @param minority_fraction fraction of nodes that belong to the minority group 8 | #' @param h_ab probability to connect a node from group a with groub b 9 | #' @param h_ba probability to connect a node from group b with groub a. If NULL, h_ab is used. 10 | #' @param directed should a directed network be created 11 | #' @details The code is an adaption of the python code from https://github.com/gesiscss/HomophilicNtwMinorities/ 12 | #' @return igraph object 13 | #' @references 14 | #' Karimi, F., Génois, M., Wagner, C., Singer, P., & Strohmaier, M. (2018). Homophily influences ranking of minorities in social networks. Scientific reports, 8(1), 1-12. (https://www.nature.com/articles/s41598-018-29405-7) 15 | #' 16 | #' Espín-Noboa, L., Wagner, C., Strohmaier, M., & Karimi, F. (2022). Inequality and inequity in network-based ranking and recommendation algorithms. Scientific reports, 12(1), 1-14. (https://www.nature.com/articles/s41598-022-05434-1) 17 | #' @author David Schoch 18 | #' #maximally heterophilic network 19 | #' sample_pa_homophilic(n = 50, m = 2,minority_fraction = 0.2,h_ab = 1) 20 | #' #maximally homophilic network 21 | #' sample_pa_homophilic(n = 50, m = 2,minority_fraction = 0.2,h_ab = 0) 22 | #' @export 23 | sample_pa_homophilic <- function(n, m, minority_fraction, h_ab, h_ba = NULL, directed = FALSE) { 24 | if (is.null(h_ba)) { 25 | h_ba <- h_ab 26 | } 27 | h_aa <- 1 - h_ab 28 | h_bb <- 1 - h_ba 29 | minority_attr <- sample( 30 | c( 31 | rep(TRUE, floor(minority_fraction * n)), 32 | rep(FALSE, n - floor(minority_fraction * n)) 33 | ) 34 | ) 35 | 36 | g <- igraph::make_empty_graph(n = 0, directed = directed) 37 | g <- igraph::add_vertices(g, n, attr = list(minority = minority_attr)) 38 | 39 | dist <- matrix(NA, n, n) 40 | dist[outer(minority_attr, minority_attr, "&")] <- h_aa # within minority 41 | dist[outer(!minority_attr, !minority_attr, "&")] <- h_bb # within majority 42 | dist[outer(minority_attr, !minority_attr, "&")] <- h_ab # min->maj 43 | dist[outer(!minority_attr, minority_attr, "&")] <- h_ba # maj->min 44 | 45 | 46 | target_list <- seq_len(m) 47 | source <- m + 1 48 | while (source <= n) { 49 | deg <- igraph::degree(g) 50 | targets <- pick_targets(deg, source, target_list, dist, m) 51 | if (length(targets != 0)) { 52 | el <- rbind(source, targets) 53 | g <- igraph::add_edges(g, c(el)) 54 | } 55 | target_list <- c(target_list, source) 56 | source <- source + 1 57 | } 58 | return(g) 59 | } 60 | 61 | pick_targets <- function(deg, source, target_list, dist, m) { 62 | target_prob <- dist[source, target_list] * (deg[target_list] + 1e-5) 63 | 64 | if (sum(target_prob > 0) < m) { 65 | return(c()) 66 | } else { 67 | targets <- sample(target_list, m, prob = target_prob) 68 | return(targets) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /R/structural_equivalence.R: -------------------------------------------------------------------------------- 1 | #' @title Maximal Structural Equivalence 2 | #' @description Calculates structural equivalence for an undirected graph 3 | #' @param g An igraph object 4 | #' @details Two nodes u and v are structurally equivalent if they have exactly the same neighbors. The equivalence classes produced with this function are either cliques or empty graphs. 5 | #' @return vector of equivalence classes 6 | #' @author David Schoch 7 | #' @export 8 | structural_equivalence <- function(g) { 9 | if (igraph::is_directed(g)) { 10 | stop("g must be undirected") 11 | } 12 | adj <- lapply(igraph::neighborhood(g, mindist = 1), function(x) x - 1) 13 | deg <- igraph::degree(g) 14 | P <- mse(adj, deg) 15 | MSE <- which((P + t(P)) == 2, arr.ind = TRUE) 16 | if (length(MSE) >= 1) { 17 | MSE <- t(apply(MSE, 1, sort)) 18 | MSE <- MSE[!duplicated(MSE), ] 19 | g <- igraph::make_empty_graph() 20 | g <- igraph::add_vertices(g, nrow(P)) 21 | g <- igraph::add_edges(g, c(t(MSE))) 22 | g <- igraph::as.undirected(g) 23 | MSE <- igraph::components(g, "weak")$membership 24 | } else { 25 | MSE <- seq_len(nrow(P)) 26 | } 27 | return(MSE) 28 | } 29 | -------------------------------------------------------------------------------- /R/triad_census_attr.R: -------------------------------------------------------------------------------- 1 | #' triad census with node attributes 2 | #' 3 | #' @param g igraph object. should be a directed graph 4 | #' @param vattr name of vertex attribute to be used 5 | #' @return triad census with node attributes 6 | #' @details The node attribute should be integers from 1 to max(attr). 7 | #' The output is a named vector where the names are of the form Txxx-abc, where xxx corresponds to the standard triad census notation and "abc" are the attributes of the involved nodes. 8 | #' 9 | #' The implemented algorithm is comparable to the algorithm in Lienert et al. 10 | #' @references Lienert, J., Koehly, L., Reed-Tsochas, F., & Marcum, C. S. (2019). An efficient counting method for the colored triad census. Social Networks, 58, 136-142. 11 | #' @author David Schoch 12 | #' @examples 13 | #' library(igraph) 14 | #' set.seed(112) 15 | #' g <- sample_gnp(20, p = 0.3, directed = TRUE) 16 | #' # add a vertex attribute 17 | #' V(g)$type <- rep(1:2, each = 10) 18 | #' triad_census_attr(g, "type") 19 | #' @export 20 | triad_census_attr <- function(g, vattr) { 21 | if (!igraph::is_directed(g)) { 22 | stop("g must be a directed graph") 23 | } 24 | if (!vattr %in% igraph::vertex_attr_names(g)) { 25 | stop(paste0("there is no vertex attribute called ", vattr)) 26 | } 27 | attr <- igraph::vertex_attr(g, vattr) 28 | if (!all(is.numeric(attr))) { 29 | stop("vertex attribute must be numeric ") 30 | } 31 | A <- igraph::as_adj(g) 32 | 33 | orbit_classes <- matrix(c( 34 | 0, 0, 0, 2, 3, 1, 2, 1, 3, 11, 12, 12, 3, 2, 1, 5, 5, 4, 6, 7, 8, 14, 15, 13, 35 | 1, 2, 3, 7, 6, 8, 10, 10, 9, 23, 24, 22, 12, 11, 12, 15, 14, 13, 24, 23, 22, 36 | 26, 26, 25, 3, 1, 2, 6, 8, 7, 5, 4, 5, 14, 13, 15, 9, 10, 10, 17, 18, 16, 37 | 17, 16, 18, 20, 19, 19, 8, 7, 6, 21, 21, 21, 18, 16, 17, 30, 29, 31, 38 | 22, 23, 24, 31, 30, 29, 28, 27, 28, 33, 32, 34, 1, 3, 2, 10, 9, 10, 7, 8, 6, 39 | 23, 22, 24, 8, 6, 7, 18, 17, 16, 21, 21, 21, 30, 31, 29, 4, 5, 5, 16, 17, 18, 40 | 16, 18, 17, 27, 28, 28, 13, 14, 15, 19, 20, 19, 29, 30, 31, 32, 33, 34, 41 | 12, 12, 11, 24, 22, 23, 15, 13, 14, 26, 25, 26, 22, 24, 23, 28, 28, 27, 42 | 31, 29, 30, 33, 34, 32, 13, 15, 14, 29, 31, 30, 19, 19, 20, 32, 34, 33, 43 | 25, 26, 26, 34, 33, 32, 34, 32, 33, 35, 35, 35 44 | ), ncol = 3, byrow = T) 45 | tri_names <- c( 46 | "003", "012", "012", "021D", "012", "102", "021C", "111U", "012", "021C", "021U", "030T", 47 | "021D", "111U", "030T", "120U", "012", "021C", "102", "111U", "021U", "111D", "111D", "201", 48 | "021C", "030C", "111D", "120C", "030T", "120C", "120D", "210", "012", "021U", "021C", "030T", 49 | "021C", "111D", "030C", "120C", "102", "111D", "111D", "120D", "111U", "201", "120C", "210", 50 | "021D", "030T", "111U", "120U", "030T", "120D", "120C", "210", "111U", "120C", "201", "210", 51 | "120U", "210", "210", "300" 52 | ) 53 | tri_names <- paste0("T", tri_names) 54 | attrcomb <- as.matrix(expand.grid(1:max(attr), 1:max(attr), 1:max(attr))) 55 | 56 | code_sort <- function(vorb, attr, i) { 57 | idx <- order(vorb, attr) 58 | vorbs <- vorb[idx] 59 | attrs <- attr[idx] 60 | if (all(vorbs == 21) && length(unique(attr)) == 3) { 61 | if (i == 39) { 62 | attrs <- rev(attrs) 63 | } 64 | } 65 | return(paste0(paste0(vorbs, collapse = ""), "-", paste0(attrs, collapse = ""))) 66 | } 67 | 68 | triad_names <- c() 69 | triad_names1 <- c() 70 | for (i in seq_len(nrow(orbit_classes))) { 71 | for (j in seq_len(nrow(attrcomb))) { 72 | new <- code_sort(orbit_classes[i, ], attrcomb[j, ], i) 73 | triad_names <- unique(c(triad_names, new)) 74 | triad_names1 <- unique(c(triad_names1, paste0(tri_names[i], "-", gsub(".*-", "", new)))) 75 | } 76 | } 77 | triads <- rep(0L, length(triad_names)) 78 | names(triads) <- triad_names 79 | triads <- triadCensusCol(A, attr, orbit_classes, triads) 80 | names(triads) <- triad_names1 81 | triads 82 | } 83 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' @title helper function 2 | #' @description small functions to deal with typical network problems 3 | #' 4 | #' @param g igraph object 5 | #' @name helpers 6 | #' @return igraph object 7 | #' @author David Schoch 8 | NULL 9 | 10 | #' @rdname helpers 11 | #' @export 12 | biggest_component <- function(g) { 13 | comps <- igraph::components(g, mode = "weak") 14 | igraph::induced_subgraph(g, which(comps$membership == which.max(comps$csize))) 15 | } 16 | 17 | #' @rdname helpers 18 | #' @export 19 | delete_isolates <- function(g) { 20 | igraph::delete_vertices(g, which(igraph::degree(g) == 0)) 21 | } 22 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | # netUtils 16 | 17 | 18 | [![CRAN status](https://www.r-pkg.org/badges/version/netUtils)](https://CRAN.R-project.org/package=netUtils) 19 | [![CRAN Downloads](https://cranlogs.r-pkg.org/badges/netUtils)](https://CRAN.R-project.org/package=netUtils) 20 | [![R-CMD-check](https://github.com/schochastics/netUtils/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/schochastics/netUtils/actions/workflows/R-CMD-check.yaml) 21 | [![Codecov test coverage](https://codecov.io/gh/schochastics/netUtils/graph/badge.svg)](https://app.codecov.io/gh/schochastics/netUtils) 22 | 23 | 24 | netUtils is a collection of tools for network analysis that may not deserve a package on their own and/or are missing from other network packages. 25 | 26 | ## Installation 27 | 28 | You can install the development version of netUtils with: 29 | 30 | ```{r install, eval=FALSE} 31 | # install.packages("remotes") 32 | remotes::install_github("schochastics/netUtils") 33 | ``` 34 | 35 | ## Functions 36 | most functions only support igraph objects 37 | 38 | **helper/convenience functions** 39 | `biggest_component()` extracts the biggest connected component of a network. 40 | `delete_isolates()` deletes vertices with degree zero. 41 | `bipartite_from_data_frame()` creates a two mode network from a data frame. 42 | `graph_from_multi_edgelist()` creates multiple graphs from a typed edgelist. 43 | `clique_vertex_mat()` computes the clique vertex matrix. 44 | `graph_cartesian()` computes the Cartesian product of two graphs. 45 | `graph_direct()` computes the direct (or tensor) product of graphs. 46 | `str()` extends str to work with igraph objects. 47 | 48 | **methods** 49 | `dyad_census_attr()` calculates dyad census with node attributes. 50 | `triad_census_attr()` calculates triad census with node attributes. 51 | `core_periphery()` fits a discrete core periphery model. 52 | `graph_kpartite()` creates a random k-partite network. 53 | `split_graph()` sample graph with perfect core periphery structure. 54 | `sample_coreseq()` creates a random graph with given coreness sequence. 55 | `sample_pa_homophilic()` creates a preferential attachment graph with two groups of nodes. 56 | `sample_lfr()` create LFR benchmark graph for community detection. 57 | `structural_equivalence()` finds structurally equivalent vertices. 58 | `reciprocity_cor()` reciprocity as a correlation coefficient. 59 | 60 | **methods to use with caution** 61 | *(this functions should only be used if you know what you are doing)* 62 | `as_adj_list1()` extracts the adjacency list faster, but less stable, from igraph objects. 63 | `as_adj_weighted()` extracts the dense weighted adjacency matrix fast. 64 | -------------------------------------------------------------------------------- /README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 588 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 |

netUtils 607 |

608 | 609 | 610 |

CRAN status CRAN Downloads R-CMD-check Codecov test coverage

611 | 612 | 613 |

netUtils is a collection of tools for network analysis that may not 614 | deserve a package on their own and/or are missing from other network 615 | packages.

616 |

Installation

617 |

You can install the development version of netUtils with:

618 |
# install.packages("remotes")
619 | remotes::install_github("schochastics/netUtils")
620 |

Functions

621 |

most functions only support igraph objects

622 |

helper/convenience functions
623 | biggest_component() extracts the biggest connected 624 | component of a network.
625 | delete_isolates() deletes vertices with degree zero.
626 | bipartite_from_data_frame() creates a two mode network from 627 | a data frame.
628 | graph_from_multi_edgelist() creates multiple graphs from a 629 | typed edgelist.
630 | clique_vertex_mat() computes the clique vertex 631 | matrix.
632 | graph_cartesian() computes the Cartesian product of two 633 | graphs.
634 | graph_direct() computes the direct (or tensor) product of 635 | graphs.
636 | str() extends str to work with igraph objects.

637 |

methods
638 | dyad_census_attr() calculates dyad census with node 639 | attributes.
640 | triad_census_attr() calculates triad census with node 641 | attributes.
642 | core_periphery() fits a discrete core periphery 643 | model.
644 | graph_kpartite() creates a random k-partite network.
645 | split_graph() sample graph with perfect core periphery 646 | structure.
647 | sample_coreseq() creates a random graph with given coreness 648 | sequence.
649 | sample_pa_homophilic() creates a preferential attachment 650 | graph with two groups of nodes.
651 | sample_lfr() create LFR benchmark graph for community 652 | detection.
653 | structural_equivalence() finds structurally equivalent 654 | vertices.
655 | reciprocity_cor() reciprocity as a correlation 656 | coefficient.

657 |

methods to use with caution
658 | (this functions should only be used if you know what you are 659 | doing)
660 | as_adj_list1() extracts the adjacency list faster, but less 661 | stable, from igraph objects.
662 | as_adj_weighted() extracts the dense weighted adjacency 663 | matrix fast.

664 | 665 | 666 | 667 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # netUtils 5 | 6 | 7 | 8 | [![CRAN 9 | status](https://www.r-pkg.org/badges/version/netUtils)](https://CRAN.R-project.org/package=netUtils) 10 | [![CRAN 11 | Downloads](https://cranlogs.r-pkg.org/badges/netUtils)](https://CRAN.R-project.org/package=netUtils) 12 | [![R-CMD-check](https://github.com/schochastics/netUtils/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/schochastics/netUtils/actions/workflows/R-CMD-check.yaml) 13 | [![Codecov test 14 | coverage](https://codecov.io/gh/schochastics/netUtils/graph/badge.svg)](https://app.codecov.io/gh/schochastics/netUtils) 15 | 16 | 17 | netUtils is a collection of tools for network analysis that may not 18 | deserve a package on their own and/or are missing from other network 19 | packages. 20 | 21 | ## Installation 22 | 23 | You can install the development version of netUtils with: 24 | 25 | ``` r 26 | # install.packages("remotes") 27 | remotes::install_github("schochastics/netUtils") 28 | ``` 29 | 30 | ## Functions 31 | 32 | most functions only support igraph objects 33 | 34 | **helper/convenience functions** 35 | `biggest_component()` extracts the biggest connected component of a 36 | network. 37 | `delete_isolates()` deletes vertices with degree zero. 38 | `bipartite_from_data_frame()` creates a two mode network from a data 39 | frame. 40 | `graph_from_multi_edgelist()` creates multiple graphs from a typed 41 | edgelist. 42 | `clique_vertex_mat()` computes the clique vertex matrix. 43 | `graph_cartesian()` computes the Cartesian product of two graphs. 44 | `graph_direct()` computes the direct (or tensor) product of graphs. 45 | `str()` extends str to work with igraph objects. 46 | 47 | **methods** 48 | `dyad_census_attr()` calculates dyad census with node attributes. 49 | `triad_census_attr()` calculates triad census with node attributes. 50 | `core_periphery()` fits a discrete core periphery model. 51 | `graph_kpartite()` creates a random k-partite network. 52 | `split_graph()` sample graph with perfect core periphery structure. 53 | `sample_coreseq()` creates a random graph with given coreness 54 | sequence. 55 | `sample_pa_homophilic()` creates a preferential attachment graph with 56 | two groups of nodes. 57 | `sample_lfr()` create LFR benchmark graph for community detection. 58 | `structural_equivalence()` finds structurally equivalent vertices. 59 | `reciprocity_cor()` reciprocity as a correlation coefficient. 60 | 61 | **methods to use with caution** 62 | *(this functions should only be used if you know what you are doing)* 63 | `as_adj_list1()` extracts the adjacency list faster, but less stable, 64 | from igraph objects. 65 | `as_adj_weighted()` extracts the dense weighted adjacency matrix fast. 66 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://schochastics.github.io/netUtils/ 2 | template: 3 | bootstrap: 5 4 | bslib: 5 | bg: "#F9F7F7" 6 | fg: "#112D4E" 7 | primary: "#3F72AF" 8 | navbar-color: "#FFFFFF" 9 | base_font: {google: "Roboto"} 10 | heading_font: {google: "Roboto Slab"} 11 | code_font: {google: "JetBrains Mono"} 12 | navbar: 13 | bg: none 14 | type: light 15 | structure: 16 | left: [intro, reference, articles, news] 17 | right: [github] 18 | 19 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Update from 0.8.2 to 0.8.3 2 | 3 | * added more tests 4 | * require igraph 2.0.0 5 | 6 | ## Test environments 7 | * ubuntu 24.04, R 4.4.1 8 | 9 | 10 | ## R CMD check results 11 | 12 | 0 errors | 0 warnings | 0 note 13 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | citHeader("To cite netUtils in publications use:") 2 | 3 | bibentry( 4 | bibtype = "Manual", 5 | title = "netUtils: A Collection of Tools for Network Analysis", 6 | author = "David Schoch", 7 | year = "2023", 8 | textVersion = "David Schoch (2023). netUtils: A Collection of Tools for Network Analysis" 9 | ) 10 | -------------------------------------------------------------------------------- /man/as_adj_list1.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graph_structures.R 3 | \name{as_adj_list1} 4 | \alias{as_adj_list1} 5 | \title{Adjacency list} 6 | \usage{ 7 | as_adj_list1(g) 8 | } 9 | \arguments{ 10 | \item{g}{An igraph object} 11 | } 12 | \value{ 13 | A list of numeric vectors. 14 | } 15 | \description{ 16 | Create adjacency lists from a graph, either for adjacent edges or for neighboring vertices. This version is faster than the version of igraph but less general. 17 | } 18 | \details{ 19 | The function does not have a mode parameter and only returns the adjacency list comparable to as_adj_list(g,mode="all) 20 | } 21 | \examples{ 22 | library(igraph) 23 | g <- make_ring(10) 24 | as_adj_list1(g) 25 | } 26 | \author{ 27 | David Schoch 28 | } 29 | -------------------------------------------------------------------------------- /man/as_adj_weighted.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graph_structures.R 3 | \name{as_adj_weighted} 4 | \alias{as_adj_weighted} 5 | \title{weighted dense adjacency matrix} 6 | \usage{ 7 | as_adj_weighted(g, attr = NULL) 8 | } 9 | \arguments{ 10 | \item{g}{An igraph object} 11 | 12 | \item{attr}{Either NULL or a character string giving an edge attribute name. If NULL a traditional adjacency matrix is returned. If not NULL then the values of the given edge attribute are included in the adjacency matrix.} 13 | } 14 | \value{ 15 | Numeric matrix 16 | } 17 | \description{ 18 | returns the weighted adjacency matrix in dense format 19 | } 20 | \details{ 21 | This method is faster than as_adj from igraph if you need the weighted adjacency matrix in dense format 22 | } 23 | \examples{ 24 | library(igraph) 25 | g <- sample_gnp(10, 0.2) 26 | E(g)$weight <- runif(ecount(g)) 27 | as_adj_weighted(g, attr = "weight") 28 | } 29 | \author{ 30 | David Schoch 31 | } 32 | -------------------------------------------------------------------------------- /man/as_multi_adj.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graph_structures.R 3 | \name{as_multi_adj} 4 | \alias{as_multi_adj} 5 | \title{Convert a list of graphs to an adjacency matrices} 6 | \usage{ 7 | as_multi_adj(g_lst, attr = NULL, sparse = FALSE) 8 | } 9 | \arguments{ 10 | \item{g_lst}{A list of igraph object} 11 | 12 | \item{attr}{Either NULL or a character string giving an edge attribute name. If NULL a binary adjacency matrix is returned.} 13 | 14 | \item{sparse}{Logical scalar, whether to create a sparse matrix. The 'Matrix' package must be installed for creating sparse matrices.} 15 | } 16 | \value{ 17 | List of numeric matrices 18 | } 19 | \description{ 20 | Convenience function that turns a list of igraph objects into adjacency matrices. 21 | } 22 | \author{ 23 | David Schoch 24 | } 25 | -------------------------------------------------------------------------------- /man/bipartite_from_data_frame.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graphs.R 3 | \name{bipartite_from_data_frame} 4 | \alias{bipartite_from_data_frame} 5 | \title{two-mode network from a data.frame} 6 | \usage{ 7 | bipartite_from_data_frame(d, type1, type2, attr = NULL, weighted = TRUE) 8 | } 9 | \arguments{ 10 | \item{d}{data.frame} 11 | 12 | \item{type1}{column name of mode 1} 13 | 14 | \item{type2}{column name of mode 2} 15 | 16 | \item{attr}{named list of edge attributes} 17 | 18 | \item{weighted}{should a weighted graph be created if multiple edges occur} 19 | } 20 | \value{ 21 | two mode network as igraph object 22 | } 23 | \description{ 24 | Create a two-mode network from a data.frame 25 | } 26 | \examples{ 27 | library(igraph) 28 | edges <- data.frame(mode1 = 1:5, mode2 = letters[1:5]) 29 | bipartite_from_data_frame(edges, "mode1", "mode2") 30 | } 31 | \author{ 32 | David Schoch 33 | } 34 | -------------------------------------------------------------------------------- /man/clique_vertex_mat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graph_structures.R 3 | \name{clique_vertex_mat} 4 | \alias{clique_vertex_mat} 5 | \title{Clique Vertex Matrix} 6 | \usage{ 7 | clique_vertex_mat(g) 8 | } 9 | \arguments{ 10 | \item{g}{An igraph object} 11 | } 12 | \value{ 13 | Numeric matrix 14 | } 15 | \description{ 16 | Creates the clique vertex matrix with entries (i,j) equal to one if node j is in clique i 17 | } 18 | \examples{ 19 | library(igraph) 20 | g <- sample_gnp(10, 0.2) 21 | clique_vertex_mat(g) 22 | } 23 | \author{ 24 | David Schoch 25 | } 26 | -------------------------------------------------------------------------------- /man/core_periphery.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/core_periphery.R 3 | \name{core_periphery} 4 | \alias{core_periphery} 5 | \title{Discrete core-periphery model} 6 | \usage{ 7 | core_periphery(graph, method = "rk1_dc", iter = 500, ...) 8 | } 9 | \arguments{ 10 | \item{graph}{igraph object} 11 | 12 | \item{method}{algorithm to use (see details)} 13 | 14 | \item{iter}{number of iterations if \code{method=GA}} 15 | 16 | \item{...}{other parameters for GA} 17 | } 18 | \value{ 19 | list with numeric vector with entries (k1,k2,...ki...) where ki assigns vertex i to either the core (ki=1) or periphery (ki=0), and the maximal correlation with an optimal pattern matrix 20 | } 21 | \description{ 22 | Fits a discrete core-periphery model to a given network 23 | } 24 | \details{ 25 | The function fits the data to an optimal pattern matrix with a genetic algorithm (method="GA") or a rank 1 approximation, either with degree centrality (method="rk1_dc") or eigenvector centrality (method="rk1_ec") . The rank 1 approximation is computationally far cheaper but also more experimental. Best is to compare the results from both models. 26 | } 27 | \examples{ 28 | set.seed(121) 29 | # split graphs have a perfect core-periphery structure 30 | sg <- split_graph(n = 20, p = 0.3, core = 0.5) 31 | core_periphery(sg) 32 | } 33 | \references{ 34 | Borgatti, Stephen P., and Martin G. Everett. "Models of core/periphery structures." Social networks 21.4 (2000): 375-395. 35 | } 36 | \author{ 37 | David Schoch 38 | } 39 | -------------------------------------------------------------------------------- /man/dyad_census_attr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/dyad_census_attr.R 3 | \name{dyad_census_attr} 4 | \alias{dyad_census_attr} 5 | \title{dyad census with node attributes} 6 | \usage{ 7 | dyad_census_attr(g, vattr) 8 | } 9 | \arguments{ 10 | \item{g}{igraph object. should be a directed graph.} 11 | 12 | \item{vattr}{name of vertex attribute to be used.} 13 | } 14 | \value{ 15 | dyad census as a data.frame. 16 | } 17 | \description{ 18 | dyad census with node attributes 19 | } 20 | \details{ 21 | The node attribute should be integers from 1 to max(attr) 22 | } 23 | \examples{ 24 | library(igraph) 25 | g <- sample_gnp(10, 0.4, directed = TRUE) 26 | V(g)$attr <- c(rep(1, 5), rep(2, 5)) 27 | dyad_census_attr(g, "attr") 28 | } 29 | \author{ 30 | David Schoch 31 | } 32 | -------------------------------------------------------------------------------- /man/fast_cliques.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fast_cliques.R 3 | \name{fast_cliques} 4 | \alias{fast_cliques} 5 | \title{Find Cliques, maximal or not, fast} 6 | \usage{ 7 | fast_cliques(g, what = "M", min = NULL, max = NULL, outfile = NA) 8 | } 9 | \arguments{ 10 | \item{g}{An igraph object} 11 | 12 | \item{what}{either "M" for maximal cliques or "C" for all cliques} 13 | 14 | \item{min}{Numeric constant, lower limit on the size of the cliques to find. NULL means no limit, ie. it is the same as 0} 15 | 16 | \item{max}{Numeric constant, upper limit on the size of the cliques to find. NULL means no limit} 17 | 18 | \item{outfile}{character. If not NA, cliques are written to file} 19 | } 20 | \value{ 21 | a list containing numeric vectors of vertex ids. Each list element is a clique. If outfile!=NA, the output is written to the specified file 22 | } 23 | \description{ 24 | Enumerates all (maximal) cliques using MACE. Can be faster than igraph in some circumstances 25 | } 26 | \details{ 27 | C Code downloaded from http://research.nii.ac.jp/~uno/codes.htm. Download the code and run make and then point an environment variable called MACE_PATH to the binary. See http://research.nii.ac.jp/~uno/code/mace.html for more details. MACE is faster than igraph for dense graphs. 28 | } 29 | \references{ 30 | Kazuhisa Makino, Takeaki Uno, "New Algorithms for Enumerating All Maximal Cliques", Lecture Notes in Computer Science 3111 (Proceedings of SWAT 2004), Springer, pp.260-272, 2004 31 | } 32 | \author{ 33 | David Schoch 34 | } 35 | -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schochastics/netUtils/89fe2043de4091e78fbf15da4bf5f6950444a920/man/figures/logo.png -------------------------------------------------------------------------------- /man/graph_cartesian.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graph_products.R 3 | \name{graph_cartesian} 4 | \alias{graph_cartesian} 5 | \title{Cartesian product of two graphs} 6 | \usage{ 7 | graph_cartesian(g, h) 8 | } 9 | \arguments{ 10 | \item{g}{An igraph object} 11 | 12 | \item{h}{An igraph object} 13 | } 14 | \value{ 15 | Cartesian product as igraph object 16 | } 17 | \description{ 18 | Compute the Cartesian product of two graphs 19 | } 20 | \details{ 21 | See https://en.wikipedia.org/wiki/Cartesian_product_of_graphs 22 | } 23 | \examples{ 24 | library(igraph) 25 | g <- make_ring(4) 26 | h <- make_full_graph(2) 27 | graph_cartesian(g, h) 28 | } 29 | \author{ 30 | David Schoch 31 | } 32 | -------------------------------------------------------------------------------- /man/graph_cor.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/qap.R 3 | \name{graph_cor} 4 | \alias{graph_cor} 5 | \alias{graph_cor.default} 6 | \alias{graph_cor.igraph} 7 | \alias{graph_cor.matrix} 8 | \alias{graph_cor.array} 9 | \title{Graph correlation} 10 | \usage{ 11 | graph_cor(object1, object2) 12 | 13 | \method{graph_cor}{default}(object1, object2) 14 | 15 | \method{graph_cor}{igraph}(object1, object2, ...) 16 | 17 | \method{graph_cor}{matrix}(object1, object2) 18 | 19 | \method{graph_cor}{array}(object1, object2) 20 | } 21 | \arguments{ 22 | \item{object1}{igraph object or adjacency matrix} 23 | 24 | \item{object2}{igraph object or adjacency matrix over the same vertex set as object1} 25 | 26 | \item{...}{additional arguments} 27 | } 28 | \value{ 29 | correlation between graphs 30 | } 31 | \description{ 32 | This function computes the correlation between networks. Implemented methods expect 33 | the graph to be an adjacency matrix, an igraph, or a network object. 34 | } 35 | -------------------------------------------------------------------------------- /man/graph_direct.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graph_products.R 3 | \name{graph_direct} 4 | \alias{graph_direct} 5 | \title{Direct product of two graphs} 6 | \usage{ 7 | graph_direct(g, h) 8 | } 9 | \arguments{ 10 | \item{g}{An igraph object} 11 | 12 | \item{h}{An igraph object} 13 | } 14 | \value{ 15 | Direct product as igraph object 16 | } 17 | \description{ 18 | Compute the direct product of two graphs 19 | } 20 | \details{ 21 | See https://en.wikipedia.org/wiki/Tensor_product_of_graphs 22 | } 23 | \examples{ 24 | library(igraph) 25 | g <- make_ring(4) 26 | h <- make_full_graph(2) 27 | graph_direct(g, h) 28 | } 29 | \author{ 30 | David Schoch 31 | } 32 | -------------------------------------------------------------------------------- /man/graph_from_multi_edgelist.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graphs.R 3 | \name{graph_from_multi_edgelist} 4 | \alias{graph_from_multi_edgelist} 5 | \title{Multiple networks from a single edgelist with a typed attribute} 6 | \usage{ 7 | graph_from_multi_edgelist( 8 | d, 9 | from = NULL, 10 | to = NULL, 11 | type = NULL, 12 | weight = NULL, 13 | directed = FALSE 14 | ) 15 | } 16 | \arguments{ 17 | \item{d}{data frame.} 18 | 19 | \item{from}{column name of sender. If NULL, defaults to first column.} 20 | 21 | \item{to}{column of receiver. If NULL, defaults to second column.} 22 | 23 | \item{type}{type attribute to split the edgelist. If NULL, defaults to third column.} 24 | 25 | \item{weight}{optional column name of edge weights. Ignored if NULL.} 26 | 27 | \item{directed}{logical scalar, whether or not to create a directed graph.} 28 | } 29 | \value{ 30 | list of igraph objects. 31 | } 32 | \description{ 33 | Create a list of igraph objects from an edgelist according to a type attribute 34 | } 35 | \examples{ 36 | library(igraph) 37 | d <- data.frame( 38 | from = rep(c(1, 2, 3), 3), to = rep(c(2, 3, 1), 3), 39 | type = rep(c("a", "b", "c"), each = 3), weight = 1:9 40 | ) 41 | graph_from_multi_edgelist(d, "from", "to", "type", "weight") 42 | } 43 | \author{ 44 | David Schoch 45 | } 46 | -------------------------------------------------------------------------------- /man/graph_kpartite.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graphs.R 3 | \name{graph_kpartite} 4 | \alias{graph_kpartite} 5 | \title{k partite graphs} 6 | \usage{ 7 | graph_kpartite(n = 10, grp = c(5, 5)) 8 | } 9 | \arguments{ 10 | \item{n}{number of nodes} 11 | 12 | \item{grp}{vector of partition sizes} 13 | } 14 | \value{ 15 | igraph object 16 | } 17 | \description{ 18 | Create a random k-partite graph. 19 | } 20 | \examples{ 21 | # 3-partite graph with equal sized groups 22 | graph_kpartite(n = 15, grp = c(5, 5, 5)) 23 | } 24 | \author{ 25 | David Schoch 26 | } 27 | -------------------------------------------------------------------------------- /man/helpers.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{helpers} 4 | \alias{helpers} 5 | \alias{biggest_component} 6 | \alias{delete_isolates} 7 | \title{helper function} 8 | \usage{ 9 | biggest_component(g) 10 | 11 | delete_isolates(g) 12 | } 13 | \arguments{ 14 | \item{g}{igraph object} 15 | } 16 | \value{ 17 | igraph object 18 | } 19 | \description{ 20 | small functions to deal with typical network problems 21 | } 22 | \author{ 23 | David Schoch 24 | } 25 | -------------------------------------------------------------------------------- /man/reciprocity_cor.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/reciprocity.R 3 | \name{reciprocity_cor} 4 | \alias{reciprocity_cor} 5 | \title{Reciprocity correlation coefficient} 6 | \usage{ 7 | reciprocity_cor(g) 8 | } 9 | \arguments{ 10 | \item{g}{igraph object. should be a directed graph} 11 | } 12 | \value{ 13 | Reciprocity as a correlation 14 | } 15 | \description{ 16 | Reciprocity correlation coefficient 17 | } 18 | \details{ 19 | The usual definition of reciprocity has some defects. It cannot tell the relative difference of reciprocity compared with purely random network with the same number of vertices and edges. 20 | The useful information from reciprocity is not the value itself, but whether mutual links occur more or less often than expected by chance. 21 | 22 | To overcome this issue, reciprocity can be defined as the correlation coefficient between the entries of the adjacency matrix of a directed graph: 23 | \deqn{ 24 | \frac{\sum_{i\neq j} (a_{ij} - a')((a_{ji} - a')}{\sum_{i\neq j} (a_{ij} - a')^2} 25 | } 26 | where a' is the density of g. 27 | 28 | This definition gives an absolute quantity which directly allows one to distinguish between reciprocal (>0) and antireciprocal (< 0) networks, with mutual links occurring more and less often than random respectively. 29 | } 30 | \examples{ 31 | library(igraph) 32 | g <- sample_gnp(20, p = 0.3, directed = TRUE) 33 | reciprocity(g) 34 | reciprocity_cor(g) 35 | } 36 | \references{ 37 | Diego Garlaschelli; Loffredo, Maria I. (2004). "Patterns of Link Reciprocity in Directed Networks". Physical Review Letters. American Physical Society. 93 (26): 268701 38 | } 39 | \author{ 40 | David Schoch 41 | } 42 | -------------------------------------------------------------------------------- /man/sample_coreseq.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sample_kcores.R 3 | \name{sample_coreseq} 4 | \alias{sample_coreseq} 5 | \title{Generate random graphs with a given coreness sequence} 6 | \usage{ 7 | sample_coreseq(cores) 8 | } 9 | \arguments{ 10 | \item{cores}{coreness sequence} 11 | } 12 | \value{ 13 | igraph object of graph with the same coreness sequence as the input 14 | } 15 | \description{ 16 | Similar to \link[igraph]{sample_degseq} just with \link[igraph]{coreness} 17 | } 18 | \details{ 19 | The code is an adaption of the python code from https://github.com/ktvank/Random-Graphs-with-Prescribed-K-Core-Sequences/ 20 | } 21 | \examples{ 22 | library(igraph) 23 | g1 <- make_graph("Zachary") 24 | kcores1 <- coreness(g1) 25 | g2 <- sample_coreseq(kcores1) 26 | kcores2 <- coreness(g2) 27 | 28 | # the sorted arrays are the same 29 | all(sort(kcores1) == sort(kcores2)) 30 | 31 | } 32 | \references{ 33 | Van Koevering, Katherine, Austin R. Benson, and Jon Kleinberg. 2021. ‘Random Graphs with Prescribed K-Core Sequences: A New Null Model for Network Analysis’. ArXiv:2102.12604. https://doi.org/10.1145/3442381.3450001. 34 | } 35 | \author{ 36 | David Schoch 37 | } 38 | -------------------------------------------------------------------------------- /man/sample_lfr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lfr_benchmark.R 3 | \name{sample_lfr} 4 | \alias{sample_lfr} 5 | \title{LFR benchmark graphs} 6 | \usage{ 7 | sample_lfr( 8 | n, 9 | tau1 = 2, 10 | tau2 = 1, 11 | mu = 0.1, 12 | average_degree, 13 | max_degree, 14 | min_community = NULL, 15 | max_community = NULL, 16 | on = 0, 17 | om = 0 18 | ) 19 | } 20 | \arguments{ 21 | \item{n}{Number of nodes in the created graph.} 22 | 23 | \item{tau1}{Power law exponent for the degree distribution of the created graph. This value must be strictly greater than one} 24 | 25 | \item{tau2}{Power law exponent for the community size distribution in the created graph. This value must be strictly greater than one} 26 | 27 | \item{mu}{Fraction of inter-community edges incident to each node. This value must be in the interval 0 to 1.} 28 | 29 | \item{average_degree}{Desired average degree of nodes in the created graph. This value must be in the interval 0 to n. Exactly one of this and \code{min_degree} must be specified, otherwise an error is raised} 30 | 31 | \item{max_degree}{Maximum degree of nodes in the created graph. If not specified, this is set to n-1.} 32 | 33 | \item{min_community}{Minimum size of communities in the graph. If not specified, this is set to \code{min_degree}} 34 | 35 | \item{max_community}{Maximum size of communities in the graph. If not specified, this is set to n, the total number of nodes in the graph.} 36 | 37 | \item{on}{number of overlapping nodes} 38 | 39 | \item{om}{number of memberships of the overlapping nodes} 40 | } 41 | \value{ 42 | an igraph object 43 | } 44 | \description{ 45 | Generates benchmark networks for clustering tasks with a priori known communities. The algorithm accounts for the heterogeneity in the distributions of node degrees and of community sizes. 46 | } 47 | \details{ 48 | code adapted from \url{https://github.com/synwalk/synwalk-analysis/tree/master/lfr_generator} 49 | } 50 | \examples{ 51 | # Simple Girven-Newman benchmark graphs 52 | g <- sample_lfr( 53 | n = 128, average_degree = 16, 54 | max_degree = 16, mu = 0.1, 55 | min_community = 32, max_community = 32 56 | ) 57 | } 58 | \references{ 59 | A. Lancichinetti, S. Fortunato, and F. Radicchi.(2008) Benchmark graphs for testing community detection algorithms. Physical Review E, 78. arXiv:0805.4770 60 | } 61 | -------------------------------------------------------------------------------- /man/sample_pa_homophilic.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sample_pa_homophilic.R 3 | \name{sample_pa_homophilic} 4 | \alias{sample_pa_homophilic} 5 | \title{Homophilic random graph using BA preferential attachment model} 6 | \usage{ 7 | sample_pa_homophilic( 8 | n, 9 | m, 10 | minority_fraction, 11 | h_ab, 12 | h_ba = NULL, 13 | directed = FALSE 14 | ) 15 | } 16 | \arguments{ 17 | \item{n}{number of nodes} 18 | 19 | \item{m}{number of edges a new node is connected to} 20 | 21 | \item{minority_fraction}{fraction of nodes that belong to the minority group} 22 | 23 | \item{h_ab}{probability to connect a node from group a with groub b} 24 | 25 | \item{h_ba}{probability to connect a node from group b with groub a. If NULL, h_ab is used.} 26 | 27 | \item{directed}{should a directed network be created} 28 | } 29 | \value{ 30 | igraph object 31 | } 32 | \description{ 33 | A graph of n nodes is grown by attaching new nodes each with m 34 | edges that are preferentially attached to existing nodes with high 35 | degree, depending on the homophily parameters. 36 | } 37 | \details{ 38 | The code is an adaption of the python code from https://github.com/gesiscss/HomophilicNtwMinorities/ 39 | } 40 | \references{ 41 | Karimi, F., Génois, M., Wagner, C., Singer, P., & Strohmaier, M. (2018). Homophily influences ranking of minorities in social networks. Scientific reports, 8(1), 1-12. (https://www.nature.com/articles/s41598-018-29405-7) 42 | 43 | Espín-Noboa, L., Wagner, C., Strohmaier, M., & Karimi, F. (2022). Inequality and inequity in network-based ranking and recommendation algorithms. Scientific reports, 12(1), 1-14. (https://www.nature.com/articles/s41598-022-05434-1) 44 | } 45 | \author{ 46 | David Schoch 47 | #maximally heterophilic network 48 | sample_pa_homophilic(n = 50, m = 2,minority_fraction = 0.2,h_ab = 1) 49 | #maximally homophilic network 50 | sample_pa_homophilic(n = 50, m = 2,minority_fraction = 0.2,h_ab = 0) 51 | } 52 | -------------------------------------------------------------------------------- /man/split_graph.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graphs.R 3 | \name{split_graph} 4 | \alias{split_graph} 5 | \title{split graph} 6 | \usage{ 7 | split_graph(n, p, core) 8 | } 9 | \arguments{ 10 | \item{n}{number of nodes} 11 | 12 | \item{p}{probability of peripheral nodes to connect to the core nodes} 13 | 14 | \item{core}{fraction of nodes in the core} 15 | } 16 | \value{ 17 | igraph object 18 | } 19 | \description{ 20 | Create a random split graph with a perfect core-periphery structure. 21 | } 22 | \examples{ 23 | # split graph with 20 nodes and a core size of 10 24 | split_graph(n = 20, p = 0.4, 0.5) 25 | } 26 | \author{ 27 | David Schoch 28 | } 29 | -------------------------------------------------------------------------------- /man/str.igraph.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/print_igraph.R 3 | \name{str.igraph} 4 | \alias{str.igraph} 5 | \title{Print graphs to terminal} 6 | \usage{ 7 | \method{str}{igraph}(object, ...) 8 | } 9 | \arguments{ 10 | \item{object}{An igraph object} 11 | 12 | \item{...}{additional arguments to print (ignored)} 13 | } 14 | \value{ 15 | str does not return anything. The obvious side effect is output to the terminal. 16 | } 17 | \description{ 18 | Prints an igraph object to terminal (different than the standard igraph method) 19 | } 20 | \author{ 21 | David Schoch 22 | } 23 | -------------------------------------------------------------------------------- /man/structural_equivalence.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/structural_equivalence.R 3 | \name{structural_equivalence} 4 | \alias{structural_equivalence} 5 | \title{Maximal Structural Equivalence} 6 | \usage{ 7 | structural_equivalence(g) 8 | } 9 | \arguments{ 10 | \item{g}{An igraph object} 11 | } 12 | \value{ 13 | vector of equivalence classes 14 | } 15 | \description{ 16 | Calculates structural equivalence for an undirected graph 17 | } 18 | \details{ 19 | Two nodes u and v are structurally equivalent if they have exactly the same neighbors. The equivalence classes produced with this function are either cliques or empty graphs. 20 | } 21 | \author{ 22 | David Schoch 23 | } 24 | -------------------------------------------------------------------------------- /man/triad_census_attr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/triad_census_attr.R 3 | \name{triad_census_attr} 4 | \alias{triad_census_attr} 5 | \title{triad census with node attributes} 6 | \usage{ 7 | triad_census_attr(g, vattr) 8 | } 9 | \arguments{ 10 | \item{g}{igraph object. should be a directed graph} 11 | 12 | \item{vattr}{name of vertex attribute to be used} 13 | } 14 | \value{ 15 | triad census with node attributes 16 | } 17 | \description{ 18 | triad census with node attributes 19 | } 20 | \details{ 21 | The node attribute should be integers from 1 to max(attr). 22 | The output is a named vector where the names are of the form Txxx-abc, where xxx corresponds to the standard triad census notation and "abc" are the attributes of the involved nodes. 23 | 24 | The implemented algorithm is comparable to the algorithm in Lienert et al. 25 | } 26 | \examples{ 27 | library(igraph) 28 | set.seed(112) 29 | g <- sample_gnp(20, p = 0.3, directed = TRUE) 30 | # add a vertex attribute 31 | V(g)$type <- rep(1:2, each = 10) 32 | triad_census_attr(g, "type") 33 | } 34 | \references{ 35 | Lienert, J., Koehly, L., Reed-Tsochas, F., & Marcum, C. S. (2019). An efficient counting method for the colored triad census. Social Networks, 58, 136-142. 36 | } 37 | \author{ 38 | David Schoch 39 | } 40 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | -------------------------------------------------------------------------------- /src/RcppExports.cpp: -------------------------------------------------------------------------------- 1 | // Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #include 5 | #include 6 | 7 | using namespace Rcpp; 8 | 9 | #ifdef RCPP_USE_GLOBAL_ROSTREAM 10 | Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); 11 | Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); 12 | #endif 13 | 14 | // benchmark 15 | Rcpp::List benchmark(bool excess, bool defect, int num_nodes, double average_k, int max_degree, double tau, double tau2, double mixing_parameter, int overlapping_nodes, int overlap_membership, int nmin, int nmax, bool fixed_range); 16 | RcppExport SEXP _netUtils_benchmark(SEXP excessSEXP, SEXP defectSEXP, SEXP num_nodesSEXP, SEXP average_kSEXP, SEXP max_degreeSEXP, SEXP tauSEXP, SEXP tau2SEXP, SEXP mixing_parameterSEXP, SEXP overlapping_nodesSEXP, SEXP overlap_membershipSEXP, SEXP nminSEXP, SEXP nmaxSEXP, SEXP fixed_rangeSEXP) { 17 | BEGIN_RCPP 18 | Rcpp::RObject rcpp_result_gen; 19 | Rcpp::RNGScope rcpp_rngScope_gen; 20 | Rcpp::traits::input_parameter< bool >::type excess(excessSEXP); 21 | Rcpp::traits::input_parameter< bool >::type defect(defectSEXP); 22 | Rcpp::traits::input_parameter< int >::type num_nodes(num_nodesSEXP); 23 | Rcpp::traits::input_parameter< double >::type average_k(average_kSEXP); 24 | Rcpp::traits::input_parameter< int >::type max_degree(max_degreeSEXP); 25 | Rcpp::traits::input_parameter< double >::type tau(tauSEXP); 26 | Rcpp::traits::input_parameter< double >::type tau2(tau2SEXP); 27 | Rcpp::traits::input_parameter< double >::type mixing_parameter(mixing_parameterSEXP); 28 | Rcpp::traits::input_parameter< int >::type overlapping_nodes(overlapping_nodesSEXP); 29 | Rcpp::traits::input_parameter< int >::type overlap_membership(overlap_membershipSEXP); 30 | Rcpp::traits::input_parameter< int >::type nmin(nminSEXP); 31 | Rcpp::traits::input_parameter< int >::type nmax(nmaxSEXP); 32 | Rcpp::traits::input_parameter< bool >::type fixed_range(fixed_rangeSEXP); 33 | rcpp_result_gen = Rcpp::wrap(benchmark(excess, defect, num_nodes, average_k, max_degree, tau, tau2, mixing_parameter, overlapping_nodes, overlap_membership, nmin, nmax, fixed_range)); 34 | return rcpp_result_gen; 35 | END_RCPP 36 | } 37 | // mse 38 | IntegerMatrix mse(List adjList, IntegerVector deg); 39 | RcppExport SEXP _netUtils_mse(SEXP adjListSEXP, SEXP degSEXP) { 40 | BEGIN_RCPP 41 | Rcpp::RObject rcpp_result_gen; 42 | Rcpp::RNGScope rcpp_rngScope_gen; 43 | Rcpp::traits::input_parameter< List >::type adjList(adjListSEXP); 44 | Rcpp::traits::input_parameter< IntegerVector >::type deg(degSEXP); 45 | rcpp_result_gen = Rcpp::wrap(mse(adjList, deg)); 46 | return rcpp_result_gen; 47 | END_RCPP 48 | } 49 | // sortxy 50 | IntegerVector sortxy(IntegerVector x, IntegerVector y); 51 | RcppExport SEXP _netUtils_sortxy(SEXP xSEXP, SEXP ySEXP) { 52 | BEGIN_RCPP 53 | Rcpp::RObject rcpp_result_gen; 54 | Rcpp::RNGScope rcpp_rngScope_gen; 55 | Rcpp::traits::input_parameter< IntegerVector >::type x(xSEXP); 56 | Rcpp::traits::input_parameter< IntegerVector >::type y(ySEXP); 57 | rcpp_result_gen = Rcpp::wrap(sortxy(x, y)); 58 | return rcpp_result_gen; 59 | END_RCPP 60 | } 61 | // triadCensusCol 62 | NumericVector triadCensusCol(const arma::sp_mat& A, IntegerVector attr, IntegerMatrix orbitClasses, NumericVector triads); 63 | RcppExport SEXP _netUtils_triadCensusCol(SEXP ASEXP, SEXP attrSEXP, SEXP orbitClassesSEXP, SEXP triadsSEXP) { 64 | BEGIN_RCPP 65 | Rcpp::RObject rcpp_result_gen; 66 | Rcpp::RNGScope rcpp_rngScope_gen; 67 | Rcpp::traits::input_parameter< const arma::sp_mat& >::type A(ASEXP); 68 | Rcpp::traits::input_parameter< IntegerVector >::type attr(attrSEXP); 69 | Rcpp::traits::input_parameter< IntegerMatrix >::type orbitClasses(orbitClassesSEXP); 70 | Rcpp::traits::input_parameter< NumericVector >::type triads(triadsSEXP); 71 | rcpp_result_gen = Rcpp::wrap(triadCensusCol(A, attr, orbitClasses, triads)); 72 | return rcpp_result_gen; 73 | END_RCPP 74 | } 75 | 76 | static const R_CallMethodDef CallEntries[] = { 77 | {"_netUtils_benchmark", (DL_FUNC) &_netUtils_benchmark, 13}, 78 | {"_netUtils_mse", (DL_FUNC) &_netUtils_mse, 2}, 79 | {"_netUtils_sortxy", (DL_FUNC) &_netUtils_sortxy, 2}, 80 | {"_netUtils_triadCensusCol", (DL_FUNC) &_netUtils_triadCensusCol, 4}, 81 | {NULL, NULL, 0} 82 | }; 83 | 84 | RcppExport void R_init_netUtils(DllInfo *dll) { 85 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 86 | R_useDynamicSymbols(dll, FALSE); 87 | } 88 | -------------------------------------------------------------------------------- /src/mse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace Rcpp; 3 | 4 | // [[Rcpp::export]] 5 | IntegerMatrix mse(List adjList, IntegerVector deg) { 6 | int n=deg.size(); 7 | IntegerVector marked(n); 8 | IntegerVector t(n); 9 | IntegerMatrix dom(n,n); 10 | for(int v = 0; v < n; ++v) { 11 | Rcpp::checkUserInterrupt(); 12 | // IntegerVector Nv = as(adjList[v]); 13 | 14 | std::vector Nv = as >(adjList[v]); 15 | //check if isolate 16 | if (Nv.empty()) { 17 | for(int j = 0; j < n; ++j){ 18 | dom(v,j)=1; 19 | } 20 | dom(v,v)=0; 21 | } 22 | 23 | for(std::vector::size_type j = 0; j!=Nv.size(); ++j){ 24 | // for(int j = 0; j < NvSize; ++j) { 25 | int u = Nv[j]; 26 | std::vector Nu=adjList[u]; 27 | Nu.push_back(u); 28 | for(std::vector::size_type i = 0; i!=Nu.size(); ++i){ 29 | // for(int i = 0; i 3 | using namespace Rcpp; 4 | 5 | // [[Rcpp::export]] 6 | IntegerVector sortxy(IntegerVector x, IntegerVector y) { 7 | IntegerVector idx = seq_along(x) - 1; 8 | std::sort(idx.begin(), idx.end(), [&](int i, int j){return y[i] < y[j];}); 9 | for(int i=0; i<2;i++){ 10 | if((y[idx[i]]==y[idx[i+1]]) && (x[idx[i]]>x[idx[i+1]])){ 11 | int tmp= idx[i+1]; 12 | idx[i+1]=idx[i]; 13 | idx[i]=tmp; 14 | } 15 | } 16 | for(int i=0; i<2;i++){ 17 | if((y[idx[i]]==y[idx[i+1]]) && (x[idx[i]]>x[idx[i+1]])){ 18 | int tmp= idx[i+1]; 19 | idx[i+1]=idx[i]; 20 | idx[i]=tmp; 21 | } 22 | } 23 | return x[idx]; 24 | } 25 | 26 | // [[Rcpp::export]] 27 | NumericVector triadCensusCol(const arma::sp_mat& A, 28 | IntegerVector attr, 29 | IntegerMatrix orbitClasses, 30 | NumericVector triads){ 31 | int code=0; 32 | int n=attr.size(); 33 | IntegerVector trorbits(3); 34 | IntegerVector orbits(3); 35 | IntegerVector trattr(3); 36 | IntegerVector attrOrder(3); 37 | IntegerVector idx(3); 38 | IntegerVector vattr(3); 39 | std::string orbStr; 40 | std::string attrStr; 41 | IntegerVector tritypes = {1,2,2,3,2,4,6,8,2,6,5,7,3,8,7,11, 42 | 2,6,4,8,5,9,9,13,6,10,9,14,7,14,12,15, 43 | 2,5,6,7,6,9,10,14,4,9,9,12,8,13,14,15, 44 | 3,7,8,11,7,12,14,15,8,14,13,15,11,15,15,16}; 45 | 46 | for(int u=0;u