├── .Rbuildignore ├── .github ├── .gitignore ├── CODE_OF_CONDUCT.md └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ ├── pr-commands.yaml │ └── test-coverage.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── aaa.R ├── activate.R ├── arrange.R ├── bind.R ├── centrality.R ├── context.R ├── cpp11.R ├── create.R ├── data_frame.R ├── data_tree.R ├── dendrogram.R ├── distinct.R ├── edge.R ├── edge_rank.R ├── filter.R ├── focus.R ├── graph.R ├── graph_measures.R ├── graph_types.R ├── group.R ├── group_by.R ├── hclust.R ├── igraph.R ├── iterate.R ├── joins.R ├── list.R ├── local.R ├── map.R ├── matrix.R ├── morph.R ├── morphers.R ├── mutate.R ├── network.R ├── node.R ├── node_rank.R ├── node_topology.R ├── pair_measures.R ├── phylo.R ├── play.R ├── plot.R ├── pull.R ├── random_walk.R ├── rename.R ├── reroute.R ├── sample_frac.R ├── sample_n.R ├── search.R ├── select.R ├── slice.R ├── tbl_graph.R ├── tibble.R ├── tidygraph-package.R ├── tidyr-utils.R └── utils.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── cran-comments.md ├── man ├── activate.Rd ├── bind_graphs.Rd ├── centrality.Rd ├── component_games.Rd ├── context_accessors.Rd ├── create_graphs.Rd ├── edge_rank.Rd ├── edge_types.Rd ├── evolution_games.Rd ├── figures │ ├── lifecycle-archived.svg │ ├── lifecycle-defunct.svg │ ├── lifecycle-deprecated.svg │ ├── lifecycle-experimental.svg │ ├── lifecycle-maturing.svg │ ├── lifecycle-questioning.svg │ ├── lifecycle-soft-deprecated.svg │ ├── lifecycle-stable.svg │ ├── lifecycle-superseded.svg │ └── logo.png ├── focus.Rd ├── fortify.tbl_graph.Rd ├── graph-context.Rd ├── graph_join.Rd ├── graph_measures.Rd ├── graph_types.Rd ├── group_graph.Rd ├── iterate.Rd ├── local_graph.Rd ├── map_bfs.Rd ├── map_bfs_back.Rd ├── map_dfs.Rd ├── map_dfs_back.Rd ├── map_local.Rd ├── morph.Rd ├── morphers.Rd ├── mutate_as_tbl.Rd ├── node_measures.Rd ├── node_rank.Rd ├── node_topology.Rd ├── node_types.Rd ├── pair_measures.Rd ├── random_walk_rank.Rd ├── reexports.Rd ├── reroute.Rd ├── sampling_games.Rd ├── search_graph.Rd ├── tbl_graph.Rd ├── tidygraph-package.Rd ├── type_games.Rd └── with_graph.Rd ├── pkgdown └── favicon │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── revdep ├── README.md ├── cran.md ├── failures.md └── problems.md ├── src ├── .gitignore ├── cpp11.cpp └── get_paths.cpp └── tests ├── testthat.R └── testthat ├── helper-context.R ├── test-activate.R ├── test-arrange.R ├── test-bind.R ├── test-centrality.R ├── test-context.R ├── test-distinct.R ├── test-edge_types.R ├── test-filter.R ├── test-focus.R ├── test-graph_attributes.R ├── test-graph_measures.R ├── test-group.R ├── test-group_by.R ├── test-iterate.R ├── test-join.R ├── test-local.R ├── test-map.R ├── test-morph.R ├── test-mutate.R ├── test-node_measures.R ├── test-node_types.R ├── test-pair_measures.R ├── test-random-walk.R ├── test-search.R ├── test-slice.R └── test-tidyr-utils.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README\.Rmd$ 4 | ^README-.*\.png$ 5 | ^appveyor\.yml$ 6 | ^\.travis\.yml$ 7 | ^codecov\.yml$ 8 | ^CODE_OF_CONDUCT\.md$ 9 | ^_pkgdown\.yml$ 10 | ^docs$ 11 | ^pkgdown$ 12 | ^\.github$ 13 | ^cran-comments\.md$ 14 | ^revdep$ 15 | ^CRAN-RELEASE$ 16 | ^CRAN-SUBMISSION$ 17 | ^LICENSE\.md$ 18 | -------------------------------------------------------------------------------- /.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 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | branches: [main, master] 12 | 13 | name: R-CMD-check 14 | 15 | jobs: 16 | R-CMD-check: 17 | runs-on: ${{ matrix.config.os }} 18 | 19 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | config: 25 | - {os: macos-latest, r: 'release'} 26 | 27 | - {os: windows-latest, r: 'release'} 28 | # Use 3.6 to trigger usage of RTools35 29 | # - {os: windows-latest, r: '3.6'} removed as dependencies can't compile as they use native pipe 30 | # use 4.1 to check with rtools40's older compiler 31 | - {os: windows-latest, r: '4.1'} 32 | 33 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 34 | - {os: ubuntu-latest, r: 'release'} 35 | - {os: ubuntu-latest, r: 'oldrel-1'} 36 | - {os: ubuntu-latest, r: 'oldrel-2'} 37 | # - {os: ubuntu-latest, r: 'oldrel-3'} same as windows 3.6 38 | # - {os: ubuntu-latest, r: 'oldrel-4'} same as windows 3.6 39 | 40 | env: 41 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 42 | R_KEEP_PKG_SOURCE: yes 43 | 44 | steps: 45 | - uses: actions/checkout@v3 46 | 47 | - uses: r-lib/actions/setup-pandoc@v2 48 | 49 | - uses: r-lib/actions/setup-r@v2 50 | with: 51 | r-version: ${{ matrix.config.r }} 52 | http-user-agent: ${{ matrix.config.http-user-agent }} 53 | use-public-rspm: true 54 | 55 | - uses: r-lib/actions/setup-r-dependencies@v2 56 | with: 57 | extra-packages: any::rcmdcheck 58 | needs: check 59 | 60 | - uses: r-lib/actions/check-r-package@v2 61 | with: 62 | upload-snapshots: true 63 | -------------------------------------------------------------------------------- /.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/pr-commands.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 | issue_comment: 5 | types: [created] 6 | 7 | name: Commands 8 | 9 | jobs: 10 | document: 11 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} 12 | name: document 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - uses: r-lib/actions/pr-fetch@v2 20 | with: 21 | repo-token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - uses: r-lib/actions/setup-r@v2 24 | with: 25 | use-public-rspm: true 26 | 27 | - uses: r-lib/actions/setup-r-dependencies@v2 28 | with: 29 | extra-packages: any::roxygen2 30 | needs: pr-document 31 | 32 | - name: Document 33 | run: roxygen2::roxygenise() 34 | shell: Rscript {0} 35 | 36 | - name: commit 37 | run: | 38 | git config --local user.name "$GITHUB_ACTOR" 39 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 40 | git add man/\* NAMESPACE 41 | git commit -m 'Document' 42 | 43 | - uses: r-lib/actions/pr-push@v2 44 | with: 45 | repo-token: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | style: 48 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} 49 | name: style 50 | runs-on: ubuntu-latest 51 | env: 52 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 53 | steps: 54 | - uses: actions/checkout@v3 55 | 56 | - uses: r-lib/actions/pr-fetch@v2 57 | with: 58 | repo-token: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - uses: r-lib/actions/setup-r@v2 61 | 62 | - name: Install dependencies 63 | run: install.packages("styler") 64 | shell: Rscript {0} 65 | 66 | - name: Style 67 | run: styler::style_pkg() 68 | shell: Rscript {0} 69 | 70 | - name: commit 71 | run: | 72 | git config --local user.name "$GITHUB_ACTOR" 73 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 74 | git add \*.R 75 | git commit -m 'Style' 76 | 77 | - uses: r-lib/actions/pr-push@v2 78 | with: 79 | repo-token: ${{ secrets.GITHUB_TOKEN }} 80 | -------------------------------------------------------------------------------- /.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(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "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 | .Ruserdata 5 | tidygraph.Rproj 6 | docs/ 7 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Type: Package 2 | Package: tidygraph 3 | Title: A Tidy API for Graph Manipulation 4 | Version: 1.3.1.9000 5 | Authors@R: 6 | person("Thomas Lin", "Pedersen", , "thomasp85@gmail.com", role = c("cre", "aut"), 7 | comment = c(ORCID = "0000-0002-5147-4711")) 8 | Maintainer: Thomas Lin Pedersen 9 | Description: A graph, while not "tidy" in itself, can be thought of as two 10 | tidy data frames describing node and edge data respectively. 11 | 'tidygraph' provides an approach to manipulate these two virtual data 12 | frames using the API defined in the 'dplyr' package, as well as 13 | provides tidy interfaces to a lot of common graph algorithms. 14 | License: MIT + file LICENSE 15 | URL: https://tidygraph.data-imaginist.com, 16 | https://github.com/thomasp85/tidygraph 17 | BugReports: https://github.com/thomasp85/tidygraph/issues 18 | Imports: 19 | cli, 20 | dplyr (>= 0.8.5), 21 | igraph (>= 2.0.0), 22 | lifecycle, 23 | magrittr, 24 | pillar, 25 | R6, 26 | rlang, 27 | stats, 28 | tibble, 29 | tidyr, 30 | tools, 31 | utils 32 | Suggests: 33 | ape, 34 | covr, 35 | data.tree, 36 | graph, 37 | influenceR, 38 | methods, 39 | netrankr (>= 1.2.4), 40 | network, 41 | seriation, 42 | testthat (>= 3.0.0) 43 | LinkingTo: 44 | cpp11 45 | Encoding: UTF-8 46 | Roxygen: list(markdown = TRUE) 47 | RoxygenNote: 7.3.1 48 | Config/testthat/edition: 3 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2023 2 | COPYRIGHT HOLDER: tidygraph authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023 tidygraph authors 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 | -------------------------------------------------------------------------------- /R/aaa.R: -------------------------------------------------------------------------------- 1 | #' @importFrom igraph graph_attr graph_attr<- 2 | `%gr_attr%` <- function(e1, e2) { 3 | graph_attr(e1) <- graph_attr(e2) 4 | attributes(e1) <- attributes(e2) 5 | e1 6 | } 7 | 8 | as_ind <- function(i, length) { 9 | seq_len(length)[i] 10 | } 11 | 12 | #' @importFrom igraph gorder 13 | as_node_ind <- function(i, graph) { 14 | if (!missing(i)) { 15 | i <- with_graph(unfocus(graph), {{i}}) 16 | } 17 | as_ind(i, gorder(graph)) 18 | } 19 | 20 | compress_rank <- function(x) { 21 | match(x, sort(x)) 22 | } 23 | 24 | expect_influencer <- function(...) { 25 | rlang::check_installed('influenceR', ...) 26 | } 27 | expect_netrankr <- function(...) { 28 | rlang::check_installed('netrankr', ...) 29 | } 30 | expect_seriation <- function(...) { 31 | rlang::check_installed('seriation', ...) 32 | } 33 | -------------------------------------------------------------------------------- /R/activate.R: -------------------------------------------------------------------------------- 1 | #' Determine the context of subsequent manipulations 2 | #' 3 | #' As a [tbl_graph] can be considered as a collection of two linked tables it is 4 | #' necessary to specify which table is referenced during manipulations. The 5 | #' `activate` verb does just that and needs affects all subsequent manipulations 6 | #' until a new table is activated. `active` is a simple query function to get 7 | #' the currently acitve context. In addition to the use of `activate` it is also 8 | #' possible to activate nodes or edges as part of the piping using the `%N>%` 9 | #' and `%E>%` pipes respectively. Do note that this approach somewhat obscures 10 | #' what is going on and is thus only recommended for quick, one-line, fixes in 11 | #' interactive use. 12 | #' 13 | #' @param .data,x,lhs A tbl_graph or a grouped_tbl_graph 14 | #' 15 | #' @param what What should get activated? Possible values are `nodes` or 16 | #' `edges`. 17 | #' 18 | #' @param rhs A function to pipe into 19 | #' 20 | #' @return A tbl_graph 21 | #' 22 | #' @note Activate will ungroup a grouped_tbl_graph. 23 | #' 24 | #' @export 25 | #' 26 | #' @examples 27 | #' gr <- create_complete(5) %>% 28 | #' activate(nodes) %>% 29 | #' mutate(class = sample(c('a', 'b'), 5, TRUE)) %>% 30 | #' activate(edges) %>% 31 | #' arrange(from) 32 | #' 33 | #' # The above could be achieved using the special pipes as well 34 | #' gr <- create_complete(5) %N>% 35 | #' mutate(class = sample(c('a', 'b'), 5, TRUE)) %E>% 36 | #' arrange(from) 37 | #' # But as you can see it obscures what part of the graph is being targeted 38 | #' 39 | activate <- function(.data, what) { 40 | UseMethod('activate') 41 | } 42 | #' @export 43 | #' @importFrom rlang enquo quo_text 44 | activate.tbl_graph <- function(.data, what) { 45 | if (is.focused_tbl_graph(.data)) { 46 | message('Unfocusing graph...') 47 | .data <- unfocus(.data) 48 | } 49 | active(.data) <- quo_text(enquo(what)) 50 | .data 51 | } 52 | #' @export 53 | #' @importFrom rlang enquo 54 | activate.grouped_tbl_graph <- function(.data, what) { 55 | what <- enquo(what) 56 | if (gsub('"', '', quo_text(what)) == active(.data)) { 57 | return(.data) 58 | } 59 | cli::cli_inform('Ungrouping {.arg .data}...') 60 | .data <- ungroup(.data) 61 | activate(.data, !!what) 62 | } 63 | #' @export 64 | activate.morphed_tbl_graph <- function(.data, what) { 65 | what <- enquo(what) 66 | .data[] <- lapply(.data, activate, what = !!what) 67 | .data 68 | } 69 | 70 | #' @rdname activate 71 | #' @export 72 | active <- function(x) { 73 | attr(x, 'active') 74 | } 75 | `active<-` <- function(x, value) { 76 | value <- gsub('"', '', value) 77 | value <- switch( 78 | value, 79 | vertices = , 80 | nodes = 'nodes', 81 | links = , 82 | edges = 'edges', 83 | cli::cli_abort('Only possible to activate nodes and edges') 84 | ) 85 | attr(x, 'active') <- value 86 | x 87 | } 88 | 89 | #' @rdname activate 90 | #' @importFrom rlang enexpr eval_bare caller_env 91 | #' @importFrom magrittr %>% 92 | #' @export 93 | `%N>%` <- function(lhs, rhs) { 94 | rhs <- enexpr(rhs) 95 | lhs <- activate(lhs, 'nodes') 96 | 97 | # Magrittr does not support inlining so caller 98 | # _must_ have `%>%` in scope 99 | expr <- call('%>%', lhs, rhs) 100 | eval_bare(expr, caller_env()) 101 | } 102 | #' @rdname activate 103 | #' @importFrom rlang enexpr eval_bare caller_env 104 | #' @importFrom magrittr %>% 105 | #' @export 106 | `%E>%` <- function(lhs, rhs) { 107 | rhs <- enexpr(rhs) 108 | lhs <- activate(lhs, 'edges') 109 | 110 | # Magrittr does not support inlining so caller 111 | # _must_ have `%>%` in scope 112 | expr <- call('%>%', lhs, rhs) 113 | eval_bare(expr, caller_env()) 114 | } 115 | -------------------------------------------------------------------------------- /R/arrange.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @importFrom dplyr arrange 3 | arrange.tbl_graph <- function(.data, ...) { 4 | .data <- unfocus(.data) 5 | .register_graph_context(.data) 6 | d_tmp <- as_tibble(.data) 7 | check_reserved(d_tmp) 8 | orig_ind <- seq_len(nrow(d_tmp)) 9 | d_tmp$.tbl_graph_index <- orig_ind 10 | d_tmp <- arrange(d_tmp, ...) 11 | 12 | switch( 13 | active(.data), 14 | nodes = permute_nodes(.data, d_tmp$.tbl_graph_index), 15 | edges = permute_edges(.data, d_tmp$.tbl_graph_index) 16 | ) %gr_attr% .data 17 | } 18 | #' @export 19 | #' @importFrom dplyr arrange 20 | arrange.morphed_tbl_graph <- function(.data, ...) { 21 | .data[] <- lapply(.data, arrange, ...) 22 | .data 23 | } 24 | #' @export 25 | dplyr::arrange 26 | 27 | #' @importFrom igraph is_directed as_data_frame 28 | permute_edges <- function(graph, order) { 29 | graph_mod <- as_data_frame(graph, what = 'both') 30 | graph_mod$edges <- graph_mod$edges[order, ] 31 | as_tbl_graph(graph_mod, directed = is_directed(graph)) 32 | } 33 | #' @importFrom igraph permute 34 | permute_nodes <- function(graph, order) { 35 | permute(graph, match(seq_along(order), order)) 36 | } 37 | -------------------------------------------------------------------------------- /R/cpp11.R: -------------------------------------------------------------------------------- 1 | # Generated by cpp11: do not edit by hand 2 | 3 | get_paths <- function(parent) { 4 | .Call(`_tidygraph_get_paths`, parent) 5 | } 6 | 7 | collect_offspring <- function(offspring, order) { 8 | .Call(`_tidygraph_collect_offspring`, offspring, order) 9 | } 10 | -------------------------------------------------------------------------------- /R/data_frame.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method for edge table and set membership table 2 | #' @export 3 | #' @importFrom igraph graph_from_data_frame 4 | as_tbl_graph.data.frame <- function(x, directed = TRUE, ...) { 5 | x <- as.data.frame(x) 6 | graph <- switch( 7 | guess_df_type(x), 8 | edge_df = as_graph_edge_df(x, directed), 9 | set_df = as_graph_set_df(x) 10 | ) 11 | 12 | as_tbl_graph(graph) 13 | } 14 | guess_df_type <- function(x) { 15 | if (all(c('to', 'from') %in% names(x))) return('edge_df') 16 | if (all(vapply(x, inherits, logical(1), 'logical'))) return('set_df') 17 | if (all(vapply(x, function(col) all(unique(col) %in% c(0,1)), logical(1)))) return('set_df') 18 | 'edge_df' 19 | } 20 | as_graph_edge_df <- function(x, directed) { 21 | from_ind <- which(names(x) == 'from') 22 | if (length(from_ind) == 0) from_ind <- 1 23 | to_ind <- which(names(x) == 'to') 24 | if (length(to_ind) == 0) to_ind <- 2 25 | x <- x[, c(from_ind, to_ind, seq_along(x)[-c(from_ind, to_ind)]), drop = FALSE] 26 | is_named <- is.character(x[[1]]) || is.character(x[[2]]) 27 | gr <- graph_from_data_frame(x, directed = directed) 28 | if (!is_named) { 29 | igraph::delete_vertex_attr(gr, 'name') 30 | } else { 31 | gr 32 | } 33 | } 34 | as_graph_set_df <- function(x, simple = TRUE) { 35 | if (simple) { 36 | x <- as.matrix(x) 37 | mode(x) <- 'integer' 38 | adj_mat <- x %*% t(x) 39 | if (!is.null(attr(x, 'row.names'))) { 40 | colnames(adj_mat) <- rownames(adj_mat) <- row.names(x) 41 | } 42 | as_graph_adj_matrix(adj_mat, FALSE) 43 | } else { 44 | edges <- do.call(rbind, lapply(names(x), function(name) { 45 | nodes <- which(as.logical(x[[name]])) 46 | edges <- expand.grid(nodes, nodes) 47 | names(edges) <- c('from', 'to') 48 | edges$type <- name 49 | edges[edges$from != edges$to, , drop = FALSE] 50 | })) 51 | if (!is.null(attr(x, 'row.names'))) { 52 | nodes <- data.frame(name = row.names(x), stringsAsFactors = FALSE) 53 | } else { 54 | nodes <- as.data.frame(matrix(ncol = 0, nrow = nrow(x))) 55 | } 56 | as_graph_node_edge(list(nodes = nodes, edges = edges), FALSE) 57 | } 58 | } 59 | #' @export 60 | as.data.frame.tbl_graph <- function(x, row.names = NULL, optional = FALSE, active = NULL, ...) { 61 | as.data.frame(as_tibble(x, active = active)) 62 | } 63 | -------------------------------------------------------------------------------- /R/data_tree.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method to deal with Node objects from the data.tree package 2 | #' @export 3 | as_tbl_graph.Node <- function(x, directed = TRUE, mode = 'out', ...) { 4 | rlang::check_installed('data.tree', 'in order to coerce Node object to tbl_graph') 5 | direction <- if (mode == 'out') 'climb' else 'descend' 6 | as_tbl_graph(data.tree::as.igraph.Node(x, directed = directed, direction = direction, ...)) 7 | } 8 | -------------------------------------------------------------------------------- /R/dendrogram.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method for dendrogram objects 2 | #' @importFrom dplyr bind_rows 3 | #' @export 4 | as_tbl_graph.dendrogram <- function(x, directed = TRUE, mode = 'out', ...) { 5 | x <- identify_nodes(x) 6 | 7 | nodes <- get_nodes(x) 8 | extraPar <- bind_rows(lapply(nodes$nodePar, as.data.frame, stringsAsFactors = FALSE)) 9 | nodes$nodePar <- NULL 10 | nodes <- cbind(nodes, extraPar) 11 | nodes <- nodes[order(nodes$.tidygraph_id), ] 12 | nodes$.tidygraph_id <- NULL 13 | if (all(nodes$label == '')) nodes$label <- NULL 14 | 15 | edges <- get_edges(x) 16 | extraPar <- bind_rows(lapply(edges$edgePar, as.data.frame, stringsAsFactors = FALSE)) 17 | edges$edgePar <- NULL 18 | edges <- cbind(edges, extraPar) 19 | if (all(edges$label == '')) edges$label <- NULL 20 | if (directed && mode == 'in') { 21 | edges[, c('from', 'to')] <- edges[, c('to', 'from')] 22 | } 23 | as_tbl_graph(list(nodes = nodes, edges = edges), directed = directed) 24 | } 25 | #' @importFrom stats is.leaf 26 | identify_nodes <- function(den, start = 1) { 27 | if (is.leaf(den)) { 28 | attr(den, '.tidygraph_id') <- start 29 | } else { 30 | den[[1]] <- identify_nodes(den[[1]], start) 31 | den[[2]] <- identify_nodes(den[[2]], attr(den[[1]], '.tidygraph_id') + 1) 32 | attr(den, '.tidygraph_id') <- attr(den[[2]], '.tidygraph_id') + 1 33 | } 34 | den 35 | } 36 | #' @importFrom stats is.leaf 37 | get_nodes <- function(den) { 38 | id <- attr(den, '.tidygraph_id') 39 | label <- attr(den, 'label') 40 | if (is.null(label)) label <- '' 41 | members <- attr(den, 'members') 42 | nodePar <- attr(den, 'nodePar') 43 | if (is.null(nodePar)) nodePar <- data.frame(row.names = 1) 44 | if (is.leaf(den)) { 45 | list( 46 | height = attr(den, 'height'), 47 | .tidygraph_id = id, 48 | leaf = TRUE, 49 | label = label, 50 | members = members, 51 | nodePar = list(nodePar) 52 | ) 53 | } else { 54 | coord1 <- get_nodes(den[[1]]) 55 | coord2 <- get_nodes(den[[2]]) 56 | list( 57 | height = c(coord1$height, coord2$height, attr(den, 'height')), 58 | .tidygraph_id = c(coord1$.tidygraph_id, coord2$.tidygraph_id, id), 59 | leaf = c(coord1$leaf, coord2$leaf, FALSE), 60 | label = c(coord1$label, coord2$label, label), 61 | members = c(coord1$members, coord2$members, members), 62 | nodePar = c(coord1$nodePar, coord2$nodePar, list(nodePar)) 63 | ) 64 | } 65 | } 66 | #' @importFrom stats is.leaf 67 | get_edges <- function(den) { 68 | id <- attr(den, '.tidygraph_id') 69 | if (is.leaf(den)) { 70 | data.frame(row.names = 1) 71 | } else { 72 | conn1 <- get_edges(den[[1]]) 73 | conn2 <- get_edges(den[[2]]) 74 | list( 75 | from = c(conn1$from, conn2$from, rep(id, 2)), 76 | to = c(conn1$to, conn2$to, unlist(lapply(den, attr, which = '.tidygraph_id'))), 77 | label = c(conn1$label, conn2$label, unlist(lapply(den, function(subden) { 78 | lab <- attr(subden, 'edgetext') 79 | if (is.null(lab)) '' else lab 80 | }))), 81 | edgePar = c(conn1$edgePar, conn2$edgePar, lapply(den, function(subden) { 82 | par <- attr(subden, 'edgePar') 83 | if (is.null(par)) data.frame(row.names = 1) else par 84 | })) 85 | ) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /R/distinct.R: -------------------------------------------------------------------------------- 1 | #' @importFrom dplyr distinct 2 | #' @importFrom rlang quos quo sym eval_tidy UQS 3 | #' @importFrom utils head 4 | #' @export 5 | distinct.tbl_graph <- function(.data, ..., .keep_all = FALSE) { 6 | .data <- unfocus(.data) 7 | .register_graph_context(.data) 8 | d_tmp <- as_tibble(.data) 9 | check_reserved(d_tmp) 10 | orig_ind <- seq_len(nrow(d_tmp)) 11 | dot_list <- quos(..., .named = TRUE) 12 | if (length(dot_list) == 0) { 13 | dot_list <- lapply(names(d_tmp), function(n) quo(!! sym(n))) 14 | names(dot_list) <- names(d_tmp) 15 | } 16 | d_tmp$.tbl_graph_index <- orig_ind 17 | 18 | d_tmp <- eval_tidy( 19 | quo( 20 | distinct(d_tmp, UQS(dot_list), .keep_all = TRUE) 21 | ) 22 | ) 23 | 24 | remove_ind <- orig_ind[-d_tmp$.tbl_graph_index] 25 | graph <- switch( 26 | active(.data), 27 | nodes = delete_vertices(.data, remove_ind), 28 | edges = delete_edges(.data, remove_ind) 29 | ) %gr_attr% .data 30 | if (!.keep_all) { 31 | d_tmp <- d_tmp[, names(d_tmp) %in% names(dot_list), drop = FALSE] 32 | } else { 33 | d_tmp <- d_tmp[, names(d_tmp) != '.tbl_graph_index', drop = FALSE] 34 | } 35 | set_graph_data(graph, d_tmp) 36 | } 37 | #' @export 38 | #' @importFrom dplyr distinct 39 | distinct.morphed_tbl_graph <- function(.data, ..., .keep_all = FALSE) { 40 | .data[] <- lapply(.data, distinct, ..., .keep_all = .keep_all) 41 | .data 42 | } 43 | #' @export 44 | dplyr::distinct 45 | -------------------------------------------------------------------------------- /R/edge_rank.R: -------------------------------------------------------------------------------- 1 | #' Calculate edge ranking 2 | #' 3 | #' This set of functions tries to calculate a ranking of the edges in a graph so 4 | #' that edges sharing certain topological traits are in proximity in the 5 | #' resulting order. 6 | #' 7 | #' @param cyclic should the eulerian path start and end at the same node 8 | #' 9 | #' @return An integer vector giving the position of each edge in the ranking 10 | #' 11 | #' @rdname edge_rank 12 | #' @name edge_rank 13 | #' 14 | #' @examples 15 | #' graph <- create_notable('meredith') %>% 16 | #' activate(edges) %>% 17 | #' mutate(rank = edge_rank_eulerian()) 18 | #' 19 | NULL 20 | 21 | #' @describeIn edge_rank Calculcate ranking as the visit order of a eulerian 22 | #' path or cycle. If no such path or cycle exist it will return a vector of 23 | #' `NA`s 24 | #' @importFrom igraph eulerian_path eulerian_cycle 25 | #' @export 26 | edge_rank_eulerian <- function(cyclic = FALSE) { 27 | expect_edges() 28 | alg <- if (cyclic) eulerian_cycle else eulerian_path 29 | rlang::try_fetch({ 30 | compress_rank(match(focus_ind(.G(), 'edges'), alg(.G())$epath)) 31 | }, 32 | error = function(...) { 33 | rep_len(NA_integer_, length(focus_ind(.G(), 'edges'))) 34 | } 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /R/filter.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @importFrom dplyr filter 3 | #' @importFrom igraph delete_vertices delete_edges 4 | filter.tbl_graph <- function(.data, ...) { 5 | .data <- unfocus(.data) 6 | .register_graph_context(.data) 7 | d_tmp <- as_tibble(.data) 8 | check_reserved(d_tmp) 9 | orig_ind <- seq_len(nrow(d_tmp)) 10 | d_tmp$.tbl_graph_index <- orig_ind 11 | d_tmp <- filter(d_tmp, ...) 12 | remove_ind <- if (nrow(d_tmp) == 0) orig_ind else orig_ind[-d_tmp$.tbl_graph_index] 13 | switch( 14 | active(.data), 15 | nodes = delete_vertices(.data, remove_ind), 16 | edges = delete_edges(.data, remove_ind) 17 | ) %gr_attr% .data 18 | } 19 | #' @export 20 | #' @importFrom dplyr filter 21 | filter.morphed_tbl_graph <- function(.data, ...) { 22 | .data[] <- lapply(.data, filter, ...) 23 | .data 24 | } 25 | #' @export 26 | dplyr::filter 27 | 28 | #' @importFrom dplyr top_n 29 | #' @export 30 | dplyr::top_n 31 | -------------------------------------------------------------------------------- /R/focus.R: -------------------------------------------------------------------------------- 1 | #' Select specific nodes or edges to compute on 2 | #' 3 | #' The `focus()`/`unfocus()` idiom allow you to temporarily tell tidygraph 4 | #' algorithms to only calculate on a subset of the data, while keeping the full 5 | #' graph intact. The purpose of this is to avoid having to calculate time 6 | #' costly measures etc on all nodes or edges of a graph if only a few is needed. 7 | #' E.g. you might only be interested in the shortest distance from one node to 8 | #' another so rather than calculating this for all nodes you apply a focus on 9 | #' one node and perform the calculation. It should be made clear that not all 10 | #' algorithms will see a performance boost by being applied to a few nodes/edges 11 | #' since their calculation is applied globally and the result for all 12 | #' nodes/edges are provided in unison. 13 | #' 14 | #' @note focusing is the lowest prioritised operation on a graph. Applying a 15 | #' [morph()] or a [group_by()] operation will unfocus the graph prior to 16 | #' performing the operation. The same is true for the inverse operations 17 | #' ([unmorph()] and [ungroup()]). Further, unfocusing will happen any time some 18 | #' graph altering operation is performed, such as the `arrange()` and `slice()` 19 | #' operations 20 | #' 21 | #' @inheritParams dplyr::filter 22 | #' 23 | #' @return A graph with focus applied 24 | #' 25 | #' @export 26 | focus <- function(.data, ...) { 27 | UseMethod('focus') 28 | } 29 | 30 | #' @rdname focus 31 | #' @export 32 | focus.tbl_graph <- function(.data, ...) { 33 | .graph_context$set(.data) 34 | on.exit(.graph_context$clear()) 35 | if (is.focused_tbl_graph(.data)) .data <- unfocus(.data) 36 | d_tmp <- as_tibble(.data) 37 | n_tmp <- nrow(d_tmp) 38 | d_tmp$.tidygraph_focus_index <- seq_len(n_tmp) 39 | d_tmp <- filter(d_tmp, ...) 40 | if (nrow(d_tmp) == 0) { 41 | cli::cli_inform("{.fun focus} didn't select any {active(.data)}. Returning unfocused graph") 42 | return(.data) 43 | } 44 | if (nrow(d_tmp) == n_tmp) { 45 | cli::cli_inform("{.fun focus} selected all {active(.data)}. Returning unfocused graph") 46 | return(.data) 47 | } 48 | apply_focus(.data, d_tmp$.tidygraph_focus_index) 49 | } 50 | 51 | #' @rdname focus 52 | #' @export 53 | focus.morphed_tbl_graph <- function(.data, ...) { 54 | .data[] <- lapply(.data, focus, ...) 55 | .data 56 | } 57 | 58 | #' @rdname focus 59 | #' @export 60 | unfocus <- function(.data, ...) { 61 | UseMethod('unfocus') 62 | } 63 | 64 | #' @rdname focus 65 | #' @export 66 | unfocus.tbl_graph <- function(.data, ...) { 67 | .data 68 | } 69 | 70 | #' @rdname focus 71 | #' @export 72 | unfocus.focused_tbl_graph <- function(.data, ...) { 73 | attr(.data, paste0(active(.data), '_focus_index')) <- NULL 74 | class(.data) <- setdiff(class(.data), 'focused_tbl_graph') 75 | .data 76 | } 77 | 78 | #' @rdname focus 79 | #' @export 80 | unfocus.morphed_tbl_graph <- function(.data, ...) { 81 | .data[] <- lapply(.data, unfocus, ...) 82 | .data 83 | } 84 | 85 | is.focused_tbl_graph <- function(x) inherits(x, 'focused_tbl_graph') 86 | 87 | # HELPERS ----------------------------------------------------------------- 88 | 89 | apply_focus <- function(graph, index) { 90 | attr(graph, paste0(active(graph), '_focus_index')) <- index 91 | if (!is.focused_tbl_graph(graph)) { 92 | class(graph) <- c('focused_tbl_graph', class(graph)) 93 | } 94 | graph 95 | } 96 | focus_ind <- function(x, active = NULL) { 97 | if (is.null(active)) active <- active(x) 98 | attr(x, paste0(active, '_focus_index')) %||% 99 | seq_len(if (active == "nodes") gorder(x) else gsize(x)) 100 | } 101 | -------------------------------------------------------------------------------- /R/graph.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method for handling graphNEL objects from the graph package (on Bioconductor) 2 | #' @importFrom dplyr bind_rows 3 | #' @importFrom tibble tibble as_tibble 4 | #' @importFrom igraph as_edgelist edge_attr<- vertex_attr<- V 5 | #' @export 6 | as_tbl_graph.graphNEL <- function(x, ...) { 7 | rlang::check_installed('graph', 'in order to coerce graphNEL object to tbl_graph') 8 | directed <- graph::edgemode(x) == 'directed' 9 | adj_list <- lapply(graph::edgeL(x), `[[`, i = 'edges') 10 | graph <- as_tbl_graph(adj_list) 11 | 12 | edgelist <- as_edgelist(graph, names = TRUE) 13 | edge_names <- apply(edgelist, 1, paste, collapse = '|') 14 | graph_ed <- graph::edgeData(x) 15 | edge_data <- bind_rows(lapply(graph_ed, as_tibble)) 16 | edge_data_full <- edge_data[rep(nrow(edge_data) + 1, length(edge_names)), ] 17 | edge_data_full[match(names(graph_ed), edge_names), ] <- edge_data 18 | 19 | graph_nd <- graph::nodeData(x) 20 | graph_nd <- lapply(names(graph_nd), function(n) { 21 | tibble(name = n, !!!graph_nd[[n]]) 22 | }) 23 | node_data <- bind_rows(graph_nd) 24 | node_data <- node_data[match(node_data$name, V(graph)$name), ] 25 | 26 | edge_attr(graph) <- edge_data_full 27 | vertex_attr(graph) <- node_data 28 | as_tbl_graph(graph) 29 | } 30 | #' @describeIn tbl_graph Method for handling graphAM objects from the graph package (on Bioconductor) 31 | #' @export 32 | as_tbl_graph.graphAM <- function(x, ...) { 33 | rlang::check_installed('methods', 'in order to coerce graphAM object to tbl_graph') 34 | as_tbl_graph(methods::as(x, 'graphNEL'), ...) 35 | } 36 | #' @describeIn tbl_graph Method for handling graphBAM objects from the graph package (on Bioconductor) 37 | #' @export 38 | as_tbl_graph.graphBAM <- function(x, ...) { 39 | rlang::check_installed('methods', 'in order to coerce graphBAM object to tbl_graph') 40 | as_tbl_graph(methods::as(x, 'graphNEL'), ...) 41 | } 42 | -------------------------------------------------------------------------------- /R/graph_types.R: -------------------------------------------------------------------------------- 1 | #' Querying graph types 2 | #' 3 | #' This set of functions lets the user query different aspects of the graph 4 | #' itself. They are all concerned with wether the graph implements certain 5 | #' properties and will all return a logical scalar. 6 | #' 7 | #' @param graph The graph to compare structure to 8 | #' @param method The algorithm to use for comparison 9 | #' @param cyclic should the eulerian path start and end at the same node 10 | #' 11 | #' @param ... Arguments passed on to the comparison methods. See 12 | #' [igraph::is_isomorphic_to()] and [igraph::is_subgraph_isomorphic_to()] 13 | #' 14 | #' @return A logical scalar 15 | #' 16 | #' @name graph_types 17 | #' @rdname graph_types 18 | #' 19 | #' @examples 20 | #' gr <- create_tree(50, 4) 21 | #' 22 | #' with_graph(gr, graph_is_tree()) 23 | #' 24 | NULL 25 | 26 | #' @describeIn graph_types Is the graph simple (no parallel edges) 27 | #' @importFrom igraph is_simple 28 | #' @export 29 | graph_is_simple <- function() { 30 | is_simple(.G()) 31 | } 32 | #' @describeIn graph_types Is the graph directed 33 | #' @importFrom igraph is_directed 34 | #' @export 35 | graph_is_directed <- function() { 36 | is_directed(.G()) 37 | } 38 | #' @describeIn graph_types Is the graph bipartite 39 | #' @importFrom igraph is_bipartite 40 | #' @export 41 | graph_is_bipartite <- function() { 42 | is_bipartite(.G()) 43 | } 44 | #' @describeIn graph_types Is the graph connected 45 | #' @importFrom igraph is_connected 46 | #' @export 47 | graph_is_connected <- function() { 48 | is_connected(.G()) 49 | } 50 | #' @describeIn graph_types Is the graph a tree 51 | #' @export 52 | graph_is_tree <- function() { 53 | is_tree(.G()) 54 | } 55 | #' @describeIn graph_types Is the graph an ensemble of multiple trees 56 | #' @export 57 | graph_is_forest <- function() { 58 | is_forest(.G()) 59 | } 60 | #' @describeIn graph_types Is the graph a directed acyclic graph 61 | #' @importFrom igraph is_dag 62 | #' @export 63 | graph_is_dag <- function() { 64 | is_dag(.G()) 65 | } 66 | #' @describeIn graph_types Is the graph chordal 67 | #' @importFrom igraph is_chordal 68 | #' @export 69 | graph_is_chordal <- function() { 70 | is_chordal(.G())$chordal 71 | } 72 | #' @describeIn graph_types Is the graph fully connected 73 | #' @export 74 | graph_is_complete <- function() { 75 | graph_is_simple() && all(centrality_degree(mode = 'all', loops = FALSE) == graph_order() - 1) 76 | } 77 | #' @describeIn graph_types Is the graph isomorphic to another graph. See [igraph::is_isomorphic_to()] 78 | #' @importFrom igraph is_isomorphic_to 79 | #' @export 80 | graph_is_isomorphic_to <- function(graph, method = 'auto', ...) { 81 | is_isomorphic_to(.G(), graph, method, ...) 82 | } 83 | #' @describeIn graph_types Is the graph an isomorphic subgraph to another graph. see [igraph::is_subgraph_isomorphic_to()] 84 | #' @importFrom igraph is_subgraph_isomorphic_to 85 | #' @export 86 | graph_is_subgraph_isomorphic_to <- function(graph, method = 'auto', ...) { 87 | is_subgraph_isomorphic_to(.G(), graph, method, ...) 88 | } 89 | #' @describeIn graph_types Can all the edges in the graph be reaches by a single 90 | #' path or cycle that only goes through each edge once 91 | #' @importFrom igraph has_eulerian_cycle has_eulerian_path 92 | #' @export 93 | graph_is_eulerian <- function(cyclic = FALSE) { 94 | if (cyclic) { 95 | has_eulerian_cycle(.G()) 96 | } else { 97 | has_eulerian_path(.G()) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /R/group_by.R: -------------------------------------------------------------------------------- 1 | #' @importFrom dplyr group_by 2 | #' @export 3 | group_by.tbl_graph <- function(.data, ..., add = FALSE) { 4 | if (is.focused_tbl_graph(.data)) { 5 | cli::cli_inform('Unfocusing prior to grouping') 6 | .data <- unfocus(.data) 7 | } 8 | .register_graph_context(.data) 9 | d_tmp <- as_tibble(.data) 10 | d_tmp <- group_by(d_tmp, ..., .add = add) 11 | .data <- set_graph_data(.data, ungroup(d_tmp)) 12 | apply_groups(.data, d_tmp) 13 | } 14 | #' @export 15 | #' @importFrom dplyr group_by 16 | group_by.morphed_tbl_graph <- function(.data, ...) { 17 | .data[] <- lapply(.data, group_by, ...) 18 | .data 19 | } 20 | #' @export 21 | dplyr::group_by 22 | 23 | #' @importFrom dplyr ungroup 24 | #' @export 25 | ungroup.tbl_graph <- function(x, ...) { 26 | x 27 | } 28 | #' @importFrom dplyr ungroup 29 | #' @export 30 | ungroup.grouped_tbl_graph <- function(x, ...) { 31 | attr(x, paste0(active(x), '_group_attr')) <- NULL 32 | class(x) <- setdiff(class(x), 'grouped_tbl_graph') 33 | unfocus(x) 34 | } 35 | #' @export 36 | #' @importFrom dplyr ungroup 37 | ungroup.morphed_tbl_graph <- function(x, ...) { 38 | x[] <- lapply(x, ungroup, ...) 39 | x 40 | } 41 | #' @export 42 | dplyr::ungroup 43 | 44 | #' @importFrom dplyr group_size 45 | #' @export 46 | group_size.tbl_graph <- function(x) { 47 | group_size(as_tibble(x)) 48 | } 49 | #' @export 50 | dplyr::group_size 51 | 52 | #' @importFrom dplyr n_groups 53 | #' @export 54 | n_groups.tbl_graph <- function(x) { 55 | n_groups(as_tibble(x)) 56 | } 57 | #' @export 58 | dplyr::n_groups 59 | 60 | #' @importFrom dplyr groups 61 | #' @export 62 | groups.tbl_graph <- function(x) { 63 | groups(as_tibble(x)) 64 | } 65 | #' @export 66 | dplyr::groups 67 | 68 | #' @importFrom dplyr group_vars 69 | #' @export 70 | group_vars.tbl_graph <- function(x) { 71 | group_vars(as_tibble(x)) 72 | } 73 | #' @export 74 | dplyr::group_vars 75 | 76 | #' @importFrom dplyr group_data 77 | #' @export 78 | group_data.tbl_graph <- function(.data) { 79 | group_data(as_tibble(.data)) 80 | } 81 | #' @export 82 | dplyr::group_data 83 | 84 | #' @importFrom dplyr group_indices 85 | #' @export 86 | group_indices.tbl_graph <- function(.data, ...) { 87 | group_indices(as_tibble(.data), ...) 88 | } 89 | #' @export 90 | dplyr::group_indices 91 | 92 | #' @importFrom dplyr group_keys 93 | #' @export 94 | group_keys.tbl_graph <- function(.tbl, ...) { 95 | group_keys(as_tibble(.data)) 96 | } 97 | #' @export 98 | dplyr::group_keys 99 | 100 | is.grouped_tbl_graph <- function(x) { 101 | inherits(x, 'grouped_tbl_graph') 102 | } 103 | apply_groups <- function(graph, group) { 104 | attr(graph, paste0(active(graph), '_group_attr')) <- attributes(group) 105 | if (!is.grouped_tbl_graph(graph)) { 106 | class(graph) <- c('grouped_tbl_graph', class(graph)) 107 | } 108 | graph 109 | } 110 | -------------------------------------------------------------------------------- /R/hclust.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method for hclust objects 2 | #' @importFrom stats as.dendrogram 3 | #' @export 4 | as_tbl_graph.hclust <- function(x, directed = TRUE, mode = 'out', ...) { 5 | as_tbl_graph(as.dendrogram(x), directed, mode) 6 | } 7 | -------------------------------------------------------------------------------- /R/igraph.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method for igraph object. Simply subclasses the object into a `tbl_graph` 2 | #' @export 3 | as_tbl_graph.igraph <- function(x, ...) { 4 | class(x) <- c('tbl_graph', 'igraph') 5 | attr(x, 'active') <- 'nodes' 6 | x 7 | } 8 | 9 | #' @importFrom igraph as.igraph 10 | #' @export 11 | as.igraph.tbl_graph <- function(x, ...) { 12 | class(x) <- 'igraph' 13 | attr(x, 'active') <- NULL 14 | x 15 | } 16 | #' @export 17 | igraph::as.igraph 18 | -------------------------------------------------------------------------------- /R/iterate.R: -------------------------------------------------------------------------------- 1 | #' Repeatedly modify a graph by a function 2 | #' 3 | #' The iterate family of functions allow you to call the same modification 4 | #' function on a graph until some condition is met. This can be used to create 5 | #' simple simulations in a tidygraph friendly API 6 | #' 7 | #' @param .data A `tbl_graph` object 8 | #' @param .f A function taking in a `tbl_graph` as the first argument and 9 | #' returning a `tbl_graph` object 10 | #' @param n The number of times to iterate 11 | #' @param cnd A condition that must evaluate to `TRUE` or `FALSE` determining if 12 | #' iteration should continue 13 | #' @param max_n The maximum number of iterations in case `cnd` never evaluates 14 | #' to `FALSE` 15 | #' @param ... Further arguments passed on to `.f` 16 | #' 17 | #' @return A `tbl_graph` object 18 | #' 19 | #' @rdname iterate 20 | #' @name iterate 21 | #' 22 | #' @examples 23 | #' # Gradually remove edges from the least connected nodes while avoiding 24 | #' # isolates 25 | #' create_notable('zachary') |> 26 | #' iterate_n(20, function(gr) { 27 | #' gr |> 28 | #' activate(nodes) |> 29 | #' mutate(deg = centrality_degree(), rank = order(deg)) |> 30 | #' activate(edges) |> 31 | #' slice( 32 | #' -which(edge_is_incident(.N()$rank == sum(.N()$deg == 1) + 1))[1] 33 | #' ) 34 | #' }) 35 | #' 36 | #' # Remove a random edge until the graph is split in two 37 | #' create_notable('zachary') |> 38 | #' iterate_while(graph_component_count() == 1, function(gr) { 39 | #' gr |> 40 | #' activate(edges) |> 41 | #' slice(-sample(graph_size(), 1)) 42 | #' }) 43 | #' 44 | NULL 45 | 46 | #' @rdname iterate 47 | #' @export 48 | #' 49 | iterate_n <- function(.data, n, .f, ...) { 50 | check_tbl_graph(.data) 51 | .f <- rlang::as_function(.f) 52 | act <- active(.data) 53 | for (i in seq_len(n)) { 54 | .data <- .f(.data, ...) 55 | check_tbl_graph(.data) 56 | } 57 | activate(.data, !!rlang::sym(act)) 58 | } 59 | 60 | #' @rdname iterate 61 | #' @export 62 | #' 63 | iterate_while <- function(.data, cnd, .f, ..., max_n = NULL) { 64 | check_tbl_graph(.data) 65 | .f <- rlang::as_function(.f) 66 | act <- active(.data) 67 | if (!is.null(max_n) && !rlang::is_integerish(max_n, 1, TRUE)) { 68 | cli::cli_abort('{.arg max_n} must either be NULL or a single integer') 69 | } 70 | cnd <- rlang::enquo(cnd) 71 | cnd <- rlang::expr(with_graph(.data, !!cnd)) 72 | n <- 1 73 | while (isTRUE(rlang::eval_tidy(cnd)) && !isTRUE(n > max_n)) { 74 | .data <- .f(.data, ...) 75 | check_tbl_graph(.data) 76 | n <- n + 1 77 | } 78 | activate(.data, !!rlang::sym(act)) 79 | } 80 | -------------------------------------------------------------------------------- /R/list.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method for adjacency lists and lists of node and edge tables 2 | #' @export 3 | as_tbl_graph.list <- function(x, directed = TRUE, node_key = 'name', ...) { 4 | graph <- switch( 5 | guess_list_type(x), 6 | adjacency = as_graph_adj_list(x, directed = directed), 7 | node_edge = as_graph_node_edge(x, directed = directed, node_key = node_key), 8 | unknown = cli::cli_abort("Unknown list format") 9 | ) 10 | as_tbl_graph(graph) 11 | } 12 | #' @export 13 | as.list.tbl_graph <- function(x, ...) { 14 | list( 15 | nodes = as_tibble(x, active = 'nodes'), 16 | edges = as_tibble(x, active = 'edges') 17 | ) 18 | } 19 | 20 | guess_list_type <- function(x) { 21 | if (length(x) == 2 && 22 | any(names(x) %in% c('nodes', 'vertices')) && 23 | any(names(x) %in% c('edges', 'links'))) { 24 | return('node_edge') 25 | } 26 | x <- lapply(x, function(el) el[!is.na(el)]) 27 | x[lengths(x) == 0] <- list(NULL) 28 | elements <- sapply(x[lengths(x) != 0], function(el) class(el)[1]) 29 | if (all(elements == 'character') && 30 | all(unlist(x) %in% names(x))) { 31 | return('adjacency') 32 | } 33 | if (any(elements %in% c('numeric'))) { 34 | x <- lapply(x, as.integer) 35 | elements[] <- 'integer' 36 | } 37 | if (all(elements == 'integer') && 38 | !anyNA(unlist(x)) && 39 | max(unlist(x)) <= length(x) && 40 | min(unlist(x)) >= 0) { 41 | return('adjacency') 42 | } 43 | 'unknown' 44 | } 45 | 46 | #' @importFrom igraph graph_from_adj_list set_vertex_attr 47 | as_graph_adj_list <- function(x, directed) { 48 | x <- lapply(x, function(el) el[!is.na(el)]) 49 | if (inherits(x[[1]], 'character')) { 50 | x <- split(match(unlist(x), names(x)), rep(factor(names(x), levels = names(x)), lengths(x))) 51 | } 52 | if (any(unlist(x) == 0)) { 53 | x <- lapply(x, `+`, 1) 54 | } 55 | gr <- graph_from_adj_list(unname(x), mode = if (directed) 'out' else 'all') 56 | if (!is.null(names(x))) { 57 | gr <- set_vertex_attr(gr, 'name', value = names(x)) 58 | } 59 | gr 60 | } 61 | 62 | #' @importFrom igraph graph_from_edgelist vertex_attr<- add_vertices gorder 63 | #' @importFrom tibble tibble 64 | as_graph_node_edge <- function(x, directed, node_key = 'name') { 65 | nodes <- x[[which(names(x) %in% c('nodes', 'vertices'))]] 66 | edges <- x[[which(names(x) %in% c('edges', 'links'))]] 67 | if (is.null(edges)) { 68 | edges <- tibble(from = integer(), to = integer()) 69 | } else { 70 | edges <- as.data.frame(edges) 71 | } 72 | from_ind <- which(names(edges) == 'from') 73 | if (length(from_ind) == 0) from_ind <- 1 74 | to_ind <- which(names(edges) == 'to') 75 | if (length(to_ind) == 0) to_ind <- 2 76 | edges <- edges[, c(from_ind, to_ind, seq_along(edges)[-c(from_ind, to_ind)]), drop = FALSE] 77 | if (is.factor(edges[[1]])) edges[[1]] <- as.character(edges[[1]]) 78 | if (is.factor(edges[[2]])) edges[[2]] <- as.character(edges[[2]]) 79 | if (!is.null(nodes)) { 80 | if (is.na(node_key)) { 81 | name_ind <- 1L 82 | } else { 83 | name_ind <- which(names(nodes) == node_key) 84 | if (length(name_ind) == 0) name_ind <- 1 85 | } 86 | if (is.character(edges[[1]])) { 87 | edges[, 1] <- match(edges[[1]], nodes[[name_ind]]) 88 | } 89 | if (is.character(edges[[2]])) { 90 | edges[, 2] <- match(edges[[2]], nodes[[name_ind]]) 91 | } 92 | } 93 | gr <- graph_from_edgelist(as.matrix(edges[, 1:2]), directed = directed) 94 | edge_attr(gr) <- as.list(edges[, -c(1:2), drop = FALSE]) 95 | if (!is.null(nodes)) { 96 | nodes <- as.data.frame(nodes) 97 | if (gorder(gr) != nrow(nodes)) { 98 | gr <- add_vertices(gr, nrow(nodes) - gorder(gr)) 99 | } 100 | vertex_attr(gr) <- as.list(nodes) 101 | } 102 | gr 103 | } 104 | -------------------------------------------------------------------------------- /R/local.R: -------------------------------------------------------------------------------- 1 | #' Measures based on the neighborhood of each node 2 | #' 3 | #' These functions wraps a set of functions that all measures quantities of the 4 | #' local neighborhood of each node. They all return a vector or list matching 5 | #' the node position. 6 | #' 7 | #' @return A numeric vector or a list (for `local_members`) with elements 8 | #' corresponding to the nodes in the graph. 9 | #' 10 | #' @name local_graph 11 | #' @rdname local_graph 12 | #' 13 | #' @examples 14 | #' # Get all neighbors of each graph 15 | #' create_notable('chvatal') %>% 16 | #' activate(nodes) %>% 17 | #' mutate(neighborhood = local_members(mindist = 1)) 18 | #' 19 | #' # These are equivalent 20 | #' create_notable('chvatal') %>% 21 | #' activate(nodes) %>% 22 | #' mutate(n_neighbors = local_size(mindist = 1), 23 | #' degree = centrality_degree()) %>% 24 | #' as_tibble() 25 | #' 26 | NULL 27 | 28 | #' @describeIn local_graph The size of the neighborhood in a given distance from 29 | #' the node. (Note that the node itself is included unless `mindist > 0`). Wraps [igraph::ego_size()]. 30 | #' @inheritParams igraph::ego_size 31 | #' @importFrom igraph ego_size 32 | #' @export 33 | local_size <- function(order = 1, mode = 'all', mindist = 0) { 34 | expect_nodes() 35 | graph <- .G() 36 | ego_size(graph = graph, order = order, nodes = focus_ind(graph, 'nodes'), mode = mode, mindist = mindist) 37 | } 38 | #' @describeIn local_graph The members of the neighborhood of each node in a 39 | #' given distance. Wraps [igraph::ego()]. 40 | #' @importFrom igraph ego 41 | #' @export 42 | local_members <- function(order = 1, mode = 'all', mindist = 0) { 43 | expect_nodes() 44 | graph <- .G() 45 | lapply(ego(graph = graph, order = order, nodes = focus_ind(graph, 'nodes'), mode = mode, mindist = mindist), as.integer) 46 | } 47 | #' @describeIn local_graph The number of triangles each node participate in. Wraps [igraph::count_triangles()]. 48 | #' @importFrom igraph count_triangles 49 | #' @export 50 | local_triangles <- function() { 51 | expect_nodes() 52 | graph <- .G() 53 | count_triangles(graph = graph, vids = focus_ind(graph, 'nodes')) 54 | } 55 | #' @describeIn local_graph Calculates the average degree based on the neighborhood of each node. Wraps [igraph::knn()]. 56 | #' @param weights An edge weight vector. For `local_ave_degree`: If this argument 57 | #' is given, the average vertex strength is calculated instead of vertex degree. 58 | #' For `local_transitivity`: if given weighted transitivity using the approach by 59 | #' *A. Barrat* will be calculated. 60 | #' @importFrom igraph knn 61 | #' @export 62 | local_ave_degree <- function(weights = NULL) { 63 | expect_nodes() 64 | weights <- enquo(weights) 65 | weights <- eval_tidy(weights, .E()) %||% NA 66 | graph <- .G() 67 | knn(graph = graph, vids = focus_ind(graph, 'nodes'), weights = weights)$knn 68 | } 69 | #' @describeIn local_graph Calculate the transitivity of each node, that is, the 70 | #' propensity for the nodes neighbors to be connected. Wraps [igraph::transitivity()] 71 | #' @importFrom igraph transitivity V 72 | #' @importFrom rlang quos 73 | #' @export 74 | local_transitivity <- function(weights = NULL) { 75 | expect_nodes() 76 | weights <- enquo(weights) 77 | weights <- eval_tidy(weights, .E()) 78 | type <- if (is.null(weights)) { 79 | 'weighted' 80 | } else { 81 | 'local' 82 | } 83 | graph <- .G() 84 | transitivity(graph = graph, type = type, vids = focus_ind(graph, 'nodes'), weights = weights, isolates = 'zero') 85 | } 86 | -------------------------------------------------------------------------------- /R/matrix.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method for edgelist, adjacency and incidence matrices 2 | #' @export 3 | as_tbl_graph.matrix <- function(x, directed = TRUE, ...) { 4 | graph <- switch( 5 | guess_matrix_type(x), 6 | edgelist = as_graph_edgelist(x, directed, ...), 7 | adjacency = as_graph_adj_matrix(x, directed, ...), 8 | incidence = as_graph_incidence(x, directed, ...), 9 | unknown = cli::cli_abort('Unknown matrix format') 10 | ) 11 | as_tbl_graph(graph) 12 | } 13 | 14 | guess_matrix_type <- function(x) { 15 | if (nrow(x) == ncol(x) && 16 | mode(x) %in% c('numeric', 'integer') && 17 | ((is.null(rownames(x)) && is.null(colnames(x))) || 18 | all(colnames(x) == rownames(x)))) { 19 | 'adjacency' 20 | } else if (ncol(x) == 2) { 21 | 'edgelist' 22 | } else if (mode(x) %in% c('numeric', 'integer')) { 23 | 'incidence' 24 | } else { 25 | 'uknown' 26 | } 27 | } 28 | 29 | #' @importFrom igraph graph_from_edgelist 30 | as_graph_edgelist <- function(x, directed, ...) { 31 | graph_from_edgelist(x, directed, ...) 32 | } 33 | #' @importFrom igraph graph_from_adjacency_matrix 34 | as_graph_adj_matrix <- function(x, directed, ...) { 35 | graph_from_adjacency_matrix(x, mode = if (directed) 'directed' else 'undirected', 36 | weighted = TRUE, ...) 37 | } 38 | #' @importFrom igraph graph_from_incidence_matrix 39 | as_graph_incidence <- function(x, directed, ...) { 40 | graph_from_incidence_matrix(x, directed, mode = 'out', weighted = TRUE, ...) 41 | } 42 | -------------------------------------------------------------------------------- /R/mutate.R: -------------------------------------------------------------------------------- 1 | #' @importFrom rlang quos !!! 2 | #' @export 3 | mutate.tbl_graph <- function(.data, ...) { 4 | dots <- quos(...) 5 | for (i in seq_along(dots)) { 6 | dot <- dots[i] 7 | .data <- mutate_as_tbl(.data, !!!dot) 8 | } 9 | .data 10 | } 11 | #' Base implementation of mutate 12 | #' 13 | #' This implementation of mutate is slightly faster than `mutate` at the expense 14 | #' of the graph only being updated in the end. This means that graph algorithms 15 | #' will not take changes happening during the mutate call into account. 16 | #' 17 | #' @details 18 | #' The order of speed increase are rather small and in the ~1 millisecond per 19 | #' mutateed column order, so for regular use this should not be a choice. The 20 | #' operations not supported by `mutate_as_tbl` are e.g. 21 | #' 22 | #' ``` 23 | #' gr %>% 24 | #' activate(nodes) %>% 25 | #' mutate(weights = runif(10), degree = centrality_degree(weights)) 26 | #' ``` 27 | #' 28 | #' as `weights` will only be made available in the graph at the end of the 29 | #' mutate call. 30 | #' 31 | #' @param .data A `tbl_graph` object 32 | #' 33 | #' @param ... columns to mutate 34 | #' 35 | #' @return A `tbl_graph` object 36 | #' 37 | #' @keywords internal 38 | #' @importFrom dplyr mutate 39 | #' @export 40 | mutate_as_tbl <- function(.data, ...) { 41 | .register_graph_context(.data) 42 | d_tmp <- as_tibble(.data) 43 | d_tmp <- mutate(d_tmp, ...) 44 | set_graph_data(.data, d_tmp) 45 | } 46 | #' @export 47 | #' @importFrom dplyr mutate 48 | mutate.morphed_tbl_graph <- function(.data, ...) { 49 | .data[] <- lapply(.data, protect_ind, .f = mutate, ...) 50 | .data 51 | } 52 | #' @export 53 | dplyr::mutate 54 | 55 | #' @importFrom dplyr transmute 56 | #' @export 57 | dplyr::transmute 58 | 59 | #' @importFrom dplyr mutate_all 60 | #' @export 61 | dplyr::mutate_all 62 | 63 | #' @importFrom dplyr mutate_at 64 | #' @export 65 | dplyr::mutate_at 66 | 67 | #' @importFrom dplyr n 68 | #' @export 69 | dplyr::n 70 | -------------------------------------------------------------------------------- /R/network.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method to handle network objects from the `network` 2 | #' package. Requires this packages to work. 3 | #' @export 4 | as_tbl_graph.network <- function(x, ...) { 5 | as_tbl_graph(network_to_igraph(x)) 6 | } 7 | #' Coerce network to igraph 8 | #' 9 | #' This utility function performs a conversion of network objects from the 10 | #' network package into igraph object compatible with ggraph. Edge and node 11 | #' attributes are preserved which, for the context of ggraph, is the most 12 | #' important. 13 | #' 14 | #' @param graph A graph as a network object 15 | #' 16 | #' @return A representation of the same graph as given in the function call but 17 | #' as an igraph object. 18 | #' 19 | #' @importFrom igraph graph_from_edgelist graph_attr<- edge_attr<- vertex_attr<- 20 | #' @importFrom utils modifyList 21 | #' @noRd 22 | #' 23 | network_to_igraph <- function(graph) { 24 | rlang::check_installed('network', 'in order to coerce network object to tbl_graph') 25 | graph_attr_names <- network::list.network.attributes(graph) 26 | graph_attr <- lapply(graph_attr_names, function(n) { 27 | network::get.network.attribute(graph, n) 28 | }) 29 | names(graph_attr) <- graph_attr_names 30 | if (graph_attr$hyper) { 31 | cli::cli_abort('Hypergraphs are currently unsupported', call. = FALSE) 32 | } 33 | 34 | node_attr_names <- network::list.vertex.attributes(graph) 35 | node_attr <- lapply(node_attr_names, function(n) { 36 | network::get.vertex.attribute(graph, n) 37 | }) 38 | names(node_attr) <- node_attr_names 39 | 40 | edge_attr_names <- network::list.edge.attributes(graph) 41 | edge_attr <- lapply(edge_attr_names, function(n) { 42 | network::get.edge.attribute(graph, n) 43 | }) 44 | names(edge_attr) <- edge_attr_names 45 | 46 | edges <- as.matrix(graph, matrix.type = 'edgelist') 47 | class(edges) <- 'matrix' 48 | attributes(edges) <- attributes(edges)[c('dim', 'class')] 49 | 50 | new_graph <- graph_from_edgelist(edges, graph_attr$directed) 51 | graph_attr(new_graph) <- modifyList( 52 | graph_attr, 53 | list(bipartite = NULL, directed = NULL, hyper = NULL, loops = NULL, 54 | mnext = NULL, multiple = NULL, n = NULL) 55 | ) 56 | if (is.character(node_attr$vertex.names)) { 57 | node_attr$name <- node_attr$vertex.names 58 | } 59 | node_attr$vertex.names <- NULL 60 | missing_nodes <- network::network.size(graph) - gorder(new_graph) 61 | new_graph <- add_vertices(new_graph, missing_nodes) 62 | vertex_attr(new_graph) <- node_attr 63 | edge_attr(new_graph) <- edge_attr 64 | 65 | new_graph 66 | } 67 | -------------------------------------------------------------------------------- /R/node_topology.R: -------------------------------------------------------------------------------- 1 | #' Node properties related to the graph topology 2 | #' 3 | #' These functions calculate properties that are dependent on the overall 4 | #' topology of the graph. 5 | #' 6 | #' @return A vector of the same length as the number of nodes in the graph 7 | #' 8 | #' @name node_topology 9 | #' @rdname node_topology 10 | #' 11 | #' @examples 12 | #' # Sort a graph based on its topological order 13 | #' create_tree(10, 2) %>% 14 | #' arrange(sample(graph_order())) %>% 15 | #' mutate(old_ind = seq_len(graph_order())) %>% 16 | #' arrange(node_topo_order()) 17 | NULL 18 | 19 | #' @describeIn node_topology Get the immediate dominator of each node. Wraps [igraph::dominator_tree()]. 20 | #' @importFrom igraph dominator_tree 21 | #' @export 22 | #' 23 | #' @param root The node to start the dominator search from 24 | #' @param mode How should edges be followed. Either `'in'` or `'out'` 25 | node_dominator <- function(root, mode = 'out') { 26 | expect_nodes() 27 | graph <- .G() 28 | root <- as_node_ind(root, graph) 29 | domtree <- as_edgelist(dominator_tree(graph, root, mode)$domtree) 30 | dom <- rep(NA, gorder(graph)) 31 | dom[domtree[, 2]] <- domtree[, 1] 32 | dom[focus_ind(graph, 'nodes')] 33 | } 34 | #' @describeIn node_topology Get the topological order of nodes in a DAG. Wraps [igraph::topo_sort()]. 35 | #' @importFrom igraph gorder topo_sort 36 | #' @export 37 | node_topo_order <- function(mode = 'out') { 38 | expect_nodes() 39 | graph <- .G() 40 | compress_rank(match(focus_ind(graph, 'nodes'), topo_sort(graph, mode = mode))) 41 | } 42 | -------------------------------------------------------------------------------- /R/phylo.R: -------------------------------------------------------------------------------- 1 | #' @describeIn tbl_graph Method for handling phylo objects from the ape package 2 | #' @importFrom igraph set_edge_attr 3 | #' @export 4 | as_tbl_graph.phylo <- function(x, directed = NULL, ...) { 5 | rlang::check_installed('ape', 'in order to coerce phylo object to tbl_graph') 6 | if (is.null(directed)) directed <- ape::is.rooted(x) 7 | gr <- ape::as.igraph.phylo(x, directed = directed, ...) 8 | if (!is.null(x$edge.length)) gr <- set_edge_attr(gr, 'length', value = x$edge.length) 9 | as_tbl_graph(gr) 10 | } 11 | 12 | #' @describeIn tbl_graph Method for handling evonet objects from the ape package 13 | #' @export 14 | as_tbl_graph.evonet <- function(x, directed = TRUE, ...) { 15 | rlang::check_installed('ape', 'in order to coerce evonet object to tbl_graph') 16 | if (is.null(directed)) directed <- ape::is.rooted(x) 17 | as_tbl_graph(ape::as.igraph.evonet(x, directed = directed, ...)) 18 | } 19 | -------------------------------------------------------------------------------- /R/plot.R: -------------------------------------------------------------------------------- 1 | #' Fortify a tbl_graph for ggplot2 plotting 2 | #' 3 | #' In general `tbl_graph` objects are intended to be plotted by network 4 | #' visualisation libraries such as `ggraph`. However, if you do wish to plot 5 | #' either the node or edge data directly with `ggplot2` you can simply add the 6 | #' `tbl_graph` object as either the global or layer data and the currently 7 | #' active data is passed on as a regular data frame. 8 | #' 9 | #' @keywords internal 10 | #' 11 | fortify.tbl_graph <- function(model, data, ...) { 12 | as_tibble(model) 13 | } 14 | 15 | rlang::on_load(register_s3_method('ggplot2', 'fortify', 'tbl_graph')) 16 | -------------------------------------------------------------------------------- /R/pull.R: -------------------------------------------------------------------------------- 1 | # TODO: Update signature to include `name` once we begin to depend on dplyr 1.0 2 | 3 | #' @export 4 | #' @importFrom dplyr pull 5 | pull.tbl_graph <- function(.data, var = -1, ...) { 6 | d_tmp <- as_tibble(.data) 7 | var <- enquo(var) 8 | pull(d_tmp, !! var, ...) 9 | } 10 | #' @export 11 | #' @importFrom dplyr pull 12 | pull.morphed_tbl_graph <- function(.data, var = -1, ...) { 13 | var <- enquo(var) 14 | lapply(.data, pull, !! var, ...) 15 | } 16 | #' @export 17 | dplyr::pull 18 | -------------------------------------------------------------------------------- /R/random_walk.R: -------------------------------------------------------------------------------- 1 | #' Perform a random walk on the graph and return encounter rank 2 | #' 3 | #' A random walk is a traversal of the graph starting from a node and going a 4 | #' number of steps by picking an edge at random (potentially weighted). 5 | #' `random_walk()` can be called both when nodes and edges are active and will 6 | #' adapt to return a value fitting to the currently active part. As the 7 | #' walk order cannot be directly encoded in the graph the return value is a list 8 | #' giving a vector of positions along the walk of each node or edge. 9 | #' 10 | #' @param n The number of steps to perform. If the walk gets stuck before 11 | #' reaching this number the walk is terminated 12 | #' @param root The node to start the walk at. If `NULL` a random node will be 13 | #' used 14 | #' @param mode How edges are followed in the search if the graph is directed. 15 | #' `"out"` only follows outbound edges, `"in"` only follows inbound edges, and 16 | #' `"all"` or `"total"` follows all edges. This is ignored for undirected 17 | #' graphs. 18 | #' @param weights The weights to use for edges when selecting the next step of 19 | #' the walk. Currently only used when edges are active 20 | #' 21 | #' @return A list with an integer vector for each node or edge (depending on 22 | #' what is active) each element encode the time the node/edge is encountered 23 | #' along the walk 24 | #' 25 | #' @importFrom igraph random_walk random_edge_walk gorder gsize 26 | #' @importFrom rlang enquo quo_is_null eval_tidy 27 | #' @export 28 | #' 29 | #' @examples 30 | #' graph <- create_notable("zachary") 31 | #' 32 | #' # Random walk returning node order 33 | #' graph |> 34 | #' mutate(walk_rank = random_walk_rank(200)) 35 | #' 36 | #' # Rank edges instead 37 | #' graph |> 38 | #' activate(edges) |> 39 | #' mutate(walk_rank = random_walk_rank(200)) 40 | #' 41 | random_walk_rank <- function(n, root = NULL, mode = "out", weights = NULL) { 42 | graph <- .G() 43 | if (is.null(root)) { 44 | root <- sample(gorder(graph), 1) 45 | } else { 46 | root <- as_node_ind(root, graph) 47 | } 48 | weights <- enquo(weights) 49 | if (active(graph) == "nodes") { 50 | if (!quo_is_null(weights)) { 51 | cli::cli_warn('{.arg weights} is ignored when doing a random walk on nodes') 52 | } 53 | walk <- as.integer(random_walk(graph, root, n, mode = mode)) 54 | len_out <- gorder(graph) 55 | } else { 56 | weights <- eval_tidy(weights, .E(focused = FALSE)) %||% NA 57 | walk <- as.integer(random_edge_walk(graph, root, n, weights, mode = mode)) 58 | len_out <- gsize(graph) 59 | } 60 | res <- rep(list(integer()), len_out) 61 | ord <- split(seq_along(walk), walk) 62 | res[as.integer(names(ord))] <- ord 63 | res[focus_ind(graph, active(graph))] 64 | } 65 | -------------------------------------------------------------------------------- /R/rename.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @importFrom dplyr rename 3 | rename.tbl_graph <- function(.data, ...) { 4 | .register_graph_context(.data) 5 | d_tmp <- as_tibble(.data) 6 | d_tmp <- rename(d_tmp, ...) 7 | set_graph_data(.data, d_tmp) 8 | } 9 | #' @export 10 | #' @importFrom dplyr rename 11 | rename.morphed_tbl_graph <- function(.data, ...) { 12 | .data[] <- lapply(.data, protect_ind, .f = rename, ...) 13 | .data 14 | } 15 | #' @export 16 | dplyr::rename 17 | -------------------------------------------------------------------------------- /R/reroute.R: -------------------------------------------------------------------------------- 1 | #' Change terminal nodes of edges 2 | #' 3 | #' The reroute verb lets you change the beginning and end node of edges by 4 | #' specifying the new indexes of the start and/or end node(s). Optionally only 5 | #' a subset of the edges can be rerouted using the subset argument, which should 6 | #' be an expression that are to be evaluated in the context of the edge data and 7 | #' should return an index compliant vector (either logical or integer). 8 | #' 9 | #' @param .data A tbl_graph or morphed_tbl_graph object. grouped_tbl_graph will 10 | #' be ungrouped prior to rerouting 11 | #' @param from,to The new indexes of the terminal nodes. If `NULL` nothing will 12 | #' be changed 13 | #' @param subset An expression evaluating to an indexing vector in the context 14 | #' of the edge data. If `NULL` it will use focused edges if available or all 15 | #' edges 16 | #' 17 | #' @return An object of the same class as .data 18 | #' @export 19 | #' 20 | #' @examples 21 | #' # Switch direction of edges 22 | #' create_notable('meredith') %>% 23 | #' activate(edges) %>% 24 | #' reroute(from = to, to = from) 25 | #' 26 | #' # Using subset 27 | #' create_notable('meredith') %>% 28 | #' activate(edges) %>% 29 | #' reroute(from = 1, subset = to > 10) 30 | reroute <- function(.data, from = NULL, to = NULL, subset = NULL) { 31 | UseMethod('reroute') 32 | } 33 | #' @export 34 | #' @importFrom rlang enquo eval_tidy 35 | #' @importFrom igraph is_directed 36 | reroute.tbl_graph <- function(.data, from = NULL, to = NULL, subset = NULL) { 37 | .register_graph_context(.data) 38 | expect_edges() 39 | from <- enquo(from) 40 | to <- enquo(to) 41 | if (is.grouped_tbl_graph(.data)) { 42 | cli::cli_inform('Ungrouping prior to rerouting edges') 43 | .data <- ungroup(.data) 44 | } 45 | edges <- as_tibble(.data, active = 'edges') 46 | subset <- enquo(subset) 47 | subset <- eval_tidy(subset, edges) 48 | if (is.null(subset)) subset <- focus_ind(.data, 'edges') 49 | edges_sub <- edges[subset, , drop = FALSE] 50 | from <- eval_tidy(from, edges_sub) 51 | if (!is.null(from)) edges$from[subset] <- rep(from, length.out = nrow(edges_sub)) 52 | to <- eval_tidy(to, edges_sub) 53 | if (!is.null(to)) edges$to[subset] <- rep(to, length.out = nrow(edges_sub)) 54 | .data <- tbl_graph( 55 | nodes = as_tibble(.data, active = 'nodes'), 56 | edges = edges, 57 | directed = is_directed(.data) 58 | ) %gr_attr% .data 59 | active(.data) <- 'edges' 60 | .data 61 | } 62 | #' @export 63 | #' @importFrom rlang enquo 64 | reroute.morphed_tbl_graph <- function(.data, from = NULL, to = NULL, subset = NULL) { 65 | from <- enquo(from) 66 | to <- enquo(to) 67 | .data[] <- lapply(.data, reroute, from = !!from, to = !!to, subset = subset) 68 | .data 69 | } 70 | -------------------------------------------------------------------------------- /R/sample_frac.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @importFrom dplyr sample_frac 3 | #' @importFrom igraph delete_vertices delete_edges 4 | #' @importFrom rlang enquo 5 | sample_frac.tbl_graph <- function(tbl, size = 1, replace = FALSE, weight = NULL, .env = parent.frame(), ...) { 6 | tbl <- unfocus(tbl) 7 | d_tmp <- as_tibble(tbl) 8 | weight <- enquo(weight) 9 | check_reserved(d_tmp) 10 | orig_ind <- seq_len(nrow(d_tmp)) 11 | d_tmp$.tbl_graph_index <- orig_ind 12 | d_tmp <- sample_frac(d_tmp, size = size, replace = replace, weight = !! weight, .env = .env) 13 | remove_ind <- orig_ind[-d_tmp$.tbl_graph_index] 14 | switch( 15 | active(tbl), 16 | nodes = delete_vertices(tbl, remove_ind), 17 | edges = delete_edges(tbl, remove_ind) 18 | ) %gr_attr% tbl 19 | } 20 | #' @export 21 | #' @importFrom dplyr sample_frac 22 | #' @importFrom rlang enquo 23 | sample_frac.morphed_tbl_graph <- function(tbl, size = 1, replace = FALSE, weight = NULL, .env = parent.frame(), ...) { 24 | weight <- enquo(weight) 25 | tbl[] <- lapply(tbl, sample_frac, size = size, replace = replace, weight = !! weight, .env = .env) 26 | tbl 27 | } 28 | 29 | #' @export 30 | dplyr::sample_frac 31 | -------------------------------------------------------------------------------- /R/sample_n.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @importFrom dplyr sample_n 3 | #' @importFrom igraph delete_vertices delete_edges 4 | #' @importFrom rlang enquo 5 | sample_n.tbl_graph <- function(tbl, size = 1, replace = FALSE, weight = NULL, .env = parent.frame(), ...) { 6 | tbl <- unfocus(tbl) 7 | d_tmp <- as_tibble(tbl) 8 | weight <- enquo(weight) 9 | check_reserved(d_tmp) 10 | orig_ind <- seq_len(nrow(d_tmp)) 11 | d_tmp$.tbl_graph_index <- orig_ind 12 | d_tmp <- sample_n(d_tmp, size = size, replace = replace, weight = !! weight, .env = .env) 13 | remove_ind <- orig_ind[-d_tmp$.tbl_graph_index] 14 | switch( 15 | active(tbl), 16 | nodes = delete_vertices(tbl, remove_ind), 17 | edges = delete_edges(tbl, remove_ind) 18 | ) %gr_attr% tbl 19 | } 20 | #' @export 21 | #' @importFrom dplyr sample_frac 22 | #' @importFrom rlang enquo 23 | sample_n.morphed_tbl_graph <- function(tbl, size = 1, replace = FALSE, weight = NULL, .env = parent.frame(), ...) { 24 | weight <- enquo(weight) 25 | tbl[] <- lapply(tbl, sample_n, size = size, replace = replace, weight = !! weight, .env = .env) 26 | tbl 27 | } 28 | 29 | #' @export 30 | dplyr::sample_n 31 | -------------------------------------------------------------------------------- /R/select.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @importFrom dplyr select 3 | select.tbl_graph <- function(.data, ...) { 4 | .register_graph_context(.data) 5 | d_tmp <- as_tibble(.data) 6 | d_tmp <- select(d_tmp, ...) 7 | set_graph_data(.data, d_tmp) 8 | } 9 | #' @export 10 | #' @importFrom dplyr select 11 | select.morphed_tbl_graph <- function(.data, ...) { 12 | .data[] <- lapply(.data, protect_ind, .f = select, ...) 13 | .data 14 | } 15 | #' @export 16 | dplyr::select 17 | 18 | #' @importFrom dplyr contains 19 | #' @export 20 | dplyr::contains 21 | 22 | #' @importFrom dplyr ends_with 23 | #' @export 24 | dplyr::ends_with 25 | 26 | #' @importFrom dplyr everything 27 | #' @export 28 | dplyr::everything 29 | 30 | #' @importFrom dplyr matches 31 | #' @export 32 | dplyr::matches 33 | 34 | #' @importFrom dplyr num_range 35 | #' @export 36 | dplyr::num_range 37 | 38 | #' @importFrom dplyr one_of 39 | #' @export 40 | dplyr::one_of 41 | 42 | #' @importFrom dplyr starts_with 43 | #' @export 44 | dplyr::starts_with 45 | -------------------------------------------------------------------------------- /R/tibble.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @importFrom tibble as_tibble 3 | as_tibble.tbl_graph <- function(x, active = NULL, focused = TRUE, ...) { 4 | if (is.null(active)) { 5 | active <- attr(x, 'active') 6 | } 7 | switch( 8 | active, 9 | nodes = node_tibble(x, focused = focused), 10 | edges = edge_tibble(x, focused = focused), 11 | cli::cli_abort('Unknown active element: {.val {active}}. Only nodes and edges supported') 12 | ) 13 | } 14 | #' @export 15 | as_tibble.grouped_tbl_graph <- function(x, active = NULL, focused = TRUE, ...) { 16 | tbl <- NextMethod() 17 | if (is.null(active)) { 18 | active <- attr(x, 'active') 19 | } 20 | group_attr <- attr(x, paste0(active, '_group_attr')) 21 | if (!is.null(group_attr)) attributes(tbl) <- group_attr 22 | tbl 23 | } 24 | #' @export 25 | as_tibble.morphed_tbl_graph <- function(x, ...) { 26 | as_tibble(crystallize(x), ...) 27 | } 28 | #' @export 29 | tibble::as_tibble 30 | 31 | #' @importFrom magrittr %>% 32 | #' @export 33 | magrittr::`%>%` 34 | 35 | #' @importFrom igraph vertex_attr gorder 36 | #' @importFrom tibble as_tibble 37 | node_tibble <- function(x, focused = TRUE) { 38 | tbl <- as_tibble(vertex_attr(x)) 39 | if (length(attr(tbl, 'row.names')) == 0) { 40 | attr(tbl, 'row.names') <- .set_row_names(gorder(x)) 41 | } 42 | if (focused && is.focused_tbl_graph(x)) { 43 | tbl <- tbl[focus_ind(x, 'nodes'), ] 44 | } 45 | tbl 46 | } 47 | #' @importFrom igraph edge_attr gsize as_edgelist 48 | #' @importFrom tibble as_tibble 49 | #' @importFrom dplyr bind_cols 50 | edge_tibble <- function(x, focused = TRUE) { 51 | tbl <- as_tibble(edge_attr(x)) 52 | if (length(attr(tbl, 'row.names')) == 0) { 53 | attr(tbl, 'row.names') <- .set_row_names(gsize(x)) 54 | } 55 | e_list <- as_edgelist(x, names = FALSE) 56 | mode(e_list) <- 'integer' 57 | colnames(e_list) <- c('from', 'to') 58 | e_list <- as_tibble(e_list) 59 | tbl <- bind_cols(e_list, tbl) 60 | if (focused && is.focused_tbl_graph(x)) { 61 | tbl <- tbl[focus_ind(x, 'edges'), ] 62 | } 63 | tbl 64 | } 65 | -------------------------------------------------------------------------------- /R/tidygraph-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | '_PACKAGE' 3 | 4 | # The following block is used by usethis to automatically manage 5 | # roxygen namespace tags. Modify with care! 6 | ## usethis namespace: start 7 | #' @importFrom lifecycle deprecated 8 | #' @useDynLib tidygraph, .registration = TRUE 9 | ## usethis namespace: end 10 | NULL 11 | -------------------------------------------------------------------------------- /R/tidyr-utils.R: -------------------------------------------------------------------------------- 1 | #' @importFrom tidyr replace_na 2 | #' @export 3 | #' 4 | replace_na.tbl_graph <- function(data, replace, ...) { 5 | d_tmp <- as_tibble(data) 6 | d_tmp <- replace_na(d_tmp, replace = replace, ...) 7 | set_graph_data(data, d_tmp) 8 | } 9 | #' @importFrom tidyr replace_na 10 | #' @export 11 | #' 12 | replace_na.morphed_tbl_graph <- function(data, replace, ...) { 13 | .data[] <- lapply(data, replace_na, replace = replace, ...) 14 | .data 15 | } 16 | #' @export 17 | tidyr::replace_na 18 | 19 | #' @importFrom tidyr drop_na 20 | #' @export 21 | #' 22 | drop_na.tbl_graph <- function(data, ...) { 23 | graph_slicer(data, drop_na, ...) 24 | } 25 | #' @importFrom tidyr drop_na 26 | #' @export 27 | #' 28 | drop_na.morphed_tbl_graph <- function(data, ...) { 29 | .data[] <- lapply(.data, drop_na, ...) 30 | .data 31 | } 32 | #' @export 33 | tidyr::drop_na 34 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | register_s3_method <- function(pkg, generic, class, fun = NULL) { 2 | stopifnot(is.character(pkg), length(pkg) == 1) 3 | stopifnot(is.character(generic), length(generic) == 1) 4 | stopifnot(is.character(class), length(class) == 1) 5 | 6 | if (is.null(fun)) { 7 | fun <- get(paste0(generic, ".", class), envir = parent.frame()) 8 | } else { 9 | stopifnot(is.function(fun)) 10 | } 11 | 12 | if (pkg %in% loadedNamespaces()) { 13 | registerS3method(generic, class, fun, envir = asNamespace(pkg)) 14 | } 15 | 16 | # Always register hook in case package is later unloaded & reloaded 17 | setHook( 18 | packageEvent(pkg, "onLoad"), 19 | function(...) { 20 | registerS3method(generic, class, fun, envir = asNamespace(pkg)) 21 | } 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | Patch release to make it compliant with changes in igraph 2 | -------------------------------------------------------------------------------- /man/activate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/activate.R 3 | \name{activate} 4 | \alias{activate} 5 | \alias{active} 6 | \alias{\%N>\%} 7 | \alias{\%E>\%} 8 | \title{Determine the context of subsequent manipulations} 9 | \usage{ 10 | activate(.data, what) 11 | 12 | active(x) 13 | 14 | lhs \%N>\% rhs 15 | 16 | lhs \%E>\% rhs 17 | } 18 | \arguments{ 19 | \item{.data, x, lhs}{A tbl_graph or a grouped_tbl_graph} 20 | 21 | \item{what}{What should get activated? Possible values are \code{nodes} or 22 | \code{edges}.} 23 | 24 | \item{rhs}{A function to pipe into} 25 | } 26 | \value{ 27 | A tbl_graph 28 | } 29 | \description{ 30 | As a \link{tbl_graph} can be considered as a collection of two linked tables it is 31 | necessary to specify which table is referenced during manipulations. The 32 | \code{activate} verb does just that and needs affects all subsequent manipulations 33 | until a new table is activated. \code{active} is a simple query function to get 34 | the currently acitve context. In addition to the use of \code{activate} it is also 35 | possible to activate nodes or edges as part of the piping using the \verb{\%N>\%} 36 | and \verb{\%E>\%} pipes respectively. Do note that this approach somewhat obscures 37 | what is going on and is thus only recommended for quick, one-line, fixes in 38 | interactive use. 39 | } 40 | \note{ 41 | Activate will ungroup a grouped_tbl_graph. 42 | } 43 | \examples{ 44 | gr <- create_complete(5) \%>\% 45 | activate(nodes) \%>\% 46 | mutate(class = sample(c('a', 'b'), 5, TRUE)) \%>\% 47 | activate(edges) \%>\% 48 | arrange(from) 49 | 50 | # The above could be achieved using the special pipes as well 51 | gr <- create_complete(5) \%N>\% 52 | mutate(class = sample(c('a', 'b'), 5, TRUE)) \%E>\% 53 | arrange(from) 54 | # But as you can see it obscures what part of the graph is being targeted 55 | 56 | } 57 | -------------------------------------------------------------------------------- /man/bind_graphs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/bind.R 3 | \name{bind_graphs} 4 | \alias{bind_graphs} 5 | \alias{bind_nodes} 6 | \alias{bind_edges} 7 | \title{Add graphs, nodes, or edges to a tbl_graph} 8 | \usage{ 9 | bind_graphs(.data, ...) 10 | 11 | bind_nodes(.data, ...) 12 | 13 | bind_edges(.data, ..., node_key = "name") 14 | } 15 | \arguments{ 16 | \item{.data}{A \code{tbl_graph}, or a list of \code{tbl_graph} objects (for 17 | \code{bind_graphs()}).} 18 | 19 | \item{...}{In case of \code{bind_nodes()} and \code{bind_edges()} data.frames to add. 20 | In the case of \code{bind_graphs()} objects that are convertible to \code{tbl_graph} 21 | using \code{as_tbl_graph()}.} 22 | 23 | \item{node_key}{The name of the column in \code{nodes} that character represented 24 | \code{to} and \code{from} columns should be matched against. If \code{NA} the first column 25 | is always chosen. This setting has no effect if \code{to} and \code{from} are given as 26 | integers.} 27 | } 28 | \value{ 29 | A \code{tbl_graph} containing the new data 30 | } 31 | \description{ 32 | These functions are tbl_graph pendants to \code{\link[dplyr:bind_rows]{dplyr::bind_rows()}} that allows 33 | you to grow your \code{tbl_graph} by adding rows to either the nodes data, the 34 | edges data, or both. As with \code{bind_rows()} columns are matched by name and 35 | are automatically filled with \code{NA} if the column doesn't exist in some 36 | instances. In the case of \code{bind_graphs()} the graphs are automatically 37 | converted to \code{tbl_graph} objects prior to binding. The edges in each graph 38 | will continue to reference the nodes in the graph where they originated, 39 | meaning that their terminal node indexes will be shifted to match the new 40 | index of the node in the combined graph. This means the \code{bind_graphs()} 41 | always result in a disconnected graph. See \code{\link[=graph_join]{graph_join()}} for merging graphs 42 | on common nodes. 43 | } 44 | \examples{ 45 | graph <- create_notable('bull') 46 | new_graph <- create_notable('housex') 47 | 48 | # Add nodes 49 | graph \%>\% bind_nodes(data.frame(new = 1:4)) 50 | 51 | # Add edges 52 | graph \%>\% bind_edges(data.frame(from = 1, to = 4:5)) 53 | 54 | # Add graphs 55 | graph \%>\% bind_graphs(new_graph) 56 | 57 | } 58 | -------------------------------------------------------------------------------- /man/component_games.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/play.R 3 | \name{component_games} 4 | \alias{component_games} 5 | \alias{play_blocks} 6 | \alias{play_blocks_hierarchy} 7 | \alias{play_islands} 8 | \alias{play_smallworld} 9 | \title{Graph games based on connected components} 10 | \usage{ 11 | play_blocks(n, size_blocks, p_between, directed = TRUE, loops = FALSE) 12 | 13 | play_blocks_hierarchy(n, size_blocks, rho, p_within, p_between) 14 | 15 | play_islands(n_islands, size_islands, p_within, m_between) 16 | 17 | play_smallworld( 18 | n_dim, 19 | dim_size, 20 | order, 21 | p_rewire, 22 | loops = FALSE, 23 | multiple = FALSE 24 | ) 25 | } 26 | \arguments{ 27 | \item{n}{The number of nodes in the graph.} 28 | 29 | \item{size_blocks}{The number of vertices in each block} 30 | 31 | \item{p_between, p_within}{The probability of edges within and between groups/blocks} 32 | 33 | \item{directed}{Should the resulting graph be directed} 34 | 35 | \item{loops}{Are loop edges allowed} 36 | 37 | \item{rho}{The fraction of vertices per cluster} 38 | 39 | \item{n_islands}{The number of densely connected islands} 40 | 41 | \item{size_islands}{The number of nodes in each island} 42 | 43 | \item{m_between}{The number of edges between groups/islands} 44 | 45 | \item{n_dim, dim_size}{The dimension and size of the starting lattice} 46 | 47 | \item{order}{The neighborhood size to create connections from} 48 | 49 | \item{p_rewire}{The rewiring probability of edges} 50 | 51 | \item{multiple}{Are multiple edges allowed} 52 | } 53 | \value{ 54 | A tbl_graph object 55 | } 56 | \description{ 57 | This set of graph creation algorithms simulate the topology by, in some way, 58 | connecting subgraphs. The nature of their algorithm is described in detail at 59 | the linked igraph documentation. 60 | } 61 | \section{Functions}{ 62 | \itemize{ 63 | \item \code{play_blocks()}: Create graphs by sampling from stochastic block 64 | model. See \code{\link[igraph:sample_sbm]{igraph::sample_sbm()}} 65 | 66 | \item \code{play_blocks_hierarchy()}: Create graphs by sampling from the hierarchical 67 | stochastic block model. See \code{\link[igraph:sample_hierarchical_sbm]{igraph::sample_hierarchical_sbm()}} 68 | 69 | \item \code{play_islands()}: Create graphs with fixed size and edge 70 | probability of subgraphs as well as fixed edge count between subgraphs. See 71 | \code{\link[igraph:sample_islands]{igraph::sample_islands()}} 72 | 73 | \item \code{play_smallworld()}: Create graphs based on the Watts-Strogatz small- 74 | world model. See \code{\link[igraph:sample_smallworld]{igraph::sample_smallworld()}} 75 | 76 | }} 77 | \examples{ 78 | plot(play_islands(4, 10, 0.7, 3)) 79 | 80 | } 81 | \seealso{ 82 | Other graph games: 83 | \code{\link{evolution_games}}, 84 | \code{\link{sampling_games}}, 85 | \code{\link{type_games}} 86 | } 87 | \concept{graph games} 88 | -------------------------------------------------------------------------------- /man/context_accessors.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/context.R 3 | \name{context_accessors} 4 | \alias{context_accessors} 5 | \alias{.G} 6 | \alias{.N} 7 | \alias{.E} 8 | \title{Access graph, nodes, and edges directly inside verbs} 9 | \usage{ 10 | .G() 11 | 12 | .N(focused = TRUE) 13 | 14 | .E(focused = TRUE) 15 | } 16 | \arguments{ 17 | \item{focused}{Should only the attributes of the currently focused nodes or 18 | edges be returned} 19 | } 20 | \value{ 21 | Either a \code{tbl_graph} (\code{.G()}) or a \code{tibble} (\code{.N()}) 22 | } 23 | \description{ 24 | These three functions makes it possible to directly access either the node 25 | data, the edge data or the graph itself while computing inside verbs. It is 26 | e.g. possible to add an attribute from the node data to the edges based on 27 | the terminating nodes of the edge, or extract some statistics from the graph 28 | itself to use in computations. 29 | } 30 | \section{Functions}{ 31 | \itemize{ 32 | \item \code{.G()}: Get the tbl_graph you're currently working on 33 | 34 | \item \code{.N()}: Get the nodes data from the graph you're currently working on 35 | 36 | \item \code{.E()}: Get the edges data from the graph you're currently working on 37 | 38 | }} 39 | \examples{ 40 | 41 | # Get data from the nodes while computing for the edges 42 | create_notable('bull') \%>\% 43 | activate(nodes) \%>\% 44 | mutate(centrality = centrality_power()) \%>\% 45 | activate(edges) \%>\% 46 | mutate(mean_centrality = (.N()$centrality[from] + .N()$centrality[to])/2) 47 | } 48 | -------------------------------------------------------------------------------- /man/create_graphs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/create.R 3 | \name{create_graphs} 4 | \alias{create_graphs} 5 | \alias{create_ring} 6 | \alias{create_path} 7 | \alias{create_chordal_ring} 8 | \alias{create_de_bruijn} 9 | \alias{create_empty} 10 | \alias{create_bipartite} 11 | \alias{create_citation} 12 | \alias{create_complete} 13 | \alias{create_notable} 14 | \alias{create_kautz} 15 | \alias{create_lattice} 16 | \alias{create_star} 17 | \alias{create_tree} 18 | \title{Create different types of well-defined graphs} 19 | \usage{ 20 | create_ring(n, directed = FALSE, mutual = FALSE) 21 | 22 | create_path(n, directed = FALSE, mutual = FALSE) 23 | 24 | create_chordal_ring(n, w) 25 | 26 | create_de_bruijn(alphabet_size, label_size) 27 | 28 | create_empty(n, directed = FALSE) 29 | 30 | create_bipartite(n1, n2, directed = FALSE, mode = "out") 31 | 32 | create_citation(n) 33 | 34 | create_complete(n) 35 | 36 | create_notable(name) 37 | 38 | create_kautz(alphabet_size, label_size) 39 | 40 | create_lattice(dim, directed = FALSE, mutual = FALSE, circular = FALSE) 41 | 42 | create_star(n, directed = FALSE, mutual = FALSE, mode = "out") 43 | 44 | create_tree(n, children, directed = TRUE, mode = "out") 45 | } 46 | \arguments{ 47 | \item{n, n1, n2}{The number of nodes in the graph} 48 | 49 | \item{directed}{Should the graph be directed} 50 | 51 | \item{mutual}{Should mutual edges be created in case of the graph being 52 | directed} 53 | 54 | \item{w}{A matrix specifying the additional edges in the chordan ring. See 55 | \code{\link[igraph:make_chordal_ring]{igraph::make_chordal_ring()}}} 56 | 57 | \item{alphabet_size}{The number of unique letters in the alphabet used for 58 | the graph} 59 | 60 | \item{label_size}{The number of characters in each node} 61 | 62 | \item{mode}{In case of a directed, non-mutual, graph should the edges flow 63 | \code{'out'} or \code{'in'}} 64 | 65 | \item{name}{The name of a notable graph. See a complete list in \code{\link[igraph:make_graph]{igraph::make_graph()}}} 66 | 67 | \item{dim}{The dimensions of the lattice} 68 | 69 | \item{circular}{Should each dimension in the lattice wrap around} 70 | 71 | \item{children}{The number of children each node has in the tree (if possible)} 72 | } 73 | \value{ 74 | A tbl_graph 75 | } 76 | \description{ 77 | These functions creates a long list of different types of well-defined graphs, 78 | that is, their structure is not based on any randomisation. All of these 79 | functions are shallow wrappers around a range of \verb{igraph::make_*} functions 80 | but returns \code{tbl_graph} rather than \code{igraph} objects. 81 | } 82 | \section{Functions}{ 83 | \itemize{ 84 | \item \code{create_ring()}: Create a simple ring graph 85 | 86 | \item \code{create_path()}: Create a simple path 87 | 88 | \item \code{create_chordal_ring()}: Create a chordal ring 89 | 90 | \item \code{create_de_bruijn()}: Create a de Bruijn graph with the specified alphabet and label size 91 | 92 | \item \code{create_empty()}: Create a graph with no edges 93 | 94 | \item \code{create_bipartite()}: Create a full bipartite graph 95 | 96 | \item \code{create_citation()}: Create a full citation graph 97 | 98 | \item \code{create_complete()}: Create a complete graph (a graph where all nodes are connected) 99 | 100 | \item \code{create_notable()}: Create a graph based on its name. See \code{\link[igraph:make_graph]{igraph::make_graph()}} 101 | 102 | \item \code{create_kautz()}: Create a Kautz graph with the specified alphabet and label size 103 | 104 | \item \code{create_lattice()}: Create a multidimensional grid of nodes 105 | 106 | \item \code{create_star()}: Create a star graph (A single node in the center connected to all other nodes) 107 | 108 | \item \code{create_tree()}: Create a tree graph 109 | 110 | }} 111 | \examples{ 112 | # Create a complete graph with 10 nodes 113 | create_complete(10) 114 | 115 | } 116 | -------------------------------------------------------------------------------- /man/edge_rank.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/edge_rank.R 3 | \name{edge_rank} 4 | \alias{edge_rank} 5 | \alias{edge_rank_eulerian} 6 | \title{Calculate edge ranking} 7 | \usage{ 8 | edge_rank_eulerian(cyclic = FALSE) 9 | } 10 | \arguments{ 11 | \item{cyclic}{should the eulerian path start and end at the same node} 12 | } 13 | \value{ 14 | An integer vector giving the position of each edge in the ranking 15 | } 16 | \description{ 17 | This set of functions tries to calculate a ranking of the edges in a graph so 18 | that edges sharing certain topological traits are in proximity in the 19 | resulting order. 20 | } 21 | \section{Functions}{ 22 | \itemize{ 23 | \item \code{edge_rank_eulerian()}: Calculcate ranking as the visit order of a eulerian 24 | path or cycle. If no such path or cycle exist it will return a vector of 25 | \code{NA}s 26 | 27 | }} 28 | \examples{ 29 | graph <- create_notable('meredith') \%>\% 30 | activate(edges) \%>\% 31 | mutate(rank = edge_rank_eulerian()) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /man/edge_types.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/edge.R 3 | \name{edge_types} 4 | \alias{edge_types} 5 | \alias{edge_is_multiple} 6 | \alias{edge_is_loop} 7 | \alias{edge_is_mutual} 8 | \alias{edge_is_from} 9 | \alias{edge_is_to} 10 | \alias{edge_is_between} 11 | \alias{edge_is_incident} 12 | \alias{edge_is_bridge} 13 | \alias{edge_is_feedback_arc} 14 | \title{Querying edge types} 15 | \usage{ 16 | edge_is_multiple() 17 | 18 | edge_is_loop() 19 | 20 | edge_is_mutual() 21 | 22 | edge_is_from(from) 23 | 24 | edge_is_to(to) 25 | 26 | edge_is_between(from, to, ignore_dir = !graph_is_directed()) 27 | 28 | edge_is_incident(nodes) 29 | 30 | edge_is_bridge() 31 | 32 | edge_is_feedback_arc(weights = NULL, approximate = TRUE) 33 | } 34 | \arguments{ 35 | \item{from, to, nodes}{A vector giving node indices} 36 | 37 | \item{ignore_dir}{Is both directions of the edge allowed} 38 | 39 | \item{weights}{The weight of the edges to use for the calculation. Will be 40 | evaluated in the context of the edge data.} 41 | 42 | \item{approximate}{Should the minimal set be approximated or exact} 43 | } 44 | \value{ 45 | A logical vector of the same length as the number of edges in the 46 | graph 47 | } 48 | \description{ 49 | These functions lets the user query whether the edges in a graph is of a 50 | specific type. All functions return a logical vector giving whether each edge 51 | in the graph corresponds to the specific type. 52 | } 53 | \section{Functions}{ 54 | \itemize{ 55 | \item \code{edge_is_multiple()}: Query whether each edge has any parallel siblings 56 | 57 | \item \code{edge_is_loop()}: Query whether each edge is a loop 58 | 59 | \item \code{edge_is_mutual()}: Query whether each edge has a sibling going in the reverse direction 60 | 61 | \item \code{edge_is_from()}: Query whether an edge goes from a set of nodes 62 | 63 | \item \code{edge_is_to()}: Query whether an edge goes to a set of nodes 64 | 65 | \item \code{edge_is_between()}: Query whether an edge goes between two sets of nodes 66 | 67 | \item \code{edge_is_incident()}: Query whether an edge goes from or to a set of nodes 68 | 69 | \item \code{edge_is_bridge()}: Query whether an edge is a bridge (ie. it's removal 70 | will increase the number of components in a graph) 71 | 72 | \item \code{edge_is_feedback_arc()}: Query whether an edge is part of the minimal feedback 73 | arc set (its removal together with the rest will break all cycles in the 74 | graph) 75 | 76 | }} 77 | \examples{ 78 | create_star(10, directed = TRUE, mutual = TRUE) \%>\% 79 | activate(edges) \%>\% 80 | sample_frac(0.7) \%>\% 81 | mutate(single_edge = !edge_is_mutual()) 82 | } 83 | -------------------------------------------------------------------------------- /man/figures/lifecycle-archived.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: archived 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | archived 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-defunct.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: defunct 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | defunct 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-deprecated.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: deprecated 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | deprecated 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-experimental.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: experimental 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | experimental 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-maturing.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: maturing 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | maturing 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-questioning.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: questioning 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | questioning 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-soft-deprecated.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: soft-deprecated 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | soft-deprecated 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-stable.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: stable 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | lifecycle 21 | 22 | 25 | 26 | stable 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /man/figures/lifecycle-superseded.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: superseded 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | superseded 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/man/figures/logo.png -------------------------------------------------------------------------------- /man/focus.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/focus.R 3 | \name{focus} 4 | \alias{focus} 5 | \alias{focus.tbl_graph} 6 | \alias{focus.morphed_tbl_graph} 7 | \alias{unfocus} 8 | \alias{unfocus.tbl_graph} 9 | \alias{unfocus.focused_tbl_graph} 10 | \alias{unfocus.morphed_tbl_graph} 11 | \title{Select specific nodes or edges to compute on} 12 | \usage{ 13 | focus(.data, ...) 14 | 15 | \method{focus}{tbl_graph}(.data, ...) 16 | 17 | \method{focus}{morphed_tbl_graph}(.data, ...) 18 | 19 | unfocus(.data, ...) 20 | 21 | \method{unfocus}{tbl_graph}(.data, ...) 22 | 23 | \method{unfocus}{focused_tbl_graph}(.data, ...) 24 | 25 | \method{unfocus}{morphed_tbl_graph}(.data, ...) 26 | } 27 | \arguments{ 28 | \item{.data}{A data frame, data frame extension (e.g. a tibble), or a 29 | lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for 30 | more details.} 31 | 32 | \item{...}{<\code{\link[rlang:args_data_masking]{data-masking}}> Expressions that 33 | return a logical value, and are defined in terms of the variables in 34 | \code{.data}. If multiple expressions are included, they are combined with the 35 | \code{&} operator. Only rows for which all conditions evaluate to \code{TRUE} are 36 | kept.} 37 | } 38 | \value{ 39 | A graph with focus applied 40 | } 41 | \description{ 42 | The \code{focus()}/\code{unfocus()} idiom allow you to temporarily tell tidygraph 43 | algorithms to only calculate on a subset of the data, while keeping the full 44 | graph intact. The purpose of this is to avoid having to calculate time 45 | costly measures etc on all nodes or edges of a graph if only a few is needed. 46 | E.g. you might only be interested in the shortest distance from one node to 47 | another so rather than calculating this for all nodes you apply a focus on 48 | one node and perform the calculation. It should be made clear that not all 49 | algorithms will see a performance boost by being applied to a few nodes/edges 50 | since their calculation is applied globally and the result for all 51 | nodes/edges are provided in unison. 52 | } 53 | \note{ 54 | focusing is the lowest prioritised operation on a graph. Applying a 55 | \code{\link[=morph]{morph()}} or a \code{\link[=group_by]{group_by()}} operation will unfocus the graph prior to 56 | performing the operation. The same is true for the inverse operations 57 | (\code{\link[=unmorph]{unmorph()}} and \code{\link[=ungroup]{ungroup()}}). Further, unfocusing will happen any time some 58 | graph altering operation is performed, such as the \code{arrange()} and \code{slice()} 59 | operations 60 | } 61 | -------------------------------------------------------------------------------- /man/fortify.tbl_graph.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot.R 3 | \name{fortify.tbl_graph} 4 | \alias{fortify.tbl_graph} 5 | \title{Fortify a tbl_graph for ggplot2 plotting} 6 | \usage{ 7 | fortify.tbl_graph(model, data, ...) 8 | } 9 | \description{ 10 | In general \code{tbl_graph} objects are intended to be plotted by network 11 | visualisation libraries such as \code{ggraph}. However, if you do wish to plot 12 | either the node or edge data directly with \code{ggplot2} you can simply add the 13 | \code{tbl_graph} object as either the global or layer data and the currently 14 | active data is passed on as a regular data frame. 15 | } 16 | \keyword{internal} 17 | -------------------------------------------------------------------------------- /man/graph-context.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/context.R 3 | \docType{data} 4 | \name{graph-context} 5 | \alias{graph-context} 6 | \alias{.graph_context} 7 | \alias{.register_graph_context} 8 | \alias{.free_graph_context} 9 | \title{Register a graph context for the duration of the current frame} 10 | \format{ 11 | An object of class \code{ContextBuilder} (inherits from \code{R6}) of length 12. 12 | } 13 | \usage{ 14 | .graph_context 15 | 16 | .register_graph_context(graph, free = FALSE, env = parent.frame()) 17 | 18 | .free_graph_context(env = parent.frame()) 19 | } 20 | \arguments{ 21 | \item{graph}{A \code{tbl_graph} object} 22 | 23 | \item{free}{Should the active state of the graph be ignored?} 24 | 25 | \item{env}{The environment where the context should be active} 26 | } 27 | \description{ 28 | This function sets the provided graph to be the context for tidygraph 29 | algorithms, such as e.g. \code{\link[=node_is_center]{node_is_center()}}, for the duration of the current 30 | environment. It automatically removes the graph once the environment exits. 31 | } 32 | \keyword{datasets} 33 | \keyword{internal} 34 | -------------------------------------------------------------------------------- /man/graph_join.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/joins.R 3 | \name{graph_join} 4 | \alias{graph_join} 5 | \title{Join graphs on common nodes} 6 | \usage{ 7 | graph_join(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) 8 | } 9 | \arguments{ 10 | \item{x}{A \code{tbl_graph}} 11 | 12 | \item{y}{An object convertible to a \code{tbl_graph} using \code{\link[=as_tbl_graph]{as_tbl_graph()}}} 13 | 14 | \item{by}{A join specification created with \code{\link[dplyr:join_by]{join_by()}}, or a character 15 | vector of variables to join by. 16 | 17 | If \code{NULL}, the default, \verb{*_join()} will perform a natural join, using all 18 | variables in common across \code{x} and \code{y}. A message lists the variables so 19 | that you can check they're correct; suppress the message by supplying \code{by} 20 | explicitly. 21 | 22 | To join on different variables between \code{x} and \code{y}, use a \code{\link[dplyr:join_by]{join_by()}} 23 | specification. For example, \code{join_by(a == b)} will match \code{x$a} to \code{y$b}. 24 | 25 | To join by multiple variables, use a \code{\link[dplyr:join_by]{join_by()}} specification with 26 | multiple expressions. For example, \code{join_by(a == b, c == d)} will match 27 | \code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. If the column names are the same between 28 | \code{x} and \code{y}, you can shorten this by listing only the variable names, like 29 | \code{join_by(a, c)}. 30 | 31 | \code{\link[dplyr:join_by]{join_by()}} can also be used to perform inequality, rolling, and overlap 32 | joins. See the documentation at \link[dplyr:join_by]{?join_by} for details on 33 | these types of joins. 34 | 35 | For simple equality joins, you can alternatively specify a character vector 36 | of variable names to join by. For example, \code{by = c("a", "b")} joins \code{x$a} 37 | to \code{y$a} and \code{x$b} to \code{y$b}. If variable names differ between \code{x} and \code{y}, 38 | use a named character vector like \code{by = c("x_a" = "y_a", "x_b" = "y_b")}. 39 | 40 | To perform a cross-join, generating all combinations of \code{x} and \code{y}, see 41 | \code{\link[dplyr:cross_join]{cross_join()}}.} 42 | 43 | \item{copy}{If \code{x} and \code{y} are not from the same data source, 44 | and \code{copy} is \code{TRUE}, then \code{y} will be copied into the 45 | same src as \code{x}. This allows you to join tables across srcs, but 46 | it is a potentially expensive operation so you must opt into it.} 47 | 48 | \item{suffix}{If there are non-joined duplicate variables in \code{x} and 49 | \code{y}, these suffixes will be added to the output to disambiguate them. 50 | Should be a character vector of length 2.} 51 | 52 | \item{...}{Other parameters passed onto methods.} 53 | } 54 | \value{ 55 | A \code{tbl_graph} containing the merged graph 56 | } 57 | \description{ 58 | This graph-specific join method makes a full join on the nodes data and 59 | updates the edges in the joining graph so they matches the new indexes of the 60 | nodes in the resulting graph. Node and edge data is combined using 61 | \code{\link[dplyr:bind_rows]{dplyr::bind_rows()}} semantic, meaning that data is matched by column name 62 | and filled with \code{NA} if it is missing in either of the graphs. 63 | } 64 | \examples{ 65 | gr1 <- create_notable('bull') \%>\% 66 | activate(nodes) \%>\% 67 | mutate(name = letters[1:5]) 68 | gr2 <- create_ring(10) \%>\% 69 | activate(nodes) \%>\% 70 | mutate(name = letters[4:13]) 71 | 72 | gr1 \%>\% graph_join(gr2) 73 | } 74 | -------------------------------------------------------------------------------- /man/graph_types.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graph_types.R 3 | \name{graph_types} 4 | \alias{graph_types} 5 | \alias{graph_is_simple} 6 | \alias{graph_is_directed} 7 | \alias{graph_is_bipartite} 8 | \alias{graph_is_connected} 9 | \alias{graph_is_tree} 10 | \alias{graph_is_forest} 11 | \alias{graph_is_dag} 12 | \alias{graph_is_chordal} 13 | \alias{graph_is_complete} 14 | \alias{graph_is_isomorphic_to} 15 | \alias{graph_is_subgraph_isomorphic_to} 16 | \alias{graph_is_eulerian} 17 | \title{Querying graph types} 18 | \usage{ 19 | graph_is_simple() 20 | 21 | graph_is_directed() 22 | 23 | graph_is_bipartite() 24 | 25 | graph_is_connected() 26 | 27 | graph_is_tree() 28 | 29 | graph_is_forest() 30 | 31 | graph_is_dag() 32 | 33 | graph_is_chordal() 34 | 35 | graph_is_complete() 36 | 37 | graph_is_isomorphic_to(graph, method = "auto", ...) 38 | 39 | graph_is_subgraph_isomorphic_to(graph, method = "auto", ...) 40 | 41 | graph_is_eulerian(cyclic = FALSE) 42 | } 43 | \arguments{ 44 | \item{graph}{The graph to compare structure to} 45 | 46 | \item{method}{The algorithm to use for comparison} 47 | 48 | \item{...}{Arguments passed on to the comparison methods. See 49 | \code{\link[igraph:isomorphic]{igraph::is_isomorphic_to()}} and \code{\link[igraph:subgraph_isomorphic]{igraph::is_subgraph_isomorphic_to()}}} 50 | 51 | \item{cyclic}{should the eulerian path start and end at the same node} 52 | } 53 | \value{ 54 | A logical scalar 55 | } 56 | \description{ 57 | This set of functions lets the user query different aspects of the graph 58 | itself. They are all concerned with wether the graph implements certain 59 | properties and will all return a logical scalar. 60 | } 61 | \section{Functions}{ 62 | \itemize{ 63 | \item \code{graph_is_simple()}: Is the graph simple (no parallel edges) 64 | 65 | \item \code{graph_is_directed()}: Is the graph directed 66 | 67 | \item \code{graph_is_bipartite()}: Is the graph bipartite 68 | 69 | \item \code{graph_is_connected()}: Is the graph connected 70 | 71 | \item \code{graph_is_tree()}: Is the graph a tree 72 | 73 | \item \code{graph_is_forest()}: Is the graph an ensemble of multiple trees 74 | 75 | \item \code{graph_is_dag()}: Is the graph a directed acyclic graph 76 | 77 | \item \code{graph_is_chordal()}: Is the graph chordal 78 | 79 | \item \code{graph_is_complete()}: Is the graph fully connected 80 | 81 | \item \code{graph_is_isomorphic_to()}: Is the graph isomorphic to another graph. See \code{\link[igraph:isomorphic]{igraph::is_isomorphic_to()}} 82 | 83 | \item \code{graph_is_subgraph_isomorphic_to()}: Is the graph an isomorphic subgraph to another graph. see \code{\link[igraph:subgraph_isomorphic]{igraph::is_subgraph_isomorphic_to()}} 84 | 85 | \item \code{graph_is_eulerian()}: Can all the edges in the graph be reaches by a single 86 | path or cycle that only goes through each edge once 87 | 88 | }} 89 | \examples{ 90 | gr <- create_tree(50, 4) 91 | 92 | with_graph(gr, graph_is_tree()) 93 | 94 | } 95 | -------------------------------------------------------------------------------- /man/iterate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/iterate.R 3 | \name{iterate} 4 | \alias{iterate} 5 | \alias{iterate_n} 6 | \alias{iterate_while} 7 | \title{Repeatedly modify a graph by a function} 8 | \usage{ 9 | iterate_n(.data, n, .f, ...) 10 | 11 | iterate_while(.data, cnd, .f, ..., max_n = NULL) 12 | } 13 | \arguments{ 14 | \item{.data}{A \code{tbl_graph} object} 15 | 16 | \item{n}{The number of times to iterate} 17 | 18 | \item{.f}{A function taking in a \code{tbl_graph} as the first argument and 19 | returning a \code{tbl_graph} object} 20 | 21 | \item{...}{Further arguments passed on to \code{.f}} 22 | 23 | \item{cnd}{A condition that must evaluate to \code{TRUE} or \code{FALSE} determining if 24 | iteration should continue} 25 | 26 | \item{max_n}{The maximum number of iterations in case \code{cnd} never evaluates 27 | to \code{FALSE}} 28 | } 29 | \value{ 30 | A \code{tbl_graph} object 31 | } 32 | \description{ 33 | The iterate family of functions allow you to call the same modification 34 | function on a graph until some condition is met. This can be used to create 35 | simple simulations in a tidygraph friendly API 36 | } 37 | \examples{ 38 | # Gradually remove edges from the least connected nodes while avoiding 39 | # isolates 40 | create_notable('zachary') |> 41 | iterate_n(20, function(gr) { 42 | gr |> 43 | activate(nodes) |> 44 | mutate(deg = centrality_degree(), rank = order(deg)) |> 45 | activate(edges) |> 46 | slice( 47 | -which(edge_is_incident(.N()$rank == sum(.N()$deg == 1) + 1))[1] 48 | ) 49 | }) 50 | 51 | # Remove a random edge until the graph is split in two 52 | create_notable('zachary') |> 53 | iterate_while(graph_component_count() == 1, function(gr) { 54 | gr |> 55 | activate(edges) |> 56 | slice(-sample(graph_size(), 1)) 57 | }) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /man/local_graph.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/local.R 3 | \name{local_graph} 4 | \alias{local_graph} 5 | \alias{local_size} 6 | \alias{local_members} 7 | \alias{local_triangles} 8 | \alias{local_ave_degree} 9 | \alias{local_transitivity} 10 | \title{Measures based on the neighborhood of each node} 11 | \usage{ 12 | local_size(order = 1, mode = "all", mindist = 0) 13 | 14 | local_members(order = 1, mode = "all", mindist = 0) 15 | 16 | local_triangles() 17 | 18 | local_ave_degree(weights = NULL) 19 | 20 | local_transitivity(weights = NULL) 21 | } 22 | \arguments{ 23 | \item{order}{Integer giving the order of the neighborhood.} 24 | 25 | \item{mode}{Character constant, it specifies how to use the direction of 26 | the edges if a directed graph is analyzed. For \sQuote{out} only the 27 | outgoing edges are followed, so all vertices reachable from the source 28 | vertex in at most \code{order} steps are counted. For \sQuote{"in"} all 29 | vertices from which the source vertex is reachable in at most \code{order} 30 | steps are counted. \sQuote{"all"} ignores the direction of the edges. This 31 | argument is ignored for undirected graphs.} 32 | 33 | \item{mindist}{The minimum distance to include the vertex in the result.} 34 | 35 | \item{weights}{An edge weight vector. For \code{local_ave_degree}: If this argument 36 | is given, the average vertex strength is calculated instead of vertex degree. 37 | For \code{local_transitivity}: if given weighted transitivity using the approach by 38 | \emph{A. Barrat} will be calculated.} 39 | } 40 | \value{ 41 | A numeric vector or a list (for \code{local_members}) with elements 42 | corresponding to the nodes in the graph. 43 | } 44 | \description{ 45 | These functions wraps a set of functions that all measures quantities of the 46 | local neighborhood of each node. They all return a vector or list matching 47 | the node position. 48 | } 49 | \section{Functions}{ 50 | \itemize{ 51 | \item \code{local_size()}: The size of the neighborhood in a given distance from 52 | the node. (Note that the node itself is included unless \code{mindist > 0}). Wraps \code{\link[igraph:ego]{igraph::ego_size()}}. 53 | 54 | \item \code{local_members()}: The members of the neighborhood of each node in a 55 | given distance. Wraps \code{\link[igraph:ego]{igraph::ego()}}. 56 | 57 | \item \code{local_triangles()}: The number of triangles each node participate in. Wraps \code{\link[igraph:count_triangles]{igraph::count_triangles()}}. 58 | 59 | \item \code{local_ave_degree()}: Calculates the average degree based on the neighborhood of each node. Wraps \code{\link[igraph:knn]{igraph::knn()}}. 60 | 61 | \item \code{local_transitivity()}: Calculate the transitivity of each node, that is, the 62 | propensity for the nodes neighbors to be connected. Wraps \code{\link[igraph:transitivity]{igraph::transitivity()}} 63 | 64 | }} 65 | \examples{ 66 | # Get all neighbors of each graph 67 | create_notable('chvatal') \%>\% 68 | activate(nodes) \%>\% 69 | mutate(neighborhood = local_members(mindist = 1)) 70 | 71 | # These are equivalent 72 | create_notable('chvatal') \%>\% 73 | activate(nodes) \%>\% 74 | mutate(n_neighbors = local_size(mindist = 1), 75 | degree = centrality_degree()) \%>\% 76 | as_tibble() 77 | 78 | } 79 | -------------------------------------------------------------------------------- /man/map_bfs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/map.R 3 | \name{map_bfs} 4 | \alias{map_bfs} 5 | \alias{map_bfs_lgl} 6 | \alias{map_bfs_chr} 7 | \alias{map_bfs_int} 8 | \alias{map_bfs_dbl} 9 | \title{Apply a function to nodes in the order of a breath first search} 10 | \usage{ 11 | map_bfs(root, mode = "out", unreachable = FALSE, .f, ...) 12 | 13 | map_bfs_lgl(root, mode = "out", unreachable = FALSE, .f, ...) 14 | 15 | map_bfs_chr(root, mode = "out", unreachable = FALSE, .f, ...) 16 | 17 | map_bfs_int(root, mode = "out", unreachable = FALSE, .f, ...) 18 | 19 | map_bfs_dbl(root, mode = "out", unreachable = FALSE, .f, ...) 20 | } 21 | \arguments{ 22 | \item{root}{The node to start the search from} 23 | 24 | \item{mode}{How should edges be followed? \code{'out'} only follows outbound 25 | edges, \code{'in'} only follows inbound edges, and \code{'all'} follows all edges. This 26 | parameter is ignored for undirected graphs.} 27 | 28 | \item{unreachable}{Should the search jump to an unvisited node if the search 29 | is completed without visiting all nodes.} 30 | 31 | \item{.f}{A function to map over all nodes. See Details} 32 | 33 | \item{...}{Additional parameters to pass to \code{.f}} 34 | } 35 | \value{ 36 | \code{map_bfs()} returns a list of the same length as the number of nodes 37 | in the graph, in the order matching the node order in the graph (that is, not 38 | in the order they are called). \verb{map_bfs_*()} tries to coerce its result into 39 | a vector of the classes \code{logical} (\code{map_bfs_lgl}), \code{character} 40 | (\code{map_bfs_chr}), \code{integer} (\code{map_bfs_int}), or \code{double} (\code{map_bfs_dbl}). 41 | These functions will throw an error if they are unsuccesful, so they are type 42 | safe. 43 | } 44 | \description{ 45 | These functions allow you to map over the nodes in a graph, by first 46 | performing a breath first search on the graph and then mapping over each 47 | node in the order they are visited. The mapping function will have access to 48 | the result and search statistics for all the nodes between itself and the 49 | root in the search. To map over the nodes in the reverse direction use 50 | \code{\link[=map_bfs_back]{map_bfs_back()}}. 51 | } 52 | \details{ 53 | The function provided to \code{.f} will be called with the following arguments in 54 | addition to those supplied through \code{...}: 55 | \itemize{ 56 | \item \code{graph}: The full \code{tbl_graph} object 57 | \item \code{node}: The index of the node currently mapped over 58 | \item \code{rank}: The rank of the node in the search 59 | \item \code{parent}: The index of the node that led to the current node 60 | \item \code{before}: The index of the node that was visited before the current node 61 | \item \code{after}: The index of the node that was visited after the current node. 62 | \item \code{dist}: The distance of the current node from the root 63 | \item \code{path}: A table containing \code{node}, \code{rank}, \code{parent}, \code{before}, \code{after}, 64 | \code{dist}, and \code{result} columns giving the values for each node leading to the 65 | current node. The \code{result} column will contain the result of the mapping 66 | of each node in a list. 67 | } 68 | 69 | Instead of spelling out all of these in the function it is possible to simply 70 | name the ones needed and use \code{...} to catch the rest. 71 | } 72 | \examples{ 73 | # Accumulate values along a search 74 | create_tree(40, children = 3, directed = TRUE) \%>\% 75 | mutate(value = round(runif(40)*100)) \%>\% 76 | mutate(value_acc = map_bfs_dbl(node_is_root(), .f = function(node, path, ...) { 77 | sum(.N()$value[c(node, path$node)]) 78 | })) 79 | } 80 | \seealso{ 81 | Other node map functions: 82 | \code{\link{map_bfs_back}()}, 83 | \code{\link{map_dfs}()}, 84 | \code{\link{map_dfs_back}()} 85 | } 86 | \concept{node map functions} 87 | -------------------------------------------------------------------------------- /man/map_bfs_back.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/map.R 3 | \name{map_bfs_back} 4 | \alias{map_bfs_back} 5 | \alias{map_bfs_back_lgl} 6 | \alias{map_bfs_back_chr} 7 | \alias{map_bfs_back_int} 8 | \alias{map_bfs_back_dbl} 9 | \title{Apply a function to nodes in the reverse order of a breath first search} 10 | \usage{ 11 | map_bfs_back(root, mode = "out", unreachable = FALSE, .f, ...) 12 | 13 | map_bfs_back_lgl(root, mode = "out", unreachable = FALSE, .f, ...) 14 | 15 | map_bfs_back_chr(root, mode = "out", unreachable = FALSE, .f, ...) 16 | 17 | map_bfs_back_int(root, mode = "out", unreachable = FALSE, .f, ...) 18 | 19 | map_bfs_back_dbl(root, mode = "out", unreachable = FALSE, .f, ...) 20 | } 21 | \arguments{ 22 | \item{root}{The node to start the search from} 23 | 24 | \item{mode}{How should edges be followed? \code{'out'} only follows outbound 25 | edges, \code{'in'} only follows inbound edges, and \code{'all'} follows all edges. This 26 | parameter is ignored for undirected graphs.} 27 | 28 | \item{unreachable}{Should the search jump to an unvisited node if the search 29 | is completed without visiting all nodes.} 30 | 31 | \item{.f}{A function to map over all nodes. See Details} 32 | 33 | \item{...}{Additional parameters to pass to \code{.f}} 34 | } 35 | \value{ 36 | \code{map_bfs_back()} returns a list of the same length as the number of 37 | nodes in the graph, in the order matching the node order in the graph (that 38 | is, not in the order they are called). \verb{map_bfs_back_*()} tries to coerce 39 | its result into a vector of the classes \code{logical} (\code{map_bfs_back_lgl}), 40 | \code{character} (\code{map_bfs_back_chr}), \code{integer} (\code{map_bfs_back_int}), or \code{double} 41 | (\code{map_bfs_back_dbl}). These functions will throw an error if they are 42 | unsuccesful, so they are type safe. 43 | } 44 | \description{ 45 | These functions allow you to map over the nodes in a graph, by first 46 | performing a breath first search on the graph and then mapping over each 47 | node in the reverse order they are visited. The mapping function will have 48 | access to the result and search statistics for all the nodes following itself 49 | in the search. To map over the nodes in the original direction use 50 | \code{\link[=map_bfs]{map_bfs()}}. 51 | } 52 | \details{ 53 | The function provided to \code{.f} will be called with the following arguments in 54 | addition to those supplied through \code{...}: 55 | \itemize{ 56 | \item \code{graph}: The full \code{tbl_graph} object 57 | \item \code{node}: The index of the node currently mapped over 58 | \item \code{rank}: The rank of the node in the search 59 | \item \code{parent}: The index of the node that led to the current node 60 | \item \code{before}: The index of the node that was visited before the current node 61 | \item \code{after}: The index of the node that was visited after the current node. 62 | \item \code{dist}: The distance of the current node from the root 63 | \item \code{path}: A table containing \code{node}, \code{rank}, \code{parent}, \code{before}, \code{after}, 64 | \code{dist}, and \code{result} columns giving the values for each node reached from 65 | the current node. The \code{result} column will contain the result of the mapping 66 | of each node in a list. 67 | } 68 | 69 | Instead of spelling out all of these in the function it is possible to simply 70 | name the ones needed and use \code{...} to catch the rest. 71 | } 72 | \examples{ 73 | # Collect values from children 74 | create_tree(40, children = 3, directed = TRUE) \%>\% 75 | mutate(value = round(runif(40)*100)) \%>\% 76 | mutate(child_acc = map_bfs_back_dbl(node_is_root(), .f = function(node, path, ...) { 77 | if (nrow(path) == 0) .N()$value[node] 78 | else { 79 | sum(unlist(path$result[path$parent == node])) 80 | } 81 | })) 82 | } 83 | \seealso{ 84 | Other node map functions: 85 | \code{\link{map_bfs}()}, 86 | \code{\link{map_dfs}()}, 87 | \code{\link{map_dfs_back}()} 88 | } 89 | \concept{node map functions} 90 | -------------------------------------------------------------------------------- /man/map_dfs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/map.R 3 | \name{map_dfs} 4 | \alias{map_dfs} 5 | \alias{map_dfs_lgl} 6 | \alias{map_dfs_chr} 7 | \alias{map_dfs_int} 8 | \alias{map_dfs_dbl} 9 | \title{Apply a function to nodes in the order of a depth first search} 10 | \usage{ 11 | map_dfs(root, mode = "out", unreachable = FALSE, .f, ...) 12 | 13 | map_dfs_lgl(root, mode = "out", unreachable = FALSE, .f, ...) 14 | 15 | map_dfs_chr(root, mode = "out", unreachable = FALSE, .f, ...) 16 | 17 | map_dfs_int(root, mode = "out", unreachable = FALSE, .f, ...) 18 | 19 | map_dfs_dbl(root, mode = "out", unreachable = FALSE, .f, ...) 20 | } 21 | \arguments{ 22 | \item{root}{The node to start the search from} 23 | 24 | \item{mode}{How should edges be followed? \code{'out'} only follows outbound 25 | edges, \code{'in'} only follows inbound edges, and \code{'all'} follows all edges. This 26 | parameter is ignored for undirected graphs.} 27 | 28 | \item{unreachable}{Should the search jump to an unvisited node if the search 29 | is completed without visiting all nodes.} 30 | 31 | \item{.f}{A function to map over all nodes. See Details} 32 | 33 | \item{...}{Additional parameters to pass to \code{.f}} 34 | } 35 | \value{ 36 | \code{map_dfs()} returns a list of the same length as the number of nodes 37 | in the graph, in the order matching the node order in the graph (that is, not 38 | in the order they are called). \verb{map_dfs_*()} tries to coerce its result into 39 | a vector of the classes \code{logical} (\code{map_dfs_lgl}), \code{character} 40 | (\code{map_dfs_chr}), \code{integer} (\code{map_dfs_int}), or \code{double} (\code{map_dfs_dbl}). 41 | These functions will throw an error if they are unsuccesful, so they are type 42 | safe. 43 | } 44 | \description{ 45 | These functions allow you to map over the nodes in a graph, by first 46 | performing a depth first search on the graph and then mapping over each 47 | node in the order they are visited. The mapping function will have access to 48 | the result and search statistics for all the nodes between itself and the 49 | root in the search. To map over the nodes in the reverse direction use 50 | \code{\link[=map_dfs_back]{map_dfs_back()}}. 51 | } 52 | \details{ 53 | The function provided to \code{.f} will be called with the following arguments in 54 | addition to those supplied through \code{...}: 55 | \itemize{ 56 | \item \code{graph}: The full \code{tbl_graph} object 57 | \item \code{node}: The index of the node currently mapped over 58 | \item \code{rank}: The rank of the node in the search 59 | \item \code{rank_out}: The rank of the completion of the nodes subtree 60 | \item \code{parent}: The index of the node that led to the current node 61 | \item \code{dist}: The distance of the current node from the root 62 | \item \code{path}: A table containing \code{node}, \code{rank}, \code{rank_out}, \code{parent}, dist\verb{, and }result\verb{columns giving the values for each node leading to the current node. The}result` column will contain the result of the mapping 63 | of each node in a list. 64 | } 65 | 66 | Instead of spelling out all of these in the function it is possible to simply 67 | name the ones needed and use \code{...} to catch the rest. 68 | } 69 | \examples{ 70 | # Add a random integer to the last value along a search 71 | create_tree(40, children = 3, directed = TRUE) \%>\% 72 | mutate(child_acc = map_dfs_int(node_is_root(), .f = function(node, path, ...) { 73 | last_val <- if (nrow(path) == 0) 0L else tail(unlist(path$result), 1) 74 | last_val + sample(1:10, 1) 75 | })) 76 | } 77 | \seealso{ 78 | Other node map functions: 79 | \code{\link{map_bfs}()}, 80 | \code{\link{map_bfs_back}()}, 81 | \code{\link{map_dfs_back}()} 82 | } 83 | \concept{node map functions} 84 | -------------------------------------------------------------------------------- /man/map_dfs_back.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/map.R 3 | \name{map_dfs_back} 4 | \alias{map_dfs_back} 5 | \alias{map_dfs_back_lgl} 6 | \alias{map_dfs_back_chr} 7 | \alias{map_dfs_back_int} 8 | \alias{map_dfs_back_dbl} 9 | \title{Apply a function to nodes in the reverse order of a depth first search} 10 | \usage{ 11 | map_dfs_back(root, mode = "out", unreachable = FALSE, .f, ...) 12 | 13 | map_dfs_back_lgl(root, mode = "out", unreachable = FALSE, .f, ...) 14 | 15 | map_dfs_back_chr(root, mode = "out", unreachable = FALSE, .f, ...) 16 | 17 | map_dfs_back_int(root, mode = "out", unreachable = FALSE, .f, ...) 18 | 19 | map_dfs_back_dbl(root, mode = "out", unreachable = FALSE, .f, ...) 20 | } 21 | \arguments{ 22 | \item{root}{The node to start the search from} 23 | 24 | \item{mode}{How should edges be followed? \code{'out'} only follows outbound 25 | edges, \code{'in'} only follows inbound edges, and \code{'all'} follows all edges. This 26 | parameter is ignored for undirected graphs.} 27 | 28 | \item{unreachable}{Should the search jump to an unvisited node if the search 29 | is completed without visiting all nodes.} 30 | 31 | \item{.f}{A function to map over all nodes. See Details} 32 | 33 | \item{...}{Additional parameters to pass to \code{.f}} 34 | } 35 | \value{ 36 | \code{map_dfs_back()} returns a list of the same length as the number of 37 | nodes in the graph, in the order matching the node order in the graph (that 38 | is, not in the order they are called). \verb{map_dfs_back_*()} tries to coerce 39 | its result into a vector of the classes \code{logical} (\code{map_dfs_back_lgl}), 40 | \code{character} (\code{map_dfs_back_chr}), \code{integer} (\code{map_dfs_back_int}), or \code{double} 41 | (\code{map_dfs_back_dbl}). These functions will throw an error if they are 42 | unsuccesful, so they are type safe. 43 | } 44 | \description{ 45 | These functions allow you to map over the nodes in a graph, by first 46 | performing a depth first search on the graph and then mapping over each 47 | node in the reverse order they are visited. The mapping function will have 48 | access to the result and search statistics for all the nodes following itself 49 | in the search. To map over the nodes in the original direction use 50 | \code{\link[=map_dfs]{map_dfs()}}. 51 | } 52 | \details{ 53 | The function provided to \code{.f} will be called with the following arguments in 54 | addition to those supplied through \code{...}: 55 | \itemize{ 56 | \item \code{graph}: The full \code{tbl_graph} object 57 | \item \code{node}: The index of the node currently mapped over 58 | \item \code{rank}: The rank of the node in the search 59 | \item \code{rank_out}: The rank of the completion of the nodes subtree 60 | \item \code{parent}: The index of the node that led to the current node 61 | \item \code{dist}: The distance of the current node from the root 62 | \item \code{path}: A table containing \code{node}, \code{rank}, \code{rank_out}, \code{parent}, dist\verb{, and }result\verb{columns giving the values for each node reached from the current node. The}result` column will contain the result of the mapping 63 | of each node in a list. 64 | } 65 | 66 | Instead of spelling out all of these in the function it is possible to simply 67 | name the ones needed and use \code{...} to catch the rest. 68 | } 69 | \examples{ 70 | # Collect values from the 2 closest layers of children in a dfs search 71 | create_tree(40, children = 3, directed = TRUE) \%>\% 72 | mutate(value = round(runif(40)*100)) \%>\% 73 | mutate(child_acc = map_dfs_back(node_is_root(), .f = function(node, path, dist, ...) { 74 | if (nrow(path) == 0) .N()$value[node] 75 | else { 76 | unlist(path$result[path$dist - dist <= 2]) 77 | } 78 | })) 79 | } 80 | \seealso{ 81 | Other node map functions: 82 | \code{\link{map_bfs}()}, 83 | \code{\link{map_bfs_back}()}, 84 | \code{\link{map_dfs}()} 85 | } 86 | \concept{node map functions} 87 | -------------------------------------------------------------------------------- /man/map_local.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/map.R 3 | \name{map_local} 4 | \alias{map_local} 5 | \alias{map_local_lgl} 6 | \alias{map_local_chr} 7 | \alias{map_local_int} 8 | \alias{map_local_dbl} 9 | \title{Map a function over a graph representing the neighborhood of each node} 10 | \usage{ 11 | map_local(order = 1, mode = "all", mindist = 0, .f, ...) 12 | 13 | map_local_lgl(order = 1, mode = "all", mindist = 0, .f, ...) 14 | 15 | map_local_chr(order = 1, mode = "all", mindist = 0, .f, ...) 16 | 17 | map_local_int(order = 1, mode = "all", mindist = 0, .f, ...) 18 | 19 | map_local_dbl(order = 1, mode = "all", mindist = 0, .f, ...) 20 | } 21 | \arguments{ 22 | \item{order}{Integer giving the order of the neighborhood.} 23 | 24 | \item{mode}{Character constant, it specifies how to use the direction of 25 | the edges if a directed graph is analyzed. For \sQuote{out} only the 26 | outgoing edges are followed, so all vertices reachable from the source 27 | vertex in at most \code{order} steps are counted. For \sQuote{"in"} all 28 | vertices from which the source vertex is reachable in at most \code{order} 29 | steps are counted. \sQuote{"all"} ignores the direction of the edges. This 30 | argument is ignored for undirected graphs.} 31 | 32 | \item{mindist}{The minimum distance to include the vertex in the result.} 33 | 34 | \item{.f}{A function to map over all nodes. See Details} 35 | 36 | \item{...}{Additional parameters to pass to \code{.f}} 37 | } 38 | \value{ 39 | \code{map_local()} returns a list of the same length as the number of 40 | nodes in the graph, in the order matching the node order in the graph. 41 | \verb{map_local_*()} tries to coerce its result into a vector of the classes 42 | \code{logical} (\code{map_local_lgl}), \code{character} (\code{map_local_chr}), \code{integer} 43 | (\code{map_local_int}), or \code{double} (\code{map_local_dbl}). These functions will throw 44 | an error if they are unsuccesful, so they are type safe. 45 | } 46 | \description{ 47 | This function extracts the neighborhood of each node as a graph and maps over 48 | each of these neighborhood graphs. Conceptually it is similar to 49 | \code{\link[igraph:local_scan]{igraph::local_scan()}}, but it borrows the type safe versions available in 50 | \code{\link[=map_bfs]{map_bfs()}} and \code{\link[=map_dfs]{map_dfs()}}. 51 | } 52 | \details{ 53 | The function provided to \code{.f} will be called with the following arguments in 54 | addition to those supplied through \code{...}: 55 | \itemize{ 56 | \item \code{neighborhood}: The neighborhood graph of the node 57 | \item \code{graph}: The full \code{tbl_graph} object 58 | \item \code{node}: The index of the node currently mapped over 59 | } 60 | 61 | The \code{neighborhood} graph will contain an extra node attribute called 62 | \code{.central_node}, which will be \code{TRUE} for the node that the neighborhood is 63 | expanded from and \code{FALSE} for everything else. 64 | } 65 | \examples{ 66 | # Smooth out values over a neighborhood 67 | create_notable('meredith') \%>\% 68 | mutate(value = rpois(graph_order(), 5)) \%>\% 69 | mutate(value_smooth = map_local_dbl(order = 2, .f = function(neighborhood, ...) { 70 | mean(as_tibble(neighborhood, active = 'nodes')$value) 71 | })) 72 | } 73 | -------------------------------------------------------------------------------- /man/morph.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/morph.R 3 | \name{morph} 4 | \alias{morph} 5 | \alias{unmorph} 6 | \alias{crystallise} 7 | \alias{crystallize} 8 | \alias{convert} 9 | \title{Create a temporary alternative representation of the graph to compute on} 10 | \usage{ 11 | morph(.data, .f, ...) 12 | 13 | unmorph(.data) 14 | 15 | crystallise(.data) 16 | 17 | crystallize(.data) 18 | 19 | convert(.data, .f, ..., .select = 1, .clean = FALSE) 20 | } 21 | \arguments{ 22 | \item{.data}{A \code{tbl_graph} or a \code{morphed_tbl_graph}} 23 | 24 | \item{.f}{A morphing function. See \link{morphers} for a list of provided one.} 25 | 26 | \item{...}{Arguments passed on to the morpher} 27 | 28 | \item{.select}{The graph to return during \code{convert()}. Either an index or the 29 | name as created during \code{crystallise()}.} 30 | 31 | \item{.clean}{Should references to the node and edge indexes in the original 32 | graph be removed when using \code{convert}} 33 | } 34 | \value{ 35 | A \code{morphed_tbl_graph} 36 | } 37 | \description{ 38 | The \code{morph}/\code{unmorph} verbs are used to create temporary representations of 39 | the graph, such as e.g. its search tree or a subgraph. A morphed graph will 40 | accept any of the standard \code{dplyr} verbs, and changes to the data is 41 | automatically propagated to the original graph when unmorphing. Tidygraph 42 | comes with a range of \link{morphers}, but is it also possible to supply your own. 43 | See Details for the requirement for custom morphers. The \code{crystallise} verb 44 | is used to extract the temporary graph representation into a tibble 45 | containing one separate graph per row and a \code{name} and \code{graph} column holding 46 | the name of each graph and the graph itself respectively. \code{convert()} is a 47 | shorthand for performing both \code{morph} and \code{crystallise} along with extracting 48 | a single \code{tbl_graph} (defaults to the first). For morphs were you know they 49 | only create a single graph, and you want to keep it, this is an easy way. 50 | } 51 | \details{ 52 | It is only possible to change and add to node and edge data from a 53 | morphed state. Any filtering/removal of nodes and edges will not result in 54 | removal from the main graph. However, nodes and edges not present in the 55 | morphed state will be unaffected in the main graph when unmorphing (if new 56 | columns were added during the morhped state they will be filled with \code{NA}). 57 | 58 | Morphing an already morhped graph will unmorph prior to applying the new 59 | morph. 60 | 61 | During a morphed state, the mapping back to the original graph is stored in 62 | \code{.tidygraph_node_index} and \code{.tidygraph_edge_index} columns. These are 63 | accesible but protected, meaning that any changes to them with e.g. mutate 64 | will be ignored. Furthermore, if the morph results in the merging of nodes 65 | and/or edges the original data is stored in a \code{.data} column. This is 66 | protected as well. 67 | 68 | When supplying your own morphers the morphing function should accept a 69 | \code{tbl_graph} as its first input. The provided graph will already have nodes 70 | and edges mapped with a \code{.tidygraph_node_index} and \code{.tidygraph_edge_index} 71 | column. The return value must be a \code{tbl_graph} or a list of \code{tbl_graph}s and 72 | these must contain either a \code{.tidygraph_node_index} column or a 73 | \code{.tidygraph_edge_index} column (or both). Note that it is possible for the 74 | morph to have the edges mapped back to the original nodes and vice versa 75 | (e.g. as with \link{to_linegraph}). In that case the edge data in the morphed 76 | graph(s) will contain a \code{.tidygraph_node_index} column and/or the node data a 77 | \code{.tidygraph_edge_index} column. If the morphing results in the collapse of 78 | multiple columns or edges the index columns should be converted to list 79 | columns mapping the new node/edge back to all the nodes/edges it represents. 80 | Furthermore the original node/edge data should be collapsed to a list of 81 | tibbles, with the row order matching the order in the index column element. 82 | } 83 | \examples{ 84 | create_notable('meredith') \%>\% 85 | mutate(group = group_infomap()) \%>\% 86 | morph(to_contracted, group) \%>\% 87 | mutate(group_centrality = centrality_pagerank()) \%>\% 88 | unmorph() 89 | } 90 | -------------------------------------------------------------------------------- /man/mutate_as_tbl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mutate.R 3 | \name{mutate_as_tbl} 4 | \alias{mutate_as_tbl} 5 | \title{Base implementation of mutate} 6 | \usage{ 7 | mutate_as_tbl(.data, ...) 8 | } 9 | \arguments{ 10 | \item{.data}{A \code{tbl_graph} object} 11 | 12 | \item{...}{columns to mutate} 13 | } 14 | \value{ 15 | A \code{tbl_graph} object 16 | } 17 | \description{ 18 | This implementation of mutate is slightly faster than \code{mutate} at the expense 19 | of the graph only being updated in the end. This means that graph algorithms 20 | will not take changes happening during the mutate call into account. 21 | } 22 | \details{ 23 | The order of speed increase are rather small and in the ~1 millisecond per 24 | mutateed column order, so for regular use this should not be a choice. The 25 | operations not supported by \code{mutate_as_tbl} are e.g. 26 | 27 | \if{html}{\out{
}}\preformatted{gr \%>\% 28 | activate(nodes) \%>\% 29 | mutate(weights = runif(10), degree = centrality_degree(weights)) 30 | }\if{html}{\out{
}} 31 | 32 | as \code{weights} will only be made available in the graph at the end of the 33 | mutate call. 34 | } 35 | \keyword{internal} 36 | -------------------------------------------------------------------------------- /man/node_measures.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/node.R 3 | \name{node_measures} 4 | \alias{node_measures} 5 | \alias{node_eccentricity} 6 | \alias{node_constraint} 7 | \alias{node_coreness} 8 | \alias{node_diversity} 9 | \alias{node_efficiency} 10 | \alias{node_bridging_score} 11 | \alias{node_effective_network_size} 12 | \alias{node_connectivity_impact} 13 | \alias{node_closeness_impact} 14 | \alias{node_fareness_impact} 15 | \title{Querying node measures} 16 | \usage{ 17 | node_eccentricity(mode = "out") 18 | 19 | node_constraint(weights = NULL) 20 | 21 | node_coreness(mode = "out") 22 | 23 | node_diversity(weights) 24 | 25 | node_efficiency(weights = NULL, directed = TRUE, mode = "all") 26 | 27 | node_bridging_score() 28 | 29 | node_effective_network_size() 30 | 31 | node_connectivity_impact() 32 | 33 | node_closeness_impact() 34 | 35 | node_fareness_impact() 36 | } 37 | \arguments{ 38 | \item{mode}{How edges are treated. In \code{node_coreness()} it chooses which kind 39 | of coreness measure to calculate. In \code{node_efficiency()} it defines how the 40 | local neighborhood is created} 41 | 42 | \item{weights}{The weights to use for each node during calculation} 43 | 44 | \item{directed}{Should the graph be treated as a directed graph if it is in 45 | fact directed} 46 | } 47 | \value{ 48 | A numeric vector of the same length as the number of nodes in the 49 | graph. 50 | } 51 | \description{ 52 | These functions are a collection of node measures that do not really fall 53 | into the class of \link{centrality} measures. For lack of a better place they are 54 | collected under the \verb{node_*} umbrella of functions. 55 | } 56 | \section{Functions}{ 57 | \itemize{ 58 | \item \code{node_eccentricity()}: measure the maximum shortest path to all other nodes in the graph 59 | 60 | \item \code{node_constraint()}: measures Burts constraint of the node. See \code{\link[igraph:constraint]{igraph::constraint()}} 61 | 62 | \item \code{node_coreness()}: measures the coreness of each node. See \code{\link[igraph:coreness]{igraph::coreness()}} 63 | 64 | \item \code{node_diversity()}: measures the diversity of the node. See \code{\link[igraph:diversity]{igraph::diversity()}} 65 | 66 | \item \code{node_efficiency()}: measures the local efficiency around each node. See \code{\link[igraph:global_efficiency]{igraph::local_efficiency()}} 67 | 68 | \item \code{node_bridging_score()}: measures Valente's Bridging measures for detecting structural bridges (\code{influenceR}) 69 | 70 | \item \code{node_effective_network_size()}: measures Burt's Effective Network Size indicating access to structural holes in the network (\code{influenceR}) 71 | 72 | \item \code{node_connectivity_impact()}: measures the impact on connectivity when removing the node (\code{netrankr}) 73 | 74 | \item \code{node_closeness_impact()}: measures the impact on closeness when removing the node (\code{netrankr}) 75 | 76 | \item \code{node_fareness_impact()}: measures the impact on fareness (distance between all node pairs) when removing the node (\code{netrankr}) 77 | 78 | }} 79 | \examples{ 80 | # Calculate Burt's Constraint for each node 81 | create_notable('meredith') \%>\% 82 | mutate(b_constraint = node_constraint()) 83 | } 84 | -------------------------------------------------------------------------------- /man/node_topology.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/node_topology.R 3 | \name{node_topology} 4 | \alias{node_topology} 5 | \alias{node_dominator} 6 | \alias{node_topo_order} 7 | \title{Node properties related to the graph topology} 8 | \usage{ 9 | node_dominator(root, mode = "out") 10 | 11 | node_topo_order(mode = "out") 12 | } 13 | \arguments{ 14 | \item{root}{The node to start the dominator search from} 15 | 16 | \item{mode}{How should edges be followed. Either \code{'in'} or \code{'out'}} 17 | } 18 | \value{ 19 | A vector of the same length as the number of nodes in the graph 20 | } 21 | \description{ 22 | These functions calculate properties that are dependent on the overall 23 | topology of the graph. 24 | } 25 | \section{Functions}{ 26 | \itemize{ 27 | \item \code{node_dominator()}: Get the immediate dominator of each node. Wraps \code{\link[igraph:dominator_tree]{igraph::dominator_tree()}}. 28 | 29 | \item \code{node_topo_order()}: Get the topological order of nodes in a DAG. Wraps \code{\link[igraph:topo_sort]{igraph::topo_sort()}}. 30 | 31 | }} 32 | \examples{ 33 | # Sort a graph based on its topological order 34 | create_tree(10, 2) \%>\% 35 | arrange(sample(graph_order())) \%>\% 36 | mutate(old_ind = seq_len(graph_order())) \%>\% 37 | arrange(node_topo_order()) 38 | } 39 | -------------------------------------------------------------------------------- /man/node_types.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/node.R 3 | \name{node_types} 4 | \alias{node_types} 5 | \alias{node_is_cut} 6 | \alias{node_is_root} 7 | \alias{node_is_leaf} 8 | \alias{node_is_sink} 9 | \alias{node_is_source} 10 | \alias{node_is_isolated} 11 | \alias{node_is_universal} 12 | \alias{node_is_simplical} 13 | \alias{node_is_center} 14 | \alias{node_is_adjacent} 15 | \alias{node_is_keyplayer} 16 | \alias{node_is_connected} 17 | \title{Querying node types} 18 | \usage{ 19 | node_is_cut() 20 | 21 | node_is_root() 22 | 23 | node_is_leaf() 24 | 25 | node_is_sink() 26 | 27 | node_is_source() 28 | 29 | node_is_isolated() 30 | 31 | node_is_universal(mode = "out") 32 | 33 | node_is_simplical(mode = "out") 34 | 35 | node_is_center(mode = "out") 36 | 37 | node_is_adjacent(to, mode = "all", include_to = TRUE) 38 | 39 | node_is_keyplayer(k, p = 0, tol = 1e-04, maxsec = 120, roundsec = 30) 40 | 41 | node_is_connected(nodes, mode = "all", any = FALSE) 42 | } 43 | \arguments{ 44 | \item{mode}{The way edges should be followed in the case of directed graphs.} 45 | 46 | \item{to}{The nodes to test for adjacency to} 47 | 48 | \item{include_to}{Should the nodes in \code{to} be marked as adjacent as well} 49 | 50 | \item{k}{The number of keyplayers to identify} 51 | 52 | \item{p}{The probability to accept a lesser state} 53 | 54 | \item{tol}{Optimisation tolerance, below which the optimisation will stop} 55 | 56 | \item{maxsec}{The total computation budget for the optimization, in seconds} 57 | 58 | \item{roundsec}{Number of seconds in between synchronizing workers' answer} 59 | 60 | \item{nodes}{The set of nodes to test connectivity to. Can be a list to use 61 | different sets for different nodes. If a list it will be recycled as 62 | necessary.} 63 | 64 | \item{any}{Logical. If \code{TRUE} the node only needs to be connected to a single 65 | node in the set for it to return \code{TRUE}} 66 | } 67 | \value{ 68 | A logical vector of the same length as the number of nodes in the 69 | graph. 70 | } 71 | \description{ 72 | These functions all lets the user query whether each node is of a certain 73 | type. All of the functions returns a logical vector indicating whether the 74 | node is of the type in question. Do note that the types are not mutually 75 | exclusive and that nodes can thus be of multiple types. 76 | } 77 | \section{Functions}{ 78 | \itemize{ 79 | \item \code{node_is_cut()}: is the node a cut node (articaultion node) 80 | 81 | \item \code{node_is_root()}: is the node a root in a tree 82 | 83 | \item \code{node_is_leaf()}: is the node a leaf in a tree 84 | 85 | \item \code{node_is_sink()}: does the node only have incomming edges 86 | 87 | \item \code{node_is_source()}: does the node only have outgoing edges 88 | 89 | \item \code{node_is_isolated()}: is the node unconnected 90 | 91 | \item \code{node_is_universal()}: is the node connected to all other nodes in the graph 92 | 93 | \item \code{node_is_simplical()}: are all the neighbors of the node connected 94 | 95 | \item \code{node_is_center()}: does the node have the minimal eccentricity in the graph 96 | 97 | \item \code{node_is_adjacent()}: is a node adjacent to any of the nodes given in \code{to} 98 | 99 | \item \code{node_is_keyplayer()}: Is a node part of the keyplayers in the graph (\code{influenceR}) 100 | 101 | \item \code{node_is_connected()}: Is a node connected to all (or any) nodes in a set 102 | 103 | }} 104 | \examples{ 105 | # Find the root and leafs in a tree 106 | create_tree(40, 2) \%>\% 107 | mutate(root = node_is_root(), leaf = node_is_leaf()) 108 | } 109 | -------------------------------------------------------------------------------- /man/random_walk_rank.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/random_walk.R 3 | \name{random_walk_rank} 4 | \alias{random_walk_rank} 5 | \title{Perform a random walk on the graph and return encounter rank} 6 | \usage{ 7 | random_walk_rank(n, root = NULL, mode = "out", weights = NULL) 8 | } 9 | \arguments{ 10 | \item{n}{The number of steps to perform. If the walk gets stuck before 11 | reaching this number the walk is terminated} 12 | 13 | \item{root}{The node to start the walk at. If \code{NULL} a random node will be 14 | used} 15 | 16 | \item{mode}{How edges are followed in the search if the graph is directed. 17 | \code{"out"} only follows outbound edges, \code{"in"} only follows inbound edges, and 18 | \code{"all"} or \code{"total"} follows all edges. This is ignored for undirected 19 | graphs.} 20 | 21 | \item{weights}{The weights to use for edges when selecting the next step of 22 | the walk. Currently only used when edges are active} 23 | } 24 | \value{ 25 | A list with an integer vector for each node or edge (depending on 26 | what is active) each element encode the time the node/edge is encountered 27 | along the walk 28 | } 29 | \description{ 30 | A random walk is a traversal of the graph starting from a node and going a 31 | number of steps by picking an edge at random (potentially weighted). 32 | \code{random_walk()} can be called both when nodes and edges are active and will 33 | adapt to return a value fitting to the currently active part. As the 34 | walk order cannot be directly encoded in the graph the return value is a list 35 | giving a vector of positions along the walk of each node or edge. 36 | } 37 | \examples{ 38 | graph <- create_notable("zachary") 39 | 40 | # Random walk returning node order 41 | graph |> 42 | mutate(walk_rank = random_walk_rank(200)) 43 | 44 | # Rank edges instead 45 | graph |> 46 | activate(edges) |> 47 | mutate(walk_rank = random_walk_rank(200)) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /man/reexports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/arrange.R, R/distinct.R, R/filter.R, 3 | % R/group_by.R, R/igraph.R, R/joins.R, R/mutate.R, R/pull.R, R/rename.R, 4 | % R/sample_frac.R, R/sample_n.R, R/select.R, R/slice.R, R/tbl_graph.R, 5 | % R/tibble.R, R/tidyr-utils.R 6 | \docType{import} 7 | \name{reexports} 8 | \alias{reexports} 9 | \alias{arrange} 10 | \alias{distinct} 11 | \alias{filter} 12 | \alias{top_n} 13 | \alias{group_by} 14 | \alias{ungroup} 15 | \alias{group_size} 16 | \alias{n_groups} 17 | \alias{groups} 18 | \alias{group_vars} 19 | \alias{group_data} 20 | \alias{group_indices} 21 | \alias{group_keys} 22 | \alias{as.igraph} 23 | \alias{left_join} 24 | \alias{right_join} 25 | \alias{inner_join} 26 | \alias{full_join} 27 | \alias{semi_join} 28 | \alias{anti_join} 29 | \alias{mutate} 30 | \alias{transmute} 31 | \alias{mutate_all} 32 | \alias{mutate_at} 33 | \alias{n} 34 | \alias{pull} 35 | \alias{rename} 36 | \alias{sample_frac} 37 | \alias{sample_n} 38 | \alias{select} 39 | \alias{contains} 40 | \alias{ends_with} 41 | \alias{everything} 42 | \alias{matches} 43 | \alias{num_range} 44 | \alias{one_of} 45 | \alias{starts_with} 46 | \alias{slice} 47 | \alias{slice_head} 48 | \alias{slice_tail} 49 | \alias{slice_min} 50 | \alias{slice_max} 51 | \alias{slice_sample} 52 | \alias{tbl_vars} 53 | \alias{as_tibble} 54 | \alias{\%>\%} 55 | \alias{replace_na} 56 | \alias{drop_na} 57 | \title{Objects exported from other packages} 58 | \keyword{internal} 59 | \description{ 60 | These objects are imported from other packages. Follow the links 61 | below to see their documentation. 62 | 63 | \describe{ 64 | \item{dplyr}{\code{\link[dplyr:filter-joins]{anti_join}}, \code{\link[dplyr]{arrange}}, \code{\link[dplyr:reexports]{contains}}, \code{\link[dplyr]{distinct}}, \code{\link[dplyr:reexports]{ends_with}}, \code{\link[dplyr:reexports]{everything}}, \code{\link[dplyr]{filter}}, \code{\link[dplyr:mutate-joins]{full_join}}, \code{\link[dplyr]{group_by}}, \code{\link[dplyr]{group_data}}, \code{\link[dplyr:group_data]{group_indices}}, \code{\link[dplyr:group_data]{group_keys}}, \code{\link[dplyr:group_data]{group_size}}, \code{\link[dplyr:group_data]{group_vars}}, \code{\link[dplyr:group_data]{groups}}, \code{\link[dplyr:mutate-joins]{inner_join}}, \code{\link[dplyr:mutate-joins]{left_join}}, \code{\link[dplyr:reexports]{matches}}, \code{\link[dplyr]{mutate}}, \code{\link[dplyr]{mutate_all}}, \code{\link[dplyr:mutate_all]{mutate_at}}, \code{\link[dplyr:context]{n}}, \code{\link[dplyr:group_data]{n_groups}}, \code{\link[dplyr:reexports]{num_range}}, \code{\link[dplyr:reexports]{one_of}}, \code{\link[dplyr]{pull}}, \code{\link[dplyr]{rename}}, \code{\link[dplyr:mutate-joins]{right_join}}, \code{\link[dplyr:sample_n]{sample_frac}}, \code{\link[dplyr]{sample_n}}, \code{\link[dplyr]{select}}, \code{\link[dplyr:filter-joins]{semi_join}}, \code{\link[dplyr]{slice}}, \code{\link[dplyr:slice]{slice_head}}, \code{\link[dplyr:slice]{slice_max}}, \code{\link[dplyr:slice]{slice_min}}, \code{\link[dplyr:slice]{slice_sample}}, \code{\link[dplyr:slice]{slice_tail}}, \code{\link[dplyr:reexports]{starts_with}}, \code{\link[dplyr]{tbl_vars}}, \code{\link[dplyr]{top_n}}, \code{\link[dplyr]{transmute}}, \code{\link[dplyr:group_by]{ungroup}}} 65 | 66 | \item{igraph}{\code{\link[igraph]{as.igraph}}} 67 | 68 | \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}} 69 | 70 | \item{tibble}{\code{\link[tibble]{as_tibble}}} 71 | 72 | \item{tidyr}{\code{\link[tidyr]{drop_na}}, \code{\link[tidyr]{replace_na}}} 73 | }} 74 | 75 | -------------------------------------------------------------------------------- /man/reroute.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/reroute.R 3 | \name{reroute} 4 | \alias{reroute} 5 | \title{Change terminal nodes of edges} 6 | \usage{ 7 | reroute(.data, from = NULL, to = NULL, subset = NULL) 8 | } 9 | \arguments{ 10 | \item{.data}{A tbl_graph or morphed_tbl_graph object. grouped_tbl_graph will 11 | be ungrouped prior to rerouting} 12 | 13 | \item{from, to}{The new indexes of the terminal nodes. If \code{NULL} nothing will 14 | be changed} 15 | 16 | \item{subset}{An expression evaluating to an indexing vector in the context 17 | of the edge data. If \code{NULL} it will use focused edges if available or all 18 | edges} 19 | } 20 | \value{ 21 | An object of the same class as .data 22 | } 23 | \description{ 24 | The reroute verb lets you change the beginning and end node of edges by 25 | specifying the new indexes of the start and/or end node(s). Optionally only 26 | a subset of the edges can be rerouted using the subset argument, which should 27 | be an expression that are to be evaluated in the context of the edge data and 28 | should return an index compliant vector (either logical or integer). 29 | } 30 | \examples{ 31 | # Switch direction of edges 32 | create_notable('meredith') \%>\% 33 | activate(edges) \%>\% 34 | reroute(from = to, to = from) 35 | 36 | # Using subset 37 | create_notable('meredith') \%>\% 38 | activate(edges) \%>\% 39 | reroute(from = 1, subset = to > 10) 40 | } 41 | -------------------------------------------------------------------------------- /man/sampling_games.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/play.R 3 | \name{sampling_games} 4 | \alias{sampling_games} 5 | \alias{play_degree} 6 | \alias{play_dotprod} 7 | \alias{play_fitness} 8 | \alias{play_fitness_power} 9 | \alias{play_gnm} 10 | \alias{play_gnp} 11 | \alias{play_geometry} 12 | \alias{play_erdos_renyi} 13 | \title{Graph games based on direct sampling} 14 | \usage{ 15 | play_degree(out_degree, in_degree = NULL, method = "simple") 16 | 17 | play_dotprod(position, directed = TRUE) 18 | 19 | play_fitness(m, out_fit, in_fit = NULL, loops = FALSE, multiple = FALSE) 20 | 21 | play_fitness_power( 22 | n, 23 | m, 24 | out_exp, 25 | in_exp = -1, 26 | loops = FALSE, 27 | multiple = FALSE, 28 | correct = TRUE 29 | ) 30 | 31 | play_gnm(n, m, directed = TRUE, loops = FALSE) 32 | 33 | play_gnp(n, p, directed = TRUE, loops = FALSE) 34 | 35 | play_geometry(n, radius, torus = FALSE) 36 | 37 | play_erdos_renyi(n, p, m, directed = TRUE, loops = FALSE) 38 | } 39 | \arguments{ 40 | \item{out_degree, in_degree}{The degrees of each node in the graph} 41 | 42 | \item{method}{The algorithm to use for the generation. Either \code{'simple'}, 43 | \code{'vl'}, or \code{'simple.no.multiple'}} 44 | 45 | \item{position}{The latent position of each node by column.} 46 | 47 | \item{directed}{Should the resulting graph be directed} 48 | 49 | \item{m}{The number of edges in the graph} 50 | 51 | \item{out_fit, in_fit}{The fitness of each node} 52 | 53 | \item{loops}{Are loop edges allowed} 54 | 55 | \item{multiple}{Are multiple edges allowed} 56 | 57 | \item{n}{The number of nodes in the graph.} 58 | 59 | \item{out_exp, in_exp}{Power law exponent of degree distribution} 60 | 61 | \item{correct}{Use finite size correction} 62 | 63 | \item{p}{The probabilty of an edge occuring} 64 | 65 | \item{radius}{The radius within which vertices are connected} 66 | 67 | \item{torus}{Should the vertices be distributed on a torus instead of a plane} 68 | } 69 | \value{ 70 | A tbl_graph object 71 | } 72 | \description{ 73 | This set of graph games creates graphs directly through sampling of different 74 | attributes, topologies, etc. The nature of their algorithm is described in 75 | detail at the linked igraph documentation. 76 | } 77 | \section{Functions}{ 78 | \itemize{ 79 | \item \code{play_degree()}: Create graphs based on the given node degrees. See 80 | \code{\link[igraph:sample_degseq]{igraph::sample_degseq()}} 81 | 82 | \item \code{play_dotprod()}: Create graphs with link probability given by the 83 | dot product of the latent position of termintating nodes. See 84 | \code{\link[igraph:sample_dot_product]{igraph::sample_dot_product()}} 85 | 86 | \item \code{play_fitness()}: Create graphs where edge probabilities are 87 | proportional to terminal node fitness scores. See \code{\link[igraph:sample_fitness]{igraph::sample_fitness()}} 88 | 89 | \item \code{play_fitness_power()}: Create graphs with an expected power-law degree 90 | distribution. See \code{\link[igraph:sample_fitness_pl]{igraph::sample_fitness_pl()}} 91 | 92 | \item \code{play_gnm()}: Create graphs with a fixed edge count. See 93 | \code{\link[igraph:sample_gnm]{igraph::sample_gnm()}} 94 | 95 | \item \code{play_gnp()}: Create graphs with a fixed edge probability. See 96 | \code{\link[igraph:sample_gnp]{igraph::sample_gnp()}} 97 | 98 | \item \code{play_geometry()}: Create graphs by positioning nodes on a plane or 99 | torus and connecting nearby ones. See \code{\link[igraph:sample_grg]{igraph::sample_grg()}} 100 | 101 | \item \code{play_erdos_renyi()}: \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Create graphs 102 | with a fixed edge probability or count. See \code{\link[igraph:sample_gnp]{igraph::sample_gnp()}} and 103 | \code{\link[igraph:sample_gnm]{igraph::sample_gnm()}} 104 | 105 | }} 106 | \examples{ 107 | plot(play_erdos_renyi(20, 0.3)) 108 | } 109 | \seealso{ 110 | Other graph games: 111 | \code{\link{component_games}}, 112 | \code{\link{evolution_games}}, 113 | \code{\link{type_games}} 114 | } 115 | \concept{graph games} 116 | -------------------------------------------------------------------------------- /man/search_graph.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/search.R 3 | \name{search_graph} 4 | \alias{search_graph} 5 | \alias{bfs_rank} 6 | \alias{bfs_parent} 7 | \alias{bfs_before} 8 | \alias{bfs_after} 9 | \alias{bfs_dist} 10 | \alias{dfs_rank} 11 | \alias{dfs_rank_out} 12 | \alias{dfs_parent} 13 | \alias{dfs_dist} 14 | \title{Search a graph with depth first and breath first} 15 | \usage{ 16 | bfs_rank(root, mode = "out", unreachable = FALSE) 17 | 18 | bfs_parent(root, mode = "out", unreachable = FALSE) 19 | 20 | bfs_before(root, mode = "out", unreachable = FALSE) 21 | 22 | bfs_after(root, mode = "out", unreachable = FALSE) 23 | 24 | bfs_dist(root, mode = "out", unreachable = FALSE) 25 | 26 | dfs_rank(root, mode = "out", unreachable = FALSE) 27 | 28 | dfs_rank_out(root, mode = "out", unreachable = FALSE) 29 | 30 | dfs_parent(root, mode = "out", unreachable = FALSE) 31 | 32 | dfs_dist(root, mode = "out", unreachable = FALSE) 33 | } 34 | \arguments{ 35 | \item{root}{The node to start the search from} 36 | 37 | \item{mode}{How edges are followed in the search if the graph is directed. 38 | \code{"out"} only follows outbound edges, \code{"in"} only follows inbound edges, and 39 | \code{"all"} or \code{"total"} follows all edges. This is ignored for undirected 40 | graphs.} 41 | 42 | \item{unreachable}{Should the search jump to a new component if the search is 43 | terminated without all nodes being visited? Default to \code{FALSE} (only reach 44 | connected nodes).} 45 | } 46 | \value{ 47 | An integer vector, the nature of which is determined by the function. 48 | } 49 | \description{ 50 | These functions wraps the \code{\link[igraph:bfs]{igraph::bfs()}} and \code{\link[igraph:dfs]{igraph::dfs()}} functions to 51 | provide a consistent return value that can be used in \code{\link[dplyr:mutate]{dplyr::mutate()}} 52 | calls. Each function returns an integer vector with values matching the order 53 | of the nodes in the graph. 54 | } 55 | \section{Functions}{ 56 | \itemize{ 57 | \item \code{bfs_rank()}: Get the succession in which the nodes are visited in a breath first search 58 | 59 | \item \code{bfs_parent()}: Get the nodes from which each node is visited in a breath first search 60 | 61 | \item \code{bfs_before()}: Get the node that was visited before each node in a breath first search 62 | 63 | \item \code{bfs_after()}: Get the node that was visited after each node in a breath first search 64 | 65 | \item \code{bfs_dist()}: Get the number of nodes between the root and each node in a breath first search 66 | 67 | \item \code{dfs_rank()}: Get the succession in which the nodes are visited in a depth first search 68 | 69 | \item \code{dfs_rank_out()}: Get the succession in which each nodes subtree is completed in a depth first search 70 | 71 | \item \code{dfs_parent()}: Get the nodes from which each node is visited in a depth first search 72 | 73 | \item \code{dfs_dist()}: Get the number of nodes between the root and each node in a depth first search 74 | 75 | }} 76 | \examples{ 77 | # Get the depth of each node in a tree 78 | create_tree(10, 2) \%>\% 79 | activate(nodes) \%>\% 80 | mutate(depth = bfs_dist(root = 1)) 81 | 82 | # Reorder nodes based on a depth first search from node 3 83 | create_notable('franklin') \%>\% 84 | activate(nodes) \%>\% 85 | mutate(order = dfs_rank(root = 3)) \%>\% 86 | arrange(order) 87 | 88 | } 89 | -------------------------------------------------------------------------------- /man/tidygraph-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tidygraph-package.R 3 | \docType{package} 4 | \name{tidygraph-package} 5 | \alias{tidygraph} 6 | \alias{tidygraph-package} 7 | \title{tidygraph: A Tidy API for Graph Manipulation} 8 | \description{ 9 | \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} 10 | 11 | A graph, while not "tidy" in itself, can be thought of as two tidy data frames describing node and edge data respectively. 'tidygraph' provides an approach to manipulate these two virtual data frames using the API defined in the 'dplyr' package, as well as provides tidy interfaces to a lot of common graph algorithms. 12 | } 13 | \seealso{ 14 | Useful links: 15 | \itemize{ 16 | \item \url{https://tidygraph.data-imaginist.com} 17 | \item \url{https://github.com/thomasp85/tidygraph} 18 | \item Report bugs at \url{https://github.com/thomasp85/tidygraph/issues} 19 | } 20 | 21 | } 22 | \author{ 23 | \strong{Maintainer}: Thomas Lin Pedersen \email{thomasp85@gmail.com} (\href{https://orcid.org/0000-0002-5147-4711}{ORCID}) 24 | 25 | } 26 | \keyword{internal} 27 | -------------------------------------------------------------------------------- /man/type_games.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/play.R 3 | \name{type_games} 4 | \alias{type_games} 5 | \alias{play_preference} 6 | \alias{play_preference_asym} 7 | \alias{play_bipartite} 8 | \alias{play_traits} 9 | \alias{play_citation_type} 10 | \title{Graph games based on different node types} 11 | \usage{ 12 | play_preference( 13 | n, 14 | n_types, 15 | p_type = rep(1, n_types), 16 | p_pref = matrix(1, n_types, n_types), 17 | fixed = FALSE, 18 | directed = TRUE, 19 | loops = FALSE 20 | ) 21 | 22 | play_preference_asym( 23 | n, 24 | n_types, 25 | p_type = matrix(1, n_types, n_types), 26 | p_pref = matrix(1, n_types, n_types), 27 | loops = FALSE 28 | ) 29 | 30 | play_bipartite(n1, n2, p, m, directed = TRUE, mode = "out") 31 | 32 | play_traits( 33 | n, 34 | n_types, 35 | growth = 1, 36 | p_type = rep(1, n_types), 37 | p_pref = matrix(1, n_types, n_types), 38 | callaway = TRUE, 39 | directed = TRUE 40 | ) 41 | 42 | play_citation_type( 43 | n, 44 | growth, 45 | types = rep(0, n), 46 | p_pref = rep(1, length(unique(types))), 47 | directed = TRUE 48 | ) 49 | } 50 | \arguments{ 51 | \item{n, n1, n2}{The number of nodes in the graph. For bipartite graphs \code{n1} 52 | and \code{n2} specifies the number of nodes of each type.} 53 | 54 | \item{n_types}{The number of different node types in the graph} 55 | 56 | \item{p_type}{The probability that a node will be the given type. Either a 57 | vector or a matrix, depending on the game} 58 | 59 | \item{p_pref}{The probability that an edge will be made to a type. Either a 60 | vector or a matrix, depending on the game} 61 | 62 | \item{fixed}{Should n_types be understood as a fixed number of nodes for each 63 | type rather than as a probability} 64 | 65 | \item{directed}{Should the resulting graph be directed} 66 | 67 | \item{loops}{Are loop edges allowed} 68 | 69 | \item{p}{The probabilty of an edge occuring} 70 | 71 | \item{m}{The number of edges in the graph} 72 | 73 | \item{mode}{The flow direction of edges} 74 | 75 | \item{growth}{The number of edges added at each iteration} 76 | 77 | \item{callaway}{Use the callaway version of the trait based game} 78 | 79 | \item{types}{The type of each node in the graph, enumerated from 0} 80 | } 81 | \value{ 82 | A tbl_graph object 83 | } 84 | \description{ 85 | This set of games are build around different types of nodes and simulating 86 | their interaction. The nature of their algorithm is described in 87 | detail at the linked igraph documentation. 88 | } 89 | \section{Functions}{ 90 | \itemize{ 91 | \item \code{play_preference()}: Create graphs by linking nodes of different types 92 | based on a defined probability. See \code{\link[igraph:sample_pref]{igraph::sample_pref()}} 93 | 94 | \item \code{play_preference_asym()}: Create graphs by linking nodes of different types 95 | based on an asymmetric probability. See \code{\link[igraph:sample_pref]{igraph::sample_asym_pref()}} 96 | 97 | \item \code{play_bipartite()}: Create bipartite graphs of fixed size and edge count 98 | or probability. See \code{\link[igraph:sample_bipartite]{igraph::sample_bipartite()}} 99 | 100 | \item \code{play_traits()}: Create graphs by evolving a graph with type based edge 101 | probabilities. See \code{\link[igraph:sample_traits_callaway]{igraph::sample_traits()}} and 102 | \code{\link[igraph:sample_traits_callaway]{igraph::sample_traits_callaway()}} 103 | 104 | \item \code{play_citation_type()}: Create citation graphs by evolving with type based 105 | linking probability. See \code{\link[igraph:sample_last_cit]{igraph::sample_cit_types()}} and 106 | \code{\link[igraph:sample_last_cit]{igraph::sample_cit_cit_types()}} 107 | 108 | }} 109 | \examples{ 110 | plot(play_bipartite(20, 30, 0.4)) 111 | 112 | } 113 | \seealso{ 114 | Other graph games: 115 | \code{\link{component_games}}, 116 | \code{\link{evolution_games}}, 117 | \code{\link{sampling_games}} 118 | } 119 | \concept{graph games} 120 | -------------------------------------------------------------------------------- /man/with_graph.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/context.R 3 | \name{with_graph} 4 | \alias{with_graph} 5 | \title{Evaluate a tidygraph algorithm in the context of a graph} 6 | \usage{ 7 | with_graph(graph, expr) 8 | } 9 | \arguments{ 10 | \item{graph}{The \code{tbl_graph} to use as context} 11 | 12 | \item{expr}{The expression to evaluate} 13 | } 14 | \value{ 15 | The value of \code{expr} 16 | } 17 | \description{ 18 | All tidygraph algorithms are meant to be called inside tidygraph verbs such 19 | as \code{mutate()}, where the graph that is currently being worked on is known and 20 | thus not needed as an argument to the function. In the off chance that you 21 | want to use an algorithm outside of the tidygraph framework you can use 22 | \code{with_graph()} to set the graph context temporarily while the algorithm is 23 | being evaluated. 24 | } 25 | \examples{ 26 | gr <- play_gnp(10, 0.3) 27 | 28 | with_graph(gr, centrality_degree()) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasp85/tidygraph/9a3385fcecc89b6f210c51c5bc9936797b100be4/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /revdep/README.md: -------------------------------------------------------------------------------- 1 | # Revdeps 2 | 3 | ## Failed to check (3) 4 | 5 | |package |version |error |warning |note | 6 | |:-------|:-------|:-----|:-------|:----| 7 | |NA |? | | | | 8 | |numbat |? | | | | 9 | |NA |? | | | | 10 | 11 | -------------------------------------------------------------------------------- /revdep/cran.md: -------------------------------------------------------------------------------- 1 | ## revdepcheck results 2 | 3 | We checked 52 reverse dependencies (50 from CRAN + 2 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package. 4 | 5 | * We saw 0 new problems 6 | * We failed to check 1 packages 7 | 8 | Issues with CRAN packages are summarised below. 9 | 10 | ### Failed to check 11 | 12 | * numbat (NA) 13 | -------------------------------------------------------------------------------- /revdep/failures.md: -------------------------------------------------------------------------------- 1 | # NA 2 | 3 |
4 | 5 | * Version: NA 6 | * GitHub: NA 7 | * Source code: https://github.com/cran/NA 8 | * Number of recursive dependencies: 0 9 | 10 | Run `cloud_details(, "NA")` for more info 11 | 12 |
13 | 14 | ## Error before installation 15 | 16 | ### Devel 17 | 18 | ``` 19 | 20 | 21 | 22 | 23 | 24 | 25 | ``` 26 | ### CRAN 27 | 28 | ``` 29 | 30 | 31 | 32 | 33 | 34 | 35 | ``` 36 | # numbat 37 | 38 |
39 | 40 | * Version: 1.3.2-1 41 | * GitHub: https://github.com/kharchenkolab/numbat 42 | * Source code: https://github.com/cran/numbat 43 | * Date/Publication: 2023-06-17 18:50:02 UTC 44 | * Number of recursive dependencies: 135 45 | 46 | Run `cloud_details(, "numbat")` for more info 47 | 48 |
49 | 50 | ## Error before installation 51 | 52 | ### Devel 53 | 54 | ``` 55 | * using log directory ‘/tmp/workdir/numbat/new/numbat.Rcheck’ 56 | * using R version 4.1.1 (2021-08-10) 57 | * using platform: x86_64-pc-linux-gnu (64-bit) 58 | * using session charset: UTF-8 59 | * using option ‘--no-manual’ 60 | * checking for file ‘numbat/DESCRIPTION’ ... OK 61 | * this is package ‘numbat’ version ‘1.3.2-1’ 62 | * package encoding: UTF-8 63 | * checking package namespace information ... OK 64 | * checking package dependencies ... ERROR 65 | Packages required but not available: 'ggtree', 'scistreer' 66 | 67 | See section ‘The DESCRIPTION file’ in the ‘Writing R Extensions’ 68 | manual. 69 | * DONE 70 | Status: 1 ERROR 71 | 72 | 73 | 74 | 75 | 76 | ``` 77 | ### CRAN 78 | 79 | ``` 80 | * using log directory ‘/tmp/workdir/numbat/old/numbat.Rcheck’ 81 | * using R version 4.1.1 (2021-08-10) 82 | * using platform: x86_64-pc-linux-gnu (64-bit) 83 | * using session charset: UTF-8 84 | * using option ‘--no-manual’ 85 | * checking for file ‘numbat/DESCRIPTION’ ... OK 86 | * this is package ‘numbat’ version ‘1.3.2-1’ 87 | * package encoding: UTF-8 88 | * checking package namespace information ... OK 89 | * checking package dependencies ... ERROR 90 | Packages required but not available: 'ggtree', 'scistreer' 91 | 92 | See section ‘The DESCRIPTION file’ in the ‘Writing R Extensions’ 93 | manual. 94 | * DONE 95 | Status: 1 ERROR 96 | 97 | 98 | 99 | 100 | 101 | ``` 102 | # NA 103 | 104 |
105 | 106 | * Version: NA 107 | * GitHub: NA 108 | * Source code: https://github.com/cran/NA 109 | * Number of recursive dependencies: 0 110 | 111 | Run `cloud_details(, "NA")` for more info 112 | 113 |
114 | 115 | ## Error before installation 116 | 117 | ### Devel 118 | 119 | ``` 120 | 121 | 122 | 123 | 124 | 125 | 126 | ``` 127 | ### CRAN 128 | 129 | ``` 130 | 131 | 132 | 133 | 134 | 135 | 136 | ``` 137 | -------------------------------------------------------------------------------- /revdep/problems.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | -------------------------------------------------------------------------------- /src/cpp11.cpp: -------------------------------------------------------------------------------- 1 | // Generated by cpp11: do not edit by hand 2 | // clang-format off 3 | 4 | 5 | #include "cpp11/declarations.hpp" 6 | #include 7 | 8 | // get_paths.cpp 9 | cpp11::list get_paths(cpp11::integers parent); 10 | extern "C" SEXP _tidygraph_get_paths(SEXP parent) { 11 | BEGIN_CPP11 12 | return cpp11::as_sexp(get_paths(cpp11::as_cpp>(parent))); 13 | END_CPP11 14 | } 15 | // get_paths.cpp 16 | cpp11::list collect_offspring(cpp11::list_of offspring, cpp11::integers order); 17 | extern "C" SEXP _tidygraph_collect_offspring(SEXP offspring, SEXP order) { 18 | BEGIN_CPP11 19 | return cpp11::as_sexp(collect_offspring(cpp11::as_cpp>>(offspring), cpp11::as_cpp>(order))); 20 | END_CPP11 21 | } 22 | 23 | extern "C" { 24 | static const R_CallMethodDef CallEntries[] = { 25 | {"_tidygraph_collect_offspring", (DL_FUNC) &_tidygraph_collect_offspring, 2}, 26 | {"_tidygraph_get_paths", (DL_FUNC) &_tidygraph_get_paths, 1}, 27 | {NULL, NULL, 0} 28 | }; 29 | } 30 | 31 | extern "C" attribute_visible void R_init_tidygraph(DllInfo* dll){ 32 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 33 | R_useDynamicSymbols(dll, FALSE); 34 | R_forceSymbols(dll, TRUE); 35 | } 36 | -------------------------------------------------------------------------------- /src/get_paths.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | [[cpp11::register]] 8 | cpp11::list get_paths(cpp11::integers parent) { 9 | cpp11::writable::list paths; 10 | int next = 0; 11 | for (int i = 0; i < parent.size(); ++i) { 12 | std::vector path; 13 | next = i; 14 | while(!cpp11::is_na(parent[next])) { 15 | next = parent[next] - 1; 16 | path.push_back(next + 1); 17 | } 18 | std::reverse(path.begin(), path.end()); 19 | paths.push_back(cpp11::integers(path)); 20 | } 21 | return paths; 22 | } 23 | 24 | [[cpp11::register]] 25 | cpp11::list collect_offspring(cpp11::list_of offspring, cpp11::integers order) { 26 | cpp11::writable::list offsprings; 27 | 28 | for (R_len_t i = 0; i < order.size(); ++i) { 29 | cpp11::writable::integers off(offspring[i].begin(), offspring[i].end()); 30 | offsprings.push_back(off); 31 | } 32 | 33 | for (R_len_t i = 0; i < order.size(); ++i) { 34 | int node = order[i] - 1; 35 | 36 | cpp11::writable::integers off = cpp11::as_cpp(offsprings[node]); 37 | for (R_len_t j = 0; j < off.size(); ++j) { 38 | int child = off[j] - 1; 39 | cpp11::integers child_off = cpp11::as_cpp(offsprings[child]); 40 | for (R_len_t k = 0; k < child_off.size(); ++k) { 41 | off.push_back(child_off[k]); 42 | } 43 | } 44 | } 45 | return offsprings; 46 | } 47 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(tidygraph) 11 | 12 | test_check("tidygraph") 13 | -------------------------------------------------------------------------------- /tests/testthat/helper-context.R: -------------------------------------------------------------------------------- 1 | test_empty_context <- function() { 2 | test_that("graph context is empty after test", { 3 | expect_length(environment(.graph_context$free)$private$context, 0) 4 | }) 5 | } 6 | -------------------------------------------------------------------------------- /tests/testthat/test-activate.R: -------------------------------------------------------------------------------- 1 | test_that("active<- and activate works for tbl_graph", { 2 | gr1 <- create_notable('bull') 3 | gr1 <- activate(gr1, edges) 4 | expect_equal(active(gr1), 'edges') 5 | gr1 <- activate(gr1, 'nodes') 6 | expect_equal(active(gr1), 'nodes') 7 | test <- 'nodes' 8 | expect_error(activate(gr1, test)) 9 | expect_equal(active(activate(gr1, !!test)), 'nodes') 10 | active(gr1) <- 'links' 11 | expect_equal(active(gr1), 'edges') 12 | active(gr1) <- 'vertices' 13 | expect_equal(active(gr1), 'nodes') 14 | expect_error(active(gr1) <- 'test') 15 | }) 16 | test_that('activate ungroups', { 17 | gr1 <- mutate(create_notable('bull'), group = sample(1:2, n(), TRUE)) 18 | gr1 <- group_by(gr1, group) 19 | expect_message(activate(gr1, edges)) 20 | expect_equal(class(activate(gr1, edges)), c('tbl_graph', 'igraph')) 21 | }) 22 | test_that('activate activates all morphed graphs', { 23 | gr1 <- gr1 <- mutate(create_notable('bull'), group = sample(1:2, n(), TRUE)) 24 | gr1 <- morph(gr1, to_split, group) 25 | gr1 <- activate(gr1, 'edges') 26 | expect_true(all(sapply(gr1, active) == 'edges')) 27 | }) 28 | 29 | test_empty_context() 30 | -------------------------------------------------------------------------------- /tests/testthat/test-arrange.R: -------------------------------------------------------------------------------- 1 | test_that("arrange works with nodes", { 2 | ord <- c(2, 4, 1, 3, 5) 3 | gr1 <- create_notable('bull') 4 | gr1 <- mutate(gr1, name = letters[1:5], order = ord) 5 | gr1 <- arrange(gr1, order) 6 | expect_equal(pull(gr1, name), letters[1:5][match(1:5, ord)]) 7 | }) 8 | test_that("arrange works with edges", { 9 | ord <- c(2, 4, 1, 3, 5) 10 | gr1 <- activate(create_notable('bull'), edges) 11 | gr1 <- mutate(gr1, name = letters[1:5], order = ord) 12 | gr1 <- arrange(gr1, order) 13 | expect_equal(pull(gr1, name), letters[1:5][match(1:5, ord)]) 14 | }) 15 | test_that('reserved words are protected', { 16 | ord <- c(2, 4, 1, 3, 5) 17 | gr1 <- create_notable('bull') 18 | gr1 <- mutate(gr1, .tbl_graph_index = letters[1:5], order = ord) 19 | expect_error(arrange(gr1, order)) 20 | }) 21 | 22 | test_empty_context() 23 | -------------------------------------------------------------------------------- /tests/testthat/test-bind.R: -------------------------------------------------------------------------------- 1 | test_that("bind_graphs works", { 2 | gr1 <- create_notable('bull') 3 | gr2 <- gr1 4 | gr1 <- mutate(gr1, group = 1) 5 | gr2 <- mutate(gr2, group = 2) 6 | gr <- bind_graphs(gr1, gr2) 7 | expect_equal(igraph::gorder(gr), 10) 8 | expect_equal(igraph::gsize(gr), 10) 9 | gr <- mutate(gr, comp = group_components()) 10 | tbl <- as_tibble(gr) 11 | expect_true(all(lengths(lapply(split(tbl$group, tbl$comp), unique)) == 1)) 12 | }) 13 | test_that('bind_nodes works', { 14 | gr1 <- tbl_graph(head(mtcars)) 15 | gr1 <- bind_nodes(gr1, tail(mtcars)) 16 | tbl <- dplyr::bind_rows(head(mtcars), tail(mtcars)) 17 | expect_equal(as_tibble(gr1), tbl, ignore_attr = TRUE) 18 | }) 19 | test_that('bind_edges works', { 20 | gr1 <- create_notable('bull') %>% 21 | activate(edges) %>% 22 | mutate(id = 1:5, filter = c(TRUE, TRUE, TRUE, FALSE, FALSE)) 23 | tbl <- as_tibble(gr1) 24 | gr2 <- filter(gr1, filter) %>% 25 | bind_edges(tbl[!tbl$filter, ]) 26 | expect_equal(as_tibble(gr2), tbl) 27 | expect_error(bind_edges(gr2, tbl[, -(1:2)])) 28 | tbl2 <- tbl 29 | tbl2$to <- tbl2$to + 10 30 | expect_error(bind_edges(gr2, tbl2)) 31 | }) 32 | 33 | test_empty_context() 34 | -------------------------------------------------------------------------------- /tests/testthat/test-centrality.R: -------------------------------------------------------------------------------- 1 | get_cent <- function(gr, fn) { 2 | gr %>% mutate(cent = fn) %>% pull(cent) 3 | } 4 | test_that("centrality returns numeric", { 5 | gr1 <- create_notable('diamond') 6 | expect_type(get_cent(gr1, centrality_alpha()), 'double') 7 | expect_type(get_cent(gr1, centrality_betweenness()), 'double') 8 | expect_type(get_cent(gr1, centrality_closeness()), 'double') 9 | expect_type(get_cent(gr1, centrality_degree()), 'double') 10 | expect_type(get_cent(gr1, centrality_pagerank()), 'double') 11 | expect_type(get_cent(gr1, centrality_power()), 'double') 12 | expect_type(get_cent(gr1, centrality_subgraph()), 'double') 13 | expect_type(get_cent(gr1, centrality_harmonic()), 'double') 14 | gr2 <- activate(gr1, 'edges') 15 | expect_type(get_cent(gr2, centrality_edge_betweenness()), 'double') 16 | 17 | skip_on_os('windows') 18 | expect_type(get_cent(gr1, centrality_authority()), 'double') 19 | expect_type(get_cent(gr1, centrality_eigen()), 'double') 20 | expect_type(get_cent(gr1, centrality_hub()), 'double') 21 | }) 22 | test_that("centrality returns correct length", { 23 | gr1 <- create_notable('diamond') 24 | expect_length(get_cent(gr1, centrality_alpha()), 4) 25 | expect_length(get_cent(gr1, centrality_betweenness()), 4) 26 | expect_length(get_cent(gr1, centrality_closeness()), 4) 27 | expect_length(get_cent(gr1, centrality_degree()), 4) 28 | expect_length(get_cent(gr1, centrality_pagerank()), 4) 29 | expect_length(get_cent(gr1, centrality_power()), 4) 30 | expect_length(get_cent(gr1, centrality_subgraph()), 4) 31 | expect_length(get_cent(gr1, centrality_harmonic()), 4) 32 | gr2 <- activate(gr1, 'edges') 33 | expect_length(get_cent(gr2, centrality_edge_betweenness()), 5) 34 | 35 | skip_on_os('windows') 36 | expect_length(get_cent(gr1, centrality_authority()), 4) 37 | expect_length(get_cent(gr1, centrality_eigen()), 4) 38 | expect_length(get_cent(gr1, centrality_hub()), 4) 39 | }) 40 | test_that("centrality returns correct length for focus", { 41 | gr1 <- create_notable('diamond') |> focus(dplyr::row_number() < 3) 42 | expect_length(get_cent(gr1, centrality_alpha()), 2) 43 | expect_length(get_cent(gr1, centrality_betweenness()), 2) 44 | expect_length(get_cent(gr1, centrality_closeness()), 2) 45 | expect_length(get_cent(gr1, centrality_degree()), 2) 46 | expect_length(get_cent(gr1, centrality_pagerank()), 2) 47 | expect_length(get_cent(gr1, centrality_power()), 2) 48 | expect_length(get_cent(gr1, centrality_subgraph()), 2) 49 | expect_length(get_cent(gr1, centrality_harmonic()), 2) 50 | gr2 <- activate(gr1, 'edges') |> focus(dplyr::row_number() < 3) 51 | expect_length(get_cent(gr2, centrality_edge_betweenness()), 2) 52 | 53 | skip_on_os('windows') 54 | expect_length(get_cent(gr1, centrality_authority()), 2) 55 | expect_length(get_cent(gr1, centrality_eigen()), 2) 56 | expect_length(get_cent(gr1, centrality_hub()), 2) 57 | }) 58 | test_that("centrality requires the right activation", { 59 | gr1 <- create_notable('diamond') 60 | expect_error(get_cent(gr1, centrality_edge_betweenness())) 61 | gr2 <- activate(gr1, 'edges') 62 | expect_error(get_cent(gr2, centrality_alpha())) 63 | expect_error(get_cent(gr2, centrality_betweenness())) 64 | expect_error(get_cent(gr2, centrality_closeness())) 65 | expect_error(get_cent(gr2, centrality_degree())) 66 | expect_error(get_cent(gr2, centrality_pagerank())) 67 | expect_error(get_cent(gr2, centrality_power())) 68 | expect_error(get_cent(gr2, centrality_subgraph())) 69 | expect_error(get_cent(gr2, centrality_harmonic())) 70 | 71 | skip_on_os('windows') 72 | expect_error(get_cent(gr2, centrality_authority())) 73 | expect_error(get_cent(gr2, centrality_eigen())) 74 | expect_error(get_cent(gr2, centrality_hub())) 75 | }) 76 | 77 | test_empty_context() 78 | -------------------------------------------------------------------------------- /tests/testthat/test-context.R: -------------------------------------------------------------------------------- 1 | test_that("graphs get added and stacked in the context", { 2 | context <- ContextBuilder$new() 3 | expect_false(context$alive()) 4 | gr1 <- create_ring(5) 5 | context$set(gr1) 6 | expect_true(context$alive()) 7 | expect_equal(context$active(), active(gr1)) 8 | expect_equal(context$graph(), gr1) 9 | expect_equal(context$nodes(), as_tibble(gr1, 'nodes')) 10 | expect_equal(context$edges(), as_tibble(gr1, 'edges')) 11 | gr2 <- create_lattice(c(4, 5)) %>% activate(edges) 12 | context$set(gr2) 13 | expect_equal(context$active(), active(gr2)) 14 | expect_equal(context$graph(), gr2) 15 | context$clear() 16 | expect_equal(context$graph(), gr1) 17 | context$clear() 18 | expect_false(context$alive()) 19 | }) 20 | 21 | test_empty_context() 22 | -------------------------------------------------------------------------------- /tests/testthat/test-distinct.R: -------------------------------------------------------------------------------- 1 | test_that("distinct works", { 2 | gr <- create_notable('bull') %>% 3 | mutate(id = c(1,1,1,2,2)) %>% 4 | activate(edges) %>% 5 | mutate(id = c(1,1,2,2,3)) 6 | gr1 <- gr %>% activate(nodes) %>% distinct() 7 | expect_equal(igraph::gorder(gr1), 2) 8 | gr2 <- gr %>% activate(edges) %>% distinct(id) 9 | expect_equal(igraph::gsize(gr2), 3) 10 | }) 11 | 12 | test_empty_context() 13 | -------------------------------------------------------------------------------- /tests/testthat/test-edge_types.R: -------------------------------------------------------------------------------- 1 | get_type <- function(gr, fn) { 2 | gr %>% mutate(type = fn) %>% pull(type) 3 | } 4 | test_that("edge types return logical", { 5 | gr <- create_notable('bull') %>% 6 | activate(edges) %>% 7 | bind_edges(tibble::tibble( 8 | to = c(1, 1, 3, 4), 9 | from = c(1, 3, 1, 2) 10 | )) 11 | expect_type(get_type(gr, edge_is_loop()), 'logical') 12 | expect_type(get_type(gr, edge_is_multiple()), 'logical') 13 | expect_type(get_type(gr, edge_is_mutual()), 'logical') 14 | expect_type(get_type(gr, edge_is_bridge()), 'logical') 15 | expect_type(get_type(gr, edge_is_feedback_arc()), 'logical') 16 | }) 17 | test_that("edge types return correct length", { 18 | gr <- create_notable('bull') %>% 19 | activate(edges) %>% 20 | bind_edges(tibble::tibble( 21 | to = c(1, 1, 3, 4), 22 | from = c(1, 3, 1, 2) 23 | )) 24 | expect_length(get_type(gr, edge_is_loop()), igraph::gsize(gr)) 25 | expect_length(get_type(gr, edge_is_multiple()), igraph::gsize(gr)) 26 | expect_length(get_type(gr, edge_is_mutual()), igraph::gsize(gr)) 27 | expect_length(get_type(gr, edge_is_bridge()), igraph::gsize(gr)) 28 | expect_length(get_type(gr, edge_is_feedback_arc()), igraph::gsize(gr)) 29 | }) 30 | 31 | test_that("edge types return correct length for focus", { 32 | gr <- create_notable('bull') %>% 33 | activate(edges) %>% 34 | bind_edges(tibble::tibble( 35 | to = c(1, 1, 3, 4), 36 | from = c(1, 3, 1, 2) 37 | )) |> 38 | focus(dplyr::row_number() < 3) 39 | expect_length(get_type(gr, edge_is_loop()), 2) 40 | expect_length(get_type(gr, edge_is_multiple()), 2) 41 | expect_length(get_type(gr, edge_is_mutual()), 2) 42 | expect_length(get_type(gr, edge_is_bridge()), 2) 43 | expect_length(get_type(gr, edge_is_feedback_arc()), 2) 44 | }) 45 | 46 | test_that("edge types require edge active", { 47 | gr <- create_notable('bull') %>% 48 | activate(nodes) %>% 49 | bind_edges(tibble::tibble( 50 | to = c(1, 1, 3, 4), 51 | from = c(1, 3, 1, 2) 52 | )) 53 | expect_error(get_type(gr, edge_is_loop())) 54 | expect_error(get_type(gr, edge_is_multiple())) 55 | expect_error(get_type(gr, edge_is_mutual())) 56 | expect_error(get_type(gr, edge_is_bridge())) 57 | expect_error(get_type(gr, edge_is_feedback_arc())) 58 | }) 59 | 60 | test_empty_context() 61 | -------------------------------------------------------------------------------- /tests/testthat/test-filter.R: -------------------------------------------------------------------------------- 1 | test_that("filter works", { 2 | id_nodes <- create_notable('bull') %>% 3 | mutate(id = seq_len(n())) %>% 4 | filter(id < 4) %>% 5 | pull(id) 6 | expect_equal(id_nodes, 1:3) 7 | id_edges <- create_notable('bull') %>% 8 | activate(edges) %>% 9 | mutate(id = seq_len(n())) %>% 10 | filter(id < 4) %>% 11 | pull(id) 12 | expect_equal(id_edges, 1:3) 13 | }) 14 | 15 | test_empty_context() 16 | -------------------------------------------------------------------------------- /tests/testthat/test-focus.R: -------------------------------------------------------------------------------- 1 | test_that("focusing and unfocusing behaves", { 2 | gr <- create_notable('meredith') 3 | expect_false(is.focused_tbl_graph(focus(gr, TRUE))) 4 | expect_false(is.focused_tbl_graph(focus(gr, FALSE))) 5 | 6 | gr_focus <- focus(gr, dplyr::row_number() %in% 6:10) 7 | expect_s3_class(gr_focus, 'focused_tbl_graph') 8 | 9 | expect_equal(focus_ind(gr_focus), 6:10) 10 | expect_equal(focus_ind(gr), seq_len(70)) 11 | 12 | expect_false(is.focused_tbl_graph(activate(gr_focus, edges))) 13 | expect_false(is.focused_tbl_graph(group_by(gr_focus, dplyr::row_number() %% 2 == 0))) 14 | expect_false(is.focused_tbl_graph(convert(gr_focus, to_complement))) 15 | expect_false(is.focused_tbl_graph(unfocus(gr_focus))) 16 | 17 | gr_morph_focus <- morph(gr, to_complement) |> focus(dplyr::row_number() == 1) 18 | expect_s3_class((crystallise(gr_morph_focus) |> pull(graph))[[1]], 'focused_tbl_graph') 19 | expect_false(is.focused_tbl_graph((crystallise(unfocus(gr_morph_focus)) |> pull(graph))[[1]])) 20 | expect_false(is.focused_tbl_graph(unmorph(gr_morph_focus))) 21 | 22 | gr_group_focus <- group_by(gr, dplyr::row_number() %% 2 == 0) |> 23 | focus(dplyr::row_number() %in% 1:6) 24 | expect_s3_class(gr_group_focus, 'focused_tbl_graph') 25 | expect_false(is.focused_tbl_graph(ungroup(gr_group_focus))) 26 | 27 | expect_false(is.focused_tbl_graph(arrange(gr_focus, rev(dplyr::row_number())))) 28 | expect_false(is.focused_tbl_graph(bind_nodes(gr_focus, mtcars))) 29 | expect_false(is.focused_tbl_graph(bind_edges(gr_focus, data.frame(from = 1:3, to = 4:6)))) 30 | expect_false(is.focused_tbl_graph(bind_graphs(gr_focus, create_notable('bull')))) 31 | expect_false(is.focused_tbl_graph(distinct(gr_focus, dplyr::row_number()))) 32 | expect_false(is.focused_tbl_graph(filter(gr_focus, dplyr::row_number() < 10))) 33 | expect_false(is.focused_tbl_graph(slice(gr_focus, 1:4))) 34 | 35 | join_tbl <- data.frame(from = 1:70, to = 70:1, info = as.character(1:70)) 36 | gr_focus <- activate(gr_focus, edges) |> focus(dplyr::row_number() %in% 6:10) 37 | expect_s3_class(left_join(gr_focus, join_tbl), 'focused_tbl_graph') 38 | expect_false(is.focused_tbl_graph(right_join(gr_focus, join_tbl))) 39 | expect_false(is.focused_tbl_graph(full_join(gr_focus, join_tbl))) 40 | expect_false(is.focused_tbl_graph(inner_join(gr_focus, join_tbl))) 41 | expect_false(is.focused_tbl_graph(semi_join(gr_focus, join_tbl))) 42 | expect_false(is.focused_tbl_graph(anti_join(gr_focus, join_tbl))) 43 | }) 44 | 45 | test_that("modifying a focused graph only affects the focus", { 46 | gr <- create_notable('meredith') |> 47 | mutate(col1 = 1) |> 48 | focus(dplyr::row_number() %in% 6:10) |> 49 | mutate(col1 = 2, col2 = 2) |> 50 | unfocus() 51 | 52 | expect_equal(pull(gr, col1), c(rep(1, 5), rep(2, 5), rep(1, 60))) 53 | expect_equal(pull(gr, col2), c(rep(NA, 5), rep(2, 5), rep(NA, 60))) 54 | }) 55 | 56 | test_empty_context() 57 | -------------------------------------------------------------------------------- /tests/testthat/test-graph_attributes.R: -------------------------------------------------------------------------------- 1 | test_that("attributes are applied correctly", { 2 | gr1 <- create_notable('bull') 3 | gr2 <- create_notable('diamond') 4 | igraph::graph_attr(gr1, 'igraph_attr') <- 'test' 5 | attr(gr1, 'standard_attr') <- 'test2' 6 | gr1 <- as_tbl_graph(gr1) 7 | gr2 <- gr2 %gr_attr% gr1 8 | expect_equal(attributes(gr1), attributes(gr2)) 9 | expect_equal(graph_attr(gr1), graph_attr(gr2)) 10 | }) 11 | 12 | test_empty_context() 13 | -------------------------------------------------------------------------------- /tests/testthat/test-graph_measures.R: -------------------------------------------------------------------------------- 1 | test_that("graph measures returns scalars", { 2 | gr <- create_notable('housex') %>% 3 | mutate(type = c(1, 1, 1, 2, 2)) 4 | .graph_context$set(gr) 5 | expect_length(graph_adhesion(), 1) 6 | expect_length(graph_assortativity(type), 1) 7 | expect_length(graph_automorphisms(), 1) 8 | expect_length(graph_clique_count(), 1) 9 | expect_length(graph_clique_num(), 1) 10 | expect_length(graph_component_count(), 1) 11 | expect_length(graph_diameter(), 1) 12 | expect_length(graph_girth(), 1) 13 | expect_length(graph_mean_dist(), 1) 14 | expect_length(graph_min_cut(), 1) 15 | expect_length(graph_motif_count(), 1) 16 | expect_length(graph_order(), 1) 17 | expect_length(graph_radius(), 1) 18 | expect_length(graph_reciprocity(), 1) 19 | expect_length(graph_size(), 1) 20 | expect_length(graph_modularity(type), 1) 21 | expect_length(graph_efficiency(), 1) 22 | .graph_context$clear() 23 | gr <- create_ring(5, TRUE) %>% 24 | mutate(type = c(1, 1, 1, 2, 2)) 25 | .graph_context$set(gr) 26 | expect_length(graph_asym_count(), 1) 27 | expect_length(graph_mutual_count(), 1) 28 | expect_length(graph_unconn_count(), 1) 29 | .graph_context$clear() 30 | }) 31 | 32 | test_empty_context() 33 | -------------------------------------------------------------------------------- /tests/testthat/test-group.R: -------------------------------------------------------------------------------- 1 | get_group <- function(gr, fn) { 2 | gr %>% mutate(group = fn) %>% pull(group) 3 | } 4 | 5 | get_number_of_groups <- function(graph, group_clustering_function) { 6 | graph %>% 7 | mutate(groups = group_clustering_function) %>% 8 | dplyr::as_tibble(what = 'vertices') %>% 9 | distinct() %>% dplyr::count() %>% dplyr::pull(1) 10 | } 11 | 12 | test_that("grouping returns integer vector", { 13 | gr <- create_notable('zachary') 14 | expect_type(get_group(gr, group_components()), 'integer') 15 | expect_type(get_group(gr, group_edge_betweenness()), 'integer') 16 | expect_type(get_group(gr, group_fast_greedy()), 'integer') 17 | expect_type(get_group(gr, group_infomap()), 'integer') 18 | expect_type(get_group(gr, group_label_prop()), 'integer') 19 | expect_type(get_group(gr, group_louvain()), 'integer') 20 | expect_type(get_group(gr, group_leiden()), 'integer') 21 | #expect_type(get_group(gr, group_optimal()), 'integer') 22 | expect_type(get_group(gr, group_spinglass()), 'integer') 23 | expect_type(get_group(gr, group_walktrap()), 'integer') 24 | expect_type(get_group(gr, group_fluid()), 'integer') 25 | expect_type(get_group(gr, group_color()), 'integer') 26 | gr1 <- activate(gr, edges) 27 | expect_type(get_group(gr1, group_biconnected_component()), 'integer') 28 | 29 | skip_on_os('windows') 30 | expect_type(get_group(gr, group_leading_eigen()), 'integer') 31 | }) 32 | test_that("grouping returns integer of correct length", { 33 | gr <- create_notable('zachary') 34 | expect_length(get_group(gr, group_components()), igraph::gorder(gr)) 35 | expect_length(get_group(gr, group_edge_betweenness()), igraph::gorder(gr)) 36 | expect_length(get_group(gr, group_fast_greedy()), igraph::gorder(gr)) 37 | expect_length(get_group(gr, group_infomap()), igraph::gorder(gr)) 38 | expect_length(get_group(gr, group_label_prop()), igraph::gorder(gr)) 39 | expect_length(get_group(gr, group_louvain()), igraph::gorder(gr)) 40 | expect_length(get_group(gr, group_leiden()), igraph::gorder(gr)) 41 | #expect_length(get_group(gr, group_optimal()), igraph::gorder(gr)) 42 | expect_length(get_group(gr, group_spinglass()), igraph::gorder(gr)) 43 | expect_length(get_group(gr, group_walktrap()), igraph::gorder(gr)) 44 | expect_length(get_group(gr, group_fluid()), igraph::gorder(gr)) 45 | expect_length(get_group(gr, group_color()), igraph::gorder(gr)) 46 | gr1 <- activate(gr, edges) 47 | expect_length(get_group(gr1, group_biconnected_component()), igraph::gsize(gr1)) 48 | 49 | skip_on_os('windows') 50 | expect_length(get_group(gr, group_leading_eigen()), igraph::gorder(gr)) 51 | }) 52 | test_that("grouping requires correct activation", { 53 | gr <- create_notable('zachary') 54 | expect_error(get_group(gr, group_biconnected_component())) 55 | gr1 <- activate(gr, edges) 56 | expect_error(get_group(gr1, group_components())) 57 | expect_error(get_group(gr1, group_edge_betweenness())) 58 | expect_error(get_group(gr1, group_fast_greedy())) 59 | expect_error(get_group(gr1, group_infomap())) 60 | expect_error(get_group(gr1, group_label_prop())) 61 | expect_error(get_group(gr1, group_louvain())) 62 | expect_error(get_group(gr1, group_leiden())) 63 | #expect_error(get_group(gr1, group_optimal())) 64 | expect_error(get_group(gr1, group_spinglass())) 65 | expect_error(get_group(gr1, group_walktrap())) 66 | expect_error(get_group(gr1, group_fluid())) 67 | expect_error(get_group(gr1, group_color())) 68 | 69 | skip_on_os('windows') 70 | expect_error(get_group(gr1, group_leading_eigen())) 71 | }) 72 | 73 | test_that("grouping with fixed number of groups", { 74 | gr <- create_notable('zachary') 75 | expect_equal( 76 | get_number_of_groups(gr, group_edge_betweenness(n_groups = 4)), 4 77 | ) 78 | expect_equal( 79 | get_number_of_groups(gr, group_fast_greedy(n_groups = 4)), 4 80 | ) 81 | expect_equal( 82 | get_number_of_groups(gr, group_leading_eigen(n_groups = 32)), 32 83 | ) 84 | expect_equal( 85 | get_number_of_groups(gr, group_walktrap(n_groups = 7)), 7 86 | ) 87 | expect_equal( 88 | get_number_of_groups(gr, group_fluid(n_groups = 7)), 7 89 | ) 90 | }) 91 | 92 | test_empty_context() 93 | -------------------------------------------------------------------------------- /tests/testthat/test-group_by.R: -------------------------------------------------------------------------------- 1 | test_that("nodes and edges are grouped", { 2 | gr <- create_notable('bull') %>% 3 | mutate(group = c(1,1,1,2,2)) %>% 4 | group_by(group) 5 | expect_s3_class(as_tibble(gr), 'grouped_df') 6 | expect_false(dplyr::is_grouped_df(as_tibble(gr, 'edges'))) 7 | gr <- gr %>% 8 | activate(edges) %>% 9 | mutate(group = c(1,1,1,2,2)) %>% 10 | group_by(group) 11 | expect_s3_class(as_tibble(gr), 'grouped_df') 12 | expect_false(dplyr::is_grouped_df(as_tibble(gr, 'nodes'))) 13 | }) 14 | 15 | test_empty_context() 16 | -------------------------------------------------------------------------------- /tests/testthat/test-iterate.R: -------------------------------------------------------------------------------- 1 | test_that("iterate_n works as expected", { 2 | gr <- create_notable('zachary') |> 3 | activate(edges) |> 4 | mutate(count = 0) |> 5 | activate(nodes) |> 6 | iterate_n(10, ~mutate(activate(., edges), count = count + 1)) 7 | 8 | expect_equal(active(gr), 'nodes') 9 | expect_true(all(pull(activate(gr, edges), count) == 10)) 10 | }) 11 | 12 | test_that("iterate_while works as expected", { 13 | gr <- create_notable('zachary') |> 14 | activate(edges) |> 15 | mutate(count = 0) |> 16 | activate(nodes) |> 17 | iterate_while(.E()$count[1] < 10, ~mutate(activate(., edges), count = count + 1)) 18 | 19 | expect_equal(active(gr), 'nodes') 20 | expect_true(all(pull(activate(gr, edges), count) == 10)) 21 | 22 | gr <- create_notable('zachary') |> 23 | activate(edges) |> 24 | mutate(count = 0) |> 25 | activate(nodes) |> 26 | iterate_while(TRUE, ~mutate(activate(., edges), count = count + 1), max_n = 10) 27 | 28 | expect_equal(active(gr), 'nodes') 29 | expect_true(all(pull(activate(gr, edges), count) == 10)) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/testthat/test-join.R: -------------------------------------------------------------------------------- 1 | test_that("TODO", { 2 | expect_equal(2 * 2, 4) 3 | }) 4 | -------------------------------------------------------------------------------- /tests/testthat/test-local.R: -------------------------------------------------------------------------------- 1 | get_loc <- function(gr, fn) { 2 | gr %>% mutate(loc = fn) %>% pull(loc) 3 | } 4 | test_that("local returns correct type", { 5 | gr <- create_notable('bull') 6 | expect_type(get_loc(gr, local_ave_degree()), 'double') 7 | expect_type(get_loc(gr, local_members()), 'list') 8 | expect_type(get_loc(gr, local_size()), 'double') 9 | expect_type(get_loc(gr, local_transitivity()), 'double') 10 | expect_type(get_loc(gr, local_triangles()), 'double') 11 | }) 12 | test_that("local returns correct length", { 13 | gr <- create_notable('bull') 14 | expect_length(get_loc(gr, local_ave_degree()), igraph::gorder(gr)) 15 | expect_length(get_loc(gr, local_members()), igraph::gorder(gr)) 16 | expect_length(get_loc(gr, local_size()), igraph::gorder(gr)) 17 | expect_length(get_loc(gr, local_transitivity()), igraph::gorder(gr)) 18 | expect_length(get_loc(gr, local_triangles()), igraph::gorder(gr)) 19 | }) 20 | test_that("local returns correct length for focus", { 21 | gr <- create_notable('bull') |> focus(dplyr::row_number() < 3) 22 | expect_length(get_loc(gr, local_ave_degree()), 2) 23 | expect_length(get_loc(gr, local_members()), 2) 24 | expect_length(get_loc(gr, local_size()), 2) 25 | expect_length(get_loc(gr, local_transitivity()), 2) 26 | expect_length(get_loc(gr, local_triangles()), 2) 27 | }) 28 | test_that("local requires active nodes", { 29 | gr <- create_notable('bull') %>% activate(edges) 30 | expect_error(get_loc(gr, local_ave_degree())) 31 | expect_error(get_loc(gr, local_members())) 32 | expect_error(get_loc(gr, local_size())) 33 | expect_error(get_loc(gr, local_transitivity())) 34 | expect_error(get_loc(gr, local_triangles())) 35 | }) 36 | 37 | test_empty_context() 38 | -------------------------------------------------------------------------------- /tests/testthat/test-map.R: -------------------------------------------------------------------------------- 1 | test_that("TODO", { 2 | expect_equal(2 * 2, 4) 3 | }) 4 | -------------------------------------------------------------------------------- /tests/testthat/test-mutate.R: -------------------------------------------------------------------------------- 1 | test_that("mutate works with nodes", { 2 | mut <- create_notable('bull') %>% 3 | mutate(letters = letters[1:5]) %>% 4 | pull(letters) 5 | expect_equal(mut, letters[1:5]) 6 | }) 7 | test_that("mutate works with edges", { 8 | mut <- create_notable('bull') %>% 9 | activate(edges) %>% 10 | mutate(letters = letters[1:5]) %>% 11 | pull(letters) 12 | expect_equal(mut, letters[1:5]) 13 | }) 14 | 15 | test_empty_context() 16 | -------------------------------------------------------------------------------- /tests/testthat/test-node_measures.R: -------------------------------------------------------------------------------- 1 | get_val <- function(gr, fn) { 2 | gr %>% mutate(val = fn) %>% pull(val) 3 | } 4 | test_that("Node measures return corrent type", { 5 | tree <- create_tree(10, 2) %>% 6 | activate(edges) %>% 7 | mutate(w = seq_len(n())) %>% 8 | activate(nodes) 9 | gr <- create_notable('meredith') %>% 10 | activate(edges) %>% 11 | mutate(w = seq_len(n())) %>% 12 | activate(nodes) 13 | gr_u <- convert(gr, to_undirected) 14 | expect_type(get_val(gr, node_constraint()), 'double') 15 | expect_type(get_val(gr, node_coreness()), 'double') 16 | expect_type(get_val(gr_u, node_diversity(w)), 'double') 17 | expect_type(get_val(gr_u, node_efficiency()), 'double') 18 | expect_type(get_val(tree, node_dominator(node_is_root())), 'double') 19 | expect_type(get_val(gr, node_eccentricity()), 'double') 20 | expect_type(get_val(tree, node_topo_order()), 'integer') 21 | }) 22 | test_that("Node measures return correct length", { 23 | tree <- create_tree(10, 2) %>% 24 | activate(edges) %>% 25 | mutate(w = seq_len(n())) %>% 26 | activate(nodes) 27 | gr <- create_notable('meredith') %>% 28 | activate(edges) %>% 29 | mutate(w = seq_len(n())) %>% 30 | activate(nodes) 31 | gr_u <- convert(gr, to_undirected) 32 | expect_length(get_val(gr, node_constraint()), igraph::gorder(gr)) 33 | expect_length(get_val(gr, node_coreness()), igraph::gorder(gr)) 34 | expect_length(get_val(gr_u, node_diversity(w)), igraph::gorder(gr)) 35 | expect_length(get_val(gr_u, node_efficiency()), igraph::gorder(gr)) 36 | expect_length(get_val(tree, node_dominator(node_is_root())), igraph::gorder(tree)) 37 | expect_length(get_val(gr, node_eccentricity()), igraph::gorder(gr)) 38 | expect_length(get_val(tree, node_topo_order()), igraph::gorder(tree)) 39 | }) 40 | test_that("Node measures return correct length for focus", { 41 | tree <- create_tree(10, 2) %>% 42 | activate(edges) %>% 43 | mutate(w = seq_len(n())) %>% 44 | activate(nodes) |> 45 | focus(dplyr::row_number() < 3) 46 | gr <- create_notable('meredith') %>% 47 | activate(edges) %>% 48 | mutate(w = seq_len(n())) %>% 49 | activate(nodes) |> 50 | focus(dplyr::row_number() < 3) 51 | gr_u <- convert(gr, to_undirected) |> 52 | focus(dplyr::row_number() < 3) 53 | expect_length(get_val(gr, node_constraint()), 2) 54 | expect_length(get_val(gr, node_coreness()), 2) 55 | expect_length(get_val(gr_u, node_diversity(w)), 2) 56 | expect_length(get_val(gr_u, node_efficiency()), 2) 57 | expect_length(get_val(tree, node_dominator(node_is_root())), 2) 58 | expect_length(get_val(gr, node_eccentricity()), 2) 59 | expect_length(get_val(tree, node_topo_order()), 2) 60 | }) 61 | test_that("Node measures requires active nodes", { 62 | tree <- create_tree(10, 2) %>% 63 | activate(edges) %>% 64 | mutate(w = seq_len(n())) 65 | gr <- create_notable('meredith') %>% 66 | activate(edges) %>% 67 | mutate(w = seq_len(n())) 68 | gr_u <- convert(gr, to_undirected) 69 | expect_error(get_val(gr, node_constraint())) 70 | expect_error(get_val(gr, node_coreness())) 71 | expect_error(get_val(gr_u, node_diversity(w))) 72 | expect_error(get_val(gr_u, node_efficiency())) 73 | expect_error(get_val(tree, node_dominator(node_is_root()))) 74 | expect_error(get_val(gr, node_eccentricity())) 75 | expect_error(get_val(tree, node_topo_order())) 76 | }) 77 | 78 | test_empty_context() 79 | -------------------------------------------------------------------------------- /tests/testthat/test-node_types.R: -------------------------------------------------------------------------------- 1 | get_type <- function(gr, fn) { 2 | gr %>% mutate(type = fn) %>% pull(type) 3 | } 4 | test_that("node types return logical", { 5 | gr <- create_tree(10, 2) 6 | expect_type(get_type(gr, node_is_center()), 'logical') 7 | expect_type(get_type(gr, node_is_cut()), 'logical') 8 | expect_type(get_type(gr, node_is_isolated()), 'logical') 9 | expect_type(get_type(gr, node_is_leaf()), 'logical') 10 | expect_type(get_type(gr, node_is_root()), 'logical') 11 | expect_type(get_type(gr, node_is_simplical()), 'logical') 12 | expect_type(get_type(gr, node_is_sink()), 'logical') 13 | expect_type(get_type(gr, node_is_source()), 'logical') 14 | expect_type(get_type(gr, node_is_universal()), 'logical') 15 | expect_type(get_type(gr, node_is_connected(1:4)), 'logical') 16 | }) 17 | test_that("node types return vector of correct length", { 18 | gr <- create_tree(10, 2) 19 | expect_length(get_type(gr, node_is_center()), igraph::gorder(gr)) 20 | expect_length(get_type(gr, node_is_cut()), igraph::gorder(gr)) 21 | expect_length(get_type(gr, node_is_isolated()), igraph::gorder(gr)) 22 | expect_length(get_type(gr, node_is_leaf()), igraph::gorder(gr)) 23 | expect_length(get_type(gr, node_is_root()), igraph::gorder(gr)) 24 | expect_length(get_type(gr, node_is_simplical()), igraph::gorder(gr)) 25 | expect_length(get_type(gr, node_is_sink()), igraph::gorder(gr)) 26 | expect_length(get_type(gr, node_is_source()), igraph::gorder(gr)) 27 | expect_length(get_type(gr, node_is_universal()), igraph::gorder(gr)) 28 | expect_length(get_type(gr, node_is_connected(1:4)), igraph::gorder(gr)) 29 | }) 30 | test_that("node types return vector of correct length for focus", { 31 | gr <- create_tree(10, 2) |> 32 | focus(dplyr::row_number() < 3) 33 | expect_length(get_type(gr, node_is_center()), 2) 34 | expect_length(get_type(gr, node_is_cut()), 2) 35 | expect_length(get_type(gr, node_is_isolated()), 2) 36 | expect_length(get_type(gr, node_is_leaf()), 2) 37 | expect_length(get_type(gr, node_is_root()), 2) 38 | expect_length(get_type(gr, node_is_simplical()), 2) 39 | expect_length(get_type(gr, node_is_sink()), 2) 40 | expect_length(get_type(gr, node_is_source()), 2) 41 | expect_length(get_type(gr, node_is_universal()), 2) 42 | expect_length(get_type(gr, node_is_connected(1:4)), 2) 43 | }) 44 | test_that("node types require active nodes", { 45 | gr <- create_tree(10, 2) %>% activate(edges) 46 | expect_error(get_type(gr, node_is_center())) 47 | expect_error(get_type(gr, node_is_cut())) 48 | expect_error(get_type(gr, node_is_isolated())) 49 | expect_error(get_type(gr, node_is_leaf())) 50 | expect_error(get_type(gr, node_is_root())) 51 | expect_error(get_type(gr, node_is_simplical())) 52 | expect_error(get_type(gr, node_is_sink())) 53 | expect_error(get_type(gr, node_is_source())) 54 | expect_error(get_type(gr, node_is_universal())) 55 | expect_error(get_type(gr, node_is_connected(1:4))) 56 | }) 57 | 58 | test_empty_context() 59 | -------------------------------------------------------------------------------- /tests/testthat/test-pair_measures.R: -------------------------------------------------------------------------------- 1 | get_val <- function(gr, fn) { 2 | gr %>% mutate(val = fn) %>% pull(val) 3 | } 4 | test_that("pair measures return correct type", { 5 | gr <- create_ring(5, directed = TRUE) 6 | expect_type(get_val(gr, node_adhesion_from(1)), 'double') 7 | expect_type(get_val(gr, node_adhesion_to(1)), 'double') 8 | expect_type(get_val(gr, node_cocitation_with(1)), 'double') 9 | expect_type(get_val(gr, node_cohesion_from(1)), 'double') 10 | expect_type(get_val(gr, node_cohesion_to(1)), 'double') 11 | expect_type(get_val(gr, node_distance_from(1)), 'double') 12 | expect_type(get_val(gr, node_distance_to(1)), 'double') 13 | expect_type(get_val(gr, node_max_flow_from(1)), 'double') 14 | expect_type(get_val(gr, node_max_flow_to(1)), 'double') 15 | expect_type(get_val(gr, node_similarity_with(1)), 'double') 16 | }) 17 | test_that("pair measures return correct length", { 18 | gr <- create_ring(5, directed = TRUE) 19 | expect_length(get_val(gr, node_adhesion_from(1)), igraph::gorder(gr)) 20 | expect_length(get_val(gr, node_adhesion_to(1)), igraph::gorder(gr)) 21 | expect_length(get_val(gr, node_cocitation_with(1)), igraph::gorder(gr)) 22 | expect_length(get_val(gr, node_cohesion_from(1)), igraph::gorder(gr)) 23 | expect_length(get_val(gr, node_cohesion_to(1)), igraph::gorder(gr)) 24 | expect_length(get_val(gr, node_distance_from(1)), igraph::gorder(gr)) 25 | expect_length(get_val(gr, node_distance_to(1)), igraph::gorder(gr)) 26 | expect_length(get_val(gr, node_max_flow_from(1)), igraph::gorder(gr)) 27 | expect_length(get_val(gr, node_max_flow_to(1)), igraph::gorder(gr)) 28 | expect_length(get_val(gr, node_similarity_with(1)), igraph::gorder(gr)) 29 | }) 30 | test_that("pair measures return correct length for focus", { 31 | gr <- create_ring(5, directed = TRUE) |> 32 | focus(dplyr::row_number() < 3) 33 | expect_length(get_val(gr, node_adhesion_from(1)), 2) 34 | expect_length(get_val(gr, node_adhesion_to(1)), 2) 35 | expect_length(get_val(gr, node_cocitation_with(1)), 2) 36 | expect_length(get_val(gr, node_cohesion_from(1)), 2) 37 | expect_length(get_val(gr, node_cohesion_to(1)), 2) 38 | expect_length(get_val(gr, node_distance_from(1)), 2) 39 | expect_length(get_val(gr, node_distance_to(1)), 2) 40 | expect_length(get_val(gr, node_max_flow_from(1)), 2) 41 | expect_length(get_val(gr, node_max_flow_to(1)), 2) 42 | expect_length(get_val(gr, node_similarity_with(1)), 2) 43 | }) 44 | test_that("pair measures requires active nodes", { 45 | gr <- create_ring(5, directed = TRUE) %>% activate(edges) 46 | expect_error(get_val(gr, node_adhesion_from(1))) 47 | expect_error(get_val(gr, node_adhesion_to(1))) 48 | expect_error(get_val(gr, node_cocitation_with(1))) 49 | expect_error(get_val(gr, node_cohesion_from(1))) 50 | expect_error(get_val(gr, node_cohesion_to(1))) 51 | expect_error(get_val(gr, node_distance_from(1))) 52 | expect_error(get_val(gr, node_distance_to(1))) 53 | expect_error(get_val(gr, node_max_flow_from(1))) 54 | expect_error(get_val(gr, node_max_flow_to(1))) 55 | expect_error(get_val(gr, node_similarity_with(1))) 56 | }) 57 | 58 | test_empty_context() 59 | -------------------------------------------------------------------------------- /tests/testthat/test-random-walk.R: -------------------------------------------------------------------------------- 1 | test_that("random_walk_rank returns correct data", { 2 | set.seed(1) 3 | node_walk <- create_notable('zachary') |> 4 | mutate(walk_rank = random_walk_rank(29, 5)) |> 5 | pull(walk_rank) 6 | 7 | edge_walk <- create_notable('zachary') |> 8 | activate(edges) |> 9 | mutate(walk_rank = random_walk_rank(30, 5)) |> 10 | pull(walk_rank) 11 | 12 | expect_length(node_walk, 34) 13 | expect_length(edge_walk, 78) 14 | expect_type(node_walk, 'list') 15 | expect_type(edge_walk, 'list') 16 | expect_equal(node_walk[[5]], c(1, 5, 7)) 17 | expect_equal(node_walk[[3]], integer()) 18 | expect_equal(edge_walk[[1]], integer()) 19 | skip_on_cran() 20 | expect_equal(edge_walk[[4]], c(1, 18)) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/testthat/test-search.R: -------------------------------------------------------------------------------- 1 | get_val <- function(gr, fn) { 2 | gr %>% mutate(val = fn) %>% pull(val) 3 | } 4 | test_that("search returns correct type", { 5 | gr <- create_tree(10, 2) 6 | expect_type(get_val(gr, bfs_after()), 'integer') 7 | expect_type(get_val(gr, bfs_before()), 'integer') 8 | expect_type(get_val(gr, bfs_dist()), 'integer') 9 | expect_type(get_val(gr, bfs_parent()), 'integer') 10 | expect_type(get_val(gr, bfs_rank()), 'integer') 11 | 12 | expect_type(get_val(gr, dfs_dist()), 'integer') 13 | expect_type(get_val(gr, dfs_parent()), 'integer') 14 | expect_type(get_val(gr, dfs_rank()), 'integer') 15 | expect_type(get_val(gr, dfs_rank_out()), 'integer') 16 | }) 17 | test_that("search returns correct length", { 18 | gr <- create_tree(10, 2) 19 | expect_length(get_val(gr, bfs_after()), igraph::gorder(gr)) 20 | expect_length(get_val(gr, bfs_before()), igraph::gorder(gr)) 21 | expect_length(get_val(gr, bfs_dist()), igraph::gorder(gr)) 22 | expect_length(get_val(gr, bfs_parent()), igraph::gorder(gr)) 23 | expect_length(get_val(gr, bfs_rank()), igraph::gorder(gr)) 24 | 25 | expect_length(get_val(gr, dfs_dist()), igraph::gorder(gr)) 26 | expect_length(get_val(gr, dfs_parent()), igraph::gorder(gr)) 27 | expect_length(get_val(gr, dfs_rank()), igraph::gorder(gr)) 28 | expect_length(get_val(gr, dfs_rank_out()), igraph::gorder(gr)) 29 | }) 30 | test_that("search returns correct length", { 31 | gr <- create_tree(10, 2) |> 32 | focus(dplyr::row_number() < 3) 33 | expect_length(get_val(gr, bfs_after()), 2) 34 | expect_length(get_val(gr, bfs_before()), 2) 35 | expect_length(get_val(gr, bfs_dist()), 2) 36 | expect_length(get_val(gr, bfs_parent()), 2) 37 | expect_length(get_val(gr, bfs_rank()), 2) 38 | 39 | expect_length(get_val(gr, dfs_dist()), 2) 40 | expect_length(get_val(gr, dfs_parent()), 2) 41 | expect_length(get_val(gr, dfs_rank()), 2) 42 | expect_length(get_val(gr, dfs_rank_out()), 2) 43 | }) 44 | test_that("search requires active nodes", { 45 | gr <- create_tree(10, 2) %>% activate(edges) 46 | expect_error(get_val(gr, bfs_after())) 47 | expect_error(get_val(gr, bfs_before())) 48 | expect_error(get_val(gr, bfs_dist())) 49 | expect_error(get_val(gr, bfs_parent())) 50 | expect_error(get_val(gr, bfs_rank())) 51 | 52 | expect_error(get_val(gr, dfs_dist())) 53 | expect_error(get_val(gr, dfs_parent())) 54 | expect_error(get_val(gr, dfs_rank())) 55 | expect_error(get_val(gr, dfs_rank_out())) 56 | }) 57 | 58 | test_empty_context() 59 | -------------------------------------------------------------------------------- /tests/testthat/test-slice.R: -------------------------------------------------------------------------------- 1 | n_val <- sample(70) 2 | e_val <- sample(140) 3 | 4 | gr <- create_notable("meredith") %>% 5 | mutate(id = seq_len(70), val = n_val) %>% 6 | activate(edges) %>% 7 | mutate(id = seq_len(140), val = e_val) %>% 8 | activate(nodes) 9 | 10 | test_that("slicing nodes works", { 11 | expect_equal(slice(gr, 1:4) %>% pull(val), n_val[1:4]) 12 | expect_equal(slice_head(gr, n = 6) %>% pull(val), n_val[1:6]) 13 | expect_equal(slice_tail(gr, n = 6) %>% pull(val), rev(rev(n_val)[1:6])) 14 | expect_equal(slice_min(gr, val, n = 6) %>% pull(id), sort(order(n_val)[1:6])) 15 | expect_equal(slice_max(gr, val, n = 6) %>% pull(id), sort(order(n_val, decreasing = TRUE)[1:6])) 16 | }) 17 | 18 | test_that("slicing edges works", { 19 | gr <- gr %>% activate(edges) 20 | expect_equal(slice(gr, 1:4) %>% pull(val), e_val[1:4]) 21 | expect_equal(slice_head(gr, n = 6) %>% pull(val), e_val[1:6]) 22 | expect_equal(slice_tail(gr, n = 6) %>% pull(val), rev(rev(e_val)[1:6])) 23 | expect_equal(slice_min(gr, val, n = 6) %>% pull(id), sort(order(e_val)[1:6])) 24 | expect_equal(slice_max(gr, val, n = 6) %>% pull(id), sort(order(e_val, decreasing = TRUE)[1:6])) 25 | }) 26 | 27 | test_empty_context() 28 | -------------------------------------------------------------------------------- /tests/testthat/test-tidyr-utils.R: -------------------------------------------------------------------------------- 1 | gr <- create_notable('house') %>% 2 | mutate(val = c(1:3, NA, NA)) %>% 3 | activate(edges) %>% 4 | mutate(val = c(1:3, NA, NA, NA)) %>% 5 | activate(nodes) 6 | 7 | test_that("tidyr utils work on nodes", { 8 | expect_equal(drop_na(gr) %>% pull(val), 1:3) 9 | expect_equal(replace_na(gr, list(val = 0)) %>% pull(val), c(1:3, 0, 0)) 10 | }) 11 | 12 | test_that("tidyr utils work on edges", { 13 | gr <- gr %>% activate(edges) 14 | expect_equal(drop_na(gr) %>% pull(val), 1:3) 15 | expect_equal(replace_na(gr, list(val = 0)) %>% pull(val), c(1:3, 0, 0, 0)) 16 | }) 17 | 18 | test_empty_context() 19 | --------------------------------------------------------------------------------