tidygraph/0000755000176200001440000000000014556176062012261 5ustar liggesuserstidygraph/NAMESPACE0000644000176200001440000004365014556122401013474 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,grouped_tbl_graph) S3method(as_tibble,morphed_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(drop_na,morphed_tbl_graph) S3method(drop_na,tbl_graph) S3method(filter,morphed_tbl_graph) S3method(filter,tbl_graph) S3method(focus,morphed_tbl_graph) S3method(focus,tbl_graph) S3method(full_join,tbl_graph) S3method(glimpse,morphed_tbl_graph) S3method(glimpse,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(replace_na,morphed_tbl_graph) S3method(replace_na,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(set_graph_data,grouped_tbl_graph) S3method(set_graph_data,tbl_graph) S3method(slice,morphed_tbl_graph) S3method(slice,tbl_graph) S3method(slice_head,morphed_tbl_graph) S3method(slice_head,tbl_graph) S3method(slice_max,morphed_tbl_graph) S3method(slice_max,tbl_graph) S3method(slice_min,morphed_tbl_graph) S3method(slice_min,tbl_graph) S3method(slice_sample,morphed_tbl_graph) S3method(slice_sample,tbl_graph) S3method(slice_tail,morphed_tbl_graph) S3method(slice_tail,tbl_graph) S3method(tbl_format_footer,named_tbl) S3method(tbl_sum,named_tbl) S3method(tbl_vars,tbl_graph) S3method(unfocus,focused_tbl_graph) S3method(unfocus,morphed_tbl_graph) S3method(unfocus,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(.free_graph_context) 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_harmonic) 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(drop_na) export(edge_is_between) export(edge_is_bridge) export(edge_is_feedback_arc) 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(edge_rank_eulerian) export(ends_with) export(everything) export(filter) export(focus) 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_efficiency) 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_eulerian) 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_color) export(group_components) export(group_data) export(group_edge_betweenness) export(group_fast_greedy) export(group_fluid) export(group_indices) export(group_infomap) export(group_keys) export(group_label_prop) export(group_leading_eigen) export(group_leiden) 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(iterate_n) export(iterate_while) 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_efficiency) export(node_fareness_impact) export(node_is_adjacent) export(node_is_center) export(node_is_connected) 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_gnm) export(play_gnp) export(play_growing) export(play_islands) export(play_preference) export(play_preference_asym) export(play_smallworld) export(play_traits) export(pull) export(random_walk_rank) export(rename) export(replace_na) export(reroute) export(right_join) export(sample_frac) export(sample_n) export(select) export(semi_join) export(slice) export(slice_head) export(slice_max) export(slice_min) export(slice_sample) export(slice_tail) 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_largest_component) export(to_linegraph) export(to_local_neighborhood) export(to_minimum_spanning_tree) export(to_random_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(unfocus) export(ungroup) export(unmorph) export(with_graph) importFrom(R6,R6Class) 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,slice_head) importFrom(dplyr,slice_max) importFrom(dplyr,slice_min) importFrom(dplyr,slice_sample) importFrom(dplyr,slice_tail) 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,arpack_defaults) 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,betweenness) importFrom(igraph,bfs) importFrom(igraph,bibcoupling) importFrom(igraph,biconnected_components) importFrom(igraph,bridges) importFrom(igraph,clique_num) importFrom(igraph,closeness) importFrom(igraph,cluster_edge_betweenness) importFrom(igraph,cluster_fast_greedy) importFrom(igraph,cluster_fluid_communities) importFrom(igraph,cluster_infomap) importFrom(igraph,cluster_label_prop) importFrom(igraph,cluster_leading_eigen) importFrom(igraph,cluster_leiden) 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_automorphisms) importFrom(igraph,count_components) importFrom(igraph,count_max_cliques) importFrom(igraph,count_motifs) importFrom(igraph,count_triangles) importFrom(igraph,cut_at) 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,eulerian_cycle) importFrom(igraph,eulerian_path) importFrom(igraph,feedback_arc_set) importFrom(igraph,girth) importFrom(igraph,global_efficiency) 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,greedy_vertex_coloring) importFrom(igraph,gsize) importFrom(igraph,harmonic_centrality) importFrom(igraph,has_eulerian_cycle) importFrom(igraph,has_eulerian_path) importFrom(igraph,hub_score) importFrom(igraph,induced_subgraph) 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,largest_component) importFrom(igraph,local_efficiency) 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,random_edge_walk) importFrom(igraph,random_walk) 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_spanning_tree) 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(lifecycle,deprecated) importFrom(magrittr,"%>%") importFrom(pillar,glimpse) importFrom(pillar,style_subtle) importFrom(pillar,tbl_format_footer) importFrom(pillar,tbl_sum) importFrom(rlang,"!!!") importFrom(rlang,"%||%") importFrom(rlang,":=") importFrom(rlang,.data) importFrom(rlang,UQS) importFrom(rlang,as_quosure) importFrom(rlang,caller_arg) importFrom(rlang,caller_env) importFrom(rlang,enexpr) importFrom(rlang,enquo) importFrom(rlang,eval_bare) importFrom(rlang,eval_tidy) importFrom(rlang,inject) importFrom(rlang,is_bare_list) importFrom(rlang,list2) importFrom(rlang,quo) importFrom(rlang,quo_is_null) 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(tidyr,drop_na) importFrom(tidyr,nest_legacy) importFrom(tidyr,replace_na) importFrom(tools,toTitleCase) importFrom(utils,head) importFrom(utils,modifyList) useDynLib(tidygraph, .registration = TRUE) tidygraph/LICENSE0000644000176200001440000000005714535605577013275 0ustar liggesusersYEAR: 2023 COPYRIGHT HOLDER: tidygraph authors tidygraph/README.md0000644000176200001440000001253514537000076013534 0ustar liggesusers # tidygraph [![R-CMD-check](https://github.com/thomasp85/tidygraph/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/thomasp85/tidygraph/actions/workflows/R-CMD-check.yaml) [![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://codecov.io/gh/thomasp85/tidygraph/branch/main/graph/badge.svg)](https://app.codecov.io/gh/thomasp85/tidygraph?branch=main) 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_gnp(10, 0.5) %>% activate(nodes) %>% mutate(degree = centrality_degree()) %>% activate(edges) %>% mutate(centrality = centrality_edge_betweenness()) %>% arrange(centrality) #> # A tbl_graph: 10 nodes and 51 edges #> # #> # A directed simple graph with 1 component #> # #> # Edge Data: 51 × 3 (active) #> from to centrality #> #> 1 2 7 1.25 #> 2 6 5 1.33 #> 3 1 3 1.4 #> 4 2 10 1.53 #> 5 2 8 1.58 #> 6 8 9 1.65 #> 7 2 3 1.67 #> 8 2 5 1.73 #> 9 3 5 1.73 #> 10 8 5 1.73 #> # ℹ 41 more rows #> # #> # Node Data: 10 × 1 #> degree #> #> 1 6 #> 2 7 #> 3 6 #> # ℹ 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('pak') pak::pak('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/0000755000176200001440000000000014556122333013024 5ustar liggesuserstidygraph/man/morph.Rd0000644000176200001440000000765714535605577014474 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 changes 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.Rd0000644000176200001440000000623014535605577015451 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 join specification created with \code{\link[dplyr:join_by]{join_by()}}, or 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 on different variables between \code{x} and \code{y}, use a \code{\link[dplyr:join_by]{join_by()}} specification. For example, \code{join_by(a == b)} will match \code{x$a} to \code{y$b}. To join by multiple variables, use a \code{\link[dplyr:join_by]{join_by()}} specification with multiple expressions. For example, \code{join_by(a == b, c == d)} will match \code{x$a} to \code{y$b} and \code{x$c} to \code{y$d}. If the column names are the same between \code{x} and \code{y}, you can shorten this by listing only the variable names, like \code{join_by(a, c)}. \code{\link[dplyr:join_by]{join_by()}} can also be used to perform inequality, rolling, and overlap joins. See the documentation at \link[dplyr:join_by]{?join_by} for details on these types of joins. For simple equality joins, you can alternatively specify a character vector of variable names to join by. For example, \code{by = c("a", "b")} joins \code{x$a} to \code{y$a} and \code{x$b} to \code{y$b}. If variable names differ between \code{x} and \code{y}, use a named character vector like \code{by = c("x_a" = "y_a", "x_b" = "y_b")}. To perform a cross-join, generating all combinations of \code{x} and \code{y}, see \code{\link[dplyr:cross_join]{cross_join()}}.} \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.Rd0000644000176200001440000000570314535605577015763 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.Rd0000644000176200001440000000240214535605577015013 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. If \code{NULL} it will use focused edges if available or all edges} } \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.Rd0000644000176200001440000001543514535605577015177 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_largest_component} \alias{to_complement} \alias{to_local_neighborhood} \alias{to_dominator_tree} \alias{to_minimum_spanning_tree} \alias{to_random_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", min_order = 1) to_largest_component(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_random_spanning_tree(graph) 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{min_order}{The minimum order (number of vertices) of the component. Components below this will not be created} \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_largest_component()}: Create a new graph only consisting of it's largest component. If multiple largest components exists, the one with containing the node with the lowest index is chosen. \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_random_spanning_tree()}: Convert a graph into a random 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.Rd0000644000176200001440000000715414535605577016537 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.Rd0000644000176200001440000000207114535605577016153 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. \if{html}{\out{
}}\preformatted{gr \%>\% activate(nodes) \%>\% mutate(weights = runif(10), degree = centrality_degree(weights)) }\if{html}{\out{
}} as \code{weights} will only be made available in the graph at the end of the mutate call. } \keyword{internal} tidygraph/man/edge_rank.Rd0000644000176200001440000000157614535605577015260 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/edge_rank.R \name{edge_rank} \alias{edge_rank} \alias{edge_rank_eulerian} \title{Calculate edge ranking} \usage{ edge_rank_eulerian(cyclic = FALSE) } \arguments{ \item{cyclic}{should the eulerian path start and end at the same node} } \value{ An integer vector giving the position of each edge in the ranking } \description{ This set of functions tries to calculate a ranking of the edges in a graph so that edges sharing certain topological traits are in proximity in the resulting order. } \section{Functions}{ \itemize{ \item \code{edge_rank_eulerian()}: Calculcate ranking as the visit order of a eulerian path or cycle. If no such path or cycle exist it will return a vector of \code{NA}s }} \examples{ graph <- create_notable('meredith') \%>\% activate(edges) \%>\% mutate(rank = edge_rank_eulerian()) } tidygraph/man/tidygraph-package.Rd0000644000176200001440000000200514535605577016711 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: style='float: 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.Rd0000644000176200001440000000711414556122324015705 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}()}, \code{\link{map_dfs_back}()} } \concept{node map functions} tidygraph/man/map_dfs.Rd0000644000176200001440000000655114556122324014733 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}()}, \code{\link{map_bfs_back}()}, \code{\link{map_dfs_back}()} } \concept{node map functions} tidygraph/man/map_dfs_back.Rd0000644000176200001440000000677514556122324015723 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}()}, \code{\link{map_bfs_back}()}, \code{\link{map_dfs}()} } \concept{node map functions} tidygraph/man/activate.Rd0000644000176200001440000000316214535605577015132 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/focus.Rd0000644000176200001440000000445514556122323014441 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/focus.R \name{focus} \alias{focus} \alias{focus.tbl_graph} \alias{focus.morphed_tbl_graph} \alias{unfocus} \alias{unfocus.tbl_graph} \alias{unfocus.focused_tbl_graph} \alias{unfocus.morphed_tbl_graph} \title{Select specific nodes or edges to compute on} \usage{ focus(.data, ...) \method{focus}{tbl_graph}(.data, ...) \method{focus}{morphed_tbl_graph}(.data, ...) unfocus(.data, ...) \method{unfocus}{tbl_graph}(.data, ...) \method{unfocus}{focused_tbl_graph}(.data, ...) \method{unfocus}{morphed_tbl_graph}(.data, ...) } \arguments{ \item{.data}{A data frame, data frame extension (e.g. a tibble), or a lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for more details.} \item{...}{<\code{\link[rlang:args_data_masking]{data-masking}}> Expressions that return a logical value, and are defined in terms of the variables in \code{.data}. If multiple expressions are included, they are combined with the \code{&} operator. Only rows for which all conditions evaluate to \code{TRUE} are kept.} } \value{ A graph with focus applied } \description{ The \code{focus()}/\code{unfocus()} idiom allow you to temporarily tell tidygraph algorithms to only calculate on a subset of the data, while keeping the full graph intact. The purpose of this is to avoid having to calculate time costly measures etc on all nodes or edges of a graph if only a few is needed. E.g. you might only be interested in the shortest distance from one node to another so rather than calculating this for all nodes you apply a focus on one node and perform the calculation. It should be made clear that not all algorithms will see a performance boost by being applied to a few nodes/edges since their calculation is applied globally and the result for all nodes/edges are provided in unison. } \note{ focusing is the lowest prioritised operation on a graph. Applying a \code{\link[=morph]{morph()}} or a \code{\link[=group_by]{group_by()}} operation will unfocus the graph prior to performing the operation. The same is true for the inverse operations (\code{\link[=unmorph]{unmorph()}} and \code{\link[=ungroup]{ungroup()}}). Further, unfocusing will happen any time some graph altering operation is performed, such as the \code{arrange()} and \code{slice()} operations } tidygraph/man/type_games.Rd0000644000176200001440000000653214535605577015473 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_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_callaway]{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_last_cit]{igraph::sample_cit_types()}} and \code{\link[igraph:sample_last_cit]{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/graph-context.Rd0000644000176200001440000000176714537003437016113 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/context.R \docType{data} \name{graph-context} \alias{graph-context} \alias{.graph_context} \alias{.register_graph_context} \alias{.free_graph_context} \title{Register a graph context for the duration of the current frame} \format{ An object of class \code{ContextBuilder} (inherits from \code{R6}) of length 12. } \usage{ .graph_context .register_graph_context(graph, free = FALSE, env = parent.frame()) .free_graph_context(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{datasets} \keyword{internal} tidygraph/man/centrality.Rd0000644000176200001440000002354514556122323015501 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_harmonic} \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 = arpack_defaults()) centrality_betweenness( weights = NULL, directed = TRUE, cutoff = -1, 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 = arpack_defaults() ) centrality_hub(weights = NULL, scale = TRUE, options = 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_harmonic( weights = NULL, mode = "out", normalized = FALSE, 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{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. These measures all only work on undirected graphs. } \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:hub_score]{igraph::authority_score()}} \item \code{centrality_betweenness()}: Wrapper for \code{\link[igraph:betweenness]{igraph::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()}} \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:betweenness]{igraph::edge_betweenness()}} \item \code{centrality_harmonic()}: Wrapper for \code{\link[igraph:harmonic_centrality]{igraph::harmonic_centrality()}} \item \code{centrality_manual()}: Manually specify your centrality score using the \code{netrankr} framework (\code{netrankr}) \item \code{centrality_closeness_harmonic()}: \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 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.Rd0000644000176200001440000001336214535605577015277 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{Functions}{ \itemize{ \item \code{as_tbl_graph(data.frame)}: Method for edge table and set membership table \item \code{as_tbl_graph(Node)}: Method to deal with Node objects from the data.tree package \item \code{as_tbl_graph(dendrogram)}: Method for dendrogram objects \item \code{as_tbl_graph(graphNEL)}: Method for handling graphNEL objects from the graph package (on Bioconductor) \item \code{as_tbl_graph(graphAM)}: Method for handling graphAM objects from the graph package (on Bioconductor) \item \code{as_tbl_graph(graphBAM)}: Method for handling graphBAM objects from the graph package (on Bioconductor) \item \code{as_tbl_graph(hclust)}: Method for hclust objects \item \code{as_tbl_graph(igraph)}: Method for igraph object. Simply subclasses the object into a \code{tbl_graph} \item \code{as_tbl_graph(list)}: Method for adjacency lists and lists of node and edge tables \item \code{as_tbl_graph(matrix)}: Method for edgelist, adjacency and incidence matrices \item \code{as_tbl_graph(network)}: Method to handle network objects from the \code{network} package. Requires this packages to work. \item \code{as_tbl_graph(phylo)}: Method for handling phylo objects from the ape package \item \code{as_tbl_graph(evonet)}: Method for handling evonet objects from the ape package \item \code{as_tbl_graph(default)}: 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/0000755000176200001440000000000014535605633014476 5ustar liggesuserstidygraph/man/figures/lifecycle-defunct.svg0000644000176200001440000000242414535605577020615 0ustar liggesusers lifecycle: defunct lifecycle defunct tidygraph/man/figures/lifecycle-maturing.svg0000644000176200001440000000243014535605577021010 0ustar liggesusers lifecycle: maturing lifecycle maturing tidygraph/man/figures/logo.png0000644000176200001440000006177514535605577016173 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/figures/lifecycle-archived.svg0000644000176200001440000000243014535605577020747 0ustar liggesusers lifecycle: archived lifecycle archived tidygraph/man/figures/lifecycle-soft-deprecated.svg0000644000176200001440000000246614535605577022244 0ustar liggesusers lifecycle: soft-deprecated lifecycle soft-deprecated tidygraph/man/figures/lifecycle-questioning.svg0000644000176200001440000000244414535605577021534 0ustar liggesusers lifecycle: questioning lifecycle questioning tidygraph/man/figures/lifecycle-superseded.svg0000644000176200001440000000244014535605577021326 0ustar liggesusers lifecycle: superseded lifecycle superseded tidygraph/man/figures/lifecycle-stable.svg0000644000176200001440000000247214535605577020442 0ustar liggesusers lifecycle: stable lifecycle stable tidygraph/man/figures/lifecycle-experimental.svg0000644000176200001440000000245014535605577021661 0ustar liggesusers lifecycle: experimental lifecycle experimental tidygraph/man/figures/lifecycle-deprecated.svg0000644000176200001440000000244014535605577021263 0ustar liggesusers lifecycle: deprecated lifecycle deprecated tidygraph/man/reexports.Rd0000644000176200001440000000625514535605577015373 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, R/tidyr-utils.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{slice_head} \alias{slice_tail} \alias{slice_min} \alias{slice_max} \alias{slice_sample} \alias{tbl_vars} \alias{as_tibble} \alias{\%>\%} \alias{replace_na} \alias{drop_na} \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:filter-joins]{anti_join}}, \code{\link[dplyr]{arrange}}, \code{\link[dplyr:reexports]{contains}}, \code{\link[dplyr]{distinct}}, \code{\link[dplyr:reexports]{ends_with}}, \code{\link[dplyr:reexports]{everything}}, \code{\link[dplyr]{filter}}, \code{\link[dplyr:mutate-joins]{full_join}}, \code{\link[dplyr]{group_by}}, \code{\link[dplyr]{group_data}}, \code{\link[dplyr:group_data]{group_indices}}, \code{\link[dplyr:group_data]{group_keys}}, \code{\link[dplyr:group_data]{group_size}}, \code{\link[dplyr:group_data]{group_vars}}, \code{\link[dplyr:group_data]{groups}}, \code{\link[dplyr:mutate-joins]{inner_join}}, \code{\link[dplyr:mutate-joins]{left_join}}, \code{\link[dplyr:reexports]{matches}}, \code{\link[dplyr]{mutate}}, \code{\link[dplyr]{mutate_all}}, \code{\link[dplyr:mutate_all]{mutate_at}}, \code{\link[dplyr:context]{n}}, \code{\link[dplyr:group_data]{n_groups}}, \code{\link[dplyr:reexports]{num_range}}, \code{\link[dplyr:reexports]{one_of}}, \code{\link[dplyr]{pull}}, \code{\link[dplyr]{rename}}, \code{\link[dplyr:mutate-joins]{right_join}}, \code{\link[dplyr:sample_n]{sample_frac}}, \code{\link[dplyr]{sample_n}}, \code{\link[dplyr]{select}}, \code{\link[dplyr:filter-joins]{semi_join}}, \code{\link[dplyr]{slice}}, \code{\link[dplyr:slice]{slice_head}}, \code{\link[dplyr:slice]{slice_max}}, \code{\link[dplyr:slice]{slice_min}}, \code{\link[dplyr:slice]{slice_sample}}, \code{\link[dplyr:slice]{slice_tail}}, \code{\link[dplyr:reexports]{starts_with}}, \code{\link[dplyr]{tbl_vars}}, \code{\link[dplyr]{top_n}}, \code{\link[dplyr]{transmute}}, \code{\link[dplyr:group_by]{ungroup}}} \item{igraph}{\code{\link[igraph]{as.igraph}}} \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}} \item{tibble}{\code{\link[tibble]{as_tibble}}} \item{tidyr}{\code{\link[tidyr]{drop_na}}, \code{\link[tidyr]{replace_na}}} }} tidygraph/man/create_graphs.Rd0000644000176200001440000000647514535605577016153 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.Rd0000644000176200001440000000221314535605577016207 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.Rd0000644000176200001440000000551514535605577016167 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_efficiency} \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_efficiency(weights = NULL, directed = TRUE, mode = "all") node_bridging_score() node_effective_network_size() node_connectivity_impact() node_closeness_impact() node_fareness_impact() } \arguments{ \item{mode}{How edges are treated. In \code{node_coreness()} it chooses which kind of coreness measure to calculate. In \code{node_efficiency()} it defines how the local neighborhood is created} \item{weights}{The weights to use for each node during calculation} \item{directed}{Should the graph be treated as a directed graph if it is in fact directed} } \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_efficiency()}: measures the local efficiency around each node. See \code{\link[igraph:global_efficiency]{igraph::local_efficiency()}} \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.Rd0000644000176200001440000000252514535605577017065 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(focused = TRUE) .E(focused = TRUE) } \arguments{ \item{focused}{Should only the attributes of the currently focused nodes or edges be returned} } \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.Rd0000644000176200001440000000666614556122324014740 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}()}, \code{\link{map_dfs_back}()} } \concept{node map functions} tidygraph/man/component_games.Rd0000644000176200001440000000471214535605577016512 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.Rd0000644000176200001440000000153514535605577015470 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_gnp(10, 0.3) with_graph(gr, centrality_degree()) } tidygraph/man/node_types.Rd0000644000176200001440000000606114535605577015504 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} \alias{node_is_connected} \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) node_is_connected(nodes, mode = "all", any = FALSE) } \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} \item{nodes}{The set of nodes to test connectivity to. Can be a list to use different sets for different nodes. If a list it will be recycled as necessary.} \item{any}{Logical. If \code{TRUE} the node only needs to be connected to a single node in the set for it to return \code{TRUE}} } \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}) \item \code{node_is_connected()}: Is a node connected to all (or any) nodes in a set }} \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.Rd0000644000176200001440000000503014535605577015653 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} \alias{graph_is_eulerian} \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", ...) graph_is_eulerian(cyclic = FALSE) } \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:isomorphic]{igraph::is_isomorphic_to()}} and \code{\link[igraph:subgraph_isomorphic]{igraph::is_subgraph_isomorphic_to()}}} \item{cyclic}{should the eulerian path start and end at the same node} } \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:isomorphic]{igraph::is_isomorphic_to()}} \item \code{graph_is_subgraph_isomorphic_to()}: Is the graph an isomorphic subgraph to another graph. see \code{\link[igraph:subgraph_isomorphic]{igraph::is_subgraph_isomorphic_to()}} \item \code{graph_is_eulerian()}: Can all the edges in the graph be reaches by a single path or cycle that only goes through each edge once }} \examples{ gr <- create_tree(50, 4) with_graph(gr, graph_is_tree()) } tidygraph/man/random_walk_rank.Rd0000644000176200001440000000330714535605577016644 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/random_walk.R \name{random_walk_rank} \alias{random_walk_rank} \title{Perform a random walk on the graph and return encounter rank} \usage{ random_walk_rank(n, root = NULL, mode = "out", weights = NULL) } \arguments{ \item{n}{The number of steps to perform. If the walk gets stuck before reaching this number the walk is terminated} \item{root}{The node to start the walk at. If \code{NULL} a random node will be used} \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{weights}{The weights to use for edges when selecting the next step of the walk. Currently only used when edges are active} } \value{ A list with an integer vector for each node or edge (depending on what is active) each element encode the time the node/edge is encountered along the walk } \description{ A random walk is a traversal of the graph starting from a node and going a number of steps by picking an edge at random (potentially weighted). \code{random_walk()} can be called both when nodes and edges are active and will adapt to return a value fitting to the currently active part. As the walk order cannot be directly encoded in the graph the return value is a list giving a vector of positions along the walk of each node or edge. } \examples{ graph <- create_notable("zachary") # Random walk returning node order graph |> mutate(walk_rank = random_walk_rank(200)) # Rank edges instead graph |> activate(edges) |> mutate(walk_rank = random_walk_rank(200)) } tidygraph/man/map_local.Rd0000644000176200001440000000564414535605577015270 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.Rd0000644000176200001440000000440014535605577015456 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} \alias{edge_is_bridge} \alias{edge_is_feedback_arc} \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(nodes) edge_is_bridge() edge_is_feedback_arc(weights = NULL, approximate = TRUE) } \arguments{ \item{from, to, nodes}{A vector giving node indices} \item{ignore_dir}{Is both directions of the edge allowed} \item{weights}{The weight of the edges to use for the calculation. Will be evaluated in the context of the edge data.} \item{approximate}{Should the minimal set be approximated or exact} } \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 \item \code{edge_is_bridge()}: Query whether an edge is a bridge (ie. it's removal will increase the number of components in a graph) \item \code{edge_is_feedback_arc()}: Query whether an edge is part of the minimal feedback arc set (its removal together with the rest will break all cycles in the graph) }} \examples{ create_star(10, directed = TRUE, mutual = TRUE) \%>\% activate(edges) \%>\% sample_frac(0.7) \%>\% mutate(single_edge = !edge_is_mutual()) } tidygraph/man/graph_measures.Rd0000644000176200001440000001673514535605577016351 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} \alias{graph_efficiency} \title{Graph measurements} \usage{ graph_adhesion() graph_assortativity(attr, in_attr = NULL, directed = TRUE) graph_automorphisms(sh = "fm", colors = NULL) 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, weights = NULL) graph_modularity(group, weights = NULL) graph_efficiency(weights = NULL, directed = TRUE) } \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{colors}{The colors of the individual vertices of the graph; only vertices having the same color are allowed to match each other in an automorphism. When omitted, igraph uses the \code{color} attribute of the vertices, or, if there is no such vertex attribute, it simply assumes that all vertices have the same color. Pass NULL explicitly if the graph has a \code{color} vertex attribute but you do not want to use it.} \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.} \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:count_automorphisms]{igraph::count_automorphisms()}} \item \code{graph_clique_num()}: Get the size of the largest clique. Wraps \code{\link[igraph:cliques]{igraph::clique_num()}} \item \code{graph_clique_count()}: Get the number of maximal cliques in the graph. Wraps \code{\link[igraph:cliques]{igraph::count_max_cliques()}} \item \code{graph_component_count()}: Count the number of unconnected componenets in the graph. Wraps \code{\link[igraph: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:distances]{igraph::mean_distance()}} \item \code{graph_modularity()}: Calculates the modularity of the graph contingent on a provided node grouping \item \code{graph_efficiency()}: Calculate the global efficiency of the graph }} \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/fortify.tbl_graph.Rd0000644000176200001440000000116314535605577016754 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.Rd0000644000176200001440000000710414535605577016320 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_gnm} \alias{play_gnp} \alias{play_geometry} \alias{play_erdos_renyi} \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_gnm(n, m, directed = TRUE, loops = FALSE) play_gnp(n, p, directed = TRUE, loops = FALSE) play_geometry(n, radius, torus = FALSE) play_erdos_renyi(n, p, m, directed = TRUE, loops = 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_gnm()}: Create graphs with a fixed edge count. See \code{\link[igraph:sample_gnm]{igraph::sample_gnm()}} \item \code{play_gnp()}: Create graphs with a fixed edge probability. See \code{\link[igraph:sample_gnp]{igraph::sample_gnp()}} \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()}} \item \code{play_erdos_renyi()}: \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Create graphs 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()}} }} \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.Rd0000644000176200001440000000562214535605577015610 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}{An edge weight vector. For \code{local_ave_degree}: If this argument is given, the average vertex strength is calculated instead of vertex degree. For \code{local_transitivity}: if given weighted transitivity using the approach by \emph{A. Barrat} will be calculated.} } \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]{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.Rd0000644000176200001440000001405714556122324015637 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_leiden} \alias{group_optimal} \alias{group_spinglass} \alias{group_walktrap} \alias{group_fluid} \alias{group_biconnected_component} \alias{group_color} \title{Group nodes and edges based on community structure} \usage{ group_components(type = "weak") group_edge_betweenness(weights = NULL, directed = TRUE, n_groups = NULL) group_fast_greedy(weights = NULL, n_groups = 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 = arpack_defaults(), n_groups = NULL ) group_louvain(weights = NULL, resolution = 1) group_leiden( weights = NULL, resolution = 1, objective_function = "CPM", beta = 0.01, label = NULL, n = 2, node_weights = NULL ) group_optimal(weights = NULL) group_spinglass(weights = NULL, ...) group_walktrap(weights = NULL, steps = 4, n_groups = NULL) group_fluid(n_groups = 2) group_biconnected_component() group_color() } \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{n_groups}{Integer scalar, the desired number of communities. If too low or two high, then an error message is given. The measure is applied to the full graph so the number of groups returned may be lower for focused graphs} \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{resolution}{Resolution of the modularity function used internally in the algorithm} \item{objective_function}{Either \code{"CPM"} (constant potts model) or \code{"modularity"}. Sets the objective function to use.} \item{beta}{Parameter affecting the randomness in the Leiden algorithm. This affects only the refinement step of the algorithm.} \item{n}{The number of iterations to run the clustering} \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_leiden()}: Group nodes according to the Leiden algorithm (\code{\link[igraph:cluster_leiden]{igraph::cluster_leiden()}}) which is similar, but more efficient and provides higher quality results than \code{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_fluid()}: Group nodes by simulating fluid interactions on the graph topology using \code{\link[igraph:cluster_fluid_communities]{igraph::cluster_fluid_communities()}} \item \code{group_biconnected_component()}: Group edges by their membership of the maximal binconnected components using \code{\link[igraph:biconnected_components]{igraph::biconnected_components()}} \item \code{group_color()}: Groups nodes by their color using \code{\link[igraph:greedy_vertex_coloring]{igraph::greedy_vertex_coloring()}}. Be aware that this is not a clustering algorithm as coloring specifically provide a color to each node so that no neighbors have the same color }} \examples{ create_notable('tutte') \%>\% activate(nodes) \%>\% mutate(group = group_infomap()) } tidygraph/man/bind_graphs.Rd0000644000176200001440000000402514535605577015611 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/iterate.Rd0000644000176200001440000000312714535605577014770 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/iterate.R \name{iterate} \alias{iterate} \alias{iterate_n} \alias{iterate_while} \title{Repeatedly modify a graph by a function} \usage{ iterate_n(.data, n, .f, ...) iterate_while(.data, cnd, .f, ..., max_n = NULL) } \arguments{ \item{.data}{A \code{tbl_graph} object} \item{n}{The number of times to iterate} \item{.f}{A function taking in a \code{tbl_graph} as the first argument and returning a \code{tbl_graph} object} \item{...}{Further arguments passed on to \code{.f}} \item{cnd}{A condition that must evaluate to \code{TRUE} or \code{FALSE} determining if iteration should continue} \item{max_n}{The maximum number of iterations in case \code{cnd} never evaluates to \code{FALSE}} } \value{ A \code{tbl_graph} object } \description{ The iterate family of functions allow you to call the same modification function on a graph until some condition is met. This can be used to create simple simulations in a tidygraph friendly API } \examples{ # Gradually remove edges from the least connected nodes while avoiding # isolates create_notable('zachary') |> iterate_n(20, function(gr) { gr |> activate(nodes) |> mutate(deg = centrality_degree(), rank = order(deg)) |> activate(edges) |> slice( -which(edge_is_incident(.N()$rank == sum(.N()$deg == 1) + 1))[1] ) }) # Remove a random edge until the graph is split in two create_notable('zachary') |> iterate_while(graph_component_count() == 1, function(gr) { gr |> activate(edges) |> slice(-sample(graph_size(), 1)) }) } tidygraph/man/pair_measures.Rd0000644000176200001440000001033014535605577016164 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:cocitation]{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.Rd0000644000176200001440000002021014535605577015263 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/DESCRIPTION0000644000176200001440000000253014556176062013767 0ustar liggesusersType: Package Package: tidygraph Title: A Tidy API for Graph Manipulation Version: 1.3.1 Authors@R: person("Thomas Lin", "Pedersen", , "thomasp85@gmail.com", role = c("cre", "aut"), 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 URL: https://tidygraph.data-imaginist.com, https://github.com/thomasp85/tidygraph BugReports: https://github.com/thomasp85/tidygraph/issues Imports: cli, dplyr (>= 0.8.5), igraph (>= 2.0.0), lifecycle, magrittr, pillar, R6, rlang, stats, tibble, tidyr, tools, utils Suggests: ape, covr, data.tree, graph, influenceR, methods, netrankr, NetSwan, network, seriation, testthat (>= 3.0.0) LinkingTo: cpp11 Encoding: UTF-8 RoxygenNote: 7.3.1 Config/testthat/edition: 3 NeedsCompilation: yes Packaged: 2024-01-30 12:17:51 UTC; thomas Author: Thomas Lin Pedersen [cre, aut] () Repository: CRAN Date/Publication: 2024-01-30 13:40:02 UTC tidygraph/tests/0000755000176200001440000000000014556125226013417 5ustar liggesuserstidygraph/tests/testthat/0000755000176200001440000000000014556176062015263 5ustar liggesuserstidygraph/tests/testthat/test-map.R0000644000176200001440000000006014535605577017141 0ustar liggesuserstest_that("TODO", { expect_equal(2 * 2, 4) }) tidygraph/tests/testthat/test-arrange.R0000644000176200001440000000134714535605577020014 0ustar liggesuserstest_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)) }) test_empty_context() tidygraph/tests/testthat/test-search.R0000644000176200001440000000431414535605577017637 0ustar liggesusersget_val <- function(gr, fn) { gr %>% mutate(val = fn) %>% pull(val) } test_that("search returns correct type", { gr <- create_tree(10, 2) expect_type(get_val(gr, bfs_after()), 'integer') expect_type(get_val(gr, bfs_before()), 'integer') expect_type(get_val(gr, bfs_dist()), 'integer') expect_type(get_val(gr, bfs_parent()), 'integer') expect_type(get_val(gr, bfs_rank()), 'integer') expect_type(get_val(gr, dfs_dist()), 'integer') expect_type(get_val(gr, dfs_parent()), 'integer') expect_type(get_val(gr, dfs_rank()), 'integer') expect_type(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 returns correct length", { gr <- create_tree(10, 2) |> focus(dplyr::row_number() < 3) expect_length(get_val(gr, bfs_after()), 2) expect_length(get_val(gr, bfs_before()), 2) expect_length(get_val(gr, bfs_dist()), 2) expect_length(get_val(gr, bfs_parent()), 2) expect_length(get_val(gr, bfs_rank()), 2) expect_length(get_val(gr, dfs_dist()), 2) expect_length(get_val(gr, dfs_parent()), 2) expect_length(get_val(gr, dfs_rank()), 2) expect_length(get_val(gr, dfs_rank_out()), 2) }) 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())) }) test_empty_context() tidygraph/tests/testthat/helper-context.R0000644000176200001440000000024414535605577020354 0ustar liggesuserstest_empty_context <- function() { test_that("graph context is empty after test", { expect_length(environment(.graph_context$free)$private$context, 0) }) } tidygraph/tests/testthat/test-graph_attributes.R0000644000176200001440000000057714535605577021750 0ustar liggesuserstest_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)) }) test_empty_context() tidygraph/tests/testthat/test-group.R0000644000176200001440000000734714535605577017537 0ustar liggesusersget_group <- function(gr, fn) { gr %>% mutate(group = fn) %>% pull(group) } get_number_of_groups <- function(graph, group_clustering_function) { graph %>% mutate(groups = group_clustering_function) %>% dplyr::as_tibble(what = 'vertices') %>% distinct() %>% dplyr::count() %>% dplyr::pull(1) } test_that("grouping returns integer vector", { gr <- create_notable('zachary') expect_type(get_group(gr, group_components()), 'integer') expect_type(get_group(gr, group_edge_betweenness()), 'integer') expect_type(get_group(gr, group_fast_greedy()), 'integer') expect_type(get_group(gr, group_infomap()), 'integer') expect_type(get_group(gr, group_label_prop()), 'integer') expect_type(get_group(gr, group_louvain()), 'integer') expect_type(get_group(gr, group_leiden()), 'integer') #expect_type(get_group(gr, group_optimal()), 'integer') expect_type(get_group(gr, group_spinglass()), 'integer') expect_type(get_group(gr, group_walktrap()), 'integer') expect_type(get_group(gr, group_fluid()), 'integer') expect_type(get_group(gr, group_color()), 'integer') gr1 <- activate(gr, edges) expect_type(get_group(gr1, group_biconnected_component()), 'integer') skip_on_os('windows') expect_type(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_leiden()), 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)) expect_length(get_group(gr, group_fluid()), igraph::gorder(gr)) expect_length(get_group(gr, group_color()), 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_leiden())) #expect_error(get_group(gr1, group_optimal())) expect_error(get_group(gr1, group_spinglass())) expect_error(get_group(gr1, group_walktrap())) expect_error(get_group(gr1, group_fluid())) expect_error(get_group(gr1, group_color())) skip_on_os('windows') expect_error(get_group(gr1, group_leading_eigen())) }) test_that("grouping with fixed number of groups", { gr <- create_notable('zachary') expect_equal( get_number_of_groups(gr, group_edge_betweenness(n_groups = 4)), 4 ) expect_equal( get_number_of_groups(gr, group_fast_greedy(n_groups = 4)), 4 ) expect_equal( get_number_of_groups(gr, group_leading_eigen(n_groups = 32)), 32 ) expect_equal( get_number_of_groups(gr, group_walktrap(n_groups = 7)), 7 ) expect_equal( get_number_of_groups(gr, group_fluid(n_groups = 7)), 7 ) }) test_empty_context() tidygraph/tests/testthat/test-pair_measures.R0000644000176200001440000000552414535605577021235 0ustar liggesusersget_val <- function(gr, fn) { gr %>% mutate(val = fn) %>% pull(val) } test_that("pair measures return correct type", { gr <- create_ring(5, directed = TRUE) expect_type(get_val(gr, node_adhesion_from(1)), 'double') expect_type(get_val(gr, node_adhesion_to(1)), 'double') expect_type(get_val(gr, node_cocitation_with(1)), 'double') expect_type(get_val(gr, node_cohesion_from(1)), 'double') expect_type(get_val(gr, node_cohesion_to(1)), 'double') expect_type(get_val(gr, node_distance_from(1)), 'double') expect_type(get_val(gr, node_distance_to(1)), 'double') expect_type(get_val(gr, node_max_flow_from(1)), 'double') expect_type(get_val(gr, node_max_flow_to(1)), 'double') expect_type(get_val(gr, node_similarity_with(1)), 'double') }) 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 return correct length for focus", { gr <- create_ring(5, directed = TRUE) |> focus(dplyr::row_number() < 3) expect_length(get_val(gr, node_adhesion_from(1)), 2) expect_length(get_val(gr, node_adhesion_to(1)), 2) expect_length(get_val(gr, node_cocitation_with(1)), 2) expect_length(get_val(gr, node_cohesion_from(1)), 2) expect_length(get_val(gr, node_cohesion_to(1)), 2) expect_length(get_val(gr, node_distance_from(1)), 2) expect_length(get_val(gr, node_distance_to(1)), 2) expect_length(get_val(gr, node_max_flow_from(1)), 2) expect_length(get_val(gr, node_max_flow_to(1)), 2) expect_length(get_val(gr, node_similarity_with(1)), 2) }) 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))) }) test_empty_context() tidygraph/tests/testthat/test-bind.R0000644000176200001440000000203214535605577017301 0ustar liggesuserstest_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_equal(as_tibble(gr1), tbl, ignore_attr = TRUE) }) 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)) }) test_empty_context() tidygraph/tests/testthat/test-node_measures.R0000644000176200001440000000600714535605577021224 0ustar liggesusersget_val <- function(gr, fn) { gr %>% mutate(val = fn) %>% pull(val) } test_that("Node measures return corrent type", { tree <- create_tree(10, 2) %>% activate(edges) %>% mutate(w = seq_len(n())) %>% activate(nodes) gr <- create_notable('meredith') %>% activate(edges) %>% mutate(w = seq_len(n())) %>% activate(nodes) gr_u <- convert(gr, to_undirected) expect_type(get_val(gr, node_constraint()), 'double') expect_type(get_val(gr, node_coreness()), 'double') expect_type(get_val(gr_u, node_diversity(w)), 'double') expect_type(get_val(gr_u, node_efficiency()), 'double') expect_type(get_val(tree, node_dominator(node_is_root())), 'double') expect_type(get_val(gr, node_eccentricity()), 'double') expect_type(get_val(tree, node_topo_order()), 'integer') }) test_that("Node measures return correct length", { tree <- create_tree(10, 2) %>% activate(edges) %>% mutate(w = seq_len(n())) %>% activate(nodes) gr <- create_notable('meredith') %>% activate(edges) %>% mutate(w = seq_len(n())) %>% activate(nodes) gr_u <- convert(gr, to_undirected) 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_u, node_diversity(w)), igraph::gorder(gr)) expect_length(get_val(gr_u, node_efficiency()), igraph::gorder(gr)) expect_length(get_val(tree, node_dominator(node_is_root())), igraph::gorder(tree)) expect_length(get_val(gr, node_eccentricity()), igraph::gorder(gr)) expect_length(get_val(tree, node_topo_order()), igraph::gorder(tree)) }) test_that("Node measures return correct length for focus", { tree <- create_tree(10, 2) %>% activate(edges) %>% mutate(w = seq_len(n())) %>% activate(nodes) |> focus(dplyr::row_number() < 3) gr <- create_notable('meredith') %>% activate(edges) %>% mutate(w = seq_len(n())) %>% activate(nodes) |> focus(dplyr::row_number() < 3) gr_u <- convert(gr, to_undirected) |> focus(dplyr::row_number() < 3) expect_length(get_val(gr, node_constraint()), 2) expect_length(get_val(gr, node_coreness()), 2) expect_length(get_val(gr_u, node_diversity(w)), 2) expect_length(get_val(gr_u, node_efficiency()), 2) expect_length(get_val(tree, node_dominator(node_is_root())), 2) expect_length(get_val(gr, node_eccentricity()), 2) expect_length(get_val(tree, node_topo_order()), 2) }) test_that("Node measures requires active nodes", { tree <- create_tree(10, 2) %>% activate(edges) %>% mutate(w = seq_len(n())) gr <- create_notable('meredith') %>% activate(edges) %>% mutate(w = seq_len(n())) gr_u <- convert(gr, to_undirected) expect_error(get_val(gr, node_constraint())) expect_error(get_val(gr, node_coreness())) expect_error(get_val(gr_u, node_diversity(w))) expect_error(get_val(gr_u, node_efficiency())) expect_error(get_val(tree, node_dominator(node_is_root()))) expect_error(get_val(gr, node_eccentricity())) expect_error(get_val(tree, node_topo_order())) }) test_empty_context() tidygraph/tests/testthat/test-tidyr-utils.R0000644000176200001440000000103014535605577020653 0ustar liggesusersgr <- create_notable('house') %>% mutate(val = c(1:3, NA, NA)) %>% activate(edges) %>% mutate(val = c(1:3, NA, NA, NA)) %>% activate(nodes) test_that("tidyr utils work on nodes", { expect_equal(drop_na(gr) %>% pull(val), 1:3) expect_equal(replace_na(gr, list(val = 0)) %>% pull(val), c(1:3, 0, 0)) }) test_that("tidyr utils work on edges", { gr <- gr %>% activate(edges) expect_equal(drop_na(gr) %>% pull(val), 1:3) expect_equal(replace_na(gr, list(val = 0)) %>% pull(val), c(1:3, 0, 0, 0)) }) test_empty_context() tidygraph/tests/testthat/test-iterate.R0000644000176200001440000000166214535605577020032 0ustar liggesuserstest_that("iterate_n works as expected", { gr <- create_notable('zachary') |> activate(edges) |> mutate(count = 0) |> activate(nodes) |> iterate_n(10, ~mutate(activate(., edges), count = count + 1)) expect_equal(active(gr), 'nodes') expect_true(all(pull(activate(gr, edges), count) == 10)) }) test_that("iterate_while works as expected", { gr <- create_notable('zachary') |> activate(edges) |> mutate(count = 0) |> activate(nodes) |> iterate_while(.E()$count[1] < 10, ~mutate(activate(., edges), count = count + 1)) expect_equal(active(gr), 'nodes') expect_true(all(pull(activate(gr, edges), count) == 10)) gr <- create_notable('zachary') |> activate(edges) |> mutate(count = 0) |> activate(nodes) |> iterate_while(TRUE, ~mutate(activate(., edges), count = count + 1), max_n = 10) expect_equal(active(gr), 'nodes') expect_true(all(pull(activate(gr, edges), count) == 10)) }) tidygraph/tests/testthat/test-filter.R0000644000176200001440000000054714535605577017663 0ustar liggesuserstest_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) }) test_empty_context() tidygraph/tests/testthat/test-group_by.R0000644000176200001440000000073014535605577020216 0ustar liggesuserstest_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'))) }) test_empty_context() tidygraph/tests/testthat/test-slice.R0000644000176200001440000000207714535605577017475 0ustar liggesusersn_val <- sample(70) e_val <- sample(140) gr <- create_notable("meredith") %>% mutate(id = seq_len(70), val = n_val) %>% activate(edges) %>% mutate(id = seq_len(140), val = e_val) %>% activate(nodes) test_that("slicing nodes works", { expect_equal(slice(gr, 1:4) %>% pull(val), n_val[1:4]) expect_equal(slice_head(gr, n = 6) %>% pull(val), n_val[1:6]) expect_equal(slice_tail(gr, n = 6) %>% pull(val), rev(rev(n_val)[1:6])) expect_equal(slice_min(gr, val, n = 6) %>% pull(id), sort(order(n_val)[1:6])) expect_equal(slice_max(gr, val, n = 6) %>% pull(id), sort(order(n_val, decreasing = TRUE)[1:6])) }) test_that("slicing edges works", { gr <- gr %>% activate(edges) expect_equal(slice(gr, 1:4) %>% pull(val), e_val[1:4]) expect_equal(slice_head(gr, n = 6) %>% pull(val), e_val[1:6]) expect_equal(slice_tail(gr, n = 6) %>% pull(val), rev(rev(e_val)[1:6])) expect_equal(slice_min(gr, val, n = 6) %>% pull(id), sort(order(e_val)[1:6])) expect_equal(slice_max(gr, val, n = 6) %>% pull(id), sort(order(e_val, decreasing = TRUE)[1:6])) }) test_empty_context() tidygraph/tests/testthat/test-morph.R0000644000176200001440000001042714535605577017521 0ustar liggesuserstest_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_largest_component works', { gr <- create_notable('bull') %>% bind_graphs(create_notable('diamond')) %>% morph(to_largest_component) %>% mutate(size = graph_order()) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, size), c(5, 5, 5, 5, 5, NA, NA, NA, NA)) expect_equal(nrow(gr2), 1) }) 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_random_spanning_tree works', { set.seed(1) gr <- create_notable('bull') %>% morph(to_random_spanning_tree) %>% mutate(order = bfs_rank(3)) gr1 <- unmorph(gr) %>% activate(nodes) gr2 <- crystallise(gr) expect_equal(pull(gr1, order), c(4, 2, 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) }) test_empty_context() tidygraph/tests/testthat/test-random-walk.R0000644000176200001440000000117614556125221020573 0ustar liggesuserstest_that("random_walk_rank returns correct data", { set.seed(1) node_walk <- create_notable('zachary') |> mutate(walk_rank = random_walk_rank(29, 5)) |> pull(walk_rank) edge_walk <- create_notable('zachary') |> activate(edges) |> mutate(walk_rank = random_walk_rank(30, 5)) |> pull(walk_rank) expect_length(node_walk, 34) expect_length(edge_walk, 78) expect_type(node_walk, 'list') expect_type(edge_walk, 'list') expect_equal(node_walk[[5]], c(1, 5, 7)) expect_equal(node_walk[[3]], integer()) expect_equal(edge_walk[[1]], integer()) skip_on_cran() expect_equal(edge_walk[[4]], c(1, 18)) }) tidygraph/tests/testthat/test-edge_types.R0000644000176200001440000000403414535605577020521 0ustar liggesusersget_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_type(get_type(gr, edge_is_loop()), 'logical') expect_type(get_type(gr, edge_is_multiple()), 'logical') expect_type(get_type(gr, edge_is_mutual()), 'logical') expect_type(get_type(gr, edge_is_bridge()), 'logical') expect_type(get_type(gr, edge_is_feedback_arc()), '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)) expect_length(get_type(gr, edge_is_bridge()), igraph::gsize(gr)) expect_length(get_type(gr, edge_is_feedback_arc()), igraph::gsize(gr)) }) test_that("edge types return correct length for focus", { gr <- create_notable('bull') %>% activate(edges) %>% bind_edges(tibble::tibble( to = c(1, 1, 3, 4), from = c(1, 3, 1, 2) )) |> focus(dplyr::row_number() < 3) expect_length(get_type(gr, edge_is_loop()), 2) expect_length(get_type(gr, edge_is_multiple()), 2) expect_length(get_type(gr, edge_is_mutual()), 2) expect_length(get_type(gr, edge_is_bridge()), 2) expect_length(get_type(gr, edge_is_feedback_arc()), 2) }) 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())) expect_error(get_type(gr, edge_is_bridge())) expect_error(get_type(gr, edge_is_feedback_arc())) }) test_empty_context() tidygraph/tests/testthat/test-join.R0000644000176200001440000000006014535605577017323 0ustar liggesuserstest_that("TODO", { expect_equal(2 * 2, 4) }) tidygraph/tests/testthat/test-focus.R0000644000176200001440000000523514535605577017514 0ustar liggesuserstest_that("focusing and unfocusing behaves", { gr <- create_notable('meredith') expect_false(is.focused_tbl_graph(focus(gr, TRUE))) expect_false(is.focused_tbl_graph(focus(gr, FALSE))) gr_focus <- focus(gr, dplyr::row_number() %in% 6:10) expect_s3_class(gr_focus, 'focused_tbl_graph') expect_equal(focus_ind(gr_focus), 6:10) expect_equal(focus_ind(gr), seq_len(70)) expect_false(is.focused_tbl_graph(activate(gr_focus, edges))) expect_false(is.focused_tbl_graph(group_by(gr_focus, dplyr::row_number() %% 2 == 0))) expect_false(is.focused_tbl_graph(convert(gr_focus, to_complement))) expect_false(is.focused_tbl_graph(unfocus(gr_focus))) gr_morph_focus <- morph(gr, to_complement) |> focus(dplyr::row_number() == 1) expect_s3_class((crystallise(gr_morph_focus) |> pull(graph))[[1]], 'focused_tbl_graph') expect_false(is.focused_tbl_graph((crystallise(unfocus(gr_morph_focus)) |> pull(graph))[[1]])) expect_false(is.focused_tbl_graph(unmorph(gr_morph_focus))) gr_group_focus <- group_by(gr, dplyr::row_number() %% 2 == 0) |> focus(dplyr::row_number() %in% 1:6) expect_s3_class(gr_group_focus, 'focused_tbl_graph') expect_false(is.focused_tbl_graph(ungroup(gr_group_focus))) expect_false(is.focused_tbl_graph(arrange(gr_focus, rev(dplyr::row_number())))) expect_false(is.focused_tbl_graph(bind_nodes(gr_focus, mtcars))) expect_false(is.focused_tbl_graph(bind_edges(gr_focus, data.frame(from = 1:3, to = 4:6)))) expect_false(is.focused_tbl_graph(bind_graphs(gr_focus, create_notable('bull')))) expect_false(is.focused_tbl_graph(distinct(gr_focus, dplyr::row_number()))) expect_false(is.focused_tbl_graph(filter(gr_focus, dplyr::row_number() < 10))) expect_false(is.focused_tbl_graph(slice(gr_focus, 1:4))) join_tbl <- data.frame(from = 1:70, to = 70:1, info = as.character(1:70)) gr_focus <- activate(gr_focus, edges) |> focus(dplyr::row_number() %in% 6:10) expect_s3_class(left_join(gr_focus, join_tbl), 'focused_tbl_graph') expect_false(is.focused_tbl_graph(right_join(gr_focus, join_tbl))) expect_false(is.focused_tbl_graph(full_join(gr_focus, join_tbl))) expect_false(is.focused_tbl_graph(inner_join(gr_focus, join_tbl))) expect_false(is.focused_tbl_graph(semi_join(gr_focus, join_tbl))) expect_false(is.focused_tbl_graph(anti_join(gr_focus, join_tbl))) }) test_that("modifying a focused graph only affects the focus", { gr <- create_notable('meredith') |> mutate(col1 = 1) |> focus(dplyr::row_number() %in% 6:10) |> mutate(col1 = 2, col2 = 2) |> unfocus() expect_equal(pull(gr, col1), c(rep(1, 5), rep(2, 5), rep(1, 60))) expect_equal(pull(gr, col2), c(rep(NA, 5), rep(2, 5), rep(NA, 60))) }) test_empty_context() tidygraph/tests/testthat/test-graph_measures.R0000644000176200001440000000210414535605577021372 0ustar liggesuserstest_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) expect_length(graph_efficiency(), 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() }) test_empty_context() tidygraph/tests/testthat/test-centrality.R0000644000176200001440000000663714535605577020562 0ustar liggesusersget_cent <- function(gr, fn) { gr %>% mutate(cent = fn) %>% pull(cent) } test_that("centrality returns numeric", { gr1 <- create_notable('diamond') expect_type(get_cent(gr1, centrality_alpha()), 'double') expect_type(get_cent(gr1, centrality_betweenness()), 'double') expect_type(get_cent(gr1, centrality_closeness()), 'double') expect_type(get_cent(gr1, centrality_degree()), 'double') expect_type(get_cent(gr1, centrality_pagerank()), 'double') expect_type(get_cent(gr1, centrality_power()), 'double') expect_type(get_cent(gr1, centrality_subgraph()), 'double') expect_type(get_cent(gr1, centrality_harmonic()), 'double') gr2 <- activate(gr1, 'edges') expect_type(get_cent(gr2, centrality_edge_betweenness()), 'double') skip_on_os('windows') expect_type(get_cent(gr1, centrality_authority()), 'double') expect_type(get_cent(gr1, centrality_eigen()), 'double') expect_type(get_cent(gr1, centrality_hub()), 'double') }) 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) expect_length(get_cent(gr1, centrality_harmonic()), 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 returns correct length for focus", { gr1 <- create_notable('diamond') |> focus(dplyr::row_number() < 3) expect_length(get_cent(gr1, centrality_alpha()), 2) expect_length(get_cent(gr1, centrality_betweenness()), 2) expect_length(get_cent(gr1, centrality_closeness()), 2) expect_length(get_cent(gr1, centrality_degree()), 2) expect_length(get_cent(gr1, centrality_pagerank()), 2) expect_length(get_cent(gr1, centrality_power()), 2) expect_length(get_cent(gr1, centrality_subgraph()), 2) expect_length(get_cent(gr1, centrality_harmonic()), 2) gr2 <- activate(gr1, 'edges') |> focus(dplyr::row_number() < 3) expect_length(get_cent(gr2, centrality_edge_betweenness()), 2) skip_on_os('windows') expect_length(get_cent(gr1, centrality_authority()), 2) expect_length(get_cent(gr1, centrality_eigen()), 2) expect_length(get_cent(gr1, centrality_hub()), 2) }) 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())) expect_error(get_cent(gr2, centrality_harmonic())) 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())) }) test_empty_context() tidygraph/tests/testthat/test-distinct.R0000644000176200001440000000053714535605577020216 0ustar liggesuserstest_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) }) test_empty_context() tidygraph/tests/testthat/test-activate.R0000644000176200001440000000200214535605577020162 0ustar liggesuserstest_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')) }) test_empty_context() tidygraph/tests/testthat/test-mutate.R0000644000176200001440000000060014535605577017663 0ustar liggesuserstest_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]) }) test_empty_context() tidygraph/tests/testthat/test-context.R0000644000176200001440000000125114535605577020053 0ustar liggesuserstest_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()) }) test_empty_context() tidygraph/tests/testthat/test-local.R0000644000176200001440000000303314535605577017461 0ustar liggesusersget_loc <- function(gr, fn) { gr %>% mutate(loc = fn) %>% pull(loc) } test_that("local returns correct type", { gr <- create_notable('bull') expect_type(get_loc(gr, local_ave_degree()), 'double') expect_type(get_loc(gr, local_members()), 'list') expect_type(get_loc(gr, local_size()), 'double') expect_type(get_loc(gr, local_transitivity()), 'double') expect_type(get_loc(gr, local_triangles()), 'double') }) test_that("local returns correct length", { 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 returns correct length for focus", { gr <- create_notable('bull') |> focus(dplyr::row_number() < 3) expect_length(get_loc(gr, local_ave_degree()), 2) expect_length(get_loc(gr, local_members()), 2) expect_length(get_loc(gr, local_size()), 2) expect_length(get_loc(gr, local_transitivity()), 2) expect_length(get_loc(gr, local_triangles()), 2) }) 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())) }) test_empty_context() tidygraph/tests/testthat/test-node_types.R0000644000176200001440000000526314535605577020547 0ustar liggesusersget_type <- function(gr, fn) { gr %>% mutate(type = fn) %>% pull(type) } test_that("node types return logical", { gr <- create_tree(10, 2) expect_type(get_type(gr, node_is_center()), 'logical') expect_type(get_type(gr, node_is_cut()), 'logical') expect_type(get_type(gr, node_is_isolated()), 'logical') expect_type(get_type(gr, node_is_leaf()), 'logical') expect_type(get_type(gr, node_is_root()), 'logical') expect_type(get_type(gr, node_is_simplical()), 'logical') expect_type(get_type(gr, node_is_sink()), 'logical') expect_type(get_type(gr, node_is_source()), 'logical') expect_type(get_type(gr, node_is_universal()), 'logical') expect_type(get_type(gr, node_is_connected(1:4)), '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)) expect_length(get_type(gr, node_is_connected(1:4)), igraph::gorder(gr)) }) test_that("node types return vector of correct length for focus", { gr <- create_tree(10, 2) |> focus(dplyr::row_number() < 3) expect_length(get_type(gr, node_is_center()), 2) expect_length(get_type(gr, node_is_cut()), 2) expect_length(get_type(gr, node_is_isolated()), 2) expect_length(get_type(gr, node_is_leaf()), 2) expect_length(get_type(gr, node_is_root()), 2) expect_length(get_type(gr, node_is_simplical()), 2) expect_length(get_type(gr, node_is_sink()), 2) expect_length(get_type(gr, node_is_source()), 2) expect_length(get_type(gr, node_is_universal()), 2) expect_length(get_type(gr, node_is_connected(1:4)), 2) }) 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())) expect_error(get_type(gr, node_is_connected(1:4))) }) test_empty_context() tidygraph/tests/testthat.R0000644000176200001440000000061614535605577015416 0ustar liggesusers# This file is part of the standard setup for testthat. # It is recommended that you do not modify it. # # Where should you do additional test configuration? # Learn more about the roles of various files in: # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview # * https://testthat.r-lib.org/articles/special-files.html library(testthat) library(tidygraph) test_check("tidygraph") tidygraph/src/0000755000176200001440000000000014556164357013054 5ustar liggesuserstidygraph/src/get_paths.cpp0000644000176200001440000000247214535605577015544 0ustar liggesusers#include #include #include #include #include [[cpp11::register]] cpp11::list get_paths(cpp11::integers parent) { cpp11::writable::list paths; int next = 0; for (int i = 0; i < parent.size(); ++i) { std::vector path; next = i; while(!cpp11::is_na(parent[next])) { next = parent[next] - 1; path.push_back(next + 1); } std::reverse(path.begin(), path.end()); paths.push_back(cpp11::integers(path)); } return paths; } [[cpp11::register]] cpp11::list collect_offspring(cpp11::list_of offspring, cpp11::integers order) { cpp11::writable::list offsprings; for (R_len_t i = 0; i < order.size(); ++i) { cpp11::writable::integers off(offspring[i].begin(), offspring[i].end()); offsprings.push_back(off); } for (R_len_t i = 0; i < order.size(); ++i) { int node = order[i] - 1; cpp11::writable::integers off = cpp11::as_cpp(offsprings[node]); for (R_len_t j = 0; j < off.size(); ++j) { int child = off[j] - 1; cpp11::integers child_off = cpp11::as_cpp(offsprings[child]); for (R_len_t k = 0; k < child_off.size(); ++k) { off.push_back(child_off[k]); } } } return offsprings; } tidygraph/src/cpp11.cpp0000644000176200001440000000226214535605577014507 0ustar liggesusers// Generated by cpp11: do not edit by hand // clang-format off #include "cpp11/declarations.hpp" #include // get_paths.cpp cpp11::list get_paths(cpp11::integers parent); extern "C" SEXP _tidygraph_get_paths(SEXP parent) { BEGIN_CPP11 return cpp11::as_sexp(get_paths(cpp11::as_cpp>(parent))); END_CPP11 } // get_paths.cpp cpp11::list collect_offspring(cpp11::list_of offspring, cpp11::integers order); extern "C" SEXP _tidygraph_collect_offspring(SEXP offspring, SEXP order) { BEGIN_CPP11 return cpp11::as_sexp(collect_offspring(cpp11::as_cpp>>(offspring), cpp11::as_cpp>(order))); END_CPP11 } extern "C" { static const R_CallMethodDef CallEntries[] = { {"_tidygraph_collect_offspring", (DL_FUNC) &_tidygraph_collect_offspring, 2}, {"_tidygraph_get_paths", (DL_FUNC) &_tidygraph_get_paths, 1}, {NULL, NULL, 0} }; } extern "C" attribute_visible void R_init_tidygraph(DllInfo* dll){ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); R_forceSymbols(dll, TRUE); } tidygraph/R/0000755000176200001440000000000014556124367012463 5ustar liggesuserstidygraph/R/igraph.R0000644000176200001440000000062014535605577014062 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.R0000644000176200001440000001256614535605577014071 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 #' @export bfs_rank <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_node_ind(root, graph) ind <- bfs(graph = graph, root = root, mode = mode, unreachable = unreachable, order = TRUE, rank = TRUE)$rank as.integer(ind)[focus_ind(graph, 'nodes')] } #' @describeIn search_graph Get the nodes from which each node is visited in a breath first search #' @importFrom igraph bfs #' @export bfs_parent <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_node_ind(root, graph) ind <- bfs(graph = graph, root = root, mode = mode, unreachable = unreachable, order = TRUE, father = TRUE)$father as.integer(ind)[focus_ind(graph, 'nodes')] } #' @describeIn search_graph Get the node that was visited before each node in a breath first search #' @importFrom igraph bfs #' @export bfs_before <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_node_ind(root, graph) ind <- bfs(graph = graph, root = root, mode = mode, unreachable = unreachable, order = TRUE, pred = TRUE)$pred as.integer(ind)[focus_ind(graph, 'nodes')] } #' @describeIn search_graph Get the node that was visited after each node in a breath first search #' @importFrom igraph bfs #' @export bfs_after <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_node_ind(root, graph) ind <- bfs(graph = graph, root = root, mode = mode, unreachable = unreachable, order = TRUE, succ = TRUE)$succ as.integer(ind)[focus_ind(graph, 'nodes')] } #' @describeIn search_graph Get the number of nodes between the root and each node in a breath first search #' @importFrom igraph bfs #' @export bfs_dist <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_node_ind(root, graph) ind <- bfs(graph = graph, root = root, mode = mode, unreachable = unreachable, order = TRUE, dist = TRUE)$dist as.integer(ind)[focus_ind(graph, 'nodes')] } # Depth First Search ------------------------------------------------------ #' @describeIn search_graph Get the succession in which the nodes are visited in a depth first search #' @importFrom igraph dfs #' @export dfs_rank <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_node_ind(root, graph) ind <- dfs(graph = graph, root = root, mode = mode, unreachable = unreachable, order = TRUE)$order match(seq_along(ind), as.integer(ind))[focus_ind(graph, 'nodes')] } #' @describeIn search_graph Get the succession in which each nodes subtree is completed in a depth first search #' @importFrom igraph dfs #' @export dfs_rank_out <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_node_ind(root, graph) ind <- dfs(graph = graph, root = root, mode = mode, unreachable = unreachable, order = TRUE, order.out = TRUE)$order.out match(seq_along(ind), as.integer(ind))[focus_ind(graph, 'nodes')] } #' @describeIn search_graph Get the nodes from which each node is visited in a depth first search #' @importFrom igraph dfs #' @export dfs_parent <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_node_ind(root, graph) ind <- dfs(graph = graph, root = root, mode = mode, unreachable = unreachable, order = TRUE, father = TRUE)$father as.integer(ind)[focus_ind(graph, 'nodes')] } #' @describeIn search_graph Get the number of nodes between the root and each node in a depth first search #' @importFrom igraph dfs #' @export dfs_dist <- function(root, mode = 'out', unreachable = FALSE) { expect_nodes() graph <- .G() root <- as_node_ind(root, graph) ind <- dfs(graph = graph, root = root, mode = mode, unreachable = unreachable, order = TRUE, dist = TRUE)$dist as.integer(ind)[focus_ind(graph, 'nodes')] } tidygraph/R/focus.R0000644000176200001440000000617014535605577013735 0ustar liggesusers#' Select specific nodes or edges to compute on #' #' The `focus()`/`unfocus()` idiom allow you to temporarily tell tidygraph #' algorithms to only calculate on a subset of the data, while keeping the full #' graph intact. The purpose of this is to avoid having to calculate time #' costly measures etc on all nodes or edges of a graph if only a few is needed. #' E.g. you might only be interested in the shortest distance from one node to #' another so rather than calculating this for all nodes you apply a focus on #' one node and perform the calculation. It should be made clear that not all #' algorithms will see a performance boost by being applied to a few nodes/edges #' since their calculation is applied globally and the result for all #' nodes/edges are provided in unison. #' #' @note focusing is the lowest prioritised operation on a graph. Applying a #' [morph()] or a [group_by()] operation will unfocus the graph prior to #' performing the operation. The same is true for the inverse operations #' ([unmorph()] and [ungroup()]). Further, unfocusing will happen any time some #' graph altering operation is performed, such as the `arrange()` and `slice()` #' operations #' #' @inheritParams dplyr::filter #' #' @return A graph with focus applied #' #' @export focus <- function(.data, ...) { UseMethod('focus') } #' @rdname focus #' @export focus.tbl_graph <- function(.data, ...) { .graph_context$set(.data) on.exit(.graph_context$clear()) if (is.focused_tbl_graph(.data)) .data <- unfocus(.data) d_tmp <- as_tibble(.data) n_tmp <- nrow(d_tmp) d_tmp$.tidygraph_focus_index <- seq_len(n_tmp) d_tmp <- filter(d_tmp, ...) if (nrow(d_tmp) == 0) { cli::cli_inform("{.fun focus} didn't select any {active(.data)}. Returning unfocused graph") return(.data) } if (nrow(d_tmp) == n_tmp) { cli::cli_inform("{.fun focus} selected all {active(.data)}. Returning unfocused graph") return(.data) } apply_focus(.data, d_tmp$.tidygraph_focus_index) } #' @rdname focus #' @export focus.morphed_tbl_graph <- function(.data, ...) { .data[] <- lapply(.data, focus, ...) .data } #' @rdname focus #' @export unfocus <- function(.data, ...) { UseMethod('unfocus') } #' @rdname focus #' @export unfocus.tbl_graph <- function(.data, ...) { .data } #' @rdname focus #' @export unfocus.focused_tbl_graph <- function(.data, ...) { attr(.data, paste0(active(.data), '_focus_index')) <- NULL class(.data) <- setdiff(class(.data), 'focused_tbl_graph') .data } #' @rdname focus #' @export unfocus.morphed_tbl_graph <- function(.data, ...) { .data[] <- lapply(.data, unfocus, ...) .data } is.focused_tbl_graph <- function(x) inherits(x, 'focused_tbl_graph') # HELPERS ----------------------------------------------------------------- apply_focus <- function(graph, index) { attr(graph, paste0(active(graph), '_focus_index')) <- index if (!is.focused_tbl_graph(graph)) { class(graph) <- c('focused_tbl_graph', class(graph)) } graph } focus_ind <- function(x, active = NULL) { if (is.null(active)) active <- active(x) attr(x, paste0(active, '_focus_index')) %||% seq_len(if (active == "nodes") gorder(x) else gsize(x)) } tidygraph/R/create.R0000644000176200001440000001073014535605577014056 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) { if (!is.character(name)) cli::cli_abort('{.arg name} must be a scalar {.cls character} vector') 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.R0000644000176200001440000003367114535605577013571 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)) cli::cli_warn('Ignoring {.arg m} as {.arg p} is provided') '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 count. See #' [igraph::sample_gnm()] #' @importFrom igraph sample_gnm #' @export play_gnm <- function(n, m, directed = TRUE, loops = FALSE) { as_tbl_graph(sample_gnm(n, m, directed, loops)) } #' @describeIn sampling_games Create graphs with a fixed edge probability. See #' [igraph::sample_gnp()] #' @importFrom igraph sample_gnp #' @export play_gnp <- function(n, p, directed = TRUE, loops = 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)) } #' @describeIn sampling_games `r lifecycle::badge('deprecated')` 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)) { lifecycle::deprecate_soft("1.3.0", "play_erdos_renyi()", "play_gnm()") as_tbl_graph(sample_gnm(n, m, directed, loops)) } else { lifecycle::deprecate_soft("1.3.0", "play_erdos_renyi()", "play_gnp()") as_tbl_graph(sample_gnp(n, p, directed, loops)) } } #' 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/utils.R0000644000176200001440000000126214535605577013753 0ustar liggesusersregister_s3_method <- function(pkg, generic, class, fun = NULL) { stopifnot(is.character(pkg), length(pkg) == 1) stopifnot(is.character(generic), length(generic) == 1) stopifnot(is.character(class), length(class) == 1) if (is.null(fun)) { fun <- get(paste0(generic, ".", class), envir = parent.frame()) } else { stopifnot(is.function(fun)) } if (pkg %in% loadedNamespaces()) { registerS3method(generic, class, fun, envir = asNamespace(pkg)) } # Always register hook in case package is later unloaded & reloaded setHook( packageEvent(pkg, "onLoad"), function(...) { registerS3method(generic, class, fun, envir = asNamespace(pkg)) } ) } tidygraph/R/sample_n.R0000644000176200001440000000177514535605577014422 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(), ...) { tbl <- unfocus(tbl) d_tmp <- as_tibble(tbl) weight <- enquo(weight) check_reserved(d_tmp) 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.R0000644000176200001440000002203514535605577013741 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 changes 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 (is.focused_tbl_graph(.data)) { cli::cli_inform('Unfocusing prior to morphing') .data <- unfocus(.data) } if (inherits(.data, 'grouped_tbl_graph')) { cli::cli_inform('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, ...) { cli::cli_inform('Unmorphing {.arg .data} 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) { if (length(.select) != 1) cli::cli_abort("{.arg .select} must be a single scalar") graphs <- crystallise(morph(.data, .f, ...)) if (is.character(.select)) { .select <- which(.select == graphs$name)[1] if (is.na(.select)) cli::cli_abort('{.arg .select} does not match any named graph') } if (.select > nrow(graphs)) cli::cli_abort(c('{.fn convert} did not create {.select} number of graphs', 'i' = 'Set a lower {.arg .select}')) 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'))) { cli::cli_abort('{.arg morph} must consist of {.cls 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)) { cli::cli_abort('{.arg morph} must contain reference to either nodes or edges') } }) 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.R0000644000176200001440000000060514535605577014062 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.R0000644000176200001440000000644514547200627013564 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 = cli::cli_abort("Unknown list format") ) 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()) } else { edges <- as.data.frame(edges) } 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.factor(edges[[1]])) edges[[1]] <- as.character(edges[[1]]) if (is.factor(edges[[2]])) edges[[2]] <- as.character(edges[[2]]) 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)) { nodes <- as.data.frame(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.R0000644000176200001440000002207114556122375014547 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, ...) { rlang::try_fetch( as_tbl_graph(as.igraph(x)), error = function(cnd) cli::cli_abort('No support for {.cls {class(x)}} objects', parent = cnd) ) } #' @rdname tbl_graph #' @export is.tbl_graph <- function(x) { inherits(x, 'tbl_graph') } #' @importFrom rlang caller_arg check_tbl_graph <- function(x, arg = caller_arg(x), call = caller_env()) { if (!is.tbl_graph(x)) { cli::cli_abort('{.arg {arg}} must be a {.cls tbl_graph} object', call = call) } } check_reserved <- function(x, call = caller_env()) { if (any(names(x) == '.tbl_graph_index')) { cli::cli_abort('The attribute name {.field .tbl_graph_index} is reserved', call = call) } } new_name_tibble <- function(x, active = NULL, name = "A tibble", suffix = "") { x <- as_tibble(x, active, focused = FALSE) attr(x, "name") <- name attr(x, "suffix") <- suffix class(x) <- c("named_tbl", class(x)) x } #' @importFrom pillar tbl_sum #' @export tbl_sum.named_tbl <- function(x) { summary <- NextMethod() names(summary)[1] <- attr(x, "name") summary[1] <- paste0(summary[1], attr(x, "suffix")) summary } #' @importFrom pillar tbl_format_footer #' @export tbl_format_footer.named_tbl <- function(x, setup, ...) { footer <- NextMethod() footer[!grepl("to see more rows", footer)] } #' @importFrom tools toTitleCase #' @importFrom rlang as_quosure sym #' @export print.tbl_graph <- function(x, ..., n_non_active = 3) { graph_desc <- describe_graph(x) not_active <- if (active(x) == 'nodes') 'edges' else 'nodes' top <- toTitleCase(paste0(substr(active(x), 1, 4), ' data')) bottom <- 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') if (is.focused_tbl_graph(x)) { cat_subtle('# Focused on ', length(focus_ind(x)), ' ', active(x), '\n') } print(new_name_tibble(x, NULL, top, " (active)"), ...) cat_subtle('#\n') print(new_name_tibble(x, not_active, bottom, ""), n = n_non_active) invisible(x) } #' @importFrom pillar glimpse #' @export glimpse.tbl_graph <- function(x, width = NULL, ...) { cli::cli_rule(left = "Nodes") glimpse(as_tibble(x, active = "nodes")) cli::cat_line() cli::cli_rule(left = "Edges") glimpse(as_tibble(x, active = "edges")) } #' @importFrom pillar glimpse #' @export glimpse.morphed_tbl_graph <- function(x, width = NULL, ...) { graph <- attr(x, '.orig_graph') cat_subtle("Currently morphed to a ", gsub('_', ' ', sub('to_', '', attr(x, '.morpher'))), " representation\n") cli::cat_line() cli::cli_rule(left = "Nodes") glimpse(as_tibble(graph, active = "nodes")) cli::cat_line() cli::cli_rule(left = "Edges") glimpse(as_tibble(graph, active = "edges")) } #' @importFrom pillar style_subtle 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') } #' @export 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), cli::cli_abort('Unknown active element: {.val {active}}. Only nodes and edges supported') ) } #' @export set_graph_data.grouped_tbl_graph <- function(x, value, active = NULL) { x <- NextMethod() apply_groups(x, value) } #' @importFrom igraph vertex_attr<- set_node_attributes <- function(x, value) { if (is.focused_tbl_graph(x)) { value <- merge_into(value, as_tibble(x, active = 'nodes', focused = FALSE), focus_ind(x, 'nodes')) } vertex_attr(x) <- as.list(value) x } #' @importFrom igraph edge_attr<- set_edge_attributes <- function(x, value) { if (is.focused_tbl_graph(x)) { value <- merge_into(value, as_tibble(x, active = 'edges', focused = FALSE), focus_ind(x, 'edges')) } 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 merge_into <- function(new, old, index) { order <- new[integer(0), , drop = FALSE] old <- bind_rows(order, old) old[, !names(old) %in% names(new)] <- NULL old[index, ] <- new old } tidygraph/R/tidygraph-package.R0000644000176200001440000000043414535605577016177 0ustar liggesusers#' @keywords internal '_PACKAGE' # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start #' @importFrom lifecycle deprecated #' @useDynLib tidygraph, .registration = TRUE ## usethis namespace: end NULL tidygraph/R/graph_types.R0000644000176200001440000000567514535605577015154 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 cyclic should the eulerian path start and end at the same node #' #' @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, ...) } #' @describeIn graph_types Can all the edges in the graph be reaches by a single #' path or cycle that only goes through each edge once #' @importFrom igraph has_eulerian_cycle has_eulerian_path #' @export graph_is_eulerian <- function(cyclic = FALSE) { if (cyclic) { has_eulerian_cycle(.G()) } else { has_eulerian_path(.G()) } } tidygraph/R/phylo.R0000644000176200001440000000151414535605577013746 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, ...) { rlang::check_installed('ape', 'in order to coerce phylo object to tbl_graph') 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, ...) { rlang::check_installed('ape', 'in order to coerce evonet object to tbl_graph') if (is.null(directed)) directed <- ape::is.rooted(x) as_tbl_graph(ape::as.igraph.evonet(x, directed = directed, ...)) } tidygraph/R/edge_rank.R0000644000176200001440000000207014535605577014530 0ustar liggesusers#' Calculate edge ranking #' #' This set of functions tries to calculate a ranking of the edges in a graph so #' that edges sharing certain topological traits are in proximity in the #' resulting order. #' #' @param cyclic should the eulerian path start and end at the same node #' #' @return An integer vector giving the position of each edge in the ranking #' #' @rdname edge_rank #' @name edge_rank #' #' @examples #' graph <- create_notable('meredith') %>% #' activate(edges) %>% #' mutate(rank = edge_rank_eulerian()) #' NULL #' @describeIn edge_rank Calculcate ranking as the visit order of a eulerian #' path or cycle. If no such path or cycle exist it will return a vector of #' `NA`s #' @importFrom igraph eulerian_path eulerian_cycle #' @export edge_rank_eulerian <- function(cyclic = FALSE) { expect_edges() alg <- if (cyclic) eulerian_cycle else eulerian_path rlang::try_fetch({ compress_rank(match(focus_ind(.G(), 'edges'), alg(.G())$epath)) }, error = function(...) { rep_len(NA_integer_, length(focus_ind(.G(), 'edges'))) } ) } tidygraph/R/map.R0000644000176200001440000005215014535605577013372 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 #' #' @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_node_ind(root, 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)[focus_ind(graph, 'nodes')] } #' @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 #' #' @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_node_ind(root, 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)[focus_ind(graph, 'nodes')] } #' @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 #' #' @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_node_ind(root, 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)[focus_ind(graph, 'nodes')] } #' @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 #' #' @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_node_ind(root, 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)[focus_ind(graph, 'nodes')] } #' @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(focus_ind(graph, 'nodes'), 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, mode = 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, mode = 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, as.integer(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 { type <- deparse(substitute(.type)) cli::cli_abort("Cannot coerce values to {.cls {type}}") } } tidygraph/R/node.R0000644000176200001440000002435614556122137013536 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() focus_ind(graph, 'nodes') %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() node_inds <- focus_ind(graph, 'nodes') if ((!is_tree(graph) && !is_forest(graph)) || !is_directed(graph)) { return(rep(FALSE, length(node_inds))) } deg_in <- degree(graph, mode = 'in') == 0 deg_out <- degree(graph, mode = 'out') == 0 root <- if (sum(deg_in) > sum(deg_out)) deg_out else deg_in root[node_inds] } #' @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() node_inds <- focus_ind(graph, 'nodes') if ((!is_tree(graph) && !is_forest(graph))) { return(rep(FALSE, length(node_inds))) } if (is_directed(graph)) { deg_in <- degree(graph, mode = 'in') == 0 deg_out <- degree(graph, mode = 'out') == 0 leaf <- if (sum(deg_out) > sum(deg_in)) deg_out else deg_in leaf[node_inds] } else { degree(graph, v = node_inds, 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() node_inds <- focus_ind(graph, 'nodes') deg_in <- degree(graph, v = node_inds, mode = 'in') deg_out <- degree(graph, v = node_inds, 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() node_inds <- focus_ind(graph, 'nodes') deg_in <- degree(graph, v = node_inds, mode = 'in') deg_out <- degree(graph, v = node_inds, 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, v = focus_ind(graph, 'nodes')) == 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, nodes = focus_ind(graph, 'nodes'), 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() node_inds <- focus_ind(graph, 'nodes') n_edges <- local_scan(graph, k = 1, mode = mode, FUN = ecount)[node_inds] n_nodes <- ego_size(graph, order = 1, nodes = node_inds, 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[focus_ind(graph, 'nodes')] == 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 adjacent_vertices #' @export node_is_adjacent <- function(to, mode = 'all', include_to = TRUE) { expect_nodes() graph <- .G() to <- as_node_ind(to, graph) include <- unlist(adjacent_vertices(graph, to, mode)) if (include_to) include <- union(to, include) focus_ind(graph, '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) focus_ind(graph, 'nodes') %in% ind } #' @describeIn node_types Is a node connected to all (or any) nodes in a set #' @param nodes The set of nodes to test connectivity to. Can be a list to use #' different sets for different nodes. If a list it will be recycled as #' necessary. #' @param any Logical. If `TRUE` the node only needs to be connected to a single #' node in the set for it to return `TRUE` #' @importFrom igraph distances gorder #' @export node_is_connected <- function(nodes, mode = 'all', any = FALSE) { expect_nodes() graph <- .G() node_inds <- focus_ind(graph, 'nodes') if (!is.list(nodes)) nodes <- list(nodes) all_nodes <- unique(unlist(nodes)) reached <- is.finite(t(distances(graph, v = node_inds, to = all_nodes, mode = mode, weights = NA))) nodes <- rep_len(nodes, length(node_inds)) vapply(seq_along(node_inds), function(i) { n <- node_inds[i] connections <- match(nodes[[i]], all_nodes) found <- reached[,n][connections] if (any) any(found) else all(found) }, logical(1)) } #' 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. #' #' @param mode How edges are treated. In `node_coreness()` it chooses which kind #' of coreness measure to calculate. In `node_efficiency()` it defines how the #' local neighborhood is created #' @param weights The weights to use for each node during calculation #' @param directed Should the graph be treated as a directed graph if it is in #' fact directed #' #' @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, focus_ind(graph, 'nodes'), 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, focus_ind(graph, 'nodes'), 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)[focus_ind(graph, 'nodes')] } #' @describeIn node_measures measures the diversity of the node. See [igraph::diversity()] #' @importFrom igraph diversity #' @export node_diversity <- function(weights) { expect_nodes() graph <- .G() if (missing(weights)) { cli::cli_abort('{.arg weights} must be provided') } weights <- enquo(weights) weights <- eval_tidy(weights, .E()) if (is.null(weights)) { cli::cli_abort('{.arg weights} must be a valid vector') } diversity(graph, weights = weights, vids = focus_ind(graph, 'nodes')) } #' @describeIn node_measures measures the local efficiency around each node. See [igraph::local_efficiency()] #' @importFrom rlang enquo eval_tidy #' @importFrom igraph local_efficiency #' @export node_efficiency <- function(weights = NULL, directed = TRUE, mode = 'all') { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA local_efficiency(graph, focus_ind(graph, 'nodes'), weights, directed, mode) } #' @describeIn node_measures measures Valente's Bridging measures for detecting structural bridges (`influenceR`) #' @export node_bridging_score <- function() { expect_influencer() expect_nodes() graph <- .G() influenceR::bridging(graph)[focus_ind(graph, 'nodes')] } #' @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() graph <- .G() influenceR::ens(graph)[focus_ind(graph, 'nodes')] } #' @describeIn node_measures measures the impact on connectivity when removing the node (`NetSwan`) #' @export node_connectivity_impact <- function() { expect_netswan() expect_nodes() graph <- .G() NetSwan::swan_connectivity(graph)[focus_ind(graph, 'nodes')] } #' @describeIn node_measures measures the impact on closeness when removing the node (`NetSwan`) #' @export node_closeness_impact <- function() { expect_netswan() expect_nodes() graph <- .G() NetSwan::swan_closeness(graph)[focus_ind(graph, 'nodes')] } #' @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() graph <- .G() NetSwan::swan_efficiency(graph)[focus_ind(graph, 'nodes')] } tidygraph/R/morphers.R0000644000176200001440000003356514535605577014465 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) cli::cli_inform('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 components #' @export to_subcomponent <- function(graph, node) { node <- eval_tidy(enquo(node), as_tibble(graph, 'nodes')) node <- as_node_ind(node, graph) if (length(node) != 1) cli::cli_abort('{.arg node} must identify a single node in the graph') 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) cli::cli_inform('Splitting 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'` #' @param min_order The minimum order (number of vertices) of the component. #' Components below this will not be created #' @importFrom igraph decompose #' @export to_components <- function(graph, type = 'weak', min_order = 1) { graphs <- decompose(graph, mode = type) graphs <- lapply(graphs, as_tbl_graph) graphs } #' @describeIn morphers Create a new graph only consisting of it's largest #' component. If multiple largest components exists, the one with containing the #' node with the lowest index is chosen. #' @importFrom igraph largest_component #' @export to_largest_component <- function(graph, type = 'weak') { list( largest_component = as_tbl_graph(largest_component(graph, mode = type)) ) } #' @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 #' @export to_local_neighborhood <- function(graph, node, order = 1, mode = 'all') { node <- eval_tidy(enquo(node), as_tibble(graph, 'nodes')) node <- as_node_ind(node, 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 #' @export to_dominator_tree <- function(graph, root, mode = 'out') { root <- eval_tidy(enquo(root), as_tibble(graph, 'nodes')) root <- as_node_ind(root, 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 Convert a graph into a random spanning tree/forest. When #' unmorphing all data will get merged back #' @importFrom igraph subgraph.edges sample_spanning_tree #' @export to_random_spanning_tree <- function(graph) { list( spanning_tree = as_tbl_graph(subgraph.edges(graph, sample_spanning_tree(graph))) ) } #' @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 #' @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_node_ind(from, graph) to <- eval_tidy(enquo(to), nodes) to <- as_node_ind(to, graph) weights <- eval_tidy(enquo(weights), as_tibble(graph, active = 'edges')) %||% 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 #' @export to_bfs_tree <- function(graph, root, mode = 'out', unreachable = FALSE) { root <- eval_tidy(enquo(root), as_tibble(graph, 'nodes')) root <- as_node_ind(root, graph) search <- bfs(graph, root, mode = 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 #' @export to_dfs_tree <- function(graph, root, mode = 'out', unreachable = FALSE) { root <- eval_tidy(enquo(root), as_tibble(graph, 'nodes')) root <- as_node_ind(root, graph) search <- dfs(graph, root, mode = 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) ind <- match(ind, unique(ind)) 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_node_ind(root, 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 gorder vertex_attr #' @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()) %||% 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)) label <- vertex_attr(hierarchy, "label") orig_label <- vertex_attr(graph, "name") %||% as.character(seq_len(gorder(graph))) hierarchy <- mutate(hierarchy, .tidygraph_node_index = match(label, orig_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.R0000644000176200001440000000135714535605577013342 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] } #' @importFrom igraph gorder as_node_ind <- function(i, graph) { if (!missing(i)) { i <- with_graph(unfocus(graph), {{i}}) } as_ind(i, gorder(graph)) } compress_rank <- function(x) { match(x, sort(x)) } expect_influencer <- function(...) { rlang::check_installed('influenceR', ...) } expect_netrankr <- function(...) { rlang::check_installed('netrankr', ...) } expect_seriation <- function(...) { rlang::check_installed('seriation', ...) } expect_netswan <- function(...) { rlang::check_installed('NetSwan', ...) } tidygraph/R/tibble.R0000644000176200001440000000337214535605577014060 0ustar liggesusers#' @export #' @importFrom tibble as_tibble as_tibble.tbl_graph <- function(x, active = NULL, focused = TRUE, ...) { if (is.null(active)) { active <- attr(x, 'active') } switch( active, nodes = node_tibble(x, focused = focused), edges = edge_tibble(x, focused = focused), cli::cli_abort('Unknown active element: {.val {active}}. Only nodes and edges supported') ) } #' @export as_tibble.grouped_tbl_graph <- function(x, active = NULL, focused = TRUE, ...) { 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 as_tibble.morphed_tbl_graph <- function(x, ...) { as_tibble(crystallize(x), ...) } #' @export tibble::as_tibble #' @importFrom magrittr %>% #' @export magrittr::`%>%` #' @importFrom igraph vertex_attr gorder #' @importFrom tibble as_tibble node_tibble <- function(x, focused = TRUE) { tbl <- as_tibble(vertex_attr(x)) if (length(attr(tbl, 'row.names')) == 0) { attr(tbl, 'row.names') <- .set_row_names(gorder(x)) } if (focused && is.focused_tbl_graph(x)) { tbl <- tbl[focus_ind(x, 'nodes'), ] } tbl } #' @importFrom igraph edge_attr gsize as_edgelist #' @importFrom tibble as_tibble #' @importFrom dplyr bind_cols edge_tibble <- function(x, focused = TRUE) { tbl <- as_tibble(edge_attr(x)) if (length(attr(tbl, 'row.names')) == 0) { attr(tbl, 'row.names') <- .set_row_names(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) tbl <- bind_cols(e_list, tbl) if (focused && is.focused_tbl_graph(x)) { tbl <- tbl[focus_ind(x, 'edges'), ] } tbl } tidygraph/R/slice.R0000644000176200001440000000741214535605577013715 0ustar liggesusers#' @export #' @importFrom dplyr slice slice.tbl_graph <- function(.data, ..., .by = NULL, .preserve = FALSE) { .data <- unfocus(.data) graph_slicer(.data, slice, ..., .by = {{.by}}, .preserve = .preserve) } #' @export #' @importFrom dplyr slice slice.morphed_tbl_graph <- function(.data, ..., .by = NULL, .preserve = FALSE) { .data[] <- lapply(.data, function(d) slice(d, ..., .by = {{.by}}, .preserve = .preserve)) .data } #' @export dplyr::slice #' @export #' @importFrom dplyr slice_head slice_head.tbl_graph <- function(.data, ..., n, prop, by = NULL) { .data <- unfocus(.data) graph_slicer(.data, slice_head, ..., n = n, prop = prop, by = {{by}}) } #' @export #' @importFrom dplyr slice_head slice_head.morphed_tbl_graph <- function(.data, ..., n, prop, by = NULL) { .data[] <- lapply(.data, function(d) slice_head(d, ..., n = n, prop = prop, by = {{by}})) .data } #' @export dplyr::slice_head #' @export #' @importFrom dplyr slice_tail slice_tail.tbl_graph <- function(.data, ..., n, prop, by = NULL) { .data <- unfocus(.data) graph_slicer(.data, slice_tail, ..., n = n, prop = prop, by = {{by}}) } #' @export #' @importFrom dplyr slice_tail slice_tail.morphed_tbl_graph <- function(.data, ..., n, prop, by = NULL) { .data[] <- lapply(.data, function(d) slice_tail(d, ..., n = n, prop = prop, by = {{by}})) .data } #' @export dplyr::slice_tail #' @export #' @importFrom dplyr slice_min slice_min.tbl_graph <- function(.data, order_by, ..., n, prop, by = NULL, with_ties = TRUE, na_rm = FALSE) { .data <- unfocus(.data) graph_slicer(.data, slice_min, order_by = {{order_by}}, ..., n = n, prop = prop, by = {{by}}, with_ties = with_ties, na_rm = na_rm) } #' @export #' @importFrom dplyr slice_min slice_min.morphed_tbl_graph <- function(.data, order_by, ..., n, prop, by = NULL, with_ties = TRUE, na_rm = FALSE) { .data[] <- lapply(.data, function(d) slice_min(d, order_by = {{order_by}}, ..., n = n, prop = prop, by = {{by}}, with_ties = with_ties, na_rm = na_rm)) .data } #' @export dplyr::slice_min #' @export #' @importFrom dplyr slice_max slice_max.tbl_graph <- function(.data, order_by, ..., n, prop, by = NULL, with_ties = TRUE, na_rm = FALSE) { .data <- unfocus(.data) graph_slicer(.data, slice_max, order_by = {{order_by}}, ..., n = n, prop = prop, by = {{by}}, with_ties = with_ties, na_rm = na_rm) } #' @export #' @importFrom dplyr slice_max slice_max.morphed_tbl_graph <- function(.data, order_by, ..., n, prop, by = NULL, with_ties = TRUE, na_rm = FALSE) { .data[] <- lapply(.data, function(d) slice_max(d, order_by = {{order_by}}, ..., n = n, prop = prop, by = {{by}}, with_ties = with_ties, na_rm = na_rm)) .data } #' @export dplyr::slice_max #' @export #' @importFrom dplyr slice_sample slice_sample.tbl_graph <- function(.data, ..., n, prop, by = NULL, weight_by = NULL, replace = FALSE) { .data <- unfocus(.data) graph_slicer(.data, slice_sample, ..., n = n, prop = prop, by = {{by}}, weight_by = weight_by, replace = replace) } #' @export #' @importFrom dplyr slice_sample slice_sample.morphed_tbl_graph <- function(.data, ..., n, prop, by = NULL, weight_by = NULL, replace = FALSE) { .data[] <- lapply(.data, function(d) slice_sample(d, ..., n = n, prop = prop, by = {{by}}, weight_by = weight_by, replace = replace)) .data } #' @export dplyr::slice_sample #' @importFrom igraph delete_vertices delete_edges graph_slicer <- function(.data, slicer, ...) { .register_graph_context(.data) d_tmp <- as_tibble(.data) check_reserved(d_tmp) orig_ind <- seq_len(nrow(d_tmp)) d_tmp$.tbl_graph_index <- orig_ind d_tmp <- slicer(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 } tidygraph/R/pull.R0000644000176200001440000000064614535605577013574 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.R0000644000176200001440000003036114535605577014555 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') { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA mat <- to_dist(.G(), dist, mode, weights, algorithm) compress_rank(hclust(mat, method)$order[focus_ind(.G(), 'nodes')]) } #' @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()) %||% 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()) %||% 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()) %||% 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()) %||% 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()) %||% 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()) %||% 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()) %||% 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()) %||% 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()) %||% 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()) %||% 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()) %||% 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()) %||% 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()) %||% 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) if (!inherits(mat, 'dist')) cli::cli_abort('{.arg dist} must return a {cls dist} object') if (attr(mat, 'Size') != gorder(graph)) cli::cli_abort('{.arg dist} must return a {.cls dist} object of the same size as the order of {.arg 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) compress_rank(seriation::get_rank(ser)[focus_ind(.G(), 'nodes')]) } tidygraph/R/edge.R0000644000176200001440000000731214535605577013521 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, eids = focus_ind(graph, 'edges')) } #' @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, eids = focus_ind(graph, 'edges')) } #' @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, eids = focus_ind(graph, 'edges')) } #' @describeIn edge_types Query whether an edge goes from a set of nodes #' @param from,to,nodes A vector giving node indices #' @export edge_is_from <- function(from) { expect_edges() .free_graph_context() graph <- .G() .E()$from[focus_ind(graph, 'edges')] %in% as_node_ind(from, graph) } #' @describeIn edge_types Query whether an edge goes to a set of nodes #' @export edge_is_to <- function(to) { expect_edges() .free_graph_context() graph <- .G() .E()$to[focus_ind(graph, 'edges')] %in% as_node_ind(to, graph) } #' @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() graph <- .G() edges <- .E()[focus_ind(graph, 'edges'), , drop = FALSE] from <- as_node_ind(from, graph) to <- as_node_ind(to, graph) 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(nodes) { expect_edges() .free_graph_context() graph <- .G() edges <- .E()[focus_ind(graph, 'edges'), , drop = FALSE] nodes <- as_node_ind(nodes, graph) edges$from %in% nodes | edges$to %in% nodes } #' @describeIn edge_types Query whether an edge is a bridge (ie. it's removal #' will increase the number of components in a graph) #' @importFrom igraph bridges gsize #' @export edge_is_bridge <- function() { expect_edges() graph <- .G() focus_ind(graph, 'edges') %in% bridges(graph) } #' @describeIn edge_types Query whether an edge is part of the minimal feedback #' arc set (its removal together with the rest will break all cycles in the #' graph) #' @importFrom rlang eval_tidy enquo #' @importFrom igraph feedback_arc_set #' @param weights The weight of the edges to use for the calculation. Will be #' evaluated in the context of the edge data. #' @param approximate Should the minimal set be approximated or exact #' @export edge_is_feedback_arc <- function(weights = NULL, approximate = TRUE) { expect_edges() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E(focused = FALSE)) %||% NA alg <- if (approximate) 'approx_eades' else 'exact_ip' focus_ind(graph, 'edges') %in% feedback_arc_set(graph, weights, alg) } tidygraph/R/node_topology.R0000644000176200001440000000255214535605577015477 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_node_ind(root, graph) domtree <- as_edgelist(dominator_tree(graph, root, mode)$domtree) dom <- rep(NA, gorder(graph)) dom[domtree[, 2]] <- domtree[, 1] dom[focus_ind(graph, 'nodes')] } #' @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() compress_rank(match(focus_ind(graph, 'nodes'), topo_sort(graph, mode = mode))) } tidygraph/R/pair_measures.R0000644000176200001440000001757514535605577015470 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 #' #' @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_node_ind(nodes, graph) source <- focus_ind(graph, 'nodes') 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 node_adhesion_from <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_node_ind(nodes, graph) target <- focus_ind(graph, 'nodes') 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 node_cohesion_to <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_node_ind(nodes, graph) source <- focus_ind(graph, 'nodes') target <- rep(nodes, length.out = length(source)) neigh <- lapply(ego(graph, 1, source, 'out', mindist = 1), as.integer) adhesion <- Map(function(s, t, n) { if (s == t) return(NA) if (t %in% n) return(NA) vertex_connectivity(graph, source = s, target = t, checks = TRUE) }, s = source, t = target, n = neigh) unlist(adhesion) } #' @describeIn pair_measures Calculate the cohesion from the specified node. Wraps [igraph::vertex_connectivity()] #' @export #' @importFrom igraph vertex_connectivity ego node_cohesion_from <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_node_ind(nodes, graph) target <- focus_ind(graph, 'nodes') source <- rep(nodes, length.out = length(target)) neigh <- lapply(ego(graph, 1, source, 'out', mindist = 1), as.integer) adhesion <- Map(function(s, t, n) { if (s == t) return(NA) if (t %in% n) return(NA) vertex_connectivity(graph, source = s, target = t, checks = TRUE) }, s = source, t = target, n = neigh) unlist(adhesion) } #' @describeIn pair_measures Calculate various distance metrics between node pairs. Wraps [igraph::distances()] #' @export #' @importFrom igraph distances #' #' @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()) %||% NA nodes <- as_node_ind(nodes, graph) source <- focus_ind(graph, 'nodes') 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) dist[cbind(source, match(target, target_unique))] } #' @describeIn pair_measures Calculate various distance metrics between node pairs. Wraps [igraph::distances()] #' @export #' @importFrom igraph distances node_distance_from <- function(nodes, mode = 'out', weights = NULL, algorithm = 'automatic') { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA nodes <- as_node_ind(nodes, graph) target <- focus_ind(graph, 'nodes') 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) dist[cbind(match(source, source_unique), target)] } #' @describeIn pair_measures Calculate node pair cocitation count. Wraps [igraph::cocitation()] #' @export #' @importFrom igraph cocitation node_cocitation_with <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_node_ind(nodes, graph) source <- focus_ind(graph, 'nodes') target <- rep(nodes, length.out = length(source)) cocite <- cocitation(graph) cocite[cbind(source, target)] } #' @describeIn pair_measures Calculate node pair bibliographic coupling. Wraps [igraph::bibcoupling()] #' @export #' @importFrom igraph bibcoupling node_bibcoupling_with <- function(nodes) { expect_nodes() graph <- .G() nodes <- as_node_ind(nodes, graph) source <- focus_ind(graph, 'nodes') target <- rep(nodes, length.out = length(source)) bibc <- bibcoupling(graph) bibc[cbind(source, target)] } #' @describeIn pair_measures Calculate various node pair similarity measures. Wraps [igraph::similarity()] #' @export #' @importFrom igraph similarity #' #' @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_node_ind(nodes, graph) source <- focus_ind(graph, 'nodes') target <- rep(nodes, length.out = length(source)) sim <- similarity(graph, mode = mode, loops = loops, method = method) sim[cbind(source, target)] } #' @describeIn pair_measures Calculate the maximum flow to a node. Wraps [igraph::max_flow()] #' @export #' @importFrom igraph max_flow #' #' @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_node_ind(nodes, graph) source <- focus_ind(graph, 'nodes') 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 node_max_flow_from <- function(nodes, capacity = NULL) { expect_nodes() graph <- .G() capacity <- enquo(capacity) capacity <- eval_tidy(capacity, .E()) nodes <- as_node_ind(nodes, graph) target <- focus_ind(graph, 'nodes') 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.R0000644000176200001440000000556514535605577014747 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.R0000644000176200001440000000175414556122056014225 0ustar liggesusers#' @export #' @importFrom dplyr arrange arrange.tbl_graph <- function(.data, ...) { .data <- unfocus(.data) .register_graph_context(.data) d_tmp <- as_tibble(.data) check_reserved(d_tmp) 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.R0000644000176200001440000000252314535605577014120 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 = cli::cli_abort('Unknown matrix format') ) as_tbl_graph(graph) } guess_matrix_type <- function(x) { if (nrow(x) == ncol(x) && mode(x) %in% c('numeric', 'integer') && ((is.null(rownames(x)) && is.null(colnames(x))) || all(colnames(x) == rownames(x)))) { 'adjacency' } else if (ncol(x) == 2) { 'edgelist' } 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.R0000644000176200001440000000407514535605577014703 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, ...) { x <- as.data.frame(x) 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] is_named <- is.character(x[[1]]) || is.character(x[[2]]) gr <- graph_from_data_frame(x, directed = directed) if (!is_named) { igraph::delete_vertex_attr(gr, 'name') } else { gr } } 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.R0000644000176200001440000003333614556123212014760 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. These measures #' all only work on undirected graphs. #' #' @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 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()) %||% NA exo <- enquo(exo) exo <- eval_tidy(exo, .N()) alpha_centrality(graph = graph, nodes = focus_ind(graph, 'nodes'), alpha = alpha, exo = exo, weights = weights, tol = tol, loops = loops) } #' @describeIn centrality Wrapper for [igraph::authority_score()] #' @importFrom igraph authority_score arpack_defaults #' @export centrality_authority <- function(weights = NULL, scale = TRUE, options = arpack_defaults()) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA graph <- .G() authority_score(graph = graph, scale = scale, weights = weights, options = options)$vector[focus_ind(graph, 'nodes')] } #' @describeIn centrality Wrapper for [igraph::betweenness()] #' @importFrom igraph V betweenness #' @importFrom rlang quos #' @export centrality_betweenness <- function(weights = NULL, directed = TRUE, cutoff = -1, normalized = FALSE) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA cutoff <- cutoff %||% -1 betweenness(graph = graph, v = focus_ind(graph, 'nodes'), directed = directed, cutoff = cutoff, weights = weights, normalized = normalized) } #' @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 = focus_ind(graph, 'nodes'), exponent = exponent, loops = loops, rescale = rescale, tol = tol) } #' @describeIn centrality Wrapper for [igraph::closeness()] #' @importFrom igraph V 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()) %||% NA cutoff <- cutoff %||% -1 closeness(graph = graph, vids = focus_ind(graph, 'nodes'), mode = mode, cutoff = cutoff, weights = weights, normalized = normalized) } #' @describeIn centrality Wrapper for [igraph::eigen_centrality()] #' @importFrom igraph eigen_centrality arpack_defaults #' @export centrality_eigen <- function(weights = NULL, directed = FALSE, scale = TRUE, options = arpack_defaults()) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA graph <- .G() eigen_centrality(graph = graph, directed = directed, scale = scale, weights = weights, options = options)$vector[focus_ind(graph, 'nodes')] } #' @describeIn centrality Wrapper for [igraph::hub_score()] #' @importFrom igraph hub_score arpack_defaults #' @export centrality_hub <- function(weights = NULL, scale = TRUE, options = arpack_defaults()) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA graph <- .G() hub_score(graph = graph, scale = scale, weights = weights, options = options)$vector[focus_ind(graph, 'nodes')] } #' @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()) %||% NA personalized <- enquo(personalized) personalized <- eval_tidy(personalized, .N()) page_rank(graph = graph, vids = focus_ind(graph, 'nodes'), 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() graph <- .G() subgraph_centrality(graph = graph, diag = loops)[focus_ind(graph, 'nodes')] } #' @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)) { degree(graph = graph, v = focus_ind(graph, 'nodes'), mode = mode, loops = loops, normalized = normalized) } else { strength(graph = graph, vids = focus_ind(graph, 'nodes'), mode = mode, loops = loops, weights = weights) } } #' @describeIn centrality Wrapper for [igraph::edge_betweenness()] #' @importFrom igraph edge_betweenness E #' @importFrom rlang enquo eval_tidy #' @export centrality_edge_betweenness <- function(weights = NULL, directed = TRUE, cutoff = NULL) { expect_edges() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA cutoff <- cutoff %||% -1 edge_betweenness(graph = graph, e = focus_ind(graph, 'edges'), directed = directed, cutoff = cutoff, weights = weights) } #' @describeIn centrality Wrapper for [igraph::harmonic_centrality()] #' @importFrom igraph harmonic_centrality #' @importFrom rlang enquo eval_tidy #' @export centrality_harmonic <- function(weights = NULL, mode = 'out', normalized = FALSE, cutoff = NULL) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA cutoff <- cutoff %||% -1 harmonic_centrality(graph, vids = focus_ind(graph, 'nodes'), mode = mode, weights = weights, normalized = normalized, cutoff = cutoff) } #' @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 #' @importFrom igraph is_directed centrality_manual <- function(relation = 'dist_sp', aggregation = 'sum', ...) { expect_netrankr() expect_nodes() graph <- .G() if (is_directed(graph)) { cli::cli_abort("Centrality measures based on the {.pkg netrankr} package only works on undirected networks") } rel <- netrankr::indirect_relations(graph, type = relation, ...) netrankr::aggregate_positions(rel, type = aggregation)[focus_ind(graph, 'nodes')] } #' @describeIn centrality `r lifecycle::badge("deprecated")` centrality based on inverse shortest path (`netrankr`) #' @export centrality_closeness_harmonic <- function() { lifecycle::deprecate_soft('1.3.0', 'centrality_closeness_harmonic()', 'centrality_harmonic()') 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(graph) ranks <- netrankr::exact_rank_prob(P) ranks$expected.rank[focus_ind(graph, 'nodes')] } tidygraph/R/select.R0000644000176200001440000000144314535605577014073 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.R0000644000176200001440000002013414556122677015615 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(focused = FALSE)) if (is.numeric(attr)) { in_attr <- enquo(in_attr) in_attr <- eval_tidy(in_attr, .N(focused = FALSE)) assortativity(graph, values = attr, values.in = in_attr, directed = directed) } else { assortativity_nominal(graph, types = as.factor(attr), directed = directed) } } #' @describeIn graph_measures Calculate the number of automorphisms of the graph. Wraps [igraph::count_automorphisms()] #' @inheritParams igraph::count_automorphisms #' @importFrom igraph count_automorphisms #' @export graph_automorphisms <- function(sh = 'fm', colors = NULL) { graph <- .G() colors <- enquo(colors) colors <- eval_tidy(colors, .N(focused = FALSE)) as.numeric(count_automorphisms(graph, colors = colors, sh = 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(focused = FALSE)) 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(focused = FALSE)) %||% 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(focused = FALSE)) 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, weights = NULL) { graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E(focused = FALSE)) %||% NA mean_distance(graph, directed = directed, unconnected = unconnected, weights = weights) } #' @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(focused = FALSE)) weights <- eval_tidy(weights, .E(focused = FALSE)) modularity(graph, group, weights) } #' @describeIn graph_measures Calculate the global efficiency of the graph #' @importFrom igraph global_efficiency #' @importFrom rlang enquo eval_tidy #' @export graph_efficiency <- function(weights = NULL, directed = TRUE) { graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E(focused = FALSE)) %||% NA global_efficiency(graph, weights = weights, directed = directed) } tidygraph/R/hclust.R0000644000176200001440000000033514535605577014115 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.R0000644000176200001440000000235314535605577014436 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) { .data <- unfocus(.data) .register_graph_context(.data) d_tmp <- as_tibble(.data) check_reserved(d_tmp) 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.R0000644000176200001440000000620214535605577014412 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) { if (is.focused_tbl_graph(.data)) { message('Unfocusing graph...') .data <- unfocus(.data) } active(.data) <- quo_text(enquo(what)) .data } #' @export #' @importFrom rlang enquo activate.grouped_tbl_graph <- function(.data, what) { what <- enquo(what) if (gsub('"', '', quo_text(what)) == active(.data)) { return(.data) } cli::cli_inform('Ungrouping {.arg .data}...') .data <- ungroup(.data) activate(.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', cli::cli_abort('Only possible to activate nodes and edges') ) 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/cpp11.R0000644000176200001440000000034414535605577013537 0ustar liggesusers# Generated by cpp11: do not edit by hand get_paths <- function(parent) { .Call(`_tidygraph_get_paths`, parent) } collect_offspring <- function(offspring, order) { .Call(`_tidygraph_collect_offspring`, offspring, order) } tidygraph/R/context.R0000644000176200001440000001225314537004413014261 0ustar liggesusers#' @importFrom R6 R6Class ContextBuilder <- R6Class( 'ContextBuilder', public = list( set = function(graph) { check_tbl_graph(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(focused = TRUE) { as_tibble(self$graph(), active = 'nodes', focused = focused) }, edges = function(focused = TRUE) { as_tibble(self$graph(), active = 'edges', focused = focused) }, 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()) { cli::cli_abort('This function should not be called directly') } } ) ) #' 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 #' #' @keywords internal #' @name graph-context #' @rdname graph-context NULL #' @rdname graph-context #' @export .graph_context <- ContextBuilder$new() expect_nodes <- function() { if (!.graph_context$free() && .graph_context$active() != 'nodes') { cli::cli_abort('This call requires nodes to be active', call. = FALSE) } } expect_edges <- function() { if (!.graph_context$free() && .graph_context$active() != 'edges') { cli::cli_abort('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. #' #' @param focused Should only the attributes of the currently focused nodes or #' edges be returned #' #' @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(focused = TRUE) { .graph_context$nodes(focused) } #' @describeIn context_accessors Get the edges data from the graph you're currently working on #' @export .E <- function(focused = TRUE) { .graph_context$edges(focused) } #' @rdname graph-context #' @export .register_graph_context <- function(graph, free = FALSE, env = parent.frame()) { check_tbl_graph(graph) if (identical(env, .GlobalEnv)) { cli::cli_abort('A context cannot be registered to the global environment') } 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) } #' @rdname graph-context #' @export .free_graph_context <- function(env = parent.frame()) { if (identical(env, .GlobalEnv)) { cli::cli_abort('A context cannot be freed in the global environment') } .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_gnp(10, 0.3) #' #' with_graph(gr, centrality_degree()) #' with_graph <- function(graph, expr) { .register_graph_context(graph, free = TRUE) expr } tidygraph/R/tidyr-utils.R0000644000176200001440000000131614535605577015104 0ustar liggesusers#' @importFrom tidyr replace_na #' @export #' replace_na.tbl_graph <- function(data, replace, ...) { d_tmp <- as_tibble(data) d_tmp <- replace_na(d_tmp, replace = replace, ...) set_graph_data(data, d_tmp) } #' @importFrom tidyr replace_na #' @export #' replace_na.morphed_tbl_graph <- function(data, replace, ...) { .data[] <- lapply(data, replace_na, replace = replace, ...) .data } #' @export tidyr::replace_na #' @importFrom tidyr drop_na #' @export #' drop_na.tbl_graph <- function(data, ...) { graph_slicer(data, drop_na, ...) } #' @importFrom tidyr drop_na #' @export #' drop_na.morphed_tbl_graph <- function(data, ...) { .data[] <- lapply(.data, drop_na, ...) .data } #' @export tidyr::drop_na tidygraph/R/bind.R0000644000176200001440000001071014535605577013525 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, ...) { .data <- unfocus(.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, ...) { .data <- unfocus(.data) check_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') { .data <- unfocus(.data) check_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 (!all(c('to', 'from') %in% names(new_edges))) { cli::cli_abort('Edges can only be added if they contain a {.col to} and {.col from} column') } 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))) { cli::cli_abort('Edges can only be added if they contain a valid {.col to} and {.col from} column') } if (max(c(new_edges$to, new_edges$from)) > gorder(.data)) { cli::cli_abort('Edges can only be added if they refer to existing nodes') } .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.R0000644000176200001440000000107514535605577013573 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. #' #' @keywords internal #' fortify.tbl_graph <- function(model, data, ...) { as_tibble(model) } rlang::on_load(register_s3_method('ggplot2', 'fortify', 'tbl_graph')) tidygraph/R/sample_frac.R0000644000176200001440000000201714535605577015066 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(), ...) { tbl <- unfocus(tbl) d_tmp <- as_tibble(tbl) weight <- enquo(weight) check_reserved(d_tmp) 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.R0000644000176200001440000000062514535605577014545 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', ...) { rlang::check_installed('data.tree', 'in order to coerce Node object to tbl_graph') direction <- if (mode == 'out') 'climb' else 'descend' as_tbl_graph(data.tree::as.igraph.Node(x, directed = directed, direction = direction, ...)) } tidygraph/R/network.R0000644000176200001440000000442714535605577014312 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) { rlang::check_installed('network', 'in order to coerce network object to tbl_graph') 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) { cli::cli_abort('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 missing_nodes <- network::network.size(graph) - gorder(new_graph) new_graph <- add_vertices(new_graph, missing_nodes) vertex_attr(new_graph) <- node_attr edge_attr(new_graph) <- edge_attr new_graph } tidygraph/R/group_by.R0000644000176200001440000000443314537776066014450 0ustar liggesusers#' @importFrom dplyr group_by #' @export group_by.tbl_graph <- function(.data, ..., add = FALSE) { if (is.focused_tbl_graph(.data)) { cli::cli_inform('Unfocusing prior to grouping') .data <- unfocus(.data) } .register_graph_context(.data) d_tmp <- as_tibble(.data) d_tmp <- group_by(d_tmp, ..., .add = add) .data <- set_graph_data(.data, ungroup(d_tmp)) apply_groups(.data, 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) <- setdiff(class(x), 'grouped_tbl_graph') unfocus(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, group) { attr(graph, paste0(active(graph), '_group_attr')) <- attributes(group) if (!is.grouped_tbl_graph(graph)) { class(graph) <- c('grouped_tbl_graph', class(graph)) } graph } tidygraph/R/group.R0000644000176200001440000002545314556122310013735 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 n_groups Integer scalar, the desired number of communities. If too low #' or two high, then an error message is given. The measure is applied to the #' full graph so the number of groups returned may be lower for focused graphs #' @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 resolution Resolution of the modularity function used internally in #' the algorithm #' @param objective_function Either `"CPM"` (constant potts model) or #' `"modularity"`. Sets the objective function to use. #' @param beta Parameter affecting the randomness in the Leiden algorithm. This #' affects only the refinement step of the algorithm. #' @param n The number of iterations to run the clustering #' @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[focus_ind(.G(), 'nodes')]) desc_enumeration(group) } #' @describeIn group_graph Group densely connected nodes using [igraph::cluster_edge_betweenness()] #' @importFrom igraph membership cluster_edge_betweenness cut_at #' @export group_edge_betweenness <- function(weights = NULL, directed = TRUE, n_groups = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA group <- cluster_edge_betweenness(graph = .G(), weights = weights, directed = directed) if (is.null(n_groups)) { group <- membership(group) } else { group <- cut_at(group, no = n_groups) } group <- as.integer(group[focus_ind(.G(), 'nodes')]) desc_enumeration(group) } #' @describeIn group_graph Group nodes by optimising modularity using [igraph::cluster_fast_greedy()] #' @importFrom igraph membership cluster_fast_greedy cut_at #' @export group_fast_greedy <- function(weights = NULL, n_groups = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA group <- cluster_fast_greedy(graph = .G(), weights = weights) if (is.null(n_groups)) { group <- membership(group) } else { group <- cut_at(group, no = n_groups) } group <- as.integer(group[focus_ind(.G(), 'nodes')]) 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()) %||% 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))[focus_ind(.G(), 'nodes')]) 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()) %||% 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))[focus_ind(.G(), 'nodes')]) 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 cut_at arpack_defaults #' @export group_leading_eigen <- function(weights = NULL, steps = -1, label = NULL, options = arpack_defaults(), n_groups = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA label <- enquo(label) label <- eval_tidy(label, .N()) group <- cluster_leading_eigen(graph = .G(), steps = steps, weights = weights, start = label, options = options) if (is.null(n_groups)) { group <- membership(group) } else { group <- cut_at(group, no = n_groups) } group <- as.integer(group[focus_ind(.G(), 'nodes')]) 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, resolution = 1) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA group <- as.integer(membership(cluster_louvain(graph = .G(), weights = weights, resolution = resolution))[focus_ind(.G(), 'nodes')]) desc_enumeration(group) } #' @describeIn group_graph Group nodes according to the Leiden algorithm ([igraph::cluster_leiden()]) which is similar, but more efficient and provides higher quality results than `cluster_louvain()` #' @importFrom igraph membership cluster_leiden #' @importFrom rlang list2 inject := #' @export group_leiden <- function(weights = NULL, resolution = 1, objective_function = 'CPM', beta = 0.01, label = NULL, n = 2, node_weights = NULL) { expect_nodes() graph <- .G() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA # `resolution_parameter` may be renamed to `resolution` in future release of igraph res_arg <- if ("resolution" %in% names(formals(igraph::cluster_leiden))) { 'resolution' } else { 'resolution_parameter' } nodes <- .N(focused = FALSE) label <- enquo(label) label <- eval_tidy(label, nodes) node_weights <- enquo(node_weights) node_weights <- eval_tidy(node_weights, nodes) args <- list2( graph = graph, objective_function = objective_function, weights = weights, {{res_arg}} := resolution, beta = beta, initial_membership = label, n_iterations = n, vertex_weights = node_weights ) group <- as.integer(membership(inject(cluster_leiden(!!!args)))[focus_ind(.G(), 'nodes')]) 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()) %||% NA group <- as.integer(membership(cluster_optimal(graph = .G(), weights = weights))[focus_ind(.G(), 'nodes')]) 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()) %||% NA group <- as.integer(membership(cluster_spinglass(graph = .G(), weights = weights, vertex = NULL, ...))[focus_ind(.G(), 'nodes')]) desc_enumeration(group) } #' @describeIn group_graph Group nodes via short random walks using [igraph::cluster_walktrap()] #' @importFrom igraph membership cluster_walktrap cut_at #' @export group_walktrap <- function(weights = NULL, steps = 4, n_groups = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA group <- cluster_walktrap(graph = .G(), weights = weights, steps = steps) if (is.null(n_groups)) { group <- membership(group) } else { group <- cut_at(group, no = n_groups) } group <- as.integer(group[focus_ind(.G(), 'nodes')]) desc_enumeration(group) } #' @describeIn group_graph Group nodes by simulating fluid interactions on the graph topology using [igraph::cluster_fluid_communities()] #' @importFrom igraph cluster_fluid_communities #' @export group_fluid <- function(n_groups = 2) { expect_nodes() graph <- .G() group <- as.integer(membership(cluster_fluid_communities(graph, n_groups))[focus_ind(graph, 'nodes')]) 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))][focus_ind(.G(), 'edges')] desc_enumeration(group) } #' @describeIn group_graph Groups nodes by their color using [igraph::greedy_vertex_coloring()]. Be aware that this is not a clustering algorithm as coloring specifically provide a color to each node so that no neighbors have the same color #' @importFrom igraph greedy_vertex_coloring #' @export group_color <- function() { expect_nodes() graph <- .G() group <- greedy_vertex_coloring(graph) group <- as.integer(group[focus_ind(.G(), 'nodes')]) 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.R0000644000176200001440000000334514535605577013720 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, ...) { rlang::check_installed('graph', 'in order to coerce graphNEL object to tbl_graph') 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) { tibble(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, ...) { rlang::check_installed('methods', 'in order to coerce graphAM object to tbl_graph') 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, ...) { rlang::check_installed('methods', 'in order to coerce graphBAM object to tbl_graph') as_tbl_graph(methods::as(x, 'graphNEL'), ...) } tidygraph/R/filter.R0000644000176200001440000000143014535605577014075 0ustar liggesusers#' @export #' @importFrom dplyr filter #' @importFrom igraph delete_vertices delete_edges filter.tbl_graph <- function(.data, ...) { .data <- unfocus(.data) .register_graph_context(.data) d_tmp <- as_tibble(.data) check_reserved(d_tmp) 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.R0000644000176200001440000001250514535605577013737 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"), ...) { x <- unfocus(x) d_tmp <- as_tibble(x) check_reserved(d_tmp) if (active(x) == 'edges' && (!all(c('from', 'to') %in% names(y)) || !(is.numeric(y$from) && is.numeric(y$to)))) { cli::cli_abort('{.arg y} must contain the {.cls numeric} columns {.col from} and {.col to}') } 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"), ...) { x <- unfocus(x) d_tmp <- as_tibble(x) check_reserved(d_tmp) 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"), ...) { x <- unfocus(x) d_tmp <- as_tibble(x) check_reserved(d_tmp) if (active(x) == 'edges' && (!all(c('from', 'to') %in% names(y)) || !(is.numeric(y$from) && is.numeric(y$to)))) { cli::cli_abort('{.arg y} must contain the {.cls numeric} columns {.col from} and {.col to}') } 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, ...) { x <- unfocus(x) d_tmp <- as_tibble(x) check_reserved(d_tmp) 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, ...) { x <- unfocus(x) d_tmp <- as_tibble(x) check_reserved(d_tmp) 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"), ...) { x <- unfocus(x) check_tbl_graph(x) y <- as_tbl_graph(y) d_tmp <- as_tibble(x, active = 'nodes') d_tmp2 <- as_tibble(y, active = 'nodes') check_reserved(d_tmp) check_reserved(d_tmp2) 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/iterate.R0000644000176200001440000000437314535605577014256 0ustar liggesusers#' Repeatedly modify a graph by a function #' #' The iterate family of functions allow you to call the same modification #' function on a graph until some condition is met. This can be used to create #' simple simulations in a tidygraph friendly API #' #' @param .data A `tbl_graph` object #' @param .f A function taking in a `tbl_graph` as the first argument and #' returning a `tbl_graph` object #' @param n The number of times to iterate #' @param cnd A condition that must evaluate to `TRUE` or `FALSE` determining if #' iteration should continue #' @param max_n The maximum number of iterations in case `cnd` never evaluates #' to `FALSE` #' @param ... Further arguments passed on to `.f` #' #' @return A `tbl_graph` object #' #' @rdname iterate #' @name iterate #' #' @examples #' # Gradually remove edges from the least connected nodes while avoiding #' # isolates #' create_notable('zachary') |> #' iterate_n(20, function(gr) { #' gr |> #' activate(nodes) |> #' mutate(deg = centrality_degree(), rank = order(deg)) |> #' activate(edges) |> #' slice( #' -which(edge_is_incident(.N()$rank == sum(.N()$deg == 1) + 1))[1] #' ) #' }) #' #' # Remove a random edge until the graph is split in two #' create_notable('zachary') |> #' iterate_while(graph_component_count() == 1, function(gr) { #' gr |> #' activate(edges) |> #' slice(-sample(graph_size(), 1)) #' }) #' NULL #' @rdname iterate #' @export #' iterate_n <- function(.data, n, .f, ...) { check_tbl_graph(.data) .f <- rlang::as_function(.f) act <- active(.data) for (i in seq_len(n)) { .data <- .f(.data, ...) check_tbl_graph(.data) } activate(.data, !!rlang::sym(act)) } #' @rdname iterate #' @export #' iterate_while <- function(.data, cnd, .f, ..., max_n = NULL) { check_tbl_graph(.data) .f <- rlang::as_function(.f) act <- active(.data) if (!is.null(max_n) && !rlang::is_integerish(max_n, 1, TRUE)) { cli::cli_abort('{.arg max_n} must either be NULL or a single integer') } cnd <- rlang::enquo(cnd) cnd <- rlang::expr(with_graph(.data, !!cnd)) n <- 1 while (isTRUE(rlang::eval_tidy(cnd)) && !isTRUE(n > max_n)) { .data <- .f(.data, ...) check_tbl_graph(.data) n <- n + 1 } activate(.data, !!rlang::sym(act)) } tidygraph/R/local.R0000644000176200001440000000614514535605577013712 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() graph <- .G() ego_size(graph = graph, order = order, nodes = focus_ind(graph, 'nodes'), 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() graph <- .G() lapply(ego(graph = graph, order = order, nodes = focus_ind(graph, 'nodes'), 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() graph <- .G() count_triangles(graph = graph, vids = focus_ind(graph, 'nodes')) } #' @describeIn local_graph Calculates the average degree based on the neighborhood of each node. Wraps [igraph::knn()]. #' @param weights An edge weight vector. For `local_ave_degree`: If this argument #' is given, the average vertex strength is calculated instead of vertex degree. #' For `local_transitivity`: if given weighted transitivity using the approach by #' *A. Barrat* will be calculated. #' @importFrom igraph knn #' @export local_ave_degree <- function(weights = NULL) { expect_nodes() weights <- enquo(weights) weights <- eval_tidy(weights, .E()) %||% NA graph <- .G() knn(graph = graph, vids = focus_ind(graph, 'nodes'), 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 = focus_ind(graph, 'nodes'), weights = weights, isolates = 'zero') } tidygraph/R/random_walk.R0000644000176200001440000000470014556124334015077 0ustar liggesusers#' Perform a random walk on the graph and return encounter rank #' #' A random walk is a traversal of the graph starting from a node and going a #' number of steps by picking an edge at random (potentially weighted). #' `random_walk()` can be called both when nodes and edges are active and will #' adapt to return a value fitting to the currently active part. As the #' walk order cannot be directly encoded in the graph the return value is a list #' giving a vector of positions along the walk of each node or edge. #' #' @param n The number of steps to perform. If the walk gets stuck before #' reaching this number the walk is terminated #' @param root The node to start the walk at. If `NULL` a random node will be #' used #' @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 weights The weights to use for edges when selecting the next step of #' the walk. Currently only used when edges are active #' #' @return A list with an integer vector for each node or edge (depending on #' what is active) each element encode the time the node/edge is encountered #' along the walk #' #' @importFrom igraph random_walk random_edge_walk gorder gsize #' @importFrom rlang enquo quo_is_null eval_tidy #' @export #' #' @examples #' graph <- create_notable("zachary") #' #' # Random walk returning node order #' graph |> #' mutate(walk_rank = random_walk_rank(200)) #' #' # Rank edges instead #' graph |> #' activate(edges) |> #' mutate(walk_rank = random_walk_rank(200)) #' random_walk_rank <- function(n, root = NULL, mode = "out", weights = NULL) { graph <- .G() if (is.null(root)) { root <- sample(gorder(graph), 1) } else { root <- as_node_ind(root, graph) } weights <- enquo(weights) if (active(graph) == "nodes") { if (!quo_is_null(weights)) { cli::cli_warn('{.arg weights} is ignored when doing a random walk on nodes') } walk <- as.integer(random_walk(graph, root, n, mode = mode)) len_out <- gorder(graph) } else { weights <- eval_tidy(weights, .E(focused = FALSE)) %||% NA walk <- as.integer(random_edge_walk(graph, root, n, weights, mode = mode)) len_out <- gsize(graph) } res <- rep(list(integer()), len_out) ord <- split(seq_along(walk), walk) res[as.integer(names(ord))] <- ord res[focus_ind(graph, active(graph))] } tidygraph/R/reroute.R0000644000176200001440000000465314556122151014270 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. If `NULL` it will use focused edges if available or all #' edges #' #' @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)) { cli::cli_inform('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 <- focus_ind(.data, '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.R0000644000176200001440000000320514535605577014111 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.md0000644000176200001440000001507514556164263013367 0ustar liggesusers# tidygraph 1.3.1 * Fix a bug in `tbl_graph()` when edge `to` and `from` where encoded as factors * Secure compitability with igraph 2.0.0 # tidygraph 1.3.0 * Add `resolution` argument to `group_louvrain()` to mirror the igraph function * `as_tbl_graph()` on an edge dataframe now only adds a name node attribute if the edges are encoded as a character (#147) * Added `node_is_connected()` to test whether a node is connected to a set of nodes (#165) * Deprecated `play_erdos_renyi()` in favour of `play_gnm()` and `play_gnp()` (#152) * Added the whole family of `slice_*()` functions from dplyr (#128) * Added methods for `tidyr::replace_na()` and `tidyr::drop_na()` (#114) * Added `edge_is_bridge()` for querying whether an edge is a bridge edge (#113) * Added a `glimpse()` method for `tbl_graph` and `morphed_tbl_graph` objects (#30) * Add `iterate_n()` and `iterate_while()` to perform repeated modifications of a graph for a specific number of times or until a condition no longer is met (#43) * Add `focus()`/`unfocus()` verbs to limit node and edge algorithms to a subset while still keeping the full graph context (#18) * Data frame subclasses with sticky columns (such as those from sf and tsibble) now works with the tbl_graph constructors (#184) * `graph_automorphisms()` gains a `color` argument in line with capabilities in igraph * `graph_mean_dist()` now supports edge weights through a new `weights` argument * Added `to_largest_component()` morpher * Added `graph_is_eulerian()` and `edge_rank_eulerian()` for eulerian path calculations * Added `to_random_spanning_tree()` morpher * Added `min_order` argument to `to_components()` morpher * Added `random_walk_rank()` to perform random walks on the graph * Added `centrality_harmonic()` + deprecated `centrality_closeness_harmonic()`. The latter is an interface to netrankr while the former is a more efficient and flexible igraph implementation. * Added `group_color()` as an interface to `greedy_vertex_coloring()` in igraph * Added `group_leiden()` to interface with `cluster_leiden()` in igraph * Added `group_fluid()` to interface with `cluster_fluid_communities()` in igraph * Added `edge_is_feedback_arc()` to interface with `feedback_arc_set()` in igraph * Added `graph_efficiency()` and `node_effeciency()` interfacing with `global_efficiency()` and `local_efficiency()` in igraph # tidygraph 1.2.3 * Small updates to work with new versions of igraph and dplyr # tidygraph 1.2.2 * Activating a grouped tbl_graph with what is already active will no longer cause grouping to be dropped (#121) * General upkeep # tidygraph 1.2.1 * Move compiled code to cpp11 * Improve messaging with rlang and cli * New feature: the following hierarchical clustering functions `group_edge_betweenness`, `group_fast_greedy`, `group_leading_eigen` and `group_walktrap` have a new argument `n_groups` that controls the numbers of groups computed. The argument expects an integer value and it is `NULL` by default. # 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/MD50000644000176200001440000001640514556176062012577 0ustar liggesusers684a9afdde7adb5eb3a4821f76c5193d *DESCRIPTION 5593031af5169ac88cc5ce4d1137daff *LICENSE efc90a8813d736f4a517c93883ef057a *NAMESPACE 9c332f0f9b432d2aa7b615dd48ccaee5 *NEWS.md ab868b48b26e1bb09c889bea5107a75f *R/aaa.R 1a8060b66d04601a20e486785d38ab9e *R/activate.R 27ffb8f1008ae0027d24d65d83563381 *R/arrange.R 7652793330b65a16c370bc806334cf53 *R/bind.R 3d20d4e1a21cdc73311f35a9d4739b70 *R/centrality.R 0a645e47f78cc5cff44382c7264a54e9 *R/context.R 574437bf8f242cc069414f6021add516 *R/cpp11.R fcbeb6c6e3962f01b3ffbcaac8acd805 *R/create.R b598a6160206d60e01afcbfc1a835a2d *R/data_frame.R 0fcad4b9e10074a6b2e0bf78a0f17aeb *R/data_tree.R ddc9ad57ebf8f4f16415acd929c9b462 *R/dendrogram.R 3d30d47e13e5f25a8dde46052df72e65 *R/distinct.R e79617f4daf34118fc54236879256181 *R/edge.R 35757904678ba0eb764536943a4f7fbc *R/edge_rank.R d4ea061b17364574d757183c5de8ea04 *R/filter.R 56e588fd50685be47ca95a1aa61937ab *R/focus.R 5e462c5862f6925c2cb860ebdd04a1ec *R/graph.R 1459a1fb658520a5861d12632ed82166 *R/graph_measures.R c65651b8e6cdaa9bc86ada8f41428edc *R/graph_types.R 9ff2cb9d77876d16232d1dac15de4fb6 *R/group.R 5c01ec62327a094ddb6a1046e3171d57 *R/group_by.R 53a6a2c27b87537e5db189c514cf8166 *R/hclust.R 82e2827289d6ba2c9db8277fa2cbe406 *R/igraph.R 925235a271b60c5b702e2b5f8c8e2b9c *R/iterate.R 97aa80a08eb8433e3e2816d1008ce967 *R/joins.R fc2deab914bbd5d79b69c747cb080e8e *R/list.R e8b8ac1e5da5c4ae88a681779b74e4ec *R/local.R f3e95b57d9851b1c30814f28316e5c61 *R/map.R 4b6abaed389cafd90b262447478aae2b *R/matrix.R 6f1a7c3d208137cbd4be2a39e243efb8 *R/morph.R a8263f68735bc19ba9815c87ac848019 *R/morphers.R fce1c13f70b59963e16eb9e0e7e3fb3e *R/mutate.R 27495f897c7f5af6bdae55fdde9d2ab2 *R/network.R c1dcc54dbe6a354c03ef674388bb3a3f *R/node.R f0a395719eb98562f683c75e8c3840fb *R/node_rank.R 6ec1f62e058feb9a1ee6a7e9986facdf *R/node_topology.R 8f5f5705b9fe5caff0a914b149c4522a *R/pair_measures.R 63bdf8a0b8a99e106fd5031f3190f379 *R/phylo.R 8ed2a1cd0625fc7723ec135cb084840c *R/play.R ca3c120dc3b2473d61d92596e7e77b94 *R/plot.R 7b7ae738f94bb977dc607cf8d0d17cec *R/pull.R 86e459de3c49e3c45534f2a90dc1f3dc *R/random_walk.R 4b5caefefb712b756abf0e9c15d98426 *R/rename.R 18a37e83d48b2a4ddbf30f7256edb96d *R/reroute.R 2fee2f553a97867069862bc58c914ad0 *R/sample_frac.R 050982f360d497f563957600e49bd0bb *R/sample_n.R d0472e3ca313ba3503118b967cc34f8a *R/search.R 2f9c5cee142fcfc15e1593119124bdb3 *R/select.R 321da55de824591914aaae99b2648f4b *R/slice.R 595afa567bdfaf9bd8183d7b93c3ddb0 *R/tbl_graph.R 508ba9ef3dc220e574ce7404a6f0dcb0 *R/tibble.R 0b49563b8c14429ffd21892100d3f2bc *R/tidygraph-package.R 32ed7c3257b8e3e7a3eb3e83d83bb35d *R/tidyr-utils.R 7321d13bd47a6ab1e543915d6283c82d *R/utils.R c191e0be276418f190f4e60639947ba2 *README.md 5b4c66c1f1eb0bec73e5b679f90a0dfc *man/activate.Rd 762bc52e809226f082b3356bb6a32fdf *man/bind_graphs.Rd b178ef5d7ff65106b20e0e6c07edefe4 *man/centrality.Rd 2faeb45c6b8ebac8115bb07483115379 *man/component_games.Rd 78fe22b26601049a11dfe1c1dab6dfba *man/context_accessors.Rd a9a6621bbf5c2074797038c8338e019c *man/create_graphs.Rd cf17cac566f6433358749f139947534d *man/edge_rank.Rd 28dbf08e0e490fec13173fd0081e2db0 *man/edge_types.Rd 9d5d922b4e96bd0c4555fa28d2622735 *man/evolution_games.Rd a1cbaf3f328e8d74e747faacf640c7fc *man/figures/lifecycle-archived.svg 6f521fb1819410630e279d1abf88685a *man/figures/lifecycle-defunct.svg 391f696f961e28914508628a7af31b74 *man/figures/lifecycle-deprecated.svg 691b1eb2aec9e1bec96b79d11ba5e631 *man/figures/lifecycle-experimental.svg 405e252e54a79b33522e9699e4e9051c *man/figures/lifecycle-maturing.svg f41ed996be135fb35afe00641621da61 *man/figures/lifecycle-questioning.svg 306bef67d1c636f209024cf2403846fd *man/figures/lifecycle-soft-deprecated.svg ed42e3fbd7cc30bc6ca8fa9b658e24a8 *man/figures/lifecycle-stable.svg bf2f1ad432ecccee3400afe533404113 *man/figures/lifecycle-superseded.svg 783835eedb18deb7b7588bd75820490f *man/figures/logo.png a55b50d3691bab2c5afd6c9866d55eeb *man/focus.Rd 0b29cc83b69904cc0c34a0641114f7b7 *man/fortify.tbl_graph.Rd 27079e2937061dc82f0de09b8f3df4e8 *man/graph-context.Rd 9079f0b59b114fbe844304ca426eaa7a *man/graph_join.Rd d744b269b74fde310650e9d60bca7859 *man/graph_measures.Rd e6f7ba68513410f83a8b1c8eb84f1e4f *man/graph_types.Rd ee66d250285ee812de94c9a84272c7bb *man/group_graph.Rd 191f419f5d352c04e99178b1624aa3cf *man/iterate.Rd c95858a9cf44de97062d8b836773031f *man/local_graph.Rd 36e3f22349940b0d7a5640531601007a *man/map_bfs.Rd d913ffc4f00436270cd9aa90140c097c *man/map_bfs_back.Rd c7ef5bfd42b53ce712af2c8635dbec57 *man/map_dfs.Rd 2be977329901f9c1f606b8f326e6168e *man/map_dfs_back.Rd e0d553c62ff4f857b7efe1787ac27b2e *man/map_local.Rd ba6a18eb64ca1a260293b5e4c6c40f12 *man/morph.Rd 1baf9d7111cf5baa679d90b65b8496c4 *man/morphers.Rd 123b6a376a3a4e01afb3bde954217559 *man/mutate_as_tbl.Rd a4c732f437329d9ec0cb53982f1a21db *man/node_measures.Rd b0ee9ae28aebe3f0d101e168c6cc8854 *man/node_rank.Rd d2758d7017226ca4fa94985e3c2e941b *man/node_topology.Rd 47c39e96e82dc2bb935786f50544a5f2 *man/node_types.Rd 613b6e3c3f85ea0a569f86d9a8cc2e21 *man/pair_measures.Rd df7b526b014293d98ebdbec3e0f868a0 *man/random_walk_rank.Rd dd7aa0a14ef1e09c32cff492e2ba2273 *man/reexports.Rd e92149fc71fb44d1348d6c9530259e85 *man/reroute.Rd 7278a8eb0427b3484ea7a25fe93da939 *man/sampling_games.Rd 81399059cb89e481ff6ed56e3ac012fc *man/search_graph.Rd ac5f2736a127653ec32f5586fd29ff97 *man/tbl_graph.Rd 1f1b66bade4b39c9198cd0854f4501fe *man/tidygraph-package.Rd e7c5129ce518f23914b864d45dc88b06 *man/type_games.Rd ac66394813f004245e01af2d4a3e2398 *man/with_graph.Rd 22de88bc3a2d108f525e5e44cc20d839 *src/cpp11.cpp d02b5dffb823592a3b8ec462726586a5 *src/get_paths.cpp eeb23c31825a29f55ac1e8864a8b8e75 *tests/testthat.R f2f8a39dec520712f603206d52602f0d *tests/testthat/helper-context.R d5a28b8f068398d66849046d92fa1c99 *tests/testthat/test-activate.R 12044edca7970930275ddf018c70f500 *tests/testthat/test-arrange.R db34f6e469c0703e9e2a622d91673d60 *tests/testthat/test-bind.R 1974ccfa3733ea69e936fa54ed1e17a1 *tests/testthat/test-centrality.R be94546d9e59d9e764169e5d0f984e48 *tests/testthat/test-context.R 65629b06c1d8861430cf065838ed4e98 *tests/testthat/test-distinct.R f269aef3f1f709334b85cab0e1c2c3d7 *tests/testthat/test-edge_types.R a0e64a34bb0bbfa72ebead33dbfcd193 *tests/testthat/test-filter.R 225c9192a4eb7cd5b8a9cc87d826a6f7 *tests/testthat/test-focus.R 27b02062f6905820855bbddfb6111329 *tests/testthat/test-graph_attributes.R 99d4fc03c6d6f280f1466ade09a41942 *tests/testthat/test-graph_measures.R 0b71ed5bc7f2b23d8a0333b72a8a995e *tests/testthat/test-group.R c8b5135d84bc2f5d6b8f96e716c8f6f0 *tests/testthat/test-group_by.R f915e1bdf20daf8f27bbca830eaaa4ce *tests/testthat/test-iterate.R e371a7e753d122149d1e841a571d2902 *tests/testthat/test-join.R 969482662fda925aad3dea3ebbe843bb *tests/testthat/test-local.R e371a7e753d122149d1e841a571d2902 *tests/testthat/test-map.R 785493e9b34977e00fa650b988f8b1f6 *tests/testthat/test-morph.R e5473a3328da23233ea6db47b7dc847e *tests/testthat/test-mutate.R 01f6346e8af59c5f3a85ea5fa4b176bf *tests/testthat/test-node_measures.R 9d5cefdfc98951c36c0f4efb41487aff *tests/testthat/test-node_types.R 4dfbf046b638b811cfa29d01641ba609 *tests/testthat/test-pair_measures.R cf230bca82238743688371d64ae59782 *tests/testthat/test-random-walk.R 8a558a481e3e1b37c6f84c5faa8aeea2 *tests/testthat/test-search.R 3650cff40f4bd9ab10190b48f2173c91 *tests/testthat/test-slice.R 797fe6a0b22aa1a568503fe0975df70e *tests/testthat/test-tidyr-utils.R