tidygraph/0000755000176200001440000000000013656450173012257 5ustar liggesuserstidygraph/NAMESPACE0000644000176200001440000003707513656437446013522 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(activate,grouped_tbl_graph) S3method(activate,morphed_tbl_graph) S3method(activate,tbl_graph) S3method(anti_join,tbl_graph) S3method(arrange,morphed_tbl_graph) S3method(arrange,tbl_graph) S3method(as.data.frame,tbl_graph) S3method(as.igraph,tbl_graph) S3method(as.list,tbl_graph) S3method(as_tbl_graph,Node) S3method(as_tbl_graph,data.frame) S3method(as_tbl_graph,default) S3method(as_tbl_graph,dendrogram) S3method(as_tbl_graph,evonet) S3method(as_tbl_graph,graphAM) S3method(as_tbl_graph,graphBAM) S3method(as_tbl_graph,graphNEL) S3method(as_tbl_graph,hclust) S3method(as_tbl_graph,igraph) S3method(as_tbl_graph,list) S3method(as_tbl_graph,matrix) S3method(as_tbl_graph,network) S3method(as_tbl_graph,phylo) S3method(as_tbl_graph,tbl_graph) S3method(as_tibble,tbl_graph) S3method(convert,tbl_graph) S3method(crystallise,morphed_tbl_graph) S3method(distinct,morphed_tbl_graph) S3method(distinct,tbl_graph) S3method(filter,morphed_tbl_graph) S3method(filter,tbl_graph) S3method(full_join,tbl_graph) S3method(group_by,morphed_tbl_graph) S3method(group_by,tbl_graph) S3method(group_data,tbl_graph) S3method(group_indices,tbl_graph) S3method(group_keys,tbl_graph) S3method(group_size,tbl_graph) S3method(group_vars,tbl_graph) S3method(groups,tbl_graph) S3method(inner_join,tbl_graph) S3method(left_join,tbl_graph) S3method(morph,morphed_tbl_graph) S3method(morph,tbl_graph) S3method(mutate,morphed_tbl_graph) S3method(mutate,tbl_graph) S3method(n_groups,tbl_graph) S3method(print,morphed_tbl_graph) S3method(print,tbl_graph) S3method(pull,morphed_tbl_graph) S3method(pull,tbl_graph) S3method(rename,morphed_tbl_graph) S3method(rename,tbl_graph) S3method(reroute,morphed_tbl_graph) S3method(reroute,tbl_graph) S3method(right_join,tbl_graph) S3method(sample_frac,morphed_tbl_graph) S3method(sample_frac,tbl_graph) S3method(sample_n,morphed_tbl_graph) S3method(sample_n,tbl_graph) S3method(select,morphed_tbl_graph) S3method(select,tbl_graph) S3method(semi_join,tbl_graph) S3method(slice,morphed_tbl_graph) S3method(slice,tbl_graph) S3method(tbl_vars,tbl_graph) S3method(ungroup,grouped_tbl_graph) S3method(ungroup,morphed_tbl_graph) S3method(ungroup,tbl_graph) S3method(unmorph,morphed_tbl_graph) export("%>%") export("%E>%") export("%N>%") export(.E) export(.G) export(.N) export(.graph_context) export(.register_graph_context) export(activate) export(active) export(anti_join) export(arrange) export(as.igraph) export(as_tbl_graph) export(as_tibble) export(bfs_after) export(bfs_before) export(bfs_dist) export(bfs_parent) export(bfs_rank) export(bind_edges) export(bind_graphs) export(bind_nodes) export(centrality_alpha) export(centrality_authority) export(centrality_betweenness) export(centrality_betweenness_communicability) export(centrality_betweenness_current) export(centrality_betweenness_network) export(centrality_betweenness_rsp_net) export(centrality_betweenness_rsp_simple) export(centrality_closeness) export(centrality_closeness_generalised) export(centrality_closeness_harmonic) export(centrality_closeness_residual) export(centrality_communicability) export(centrality_communicability_even) export(centrality_communicability_odd) export(centrality_decay) export(centrality_degree) export(centrality_edge_betweenness) export(centrality_eigen) export(centrality_expected) export(centrality_hub) export(centrality_information) export(centrality_integration) export(centrality_katz) export(centrality_manual) export(centrality_pagerank) export(centrality_power) export(centrality_random_walk) export(centrality_subgraph) export(centrality_subgraph_even) export(centrality_subgraph_odd) export(contains) export(convert) export(create_bipartite) export(create_chordal_ring) export(create_citation) export(create_complete) export(create_de_bruijn) export(create_empty) export(create_kautz) export(create_lattice) export(create_notable) export(create_path) export(create_ring) export(create_star) export(create_tree) export(crystallise) export(crystallize) export(dfs_dist) export(dfs_parent) export(dfs_rank) export(dfs_rank_out) export(distinct) export(edge_is_between) export(edge_is_from) export(edge_is_incident) export(edge_is_loop) export(edge_is_multiple) export(edge_is_mutual) export(edge_is_to) export(ends_with) export(everything) export(filter) export(fortify.tbl_graph) export(full_join) export(graph_adhesion) export(graph_assortativity) export(graph_asym_count) export(graph_automorphisms) export(graph_clique_count) export(graph_clique_num) export(graph_component_count) export(graph_diameter) export(graph_girth) export(graph_is_bipartite) export(graph_is_chordal) export(graph_is_complete) export(graph_is_connected) export(graph_is_dag) export(graph_is_directed) export(graph_is_forest) export(graph_is_isomorphic_to) export(graph_is_simple) export(graph_is_subgraph_isomorphic_to) export(graph_is_tree) export(graph_join) export(graph_mean_dist) export(graph_min_cut) export(graph_modularity) export(graph_motif_count) export(graph_mutual_count) export(graph_order) export(graph_radius) export(graph_reciprocity) export(graph_size) export(graph_unconn_count) export(group_biconnected_component) export(group_by) export(group_components) export(group_data) export(group_edge_betweenness) export(group_fast_greedy) export(group_indices) export(group_infomap) export(group_keys) export(group_label_prop) export(group_leading_eigen) export(group_louvain) export(group_optimal) export(group_size) export(group_spinglass) export(group_vars) export(group_walktrap) export(groups) export(inner_join) export(is.tbl_graph) export(left_join) export(local_ave_degree) export(local_members) export(local_size) export(local_transitivity) export(local_triangles) export(map_bfs) export(map_bfs_back) export(map_bfs_back_chr) export(map_bfs_back_dbl) export(map_bfs_back_int) export(map_bfs_back_lgl) export(map_bfs_chr) export(map_bfs_dbl) export(map_bfs_int) export(map_bfs_lgl) export(map_dfs) export(map_dfs_back) export(map_dfs_back_chr) export(map_dfs_back_dbl) export(map_dfs_back_int) export(map_dfs_back_lgl) export(map_dfs_chr) export(map_dfs_dbl) export(map_dfs_int) export(map_dfs_lgl) export(map_local) export(map_local_chr) export(map_local_dbl) export(map_local_int) export(map_local_lgl) export(matches) export(morph) export(mutate) export(mutate_all) export(mutate_as_tbl) export(mutate_at) export(n) export(n_groups) export(node_adhesion_from) export(node_adhesion_to) export(node_bibcoupling_with) export(node_bridging_score) export(node_closeness_impact) export(node_cocitation_with) export(node_cohesion_from) export(node_cohesion_to) export(node_connectivity_impact) export(node_constraint) export(node_coreness) export(node_distance_from) export(node_distance_to) export(node_diversity) export(node_dominator) export(node_eccentricity) export(node_effective_network_size) export(node_fareness_impact) export(node_is_adjacent) export(node_is_center) export(node_is_cut) export(node_is_isolated) export(node_is_keyplayer) export(node_is_leaf) export(node_is_root) export(node_is_simplical) export(node_is_sink) export(node_is_source) export(node_is_universal) export(node_max_flow_from) export(node_max_flow_to) export(node_rank_anneal) export(node_rank_branch_bound) export(node_rank_dendser) export(node_rank_genetic) export(node_rank_hclust) export(node_rank_leafsort) export(node_rank_mds) export(node_rank_quadratic) export(node_rank_spectral) export(node_rank_spin_in) export(node_rank_spin_out) export(node_rank_traveller) export(node_rank_two) export(node_rank_visual) export(node_similarity_with) export(node_topo_order) export(num_range) export(one_of) export(play_barabasi_albert) export(play_barabasi_albert_aging) export(play_bipartite) export(play_blocks) export(play_blocks_hierarchy) export(play_citation_age) export(play_citation_type) export(play_degree) export(play_dotprod) export(play_erdos_renyi) export(play_fitness) export(play_fitness_power) export(play_forestfire) export(play_geometry) export(play_growing) export(play_islands) export(play_preference) export(play_preference_asym) export(play_smallworld) export(play_traits) export(pull) export(rename) export(reroute) export(right_join) export(sample_frac) export(sample_n) export(select) export(semi_join) export(slice) export(starts_with) export(tbl_graph) export(tbl_vars) export(to_bfs_tree) export(to_complement) export(to_components) export(to_contracted) export(to_dfs_tree) export(to_directed) export(to_dominator_tree) export(to_hierarchical_clusters) export(to_linegraph) export(to_local_neighborhood) export(to_minimum_spanning_tree) export(to_shortest_path) export(to_simple) export(to_split) export(to_subcomponent) export(to_subgraph) export(to_undirected) export(to_unfolded_tree) export(top_n) export(transmute) export(ungroup) export(unmorph) export(with_graph) importFrom(R6,R6Class) importFrom(Rcpp,sourceCpp) importFrom(dplyr,anti_join) importFrom(dplyr,arrange) importFrom(dplyr,bind_cols) importFrom(dplyr,bind_rows) importFrom(dplyr,contains) importFrom(dplyr,distinct) importFrom(dplyr,ends_with) importFrom(dplyr,everything) importFrom(dplyr,filter) importFrom(dplyr,full_join) importFrom(dplyr,group_by) importFrom(dplyr,group_data) importFrom(dplyr,group_indices) importFrom(dplyr,group_keys) importFrom(dplyr,group_rows) importFrom(dplyr,group_size) importFrom(dplyr,group_vars) importFrom(dplyr,groups) importFrom(dplyr,inner_join) importFrom(dplyr,left_join) importFrom(dplyr,matches) importFrom(dplyr,mutate) importFrom(dplyr,mutate_all) importFrom(dplyr,mutate_at) importFrom(dplyr,n) importFrom(dplyr,n_groups) importFrom(dplyr,num_range) importFrom(dplyr,one_of) importFrom(dplyr,pull) importFrom(dplyr,rename) importFrom(dplyr,right_join) importFrom(dplyr,sample_frac) importFrom(dplyr,sample_n) importFrom(dplyr,select) importFrom(dplyr,semi_join) importFrom(dplyr,slice) importFrom(dplyr,starts_with) importFrom(dplyr,tbl_vars) importFrom(dplyr,top_n) importFrom(dplyr,transmute) importFrom(dplyr,ungroup) importFrom(igraph,"V<-") importFrom(igraph,"edge_attr<-") importFrom(igraph,"graph_attr<-") importFrom(igraph,"vertex_attr<-") importFrom(igraph,E) importFrom(igraph,V) importFrom(igraph,add_edges) importFrom(igraph,add_vertices) importFrom(igraph,adjacent_vertices) importFrom(igraph,alpha_centrality) importFrom(igraph,articulation_points) importFrom(igraph,as.igraph) importFrom(igraph,as_adjacency_matrix) importFrom(igraph,as_data_frame) importFrom(igraph,as_edgelist) importFrom(igraph,assortativity) importFrom(igraph,assortativity_nominal) importFrom(igraph,authority_score) importFrom(igraph,automorphisms) importFrom(igraph,betweenness) importFrom(igraph,bfs) importFrom(igraph,bibcoupling) importFrom(igraph,biconnected_components) importFrom(igraph,clique_num) importFrom(igraph,closeness) importFrom(igraph,cluster_edge_betweenness) importFrom(igraph,cluster_fast_greedy) importFrom(igraph,cluster_infomap) importFrom(igraph,cluster_label_prop) importFrom(igraph,cluster_leading_eigen) importFrom(igraph,cluster_louvain) importFrom(igraph,cluster_optimal) importFrom(igraph,cluster_spinglass) importFrom(igraph,cluster_walktrap) importFrom(igraph,cocitation) importFrom(igraph,complementer) importFrom(igraph,components) importFrom(igraph,constraint) importFrom(igraph,contract) importFrom(igraph,coreness) importFrom(igraph,count_components) importFrom(igraph,count_max_cliques) importFrom(igraph,count_motifs) importFrom(igraph,count_triangles) importFrom(igraph,decompose) importFrom(igraph,degree) importFrom(igraph,delete_edges) importFrom(igraph,delete_vertices) importFrom(igraph,dfs) importFrom(igraph,diameter) importFrom(igraph,distances) importFrom(igraph,diversity) importFrom(igraph,dominator_tree) importFrom(igraph,dyad_census) importFrom(igraph,eccentricity) importFrom(igraph,ecount) importFrom(igraph,edge_attr) importFrom(igraph,edge_attr_names) importFrom(igraph,edge_betweenness) importFrom(igraph,edge_connectivity) importFrom(igraph,ego) importFrom(igraph,ego_size) importFrom(igraph,eigen_centrality) importFrom(igraph,estimate_betweenness) importFrom(igraph,estimate_closeness) importFrom(igraph,estimate_edge_betweenness) importFrom(igraph,girth) importFrom(igraph,gorder) importFrom(igraph,graph_attr) importFrom(igraph,graph_from_adj_list) importFrom(igraph,graph_from_adjacency_matrix) importFrom(igraph,graph_from_data_frame) importFrom(igraph,graph_from_edgelist) importFrom(igraph,graph_from_incidence_matrix) importFrom(igraph,gsize) importFrom(igraph,hub_score) importFrom(igraph,induced_subgraph) importFrom(igraph,is.directed) importFrom(igraph,is_bipartite) importFrom(igraph,is_chordal) importFrom(igraph,is_connected) importFrom(igraph,is_dag) importFrom(igraph,is_directed) importFrom(igraph,is_isomorphic_to) importFrom(igraph,is_simple) importFrom(igraph,is_subgraph_isomorphic_to) importFrom(igraph,knn) importFrom(igraph,local_scan) importFrom(igraph,make_chordal_ring) importFrom(igraph,make_de_bruijn_graph) importFrom(igraph,make_ego_graph) importFrom(igraph,make_empty_graph) importFrom(igraph,make_full_bipartite_graph) importFrom(igraph,make_full_citation_graph) importFrom(igraph,make_full_graph) importFrom(igraph,make_graph) importFrom(igraph,make_kautz_graph) importFrom(igraph,make_lattice) importFrom(igraph,make_line_graph) importFrom(igraph,make_ring) importFrom(igraph,make_star) importFrom(igraph,make_tree) importFrom(igraph,max_flow) importFrom(igraph,mean_distance) importFrom(igraph,membership) importFrom(igraph,min_cut) importFrom(igraph,modularity) importFrom(igraph,mst) importFrom(igraph,page_rank) importFrom(igraph,permute) importFrom(igraph,power_centrality) importFrom(igraph,radius) importFrom(igraph,reciprocity) importFrom(igraph,sample_asym_pref) importFrom(igraph,sample_bipartite) importFrom(igraph,sample_cit_cit_types) importFrom(igraph,sample_cit_types) importFrom(igraph,sample_degseq) importFrom(igraph,sample_dot_product) importFrom(igraph,sample_fitness) importFrom(igraph,sample_fitness_pl) importFrom(igraph,sample_forestfire) importFrom(igraph,sample_gnm) importFrom(igraph,sample_gnp) importFrom(igraph,sample_grg) importFrom(igraph,sample_growing) importFrom(igraph,sample_hierarchical_sbm) importFrom(igraph,sample_islands) importFrom(igraph,sample_last_cit) importFrom(igraph,sample_pa) importFrom(igraph,sample_pa_age) importFrom(igraph,sample_pref) importFrom(igraph,sample_sbm) importFrom(igraph,sample_smallworld) importFrom(igraph,sample_traits) importFrom(igraph,sample_traits_callaway) importFrom(igraph,set_edge_attr) importFrom(igraph,set_vertex_attr) importFrom(igraph,shortest_paths) importFrom(igraph,similarity) importFrom(igraph,simplify) importFrom(igraph,strength) importFrom(igraph,subgraph.edges) importFrom(igraph,subgraph_centrality) importFrom(igraph,topo_sort) importFrom(igraph,transitivity) importFrom(igraph,unfold_tree) importFrom(igraph,vertex_attr) importFrom(igraph,vertex_attr_names) importFrom(igraph,vertex_connectivity) importFrom(igraph,which_loop) importFrom(igraph,which_multiple) importFrom(igraph,which_mutual) importFrom(magrittr,"%>%") importFrom(pillar,style_subtle) importFrom(rlang,"!!!") importFrom(rlang,"%||%") importFrom(rlang,.data) importFrom(rlang,UQS) importFrom(rlang,as_quosure) importFrom(rlang,caller_env) importFrom(rlang,enexpr) importFrom(rlang,enquo) importFrom(rlang,eval_bare) importFrom(rlang,eval_tidy) importFrom(rlang,is_bare_list) importFrom(rlang,list2) importFrom(rlang,quo) importFrom(rlang,quo_text) importFrom(rlang,quos) importFrom(rlang,sym) importFrom(stats,as.dendrogram) importFrom(stats,as.dist) importFrom(stats,dist) importFrom(stats,hclust) importFrom(stats,is.leaf) importFrom(stats,na.omit) importFrom(stats,setNames) importFrom(tibble,as_tibble) importFrom(tibble,tibble) importFrom(tibble,trunc_mat) importFrom(tidyr,nest_legacy) importFrom(tools,toTitleCase) importFrom(utils,head) importFrom(utils,modifyList) useDynLib(tidygraph) tidygraph/LICENSE0000644000176200001440000000006113545322217013253 0ustar liggesusersYEAR: 2017 COPYRIGHT HOLDER: Thomas Lin Pedersen tidygraph/README.md0000644000176200001440000001235613656244730013545 0ustar liggesusers # tidygraph [![R build status](https://github.com/thomasp85/tidygraph/workflows/R-CMD-check/badge.svg)](https://github.com/thomasp85/tidygraph/actions) [![CRAN\_Release\_Badge](http://www.r-pkg.org/badges/version-ago/tidygraph)](https://CRAN.R-project.org/package=tidygraph) [![CRAN\_Download\_Badge](http://cranlogs.r-pkg.org/badges/tidygraph)](https://CRAN.R-project.org/package=tidygraph) [![Coverage Status](https://img.shields.io/codecov/c/github/thomasp85/tidygraph/master.svg)](https://codecov.io/github/thomasp85/tidygraph?branch=master) This package provides a tidy API for graph/network manipulation. While network data itself is not tidy, it can be envisioned as two tidy tables, one for node data and one for edge data. `tidygraph` provides a way to switch between the two tables and provides `dplyr` verbs for manipulating them. Furthermore it provides access to a lot of graph algorithms with return values that facilitate their use in a tidy workflow. ## An example ``` r library(tidygraph) play_erdos_renyi(10, 0.5) %>% activate(nodes) %>% mutate(degree = centrality_degree()) %>% activate(edges) %>% mutate(centrality = centrality_edge_betweenness()) %>% arrange(centrality) #> # A tbl_graph: 10 nodes and 46 edges #> # #> # A directed simple graph with 1 component #> # #> # Edge Data: 46 x 3 (active) #> from to centrality #> * #> 1 1 8 1.33 #> 2 5 8 1.42 #> 3 5 3 1.75 #> 4 3 7 1.75 #> 5 5 7 1.92 #> 6 5 1 2.00 #> # … with 40 more rows #> # #> # Node Data: 10 x 1 #> degree #> #> 1 5 #> 2 6 #> 3 4 #> # … with 7 more rows ``` ## Overview `tidygraph` is a huge package that exports 280 different functions and methods. It more or less wraps the full functionality of `igraph` in a tidy API giving you access to almost all of the `dplyr` verbs plus a few more, developed for use with relational data. ### More verbs `tidygraph` adds some extra verbs for specific use in network analysis and manipulation. The `activate()` function defines whether one is manipulating node or edge data at the moment as shown in the example above. `bind_edges()`, `bind_nodes()`, and `bind_graphs()` let you expand the graph structure you’re working with, while `graph_join()` lets you merge two graphs on some node identifier. `reroute()`, on the other hand, lets you change the terminal nodes of the edges in the graph. ### More algorithms `tidygraph` wraps almost all of the graph algorithms from `igraph` and provides a consistent interface and output that always matches the sequence of nodes and edges. All `tidygraph` algorithm wrappers are intended for use inside verbs where they know the context they are being called in. In the example above it is not necessary to supply the graph nor the node/edge IDs to `centrality_degree()` and `centrality_edge_betweenness()` as they are aware of them already. This leads to much clearer code and less typing. ### More maps `tidygraph` goes beyond `dplyr` and also implements graph centric version of the `purrr` map functions. You can now call a function on the nodes in the order of a breadth or depth first search while getting access to the result of the previous calls. ### More morphs `tidygraph` lets you temporarily change the representation of your graph, do some manipulation of the node and edge data, and then change back to the original graph with the changes being merged in automatically. This is powered by the new `morph()`/`unmorph()` verbs that let you e.g. contract nodes, work on the linegraph representation, split communities to separate graphs etc. If you wish to continue with the morphed version, the `crystallise()` verb lets you *freeze* the temporary representation into a proper `tbl_graph`. ### More data structure support While `tidygraph` is powered by igraph underneath it wants everyone to join the fun. The `as_tbl_graph()` function can easily convert relational data from all your favourite objects, such as `network`, `phylo`, `dendrogram`, `data.tree`, `graph`, etc. More conversion will be added in the order I become aware of them. ## Visualisation `tidygraph` itself does not provide any means of visualisation, but it works flawlessly with `ggraph`. This division makes it easy to develop the visualisation and manipulation code at different speeds depending on where the needs arise. ## Installation `tidygraph` is available on CRAN and can be installed simply, using `install.packages('tidygraph')`. For the development version available on GitHub, use the `devtools` package for installation: ``` r # install.packages('devtools') devtools::install_github('thomasp85/tidygraph') ``` ## Thanks `tidygraph` stands on the shoulders of particularly the `igraph` and `dplyr`/`tidyverse` teams. It would not have happened without them, so thanks so much to them. ## Code of Conduct Please note that the ‘tidygraph’ project is released with a [Contributor Code of Conduct](https://tidygraph.data-imaginist.com/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. tidygraph/man/0000755000176200001440000000000013545322217013024 5ustar liggesuserstidygraph/man/morph.Rd0000644000176200001440000000765713545322217014457 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/morph.R \name{morph} \alias{morph} \alias{unmorph} \alias{crystallise} \alias{crystallize} \alias{convert} \title{Create a temporary alternative representation of the graph to compute on} \usage{ morph(.data, .f, ...) unmorph(.data) crystallise(.data) crystallize(.data) convert(.data, .f, ..., .select = 1, .clean = FALSE) } \arguments{ \item{.data}{A \code{tbl_graph} or a \code{morphed_tbl_graph}} \item{.f}{A morphing function. See \link{morphers} for a list of provided one.} \item{...}{Arguments passed on to the morpher} \item{.select}{The graph to return during \code{convert()}. Either an index or the name as created during \code{crystallise()}.} \item{.clean}{Should references to the node and edge indexes in the original graph be removed when using \code{convert}} } \value{ A \code{morphed_tbl_graph} } \description{ The \code{morph}/\code{unmorph} verbs are used to create temporary representations of the graph, such as e.g. its search tree or a subgraph. A morphed graph will accept any of the standard \code{dplyr} verbs, and changed to the data is automatically propagated to the original graph when unmorphing. Tidygraph comes with a range of \link{morphers}, but is it also possible to supply your own. See Details for the requirement for custom morphers. The \code{crystallise} verb is used to extract the temporary graph representation into a tibble containing one separate graph per row and a \code{name} and \code{graph} column holding the name of each graph and the graph itself respectively. \code{convert()} is a shorthand for performing both \code{morph} and \code{crystallise} along with extracting a single \code{tbl_graph} (defaults to the first). For morphs were you know they only create a single graph, and you want to keep it, this is an easy way. } \details{ It is only possible to change and add to node and edge data from a morphed state. Any filtering/removal of nodes and edges will not result in removal from the main graph. However, nodes and edges not present in the morphed state will be unaffected in the main graph when unmorphing (if new columns were added during the morhped state they will be filled with \code{NA}). Morphing an already morhped graph will unmorph prior to applying the new morph. During a morphed state, the mapping back to the original graph is stored in \code{.tidygraph_node_index} and \code{.tidygraph_edge_index} columns. These are accesible but protected, meaning that any changes to them with e.g. mutate will be ignored. Furthermore, if the morph results in the merging of nodes and/or edges the original data is stored in a \code{.data} column. This is protected as well. When supplying your own morphers the morphing function should accept a \code{tbl_graph} as its first input. The provided graph will already have nodes and edges mapped with a \code{.tidygraph_node_index} and \code{.tidygraph_edge_index} column. The return value must be a \code{tbl_graph} or a list of \code{tbl_graph}s and these must contain either a \code{.tidygraph_node_index} column or a \code{.tidygraph_edge_index} column (or both). Note that it is possible for the morph to have the edges mapped back to the original nodes and vice versa (e.g. as with \link{to_linegraph}). In that case the edge data in the morphed graph(s) will contain a \code{.tidygraph_node_index} column and or the node data a \code{.tidygraph_edge_index} column. If the morphing results in the collapse of multiple columns or edges the index columns should be converted to list columns mapping the new node/edge back to all the nodes/edges it represents. Furthermore the original node/edge data should be collapsed to a list of tibbles, with the row order matching the order in the index column element. } \examples{ create_notable('meredith') \%>\% mutate(group = group_infomap()) \%>\% morph(to_contracted, group) \%>\% mutate(group_centrality = centrality_pagerank()) \%>\% unmorph() } tidygraph/man/graph_join.Rd0000644000176200001440000000473013656325570015447 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/joins.R \name{graph_join} \alias{graph_join} \title{Join graphs on common nodes} \usage{ graph_join(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) } \arguments{ \item{x}{A \code{tbl_graph}} \item{y}{An object convertible to a \code{tbl_graph} using \code{\link[=as_tbl_graph]{as_tbl_graph()}}} \item{by}{A character vector of variables to join by. If \code{NULL}, the default, \verb{*_join()} will perform a natural join, using all variables in common across \code{x} and \code{y}. A message lists the variables so that you can check they're correct; suppress the message by supplying \code{by} explicitly. To join by different variables on \code{x} and \code{y}, use a named vector. For example, \code{by = c("a" = "b")} will match \code{x$a} to \code{y$b}. To join by multiple variables, use a vector with length > 1. For example, \code{by = c("a", "b")} will match \code{x$a} to \code{y$a} and \code{x$b} to \code{y$b}. Use a named vector to match different variables in \code{x} and \code{y}. For example, \code{by = c("a" = "b", "c" = "d")} will match \code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. To perform a cross-join, generating all combinations of \code{x} and \code{y}, use \code{by = character()}.} \item{copy}{If \code{x} and \code{y} are not from the same data source, and \code{copy} is \code{TRUE}, then \code{y} will be copied into the same src as \code{x}. This allows you to join tables across srcs, but it is a potentially expensive operation so you must opt into it.} \item{suffix}{If there are non-joined duplicate variables in \code{x} and \code{y}, these suffixes will be added to the output to disambiguate them. Should be a character vector of length 2.} \item{...}{Other parameters passed onto methods.} } \value{ A \code{tbl_graph} containing the merged graph } \description{ This graph-specific join method makes a full join on the nodes data and updates the edges in the joining graph so they matches the new indexes of the nodes in the resulting graph. Node and edge data is combined using \code{\link[dplyr:bind_rows]{dplyr::bind_rows()}} semantic, meaning that data is matched by column name and filled with \code{NA} if it is missing in either of the graphs. } \examples{ gr1 <- create_notable('bull') \%>\% activate(nodes) \%>\% mutate(name = letters[1:5]) gr2 <- create_ring(10) \%>\% activate(nodes) \%>\% mutate(name = letters[4:13]) gr1 \%>\% graph_join(gr2) } tidygraph/man/search_graph.Rd0000644000176200001440000000566113545322217015751 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/search.R \name{search_graph} \alias{search_graph} \alias{bfs_rank} \alias{bfs_parent} \alias{bfs_before} \alias{bfs_after} \alias{bfs_dist} \alias{dfs_rank} \alias{dfs_rank_out} \alias{dfs_parent} \alias{dfs_dist} \title{Search a graph with depth first and breath first} \usage{ bfs_rank(root, mode = "out", unreachable = FALSE) bfs_parent(root, mode = "out", unreachable = FALSE) bfs_before(root, mode = "out", unreachable = FALSE) bfs_after(root, mode = "out", unreachable = FALSE) bfs_dist(root, mode = "out", unreachable = FALSE) dfs_rank(root, mode = "out", unreachable = FALSE) dfs_rank_out(root, mode = "out", unreachable = FALSE) dfs_parent(root, mode = "out", unreachable = FALSE) dfs_dist(root, mode = "out", unreachable = FALSE) } \arguments{ \item{root}{The node to start the search from} \item{mode}{How edges are followed in the search if the graph is directed. \code{"out"} only follows outbound edges, \code{"in"} only follows inbound edges, and \code{"all"} or \code{"total"} follows all edges. This is ignored for undirected graphs.} \item{unreachable}{Should the search jump to a new component if the search is terminated without all nodes being visited? Default to \code{FALSE} (only reach connected nodes).} } \value{ An integer vector, the nature of which is determined by the function. } \description{ These functions wraps the \code{\link[igraph:bfs]{igraph::bfs()}} and \code{\link[igraph:dfs]{igraph::dfs()}} functions to provide a consistent return value that can be used in \code{\link[dplyr:mutate]{dplyr::mutate()}} calls. Each function returns an integer vector with values matching the order of the nodes in the graph. } \section{Functions}{ \itemize{ \item \code{bfs_rank}: Get the succession in which the nodes are visited in a breath first search \item \code{bfs_parent}: Get the nodes from which each node is visited in a breath first search \item \code{bfs_before}: Get the node that was visited before each node in a breath first search \item \code{bfs_after}: Get the node that was visited after each node in a breath first search \item \code{bfs_dist}: Get the number of nodes between the root and each node in a breath first search \item \code{dfs_rank}: Get the succession in which the nodes are visited in a depth first search \item \code{dfs_rank_out}: Get the succession in which each nodes subtree is completed in a depth first search \item \code{dfs_parent}: Get the nodes from which each node is visited in a depth first search \item \code{dfs_dist}: Get the number of nodes between the root and each node in a depth first search }} \examples{ # Get the depth of each node in a tree create_tree(10, 2) \%>\% activate(nodes) \%>\% mutate(depth = bfs_dist(root = 1)) # Reorder nodes based on a depth first search from node 3 create_notable('franklin') \%>\% activate(nodes) \%>\% mutate(order = dfs_rank(root = 3)) \%>\% arrange(order) } tidygraph/man/reroute.Rd0000644000176200001440000000227713545322217015010 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/reroute.R \name{reroute} \alias{reroute} \title{Change terminal nodes of edges} \usage{ reroute(.data, from = NULL, to = NULL, subset = NULL) } \arguments{ \item{.data}{A tbl_graph or morphed_tbl_graph object. grouped_tbl_graph will be ungrouped prior to rerouting} \item{from, to}{The new indexes of the terminal nodes. If \code{NULL} nothing will be changed} \item{subset}{An expression evaluating to an indexing vector in the context of the edge data.} } \value{ An object of the same class as .data } \description{ The reroute verb lets you change the beginning and end node of edges by specifying the new indexes of the start and/or end node(s). Optionally only a subset of the edges can be rerouted using the subset argument, which should be an expression that are to be evaluated in the context of the edge data and should return an index compliant vector (either logical or integer). } \examples{ # Switch direction of edges create_notable('meredith') \%>\% activate(edges) \%>\% reroute(from = to, to = from) # Using subset create_notable('meredith') \%>\% activate(edges) \%>\% reroute(from = 1, subset = to > 10) } tidygraph/man/morphers.Rd0000644000176200001440000001422313656245051015157 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/morphers.R \name{morphers} \alias{morphers} \alias{to_linegraph} \alias{to_subgraph} \alias{to_subcomponent} \alias{to_split} \alias{to_components} \alias{to_complement} \alias{to_local_neighborhood} \alias{to_dominator_tree} \alias{to_minimum_spanning_tree} \alias{to_shortest_path} \alias{to_bfs_tree} \alias{to_dfs_tree} \alias{to_simple} \alias{to_contracted} \alias{to_unfolded_tree} \alias{to_directed} \alias{to_undirected} \alias{to_hierarchical_clusters} \title{Functions to generate alternate representations of graphs} \usage{ to_linegraph(graph) to_subgraph(graph, ..., subset_by = NULL) to_subcomponent(graph, node) to_split(graph, ..., split_by = NULL) to_components(graph, type = "weak") to_complement(graph, loops = FALSE) to_local_neighborhood(graph, node, order = 1, mode = "all") to_dominator_tree(graph, root, mode = "out") to_minimum_spanning_tree(graph, weights = NULL) to_shortest_path(graph, from, to, mode = "out", weights = NULL) to_bfs_tree(graph, root, mode = "out", unreachable = FALSE) to_dfs_tree(graph, root, mode = "out", unreachable = FALSE) to_simple(graph, remove_multiples = TRUE, remove_loops = TRUE) to_contracted(graph, ..., simplify = TRUE) to_unfolded_tree(graph, root, mode = "out") to_directed(graph) to_undirected(graph) to_hierarchical_clusters(graph, method = "walktrap", weights = NULL, ...) } \arguments{ \item{graph}{A \code{tbl_graph}} \item{...}{Arguments to pass on to \code{\link[=filter]{filter()}}, \code{\link[=group_by]{group_by()}}, or the cluster algorithm (see \code{\link[igraph:cluster_walktrap]{igraph::cluster_walktrap()}}, \code{\link[igraph:cluster_leading_eigen]{igraph::cluster_leading_eigen()}}, and \code{\link[igraph:cluster_edge_betweenness]{igraph::cluster_edge_betweenness()}})} \item{subset_by, split_by}{Whether to create subgraphs based on nodes or edges} \item{node}{The center of the neighborhood for \code{to_local_neighborhood()} and the node to that should be included in the component for \code{to_subcomponent()}} \item{type}{The type of component to split into. Either \code{'weak'} or \code{'strong'}} \item{loops}{Should loops be included. Defaults to \code{FALSE}} \item{order}{The radius of the neighborhood} \item{mode}{How should edges be followed? \code{'out'} only follows outbound edges, \code{'in'} only follows inbound edges, and \code{'all'} follows all edges. This parameter is ignored for undirected graphs.} \item{root}{The root of the tree} \item{weights}{Optional edge weights for the calculations} \item{from, to}{The start and end node of the path} \item{unreachable}{Should the search jump to a node in a new component when stuck.} \item{remove_multiples}{Should edges that run between the same nodes be reduced to one} \item{remove_loops}{Should edges that start and end at the same node be removed} \item{simplify}{Should edges in the contracted graph be simplified? Defaults to \code{TRUE}} \item{method}{The clustering method to use. Either \code{'walktrap'}, \code{'leading_eigen'}, or \code{'edge_betweenness'}} } \value{ A list of \code{tbl_graph}s } \description{ These functions are meant to be passed into \code{\link[=morph]{morph()}} to create a temporary alternate representation of the input graph. They are thus not meant to be called directly. See below for detail of each morpher. } \section{Functions}{ \itemize{ \item \code{to_linegraph}: Convert a graph to its line graph. When unmorphing node data will be merged back into the original edge data. Edge data will be ignored. \item \code{to_subgraph}: Convert a graph to a single subgraph. \code{...} is evaluated in the same manner as \code{filter}. When unmorphing all data in the subgraph will get merged back. \item \code{to_subcomponent}: Convert a graph to a single component containing the specified node \item \code{to_split}: Convert a graph into a list of separate subgraphs. \code{...} is evaluated in the same manner as \code{group_by}. When unmorphing all data in the subgraphs will get merged back, but in the case of \code{split_by = 'edges'} only the first instance of node data will be used (as the same node can be present in multiple subgraphs). \item \code{to_components}: Split a graph into its separate components. When unmorphing all data in the subgraphs will get merged back. \item \code{to_complement}: Convert a graph into its complement. When unmorphing only node data will get merged back. \item \code{to_local_neighborhood}: Convert a graph into the local neighborhood around a single node. When unmorphing all data will be merged back. \item \code{to_dominator_tree}: Convert a graph into its dominator tree based on a specific root. When unmorphing only node data will get merged back. \item \code{to_minimum_spanning_tree}: Convert a graph into its minimum spanning tree/forest. When unmorphing all data will get merged back. \item \code{to_shortest_path}: Limit a graph to the shortest path between two nodes. When unmorphing all data is merged back. \item \code{to_bfs_tree}: Convert a graph into a breath-first search tree based on a specific root. When unmorphing only node data is merged back. \item \code{to_dfs_tree}: Convert a graph into a depth-first search tree based on a specific root. When unmorphing only node data is merged back. \item \code{to_simple}: Collapse parallel edges and remove loops in a graph. When unmorphing all data will get merged back \item \code{to_contracted}: Combine multiple nodes into one. \code{...} is evaluated in the same manner as \code{group_by}. When unmorphing all data will get merged back. \item \code{to_unfolded_tree}: Unfold a graph to a tree or forest starting from multiple roots (or one), potentially duplicating nodes and edges. \item \code{to_directed}: Make a graph directed in the direction given by from and to \item \code{to_undirected}: Make a graph undirected \item \code{to_hierarchical_clusters}: Convert a graph into a hierarchical clustering based on a grouping }} \examples{ # Compute only on a subgraph of every even node create_notable('meredith') \%>\% morph(to_subgraph, seq_len(graph_order()) \%\% 2 == 0) \%>\% mutate(neighbour_count = centrality_degree()) \%>\% unmorph() } tidygraph/man/evolution_games.Rd0000644000176200001440000000714213654774463016536 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/play.R \name{evolution_games} \alias{evolution_games} \alias{play_citation_age} \alias{play_forestfire} \alias{play_growing} \alias{play_barabasi_albert} \alias{play_barabasi_albert_aging} \title{Graph games based on evolution} \usage{ play_citation_age( n, growth = 1, bins = n/7100, p_pref = (1:(bins + 1))^-3, directed = TRUE ) play_forestfire( n, p_forward, p_backward = p_forward, growth = 1, directed = TRUE ) play_growing(n, growth = 1, directed = TRUE, citation = FALSE) play_barabasi_albert( n, power, growth = 1, growth_dist = NULL, use_out = FALSE, appeal_zero = 1, directed = TRUE, method = "psumtree" ) play_barabasi_albert_aging( n, power, power_age, growth = 1, growth_dist = NULL, bins = 300, use_out = FALSE, appeal_zero = 1, appeal_zero_age = 0, directed = TRUE, coefficient = 1, coefficient_age = 1, window = NULL ) } \arguments{ \item{n}{The number of nodes in the graph.} \item{growth}{The number of edges added at each iteration} \item{bins}{The number of aging bins} \item{p_pref}{The probability that an edge will be made to an age bin.} \item{directed}{Should the resulting graph be directed} \item{p_forward, p_backward}{Forward and backward burning probability} \item{citation}{Should a citation graph be created} \item{power}{The power of the preferential attachment} \item{growth_dist}{The distribution of the number of added edges at each iteration} \item{use_out}{Should outbound edges be used for calculating citation probability} \item{appeal_zero}{The appeal value for unconnected nodes} \item{method}{The algorithm to use for graph creation. Either \code{'psumtree'}, \code{'psumtree-multiple'}, or \code{'bag'}} \item{power_age}{The aging exponent} \item{appeal_zero_age}{The appeal value of nodes without age} \item{coefficient}{The coefficient of the degree dependent part of attrictiveness} \item{coefficient_age}{The coefficient of the age dependent part of attrictiveness} \item{window}{The aging window to take into account when calculating the preferential attraction} } \value{ A tbl_graph object } \description{ This games create graphs through different types of evolutionary mechanisms (not necessarily in a biological sense). The nature of their algorithm is described in detail at the linked igraph documentation. } \section{Functions}{ \itemize{ \item \code{play_citation_age}: Create citation graphs based on a specific age link probability. See \code{\link[igraph:sample_last_cit]{igraph::sample_last_cit()}} \item \code{play_forestfire}: Create graphs by simulating the spead of fire in a forest. See \code{\link[igraph:sample_forestfire]{igraph::sample_forestfire()}} \item \code{play_growing}: Create graphs by adding a fixed number of edges at each iteration. See \code{\link[igraph:sample_growing]{igraph::sample_growing()}} \item \code{play_barabasi_albert}: Create graphs based on the Barabasi-Alberts preferential attachment model. See \code{\link[igraph:sample_pa]{igraph::sample_pa()}} \item \code{play_barabasi_albert_aging}: Create graphs based on the Barabasi-Alberts preferential attachment model, incoorporating node age preferrence. See \code{\link[igraph:sample_pa_age]{igraph::sample_pa_age()}}. }} \examples{ plot(play_forestfire(50, 0.5)) } \seealso{ \code{\link[=play_traits]{play_traits()}} and \code{\link[=play_citation_type]{play_citation_type()}} for an evolutionary algorithm based on different node types Other graph games: \code{\link{component_games}}, \code{\link{sampling_games}}, \code{\link{type_games}} } \concept{graph games} tidygraph/man/mutate_as_tbl.Rd0000644000176200001440000000176713545322217016151 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/mutate.R \name{mutate_as_tbl} \alias{mutate_as_tbl} \title{Base implementation of mutate} \usage{ mutate_as_tbl(.data, ...) } \arguments{ \item{.data}{A \code{tbl_graph} object} \item{...}{columns to mutate} } \value{ A \code{tbl_graph} object } \description{ This implementation of mutate is slightly faster than \code{mutate} at the expense of the graph only being updated in the end. This means that graph algorithms will not take changes happening during the mutate call into account. } \details{ The order of speed increase are rather small and in the ~1 millisecond per mutateed column order, so for regular use this should not be a choice. The operations not supported by \code{mutate_as_tbl} are e.g.\preformatted{gr \%>\% activate(nodes) \%>\% mutate(weights = runif(10), degree = centrality_degree(weights)) } as \code{weights} will only be made available in the graph at the end of the mutate call. } \keyword{internal} tidygraph/man/tidygraph-package.Rd0000644000176200001440000000201713654774463016716 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tidygraph-package.R \docType{package} \name{tidygraph-package} \alias{tidygraph} \alias{tidygraph-package} \title{tidygraph: A Tidy API for Graph Manipulation} \description{ \if{html}{\figure{logo.png}{options: align='right' alt='logo' width='120'}} 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. } \seealso{ Useful links: \itemize{ \item \url{https://tidygraph.data-imaginist.com} \item \url{https://github.com/thomasp85/tidygraph} \item Report bugs at \url{https://github.com/thomasp85/tidygraph/issues} } } \author{ \strong{Maintainer}: Thomas Lin Pedersen \email{thomasp85@gmail.com} (\href{https://orcid.org/0000-0002-5147-4711}{ORCID}) } \keyword{internal} tidygraph/man/map_bfs_back.Rd0000644000176200001440000000711413654774463015724 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/map.R \name{map_bfs_back} \alias{map_bfs_back} \alias{map_bfs_back_lgl} \alias{map_bfs_back_chr} \alias{map_bfs_back_int} \alias{map_bfs_back_dbl} \title{Apply a function to nodes in the reverse order of a breath first search} \usage{ map_bfs_back(root, mode = "out", unreachable = FALSE, .f, ...) map_bfs_back_lgl(root, mode = "out", unreachable = FALSE, .f, ...) map_bfs_back_chr(root, mode = "out", unreachable = FALSE, .f, ...) map_bfs_back_int(root, mode = "out", unreachable = FALSE, .f, ...) map_bfs_back_dbl(root, mode = "out", unreachable = FALSE, .f, ...) } \arguments{ \item{root}{The node to start the search from} \item{mode}{How should edges be followed? \code{'out'} only follows outbound edges, \code{'in'} only follows inbound edges, and \code{'all'} follows all edges. This parameter is ignored for undirected graphs.} \item{unreachable}{Should the search jump to an unvisited node if the search is completed without visiting all nodes.} \item{.f}{A function to map over all nodes. See Details} \item{...}{Additional parameters to pass to \code{.f}} } \value{ \code{map_bfs_back()} returns a list of the same length as the number of nodes in the graph, in the order matching the node order in the graph (that is, not in the order they are called). \verb{map_bfs_back_*()} tries to coerce its result into a vector of the classes \code{logical} (\code{map_bfs_back_lgl}), \code{character} (\code{map_bfs_back_chr}), \code{integer} (\code{map_bfs_back_int}), or \code{double} (\code{map_bfs_back_dbl}). These functions will throw an error if they are unsuccesful, so they are type safe. } \description{ These functions allow you to map over the nodes in a graph, by first performing a breath first search on the graph and then mapping over each node in the reverse order they are visited. The mapping function will have access to the result and search statistics for all the nodes following itself in the search. To map over the nodes in the original direction use \code{\link[=map_bfs]{map_bfs()}}. } \details{ The function provided to \code{.f} will be called with the following arguments in addition to those supplied through \code{...}: \itemize{ \item \code{graph}: The full \code{tbl_graph} object \item \code{node}: The index of the node currently mapped over \item \code{rank}: The rank of the node in the search \item \code{parent}: The index of the node that led to the current node \item \code{before}: The index of the node that was visited before the current node \item \code{after}: The index of the node that was visited after the current node. \item \code{dist}: The distance of the current node from the root \item \code{path}: A table containing \code{node}, \code{rank}, \code{parent}, \code{before}, \code{after}, \code{dist}, and \code{result} columns giving the values for each node reached from the current node. The \code{result} column will contain the result of the mapping of each node in a list. } Instead of spelling out all of these in the function it is possible to simply name the ones needed and use \code{...} to catch the rest. } \examples{ # Collect values from children create_tree(40, children = 3, directed = TRUE) \%>\% mutate(value = round(runif(40)*100)) \%>\% mutate(child_acc = map_bfs_back_dbl(node_is_root(), .f = function(node, path, ...) { if (nrow(path) == 0) .N()$value[node] else { sum(unlist(path$result[path$parent == node])) } })) } \seealso{ Other node map functions: \code{\link{map_bfs}()}, \code{\link{map_dfs_back}()}, \code{\link{map_dfs}()} } \concept{node map functions} tidygraph/man/map_dfs.Rd0000644000176200001440000000655113654774463014752 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/map.R \name{map_dfs} \alias{map_dfs} \alias{map_dfs_lgl} \alias{map_dfs_chr} \alias{map_dfs_int} \alias{map_dfs_dbl} \title{Apply a function to nodes in the order of a depth first search} \usage{ map_dfs(root, mode = "out", unreachable = FALSE, .f, ...) map_dfs_lgl(root, mode = "out", unreachable = FALSE, .f, ...) map_dfs_chr(root, mode = "out", unreachable = FALSE, .f, ...) map_dfs_int(root, mode = "out", unreachable = FALSE, .f, ...) map_dfs_dbl(root, mode = "out", unreachable = FALSE, .f, ...) } \arguments{ \item{root}{The node to start the search from} \item{mode}{How should edges be followed? \code{'out'} only follows outbound edges, \code{'in'} only follows inbound edges, and \code{'all'} follows all edges. This parameter is ignored for undirected graphs.} \item{unreachable}{Should the search jump to an unvisited node if the search is completed without visiting all nodes.} \item{.f}{A function to map over all nodes. See Details} \item{...}{Additional parameters to pass to \code{.f}} } \value{ \code{map_dfs()} returns a list of the same length as the number of nodes in the graph, in the order matching the node order in the graph (that is, not in the order they are called). \verb{map_dfs_*()} tries to coerce its result into a vector of the classes \code{logical} (\code{map_dfs_lgl}), \code{character} (\code{map_dfs_chr}), \code{integer} (\code{map_dfs_int}), or \code{double} (\code{map_dfs_dbl}). These functions will throw an error if they are unsuccesful, so they are type safe. } \description{ These functions allow you to map over the nodes in a graph, by first performing a depth first search on the graph and then mapping over each node in the order they are visited. The mapping function will have access to the result and search statistics for all the nodes between itself and the root in the search. To map over the nodes in the reverse direction use \code{\link[=map_dfs_back]{map_dfs_back()}}. } \details{ The function provided to \code{.f} will be called with the following arguments in addition to those supplied through \code{...}: \itemize{ \item \code{graph}: The full \code{tbl_graph} object \item \code{node}: The index of the node currently mapped over \item \code{rank}: The rank of the node in the search \item \code{rank_out}: The rank of the completion of the nodes subtree \item \code{parent}: The index of the node that led to the current node \item \code{dist}: The distance of the current node from the root \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 of each node in a list. } Instead of spelling out all of these in the function it is possible to simply name the ones needed and use \code{...} to catch the rest. } \examples{ # Add a random integer to the last value along a search create_tree(40, children = 3, directed = TRUE) \%>\% mutate(child_acc = map_dfs_int(node_is_root(), .f = function(node, path, ...) { last_val <- if (nrow(path) == 0) 0L else tail(unlist(path$result), 1) last_val + sample(1:10, 1) })) } \seealso{ Other node map functions: \code{\link{map_bfs_back}()}, \code{\link{map_bfs}()}, \code{\link{map_dfs_back}()} } \concept{node map functions} tidygraph/man/map_dfs_back.Rd0000644000176200001440000000677513654774463015742 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/map.R \name{map_dfs_back} \alias{map_dfs_back} \alias{map_dfs_back_lgl} \alias{map_dfs_back_chr} \alias{map_dfs_back_int} \alias{map_dfs_back_dbl} \title{Apply a function to nodes in the reverse order of a depth first search} \usage{ map_dfs_back(root, mode = "out", unreachable = FALSE, .f, ...) map_dfs_back_lgl(root, mode = "out", unreachable = FALSE, .f, ...) map_dfs_back_chr(root, mode = "out", unreachable = FALSE, .f, ...) map_dfs_back_int(root, mode = "out", unreachable = FALSE, .f, ...) map_dfs_back_dbl(root, mode = "out", unreachable = FALSE, .f, ...) } \arguments{ \item{root}{The node to start the search from} \item{mode}{How should edges be followed? \code{'out'} only follows outbound edges, \code{'in'} only follows inbound edges, and \code{'all'} follows all edges. This parameter is ignored for undirected graphs.} \item{unreachable}{Should the search jump to an unvisited node if the search is completed without visiting all nodes.} \item{.f}{A function to map over all nodes. See Details} \item{...}{Additional parameters to pass to \code{.f}} } \value{ \code{map_dfs_back()} returns a list of the same length as the number of nodes in the graph, in the order matching the node order in the graph (that is, not in the order they are called). \verb{map_dfs_back_*()} tries to coerce its result into a vector of the classes \code{logical} (\code{map_dfs_back_lgl}), \code{character} (\code{map_dfs_back_chr}), \code{integer} (\code{map_dfs_back_int}), or \code{double} (\code{map_dfs_back_dbl}). These functions will throw an error if they are unsuccesful, so they are type safe. } \description{ These functions allow you to map over the nodes in a graph, by first performing a depth first search on the graph and then mapping over each node in the reverse order they are visited. The mapping function will have access to the result and search statistics for all the nodes following itself in the search. To map over the nodes in the original direction use \code{\link[=map_dfs]{map_dfs()}}. } \details{ The function provided to \code{.f} will be called with the following arguments in addition to those supplied through \code{...}: \itemize{ \item \code{graph}: The full \code{tbl_graph} object \item \code{node}: The index of the node currently mapped over \item \code{rank}: The rank of the node in the search \item \code{rank_out}: The rank of the completion of the nodes subtree \item \code{parent}: The index of the node that led to the current node \item \code{dist}: The distance of the current node from the root \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 of each node in a list. } Instead of spelling out all of these in the function it is possible to simply name the ones needed and use \code{...} to catch the rest. } \examples{ # Collect values from the 2 closest layers of children in a dfs search create_tree(40, children = 3, directed = TRUE) \%>\% mutate(value = round(runif(40)*100)) \%>\% mutate(child_acc = map_dfs_back(node_is_root(), .f = function(node, path, dist, ...) { if (nrow(path) == 0) .N()$value[node] else { unlist(path$result[path$dist - dist <= 2]) } })) } \seealso{ Other node map functions: \code{\link{map_bfs_back}()}, \code{\link{map_bfs}()}, \code{\link{map_dfs}()} } \concept{node map functions} tidygraph/man/activate.Rd0000644000176200001440000000316213654774463015134 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/activate.R \name{activate} \alias{activate} \alias{active} \alias{\%N>\%} \alias{\%E>\%} \title{Determine the context of subsequent manipulations} \usage{ activate(.data, what) active(x) lhs \%N>\% rhs lhs \%E>\% rhs } \arguments{ \item{.data, x, lhs}{A tbl_graph or a grouped_tbl_graph} \item{what}{What should get activated? Possible values are \code{nodes} or \code{edges}.} \item{rhs}{A function to pipe into} } \value{ A tbl_graph } \description{ As a \link{tbl_graph} can be considered as a collection of two linked tables it is necessary to specify which table is referenced during manipulations. The \code{activate} verb does just that and needs affects all subsequent manipulations until a new table is activated. \code{active} is a simple query function to get the currently acitve context. In addition to the use of \code{activate} it is also possible to activate nodes or edges as part of the piping using the \verb{\%N>\%} and \verb{\%E>\%} pipes respectively. Do note that this approach somewhat obscures what is going on and is thus only recommended for quick, one-line, fixes in interactive use. } \note{ Activate will ungroup a grouped_tbl_graph. } \examples{ gr <- create_complete(5) \%>\% activate(nodes) \%>\% mutate(class = sample(c('a', 'b'), 5, TRUE)) \%>\% activate(edges) \%>\% arrange(from) # The above could be achieved using the special pipes as well gr <- create_complete(5) \%N>\% mutate(class = sample(c('a', 'b'), 5, TRUE)) \%E>\% arrange(from) # But as you can see it obscures what part of the graph is being targeted } tidygraph/man/type_games.Rd0000644000176200001440000000652213654774463015474 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/play.R \name{type_games} \alias{type_games} \alias{play_preference} \alias{play_preference_asym} \alias{play_bipartite} \alias{play_traits} \alias{play_citation_type} \title{Graph games based on different node types} \usage{ play_preference( n, n_types, p_type = rep(1, n_types), p_pref = matrix(1, n_types, n_types), fixed = FALSE, directed = TRUE, loops = FALSE ) play_preference_asym( n, n_types, p_type = matrix(1, n_types, n_types), p_pref = matrix(1, n_types, n_types), loops = FALSE ) play_bipartite(n1, n2, p, m, directed = TRUE, mode = "out") play_traits( n, n_types, growth = 1, p_type = rep(1, n_types), p_pref = matrix(1, n_types, n_types), callaway = TRUE, directed = TRUE ) play_citation_type( n, growth, types = rep(0, n), p_pref = rep(1, length(unique(types))), directed = TRUE ) } \arguments{ \item{n, n1, n2}{The number of nodes in the graph. For bipartite graphs \code{n1} and \code{n2} specifies the number of nodes of each type.} \item{n_types}{The number of different node types in the graph} \item{p_type}{The probability that a node will be the given type. Either a vector or a matrix, depending on the game} \item{p_pref}{The probability that an edge will be made to a type. Either a vector or a matrix, depending on the game} \item{fixed}{Should n_types be understood as a fixed number of nodes for each type rather than as a probability} \item{directed}{Should the resulting graph be directed} \item{loops}{Are loop edges allowed} \item{p}{The probabilty of an edge occuring} \item{m}{The number of edges in the graph} \item{mode}{The flow direction of edges} \item{growth}{The number of edges added at each iteration} \item{callaway}{Use the callaway version of the trait based game} \item{types}{The type of each node in the graph, enumerated from 0} } \value{ A tbl_graph object } \description{ This set of games are build around different types of nodes and simulating their interaction. The nature of their algorithm is described in detail at the linked igraph documentation. } \section{Functions}{ \itemize{ \item \code{play_preference}: Create graphs by linking nodes of different types based on a defined probability. See \code{\link[igraph:sample_pref]{igraph::sample_pref()}} \item \code{play_preference_asym}: Create graphs by linking nodes of different types based on an asymmetric probability. See \code{\link[igraph:sample_asym_pref]{igraph::sample_asym_pref()}} \item \code{play_bipartite}: Create bipartite graphs of fixed size and edge count or probability. See \code{\link[igraph:sample_bipartite]{igraph::sample_bipartite()}} \item \code{play_traits}: Create graphs by evolving a graph with type based edge probabilities. See \code{\link[igraph:sample_traits]{igraph::sample_traits()}} and \code{\link[igraph:sample_traits_callaway]{igraph::sample_traits_callaway()}} \item \code{play_citation_type}: Create citation graphs by evolving with type based linking probability. See \code{\link[igraph:sample_cit_types]{igraph::sample_cit_types()}} and \code{\link[igraph:sample_cit_cit_types]{igraph::sample_cit_cit_types()}} }} \examples{ plot(play_bipartite(20, 30, 0.4)) } \seealso{ Other graph games: \code{\link{component_games}}, \code{\link{evolution_games}}, \code{\link{sampling_games}} } \concept{graph games} tidygraph/man/centrality.Rd0000644000176200001440000002315513654774463015516 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/centrality.R \name{centrality} \alias{centrality} \alias{centrality_alpha} \alias{centrality_authority} \alias{centrality_betweenness} \alias{centrality_power} \alias{centrality_closeness} \alias{centrality_eigen} \alias{centrality_hub} \alias{centrality_pagerank} \alias{centrality_subgraph} \alias{centrality_degree} \alias{centrality_edge_betweenness} \alias{centrality_manual} \alias{centrality_closeness_harmonic} \alias{centrality_closeness_residual} \alias{centrality_closeness_generalised} \alias{centrality_integration} \alias{centrality_communicability} \alias{centrality_communicability_odd} \alias{centrality_communicability_even} \alias{centrality_subgraph_odd} \alias{centrality_subgraph_even} \alias{centrality_katz} \alias{centrality_betweenness_network} \alias{centrality_betweenness_current} \alias{centrality_betweenness_communicability} \alias{centrality_betweenness_rsp_simple} \alias{centrality_betweenness_rsp_net} \alias{centrality_information} \alias{centrality_decay} \alias{centrality_random_walk} \alias{centrality_expected} \title{Calculate node and edge centrality} \usage{ centrality_alpha( weights = NULL, alpha = 1, exo = 1, tol = 1e-07, loops = FALSE ) centrality_authority( weights = NULL, scale = TRUE, options = igraph::arpack_defaults ) centrality_betweenness( weights = NULL, directed = TRUE, cutoff = NULL, nobigint = TRUE, normalized = FALSE ) centrality_power(exponent = 1, rescale = FALSE, tol = 1e-07, loops = FALSE) centrality_closeness( weights = NULL, mode = "out", normalized = FALSE, cutoff = NULL ) centrality_eigen( weights = NULL, directed = FALSE, scale = TRUE, options = igraph::arpack_defaults ) centrality_hub(weights = NULL, scale = TRUE, options = igraph::arpack_defaults) centrality_pagerank( weights = NULL, directed = TRUE, damping = 0.85, personalized = NULL ) centrality_subgraph(loops = FALSE) centrality_degree( weights = NULL, mode = "out", loops = TRUE, normalized = FALSE ) centrality_edge_betweenness(weights = NULL, directed = TRUE, cutoff = NULL) centrality_manual(relation = "dist_sp", aggregation = "sum", ...) centrality_closeness_harmonic() centrality_closeness_residual() centrality_closeness_generalised(alpha) centrality_integration() centrality_communicability() centrality_communicability_odd() centrality_communicability_even() centrality_subgraph_odd() centrality_subgraph_even() centrality_katz(alpha = NULL) centrality_betweenness_network(netflowmode = "raw") centrality_betweenness_current() centrality_betweenness_communicability() centrality_betweenness_rsp_simple(rspxparam = 1) centrality_betweenness_rsp_net(rspxparam = 1) centrality_information() centrality_decay(alpha = 1) centrality_random_walk() centrality_expected() } \arguments{ \item{weights}{The weight of the edges to use for the calculation. Will be evaluated in the context of the edge data.} \item{alpha}{Relative importance of endogenous vs exogenous factors (\code{centrality_alpha}), the exponent to the power transformation of the distance metric (\code{centrality_closeness_generalised}), the base of power transformation (\code{centrality_decay}), or the attenuation factor (\code{centrality_katz})} \item{exo}{The exogenous factors of the nodes. Either a scalar or a number number for each node. Evaluated in the context of the node data.} \item{tol}{Tolerance for near-singularities during matrix inversion} \item{loops}{Should loops be included in the calculation} \item{scale}{Should the output be scaled between 0 and 1} \item{options}{Settings passed on to \code{igraph::arpack()}} \item{directed}{Should direction of edges be used for the calculations} \item{cutoff}{maximum path length to use during calculations} \item{nobigint}{Should big integers be avoided during calculations} \item{normalized}{Should the output be normalized} \item{exponent}{The decay rate for the Bonacich power centrality} \item{rescale}{Should the output be scaled to sum up to 1} \item{mode}{How should edges be followed. Ignored for undirected graphs} \item{damping}{The damping factor of the page rank algorithm} \item{personalized}{The probability of jumping to a node when abandoning a random walk. Evaluated in the context of the node data.} \item{relation}{The indirect relation measure type to be used in \code{netrankr::indirect_relations}} \item{aggregation}{The aggregation type to use on the indirect relations to be used in \code{netrankr::aggregate_positions}} \item{...}{Arguments to pass on to \code{netrankr::indirect_relations}} \item{netflowmode}{The return type of the network flow distance, either \code{'raw'} or \code{'frac'}} \item{rspxparam}{inverse temperature parameter} } \value{ A numeric vector giving the centrality measure of each node. } \description{ The centrality of a node measures the importance of node in the network. As the concept of importance is ill-defined and dependent on the network and the questions under consideration, many centrality measures exist. \code{tidygraph} provides a consistent set of wrappers for all the centrality measures implemented in \code{igraph} for use inside \code{\link[dplyr:mutate]{dplyr::mutate()}} and other relevant verbs. All functions provided by \code{tidygraph} have a consistent naming scheme and automatically calls the function on the graph, returning a vector with measures ready to be added to the node data. Further \code{tidygraph} provides access to the \code{netrankr} engine for centrality calculations and define a number of centrality measures based on that, as well as provide a manual mode for specifying more-or-less any centrality score. } \section{Functions}{ \itemize{ \item \code{centrality_alpha}: Wrapper for \code{\link[igraph:alpha_centrality]{igraph::alpha_centrality()}} \item \code{centrality_authority}: Wrapper for \code{\link[igraph:authority_score]{igraph::authority_score()}} \item \code{centrality_betweenness}: Wrapper for \code{\link[igraph:betweenness]{igraph::betweenness()}} and \code{\link[igraph:estimate_betweenness]{igraph::estimate_betweenness()}} \item \code{centrality_power}: Wrapper for \code{\link[igraph:power_centrality]{igraph::power_centrality()}} \item \code{centrality_closeness}: Wrapper for \code{\link[igraph:closeness]{igraph::closeness()}} and \code{\link[igraph:estimate_closeness]{igraph::estimate_closeness()}} \item \code{centrality_eigen}: Wrapper for \code{\link[igraph:eigen_centrality]{igraph::eigen_centrality()}} \item \code{centrality_hub}: Wrapper for \code{\link[igraph:hub_score]{igraph::hub_score()}} \item \code{centrality_pagerank}: Wrapper for \code{\link[igraph:page_rank]{igraph::page_rank()}} \item \code{centrality_subgraph}: Wrapper for \code{\link[igraph:subgraph_centrality]{igraph::subgraph_centrality()}} \item \code{centrality_degree}: Wrapper for \code{\link[igraph:degree]{igraph::degree()}} and \code{\link[igraph:strength]{igraph::strength()}} \item \code{centrality_edge_betweenness}: Wrapper for \code{\link[igraph:edge_betweenness]{igraph::edge_betweenness()}} \item \code{centrality_manual}: Manually specify your centrality score using the \code{netrankr} framework (\code{netrankr}) \item \code{centrality_closeness_harmonic}: centrality based on inverse shortest path (\code{netrankr}) \item \code{centrality_closeness_residual}: centrality based on 2-to-the-power-of negative shortest path (\code{netrankr}) \item \code{centrality_closeness_generalised}: centrality based on alpha-to-the-power-of negative shortest path (\code{netrankr}) \item \code{centrality_integration}: centrality based on \eqn{1 - (x - 1)/max(x)} transformation of shortest path (\code{netrankr}) \item \code{centrality_communicability}: centrality an exponential tranformation of walk counts (\code{netrankr}) \item \code{centrality_communicability_odd}: centrality an exponential tranformation of odd walk counts (\code{netrankr}) \item \code{centrality_communicability_even}: centrality an exponential tranformation of even walk counts (\code{netrankr}) \item \code{centrality_subgraph_odd}: subgraph centrality based on odd walk counts (\code{netrankr}) \item \code{centrality_subgraph_even}: subgraph centrality based on even walk counts (\code{netrankr}) \item \code{centrality_katz}: centrality based on walks penalizing distant nodes (\code{netrankr}) \item \code{centrality_betweenness_network}: Betweenness centrality based on network flow (\code{netrankr}) \item \code{centrality_betweenness_current}: Betweenness centrality based on current flow (\code{netrankr}) \item \code{centrality_betweenness_communicability}: Betweenness centrality based on communicability (\code{netrankr}) \item \code{centrality_betweenness_rsp_simple}: Betweenness centrality based on simple randomised shortest path dependencies (\code{netrankr}) \item \code{centrality_betweenness_rsp_net}: Betweenness centrality based on net randomised shortest path dependencies (\code{netrankr}) \item \code{centrality_information}: centrality based on inverse sum of resistance distance between nodes (\code{netrankr}) \item \code{centrality_decay}: based on a power transformation of the shortest path (\code{netrankr}) \item \code{centrality_random_walk}: centrality based on the inverse sum of expected random walk length between nodes (\code{netrankr}) \item \code{centrality_expected}: Expected centrality ranking based on exact rank probability (\code{netrankr}) }} \examples{ create_notable('bull') \%>\% activate(nodes) \%>\% mutate(importance = centrality_alpha()) # Most centrality measures are for nodes but not all create_notable('bull') \%>\% activate(edges) \%>\% mutate(importance = centrality_edge_betweenness()) } tidygraph/man/tbl_graph.Rd0000644000176200001440000001320313656212072015254 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/data_frame.R, R/data_tree.R, R/dendrogram.R, % R/graph.R, R/hclust.R, R/igraph.R, R/list.R, R/matrix.R, R/network.R, % R/phylo.R, R/tbl_graph.R \name{as_tbl_graph.data.frame} \alias{as_tbl_graph.data.frame} \alias{as_tbl_graph.Node} \alias{as_tbl_graph.dendrogram} \alias{as_tbl_graph.graphNEL} \alias{as_tbl_graph.graphAM} \alias{as_tbl_graph.graphBAM} \alias{as_tbl_graph.hclust} \alias{as_tbl_graph.igraph} \alias{as_tbl_graph.list} \alias{as_tbl_graph.matrix} \alias{as_tbl_graph.network} \alias{as_tbl_graph.phylo} \alias{as_tbl_graph.evonet} \alias{tbl_graph} \alias{as_tbl_graph} \alias{as_tbl_graph.default} \alias{is.tbl_graph} \title{A data structure for tidy graph manipulation} \usage{ \method{as_tbl_graph}{data.frame}(x, directed = TRUE, ...) \method{as_tbl_graph}{Node}(x, directed = TRUE, mode = "out", ...) \method{as_tbl_graph}{dendrogram}(x, directed = TRUE, mode = "out", ...) \method{as_tbl_graph}{graphNEL}(x, ...) \method{as_tbl_graph}{graphAM}(x, ...) \method{as_tbl_graph}{graphBAM}(x, ...) \method{as_tbl_graph}{hclust}(x, directed = TRUE, mode = "out", ...) \method{as_tbl_graph}{igraph}(x, ...) \method{as_tbl_graph}{list}(x, directed = TRUE, node_key = "name", ...) \method{as_tbl_graph}{matrix}(x, directed = TRUE, ...) \method{as_tbl_graph}{network}(x, ...) \method{as_tbl_graph}{phylo}(x, directed = NULL, ...) \method{as_tbl_graph}{evonet}(x, directed = TRUE, ...) tbl_graph(nodes = NULL, edges = NULL, directed = TRUE, node_key = "name") as_tbl_graph(x, ...) \method{as_tbl_graph}{default}(x, ...) is.tbl_graph(x) } \arguments{ \item{x}{An object convertible to a \code{tbl_graph}} \item{directed}{Should the constructed graph be directed (defaults to \code{TRUE})} \item{...}{Arguments passed on to the conversion function} \item{mode}{In case \code{directed = TRUE} should the edge direction be away from node or towards. Possible values are \code{"out"} (default) or \code{"in"}.} \item{node_key}{The name of the column in \code{nodes} that character represented \code{to} and \code{from} columns should be matched against. If \code{NA} the first column is always chosen. This setting has no effect if \code{to} and \code{from} are given as integers.} \item{nodes}{A \code{data.frame} containing information about the nodes in the graph. If \code{edges$to} and/or \code{edges$from} are characters then they will be matched to the column named according to \code{node_key} in nodes, if it exists. If not, they will be matched to the first column.} \item{edges}{A \code{data.frame} containing information about the edges in the graph. The terminal nodes of each edge must either be encoded in a \code{to} and \code{from} column, or in the two first columns, as integers. These integers refer to \code{nodes} index.} } \value{ A \code{tbl_graph} object } \description{ The \code{tbl_graph} class is a thin wrapper around an \code{igraph} object that provides methods for manipulating the graph using the tidy API. As it is just a subclass of \code{igraph} every igraph method will work as expected. A \code{grouped_tbl_graph} is the equivalent of a \code{grouped_df} where either the nodes or the edges has been grouped. The \code{grouped_tbl_graph} is not constructed directly but by using the \code{\link[=group_by]{group_by()}} verb. After creation of a \code{tbl_graph} the nodes are activated by default. The context can be changed using the \code{\link[=activate]{activate()}} verb and affects all subsequent operations. Changing context automatically drops any grouping. The current active context can always be extracted with \code{\link[=as_tibble]{as_tibble()}}, which drops the graph structure and just returns a \code{tbl_df} or a \code{grouped_df} depending on the state of the \code{tbl_graph}. The returned context can be overriden by using the \code{active} argument in \code{\link[=as_tibble]{as_tibble()}}. } \details{ Constructors are provided for most data structures that resembles networks. If a class provides an \code{\link[igraph:as.igraph]{igraph::as.igraph()}} method it is automatically supported. } \section{Methods (by generic)}{ \itemize{ \item \code{as_tbl_graph}: Method for edge table and set membership table \item \code{as_tbl_graph}: Method to deal with Node objects from the data.tree package \item \code{as_tbl_graph}: Method for dendrogram objects \item \code{as_tbl_graph}: Method for handling graphNEL objects from the graph package (on Bioconductor) \item \code{as_tbl_graph}: Method for handling graphAM objects from the graph package (on Bioconductor) \item \code{as_tbl_graph}: Method for handling graphBAM objects from the graph package (on Bioconductor) \item \code{as_tbl_graph}: Method for hclust objects \item \code{as_tbl_graph}: Method for igraph object. Simply subclasses the object into a \code{tbl_graph} \item \code{as_tbl_graph}: Method for adjacency lists and lists of node and edge tables \item \code{as_tbl_graph}: Method for edgelist, adjacency and incidence matrices \item \code{as_tbl_graph}: Method to handle network objects from the \code{network} package. Requires this packages to work. \item \code{as_tbl_graph}: Method for handling phylo objects from the ape package \item \code{as_tbl_graph}: Method for handling evonet objects from the ape package \item \code{as_tbl_graph}: Default method. tries to call \code{\link[igraph:as.igraph]{igraph::as.igraph()}} on the input. }} \examples{ rstat_nodes <- data.frame(name = c("Hadley", "David", "Romain", "Julia")) rstat_edges <- data.frame(from = c(1, 1, 1, 2, 3, 3, 4, 4, 4), to = c(2, 3, 4, 1, 1, 2, 1, 2, 3)) tbl_graph(nodes = rstat_nodes, edges = rstat_edges) } tidygraph/man/figures/0000755000176200001440000000000013545322217014470 5ustar liggesuserstidygraph/man/figures/logo.png0000644000176200001440000006177513545322217016156 0ustar liggesusersPNG  IHDRG?iCCPsRGB IEC61966-2.1(u+DQ?f0#d1i Qe$4Q7ofx7dl%6~- *kll zΛI==l6j=߹^t:^hN=n"3T{jxoժ~_k5 £焧gVs;J2>w-ʼn"[`kv&*8ZJRO qSyt%jf~Nbx7A&d 1Ȉ>2 +{ d%WYc $'j^㢫2RYW#>-VoCݳic~L4r~?D*kh݀݅M|"z m8BKoqس>'Z}p_'glx pHYs   IDATxwt[ו?A` %QQj.%ٱ-ۉg7I6JO&yL&ɛIL2qN\cVU-DI zb{Z^^"s.Q~}m2S  %@/p"&lɺrZ`MB h^kBHuɾN} wVp pJza2נ|>g/3yda$5dMM8$U #I\&`J"^ςHTEϒssXwV',E> RO4ӑ%6 2B2lCDL,,ѕI-em%bYG㉓|-SɹF1 Kl %IIz,]JVVSZ .M }d&@@]~,L $;Xׄ/tda礞 EifkJlA ΓtIF;n#Gt **+IMߢb4Ν ׮I5T63875#v.֖|j;%% dff=dppcs3mmmT*z=:>iSH1lD8^$g+˿EwPF2/`0PZRByc` ]]]Fџgggc0(+/'%%_~$%tdؑ%No)Ey ׬f?jfd4A0@N^tiveMɄd ,)J0 | p_|8T UZ@8߽+˕eM ?4E'z^ZZZ0\. %%%ϟuL7z-F#]]]BXOPe0IKK3G>yS ל=uj8#w{ZF9s_{zr?+V"-Ah4b6NI@EEŌ ~0 9),,P]MQQ8ko{f~kp 6Y\bC`Uzz:7n$CE W.u̫un.?܍b "p80FTTTPe0̘` &6~?VVRe0N޷Iiy<8[%}P*p:>|8".kYX&~\B:== 7Ӱ?i A/_>l ej*j)ٸ;шv}@v Dic{ V  Ɗ>tކa-/'{|,)J*VmLYyy#oIB"yLFq߽+MT:je̝ * u^"RR{f:;;tUUTuݴb2E(JJKK1TW;Z0Vdn7<6B0Hy«kWr%ٸq9+Oeldaݻ Zuxs  pJ%۶o0(<--LaR y ÃUUT zٻg`6oN\(2 aQ;NΆ Fў?wHyERNA4J`0P.aw`l@qqq\Dp .[6v#G~l߽+WlظqTQݳϦoN2~*vL&{++1 a`lj8kb06جV:Djj*۶oUx G!E%`x`28w,YYY:k6\f݊%,*vLUT룬 Ѯs*D/?υ dff2VRRZ:cjb դk4\.\.W^fRjb뙚ngppB18F ?A+c"R{W!Q/%%uO)YkhMzi 3Poo/IpU;Rjѣ_w=WkM6FX¢G~1Ddٳdfeq[͜l6F#: &388+S:qẙY߽K< ],Q8s O4i*8{ MMM  `κ{ S]MfVpw``pW`oOOn{_=,;g9;j)+/ԔG(k6܌3`JMVKNN+;?;W.ZXwR |PjQA,^dB@ @{{;Ξʕ+ 3cXhWpftT*TWWӃ2%N76)U'V%';;".+..VX¢[ kRT\,|]hXl٘զ&vB^ϊ+WSCVVִȕBAVVzP?%EWW& χV VMVvdZl:CR Tܱ*.RX¢G k֮XBQ8<.y̅cZ-gem-er0vFVt^p@J5"ثP(Z,^*&a+:,:;;V;w~:˴E?* ֬YCiaٿo m۶EE G3cD, 냽Z-pfod3j-k-VItj_S(^FZhr%jTYYI?9}4|>iiiTϙCmm-U7YTf/ `oJJ pfl7q\h4Z- R)ٺN#뭕jd/L˴fޥ $*Hك磦L___yy JKK`0Hggg.Bnn.\mjBRm4j>UB\E'VY#yEqWx "MIICYFz1G3{#E3w܄\uK3A\.,aQiH0,Y*V`0PQY9)P|>FGC"2wvtp?S%^Il>L'3=tTB7Hf7H0?v%S\ O&*|2gN;jx<;w,*2&)b `Ӊ&#CL:]: g IjO! Ό|2@nn.-b%\nlDV\)Ȉ&#Ʉc;ߏdl6cfo.Z$ȈR Iu5===d9Z]:--ZiZ1673?%deeعcDK„%,*| `em; |>L&gNYtR,YB^^ވްuaZZ+Vc+20nUr۱NR.LVVqܱDKB%,*r)T p9lS04kCXq1bd0H%)sP(yl6r**+QD={pfdYh22̀z&B\$|+A:::BF؜\.ZĊ+)**pv9wYwAFFfz~\ JEaa!jtZ-nWtd2aXbql4M$_f@s}RKLWY_CXt@7 PĈ3u+VPg[J˅);f$36;;;\̕+W(,*bݺuq=WF&=lf̟??E3{mujᶺ&ZZZ8{Le<b( |]6Sׅ/+c?5~^ׄkEXT)*fydel!da1ed&`l6b2Q3o8jrrrYEG:N'MMM\z5Ta_]ݩ^Aٳנ! `(Cc5XbPj1][kw}3r˗/GڊآMAaa F[A[IWvfJP1h7DB7ۊ Rc8n1"𭺇I(Y55{/F|«䣚{nCIT}y%j#X,,\($+W|2_^1&I1L;{{~?MW7OrO\z O[F߮~8I)lg"\.Zj;4[V^` ''GKd᛫$uz:%tvt`2YdhcG2+++LGGV VUQUU5@ 'N1Q} uMP} 5 gƋN'ʴPfQػg> 7𴕑^#G$*h4rQ E(7lz9c7=}/0Ȋ勈,*q^=  %$сCɢ"p tv:::&+iii̝79s{z줳NT! "/W >aT`l/6ktҸȈEss3Ο'''o%s;LF#z{ n%(UۑJT*Uŋ.*}}}E22ɠOZKmvTiR %pX, 1Hж/T-J( RF&D>DwJ"Ym1_Lf[k+@ܸkdd&;;mmm _ 4%&_IkJ=" .HfY$c;Y\?p*D̤JKbnR$u2T*v;I԰~.) BKJCiP'-p,#s=lYcxE(ii&TDB%gXY$#,"ۡNHV_U%;L[ E-G?XG4!a#O61gY/jbÁliqv$^Bl6bV|b|2bl5щ[ e[65\LDJLL;" &-:1tXC)rng> &N*DC?$mF tC`˖+##%iiia۩F)m`u+ɜSM3&SēC8⯨k)B^;C۹G(^bkKdSPPV9D[&8 l k0FMI}J|N**`Y)z k?iu\9{nrssr 22Af3')t׬yp<TAw׀]WAcZՇ0V_C. vwIII]};KQZʉ'd2hѢ)>-d$ퟌTVVrFR*M&̣䖛Yf Nsn^4 kbŠz`5aPRKn5v_e0EkK ,H>:++/]LPTE;._"AZ[[pRBAFY%z|uS5 Xj, ywRTG?xȌŽdq1F")KBػxsmk$OFf4* 3yQ#1%(ODR!$1{;;;zdh4 BrRW&XQO&ChBi+D$bK=~Lyp`՚$$HXҢFL8bK.=3LR9Q*++#--MA#>1Z,53&\d{L撕Cî1Xj N^s V}vAi৊0*}A뷏s۝x9)$~~8D;bOqo{FF~ҧ`ٳƘb*R^=K/ǣ#bi w߹)^k?^::ʹvcl "--cnAhb=GxEZۻF qx:32عfV.OӵV?K{-lZexkOQVRNLq_:ij/:jGNo_K,_:v{' Sk??@Hv{vC.RY^BifޜN>՟w:lLAx[_658t _`ɢ9mMܼa#pE{p?>e}o|?? jHr{x{ ~;L)7* ޽x<nhfh4:E{{;j.]\=/ {| HKUUIZ,%&]__PR?z-0. gµKҞ#}m~|훿v|-}4w_5P(1'Cہ8zghD+?lqƕ#Q(b{ Ͼ[6E7ܴ?**U*b|dD@C- 3:NMHJ|r$I^~V,~;>QT6B`}[`LD{or6^6"lιnMxHuϟ1m(/^ۇB8( 6_7c G(.'+sb2v}A~?4GINN渏y ulii񈥸7bOf'IY Db&1̂Y,%G8Olrcߺi%*U*{q{VtWg7PT0 JJUeȯ)m78V񯧬$g;Uc#:^5KC.)\ad{'d6a))-EVt:1~b)xi*ˋ)/o!3SJ 1'Ͽt;1/\@X#+/b6GKv8-Mŝ6pQǮ,t2xŽf)x$EXJ%->.^k?^?څ-m{SsͶX,zݭK+^eْ o<23{B)qOEE#U^DKAG 5U,Z`Z+F@_9-F ej{8_fS;v'-O]rU~ǿ+?㱄 J(LLF+tO,**Jj|2כG7Rbk T޲q%i텃8۫_&kW/$?//|}ilpF<>*ˋpѮ"n/G?~ۼ *ˋՏ&"b[T8vtLhNh Q_xl=FȈN}w Kکhf"qfi*GEN6\|яDO0O|t'_U7,I<.,ITT FvN ϣ Kdx%65Nh䄷@f[۶o˳dmkQ(xȧ?9v93`jrs1}q#`xE;KZ``0ts,O~\)"Ž\m LdRB˗/pN p,ySA7f%pt#-m]TUpeܶ!kO羻oeuBA^kYdWKJ.]6`ɾ:a'$iii燖J 1XPGQcjZs|P; kmSl i'N]ß6}NV.@TPXpQIGJ&`޻oA91*/??Z'.,CRFZNc?,<n >Di~W7J ;gzxċ8Cv\oOx.,ko/cth+CNIl}S2y(yi^=Ƅ9ՍN;nj?~;y$ LB å g/':^?Փ|h =};nqoH jC`BФcyQ<̫#r7xbs5ʊb>wßyeL#M&_20|2 lu5nmJ%Ǟxc'.Xy}>js@+F=3nQ;?o66xs:Ij&MdwXu@5w\R!gNxz-\."'wqΜk 8XfX*񊉽ߠLN tc,^8L-87?LMG}O̮OB^x0>4T21r9pwsb~-2uwS/ē/IlnZm̹3:볓%}U P@F>f9:}+/rSf'.3ϲr|۱;\xCWw/=%#K?Zۺٻ8Nsh R .\}?xoY=nIF m;xCGΆ@NfW݋ CN!#C3զ&RSSYp!gCuo^ߍ 7zJOOǏCVu6.\h#cA'1[qZ֯]ʭVxs ^ޮP?>4cWg^'p<(Y%~RV, l7 mFv?|#t>+'޲AH>m7Ex=4]x[px{ӯ0hwΚE,[2-)y꯯ˇ1Xjw1o,ӦywK8tL ?%pYzh<'~#oXI;|ۨn@{[KenEES+SR 280P*l۾] Xmdg".0 b$33#n<-6뱿/|(ڝPQ^4A0paP&'olݸR@;n7",l}@30h'MBgw:zٻg`6o&33F65Q\\ڛf1aIZb)}8w%3++ib.˷[}9IEIIQNI,M$>5sjSݸ\ %-x;VTbs (CYP>ՇIKSQ"o[Fq e vB$)24r}Uii)iii8,KoTw߹1\Jk{7 ǗݬT*7BTqSZZr/MIpå +J&;'_KC$-^O^͕&˗#5ur(e%b)RxꚺdHLdeo/m7R2ZZso8}ǃ2ryGj$v^X$\X"M+ȠAz#l/Dވznw_aۖXv٤pk"^, KdR:Sdb2!$il.]nĩK9v^|O}oˤsJK y|q.>j>42 7:$#fO[[0qi墫Ҳ%Da҇M|=wOz<;1mv4𱏼k (0[l4:d>x?wl]"c7Q.56ē/xa553Xtuuxh4NWe0N[k+ .r|<$TXÝ o1L7,_Z9rnm =45֧8wg_x>w Nc*nTc1MG^/O>"޽s}iF6Sz< ]^^YY  >j555ʘțsB|Q6~?MAIGGGL",Khddn$"۠ɴƉAZ[clv?E$⡢듾ņtf*&i6^ɅebXIMMEFcɡ$WraKUUMUDqQ+#3zQtMtq2XjMM>QF&ٴ㓹yydegAz=,TXb)Ð l%3!&J\?$ +{pp+@r, #%Z"iqd)rH*s|&ddE$R9dJm%D^fNe>v6tvOVX3J$2bXj)L>QF&YDEE'RףP*ŽR LX*",Dd ɩ1E"{E?bx(*.nEBWw7F3nc9KU+t͜&S,+Cə2 CϗWtaKZQ(X$'$ݎbATN9t",UX"RC= b>Q"ҒICWTa䖔'ĸ7m ̌gfNE^ф1I3ɒ~ IDATNE疑t l333OȜR&,mC̜Z,X+31(*EXAf%j!OMdf,}}}S6s,U",gXjfN%UA\ih|RJeC{2)Z_C}*RRJK`ReeKC@KK ,^UKbNk׮a2),(с84{ꚘȘ75(]@hKj^+:}6K,zΜ\dx7xl\ =عa5aouMwU,k | 00Ζ)A?\+HI( p\IQ}rEzŇ_~Q҄m;9 #Cx/c~t<SLiY**(#39T*ʒП8<}pw^a+t"vӹ+';L(J^/6@ 7IF&^.^nZñc{k޽3+O[9atR_C0+ӱoXDK6n.nl>qPp&\`AYYzTv;nBss3tt 2J%^'ځ L6s2 xgΜl6P̙;UQ?jEJ Kb^Bx 0Ai+Wwcekڸ/P]]Mff&---tT*t:ghT i+#q9{,+kkE]9{,MW000 hb/_Naa᨟n6rXD}ì)rdg>g-nSm===G gYYT{v\.]]F|^/ZV4V+NUZIob9"up:\mj]srQLYY.58NuuuiMWXHDWWpOZ-Iٌ7یϧ&TVq6w:o[NŷhD[-𦥇[7Z灯{̏߱~-{$">Dށ\nn.fA&%$Ioogrr*jvq:999?q" O>&s؊ :6&odٔm;S+ /_u9'Z1D3&P/Ը֪~ ={P` 6v\NOSUUYf-ƣy6mpssBHWxhؾ"^GGG"\);;Zjf6$c۹sv~rǿњ@ Pׁ=KKFw_سw/555 'r"^WyA읜|{;$k`=C  t:8H$@EFGblْ v;i!QI% vBL%g^7/Y'n6&$(eJnfhdllqz=6mZ?e˖%-Յ`R6ckfIm>}:[TuE ,>Kn7yyIΦB&3{^|L皽Y&nL\233CwWHv-\$<OԌ{WiGCC{Ì]~nww~&!*&aE\QM2`0PRR 2pp8" 0rsqW闫vq:8hlliDQ Q JdlΝHIi5tw) kH".D[nwJ:<*kˑ'Ol#{u:rBi/h{444PRRDk<|nFFFNjΦM8N$W^V-)>`0x䰉Y`Ii)2p8tRJX!aE\ ۷O)ʔj|>r.V&++ccԚ޽[5wnnSXX$SryHt8LT+++!* o~U?`t:B3r5B$nn޼9o^^ P]Sj8ɭ7c кR+,,˟߿_cIqDT@.V7nTbr:LT|y%E2@X@ȉj˃CҳlX,C;v` /e.7n~3H]!-Pt\_PEEEHdfH >͆`0m9ػwC|>$QRRBuM aL&O)nk a9Rp\.+#4z=Pёr6lTl.6p8ee4V<~[X^OuM {졾͆Ǭb1ٸ9} dVh6ֶV=QŘv8_NĉJ4OvDeӴXfp3e3v h?wI8ꫪ_ n77o܈//YN:hd̊%ƙWl~\.T^dee1 2(@ %栴p8x4D~~#%52i6ђTTTsN)-q#[mSu~sE忓(,YiM."Uh4r:C@"{Ao0(5{=b_ rsۡrnww300D uu455!‚$-s  p㙨O3QT BEN"ϟ'49C'?/]ͨJԢDg"#KqQYYg8vsusr8q8:>_m d%r!`:NUixx OOSDo]NGaQf38].v;88hE?Q3{а}{e >C۶neJ%8g]& d%@t^T^q&&&p| /ƢeAO#>R]4 r9%gpk׮kWb!\~l$i墸$+Ę-e0$\`0PZZPWGII J~N'P^3{B͓̌'O wqΏˈ͛5S=u<a K yz: pWʼn'AlnW A˪V1DQ߯DƚL&jkk1 B«D"ϝcjjͪׯ]ջ ET` HT\~NsСn1$Iŋst""yLA`f."oLYX.8)gxLTS6@Xu3gD~$t:. C!E"x<B <^OQQffo05׺4&${{3vB6cfEE)w291A}}=);lQ;DV%rgz=,+155Esz^ٙgvzzO\^ɓ)ghhkWD_pC^|!IҋNҲ\ @1r?fń0{ mLxzN|3eHN@NVyxxxW*^İC`a%$9p?h4rɴmOyfouu5B]_6c癱5fsCaν333K5m0W^%/sSl ɬzaE\)|ڵ ,x033D|>ҲyjQB$v#1cthȽw)**/yLT?EֈXZS%.^rX*##nl6Sk6LNNbٰlsVVV"BJV r "_+Wb+TsY3=T {p8Lѣ)uajj ݎMPz2zX,3vÆ 2z|ɓ'Z'*~f bv!Μ2$idcYY cz&C!FGGD"i+rDi(~Kᅬ^"PB &<}T1{F#՘!c^߯DƆћ&rL" ގ,W:;cC_ZNrPf4innNzo0رT$9Qfffp8晽eٻ9^)vcENj0 UUU2Yޞ\.bPk6S[[vW1cvB@tE6c]M Ip<IJgglu#2f I|TD8N(ÔPA B;ݲ%j g1f5nj5fܶy  p59~DQQPkĺk[k.Qq9n2h>r$!q~:n7/\ ~DQTr6nKR${`o[XXPWfl<^*6oqq3vzXgxd2q%e W^Mzx^ ik%{fc٘X^Oݔp aHϕ f,w6e3bq2^/ȶ8KSj3Yk$?7?kx5z^k NG~~s;<FkUTccx^z=555K>|:XcBrlL墢b%``<?6TkHĀͽ{裏E$ I&&&EkF֘T~:X[XZ FCHKˢ]G^._"Pj 2X |,+.SZrrr0 kk ô;G8K/-`jj g,Nk&,2?%;;#G.]bll]wcVL:Pbݠx"QAZ-l6޹Caa!/s7OT:O[N^фeֶBr$;;#--b4ᰒ8;~eRys[JE r%cc޽$**KtMX!ˏerьW+(F#rdleGE+qB)6w\>\4aYk[kѼC˽wEj>ϥxmも%2V-(2=]np8v}D*QQ}d3k[k1Qq9CKK]fJe tjYbe2Oc>:::bk0,,.999Y@\ ;&.)aCE94@zO7 0 110@;$E(zL(ee+pr4aYk[k $} l660AX HYSGPH&L7J(27 ;", L;vYD&QQI ā4ܼۢUQQ.R 4aIk[kpGsqRGne%Gl(_<KLJZ֚Kt*AJҘLס8Jh’ On gDRm:pXjPQ윜lbpKWUnJ$zO[~~/5\h’zJfW̧#Hq };TO hAVDM+>1/Cm{ll5K75:ۜϵ_zpPJh8ZD8S?m> D[ uuTb3Vmbfקe{߇'Orf35YǏ\ZFU=FA@{z_d6MZPJmH]zkMXZ >|kGkʏMr^ z]a${1H. IDAT"hi)l;x.cǽ5f4aYMXch駤Hd pe$U4sa K|<<@|etS:ZE \hmXz˫4VI{wS>4@1Mktw8u"ZE#9S* Kh??"ZC-ka7JK~/T&s@#4aK/ᥞ7T8z5Z_ٯI4aIKV_0qpoXzH:Eh?RxΟ~?[h’ B<| v 3u;aG[M5V%IVIEKlP˿|҃֐, 4aYztIqD"z% KjM#\w_!sNl K 8PI4YC48[ZԕZO 8n\h nmk|?I|IENDB`tidygraph/man/reexports.Rd0000644000176200001440000000501013656175002015343 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/arrange.R, R/distinct.R, R/filter.R, % R/group_by.R, R/igraph.R, R/joins.R, R/mutate.R, R/pull.R, R/rename.R, % R/sample_frac.R, R/sample_n.R, R/select.R, R/slice.R, R/tbl_graph.R, % R/tibble.R \docType{import} \name{reexports} \alias{reexports} \alias{arrange} \alias{distinct} \alias{filter} \alias{top_n} \alias{group_by} \alias{ungroup} \alias{group_size} \alias{n_groups} \alias{groups} \alias{group_vars} \alias{group_data} \alias{group_indices} \alias{group_keys} \alias{as.igraph} \alias{left_join} \alias{right_join} \alias{inner_join} \alias{full_join} \alias{semi_join} \alias{anti_join} \alias{mutate} \alias{transmute} \alias{mutate_all} \alias{mutate_at} \alias{n} \alias{pull} \alias{rename} \alias{sample_frac} \alias{sample_n} \alias{select} \alias{contains} \alias{ends_with} \alias{everything} \alias{matches} \alias{num_range} \alias{one_of} \alias{starts_with} \alias{slice} \alias{tbl_vars} \alias{as_tibble} \alias{\%>\%} \title{Objects exported from other packages} \keyword{internal} \description{ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ \item{dplyr}{\code{\link[dplyr]{anti_join}}, \code{\link[dplyr]{arrange}}, \code{\link[dplyr]{contains}}, \code{\link[dplyr]{distinct}}, \code{\link[dplyr]{ends_with}}, \code{\link[dplyr]{everything}}, \code{\link[dplyr]{filter}}, \code{\link[dplyr]{full_join}}, \code{\link[dplyr]{group_by}}, \code{\link[dplyr]{group_data}}, \code{\link[dplyr]{group_indices}}, \code{\link[dplyr]{group_keys}}, \code{\link[dplyr]{group_size}}, \code{\link[dplyr]{group_vars}}, \code{\link[dplyr]{groups}}, \code{\link[dplyr]{inner_join}}, \code{\link[dplyr]{left_join}}, \code{\link[dplyr]{matches}}, \code{\link[dplyr]{mutate}}, \code{\link[dplyr]{mutate_all}}, \code{\link[dplyr]{mutate_at}}, \code{\link[dplyr]{n}}, \code{\link[dplyr]{n_groups}}, \code{\link[dplyr]{num_range}}, \code{\link[dplyr]{one_of}}, \code{\link[dplyr]{pull}}, \code{\link[dplyr]{rename}}, \code{\link[dplyr]{right_join}}, \code{\link[dplyr]{sample_frac}}, \code{\link[dplyr]{sample_n}}, \code{\link[dplyr]{select}}, \code{\link[dplyr]{semi_join}}, \code{\link[dplyr]{slice}}, \code{\link[dplyr]{starts_with}}, \code{\link[dplyr]{tbl_vars}}, \code{\link[dplyr]{top_n}}, \code{\link[dplyr]{transmute}}, \code{\link[dplyr]{ungroup}}} \item{igraph}{\code{\link[igraph]{as.igraph}}} \item{magrittr}{\code{\link[magrittr]{\%>\%}}} \item{tibble}{\code{\link[tibble]{as_tibble}}} }} tidygraph/man/create_graphs.Rd0000644000176200001440000000644313654774463016150 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/create.R \name{create_graphs} \alias{create_graphs} \alias{create_ring} \alias{create_path} \alias{create_chordal_ring} \alias{create_de_bruijn} \alias{create_empty} \alias{create_bipartite} \alias{create_citation} \alias{create_complete} \alias{create_notable} \alias{create_kautz} \alias{create_lattice} \alias{create_star} \alias{create_tree} \title{Create different types of well-defined graphs} \usage{ create_ring(n, directed = FALSE, mutual = FALSE) create_path(n, directed = FALSE, mutual = FALSE) create_chordal_ring(n, w) create_de_bruijn(alphabet_size, label_size) create_empty(n, directed = FALSE) create_bipartite(n1, n2, directed = FALSE, mode = "out") create_citation(n) create_complete(n) create_notable(name) create_kautz(alphabet_size, label_size) create_lattice(dim, directed = FALSE, mutual = FALSE, circular = FALSE) create_star(n, directed = FALSE, mutual = FALSE, mode = "out") create_tree(n, children, directed = TRUE, mode = "out") } \arguments{ \item{n, n1, n2}{The number of nodes in the graph} \item{directed}{Should the graph be directed} \item{mutual}{Should mutual edges be created in case of the graph being directed} \item{w}{A matrix specifying the additional edges in the chordan ring. See \code{\link[igraph:make_chordal_ring]{igraph::make_chordal_ring()}}} \item{alphabet_size}{The number of unique letters in the alphabet used for the graph} \item{label_size}{The number of characters in each node} \item{mode}{In case of a directed, non-mutual, graph should the edges flow \code{'out'} or \code{'in'}} \item{name}{The name of a notable graph. See a complete list in \code{\link[igraph:make_graph]{igraph::make_graph()}}} \item{dim}{The dimensions of the lattice} \item{circular}{Should each dimension in the lattice wrap around} \item{children}{The number of children each node has in the tree (if possible)} } \value{ A tbl_graph } \description{ These functions creates a long list of different types of well-defined graphs, that is, their structure is not based on any randomisation. All of these functions are shallow wrappers around a range of \verb{igraph::make_*} functions but returns \code{tbl_graph} rather than \code{igraph} objects. } \section{Functions}{ \itemize{ \item \code{create_ring}: Create a simple ring graph \item \code{create_path}: Create a simple path \item \code{create_chordal_ring}: Create a chordal ring \item \code{create_de_bruijn}: Create a de Bruijn graph with the specified alphabet and label size \item \code{create_empty}: Create a graph with no edges \item \code{create_bipartite}: Create a full bipartite graph \item \code{create_citation}: Create a full citation graph \item \code{create_complete}: Create a complete graph (a graph where all nodes are connected) \item \code{create_notable}: Create a graph based on its name. See \code{\link[igraph:make_graph]{igraph::make_graph()}} \item \code{create_kautz}: Create a Kautz graph with the specified alphabet and label size \item \code{create_lattice}: Create a multidimensional grid of nodes \item \code{create_star}: Create a star graph (A single node in the center connected to all other nodes) \item \code{create_tree}: Create a tree graph }} \examples{ # Create a complete graph with 10 nodes create_complete(10) } tidygraph/man/node_topology.Rd0000644000176200001440000000220713545322217016175 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/node_topology.R \name{node_topology} \alias{node_topology} \alias{node_dominator} \alias{node_topo_order} \title{Node properties related to the graph topology} \usage{ node_dominator(root, mode = "out") node_topo_order(mode = "out") } \arguments{ \item{root}{The node to start the dominator search from} \item{mode}{How should edges be followed. Either \code{'in'} or \code{'out'}} } \value{ A vector of the same length as the number of nodes in the graph } \description{ These functions calculate properties that are dependent on the overall topology of the graph. } \section{Functions}{ \itemize{ \item \code{node_dominator}: Get the immediate dominator of each node. Wraps \code{\link[igraph:dominator_tree]{igraph::dominator_tree()}}. \item \code{node_topo_order}: Get the topological order of nodes in a DAG. Wraps \code{\link[igraph:topo_sort]{igraph::topo_sort()}}. }} \examples{ # Sort a graph based on its topological order create_tree(10, 2) \%>\% arrange(sample(graph_order())) \%>\% mutate(old_ind = seq_len(graph_order())) \%>\% arrange(node_topo_order()) } tidygraph/man/node_measures.Rd0000644000176200001440000000457213656245051016157 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/node.R \name{node_measures} \alias{node_measures} \alias{node_eccentricity} \alias{node_constraint} \alias{node_coreness} \alias{node_diversity} \alias{node_bridging_score} \alias{node_effective_network_size} \alias{node_connectivity_impact} \alias{node_closeness_impact} \alias{node_fareness_impact} \title{Querying node measures} \usage{ node_eccentricity(mode = "out") node_constraint(weights = NULL) node_coreness(mode = "out") node_diversity(weights) node_bridging_score() node_effective_network_size() node_connectivity_impact() node_closeness_impact() node_fareness_impact() } \arguments{ \item{mode}{The way edges should be followed in the case of directed graphs.} \item{weights}{The weights to use for each node during calculation} } \value{ A numeric vector of the same length as the number of nodes in the graph. } \description{ These functions are a collection of node measures that do not really fall into the class of \link{centrality} measures. For lack of a better place they are collected under the \verb{node_*} umbrella of functions. } \section{Functions}{ \itemize{ \item \code{node_eccentricity}: measure the maximum shortest path to all other nodes in the graph \item \code{node_constraint}: measures Burts constraint of the node. See \code{\link[igraph:constraint]{igraph::constraint()}} \item \code{node_coreness}: measures the coreness of each node. See \code{\link[igraph:coreness]{igraph::coreness()}} \item \code{node_diversity}: measures the diversity of the node. See \code{\link[igraph:diversity]{igraph::diversity()}} \item \code{node_bridging_score}: measures Valente's Bridging measures for detecting structural bridges (\code{influenceR}) \item \code{node_effective_network_size}: measures Burt's Effective Network Size indicating access to structural holes in the network (\code{influenceR}) \item \code{node_connectivity_impact}: measures the impact on connectivity when removing the node (\code{NetSwan}) \item \code{node_closeness_impact}: measures the impact on closeness when removing the node (\code{NetSwan}) \item \code{node_fareness_impact}: measures the impact on fareness (distance between all node pairs) when removing the node (\code{NetSwan}) }} \examples{ # Calculate Burt's Constraint for each node create_notable('meredith') \%>\% mutate(b_constraint = node_constraint()) } tidygraph/man/context_accessors.Rd0000644000176200001440000000230613545322217017045 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/context.R \name{context_accessors} \alias{context_accessors} \alias{.G} \alias{.N} \alias{.E} \title{Access graph, nodes, and edges directly inside verbs} \usage{ .G() .N() .E() } \value{ Either a \code{tbl_graph} (\code{.G()}) or a \code{tibble} (\code{.N()}) } \description{ These three functions makes it possible to directly access either the node data, the edge data or the graph itself while computing inside verbs. It is e.g. possible to add an attribute from the node data to the edges based on the terminating nodes of the edge, or extract some statistics from the graph itself to use in computations. } \section{Functions}{ \itemize{ \item \code{.G}: Get the tbl_graph you're currently working on \item \code{.N}: Get the nodes data from the graph you're currently working on \item \code{.E}: Get the edges data from the graph you're currently working on }} \examples{ # Get data from the nodes while computing for the edges create_notable('bull') \%>\% activate(nodes) \%>\% mutate(centrality = centrality_power()) \%>\% activate(edges) \%>\% mutate(mean_centrality = (.N()$centrality[from] + .N()$centrality[to])/2) } tidygraph/man/map_bfs.Rd0000644000176200001440000000666613654774463014757 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/map.R \name{map_bfs} \alias{map_bfs} \alias{map_bfs_lgl} \alias{map_bfs_chr} \alias{map_bfs_int} \alias{map_bfs_dbl} \title{Apply a function to nodes in the order of a breath first search} \usage{ map_bfs(root, mode = "out", unreachable = FALSE, .f, ...) map_bfs_lgl(root, mode = "out", unreachable = FALSE, .f, ...) map_bfs_chr(root, mode = "out", unreachable = FALSE, .f, ...) map_bfs_int(root, mode = "out", unreachable = FALSE, .f, ...) map_bfs_dbl(root, mode = "out", unreachable = FALSE, .f, ...) } \arguments{ \item{root}{The node to start the search from} \item{mode}{How should edges be followed? \code{'out'} only follows outbound edges, \code{'in'} only follows inbound edges, and \code{'all'} follows all edges. This parameter is ignored for undirected graphs.} \item{unreachable}{Should the search jump to an unvisited node if the search is completed without visiting all nodes.} \item{.f}{A function to map over all nodes. See Details} \item{...}{Additional parameters to pass to \code{.f}} } \value{ \code{map_bfs()} returns a list of the same length as the number of nodes in the graph, in the order matching the node order in the graph (that is, not in the order they are called). \verb{map_bfs_*()} tries to coerce its result into a vector of the classes \code{logical} (\code{map_bfs_lgl}), \code{character} (\code{map_bfs_chr}), \code{integer} (\code{map_bfs_int}), or \code{double} (\code{map_bfs_dbl}). These functions will throw an error if they are unsuccesful, so they are type safe. } \description{ These functions allow you to map over the nodes in a graph, by first performing a breath first search on the graph and then mapping over each node in the order they are visited. The mapping function will have access to the result and search statistics for all the nodes between itself and the root in the search. To map over the nodes in the reverse direction use \code{\link[=map_bfs_back]{map_bfs_back()}}. } \details{ The function provided to \code{.f} will be called with the following arguments in addition to those supplied through \code{...}: \itemize{ \item \code{graph}: The full \code{tbl_graph} object \item \code{node}: The index of the node currently mapped over \item \code{rank}: The rank of the node in the search \item \code{parent}: The index of the node that led to the current node \item \code{before}: The index of the node that was visited before the current node \item \code{after}: The index of the node that was visited after the current node. \item \code{dist}: The distance of the current node from the root \item \code{path}: A table containing \code{node}, \code{rank}, \code{parent}, \code{before}, \code{after}, \code{dist}, and \code{result} columns giving the values for each node leading to the current node. The \code{result} column will contain the result of the mapping of each node in a list. } Instead of spelling out all of these in the function it is possible to simply name the ones needed and use \code{...} to catch the rest. } \examples{ # Accumulate values along a search create_tree(40, children = 3, directed = TRUE) \%>\% mutate(value = round(runif(40)*100)) \%>\% mutate(value_acc = map_bfs_dbl(node_is_root(), .f = function(node, path, ...) { sum(.N()$value[c(node, path$node)]) })) } \seealso{ Other node map functions: \code{\link{map_bfs_back}()}, \code{\link{map_dfs_back}()}, \code{\link{map_dfs}()} } \concept{node map functions} tidygraph/man/component_games.Rd0000644000176200001440000000470213654774463016513 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/play.R \name{component_games} \alias{component_games} \alias{play_blocks} \alias{play_blocks_hierarchy} \alias{play_islands} \alias{play_smallworld} \title{Graph games based on connected components} \usage{ play_blocks(n, size_blocks, p_between, directed = TRUE, loops = FALSE) play_blocks_hierarchy(n, size_blocks, rho, p_within, p_between) play_islands(n_islands, size_islands, p_within, m_between) play_smallworld( n_dim, dim_size, order, p_rewire, loops = FALSE, multiple = FALSE ) } \arguments{ \item{n}{The number of nodes in the graph.} \item{size_blocks}{The number of vertices in each block} \item{p_between, p_within}{The probability of edges within and between groups/blocks} \item{directed}{Should the resulting graph be directed} \item{loops}{Are loop edges allowed} \item{rho}{The fraction of vertices per cluster} \item{n_islands}{The number of densely connected islands} \item{size_islands}{The number of nodes in each island} \item{m_between}{The number of edges between groups/islands} \item{n_dim, dim_size}{The dimension and size of the starting lattice} \item{order}{The neighborhood size to create connections from} \item{p_rewire}{The rewiring probability of edges} \item{multiple}{Are multiple edges allowed} } \value{ A tbl_graph object } \description{ This set of graph creation algorithms simulate the topology by, in some way, connecting subgraphs. The nature of their algorithm is described in detail at the linked igraph documentation. } \section{Functions}{ \itemize{ \item \code{play_blocks}: Create graphs by sampling from stochastic block model. See \code{\link[igraph:sample_sbm]{igraph::sample_sbm()}} \item \code{play_blocks_hierarchy}: Create graphs by sampling from the hierarchical stochastic block model. See \code{\link[igraph:sample_hierarchical_sbm]{igraph::sample_hierarchical_sbm()}} \item \code{play_islands}: Create graphs with fixed size and edge probability of subgraphs as well as fixed edge count between subgraphs. See \code{\link[igraph:sample_islands]{igraph::sample_islands()}} \item \code{play_smallworld}: Create graphs based on the Watts-Strogatz small- world model. See \code{\link[igraph:sample_smallworld]{igraph::sample_smallworld()}} }} \examples{ plot(play_islands(4, 10, 0.7, 3)) } \seealso{ Other graph games: \code{\link{evolution_games}}, \code{\link{sampling_games}}, \code{\link{type_games}} } \concept{graph games} tidygraph/man/with_graph.Rd0000644000176200001440000000154513545322217015454 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/context.R \name{with_graph} \alias{with_graph} \title{Evaluate a tidygraph algorithm in the context of a graph} \usage{ with_graph(graph, expr) } \arguments{ \item{graph}{The \code{tbl_graph} to use as context} \item{expr}{The expression to evaluate} } \value{ The value of \code{expr} } \description{ All tidygraph algorithms are meant to be called inside tidygraph verbs such as \code{mutate()}, where the graph that is currently being worked on is known and thus not needed as an argument to the function. In the off chance that you want to use an algorithm outside of the tidygraph framework you can use \code{with_graph()} to set the graph context temporarily while the algorithm is being evaluated. } \examples{ gr <- play_erdos_renyi(10, 0.3) with_graph(gr, centrality_degree()) } tidygraph/man/node_types.Rd0000644000176200001440000000512613654774463015507 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/node.R \name{node_types} \alias{node_types} \alias{node_is_cut} \alias{node_is_root} \alias{node_is_leaf} \alias{node_is_sink} \alias{node_is_source} \alias{node_is_isolated} \alias{node_is_universal} \alias{node_is_simplical} \alias{node_is_center} \alias{node_is_adjacent} \alias{node_is_keyplayer} \title{Querying node types} \usage{ node_is_cut() node_is_root() node_is_leaf() node_is_sink() node_is_source() node_is_isolated() node_is_universal(mode = "out") node_is_simplical(mode = "out") node_is_center(mode = "out") node_is_adjacent(to, mode = "all", include_to = TRUE) node_is_keyplayer(k, p = 0, tol = 1e-04, maxsec = 120, roundsec = 30) } \arguments{ \item{mode}{The way edges should be followed in the case of directed graphs.} \item{to}{The nodes to test for adjacency to} \item{include_to}{Should the nodes in \code{to} be marked as adjacent as well} \item{k}{The number of keyplayers to identify} \item{p}{The probability to accept a lesser state} \item{tol}{Optimisation tolerance, below which the optimisation will stop} \item{maxsec}{The total computation budget for the optimization, in seconds} \item{roundsec}{Number of seconds in between synchronizing workers' answer} } \value{ A logical vector of the same length as the number of nodes in the graph. } \description{ These functions all lets the user query whether each node is of a certain type. All of the functions returns a logical vector indicating whether the node is of the type in question. Do note that the types are not mutually exclusive and that nodes can thus be of multiple types. } \section{Functions}{ \itemize{ \item \code{node_is_cut}: is the node a cut node (articaultion node) \item \code{node_is_root}: is the node a root in a tree \item \code{node_is_leaf}: is the node a leaf in a tree \item \code{node_is_sink}: does the node only have incomming edges \item \code{node_is_source}: does the node only have outgoing edges \item \code{node_is_isolated}: is the node unconnected \item \code{node_is_universal}: is the node connected to all other nodes in the graph \item \code{node_is_simplical}: are all the neighbors of the node connected \item \code{node_is_center}: does the node have the minimal eccentricity in the graph \item \code{node_is_adjacent}: is a node adjacent to any of the nodes given in \code{to} \item \code{node_is_keyplayer}: Is a node part of the keyplayers in the graph (\code{influenceR}) }} \examples{ # Find the root and leafs in a tree create_tree(40, 2) \%>\% mutate(root = node_is_root(), leaf = node_is_leaf()) } tidygraph/man/graph_types.Rd0000644000176200001440000000441013545322217015637 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/graph_types.R \name{graph_types} \alias{graph_types} \alias{graph_is_simple} \alias{graph_is_directed} \alias{graph_is_bipartite} \alias{graph_is_connected} \alias{graph_is_tree} \alias{graph_is_forest} \alias{graph_is_dag} \alias{graph_is_chordal} \alias{graph_is_complete} \alias{graph_is_isomorphic_to} \alias{graph_is_subgraph_isomorphic_to} \title{Querying graph types} \usage{ graph_is_simple() graph_is_directed() graph_is_bipartite() graph_is_connected() graph_is_tree() graph_is_forest() graph_is_dag() graph_is_chordal() graph_is_complete() graph_is_isomorphic_to(graph, method = "auto", ...) graph_is_subgraph_isomorphic_to(graph, method = "auto", ...) } \arguments{ \item{graph}{The graph to compare structure to} \item{method}{The algorithm to use for comparison} \item{...}{Arguments passed on to the comparison methods. See \code{\link[igraph:is_isomorphic_to]{igraph::is_isomorphic_to()}} and \code{\link[igraph:is_subgraph_isomorphic_to]{igraph::is_subgraph_isomorphic_to()}}} } \value{ A logical scalar } \description{ This set of functions lets the user query different aspects of the graph itself. They are all concerned with wether the graph implements certain properties and will all return a logical scalar. } \section{Functions}{ \itemize{ \item \code{graph_is_simple}: Is the graph simple (no parallel edges) \item \code{graph_is_directed}: Is the graph directed \item \code{graph_is_bipartite}: Is the graph bipartite \item \code{graph_is_connected}: Is the graph connected \item \code{graph_is_tree}: Is the graph a tree \item \code{graph_is_forest}: Is the graph an ensemble of multiple trees \item \code{graph_is_dag}: Is the graph a directed acyclic graph \item \code{graph_is_chordal}: Is the graph chordal \item \code{graph_is_complete}: Is the graph fully connected \item \code{graph_is_isomorphic_to}: Is the graph isomorphic to another graph. See \code{\link[igraph:is_isomorphic_to]{igraph::is_isomorphic_to()}} \item \code{graph_is_subgraph_isomorphic_to}: Is the graph an isomorphic subgraph to another graph. see \code{\link[igraph:is_subgraph_isomorphic_to]{igraph::is_subgraph_isomorphic_to()}} }} \examples{ gr <- create_tree(50, 4) with_graph(gr, graph_is_tree()) } tidygraph/man/map_local.Rd0000644000176200001440000000564413656245051015256 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/map.R \name{map_local} \alias{map_local} \alias{map_local_lgl} \alias{map_local_chr} \alias{map_local_int} \alias{map_local_dbl} \title{Map a function over a graph representing the neighborhood of each node} \usage{ map_local(order = 1, mode = "all", mindist = 0, .f, ...) map_local_lgl(order = 1, mode = "all", mindist = 0, .f, ...) map_local_chr(order = 1, mode = "all", mindist = 0, .f, ...) map_local_int(order = 1, mode = "all", mindist = 0, .f, ...) map_local_dbl(order = 1, mode = "all", mindist = 0, .f, ...) } \arguments{ \item{order}{Integer giving the order of the neighborhood.} \item{mode}{Character constant, it specifies how to use the direction of the edges if a directed graph is analyzed. For \sQuote{out} only the outgoing edges are followed, so all vertices reachable from the source vertex in at most \code{order} steps are counted. For \sQuote{"in"} all vertices from which the source vertex is reachable in at most \code{order} steps are counted. \sQuote{"all"} ignores the direction of the edges. This argument is ignored for undirected graphs.} \item{mindist}{The minimum distance to include the vertex in the result.} \item{.f}{A function to map over all nodes. See Details} \item{...}{Additional parameters to pass to \code{.f}} } \value{ \code{map_local()} returns a list of the same length as the number of nodes in the graph, in the order matching the node order in the graph. \verb{map_local_*()} tries to coerce its result into a vector of the classes \code{logical} (\code{map_local_lgl}), \code{character} (\code{map_local_chr}), \code{integer} (\code{map_local_int}), or \code{double} (\code{map_local_dbl}). These functions will throw an error if they are unsuccesful, so they are type safe. } \description{ This function extracts the neighborhood of each node as a graph and maps over each of these neighborhood graphs. Conceptually it is similar to \code{\link[igraph:local_scan]{igraph::local_scan()}}, but it borrows the type safe versions available in \code{\link[=map_bfs]{map_bfs()}} and \code{\link[=map_dfs]{map_dfs()}}. } \details{ The function provided to \code{.f} will be called with the following arguments in addition to those supplied through \code{...}: \itemize{ \item \code{neighborhood}: The neighborhood graph of the node \item \code{graph}: The full \code{tbl_graph} object \item \code{node}: The index of the node currently mapped over } The \code{neighborhood} graph will contain an extra node attribute called \code{.central_node}, which will be \code{TRUE} for the node that the neighborhood is expanded from and \code{FALSE} for everything else. } \examples{ # Smooth out values over a neighborhood create_notable('meredith') \%>\% mutate(value = rpois(graph_order(), 5)) \%>\% mutate(value_smooth = map_local_dbl(order = 2, .f = function(neighborhood, ...) { mean(as_tibble(neighborhood, active = 'nodes')$value) })) } tidygraph/man/edge_types.Rd0000644000176200001440000000317213545322217015446 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/edge.R \name{edge_types} \alias{edge_types} \alias{edge_is_multiple} \alias{edge_is_loop} \alias{edge_is_mutual} \alias{edge_is_from} \alias{edge_is_to} \alias{edge_is_between} \alias{edge_is_incident} \title{Querying edge types} \usage{ edge_is_multiple() edge_is_loop() edge_is_mutual() edge_is_from(from) edge_is_to(to) edge_is_between(from, to, ignore_dir = !graph_is_directed()) edge_is_incident(i) } \arguments{ \item{from, to, i}{A vector giving node indices} \item{ignore_dir}{Is both directions of the edge allowed} } \value{ A logical vector of the same length as the number of edges in the graph } \description{ These functions lets the user query whether the edges in a graph is of a specific type. All functions return a logical vector giving whether each edge in the graph corresponds to the specific type. } \section{Functions}{ \itemize{ \item \code{edge_is_multiple}: Query whether each edge has any parallel siblings \item \code{edge_is_loop}: Query whether each edge is a loop \item \code{edge_is_mutual}: Query whether each edge has a sibling going in the reverse direction \item \code{edge_is_from}: Query whether an edge goes from a set of nodes \item \code{edge_is_to}: Query whether an edge goes to a set of nodes \item \code{edge_is_between}: Query whether an edge goes between two sets of nodes \item \code{edge_is_incident}: Query whether an edge goes from or to a set of nodes }} \examples{ create_star(10, directed = TRUE, mutual = TRUE) \%>\% activate(edges) \%>\% sample_frac(0.7) \%>\% mutate(single_edge = !edge_is_mutual()) } tidygraph/man/graph_measures.Rd0000644000176200001440000001560413656224046016332 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/graph_measures.R \name{graph_measures} \alias{graph_measures} \alias{graph_adhesion} \alias{graph_assortativity} \alias{graph_automorphisms} \alias{graph_clique_num} \alias{graph_clique_count} \alias{graph_component_count} \alias{graph_motif_count} \alias{graph_diameter} \alias{graph_girth} \alias{graph_radius} \alias{graph_mutual_count} \alias{graph_asym_count} \alias{graph_unconn_count} \alias{graph_size} \alias{graph_order} \alias{graph_reciprocity} \alias{graph_min_cut} \alias{graph_mean_dist} \alias{graph_modularity} \title{Graph measurements} \usage{ graph_adhesion() graph_assortativity(attr, in_attr = NULL, directed = TRUE) graph_automorphisms(sh = "fm") graph_clique_num() graph_clique_count(min = NULL, max = NULL, subset = NULL) graph_component_count(type = "weak") graph_motif_count(size = 3, cut.prob = rep(0, size)) graph_diameter(weights = NULL, directed = TRUE, unconnected = TRUE) graph_girth() graph_radius(mode = "out") graph_mutual_count() graph_asym_count() graph_unconn_count() graph_size() graph_order() graph_reciprocity(ignore_loops = TRUE, ratio = FALSE) graph_min_cut(capacity = NULL) graph_mean_dist(directed = TRUE, unconnected = TRUE) graph_modularity(group, weights = NULL) } \arguments{ \item{attr}{The node attribute to measure on} \item{in_attr}{An alternative node attribute to use for incomming node. If \code{NULL} the attribute given by \code{type} will be used} \item{directed}{Should a directed graph be treated as directed} \item{sh}{The splitting heuristics for the BLISS algorithm. Possible values are: \sQuote{\code{f}}: first non-singleton cell, \sQuote{\code{fl}}: first largest non-singleton cell, \sQuote{\code{fs}}: first smallest non-singleton cell, \sQuote{\code{fm}}: first maximally non-trivially connected non-singleton cell, \sQuote{\code{flm}}: first largest maximally non-trivially connected non-singleton cell, \sQuote{\code{fsm}}: first smallest maximally non-trivially connected non-singleton cell.} \item{min, max}{The upper and lower bounds of the cliques to be considered.} \item{subset}{The indexes of the nodes to start the search from (logical or integer). If provided only the cliques containing these nodes will be counted.} \item{type}{The type of component to count, either 'weak' or 'strong'. Ignored for undirected graphs.} \item{size}{The size of the motif, currently 3 and 4 are supported only.} \item{cut.prob}{Numeric vector giving the probabilities that the search graph is cut at a certain level. Its length should be the same as the size of the motif (the \code{size} argument). By default no cuts are made.} \item{weights}{Optional positive weight vector for calculating weighted distances. If the graph has a \code{weight} edge attribute, then this is used by default.} \item{unconnected}{Logical, what to do if the graph is unconnected. If FALSE, the function will return a number that is one larger the largest possible diameter, which is always the number of vertices. If TRUE, the diameters of the connected components will be calculated and the largest one will be returned.} \item{mode}{How should eccentricity be calculated. If \code{"out"} only outbound edges are followed. If \code{"in"} only inbound are followed. If \code{"all"} all edges are followed. Ignored for undirected graphs.} \item{ignore_loops}{Logical. Should loops be ignored while calculating the reciprocity} \item{ratio}{Should the old "ratio" approach from igraph < v0.6 be used} \item{capacity}{The capacity of the edges} \item{group}{The node grouping to calculate the modularity on} } \value{ A scalar, the type depending on the function } \description{ This set of functions provide wrappers to a number of \code{ìgraph}s graph statistic algorithms. As for the other wrappers provided, they are intended for use inside the \code{tidygraph} framework and it is thus not necessary to supply the graph being computed on as the context is known. All of these functions are guarantied to return scalars making it easy to compute with them. } \section{Functions}{ \itemize{ \item \code{graph_adhesion}: Gives the minimum edge connectivity. Wraps \code{\link[igraph:edge_connectivity]{igraph::edge_connectivity()}} \item \code{graph_assortativity}: Measures the propensity of similar nodes to be connected. Wraps \code{\link[igraph:assortativity]{igraph::assortativity()}} \item \code{graph_automorphisms}: Calculate the number of automorphisms of the graph. Wraps \code{\link[igraph:automorphisms]{igraph::automorphisms()}} \item \code{graph_clique_num}: Get the size of the largest clique. Wraps \code{\link[igraph:clique_num]{igraph::clique_num()}} \item \code{graph_clique_count}: Get the number of maximal cliques in the graph. Wraps \code{\link[igraph:count_max_cliques]{igraph::count_max_cliques()}} \item \code{graph_component_count}: Count the number of unconnected componenets in the graph. Wraps \code{\link[igraph:count_components]{igraph::count_components()}} \item \code{graph_motif_count}: Count the number of motifs in a graph. Wraps \code{\link[igraph:count_motifs]{igraph::count_motifs()}} \item \code{graph_diameter}: Measures the length of the longest geodesic. Wraps \code{\link[igraph:diameter]{igraph::diameter()}} \item \code{graph_girth}: Measrues the length of the shortest circle in the graph. Wraps \code{\link[igraph:girth]{igraph::girth()}} \item \code{graph_radius}: Measures the smallest eccentricity in the graph. Wraps \code{\link[igraph:radius]{igraph::radius()}} \item \code{graph_mutual_count}: Counts the number of mutually connected nodes. Wraps \code{\link[igraph:dyad_census]{igraph::dyad_census()}} \item \code{graph_asym_count}: Counts the number of asymmetrically connected nodes. Wraps \code{\link[igraph:dyad_census]{igraph::dyad_census()}} \item \code{graph_unconn_count}: Counts the number of unconnected node pairs. Wraps \code{\link[igraph:dyad_census]{igraph::dyad_census()}} \item \code{graph_size}: Counts the number of edges in the graph. Wraps \code{\link[igraph:gsize]{igraph::gsize()}} \item \code{graph_order}: Counts the number of nodes in the graph. Wraps \code{\link[igraph:gorder]{igraph::gorder()}} \item \code{graph_reciprocity}: Measures the proportion of mutual connections in the graph. Wraps \code{\link[igraph:reciprocity]{igraph::reciprocity()}} \item \code{graph_min_cut}: Calculates the minimum number of edges to remove in order to split the graph into two clusters. Wraps \code{\link[igraph:min_cut]{igraph::min_cut()}} \item \code{graph_mean_dist}: Calculates the mean distance between all node pairs in the graph. Wraps \code{\link[igraph:mean_distance]{igraph::mean_distance()}} \item \code{graph_modularity}: Calculates the modularity of the graph contingent on a provided node grouping }} \examples{ # Use e.g. to modify computations on nodes and edges create_notable('meredith') \%>\% activate(nodes) \%>\% mutate(rel_neighbors = centrality_degree()/graph_order()) } tidygraph/man/dot-register_graph_context.Rd0000644000176200001440000000137713545322217020660 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/context.R \name{.register_graph_context} \alias{.register_graph_context} \title{Register a graph context for the duration of the current frame} \usage{ .register_graph_context(graph, free = FALSE, env = parent.frame()) } \arguments{ \item{graph}{A \code{tbl_graph} object} \item{free}{Should the active state of the graph be ignored?} \item{env}{The environment where the context should be active} } \description{ This function sets the provided graph to be the context for tidygraph algorithms, such as e.g. \code{\link[=node_is_center]{node_is_center()}}, for the duration of the current environment. It automatically removes the graph once the environment exits. } \keyword{internal} tidygraph/man/fortify.tbl_graph.Rd0000644000176200001440000000116313545322217016737 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/plot.R \name{fortify.tbl_graph} \alias{fortify.tbl_graph} \title{Fortify a tbl_graph for ggplot2 plotting} \usage{ fortify.tbl_graph(model, data, ...) } \description{ In general \code{tbl_graph} objects are intended to be plotted by network visualisation libraries such as \code{ggraph}. However, if you do wish to plot either the node or edge data directly with \code{ggplot2} you can simply add the \code{tbl_graph} object as either the global or layer data and the currently active data is passed on as a regular data frame. } \keyword{internal} tidygraph/man/sampling_games.Rd0000644000176200001440000000602113654774463016317 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/play.R \name{sampling_games} \alias{sampling_games} \alias{play_degree} \alias{play_dotprod} \alias{play_fitness} \alias{play_fitness_power} \alias{play_erdos_renyi} \alias{play_geometry} \title{Graph games based on direct sampling} \usage{ play_degree(out_degree, in_degree = NULL, method = "simple") play_dotprod(position, directed = TRUE) play_fitness(m, out_fit, in_fit = NULL, loops = FALSE, multiple = FALSE) play_fitness_power( n, m, out_exp, in_exp = -1, loops = FALSE, multiple = FALSE, correct = TRUE ) play_erdos_renyi(n, p, m, directed = TRUE, loops = FALSE) play_geometry(n, radius, torus = FALSE) } \arguments{ \item{out_degree, in_degree}{The degrees of each node in the graph} \item{method}{The algorithm to use for the generation. Either \code{'simple'}, \code{'vl'}, or \code{'simple.no.multiple'}} \item{position}{The latent position of each node by column.} \item{directed}{Should the resulting graph be directed} \item{m}{The number of edges in the graph} \item{out_fit, in_fit}{The fitness of each node} \item{loops}{Are loop edges allowed} \item{multiple}{Are multiple edges allowed} \item{n}{The number of nodes in the graph.} \item{out_exp, in_exp}{Power law exponent of degree distribution} \item{correct}{Use finite size correction} \item{p}{The probabilty of an edge occuring} \item{radius}{The radius within which vertices are connected} \item{torus}{Should the vertices be distributed on a torus instead of a plane} } \value{ A tbl_graph object } \description{ This set of graph games creates graphs directly through sampling of different attributes, topologies, etc. The nature of their algorithm is described in detail at the linked igraph documentation. } \section{Functions}{ \itemize{ \item \code{play_degree}: Create graphs based on the given node degrees. See \code{\link[igraph:sample_degseq]{igraph::sample_degseq()}} \item \code{play_dotprod}: Create graphs with link probability given by the dot product of the latent position of termintating nodes. See \code{\link[igraph:sample_dot_product]{igraph::sample_dot_product()}} \item \code{play_fitness}: Create graphs where edge probabilities are proportional to terminal node fitness scores. See \code{\link[igraph:sample_fitness]{igraph::sample_fitness()}} \item \code{play_fitness_power}: Create graphs with an expected power-law degree distribution. See \code{\link[igraph:sample_fitness_pl]{igraph::sample_fitness_pl()}} \item \code{play_erdos_renyi}: Create graphs with a fixed edge probability or count. See \code{\link[igraph:sample_gnp]{igraph::sample_gnp()}} and \code{\link[igraph:sample_gnm]{igraph::sample_gnm()}} \item \code{play_geometry}: Create graphs by positioning nodes on a plane or torus and connecting nearby ones. See \code{\link[igraph:sample_grg]{igraph::sample_grg()}} }} \examples{ plot(play_erdos_renyi(20, 0.3)) } \seealso{ Other graph games: \code{\link{component_games}}, \code{\link{evolution_games}}, \code{\link{type_games}} } \concept{graph games} tidygraph/man/local_graph.Rd0000644000176200001440000000604413654774463015611 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/local.R \name{local_graph} \alias{local_graph} \alias{local_size} \alias{local_members} \alias{local_triangles} \alias{local_ave_degree} \alias{local_transitivity} \title{Measures based on the neighborhood of each node} \usage{ local_size(order = 1, mode = "all", mindist = 0) local_members(order = 1, mode = "all", mindist = 0) local_triangles() local_ave_degree(weights = NULL) local_transitivity(weights = NULL) } \arguments{ \item{order}{Integer giving the order of the neighborhood.} \item{mode}{Character constant, it specifies how to use the direction of the edges if a directed graph is analyzed. For \sQuote{out} only the outgoing edges are followed, so all vertices reachable from the source vertex in at most \code{order} steps are counted. For \sQuote{"in"} all vertices from which the source vertex is reachable in at most \code{order} steps are counted. \sQuote{"all"} ignores the direction of the edges. This argument is ignored for undirected graphs.} \item{mindist}{The minimum distance to include the vertex in the result.} \item{weights}{Weight vector. If the graph has a \code{weight} edge attribute, then this is used by default. If this argument is given, then vertex strength (see \code{\link[igraph]{strength}}) is used instead of vertex degree. But note that \code{knnk} is still given in the function of the normal vertex degree. Weights are are used to calculate a weighted degree (also called \code{\link[igraph]{strength}}) instead of the degree.} } \value{ A numeric vector or a list (for \code{local_members}) with elements corresponding to the nodes in the graph. } \description{ These functions wraps a set of functions that all measures quantities of the local neighborhood of each node. They all return a vector or list matching the node position. } \section{Functions}{ \itemize{ \item \code{local_size}: The size of the neighborhood in a given distance from the node. (Note that the node itself is included unless \code{mindist > 0}). Wraps \code{\link[igraph:ego_size]{igraph::ego_size()}}. \item \code{local_members}: The members of the neighborhood of each node in a given distance. Wraps \code{\link[igraph:ego]{igraph::ego()}}. \item \code{local_triangles}: The number of triangles each node participate in. Wraps \code{\link[igraph:count_triangles]{igraph::count_triangles()}}. \item \code{local_ave_degree}: Calculates the average degree based on the neighborhood of each node. Wraps \code{\link[igraph:knn]{igraph::knn()}}. \item \code{local_transitivity}: Calculate the transitivity of each node, that is, the propensity for the nodes neighbors to be connected. Wraps \code{\link[igraph:transitivity]{igraph::transitivity()}} }} \examples{ # Get all neighbors of each graph create_notable('chvatal') \%>\% activate(nodes) \%>\% mutate(neighborhood = local_members(mindist = 1)) # These are equivalent create_notable('chvatal') \%>\% activate(nodes) \%>\% mutate(n_neighbors = local_size(mindist = 1), degree = centrality_degree()) \%>\% as_tibble() } tidygraph/man/group_graph.Rd0000644000176200001440000001060513654774463015651 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/group.R \name{group_graph} \alias{group_graph} \alias{group_components} \alias{group_edge_betweenness} \alias{group_fast_greedy} \alias{group_infomap} \alias{group_label_prop} \alias{group_leading_eigen} \alias{group_louvain} \alias{group_optimal} \alias{group_spinglass} \alias{group_walktrap} \alias{group_biconnected_component} \title{Group nodes and edges based on community structure} \usage{ group_components(type = "weak") group_edge_betweenness(weights = NULL, directed = TRUE) group_fast_greedy(weights = NULL) group_infomap(weights = NULL, node_weights = NULL, trials = 10) group_label_prop(weights = NULL, label = NULL, fixed = NULL) group_leading_eigen( weights = NULL, steps = -1, label = NULL, options = igraph::arpack_defaults ) group_louvain(weights = NULL) group_optimal(weights = NULL) group_spinglass(weights = NULL, ...) group_walktrap(weights = NULL, steps = 4) group_biconnected_component() } \arguments{ \item{type}{The type of component to find. Either \code{'weak'} or \code{'strong'}} \item{weights}{The weight of the edges to use for the calculation. Will be evaluated in the context of the edge data.} \item{directed}{Should direction of edges be used for the calculations} \item{node_weights}{The weight of the nodes to use for the calculation. Will be evaluated in the context of the node data.} \item{trials}{Number of times partition of the network should be attempted} \item{label}{The initial groups of the nodes. Will be evaluated in the context of the node data.} \item{fixed}{A logical vector determining which nodes should keep their initial groups. Will be evaluated in the context of the node data.} \item{steps}{The number of steps in the random walks} \item{options}{Settings passed on to \code{igraph::arpack()}} \item{...}{arguments passed on to \code{\link[igraph:cluster_spinglass]{igraph::cluster_spinglass()}}} } \value{ a numeric vector with the membership for each node in the graph. The enumeration happens in order based on group size progressing from the largest to the smallest group } \description{ These functions are wrappers around the various clustering functions provided by \code{igraph}. As with the other wrappers they automatically use the graph that is being computed on, and otherwise passes on its arguments to the relevant clustering function. The return value is always a numeric vector of group memberships so that nodes or edges with the same number are part of the same group. Grouping is predominantly made on nodes and currently the only grouping of edges supported is biconnected components. } \section{Functions}{ \itemize{ \item \code{group_components}: Group by connected compenents using \code{\link[igraph:components]{igraph::components()}} \item \code{group_edge_betweenness}: Group densely connected nodes using \code{\link[igraph:cluster_edge_betweenness]{igraph::cluster_edge_betweenness()}} \item \code{group_fast_greedy}: Group nodes by optimising modularity using \code{\link[igraph:cluster_fast_greedy]{igraph::cluster_fast_greedy()}} \item \code{group_infomap}: Group nodes by minimizing description length using \code{\link[igraph:cluster_infomap]{igraph::cluster_infomap()}} \item \code{group_label_prop}: Group nodes by propagating labels using \code{\link[igraph:cluster_label_prop]{igraph::cluster_label_prop()}} \item \code{group_leading_eigen}: Group nodes based on the leading eigenvector of the modularity matrix using \code{\link[igraph:cluster_leading_eigen]{igraph::cluster_leading_eigen()}} \item \code{group_louvain}: Group nodes by multilevel optimisation of modularity using \code{\link[igraph:cluster_louvain]{igraph::cluster_louvain()}} \item \code{group_optimal}: Group nodes by optimising the moldularity score using \code{\link[igraph:cluster_optimal]{igraph::cluster_optimal()}} \item \code{group_spinglass}: Group nodes using simulated annealing with \code{\link[igraph:cluster_spinglass]{igraph::cluster_spinglass()}} \item \code{group_walktrap}: Group nodes via short random walks using \code{\link[igraph:cluster_walktrap]{igraph::cluster_walktrap()}} \item \code{group_biconnected_component}: Group edges by their membership of the maximal binconnected components using \code{\link[igraph:biconnected_components]{igraph::biconnected_components()}} }} \examples{ create_notable('tutte') \%>\% activate(nodes) \%>\% mutate(group = group_infomap()) } tidygraph/man/bind_graphs.Rd0000644000176200001440000000402513656234231015575 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/bind.R \name{bind_graphs} \alias{bind_graphs} \alias{bind_nodes} \alias{bind_edges} \title{Add graphs, nodes, or edges to a tbl_graph} \usage{ bind_graphs(.data, ...) bind_nodes(.data, ...) bind_edges(.data, ..., node_key = "name") } \arguments{ \item{.data}{A \code{tbl_graph}, or a list of \code{tbl_graph} objects (for \code{bind_graphs()}).} \item{...}{In case of \code{bind_nodes()} and \code{bind_edges()} data.frames to add. In the case of \code{bind_graphs()} objects that are convertible to \code{tbl_graph} using \code{as_tbl_graph()}.} \item{node_key}{The name of the column in \code{nodes} that character represented \code{to} and \code{from} columns should be matched against. If \code{NA} the first column is always chosen. This setting has no effect if \code{to} and \code{from} are given as integers.} } \value{ A \code{tbl_graph} containing the new data } \description{ These functions are tbl_graph pendants to \code{\link[dplyr:bind_rows]{dplyr::bind_rows()}} that allows you to grow your \code{tbl_graph} by adding rows to either the nodes data, the edges data, or both. As with \code{bind_rows()} columns are matched by name and are automatically filled with \code{NA} if the column doesn't exist in some instances. In the case of \code{bind_graphs()} the graphs are automatically converted to \code{tbl_graph} objects prior to binding. The edges in each graph will continue to reference the nodes in the graph where they originated, meaning that their terminal node indexes will be shifted to match the new index of the node in the combined graph. This means the \code{bind_graphs()} always result in a disconnected graph. See \code{\link[=graph_join]{graph_join()}} for merging graphs on common nodes. } \examples{ graph <- create_notable('bull') new_graph <- create_notable('housex') # Add nodes graph \%>\% bind_nodes(data.frame(new = 1:4)) # Add edges graph \%>\% bind_edges(data.frame(from = 1, to = 4:5)) # Add graphs graph \%>\% bind_graphs(new_graph) } tidygraph/man/pair_measures.Rd0000644000176200001440000001030313656245051016152 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/pair_measures.R \name{pair_measures} \alias{pair_measures} \alias{node_adhesion_to} \alias{node_adhesion_from} \alias{node_cohesion_to} \alias{node_cohesion_from} \alias{node_distance_to} \alias{node_distance_from} \alias{node_cocitation_with} \alias{node_bibcoupling_with} \alias{node_similarity_with} \alias{node_max_flow_to} \alias{node_max_flow_from} \title{Calculate node pair properties} \usage{ node_adhesion_to(nodes) node_adhesion_from(nodes) node_cohesion_to(nodes) node_cohesion_from(nodes) node_distance_to(nodes, mode = "out", weights = NULL, algorithm = "automatic") node_distance_from( nodes, mode = "out", weights = NULL, algorithm = "automatic" ) node_cocitation_with(nodes) node_bibcoupling_with(nodes) node_similarity_with(nodes, mode = "out", loops = FALSE, method = "jaccard") node_max_flow_to(nodes, capacity = NULL) node_max_flow_from(nodes, capacity = NULL) } \arguments{ \item{nodes}{The other part of the node pair (the first part is the node defined by the row). Recycled if necessary.} \item{mode}{How should edges be followed? If \code{'all'} all edges are considered, if \code{'in'} only inbound edges are considered, and if \code{'out'} only outbound edges are considered} \item{weights}{The weights to use for calculation} \item{algorithm}{The distance algorithms to use. By default it will try to select the fastest suitable algorithm. Possible values are \code{"automatic"}, \code{"unweighted"}, \code{"dijkstra"}, \code{"bellman-ford"}, and \code{"johnson"}} \item{loops}{Should loop edges be considered} \item{method}{The similarity measure to calculate. Possible values are: \code{"jaccard"}, \code{"dice"}, and \code{"invlogweighted"}} \item{capacity}{The edge capacity to use} } \value{ A numeric vector of the same length as the number of nodes in the graph } \description{ This set of functions can be used for calculations that involve node pairs. If the calculateable measure is not symmetric the function will come in two flavours, differentiated with \verb{_to}/\verb{_from} suffix. The \verb{*_to()} functions will take the provided node indexes as the target node (recycling if necessary). For the \verb{*_from()} functions the provided nodes are taken as the source. As for the other wrappers provided, they are intended for use inside the \code{tidygraph} framework and it is thus not necessary to supply the graph being computed on as the context is known. } \section{Functions}{ \itemize{ \item \code{node_adhesion_to}: Calculate the adhesion to the specified node. Wraps \code{\link[igraph:edge_connectivity]{igraph::edge_connectivity()}} \item \code{node_adhesion_from}: Calculate the adhesion from the specified node. Wraps \code{\link[igraph:edge_connectivity]{igraph::edge_connectivity()}} \item \code{node_cohesion_to}: Calculate the cohesion to the specified node. Wraps \code{\link[igraph:vertex_connectivity]{igraph::vertex_connectivity()}} \item \code{node_cohesion_from}: Calculate the cohesion from the specified node. Wraps \code{\link[igraph:vertex_connectivity]{igraph::vertex_connectivity()}} \item \code{node_distance_to}: Calculate various distance metrics between node pairs. Wraps \code{\link[igraph:distances]{igraph::distances()}} \item \code{node_distance_from}: Calculate various distance metrics between node pairs. Wraps \code{\link[igraph:distances]{igraph::distances()}} \item \code{node_cocitation_with}: Calculate node pair cocitation count. Wraps \code{\link[igraph:cocitation]{igraph::cocitation()}} \item \code{node_bibcoupling_with}: Calculate node pair bibliographic coupling. Wraps \code{\link[igraph:bibcoupling]{igraph::bibcoupling()}} \item \code{node_similarity_with}: Calculate various node pair similarity measures. Wraps \code{\link[igraph:similarity]{igraph::similarity()}} \item \code{node_max_flow_to}: Calculate the maximum flow to a node. Wraps \code{\link[igraph:max_flow]{igraph::max_flow()}} \item \code{node_max_flow_from}: Calculate the maximum flow from a node. Wraps \code{\link[igraph:max_flow]{igraph::max_flow()}} }} \examples{ # Calculate the distance to the center node create_notable('meredith') \%>\% mutate(dist_to_center = node_distance_to(node_is_center())) } tidygraph/man/node_rank.Rd0000644000176200001440000002015413656245051015260 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/node_rank.R \name{node_rank} \alias{node_rank} \alias{node_rank_hclust} \alias{node_rank_anneal} \alias{node_rank_branch_bound} \alias{node_rank_traveller} \alias{node_rank_two} \alias{node_rank_mds} \alias{node_rank_leafsort} \alias{node_rank_visual} \alias{node_rank_spectral} \alias{node_rank_spin_out} \alias{node_rank_spin_in} \alias{node_rank_quadratic} \alias{node_rank_genetic} \alias{node_rank_dendser} \title{Calculate node ranking} \usage{ node_rank_hclust( method = "average", dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_anneal( cool = 0.5, tmin = 1e-04, swap_to_inversion = 0.5, step_multiplier = 100, reps = 1, dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_branch_bound( weighted_gradient = FALSE, dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_traveller( method = "two_opt", ..., dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_two( dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_mds( method = "cmdscale", dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_leafsort( method = "average", type = "OLO", dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_visual( dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_spectral( normalized = FALSE, dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_spin_out( step = 25, nstart = 10, dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_spin_in( step = 5, sigma = seq(20, 1, length.out = 10), dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_quadratic( criterion = "2SUM", reps = 1, step = 2 * graph_order(), step_multiplier = 1.1, temp_multiplier = 0.5, maxsteps = 50, dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_genetic( ..., dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) node_rank_dendser( ..., dist = "shortest", mode = "out", weights = NULL, algorithm = "automatic" ) } \arguments{ \item{method}{The method to use. See \emph{Functions} section for reference} \item{dist}{The algorithm to use for deriving a distance matrix from the graph. One of \itemize{ \item \code{"shortest"} (default): Use the shortest path between all nodes \item \code{"euclidean"}: Calculate the L2 norm on the adjacency matrix of the graph \item \code{"manhattan"}: Calculate the L1 norm on the adjacency matrix of the graph \item \code{"maximum"}: Calculate the supremum norm on the adjacenecy matrix of the graph \item \code{"canberra"}: Calculate a weighted manhattan distance on the adjacency matrix of the graph \item \code{"binary"}: Calculate distance as the proportion of agreement between nodes based on the adjacency matrix of the graph } or a function that takes a \code{tbl_graph} and return a \code{dist} object with a size matching the order of the graph.} \item{mode}{Which edges should be included in the distance calculation. For distance measures based on the adjacency matrix, \code{'out' } will use the matrix as is, \code{'in'} will use the transpose, and \code{'all'} will take the mean of the two. Defaults to \code{'out'}. Ignored for undirected graphs.} \item{weights}{An edge variable to use as weight for the shortest path calculation if \code{dist = 'shortest'}} \item{algorithm}{The algorithm to use for the shortest path calculation if \code{dist = 'shortest'}} \item{cool}{cooling rate} \item{tmin}{minimum temperature} \item{swap_to_inversion}{Proportion of swaps in local neighborhood search} \item{step_multiplier}{Multiplication factor for number of iterations per temperature} \item{reps}{Number of repeats with random initialisation} \item{weighted_gradient}{minimize the weighted gradient measure? Defaults to \code{FALSE}} \item{...}{Arguments passed on to other algorithms. See \emph{Functions} section for reference} \item{type}{The type of leaf reordering, either \code{'GW'} to use the "GW" method or \code{'OLO'} to use the "OLO" method (both in \code{seriation})} \item{normalized}{Should the normalized laplacian of the similarity matrix be used?} \item{step}{The number iterations to run per initialisation} \item{nstart}{The number of random initialisations to perform} \item{sigma}{The variance around the diagonal to use for the weight matrix. Either a single number or a decreasing sequence.} \item{criterion}{The criterion to minimize. Either "LS" (Linear Seriation Problem), "2SUM" (2-Sum Problem), "BAR" (Banded Anti-Robinson form), or "Inertia" (Inertia criterion)} \item{temp_multiplier}{Temperature multiplication factor between 0 and 1} \item{maxsteps}{The upper bound of iterations} } \value{ An integer vector giving the position of each node in the ranking } \description{ This set of functions tries to calculate a ranking of the nodes in a graph so that nodes sharing certain topological traits are in proximity in the resulting order. These functions are of great value when composing matrix layouts and arc diagrams but could concievably be used for other things as well. } \section{Functions}{ \itemize{ \item \code{node_rank_hclust}: Use hierarchical clustering to rank nodes (see \code{\link[stats:hclust]{stats::hclust()}} for allowed methods) \item \code{node_rank_anneal}: Use simulated annealing based on the "ARSA" method in \code{seriation} \item \code{node_rank_branch_bound}: Use branch and bounds strategy to minimize the gradient measure (only feasable for small graphs). Will use "BBURCG" or "BBWRCG" in \code{seriation} dependent on the \code{weighted_gradient} argument \item \code{node_rank_traveller}: Minimize hamiltonian path length using a travelling salesperson solver. See the the \code{solve_TSP} function in \code{TSP} for an overview of possible arguments \item \code{node_rank_two}: Use Rank-two ellipse seriation to rank the nodes. Uses "R2E" method in \code{seriation} \item \code{node_rank_mds}: Rank by multidimensional scaling onto one dimension. \code{method = 'cmdscale'} will use the classic scaling from \code{stats}, \code{method = 'isoMDS'} will use \code{isoMDS} from \code{MASS}, and \code{method = 'sammon'} will use \code{sammon} from \code{MASS} \item \code{node_rank_leafsort}: Minimize hamiltonian path length by reordering leafs in a hierarchical clustering. Method refers to the clustering algorithm (either 'average', 'single', 'complete', or 'ward') \item \code{node_rank_visual}: Use Prim's algorithm to find a minimum spanning tree giving the rank. Uses the "VAT" method in \code{seriation} \item \code{node_rank_spectral}: Minimize the 2-sum problem using a relaxation approach. Uses the "Spectral" or "Spectral_norm" methods in \code{seriation} depending on the value of the \code{norm} argument \item \code{node_rank_spin_out}: Sorts points into neighborhoods by pushing large distances away from the diagonal. Uses the "SPIN_STS" method in \code{seriation} \item \code{node_rank_spin_in}: Sorts points into neighborhoods by concentrating low distances around the diagonal. Uses the "SPIN_NH" method in \code{seriation} \item \code{node_rank_quadratic}: Use quadratic assignment problem formulations to minimize criterions using simulated annealing. Uses the "QAP_LS", "QAP_2SUM", "QAP_BAR", or "QAP_Inertia" methods from \code{seriation} dependant on the \code{criterion} argument \item \code{node_rank_genetic}: Optimizes different criteria based on a genetic algorithm. Uses the "GA" method from \code{seriation}. See \code{register_GA} for an overview of relevant arguments \item \code{node_rank_dendser}: Optimizes different criteria based on heuristic dendrogram seriation. Uses the "DendSer" method from \code{seriation}. See \code{register_DendSer} for an overview of relevant arguments }} \examples{ graph <- create_notable('zachary') \%>\% mutate(rank = node_rank_hclust()) } tidygraph/DESCRIPTION0000644000176200001440000000254413656450173013772 0ustar liggesusersPackage: tidygraph Type: Package Title: A Tidy API for Graph Manipulation Version: 1.2.0 Authors@R: person(given = "Thomas Lin", family = "Pedersen", role = c("cre", "aut"), email = "thomasp85@gmail.com", comment = c(ORCID = "0000-0002-5147-4711")) Maintainer: Thomas Lin Pedersen Description: 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. License: MIT + file LICENSE Encoding: UTF-8 LazyData: true RoxygenNote: 7.1.0 Imports: tibble, dplyr (>= 0.8.5), igraph, magrittr, utils, rlang, R6, Rcpp, tools, stats, tidyr, pillar URL: https://tidygraph.data-imaginist.com, https://github.com/thomasp85/tidygraph BugReports: https://github.com/thomasp85/tidygraph/issues LinkingTo: Rcpp Suggests: network, data.tree, ape, graph, methods, testthat, covr, seriation, netrankr, influenceR, NetSwan NeedsCompilation: yes Packaged: 2020-05-12 06:17:33 UTC; thomas Author: Thomas Lin Pedersen [cre, aut] () Repository: CRAN Date/Publication: 2020-05-12 07:30:03 UTC tidygraph/tests/0000755000176200001440000000000013545322217013413 5ustar liggesuserstidygraph/tests/testthat/0000755000176200001440000000000013656450173015261 5ustar liggesuserstidygraph/tests/testthat/test-map.R0000644000176200001440000000010013545322217017117 0ustar liggesuserscontext("map") test_that("TODO", { expect_equal(2 * 2, 4) }) tidygraph/tests/testthat/test-arrange.R0000644000176200001440000000134513545322217017775 0ustar liggesuserscontext("arrange") test_that("arrange works with nodes", { ord <- c(2, 4, 1, 3, 5) gr1 <- create_notable('bull') gr1 <- mutate(gr1, name = letters[1:5], order = ord) gr1 <- arrange(gr1, order) expect_equal(pull(gr1, name), letters[1:5][match(1:5, ord)]) }) test_that("arrange works with edges", { ord <- c(2, 4, 1, 3, 5) gr1 <- activate(create_notable('bull'), edges) gr1 <- mutate(gr1, name = letters[1:5], order = ord) gr1 <- arrange(gr1, order) expect_equal(pull(gr1, name), letters[1:5][match(1:5, ord)]) }) test_that('reserved words are protected', { ord <- c(2, 4, 1, 3, 5) gr1 <- create_notable('bull') gr1 <- mutate(gr1, .tbl_graph_index = letters[1:5], order = ord) expect_error(arrange(gr1, order)) }) tidygraph/tests/testthat/test-search.R0000644000176200001440000000325613545322217017626 0ustar liggesuserscontext("search") get_val <- function(gr, fn) { gr %>% mutate(val = fn) %>% pull(val) } test_that("search returns correct type", { gr <- create_tree(10, 2) expect_is(get_val(gr, bfs_after()), 'integer') expect_is(get_val(gr, bfs_before()), 'integer') expect_is(get_val(gr, bfs_dist()), 'integer') expect_is(get_val(gr, bfs_parent()), 'integer') expect_is(get_val(gr, bfs_rank()), 'integer') expect_is(get_val(gr, dfs_dist()), 'integer') expect_is(get_val(gr, dfs_parent()), 'integer') expect_is(get_val(gr, dfs_rank()), 'integer') expect_is(get_val(gr, dfs_rank_out()), 'integer') }) test_that("search returns correct length", { gr <- create_tree(10, 2) expect_length(get_val(gr, bfs_after()), igraph::gorder(gr)) expect_length(get_val(gr, bfs_before()), igraph::gorder(gr)) expect_length(get_val(gr, bfs_dist()), igraph::gorder(gr)) expect_length(get_val(gr, bfs_parent()), igraph::gorder(gr)) expect_length(get_val(gr, bfs_rank()), igraph::gorder(gr)) expect_length(get_val(gr, dfs_dist()), igraph::gorder(gr)) expect_length(get_val(gr, dfs_parent()), igraph::gorder(gr)) expect_length(get_val(gr, dfs_rank()), igraph::gorder(gr)) expect_length(get_val(gr, dfs_rank_out()), igraph::gorder(gr)) }) test_that("search requires active nodes", { gr <- create_tree(10, 2) %>% activate(edges) expect_error(get_val(gr, bfs_after())) expect_error(get_val(gr, bfs_before())) expect_error(get_val(gr, bfs_dist())) expect_error(get_val(gr, bfs_parent())) expect_error(get_val(gr, bfs_rank())) expect_error(get_val(gr, dfs_dist())) expect_error(get_val(gr, dfs_parent())) expect_error(get_val(gr, dfs_rank())) expect_error(get_val(gr, dfs_rank_out())) }) tidygraph/tests/testthat/test-graph_attributes.R0000644000176200001440000000060613545322217021724 0ustar liggesuserscontext("graph_attributes") test_that("attributes are applied correctly", { gr1 <- create_notable('bull') gr2 <- create_notable('diamond') igraph::graph_attr(gr1, 'igraph_attr') <- 'test' attr(gr1, 'standard_attr') <- 'test2' gr1 <- as_tbl_graph(gr1) gr2 <- gr2 %gr_attr% gr1 expect_equal(attributes(gr1), attributes(gr2)) expect_equal(graph_attr(gr1), graph_attr(gr2)) }) tidygraph/tests/testthat/test-group.R0000644000176200001440000000475113545322217017516 0ustar liggesuserscontext("group") get_group <- function(gr, fn) { gr %>% mutate(group = fn) %>% pull(group) } test_that("grouping returns integer vector", { gr <- create_notable('zachary') expect_is(get_group(gr, group_components()), 'integer') expect_is(get_group(gr, group_edge_betweenness()), 'integer') expect_is(get_group(gr, group_fast_greedy()), 'integer') expect_is(get_group(gr, group_infomap()), 'integer') expect_is(get_group(gr, group_label_prop()), 'integer') expect_is(get_group(gr, group_louvain()), 'integer') #expect_is(get_group(gr, group_optimal()), 'integer') expect_is(get_group(gr, group_spinglass()), 'integer') expect_is(get_group(gr, group_walktrap()), 'integer') gr1 <- activate(gr, edges) expect_is(get_group(gr1, group_biconnected_component()), 'integer') skip_on_os('windows') expect_is(get_group(gr, group_leading_eigen()), 'integer') }) test_that("grouping returns integer of correct length", { gr <- create_notable('zachary') expect_length(get_group(gr, group_components()), igraph::gorder(gr)) expect_length(get_group(gr, group_edge_betweenness()), igraph::gorder(gr)) expect_length(get_group(gr, group_fast_greedy()), igraph::gorder(gr)) expect_length(get_group(gr, group_infomap()), igraph::gorder(gr)) expect_length(get_group(gr, group_label_prop()), igraph::gorder(gr)) expect_length(get_group(gr, group_louvain()), igraph::gorder(gr)) #expect_length(get_group(gr, group_optimal()), igraph::gorder(gr)) expect_length(get_group(gr, group_spinglass()), igraph::gorder(gr)) expect_length(get_group(gr, group_walktrap()), igraph::gorder(gr)) gr1 <- activate(gr, edges) expect_length(get_group(gr1, group_biconnected_component()), igraph::gsize(gr1)) skip_on_os('windows') expect_length(get_group(gr, group_leading_eigen()), igraph::gorder(gr)) }) test_that("grouping requires correct activation", { gr <- create_notable('zachary') expect_error(get_group(gr, group_biconnected_component())) gr1 <- activate(gr, edges) expect_error(get_group(gr1, group_components())) expect_error(get_group(gr1, group_edge_betweenness())) expect_error(get_group(gr1, group_fast_greedy())) expect_error(get_group(gr1, group_infomap())) expect_error(get_group(gr1, group_label_prop())) expect_error(get_group(gr1, group_louvain())) #expect_error(get_group(gr1, group_optimal())) expect_error(get_group(gr1, group_spinglass())) expect_error(get_group(gr1, group_walktrap())) skip_on_os('windows') expect_error(get_group(gr1, group_leading_eigen())) }) tidygraph/tests/testthat/test-pair_measures.R0000644000176200001440000000423613545322217021217 0ustar liggesuserscontext("pair_measures") get_val <- function(gr, fn) { gr %>% mutate(val = fn) %>% pull(val) } test_that("pair measures return correct type", { gr <- create_ring(5, directed = TRUE) expect_is(get_val(gr, node_adhesion_from(1)), 'numeric') expect_is(get_val(gr, node_adhesion_to(1)), 'numeric') expect_is(get_val(gr, node_cocitation_with(1)), 'numeric') expect_is(get_val(gr, node_cohesion_from(1)), 'numeric') expect_is(get_val(gr, node_cohesion_to(1)), 'numeric') expect_is(get_val(gr, node_distance_from(1)), 'numeric') expect_is(get_val(gr, node_distance_to(1)), 'numeric') expect_is(get_val(gr, node_max_flow_from(1)), 'numeric') expect_is(get_val(gr, node_max_flow_to(1)), 'numeric') expect_is(get_val(gr, node_similarity_with(1)), 'numeric') }) test_that("pair measures return correct length", { gr <- create_ring(5, directed = TRUE) expect_length(get_val(gr, node_adhesion_from(1)), igraph::gorder(gr)) expect_length(get_val(gr, node_adhesion_to(1)), igraph::gorder(gr)) expect_length(get_val(gr, node_cocitation_with(1)), igraph::gorder(gr)) expect_length(get_val(gr, node_cohesion_from(1)), igraph::gorder(gr)) expect_length(get_val(gr, node_cohesion_to(1)), igraph::gorder(gr)) expect_length(get_val(gr, node_distance_from(1)), igraph::gorder(gr)) expect_length(get_val(gr, node_distance_to(1)), igraph::gorder(gr)) expect_length(get_val(gr, node_max_flow_from(1)), igraph::gorder(gr)) expect_length(get_val(gr, node_max_flow_to(1)), igraph::gorder(gr)) expect_length(get_val(gr, node_similarity_with(1)), igraph::gorder(gr)) }) test_that("pair measures requires active nodes", { gr <- create_ring(5, directed = TRUE) %>% activate(edges) expect_error(get_val(gr, node_adhesion_from(1))) expect_error(get_val(gr, node_adhesion_to(1))) expect_error(get_val(gr, node_cocitation_with(1))) expect_error(get_val(gr, node_cohesion_from(1))) expect_error(get_val(gr, node_cohesion_to(1))) expect_error(get_val(gr, node_distance_from(1))) expect_error(get_val(gr, node_distance_to(1))) expect_error(get_val(gr, node_max_flow_from(1))) expect_error(get_val(gr, node_max_flow_to(1))) expect_error(get_val(gr, node_similarity_with(1))) }) tidygraph/tests/testthat/test-bind.R0000644000176200001440000000200613654767364017306 0ustar liggesuserscontext("bind") test_that("bind_graphs works", { gr1 <- create_notable('bull') gr2 <- gr1 gr1 <- mutate(gr1, group = 1) gr2 <- mutate(gr2, group = 2) gr <- bind_graphs(gr1, gr2) expect_equal(igraph::gorder(gr), 10) expect_equal(igraph::gsize(gr), 10) gr <- mutate(gr, comp = group_components()) tbl <- as_tibble(gr) expect_true(all(lengths(lapply(split(tbl$group, tbl$comp), unique)) == 1)) }) test_that('bind_nodes works', { gr1 <- tbl_graph(head(mtcars)) gr1 <- bind_nodes(gr1, tail(mtcars)) tbl <- dplyr::bind_rows(head(mtcars), tail(mtcars)) expect_equivalent(as_tibble(gr1), tbl) }) test_that('bind_edges works', { gr1 <- create_notable('bull') %>% activate(edges) %>% mutate(id = 1:5, filter = c(TRUE, TRUE, TRUE, FALSE, FALSE)) tbl <- as_tibble(gr1) gr2 <- filter(gr1, filter) %>% bind_edges(tbl[!tbl$filter, ]) expect_equal(as_tibble(gr2), tbl) expect_error(bind_edges(gr2, tbl[, -(1:2)])) tbl2 <- tbl tbl2$to <- tbl2$to + 10 expect_error(bind_edges(gr2, tbl2)) }) tidygraph/tests/testthat/test-node_measures.R0000644000176200001440000000312113545322217021201 0ustar liggesuserscontext("node_measures") get_val <- function(gr, fn) { gr %>% mutate(val = fn) %>% pull(val) } test_that("Node measures return corrent type", { gr <- create_tree(10, 2) %>% activate(edges) %>% mutate(w = seq_len(n())) %>% activate(nodes) expect_is(get_val(gr, node_constraint()), 'numeric') expect_is(get_val(gr, node_coreness()), 'numeric') expect_is(get_val(gr, node_diversity(w)), 'numeric') expect_is(get_val(gr, node_dominator(node_is_root())), 'numeric') expect_is(get_val(gr, node_eccentricity()), 'numeric') expect_is(get_val(gr, node_topo_order()), 'integer') }) test_that("Node measures return correct length", { gr <- create_tree(10, 2) %>% activate(edges) %>% mutate(w = seq_len(n())) %>% activate(nodes) expect_length(get_val(gr, node_constraint()), igraph::gorder(gr)) expect_length(get_val(gr, node_coreness()), igraph::gorder(gr)) expect_length(get_val(gr, node_diversity(w)), igraph::gorder(gr)) expect_length(get_val(gr, node_dominator(node_is_root())), igraph::gorder(gr)) expect_length(get_val(gr, node_eccentricity()), igraph::gorder(gr)) expect_length(get_val(gr, node_topo_order()), igraph::gorder(gr)) }) test_that("Node measures requires active nodes", { gr <- create_tree(10, 2) %>% activate(edges) %>% mutate(w = seq_len(n())) expect_error(get_val(gr, node_constraint())) expect_error(get_val(gr, node_coreness())) expect_error(get_val(gr, node_diversity(w))) expect_error(get_val(gr, node_dominator(node_is_root()))) expect_error(get_val(gr, node_eccentricity())) expect_error(get_val(gr, node_topo_order())) }) tidygraph/tests/testthat/test-filter.R0000644000176200001440000000054413545322217017643 0ustar liggesuserscontext("filter") test_that("filter works", { id_nodes <- create_notable('bull') %>% mutate(id = seq_len(n())) %>% filter(id < 4) %>% pull(id) expect_equal(id_nodes, 1:3) id_edges <- create_notable('bull') %>% activate(edges) %>% mutate(id = seq_len(n())) %>% filter(id < 4) %>% pull(id) expect_equal(id_edges, 1:3) }) tidygraph/tests/testthat/test-group_by.R0000644000176200001440000000072713545322217020207 0ustar liggesuserscontext("group_by") test_that("nodes and edges are grouped", { gr <- create_notable('bull') %>% mutate(group = c(1,1,1,2,2)) %>% group_by(group) expect_s3_class(as_tibble(gr), 'grouped_df') expect_false(dplyr::is_grouped_df(as_tibble(gr, 'edges'))) gr <- gr %>% activate(edges) %>% mutate(group = c(1,1,1,2,2)) %>% group_by(group) expect_s3_class(as_tibble(gr), 'grouped_df') expect_false(dplyr::is_grouped_df(as_tibble(gr, 'nodes'))) }) tidygraph/tests/testthat/test-morph.R0000644000176200001440000000716313545322217017507 0ustar liggesuserscontext("morph") test_that("to_linegraph works", { gr <- create_notable('bull') %>% morph(to_linegraph) %>% activate(nodes) %>% mutate(id = seq_len(n())) gr1 <- unmorph(gr) %>% activate(edges) gr2 <- crystallise(gr) expect_equal(pull(gr1, id), 1:5) expect_equal(nrow(gr2), 1) }) test_that('to_subgraph works', { gr <- create_notable('bull') %>% morph(to_subgraph, seq_len(n()) < 4) %>% mutate(selected = TRUE) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, selected), c(TRUE, TRUE, TRUE, NA, NA)) expect_equal(nrow(gr2), 1) }) test_that('to_split works', { gr <- create_notable('bull') %>% mutate(group = c(1,1,1,2,2)) %>% morph(to_split, group) %>% mutate(size = graph_order()) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, size), c(3, 3, 3, 2, 2)) expect_equal(nrow(gr2), 2) }) test_that('to_components works', { gr <- create_notable('bull') %>% bind_graphs(create_notable('diamond')) %>% morph(to_components) %>% mutate(size = graph_order()) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, size), c(5, 5, 5, 5, 5, 4, 4, 4, 4)) expect_equal(nrow(gr2), 2) }) test_that('to_local_neighborhood works', { gr <- create_notable('bull') %>% morph(to_local_neighborhood, 5) %>% mutate(selected = TRUE) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, selected), c(NA, NA, TRUE, NA, TRUE)) expect_equal(nrow(gr2), 1) }) test_that('to_dominator_tree works', { gr <- create_ring(5, directed = TRUE) %>% morph(to_dominator_tree, 3) %>% mutate(order = node_topo_order()) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, order), c(4, 5, 1, 2, 3)) expect_equal(nrow(gr2), 1) }) test_that('to_minimum_spanning_tree works', { gr <- create_notable('bull') %>% morph(to_minimum_spanning_tree) %>% mutate(order = bfs_rank(3)) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, order), c(2, 4, 1, 5,3)) expect_equal(nrow(gr2), 1) }) test_that('to_shortest_path works', { gr <- create_notable('bull') %>% morph(to_shortest_path, 4, 5) %>% mutate(selected = TRUE) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, selected), c(NA, TRUE, TRUE, TRUE, TRUE)) expect_equal(nrow(gr2), 1) }) test_that('to_bfs_tree works', { gr <- create_notable('bull') %>% morph(to_bfs_tree, 5) %>% mutate(degree = centrality_degree()) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, degree), c(0, 1, 2, 0, 1)) expect_equal(nrow(gr2), 1) }) test_that('to_dfs_tree works', { gr <- create_notable('bull') %>% morph(to_dfs_tree, 5) %>% mutate(degree = centrality_degree()) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, degree), c(1, 1, 1, 0, 1)) expect_equal(nrow(gr2), 1) }) test_that('to_simple works', { gr <- create_ring(5, directed = TRUE) gr <- bind_edges(gr, as_tibble(gr, 'edges')) %>% morph(to_simple) %>% mutate(size = graph_size()) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, size), rep(5, 5)) expect_equal(nrow(gr2), 1) }) test_that('to_contracted works', { gr <- create_notable('bull') %>% mutate(group = c(1,1,1,2,2)) %>% morph(to_contracted, group) %>% mutate(node = rev(seq_len(n()))) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, node), c(2,2,2,1,1)) expect_equal(nrow(gr2), 1) }) tidygraph/tests/testthat/test-edge_types.R0000644000176200001440000000225713545322217020511 0ustar liggesuserscontext("edge_types") get_type <- function(gr, fn) { gr %>% mutate(type = fn) %>% pull(type) } test_that("edge types return logical", { gr <- create_notable('bull') %>% activate(edges) %>% bind_edges(tibble::tibble( to = c(1, 1, 3, 4), from = c(1, 3, 1, 2) )) expect_is(get_type(gr, edge_is_loop()), 'logical') expect_is(get_type(gr, edge_is_multiple()), 'logical') expect_is(get_type(gr, edge_is_mutual()), 'logical') }) test_that("edge types return correct length", { gr <- create_notable('bull') %>% activate(edges) %>% bind_edges(tibble::tibble( to = c(1, 1, 3, 4), from = c(1, 3, 1, 2) )) expect_length(get_type(gr, edge_is_loop()), igraph::gsize(gr)) expect_length(get_type(gr, edge_is_multiple()), igraph::gsize(gr)) expect_length(get_type(gr, edge_is_mutual()), igraph::gsize(gr)) }) test_that("edge types require edge active", { gr <- create_notable('bull') %>% activate(nodes) %>% bind_edges(tibble::tibble( to = c(1, 1, 3, 4), from = c(1, 3, 1, 2) )) expect_error(get_type(gr, edge_is_loop())) expect_error(get_type(gr, edge_is_multiple())) expect_error(get_type(gr, edge_is_mutual())) }) tidygraph/tests/testthat/test-join.R0000644000176200001440000000010113545322217017302 0ustar liggesuserscontext("join") test_that("TODO", { expect_equal(2 * 2, 4) }) tidygraph/tests/testthat/test-graph_measures.R0000644000176200001440000000204213656223303021355 0ustar liggesuserscontext("graph_measures") test_that("graph measures returns scalars", { gr <- create_notable('housex') %>% mutate(type = c(1, 1, 1, 2, 2)) .graph_context$set(gr) expect_length(graph_adhesion(), 1) expect_length(graph_assortativity(type), 1) expect_length(graph_automorphisms(), 1) expect_length(graph_clique_count(), 1) expect_length(graph_clique_num(), 1) expect_length(graph_component_count(), 1) expect_length(graph_diameter(), 1) expect_length(graph_girth(), 1) expect_length(graph_mean_dist(), 1) expect_length(graph_min_cut(), 1) expect_length(graph_motif_count(), 1) expect_length(graph_order(), 1) expect_length(graph_radius(), 1) expect_length(graph_reciprocity(), 1) expect_length(graph_size(), 1) expect_length(graph_modularity(type), 1) .graph_context$clear() gr <- create_ring(5, TRUE) %>% mutate(type = c(1, 1, 1, 2, 2)) .graph_context$set(gr) expect_length(graph_asym_count(), 1) expect_length(graph_mutual_count(), 1) expect_length(graph_unconn_count(), 1) .graph_context$clear() }) tidygraph/tests/testthat/test-centrality.R0000644000176200001440000000454213545322217020536 0ustar liggesuserscontext("centrality") get_cent <- function(gr, fn) { gr %>% mutate(cent = fn) %>% pull(cent) } test_that("centrality returns numeric", { gr1 <- create_notable('diamond') expect_is(get_cent(gr1, centrality_alpha()), 'numeric') expect_is(get_cent(gr1, centrality_betweenness()), 'numeric') expect_is(get_cent(gr1, centrality_closeness()), 'numeric') expect_is(get_cent(gr1, centrality_degree()), 'numeric') expect_is(get_cent(gr1, centrality_pagerank()), 'numeric') expect_is(get_cent(gr1, centrality_power()), 'numeric') expect_is(get_cent(gr1, centrality_subgraph()), 'numeric') gr2 <- activate(gr1, 'edges') expect_is(get_cent(gr2, centrality_edge_betweenness()), 'numeric') skip_on_os('windows') expect_is(get_cent(gr1, centrality_authority()), 'numeric') expect_is(get_cent(gr1, centrality_eigen()), 'numeric') expect_is(get_cent(gr1, centrality_hub()), 'numeric') }) test_that("centrality returns correct length", { gr1 <- create_notable('diamond') expect_length(get_cent(gr1, centrality_alpha()), 4) expect_length(get_cent(gr1, centrality_betweenness()), 4) expect_length(get_cent(gr1, centrality_closeness()), 4) expect_length(get_cent(gr1, centrality_degree()), 4) expect_length(get_cent(gr1, centrality_pagerank()), 4) expect_length(get_cent(gr1, centrality_power()), 4) expect_length(get_cent(gr1, centrality_subgraph()), 4) gr2 <- activate(gr1, 'edges') expect_length(get_cent(gr2, centrality_edge_betweenness()), 5) skip_on_os('windows') expect_length(get_cent(gr1, centrality_authority()), 4) expect_length(get_cent(gr1, centrality_eigen()), 4) expect_length(get_cent(gr1, centrality_hub()), 4) }) test_that("centrality requires the right activation", { gr1 <- create_notable('diamond') expect_error(get_cent(gr1, centrality_edge_betweenness())) gr2 <- activate(gr1, 'edges') expect_error(get_cent(gr2, centrality_alpha())) expect_error(get_cent(gr2, centrality_betweenness())) expect_error(get_cent(gr2, centrality_closeness())) expect_error(get_cent(gr2, centrality_degree())) expect_error(get_cent(gr2, centrality_pagerank())) expect_error(get_cent(gr2, centrality_power())) expect_error(get_cent(gr2, centrality_subgraph())) skip_on_os('windows') expect_error(get_cent(gr2, centrality_authority())) expect_error(get_cent(gr2, centrality_eigen())) expect_error(get_cent(gr2, centrality_hub())) }) tidygraph/tests/testthat/test-distinct.R0000644000176200001440000000053613545322217020200 0ustar liggesuserscontext("distinct") test_that("distinct works", { gr <- create_notable('bull') %>% mutate(id = c(1,1,1,2,2)) %>% activate(edges) %>% mutate(id = c(1,1,2,2,3)) gr1 <- gr %>% activate(nodes) %>% distinct() expect_equal(igraph::gorder(gr1), 2) gr2 <- gr %>% activate(edges) %>% distinct(id) expect_equal(igraph::gsize(gr2), 3) }) tidygraph/tests/testthat/test-activate.R0000644000176200001440000000200113545322217020144 0ustar liggesuserscontext("activate") test_that("active<- and activate works for tbl_graph", { gr1 <- create_notable('bull') gr1 <- activate(gr1, edges) expect_equal(active(gr1), 'edges') gr1 <- activate(gr1, 'nodes') expect_equal(active(gr1), 'nodes') test <- 'nodes' expect_error(activate(gr1, test)) expect_equal(active(activate(gr1, !!test)), 'nodes') active(gr1) <- 'links' expect_equal(active(gr1), 'edges') active(gr1) <- 'vertices' expect_equal(active(gr1), 'nodes') expect_error(active(gr1) <- 'test') }) test_that('activate ungroups', { gr1 <- mutate(create_notable('bull'), group = sample(1:2, n(), TRUE)) gr1 <- group_by(gr1, group) expect_message(activate(gr1, edges)) expect_equal(class(activate(gr1, edges)), c('tbl_graph', 'igraph')) }) test_that('activate activates all morphed graphs', { gr1 <- gr1 <- mutate(create_notable('bull'), group = sample(1:2, n(), TRUE)) gr1 <- morph(gr1, to_split, group) gr1 <- activate(gr1, 'edges') expect_true(all(sapply(gr1, active) == 'edges')) }) tidygraph/tests/testthat/test-mutate.R0000644000176200001440000000057513545322217017661 0ustar liggesuserscontext("mutate") test_that("mutate works with nodes", { mut <- create_notable('bull') %>% mutate(letters = letters[1:5]) %>% pull(letters) expect_equal(mut, letters[1:5]) }) test_that("mutate works with edges", { mut <- create_notable('bull') %>% activate(edges) %>% mutate(letters = letters[1:5]) %>% pull(letters) expect_equal(mut, letters[1:5]) }) tidygraph/tests/testthat/test-context.R0000644000176200001440000000124713545322217020043 0ustar liggesuserscontext("context") test_that("graphs get added and stacked in the context", { context <- ContextBuilder$new() expect_false(context$alive()) gr1 <- create_ring(5) context$set(gr1) expect_true(context$alive()) expect_equal(context$active(), active(gr1)) expect_equal(context$graph(), gr1) expect_equal(context$nodes(), as_tibble(gr1, 'nodes')) expect_equal(context$edges(), as_tibble(gr1, 'edges')) gr2 <- create_lattice(c(4, 5)) %>% activate(edges) context$set(gr2) expect_equal(context$active(), active(gr2)) expect_equal(context$graph(), gr2) context$clear() expect_equal(context$graph(), gr1) context$clear() expect_false(context$alive()) }) tidygraph/tests/testthat/test-local.R0000644000176200001440000000223113545322217017443 0ustar liggesuserscontext("local") get_loc <- function(gr, fn) { gr %>% mutate(loc = fn) %>% pull(loc) } test_that("local returns correct type", { gr <- create_notable('bull') expect_is(get_loc(gr, local_ave_degree()), 'numeric') expect_is(get_loc(gr, local_members()), 'list') expect_is(get_loc(gr, local_size()), 'numeric') expect_is(get_loc(gr, local_transitivity()), 'numeric') expect_is(get_loc(gr, local_triangles()), 'numeric') }) test_that("local returns correct type", { gr <- create_notable('bull') expect_length(get_loc(gr, local_ave_degree()), igraph::gorder(gr)) expect_length(get_loc(gr, local_members()), igraph::gorder(gr)) expect_length(get_loc(gr, local_size()), igraph::gorder(gr)) expect_length(get_loc(gr, local_transitivity()), igraph::gorder(gr)) expect_length(get_loc(gr, local_triangles()), igraph::gorder(gr)) }) test_that("local requires active nodes", { gr <- create_notable('bull') %>% activate(edges) expect_error(get_loc(gr, local_ave_degree())) expect_error(get_loc(gr, local_members())) expect_error(get_loc(gr, local_size())) expect_error(get_loc(gr, local_transitivity())) expect_error(get_loc(gr, local_triangles())) }) tidygraph/tests/testthat/test-node_types.R0000644000176200001440000000353113545322217020526 0ustar liggesuserscontext("node_types") get_type <- function(gr, fn) { gr %>% mutate(type = fn) %>% pull(type) } test_that("node types return logical", { gr <- create_tree(10, 2) expect_is(get_type(gr, node_is_center()), 'logical') expect_is(get_type(gr, node_is_cut()), 'logical') expect_is(get_type(gr, node_is_isolated()), 'logical') expect_is(get_type(gr, node_is_leaf()), 'logical') expect_is(get_type(gr, node_is_root()), 'logical') expect_is(get_type(gr, node_is_simplical()), 'logical') expect_is(get_type(gr, node_is_sink()), 'logical') expect_is(get_type(gr, node_is_source()), 'logical') expect_is(get_type(gr, node_is_universal()), 'logical') }) test_that("node types return vector of correct length", { gr <- create_tree(10, 2) expect_length(get_type(gr, node_is_center()), igraph::gorder(gr)) expect_length(get_type(gr, node_is_cut()), igraph::gorder(gr)) expect_length(get_type(gr, node_is_isolated()), igraph::gorder(gr)) expect_length(get_type(gr, node_is_leaf()), igraph::gorder(gr)) expect_length(get_type(gr, node_is_root()), igraph::gorder(gr)) expect_length(get_type(gr, node_is_simplical()), igraph::gorder(gr)) expect_length(get_type(gr, node_is_sink()), igraph::gorder(gr)) expect_length(get_type(gr, node_is_source()), igraph::gorder(gr)) expect_length(get_type(gr, node_is_universal()), igraph::gorder(gr)) }) test_that("node types require active nodes", { gr <- create_tree(10, 2) %>% activate(edges) expect_error(get_type(gr, node_is_center())) expect_error(get_type(gr, node_is_cut())) expect_error(get_type(gr, node_is_isolated())) expect_error(get_type(gr, node_is_leaf())) expect_error(get_type(gr, node_is_root())) expect_error(get_type(gr, node_is_simplical())) expect_error(get_type(gr, node_is_sink())) expect_error(get_type(gr, node_is_source())) expect_error(get_type(gr, node_is_universal())) }) tidygraph/tests/testthat.R0000644000176200001440000000007613545322217015401 0ustar liggesuserslibrary(testthat) library(tidygraph) test_check("tidygraph") tidygraph/src/0000755000176200001440000000000013656437575013061 5ustar liggesuserstidygraph/src/get_paths.cpp0000644000176200001440000000213313545322217015521 0ustar liggesusers#include using namespace Rcpp; //[[Rcpp::export]] List get_paths(IntegerVector parent) { std::deque< std::deque > paths; int i, next; LogicalVector last = is_na(parent); for (i = 0; i < parent.size(); ++i) { std::deque path; next = i; while(!last[next]) { next = parent[next] - 1; path.push_back(next + 1); } std::reverse(path.begin(), path.end()); paths.push_back(path); } return wrap(paths); } //[[Rcpp::export]] List collect_offspring(ListOf offspring, IntegerVector order) { std::deque< std::deque > offsprings; int i, j, node, n_children, child; for (i = 0; i < order.size(); ++i) { std::deque off(offspring[i].begin(), offspring[i].end()); offsprings.push_back(off); } for (i = 0; i < order.size(); ++i) { node = order[i] - 1; n_children = offsprings[node].size(); for (j = 0; j < n_children; ++j) { child = offsprings[node][j] - 1; offsprings[node].insert(offsprings[node].end(), offsprings[child].begin(), offsprings[child].end()); } } return wrap(offsprings); } tidygraph/src/RcppExports.cpp0000644000176200001440000000264113545322217016040 0ustar liggesusers// Generated by using Rcpp::compileAttributes() -> do not edit by hand // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 #include using namespace Rcpp; // get_paths List get_paths(IntegerVector parent); RcppExport SEXP _tidygraph_get_paths(SEXP parentSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type parent(parentSEXP); rcpp_result_gen = Rcpp::wrap(get_paths(parent)); return rcpp_result_gen; END_RCPP } // collect_offspring List collect_offspring(ListOf offspring, IntegerVector order); RcppExport SEXP _tidygraph_collect_offspring(SEXP offspringSEXP, SEXP orderSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< ListOf >::type offspring(offspringSEXP); Rcpp::traits::input_parameter< IntegerVector >::type order(orderSEXP); rcpp_result_gen = Rcpp::wrap(collect_offspring(offspring, order)); return rcpp_result_gen; END_RCPP } static const R_CallMethodDef CallEntries[] = { {"_tidygraph_get_paths", (DL_FUNC) &_tidygraph_get_paths, 1}, {"_tidygraph_collect_offspring", (DL_FUNC) &_tidygraph_collect_offspring, 2}, {NULL, NULL, 0} }; RcppExport void R_init_tidygraph(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); } tidygraph/R/0000755000176200001440000000000013656437441012463 5ustar liggesuserstidygraph/R/igraph.R0000644000176200001440000000062013545322217014045 0ustar liggesusers#' @describeIn tbl_graph Method for igraph object. Simply subclasses the object into a `tbl_graph` #' @export as_tbl_graph.igraph <- function(x, ...) { class(x) <- c('tbl_graph', 'igraph') attr(x, 'active') <- 'nodes' x } #' @importFrom igraph as.igraph #' @export as.igraph.tbl_graph <- function(x, ...) { class(x) <- 'igraph' attr(x, 'active') <- NULL x } #' @export igraph::as.igraph tidygraph/R/search.R0000644000176200001440000001237013545322217014045 0ustar liggesusers#' Search a graph with depth first and breath first #' #' These functions wraps the [igraph::bfs()] and [igraph::dfs()] functions to #' provide a consistent return value that can be used in [dplyr::mutate()] #' calls. Each function returns an integer vector with values matching the order #' of the nodes in the graph. #' #' @param root The node to start the search from #' #' @param mode How edges are followed in the search if the graph is directed. #' `"out"` only follows outbound edges, `"in"` only follows inbound edges, and #' `"all"` or `"total"` follows all edges. This is ignored for undirected #' graphs. #' #' @param unreachable Should the search jump to a new component if the search is #' terminated without all nodes being visited? Default to `FALSE` (only reach #' connected nodes). #' #' @return An integer vector, the nature of which is determined by the function. #' #' @name search_graph #' @rdname search_graph #' #' @examples #' # Get the depth of each node in a tree #' create_tree(10, 2) %>% #' activate(nodes) %>% #' mutate(depth = bfs_dist(root = 1)) #' #' # Reorder nodes based on a depth first search from node 3 #' create_notable('franklin') %>% #' activate(nodes) %>% #' mutate(order = dfs_rank(root = 3)) %>% #' arrange(order) #' NULL # Breath First Search ----------------------------------------------------- #' @describeIn search_graph Get the succession in which the nodes are visited in a breath first search #' @importFrom igraph bfs gorder #' @export bfs_rank <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) ind <- bfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, rank = TRUE)$rank as.integer(ind) } #' @describeIn search_graph Get the nodes from which each node is visited in a breath first search #' @importFrom igraph bfs gorder #' @export bfs_parent <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) ind <- bfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, father = TRUE)$father as.integer(ind) } #' @describeIn search_graph Get the node that was visited before each node in a breath first search #' @importFrom igraph bfs gorder #' @export bfs_before <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) ind <- bfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, pred = TRUE)$pred as.integer(ind) } #' @describeIn search_graph Get the node that was visited after each node in a breath first search #' @importFrom igraph bfs gorder #' @export bfs_after <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) ind <- bfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, succ = TRUE)$succ as.integer(ind) } #' @describeIn search_graph Get the number of nodes between the root and each node in a breath first search #' @importFrom igraph bfs gorder #' @export bfs_dist <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) ind <- bfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, dist = TRUE)$dist as.integer(ind) } # Depth First Search ------------------------------------------------------ #' @describeIn search_graph Get the succession in which the nodes are visited in a depth first search #' @importFrom igraph dfs gorder #' @export dfs_rank <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) ind <- dfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE)$order match(seq_along(ind), as.integer(ind)) } #' @describeIn search_graph Get the succession in which each nodes subtree is completed in a depth first search #' @importFrom igraph dfs gorder #' @export dfs_rank_out <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) ind <- dfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, order.out = TRUE)$order.out match(seq_along(ind), as.integer(ind)) } #' @describeIn search_graph Get the nodes from which each node is visited in a depth first search #' @importFrom igraph dfs gorder #' @export dfs_parent <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) ind <- dfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, father = TRUE)$father as.integer(ind) } #' @describeIn search_graph Get the number of nodes between the root and each node in a depth first search #' @importFrom igraph dfs gorder #' @export dfs_dist <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) ind <- dfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, dist = TRUE)$dist as.integer(ind) } tidygraph/R/create.R0000644000176200001440000001062613545322217014045 0ustar liggesusers#' Create different types of well-defined graphs #' #' These functions creates a long list of different types of well-defined graphs, #' that is, their structure is not based on any randomisation. All of these #' functions are shallow wrappers around a range of `igraph::make_*` functions #' but returns `tbl_graph` rather than `igraph` objects. #' #' @param n,n1,n2 The number of nodes in the graph #' @param directed Should the graph be directed #' @param mode In case of a directed, non-mutual, graph should the edges flow #' `'out'` or `'in'` #' @param mutual Should mutual edges be created in case of the graph being #' directed #' @param name The name of a notable graph. See a complete list in [igraph::make_graph()] #' @param w A matrix specifying the additional edges in the chordan ring. See #' [igraph::make_chordal_ring()] #' @param alphabet_size The number of unique letters in the alphabet used for #' the graph #' @param label_size The number of characters in each node #' @param dim The dimensions of the lattice #' @param circular Should each dimension in the lattice wrap around #' @param children The number of children each node has in the tree (if possible) #' #' @return A tbl_graph #' #' @name create_graphs #' @rdname create_graphs #' #' @examples #' # Create a complete graph with 10 nodes #' create_complete(10) #' NULL #' @describeIn create_graphs Create a simple ring graph #' @importFrom igraph make_ring #' @export create_ring <- function(n, directed = FALSE, mutual = FALSE) { as_tbl_graph(make_ring(n, directed, mutual, circular = TRUE)) } #' @describeIn create_graphs Create a simple path #' @importFrom igraph make_ring #' @export create_path <- function(n, directed = FALSE, mutual = FALSE) { as_tbl_graph(make_ring(n, directed, mutual, circular = FALSE)) } #' @describeIn create_graphs Create a chordal ring #' @importFrom igraph make_chordal_ring #' @export create_chordal_ring <- function(n, w) { as_tbl_graph(make_chordal_ring(n, w)) } #' @describeIn create_graphs Create a de Bruijn graph with the specified alphabet and label size #' @importFrom igraph make_de_bruijn_graph #' @export create_de_bruijn <- function(alphabet_size, label_size) { as_tbl_graph(make_de_bruijn_graph(alphabet_size, label_size)) } #' @describeIn create_graphs Create a graph with no edges #' @importFrom igraph make_empty_graph #' @export create_empty <- function(n, directed = FALSE) { as_tbl_graph(make_empty_graph(n, directed)) } #' @describeIn create_graphs Create a full bipartite graph #' @importFrom igraph make_full_bipartite_graph #' @export create_bipartite <- function(n1, n2, directed = FALSE, mode = 'out') { as_tbl_graph(make_full_bipartite_graph(n1, n2, directed, mode)) } #' @describeIn create_graphs Create a full citation graph #' @importFrom igraph make_full_citation_graph #' @export create_citation <- function(n) { as_tbl_graph(make_full_citation_graph(n)) } #' @describeIn create_graphs Create a complete graph (a graph where all nodes are connected) #' @importFrom igraph make_full_graph #' @export create_complete <- function(n) { as_tbl_graph(make_full_graph(n)) } #' @describeIn create_graphs Create a graph based on its name. See [igraph::make_graph()] #' @importFrom igraph make_graph #' @export create_notable <- function(name) { stopifnot(is.character(name)) as_tbl_graph(make_graph(name)) } #' @describeIn create_graphs Create a Kautz graph with the specified alphabet and label size #' @importFrom igraph make_kautz_graph #' @export create_kautz <- function(alphabet_size, label_size) { as_tbl_graph(make_kautz_graph(alphabet_size, label_size)) } #' @describeIn create_graphs Create a multidimensional grid of nodes #' @importFrom igraph make_lattice #' @export create_lattice <- function(dim, directed = FALSE, mutual = FALSE, circular = FALSE) { as_tbl_graph(make_lattice(dim, directed = directed, mutual = mutual, circular = circular)) } #' @describeIn create_graphs Create a star graph (A single node in the center connected to all other nodes) #' @importFrom igraph make_star #' @export create_star <- function(n, directed = FALSE, mutual = FALSE, mode = 'out') { if (!directed) mode <- 'undirected' else if (mutual) mode <- 'mutual' as_tbl_graph(make_star(n, mode)) } #' @describeIn create_graphs Create a tree graph #' @importFrom igraph make_tree #' @export create_tree <- function(n, children, directed = TRUE, mode = 'out') { if (!directed) mode <- 'undirected' as_tbl_graph(make_tree(n, children, mode)) } tidygraph/R/play.R0000644000176200001440000003250513545322217013547 0ustar liggesusers#' Graph games based on different node types #' #' This set of games are build around different types of nodes and simulating #' their interaction. The nature of their algorithm is described in #' detail at the linked igraph documentation. #' #' @param n,n1,n2 The number of nodes in the graph. For bipartite graphs `n1` #' and `n2` specifies the number of nodes of each type. #' @inheritParams sampling_games #' @inheritParams evolution_games #' @param mode The flow direction of edges #' @param types The type of each node in the graph, enumerated from 0 #' @param n_types The number of different node types in the graph #' @param p_type The probability that a node will be the given type. Either a #' vector or a matrix, depending on the game #' @param p_pref The probability that an edge will be made to a type. Either a #' vector or a matrix, depending on the game #' @param fixed Should n_types be understood as a fixed number of nodes for each #' type rather than as a probability #' @param callaway Use the callaway version of the trait based game #' #' @return A tbl_graph object #' #' @rdname type_games #' @name type_games #' @family graph games #' #' @examples #' plot(play_bipartite(20, 30, 0.4)) #' NULL #' @describeIn type_games Create graphs by linking nodes of different types #' based on a defined probability. See [igraph::sample_pref()] #' @importFrom igraph sample_pref #' @export play_preference <- function(n, n_types, p_type = rep(1, n_types), p_pref = matrix(1, n_types, n_types), fixed = FALSE, directed = TRUE, loops = FALSE) { as_tbl_graph(sample_pref(n, n_types, p_type, fixed, p_pref, directed, loops)) } #' @describeIn type_games Create graphs by linking nodes of different types #' based on an asymmetric probability. See [igraph::sample_asym_pref()] #' @importFrom igraph sample_asym_pref #' @export play_preference_asym <- function(n, n_types, p_type = matrix(1, n_types, n_types), p_pref = matrix(1, n_types, n_types), loops = FALSE) { as_tbl_graph(sample_asym_pref(n, n_types, p_type, p_pref, loops)) } #' @describeIn type_games Create bipartite graphs of fixed size and edge count #' or probability. See [igraph::sample_bipartite()] #' @importFrom igraph sample_bipartite #' @export play_bipartite <- function(n1, n2, p, m, directed = TRUE, mode = 'out') { type <- if (missing(p)) { 'gnm' } else { if (!missing(m)) warning('Ignoring "m" as "p" is provided', call. = FALSE) 'gnp' } as_tbl_graph(sample_bipartite(n1, n2, type, p, m, directed, mode)) } #' @describeIn type_games Create graphs by evolving a graph with type based edge #' probabilities. See [igraph::sample_traits()] and #' [igraph::sample_traits_callaway()] #' @importFrom igraph sample_traits_callaway sample_traits #' @export play_traits <- function(n, n_types, growth = 1, p_type = rep(1, n_types), p_pref = matrix(1, n_types, n_types), callaway = TRUE, directed = TRUE) { if (callaway) { as_tbl_graph(sample_traits_callaway(n, n_types, growth, p_type, p_pref, directed)) } else { as_tbl_graph(sample_traits(n, n_types, growth, p_type, p_pref, directed)) } } #' @describeIn type_games Create citation graphs by evolving with type based #' linking probability. See [igraph::sample_cit_types()] and #' [igraph::sample_cit_cit_types()] #' @importFrom igraph sample_cit_types sample_cit_cit_types #' @export play_citation_type <- function(n, growth, types = rep(0, n), p_pref = rep(1, length(unique(types))), directed = TRUE) { if (is.matrix(p_pref)) { as_tbl_graph(sample_cit_cit_types(n, growth, types, p_pref, directed)) } else { as_tbl_graph(sample_cit_types(n, growth, types, p_pref, directed)) } } #' Graph games based on direct sampling #' #' This set of graph games creates graphs directly through sampling of different #' attributes, topologies, etc. The nature of their algorithm is described in #' detail at the linked igraph documentation. #' #' @param n The number of nodes in the graph. #' @param p The probabilty of an edge occuring #' @param m The number of edges in the graph #' @param directed Should the resulting graph be directed #' @param loops Are loop edges allowed #' @param multiple Are multiple edges allowed #' @param method The algorithm to use for the generation. Either `'simple'`, #' `'vl'`, or `'simple.no.multiple'` #' @param out_degree,in_degree The degrees of each node in the graph #' @param out_fit,in_fit The fitness of each node #' @param out_exp,in_exp Power law exponent of degree distribution #' @param position The latent position of each node by column. #' @param radius The radius within which vertices are connected #' @param torus Should the vertices be distributed on a torus instead of a plane #' @param correct Use finite size correction #' #' @return A tbl_graph object #' #' @rdname sampling_games #' @name sampling_games #' @family graph games #' #' @examples #' plot(play_erdos_renyi(20, 0.3)) NULL #' @describeIn sampling_games Create graphs based on the given node degrees. See #' [igraph::sample_degseq()] #' @importFrom igraph sample_degseq #' @export play_degree <- function(out_degree, in_degree = NULL, method = 'simple') { as_tbl_graph(sample_degseq(out_degree, in_degree, method)) } #' @describeIn sampling_games Create graphs with link probability given by the #' dot product of the latent position of termintating nodes. See #' [igraph::sample_dot_product()] #' @importFrom igraph sample_dot_product #' @export play_dotprod <- function(position, directed = TRUE) { as_tbl_graph(sample_dot_product(position, directed)) } #' @describeIn sampling_games Create graphs where edge probabilities are #' proportional to terminal node fitness scores. See [igraph::sample_fitness()] #' @importFrom igraph sample_fitness #' @export play_fitness <- function(m, out_fit, in_fit = NULL, loops = FALSE, multiple = FALSE) { as_tbl_graph(sample_fitness(m, out_fit, in_fit, loops, multiple)) } #' @describeIn sampling_games Create graphs with an expected power-law degree #' distribution. See [igraph::sample_fitness_pl()] #' @importFrom igraph sample_fitness_pl #' @export play_fitness_power <- function(n, m, out_exp, in_exp = -1, loops = FALSE, multiple = FALSE, correct = TRUE) { as_tbl_graph(sample_fitness_pl(n, m, out_exp, in_exp, loops, multiple, correct)) } #' @describeIn sampling_games Create graphs with a fixed edge probability or #' count. See [igraph::sample_gnp()] and [igraph::sample_gnm()] #' @importFrom igraph sample_gnm sample_gnp #' @export play_erdos_renyi <- function(n, p, m, directed = TRUE, loops = FALSE) { if (missing(p)) { as_tbl_graph(sample_gnm(n, m, directed, loops)) } else { if (!missing(m)) warning('Ignoring "m" as "p" is provided', call. = FALSE) as_tbl_graph(sample_gnp(n, p, directed, loops)) } } #' @describeIn sampling_games Create graphs by positioning nodes on a plane or #' torus and connecting nearby ones. See [igraph::sample_grg()] #' @importFrom igraph sample_grg #' @export play_geometry <- function(n, radius, torus = FALSE) { as_tbl_graph(sample_grg(n, radius, torus, TRUE)) } #' Graph games based on evolution #' #' This games create graphs through different types of evolutionary mechanisms #' (not necessarily in a biological sense). The nature of their algorithm is #' described in detail at the linked igraph documentation. #' #' @inheritParams sampling_games #' @param growth The number of edges added at each iteration #' @param growth_dist The distribution of the number of added edges at each iteration #' @param use_out Should outbound edges be used for calculating citation probability #' @param appeal_zero The appeal value for unconnected nodes #' @param appeal_zero_age The appeal value of nodes without age #' @param coefficient The coefficient of the degree dependent part of attrictiveness #' @param coefficient_age The coefficient of the age dependent part of attrictiveness #' @param power The power of the preferential attachment #' @param power_age The aging exponent #' @param window The aging window to take into account when calculating the preferential attraction #' @param bins The number of aging bins #' @param p_pref The probability that an edge will be made to an age bin. #' @param p_forward,p_backward Forward and backward burning probability #' @param citation Should a citation graph be created #' @param method The algorithm to use for graph creation. Either `'psumtree'`, #' `'psumtree-multiple'`, or `'bag'` #' #' @return A tbl_graph object #' #' @rdname evolution_games #' @name evolution_games #' @seealso [play_traits()] and [play_citation_type()] for an evolutionary #' algorithm based on different node types #' @family graph games #' #' @examples #' plot(play_forestfire(50, 0.5)) #' NULL #' @describeIn evolution_games Create citation graphs based on a specific age #' link probability. See [igraph::sample_last_cit()] #' @importFrom igraph sample_last_cit #' @export play_citation_age <- function(n, growth = 1, bins = n/7100, p_pref = (1:(bins + 1))^-3, directed = TRUE) { as_tbl_graph(sample_last_cit(n, growth, bins, p_pref, directed)) } #' @describeIn evolution_games Create graphs by simulating the spead of fire in #' a forest. See [igraph::sample_forestfire()] #' @importFrom igraph sample_forestfire #' @export play_forestfire <- function(n, p_forward, p_backward = p_forward, growth = 1, directed = TRUE) { as_tbl_graph(sample_forestfire(n, p_forward, p_forward/p_backward, growth, directed)) } #' @describeIn evolution_games Create graphs by adding a fixed number of edges #' at each iteration. See [igraph::sample_growing()] #' @importFrom igraph sample_growing #' @export play_growing <- function(n, growth = 1, directed = TRUE, citation = FALSE) { as_tbl_graph(sample_growing(n, growth, directed, citation)) } #' @describeIn evolution_games Create graphs based on the Barabasi-Alberts #' preferential attachment model. See [igraph::sample_pa()] #' @importFrom igraph sample_pa #' @export play_barabasi_albert <- function(n, power, growth = 1, growth_dist = NULL, use_out = FALSE, appeal_zero = 1, directed = TRUE, method = 'psumtree') { if (length(growth) == 1) { m <- growth out.seq <- NULL } else { m <- NULL out.seq <- growth } if (!is.null(growth_dist)) { m <- NULL out.seq <- NULL } as_tbl_graph(sample_pa(n, power, m, growth_dist, out.seq, use_out, appeal_zero, directed, method)) } #' @describeIn evolution_games Create graphs based on the Barabasi-Alberts #' preferential attachment model, incoorporating node age preferrence. See #' [igraph::sample_pa_age()]. #' @importFrom igraph sample_pa_age #' @export play_barabasi_albert_aging <- function(n, power, power_age, growth = 1, growth_dist = NULL, bins = 300, use_out = FALSE, appeal_zero = 1, appeal_zero_age = 0, directed = TRUE, coefficient = 1, coefficient_age = 1, window = NULL) { if (length(growth) == 1) { m <- growth out.seq <- NULL } else { m <- NULL out.seq <- growth } if (!is.null(growth_dist)) { m <- NULL out.seq <- NULL } as_tbl_graph(sample_pa_age(n, power, power_age, m, bins, growth_dist, out.seq, use_out, directed, appeal_zero, appeal_zero_age, coefficient, coefficient_age, window)) } #' Graph games based on connected components #' #' This set of graph creation algorithms simulate the topology by, in some way, #' connecting subgraphs. The nature of their algorithm is described in detail at #' the linked igraph documentation. #' #' @inheritParams sampling_games #' @param m_between The number of edges between groups/islands #' @param n_islands The number of densely connected islands #' @param size_islands The number of nodes in each island #' @param size_blocks The number of vertices in each block #' @param p_between,p_within The probability of edges within and between groups/blocks #' @param rho The fraction of vertices per cluster #' @param n_dim,dim_size The dimension and size of the starting lattice #' @param p_rewire The rewiring probability of edges #' @param order The neighborhood size to create connections from #' #' @return A tbl_graph object #' #' @rdname component_games #' @name component_games #' @family graph games #' #' @examples #' plot(play_islands(4, 10, 0.7, 3)) #' NULL #' @describeIn component_games Create graphs by sampling from stochastic block #' model. See [igraph::sample_sbm()] #' @importFrom igraph sample_sbm #' @export play_blocks <- function(n, size_blocks, p_between, directed = TRUE, loops = FALSE) { as_tbl_graph(sample_sbm(n, p_between, size_blocks, directed, loops)) } #' @describeIn component_games Create graphs by sampling from the hierarchical #' stochastic block model. See [igraph::sample_hierarchical_sbm()] #' @importFrom igraph sample_hierarchical_sbm #' @export play_blocks_hierarchy <- function(n, size_blocks, rho, p_within, p_between) { as_tbl_graph(sample_hierarchical_sbm(n, size_blocks, rho, p_within, p_between)) } #' @describeIn component_games Create graphs with fixed size and edge #' probability of subgraphs as well as fixed edge count between subgraphs. See #' [igraph::sample_islands()] #' @importFrom igraph sample_islands #' @export play_islands <- function(n_islands, size_islands, p_within, m_between) { as_tbl_graph(sample_islands(n_islands, size_islands, p_within, m_between)) } #' @describeIn component_games Create graphs based on the Watts-Strogatz small- #' world model. See [igraph::sample_smallworld()] #' @importFrom igraph sample_smallworld #' @export play_smallworld <- function(n_dim, dim_size, order, p_rewire, loops = FALSE, multiple = FALSE) { as_tbl_graph(sample_smallworld(n_dim, dim_size, order, p_rewire, loops, multiple)) } tidygraph/R/sample_n.R0000644000176200001440000000211613545322217014373 0ustar liggesusers#' @export #' @importFrom dplyr sample_n #' @importFrom igraph delete_vertices delete_edges #' @importFrom rlang enquo sample_n.tbl_graph <- function(tbl, size = 1, replace = FALSE, weight = NULL, .env = parent.frame(), ...) { d_tmp <- as_tibble(tbl) weight <- enquo(weight) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- sample_n(d_tmp, size = size, replace = replace, weight = !! weight, .env = .env) remove_ind <- orig_ind[-d_tmp$.tbl_graph_index] switch( active(tbl), nodes = delete_vertices(tbl, remove_ind), edges = delete_edges(tbl, remove_ind) ) %gr_attr% tbl } #' @export #' @importFrom dplyr sample_frac #' @importFrom rlang enquo sample_n.morphed_tbl_graph <- function(tbl, size = 1, replace = FALSE, weight = NULL, .env = parent.frame(), ...) { weight <- enquo(weight) tbl[] <- lapply(tbl, sample_n, size = size, replace = replace, weight = !! weight, .env = .env) tbl } #' @export dplyr::sample_n tidygraph/R/morph.R0000644000176200001440000002142713656200050013721 0ustar liggesusers#' Create a temporary alternative representation of the graph to compute on #' #' The `morph`/`unmorph` verbs are used to create temporary representations of #' the graph, such as e.g. its search tree or a subgraph. A morphed graph will #' accept any of the standard `dplyr` verbs, and changed to the data is #' automatically propagated to the original graph when unmorphing. Tidygraph #' comes with a range of [morphers], but is it also possible to supply your own. #' See Details for the requirement for custom morphers. The `crystallise` verb #' is used to extract the temporary graph representation into a tibble #' containing one separate graph per row and a `name` and `graph` column holding #' the name of each graph and the graph itself respectively. `convert()` is a #' shorthand for performing both `morph` and `crystallise` along with extracting #' a single `tbl_graph` (defaults to the first). For morphs were you know they #' only create a single graph, and you want to keep it, this is an easy way. #' #' @details #' It is only possible to change and add to node and edge data from a #' morphed state. Any filtering/removal of nodes and edges will not result in #' removal from the main graph. However, nodes and edges not present in the #' morphed state will be unaffected in the main graph when unmorphing (if new #' columns were added during the morhped state they will be filled with `NA`). #' #' Morphing an already morhped graph will unmorph prior to applying the new #' morph. #' #' During a morphed state, the mapping back to the original graph is stored in #' `.tidygraph_node_index` and `.tidygraph_edge_index` columns. These are #' accesible but protected, meaning that any changes to them with e.g. mutate #' will be ignored. Furthermore, if the morph results in the merging of nodes #' and/or edges the original data is stored in a `.data` column. This is #' protected as well. #' #' When supplying your own morphers the morphing function should accept a #' `tbl_graph` as its first input. The provided graph will already have nodes #' and edges mapped with a `.tidygraph_node_index` and `.tidygraph_edge_index` #' column. The return value must be a `tbl_graph` or a list of `tbl_graph`s and #' these must contain either a `.tidygraph_node_index` column or a #' `.tidygraph_edge_index` column (or both). Note that it is possible for the #' morph to have the edges mapped back to the original nodes and vice versa #' (e.g. as with [to_linegraph]). In that case the edge data in the morphed #' graph(s) will contain a `.tidygraph_node_index` column and or the node data a #' `.tidygraph_edge_index` column. If the morphing results in the collapse of #' multiple columns or edges the index columns should be converted to list #' columns mapping the new node/edge back to all the nodes/edges it represents. #' Furthermore the original node/edge data should be collapsed to a list of #' tibbles, with the row order matching the order in the index column element. #' #' @param .data A `tbl_graph` or a `morphed_tbl_graph` #' #' @param .f A morphing function. See [morphers] for a list of provided one. #' #' @param ... Arguments passed on to the morpher #' #' @param .select The graph to return during `convert()`. Either an index or the #' name as created during `crystallise()`. #' #' @param .clean Should references to the node and edge indexes in the original #' graph be removed when using `convert` #' #' @return A `morphed_tbl_graph` #' #' @export #' #' @examples #' create_notable('meredith') %>% #' mutate(group = group_infomap()) %>% #' morph(to_contracted, group) %>% #' mutate(group_centrality = centrality_pagerank()) %>% #' unmorph() morph <- function(.data, .f, ...) { UseMethod('morph') } #' @rdname morph #' @export unmorph <- function(.data) { UseMethod('unmorph') } #' @rdname morph #' @export crystallise <- function(.data) { UseMethod('crystallise') } #' @rdname morph #' @export crystallize <- crystallise #' @rdname morph #' @export convert <- function(.data, .f, ..., .select = 1, .clean = FALSE) { UseMethod('convert') } #' @export #' @importFrom rlang as_quosure sym quo_text enquo morph.tbl_graph <- function(.data, .f, ...) { if (inherits(.data, 'grouped_tbl_graph')) { message('Ungrouping prior to morphing') .data <- ungroup(.data) } .register_graph_context(.data) morph_name <- quo_text(enquo(.f)) current_active <- as_quosure(sym(active(.data)), environment()) .data <- mutate(activate(.data, 'nodes'), .tidygraph_node_index = seq_len(n())) .data <- mutate(activate(.data, 'edges'), .tidygraph_edge_index = seq_len(n())) .data <- activate(.data, !! current_active) morphed <- .f(.data, ...) if (!inherits(morphed, 'list')) morphed <- list(morphed) check_morph(morphed) morphed[] <- lapply(morphed, activate, what = !!current_active) structure( morphed, class = c('morphed_tbl_graph', 'list'), .orig_graph = .data, .morpher = morph_name ) } #' @export morph.morphed_tbl_graph <- function(.data, .f, ...) { message('Unmorphing tbl_graph first...') .data <- unmorph(.data) morph(.data, .f, ...) } #' @export unmorph.morphed_tbl_graph <- function(.data) { current_active <- as_quosure(sym(active(.data[[1]])), environment()) nodes <- bind_rows(lapply(.data, as_tibble, active = 'nodes')) edges <- bind_rows(lapply(.data, as_tibble, active = 'edges')) graph <- attr(.data, '.orig_graph') real_nodes <- as_tibble(graph, active = 'nodes') real_edges <- as_tibble(graph, active = 'edges') if ('.tidygraph_node_index' %in% names(nodes)) { real_nodes <- merge_meta(nodes, real_nodes, '.tidygraph_node_index') } else if ('.tidygraph_node_index' %in% names(edges)) { real_nodes <- merge_meta(edges, real_nodes, '.tidygraph_node_index') } if ('.tidygraph_edge_index' %in% names(nodes)) { real_edges <- merge_meta(nodes, real_edges, '.tidygraph_edge_index') } else if ('.tidygraph_edge_index' %in% names(edges)) { real_edges <- merge_meta(edges, real_edges, '.tidygraph_edge_index') } real_nodes$.tidygraph_node_index <- NULL real_edges$.tidygraph_edge_index <- NULL graph <- set_node_attributes(graph, real_nodes) graph <- set_edge_attributes(graph, real_edges) activate(graph, !!current_active) } #' @importFrom tibble tibble #' @importFrom rlang %||% #' @export crystallise.morphed_tbl_graph <- function(.data) { class(.data) <- 'list' attr(.data, '.orig_graph') <- NULL attr(.data, '.morpher') <- NULL name <- names(.data) %||% as.character(seq_along(.data)) graph <- unname(.data) tibble( name = name, graph = graph ) } #' @export convert.tbl_graph <- function(.data, .f, ..., .select = 1, .clean = FALSE) { stopifnot(length(.select) == 1) graphs <- crystallise(morph(.data, .f, ...)) if (is.character(.select)) { .select <- which(.select == graphs$name)[1] if (is.na(.select)) stop('.select does not match any named graph', call. = FALSE) } if (.select > nrow(graphs)) stop('convert did not create ', .select, ' graphs', call. = FALSE) graph <- graphs$graph[[.select]] if (.clean) { nodes <- as_tibble(graph, active = 'nodes') edges <- as_tibble(graph, active = 'edges') nodes$.tidygraph_node_index <- NULL edges$.tidygraph_edge_index <- NULL graph <- set_node_attributes(graph, nodes) graph <- set_edge_attributes(graph, edges) } graph } # HELPERS ----------------------------------------------------------------- #' @importFrom igraph vertex_attr_names edge_attr_names check_morph <- function(morph) { if (!all(vapply(morph, inherits, logical(1), 'tbl_graph'))) { stop('morph must consist of tbl_graphs') } lapply(morph, function(m) { attr_names <- c(vertex_attr_names(m), edge_attr_names(m)) if (!any(c('.tidygraph_node_index', '.tidygraph_edge_index') %in% attr_names)) { stop('Morph must contain reference to at either nodes or edges', call. = FALSE) } }) NULL } merge_meta <- function(new, into, col) { if (is.list(new[[col]])) { index <- new[[col]] new[[col]] <- NULL data <- new$.orig_data new$.orig_data <- NULL new <- new[rep(seq_along(index), lengths(index)), ] new[[col]] <- unlist(index) if (!is.null(data)) { data <- bind_rows(data) data <- data[, !names(data) %in% names(new)] new <- bind_cols(new, data) } } new <- new[!is.na(new[[col]]) & !duplicated(new[[col]]), ] complete <- as_tibble(modifyList( into[new[[col]], ], new )) complete <- bind_rows(complete, into[!into[[col]] %in% complete[[col]], ]) complete <- complete[order(complete[[col]]), ] complete } protect_ind <- function(data, .f, ...) { new_data <- .f(data, ...) new_data$.tidygraph_node_index <- data$.tidygraph_node_index new_data$.tidygraph_edge_index <- data$.tidygraph_edge_index if ((is.list(data$.tidygraph_node_index) || is.list(data$.tidygraph_edge_index)) && !is.null(data$.data)) { new_data$.data <- data$.data } new_data } tidygraph/R/rename.R0000644000176200001440000000060513545322217014045 0ustar liggesusers#' @export #' @importFrom dplyr rename rename.tbl_graph <- function(.data, ...) { .register_graph_context(.data) d_tmp <- as_tibble(.data) d_tmp <- rename(d_tmp, ...) set_graph_data(.data, d_tmp) } #' @export #' @importFrom dplyr rename rename.morphed_tbl_graph <- function(.data, ...) { .data[] <- lapply(.data, protect_ind, .f = rename, ...) .data } #' @export dplyr::rename tidygraph/R/list.R0000644000176200001440000000612313656211573013557 0ustar liggesusers#' @describeIn tbl_graph Method for adjacency lists and lists of node and edge tables #' @export as_tbl_graph.list <- function(x, directed = TRUE, node_key = 'name', ...) { graph <- switch( guess_list_type(x), adjacency = as_graph_adj_list(x, directed = directed), node_edge = as_graph_node_edge(x, directed = directed, node_key = node_key), unknown = stop("Unknown list format", call. = FALSE) ) as_tbl_graph(graph) } #' @export as.list.tbl_graph <- function(x, ...) { list( nodes = as_tibble(x, active = 'nodes'), edges = as_tibble(x, active = 'edges') ) } guess_list_type <- function(x) { if (length(x) == 2 && any(names(x) %in% c('nodes', 'vertices')) && any(names(x) %in% c('edges', 'links'))) { return('node_edge') } x <- lapply(x, function(el) el[!is.na(el)]) x[lengths(x) == 0] <- list(NULL) elements <- sapply(x[lengths(x) != 0], function(el) class(el)[1]) if (all(elements == 'character') && all(unlist(x) %in% names(x))) { return('adjacency') } if (any(elements %in% c('numeric'))) { x <- lapply(x, as.integer) elements[] <- 'integer' } if (all(elements == 'integer') && !anyNA(unlist(x)) && max(unlist(x)) <= length(x) && min(unlist(x)) >= 0) { return('adjacency') } 'unknown' } #' @importFrom igraph graph_from_adj_list set_vertex_attr as_graph_adj_list <- function(x, directed) { x <- lapply(x, function(el) el[!is.na(el)]) if (inherits(x[[1]], 'character')) { x <- split(match(unlist(x), names(x)), rep(factor(names(x), levels = names(x)), lengths(x))) } if (any(unlist(x) == 0)) { x <- lapply(x, `+`, 1) } gr <- graph_from_adj_list(unname(x), mode = if (directed) 'out' else 'all') if (!is.null(names(x))) { gr <- set_vertex_attr(gr, 'name', value = names(x)) } gr } #' @importFrom igraph graph_from_edgelist vertex_attr<- add_vertices gorder #' @importFrom tibble tibble as_graph_node_edge <- function(x, directed, node_key = 'name') { nodes <- x[[which(names(x) %in% c('nodes', 'vertices'))]] edges <- x[[which(names(x) %in% c('edges', 'links'))]] if (is.null(edges)) { edges <- tibble(from = integer(), to = integer()) } from_ind <- which(names(edges) == 'from') if (length(from_ind) == 0) from_ind <- 1 to_ind <- which(names(edges) == 'to') if (length(to_ind) == 0) to_ind <- 2 edges <- edges[, c(from_ind, to_ind, seq_along(edges)[-c(from_ind, to_ind)]), drop = FALSE] if (!is.null(nodes)) { if (is.na(node_key)) { name_ind <- 1L } else { name_ind <- which(names(nodes) == node_key) if (length(name_ind) == 0) name_ind <- 1 } if (is.character(edges[[1]])) { edges[, 1] <- match(edges[[1]], nodes[[name_ind]]) } if (is.character(edges[[2]])) { edges[, 2] <- match(edges[[2]], nodes[[name_ind]]) } } gr <- graph_from_edgelist(as.matrix(edges[, 1:2]), directed = directed) edge_attr(gr) <- as.list(edges[, -c(1:2), drop = FALSE]) if (!is.null(nodes)) { if (gorder(gr) != nrow(nodes)) { gr <- add_vertices(gr, nrow(nodes) - gorder(gr)) } vertex_attr(gr) <- as.list(nodes) } gr } tidygraph/R/tbl_graph.R0000644000176200001440000001573713656212064014555 0ustar liggesusers#' A data structure for tidy graph manipulation #' #' The `tbl_graph` class is a thin wrapper around an `igraph` object that #' provides methods for manipulating the graph using the tidy API. As it is just #' a subclass of `igraph` every igraph method will work as expected. A #' `grouped_tbl_graph` is the equivalent of a `grouped_df` where either the #' nodes or the edges has been grouped. The `grouped_tbl_graph` is not #' constructed directly but by using the [group_by()] verb. After creation of a #' `tbl_graph` the nodes are activated by default. The context can be changed #' using the [activate()] verb and affects all subsequent operations. Changing #' context automatically drops any grouping. The current active context can #' always be extracted with [as_tibble()], which drops the graph structure and #' just returns a `tbl_df` or a `grouped_df` depending on the state of the #' `tbl_graph`. The returned context can be overriden by using the `active` #' argument in [as_tibble()]. #' #' @details #' Constructors are provided for most data structures that resembles networks. #' If a class provides an [igraph::as.igraph()] method it is automatically #' supported. #' #' @param nodes A `data.frame` containing information about the nodes in the #' graph. If `edges$to` and/or `edges$from` are characters then they will be #' matched to the column named according to `node_key` in nodes, if it exists. #' If not, they will be matched to the first column. #' #' @param edges A `data.frame` containing information about the edges in the #' graph. The terminal nodes of each edge must either be encoded in a `to` and #' `from` column, or in the two first columns, as integers. These integers refer to #' `nodes` index. #' #' @param x An object convertible to a `tbl_graph` #' #' @param directed Should the constructed graph be directed (defaults to `TRUE`) #' #' @param node_key The name of the column in `nodes` that character represented #' `to` and `from` columns should be matched against. If `NA` the first column #' is always chosen. This setting has no effect if `to` and `from` are given as #' integers. #' #' @param mode In case `directed = TRUE` should the edge direction be away from #' node or towards. Possible values are `"out"` (default) or `"in"`. #' #' @param ... Arguments passed on to the conversion function #' #' @return A `tbl_graph` object #' #' @examples #' rstat_nodes <- data.frame(name = c("Hadley", "David", "Romain", "Julia")) #' rstat_edges <- data.frame(from = c(1, 1, 1, 2, 3, 3, 4, 4, 4), #' to = c(2, 3, 4, 1, 1, 2, 1, 2, 3)) #' tbl_graph(nodes = rstat_nodes, edges = rstat_edges) #' @export #' tbl_graph <- function(nodes = NULL, edges = NULL, directed = TRUE, node_key = 'name') { as_tbl_graph(list(nodes = nodes, edges = edges), directed = directed, node_key = node_key) } #' @rdname tbl_graph #' @export as_tbl_graph <- function(x, ...) { UseMethod('as_tbl_graph') } #' @describeIn tbl_graph Default method. tries to call [igraph::as.igraph()] on the input. #' @export #' @importFrom igraph as.igraph as_tbl_graph.default <- function(x, ...) { tryCatch({ as_tbl_graph(as.igraph(x)) }, error = function(e) stop('No support for ', class(x)[1], ' objects', call. = FALSE)) } #' @rdname tbl_graph #' @export is.tbl_graph <- function(x) { inherits(x, 'tbl_graph') } #' @importFrom tibble trunc_mat #' @importFrom tools toTitleCase #' @importFrom rlang as_quosure sym #' @importFrom pillar style_subtle #' @export print.tbl_graph <- function(x, ...) { arg_list <- list(...) graph_desc <- describe_graph(x) not_active <- if (active(x) == 'nodes') 'edges' else 'nodes' top <- do.call(trunc_mat, modifyList(arg_list, list(x = as_tibble(x), n = 6))) top$summary[1] <- paste0(top$summary[1], ' (active)') names(top$summary)[1] <- toTitleCase(paste0(substr(active(x), 1, 4), ' data')) bottom <- do.call(trunc_mat, modifyList(arg_list, list(x = as_tibble(x, active = not_active), n = 3))) names(bottom$summary)[1] <- toTitleCase(paste0(substr(not_active, 1, 4), ' data')) cat_subtle('# A tbl_graph: ', gorder(x), ' nodes and ', gsize(x), ' edges\n', sep = '') cat_subtle('#\n') cat_subtle('# ', graph_desc, '\n', sep = '') cat_subtle('#\n') print(top) cat_subtle('#\n') print(bottom) invisible(x) } cat_subtle <- function(...) cat(pillar::style_subtle(paste0(...))) #' @export print.morphed_tbl_graph <- function(x, ...) { graph <- attr(x, '.orig_graph') cat('# A tbl_graph temporarily morphed to a ', gsub('_', ' ', sub('to_', '', attr(x, '.morpher'))), ' representation\n', sep = '') cat('# \n') cat('# Original graph is ', tolower(describe_graph(graph)), '\n', sep = '') cat('# consisting of ', gorder(graph), ' nodes and ', gsize(graph), ' edges\n', sep = '') } #' @importFrom igraph is_simple is_directed is_bipartite is_connected is_dag gorder describe_graph <- function(x) { if (gorder(x) == 0) return('An empty graph') prop <- list(simple = is_simple(x), directed = is_directed(x), bipartite = is_bipartite(x), connected = is_connected(x), tree = is_tree(x), forest = is_forest(x), DAG = is_dag(x)) desc <- c() if (prop$tree || prop$forest) { desc[1] <- if (prop$directed) 'A rooted' else 'An unrooted' desc[2] <- if (prop$tree) 'tree' else paste0('forest with ', count_components(x), ' trees') } else { desc[1] <- if (prop$DAG) 'A directed acyclic' else if (prop$bipartite) 'A bipartite' else if (prop$directed) 'A directed' else 'An undirected' desc[2] <- if (prop$simple) 'simple graph' else 'multigraph' n_comp <- count_components(x) desc[3] <- paste0('with ' , n_comp, ' component', if (n_comp > 1) 's' else '') } paste(desc, collapse = ' ') } #' @importFrom igraph is_connected is_simple gorder gsize is_directed is_tree <- function(x) { is_connected(x) && is_simple(x) && (gorder(x) - gsize(x) == 1) } #' @importFrom igraph is_connected is_simple gorder gsize count_components is_directed is_forest <- function(x) { !is_connected(x) && is_simple(x) && (gorder(x) - gsize(x) - count_components(x) == 0) } #' @export as_tbl_graph.tbl_graph <- function(x, ...) { x } set_graph_data <- function(x, value, active) { UseMethod('set_graph_data') } set_graph_data.tbl_graph <- function(x, value, active = NULL) { if (is.null(active)) active <- active(x) switch( active, nodes = set_node_attributes(x, value), edges = set_edge_attributes(x, value), stop('Unknown active element: ', active(x), '. Only nodes and edges supported', call. = FALSE) ) } set_graph_data.grouped_tbl_graph <- function(x, value, active = NULL) { x <- NextMethod() apply_groups(x, attributes(value)) } #' @importFrom igraph vertex_attr<- set_node_attributes <- function(x, value) { vertex_attr(x) <- as.list(value) x } #' @importFrom igraph edge_attr<- set_edge_attributes <- function(x, value) { value <- value[, !names(value) %in% c('from', 'to')] edge_attr(x) <- as.list(value) x } #' @importFrom dplyr tbl_vars #' @export tbl_vars.tbl_graph <- function(x) { tbl_vars(as_tibble(x)) } #' @export dplyr::tbl_vars tidygraph/R/tidygraph-package.R0000644000176200001440000000012713545334122016157 0ustar liggesusers#' @useDynLib tidygraph #' @importFrom Rcpp sourceCpp #' @keywords internal '_PACKAGE' tidygraph/R/graph_types.R0000644000176200001440000000503713545322217015127 0ustar liggesusers#' Querying graph types #' #' This set of functions lets the user query different aspects of the graph #' itself. They are all concerned with wether the graph implements certain #' properties and will all return a logical scalar. #' #' @param graph The graph to compare structure to #' #' @param method The algorithm to use for comparison #' #' @param ... Arguments passed on to the comparison methods. See #' [igraph::is_isomorphic_to()] and [igraph::is_subgraph_isomorphic_to()] #' #' @return A logical scalar #' #' @name graph_types #' @rdname graph_types #' #' @examples #' gr <- create_tree(50, 4) #' #' with_graph(gr, graph_is_tree()) #' NULL #' @describeIn graph_types Is the graph simple (no parallel edges) #' @importFrom igraph is_simple #' @export graph_is_simple <- function() { is_simple(.G()) } #' @describeIn graph_types Is the graph directed #' @importFrom igraph is_directed #' @export graph_is_directed <- function() { is_directed(.G()) } #' @describeIn graph_types Is the graph bipartite #' @importFrom igraph is_bipartite #' @export graph_is_bipartite <- function() { is_bipartite(.G()) } #' @describeIn graph_types Is the graph connected #' @importFrom igraph is_connected #' @export graph_is_connected <- function() { is_connected(.G()) } #' @describeIn graph_types Is the graph a tree #' @export graph_is_tree <- function() { is_tree(.G()) } #' @describeIn graph_types Is the graph an ensemble of multiple trees #' @export graph_is_forest <- function() { is_forest(.G()) } #' @describeIn graph_types Is the graph a directed acyclic graph #' @importFrom igraph is_dag #' @export graph_is_dag <- function() { is_dag(.G()) } #' @describeIn graph_types Is the graph chordal #' @importFrom igraph is_chordal #' @export graph_is_chordal <- function() { is_chordal(.G())$chordal } #' @describeIn graph_types Is the graph fully connected #' @export graph_is_complete <- function() { graph_is_simple() && all(centrality_degree(mode = 'all', loops = FALSE) == graph_order() - 1) } #' @describeIn graph_types Is the graph isomorphic to another graph. See [igraph::is_isomorphic_to()] #' @importFrom igraph is_isomorphic_to #' @export graph_is_isomorphic_to <- function(graph, method = 'auto', ...) { is_isomorphic_to(.G(), graph, method, ...) } #' @describeIn graph_types Is the graph an isomorphic subgraph to another graph. see [igraph::is_subgraph_isomorphic_to()] #' @importFrom igraph is_subgraph_isomorphic_to #' @export graph_is_subgraph_isomorphic_to <- function(graph, method = 'auto', ...) { is_subgraph_isomorphic_to(.G(), graph, method, ...) } tidygraph/R/phylo.R0000644000176200001440000000170313545322217013731 0ustar liggesusers#' @describeIn tbl_graph Method for handling phylo objects from the ape package #' @importFrom igraph set_edge_attr #' @export as_tbl_graph.phylo <- function(x, directed = NULL, ...) { if (!requireNamespace("ape", quietly = TRUE)) { stop('The "ape" package is needed for this functionality to work', call. = FALSE) } if (is.null(directed)) directed <- ape::is.rooted(x) gr <- ape::as.igraph.phylo(x, directed = directed, ...) if (!is.null(x$edge.length)) gr <- set_edge_attr(gr, 'length', value = x$edge.length) as_tbl_graph(gr) } #' @describeIn tbl_graph Method for handling evonet objects from the ape package #' @export as_tbl_graph.evonet <- function(x, directed = TRUE, ...) { if (!requireNamespace("ape", quietly = TRUE)) { stop('The "ape" package is needed for this functionality to work', call. = FALSE) } if (is.null(directed)) directed <- ape::is.rooted(x) as_tbl_graph(ape::as.igraph.evonet(x, directed = directed, ...)) } tidygraph/R/map.R0000644000176200001440000005214013656245241013360 0ustar liggesusers#' Apply a function to nodes in the order of a breath first search #' #' These functions allow you to map over the nodes in a graph, by first #' performing a breath first search on the graph and then mapping over each #' node in the order they are visited. The mapping function will have access to #' the result and search statistics for all the nodes between itself and the #' root in the search. To map over the nodes in the reverse direction use #' [map_bfs_back()]. #' #' @details #' The function provided to `.f` will be called with the following arguments in #' addition to those supplied through `...`: #' #' * `graph`: The full `tbl_graph` object #' * `node`: The index of the node currently mapped over #' * `rank`: The rank of the node in the search #' * `parent`: The index of the node that led to the current node #' * `before`: The index of the node that was visited before the current node #' * `after`: The index of the node that was visited after the current node. #' * `dist`: The distance of the current node from the root #' * `path`: A table containing `node`, `rank`, `parent`, `before`, `after`, #' `dist`, and `result` columns giving the values for each node leading to the #' current node. The `result` column will contain the result of the mapping #' of each node in a list. #' #' Instead of spelling out all of these in the function it is possible to simply #' name the ones needed and use `...` to catch the rest. #' #' @param root The node to start the search from #' #' @param mode How should edges be followed? `'out'` only follows outbound #' edges, `'in'` only follows inbound edges, and `'all'` follows all edges. This #' parameter is ignored for undirected graphs. #' #' @param unreachable Should the search jump to an unvisited node if the search #' is completed without visiting all nodes. #' #' @param .f A function to map over all nodes. See Details #' #' @param ... Additional parameters to pass to `.f` #' #' @return `map_bfs()` returns a list of the same length as the number of nodes #' in the graph, in the order matching the node order in the graph (that is, not #' in the order they are called). `map_bfs_*()` tries to coerce its result into #' a vector of the classes `logical` (`map_bfs_lgl`), `character` #' (`map_bfs_chr`), `integer` (`map_bfs_int`), or `double` (`map_bfs_dbl`). #' These functions will throw an error if they are unsuccesful, so they are type #' safe. #' #' @family node map functions #' #' @export #' @importFrom igraph gorder #' #' @examples #' # Accumulate values along a search #' create_tree(40, children = 3, directed = TRUE) %>% #' mutate(value = round(runif(40)*100)) %>% #' mutate(value_acc = map_bfs_dbl(node_is_root(), .f = function(node, path, ...) { #' sum(.N()$value[c(node, path$node)]) #' })) map_bfs <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) dot_params <- list(...) search_df <- bfs_df(graph, root, mode, unreachable) paths <- get_paths(as.integer(search_df$parent)) call_nodes(graph, .f, search_df, paths, dot_params) } #' @rdname map_bfs #' @export map_bfs_lgl <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_bfs(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = logical(1)) } #' @rdname map_bfs #' @export map_bfs_chr <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_bfs(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = character(1)) } #' @rdname map_bfs #' @export map_bfs_int <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_bfs(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = integer(1)) } #' @rdname map_bfs #' @export map_bfs_dbl <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_bfs(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = double(1)) } #' Apply a function to nodes in the reverse order of a breath first search #' #' These functions allow you to map over the nodes in a graph, by first #' performing a breath first search on the graph and then mapping over each #' node in the reverse order they are visited. The mapping function will have #' access to the result and search statistics for all the nodes following itself #' in the search. To map over the nodes in the original direction use #' [map_bfs()]. #' #' @details #' The function provided to `.f` will be called with the following arguments in #' addition to those supplied through `...`: #' #' * `graph`: The full `tbl_graph` object #' * `node`: The index of the node currently mapped over #' * `rank`: The rank of the node in the search #' * `parent`: The index of the node that led to the current node #' * `before`: The index of the node that was visited before the current node #' * `after`: The index of the node that was visited after the current node. #' * `dist`: The distance of the current node from the root #' * `path`: A table containing `node`, `rank`, `parent`, `before`, `after`, #' `dist`, and `result` columns giving the values for each node reached from #' the current node. The `result` column will contain the result of the mapping #' of each node in a list. #' #' Instead of spelling out all of these in the function it is possible to simply #' name the ones needed and use `...` to catch the rest. #' #' @inheritParams map_bfs #' #' @return `map_bfs_back()` returns a list of the same length as the number of #' nodes in the graph, in the order matching the node order in the graph (that #' is, not in the order they are called). `map_bfs_back_*()` tries to coerce #' its result into a vector of the classes `logical` (`map_bfs_back_lgl`), #' `character` (`map_bfs_back_chr`), `integer` (`map_bfs_back_int`), or `double` #' (`map_bfs_back_dbl`). These functions will throw an error if they are #' unsuccesful, so they are type safe. #' #' @family node map functions #' #' @export #' @importFrom igraph gorder #' #' @examples #' # Collect values from children #' create_tree(40, children = 3, directed = TRUE) %>% #' mutate(value = round(runif(40)*100)) %>% #' mutate(child_acc = map_bfs_back_dbl(node_is_root(), .f = function(node, path, ...) { #' if (nrow(path) == 0) .N()$value[node] #' else { #' sum(unlist(path$result[path$parent == node])) #' } #' })) map_bfs_back <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) dot_params <- list(...) search_df <- bfs_df(graph, root, mode, unreachable) offspring <- get_offspring(as.integer(search_df$parent), order(search_df$rank)) call_nodes(graph, .f, search_df, offspring, dot_params, reverse = TRUE) } #' @rdname map_bfs_back #' @export map_bfs_back_lgl <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_bfs_back(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = logical(1)) } #' @rdname map_bfs_back #' @export map_bfs_back_chr <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_bfs_back(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = character(1)) } #' @rdname map_bfs_back #' @export map_bfs_back_int <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_bfs_back(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = integer(1)) } #' @rdname map_bfs_back #' @export map_bfs_back_dbl <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_bfs_back(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = double(1)) } #' Apply a function to nodes in the order of a depth first search #' #' These functions allow you to map over the nodes in a graph, by first #' performing a depth first search on the graph and then mapping over each #' node in the order they are visited. The mapping function will have access to #' the result and search statistics for all the nodes between itself and the #' root in the search. To map over the nodes in the reverse direction use #' [map_dfs_back()]. #' #' @details #' The function provided to `.f` will be called with the following arguments in #' addition to those supplied through `...`: #' #' * `graph`: The full `tbl_graph` object #' * `node`: The index of the node currently mapped over #' * `rank`: The rank of the node in the search #' * `rank_out`: The rank of the completion of the nodes subtree #' * `parent`: The index of the node that led to the current node #' * `dist`: The distance of the current node from the root #' * `path`: A table containing `node`, `rank`, `rank_out`, `parent`, dist`, and #' `result` columns giving the values for each node leading to the #' current node. The `result` column will contain the result of the mapping #' of each node in a list. #' #' Instead of spelling out all of these in the function it is possible to simply #' name the ones needed and use `...` to catch the rest. #' #' @inheritParams map_bfs #' #' @return `map_dfs()` returns a list of the same length as the number of nodes #' in the graph, in the order matching the node order in the graph (that is, not #' in the order they are called). `map_dfs_*()` tries to coerce its result into #' a vector of the classes `logical` (`map_dfs_lgl`), `character` #' (`map_dfs_chr`), `integer` (`map_dfs_int`), or `double` (`map_dfs_dbl`). #' These functions will throw an error if they are unsuccesful, so they are type #' safe. #' #' @family node map functions #' #' @export #' @importFrom igraph gorder #' #' @examples #' # Add a random integer to the last value along a search #' create_tree(40, children = 3, directed = TRUE) %>% #' mutate(child_acc = map_dfs_int(node_is_root(), .f = function(node, path, ...) { #' last_val <- if (nrow(path) == 0) 0L else tail(unlist(path$result), 1) #' last_val + sample(1:10, 1) #' })) map_dfs <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) dot_params <- list(...) search_df <- dfs_df(graph, root, mode, unreachable) paths <- get_paths(as.integer(search_df$parent)) call_nodes(graph, .f, search_df, paths, dot_params) } #' @rdname map_dfs #' @export map_dfs_lgl <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_dfs(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = logical(1)) } #' @rdname map_dfs #' @export map_dfs_chr <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_dfs(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = character(1)) } #' @rdname map_dfs #' @export map_dfs_int <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_dfs(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = integer(1)) } #' @rdname map_dfs #' @export map_dfs_dbl <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_dfs(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = double(1)) } #' Apply a function to nodes in the reverse order of a depth first search #' #' These functions allow you to map over the nodes in a graph, by first #' performing a depth first search on the graph and then mapping over each #' node in the reverse order they are visited. The mapping function will have #' access to the result and search statistics for all the nodes following itself #' in the search. To map over the nodes in the original direction use #' [map_dfs()]. #' #' @details #' The function provided to `.f` will be called with the following arguments in #' addition to those supplied through `...`: #' #' * `graph`: The full `tbl_graph` object #' * `node`: The index of the node currently mapped over #' * `rank`: The rank of the node in the search #' * `rank_out`: The rank of the completion of the nodes subtree #' * `parent`: The index of the node that led to the current node #' * `dist`: The distance of the current node from the root #' * `path`: A table containing `node`, `rank`, `rank_out`, `parent`, dist`, and #' `result` columns giving the values for each node reached from #' the current node. The `result` column will contain the result of the mapping #' of each node in a list. #' #' Instead of spelling out all of these in the function it is possible to simply #' name the ones needed and use `...` to catch the rest. #' #' @inheritParams map_bfs #' #' @return `map_dfs_back()` returns a list of the same length as the number of #' nodes in the graph, in the order matching the node order in the graph (that #' is, not in the order they are called). `map_dfs_back_*()` tries to coerce #' its result into a vector of the classes `logical` (`map_dfs_back_lgl`), #' `character` (`map_dfs_back_chr`), `integer` (`map_dfs_back_int`), or `double` #' (`map_dfs_back_dbl`). These functions will throw an error if they are #' unsuccesful, so they are type safe. #' #' @family node map functions #' #' @export #' @importFrom igraph gorder #' #' @examples #' # Collect values from the 2 closest layers of children in a dfs search #' create_tree(40, children = 3, directed = TRUE) %>% #' mutate(value = round(runif(40)*100)) %>% #' mutate(child_acc = map_dfs_back(node_is_root(), .f = function(node, path, dist, ...) { #' if (nrow(path) == 0) .N()$value[node] #' else { #' unlist(path$result[path$dist - dist <= 2]) #' } #' })) map_dfs_back <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) dot_params <- list(...) search_df <- dfs_df(graph, root, mode, unreachable) offspring <- get_offspring(as.integer(search_df$parent), order(search_df$rank)) call_nodes(graph, .f, search_df, offspring, dot_params, reverse = TRUE) } #' @rdname map_dfs_back #' @export map_dfs_back_lgl <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_dfs_back(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = logical(1)) } #' @rdname map_dfs_back #' @export map_dfs_back_chr <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_dfs_back(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = character(1)) } #' @rdname map_dfs_back #' @export map_dfs_back_int <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_dfs_back(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = integer(1)) } #' @rdname map_dfs_back #' @export map_dfs_back_dbl <- function(root, mode = 'out', unreachable = FALSE, .f, ...) { res <- map_dfs_back(root = root, mode = mode, unreachable = unreachable, .f = .f, ...) as_vector(res, .type = double(1)) } #' Map a function over a graph representing the neighborhood of each node #' #' This function extracts the neighborhood of each node as a graph and maps over #' each of these neighborhood graphs. Conceptually it is similar to #' [igraph::local_scan()], but it borrows the type safe versions available in #' [map_bfs()] and [map_dfs()]. #' #' @details #' The function provided to `.f` will be called with the following arguments in #' addition to those supplied through `...`: #' #' * `neighborhood`: The neighborhood graph of the node #' * `graph`: The full `tbl_graph` object #' * `node`: The index of the node currently mapped over #' #' The `neighborhood` graph will contain an extra node attribute called #' `.central_node`, which will be `TRUE` for the node that the neighborhood is #' expanded from and `FALSE` for everything else. #' #' @inheritParams igraph::ego #' @inheritParams map_bfs #' #' @return `map_local()` returns a list of the same length as the number of #' nodes in the graph, in the order matching the node order in the graph. #' `map_local_*()` tries to coerce its result into a vector of the classes #' `logical` (`map_local_lgl`), `character` (`map_local_chr`), `integer` #' (`map_local_int`), or `double` (`map_local_dbl`). These functions will throw #' an error if they are unsuccesful, so they are type safe. #' #' @importFrom igraph gorder make_ego_graph V<- #' @export #' #' @examples #' # Smooth out values over a neighborhood #' create_notable('meredith') %>% #' mutate(value = rpois(graph_order(), 5)) %>% #' mutate(value_smooth = map_local_dbl(order = 2, .f = function(neighborhood, ...) { #' mean(as_tibble(neighborhood, active = 'nodes')$value) #' })) map_local <- function(order = 1, mode = 'all', mindist = 0, .f, ...) { expect_nodes() graph <- .G() V(graph)$.central_node <- FALSE res <- lapply(seq_len(gorder(graph)), function(i) { V(graph)$.central_node[i] <- TRUE ego_graph <- make_ego_graph(graph, order = order, nodes = i, mode = mode, mindist = mindist)[[1]] .f(neighborhood = as_tbl_graph(ego_graph), graph = graph, node = i, ...) }) } #' @rdname map_local #' @export map_local_lgl <- function(order = 1, mode = 'all', mindist = 0, .f, ...) { res <- map_local(order = order, mode = mode, mindist = mindist, .f = .f, ...) as_vector(res, .type = logical(1)) } #' @rdname map_local #' @export map_local_chr <- function(order = 1, mode = 'all', mindist = 0, .f, ...) { res <- map_local(order = order, mode = mode, mindist = mindist, .f = .f, ...) as_vector(res, .type = character(1)) } #' @rdname map_local #' @export map_local_int <- function(order = 1, mode = 'all', mindist = 0, .f, ...) { res <- map_local(order = order, mode = mode, mindist = mindist, .f = .f, ...) as_vector(res, .type = integer(1)) } #' @rdname map_local #' @export map_local_dbl <- function(order = 1, mode = 'all', mindist = 0, .f, ...) { res <- map_local(order = order, mode = mode, mindist = mindist, .f = .f, ...) as_vector(res, .type = double(1)) } # Helpers ----------------------------------------------------------------- #' @importFrom igraph bfs #' @importFrom tibble tibble bfs_df <- function(graph, root, mode, unreachable) { search <- bfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, rank = TRUE, father = TRUE, pred = TRUE, succ = TRUE, dist = TRUE) nodes <- seq_along(search$order) tibble( node = nodes, rank = as.integer(search$rank), parent = as.integer(search$father), before = as.integer(search$pred), after = as.integer(search$succ), dist = as.integer(search$dist), result = rep(list(NULL), length(nodes)) ) } #' @importFrom igraph dfs #' @importFrom tibble tibble dfs_df <- function(graph, root, mode, unreachable) { search <- dfs(graph = graph, root = root, neimode = mode, unreachable = unreachable, order = TRUE, order.out = TRUE, father = TRUE, dist = TRUE) nodes <- seq_along(search$order) tibble( node = nodes, rank = match(nodes, as.integer(search$order)), rank_out = match(nodes, as.integer(search$order.out)), parent = as.integer(search$father), dist = as.integer(search$dist), result = rep(list(NULL), length(nodes)) ) } call_nodes <- function(graph, .f, search, connections, dot_params, reverse = FALSE) { not_results <- which(names(search) != 'result') call_order <- order(search$rank) if (reverse) call_order <- rev(call_order) for (i in call_order) { if (is.na(i)) break conn <- connections[[i]] search$result[[i]] <- do.call( .f, c(list(graph = graph), as.list(search[i, not_results]), list(path = search[conn, , drop = FALSE]), dot_params) ) } search$result } get_offspring <- function(parent, order) { offspring <- rep(list(integer(0)), length(parent)) direct_offspring <- split(seq_along(parent), parent) offspring[as.integer(names(direct_offspring))] <- direct_offspring offspring <- collect_offspring(offspring, rev(order)) lapply(offspring, function(x) x[order(match(x, order))]) } # Avoid importing full purrr for as_vector fun can_simplify <- function(x, type = NULL) { is_atomic <- vapply(x, is.atomic, logical(1)) if (!all(is_atomic)) return(FALSE) mode <- unique(vapply(x, typeof, character(1))) if (length(mode) > 1 && !all(c("double", "integer") %in% mode)) { return(FALSE) } is.null(type) || can_coerce(x, type) } can_coerce <- function(x, type) { actual <- typeof(x[[1]]) if (is_mold(type)) { lengths <- unique(lengths(x)) if (length(lengths) > 1 || !(lengths == length(type))) { return(FALSE) } else { type <- typeof(type) } } if (actual == "integer" && type %in% c("integer", "double", "numeric")) { return(TRUE) } if (actual %in% c("integer", "double") && type == "numeric") { return(TRUE) } actual == type } is_mold <- function (type) { modes <- c("numeric", "logical", "integer", "double", "complex", "character", "raw") length(type) > 1 || (!type %in% modes) } as_vector <- function(.x, .type = NULL){ null_elem <- sapply(.x, is.null) if (any(null_elem)) { na <- rep(NA, length(.type)) class(na) <- class(.type) .x[null_elem] <- na } if (can_simplify(.x, .type)) { unlist(.x) } else { stop("Cannot coerce values to ", deparse(substitute(.type)), call. = FALSE) } } tidygraph/R/node.R0000644000176200001440000001740113656241404013526 0ustar liggesusers#' Querying node types #' #' These functions all lets the user query whether each node is of a certain #' type. All of the functions returns a logical vector indicating whether the #' node is of the type in question. Do note that the types are not mutually #' exclusive and that nodes can thus be of multiple types. #' #' @param mode The way edges should be followed in the case of directed graphs. #' #' @return A logical vector of the same length as the number of nodes in the #' graph. #' #' @name node_types #' @rdname node_types #' #' @examples #' # Find the root and leafs in a tree #' create_tree(40, 2) %>% #' mutate(root = node_is_root(), leaf = node_is_leaf()) NULL #' @describeIn node_types is the node a cut node (articaultion node) #' @importFrom igraph gorder articulation_points #' @export node_is_cut <- function() { expect_nodes() graph <- .G() seq_len(gorder(graph)) %in% articulation_points(graph) } #' @describeIn node_types is the node a root in a tree #' @importFrom igraph degree is.directed #' @export node_is_root <- function() { expect_nodes() graph <- .G() if ((!is_tree(graph) && !is_forest(graph)) || !is.directed(graph)) { return(rep(FALSE, gorder(graph))) } deg_in <- degree(graph, mode = 'in') == 0 deg_out <- degree(graph, mode = 'out') == 0 if (sum(deg_in) > sum(deg_out)) deg_out else deg_in } #' @describeIn node_types is the node a leaf in a tree #' @importFrom igraph degree is.directed #' @export node_is_leaf <- function() { expect_nodes() graph <- .G() if ((!is_tree(graph) && !is_forest(graph))) { return(rep(FALSE, gorder(graph))) } if (is.directed(graph)) { deg_in <- degree(graph, mode = 'in') == 0 deg_out <- degree(graph, mode = 'out') == 0 if (sum(deg_out) > sum(deg_in)) deg_out else deg_in } else { degree(graph, mode = 'all') == 1 } } #' @describeIn node_types does the node only have incomming edges #' @importFrom igraph degree #' @export node_is_sink <- function() { expect_nodes() graph <- .G() deg_in <- degree(graph, mode = 'in') deg_out <- degree(graph, mode = 'out') deg_out == 0 & deg_in != 0 } #' @describeIn node_types does the node only have outgoing edges #' @importFrom igraph degree #' @export node_is_source <- function() { expect_nodes() graph <- .G() deg_in <- degree(graph, mode = 'in') deg_out <- degree(graph, mode = 'out') deg_out != 0 & deg_in == 0 } #' @describeIn node_types is the node unconnected #' @importFrom igraph degree #' @export node_is_isolated <- function() { expect_nodes() graph <- .G() degree(graph) == 0 } #' @describeIn node_types is the node connected to all other nodes in the graph #' @importFrom igraph ego_size gorder #' @export node_is_universal <- function(mode = 'out') { expect_nodes() graph <- .G() ego_size(graph, order = 1, mode = mode) == gorder(graph) } #' @describeIn node_types are all the neighbors of the node connected #' @importFrom igraph local_scan ecount ego_size #' @export node_is_simplical <- function(mode = 'out') { expect_nodes() graph <- .G() n_edges <- local_scan(graph, k = 1, mode = mode, FUN = ecount) n_nodes <- ego_size(graph, order = 1, mode = mode) n_edges == n_nodes * (n_nodes - 1) * 0.5 } #' @describeIn node_types does the node have the minimal eccentricity in the graph #' @importFrom igraph eccentricity #' @export node_is_center <- function(mode = 'out') { expect_nodes() graph <- .G() ecc <- eccentricity(graph, mode = mode) ecc == min(ecc) } #' @describeIn node_types is a node adjacent to any of the nodes given in `to` #' @param to The nodes to test for adjacency to #' @param include_to Should the nodes in `to` be marked as adjacent as well #' @importFrom igraph gorder adjacent_vertices #' @export node_is_adjacent <- function(to, mode = 'all', include_to = TRUE) { expect_nodes() graph <- .G() n_nodes <- gorder(graph) to <- as_ind(to, n_nodes) include <- unlist(adjacent_vertices(graph, to, mode)) if (include_to) include <- union(to, include) seq_len(n_nodes) %in% include } #' @describeIn node_types Is a node part of the keyplayers in the graph (`influenceR`) #' @param k The number of keyplayers to identify #' @param p The probability to accept a lesser state #' @param tol Optimisation tolerance, below which the optimisation will stop #' @param maxsec The total computation budget for the optimization, in seconds #' @param roundsec Number of seconds in between synchronizing workers' answer #' @importFrom igraph gorder #' @export node_is_keyplayer <- function(k, p = 0, tol = 1e-4, maxsec = 120, roundsec = 30) { expect_influencer() expect_nodes() graph <- .G() ind <- influenceR::keyplayer(graph, k = k, prob = p, tol = tol, maxsec = maxsec, roundsec = roundsec) seq_len(gorder(graph)) %in% ind } #' Querying node measures #' #' These functions are a collection of node measures that do not really fall #' into the class of [centrality] measures. For lack of a better place they are #' collected under the `node_*` umbrella of functions. #' #' @inheritParams node_types #' @param weights The weights to use for each node during calculation #' #' @return A numeric vector of the same length as the number of nodes in the #' graph. #' #' @name node_measures #' @rdname node_measures #' #' @examples #' # Calculate Burt's Constraint for each node #' create_notable('meredith') %>% #' mutate(b_constraint = node_constraint()) NULL #' @describeIn node_measures measure the maximum shortest path to all other nodes in the graph #' @importFrom igraph eccentricity #' @export node_eccentricity <- function(mode = 'out') { expect_nodes() graph <- .G() eccentricity(graph, V(graph), mode = mode) } #' @describeIn node_measures measures Burts constraint of the node. See [igraph::constraint()] #' @importFrom igraph constraint #' @export node_constraint <- function(weights = NULL) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- rep_len(1L, gsize(graph)) } constraint(graph, V(graph), weights = weights) } #' @describeIn node_measures measures the coreness of each node. See [igraph::coreness()] #' @importFrom igraph coreness #' @export node_coreness <- function(mode = 'out') { expect_nodes() graph <- .G() coreness(graph, mode = mode) } #' @describeIn node_measures measures the diversity of the node. See [igraph::diversity()] #' @importFrom igraph diversity #' @export node_diversity <- function(weights) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { stop('weights must evaluate to a valid vector', call. = FALSE) } diversity(graph, weights = weights, vids = V(graph)) } #' @describeIn node_measures measures Valente's Bridging measures for detecting structural bridges (`influenceR`) #' @export node_bridging_score <- function() { expect_influencer() expect_nodes() influenceR::bridging(.G()) } #' @describeIn node_measures measures Burt's Effective Network Size indicating access to structural holes in the network (`influenceR`) #' @export node_effective_network_size <- function() { expect_influencer() expect_nodes() influenceR::ens(.G()) } #' @describeIn node_measures measures the impact on connectivity when removing the node (`NetSwan`) #' @export node_connectivity_impact <- function() { expect_netswan() expect_nodes() NetSwan::swan_connectivity(.G()) } #' @describeIn node_measures measures the impact on closeness when removing the node (`NetSwan`) #' @export node_closeness_impact <- function() { expect_netswan() expect_nodes() NetSwan::swan_closeness(.G()) } #' @describeIn node_measures measures the impact on fareness (distance between all node pairs) when removing the node (`NetSwan`) #' @export node_fareness_impact <- function() { expect_netswan() expect_nodes() NetSwan::swan_efficiency(.G()) } tidygraph/R/morphers.R0000644000176200001440000003177413656237254014461 0ustar liggesusers#' Functions to generate alternate representations of graphs #' #' These functions are meant to be passed into [morph()] to create a temporary #' alternate representation of the input graph. They are thus not meant to be #' called directly. See below for detail of each morpher. #' #' @param graph A `tbl_graph` #' #' @param ... Arguments to pass on to [filter()], [group_by()], or the cluster #' algorithm (see [igraph::cluster_walktrap()], [igraph::cluster_leading_eigen()], #' and [igraph::cluster_edge_betweenness()]) #' #' @param subset_by,split_by Whether to create subgraphs based on nodes or edges #' #' @return A list of `tbl_graph`s #' #' @rdname morphers #' @name morphers #' #' @examples #' # Compute only on a subgraph of every even node #' create_notable('meredith') %>% #' morph(to_subgraph, seq_len(graph_order()) %% 2 == 0) %>% #' mutate(neighbour_count = centrality_degree()) %>% #' unmorph() NULL #' @describeIn morphers Convert a graph to its line graph. When unmorphing node #' data will be merged back into the original edge data. Edge data will be #' ignored. #' @importFrom igraph make_line_graph #' @export to_linegraph <- function(graph) { line_graph <- as_tbl_graph(make_line_graph(graph)) line_graph <- mutate(activate(line_graph, 'nodes'), .tidygraph_edge_index = E(graph)$.tidygraph_edge_index) list( line_graph = line_graph ) } #' @describeIn morphers Convert a graph to a single subgraph. `...` is evaluated #' in the same manner as `filter`. When unmorphing all data in the subgraph #' will get merged back. #' @importFrom igraph induced_subgraph subgraph.edges #' @export to_subgraph <- function(graph, ..., subset_by = NULL) { if (is.null(subset_by)) { subset_by <- active(graph) message('Subsetting by ', subset_by) } ind <- as_tibble(graph, active = subset_by) ind <- mutate(ind, .tidygraph_index = seq_len(n())) ind <- filter(ind, ...) ind <- ind$.tidygraph_index subset <- switch( subset_by, nodes = induced_subgraph(graph, ind), edges = subgraph.edges(graph, ind, delete.vertices = FALSE) ) list( subgraph = as_tbl_graph(subset) ) } #' @describeIn morphers Convert a graph to a single component containing the specified node #' @param node The center of the neighborhood for `to_local_neighborhood()` and #' the node to that should be included in the component for `to_subcomponent()` #' @importFrom igraph gorder components #' @export to_subcomponent <- function(graph, node) { node <- eval_tidy(enquo(node), as_tibble(graph, 'nodes')) node <- as_ind(node, gorder(graph)) if (length(node) != 1) stop('Please provide a single node for defining the subcomponent', call. = FALSE) component_membership <- components(graph)$membership == components(graph)$membership[node] to_subgraph(graph, component_membership, subset_by = 'nodes') } #' @describeIn morphers Convert a graph into a list of separate subgraphs. `...` #' is evaluated in the same manner as `group_by`. When unmorphing all data in #' the subgraphs will get merged back, but in the case of `split_by = 'edges'` #' only the first instance of node data will be used (as the same node can be #' present in multiple subgraphs). #' @importFrom igraph induced_subgraph subgraph.edges #' @importFrom stats setNames #' @importFrom dplyr group_rows #' @export to_split <- function(graph, ..., split_by = NULL) { if (is.null(split_by)) { split_by <- active(graph) message('Subsetting by ', split_by) } ind <- as_tibble(graph, active = split_by) ind <- group_by(ind, ...) splits <- lapply(group_rows(ind), function(i) { g <- switch( split_by, nodes = induced_subgraph(graph, i), edges = subgraph.edges(graph, i) ) as_tbl_graph(g) }) split_names <- group_keys(ind) split_names <- lapply(names(split_names), function(n) { paste(n, split_names[[n]], sep = ': ') }) split_names <- do.call(paste, modifyList(unname(split_names), list(sep = ', '))) setNames(splits, split_names) } #' @describeIn morphers Split a graph into its separate components. When #' unmorphing all data in the subgraphs will get merged back. #' @param type The type of component to split into. Either `'weak'` or `'strong'` #' @importFrom igraph decompose #' @export to_components <- function(graph, type = 'weak') { graphs <- decompose(graph, mode = type) graphs <- lapply(graphs, as_tbl_graph) graphs } #' @describeIn morphers Convert a graph into its complement. When unmorphing #' only node data will get merged back. #' @param loops Should loops be included. Defaults to `FALSE` #' @importFrom igraph complementer #' @export to_complement <- function(graph, loops = FALSE) { complement <- complementer(graph, loops = loops) list( complement = as_tbl_graph(complement) ) } #' @describeIn morphers Convert a graph into the local neighborhood around a #' single node. When unmorphing all data will be merged back. #' @param order The radius of the neighborhood #' @param mode How should edges be followed? `'out'` only follows outbound #' edges, `'in'` only follows inbound edges, and `'all'` follows all edges. This #' parameter is ignored for undirected graphs. #' @importFrom igraph make_ego_graph gorder #' @export to_local_neighborhood <- function(graph, node, order = 1, mode = 'all') { node <- eval_tidy(enquo(node), as_tibble(graph, 'nodes')) node <- as_ind(node, gorder(graph)) ego <- make_ego_graph(graph, order = order, nodes = node, mode = mode) list( neighborhood = as_tbl_graph(ego[[1]]) ) } #' @describeIn morphers Convert a graph into its dominator tree based on a #' specific root. When unmorphing only node data will get merged back. #' @param root The root of the tree #' @importFrom igraph dominator_tree gorder #' @export to_dominator_tree <- function(graph, root, mode = 'out') { root <- eval_tidy(enquo(root), as_tibble(graph, 'nodes')) root <- as_ind(root, gorder(graph)) dom <- dominator_tree(graph, root = root, mode = mode) list( dominator_tree = as_tbl_graph(dom$domtree) ) } #' @describeIn morphers Convert a graph into its minimum spanning tree/forest. #' When unmorphing all data will get merged back. #' @param weights Optional edge weights for the calculations #' @importFrom igraph mst #' @importFrom rlang enquo eval_tidy #' @export to_minimum_spanning_tree <- function(graph, weights = NULL) { weights <- eval_tidy(enquo(weights), as_tibble(graph, 'edges')) algorithm <- if (is.null(weights)) 'unweighted' else 'prim' mst <- mst(graph, weights = weights, algorithm = algorithm) list( mst = as_tbl_graph(mst) ) } #' @describeIn morphers Limit a graph to the shortest path between two nodes. #' When unmorphing all data is merged back. #' @param from,to The start and end node of the path #' @importFrom igraph shortest_paths gorder #' @importFrom rlang enquo eval_tidy #' @export to_shortest_path <- function(graph, from, to, mode = 'out', weights = NULL) { nodes <- as_tibble(graph, 'nodes') from <- eval_tidy(enquo(from), nodes) from <- as_ind(from, gorder(graph)) to <- eval_tidy(enquo(to), nodes) to <- as_ind(to, gorder(graph)) weights <- eval_tidy(enquo(weights), as_tibble(graph, active = 'edges')) if (is.null(weights)) { weights <- NA } path <- shortest_paths(graph, from = from, to = to, mode = mode, weights = weights, output = 'both') short_path <- slice(activate(graph, 'edges'), as.integer(path$epath[[1]])) short_path <- slice(activate(short_path, 'nodes'), as.integer(path$vpath[[1]])) list( shortest_path = short_path ) } #' @describeIn morphers Convert a graph into a breath-first search tree based on #' a specific root. When unmorphing only node data is merged back. #' @param unreachable Should the search jump to a node in a new component when #' stuck. #' @importFrom igraph bfs gorder #' @export to_bfs_tree <- function(graph, root, mode = 'out', unreachable = FALSE) { root <- eval_tidy(enquo(root), as_tibble(graph, 'nodes')) root <- as_ind(root, gorder(graph)) search <- bfs(graph, root, neimode = mode, unreachable = unreachable, father = TRUE) bfs_graph <- search_to_graph(graph, search) list( bfs = bfs_graph ) } #' @describeIn morphers Convert a graph into a depth-first search tree based on #' a specific root. When unmorphing only node data is merged back. #' @importFrom igraph bfs gorder #' @export to_dfs_tree <- function(graph, root, mode = 'out', unreachable = FALSE) { root <- eval_tidy(enquo(root), as_tibble(graph, 'nodes')) root <- as_ind(root, gorder(graph)) search <- dfs(graph, root, neimode = mode, unreachable = unreachable, father = TRUE) dfs_graph <- search_to_graph(graph, search) list( dfs = dfs_graph ) } #' @describeIn morphers Collapse parallel edges and remove loops in a graph. #' When unmorphing all data will get merged back #' @param remove_multiples Should edges that run between the same nodes be #' reduced to one #' @param remove_loops Should edges that start and end at the same node be removed #' @importFrom igraph simplify #' @export to_simple <- function(graph, remove_multiples = TRUE, remove_loops = TRUE) { edges <- as_tibble(graph, active = 'edges') graph <- set_edge_attributes(graph, edges[, '.tidygraph_edge_index', drop = FALSE]) edges$.tidygraph_edge_index <- NULL simple <- as_tbl_graph(simplify(graph, remove.multiple = remove_multiples, remove.loops = remove_loops, edge.attr.comb = list)) new_edges <- as_tibble(simple, active = 'edges') new_edges$.orig_data <- lapply(new_edges$.tidygraph_edge_index, function(i) edges[i, , drop = FALSE]) simple <- set_edge_attributes(simple, new_edges) list( simple = simple ) } #' @describeIn morphers Combine multiple nodes into one. `...` #' is evaluated in the same manner as `group_by`. When unmorphing all #' data will get merged back. #' @param simplify Should edges in the contracted graph be simplified? Defaults #' to `TRUE` #' @importFrom tidyr nest_legacy #' @importFrom igraph contract #' @export to_contracted <- function(graph, ..., simplify = TRUE) { nodes <- as_tibble(graph, active = 'nodes') nodes <- group_by(nodes, ...) ind <- group_indices(nodes) contracted <- as_tbl_graph(contract(graph, ind, vertex.attr.comb = 'ignore')) nodes <- nest_legacy(nodes, .key = '.orig_data') ind <- lapply(nodes$.orig_data, `[[`, '.tidygraph_node_index') nodes$.orig_data <- lapply(nodes$.orig_data, function(x) {x$.tidygraph_node_index <- NULL; x}) nodes$.tidygraph_node_index <- ind contracted <- set_node_attributes(contracted, nodes) if (simplify) { contracted <- to_simple(contracted)[[1]] } list( contracted = contracted ) } #' @describeIn morphers Unfold a graph to a tree or forest starting from #' multiple roots (or one), potentially duplicating nodes and edges. #' @importFrom igraph unfold_tree #' @export to_unfolded_tree <- function(graph, root, mode = 'out') { root <- eval_tidy(enquo(root), as_tibble(graph, 'nodes')) roots <- as_ind(root, gorder(graph)) unfolded <- unfold_tree(graph, mode, roots) tree <- as_tbl_graph(unfolded$tree) tree <- set_node_attributes(tree, as_tibble(graph, 'nodes')[unfolded$vertex_index, ]) tree <- set_edge_attributes(tree, as_tibble(graph, 'edges')) list( tree = tree ) } #' @describeIn morphers Make a graph directed in the direction given by from and #' to #' @export to_directed <- function(graph) { tbl_graph(as_tibble(graph, active = 'nodes'), as_tibble(graph, active = 'edges'), directed = TRUE) %gr_attr% graph } #' @describeIn morphers Make a graph undirected #' @export to_undirected <- function(graph) { tbl_graph(as_tibble(graph, active = 'nodes'), as_tibble(graph, active = 'edges'), directed = FALSE) %gr_attr% graph } #' @describeIn morphers Convert a graph into a hierarchical clustering based on a grouping #' @param method The clustering method to use. Either `'walktrap'`, `'leading_eigen'`, or `'edge_betweenness'` #' @importFrom igraph cluster_walktrap cluster_leading_eigen cluster_edge_betweenness #' @importFrom stats as.dendrogram #' @importFrom rlang .data enquo eval_tidy #' @export to_hierarchical_clusters <- function(graph, method = 'walktrap', weights = NULL, ...) { weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } hierarchy <- switch( method, walktrap = cluster_walktrap(graph, weights = weights, ...), leading_eigen = cluster_leading_eigen(graph, weights = weights, ...), edge_betweenness = cluster_edge_betweenness(graph, weights = weights, ...) ) hierarchy <- as_tbl_graph(as.dendrogram(hierarchy)) hierarchy <- mutate(hierarchy, .tidygraph_node_index = as.integer(as.character(.data$label)), label = NULL) hierarchy <- left_join(hierarchy, as_tibble(graph, active = 'nodes'), by = c('.tidygraph_node_index' = '.tidygraph_node_index')) hierarchy %gr_attr% graph } # HELPERS ----------------------------------------------------------------- search_to_graph <- function(graph, search) { nodes <- as_tibble(graph, active = 'nodes') edges <- tibble(from = search$father, to = seq_len(nrow(nodes))) edges <- edges[!is.na(edges$from), , drop = FALSE] tbl_graph(nodes, edges) } tidygraph/R/aaa.R0000644000176200001440000000155013545322217013320 0ustar liggesusers#' @importFrom igraph graph_attr graph_attr<- `%gr_attr%` <- function(e1, e2) { graph_attr(e1) <- graph_attr(e2) attributes(e1) <- attributes(e2) e1 } as_ind <- function(i, length) { seq_len(length)[i] } expect_influencer <- function() { if (!requireNamespace('influenceR', quietly = TRUE)) { stop('The `influenceR` package is required for this functionality') } } expect_netrankr <- function() { if (!requireNamespace('netrankr', quietly = TRUE)) { stop('The `netrankr` package is required for this functionality') } } expect_seriation <- function() { if (!requireNamespace('seriation', quietly = TRUE)) { stop('The `seriation` package is required for this functionality') } } expect_netswan <- function() { if (!requireNamespace('NetSwan', quietly = TRUE)) { stop('The `NetSwan` package is required for this functionality') } } tidygraph/R/tibble.R0000644000176200001440000000254013545322217014037 0ustar liggesusers#' @export #' @importFrom tibble as_tibble as_tibble.tbl_graph <- function(x, active = NULL, ...) { if (is.null(active)) { active <- attr(x, 'active') } switch( active, nodes = node_tibble(x), edges = edge_tibble(x), stop('Unknown active element: ', active, '. Only nodes and edges supported', call. = FALSE) ) } as_tibble.grouped_tbl_graph <- function(x, active = NULL, ...) { tbl <- NextMethod() if (is.null(active)) { active <- attr(x, 'active') } group_attr <- attr(x, paste0(active, '_group_attr')) if (!is.null(group_attr)) attributes(tbl) <- group_attr tbl } #' @export tibble::as_tibble #' @importFrom magrittr %>% #' @export magrittr::`%>%` #' @importFrom igraph vertex_attr gorder #' @importFrom tibble as_tibble node_tibble <- function(x) { tbl <- as_tibble(vertex_attr(x)) if (length(attr(tbl, 'row.names')) == 0) { attr(tbl, 'row.names') <- seq_len(gorder(x)) } tbl } #' @importFrom igraph edge_attr gsize as_edgelist #' @importFrom tibble as_tibble #' @importFrom dplyr bind_cols edge_tibble <- function(x) { tbl <- as_tibble(edge_attr(x)) if (length(attr(tbl, 'row.names')) == 0) { attr(tbl, 'row.names') <- seq_len(gsize(x)) } e_list <- as_edgelist(x, names = FALSE) mode(e_list) <- 'integer' colnames(e_list) <- c('from', 'to') e_list <- as_tibble(e_list) bind_cols(e_list, tbl) } tidygraph/R/slice.R0000644000176200001440000000145213545322217013676 0ustar liggesusers#' @export #' @importFrom dplyr slice #' @importFrom igraph delete_vertices delete_edges slice.tbl_graph <- function(.data, ...) { .register_graph_context(.data) d_tmp <- as_tibble(.data) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- slice(d_tmp, ...) remove_ind <- if (nrow(d_tmp) == 0) orig_ind else orig_ind[-d_tmp$.tbl_graph_index] switch( active(.data), nodes = delete_vertices(.data, remove_ind), edges = delete_edges(.data, remove_ind) ) %gr_attr% .data } #' @export #' @importFrom dplyr slice slice.morphed_tbl_graph <- function(.data, ...) { .data[] <- lapply(.data, slice, ...) .data } #' @export dplyr::slice tidygraph/R/pull.R0000644000176200001440000000064613656264464013573 0ustar liggesusers# TODO: Update signature to include `name` once we begin to depend on dplyr 1.0 #' @export #' @importFrom dplyr pull pull.tbl_graph <- function(.data, var = -1, ...) { d_tmp <- as_tibble(.data) var <- enquo(var) pull(d_tmp, !! var, ...) } #' @export #' @importFrom dplyr pull pull.morphed_tbl_graph <- function(.data, var = -1, ...) { var <- enquo(var) lapply(.data, pull, !! var, ...) } #' @export dplyr::pull tidygraph/R/node_rank.R0000644000176200001440000003104513656235772014554 0ustar liggesusers#' Calculate node ranking #' #' This set of functions tries to calculate a ranking of the nodes in a graph so #' that nodes sharing certain topological traits are in proximity in the #' resulting order. These functions are of great value when composing matrix #' layouts and arc diagrams but could concievably be used for other things as #' well. #' #' @param dist The algorithm to use for deriving a distance matrix from the #' graph. One of #' #' - `"shortest"` (default): Use the shortest path between all nodes #' - `"euclidean"`: Calculate the L2 norm on the adjacency matrix of the graph #' - `"manhattan"`: Calculate the L1 norm on the adjacency matrix of the graph #' - `"maximum"`: Calculate the supremum norm on the adjacenecy matrix of the graph #' - `"canberra"`: Calculate a weighted manhattan distance on the adjacency matrix of the graph #' - `"binary"`: Calculate distance as the proportion of agreement between nodes based on the adjacency matrix of the graph #' #' or a function that takes a `tbl_graph` and return a `dist` object with a size #' matching the order of the graph. #' #' @param mode Which edges should be included in the distance calculation. For #' distance measures based on the adjacency matrix, `'out' ` will use the matrix #' as is, `'in'` will use the transpose, and `'all'` will take the mean of the #' two. Defaults to `'out'`. Ignored for undirected graphs. #' #' @param weights An edge variable to use as weight for the shortest path #' calculation if `dist = 'shortest'` #' #' @param algorithm The algorithm to use for the shortest path calculation if #' `dist = 'shortest'` #' #' @param ... Arguments passed on to other algorithms. See *Functions* section for reference #' #' @return An integer vector giving the position of each node in the ranking #' #' @rdname node_rank #' @name node_rank #' #' @examples #' graph <- create_notable('zachary') %>% #' mutate(rank = node_rank_hclust()) #' NULL #' @describeIn node_rank Use hierarchical clustering to rank nodes (see [stats::hclust()] for allowed methods) #' @param method The method to use. See *Functions* section for reference #' @importFrom rlang enquo eval_tidy #' @importFrom stats hclust #' @export node_rank_hclust <- function(method = 'average', dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) order(hclust(mat, method)$order) } #' @describeIn node_rank Use simulated annealing based on the "ARSA" method in `seriation` #' @param cool cooling rate #' @param tmin minimum temperature #' @param swap_to_inversion Proportion of swaps in local neighborhood search #' @param step_multiplier Multiplication factor for number of iterations per temperature #' @param reps Number of repeats with random initialisation #' @importFrom rlang enquo eval_tidy #' @export node_rank_anneal <- function(cool = 0.5, tmin = 1e-4, swap_to_inversion = 0.5, step_multiplier = 100, reps = 1, dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) control <- list(cool = cool, tmin = tmin, swap_to_inversion = swap_to_inversion, try_multiplier = step_multiplier, reps = reps) seriate(mat, 'ARSA', control) } #' @describeIn node_rank Use branch and bounds strategy to minimize the gradient measure (only feasable for small graphs). Will use "BBURCG" or "BBWRCG" in `seriation` dependent on the `weighted_gradient` argument #' @param weighted_gradient minimize the weighted gradient measure? Defaults to `FALSE` #' @importFrom rlang enquo eval_tidy #' @export node_rank_branch_bound <- function(weighted_gradient = FALSE, dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) method <- if (weighted_gradient) 'BBWRCG' else "BBURCG" seriate(mat, method, list()) } #' @describeIn node_rank Minimize hamiltonian path length using a travelling salesperson solver. See the the `solve_TSP` function in `TSP` for an overview of possible arguments #' @importFrom rlang enquo eval_tidy #' @export node_rank_traveller <- function(method = 'two_opt', ..., dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) control <- list(method = method, ...) seriate(mat, 'TSP', control) } #' @describeIn node_rank Use Rank-two ellipse seriation to rank the nodes. Uses "R2E" method in `seriation` #' @importFrom rlang enquo eval_tidy #' @export node_rank_two <- function(dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) seriate(mat, 'R2E', list()) } #' @describeIn node_rank Rank by multidimensional scaling onto one dimension. `method = 'cmdscale'` will use the classic scaling from `stats`, `method = 'isoMDS'` will use `isoMDS` from `MASS`, and `method = 'sammon'` will use `sammon` from `MASS` #' @importFrom rlang enquo eval_tidy #' @export node_rank_mds <- function(method = 'cmdscale', dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) seriate(mat, 'MDS', list(method = method)) } #' @describeIn node_rank Minimize hamiltonian path length by reordering leafs in a hierarchical clustering. Method refers to the clustering algorithm (either 'average', 'single', 'complete', or 'ward') #' @param type The type of leaf reordering, either `'GW'` to use the "GW" method or `'OLO'` to use the "OLO" method (both in `seriation`) #' @importFrom rlang enquo eval_tidy #' @export node_rank_leafsort <- function(method = 'average', type = 'OLO', dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) seriate(mat, type, list(method = method)) } #' @describeIn node_rank Use Prim's algorithm to find a minimum spanning tree giving the rank. Uses the "VAT" method in `seriation` #' @importFrom rlang enquo eval_tidy #' @export node_rank_visual <- function(dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) seriate(mat, 'VAT', list()) } #' @describeIn node_rank Minimize the 2-sum problem using a relaxation approach. Uses the "Spectral" or "Spectral_norm" methods in `seriation` depending on the value of the `norm` argument #' @param normalized Should the normalized laplacian of the similarity matrix be used? #' @importFrom rlang enquo eval_tidy #' @export node_rank_spectral <- function(normalized = FALSE, dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) method <- if(normalized) 'Spectral_norm' else 'Spectral' seriate(mat, method, list()) } #' @describeIn node_rank Sorts points into neighborhoods by pushing large distances away from the diagonal. Uses the "SPIN_STS" method in `seriation` #' @param step The number iterations to run per initialisation #' @param nstart The number of random initialisations to perform #' @importFrom rlang enquo eval_tidy #' @export node_rank_spin_out <- function(step = 25, nstart = 10, dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) seriate(mat, 'SPIN_STS', list(step = step, nstart = nstart)) } #' @describeIn node_rank Sorts points into neighborhoods by concentrating low distances around the diagonal. Uses the "SPIN_NH" method in `seriation` #' @param sigma The variance around the diagonal to use for the weight matrix. Either a single number or a decreasing sequence. #' @importFrom rlang enquo eval_tidy #' @export node_rank_spin_in <- function(step = 5, sigma = seq(20, 1, length.out = 10), dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) seriate(mat, 'SPIN_NH', list(step = step, sigma = sigma)) } #' @describeIn node_rank Use quadratic assignment problem formulations to minimize criterions using simulated annealing. Uses the "QAP_LS", "QAP_2SUM", "QAP_BAR", or "QAP_Inertia" methods from `seriation` dependant on the `criterion` argument #' @param criterion The criterion to minimize. Either "LS" (Linear Seriation Problem), "2SUM" (2-Sum Problem), "BAR" (Banded Anti-Robinson form), or "Inertia" (Inertia criterion) #' @param temp_multiplier Temperature multiplication factor between 0 and 1 #' @param maxsteps The upper bound of iterations #' @importFrom rlang enquo eval_tidy #' @export node_rank_quadratic <- function(criterion = '2SUM', reps = 1, step = 2 * graph_order(), step_multiplier = 1.1, temp_multiplier = 0.5, maxsteps = 50, dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) control = list(rep = reps, miter = step, fiter = step_multiplier, ft = temp_multiplier, maxsteps = maxsteps) method = paste0('QAP_', toupper(criterion)) seriate(mat, method, control) } #' @describeIn node_rank Optimizes different criteria based on a genetic algorithm. Uses the "GA" method from `seriation`. See `register_GA` for an overview of relevant arguments #' @importFrom rlang enquo eval_tidy #' @export node_rank_genetic <- function(... , dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) seriate(mat, 'GA', list()) } #' @describeIn node_rank Optimizes different criteria based on heuristic dendrogram seriation. Uses the "DendSer" method from `seriation`. See `register_DendSer` for an overview of relevant arguments #' @importFrom rlang enquo eval_tidy #' @export node_rank_dendser <- function(... , dist = 'shortest', mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } mat <- to_dist(.G(), dist, mode, weights, algorithm) seriate(mat, 'DendSer', list()) } # HELPERS ----------------------------------------------------------------- #' @importFrom igraph distances as_adjacency_matrix #' @importFrom stats dist as.dist to_dist <- function(graph, dist, mode, weights, algorithm) { if (is.function(dist)) { mat <- dist(graph) stopifnot(inherits(mat, 'dist')) stopifnot(attr(mat, 'Size') == gorder(graph)) } else if (is.character(dist)) { if (dist == 'shortest') { mat <- distances(graph, mode = mode, weights = weights, algorithm = algorithm) mat <- as.dist(mat) } else { mat <- as_adjacency_matrix(graph, type = 'both', sparse = FALSE) if (mode == 'out') mat <- dist(mat, dist) if (mode == 'in') mat <- dist(t(mat), dist) if (mode == 'both') mat <- (dist(mat, dist) + dist(t(mat), dist))/2 } } mat } seriate <- function(mat, method, control) { expect_seriation() if (method == 'GA') seriation::register_GA() if (method == 'DendSer') seriation::register_DendSer() ser <- seriation::seriate(mat, method, control) seriation::get_rank(ser) } tidygraph/R/edge.R0000644000176200001440000000455013545322217013505 0ustar liggesusers#' Querying edge types #' #' These functions lets the user query whether the edges in a graph is of a #' specific type. All functions return a logical vector giving whether each edge #' in the graph corresponds to the specific type. #' #' @return A logical vector of the same length as the number of edges in the #' graph #' #' @name edge_types #' @rdname edge_types #' #' @examples #' create_star(10, directed = TRUE, mutual = TRUE) %>% #' activate(edges) %>% #' sample_frac(0.7) %>% #' mutate(single_edge = !edge_is_mutual()) NULL #' @describeIn edge_types Query whether each edge has any parallel siblings #' @importFrom igraph which_multiple #' @export edge_is_multiple <- function() { expect_edges() graph <- .G() which_multiple(graph) } #' @describeIn edge_types Query whether each edge is a loop #' @importFrom igraph which_loop #' @export edge_is_loop <- function() { expect_edges() graph <- .G() which_loop(graph) } #' @describeIn edge_types Query whether each edge has a sibling going in the reverse direction #' @importFrom igraph which_mutual #' @export edge_is_mutual <- function() { expect_edges() graph <- .G() which_mutual(graph) } #' @describeIn edge_types Query whether an edge goes from a set of nodes #' @param from,to,i A vector giving node indices #' @export edge_is_from <- function(from) { expect_edges() .free_graph_context() .E()$from %in% as_ind(from, graph_order()) } #' @describeIn edge_types Query whether an edge goes to a set of nodes #' @export edge_is_to <- function(to) { expect_edges() .free_graph_context() .E()$to %in% as_ind(to, graph_order()) } #' @describeIn edge_types Query whether an edge goes between two sets of nodes #' @param ignore_dir Is both directions of the edge allowed #' @export edge_is_between <- function(from, to, ignore_dir = !graph_is_directed()) { expect_edges() .free_graph_context() edges <- .E() from <- as_ind(from, graph_order()) to <- as_ind(to, graph_order()) include <- edges$from %in% from & edges$to %in% to if (ignore_dir) { include2 <- edges$to %in% from & edges$from %in% to include <- include | include2 } include } #' @describeIn edge_types Query whether an edge goes from or to a set of nodes #' @export edge_is_incident <- function(i) { expect_edges() .free_graph_context() edges <- .E() i <- as_ind(i, graph_order()) edges$from %in% i | edges$to %in% i } tidygraph/R/RcppExports.R0000644000176200001440000000055313656437441015102 0ustar liggesusers# Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 get_paths <- function(parent) { .Call('_tidygraph_get_paths', PACKAGE = 'tidygraph', parent) } collect_offspring <- function(offspring, order) { .Call('_tidygraph_collect_offspring', PACKAGE = 'tidygraph', offspring, order) } tidygraph/R/node_topology.R0000644000176200001440000000250013545322217015453 0ustar liggesusers#' Node properties related to the graph topology #' #' These functions calculate properties that are dependent on the overall #' topology of the graph. #' #' @return A vector of the same length as the number of nodes in the graph #' #' @name node_topology #' @rdname node_topology #' #' @examples #' # Sort a graph based on its topological order #' create_tree(10, 2) %>% #' arrange(sample(graph_order())) %>% #' mutate(old_ind = seq_len(graph_order())) %>% #' arrange(node_topo_order()) NULL #' @describeIn node_topology Get the immediate dominator of each node. Wraps [igraph::dominator_tree()]. #' @importFrom igraph dominator_tree #' @export #' #' @param root The node to start the dominator search from #' @param mode How should edges be followed. Either `'in'` or `'out'` node_dominator <- function(root, mode = 'out') { expect_nodes() graph <- .G() root <- as_ind(root, gorder(graph)) domtree <- as_edgelist(dominator_tree(graph, root, mode)$domtree) dom <- rep(NA, gorder(graph)) dom[domtree[, 2]] <- domtree[, 1] dom } #' @describeIn node_topology Get the topological order of nodes in a DAG. Wraps [igraph::topo_sort()]. #' @importFrom igraph gorder topo_sort #' @export node_topo_order <- function(mode = 'out') { expect_nodes() graph <- .G() match(seq_len(gorder(graph)), topo_sort(graph, mode = mode)) } tidygraph/R/pair_measures.R0000644000176200001440000002031113656236100015427 0ustar liggesusers#' Calculate node pair properties #' #' This set of functions can be used for calculations that involve node pairs. #' If the calculateable measure is not symmetric the function will come in two #' flavours, differentiated with `_to`/`_from` suffix. The `*_to()` functions #' will take the provided node indexes as the target node (recycling if #' necessary). For the `*_from()` functions the provided nodes are taken as #' the source. As for the other wrappers provided, they are intended #' for use inside the `tidygraph` framework and it is thus not necessary to #' supply the graph being computed on as the context is known. #' #' @return A numeric vector of the same length as the number of nodes in the #' graph #' #' @name pair_measures #' @rdname pair_measures #' #' @examples #' # Calculate the distance to the center node #' create_notable('meredith') %>% #' mutate(dist_to_center = node_distance_to(node_is_center())) NULL #' @describeIn pair_measures Calculate the adhesion to the specified node. Wraps [igraph::edge_connectivity()] #' @export #' @importFrom igraph edge_connectivity gorder #' #' @param nodes The other part of the node pair (the first part is the node #' defined by the row). Recycled if necessary. node_adhesion_to <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_ind(nodes, gorder(graph)) source <- seq_len(gorder(graph)) target <- rep(nodes, length.out = length(source)) adhesion <- Map(function(s, t) { if (s == t) return(NA) edge_connectivity(graph, source = s, target = t, checks = TRUE) }, s = source, t = target) unlist(adhesion) } #' @describeIn pair_measures Calculate the adhesion from the specified node. Wraps [igraph::edge_connectivity()] #' @export #' @importFrom igraph edge_connectivity gorder node_adhesion_from <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_ind(nodes, gorder(graph)) target <- seq_len(gorder(graph)) source <- rep(nodes, length.out = length(target)) adhesion <- Map(function(s, t) { if (s == t) return(NA) edge_connectivity(graph, source = s, target = t, checks = TRUE) }, s = source, t = target) unlist(adhesion) } #' @describeIn pair_measures Calculate the cohesion to the specified node. Wraps [igraph::vertex_connectivity()] #' @export #' @importFrom igraph vertex_connectivity gorder node_cohesion_to <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_ind(nodes, gorder(graph)) source <- seq_len(gorder(graph)) target <- rep(nodes, length.out = length(source)) neigh <- lapply(ego(graph, 1, source, 'out', mindist = 1), as.integer) adhesion <- Map(function(s, t) { if (s == t) return(NA) if (t %in% neigh[[s]]) return(NA) vertex_connectivity(graph, source = s, target = t, checks = TRUE) }, s = source, t = target) unlist(adhesion) } #' @describeIn pair_measures Calculate the cohesion from the specified node. Wraps [igraph::vertex_connectivity()] #' @export #' @importFrom igraph vertex_connectivity gorder ego node_cohesion_from <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_ind(nodes, gorder(graph)) target <- seq_len(gorder(graph)) source <- rep(nodes, length.out = length(target)) neigh <- lapply(ego(graph, 1, source, 'out', mindist = 1), as.integer) adhesion <- Map(function(s, t) { if (s == t) return(NA) if (t %in% neigh[[s]]) return(NA) vertex_connectivity(graph, source = s, target = t, checks = TRUE) }, s = source, t = target) unlist(adhesion) } #' @describeIn pair_measures Calculate various distance metrics between node pairs. Wraps [igraph::distances()] #' @export #' @importFrom igraph distances gorder #' #' @param mode How should edges be followed? If `'all'` all edges are #' considered, if `'in'` only inbound edges are considered, and if `'out'` only #' outbound edges are considered #' @param weights The weights to use for calculation #' @param algorithm The distance algorithms to use. By default it will try to #' select the fastest suitable algorithm. Possible values are `"automatic"`, #' `"unweighted"`, `"dijkstra"`, `"bellman-ford"`, and `"johnson"` node_distance_to <- function(nodes, mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } nodes <- as_ind(nodes, gorder(graph)) source <- seq_len(gorder(graph)) target <- rep(nodes, length.out = length(source)) target_unique <- unique(target) dist <- distances(graph, v = source, to = target_unique, mode = mode, weights = weights, algorithm = algorithm) unlist(Map(function(s, t) {dist[s, t]}, s = source, t = match(target, target_unique))) } #' @describeIn pair_measures Calculate various distance metrics between node pairs. Wraps [igraph::distances()] #' @export #' @importFrom igraph distances gorder node_distance_from <- function(nodes, mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } nodes <- as_ind(nodes, gorder(graph)) target <- seq_len(gorder(graph)) source <- rep(nodes, length.out = length(target)) source_unique <- unique(source) dist <- distances(graph, v = source_unique, to = target, mode = mode, weights = weights, algorithm = algorithm) unlist(Map(function(s, t) {dist[s, t]}, s = match(source, source_unique), t = target)) } #' @describeIn pair_measures Calculate node pair cocitation count. Wraps [igraph::cocitation()] #' @export #' @importFrom igraph cocitation gorder node_cocitation_with <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_ind(nodes, gorder(graph)) source <- seq_len(gorder(graph)) target <- rep(nodes, length.out = length(source)) cocite <- cocitation(graph) unlist(Map(function(s, t) {cocite[s, t]}, s = source, t = target)) } #' @describeIn pair_measures Calculate node pair bibliographic coupling. Wraps [igraph::bibcoupling()] #' @export #' @importFrom igraph bibcoupling gorder node_bibcoupling_with <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_ind(nodes, gorder(graph)) source <- seq_len(gorder(graph)) target <- rep(nodes, length.out = length(source)) bibc <- bibcoupling(graph) unlist(Map(function(s, t) {bibc[s, t]}, s = source, t = target)) } #' @describeIn pair_measures Calculate various node pair similarity measures. Wraps [igraph::similarity()] #' @export #' @importFrom igraph similarity gorder #' #' @param loops Should loop edges be considered #' @param method The similarity measure to calculate. Possible values are: #' `"jaccard"`, `"dice"`, and `"invlogweighted"` node_similarity_with <- function(nodes, mode = 'out', loops = FALSE, method = 'jaccard') { expect_nodes() graph <- .G() nodes <- as_ind(nodes, gorder(graph)) source <- seq_len(gorder(graph)) target <- rep(nodes, length.out = length(source)) sim <- similarity(graph, mode = mode, loops = loops, method = method) unlist(Map(function(s, t) {sim[s, t]}, s = source, t = target)) } #' @describeIn pair_measures Calculate the maximum flow to a node. Wraps [igraph::max_flow()] #' @export #' @importFrom igraph max_flow gorder #' #' @param capacity The edge capacity to use node_max_flow_to <- function(nodes, capacity = NULL) { expect_nodes() graph <- .G() capacity <- enquo(capacity) capacity <- eval_tidy(capacity, .E()) nodes <- as_ind(nodes, gorder(graph)) source <- seq_len(gorder(graph)) target <- rep(nodes, length.out = length(source)) flow <- Map(function(s, t) { if (s == t) return(NA) max_flow(graph, source = s, target = t, capacity = capacity)$value }, s = source, t = target) unlist(flow) } #' @describeIn pair_measures Calculate the maximum flow from a node. Wraps [igraph::max_flow()] #' @export #' @importFrom igraph max_flow gorder node_max_flow_from <- function(nodes, capacity = NULL) { expect_nodes() graph <- .G() capacity <- enquo(capacity) capacity <- eval_tidy(capacity, .E()) nodes <- as_ind(nodes, gorder(graph)) target <- seq_len(gorder(graph)) source <- rep(nodes, length.out = length(target)) flow <- Map(function(s, t) { if (s == t) return(NA) max_flow(graph, source = s, target = t, capacity = capacity)$value }, s = source, t = target) unlist(flow) } tidygraph/R/dendrogram.R0000644000176200001440000000556513545322217014732 0ustar liggesusers#' @describeIn tbl_graph Method for dendrogram objects #' @importFrom dplyr bind_rows #' @export as_tbl_graph.dendrogram <- function(x, directed = TRUE, mode = 'out', ...) { x <- identify_nodes(x) nodes <- get_nodes(x) extraPar <- bind_rows(lapply(nodes$nodePar, as.data.frame, stringsAsFactors = FALSE)) nodes$nodePar <- NULL nodes <- cbind(nodes, extraPar) nodes <- nodes[order(nodes$.tidygraph_id), ] nodes$.tidygraph_id <- NULL if (all(nodes$label == '')) nodes$label <- NULL edges <- get_edges(x) extraPar <- bind_rows(lapply(edges$edgePar, as.data.frame, stringsAsFactors = FALSE)) edges$edgePar <- NULL edges <- cbind(edges, extraPar) if (all(edges$label == '')) edges$label <- NULL if (directed && mode == 'in') { edges[, c('from', 'to')] <- edges[, c('to', 'from')] } as_tbl_graph(list(nodes = nodes, edges = edges), directed = directed) } #' @importFrom stats is.leaf identify_nodes <- function(den, start = 1) { if (is.leaf(den)) { attr(den, '.tidygraph_id') <- start } else { den[[1]] <- identify_nodes(den[[1]], start) den[[2]] <- identify_nodes(den[[2]], attr(den[[1]], '.tidygraph_id') + 1) attr(den, '.tidygraph_id') <- attr(den[[2]], '.tidygraph_id') + 1 } den } #' @importFrom stats is.leaf get_nodes <- function(den) { id <- attr(den, '.tidygraph_id') label <- attr(den, 'label') if (is.null(label)) label <- '' members <- attr(den, 'members') nodePar <- attr(den, 'nodePar') if (is.null(nodePar)) nodePar <- data.frame(row.names = 1) if (is.leaf(den)) { list( height = attr(den, 'height'), .tidygraph_id = id, leaf = TRUE, label = label, members = members, nodePar = list(nodePar) ) } else { coord1 <- get_nodes(den[[1]]) coord2 <- get_nodes(den[[2]]) list( height = c(coord1$height, coord2$height, attr(den, 'height')), .tidygraph_id = c(coord1$.tidygraph_id, coord2$.tidygraph_id, id), leaf = c(coord1$leaf, coord2$leaf, FALSE), label = c(coord1$label, coord2$label, label), members = c(coord1$members, coord2$members, members), nodePar = c(coord1$nodePar, coord2$nodePar, list(nodePar)) ) } } #' @importFrom stats is.leaf get_edges <- function(den) { id <- attr(den, '.tidygraph_id') if (is.leaf(den)) { data.frame(row.names = 1) } else { conn1 <- get_edges(den[[1]]) conn2 <- get_edges(den[[2]]) list( from = c(conn1$from, conn2$from, rep(id, 2)), to = c(conn1$to, conn2$to, unlist(lapply(den, attr, which = '.tidygraph_id'))), label = c(conn1$label, conn2$label, unlist(lapply(den, function(subden) { lab <- attr(subden, 'edgetext') if (is.null(lab)) '' else lab }))), edgePar = c(conn1$edgePar, conn2$edgePar, lapply(den, function(subden) { par <- attr(subden, 'edgePar') if (is.null(par)) data.frame(row.names = 1) else par })) ) } } tidygraph/R/arrange.R0000644000176200001440000000207113545322217014214 0ustar liggesusers#' @export #' @importFrom dplyr arrange arrange.tbl_graph <- function(.data, ...) { .register_graph_context(.data) d_tmp <- as_tibble(.data) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- arrange(d_tmp, ...) switch( active(.data), nodes = permute_nodes(.data, d_tmp$.tbl_graph_index), edges = permute_edges(.data, d_tmp$.tbl_graph_index) ) %gr_attr% .data } #' @export #' @importFrom dplyr arrange arrange.morphed_tbl_graph <- function(.data, ...) { .data[] <- lapply(.data, arrange, ...) .data } #' @export dplyr::arrange #' @importFrom igraph is.directed as_data_frame permute_edges <- function(graph, order) { graph_mod <- as_data_frame(graph, what = 'both') graph_mod$edges <- graph_mod$edges[order, ] as_tbl_graph(graph_mod, directed = is.directed(graph)) } #' @importFrom igraph permute permute_nodes <- function(graph, order) { permute(graph, match(seq_along(order), order)) } tidygraph/R/matrix.R0000644000176200001440000000247313545322217014107 0ustar liggesusers#' @describeIn tbl_graph Method for edgelist, adjacency and incidence matrices #' @export as_tbl_graph.matrix <- function(x, directed = TRUE, ...) { graph <- switch( guess_matrix_type(x), edgelist = as_graph_edgelist(x, directed), adjacency = as_graph_adj_matrix(x, directed), incidence = as_graph_incidence(x, directed), unknown = stop('Unknown matrix format', call. = FALSE) ) as_tbl_graph(graph) } guess_matrix_type <- function(x) { if (ncol(x) == 2) { 'edgelist' } else if (nrow(x) == ncol(x) && mode(x) %in% c('numeric', 'integer') && ((is.null(rownames(x)) && is.null(colnames(x))) || colnames(x) == rownames(x))) { 'adjacency' } else if (mode(x) %in% c('numeric', 'integer')) { 'incidence' } else { 'uknown' } } #' @importFrom igraph graph_from_edgelist as_graph_edgelist <- function(x, directed) { graph_from_edgelist(x, directed) } #' @importFrom igraph graph_from_adjacency_matrix as_graph_adj_matrix <- function(x, directed) { graph_from_adjacency_matrix(x, mode = if (directed) 'directed' else 'undirected', weighted = TRUE) } #' @importFrom igraph graph_from_incidence_matrix as_graph_incidence <- function(x, directed) { graph_from_incidence_matrix(x, directed, mode = 'out', weighted = TRUE) } tidygraph/R/data_frame.R0000644000176200001440000000362013545322217014661 0ustar liggesusers#' @describeIn tbl_graph Method for edge table and set membership table #' @export #' @importFrom igraph graph_from_data_frame as_tbl_graph.data.frame <- function(x, directed = TRUE, ...) { graph <- switch( guess_df_type(x), edge_df = as_graph_edge_df(x, directed), set_df = as_graph_set_df(x) ) as_tbl_graph(graph) } guess_df_type <- function(x) { if (all(c('to', 'from') %in% names(x))) return('edge_df') if (all(vapply(x, inherits, logical(1), 'logical'))) return('set_df') if (all(vapply(x, function(col) all(unique(col) %in% c(0,1)), logical(1)))) return('set_df') 'edge_df' } as_graph_edge_df <- function(x, directed) { from_ind <- which(names(x) == 'from') if (length(from_ind) == 0) from_ind <- 1 to_ind <- which(names(x) == 'to') if (length(to_ind) == 0) to_ind <- 2 x <- x[, c(from_ind, to_ind, seq_along(x)[-c(from_ind, to_ind)]), drop = FALSE] graph_from_data_frame(x, directed = directed) } as_graph_set_df <- function(x, simple = TRUE) { if (simple) { x <- as.matrix(x) mode(x) <- 'integer' adj_mat <- x %*% t(x) if (!is.null(attr(x, 'row.names'))) { colnames(adj_mat) <- rownames(adj_mat) <- row.names(x) } as_graph_adj_matrix(adj_mat, FALSE) } else { edges <- do.call(rbind, lapply(names(x), function(name) { nodes <- which(as.logical(x[[name]])) edges <- expand.grid(nodes, nodes) names(edges) <- c('from', 'to') edges$type <- name edges[edges$from != edges$to, , drop = FALSE] })) if (!is.null(attr(x, 'row.names'))) { nodes <- data.frame(name = row.names(x), stringsAsFactors = FALSE) } else { nodes <- as.data.frame(matrix(ncol = 0, nrow = nrow(x))) } as_graph_node_edge(list(nodes = nodes, edges = edges), FALSE) } } #' @export as.data.frame.tbl_graph <- function(x, row.names = NULL, optional = FALSE, active = NULL, ...) { as.data.frame(as_tibble(x, active = active)) } tidygraph/R/centrality.R0000644000176200001440000003263613656236253014774 0ustar liggesusers#' Calculate node and edge centrality #' #' The centrality of a node measures the importance of node in the network. As #' the concept of importance is ill-defined and dependent on the network and #' the questions under consideration, many centrality measures exist. #' `tidygraph` provides a consistent set of wrappers for all the centrality #' measures implemented in `igraph` for use inside [dplyr::mutate()] and other #' relevant verbs. All functions provided by `tidygraph` have a consistent #' naming scheme and automatically calls the function on the graph, returning a #' vector with measures ready to be added to the node data. Further `tidygraph` #' provides access to the `netrankr` engine for centrality calculations and #' define a number of centrality measures based on that, as well as provide a #' manual mode for specifying more-or-less any centrality score. #' #' @param weights The weight of the edges to use for the calculation. Will be #' evaluated in the context of the edge data. #' @param mode How should edges be followed. Ignored for undirected graphs #' @param directed Should direction of edges be used for the calculations #' @param loops Should loops be included in the calculation #' @param scale Should the output be scaled between 0 and 1 #' @param rescale Should the output be scaled to sum up to 1 #' @param normalized Should the output be normalized #' @param tol Tolerance for near-singularities during matrix inversion #' @param options Settings passed on to `igraph::arpack()` #' @param cutoff maximum path length to use during calculations #' @param nobigint Should big integers be avoided during calculations #' @param alpha Relative importance of endogenous vs exogenous factors (`centrality_alpha`), the exponent to the power transformation of the distance metric (`centrality_closeness_generalised`), the base of power transformation (`centrality_decay`), or the attenuation factor (`centrality_katz`) #' @param exo The exogenous factors of the nodes. Either a scalar or a number #' number for each node. Evaluated in the context of the node data. #' @param exponent The decay rate for the Bonacich power centrality #' @param damping The damping factor of the page rank algorithm #' @param personalized The probability of jumping to a node when abandoning a #' random walk. Evaluated in the context of the node data. #' #' @return A numeric vector giving the centrality measure of each node. #' #' @name centrality #' @rdname centrality #' #' @examples #' create_notable('bull') %>% #' activate(nodes) %>% #' mutate(importance = centrality_alpha()) #' #' # Most centrality measures are for nodes but not all #' create_notable('bull') %>% #' activate(edges) %>% #' mutate(importance = centrality_edge_betweenness()) NULL #' @describeIn centrality Wrapper for [igraph::alpha_centrality()] #' @importFrom igraph V alpha_centrality #' @export centrality_alpha <- function(weights = NULL, alpha = 1, exo = 1, tol = 1e-7, loops = FALSE) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } exo <- enquo(exo) exo <- eval_tidy(exo, .N()) alpha_centrality(graph = graph, nodes = V(graph), alpha = alpha, exo = exo, weights = weights, tol = tol, loops = loops) } #' @describeIn centrality Wrapper for [igraph::authority_score()] #' @importFrom igraph authority_score #' @export centrality_authority <- function(weights = NULL, scale = TRUE, options = igraph::arpack_defaults) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } authority_score(graph = .G(), scale = scale, weights = weights, options = options)$vector } #' @describeIn centrality Wrapper for [igraph::betweenness()] and [igraph::estimate_betweenness()] #' @importFrom igraph V betweenness estimate_betweenness #' @importFrom rlang quos #' @export centrality_betweenness <- function(weights = NULL, directed = TRUE, cutoff = NULL, nobigint = TRUE, normalized = FALSE) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } if (is.null(cutoff)) { betweenness(graph = graph, v = V(graph), directed = directed, weights = weights, nobigint = nobigint, normalized = normalized) } else { estimate_betweenness(graph = graph, vids = V(graph), directed = directed, cutoff = cutoff, weights = weights, nobigint = nobigint) } } #' @describeIn centrality Wrapper for [igraph::power_centrality()] #' @importFrom igraph V power_centrality #' @export centrality_power <- function(exponent = 1, rescale = FALSE, tol = 1e-7, loops = FALSE) { expect_nodes() graph <- .G() power_centrality(graph = graph, nodes = V(graph), exponent = exponent, loops = loops, rescale = rescale, tol = tol) } #' @describeIn centrality Wrapper for [igraph::closeness()] and [igraph::estimate_closeness()] #' @importFrom igraph V closeness estimate_closeness #' @importFrom rlang quos #' @export centrality_closeness <- function(weights = NULL, mode = 'out', normalized = FALSE, cutoff = NULL) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } if (is.null(cutoff)) { closeness(graph = graph, vids = V(graph), mode = mode, weights = weights, normalized = normalized) } else { estimate_closeness(graph = graph, vids = V(graph), mode = mode, cutoff = cutoff, weights = weights, normalized = normalized) } } #' @describeIn centrality Wrapper for [igraph::eigen_centrality()] #' @importFrom igraph eigen_centrality #' @export centrality_eigen <- function(weights = NULL, directed = FALSE, scale = TRUE, options = igraph::arpack_defaults) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } eigen_centrality(graph = .G(), directed = directed, scale = scale, weights = weights, options = options)$vector } #' @describeIn centrality Wrapper for [igraph::hub_score()] #' @importFrom igraph hub_score #' @export centrality_hub <- function(weights = NULL, scale = TRUE, options = igraph::arpack_defaults) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } hub_score(graph = .G(), scale = scale, weights = weights, options = options)$vector } #' @describeIn centrality Wrapper for [igraph::page_rank()] #' @importFrom igraph V page_rank #' @export centrality_pagerank <- function(weights = NULL, directed = TRUE, damping = 0.85, personalized = NULL) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } personalized <- enquo(personalized) personalized <- eval_tidy(personalized, .N()) page_rank(graph = graph, vids = V(graph), directed = directed, damping = damping, personalized = personalized, weights = weights)$vector } #' @describeIn centrality Wrapper for [igraph::subgraph_centrality()] #' @importFrom igraph subgraph_centrality #' @export centrality_subgraph <- function(loops = FALSE) { expect_nodes() subgraph_centrality(graph = .G(), diag = loops) } #' @describeIn centrality Wrapper for [igraph::degree()] and [igraph::strength()] #' @importFrom igraph V degree strength #' @importFrom rlang quos #' @export centrality_degree <- function(weights = NULL, mode = 'out', loops = TRUE, normalized = FALSE) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } if (is.null(weights)) { degree(graph = graph, v = V(graph), mode = mode, loops = loops, normalized = normalized) } else { strength(graph = graph, vids = V(graph), mode = mode, loops = loops, weights = weights) } } #' @describeIn centrality Wrapper for [igraph::edge_betweenness()] #' @importFrom igraph edge_betweenness estimate_edge_betweenness E #' @importFrom rlang quos #' @export centrality_edge_betweenness <- function(weights = NULL, directed = TRUE, cutoff = NULL) { expect_edges() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } if (is.null(cutoff)) { edge_betweenness(graph = graph, e = E(graph), directed = directed, weights = weights) } else { estimate_edge_betweenness(graph = graph, e = E(graph), directed = directed, cutoff = cutoff, weights = weights) } } #' @describeIn centrality Manually specify your centrality score using the `netrankr` framework (`netrankr`) #' @param relation The indirect relation measure type to be used in `netrankr::indirect_relations` #' @param aggregation The aggregation type to use on the indirect relations to be used in `netrankr::aggregate_positions` #' @param ... Arguments to pass on to `netrankr::indirect_relations` #' @export centrality_manual <- function(relation = 'dist_sp', aggregation = 'sum', ...) { expect_netrankr() expect_nodes() graph <- .G() rel <- netrankr::indirect_relations(graph, type = relation, ...) netrankr::aggregate_positions(rel, type = aggregation) } #' @describeIn centrality centrality based on inverse shortest path (`netrankr`) #' @export centrality_closeness_harmonic <- function() { centrality_manual('dist_sp', FUN = netrankr::dist_inv) } #' @describeIn centrality centrality based on 2-to-the-power-of negative shortest path (`netrankr`) #' @export centrality_closeness_residual <- function() { centrality_manual('dist_sp', FUN = netrankr::dist_2pow) } #' @describeIn centrality centrality based on alpha-to-the-power-of negative shortest path (`netrankr`) #' @export centrality_closeness_generalised <- function(alpha) { centrality_manual('dist_sp', FUN = netrankr::dist_dpow, alpha = alpha) } #' @describeIn centrality centrality based on \eqn{1 - (x - 1)/max(x)} transformation of shortest path (`netrankr`) #' @export centrality_integration <- function() { centrality_manual('dist_sp', FUN = function(x) 1 - (x - 1)/max(x)) } #' @describeIn centrality centrality an exponential tranformation of walk counts (`netrankr`) #' @export centrality_communicability <- function() { centrality_manual('walks', FUN = netrankr::walks_exp) } #' @describeIn centrality centrality an exponential tranformation of odd walk counts (`netrankr`) #' @export centrality_communicability_odd <- function() { centrality_manual('walks', FUN = netrankr::walks_exp_odd) } #' @describeIn centrality centrality an exponential tranformation of even walk counts (`netrankr`) #' @export centrality_communicability_even <- function() { centrality_manual('walks', FUN = netrankr::walks_exp_even) } #' @describeIn centrality subgraph centrality based on odd walk counts (`netrankr`) #' @export centrality_subgraph_odd <- function() { centrality_manual('walks', 'self', FUN = netrankr::walks_exp_odd) } #' @describeIn centrality subgraph centrality based on even walk counts (`netrankr`) #' @export centrality_subgraph_even <- function() { centrality_manual('walks', 'self', FUN = netrankr::walks_exp_even) } #' @describeIn centrality centrality based on walks penalizing distant nodes (`netrankr`) #' @export centrality_katz <- function(alpha = NULL) { if (is.null(alpha)) { centrality_manual('walks', FUN = netrankr::walks_attenuated) } else { centrality_manual('walks', FUN = netrankr::walks_attenuated, alpha = alpha) } } #' @describeIn centrality Betweenness centrality based on network flow (`netrankr`) #' @param netflowmode The return type of the network flow distance, either `'raw'` or `'frac'` #' @export centrality_betweenness_network <- function(netflowmode = 'raw') { centrality_manual('depend_netflow', netflowmode = netflowmode) } #' @describeIn centrality Betweenness centrality based on current flow (`netrankr`) #' @export centrality_betweenness_current <- function() { centrality_manual('depend_curflow') } #' @describeIn centrality Betweenness centrality based on communicability (`netrankr`) #' @export centrality_betweenness_communicability <- function() { centrality_manual('depend_exp') } #' @describeIn centrality Betweenness centrality based on simple randomised shortest path dependencies (`netrankr`) #' @param rspxparam inverse temperature parameter #' @export centrality_betweenness_rsp_simple <- function(rspxparam = 1) { centrality_manual('depend_rsps', rspxparam = rspxparam) } #' @describeIn centrality Betweenness centrality based on net randomised shortest path dependencies (`netrankr`) #' @export centrality_betweenness_rsp_net <- function(rspxparam = 1) { centrality_manual('depend_rspn', rspxparam = rspxparam) } #' @describeIn centrality centrality based on inverse sum of resistance distance between nodes (`netrankr`) #' @export centrality_information <- function() { centrality_manual('dist_resist', 'invsum') } #' @describeIn centrality based on a power transformation of the shortest path (`netrankr`) #' @export centrality_decay <- function(alpha = 1) { centrality_manual('dist_sp', FUN = netrankr::dist_powd, alpha = alpha) } #' @describeIn centrality centrality based on the inverse sum of expected random walk length between nodes (`netrankr`) #' @export centrality_random_walk <- function() { centrality_manual('dist_rwalk', 'invsum') } #' @describeIn centrality Expected centrality ranking based on exact rank probability (`netrankr`) #' @export centrality_expected <- function() { expect_netrankr() expect_nodes() graph <- .G() P <- netrankr::neighborhood_inclusion() ranks <- netrankr::exact_rank_prob(P) ranks$expected.rank } tidygraph/R/select.R0000644000176200001440000000144313545322217014056 0ustar liggesusers#' @export #' @importFrom dplyr select select.tbl_graph <- function(.data, ...) { .register_graph_context(.data) d_tmp <- as_tibble(.data) d_tmp <- select(d_tmp, ...) set_graph_data(.data, d_tmp) } #' @export #' @importFrom dplyr select select.morphed_tbl_graph <- function(.data, ...) { .data[] <- lapply(.data, protect_ind, .f = select, ...) .data } #' @export dplyr::select #' @importFrom dplyr contains #' @export dplyr::contains #' @importFrom dplyr ends_with #' @export dplyr::ends_with #' @importFrom dplyr everything #' @export dplyr::everything #' @importFrom dplyr matches #' @export dplyr::matches #' @importFrom dplyr num_range #' @export dplyr::num_range #' @importFrom dplyr one_of #' @export dplyr::one_of #' @importFrom dplyr starts_with #' @export dplyr::starts_with tidygraph/R/graph_measures.R0000644000176200001440000001652113656224037015613 0ustar liggesusers#' Graph measurements #' #' This set of functions provide wrappers to a number of `ìgraph`s graph #' statistic algorithms. As for the other wrappers provided, they are intended #' for use inside the `tidygraph` framework and it is thus not necessary to #' supply the graph being computed on as the context is known. All of these #' functions are guarantied to return scalars making it easy to compute with #' them. #' #' @return A scalar, the type depending on the function #' #' @name graph_measures #' @rdname graph_measures #' #' @examples #' # Use e.g. to modify computations on nodes and edges #' create_notable('meredith') %>% #' activate(nodes) %>% #' mutate(rel_neighbors = centrality_degree()/graph_order()) NULL #' @describeIn graph_measures Gives the minimum edge connectivity. Wraps [igraph::edge_connectivity()] #' @importFrom igraph edge_connectivity #' @export graph_adhesion <- function() { graph <- .G() edge_connectivity(graph) } #' @describeIn graph_measures Measures the propensity of similar nodes to be connected. Wraps [igraph::assortativity()] #' @param attr The node attribute to measure on #' @param in_attr An alternative node attribute to use for incomming node. If `NULL` the attribute given by `type` will be used #' @param directed Should a directed graph be treated as directed #' @importFrom igraph assortativity assortativity_nominal #' @export graph_assortativity <- function(attr, in_attr = NULL, directed = TRUE) { graph <- .G() attr <- enquo(attr) attr <- eval_tidy(attr, .N()) if (is.numeric(attr)) { in_attr <- enquo(in_attr) in_attr <- eval_tidy(in_attr, .N()) assortativity(graph, attr, in_attr, directed) } else { assortativity_nominal(graph, as.factor(attr), directed) } } #' @describeIn graph_measures Calculate the number of automorphisms of the graph. Wraps [igraph::automorphisms()] #' @inheritParams igraph::automorphisms #' @importFrom igraph automorphisms #' @export graph_automorphisms <- function(sh = 'fm') { graph <- .G() as.numeric(automorphisms(graph, sh)$group_size) } #' @describeIn graph_measures Get the size of the largest clique. Wraps [igraph::clique_num()] #' @importFrom igraph clique_num #' @export graph_clique_num <- function() { graph <- .G() clique_num(graph) } #' @describeIn graph_measures Get the number of maximal cliques in the graph. Wraps [igraph::count_max_cliques()] #' @param min,max The upper and lower bounds of the cliques to be considered. #' @param subset The indexes of the nodes to start the search from (logical or integer). If provided only the cliques containing these nodes will be counted. #' @importFrom igraph count_max_cliques #' @export graph_clique_count <- function(min = NULL, max = NULL, subset = NULL) { graph <- .G() subset <- enquo(subset) subset <- eval_tidy(subset, .N()) if (is.logical(subset)) subset <- which(subset) count_max_cliques(graph, min, max, subset) } #' @describeIn graph_measures Count the number of unconnected componenets in the graph. Wraps [igraph::count_components()] #' @param type The type of component to count, either 'weak' or 'strong'. Ignored for undirected graphs. #' @importFrom igraph count_components #' @export graph_component_count <- function(type = 'weak') { graph <- .G() count_components(graph, type) } #' @describeIn graph_measures Count the number of motifs in a graph. Wraps [igraph::count_motifs()] #' @inheritParams igraph::count_motifs #' @importFrom igraph count_motifs #' @export graph_motif_count <- function(size = 3, cut.prob = rep(0, size)) { graph <- .G() count_motifs(graph, size, cut.prob) } #' @describeIn graph_measures Measures the length of the longest geodesic. Wraps [igraph::diameter()] #' @inheritParams igraph::diameter #' @importFrom igraph diameter #' @export graph_diameter <- function(weights = NULL, directed = TRUE, unconnected = TRUE) { graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } diameter(graph, directed, unconnected, weights) } #' @describeIn graph_measures Measrues the length of the shortest circle in the graph. Wraps [igraph::girth()] #' @importFrom igraph girth #' @export graph_girth <- function() { graph <- .G() girth(graph, F)$girth } #' @describeIn graph_measures Measures the smallest eccentricity in the graph. Wraps [igraph::radius()] #' @param mode How should eccentricity be calculated. If `"out"` only outbound edges are followed. If `"in"` only inbound are followed. If `"all"` all edges are followed. Ignored for undirected graphs. #' @importFrom igraph radius #' @export graph_radius <- function(mode = 'out') { graph <- .G() radius(graph, mode) } #' @describeIn graph_measures Counts the number of mutually connected nodes. Wraps [igraph::dyad_census()] #' @importFrom igraph dyad_census #' @export graph_mutual_count <- function() { graph <- .G() unname(dyad_census(graph)['mut']) } #' @describeIn graph_measures Counts the number of asymmetrically connected nodes. Wraps [igraph::dyad_census()] #' @importFrom igraph dyad_census #' @export graph_asym_count <- function() { graph <- .G() unname(dyad_census(graph)['asym']) } #' @describeIn graph_measures Counts the number of unconnected node pairs. Wraps [igraph::dyad_census()] #' @importFrom igraph dyad_census #' @export graph_unconn_count <- function() { graph <- .G() unname(dyad_census(graph)['null']) } #' @describeIn graph_measures Counts the number of edges in the graph. Wraps [igraph::gsize()] #' @importFrom igraph gsize #' @export graph_size <- function() { graph <- .G() gsize(graph) } #' @describeIn graph_measures Counts the number of nodes in the graph. Wraps [igraph::gorder()] #' @importFrom igraph gorder #' @export graph_order <- function() { graph <- .G() gorder(graph) } #' @describeIn graph_measures Measures the proportion of mutual connections in the graph. Wraps [igraph::reciprocity()] #' @param ignore_loops Logical. Should loops be ignored while calculating the reciprocity #' @param ratio Should the old "ratio" approach from igraph < v0.6 be used #' @importFrom igraph reciprocity #' @export graph_reciprocity <- function(ignore_loops = TRUE, ratio = FALSE) { graph <- .G() reciprocity(graph, ignore_loops, mode = if (ratio) 'ratio' else 'default') } #' @describeIn graph_measures Calculates the minimum number of edges to remove in order to split the graph into two clusters. Wraps [igraph::min_cut()] #' @param capacity The capacity of the edges #' @importFrom igraph min_cut #' @export graph_min_cut <- function(capacity = NULL) { graph <- .G() capacity <- enquo(capacity) capacity <- eval_tidy(capacity, .E()) min_cut(graph, capacity = capacity) } #' @describeIn graph_measures Calculates the mean distance between all node pairs in the graph. Wraps [igraph::mean_distance()] #' @importFrom igraph mean_distance #' @export graph_mean_dist <- function(directed = TRUE, unconnected = TRUE) { graph <- .G() mean_distance(graph, directed = directed, unconnected = unconnected) } #' @describeIn graph_measures Calculates the modularity of the graph contingent on a provided node grouping #' @param group The node grouping to calculate the modularity on #' @importFrom igraph modularity #' @export graph_modularity <- function(group, weights = NULL) { graph <- .G() group <- enquo(group) weights <- enquo(weights) group <- eval_tidy(group, .N()) weights <- eval_tidy(weights, .E()) modularity(graph, group, weights) } tidygraph/R/hclust.R0000644000176200001440000000033513545322217014100 0ustar liggesusers#' @describeIn tbl_graph Method for hclust objects #' @importFrom stats as.dendrogram #' @export as_tbl_graph.hclust <- function(x, directed = TRUE, mode = 'out', ...) { as_tbl_graph(as.dendrogram(x), directed, mode) } tidygraph/R/distinct.R0000644000176200001440000000247013545322217014421 0ustar liggesusers#' @importFrom dplyr distinct #' @importFrom rlang quos quo sym eval_tidy UQS #' @importFrom utils head #' @export distinct.tbl_graph <- function(.data, ..., .keep_all = FALSE) { .register_graph_context(.data) d_tmp <- as_tibble(.data) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) dot_list <- quos(..., .named = TRUE) if (length(dot_list) == 0) { dot_list <- lapply(names(d_tmp), function(n) quo(!! sym(n))) names(dot_list) <- names(d_tmp) } d_tmp$.tbl_graph_index <- orig_ind d_tmp <- eval_tidy( quo( distinct(d_tmp, UQS(dot_list), .keep_all = TRUE) ) ) remove_ind <- orig_ind[-d_tmp$.tbl_graph_index] graph <- switch( active(.data), nodes = delete_vertices(.data, remove_ind), edges = delete_edges(.data, remove_ind) ) %gr_attr% .data if (!.keep_all) { d_tmp <- d_tmp[, names(d_tmp) %in% names(dot_list), drop = FALSE] } else { d_tmp <- d_tmp[, names(d_tmp) != '.tbl_graph_index', drop = FALSE] } set_graph_data(graph, d_tmp) } #' @export #' @importFrom dplyr distinct distinct.morphed_tbl_graph <- function(.data, ..., .keep_all = FALSE) { .data[] <- lapply(.data, distinct, ..., .keep_all = .keep_all) .data } #' @export dplyr::distinct tidygraph/R/activate.R0000644000176200001440000000566113545322217014405 0ustar liggesusers#' Determine the context of subsequent manipulations #' #' As a [tbl_graph] can be considered as a collection of two linked tables it is #' necessary to specify which table is referenced during manipulations. The #' `activate` verb does just that and needs affects all subsequent manipulations #' until a new table is activated. `active` is a simple query function to get #' the currently acitve context. In addition to the use of `activate` it is also #' possible to activate nodes or edges as part of the piping using the `%N>%` #' and `%E>%` pipes respectively. Do note that this approach somewhat obscures #' what is going on and is thus only recommended for quick, one-line, fixes in #' interactive use. #' #' @param .data,x,lhs A tbl_graph or a grouped_tbl_graph #' #' @param what What should get activated? Possible values are `nodes` or #' `edges`. #' #' @param rhs A function to pipe into #' #' @return A tbl_graph #' #' @note Activate will ungroup a grouped_tbl_graph. #' #' @export #' #' @examples #' gr <- create_complete(5) %>% #' activate(nodes) %>% #' mutate(class = sample(c('a', 'b'), 5, TRUE)) %>% #' activate(edges) %>% #' arrange(from) #' #' # The above could be achieved using the special pipes as well #' gr <- create_complete(5) %N>% #' mutate(class = sample(c('a', 'b'), 5, TRUE)) %E>% #' arrange(from) #' # But as you can see it obscures what part of the graph is being targeted #' activate <- function(.data, what) { UseMethod('activate') } #' @export #' @importFrom rlang enquo quo_text activate.tbl_graph <- function(.data, what) { active(.data) <- quo_text(enquo(what)) .data } #' @export #' @importFrom rlang enquo activate.grouped_tbl_graph <- function(.data, what) { message('Ungrouping graph...') what <- enquo(what) activate(ungroup(.data), !!what) } #' @export activate.morphed_tbl_graph <- function(.data, what) { what <- enquo(what) .data[] <- lapply(.data, activate, what = !!what) .data } #' @rdname activate #' @export active <- function(x) { attr(x, 'active') } `active<-` <- function(x, value) { value <- gsub('"', '', value) value <- switch( value, vertices = , nodes = 'nodes', links = , edges = 'edges', stop('Only possible to activate nodes and edges', call. = FALSE) ) attr(x, 'active') <- value x } #' @rdname activate #' @importFrom rlang enexpr eval_bare caller_env #' @importFrom magrittr %>% #' @export `%N>%` <- function(lhs, rhs) { rhs <- enexpr(rhs) lhs <- activate(lhs, 'nodes') # Magrittr does not support inlining so caller # _must_ have `%>%` in scope expr <- call('%>%', lhs, rhs) eval_bare(expr, caller_env()) } #' @rdname activate #' @importFrom rlang enexpr eval_bare caller_env #' @importFrom magrittr %>% #' @export `%E>%` <- function(lhs, rhs) { rhs <- enexpr(rhs) lhs <- activate(lhs, 'edges') # Magrittr does not support inlining so caller # _must_ have `%>%` in scope expr <- call('%>%', lhs, rhs) eval_bare(expr, caller_env()) } tidygraph/R/context.R0000644000176200001440000001155013545322217014263 0ustar liggesusers#' @importFrom R6 R6Class ContextBuilder <- R6Class( 'ContextBuilder', public = list( set = function(graph) { stopifnot(inherits(graph, 'tbl_graph')) private$context <- c(private$context, list(graph)) invisible(self) }, clear = function() { private$context <- private$context[-length(private$context)] }, alive = function() { length(private$context) != 0 }, graph = function() { private$check() private$context[[length(private$context)]] }, nodes = function() { as_tibble(self$graph(), active = 'nodes') }, edges = function() { as_tibble(self$graph(), active = 'edges') }, active = function() { private$check() active(self$graph()) }, free = function() { private$FREE != 0 || inherits(self$graph(), 'free_context_tbl_graph') }, force_free = function() { private$FREE <- private$FREE + 1 }, force_unfree = function() { private$FREE <- private$FREE - 1 } ), private = list( context = list(), FREE = 0, check = function() { if (!self$alive()) { stop('This function should not be called directly', call. = FALSE) } } ) ) #' @export .graph_context <- ContextBuilder$new() expect_nodes <- function() { if (!.graph_context$free() && .graph_context$active() != 'nodes') { stop('This call requires nodes to be active', call. = FALSE) } } expect_edges <- function() { if (!.graph_context$free() && .graph_context$active() != 'edges') { stop('This call requires edges to be active', call. = FALSE) } } #' Access graph, nodes, and edges directly inside verbs #' #' These three functions makes it possible to directly access either the node #' data, the edge data or the graph itself while computing inside verbs. It is #' e.g. possible to add an attribute from the node data to the edges based on #' the terminating nodes of the edge, or extract some statistics from the graph #' itself to use in computations. #' #' @return Either a `tbl_graph` (`.G()`) or a `tibble` (`.N()`) #' #' @rdname context_accessors #' @name context_accessors #' #' @examples #' #' # Get data from the nodes while computing for the edges #' create_notable('bull') %>% #' activate(nodes) %>% #' mutate(centrality = centrality_power()) %>% #' activate(edges) %>% #' mutate(mean_centrality = (.N()$centrality[from] + .N()$centrality[to])/2) NULL #' @describeIn context_accessors Get the tbl_graph you're currently working on #' @export .G <- function() { .graph_context$graph() } #' @describeIn context_accessors Get the nodes data from the graph you're currently working on #' @export .N <- function() { .graph_context$nodes() } #' @describeIn context_accessors Get the edges data from the graph you're currently working on #' @export .E <- function() { .graph_context$edges() } #' Register a graph context for the duration of the current frame #' #' This function sets the provided graph to be the context for tidygraph #' algorithms, such as e.g. [node_is_center()], for the duration of the current #' environment. It automatically removes the graph once the environment exits. #' #' @param graph A `tbl_graph` object #' #' @param free Should the active state of the graph be ignored? #' #' @param env The environment where the context should be active #' #' @export #' @keywords internal .register_graph_context <- function(graph, free = FALSE, env = parent.frame()) { stopifnot(is.tbl_graph(graph)) if (identical(env, .GlobalEnv)) { stop('A context cannot be registered to the global environment', call. = FALSE) } if (free) { class(graph) <- c('free_context_tbl_graph', class(graph)) } .graph_context$set(graph) do.call(on.exit, alist(expr = .graph_context$clear(), add = TRUE), envir = env) invisible(NULL) } .free_graph_context <- function(env = parent.frame()) { if (identical(env, .GlobalEnv)) { stop('A context cannot be freed in the global environment', call. = FALSE) } .graph_context$force_free() do.call(on.exit, alist(expr = .graph_context$force_unfree(), add = TRUE), envir = env) invisible(NULL) } #' Evaluate a tidygraph algorithm in the context of a graph #' #' All tidygraph algorithms are meant to be called inside tidygraph verbs such #' as `mutate()`, where the graph that is currently being worked on is known and #' thus not needed as an argument to the function. In the off chance that you #' want to use an algorithm outside of the tidygraph framework you can use #' `with_graph()` to set the graph context temporarily while the algorithm is #' being evaluated. #' #' @param graph The `tbl_graph` to use as context #' #' @param expr The expression to evaluate #' #' @return The value of `expr` #' #' @export #' #' @examples #' gr <- play_erdos_renyi(10, 0.3) #' #' with_graph(gr, centrality_degree()) #' with_graph <- function(graph, expr) { .register_graph_context(graph, free = TRUE) expr } tidygraph/R/bind.R0000644000176200001440000001036013656234650013517 0ustar liggesusers#' Add graphs, nodes, or edges to a tbl_graph #' #' These functions are tbl_graph pendants to [dplyr::bind_rows()] that allows #' you to grow your `tbl_graph` by adding rows to either the nodes data, the #' edges data, or both. As with `bind_rows()` columns are matched by name and #' are automatically filled with `NA` if the column doesn't exist in some #' instances. In the case of `bind_graphs()` the graphs are automatically #' converted to `tbl_graph` objects prior to binding. The edges in each graph #' will continue to reference the nodes in the graph where they originated, #' meaning that their terminal node indexes will be shifted to match the new #' index of the node in the combined graph. This means the `bind_graphs()` #' always result in a disconnected graph. See [graph_join()] for merging graphs #' on common nodes. #' #' @param .data A `tbl_graph`, or a list of `tbl_graph` objects (for #' `bind_graphs()`). #' #' @param ... In case of `bind_nodes()` and `bind_edges()` data.frames to add. #' In the case of `bind_graphs()` objects that are convertible to `tbl_graph` #' using `as_tbl_graph()`. #' #' @param node_key The name of the column in `nodes` that character represented #' `to` and `from` columns should be matched against. If `NA` the first column #' is always chosen. This setting has no effect if `to` and `from` are given as #' integers. #' #' @return A `tbl_graph` containing the new data #' #' @importFrom dplyr bind_rows #' @importFrom tibble as_tibble #' @importFrom igraph is_directed #' @importFrom rlang is_bare_list list2 #' @export #' #' @examples #' graph <- create_notable('bull') #' new_graph <- create_notable('housex') #' #' # Add nodes #' graph %>% bind_nodes(data.frame(new = 1:4)) #' #' # Add edges #' graph %>% bind_edges(data.frame(from = 1, to = 4:5)) #' #' # Add graphs #' graph %>% bind_graphs(new_graph) #' bind_graphs <- function(.data, ...) { if (is_bare_list(.data)) { .data <- lapply(c(.data, list2(...)), as_tbl_graph) dots <- .data[-1] .data <- .data[[1]] } else { .data <- as_tbl_graph(.data) dots <- lapply(list2(...), as_tbl_graph) } if (length(dots) == 0) return(.data) n_nodes <- sapply(dots, gorder) n_edges <- sapply(dots, gsize) offset <- rep(c(gorder(.data), gorder(.data) + cumsum(n_nodes)[-length(n_nodes)]), n_edges) nodes <- bind_rows(as_tibble(.data, active = 'nodes'), lapply(dots, as_tibble, active = 'nodes')) edges <- bind_rows(lapply(dots, as_tibble, active = 'edges')) edges$from <- edges$from + offset edges$to <- edges$to + offset edges <- bind_rows(as_tibble(.data, active = 'edges'), edges) as_tbl_graph(list(nodes = nodes, edges = edges), directed = is_directed(.data)) %gr_attr% .data } #' @rdname bind_graphs #' @importFrom igraph add_vertices #' @importFrom dplyr bind_rows #' @importFrom tibble as_tibble #' @export bind_nodes <- function(.data, ...) { stopifnot(is.tbl_graph(.data)) d_tmp <- as_tibble(.data, acitve = 'nodes') new_nodes <- bind_rows(d_tmp, ...) .data <- add_vertices(.data, nrow(new_nodes) - nrow(d_tmp)) %gr_attr% .data set_graph_data(.data, new_nodes, active = 'nodes') } #' @rdname bind_graphs #' @importFrom tibble as_tibble #' @importFrom dplyr bind_rows #' @importFrom igraph gorder add_edges #' @export bind_edges <- function(.data, ..., node_key = 'name') { stopifnot(is.tbl_graph(.data)) d_tmp <- as_tibble(.data, active = 'edges') nodes <- as_tibble(.data, active = 'nodes') if (is.na(node_key)) { name_ind <- 1L } else { name_ind <- which(names(nodes) == node_key) if (length(name_ind) == 0) name_ind <- 1 } new_edges <- bind_rows(...) if (is.character(new_edges$from)) { new_edges$from <- match(new_edges$from, nodes[[name_ind]]) } if (is.character(new_edges$to)) { new_edges$to <- match(new_edges$to, nodes[[name_ind]]) } all_edges <- bind_rows(d_tmp, new_edges) if (any(is.na(all_edges$from)) || any(is.na(all_edges$to))) { stop('Edges can only be added if they contain a valid "to" and "from" column', call. = FALSE) } if (max(c(new_edges$to, new_edges$from)) > gorder(.data)) { stop('Edges can only be added if they refer to existing nodes', call. = FALSE) } .data <- add_edges(.data, rbind(new_edges$from, new_edges$to)) %gr_attr% .data set_graph_data(.data, all_edges, active = 'edges') } tidygraph/R/plot.R0000644000176200001440000000100113545322217013543 0ustar liggesusers#' Fortify a tbl_graph for ggplot2 plotting #' #' In general `tbl_graph` objects are intended to be plotted by network #' visualisation libraries such as `ggraph`. However, if you do wish to plot #' either the node or edge data directly with `ggplot2` you can simply add the #' `tbl_graph` object as either the global or layer data and the currently #' active data is passed on as a regular data frame. #' #' @export #' @keywords internal #' fortify.tbl_graph <- function(model, data, ...) { as_tibble(model) } tidygraph/R/sample_frac.R0000644000176200001440000000214013545322217015046 0ustar liggesusers#' @export #' @importFrom dplyr sample_frac #' @importFrom igraph delete_vertices delete_edges #' @importFrom rlang enquo sample_frac.tbl_graph <- function(tbl, size = 1, replace = FALSE, weight = NULL, .env = parent.frame(), ...) { d_tmp <- as_tibble(tbl) weight <- enquo(weight) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- sample_frac(d_tmp, size = size, replace = replace, weight = !! weight, .env = .env) remove_ind <- orig_ind[-d_tmp$.tbl_graph_index] switch( active(tbl), nodes = delete_vertices(tbl, remove_ind), edges = delete_edges(tbl, remove_ind) ) %gr_attr% tbl } #' @export #' @importFrom dplyr sample_frac #' @importFrom rlang enquo sample_frac.morphed_tbl_graph <- function(tbl, size = 1, replace = FALSE, weight = NULL, .env = parent.frame(), ...) { weight <- enquo(weight) tbl[] <- lapply(tbl, sample_frac, size = size, replace = replace, weight = !! weight, .env = .env) tbl } #' @export dplyr::sample_frac tidygraph/R/data_tree.R0000644000176200001440000000073013654767364014546 0ustar liggesusers#' @describeIn tbl_graph Method to deal with Node objects from the data.tree package #' @export as_tbl_graph.Node <- function(x, directed = TRUE, mode = 'out', ...) { if (!requireNamespace("data.tree", quietly = TRUE)) { stop('The "data.tree" package is needed for this functionality to work', call. = FALSE) } direction <- if (mode == 'out') 'climb' else 'descend' as_tbl_graph(data.tree::as.igraph.Node(x, directed = directed, direction = direction, ...)) } tidygraph/R/network.R0000644000176200001440000000432113545322217014266 0ustar liggesusers#' @describeIn tbl_graph Method to handle network objects from the `network` #' package. Requires this packages to work. #' @export as_tbl_graph.network <- function(x, ...) { as_tbl_graph(network_to_igraph(x)) } #' Coerce network to igraph #' #' This utility function performs a conversion of network objects from the #' network package into igraph object compatible with ggraph. Edge and node #' attributes are preserved which, for the context of ggraph, is the most #' important. #' #' @param graph A graph as a network object #' #' @return A representation of the same graph as given in the function call but #' as an igraph object. #' #' @importFrom igraph graph_from_edgelist graph_attr<- edge_attr<- vertex_attr<- #' @importFrom utils modifyList #' @noRd #' network_to_igraph <- function(graph) { if (!requireNamespace("network", quietly = TRUE)) { stop('The "network" package is needed for this functionality to work', call. = FALSE) } graph_attr_names <- network::list.network.attributes(graph) graph_attr <- lapply(graph_attr_names, function(n) { network::get.network.attribute(graph, n) }) names(graph_attr) <- graph_attr_names if (graph_attr$hyper) { stop('Hypergraphs are currently unsupported', call. = FALSE) } node_attr_names <- network::list.vertex.attributes(graph) node_attr <- lapply(node_attr_names, function(n) { network::get.vertex.attribute(graph, n) }) names(node_attr) <- node_attr_names edge_attr_names <- network::list.edge.attributes(graph) edge_attr <- lapply(edge_attr_names, function(n) { network::get.edge.attribute(graph, n) }) names(edge_attr) <- edge_attr_names edges <- as.matrix(graph, matrix.type = 'edgelist') class(edges) <- 'matrix' attributes(edges) <- attributes(edges)[c('dim', 'class')] new_graph <- graph_from_edgelist(edges, graph_attr$directed) graph_attr(new_graph) <- modifyList( graph_attr, list(bipartite = NULL, directed = NULL, hyper = NULL, loops = NULL, mnext = NULL, multiple = NULL, n = NULL) ) if (is.character(node_attr$vertex.names)) { node_attr$name <- node_attr$vertex.names } node_attr$vertex.names <- NULL vertex_attr(new_graph) <- node_attr edge_attr(new_graph) <- edge_attr new_graph } tidygraph/R/group_by.R0000644000176200001440000000416413656201203014422 0ustar liggesusers#' @importFrom dplyr group_by #' @export group_by.tbl_graph <- function(.data, ..., add = FALSE) { .register_graph_context(.data) d_tmp <- as_tibble(.data) d_tmp <- group_by(d_tmp, ..., add = add) apply_groups(.data, attributes(d_tmp)) } #' @export #' @importFrom dplyr group_by group_by.morphed_tbl_graph <- function(.data, ...) { .data[] <- lapply(.data, group_by, ...) .data } #' @export dplyr::group_by #' @importFrom dplyr ungroup #' @export ungroup.tbl_graph <- function(x, ...) { x } #' @importFrom dplyr ungroup #' @export ungroup.grouped_tbl_graph <- function(x, ...) { attr(x, paste0(active(x), '_group_attr')) <- NULL class(x) <- class(x)[class(x) != 'grouped_tbl_graph'] x } #' @export #' @importFrom dplyr ungroup ungroup.morphed_tbl_graph <- function(x, ...) { x[] <- lapply(x, ungroup, ...) x } #' @export dplyr::ungroup #' @importFrom dplyr group_size #' @export group_size.tbl_graph <- function(x) { group_size(as_tibble(x)) } #' @export dplyr::group_size #' @importFrom dplyr n_groups #' @export n_groups.tbl_graph <- function(x) { n_groups(as_tibble(x)) } #' @export dplyr::n_groups #' @importFrom dplyr groups #' @export groups.tbl_graph <- function(x) { groups(as_tibble(x)) } #' @export dplyr::groups #' @importFrom dplyr group_vars #' @export group_vars.tbl_graph <- function(x) { group_vars(as_tibble(x)) } #' @export dplyr::group_vars #' @importFrom dplyr group_data #' @export group_data.tbl_graph <- function(.data) { group_data(as_tibble(.data)) } #' @export dplyr::group_data #' @importFrom dplyr group_indices #' @export group_indices.tbl_graph <- function(.data, ...) { group_indices(as_tibble(.data), ...) } #' @export dplyr::group_indices #' @importFrom dplyr group_keys #' @export group_keys.tbl_graph <- function(.tbl, ...) { group_keys(as_tibble(.data)) } #' @export dplyr::group_keys is.grouped_tbl_graph <- function(x) { inherits(x, 'grouped_tbl_graph') } apply_groups <- function(graph, attributes) { attr(graph, paste0(active(graph), '_group_attr')) <- attributes if (!is.grouped_tbl_graph(graph)) { class(graph) <- c('grouped_tbl_graph', class(graph)) } graph } tidygraph/R/group.R0000644000176200001440000001701513656241412013735 0ustar liggesusers#' Group nodes and edges based on community structure #' #' These functions are wrappers around the various clustering functions provided #' by `igraph`. As with the other wrappers they automatically use the graph that #' is being computed on, and otherwise passes on its arguments to the relevant #' clustering function. The return value is always a numeric vector of group #' memberships so that nodes or edges with the same number are part of the same #' group. Grouping is predominantly made on nodes and currently the only #' grouping of edges supported is biconnected components. #' #' @param weights The weight of the edges to use for the calculation. Will be #' evaluated in the context of the edge data. #' @param node_weights The weight of the nodes to use for the calculation. Will #' be evaluated in the context of the node data. #' @param label The initial groups of the nodes. Will be evaluated in the #' context of the node data. #' @param fixed A logical vector determining which nodes should keep their #' initial groups. Will be evaluated in the context of the node data. #' @param type The type of component to find. Either `'weak'` or `'strong'` #' @param directed Should direction of edges be used for the calculations #' @param trials Number of times partition of the network should be attempted #' @param steps The number of steps in the random walks #' @param options Settings passed on to `igraph::arpack()` #' @param ... arguments passed on to [igraph::cluster_spinglass()] #' #' @return a numeric vector with the membership for each node in the graph. The #' enumeration happens in order based on group size progressing from the largest #' to the smallest group #' #' @name group_graph #' @rdname group_graph #' #' @examples #' create_notable('tutte') %>% #' activate(nodes) %>% #' mutate(group = group_infomap()) #' NULL #' @describeIn group_graph Group by connected compenents using [igraph::components()] #' @importFrom igraph components #' @export group_components <- function(type = 'weak') { expect_nodes() group <- as.integer(components(graph = .G(), mode = type)$membership) desc_enumeration(group) } #' @describeIn group_graph Group densely connected nodes using [igraph::cluster_edge_betweenness()] #' @importFrom igraph membership cluster_edge_betweenness #' @export group_edge_betweenness <- function(weights = NULL, directed = TRUE) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) # NULL in weights is for once respected despite a weight attribute group <- as.integer(membership(cluster_edge_betweenness(graph = .G(), weights = weights, directed = directed))) desc_enumeration(group) } #' @describeIn group_graph Group nodes by optimising modularity using [igraph::cluster_fast_greedy()] #' @importFrom igraph membership cluster_fast_greedy #' @export group_fast_greedy <- function(weights = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) # NULL in weights is for once respected despite a weight attribute group <- as.integer(membership(cluster_fast_greedy(graph = .G(), weights = weights))) desc_enumeration(group) } #' @describeIn group_graph Group nodes by minimizing description length using [igraph::cluster_infomap()] #' @importFrom igraph membership cluster_infomap #' @export group_infomap <- function(weights = NULL, node_weights = NULL, trials = 10) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } node_weights <- enquo(node_weights) node_weights <- eval_tidy(node_weights, .N()) group <- as.integer(membership(cluster_infomap(graph = .G(), e.weights = weights, v.weights = node_weights, nb.trials = trials))) desc_enumeration(group) } #' @describeIn group_graph Group nodes by propagating labels using [igraph::cluster_label_prop()] #' @importFrom igraph membership cluster_label_prop #' @export group_label_prop <- function(weights = NULL, label = NULL, fixed = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } N <- .N() label <- enquo(label) label <- eval_tidy(label, N) fixed <- enquo(fixed) fixed <- eval_tidy(fixed, N) group <- as.integer(membership(cluster_label_prop(graph = .G(), weights = weights, initial = label, fixed = fixed))) desc_enumeration(group) } #' @describeIn group_graph Group nodes based on the leading eigenvector of the modularity matrix using [igraph::cluster_leading_eigen()] #' @importFrom igraph membership cluster_leading_eigen #' @export group_leading_eigen <- function(weights = NULL, steps = -1, label = NULL, options = igraph::arpack_defaults) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } label <- enquo(label) label <- eval_tidy(label, .N()) group <- as.integer(membership(cluster_leading_eigen(graph = .G(), steps = steps, weights = weights, start = label, options = options))) desc_enumeration(group) } #' @describeIn group_graph Group nodes by multilevel optimisation of modularity using [igraph::cluster_louvain()] #' @importFrom igraph membership cluster_louvain #' @export group_louvain <- function(weights = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } group <- as.integer(membership(cluster_louvain(graph = .G(), weights = weights))) desc_enumeration(group) } #' @describeIn group_graph Group nodes by optimising the moldularity score using [igraph::cluster_optimal()] #' @importFrom igraph membership cluster_optimal #' @export group_optimal <- function(weights = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } group <- as.integer(membership(cluster_optimal(graph = .G(), weights = weights))) desc_enumeration(group) } #' @describeIn group_graph Group nodes using simulated annealing with [igraph::cluster_spinglass()] #' @importFrom igraph membership cluster_spinglass #' @export group_spinglass <- function(weights = NULL, ...) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } group <- as.integer(membership(cluster_spinglass(graph = .G(), weights = weights, vertex = NULL, ...))) desc_enumeration(group) } #' @describeIn group_graph Group nodes via short random walks using [igraph::cluster_walktrap()] #' @importFrom igraph membership cluster_walktrap #' @export group_walktrap <- function(weights = NULL, steps = 4) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } group <- as.integer(membership(cluster_walktrap(graph = .G(), weights = weights, steps = steps))) desc_enumeration(group) } #' @describeIn group_graph Group edges by their membership of the maximal binconnected components using [igraph::biconnected_components()] #' @importFrom igraph biconnected_components #' @export group_biconnected_component <- function() { expect_edges() graph <- .G() comp <- biconnected_components(graph) ind <- lapply(comp$component_edges, as.integer) group <- rep(seq_along(ind), lengths(ind))[order(unlist(ind))] desc_enumeration(group) } # HELPERS ----------------------------------------------------------------- # Take an integer vector and recode it so the most prevalent integer is 1 and so # forth desc_enumeration <- function(group) { match(group, as.integer(names(sort(table(group), decreasing = TRUE)))) } tidygraph/R/graph.R0000644000176200001440000000376313545322217013707 0ustar liggesusers#' @describeIn tbl_graph Method for handling graphNEL objects from the graph package (on Bioconductor) #' @importFrom dplyr bind_rows #' @importFrom tibble tibble as_tibble #' @importFrom igraph as_edgelist edge_attr<- vertex_attr<- V #' @export as_tbl_graph.graphNEL <- function(x, ...) { if (!requireNamespace("graph", quietly = TRUE)) { stop('The "graph" package is needed for this functionality to work', call. = FALSE) } directed <- graph::edgemode(x) == 'directed' adj_list <- lapply(graph::edgeL(x), `[[`, i = 'edges') graph <- as_tbl_graph(adj_list) edgelist <- as_edgelist(graph, names = TRUE) edge_names <- apply(edgelist, 1, paste, collapse = '|') graph_ed <- graph::edgeData(x) edge_data <- bind_rows(lapply(graph_ed, as_tibble)) edge_data_full <- edge_data[rep(nrow(edge_data) + 1, length(edge_names)), ] edge_data_full[match(names(graph_ed), edge_names), ] <- edge_data graph_nd <- graph::nodeData(x) graph_nd <- lapply(names(graph_nd), function(n) { if (length(graph_nd[[n]]) == 0) { tibble(name = n) } else { as_tibble(list(name = n, graph_nd[[n]])) } }) node_data <- bind_rows(graph_nd) node_data <- node_data[match(node_data$name, V(graph)$name), ] edge_attr(graph) <- edge_data_full vertex_attr(graph) <- node_data as_tbl_graph(graph) } #' @describeIn tbl_graph Method for handling graphAM objects from the graph package (on Bioconductor) #' @export as_tbl_graph.graphAM <- function(x, ...) { if (!requireNamespace("methods", quietly = TRUE)) { stop('The "methods" package is needed for this functionality to work', call. = FALSE) } as_tbl_graph(methods::as(x, 'graphNEL'), ...) } #' @describeIn tbl_graph Method for handling graphBAM objects from the graph package (on Bioconductor) #' @export as_tbl_graph.graphBAM <- function(x, ...) { if (!requireNamespace("methods", quietly = TRUE)) { stop('The "methods" package is needed for this functionality to work', call. = FALSE) } as_tbl_graph(methods::as(x, 'graphNEL'), ...) } tidygraph/R/filter.R0000644000176200001440000000154513545322217014067 0ustar liggesusers#' @export #' @importFrom dplyr filter #' @importFrom igraph delete_vertices delete_edges filter.tbl_graph <- function(.data, ...) { .register_graph_context(.data) d_tmp <- as_tibble(.data) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- filter(d_tmp, ...) remove_ind <- if (nrow(d_tmp) == 0) orig_ind else orig_ind[-d_tmp$.tbl_graph_index] switch( active(.data), nodes = delete_vertices(.data, remove_ind), edges = delete_edges(.data, remove_ind) ) %gr_attr% .data } #' @export #' @importFrom dplyr filter filter.morphed_tbl_graph <- function(.data, ...) { .data[] <- lapply(.data, filter, ...) .data } #' @export dplyr::filter #' @importFrom dplyr top_n #' @export dplyr::top_n tidygraph/R/joins.R0000644000176200001440000001343613545322217013726 0ustar liggesusers#' @importFrom dplyr left_join #' @export left_join.tbl_graph <- function(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) { d_tmp <- as_tibble(x) d_tmp <- left_join(d_tmp, y, by = by, copy = copy, suffix = suffix, ...) set_graph_data(x, d_tmp) } #' @export dplyr::left_join #' @importFrom dplyr right_join #' @importFrom stats na.omit #' @export right_join.tbl_graph <- function(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) { d_tmp <- as_tibble(x) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } if (active(x) == 'edges' && (!all(c('from', 'to') %in% names(y)) || !(is.numeric(y$from) && is.numeric(y$to)))) { stop('y must contain the numeric columns "from" and "to"', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- right_join(d_tmp, y, by = by, copy = copy, suffix = suffix, ...) new_order <- order(d_tmp$.tbl_graph_index) # Will never eclipse Joy Division d_tmp <- d_tmp[new_order, ] x <- slice(x, na.omit(d_tmp$.tbl_graph_index)) new_rows <- which(is.na(d_tmp$.tbl_graph_index)) d_tmp$.tbl_graph_index <- NULL x <- switch( active(x), nodes = add_vertices(x, length(new_rows)), edges = add_edges(x, as.vector(rbind(d_tmp$from[new_rows], d_tmp$to[new_rows]))) ) %gr_attr% x x <- set_graph_data(x, d_tmp) arrange(x, seq_len(nrow(d_tmp))[new_order]) } #' @export dplyr::right_join #' @importFrom dplyr inner_join #' @export inner_join.tbl_graph <- function(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) { d_tmp <- as_tibble(x) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- inner_join(d_tmp, y, by = by, copy = copy, suffix = suffix, ...) x <- slice(x, d_tmp$.tbl_graph_index) d_tmp$.tbl_graph_index <- NULL set_graph_data(x, d_tmp) } #' @export dplyr::inner_join #' @importFrom dplyr full_join #' @importFrom igraph add_vertices add_edges #' @export full_join.tbl_graph <- function(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) { d_tmp <- as_tibble(x) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } if (active(x) == 'edges' && (!all(c('from', 'to') %in% names(y)) || !(is.numeric(y$from) && is.numeric(y$to)))) { stop('y must contain the numeric columns "from" and "to"', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- full_join(d_tmp, y, by = by, copy = copy, suffix = suffix, ...) new_rows <- which(is.na(d_tmp$.tbl_graph_index)) d_tmp$.tbl_graph_index <- NULL x <- switch( active(x), nodes = add_vertices(x, length(new_rows)), edges = add_edges(x, as.vector(rbind(d_tmp$from[new_rows], d_tmp$to[new_rows]))) ) %gr_attr% x set_graph_data(x, d_tmp) } #' @export dplyr::full_join #' @importFrom dplyr semi_join #' @export semi_join.tbl_graph <- function(x, y, by = NULL, copy = FALSE, ...) { d_tmp <- as_tibble(x) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- semi_join(d_tmp, y, by = by, copy = copy, ...) slice(x, d_tmp$.tbl_graph_index) } #' @export dplyr::semi_join #' @importFrom dplyr anti_join #' @export anti_join.tbl_graph <- function(x, y, by = NULL, copy = FALSE, ...) { d_tmp <- as_tibble(x) if ('.tbl_graph_index' %in% names(d_tmp)) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- anti_join(d_tmp, y, by = by, copy = copy, ...) slice(x, d_tmp$.tbl_graph_index) } #' @export dplyr::anti_join #' Join graphs on common nodes #' #' This graph-specific join method makes a full join on the nodes data and #' updates the edges in the joining graph so they matches the new indexes of the #' nodes in the resulting graph. Node and edge data is combined using #' [dplyr::bind_rows()] semantic, meaning that data is matched by column name #' and filled with `NA` if it is missing in either of the graphs. #' #' @param x A `tbl_graph` #' @param y An object convertible to a `tbl_graph` using [as_tbl_graph()] #' @inheritParams dplyr::full_join #' #' @return A `tbl_graph` containing the merged graph #' #' @importFrom tibble as_tibble #' @importFrom dplyr full_join bind_rows #' @export #' #' @examples #' gr1 <- create_notable('bull') %>% #' activate(nodes) %>% #' mutate(name = letters[1:5]) #' gr2 <- create_ring(10) %>% #' activate(nodes) %>% #' mutate(name = letters[4:13]) #' #' gr1 %>% graph_join(gr2) graph_join <- function(x, y, by = NULL, copy = FALSE, suffix = c(".x", ".y"), ...) { stopifnot(is.tbl_graph(x)) y <- as_tbl_graph(y) d_tmp <- as_tibble(x, active = 'nodes') d_tmp2 <- as_tibble(y, active = 'nodes') if ('.tbl_graph_index' %in% c(names(d_tmp), names(d_tmp2))) { stop('The attribute name ".tbl_graph_index" is reserved', call. = FALSE) } orig_ind <- seq_len(nrow(d_tmp2)) d_tmp2$.tbl_graph_index <- orig_ind nodes <- full_join(d_tmp, d_tmp2, by = by, copy = copy, suffix = suffix, ...) ind_lookup <- data.frame(new = seq_len(nrow(nodes)), old = nodes$.tbl_graph_index) nodes$.tbl_graph_index <- NULL edges <- as_tibble(x, active = 'edges') edges2 <- as_tibble(y, active = 'edges') edges2$from <- ind_lookup$new[match(edges2$from, ind_lookup$old)] edges2$to <- ind_lookup$new[match(edges2$to, ind_lookup$old)] edges <- bind_rows(edges, edges2) as_tbl_graph(list(nodes = nodes, edges = edges)) %gr_attr% y %gr_attr% x } tidygraph/R/local.R0000644000176200001440000000526413656240271013700 0ustar liggesusers#' Measures based on the neighborhood of each node #' #' These functions wraps a set of functions that all measures quantities of the #' local neighborhood of each node. They all return a vector or list matching #' the node position. #' #' @return A numeric vector or a list (for `local_members`) with elements #' corresponding to the nodes in the graph. #' #' @name local_graph #' @rdname local_graph #' #' @examples #' # Get all neighbors of each graph #' create_notable('chvatal') %>% #' activate(nodes) %>% #' mutate(neighborhood = local_members(mindist = 1)) #' #' # These are equivalent #' create_notable('chvatal') %>% #' activate(nodes) %>% #' mutate(n_neighbors = local_size(mindist = 1), #' degree = centrality_degree()) %>% #' as_tibble() #' NULL #' @describeIn local_graph The size of the neighborhood in a given distance from #' the node. (Note that the node itself is included unless `mindist > 0`). Wraps [igraph::ego_size()]. #' @inheritParams igraph::ego_size #' @importFrom igraph ego_size #' @export local_size <- function(order = 1, mode = 'all', mindist = 0) { expect_nodes() ego_size(graph = .G(), order = order, mode = mode, mindist = mindist) } #' @describeIn local_graph The members of the neighborhood of each node in a #' given distance. Wraps [igraph::ego()]. #' @importFrom igraph ego #' @export local_members <- function(order = 1, mode = 'all', mindist = 0) { expect_nodes() lapply(ego(graph = .G(), order = order, mode = mode, mindist = mindist), as.integer) } #' @describeIn local_graph The number of triangles each node participate in. Wraps [igraph::count_triangles()]. #' @importFrom igraph count_triangles #' @export local_triangles <- function() { expect_nodes() count_triangles(graph = .G()) } #' @describeIn local_graph Calculates the average degree based on the neighborhood of each node. Wraps [igraph::knn()]. #' @inheritParams igraph::knn #' @importFrom igraph knn #' @export local_ave_degree <- function(weights = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { weights <- NA } knn(graph = .G(), weights = weights)$knn } #' @describeIn local_graph Calculate the transitivity of each node, that is, the #' propensity for the nodes neighbors to be connected. Wraps [igraph::transitivity()] #' @importFrom igraph transitivity V #' @importFrom rlang quos #' @export local_transitivity <- function(weights = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) type <- if (is.null(weights)) { 'weighted' } else { 'local' } graph <- .G() transitivity(graph = graph, type = type, vids = V(graph), weights = weights, isolates = 'zero') } tidygraph/R/reroute.R0000644000176200001440000000453513545322217014271 0ustar liggesusers#' Change terminal nodes of edges #' #' The reroute verb lets you change the beginning and end node of edges by #' specifying the new indexes of the start and/or end node(s). Optionally only #' a subset of the edges can be rerouted using the subset argument, which should #' be an expression that are to be evaluated in the context of the edge data and #' should return an index compliant vector (either logical or integer). #' #' @param .data A tbl_graph or morphed_tbl_graph object. grouped_tbl_graph will #' be ungrouped prior to rerouting #' @param from,to The new indexes of the terminal nodes. If `NULL` nothing will #' be changed #' @param subset An expression evaluating to an indexing vector in the context #' of the edge data. #' #' @return An object of the same class as .data #' @export #' #' @examples #' # Switch direction of edges #' create_notable('meredith') %>% #' activate(edges) %>% #' reroute(from = to, to = from) #' #' # Using subset #' create_notable('meredith') %>% #' activate(edges) %>% #' reroute(from = 1, subset = to > 10) reroute <- function(.data, from = NULL, to = NULL, subset = NULL) { UseMethod('reroute') } #' @export #' @importFrom rlang enquo eval_tidy #' @importFrom igraph is.directed reroute.tbl_graph <- function(.data, from = NULL, to = NULL, subset = NULL) { .register_graph_context(.data) expect_edges() from <- enquo(from) to <- enquo(to) if (is.grouped_tbl_graph(.data)) { message('Ungrouping prior to rerouting edges') .data <- ungroup(.data) } edges <- as_tibble(.data, active = 'edges') subset <- enquo(subset) subset <- eval_tidy(subset, edges) if (is.null(subset)) subset <- seq_len(nrow(edges)) edges_sub <- edges[subset, , drop = FALSE] from <- eval_tidy(from, edges_sub) if (!is.null(from)) edges$from[subset] <- rep(from, length.out = nrow(edges_sub)) to <- eval_tidy(to, edges_sub) if (!is.null(to)) edges$to[subset] <- rep(to, length.out = nrow(edges_sub)) .data <- tbl_graph( nodes = as_tibble(.data, active = 'nodes'), edges = edges, directed = is.directed(.data) ) %gr_attr% .data active(.data) <- 'edges' .data } #' @export #' @importFrom rlang enquo reroute.morphed_tbl_graph <- function(.data, from = NULL, to = NULL, subset = NULL) { from <- enquo(from) to <- enquo(to) .data[] <- lapply(.data, reroute, from = !!from, to = !!to, subset = subset) .data } tidygraph/R/mutate.R0000644000176200001440000000320513545322217014074 0ustar liggesusers#' @importFrom rlang quos !!! #' @export mutate.tbl_graph <- function(.data, ...) { dots <- quos(...) for (i in seq_along(dots)) { dot <- dots[i] .data <- mutate_as_tbl(.data, !!!dot) } .data } #' Base implementation of mutate #' #' This implementation of mutate is slightly faster than `mutate` at the expense #' of the graph only being updated in the end. This means that graph algorithms #' will not take changes happening during the mutate call into account. #' #' @details #' The order of speed increase are rather small and in the ~1 millisecond per #' mutateed column order, so for regular use this should not be a choice. The #' operations not supported by `mutate_as_tbl` are e.g. #' #' ``` #' gr %>% #' activate(nodes) %>% #' mutate(weights = runif(10), degree = centrality_degree(weights)) #' ``` #' #' as `weights` will only be made available in the graph at the end of the #' mutate call. #' #' @param .data A `tbl_graph` object #' #' @param ... columns to mutate #' #' @return A `tbl_graph` object #' #' @keywords internal #' @importFrom dplyr mutate #' @export mutate_as_tbl <- function(.data, ...) { .register_graph_context(.data) d_tmp <- as_tibble(.data) d_tmp <- mutate(d_tmp, ...) set_graph_data(.data, d_tmp) } #' @export #' @importFrom dplyr mutate mutate.morphed_tbl_graph <- function(.data, ...) { .data[] <- lapply(.data, protect_ind, .f = mutate, ...) .data } #' @export dplyr::mutate #' @importFrom dplyr transmute #' @export dplyr::transmute #' @importFrom dplyr mutate_all #' @export dplyr::mutate_all #' @importFrom dplyr mutate_at #' @export dplyr::mutate_at #' @importFrom dplyr n #' @export dplyr::n tidygraph/NEWS.md0000644000176200001440000000715013656437433013364 0ustar liggesusers# tidygraph 1.2.0 * graph description now recognise undirected trees * Added pkgdown site at https://tidygraph.data-imaginist.com * Prepare tidygraph for dplyr 1.0.0 (#118 and #119) * Add possibility of controlling which column in `nodes` are used for matching if the `to` and `from` columns in edges are character vectors during construction (#89) * `bind_graph()` now accepts a list of graphs as its first argument (#88) * Add `graph_modularity()` for calculating modularity contingent on a node grouping (#97) * Edge weights are now handled more consistently to avoid igraph using a possible `weight` edge attribute. `weights = NULL` will always mean that no edge weight is used (#106). * Neighborhood graph in `map_local()` and siblings will now contain a `.central_node` node attribute that will identify the node from which the local graph has been calculated (#107) # tidygraph 1.1.2 * Compatibility with `dplyr` 0.8 # tidygraph 1.1.1 * Better conversion of `network` objects. Old conversion could mess up edge attributes. * Changes to anticipate new version of `tibble` and `dplyr` * `tibble`-like dimming of non-data text in printing * Edge-length is now preserved when converting from `phylo` * Added `to_subcomponent` morpher to work with a single component containing a specified node * Morphers that reference nodes now correctly tidy eval the node argument * Add `node_is_adjacent` to query which nodes are directly connected to a set of nodes * Add `fortify` method for `tbl_graph` object for plotting as regular data with `ggplot2` # tidygraph 1.1.0 * Fix bug when coercing to `tbl_graph` from an adjacency list containing `NULL` or `NA` elements. * Change license to MIT * Add `convert` verb to perform both `morph` and `crystallise` in one go, returning a single `tbl_graph` * When collapsing edges or nodes during `morph` the original data will be stored in `.orig_data` instead of `.data` to avoid conflicts with `.data` argument in many tidyverse verbs (**BREAKING**) * `as_tbl_graph.data.frame` now recognises set tables (each column gives eachs rows membership to that set) * Add `with_graph` to allow computation of algorithms outside of verbs * `graph_is_*` set of querying functions has been added that all returns logical scalars. * Add `%N>%` and `%E>%` for activating nodes and edges respectively as part of the piping. * `mutate` now lets you reference created columns in graph algorithms so it behaves in line with expected `mutate` behaviour. This has led to a slight performance decrease (millisecond scale). The old behaviour can be accessed using `mutate_as_tbl` where the graph will only get updated in the end. * When using to_subgraph with edges, isolated nodes are no longer deleted * `bind_graphs` now work with a single `tbl_graph` * Added `.register_graph_context` to allow the use of tidygraph algorithms in external functions. * Added `to_unfolded_tree`, `to_directed`, and `to_undirected` morphers * Add the `node_rank_*` family of algorithms for seriation of nodes * Added `to_hierarchical_clusters` morpher to work with hierarchical representations of community detection algorithms. * All `group_*` algorithms now ensure that the groups are enumerated in descending order based on size, i.e. members of the largest group/community will always have `1`, etc. * Fix a bug when filtering all nodes or edges where no nodes/edges would be removed (#42) * Added interface to `netrankr` resulting in 19 new centrality scores and a manual mode for composing new centrality scores * Added `edge_is_[from|to|between|incident]()` to help find edges related to certain nodes tidygraph/MD50000644000176200001440000001346513656450173012600 0ustar liggesusers0446d8e0ddd2e5742bc835291919c293 *DESCRIPTION 390aa971d748d2d8595f400ac17045c9 *LICENSE 657fcada9bb8ee6432e48e80d1477686 *NAMESPACE 3475f656253f5fd457cb9aa0ee74f67f *NEWS.md f81c72d4b332260cf171daba16ff4375 *R/RcppExports.R 4ed9047e8043852850764797d822b39b *R/aaa.R d7ea9f025fbe09f02a91797363d83349 *R/activate.R d2a111629b4f0f2ae33bc6596ba27a12 *R/arrange.R bab47c6de2279c2a3829ea9f54e2cd38 *R/bind.R 76365461b91ca5c5eb402372efd87879 *R/centrality.R f01b09780f5e54755155f70cd550ce4f *R/context.R a047fd39b52c4d2d963587191b936912 *R/create.R 8caf8d46f9cc809c9dd01a33312dc392 *R/data_frame.R aefcd0337452f6fb6dc84ca32381b234 *R/data_tree.R ddc9ad57ebf8f4f16415acd929c9b462 *R/dendrogram.R 3855fcef9fa69c24703ddb95da22ca17 *R/distinct.R 5d9074de64a02b8caa997410ed79688b *R/edge.R 1cf4fdb7c11aa04356ff5f27a1b49c36 *R/filter.R b77a8778d3f47732506fcfd1b1ce3c5e *R/graph.R 58feab7adfbd477de1c3adadab30200a *R/graph_measures.R 74b55be9a9bb31e726c876925d3df124 *R/graph_types.R fdcfa153d44d9a9c8d60c841c8dab189 *R/group.R 46282f6a1a933745e25999393a119eab *R/group_by.R 53a6a2c27b87537e5db189c514cf8166 *R/hclust.R 82e2827289d6ba2c9db8277fa2cbe406 *R/igraph.R 12ae2e211e797036d13f8fbd5b684115 *R/joins.R b176446925899dc3ebbf4c70fd12a8ce *R/list.R f4dba43106c4e2d8e5220d1e08ed49fb *R/local.R 82ac058d158e1d5b45750311b4268951 *R/map.R e3227729902c71ee1abe09113e38efca *R/matrix.R 57aa395cf3fb60ef02ffb8f0028950e4 *R/morph.R 144ab849ba134d0d97487ebb58508b7f *R/morphers.R fce1c13f70b59963e16eb9e0e7e3fb3e *R/mutate.R 37fa468550cbd5f95b98010cf936326b *R/network.R b9a49363586d73f5363e1a9d91b8192c *R/node.R 12b191163688adaa075d76e8b87ba0d0 *R/node_rank.R effa5320907a082718d25b5cd92dda30 *R/node_topology.R 46403017ec7277eb782efc889afcb257 *R/pair_measures.R ec901a6986ac4abe0fee12aceb6c747e *R/phylo.R e754add81337b95d8d3722322c31eed1 *R/play.R ccbd8cbc79386e2cf8fc3b1f20581644 *R/plot.R 7b7ae738f94bb977dc607cf8d0d17cec *R/pull.R 4b5caefefb712b756abf0e9c15d98426 *R/rename.R 7368a493baf551853fb3bc5c34973d07 *R/reroute.R 468a5161cb0a13e042e30d44edadcc58 *R/sample_frac.R 5aa1865963b7391210f2ce8db4ecefc7 *R/sample_n.R 35ecb1ba9b0d2111119c3f56bffc6a1f *R/search.R 2f9c5cee142fcfc15e1593119124bdb3 *R/select.R 9b2335eb7392d7baa8219ccb87a1ad59 *R/slice.R 587b9bbcd3cf76c951dd19851a7c1292 *R/tbl_graph.R e96ba49fead43ee0575c7c9ee816db8b *R/tibble.R 5c467e4cf72d6fc06f38adbca69db15d *R/tidygraph-package.R 0ccf26a331e854db0736eb0b6b7894e6 *README.md 5b4c66c1f1eb0bec73e5b679f90a0dfc *man/activate.Rd 762bc52e809226f082b3356bb6a32fdf *man/bind_graphs.Rd a735a6e49ba077085a1ea58d64780333 *man/centrality.Rd aa4b51e7a9056e605e46076d0af4a456 *man/component_games.Rd 63f78c514090a3bab4a5b3c0ab4e488d *man/context_accessors.Rd 88b7f79d99cddd703e5d3035132afa54 *man/create_graphs.Rd f4792f448edd844923bc95c91d422402 *man/dot-register_graph_context.Rd 5ac61c69da582ef5592a694ef76391e7 *man/edge_types.Rd 81b7ba9ef186434322cf6c0cee095947 *man/evolution_games.Rd 783835eedb18deb7b7588bd75820490f *man/figures/logo.png 0b29cc83b69904cc0c34a0641114f7b7 *man/fortify.tbl_graph.Rd ce2796cc135f859118800b93e08776c0 *man/graph_join.Rd ec1c8805fea8f7ebb8da395900fbf4c3 *man/graph_measures.Rd ccf5ccae738df2984828f8493acc1966 *man/graph_types.Rd de192f93a8cb2ac27e928b0c5c12d078 *man/group_graph.Rd 6e420e292e17b7922a66bdbbfa8b23bf *man/local_graph.Rd 23acd7ba2897775e90b1433878836552 *man/map_bfs.Rd 2c968c2fc1969e062cc4b3dd6d180976 *man/map_bfs_back.Rd b109879a3ae89d8552ae25135cd066d3 *man/map_dfs.Rd e93190c60c9a34ffcb4cdcfb6a1aa69c *man/map_dfs_back.Rd e0d553c62ff4f857b7efe1787ac27b2e *man/map_local.Rd 5711d7931fe862bec61221cf5e6b6f81 *man/morph.Rd e401384119de2ec430689d2cd67f2d4e *man/morphers.Rd 19b6ef7e8ebcedf0235684d8d5fc4bbc *man/mutate_as_tbl.Rd 1e072ab5c1216e9f20536a69eea79160 *man/node_measures.Rd 13685da139cbb002fda720dd3b4e9779 *man/node_rank.Rd 41f9b0cfbe52fd2437f558b637c91d22 *man/node_topology.Rd 846c74307e828bc9997ffdf345dad8fe *man/node_types.Rd 67b962ce9f9ccb238a746476dc8b7f95 *man/pair_measures.Rd 564be128ea33db901298fc843f8ecc01 *man/reexports.Rd e5cdf2c9cad673b72c17988b5a5d6456 *man/reroute.Rd e019ec9d75995c8d86a33e7bc7e05ca2 *man/sampling_games.Rd ecd752ef035b26cc57c4ec1847cbbfba *man/search_graph.Rd 949cb378998d913d76c514003ea8899a *man/tbl_graph.Rd ea6927203b70d413ed17906e5b621983 *man/tidygraph-package.Rd ab2dd6b722ace7cd9bb454feb9ce1071 *man/type_games.Rd 54ed70d13a7f3e12cda1f3d3cee9a7b5 *man/with_graph.Rd 78a9c7accc69df65d080b740d31b8b0f *src/RcppExports.cpp 954472c542bf9e596f3f1ccefab52651 *src/get_paths.cpp e12c49758da25518ea09c1721ad4a1c5 *tests/testthat.R d14e087ccd795bd5a47f99eb680e9361 *tests/testthat/test-activate.R 63545085ba326028758aa7d325cab61c *tests/testthat/test-arrange.R be5b5d39b8bdd9ccbf181b5620d9728f *tests/testthat/test-bind.R e8eb90b514c82bdca816206e677043ba *tests/testthat/test-centrality.R dfbe460ecc53f82d1a8a4fc41552f7e3 *tests/testthat/test-context.R 606efa52d042bafa3ac024baceaf8a88 *tests/testthat/test-distinct.R 35276d49a8c1b7b43703c056cce00d88 *tests/testthat/test-edge_types.R c936eebda5c11317c0c66bed7a022a78 *tests/testthat/test-filter.R 0291fc4e36c56aea8786972527f7d45d *tests/testthat/test-graph_attributes.R b6ce408a50b9e936c638a2ac6de5b93c *tests/testthat/test-graph_measures.R 0424d6c7fc84c0cd8f545dff54729e85 *tests/testthat/test-group.R 81af8dcacd1cea3d885f80cf3164eba9 *tests/testthat/test-group_by.R 5e24e8601df09ea2cbad0328b4439ac1 *tests/testthat/test-join.R b5152db5785617e01889e328287ba871 *tests/testthat/test-local.R 32a9f6c09d433d1258e656c674bf9c24 *tests/testthat/test-map.R 74caaa7bb14021233062e82e342c949f *tests/testthat/test-morph.R dfeb32fd44e0906aa932f3666040122f *tests/testthat/test-mutate.R a4a2b4be07678d36babb0b137139a1e3 *tests/testthat/test-node_measures.R 7c7da23846c7ca0bf83aea7d7d959237 *tests/testthat/test-node_types.R 181348ea77d83786b362eb270e12a3b9 *tests/testthat/test-pair_measures.R 7aaf1e892179b4f37f32181d9453d329 *tests/testthat/test-search.R