├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS.md ├── R ├── aggregateMultiplex.R ├── calcExtinctionIndices.r ├── calcFunctionalIndices.r ├── calcInteractionIntensity.r ├── calcTopologicalIndices.r ├── data.r ├── models.r ├── modularity.R ├── plotNetwork.r ├── plotNetworkGgplot.R ├── plot_multiplex.R ├── readNetwork.r ├── shuffling.R ├── supraAdjacencyMatrix.R └── svdcentrality.R ├── README.md ├── data ├── metadata.rda └── netData.rda ├── inst └── extdata │ ├── BarentsBoreal_FW.csv │ ├── Kefi2015_chilean_NTIneg.txt │ ├── Kefi2015_chilean_NTIpos.txt │ ├── Kefi2015_chilean_TI.txt │ ├── WeddellSea_FW.csv │ └── carpinteria_FW.txt ├── man ├── BuildSupraAdjacencyMatrixFromExtendedEdgelist.Rd ├── add_interlayer_links.Rd ├── aggregate_multiplex_network.Rd ├── calc_QSS.Rd ├── calc_QSS_extinction_dif.Rd ├── calc_QSS_extinction_dif_grp.Rd ├── calc_QSS_extinctions_seq.Rd ├── calc_centrality.Rd ├── calc_incoherence.Rd ├── calc_interaction_intensity.Rd ├── calc_interaction_intensity2.Rd ├── calc_modularity.Rd ├── calc_modularity_swness_zscore.Rd ├── calc_quantitative_connectance.Rd ├── calc_svd_entropy.Rd ├── calc_svd_entropy_importance.Rd ├── calc_swness_zscore.Rd ├── calc_topological_indices.Rd ├── calc_topological_roles.Rd ├── calc_weighted_topological_indices.Rd ├── classify_topological_roles.Rd ├── convert_infomap_result_to_muxviz.Rd ├── convert_to_intra_format.Rd ├── convert_to_supra_adjacency.Rd ├── curve_ball.Rd ├── fromGLVadjToIgraph.Rd ├── fromIgraphToMgraph.Rd ├── generate_er_basal.Rd ├── generate_niche.Rd ├── generate_shuffled_seq.Rd ├── generate_shuffled_seq_tol.Rd ├── harmonize_node_sets.Rd ├── metadata.Rd ├── netData.Rd ├── plot_multi3D.Rd ├── plot_multiplex_modules.Rd ├── plot_troph_level.Rd ├── plot_troph_level_ggplot.Rd ├── readMultiplex.Rd ├── readNetwork.Rd ├── run_infomap.Rd ├── run_infomap_multi.Rd ├── shuffle_network_deg.Rd ├── shuffle_network_ws.Rd ├── toGLVadjMat.Rd └── write_multilayer_network.Rd ├── multiweb.Rproj ├── testPackage.r ├── test_multi3D.r └── tests ├── testthat.R └── testthat ├── test-calc_modularity.R ├── test-calc_topological_indices.R ├── test-calc_topological_roles.R ├── test-topological.R └── testReadNetwork.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^testPackage\.r$ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | **/.DS_Store 6 | **/_snaps 7 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: multiweb 2 | Type: Package 3 | Title: Ecological network analyses including multiplex networks 4 | Version: 0.8.30.0000 5 | Authors@R: c( 6 | person("Leonardo A.", "Saravia", email = "lsaravia@conicet.gov.ar", role ="cre"), 7 | person("Susanne", "Kortsch", email = "susanne.kortsch@helsinki.fi", role ="ctb"), 8 | person("Tomas I.", "Marina", email = "tomasimarina@gmail.com", role ="ctb"), 9 | person("Iara D.", "Rodriguez", email = "irodriguez@campus.ungs.edu.ar", role ="ctb")) 10 | Description: This package calculates indices for ecological network analyses. 11 | Depends: R (>= 3.5.0) 12 | License: MIT + file LICENSE 13 | Encoding: UTF-8 14 | LazyData: true 15 | Imports: 16 | igraph (>= 1.1.2), 17 | NetIndices (>= 1.4.4), 18 | RColorBrewer (>= 1.1-2), 19 | doParallel (>= 1.0.10), 20 | doFuture (>= 0.6.0), 21 | future.apply (>= 1.2.0), 22 | dplyr (>= 0.8.0.1), 23 | kSamples (>= 1.2-9), 24 | doRNG (>= 1.8), 25 | ggraph (>= 2.2.1), 26 | rgl (>= 1.3.17) 27 | RoxygenNote: 7.2.3 28 | Suggests: 29 | testthat (>= 3.0.0) 30 | Roxygen: list(markdown = TRUE) 31 | URL: https://github.com/lsaravia/multiweb 32 | BugReports: https://github.com/lsaravia/multiweb/issues 33 | Config/testthat/edition: 3 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Leonardo A. Saravia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(aggregate_multiplex_network) 4 | export(calcIncoherence) 5 | export(calcModularitySWnessZScore) 6 | export(calcTopologicalIndices) 7 | export(calc_QSS) 8 | export(calc_QSS_extinction_dif) 9 | export(calc_QSS_extinction_dif_grp) 10 | export(calc_QSS_extinctions_seq) 11 | export(calc_centrality) 12 | export(calc_incoherence) 13 | export(calc_interaction_intensity) 14 | export(calc_interaction_intensity2) 15 | export(calc_modularity) 16 | export(calc_modularity_swness_zscore) 17 | export(calc_quantitative_connectance) 18 | export(calc_svd_entropy) 19 | export(calc_svd_entropy_importance) 20 | export(calc_swness_zscore) 21 | export(calc_topological_indices) 22 | export(calc_topological_roles) 23 | export(calc_weighted_topological_indices) 24 | export(classify_topological_roles) 25 | export(convert_infomap_result_to_muxviz) 26 | export(convert_to_intra_format) 27 | export(convert_to_supra_adjacency) 28 | export(curveBall) 29 | export(curve_ball) 30 | export(fromGLVadjToIgraph) 31 | export(fromIgraphToMgraph) 32 | export(generateERbasal) 33 | export(generate_er_basal) 34 | export(generate_niche) 35 | export(generate_shuffled_seq) 36 | export(generate_shuffled_seq_tol) 37 | export(harmonize_node_sets) 38 | export(plotTrophLevel) 39 | export(plot_multi3D) 40 | export(plot_multiplex_modules) 41 | export(plot_troph_level) 42 | export(plot_troph_level_ggplot) 43 | export(readMultiplex) 44 | export(readNetwork) 45 | export(run_infomap) 46 | export(run_infomap_multi) 47 | export(shuffle_network_deg) 48 | export(shuffle_network_ws) 49 | export(toGLVadjMat) 50 | export(write_multilayer_network) 51 | import(Matrix) 52 | import(dplyr) 53 | import(ggplot2) 54 | import(ggraph) 55 | import(ggrepel) 56 | import(igraph) 57 | import(rgl) 58 | import(rlang) 59 | import(tibble) 60 | import(tidygraph) 61 | import(tidyr) 62 | import(viridis) 63 | importFrom(NetIndices,TrophInd) 64 | importFrom(RColorBrewer,brewer.pal) 65 | importFrom(doFuture,registerDoFuture) 66 | importFrom(dplyr,"%>%") 67 | importFrom(dplyr,arrange) 68 | importFrom(dplyr,bind_rows) 69 | importFrom(dplyr,dense_rank) 70 | importFrom(dplyr,filter) 71 | importFrom(dplyr,group_by) 72 | importFrom(dplyr,if_else) 73 | importFrom(dplyr,inner_join) 74 | importFrom(dplyr,left_join) 75 | importFrom(dplyr,mutate) 76 | importFrom(dplyr,rename) 77 | importFrom(dplyr,row_number) 78 | importFrom(dplyr,select) 79 | importFrom(dplyr,summarise_all) 80 | importFrom(foreach,"%dopar%") 81 | importFrom(foreach,foreach) 82 | importFrom(future,multisession) 83 | importFrom(future,sequential) 84 | importFrom(future.apply,future_lapply) 85 | importFrom(ggplot2,element_blank) 86 | importFrom(ggplot2,element_text) 87 | importFrom(ggplot2,geom_curve) 88 | importFrom(ggplot2,geom_point) 89 | importFrom(ggplot2,geom_segment) 90 | importFrom(ggplot2,geom_vline) 91 | importFrom(ggplot2,ggplot) 92 | importFrom(ggplot2,labs) 93 | importFrom(ggplot2,scale_color_viridis_c) 94 | importFrom(ggplot2,theme) 95 | importFrom(ggplot2,theme_bw) 96 | importFrom(ggrepel,geom_text_repel) 97 | importFrom(grid,unit) 98 | importFrom(igraph,E) 99 | importFrom(igraph,V) 100 | importFrom(igraph,as_edgelist) 101 | importFrom(igraph,average.path.length) 102 | importFrom(igraph,cluster_spinglass) 103 | importFrom(igraph,components) 104 | importFrom(igraph,count_components) 105 | importFrom(igraph,degree) 106 | importFrom(igraph,ecount) 107 | importFrom(igraph,get.adjacency) 108 | importFrom(igraph,graph_from_adjacency_matrix) 109 | importFrom(igraph,graph_from_data_frame) 110 | importFrom(igraph,induced_subgraph) 111 | importFrom(igraph,is_directed) 112 | importFrom(igraph,transitivity) 113 | importFrom(igraph,vcount) 114 | importFrom(tibble,as_tibble) 115 | importFrom(tibble,tibble) 116 | importFrom(viridis,viridis) 117 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # Version 0.8.30.0000 2 | 3 | * add aggregate_multiplex_network() function to aggregate a multiplex network into a single layer network 4 | 5 | # Version 0.8.20.0000 6 | 7 | * add plot_multiplex_modules() function to plot the modules of a multiplex network 8 | 9 | # Version 0.8.10.0000 10 | 11 | * Improve plot_multi3D() and added harmonized_node_sets to add all the nodes to all layers, needed for plot_multi3D(). 12 | 13 | # Version 0.8.00.0000 14 | 15 | * Added functions 16 | 17 | - plot_multi3D() was adapted from package muxViz. 18 | - convert_infomap_result_to_muxviz() to convert the output of `run_infomap_multi()` to 19 | muxViz-Compatible Format. 20 | 21 | # Version 0.7.90.0000 22 | 23 | * The function plot_troph_level_ggplot() was updated to use return the ggplot2 object. 24 | 25 | # Version 0.7.80.0000 26 | 27 | * Added functions 28 | - run_infomap_multi to run the python implementation of Infomap for node aligned multiplex networks 29 | - Other aux functions like write_multilayer_network() convert_to_intra_format() 30 | 31 | # Version 0.7.40.0000 32 | 33 | * Added functions 34 | - run_infomap to run the python implementation of Infomap 35 | - calc_centrality instead of calc_eigencentrality to calculate the centrality of a network using any defined measure 36 | 37 | 38 | # Version 0.7.30.0000 39 | 40 | * Added functions calc_svd_entropy shuffle_network_deg shuffle_network_deg_svd calc_eigencentrality calc_svd_entropy_importance 41 | 42 | # Version 0.7.10.0000 43 | 44 | * Added function generate_niche to generate a niche model with a given number of species and connectance 45 | 46 | 47 | # Version 0.7.01.0000 48 | 49 | * Added function plot_troph_level_ggplot() with improved network visualization 50 | 51 | 52 | # Version 0.7.00.0000 53 | 54 | * Added function calc_interaction_intensity2() with estimation of interaction strength using O'Gorman 2010 method 55 | 56 | 57 | # Version 0.6.9.0000 58 | 59 | * Updated function calc_interaction_intensity() with more error checking and warnings 60 | 61 | 62 | # Version 0.6.8.0000 63 | 64 | * Updated function calc_QSS_extinctions_seq() and calc_QSS_extinction_dif_grp() to return the raw values of the QSS simulations, KA and AD test are not performed. 65 | 66 | # Version 0.6.7.0000 67 | 68 | * Updated function calc_QSS_extinction_dif() to return the raw values of the QSS simulations and the difference between them, KA and AD test are not performed, the null model with mean IS is also deleted. 69 | 70 | 71 | # Version 0.6.67.0000 72 | 73 | * Updated function calc_topological_roles() to accept a community object 74 | 75 | # Version 0.6.66.0000 76 | 77 | * Updated function classify_topological_roles() when all species are module specialists 'modspe' 78 | 79 | 80 | # Version 0.6.65.0000 81 | 82 | * Updated README with function fromIgraphToMgraph 83 | 84 | * Fixed calc_QSS for mgraph objects 85 | 86 | # Version 0.6.61.0000 87 | 88 | * Fixed warmings of function calc_topological_roles. 89 | 90 | # Version 0.6.60.0000 91 | 92 | * Add function calc_QSS_extinction_dif_grp to calculate the difference in QSS with a group of species. 93 | 94 | 95 | # Version 0.6.50.0000 96 | 97 | * Updated version of calc_interaction_intensity() with different sd for the normal distributions used 98 | for simulations around the mean of the coeficients estimated by Pawar 2012. 99 | 100 | # Version 0.6.31.0000 101 | 102 | * Updated documentation of calc_interaction_intensity function 103 | 104 | * Add @importFrom kSamples to calc_QSS_extinction_dif 105 | 106 | # Version 0.6.30.0000 107 | 108 | * Updated defaults of calc_QSS(), now the parameters defaults are `negative=-10, positive=1, selfDamping=-1` 109 | equivalent to a ecological transfer efficieny of 10%. 110 | Previusly they were `negative=-10, positive=.1, selfDamping=-1`. 111 | 112 | * Updated documentation with better explication of calc_interaction_intensity function 113 | -------------------------------------------------------------------------------- /R/aggregateMultiplex.R: -------------------------------------------------------------------------------- 1 | #' Aggregate a Multiplex Network into a Single Layer 2 | #' 3 | #' Combines a list of `igraph` layers into one aggregate network. 4 | #' Edges present in multiple layers will have their weights summed. 5 | #' 6 | #' @param g_list A list of `igraph` objects (directed or undirected). 7 | #' @param directed Logical; whether to treat the result as directed (default: `TRUE`). 8 | #' 9 | #' @return An `igraph` object representing the aggregate network. 10 | #' 11 | #' @import igraph 12 | #' @export 13 | aggregate_multiplex_network <- function(g_list, directed = TRUE) { 14 | if (!all(sapply(g_list, igraph::is.igraph))) stop("All elements of g_list must be igraph objects.") 15 | 16 | # Combine all node names 17 | all_nodes <- unique(unlist(lapply(g_list, function(g) V(g)$name))) 18 | 19 | # Create empty graph with all nodes 20 | g_aggr <- make_empty_graph(directed = directed) %>% 21 | add_vertices(length(all_nodes), name = all_nodes) 22 | 23 | # Merge edges across layers 24 | edge_df_list <- lapply(g_list, function(g) { 25 | el <- igraph::as_data_frame(g, what = "edges") 26 | if (is.null(el$weight)) el$weight <- 1 27 | el 28 | }) 29 | 30 | combined_edges <- do.call(rbind, edge_df_list) 31 | 32 | # Sum weights of duplicate edges 33 | edge_sum <- combined_edges %>% 34 | group_by(from, to) %>% 35 | summarise(weight = sum(weight), .groups = "drop") 36 | 37 | # Add edges to the aggregate graph 38 | g_aggr <- add_edges(g_aggr, t(as.matrix(edge_sum[, c("from", "to")]))) 39 | E(g_aggr)$weight <- edge_sum$weight 40 | 41 | return(g_aggr) 42 | } 43 | -------------------------------------------------------------------------------- /R/calcExtinctionIndices.r: -------------------------------------------------------------------------------- 1 | 2 | #' Calculates the QSS difference between the full network and the network minus 3 | #' one species 4 | #' 5 | #' The Quasi-sign stability is estimated with of the community matrix (Jacobian) and characterizes 6 | #' the linear stability of the network. This uses the function [multiweb::calc_QSS()] so it can take 7 | #' into account the interaction strength if weights are present. 8 | #' 9 | #' @param g igraph network 10 | #' @param sp_list list with the species/nodes we will delete for the comparison 11 | #' @param nsim number of simulations to calculate QSS 12 | #' @param ncores number of cores used to perform the operation 13 | #' @param istrength if TRUE takes the weight attribute of the network as interaction strength to 14 | #' calculate QSS. 15 | #' 16 | #' @return a data.frame with nsim rows for each species deleted with: 17 | #' * the node deleted 18 | #' * The QSS of the complete network for nsim simulations 19 | #' * The QSS of the network with the deleted node for nsim simulations 20 | #' * The difference between the two previous QSS 21 | #' 22 | #' 23 | #' @import igraph 24 | #' @importFrom dplyr bind_rows 25 | #' @export 26 | #' 27 | #' @examples 28 | #' \dontrun{ 29 | #' g <- netData[[1]] 30 | #' 31 | #' # Generate random weights 32 | #' # 33 | #' V(g)$weight <- runif(vcount(g)) 34 | #' 35 | #' # Without interaction strength 36 | #' # 37 | #' calc_QSS_extinction_dif(g,V(g)$name[1:3],nsim=10,istrength = FALSE) 38 | #' 39 | #' # With interaction strength 40 | #' # 41 | #' calc_QSS_extinction_dif(g,V(g)$name[1:3],nsim=10,istrength = TRUE) 42 | #'} 43 | 44 | calc_QSS_extinction_dif <- function(g, sp_list,nsim=1000, ncores=4, istrength = FALSE){ 45 | 46 | if( istrength ) { 47 | if( length(edge_attr_names(g))==0 ) { 48 | stop("Network must have weight attribute") 49 | } else { 50 | if( !any(edge_attr_names(g) %in% "weight") ) 51 | stop("Network must have weight attribute") 52 | } 53 | } 54 | 55 | # QSS for complete and deleted network 56 | QSS_all <- multiweb::calc_QSS(g, nsim = nsim, ncores = ncores, istrength = istrength, returnRaw = TRUE) %>% 57 | mutate(Network = "All spp") 58 | 59 | comp_webs <- lapply(sp_list, function(i){ 60 | # delete one sp and create igraph object 61 | g_ext <- delete_vertices(g, i) 62 | # size <- vcount(g_ext) 63 | # subset mean strength for deleted sp 64 | QSS_ext <- multiweb::calc_QSS(g_ext, nsim = nsim, ncores = ncores, istrength = istrength, returnRaw = TRUE) %>% 65 | mutate(Network = "One sp less") 66 | #QSS <- bind_rows(QSS_all, QSS_ext) 67 | 68 | data.frame(Deleted = i, 69 | QSS_all=QSS_all$maxre, QSS_ext=QSS_ext$maxre, difQSS = QSS_all$maxre-QSS_ext$maxre) 70 | 71 | }) 72 | bind_rows(comp_webs) 73 | } 74 | 75 | 76 | #' Calculates the QSS difference between the full network and the network minus 77 | #' a group of species 78 | #' 79 | #' The Quasi-sign stability is estimated with the maximum eigenvalue of the community matrix (Jacobian) and characterizes 80 | #' the linear stability of the network. This uses the function [multiweb::calc_QSS()] so it can take 81 | #' into account the interaction strength if weights are present. The comparison is made using the Anderson-Darling test with 82 | #' the function [kSamples::ad.test()] and the Kolmogorov-Smirnov test [stats::ks.test()], both the p-values are reported as a 83 | #' measure of strength of the difference. 84 | #' 85 | #' @param g igraph network 86 | #' @param sp_list list with the group pf species/nodes we will delete for the comparison 87 | #' @param nsim number of simulations to calculate QSS 88 | #' @param ncores number of cores used to perform the operation 89 | #' @param istrength if TRUE takes the weight attribute of the network as interaction strength to 90 | #' calculate QSS. 91 | #' 92 | #' @return a data.frame with: 93 | #' * Size of the network with deleted nodes 94 | #' * Connectance of the network with deleted nodes 95 | #' * Number of components of the network with deleted nodes 96 | #' * QSS of the complete network 97 | #' * QSS of the network with the deleted nodes 98 | #' * difference between the two previous QSS 99 | #' 100 | #' @import igraph 101 | #' @importFrom dplyr bind_rows 102 | #' @export 103 | #' 104 | #' @examples 105 | #' \dontrun{ 106 | #' g <- netData[[1]] 107 | #' 108 | #' # Generate random weights 109 | #' # 110 | #' V(g)$weight <- runif(vcount(g)) 111 | #' 112 | #' # Without interaction strength 113 | #' # 114 | #' calc_QSS_extinction_dif_grp(g,V(g)$name[1:3],nsim=10,istrength = FALSE) 115 | #' 116 | #' # With interaction strength 117 | #' # 118 | #' calc_QSS_extinction_dif_grp(g,V(g)$name[1:3],nsim=10,istrength = TRUE) 119 | #'} 120 | 121 | calc_QSS_extinction_dif_grp <- function(g, sp_list,nsim=1000, ncores=4, istrength = FALSE){ 122 | 123 | if( istrength ) { 124 | if( length(edge_attr_names(g))==0 ) { 125 | stop("Network must have weight attribute") 126 | } else { 127 | if( !any(edge_attr_names(g) %in% "weight") ) 128 | stop("Network must have weight attribute") 129 | } 130 | } 131 | 132 | # QSS for complete and deleted network 133 | QSS_all <- multiweb::calc_QSS(g, nsim = nsim, ncores = ncores, istrength = istrength, returnRaw = TRUE) %>% 134 | mutate(Network = "All spp") 135 | 136 | # delete one sp and create igraph object 137 | g_ext <- delete_vertices(g, sp_list) 138 | con <- multiweb::calc_topological_indices(g_ext)$Connectance 139 | comp <- multiweb::calc_topological_indices(g_ext)$Components 140 | size <- vcount(g_ext) 141 | # Calculates the QSS 142 | QSS_ext <- multiweb::calc_QSS(g_ext, nsim = nsim, ncores = ncores, istrength = istrength, returnRaw = TRUE) %>% 143 | mutate(Network = "Minus Group") 144 | # QSS <- bind_rows(QSS_all, QSS_ext) 145 | # extract p-value for Anderson-Darling test comparing complete and deleted network 146 | # ad_test <- kSamples::ad.test(maxre ~ Network, data = QSS) 147 | # ks_test <- ks.test(QSS_all$maxre,QSS_ext$maxre) 148 | 149 | # result data frame 150 | 151 | data.frame(Size = size, Connectance = con, Components = comp, 152 | #Ad_pvalue=ad_test$ad[1,3], 153 | #KS_pvalue=ks_test$p.value, 154 | QSS_all=QSS_all$maxre, QSS_ext=QSS_ext$maxre, difQSS = QSS_all$maxre-QSS_ext$maxre) 155 | 156 | } 157 | 158 | #' Calculates the QSS for an extinction sequence 159 | #' 160 | #' This functions calculates the QSS [multiweb::calc_QSS()] for a sequence of incremental deletions of species (nodes) 161 | #' given by the vector `seq`, it does not produce secondary extinctions 162 | #' 163 | #' @param g_del igraph object for the deletion sequence 164 | #' @param seq vector or list with the nodes for the extinction sequence, the 165 | #' extinctions are incremental. 166 | #' @param nsim number of simulations used in QSS calculation 167 | #' @param ncores number of cores used to calculate QSS 168 | #' @param istrength parameter istrength for QSS function 169 | #' 170 | #' @return data frame with the number of remaining nodes, the connectance, the number unconnected of components, the median QSS, and the name 171 | #' of the last deleted node. 172 | #' @export 173 | #' @import igraph 174 | #' @importFrom dplyr %>% mutate 175 | #' 176 | #' @examples 177 | #' \dontrun{ 178 | #' g <- netData[[1]] 179 | #' 180 | #' # Generate random weights 181 | #' # 182 | #' V(g)$weight <- runif(vcount(g)) 183 | #' 184 | #' # Without interaction strength 185 | #' # 186 | #' calc_QSS_extinctions_seq(g,V(g)$name[1:3],nsim=10,istrength = FALSE) 187 | #' 188 | #' # With interaction strength 189 | #' # 190 | #' calc_QSS_extinctions_seq(g,V(g)$name[1:3],nsim=10,istrength = TRUE) 191 | #'} 192 | 193 | calc_QSS_extinctions_seq <- function(g_del, seq, nsim=1000, ncores=4, istrength=FALSE){ 194 | lseq <- length(seq) 195 | if(lseq ==vcount(g_del)){ # if the secuence is equal to the number of nodes delete the last component 196 | seq <- seq[1:(lseq-1)] 197 | } 198 | con <- multiweb::calc_topological_indices(g_del)$Connectance 199 | comp <- multiweb::calc_topological_indices(g_del)$Components 200 | QSS_all <- multiweb::calc_QSS(g_del, nsim = nsim, ncores = ncores, istrength = istrength, returnRaw = TRUE) %>% 201 | mutate(Size = vcount(g_del), Connectance = con, Components = comp, Last_deleted = "Total") 202 | 203 | qdel <- lapply(seq, function(i){ 204 | g_del <<- delete_vertices(g_del, i) 205 | size <- vcount(g_del) 206 | con <- multiweb::calc_topological_indices(g_del)$Connectance 207 | comp <- multiweb::calc_topological_indices(g_del)$Components 208 | QSS <- multiweb::calc_QSS(g_del, nsim = nsim, ncores = ncores, istrength = istrength, returnRaw = TRUE) %>% 209 | mutate(Size = size, Connectance = con, Components = comp, Last_deleted = i) 210 | }) 211 | qdel <- bind_rows(QSS_all,qdel) 212 | } 213 | 214 | -------------------------------------------------------------------------------- /R/calcFunctionalIndices.r: -------------------------------------------------------------------------------- 1 | #' Calculates the quantitative connectance, and the effective number of flows using the interaction matrix and a vector of species abundances 2 | #' 3 | #' The Quantitative connectance (Cq) takes into account both the distribution of per capita interaction strengths 4 | #' among species in the web and the distribution of species’ abundances and quantifies the diversity of network fluxes, 5 | #' if all the species have the same flux is equal to the directed connectance. 6 | #' The mean or effective number of flows impinging upon or emanating from a tipical node (LDq) is based the average flow diversity, and when all flows are equal is similar to linkage density. Both measures are based in Shannon information theory. 7 | #' The total interaction flux is measured 8 | #' as ```T[i,j] <- d[i] * d[j] * interM[i,j]```. The effective Cq is calculated following the formulas in appendix 2 of [1], LDq follows [2] 9 | #' 10 | #' @references 11 | #' 1. Fahimipour, A.K. & Hein, A.M. (2014). The dynamics of assembling food webs. Ecol. Lett., 17, 606–613 12 | #' 13 | #' 1. Ulanowicz, R.E. & Wolff, W.F. (1991). Ecosystem flow networks: Loaded dice? Math. Biosci., 103, 45–68 14 | #' 15 | #' @param interM per capita interaction strength matrix 16 | #' @param d species' abundances vector 17 | #' 18 | #' @return A list with Cq,the quantitative connectance index and LDq, = 19 | #' 20 | #' @aliases calcQuantitativeConnectance 21 | #' 22 | #' @export 23 | #' 24 | #' @examples 25 | #' 26 | #' # 3 predators 2 preys unequal fluxes 27 | #' # 28 | #' m <- matrix() 29 | #' matrix(0,nrow=4,ncol=4) 30 | #' m[1,2] <- m[1,3] <- m[3,4]<- .2 31 | #' m[2,1] <- m[3,1] <- m[4,3] <- -2 32 | #' 33 | #' calc_quantitative_connectance(m, c(1,1,1,1)) 34 | #' 35 | #'# Equal input and output fluxes 36 | #' 37 | #'m <- matrix(0,nrow=4,ncol=4) 38 | #'m[1,2] <- m[1,3] <- m[3,4]<- 2 39 | #'m[2,1] <- m[3,1] <- m[4,3] <- -2 40 | #'calc_quantitative_connectance(m, c(1,1,1,1)) 41 | 42 | 43 | calc_quantitative_connectance <- function(interM,d){ 44 | 45 | nsp <- length(d) # number of species 46 | if(ncol(interM)!=nrow(interM)) stop("interM has to be square, number of rows has to be equal to number of columns") 47 | if(nsp!=nrow(interM)) stop("length of species vector has to be equal to interM dimensions") 48 | 49 | TT <- matrix(0,nsp,nsp) 50 | SS <- matrix(0,nsp,nsp) 51 | 52 | Hout <- numeric(nsp) 53 | Hin <- numeric(nsp) 54 | 55 | for(i in seq_along(d)) { 56 | for(j in seq_along(d)) { 57 | TT[i,j] <- abs(interM[i,j])*d[i]*d[j] 58 | } 59 | } 60 | TTcol <- colSums(TT) # Input 61 | TTrow <- rowSums(TT) # Output 62 | TTT <- sum(TTrow) 63 | 64 | for(k in seq_along(d)) { 65 | for(i in seq_along(d)) { 66 | SS[i,k] <- TT[i,k]/TTT * log2(TT[i,k]^2/(TTrow[i]*TTcol[k])) 67 | if(TT[i,k]!=0){ 68 | Hin[k] <- Hin[k] - TT[i,k]/TTcol[k]*log(TT[i,k]/TTcol[k]) 69 | } 70 | if(TT[k,i]!=0){ 71 | Hout[k] <- Hout[k] - TT[k,i]/TTrow[k]*log(TT[k,i]/TTrow[k]) 72 | } 73 | } 74 | } 75 | # SS1 is the \Sigma separated in input and output components see Ulanowicz eq 3 76 | # (SS1 <- (sum(TTcol*Hin)+sum(TTrow*Hout))/TTT) == sum(SS) 77 | # 78 | nout <- exp(Hout) 79 | nout[TTrow==0] <- 0 80 | nin <- exp(Hout) 81 | nin[TTcol==0] <- 0 82 | LDq <- 2^(-sum(SS,na.rm = T)/2) # C= linkage Density / S 83 | Cq <- 1/(2*nsp^2)*(sum(nout+nin)) # 84 | return(list(Cq=Cq,LDq=LDq)) 85 | } 86 | 87 | calcQuantitativeConnectance <- function(interM,d){ 88 | calc_quantitative_connectance(interM,d)} 89 | 90 | 91 | #' Calc the Quasi Sign Stability measure for antagonistic (predator-prey) or mgraph networks with multiple interactions. 92 | #' 93 | #' The QSS measure is the proportion of matrices that are locally stable, these matrices are created by sampling the values of the community matrix 94 | #' (the Jacobian) from a uniform distribution, preserving the sign structure [1]. If the 'ig' parameter is 95 | #' an `mgraph` network it needs to have been built with the order `c("Competitive", "Mutualistic", "Trophic")` 96 | #' It also calculates the mean of the real part of the maximum eingenvalue, which is also a measure of stability [2]. 97 | #' It uses a uniform distribution between 0 and maximum values given by the parameters `negative`, `positive` and `selfDamping`, 98 | #' corresponding to the sign of interactions and self-limitation effect [3,4]. 99 | #' If the edges of the networks have a weigth attribute and `istrength` parameter is true, weigth will be used as interaction strength, 100 | #' then the limits of the uniform distribution will be `negative*-x`, `positive*x`, `selfDamping*x`, where x is the value of the weigth for the edge. 101 | #' If the values of these parameters are 0 then there is no interaction of that kind. The default values for `negative`, `positive` and `selfDumping` 102 | #' assume a maximum ecological transfer efficience of 10%. 103 | #' 104 | #' @references 105 | #' 106 | #' 1. Allesina, S. & Pascual, M. (2008). Network structure, predator - Prey modules, and stability in large food webs. 107 | #' Theor. Ecol., 1, 55–64. 108 | #' 2. Grilli, J., Rogers, T. & Allesina, S. (2016). Modularity and stability in ecological communities. Nat. Commun., 7, 12031 109 | #' 3. Monteiro, A.B. & Del Bianco Faria, L. (2017). Causal relationships between population stability and food-web topology. Functional Ecology, 31, 1294–1300. 110 | #' 4. Borrelli, J. J. 2015. Selection against instability: stable subgraphs are most frequent in empirical food webs. - Oikos 124: 1583–1588. 111 | #' 112 | #' @param ig igraph or a list of igraph networks or mgraph network 113 | #' @param nsim number of simulations to calculate QSS, if the number of simulations is 1 then it calculates the maximum eingenvalue for the mean of interaction 114 | #' strength, if `istrength==FALSE` the mean interaction strength are `negative/2`, `positive/2`, `selfDamping/2`. 115 | #' @param ncores number of cores to use in parallel comutation if 0 it uses sequential processing 116 | #' @param negative the maximum magnitude of the negative interaction (the effect of the predator on the prey) must be <= 0 117 | #' @param positive the maximum magnitude of the positive interaction (the effect of the prey on the predator) must be >= 0 118 | #' @param selfDamping the maximum magnitude of the self-limitation (the effect of the species on itself) must be <= 0, only for species with links to itself. 119 | #' @param istrength If TRUE takes the weigth attribute of the network as interaction strength. 120 | #' @param returnRaw if TRUE returns all the values of the maximum eingenvalues 121 | #' 122 | #' @return if parameter `returnRaw` is `FALSE` returns a data.frame with the QSS, and MEing, the mean of the real part of the maximum eingenvalue. 123 | #' If `returnRaw` is `TRUE` it returns the values of randomized real part of maximum eingenvalue (maxre) 124 | #' 125 | #' @export 126 | #' 127 | #' @import igraph 128 | #' @importFrom future sequential multisession 129 | #' @importFrom future.apply future_lapply 130 | #' @importFrom dplyr bind_rows 131 | #' 132 | #' @examples 133 | #' \dontrun{ 134 | #' 135 | #' g <- netData[[2]] 136 | #' 137 | #' tp <- calc_QSS(g) 138 | #' 139 | #' # Read Multiplex network and calculate QSS 140 | #' 141 | #' fileName <- c(system.file("extdata", package = "multiweb")) 142 | #' dn <- list.files(fileName,pattern = "^Kefi2015.*\\.txt$") 143 | #' gt <- readMultiplex(dn,types,"inst/extdata", skipColumn = 2) 144 | #' calc_QSS(gt) 145 | #' 146 | #' } 147 | calc_QSS <- function(ig,nsim=1000,ncores=0,negative=-10, positive=1, selfDamping=-1,istrength=FALSE,returnRaw=FALSE) { 148 | if(inherits(ig,"igraph")) { 149 | ig <- list(ig) 150 | } else if(class(ig[[1]])!="igraph") { 151 | stop("parameter ig must be an igraph object") 152 | } 153 | if(negative>0 || positive<0 || selfDamping>0) 154 | stop("Parameters should be negative<=0, positive>=0, selfDamping<=0") 155 | 156 | if(ncores) { 157 | cn <- future::availableCores() 158 | if(ncores>cn) 159 | ncores <- cn 160 | future::plan(multisession, workers=ncores) 161 | on.exit(future::plan(sequential)) 162 | } else { 163 | future::plan(sequential) 164 | } 165 | 166 | if(class(ig)!='mgraph') { 167 | df <- lapply(ig, function(red) 168 | { 169 | lred <- fromIgraphToMgraph(list(make_empty_graph(n=vcount(red)),make_empty_graph(n=vcount(red)),red), 170 | c("empty","empty","Antagonistic")) 171 | mat <- toGLVadjMat(lred,c("empty","empty","Antagonistic"),istrength = istrength) # 172 | 173 | eing <- aux_calc_QSS(nsim,mat,negative,positive,selfDamping) 174 | 175 | if(returnRaw) 176 | ret <- data.frame(maxre = eing) 177 | else 178 | ret <- data.frame(QSS=sum(eing<0)/nsim,MEing=mean(eing)) 179 | 180 | }) 181 | } else { 182 | mat <- toGLVadjMat(ig,c("Competitive", "Mutualistic", "Trophic"),istrength = istrength) # 183 | 184 | eing <- aux_calc_QSS(nsim,mat,negative,positive,selfDamping) 185 | 186 | if(returnRaw) 187 | df <- data.frame(maxre = eing) 188 | else 189 | df <- data.frame(QSS=sum(eing<0)/nsim,MEing=mean(eing)) 190 | 191 | } 192 | return(bind_rows(df)) 193 | } 194 | 195 | # Auxiliar function of calc_QSS to calculate max eingenvalue 196 | # 197 | # If the number of simulations nsim == 1 it calculates the eingenvalue of the matrix as is 198 | # 199 | aux_calc_QSS <- function(nsim,mat,negative,positive,selfDamping) 200 | { 201 | if(nsim>1) { 202 | df <- future_lapply(seq_len(nsim), function(i){ 203 | ranmat <- ranUnif(mat,negative,positive,selfDamping) 204 | eigs <- maxRE(ranmat) 205 | },future.seed = TRUE) 206 | eing <- do.call(rbind,df) 207 | } else { 208 | newmat <- apply(mat, c(1,2), function(x){ 209 | if(x>0){ x*positive/2 } else if(x<0){-x*negative/2} else{0}}) 210 | diag(newmat) <- sapply(diag(mat), function(x) ifelse(x>0, x*selfDamping/2,0)) 211 | eing <- maxRE(newmat) 212 | 213 | } 214 | } 215 | 216 | # Auxiliar function of calc_QSS, Calculates a random community mattrix with a fixed signed structure 217 | # 218 | ranUnif <- function(motmat, negative=-10,positive=0.1,selfDamping=-1){ 219 | newmat <- apply(motmat, c(1,2), function(x){ 220 | if(x>0){runif(1, 0, x*positive)}else if(x<0){runif(1,-x*negative, 0)} else{0} 221 | }) 222 | diag(newmat) <- sapply(diag(motmat), function(x) ifelse(x>0, runif(1,x*selfDamping,0),0)) 223 | # 224 | return(newmat) 225 | } 226 | 227 | # Auxiliar function of calc_QSS, Compute the eigenvalues and return the largest real part. 228 | # 229 | maxRE <- function(rmat){ 230 | lam.max <- max(Re(eigen(rmat)$values)) 231 | return(lam.max) 232 | } 233 | 234 | 235 | #' Function to calculate weighted network indices 236 | #' 237 | #' The function calculates: weighted linkage density, connectance, generality and vulnerability and the SDs of the last two based on [1] 238 | #' and the level of omnivory, mean and maximum trophic level based on [2] using [NetIndices::TrophInd()] function. 239 | #' The igraph networks must have the weight attribute. 240 | #' 241 | #' @param ig An igraph object with the weight attribute representing fluxes 242 | #' @param ncores number of cores used to compute in parallel, if 0 sequential processing is used. 243 | #' 244 | #' @references 245 | #' 246 | #' 1. Bersier, LF. et al. (2002). Quantitative Descriptors Of Food-web Matrices. Ecology, 83(9), 2394–2407. 247 | #' 2. Kones, JK. et al. (2009). Are network indices robust indicators of food web functioning? A Monte Carlo approach. 248 | #' Ecological Modelling, 220, 370–382. 249 | #' 250 | #' 251 | #' @return a data.frame with the following fields: 252 | #' 253 | #' * LD: linkage density 254 | #' * Connectance: directed Connectance 255 | #' * TLmean: mean trophic level 256 | #' * TLmax: maximum trophic level 257 | #' * LOmnivory: Level of omnivory, quantifies mean of the variety in trophic levels of the preys of a consumer 258 | #' * Vulnerability: mean of number of consumers per prey 259 | #' * VulSD: the standard deviation of normalized Vulnerability 260 | #' * Generality: mean number of prey per consumer 261 | #' * GenSD: the standard deviation of normalized Generality 262 | #' 263 | #' 264 | #' @export 265 | #' @importFrom NetIndices TrophInd 266 | #' @import igraph 267 | #' @importFrom foreach foreach %dopar% 268 | #' @importFrom doFuture registerDoFuture 269 | #' @importFrom future sequential multisession 270 | #' 271 | #' @examples 272 | #' 273 | #' # Generate a test network 274 | #' 275 | #' g <- graph_from_literal( 1 -+ 4 -+ 7,2 -+ 5 -+7, 3-+6-+7, 7-+7, 4+-3, simplify = FALSE) 276 | #' 277 | #' # Add weight 278 | #' 279 | #' E(g)$weight <- sample(c(.1,.2,.8,.9),gsize(g),replace=TRUE) 280 | #' 281 | #' calc_weighted_topological_indices(g) 282 | 283 | calc_weighted_topological_indices<- function(ig,ncores=0){ 284 | 285 | if(inherits(ig,"igraph")) { 286 | ig <- list(ig) 287 | } else if(class(ig[[1]])!="igraph") { 288 | stop("parameter ig must be an igraph object") 289 | } 290 | 291 | registerDoFuture() 292 | if(ncores) { 293 | cn <- future::availableCores() 294 | if(ncores>cn) 295 | ncores <- cn 296 | future::plan(multisession, workers=ncores) 297 | on.exit(future::plan(sequential)) 298 | } else { 299 | future::plan(sequential) 300 | } 301 | 302 | 303 | df <- foreach(g=ig,.combine='rbind',.inorder=FALSE,.packages=c('igraph','NetIndices')) %dopar% { 304 | 305 | W.net <- as_adjacency_matrix(g,sparse=FALSE,attr="weight") 306 | res<-c() 307 | # The flux matrix 308 | 309 | ### Taxon-specific Shannon indices of inflows 310 | # sum of k species inflows --> colsums 311 | sum.in<-apply(W.net, 2, sum) 312 | 313 | # Diversity of k species inflows 314 | H.in.mat<- t(t(W.net)/sum.in)*t(log(t(W.net)/sum.in)) #columns divided by the total col sum 315 | H.in.mat[!is.finite(H.in.mat)] <- 0 #converts NaN to 0's 316 | H.in<- apply(H.in.mat, 2, sum)*-1 317 | 318 | ### Taxon-specific Shannon indices of outflows 319 | # sum of k speies outflows --> rowsums 320 | sum.out<-apply(W.net, 1, sum) 321 | 322 | # Diversity of k species outflows 323 | H.out.mat<- (W.net/sum.out)*log(W.net/sum.out) #rows divided by the total row sum 324 | H.out.mat[!is.finite(H.out.mat)] <- 0 #converts NaN to 0's 325 | H.out<- apply(H.out.mat, 1, sum)*-1 326 | 327 | # Effective number of prey or resources = N(R,k) 328 | # The reciprocal of H(R,k) --> N (R,k) is the equivalent number of prey for species k 329 | # N.res<-exp(H.in) 330 | N.res<-ifelse(sum.in==0, H.in, exp(H.in)) 331 | 332 | # Effective number of predators or consumers = N(C,k) 333 | # The reciprocal of H(C,k) --> N (C,k) is the equivalent number of predators for species k 334 | # N.con<-exp(H.out) 335 | N.con<-ifelse(sum.out==0, H.out, exp(H.out)) 336 | 337 | ### Quantitative Weighted and unweighted link density and weighted connectance 338 | no.species<-ncol(W.net) 339 | 340 | # The weighted link density (LDw) is: 341 | # In the weighted version the effective number of predators for species i is weighted by i's 342 | # contribution to the total outflow 343 | # the same is the case for the inflows 344 | 345 | tot.mat<- sum(W.net) 346 | # LD.w <- (sum((sum.in/tot.mat)*N.res) + sum((sum.out/tot.mat)*N.con))/2 347 | # equivalent to next formula, but next one is closer to manuscript 348 | res$qLD.w <- 1/(2*tot.mat)*(sum(sum.in*N.res) + sum(sum.out*N.con)) 349 | 350 | # Weighted connectance 351 | res$qC.w<- res$qLD.w/no.species 352 | 353 | # defintion according to Bersier et al. 2002 top species = [0.99, 1] 354 | # int.sp<- pos.ind[pos.ind>0&pos.ind<0.99]# intermediate are all species that are not basal nor top 355 | 356 | # weighted quantitative Generality 357 | res$qG.w<-sum(sum.in*N.res/sum(W.net)) 358 | 359 | # weighted quantitative Vulnerability 360 | res$qV.w<-sum(sum.out*N.con/sum(W.net)) 361 | 362 | # Standard deviation of stanardized unweighted quantitative Generality 363 | s<-no.species # length(pos.ind) 364 | 365 | # Standard deviation of stanardized weighted quantitative Generality 366 | res$qGsd.w<-sd(s*sum.in*N.res/sum(N.res*sum.in)) 367 | 368 | # Standard deviation of stanardized weighted quantitative Vulnerability 369 | res$qVsd.w<-sd(s*sum.out*N.con/sum(N.con*sum.out)) 370 | 371 | # Weigthed trophic levels 372 | # 373 | wtl <- TrophInd(W.net) 374 | 375 | wOmn <- mean(wtl$OI) # Omnivory Level 376 | 377 | data.frame( wLD=res$qLD.w, wConnectance=res$qC.w, wTLmean=mean(wtl$TL),wTLmax=max(wtl$TL),wLOmnivory=wOmn, 378 | wVulnerability=res$qV.w,wVulSD=res$qVsd.w,wGenerality=res$qG.w,wGenSD=res$qGsd.w) 379 | } 380 | return(df) 381 | 382 | } 383 | 384 | 385 | 386 | 387 | -------------------------------------------------------------------------------- /R/calcInteractionIntensity.r: -------------------------------------------------------------------------------- 1 | #' Calculates the interaction intensity of a food web using the metabolic theory and 2 | #' the interaction dimensionality 3 | #' 4 | #' The function uses the body mass in Kg of predator/consumer and prey/resources and the dimensionality of the interaction as source data, 5 | #' then the interaction intensity is estimated with all the coefficients from Pawar (2012) as `alfa*xR*mR/mC`, where `alpha` is the search rate `xR` 6 | #' the resource density, `mR` the resource body mass and `mC` the consumer body mass. This value of the interaction strength quantifies 7 | #' the effect of the predator on the prey by biomass unit of the predator. Assuming a Lotka-Volterra model is equivalent to the entry A(i,j) of the community matrix, where i is the 8 | #' prey and j the predator. 9 | #' 10 | #' If the resource density is unknown (parameter `res_den`) you could set the column to a less than 0 value; and it 11 | #' will be estimated according to the equation S18 and supplementary figures 2i & j (individuals/m2 - m3) 12 | #' 13 | #' If the mean mass of the resource for detritus or sediment (parameter `res_mm`) is unknown, it can be designated as 14 | #' negative. This will result in the calculation of the resource body mass (in kilograms) using allometric formulas given 15 | #' in the Equation S9 and Supplementary Figures 2c & d from the paper. This is only valid when the size ratios tend 16 | #' to be optimal. 17 | #' 18 | #' If the Biomass of the resource is known you should use it as `res_mm` and set `res_den` to 1. This is the best choice to 19 | #' avoid the previous allometric calculations of `res_den` and `res_mm` when they are unknown. 20 | #' 21 | #' If resource size `res_mm` and resource density `res_den` are decoupled from consumer size you could assign 1 to both see 22 | #' pag 487 **Dimensionality and trophic interaction strengths** in Pawar's paper. 23 | #' 24 | #' If the parameter 'nsims > 1 ' the function will estimate the variability on each interaction strength. It takes random values from a normal distribution 25 | #' with mean and standard deviation given by the Pawar's regressions for the slopes of allometric exponents. 26 | #' 27 | #' @references 28 | #' 1. Pawar, S., Dell, A. I., & Van M. Savage. (2012). Dimensionality of consumer search space drives trophic interaction strengths. Nature, 486, 485. https://doi.org/10.1038/nature11131 29 | #' 30 | #' @param da data.frame with the interactions body mass and type of interaction dimensionality 31 | #' @param res_mm name of the column with the resource body mass mean 32 | #' @param res_den name of the column with the resource density in Individuals/m^2 in 2D or m^3 in 3D. If lower than 0 it uses the previously mentioned 33 | #' estimation. 34 | #' @param con_mm name of the column with the consumer body mass mean 35 | #' @param int_dim name of the column with the interaction dimensionality 36 | #' 37 | #' @return A data.frame based on `da` with the following fields added 38 | #' 39 | #' * mR: if `res_mm<0` is the resource mass calculated with the equations from ref 1, 40 | #' if `res_mm>0` duplicates the value of `res_mm` 41 | #' * xR: calculated resource density or the same value as in the input data.frame. 42 | #' * alfa: calculated search rate. 43 | #' * qRC: calculated trophic interaction strength as `alfa*xR*mR/mC` where `mC` is the consumer body mass. 44 | #' 45 | #' @importFrom dplyr %>% filter mutate if_else bind_rows 46 | #' @export 47 | #' 48 | #' @examples 49 | #' \dontrun{ 50 | #' g <- netData[[1]] 51 | #' 52 | #' require(dplyr) 53 | #' 54 | #' # build the data.frame with random values 55 | #' 56 | #' set.seed(7815) 57 | #' da <- as_long_data_frame(g) %>% dplyr::select(from:to) %>% mutate(con_mm=rlnorm(n(),5,2),res_mm=con_mm - 30 ,int_dim=sample(c("2D","3D"),n(),replace=TRUE), res_den = -999) 58 | #' 59 | #' calc_interaction_intensity(da,res_mm,res_den,con_mm,int_dim, nsims=1) 60 | #' } 61 | 62 | calc_interaction_intensity <- function(da,res_mm,res_den,con_mm,int_dim, nsims=1) 63 | { 64 | # 65 | # if( typeof(wedd_df$interaction_dim) != "character") 66 | # stop("int_dim must be character type") 67 | # 68 | # resource Mass vs consumer Mass Exponents (Sup Fig 2 c&d) 69 | pm2D = 0.73 # 0.67 # 95% CI 0.17 sd = SE * sqrt(n) / 1.96, n = 39 (total n overestimate SD) 70 | pm3D = 0.92 # 1.46 # 95% CI 0.12 71 | int_pm2D = -2.6 # intercepts 72 | int_pm3D = -2.96 73 | # Minimun Resource Density 74 | # 75 | px2D <- -0.79 # 95% CI ± 0.09 Fig 2 i&j n = 123 (total n) 76 | px3D <- -0.86 # 95% CI ± 0.06 n = 131 77 | x02D <- -2.67 78 | x03D <- -2.48 79 | # Search Rate alfa scaling 80 | # 81 | al2D <- 0.68 # CI 0.12 n = 255 (we use the total n reported in the paper which overestimates the SD) 82 | al3D <- 1.05 # CI 0.08 n = 255 83 | int_al2D <- -3.08 84 | int_al3D <- -1.77 85 | 86 | det <- da %>% filter(is.na({{int_dim}})) 87 | if( nrow(det) > 0 ) warning(paste("Interaction dimensionality is not defined for", nrow(det), "rows")) 88 | det <- da %>% filter({{res_mm}}<0,{{res_den}}<0) 89 | if( nrow(det) > 0 ) warning(paste("Resource size res_mm and resource density res_den are both < 0 for", nrow(det), "rows\n and the estimationc will be highly uncertain")) 90 | det <- da %>% filter({{res_mm}}==0) 91 | if( nrow(det) > 0 ) warning(paste("If resource size res_mm == 0 the interaction strength will be 0 for", nrow(det), "rows")) 92 | 93 | det <- da %>% filter({{con_mm}}<0 ) 94 | if( nrow(det) > 0 ) stop(paste("Consumer size con_mm cannot be < 0 for", nrow(det), "rows")) 95 | 96 | 97 | if(nsims == 1 ){ 98 | d2D <- filter(da, {{int_dim}}=="2D") %>% 99 | mutate(mR=if_else({{res_mm}} < 0, 10^int_pm2D*{{con_mm}}^pm2D , {{res_mm}} ), 100 | xR=if_else({{res_den}} < 0 , 10^x02D * mR^px2D,{{res_den}}), 101 | alfa=10^int_al2D *{{con_mm}}^al2D, 102 | qRC = alfa*xR*mR/{{con_mm}}) 103 | 104 | d3D <- filter(da, {{int_dim}}=="3D") %>% 105 | mutate(mR=if_else({{res_mm}} < 0, 10^int_pm3D*{{con_mm}}^pm3D , {{res_mm}} ), 106 | xR=if_else({{res_den}} < 0 , 10^x03D * mR^px3D,{{res_den}}), 107 | alfa = 10^int_al3D * {{con_mm}}^al3D, 108 | qRC = alfa*xR*mR/{{con_mm}} 109 | ) 110 | da <- bind_rows(d2D,d3D) 111 | } else { 112 | # 113 | # 114 | da <- lapply(seq_len(nsims), function(x){ 115 | pm2D <- rnorm(1,0.73, 0.10) # We assume that the values reported are the SD or 95% CI, in that last case we may be overestimating the true sd 116 | pm3D <- rnorm(1,0.92, 0.08) # 95% CI 0.12 117 | px2D <- rnorm(1,-0.79, 0.09) # 95% CI ± 0.09 n = 123 118 | px3D <- rnorm(1,-0.86, 0.06) # 95% CI ± 0.06 n = 131 119 | al2D <- rnorm(1,0.68, 0.12) # CI 0.12 n = 127 (we use the total n=255 / 2 ) 120 | al3D <- rnorm(1,1.05, 0.08) # CI 0.08 n = 128 121 | d2D <- filter(da, {{int_dim}}=="2D") %>% 122 | mutate(mR=if_else({{res_mm}} < 0, 10^int_pm2D*{{con_mm}}^pm2D , {{res_mm}} ), 123 | xR=if_else({{res_den}} < 0 , 10^x02D * mR^px2D,{{res_den}}), 124 | alfa=10^int_al2D *{{con_mm}}^al2D, 125 | qRC = alfa*xR*mR/{{con_mm}}) 126 | 127 | d3D <- filter(da, {{int_dim}}=="3D") %>% 128 | mutate(mR=if_else({{res_mm}} < 0, 10^int_pm3D*{{con_mm}}^pm3D , {{res_mm}} ), 129 | xR=if_else({{res_den}} < 0 , 10^x03D * mR^px3D,{{res_den}}), 130 | alfa = 10^int_al3D * {{con_mm}}^al3D, 131 | qRC = alfa*xR*mR/{{con_mm}} 132 | ) 133 | bind_rows(d2D,d3D) 134 | 135 | }) 136 | da <- bind_rows(da) 137 | } 138 | return(da) 139 | # 140 | } 141 | 142 | 143 | #' Calculates the interaction intensity of a food web using allometric scaling relationships 144 | #' to approximate basal metabolic rate 145 | #' 146 | #' The function uses the body mass of predator/consumer and the number of prey/resources as source data, 147 | #' then the interaction intensity is estimated based on O'Gorman (2010) as `a_ij = -b*(M_jˆ0.25 / s_j)`, where `M_j` is the body mass of predator j, 148 | #' `s_j` is the number of preys, `b` is a free parameter assumed 0.01. This value of the interaction strength quantifies 149 | #' the effect of the predator on the prey per unit of biomass. Assuming a Lotka-Volterra model is equivalent 150 | #' to the entry A(i,j) of the community matrix, where i is the 151 | #' prey and j the predator. To predict the direct effect of each prey on its predator, `a_ji`, 152 | #' we could assume an ecological efficiency, e=0.1, reflecting a 10% transfer of energy between trophic levels, 153 | #' hence `a_ji = e * a_ij` 154 | #' 155 | #' The function provides two output formats: an edge list with only the **effect of predators on prey** or 156 | #' an adjacency matrix as a positive number. 157 | #' 158 | #' @references 159 | #' O’Gorman, E. J., Jacob, U., Jonsson, T., & Emmerson, M. C. (2010). Interaction strength, food web topology and the 160 | #' relative importance of species in food webs. Journal of Animal Ecology, 79(3), 682–692. https://doi.org/10.1111/j.1365-2656.2009.01658.x 161 | #' 162 | #' @param edge_list A tibble containing three columns: consumer, resource, and body mass. 163 | #' @param consumer_n The column name for consumers (predators). 164 | #' @param resource_n The column name for resources (prey). 165 | #' @param bodymass_n The column name for body mass of the consumer. 166 | #' @param b Free parameter (default `0.01`). 167 | #' @param output_format Output type: `"edgelist"` (default, containing only predator effects) or `"matrix"` (full adjacency matrix). 168 | #' 169 | #' @return A tibble (if `output_format = "edgelist"`) with interaction strengths **only for predators on prey** 170 | #' on a column named `qRC`. If `output_format = "matrix"`, an adjacency matrix with interaction strengths. 171 | #' If `output_format = "igraph" `, an igraph object where edge weights represent predator effects on prey. 172 | #' Always the IS is a positive number. 173 | #' @import dplyr tidyr tibble rlang 174 | #' @export 175 | #' 176 | #' @examples 177 | #' library(tibble) 178 | #' 179 | #' # Define an edge list (consumer, resource, bodymass) 180 | #' edge_list <- tibble( 181 | #' predator = c("A", "A", "B", "C"), 182 | #' prey = c("B", "C", "D", "D"), 183 | #' predator_mass = c(10, 10, 5, 3) # Body mass for consumers 184 | #' ) 185 | #' 186 | #' # Compute interaction strength as an edge list (predator effects only) 187 | #' interaction_strength_edgelist <- calc_interaction_intensity2( 188 | #' edge_list, consumer_n = predator, resource_n = prey, bodymass_n = predator_mass, output_format = "edgelist" 189 | #' ) 190 | #' print(interaction_strength_edgelist) 191 | #' 192 | #' # Compute interaction strength as an adjacency matrix 193 | #' interaction_strength_matrix <- calc_interaction_intensity2( 194 | #' edge_list, predator, prey, predator_mass, output_format = "matrix" 195 | #' ) 196 | #' print(interaction_strength_matrix) 197 | calc_interaction_intensity2 <- function(edge_list, consumer_n, resource_n, bodymass_n, 198 | b = 0.01, e = 0.1, output_format = "edgelist") { 199 | # library(dplyr) 200 | # library(tidyr) 201 | # library(tibble) 202 | # library(rlang) 203 | 204 | # Ensure input has correct columns 205 | col_names <- colnames(edge_list) 206 | if (!all(c(as_name(enquo(consumer_n)), as_name(enquo(resource_n)), as_name(enquo(bodymass_n))) %in% col_names)) { 207 | stop("Input edge list must contain the specified consumer, resource, and body mass columns.") 208 | } 209 | 210 | # Compute number of unique resources for each consumer 211 | data <- edge_list %>% 212 | group_by({{consumer_n}}) %>% 213 | mutate(n_res_taxonomy = n_distinct({{resource_n}})) %>% 214 | ungroup() %>% 215 | mutate(qRC = b * {{bodymass_n}}^(-0.25) / n_res_taxonomy) # Calculate interaction strength 216 | 217 | # Select only the effect of predators on prey 218 | interaction_strength <- data %>% 219 | select({{consumer_n}}, {{resource_n}}, qRC) 220 | 221 | # Return edge list format (only predator effect) 222 | if (output_format == "edgelist") { 223 | return(interaction_strength) 224 | } 225 | 226 | # Convert to adjacency matrix format 227 | if (output_format == "matrix") { 228 | # Create a unique list of species (both consumers and resources) 229 | species <- unique(c(interaction_strength[[as_name(enquo(consumer_n))]], 230 | interaction_strength[[as_name(enquo(resource_n))]])) 231 | 232 | # Initialize an empty adjacency matrix 233 | C <- matrix(0, nrow = length(species), ncol = length(species), dimnames = list(species, species)) 234 | 235 | # Populate matrix with interaction strengths 236 | for (i in 1:nrow(interaction_strength)) { 237 | C[interaction_strength[[as_name(enquo(resource_n))]][i], 238 | interaction_strength[[as_name(enquo(consumer_n))]][i]] <- interaction_strength$qRC[i] # Prey effect 239 | } 240 | 241 | return(C) 242 | } 243 | 244 | # Convert to igraph format 245 | if (output_format == "igraph") { 246 | g <- graph_from_data_frame( 247 | interaction_strength %>% select({{resource_n}},{{consumer_n}}, qRC) %>% rename(weight = qRC), 248 | directed = TRUE 249 | ) 250 | return(g) 251 | } 252 | 253 | stop("Invalid output_format. Choose 'edgelist' or 'matrix'.") 254 | } 255 | 256 | -------------------------------------------------------------------------------- /R/data.r: -------------------------------------------------------------------------------- 1 | 2 | #' Metadata for `netData` 3 | #' 4 | #' A data.frame with extended information about the 29 networks from (1), included in `netData` 5 | #' 6 | #' @format A data frame 7 | #' \describe{ 8 | #' \item{Network}{Name of the network} 9 | #' \item{Longitude}{Longitude of the geographical coordinates of the network} 10 | #' \item{Latitude}{Latitude of the geographical coordinates of the network} 11 | #' \item{Date}{Date range or approximate date when the data was collected} 12 | #' \item{InteractionsSource}{Method used to determine the interactions} 13 | #' \item{Reference}{Reference of the study from where data was obtained} 14 | #' \item{ReferenceLink}{URL of the reference} 15 | #' } 16 | #' 17 | #' @source \url{https://doi.org/10.1371/journal.pone.0198217} 18 | #' 19 | #' 1. Marina, T. I., Saravia, L. A., Cordone, G., Salinas, V., Doyle, S. R., & Momo, F. R. (2018). Architecture of marine food webs: To be or not be a ‘small-world.’ PLoS ONE, 13(5), 1–13. https://doi.org/10.1371/journal.pone.0198217 20 | #' 21 | "metadata" 22 | 23 | 24 | #' Compilation of Ecological Networks (food webs) in igraph format. 25 | #' 26 | #' A dataset 29 curated and highly-resolved marine networks (food webs), updated from (1) 27 | #' 28 | #' @format A list with 29 igraph network objects 29 | #' 30 | #' @source \url{https://doi.org/10.1371/journal.pone.0198217} 31 | #' 32 | #' @references 33 | #' 34 | #' 1. Marina, T. I. et al. 2018. Architecture of marine food webs: To be or not be a ‘small-world.’ - PLoS ONE 13: 1–13. 35 | "netData" 36 | -------------------------------------------------------------------------------- /R/models.r: -------------------------------------------------------------------------------- 1 | 2 | #' Curbe ball algorithm to generate random networks with given an igraph network 3 | #' 4 | #' This extract the adjacency matrix from an igraph object and generates a randomized version 5 | #' of the network with the same row and column totals, the results have the same in and out degree sequence 6 | #' than the original network. The diagonals are not avoided so it can generate self-links or cannibalism in 7 | #' the context of food-webs. In the case that the network has multiple components (or disconnected networks) 8 | #' the algorithm simulates around the same number of components, if the original network has 1 component 9 | #' the algorithm enforces that the results have all 1 component. If the edge attribute weight is present 10 | #' and istrength=TRUE then the weigth is additionally randomized keeping the column sum equal to the original matrix. 11 | #' 12 | #' Based on: 13 | #' 14 | #' @references Strona, G. et al. 2014. A fast and unbiased procedure to randomize ecological binary matrices with 15 | #' fixed row and column totals. -Nat. Comm. 5: 4114. doi: 10.1038/ncomms5114 16 | #' 17 | #' @aliases curveBall 18 | #' @param g igraph object to extract adjacency matrix 19 | #' @param nsim number of generated random networks 20 | #' @param istrength if TRUE the edge attribute weight is taken as interaction strength and randomized keeping the column sum equal to the original matrix. 21 | #' 22 | #' @return a list of randomized igraph objects 23 | #' @export 24 | #' 25 | #' @examples 26 | #' 27 | #' curve_ball(netData[[1]]) 28 | #' 29 | curve_ball<-function(g,nsim=1000,istrength=FALSE){ 30 | stopifnot(class(g)=="igraph") 31 | 32 | m <- get.adjacency(g,sparse=FALSE) 33 | 34 | if(components(g)$no>1) { 35 | 36 | nets<- lapply(1:nsim, function (x) { 37 | # repeat { 38 | RC <- dim(m) 39 | R <- RC[1] 40 | C <- RC[2] 41 | hp <- list() 42 | for (row in 1:dim(m)[1]) {hp[[row]] <- (which(m[row,]==1))} 43 | l_hp <- length(hp) 44 | for (rep in 1:5*l_hp){ 45 | AB <- sample(1:l_hp,2) 46 | a <- hp[[AB[1]]] 47 | b <- hp[[AB[2]]] 48 | ab <- intersect(a,b) 49 | l_ab <- length(ab) 50 | l_a <- length(a) 51 | l_b <- length(b) 52 | if ((l_ab %in% c(l_a,l_b))==FALSE){ 53 | tot <- setdiff(c(a,b),ab) 54 | l_tot <- length(tot) 55 | tot <- sample(tot, l_tot, replace = FALSE, prob = NULL) 56 | L <- l_a-l_ab 57 | hp[[AB[1]]] <- c(ab,tot[1:L]) 58 | hp[[AB[2]]] <- c(ab,tot[(L+1):l_tot])} 59 | 60 | } 61 | rm <- matrix(0,R,C) 62 | for (row in 1:R){rm[row,hp[[row]]] <- 1} 63 | 64 | g <- graph_from_adjacency_matrix(rm,mode="directed") 65 | return(g) 66 | }) 67 | } else { #if the the networks has only one component enforce that in the simulations 68 | 69 | nets<- lapply(1:nsim, function (x) { 70 | repeat { 71 | RC <- dim(m) 72 | R <- RC[1] 73 | C <- RC[2] 74 | hp <- list() 75 | for (row in 1:dim(m)[1]) {hp[[row]] <- (which(m[row,]==1))} 76 | l_hp <- length(hp) 77 | for (rep in 1:5*l_hp){ 78 | AB <- sample(1:l_hp,2) 79 | a <- hp[[AB[1]]] 80 | b <- hp[[AB[2]]] 81 | ab <- intersect(a,b) 82 | l_ab <- length(ab) 83 | l_a <- length(a) 84 | l_b <- length(b) 85 | if ((l_ab %in% c(l_a,l_b))==FALSE){ 86 | tot <- setdiff(c(a,b),ab) 87 | l_tot <- length(tot) 88 | tot <- sample(tot, l_tot, replace = FALSE, prob = NULL) 89 | L <- l_a-l_ab 90 | hp[[AB[1]]] <- c(ab,tot[1:L]) 91 | hp[[AB[2]]] <- c(ab,tot[(L+1):l_tot])} 92 | 93 | } 94 | rm <- matrix(0,R,C) 95 | for (row in 1:R){rm[row,hp[[row]]] <- 1} 96 | 97 | g <- graph_from_adjacency_matrix(rm,mode="directed") 98 | if(components(g)$no==1) 99 | break 100 | } 101 | return(g) 102 | }) 103 | } 104 | 105 | if(istrength) { 106 | m <- get.adjacency(g,sparse=FALSE,attr="weight") 107 | r <- dim(m)[1] 108 | c <- dim(m)[2] 109 | 110 | wnets<- lapply(nets, function (x) { 111 | m1 <- get.adjacency(x,sparse=FALSE) 112 | for(i in seq_len(c) ){ 113 | ss <- sample(m[,i]) 114 | ss <- ss[ss>0] 115 | k <- 1 116 | for( j in seq_len(r)){ 117 | if(m1[j,i]>0 ) { 118 | m1[j,i] <- ss[k] 119 | k <- k+1 120 | } 121 | } 122 | } 123 | graph_from_adjacency_matrix(m1,mode="directed",weighted = "weight") 124 | }) 125 | } else { 126 | return(nets) 127 | } 128 | } 129 | 130 | #' @export 131 | curveBall<-function(g,nsim=1000,istrength=FALSE){curve_ball(g,nsim,istrength)} 132 | 133 | 134 | 135 | #' Generate directed Erdos-Renyi random networks with at least 1 basal node and only one component 136 | #' 137 | #' This uses the igraph's function sample_gnm to generate nsim random networks with the same number of nodes 138 | #' and links than the parameter ig and two restrictions: 139 | #' 1) at least one basal species/node, that is a species that has no prey, 2) 1 connected component so there is no 140 | #' disconnected species or sub-community. 141 | #' 142 | #' @param ig igraph object with parameters to use in the random network simulations: number of species/nodes 143 | #' and number of links/edges 144 | #' @param nsim number of simulations 145 | #' 146 | #' @aliases generateERbasal 147 | #' 148 | #' @return a list with igraph objects 149 | #' @export 150 | #' 151 | #' @examples 152 | #' 153 | #' generateERbasal(netData[[1]]) 154 | generate_er_basal <- function(ig,nsim=1000){ 155 | if(!is_igraph(ig)) 156 | stop("Parameter ig must be an igraph object") 157 | 158 | size <- vcount(ig) 159 | 160 | links <- ecount(ig) 161 | 162 | er <- lapply(1:nsim, function (x) { 163 | e <- sample_gnm(size, links, directed = TRUE) 164 | basal <- length(V(e)[degree(e,mode="in")==0]) 165 | while(components(e)$no>1 | basal==0){ 166 | e <- sample_gnm(size, links,directed = TRUE) 167 | basal <- length(V(e)[degree(e,mode="in")==0]) 168 | } 169 | return(e) }) 170 | } 171 | 172 | #' @export 173 | generateERbasal <- function(ig,nsim=1000){generate_er_basal(ig,nsim)} 174 | 175 | 176 | #' Generate Niche Model Food Web(s) 177 | #' 178 | #' This function generates one or multiple food webs using the **Niche Model** proposed by Williams & Martinez (2000). 179 | #' The model assumes that each species has a niche value and consumes resources within a defined range. 180 | #' 181 | #' @param S Integer. The number of species in the community. Must be `S > 1`. 182 | #' @param C Numeric. The connectance (fraction of realized links). Must be `0 < C ≤ 1`. 183 | #' @param nsim Integer. The number of networks to generate (`nsim ≥ 1`). 184 | #' 185 | #' @return If `nsim = 1`, returns a **binary adjacency matrix (`S × S`)**. 186 | #' If `nsim > 1`, returns a **list of `igraph` objects**, each representing a food web. 187 | #' @export 188 | #' 189 | #' @references 190 | #' Williams, R. J., and N. D. Martinez. 2000. Simple rules yield complex food webs. *Nature* 404:180–183. 191 | #' 192 | #' @examples 193 | #' generate_niche(20, 0.1) # Single adjacency matrix 194 | #' generate_niche(20, 0.1, nsim=5) # List of 5 food webs as igraph objects 195 | generate_niche <- function(S, C, nsim = 1) { 196 | # Validate inputs 197 | if (!is.numeric(S) || S <= 1 || S %% 1 != 0) stop("S must be an integer greater than 1.") 198 | if (!is.numeric(C) || C <= 0 || C > 1) stop("C must be a number between 0 and 1.") 199 | if (!is.numeric(nsim) || nsim < 1 || nsim %% 1 != 0) stop("nsim must be a positive integer.") 200 | 201 | generate_single_network <- function() { 202 | connected <- FALSE 203 | while (!connected) { 204 | # Assign niche values 205 | n.i <- sort(runif(S)) # Sorted niche values 206 | 207 | # Determine feeding range 208 | r.i <- rbeta(S, 1, ((1 / (2 * C)) - 1)) * n.i # Feeding range 209 | c.i <- runif(S, r.i / 2, n.i) # Feeding center 210 | 211 | # Initialize adjacency matrix 212 | a <- matrix(0, nrow = S, ncol = S) 213 | 214 | # Assign feeding relationships 215 | for (i in 2:S) { # Skip basal species 216 | for (j in 1:S) { 217 | if (n.i[j] > (c.i[i] - 0.5 * r.i[i]) && n.i[j] < (c.i[i] + 0.5 * r.i[i])) { 218 | a[j, i] <- 1 219 | } 220 | } 221 | } 222 | 223 | # Check connectivity 224 | g <- igraph::graph_from_adjacency_matrix(a, mode = "directed") 225 | connected <- igraph::is_connected(g) 226 | } 227 | return(g) # Return igraph 228 | } 229 | 230 | if (nsim == 1) { 231 | return(generate_single_network()) # Return a single adjacency matrix 232 | } else { 233 | return(lapply(seq_len(nsim), function(x) generate_single_network())) # Return list of igraph objects 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /R/plotNetwork.r: -------------------------------------------------------------------------------- 1 | 2 | #' Plot ecological network organized by trophic level, with node size determined by the node degree, and modules. 3 | #' 4 | #' @param ig igraph object 5 | #' @param vertexLabel logical plot vertex labels 6 | #' @param vertexSizeFactor numeric factor to determine the size of the label with degree 7 | #' @param vertexSizeMin numeric determine the minimum size of the label 8 | #' @param tk TRUE generate an interactive plot using tkplot and returns a matrix with coordinates from [igraph::tkplot.getcoords()] 9 | #' @param lMat Matrix of postions for the nodes 10 | #' @param modules if TRUE plot modules in the x axis, obtained with the cluster spinglass algorithm 11 | #' @param weights The weights of the edges for the [igraph::cluster_spinglass()] community detection, either a numeric vector, NULL, or NA. 12 | #' if NULL and the network has a 'weight' attribute then that will be used, 13 | #' if NA then the 'weight' attributte is not considered. 14 | #' @param community_obj Insteado of calculating modules/communities with cluster spinglass take a community object 15 | #' @param bpal if NULL it uses the "RdYlGn" RColorBrewer palette, else must be a vector of colors of length 11. 16 | #' @param maxTL maximum trophic level to draw y-axis 17 | #' 18 | #' @param ... Addittional parameters to the plot function 19 | #' 20 | #' @return returns a plot and if tk==TRUE returns a layout matrix 21 | #' @export 22 | #' 23 | #' @aliases plotTrophLevel 24 | #' 25 | #' 26 | #' @importFrom NetIndices TrophInd 27 | #' @importFrom igraph V degree get.adjacency cluster_spinglass vcount 28 | #' @importFrom RColorBrewer brewer.pal 29 | #' @importFrom dplyr "%>%" mutate dense_rank 30 | #' @examples 31 | #' 32 | #' plot_troph_level(netData[[1]]) 33 | plot_troph_level <- function(g,vertexLabel=FALSE,vertexSizeFactor=5,vertexSizeMin=5,tk=FALSE,modules=FALSE,lMat=NULL,weights=NA,community_obj=NULL, bpal= NULL, 34 | maxTL=NULL,...){ 35 | 36 | deg <- degree(g, mode="all") # calculate the degree: the number of edges 37 | # or interactions 38 | 39 | V(g)$size <- log10(deg)*vertexSizeFactor+vertexSizeMin # add node degrees to igraph object 40 | 41 | V(g)$frame.color <- "white" # Specify plot options directly on the object 42 | 43 | V(g)$color <- "orange" # 44 | E(g)$color <- "gray50" 45 | #E(g)$width <- .3 46 | # if( !is.null(edge.width )) { 47 | # if( is.null(weights) ) 48 | # E(g)$width <- E(g)$width* edge.width 49 | # else if( is.vector(weights, mode="numeric")) 50 | # E(g)$width <- weights* edge.width 51 | # } 52 | 53 | if(!vertexLabel) 54 | V(g)$label <- NA 55 | 56 | if(inherits(g, "mgraph") && ("Trophic" %in% unique(unlist(edge.attributes(g)))) ){ 57 | tt <- subgraph.edges(g,E(g)[E(g)$type=="Trophic"]) 58 | tl <- TrophInd(get.adjacency(tt,sparse=F)) 59 | } else { 60 | adj <- get.adjacency(g,sparse=F) 61 | colnames(adj) <- NULL 62 | rownames(adj) <- NULL 63 | tl <- TrophInd(adj) # Calculate the trophic level 64 | } 65 | # Layout matrix to specify the position of each vertix 66 | # Rows equal to the number of vertices (species) 67 | if(is.null(lMat)){ 68 | 69 | lMat <-matrix( 70 | nrow=vcount(g), 71 | ncol=2 72 | ) 73 | 74 | lMat[,2]<-jitter(tl$TL,0.1) # y-axis value based on trophic level 75 | 76 | if(modules) { 77 | if(!is.null(community_obj)) { 78 | m <- community_obj 79 | } else { 80 | if(count_components(g)>1){ 81 | if(!is.named(g)) V(g)$name <- (1:vcount(g)) 82 | dg <- components(g) 83 | V(g)$membership = 0 84 | for(comp in unique(dg$membership)) { 85 | g1 <- induced_subgraph(g, which(dg$membership == comp)) 86 | m<-cluster_spinglass(g1,weights=weights) 87 | if(length(m$membership)==0) 88 | m$membership <- 1 89 | V(g)[V(g1)$name]$membership <- m$membership + max(V(g)$membership) 90 | } 91 | m$membership <- V(g)$membership 92 | 93 | } else { 94 | m<-cluster_spinglass(g,weights=weights) 95 | } 96 | } 97 | # Order groups in ascending trofic level 98 | # 99 | df <- data.frame(tl=tl$TL,m=m$membership) 100 | df <- df %>% mutate(m = dense_rank(ave(tl, m, FUN = max))) 101 | lMat[,1]<-jitter(df$m,1) # randomly assign along x-axis 102 | 103 | } else { 104 | lMat[,1]<-runif(vcount(g)) # randomly assign along x-axis 105 | } 106 | } 107 | 108 | colTL <-as.numeric(cut(tl$TL,11)) # Divide trophic levels in 11 segments 109 | if( !is.null(bpal)) { 110 | colnet <- colorRampPalette(bpal)(11) 111 | } else { 112 | colnet <- brewer.pal(11,"RdYlGn") # Assign colors to trophic levels 113 | } 114 | V(g)$color <- colnet[12-colTL] # Add colors to the igraph object 115 | 116 | 117 | if(tk){ 118 | tkid <- tkplot(g, edge.arrow.size=.4, 119 | edge.curved=0.3, layout=lMat,...) 120 | return( tkplot.getcoords(tkid)) 121 | 122 | } else { 123 | plot(g, edge.arrow.size=.4, 124 | edge.curved=0.3, layout=lMat,...) 125 | maxnew <- max(tl$TL) 126 | minnew <- min(tl$TL) 127 | maxold <- 1 128 | minold <- -1 129 | t2 <- function(x) (maxold-minold)/(maxnew -minnew)*(x - maxnew)+maxold 130 | tlseq <- seq(1,ifelse(is.null(maxTL),maxnew+1,maxTL),by=1) 131 | axis(side=2,at=t2(tlseq),labels=tlseq, las=1, col = NA, col.ticks = 1) 132 | 133 | 134 | } 135 | 136 | } 137 | 138 | 139 | #' @export 140 | #' 141 | plotTrophLevel <- function(g,vertexLabel=FALSE,vertexSizeFactor=5,vertexSizeMin=5,tk=FALSE,modules=FALSE,lMat=NULL,weights=NA,community_obj=NULL, bpal= NULL, 142 | maxTL=NULL,...) 143 | { 144 | plot_troph_level(g,vertexLabel,vertexSizeFactor,vertexSizeMin,tk,modules,lMat,weights, community_obj,bpal,maxTL, ...) 145 | } 146 | 147 | 148 | -------------------------------------------------------------------------------- /R/plotNetworkGgplot.R: -------------------------------------------------------------------------------- 1 | #' Plot an Ecological Network Organized by Trophic Level 2 | #' 3 | #' This function visualizes a food web using `ggplot2`, where nodes represent species and edges represent interactions. 4 | #' Nodes are positioned by trophic level on the y-axis, and optionally, community modules on the x-axis. 5 | #' 6 | #' @param g An `igraph` object representing the ecological network. 7 | #' @param vertexSizeFactor Numeric factor to determine node size based on degree (default: 5). 8 | #' @param vertexSizeMin Numeric value for the minimum node size (default: 5). 9 | #' @param modules Logical; if `TRUE`, nodes are grouped by community modules detected using `cluster_spinglass()` (default: `FALSE`). 10 | #' @param weights A numeric vector, `NULL`, or `NA`, specifying edge weights for community detection. 11 | #' If `NULL`, the function uses the `weight` attribute of `g` if available. If `NA`, weights are ignored (default: `NA`). 12 | #' @param community_obj An optional community detection object. If `NULL`, the function calculates modules automatically (default: `NULL`). 13 | #' @param maxTL Numeric, maximum trophic level to display on the y-axis. If `NULL`, it is determined automatically (default: `NULL`). 14 | #' @param use_numbers Logical; if `TRUE`, nodes are labeled with numeric IDs instead of species names to reduce label overlap in large networks (default: `FALSE`). 15 | #' @param label_size Numeric, font size for node labels (default: 4). 16 | #' @param arrow_size Numeric, size of the arrowheads for directed edges (default: 0.15). 17 | #' @param shorten_factor Numeric, a small factor to adjust arrow placement by slightly shortening edges. 18 | #' This prevents arrowheads from overlapping with node centers. A small positive value (e.g., 0.005) works well (default: `0.005`). 19 | #' 20 | #' @return A `ggplot` object visualizing the trophic structure of the network. 21 | #' If `use_numbers = TRUE`, it also returns a `tibble` mapping numeric labels to species names. 22 | #' 23 | #' @importFrom igraph degree get.adjacency V count_components cluster_spinglass induced_subgraph components is_directed 24 | #' @importFrom NetIndices TrophInd 25 | #' @importFrom ggplot2 ggplot geom_segment geom_point theme_bw theme element_blank element_text labs scale_color_viridis_c geom_curve geom_vline 26 | #' @importFrom ggrepel geom_text_repel 27 | #' @importFrom dplyr mutate row_number select left_join rename 28 | #' @importFrom tibble tibble as_tibble 29 | #' @importFrom grid unit 30 | #' @export 31 | #' 32 | #' @examples 33 | #' 34 | #' g <- netData$Angola 35 | #' plot_troph_level_ggplot(g, modules = TRUE, use_numbers = TRUE) 36 | #' 37 | #' 38 | plot_troph_level_ggplot <- function(g, vertexSizeFactor = 5, vertexSizeMin = 5, modules = FALSE, 39 | weights = NA, community_obj = NULL, maxTL = NULL, 40 | use_numbers = FALSE, label_size = 4, arrow_size = 0.15,shorten_factor=0.005) { 41 | 42 | # Compute node degree and trophic level 43 | deg <- degree(g, mode = "all") 44 | adj <- as_adjacency_matrix(g, sparse = FALSE) 45 | tl <- TrophInd(adj) 46 | 47 | # Ensure node names exist, otherwise assign numeric IDs 48 | if (is.null(V(g)$name)) { 49 | V(g)$name <- as.character(1:vcount(g)) # Assign numbers as names 50 | } 51 | # Create node tibble 52 | nodes <- tibble( 53 | id = V(g)$name, 54 | degree = deg, 55 | size = log10(deg + 1) * vertexSizeFactor + vertexSizeMin, 56 | trophic_level = tl$TL 57 | ) 58 | 59 | # Assign numbers to nodes if `use_numbers = TRUE` 60 | if (use_numbers) { 61 | nodes <- nodes %>% mutate(numeric_id = row_number()) 62 | label_column <- "numeric_id" 63 | node_map <- nodes %>% select(numeric_id, id) # Store mapping of numbers to names 64 | } else { 65 | label_column <- "id" 66 | node_map <- NULL 67 | } 68 | 69 | # Compute layout (x = modularity, y = trophic level) 70 | if (modules) { 71 | if (!is.null(community_obj)) { 72 | m <- community_obj 73 | } else { 74 | if (count_components(g) > 1) { 75 | if (!is.named(g)) V(g)$name <- as.character(1:vcount(g)) 76 | dg <- components(g) 77 | V(g)$membership <- 0 78 | for (comp in unique(dg$membership)) { 79 | g1 <- induced_subgraph(g, which(dg$membership == comp)) 80 | m <- cluster_spinglass(g1, weights = weights) 81 | if (length(m$membership) == 0) m$membership <- 1 82 | V(g)[V(g1)$name]$membership <- m$membership + max(V(g)$membership) 83 | } 84 | m$membership <- V(g)$membership 85 | } else { 86 | m <- cluster_spinglass(g, weights = weights) 87 | } 88 | } 89 | nodes <- nodes %>% mutate(module = factor(m$membership)) 90 | nodes <- nodes %>% mutate(x = jitter(as.numeric(module), amount = 0.5)) 91 | } else { 92 | nodes <- nodes %>% mutate(x = runif(n(), min = -1, max = 1)) 93 | } 94 | 95 | nodes <- nodes %>% mutate(y = jitter(trophic_level, amount = 0.05)) 96 | 97 | # Convert edges to a tibble 98 | edges <- as.data.frame(as_edgelist(g)) %>% 99 | rename(from = V1, to = V2) %>% 100 | left_join(nodes, by = c("from" = "id")) %>% 101 | rename(x_from = x, y_from = y, size_from = size) %>% 102 | left_join(nodes, by = c("to" = "id")) %>% 103 | rename(x_to = x, y_to = y, size_to = size) 104 | 105 | 106 | # Create vertical separator lines if modules are enabled 107 | module_lines <- NULL 108 | if (modules) { 109 | module_positions <- sort(unique(as.numeric(nodes$module))) 110 | if (length(module_positions) > 1) { 111 | line_positions <- head(module_positions, -1) + diff(module_positions) / 2 # Midpoints 112 | module_lines <- tibble(x = line_positions) 113 | } 114 | } 115 | 116 | # Check if the graph is directed 117 | directed <- is_directed(g) 118 | 119 | if (directed) { 120 | # Adjust arrow placement by shortening the edges 121 | edges <- edges %>% 122 | mutate( 123 | dx = x_to - x_from, 124 | dy = y_to - y_from, 125 | length = sqrt(dx^2 + dy^2), 126 | x_from = x_from + shorten_factor * dx / length * size_from, 127 | y_from = y_from + shorten_factor * dy / length * size_from, 128 | x_to = x_to - shorten_factor * dx / length * size_to, 129 | y_to = y_to - shorten_factor * dy / length * size_to 130 | ) 131 | } 132 | 133 | 134 | # Define arrow style with adjustable arrow size 135 | arrow_style <- if (directed) arrow(type = "closed", length = unit(arrow_size, "inches")) else NULL 136 | 137 | # Create base ggplot network plot 138 | p <- ggplot() + 139 | geom_segment(data = edges, aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 140 | color = "gray50", alpha = 0.6, size = 0.4, arrow = arrow_style) + 141 | geom_point(data = nodes, aes(x = x, y = y, size = size, color = trophic_level), alpha = 0.9) + 142 | geom_text_repel(data = nodes, aes(x = x, y = y, label = .data[[label_column]]), 143 | max.overlaps = 15, size = label_size) + 144 | scale_color_viridis_c(option = "D") + 145 | theme_bw() + 146 | theme( 147 | legend.position = "none", 148 | panel.grid.major = element_blank(), 149 | panel.grid.minor = element_blank() 150 | ) + 151 | labs(y = "Trophic Level") 152 | 153 | 154 | # Add vertical separator lines **only between modules** 155 | if (!is.null(module_lines)) { 156 | p <- p + geom_vline(data = module_lines, aes(xintercept = x), color = "black", 157 | linetype = "dashed", alpha = 0.7) 158 | } 159 | 160 | # Add x-axis label **only if modules are used** 161 | if (modules) { 162 | p <- p + labs(x = "Community Module") 163 | } else { 164 | p <- p + theme(axis.title.x = element_blank()) # Remove x-axis label 165 | } 166 | 167 | # print(p) 168 | 169 | # Return node mapping if using numbers 170 | if (use_numbers) { 171 | return(node_map) 172 | } else { 173 | return(p) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /R/readNetwork.r: -------------------------------------------------------------------------------- 1 | # Function definitions 2 | 3 | 4 | 5 | #' Read ecological networks in CSV or tab separated file format as edge list or adyacency matrix 6 | #' 7 | #' If the network is in edge list format and there is a third column is treated as an edge attribute with the name of the column 8 | #' 9 | #' @param fileName vector of fileNames with the networks 10 | #' @param filePath path of the files NULL by default 11 | #' @param fhead TRUE if the files have header fields, FALSE otherwise. 12 | #' @param skipColum integer, number of columns that are skiped 1 by default 13 | #' @param edgeListFormat integer, for the edge list format, if 1 the first column is the "in" (the predator), 14 | #' if 2 the first column is the "out" link (the prey). 15 | #' 16 | #' @return an igraph object if there is only one file or a list of igraph objects named after the list without extension 17 | #' @export 18 | #' 19 | #' @examples 20 | #' 21 | #' # Reads a network in edge list (interaction list) format, with predators as the first column by default 22 | #' # 23 | #' fileName <- system.file("extdata", "WeddellSea_FW.csv", package = "multiweb") 24 | #' g <- readNetwork(fileName) 25 | #' 26 | #' # Reads a network in adyacency matrix format, with predators as columns 27 | #' # 28 | #' fileName <- system.file("extdata", "BarentsBoreal_FW.csv", package = "multiweb") 29 | #' g <- readNetwork(fileName) 30 | #' 31 | #' # Read a vector of files 32 | #' # 33 | #'\dontrun{ 34 | #' dn <- list.files("inst/extdata",pattern = "^.*\\.csv$") 35 | #' netData <- readNetwork(dn,"inst/extdata") 36 | #'} 37 | 38 | readNetwork <- function(fileName,filePath=NULL,fhead=TRUE,skipColumn=1,edgeListFormat=1){ 39 | 40 | fn <- if(!is.null(filePath)) paste0(filePath,"/",fileName) else fileName 41 | 42 | g <- lapply(fn, function(fname){ 43 | 44 | fe <- tools::file_ext(fname) 45 | if(fe=="csv") 46 | web <- read.csv(fname, header = fhead,check.names = F,stringsAsFactors = FALSE) 47 | else { 48 | 49 | web <- read.delim(fname, header = fhead,check.names = F,stringsAsFactors = FALSE) 50 | if(ncol(web)==1) 51 | web <- read.table(fname,header = fhead,check.names = F,stringsAsFactors = FALSE) 52 | 53 | } 54 | 55 | if( ncol(web)<=3 ){ 56 | if( edgeListFormat==1 ) 57 | if( ncol(web)==2) 58 | web <- web[,c(2,1)] 59 | else 60 | web <- web[,c(2,1,3)] 61 | 62 | g <- igraph::graph_from_data_frame(web) # the 3d field generate an attribute 63 | 64 | } else { 65 | if( (ncol(web)-skipColumn) == nrow(web) ) { # The adjacency matrix must be square 66 | skipColumn <- skipColumn+1 67 | g <- igraph::graph_from_adjacency_matrix(as.matrix(web[,skipColumn:ncol(web)]),mode="directed") 68 | if(skipColumn>1){ 69 | # 70 | # Assume first column before data is species names 71 | # 72 | nmes <- web[,skipColumn-1] 73 | igraph::V(g)$name <- nmes 74 | } else { 75 | # if all col names starts with V strip the V 76 | # 77 | if( all(grepl("^V",names(web))) ) { 78 | names(web) <- sub("^V(*.)","\\1",names(web)) 79 | igraph::V(g)$name <- names(web) 80 | } 81 | } 82 | g 83 | } else { 84 | g <- NULL 85 | warning("Invalid file format: ",fname) 86 | } 87 | } 88 | 89 | }) 90 | 91 | names(g) <- tools::file_path_sans_ext(fileName) 92 | 93 | if(length(g)==1) 94 | g <- g[[1]] 95 | 96 | return(g) 97 | } 98 | 99 | #' Read ecological multiplex networks using different files for each layer as a 'node-aligned' multilayer network 100 | #' 101 | #' This functions uses [readNetwork()] to read files that represent network layers that can be 102 | #' different interaction types, represented by the type attribute of the igraph object. 103 | #' 104 | #' @param fileName vector of fileNames with the layers of the networks 105 | #' @param types vector of types that represent the layers 106 | #' @param filePath path of the files NULL by default 107 | #' @param fhead TRUE if the files have header fields, FALSE otherwise. 108 | #' @param skipColum integer, number of columns that are skiped 1 by default 109 | #' @param format string, "layers" is the default were different layers are coded as different files 110 | #' there must be the same number of files as the length of the types vector as each type represent 111 | #' a layer. "GLV" Represent multiple interaction types as pairs of entries in a matrix so competition 112 | #' is represented as a[i,j]= -1, a[j,i]=-1, predation a[i,j]=1,a[j,i]=-1 where species j is 113 | #' the predator. Mutualism is a[i,j]=a[j,i]=1. Any negative or positive number works because intensity of interactions are not registered. 114 | #' 115 | #' @return an mgraph object that is a list of igraph objects with the type attribute set to each kind of layer or interaction 116 | #' @export 117 | #' 118 | #' @importFrom igraph E graph_from_data_frame graph_from_adjacency_matrix V 119 | #' @seealso [readNetwork()] 120 | #' @examples 121 | #' 122 | #' # Read a vector of files 123 | #' # 124 | #'\dontrun{ 125 | #' 126 | #' fpath <- system.file("extdata", package = "multiweb") 127 | #' dn <- list.files(fpath,pattern = "^Kefi2015.*\\.txt$") 128 | #' netData <- readMultiplex(dn,c("Negative","Positive","Antagonistic"),fpath,skipColum=2) 129 | #'} 130 | 131 | readMultiplex <- function(fileName,types=c("Negative","Positive","Antagonistic"),filePath=NULL, 132 | fhead=TRUE,skipColumn=1,format="layers"){ 133 | 134 | if( format=="layers"){ 135 | gt <- readNetwork(fileName=fileName,filePath=filePath, fhead=fhead,skipColumn = skipColumn) 136 | stopifnot(length(gt)==length(types)) 137 | 138 | for(t in seq_along(types)){ 139 | E(gt[[t]])$type <- types[t] 140 | } 141 | names(gt) <- types 142 | 143 | # gg <- lapply(g, igraph::as_data_frame) 144 | # gt <- do.call(rbind,gg) 145 | # gt <- graph_from_data_frame(gt) 146 | 147 | } else if(format=="GLV"){ 148 | 149 | stopifnot(length(types)==3) 150 | 151 | fn <- if(!is.null(filePath)) paste0(filePath,"/",fileName) else fileName 152 | 153 | web <- read.delim(fn,stringsAsFactors = FALSE) 154 | if( (ncol(web)-skipColumn) == nrow(web) ) { # The adjacency matrix must be square 155 | 156 | nmes <- web[,skipColumn] 157 | skipColumn <- skipColumn+1 158 | web <- web[,skipColumn:ncol(web)] 159 | pred <- matrix(0,nrow = nrow(web),ncol = nrow(web)) 160 | comp <- matrix(0,nrow = nrow(web),ncol = nrow(web)) 161 | mut <- matrix(0,nrow = nrow(web),ncol = nrow(web)) 162 | 163 | for( i in 1:nrow(web)) 164 | for(j in 1:nrow(web)){ 165 | if(web[i,j]!=0){ 166 | if(web[i,j]>0 && web[j,i]<0){ 167 | pred[i,j]<-1 168 | } else if(web[i,j]<0 && web[j,i]==0) { 169 | comp[i,j] <- 1 170 | }else if(web[i,j]<0 && web[j,i]<0) { 171 | comp[i,j] <- 1 172 | comp[j,i] <- 1 173 | } else if(web[i,j]>0 && web[j,i]==0) { 174 | mut[i,j] <- 1 175 | } else if(web[i,j]>0 && web[j,i]>0){ 176 | mut[i,j] <- 1 177 | mut[j,i] <- 1 178 | } else if(web[i,j]<0 && web[j,i]>0){ 179 | pred[j,i]<-1 180 | } else { 181 | warning("Unclasified interaction type ", web[i,j], web[j,i]) 182 | } 183 | 184 | } 185 | 186 | } 187 | 188 | gg <- list(comp,mut,pred) 189 | # convert to igraph 190 | gt <- lapply(seq_along(gg), function(t){ 191 | g <- graph_from_adjacency_matrix(gg[[t]]) 192 | E(g)$type <- types[t] 193 | V(g)$name <- nmes 194 | g 195 | #df <- igraph::as_data_frame(g) 196 | }) 197 | #gt <- do.call(rbind,gdf) 198 | #gt <- graph_from_data_frame(gt) 199 | #V(gt)$name <- nmes 200 | names(gt) <- types 201 | 202 | } else { 203 | gt <- NULL 204 | warning("Interaction matrix not square") 205 | } 206 | 207 | } 208 | class(gt) <- c("mgraph") 209 | 210 | return(gt) 211 | } 212 | 213 | 214 | 215 | #' From multiple interaction object 'mgraph'to GLV adjacency matrix 216 | #' 217 | #' This functions takes a 'mgraph' object and convert it to a Generalized Lotka-Volterra adjacency matrix 218 | #' Position is important in fact the order is Competitive/Negative,Mutualistic/Positive,Trophic/Antagonistic 219 | #' If 'istrength' is TRUE the attribute weight is assumed as the strength of the interacion 220 | #' 221 | #' 222 | #' @param mg multiple interaction object, class 'mgraph' 223 | #' @param types vector of types that represent the layers 224 | #' @param istrength edge weights 225 | #' 226 | #' @return an matrix with the signs of the interactions 227 | #' @export 228 | #' 229 | #' @importFrom igraph E graph_from_data_frame graph_from_adjacency_matrix V 230 | #' @seealso [readMultiplex()] 231 | #' @examples 232 | #' 233 | #' # Read a vector of files 234 | #' # 235 | #'\dontrun{ 236 | #' 237 | #' fpath <- system.file("extdata", package = "multiweb") 238 | #' dn <- list.files(fpath,pattern = "^Kefi2015.*\\.txt$") 239 | #' netData <- readMultiplex(dn,c("Negative","Positive","Antagonistic"),fpath,skipColum=2) 240 | #' toGLVadjMat(netData) 241 | #'} 242 | 243 | toGLVadjMat <- function(mg,types=c("Negative","Positive","Antagonistic"),istrength=FALSE){ 244 | 245 | if( class(mg)!='mgraph') 246 | stop("parameter mg must be an mgraph object") 247 | if( length(mg)!=3 ) 248 | stop("parameter mg must have 3 components") 249 | 250 | stopifnot(length(types)==3) 251 | 252 | if(is.null(edge_attr(mg[[types[3]]],"weight"))) 253 | pred <- as_adj(mg[[types[3]]],sparse = FALSE) 254 | else 255 | pred <- as_adj(mg[[types[3]]],sparse = FALSE,attr="weight") 256 | 257 | if(is.null(edge_attr(mg[[types[1]]],"weight"))) 258 | comp <- as_adj(mg[[types[1]]],sparse = FALSE) 259 | else 260 | comp <- as_adj(mg[[types[1]]],sparse = FALSE,attr="weight") 261 | 262 | if(is.null(edge_attr(mg[[types[2]]],"weight"))) 263 | mut <- as_adj(mg[[types[2]]],sparse = FALSE) 264 | else 265 | mut <- as_adj(mg[[types[2]]],sparse = FALSE,attr="weight") 266 | 267 | web <- matrix(0,nrow = nrow(pred),ncol = nrow(pred)) 268 | 269 | if(istrength) 270 | { 271 | for( i in 1:nrow(web)){ 272 | for(j in 1:nrow(web)){ 273 | if(pred[i,j]){ 274 | web[i,j] <- -pred[i,j] 275 | web[j,i] <- pred[i,j] 276 | } else if(comp[i,j]) { 277 | 278 | web[i,j] <- -comp[i,j] 279 | 280 | } else if(mut[i,j]) { 281 | web[i,j] <- mut[i,j] 282 | } 283 | 284 | } 285 | 286 | } 287 | 288 | } else { 289 | 290 | for( i in 1:nrow(web)){ 291 | for(j in 1:nrow(web)){ 292 | if(pred[i,j]){ 293 | web[i,j] <- -1 294 | web[j,i] <- 1 295 | } else if(comp[i,j]) { 296 | 297 | web[i,j] <- -1 298 | 299 | } else if(mut[i,j]) { 300 | web[i,j] <- 1 301 | } 302 | 303 | } 304 | 305 | } 306 | } 307 | 308 | return(web) 309 | } 310 | 311 | 312 | 313 | #' From multiple igraph objects to 'mgraph' 314 | #' 315 | #' This functions takes a list of igraph objects and convert it to a 'mgraph' object. 316 | #' 317 | #' 318 | #' @param mg list of 'igraph' objects 319 | #' @param types vector of types that represent the layers with the same length as mg 320 | #' 321 | #' @return class 'mgraph' object 322 | #' @export 323 | #' 324 | #' @seealso [readMultiplex()] 325 | #' @examples 326 | #' 327 | #' # Read a vector of files 328 | #' # 329 | #'\dontrun{ 330 | #' 331 | #' fileName <- c(system.file("extdata", package = "multiweb")) 332 | #' dn <- list.files("inst/extdata",pattern = "^Kefi2015.*\\.txt$") 333 | #' g <- readNetwork(dn,"inst/extdata", skipColumn = 2) 334 | #' fromIgraphToMgraph(g,c("Negative","Positive","Antagonistic")) 335 | #' 336 | #'} 337 | 338 | fromIgraphToMgraph <- function(g,types){ 339 | 340 | if( any(sapply(g,class)!='igraph')) 341 | stop("parameter mg must be a list of igraph objects") 342 | 343 | if(length(g)!= length(types)) 344 | stop("Length of mg must be equal to length of types") 345 | 346 | names(g) <- types 347 | class(g) <- 'mgraph' 348 | 349 | return(g) 350 | } 351 | 352 | #' From Generalized Lotka Volterra adjacency matrix to igraph topological object 353 | #' 354 | #' It counts predator-prey/Antagonistic interactions like 1 edge, 355 | #' competition and mutualisms are counted as is: two edges or one edge 356 | #' 357 | #' @param glvAdj numeric matrix Generalized Lotka-Volterra adjacency matrix 358 | #' @param spc numeric vector of present Species 359 | #' 360 | #' @return an igraph object 361 | #' @export 362 | #' @importFrom igraph graph_from_adjacency_matrix V 363 | #' 364 | #' @examples 365 | #' # Build a matrix 366 | #' 367 | #' m <- matrix(0,nrow=5,ncol=5) 368 | #' m[1,2] <- m[1,3] <- m[3,4]<- .2 369 | #' m[2,1] <- m[3,1] <- m[4,3] <- -2 370 | #' m[5,4] <- m[4,5] <- 0.1 # Mutualistic 371 | #' m[1,1] <- -0.01 # Cannibalistic 372 | #' 373 | #' g <- fromGLVadjToIgraph(m,c(1,1,1,1,0)) 374 | #' 375 | #' g <- fromGLVadjToIgraph(m,c(0,1,1,1,1)) 376 | #' 377 | fromGLVadjToIgraph<- function(glvAdj,spc){ 378 | 379 | stopifnot(nrow(glvAdj)==ncol(glvAdj)) 380 | if(length(spc)!=nrow(glvAdj)) stop("length of species vector has to be equal to glvAdj dimensions") 381 | 382 | d <- spc!=0 383 | A <- glvAdj[d,d] 384 | for(i in seq_len(nrow(A))) 385 | for(j in seq_len(nrow(A))){ 386 | if(A[i,j]<0 && A[j,i]>0){ 387 | A[i,j] <- 1 388 | A[j,i] <- 0 389 | } 390 | } 391 | # diag(A) <- 0 392 | A[A>0] <- 1 393 | A[A<0] <- 1 394 | g <- graph_from_adjacency_matrix(A,mode="directed") 395 | V(g)$name <- which(spc>0) # Maintains the "names" of the original matrix 396 | return(g) 397 | } 398 | -------------------------------------------------------------------------------- /R/shuffling.R: -------------------------------------------------------------------------------- 1 | #' Generate a Sequence of Shuffled Networks and Track SVD/Modularity Evolution 2 | #' 3 | #' This function generates a sequence of shuffled networks from an original graph by applying 4 | #' incremental shuffling steps. It tracks modularity and SVD entropy throughout the process, 5 | #' and stops when the change in these metrics falls below a specified `tolerance` threshold. 6 | #' 7 | #' @param input_graph An igraph object or adjacency matrix (directed or undirected, weighted or unweighted). 8 | #' @param delta Integer. Number of link swaps per iteration (default: 10). 9 | #' @param max_iterations Integer. Maximum number of iterations until network metrics stabilize (default: 100). 10 | #' @param tolerance Numeric. Tolerance for metric stability - minimum change in modularity or SVD entropy (default: 1e-3). 11 | #' @param directed Logical. Whether the network is directed (default: TRUE). 12 | #' @param weighted Logical. Whether the network is weighted (default: TRUE). 13 | #' @param modularity_func Function. Function to calculate modularity (default: \code{cluster_infomap}). 14 | #' @param shuffle_func Function. Function to perform network shuffling (default: \code{shuffle_network_deg}). 15 | #' 16 | #' @return A list containing: 17 | #' \describe{ 18 | #' \item{New_A}{Shuffled adjacency matrix} 19 | #' \item{Metrics}{Data frame tracking metric evolution across iterations, including the original network} 20 | #' } 21 | #' 22 | #' @import igraph 23 | #' @export 24 | generate_shuffled_seq_tol <- function(input_graph, delta = 10, max_iterations = 100, 25 | tolerance = 1e-3, directed = TRUE, weighted=TRUE, 26 | modularity_func = cluster_infomap, 27 | shuffle_func = shuffle_network_deg) { 28 | 29 | # Convert igraph object to adjacency matrix if needed 30 | if (inherits(input_graph, "igraph")) { 31 | if(weighted) { 32 | if(is.null(edge_attr(input_graph, "weight"))) { 33 | stop( "Weighted graph must have 'weight' attribute on edges.") 34 | } 35 | A <- as.matrix(as_adjacency_matrix(input_graph, attr = "weight", sparse = FALSE)) 36 | } else { 37 | A <- as.matrix(as_adjacency_matrix(input_graph, sparse = FALSE)) 38 | } 39 | } else { 40 | A <- input_graph # Assume it's already an adjacency matrix 41 | } 42 | original_A <- A # Save original matrix 43 | n <- nrow(A) # Number of nodes 44 | 45 | mode <- ifelse(directed, "directed", "undirected") 46 | if(weighted) 47 | G <- graph_from_adjacency_matrix(A, mode = mode, weighted = TRUE, diag = TRUE) 48 | else 49 | G <- graph_from_adjacency_matrix(A, mode = mode, weighted = NULL, diag = TRUE) 50 | 51 | modularity_values <- c(modularity(modularity_func(G))) 52 | entropy_values <- c(calc_svd_entropy(A)$Entropy) 53 | 54 | for (iter in 1:max_iterations) { 55 | A <- shuffle_func(A, delta = delta, directed = directed, weighted = weighted) 56 | # Compute new modularity and entropy 57 | G_new <- graph_from_adjacency_matrix(A, mode = mode, weighted = weighted, diag = TRUE) 58 | modularity_values <- c(modularity_values, modularity(modularity_func(G_new))) 59 | entropy_values <- c(entropy_values, calc_svd_entropy(A)$Entropy) 60 | 61 | # Check convergence 62 | if (iter > 1) { 63 | if (abs(modularity_values[iter] - modularity_values[iter - 1]) < tolerance && 64 | abs(entropy_values[iter] - entropy_values[iter - 1]) < tolerance) { 65 | break 66 | } 67 | } 68 | } 69 | 70 | # Store results 71 | metrics <- data.frame( 72 | Iteration = 0:(length(modularity_values) - 1), 73 | SVD_Entropy = entropy_values, 74 | Modularity = modularity_values 75 | ) 76 | 77 | return(list(New_A = A, Metrics = metrics)) 78 | } 79 | 80 | 81 | 82 | #' Degree-Preserving Network Shuffling via Edge Swaps 83 | #' 84 | #' This function shuffles a directed network while preserving in-degree and out-degree. 85 | #' It follows a controlled randomization process by swapping edges iteratively. 86 | #' Similar to the `curve_ball()` function but more computationally demanding. 87 | #' This function is useful for generating increasingly randomized networks, 88 | #' to generate fully randomized networks, the `curbe_ball()` function is preferred. 89 | #' 90 | #' @param A A square adjacency matrix (directed, weighted or unweighted). 91 | #' @param delta Number of edge swaps to perform. 92 | #' @param max_attempts Number of times to attempt a valid swap before stopping. 93 | #' 94 | #' @return A shuffled adjacency matrix preserving in-degree and out-degree. 95 | #' 96 | #' @references 97 | #' 98 | #' Huaylla, C. A., Nacif, M. E., Coulin, C., Kuperman, M. N., & Garibaldi, L. A. (2021). 99 | #' Decoding information in multilayer ecological networks: The keystone species case. 100 | #' Ecological Modelling, 460, 109734. https://doi.org/10.1016/j.ecolmodel.2021.109734 101 | #' 102 | #' Strona, G., Nappo, D., Boccacci, F., Fattorini, S., & San-Miguel-Ayanz, J. (2014). 103 | #' A fast and unbiased procedure to randomize ecological binary matrices with fixed row and column totals. 104 | #' Nature Communications, 5, 4114. https://doi.org/10.1038/ncomms5114 105 | #' 106 | #' @import igraph 107 | #' @export 108 | shuffle_network_deg <- function(input_graph, delta = 100, directed = TRUE,weighted=TRUE) { 109 | if(weighted) { 110 | w_attr <- "weight" 111 | } else { 112 | w_attr <- NULL 113 | } 114 | 115 | # Check for weighted graphs 116 | if (weighted && inherits(input_graph, "igraph") && is.null(edge_attr(input_graph, "weight"))) { 117 | stop("Weighted graph must have 'weight' attribute on edges.") 118 | } 119 | 120 | # Convert igraph object to adjacency matrix if needed 121 | if (inherits(input_graph, "igraph")) { 122 | A <- as.matrix(as_adjacency_matrix(input_graph, attr = w_attr, sparse = FALSE)) 123 | } else { 124 | A <- input_graph # Assume it's already an adjacency matrix 125 | } 126 | 127 | mode <- ifelse(directed, "directed", "undirected") 128 | original_A <- A # Save the original network 129 | 130 | for (iter in 1:delta) { 131 | 132 | # Select two distinct edges (i, j) and (i', j') to swap 133 | edges <- which(A > 0, arr.ind = TRUE) # Get all existing edges 134 | if (nrow(edges) < 2) break # Ensure at least two edges exist 135 | 136 | selected_edges <- sample(1:nrow(edges), 2) 137 | i <- edges[selected_edges[1], 1] 138 | j <- edges[selected_edges[1], 2] 139 | i_prime <- edges[selected_edges[2], 1] 140 | j_prime <- edges[selected_edges[2], 2] 141 | 142 | # Ensure valid swap: i ≠ i', j ≠ j', and no duplicate edges 143 | if (i != i_prime && j != j_prime && A[i, j_prime] == 0 && A[i_prime, j] == 0) { 144 | 145 | # Swap edges 146 | A[i, j_prime] <- A[i, j] 147 | A[i_prime, j] <- A[i_prime, j_prime] 148 | 149 | # Remove old edges 150 | A[i, j] <- 0 151 | A[i_prime, j_prime] <- 0 152 | } 153 | } 154 | if(weighted) { 155 | r <- dim(original_A)[1] 156 | c <- dim(original_A)[2] 157 | 158 | 159 | for(i in seq_len(c) ){ 160 | ss <- sample(original_A[,i]) 161 | ss <- ss[ss>0] 162 | k <- 1 163 | for( j in seq_len(r)){ 164 | if(A[j,i]>0 ) { 165 | A[j,i] <- ss[k] 166 | k <- k+1 167 | } 168 | } 169 | } 170 | } 171 | return(A) 172 | } 173 | 174 | 175 | #' Generate a Sequence of Shuffled Networks and Track Metric Evolution 176 | #' 177 | #' This function generates a sequence of shuffled networks from an original graph, applying 178 | #' incremental shuffling steps while tracking modularity, SVD rank and entropy . 179 | #' 180 | #' @param original_graph An igraph object representing the original network. 181 | #' @param max_delta Integer. Number of total shuffling steps to perform. 182 | #' @param delta Integer. Number of swaps per shuffling step. 183 | #' @param directed Logical. Whether the network is directed (default: TRUE). 184 | #' @param weighted Logical. Whether the network is weighted (default: TRUE). 185 | #' @param shuffle_func Function. A network shuffling function (default: `shuffle_network_ws`). 186 | #' @param modularity_func Function. A modularity calculation function (default: `cluster_infomap`). 187 | #' 188 | #' @return A list with: 189 | #' \item{Networks}{A list of igraph objects representing the shuffled networks at each step.} 190 | #' \item{Metrics}{A tibble containing the step number, SVD entropy, SVD Rank, and modularity score.} 191 | #' 192 | #' @import igraph dplyr 193 | #' @export 194 | generate_shuffled_seq <- function(original_graph, max_delta = 10, delta = 10, 195 | directed = TRUE, weighted = TRUE, 196 | shuffle_func = shuffle_network_ws, 197 | modularity_func = cluster_infomap) { 198 | # Initialize 199 | shuffled_networks <- list() 200 | results <- tibble() 201 | 202 | A_current <- as.matrix(as_adjacency_matrix(original_graph, sparse = FALSE)) # Start from original 203 | shuffled_networks[[1]] <- original_graph 204 | 205 | # Calculate metrics 206 | svd <- calc_svd_entropy(A_current) 207 | entropy <- svd$Entropy 208 | rank <- svd$Rank 209 | modularity_score <- modularity(modularity_func(original_graph)) 210 | 211 | results <- bind_rows(results, tibble(Step = 1, SVD_Entropy = entropy, SVD_Rank = rank, 212 | Modularity = modularity_score)) 213 | 214 | # Generate sequence of networks 215 | for (d in 1:max_delta) { 216 | A_shuffled <- shuffle_func(A_current, delta = delta, directed = directed, weighted = weighted) # Apply shuffle 217 | g_shuffled <- graph_from_adjacency_matrix(A_shuffled, mode = "directed", weighted = weighted) 218 | 219 | # Store network 220 | shuffled_networks[[d + 1]] <- g_shuffled 221 | 222 | # Calculate metrics 223 | 224 | svd <- calc_svd_entropy(A_shuffled) 225 | entropy <- svd$Entropy 226 | rank <- svd$Rank 227 | 228 | modularity_score <- modularity(modularity_func(g_shuffled)) 229 | 230 | results <- bind_rows(results, tibble(Step = d+1, SVD_Entropy = entropy, SVD_Rank = rank, 231 | Modularity = modularity_score)) 232 | 233 | # Update A_current for next iteration 234 | A_current <- A_shuffled 235 | } 236 | 237 | return(list(Networks = shuffled_networks, Metrics = results)) 238 | } 239 | 240 | #' Random Network Rewiring Without Preserving Degree Distribution 241 | #' 242 | #' This function randomly rewires a directed network while preserving 243 | #' the total number of links but NOT the degree distribution. This is based 244 | #' on an approach described by Watts and Strogatz (1998) for small-world networks. 245 | #' 246 | #' @param input_graph A square adjacency matrix (directed, weighted or unweighted) or an igraph object. 247 | #' @param delta Number of rewiring attempts to perform. 248 | #' @param directed Logical, whether the network is directed (default: TRUE). 249 | #' @param weighted Logical, whether the network is weighted (default: TRUE). 250 | #' 251 | #' @return A shuffled adjacency matrix preserving the number of links but not degree distribution. 252 | #' 253 | #' @references 254 | #' Watts, D. J., & Strogatz, S. H. (1998). Collective dynamics of 'small-world' networks. 255 | #' Nature, 393(6684), 440-442. \doi{10.1038/30918} 256 | #' 257 | #' @import igraph 258 | #' @export 259 | #' 260 | shuffle_network_ws <- function(input_graph, delta = 100, directed = TRUE, weighted = TRUE) { 261 | if (weighted) { 262 | w_attr <- "weight" 263 | } else { 264 | w_attr <- NULL 265 | } 266 | 267 | # Check for weighted graphs 268 | if (weighted && inherits(input_graph, "igraph") && is.null(edge_attr(input_graph, "weight"))) { 269 | stop("Weighted graph must have 'weight' attribute on edges.") 270 | } 271 | 272 | # Convert igraph object to adjacency matrix if needed 273 | if (inherits(input_graph, "igraph")) { 274 | A_orig <- as.matrix(as_adjacency_matrix(input_graph, attr = w_attr, sparse = FALSE)) 275 | } else { 276 | A_orig <- input_graph 277 | } 278 | 279 | mode <- ifelse(directed, "directed", "undirected") 280 | 281 | repeat { 282 | A <- A_orig # Start from original each time 283 | 284 | # Step 1: Get positions of existing links (1s) and absent links (0s) 285 | ones_list <- which(A > 0, arr.ind = TRUE) 286 | zeros_list <- which(A == 0, arr.ind = TRUE) 287 | 288 | for (iter in seq_len(delta)) { 289 | if (nrow(ones_list) == 0 || nrow(zeros_list) == 0) break 290 | 291 | # Step 3: Choose a random existing edge 292 | selected_edge <- sample(nrow(ones_list), 1) 293 | i <- ones_list[selected_edge, 1] 294 | j <- ones_list[selected_edge, 2] 295 | 296 | # Step 4: Check if the node has more than one neighbor 297 | if (sum(A[i, ] > 0) > 1) { 298 | 299 | # Step 5: Choose a random absent link (to replace the existing one) 300 | selected_zero <- sample(nrow(zeros_list), 1) 301 | i_new <- zeros_list[selected_zero, 1] 302 | j_new <- zeros_list[selected_zero, 2] 303 | 304 | 305 | # Step 6: Perform the swap in the adjacency matrix 306 | A[i_new, j_new] <- A[i, j] # Move weight (if weighted) or assign 1 (if unweighted) 307 | A[i, j] <- 0 # Remove the original link 308 | 309 | # Update ones_list and zeros_list accordingly 310 | ones_list <- which(A > 0, arr.ind = TRUE) 311 | zeros_list <- which(A == 0, arr.ind = TRUE) 312 | } 313 | } 314 | 315 | # Check connectivity 316 | g <- graph_from_adjacency_matrix(A, mode = mode, weighted = weighted) 317 | 318 | if (components(g, mode = "weak")$no == 1) { 319 | return(A) 320 | } 321 | 322 | # else: repeat again 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /R/supraAdjacencyMatrix.R: -------------------------------------------------------------------------------- 1 | #' Generate Node aligned Interlayer Edges for a Multilayer Network from the function `convert_to_intra_format` 2 | #' 3 | #' @param intra_data A list with `$vertices` (node info) and `$intra` (intralayer edges). 4 | #' @param interlayer_weight Numeric, the weight for interlayer links (default: 1). 5 | #' @return A dataframe with interlayer edges. 6 | add_interlayer_links <- function(intra_data, interlayer_weight = 1) { 7 | nodes <- intra_data$vertices 8 | intra_edges <- intra_data$intra 9 | 10 | # Extract unique node-layer combinations from both columns 11 | node_layers <- unique(rbind( 12 | data.frame(layer_id = intra_edges$layer_id, node_id = intra_edges$node_id1), 13 | data.frame(layer_id = intra_edges$layer_id, node_id = intra_edges$node_id2) 14 | )) 15 | 16 | # Identify nodes that exist in multiple layers 17 | multilayer_nodes <- table(node_layers$node_id) 18 | aligned_nodes <- as.integer(names(multilayer_nodes[multilayer_nodes > 1])) 19 | 20 | # Generate interlayer edges only for nodes in multiple layers 21 | inter_edges <- do.call(rbind, lapply(aligned_nodes, function(node) { 22 | layers <- sort(unique(node_layers$layer_id[node_layers$node_id == node])) 23 | 24 | if (length(layers) > 1) { 25 | df <- data.frame( 26 | node_id = rep(node, length(layers) - 1), 27 | layer_from = head(layers, -1), 28 | layer_to = tail(layers, -1), 29 | weight = interlayer_weight 30 | ) 31 | 32 | # Create bidirectional edges by swapping from ↔ to 33 | df_bidirectional <- df[, c("node_id", "layer_to", "layer_from", "weight")] 34 | colnames(df_bidirectional) <- colnames(df) 35 | 36 | return(rbind(df, df_bidirectional)) # Combine both directions 37 | } 38 | return(NULL) 39 | })) 40 | 41 | return(inter_edges) 42 | } 43 | 44 | 45 | 46 | #' Convert Multilayer Network to Supra-Adjacency Matrix (SAM) 47 | #' 48 | #' @param igraph_list A list of `igraph` objects (each representing a layer). 49 | #' @param layer_names (Optional) A vector of names for each layer. 50 | #' @param interlayer_weight Numeric, the weight for interlayer links (default: 1). 51 | #' @param isDirected Logical, whether the network is directed. 52 | #' @param sparse Logical, whether to return the supra-adjacency matrix as a sparse matrix (default: TRUE). 53 | #' @param use_names Logical; if `TRUE`, edges will use node names instead of numeric IDs (default: `FALSE`). 54 | #' @param interlayer Logical; if `FALSE`, no interlayer links are added (default: `TRUE`). 55 | #' @param clean Logical; if `TRUE`, removes empty rows/columns (state nodes with no edges) from the output (default: TRUE). 56 | #' @return A list with: 57 | #' \item{supra_matrix}{Supra-adjacency matrix with optional inter-layer links.} 58 | #' \item{state_nodes_map}{Mapping of state nodes (layer-node combinations).} 59 | #' @import igraph 60 | #' @import Matrix 61 | #' @export 62 | convert_to_supra_adjacency <- function(igraph_list, layer_names = NULL, interlayer_weight = 1, 63 | isDirected = TRUE, sparse = TRUE, use_names = FALSE, 64 | interlayer = TRUE, clean = TRUE) { 65 | # Step 1: Convert to intra-layer format 66 | intra_data <- convert_to_intra_format(igraph_list) 67 | num_layers <- length(igraph_list) 68 | num_nodes <- nrow(intra_data$vertices) 69 | 70 | # Assign default layer names if not provided 71 | if (is.null(layer_names)) { 72 | layer_names <- paste0("layer_", seq_len(num_layers)) 73 | } 74 | 75 | # Step 2: Create state-node mapping 76 | state_nodes_map <- expand.grid( 77 | node_id = intra_data$vertices$node_id, 78 | layer_id = seq_len(num_layers) 79 | ) 80 | state_nodes_map <- merge(state_nodes_map, intra_data$vertices, by = "node_id", all.x = TRUE) 81 | state_nodes_map <- merge(state_nodes_map, data.frame(layer_id = seq_len(num_layers), layer_name = layer_names), 82 | by = "layer_id", all.x = TRUE) 83 | 84 | # Assign unique state-node IDs 85 | state_nodes_map <- state_nodes_map[order(state_nodes_map$layer_id, state_nodes_map$node_id), ] 86 | state_nodes_map$sn_id <- seq_len(nrow(state_nodes_map)) 87 | 88 | # Use node names if requested 89 | if (use_names) { 90 | state_nodes_map$tuple <- paste(state_nodes_map$layer_name, state_nodes_map$name, sep = "_") 91 | } else { 92 | state_nodes_map$tuple <- paste(state_nodes_map$layer_name, state_nodes_map$node_id, sep = "_") 93 | } 94 | 95 | # Step 3: Generate inter-layer edges (if enabled) 96 | interlayer_edges <- if (interlayer) add_interlayer_links(intra_data, interlayer_weight) else NULL 97 | 98 | # Step 4: Convert intra-layer edges to extended edge list format 99 | intra_edges <- intra_data$intra 100 | extended_intra <- data.frame( 101 | node_id1 = intra_edges$node_id1, 102 | layer_id1 = intra_edges$layer_id, 103 | node_id2 = intra_edges$node_id2, 104 | layer_id2 = intra_edges$layer_id, 105 | weight = intra_edges$weight 106 | ) 107 | 108 | # Step 5: Convert inter-layer edges (if available) 109 | extended_inter <- if (interlayer) { 110 | data.frame( 111 | node_id1 = interlayer_edges$node_id, 112 | layer_id1 = interlayer_edges$layer_from, 113 | node_id2 = interlayer_edges$node_id, 114 | layer_id2 = interlayer_edges$layer_to, 115 | weight = interlayer_edges$weight 116 | ) 117 | } else NULL 118 | 119 | # Merge intra-layer and inter-layer edges 120 | full_edge_list <- if (!is.null(extended_inter)) rbind(extended_intra, extended_inter) else extended_intra 121 | 122 | # Step 6: Convert to Supra-Adjacency Matrix (SAM) 123 | SAM <- BuildSupraAdjacencyMatrixFromExtendedEdgelist( 124 | mEdges = full_edge_list, 125 | Layers = num_layers, 126 | Nodes = num_nodes, 127 | isDirected = isDirected 128 | ) 129 | 130 | # Assign row/column names if using node names 131 | if (use_names) { 132 | rownames(SAM) <- state_nodes_map$tuple 133 | colnames(SAM) <- state_nodes_map$tuple 134 | } 135 | 136 | # Step 7: Remove empty rows/columns if requested 137 | if (clean) { 138 | active <- Matrix::rowSums(SAM) + Matrix::colSums(SAM) > 0 139 | SAM <- SAM[active, active, drop = FALSE] 140 | state_nodes_map <- state_nodes_map[active, , drop = FALSE] 141 | } 142 | 143 | return(list(supra_matrix = SAM, state_nodes_map = state_nodes_map)) 144 | } 145 | 146 | #' Build supra-adjacency matrix from edge lists 147 | #' 148 | #' 149 | #' @param mEdges data frame with extended edge list, i.e a data frame with 150 | #' columns: \code{node.from, layer.from, node.to, layer.to weight} 151 | #' @param Layers scalar, number of layers 152 | #' @param Nodes scalar, number of nodes 153 | #' @param isDirected logical 154 | #' @return 155 | #' Supra-adjacency matrix, a square matrix of dimension \code{Nodes * Layers}. 156 | BuildSupraAdjacencyMatrixFromExtendedEdgelist <- 157 | function(mEdges, Layers, Nodes, isDirected) { 158 | 159 | if (max(max(mEdges[, 2]), max(mEdges[, 4])) != Layers) { 160 | stop("Error: expected number of layers does not match the data. Aborting process.") 161 | } 162 | 163 | edges <- data.frame( 164 | from = mEdges[, 1] + Nodes * (mEdges[, 2] - 1), 165 | to = mEdges[, 3] + Nodes * (mEdges[, 4] - 1), 166 | weight = mEdges[, 5] 167 | ) 168 | 169 | M <- 170 | Matrix::sparseMatrix( 171 | i = edges$from, 172 | j = edges$to, 173 | x = edges$weight, 174 | dims = c(Nodes * Layers, Nodes * Layers) 175 | ) 176 | 177 | if (sum(abs(M - Matrix::t(M))) > 1e-12 && isDirected == FALSE) { 178 | message( 179 | "WARNING: The input data is directed but isDirected=FALSE, I am symmetrizing by average." 180 | ) 181 | M <- (M + Matrix::t(M)) / 2 182 | } 183 | return(M) 184 | } 185 | -------------------------------------------------------------------------------- /R/svdcentrality.R: -------------------------------------------------------------------------------- 1 | #' Singular Value Decomposition (SVD) Analysis of Networks 2 | #' 3 | #' This function performs Singular Value Decomposition (SVD) on a network adjacency matrix. 4 | #' It computes species importance based on the dominant singular values and returns entropy, rank, and key visualizations. 5 | #' 6 | #' @param A A square adjacency matrix where rows represent prey and columns represent predators. 7 | #' The values represent interaction strengths. 8 | #' 9 | #' @return A list containing: 10 | #' \item{Rank}{The numerical rank of the matrix after small values are rounded for numerical stability.} 11 | #' \item{Entropy}{The entropy of the singular value distribution.} 12 | #' \item{Prey_Importance}{A data frame ranking prey species by their contribution to the largest singular value.} 13 | #' \item{Predator_Importance}{A data frame ranking predator species by their contribution to the largest singular value.} 14 | #' \item{Singular_Values}{A numeric vector of the singular values of the matrix.} 15 | #' \item{Plot_Singular_Values}{A ggplot object showing the distribution of singular values.} 16 | #' \item{Plot_Prey_Importance}{A ggplot object showing the top prey species contributing to the largest singular value.} 17 | #' \item{Plot_Predator_Importance}{A ggplot object showing the top predator species contributing to the largest singular value.} 18 | #' 19 | #' @seealso \code{\link{calc_svd_entropy}} for a lighter version returning only entropy and rank. 20 | #' 21 | #' @examples 22 | #' 23 | #' results <- calc_svd_entropy_importance(netData[[1]]) 24 | #' print(results$Rank) 25 | #' print(results$Entropy) 26 | #' print(results$Plot_Singular_Values) 27 | #' print(results$Plot_Prey_Importance) 28 | #' print(results$Plot_Predator_Importance) 29 | #' 30 | #' @import ggplot2 dplyr 31 | #' @export 32 | calc_svd_entropy_importance <- function(A, threshold_factor = 1e-6) { 33 | 34 | # Detect if input is an igraph object and extract adjacency matrix 35 | if (inherits(A, "igraph")) { 36 | if(is.null(edge_attr(A, "weight"))) { 37 | A <- as_adjacency_matrix(A, sparse = FALSE) 38 | } else { 39 | A <- as_adjacency_matrix(A, attr="weight", sparse = FALSE) 40 | } 41 | } 42 | 43 | # Check if A is a valid matrix 44 | if (!is.matrix(A) || nrow(A) != ncol(A)) { 45 | stop("A must be a square adjacency matrix or an igraph object.") 46 | } 47 | # Compute SVD 48 | svd_result <- svd(A) 49 | 50 | # Extract row and column names from matrix A 51 | 52 | prey_names <- rownames(A) 53 | predator_names <- colnames(A) 54 | if(is.null(prey_names)) { 55 | prey_names <- 1:nrow(A) 56 | } 57 | if(is.null(predator_names)) { 58 | predator_names <- 1:nrow(A) 59 | } 60 | 61 | # Normalize singular values 62 | p <- svd_result$d / sum(svd_result$d) 63 | 64 | # Compute effective rank (rounding small values) 65 | threshold <- max(svd_result$d) * threshold_factor 66 | rank_approx <- sum(svd_result$d > threshold) 67 | 68 | # Compute SVD entropy 69 | svd_entropy <- -log(rank_approx) *sum(p[p > 0] * log(p[p > 0])) 70 | 71 | # Find species contributing to the largest singular value 72 | first_singular_vector_prey <- abs(svd_result$u[, 1]) # Prey contributions 73 | first_singular_vector_pred <- abs(svd_result$v[, 1]) # Predator contributions 74 | 75 | # Assign names 76 | names(first_singular_vector_prey) <- prey_names 77 | names(first_singular_vector_pred) <- predator_names 78 | 79 | # Sort species by importance 80 | prey_importance <- sort(first_singular_vector_prey, decreasing = TRUE) 81 | predator_importance <- sort(first_singular_vector_pred, decreasing = TRUE) 82 | 83 | # Convert to data frame for ggplot 84 | df_singular_values <- tibble(Index = seq_along(svd_result$d), Value = svd_result$d) 85 | df_prey_importance <- tibble(Prey = names(prey_importance), Importance = prey_importance) 86 | df_predator_importance <- tibble(Predator = names(predator_importance), Importance = predator_importance) 87 | 88 | # Plot Singular Values Distribution 89 | p1 <- ggplot(df_singular_values[1:rank_approx,], aes(x = Index, y = Value)) + 90 | geom_point(color = "blue") + 91 | geom_line(color = "blue") + 92 | labs(title = "Singular Value Spectrum", x = "Index", y = "Singular Value") + 93 | theme_bw() 94 | 95 | # Plot Top Contributing Prey 96 | p2 <- ggplot(df_prey_importance[1:10,], aes(x = reorder(Prey, Importance), 97 | y = Importance, fill=Importance)) + 98 | geom_col() + 99 | scale_fill_viridis_c(option = "D", direction = -1) + 100 | guides(fill = FALSE) + 101 | coord_flip() + 102 | labs(x = "Prey", y = "Contribution") + 103 | theme_bw() 104 | 105 | # Plot Top Contributing Predators 106 | p3 <- ggplot(df_predator_importance[1:10,], aes(x = reorder(Predator, Importance), 107 | y = Importance, fill=Importance)) + 108 | geom_col() + 109 | scale_fill_viridis_c(option = "D", direction = -1) + 110 | guides(fill = FALSE) + 111 | coord_flip() + 112 | labs(x = "Predator", y = "Contribution") + 113 | #theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 114 | theme_bw() 115 | 116 | # Return results 117 | list( 118 | Rank = rank_approx, 119 | Entropy = svd_entropy, 120 | ImportancePrey = df_prey_importance, 121 | ImportancePredators = df_predator_importance, 122 | Plot_Singular_Values = p1, 123 | Plot_Prey_Importance = p2, 124 | Plot_Predator_Importance = p3 125 | ) 126 | } 127 | 128 | #' Compute Centrality for a Weighted/Unweighted Network 129 | #' 130 | #' This function calculates the centrality of species in a network 131 | #' based on an adjacency matrix or igraph object using a specified centrality measure. 132 | #' 133 | #' @param A An igraph object or an adjacency matrix. 134 | #' The values represent interaction strengths. 135 | #' @param centrality_func A function to compute centrality. Default is `eigen_centrality`. 136 | #' Other options: `page_rank`, `betweenness`, `degree`, etc. 137 | #' 138 | #' @return A list containing: 139 | #' \item{CentralityScores}{A data frame with species names and their centrality scores.} 140 | #' \item{Plot_Centrality}{A ggplot object showing the top 10 species by centrality.} 141 | #' 142 | #' @examples 143 | #' # Example usage with an adjacency matrix A 144 | #' results <- calc_centrality(A, centrality_func = page_rank) 145 | #' print(results$CentralityScores) 146 | #' print(results$Plot_Centrality) 147 | #' 148 | #' @import igraph ggplot2 viridis 149 | #' @export 150 | calc_centrality <- function(A, centrality_func = eigen_centrality) { 151 | 152 | if (inherits(A, "igraph")) { 153 | graph_A <- A 154 | } else { 155 | # Convert matrix A to an igraph object 156 | graph_A <- graph_from_adjacency_matrix(A, mode = "directed", weighted = TRUE) 157 | } 158 | 159 | # Compute centrality using the specified function 160 | centrality_result <- centrality_func(graph_A, directed = TRUE) 161 | 162 | # Extract the vector if function returns a list 163 | if (is.list(centrality_result) && "vector" %in% names(centrality_result)) { 164 | centrality_result <- centrality_result$vector 165 | } 166 | 167 | # Create a data frame with species names and their centrality scores 168 | if (is.null(names(centrality_result))) { 169 | names(centrality_result) <- 1:vcount(graph_A) 170 | } 171 | 172 | df_centrality <- tibble(Species = names(centrality_result), Centrality = centrality_result) 173 | 174 | # Sort by centrality 175 | df_centrality <- df_centrality[order(-df_centrality$Centrality), ] 176 | 177 | # Plot top 10 species by centrality 178 | p <- ggplot(df_centrality[1:min(10, nrow(df_centrality)),], 179 | aes(x = Centrality, y = reorder(Species, Centrality), fill = Centrality)) + 180 | geom_col() + 181 | scale_fill_viridis_c(option = "D", direction = -1) + 182 | labs(x = "Centrality Score", y = "Species") + 183 | theme_minimal() + guides(fill = FALSE) + 184 | theme(axis.text.y = element_text(angle = 45, hjust = 1)) 185 | 186 | # Return results 187 | list( 188 | CentralityScores = df_centrality, 189 | Plot_Centrality = p 190 | ) 191 | } 192 | 193 | 194 | 195 | 196 | 197 | #' Singular Value Decomposition (SVD) Entropy and Rank Calculation 198 | #' 199 | #' This function calculates the entropy and rank of a network using Singular Value Decomposition (SVD). 200 | #' It accepts either an adjacency matrix or an igraph object and processes it accordingly. 201 | #' 202 | #' @param A An adjacency matrix (square, weighted or unweighted) or an igraph object. 203 | #' @param threshold_factor A small value (default = 1e-6) to determine numerical rank approximation. 204 | #' 205 | #' @return A list containing: 206 | #' \item{Rank}{Effective rank of the matrix.} 207 | #' \item{Entropy}{SVD entropy of the network.} 208 | #' 209 | #' @import igraph 210 | #' @export 211 | calc_svd_entropy <- function(A, threshold_factor = 1e-6) { 212 | #library(igraph) 213 | 214 | # Convert igraph object to adjacency matrix if necessary 215 | if (inherits(A, "igraph")) { 216 | if(is.null(edge_attr(A, "weight"))) { 217 | A <- as.matrix(as_adjacency_matrix(A, sparse = FALSE)) 218 | } else { 219 | A <- as.matrix(as_adjacency_matrix(A, attr = "weight", sparse = FALSE)) 220 | } 221 | } 222 | 223 | # Ensure A is a valid matrix 224 | if (!is.matrix(A)) stop("Input must be an adjacency matrix or an igraph object.") 225 | 226 | # Compute SVD 227 | svd_result <- svd(A) 228 | 229 | # Normalize singular values 230 | p <- svd_result$d / sum(svd_result$d) 231 | 232 | # Compute effective rank (rounding small values) 233 | threshold <- max(svd_result$d) * threshold_factor 234 | rank_approx <- sum(svd_result$d > threshold) 235 | 236 | # Compute SVD entropy 237 | svd_entropy <- -1/log(rank_approx) * sum(p[p > 0] * log(p[p > 0])) 238 | 239 | # Return results 240 | list( 241 | Rank = rank_approx, 242 | Entropy = svd_entropy 243 | ) 244 | } 245 | 246 | 247 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # multiweb 3 | 4 | [![DOI](https://zenodo.org/badge/142077196.svg)](https://zenodo.org/badge/latestdoi/142077196) 5 | 6 | This is an R package to analize ecological networks, including multilayer networks, recently developed network metrics and also some old ones conveniently in the same place. Functions to read, plot and a big set of networks are also included in the package. 7 | 8 | ## Instalation 9 | 10 | ```R 11 | require(devtools) 12 | install_github("lsaravia/multiweb") 13 | ``` 14 | 15 | ## Examples 16 | 17 | Read the example data included in the package 18 | 19 | ```R 20 | require(multiweb) 21 | 22 | fileName <- c(system.file("extdata", package = "multiweb")) 23 | fileName <- system.file("extdata", "BarentsBoreal_FW.csv", package = "multiweb") 24 | g <- readNetwork(fileName) 25 | 26 | 27 | ``` 28 | 29 | Read multiple interaction network in different layers as a list 30 | 31 | ```R 32 | 33 | fileName <- c(system.file("extdata", package = "multiweb")) 34 | dn <- list.files(fileName,pattern = "^Kefi2015.*\\.txt$") 35 | g <- readNetwork(dn,fileName, skipColumn = 2) 36 | ``` 37 | 38 | Convert to mgraph type 39 | 40 | ```R 41 | 42 | gt <- fromIgraphToMgraph(g,c("Negative","Positive","Antagonistic")) 43 | 44 | ``` 45 | 46 | Read multiple interaction network with a function 47 | 48 | ```R 49 | 50 | types <- c("Negative","Positive","Antagonistic") 51 | gt <- readMultiplex(dn,types,fileName, skipColumn = 2) 52 | 53 | ``` 54 | 55 | Calculate QSS (quasi-sign-stability) for multiple interactions networks (mgraph) 56 | 57 | ```R 58 | 59 | calc_QSS(gt) 60 | 61 | 62 | ``` 63 | 64 | 65 | 66 | ## References 67 | 68 | 1. Marina, T. I., Saravia, L. A., Cordone, G., Salinas, V., Doyle, S. R., & Momo, F. R. (2018). Architecture of marine food webs: To be or not be a ‘small-world.’ PLoS ONE, 13(5), 1–13. https://doi.org/10.1371/journal.pone.0198217 69 | 70 | 2. Saravia, L. A., Marina, T. I., Kristensen, N. P., De Troch, M., & Momo, F. R. (2022). Ecological network assembly: How the regional metaweb influences local food webs. Journal of Animal Ecology, 91(3), 630–642. https://doi.org/10.1111/1365-2656.13652 71 | -------------------------------------------------------------------------------- /data/metadata.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsaravia/multiweb/29993d1aeac54ecea58278f416e5e6990343f98e/data/metadata.rda -------------------------------------------------------------------------------- /data/netData.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsaravia/multiweb/29993d1aeac54ecea58278f416e5e6990343f98e/data/netData.rda -------------------------------------------------------------------------------- /man/BuildSupraAdjacencyMatrixFromExtendedEdgelist.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/supraAdjacencyMatrix.R 3 | \name{BuildSupraAdjacencyMatrixFromExtendedEdgelist} 4 | \alias{BuildSupraAdjacencyMatrixFromExtendedEdgelist} 5 | \title{Build supra-adjacency matrix from edge lists} 6 | \usage{ 7 | BuildSupraAdjacencyMatrixFromExtendedEdgelist( 8 | mEdges, 9 | Layers, 10 | Nodes, 11 | isDirected 12 | ) 13 | } 14 | \arguments{ 15 | \item{mEdges}{data frame with extended edge list, i.e a data frame with 16 | columns: \code{node.from, layer.from, node.to, layer.to weight}} 17 | 18 | \item{Layers}{scalar, number of layers} 19 | 20 | \item{Nodes}{scalar, number of nodes} 21 | 22 | \item{isDirected}{logical} 23 | } 24 | \value{ 25 | Supra-adjacency matrix, a square matrix of dimension \code{Nodes * Layers}. 26 | } 27 | \description{ 28 | Build supra-adjacency matrix from edge lists 29 | } 30 | -------------------------------------------------------------------------------- /man/add_interlayer_links.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/supraAdjacencyMatrix.R 3 | \name{add_interlayer_links} 4 | \alias{add_interlayer_links} 5 | \title{Generate Node aligned Interlayer Edges for a Multilayer Network from the function \code{convert_to_intra_format}} 6 | \usage{ 7 | add_interlayer_links(intra_data, interlayer_weight = 1) 8 | } 9 | \arguments{ 10 | \item{intra_data}{A list with \verb{$vertices} (node info) and \verb{$intra} (intralayer edges).} 11 | 12 | \item{interlayer_weight}{Numeric, the weight for interlayer links (default: 1).} 13 | } 14 | \value{ 15 | A dataframe with interlayer edges. 16 | } 17 | \description{ 18 | Generate Node aligned Interlayer Edges for a Multilayer Network from the function \code{convert_to_intra_format} 19 | } 20 | -------------------------------------------------------------------------------- /man/aggregate_multiplex_network.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/aggregateMultiplex.R 3 | \name{aggregate_multiplex_network} 4 | \alias{aggregate_multiplex_network} 5 | \title{Aggregate a Multiplex Network into a Single Layer} 6 | \usage{ 7 | aggregate_multiplex_network(g_list, directed = TRUE) 8 | } 9 | \arguments{ 10 | \item{g_list}{A list of \code{igraph} objects (directed or undirected).} 11 | 12 | \item{directed}{Logical; whether to treat the result as directed (default: \code{TRUE}).} 13 | } 14 | \value{ 15 | An \code{igraph} object representing the aggregate network. 16 | } 17 | \description{ 18 | Combines a list of \code{igraph} layers into one aggregate network. 19 | Edges present in multiple layers will have their weights summed. 20 | } 21 | -------------------------------------------------------------------------------- /man/calc_QSS.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcFunctionalIndices.r 3 | \name{calc_QSS} 4 | \alias{calc_QSS} 5 | \title{Calc the Quasi Sign Stability measure for antagonistic (predator-prey) or mgraph networks with multiple interactions.} 6 | \usage{ 7 | calc_QSS( 8 | ig, 9 | nsim = 1000, 10 | ncores = 0, 11 | negative = -10, 12 | positive = 1, 13 | selfDamping = -1, 14 | istrength = FALSE, 15 | returnRaw = FALSE 16 | ) 17 | } 18 | \arguments{ 19 | \item{ig}{igraph or a list of igraph networks or mgraph network} 20 | 21 | \item{nsim}{number of simulations to calculate QSS, if the number of simulations is 1 then it calculates the maximum eingenvalue for the mean of interaction 22 | strength, if \code{istrength==FALSE} the mean interaction strength are \code{negative/2}, \code{positive/2}, \code{selfDamping/2}.} 23 | 24 | \item{ncores}{number of cores to use in parallel comutation if 0 it uses sequential processing} 25 | 26 | \item{negative}{the maximum magnitude of the negative interaction (the effect of the predator on the prey) must be <= 0} 27 | 28 | \item{positive}{the maximum magnitude of the positive interaction (the effect of the prey on the predator) must be >= 0} 29 | 30 | \item{selfDamping}{the maximum magnitude of the self-limitation (the effect of the species on itself) must be <= 0, only for species with links to itself.} 31 | 32 | \item{istrength}{If TRUE takes the weigth attribute of the network as interaction strength.} 33 | 34 | \item{returnRaw}{if TRUE returns all the values of the maximum eingenvalues} 35 | } 36 | \value{ 37 | if parameter \code{returnRaw} is \code{FALSE} returns a data.frame with the QSS, and MEing, the mean of the real part of the maximum eingenvalue. 38 | If \code{returnRaw} is \code{TRUE} it returns the values of randomized real part of maximum eingenvalue (maxre) 39 | } 40 | \description{ 41 | The QSS measure is the proportion of matrices that are locally stable, these matrices are created by sampling the values of the community matrix 42 | (the Jacobian) from a uniform distribution, preserving the sign structure \link{1}. If the 'ig' parameter is 43 | an \code{mgraph} network it needs to have been built with the order \code{c("Competitive", "Mutualistic", "Trophic")} 44 | It also calculates the mean of the real part of the maximum eingenvalue, which is also a measure of stability \link{2}. 45 | It uses a uniform distribution between 0 and maximum values given by the parameters \code{negative}, \code{positive} and \code{selfDamping}, 46 | corresponding to the sign of interactions and self-limitation effect \link{3,4}. 47 | If the edges of the networks have a weigth attribute and \code{istrength} parameter is true, weigth will be used as interaction strength, 48 | then the limits of the uniform distribution will be \code{negative*-x}, \code{positive*x}, \code{selfDamping*x}, where x is the value of the weigth for the edge. 49 | If the values of these parameters are 0 then there is no interaction of that kind. The default values for \code{negative}, \code{positive} and \code{selfDumping} 50 | assume a maximum ecological transfer efficience of 10\%. 51 | } 52 | \examples{ 53 | \dontrun{ 54 | 55 | g <- netData[[2]] 56 | 57 | tp <- calc_QSS(g) 58 | 59 | # Read Multiplex network and calculate QSS 60 | 61 | fileName <- c(system.file("extdata", package = "multiweb")) 62 | dn <- list.files(fileName,pattern = "^Kefi2015.*\\\\.txt$") 63 | gt <- readMultiplex(dn,types,"inst/extdata", skipColumn = 2) 64 | calc_QSS(gt) 65 | 66 | } 67 | } 68 | \references{ 69 | \enumerate{ 70 | \item Allesina, S. & Pascual, M. (2008). Network structure, predator - Prey modules, and stability in large food webs. 71 | Theor. Ecol., 1, 55–64. 72 | \item Grilli, J., Rogers, T. & Allesina, S. (2016). Modularity and stability in ecological communities. Nat. Commun., 7, 12031 73 | \item Monteiro, A.B. & Del Bianco Faria, L. (2017). Causal relationships between population stability and food-web topology. Functional Ecology, 31, 1294–1300. 74 | \item Borrelli, J. J. 2015. Selection against instability: stable subgraphs are most frequent in empirical food webs. - Oikos 124: 1583–1588. 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /man/calc_QSS_extinction_dif.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcExtinctionIndices.r 3 | \name{calc_QSS_extinction_dif} 4 | \alias{calc_QSS_extinction_dif} 5 | \title{Calculates the QSS difference between the full network and the network minus 6 | one species} 7 | \usage{ 8 | calc_QSS_extinction_dif(g, sp_list, nsim = 1000, ncores = 4, istrength = FALSE) 9 | } 10 | \arguments{ 11 | \item{g}{igraph network} 12 | 13 | \item{sp_list}{list with the species/nodes we will delete for the comparison} 14 | 15 | \item{nsim}{number of simulations to calculate QSS} 16 | 17 | \item{ncores}{number of cores used to perform the operation} 18 | 19 | \item{istrength}{if TRUE takes the weight attribute of the network as interaction strength to 20 | calculate QSS.} 21 | } 22 | \value{ 23 | a data.frame with nsim rows for each species deleted with: 24 | \itemize{ 25 | \item the node deleted 26 | \item The QSS of the complete network for nsim simulations 27 | \item The QSS of the network with the deleted node for nsim simulations 28 | \item The difference between the two previous QSS 29 | } 30 | } 31 | \description{ 32 | The Quasi-sign stability is estimated with of the community matrix (Jacobian) and characterizes 33 | the linear stability of the network. This uses the function \code{\link[=calc_QSS]{calc_QSS()}} so it can take 34 | into account the interaction strength if weights are present. 35 | } 36 | \examples{ 37 | \dontrun{ 38 | g <- netData[[1]] 39 | 40 | # Generate random weights 41 | # 42 | V(g)$weight <- runif(vcount(g)) 43 | 44 | # Without interaction strength 45 | # 46 | calc_QSS_extinction_dif(g,V(g)$name[1:3],nsim=10,istrength = FALSE) 47 | 48 | # With interaction strength 49 | # 50 | calc_QSS_extinction_dif(g,V(g)$name[1:3],nsim=10,istrength = TRUE) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /man/calc_QSS_extinction_dif_grp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcExtinctionIndices.r 3 | \name{calc_QSS_extinction_dif_grp} 4 | \alias{calc_QSS_extinction_dif_grp} 5 | \title{Calculates the QSS difference between the full network and the network minus 6 | a group of species} 7 | \usage{ 8 | calc_QSS_extinction_dif_grp( 9 | g, 10 | sp_list, 11 | nsim = 1000, 12 | ncores = 4, 13 | istrength = FALSE 14 | ) 15 | } 16 | \arguments{ 17 | \item{g}{igraph network} 18 | 19 | \item{sp_list}{list with the group pf species/nodes we will delete for the comparison} 20 | 21 | \item{nsim}{number of simulations to calculate QSS} 22 | 23 | \item{ncores}{number of cores used to perform the operation} 24 | 25 | \item{istrength}{if TRUE takes the weight attribute of the network as interaction strength to 26 | calculate QSS.} 27 | } 28 | \value{ 29 | a data.frame with: 30 | \itemize{ 31 | \item Size of the network with deleted nodes 32 | \item Connectance of the network with deleted nodes 33 | \item Number of components of the network with deleted nodes 34 | \itemize{ 35 | \item QSS of the complete network 36 | \item QSS of the network with the deleted nodes 37 | \item difference between the two previous QSS 38 | } 39 | } 40 | } 41 | \description{ 42 | The Quasi-sign stability is estimated with the maximum eigenvalue of the community matrix (Jacobian) and characterizes 43 | the linear stability of the network. This uses the function \code{\link[=calc_QSS]{calc_QSS()}} so it can take 44 | into account the interaction strength if weights are present. The comparison is made using the Anderson-Darling test with 45 | the function \code{\link[kSamples:ad.test]{kSamples::ad.test()}} and the Kolmogorov-Smirnov test \code{\link[stats:ks.test]{stats::ks.test()}}, both the p-values are reported as a 46 | measure of strength of the difference. 47 | } 48 | \examples{ 49 | \dontrun{ 50 | g <- netData[[1]] 51 | 52 | # Generate random weights 53 | # 54 | V(g)$weight <- runif(vcount(g)) 55 | 56 | # Without interaction strength 57 | # 58 | calc_QSS_extinction_dif_grp(g,V(g)$name[1:3],nsim=10,istrength = FALSE) 59 | 60 | # With interaction strength 61 | # 62 | calc_QSS_extinction_dif_grp(g,V(g)$name[1:3],nsim=10,istrength = TRUE) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /man/calc_QSS_extinctions_seq.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcExtinctionIndices.r 3 | \name{calc_QSS_extinctions_seq} 4 | \alias{calc_QSS_extinctions_seq} 5 | \title{Calculates the QSS for an extinction sequence} 6 | \usage{ 7 | calc_QSS_extinctions_seq( 8 | g_del, 9 | seq, 10 | nsim = 1000, 11 | ncores = 4, 12 | istrength = FALSE 13 | ) 14 | } 15 | \arguments{ 16 | \item{g_del}{igraph object for the deletion sequence} 17 | 18 | \item{seq}{vector or list with the nodes for the extinction sequence, the 19 | extinctions are incremental.} 20 | 21 | \item{nsim}{number of simulations used in QSS calculation} 22 | 23 | \item{ncores}{number of cores used to calculate QSS} 24 | 25 | \item{istrength}{parameter istrength for QSS function} 26 | } 27 | \value{ 28 | data frame with the number of remaining nodes, the connectance, the number unconnected of components, the median QSS, and the name 29 | of the last deleted node. 30 | } 31 | \description{ 32 | This functions calculates the QSS \code{\link[=calc_QSS]{calc_QSS()}} for a sequence of incremental deletions of species (nodes) 33 | given by the vector \code{seq}, it does not produce secondary extinctions 34 | } 35 | \examples{ 36 | \dontrun{ 37 | g <- netData[[1]] 38 | 39 | # Generate random weights 40 | # 41 | V(g)$weight <- runif(vcount(g)) 42 | 43 | # Without interaction strength 44 | # 45 | calc_QSS_extinctions_seq(g,V(g)$name[1:3],nsim=10,istrength = FALSE) 46 | 47 | # With interaction strength 48 | # 49 | calc_QSS_extinctions_seq(g,V(g)$name[1:3],nsim=10,istrength = TRUE) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /man/calc_centrality.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/svdcentrality.R 3 | \name{calc_centrality} 4 | \alias{calc_centrality} 5 | \title{Compute Centrality for a Weighted/Unweighted Network} 6 | \usage{ 7 | calc_centrality(A, centrality_func = eigen_centrality) 8 | } 9 | \arguments{ 10 | \item{A}{An igraph object or an adjacency matrix. 11 | The values represent interaction strengths.} 12 | 13 | \item{centrality_func}{A function to compute centrality. Default is \code{eigen_centrality}. 14 | Other options: \code{page_rank}, \code{betweenness}, \code{degree}, etc.} 15 | } 16 | \value{ 17 | A list containing: 18 | \item{CentralityScores}{A data frame with species names and their centrality scores.} 19 | \item{Plot_Centrality}{A ggplot object showing the top 10 species by centrality.} 20 | } 21 | \description{ 22 | This function calculates the centrality of species in a network 23 | based on an adjacency matrix or igraph object using a specified centrality measure. 24 | } 25 | \examples{ 26 | # Example usage with an adjacency matrix A 27 | results <- calc_centrality(A, centrality_func = page_rank) 28 | print(results$CentralityScores) 29 | print(results$Plot_Centrality) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /man/calc_incoherence.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcTopologicalIndices.r 3 | \name{calc_incoherence} 4 | \alias{calc_incoherence} 5 | \alias{calcIncoherence} 6 | \title{Calculate the incoherence index of a food web} 7 | \usage{ 8 | calc_incoherence(ig, ncores = 0) 9 | } 10 | \arguments{ 11 | \item{ig}{an igraph object or a list of igraph objects} 12 | 13 | \item{ncores}{number of cores used to compute in parallel, if 0 sequential processing is used.} 14 | } 15 | \value{ 16 | a data.frame with the following fields 17 | 18 | \item{Q}{incoherence (0=coherent)} 19 | \item{rQ}{ratio of Q with expected Q under null expectation of a random network given N=nodes L=links B=basal nodes Lb=basal links} 20 | \item{mTI}{mean trophic level} 21 | \item{rTI}{ratio of mTI with expected TI under the same null model expectation than Q} 22 | } 23 | \description{ 24 | The incoherence index is based in how the species fit in discrete trophic levels 25 | when Q is closer to 0 more coherent and stable is a food web, and the less omnivory it has. 26 | It calculates the trophic level using the package NetIndices. 27 | } 28 | \details{ 29 | Based on: 30 | } 31 | \examples{ 32 | 33 | calc_incoherence(netData[[1]]) 34 | 35 | 36 | } 37 | \references{ 38 | Johnson, S., Domínguez-García, V., Donetti, L., & Muñoz, M. A. (2014). Trophic coherence determines food-web stability. Proceedings of the National Academy of Sciences , 111(50), 17923–17928. https://doi.org/10.1073/pnas.1409077111 39 | 40 | Johnson, S., & Jones, N. S. (2017). Looplessness in networks is linked to trophic coherence. Proceedings of the National Academy of Sciences, 114(22), 5618–5623. https://doi.org/10.1073/pnas.1613786114 41 | } 42 | -------------------------------------------------------------------------------- /man/calc_interaction_intensity.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcInteractionIntensity.r 3 | \name{calc_interaction_intensity} 4 | \alias{calc_interaction_intensity} 5 | \title{Calculates the interaction intensity of a food web using the metabolic theory and 6 | the interaction dimensionality} 7 | \usage{ 8 | calc_interaction_intensity(da, res_mm, res_den, con_mm, int_dim, nsims = 1) 9 | } 10 | \arguments{ 11 | \item{da}{data.frame with the interactions body mass and type of interaction dimensionality} 12 | 13 | \item{res_mm}{name of the column with the resource body mass mean} 14 | 15 | \item{res_den}{name of the column with the resource density in Individuals/m^2 in 2D or m^3 in 3D. If lower than 0 it uses the previously mentioned 16 | estimation.} 17 | 18 | \item{con_mm}{name of the column with the consumer body mass mean} 19 | 20 | \item{int_dim}{name of the column with the interaction dimensionality} 21 | } 22 | \value{ 23 | A data.frame based on \code{da} with the following fields added 24 | \itemize{ 25 | \item mR: if \code{res_mm<0} is the resource mass calculated with the equations from ref 1, 26 | if \code{res_mm>0} duplicates the value of \code{res_mm} 27 | \item xR: calculated resource density or the same value as in the input data.frame. 28 | \item alfa: calculated search rate. 29 | \item qRC: calculated trophic interaction strength as \code{alfa*xR*mR/mC} where \code{mC} is the consumer body mass. 30 | } 31 | } 32 | \description{ 33 | The function uses the body mass in Kg of predator/consumer and prey/resources and the dimensionality of the interaction as source data, 34 | then the interaction intensity is estimated with all the coefficients from Pawar (2012) as \code{alfa*xR*mR/mC}, where \code{alpha} is the search rate \code{xR} 35 | the resource density, \code{mR} the resource body mass and \code{mC} the consumer body mass. This value of the interaction strength quantifies 36 | the effect of the predator on the prey by biomass unit of the predator. Assuming a Lotka-Volterra model is equivalent to the entry A(i,j) of the community matrix, where i is the 37 | prey and j the predator. 38 | } 39 | \details{ 40 | If the resource density is unknown (parameter \code{res_den}) you could set the column to a less than 0 value; and it 41 | will be estimated according to the equation S18 and supplementary figures 2i & j (individuals/m2 - m3) 42 | 43 | If the mean mass of the resource for detritus or sediment (parameter \code{res_mm}) is unknown, it can be designated as 44 | negative. This will result in the calculation of the resource body mass (in kilograms) using allometric formulas given 45 | in the Equation S9 and Supplementary Figures 2c & d from the paper. This is only valid when the size ratios tend 46 | to be optimal. 47 | 48 | If the Biomass of the resource is known you should use it as \code{res_mm} and set \code{res_den} to 1. This is the best choice to 49 | avoid the previous allometric calculations of \code{res_den} and \code{res_mm} when they are unknown. 50 | 51 | If resource size \code{res_mm} and resource density \code{res_den} are decoupled from consumer size you could assign 1 to both see 52 | pag 487 \strong{Dimensionality and trophic interaction strengths} in Pawar's paper. 53 | 54 | If the parameter 'nsims > 1 ' the function will estimate the variability on each interaction strength. It takes random values from a normal distribution 55 | with mean and standard deviation given by the Pawar's regressions for the slopes of allometric exponents. 56 | } 57 | \examples{ 58 | \dontrun{ 59 | g <- netData[[1]] 60 | 61 | require(dplyr) 62 | 63 | # build the data.frame with random values 64 | 65 | set.seed(7815) 66 | da <- as_long_data_frame(g) \%>\% dplyr::select(from:to) \%>\% mutate(con_mm=rlnorm(n(),5,2),res_mm=con_mm - 30 ,int_dim=sample(c("2D","3D"),n(),replace=TRUE), res_den = -999) 67 | 68 | calc_interaction_intensity(da,res_mm,res_den,con_mm,int_dim, nsims=1) 69 | } 70 | } 71 | \references{ 72 | \enumerate{ 73 | \item Pawar, S., Dell, A. I., & Van M. Savage. (2012). Dimensionality of consumer search space drives trophic interaction strengths. Nature, 486, 485. https://doi.org/10.1038/nature11131 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /man/calc_interaction_intensity2.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcInteractionIntensity.r 3 | \name{calc_interaction_intensity2} 4 | \alias{calc_interaction_intensity2} 5 | \title{Calculates the interaction intensity of a food web using allometric scaling relationships 6 | to approximate basal metabolic rate} 7 | \usage{ 8 | calc_interaction_intensity2( 9 | edge_list, 10 | consumer_n, 11 | resource_n, 12 | bodymass_n, 13 | b = 0.01, 14 | e = 0.1, 15 | output_format = "edgelist" 16 | ) 17 | } 18 | \arguments{ 19 | \item{edge_list}{A tibble containing three columns: consumer, resource, and body mass.} 20 | 21 | \item{consumer_n}{The column name for consumers (predators).} 22 | 23 | \item{resource_n}{The column name for resources (prey).} 24 | 25 | \item{bodymass_n}{The column name for body mass of the consumer.} 26 | 27 | \item{b}{Free parameter (default \code{0.01}).} 28 | 29 | \item{output_format}{Output type: \code{"edgelist"} (default, containing only predator effects) or \code{"matrix"} (full adjacency matrix).} 30 | } 31 | \value{ 32 | A tibble (if \code{output_format = "edgelist"}) with interaction strengths \strong{only for predators on prey} 33 | on a column named \code{qRC}. If \code{output_format = "matrix"}, an adjacency matrix with interaction strengths. 34 | If \code{output_format = "igraph" }, an igraph object where edge weights represent predator effects on prey. 35 | Always the IS is a positive number. 36 | } 37 | \description{ 38 | The function uses the body mass of predator/consumer and the number of prey/resources as source data, 39 | then the interaction intensity is estimated based on O'Gorman (2010) as \code{a_ij = -b*(M_jˆ0.25 / s_j)}, where \code{M_j} is the body mass of predator j, 40 | \code{s_j} is the number of preys, \code{b} is a free parameter assumed 0.01. This value of the interaction strength quantifies 41 | the effect of the predator on the prey per unit of biomass. Assuming a Lotka-Volterra model is equivalent 42 | to the entry A(i,j) of the community matrix, where i is the 43 | prey and j the predator. To predict the direct effect of each prey on its predator, \code{a_ji}, 44 | we could assume an ecological efficiency, e=0.1, reflecting a 10\% transfer of energy between trophic levels, 45 | hence \code{a_ji = e * a_ij} 46 | } 47 | \details{ 48 | The function provides two output formats: an edge list with only the \strong{effect of predators on prey} or 49 | an adjacency matrix as a positive number. 50 | } 51 | \examples{ 52 | library(tibble) 53 | 54 | # Define an edge list (consumer, resource, bodymass) 55 | edge_list <- tibble( 56 | predator = c("A", "A", "B", "C"), 57 | prey = c("B", "C", "D", "D"), 58 | predator_mass = c(10, 10, 5, 3) # Body mass for consumers 59 | ) 60 | 61 | # Compute interaction strength as an edge list (predator effects only) 62 | interaction_strength_edgelist <- calc_interaction_intensity2( 63 | edge_list, consumer_n = predator, resource_n = prey, bodymass_n = predator_mass, output_format = "edgelist" 64 | ) 65 | print(interaction_strength_edgelist) 66 | 67 | # Compute interaction strength as an adjacency matrix 68 | interaction_strength_matrix <- calc_interaction_intensity2( 69 | edge_list, predator, prey, predator_mass, output_format = "matrix" 70 | ) 71 | print(interaction_strength_matrix) 72 | } 73 | \references{ 74 | O’Gorman, E. J., Jacob, U., Jonsson, T., & Emmerson, M. C. (2010). Interaction strength, food web topology and the 75 | relative importance of species in food webs. Journal of Animal Ecology, 79(3), 682–692. https://doi.org/10.1111/j.1365-2656.2009.01658.x 76 | } 77 | -------------------------------------------------------------------------------- /man/calc_modularity.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modularity.R 3 | \name{calc_modularity} 4 | \alias{calc_modularity} 5 | \title{Calculation of Modularity for a List of igraph Objects} 6 | \usage{ 7 | calc_modularity(ig, ncores = 0, cluster_function = cluster_spinglass) 8 | } 9 | \arguments{ 10 | \item{ig}{A list of igraph objects for which modularity will be calculated.} 11 | 12 | \item{ncores}{Number of cores used for parallel computation. If set to 0, sequential processing is used.} 13 | 14 | \item{cluster_function}{The \strong{igraph} clustering function to use, or a function that returns modularity. 15 | default \code{cluster_spinglass}.} 16 | 17 | \item{weights}{Edge weights. Can be a numeric vector, \code{NULL}, or \code{NA}: 18 | \itemize{ 19 | \item If \code{NULL}, the function checks for a 'weight' edge attribute and uses it if present. 20 | \item If \code{NA}, weights are ignored even if the graph has a 'weight' edge attribute. 21 | }} 22 | } 23 | \value{ 24 | A data frame with a column \code{Modularity} containing the modularity values for each network. 25 | } 26 | \description{ 27 | This function calculates the modularity of a list of networks provided in the \code{ig} parameter. 28 | Modularity measures the strength of division of a network into modules (communities). 29 | } 30 | \details{ 31 | \strong{Note:} \code{cluster_spinglass()} only works on networks with a single connected component. 32 | } 33 | \examples{ 34 | \dontrun{ 35 | nullg <- generateERbasal(netData[[1]], 10) 36 | calc_modularity(nullg, cluster_function = cluster_infomap) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /man/calc_modularity_swness_zscore.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcTopologicalIndices.r 3 | \name{calc_modularity_swness_zscore} 4 | \alias{calc_modularity_swness_zscore} 5 | \title{Calculation of Modularity and Small-world-ness z-scores} 6 | \usage{ 7 | calc_modularity_swness_zscore( 8 | g, 9 | nullDist, 10 | sLevel = 0.01, 11 | ncores = 0, 12 | weights = NA 13 | ) 14 | } 15 | \arguments{ 16 | \item{g}{igraph object} 17 | 18 | \item{nullDist}{list of igraph object with the null model simulations} 19 | 20 | \item{sLevel}{significance level to calculate CI (two tails)} 21 | 22 | \item{ncores}{number of cores to use paralell computation, if 0 sequential processing is used.} 23 | 24 | \item{weights}{The weights of the edges. Either a numeric vector or NULL or NA. If it is null and the input graph has a ‘weight’ edge attribute 25 | then that will be used. If NULL and no such attribute is present then the edges will have equal weights. 26 | Set this to NA if the graph was a ‘weight’ edge attribute, but you don't want to use it for community detection.} 27 | } 28 | \value{ 29 | a list with two data frames: one with indices z-scores and CI 30 | 31 | \item{Clustering}{ Clustering coefficient, measures the average fraction of pairs of neighbors of a node that are also neighbors of each other} 32 | \item{PathLength}{ Mean of the shortest paths between all pair of vertices } 33 | \item{Modularity}{ modularity measures how separated are different groups from each other, the algorithm \code{cluster_spinglass} was used to obtain the groups} 34 | \item{zCC,zCP,zMO}{Z-scores of Clustering,PathLength and Modularity with respect to a random Erdos-Renyi null model} 35 | \item{CClow,CChigh,CPlow,CPhigh,MOlow,MOhigh}{sLevel confidence intervals} 36 | \item{SWness,SWnessCI}{ Small-world-ness and it CI value} 37 | \item{isSW,isSWness}{ Logical variable signalling if the network is Small-world by the method of Marina 2018 or the method of Humprhies & Gurney 2008 } 38 | 39 | Another data.frame with the values calculated for the nullDist. 40 | } 41 | \description{ 42 | The function calculates modularity, number of groups and small-world-ness z-scores and 99\\% CI intervals 43 | using as null model the list of networks in the nullDist parameters. Modularity is calculated using the \code{\link[igraph:cluster_spinglass]{igraph::cluster_spinglass()}} 44 | if the parameter weights is NULL the atribute "weigths" is used, or if it has the name of an network attribute, that is used as a weigth 45 | to build the modules, when this parameter is NA then no weigth is used. 46 | Only works for one component networks 47 | } 48 | \examples{ 49 | \dontrun{ 50 | nullg <- generateERbasal(netData[[1]],10) 51 | calcModularitySWnessZScore(netData[[1]],nullg) 52 | } 53 | 54 | } 55 | \references{ 56 | Marina, T. I., Saravia, L. A., Cordone, G., Salinas, V., Doyle, S. R., & Momo, F. R. (2018). Architecture of marine food webs: To be or not be a ‘small-world.’ PLoS ONE, 13(5), 1–13. https://doi.org/10.1371/journal.pone.0198217 57 | } 58 | -------------------------------------------------------------------------------- /man/calc_quantitative_connectance.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcFunctionalIndices.r 3 | \name{calc_quantitative_connectance} 4 | \alias{calc_quantitative_connectance} 5 | \alias{calcQuantitativeConnectance} 6 | \title{Calculates the quantitative connectance, and the effective number of flows using the interaction matrix and a vector of species abundances} 7 | \usage{ 8 | calc_quantitative_connectance(interM, d) 9 | } 10 | \arguments{ 11 | \item{interM}{per capita interaction strength matrix} 12 | 13 | \item{d}{species' abundances vector} 14 | } 15 | \value{ 16 | A list with Cq,the quantitative connectance index and LDq, = 17 | } 18 | \description{ 19 | The Quantitative connectance (Cq) takes into account both the distribution of per capita interaction strengths 20 | among species in the web and the distribution of species’ abundances and quantifies the diversity of network fluxes, 21 | if all the species have the same flux is equal to the directed connectance. 22 | The mean or effective number of flows impinging upon or emanating from a tipical node (LDq) is based the average flow diversity, and when all flows are equal is similar to linkage density. Both measures are based in Shannon information theory. 23 | The total interaction flux is measured 24 | as \code{T[i,j] <- d[i] * d[j] * interM[i,j]}. The effective Cq is calculated following the formulas in appendix 2 of \link{1}, LDq follows \link{2} 25 | } 26 | \examples{ 27 | 28 | # 3 predators 2 preys unequal fluxes 29 | # 30 | m <- matrix() 31 | matrix(0,nrow=4,ncol=4) 32 | m[1,2] <- m[1,3] <- m[3,4]<- .2 33 | m[2,1] <- m[3,1] <- m[4,3] <- -2 34 | 35 | calc_quantitative_connectance(m, c(1,1,1,1)) 36 | 37 | # Equal input and output fluxes 38 | 39 | m <- matrix(0,nrow=4,ncol=4) 40 | m[1,2] <- m[1,3] <- m[3,4]<- 2 41 | m[2,1] <- m[3,1] <- m[4,3] <- -2 42 | calc_quantitative_connectance(m, c(1,1,1,1)) 43 | } 44 | \references{ 45 | \enumerate{ 46 | \item Fahimipour, A.K. & Hein, A.M. (2014). The dynamics of assembling food webs. Ecol. Lett., 17, 606–613 47 | \item Ulanowicz, R.E. & Wolff, W.F. (1991). Ecosystem flow networks: Loaded dice? Math. Biosci., 103, 45–68 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /man/calc_svd_entropy.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/svdcentrality.R 3 | \name{calc_svd_entropy} 4 | \alias{calc_svd_entropy} 5 | \title{Singular Value Decomposition (SVD) Entropy and Rank Calculation} 6 | \usage{ 7 | calc_svd_entropy(A, threshold_factor = 1e-06) 8 | } 9 | \arguments{ 10 | \item{A}{An adjacency matrix (square, weighted or unweighted) or an igraph object.} 11 | 12 | \item{threshold_factor}{A small value (default = 1e-6) to determine numerical rank approximation.} 13 | } 14 | \value{ 15 | A list containing: 16 | \item{Rank}{Effective rank of the matrix.} 17 | \item{Entropy}{SVD entropy of the network.} 18 | } 19 | \description{ 20 | This function calculates the entropy and rank of a network using Singular Value Decomposition (SVD). 21 | It accepts either an adjacency matrix or an igraph object and processes it accordingly. 22 | } 23 | -------------------------------------------------------------------------------- /man/calc_svd_entropy_importance.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/svdcentrality.R 3 | \name{calc_svd_entropy_importance} 4 | \alias{calc_svd_entropy_importance} 5 | \title{Singular Value Decomposition (SVD) Analysis of Networks} 6 | \usage{ 7 | calc_svd_entropy_importance(A, threshold_factor = 1e-06) 8 | } 9 | \arguments{ 10 | \item{A}{A square adjacency matrix where rows represent prey and columns represent predators. 11 | The values represent interaction strengths.} 12 | } 13 | \value{ 14 | A list containing: 15 | \item{Rank}{The numerical rank of the matrix after small values are rounded for numerical stability.} 16 | \item{Entropy}{The entropy of the singular value distribution.} 17 | \item{Prey_Importance}{A data frame ranking prey species by their contribution to the largest singular value.} 18 | \item{Predator_Importance}{A data frame ranking predator species by their contribution to the largest singular value.} 19 | \item{Singular_Values}{A numeric vector of the singular values of the matrix.} 20 | \item{Plot_Singular_Values}{A ggplot object showing the distribution of singular values.} 21 | \item{Plot_Prey_Importance}{A ggplot object showing the top prey species contributing to the largest singular value.} 22 | \item{Plot_Predator_Importance}{A ggplot object showing the top predator species contributing to the largest singular value.} 23 | } 24 | \description{ 25 | This function performs Singular Value Decomposition (SVD) on a network adjacency matrix. 26 | It computes species importance based on the dominant singular values and returns entropy, rank, and key visualizations. 27 | } 28 | \examples{ 29 | 30 | results <- calc_svd_entropy_importance(netData[[1]]) 31 | print(results$Rank) 32 | print(results$Entropy) 33 | print(results$Plot_Singular_Values) 34 | print(results$Plot_Prey_Importance) 35 | print(results$Plot_Predator_Importance) 36 | 37 | } 38 | \seealso{ 39 | \code{\link{calc_svd_entropy}} for a lighter version returning only entropy and rank. 40 | } 41 | -------------------------------------------------------------------------------- /man/calc_swness_zscore.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcTopologicalIndices.r 3 | \name{calc_swness_zscore} 4 | \alias{calc_swness_zscore} 5 | \title{Calculation Small-world-ness z-scores} 6 | \usage{ 7 | calc_swness_zscore(g, nullDist, sLevel = 0.01, ncores = 0, weights = NA) 8 | } 9 | \arguments{ 10 | \item{g}{igraph object} 11 | 12 | \item{nullDist}{list of igraph object with the null model simulations} 13 | 14 | \item{sLevel}{significance level to calculate CI (two tails)} 15 | 16 | \item{ncores}{number of cores to use paralell computation, if 0 sequential processing is used.} 17 | } 18 | \value{ 19 | a list with two data frames: one with indices z-scores and CI 20 | 21 | \item{Clustering}{ Clustering coefficient, measures the average fraction of pairs of neighbors of a node that are also neighbors of each other} 22 | \item{PathLength}{ Mean of the shortest paths between all pair of vertices } 23 | \item{Modularity}{ modularity measures how separated are different groups from each other, the algorithm \code{cluster_spinglass} was used to obtain the groups} 24 | \item{zCC,zCP,zMO}{Z-scores of Clustering,PathLength and Modularity with respect to a random Erdos-Renyi null model} 25 | \item{CClow,CChigh,CPlow,CPhigh,MOlow,MOhigh}{sLevel confidence intervals} 26 | \item{SWness,SWnessCI}{ Small-world-ness and it CI value} 27 | \item{isSW,isSWness}{ Logical variable signalling if the network is Small-world by the method of Marina 2018 or the method of Humprhies & Gurney 2008 } 28 | 29 | Another data.frame with the values calculated for the nullDist. 30 | } 31 | \description{ 32 | The function calculates small-world-ness z-scores and 99\\% CI intervals, 33 | using as null model the list of networks in the nullDist parameter. 34 | } 35 | \examples{ 36 | \dontrun{ 37 | nullg <- generateERbasal(netData[[1]],10) 38 | calc_swness_zscore(netData[[1]],nullg) 39 | } 40 | 41 | } 42 | \references{ 43 | Marina, T. I., Saravia, L. A., Cordone, G., Salinas, V., Doyle, S. R., & Momo, F. R. (2018). Architecture of marine food webs: To be or not be a ‘small-world.’ PLoS ONE, 13(5), 1–13. https://doi.org/10.1371/journal.pone.0198217 44 | } 45 | -------------------------------------------------------------------------------- /man/calc_topological_indices.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcTopologicalIndices.r 3 | \name{calc_topological_indices} 4 | \alias{calc_topological_indices} 5 | \alias{calcTopologicalIndices} 6 | \title{Calculate topological indices for ecological networks. The mean trophic level 7 | and Omnivory and the Level of omnivory are calculated with the function \code{\link[NetIndices:TrophInd]{NetIndices::TrophInd()}}} 8 | \usage{ 9 | calc_topological_indices(ig, ncores = 0) 10 | } 11 | \arguments{ 12 | \item{ig}{vector of igraph objects} 13 | 14 | \item{ncores}{number of cores used to compute in parallel, if 0 sequential processing is used.} 15 | } 16 | \value{ 17 | a data.frame with the following fields: 18 | \itemize{ 19 | \item Network: Name of the network if the list have names 20 | \item Size: Number of species 21 | \item Top: Number of top predator species 22 | \item Basal: Number of basal especies 23 | \item Omnivory: Proportion of omnivorous species 24 | \item Links: number of interactions 25 | \item LD: linkage density 26 | \item Connectance: directed Connectance 27 | \item PathLength: average path length 28 | \item Clustering: clustering coeficient 29 | \item Cannib: number of cannibalistic species 30 | \item TLmean: mean trophic level 31 | \item TLmax: maximum trophic level 32 | \item LOmnivory: Level of omnivory, quantifies mean of the variety in trophic levels of the preys of a consumer 33 | \item Components: number of weakly connected components 34 | \item Vulnerability: mean of number of consumers per prey 35 | \item VulSD: the standard deviation of normalized Vulnerability 36 | \item Generality: mean number of prey per consumer 37 | \item GenSD: the standard deviation of normalized Generality 38 | } 39 | } 40 | \description{ 41 | Calculate topological indices for ecological networks. The mean trophic level 42 | and Omnivory and the Level of omnivory are calculated with the function \code{\link[NetIndices:TrophInd]{NetIndices::TrophInd()}} 43 | } 44 | \examples{ 45 | 46 | calc_topological_indices(netData) 47 | 48 | # Generate a test network 49 | 50 | g <- graph_from_literal( 1 -+ 4 -+ 7,2 -+ 5 -+7, 3-+6-+7, 7-+7, 4+-3, simplify = FALSE) 51 | 52 | calc_topological_indices(g) 53 | 54 | } 55 | -------------------------------------------------------------------------------- /man/calc_topological_roles.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcTopologicalIndices.r 3 | \name{calc_topological_roles} 4 | \alias{calc_topological_roles} 5 | \title{Calc topological roles among network communities/modules} 6 | \usage{ 7 | calc_topological_roles(g, nsim = 1000, ncores = 0, community = NULL) 8 | } 9 | \arguments{ 10 | \item{g}{an Igraph object with the network} 11 | 12 | \item{nsim}{number of simulations for \code{\link[igraph:cluster_spinglass]{igraph::cluster_spinglass()}} method} 13 | 14 | \item{ncores}{number of cores to use paralell computation, if 0 sequential processing is used.} 15 | 16 | \item{community}{a community object obtained with other method, if NULL the community is calculated with \code{\link[igraph:cluster_spinglass]{igraph::cluster_spinglass()}}} 17 | } 18 | \value{ 19 | a data frame with two numeric fields: within_module_degree, among_module_conn 20 | } 21 | \description{ 22 | Topological roles characterize species as its roles between communities or modules, we calculate the modules using 23 | \code{\link[igraph:cluster_spinglass]{igraph::cluster_spinglass()}} function. Alternatively you can pass a community object obtained with other method using the 24 | parameter \code{community}. 25 | Topological roles are described by two parameters: the standardized within-module degree \eqn{dz} and the among-module 26 | connectivity participation coefficient \eqn{PC}. The within-module degree is a z-score that measures how well a species is 27 | connected to other species within its own module compared with a random graph. The participation coefficient \eqn{PC} 28 | estimates the distribution of the links of species among modules. As the community algorithm is stochastic we run it several 29 | times and return the repeated runs for both parameters. 30 | } 31 | \examples{ 32 | #' \dontrun{ 33 | 34 | g <- netData[[2]] 35 | 36 | tp <- calc_topological_roles(g,nsim=10) 37 | 38 | # using a community object 39 | 40 | m <- cluster_walktrap(g) 41 | 42 | tp <- calc_topological_roles(g,community=m) 43 | } 44 | } 45 | \references{ 46 | \enumerate{ 47 | \item Guimerà, R. & Nunes Amaral, L.A. (2005). Functional cartography of complex metabolic networks. Nature, 433, 895–900 48 | \item Kortsch, S. et al. 2015. Climate change alters the structure of arctic marine food webs due to poleward shifts of boreal generalists. - Proceedings of the Royal Society B: Biological Sciences 282: 20151546. https://doi.org/10.1098/rspb.2015.1546 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /man/calc_weighted_topological_indices.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcFunctionalIndices.r 3 | \name{calc_weighted_topological_indices} 4 | \alias{calc_weighted_topological_indices} 5 | \title{Function to calculate weighted network indices} 6 | \usage{ 7 | calc_weighted_topological_indices(ig, ncores = 0) 8 | } 9 | \arguments{ 10 | \item{ig}{An igraph object with the weight attribute representing fluxes} 11 | 12 | \item{ncores}{number of cores used to compute in parallel, if 0 sequential processing is used.} 13 | } 14 | \value{ 15 | a data.frame with the following fields: 16 | \itemize{ 17 | \item LD: linkage density 18 | \item Connectance: directed Connectance 19 | \item TLmean: mean trophic level 20 | \item TLmax: maximum trophic level 21 | \item LOmnivory: Level of omnivory, quantifies mean of the variety in trophic levels of the preys of a consumer 22 | \item Vulnerability: mean of number of consumers per prey 23 | \item VulSD: the standard deviation of normalized Vulnerability 24 | \item Generality: mean number of prey per consumer 25 | \item GenSD: the standard deviation of normalized Generality 26 | } 27 | } 28 | \description{ 29 | The function calculates: weighted linkage density, connectance, generality and vulnerability and the SDs of the last two based on \link{1} 30 | and the level of omnivory, mean and maximum trophic level based on \link{2} using \code{\link[NetIndices:TrophInd]{NetIndices::TrophInd()}} function. 31 | The igraph networks must have the weight attribute. 32 | } 33 | \examples{ 34 | 35 | # Generate a test network 36 | 37 | g <- graph_from_literal( 1 -+ 4 -+ 7,2 -+ 5 -+7, 3-+6-+7, 7-+7, 4+-3, simplify = FALSE) 38 | 39 | # Add weight 40 | 41 | E(g)$weight <- sample(c(.1,.2,.8,.9),gsize(g),replace=TRUE) 42 | 43 | calc_weighted_topological_indices(g) 44 | } 45 | \references{ 46 | \enumerate{ 47 | \item Bersier, LF. et al. (2002). Quantitative Descriptors Of Food-web Matrices. Ecology, 83(9), 2394–2407. 48 | \item Kones, JK. et al. (2009). Are network indices robust indicators of food web functioning? A Monte Carlo approach. 49 | Ecological Modelling, 220, 370–382. 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /man/classify_topological_roles.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calcTopologicalIndices.r 3 | \name{classify_topological_roles} 4 | \alias{classify_topological_roles} 5 | \title{Classify and plot topological roles} 6 | \usage{ 7 | classify_topological_roles(tRoles, g, community = NULL) 8 | } 9 | \arguments{ 10 | \item{tRoles}{A data frame of calculated topological roles obtained from \code{\link[=calc_topological_roles]{calc_topological_roles()}}.} 11 | 12 | \item{g}{An igraph network object.} 13 | 14 | \item{community}{An igraph community object used to determine module membership. If NULL, the function calculates community structure using \code{cluster_spinglass()}.} 15 | } 16 | \value{ 17 | A list containing: 18 | \itemize{ 19 | \item \code{hub_conn}: A data frame with node classifications and topological role values. 20 | \item \code{gp}: A ggplot object visualizing the topological roles. 21 | } 22 | } 23 | \description{ 24 | This function classifies species into four topological roles based on their within-module degree (dz) and among-module connectivity (PC), following the approach of Kortsch (2015). The classification is as follows: 25 | \itemize{ 26 | \item \strong{modhub}: Species with high within-module degree (dz >= 2.5) and low among-module connectivity (PC < 0.625). 27 | \item \strong{hubcon}: Species with high within-module degree (dz >= 2.5) and high among-module connectivity (PC >= 0.625). 28 | \item \strong{modspe}: Species with low within-module degree (dz < 2.5) and low among-module connectivity (PC < 0.625). 29 | \item \strong{modcon}: Species with low within-module degree (dz < 2.5) and high among-module connectivity (PC >= 0.625). 30 | } 31 | } 32 | \details{ 33 | The function also generates a ggplot visualization of the species' topological roles, with nodes colored according to their module membership. 34 | } 35 | \examples{ 36 | \dontrun{ 37 | 38 | g <- netData[[2]] 39 | 40 | tp <- calc_topological_roles(g,nsim=10) 41 | 42 | classify_topological_roles(tp,g) 43 | } 44 | } 45 | \references{ 46 | \enumerate{ 47 | \item Kortsch, S., Primicerio, R., Fossheim, M., Dolgov, A. V., & Aschan, M. (2015). Climate change alters the structure of arctic marine food webs due to poleward shifts of boreal generalists. 48 | Proceedings of the Royal Society B: Biological Sciences, 282(1814), 20151546. https://doi.org/10.1098/rspb.2015.1546 49 | \item Saravia, L. A., Marina, T. I., Kristensen, N. P., De Troch, M., & Momo, F. R. (2022). Ecological network assembly: How the regional metaweb influences local food webs. 50 | Journal of Animal Ecology, 91(3), 630–642. https://doi.org/10.1111/1365-2656.13652 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /man/convert_infomap_result_to_muxviz.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_multiplex.R 3 | \name{convert_infomap_result_to_muxviz} 4 | \alias{convert_infomap_result_to_muxviz} 5 | \title{Convert Infomap Community Output to muxViz-Compatible Format} 6 | \usage{ 7 | convert_infomap_result_to_muxviz(communities) 8 | } 9 | \arguments{ 10 | \item{communities}{A data frame returned by \code{run_infomap_multi()$communities}, containing columns \code{node}, \code{layer}, \code{module}, and \code{flow}.} 11 | } 12 | \value{ 13 | A named list with the following components: 14 | \describe{ 15 | \item{membership.multi}{A data frame with columns \code{node}, \code{layer}, and \code{module} representing the state node assignment. Node and layer are returned as integers (1-based index).} 16 | \item{membership.aggr}{A data frame with columns \code{node} and \code{module}, where each physical node is assigned to its dominant module across all layers.} 17 | \item{modules.multi}{Integer; the number of unique modules detected in the multilayer network.} 18 | \item{modules.aggr}{Integer; the number of unique modules in the aggregated network.} 19 | } 20 | } 21 | \description{ 22 | This function converts the community detection result from \code{run_infomap_multi()} (from the \code{multiweb} package) 23 | into a format compatible with \code{muxViz}, allowing direct use with visualization function \code{plot_multimodules()}. 24 | } 25 | \details{ 26 | It creates two key data frames: 27 | \itemize{ 28 | \item \code{membership.multi}: Community membership for each node-layer combination. 29 | \item \code{membership.aggr}: Aggregate community membership per physical node (across layers), 30 | based on the highest flow. 31 | } 32 | } 33 | \examples{ 34 | \dontrun{ 35 | # Run Infomap community detection on a multiplex ecological network 36 | g_list <- readNetwork(dn, fileName, skipColumn = 2) 37 | names(g_list) <- c("Negative", "Positive", "Trophic") 38 | result <- run_infomap_multi(g_list, layer_names = names(g_list)) 39 | 40 | # Convert for muxViz plotting 41 | mux_data <- convert_infomap_result_to_muxviz(result$communities) 42 | 43 | # Visualize 44 | plot_multimodules(mux_data) 45 | } 46 | 47 | } 48 | \seealso{ 49 | \code{\link{run_infomap_multi}}, \code{\link{plot_multi3D}}, \code{\link{plot_multimodules}} 50 | } 51 | -------------------------------------------------------------------------------- /man/convert_to_intra_format.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modularity.R 3 | \name{convert_to_intra_format} 4 | \alias{convert_to_intra_format} 5 | \title{Convert a List of igraph Objects to Intralayer Edge Format} 6 | \usage{ 7 | convert_to_intra_format(igraph_list, use_names = FALSE) 8 | } 9 | \arguments{ 10 | \item{igraph_list}{A list of \code{igraph} objects, each representing a network layer.} 11 | 12 | \item{use_names}{Logical; if \code{TRUE}, edges will use node names instead of numeric IDs (default: \code{FALSE}).} 13 | } 14 | \value{ 15 | A list with two data frames: 16 | \item{vertices}{A data frame containing node_id and corresponding node names.} 17 | \item{intra}{A data frame containing intra-layer edges with columns: \code{layer_id}, \code{node_id1}, \code{node_id2}, \code{weight}.} 18 | } 19 | \description{ 20 | This function converts a multilayer network, represented as a list of igraph objects, 21 | into a standardized format suitable for Infomap and other multilayer network analyses. 22 | } 23 | \details{ 24 | Each node is assigned a unique numeric ID, and edges across layers are recorded 25 | with layer-specific identifiers. 26 | } 27 | \examples{ 28 | \dontrun{ 29 | multi_format <- convert_to_intra_format(list(layer1, layer2)) 30 | print(multi_format$vertices) 31 | print(multi_format$intra) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /man/convert_to_supra_adjacency.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/supraAdjacencyMatrix.R 3 | \name{convert_to_supra_adjacency} 4 | \alias{convert_to_supra_adjacency} 5 | \title{Convert Multilayer Network to Supra-Adjacency Matrix (SAM)} 6 | \usage{ 7 | convert_to_supra_adjacency( 8 | igraph_list, 9 | layer_names = NULL, 10 | interlayer_weight = 1, 11 | isDirected = TRUE, 12 | sparse = TRUE, 13 | use_names = FALSE, 14 | interlayer = TRUE, 15 | clean = TRUE 16 | ) 17 | } 18 | \arguments{ 19 | \item{igraph_list}{A list of \code{igraph} objects (each representing a layer).} 20 | 21 | \item{layer_names}{(Optional) A vector of names for each layer.} 22 | 23 | \item{interlayer_weight}{Numeric, the weight for interlayer links (default: 1).} 24 | 25 | \item{isDirected}{Logical, whether the network is directed.} 26 | 27 | \item{sparse}{Logical, whether to return the supra-adjacency matrix as a sparse matrix (default: TRUE).} 28 | 29 | \item{use_names}{Logical; if \code{TRUE}, edges will use node names instead of numeric IDs (default: \code{FALSE}).} 30 | 31 | \item{interlayer}{Logical; if \code{FALSE}, no interlayer links are added (default: \code{TRUE}).} 32 | 33 | \item{clean}{Logical; if \code{TRUE}, removes empty rows/columns (state nodes with no edges) from the output (default: TRUE).} 34 | } 35 | \value{ 36 | A list with: 37 | \item{supra_matrix}{Supra-adjacency matrix with optional inter-layer links.} 38 | \item{state_nodes_map}{Mapping of state nodes (layer-node combinations).} 39 | } 40 | \description{ 41 | Convert Multilayer Network to Supra-Adjacency Matrix (SAM) 42 | } 43 | -------------------------------------------------------------------------------- /man/curve_ball.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/models.r 3 | \name{curve_ball} 4 | \alias{curve_ball} 5 | \alias{curveBall} 6 | \title{Curbe ball algorithm to generate random networks with given an igraph network} 7 | \usage{ 8 | curve_ball(g, nsim = 1000, istrength = FALSE) 9 | } 10 | \arguments{ 11 | \item{g}{igraph object to extract adjacency matrix} 12 | 13 | \item{nsim}{number of generated random networks} 14 | 15 | \item{istrength}{if TRUE the edge attribute weight is taken as interaction strength and randomized keeping the column sum equal to the original matrix.} 16 | } 17 | \value{ 18 | a list of randomized igraph objects 19 | } 20 | \description{ 21 | This extract the adjacency matrix from an igraph object and generates a randomized version 22 | of the network with the same row and column totals, the results have the same in and out degree sequence 23 | than the original network. The diagonals are not avoided so it can generate self-links or cannibalism in 24 | the context of food-webs. In the case that the network has multiple components (or disconnected networks) 25 | the algorithm simulates around the same number of components, if the original network has 1 component 26 | the algorithm enforces that the results have all 1 component. If the edge attribute weight is present 27 | and istrength=TRUE then the weigth is additionally randomized keeping the column sum equal to the original matrix. 28 | } 29 | \details{ 30 | Based on: 31 | } 32 | \examples{ 33 | 34 | curve_ball(netData[[1]]) 35 | 36 | } 37 | \references{ 38 | Strona, G. et al. 2014. A fast and unbiased procedure to randomize ecological binary matrices with 39 | fixed row and column totals. -Nat. Comm. 5: 4114. doi: 10.1038/ncomms5114 40 | } 41 | -------------------------------------------------------------------------------- /man/fromGLVadjToIgraph.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/readNetwork.r 3 | \name{fromGLVadjToIgraph} 4 | \alias{fromGLVadjToIgraph} 5 | \title{From Generalized Lotka Volterra adjacency matrix to igraph topological object} 6 | \usage{ 7 | fromGLVadjToIgraph(glvAdj, spc) 8 | } 9 | \arguments{ 10 | \item{glvAdj}{numeric matrix Generalized Lotka-Volterra adjacency matrix} 11 | 12 | \item{spc}{numeric vector of present Species} 13 | } 14 | \value{ 15 | an igraph object 16 | } 17 | \description{ 18 | It counts predator-prey/Antagonistic interactions like 1 edge, 19 | competition and mutualisms are counted as is: two edges or one edge 20 | } 21 | \examples{ 22 | # Build a matrix 23 | 24 | m <- matrix(0,nrow=5,ncol=5) 25 | m[1,2] <- m[1,3] <- m[3,4]<- .2 26 | m[2,1] <- m[3,1] <- m[4,3] <- -2 27 | m[5,4] <- m[4,5] <- 0.1 # Mutualistic 28 | m[1,1] <- -0.01 # Cannibalistic 29 | 30 | g <- fromGLVadjToIgraph(m,c(1,1,1,1,0)) 31 | 32 | g <- fromGLVadjToIgraph(m,c(0,1,1,1,1)) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /man/fromIgraphToMgraph.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/readNetwork.r 3 | \name{fromIgraphToMgraph} 4 | \alias{fromIgraphToMgraph} 5 | \title{From multiple igraph objects to 'mgraph'} 6 | \usage{ 7 | fromIgraphToMgraph(g, types) 8 | } 9 | \arguments{ 10 | \item{types}{vector of types that represent the layers with the same length as mg} 11 | 12 | \item{mg}{list of 'igraph' objects} 13 | } 14 | \value{ 15 | class 'mgraph' object 16 | } 17 | \description{ 18 | This functions takes a list of igraph objects and convert it to a 'mgraph' object. 19 | } 20 | \examples{ 21 | 22 | # Read a vector of files 23 | # 24 | \dontrun{ 25 | 26 | fileName <- c(system.file("extdata", package = "multiweb")) 27 | dn <- list.files("inst/extdata",pattern = "^Kefi2015.*\\\\.txt$") 28 | g <- readNetwork(dn,"inst/extdata", skipColumn = 2) 29 | fromIgraphToMgraph(g,c("Negative","Positive","Antagonistic")) 30 | 31 | } 32 | } 33 | \seealso{ 34 | \code{\link[=readMultiplex]{readMultiplex()}} 35 | } 36 | -------------------------------------------------------------------------------- /man/generate_er_basal.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/models.r 3 | \name{generate_er_basal} 4 | \alias{generate_er_basal} 5 | \alias{generateERbasal} 6 | \title{Generate directed Erdos-Renyi random networks with at least 1 basal node and only one component} 7 | \usage{ 8 | generate_er_basal(ig, nsim = 1000) 9 | } 10 | \arguments{ 11 | \item{ig}{igraph object with parameters to use in the random network simulations: number of species/nodes 12 | and number of links/edges} 13 | 14 | \item{nsim}{number of simulations} 15 | } 16 | \value{ 17 | a list with igraph objects 18 | } 19 | \description{ 20 | This uses the igraph's function sample_gnm to generate nsim random networks with the same number of nodes 21 | and links than the parameter ig and two restrictions: 22 | \enumerate{ 23 | \item at least one basal species/node, that is a species that has no prey, 2) 1 connected component so there is no 24 | disconnected species or sub-community. 25 | } 26 | } 27 | \examples{ 28 | 29 | generateERbasal(netData[[1]]) 30 | } 31 | -------------------------------------------------------------------------------- /man/generate_niche.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/models.r 3 | \name{generate_niche} 4 | \alias{generate_niche} 5 | \title{Generate Niche Model Food Web(s)} 6 | \usage{ 7 | generate_niche(S, C, nsim = 1) 8 | } 9 | \arguments{ 10 | \item{S}{Integer. The number of species in the community. Must be \code{S > 1}.} 11 | 12 | \item{C}{Numeric. The connectance (fraction of realized links). Must be \verb{0 < C ≤ 1}.} 13 | 14 | \item{nsim}{Integer. The number of networks to generate (\verb{nsim ≥ 1}).} 15 | } 16 | \value{ 17 | If \code{nsim = 1}, returns a \strong{binary adjacency matrix (\verb{S × S})}. 18 | If \code{nsim > 1}, returns a \strong{list of \code{igraph} objects}, each representing a food web. 19 | } 20 | \description{ 21 | This function generates one or multiple food webs using the \strong{Niche Model} proposed by Williams & Martinez (2000). 22 | The model assumes that each species has a niche value and consumes resources within a defined range. 23 | } 24 | \examples{ 25 | generate_niche(20, 0.1) # Single adjacency matrix 26 | generate_niche(20, 0.1, nsim=5) # List of 5 food webs as igraph objects 27 | } 28 | \references{ 29 | Williams, R. J., and N. D. Martinez. 2000. Simple rules yield complex food webs. \emph{Nature} 404:180–183. 30 | } 31 | -------------------------------------------------------------------------------- /man/generate_shuffled_seq.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffling.R 3 | \name{generate_shuffled_seq} 4 | \alias{generate_shuffled_seq} 5 | \title{Generate a Sequence of Shuffled Networks and Track Metric Evolution} 6 | \usage{ 7 | generate_shuffled_seq( 8 | original_graph, 9 | max_delta = 10, 10 | delta = 10, 11 | directed = TRUE, 12 | weighted = TRUE, 13 | shuffle_func = shuffle_network_ws, 14 | modularity_func = cluster_infomap 15 | ) 16 | } 17 | \arguments{ 18 | \item{original_graph}{An igraph object representing the original network.} 19 | 20 | \item{max_delta}{Integer. Number of total shuffling steps to perform.} 21 | 22 | \item{delta}{Integer. Number of swaps per shuffling step.} 23 | 24 | \item{directed}{Logical. Whether the network is directed (default: TRUE).} 25 | 26 | \item{weighted}{Logical. Whether the network is weighted (default: TRUE).} 27 | 28 | \item{shuffle_func}{Function. A network shuffling function (default: \code{shuffle_network_ws}).} 29 | 30 | \item{modularity_func}{Function. A modularity calculation function (default: \code{cluster_infomap}).} 31 | } 32 | \value{ 33 | A list with: 34 | \item{Networks}{A list of igraph objects representing the shuffled networks at each step.} 35 | \item{Metrics}{A tibble containing the step number, SVD entropy, SVD Rank, and modularity score.} 36 | } 37 | \description{ 38 | This function generates a sequence of shuffled networks from an original graph, applying 39 | incremental shuffling steps while tracking modularity, SVD rank and entropy . 40 | } 41 | -------------------------------------------------------------------------------- /man/generate_shuffled_seq_tol.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffling.R 3 | \name{generate_shuffled_seq_tol} 4 | \alias{generate_shuffled_seq_tol} 5 | \title{Generate a Sequence of Shuffled Networks and Track SVD/Modularity Evolution} 6 | \usage{ 7 | generate_shuffled_seq_tol( 8 | input_graph, 9 | delta = 10, 10 | max_iterations = 100, 11 | tolerance = 0.001, 12 | directed = TRUE, 13 | weighted = TRUE, 14 | modularity_func = cluster_infomap, 15 | shuffle_func = shuffle_network_deg 16 | ) 17 | } 18 | \arguments{ 19 | \item{input_graph}{An igraph object or adjacency matrix (directed or undirected, weighted or unweighted).} 20 | 21 | \item{delta}{Integer. Number of link swaps per iteration (default: 10).} 22 | 23 | \item{max_iterations}{Integer. Maximum number of iterations until network metrics stabilize (default: 100).} 24 | 25 | \item{tolerance}{Numeric. Tolerance for metric stability - minimum change in modularity or SVD entropy (default: 1e-3).} 26 | 27 | \item{directed}{Logical. Whether the network is directed (default: TRUE).} 28 | 29 | \item{weighted}{Logical. Whether the network is weighted (default: TRUE).} 30 | 31 | \item{modularity_func}{Function. Function to calculate modularity (default: \code{cluster_infomap}).} 32 | 33 | \item{shuffle_func}{Function. Function to perform network shuffling (default: \code{shuffle_network_deg}).} 34 | } 35 | \value{ 36 | A list containing: 37 | \describe{ 38 | \item{New_A}{Shuffled adjacency matrix} 39 | \item{Metrics}{Data frame tracking metric evolution across iterations, including the original network} 40 | } 41 | } 42 | \description{ 43 | This function generates a sequence of shuffled networks from an original graph by applying 44 | incremental shuffling steps. It tracks modularity and SVD entropy throughout the process, 45 | and stops when the change in these metrics falls below a specified \code{tolerance} threshold. 46 | } 47 | -------------------------------------------------------------------------------- /man/harmonize_node_sets.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_multiplex.R 3 | \name{harmonize_node_sets} 4 | \alias{harmonize_node_sets} 5 | \title{Harmonize Node Sets Across Layers in a Multiplex Network} 6 | \usage{ 7 | harmonize_node_sets(g.list) 8 | } 9 | \arguments{ 10 | \item{g.list}{A list of igraph objects representing the multiplex layers.} 11 | } 12 | \value{ 13 | A list of igraph objects with identical node sets. 14 | } 15 | \description{ 16 | This function ensures that all igraph layers in a multiplex network have the same set of nodes. 17 | Nodes missing in any layer are added as isolated nodes (no edges). 18 | } 19 | \examples{ 20 | g.list <- harmonize_node_sets(g.list) 21 | } 22 | -------------------------------------------------------------------------------- /man/metadata.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.r 3 | \docType{data} 4 | \name{metadata} 5 | \alias{metadata} 6 | \title{Metadata for \code{netData}} 7 | \format{ 8 | A data frame 9 | \describe{ 10 | \item{Network}{Name of the network} 11 | \item{Longitude}{Longitude of the geographical coordinates of the network} 12 | \item{Latitude}{Latitude of the geographical coordinates of the network} 13 | \item{Date}{Date range or approximate date when the data was collected} 14 | \item{InteractionsSource}{Method used to determine the interactions} 15 | \item{Reference}{Reference of the study from where data was obtained} 16 | \item{ReferenceLink}{URL of the reference} 17 | } 18 | } 19 | \source{ 20 | \url{https://doi.org/10.1371/journal.pone.0198217} 21 | \enumerate{ 22 | \item Marina, T. I., Saravia, L. A., Cordone, G., Salinas, V., Doyle, S. R., & Momo, F. R. (2018). Architecture of marine food webs: To be or not be a ‘small-world.’ PLoS ONE, 13(5), 1–13. https://doi.org/10.1371/journal.pone.0198217 23 | } 24 | } 25 | \usage{ 26 | metadata 27 | } 28 | \description{ 29 | A data.frame with extended information about the 29 networks from (1), included in \code{netData} 30 | } 31 | \keyword{datasets} 32 | -------------------------------------------------------------------------------- /man/netData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.r 3 | \docType{data} 4 | \name{netData} 5 | \alias{netData} 6 | \title{Compilation of Ecological Networks (food webs) in igraph format.} 7 | \format{ 8 | A list with 29 igraph network objects 9 | } 10 | \source{ 11 | \url{https://doi.org/10.1371/journal.pone.0198217} 12 | } 13 | \usage{ 14 | netData 15 | } 16 | \description{ 17 | A dataset 29 curated and highly-resolved marine networks (food webs), updated from (1) 18 | } 19 | \references{ 20 | \enumerate{ 21 | \item Marina, T. I. et al. 2018. Architecture of marine food webs: To be or not be a ‘small-world.’ - PLoS ONE 13: 1–13. 22 | } 23 | } 24 | \keyword{datasets} 25 | -------------------------------------------------------------------------------- /man/plot_multi3D.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_multiplex.R 3 | \name{plot_multi3D} 4 | \alias{plot_multi3D} 5 | \title{3D Visualization of a Multiplex Ecological Network} 6 | \usage{ 7 | plot_multi3D( 8 | g.list, 9 | layer.colors, 10 | as.undirected = T, 11 | layer.layout = "auto", 12 | layer.labels = "auto", 13 | layer.labels.cex = 2, 14 | edge.colors = "auto", 15 | edge.normalize = F, 16 | edge.size.scale = 1, 17 | node.colors = "auto", 18 | node.size.values = 0.5, 19 | node.size.scale = 1, 20 | node.alpha = 1, 21 | edge.alpha = 1, 22 | layer.alpha = "auto", 23 | layout = "fr", 24 | show.nodeLabels = F, 25 | show.aggregate = F, 26 | aggr.alpha = "auto", 27 | aggr.color = "#dadada", 28 | node.colors.aggr = "#dadada", 29 | layer.scale = 2, 30 | layer.shift.x = 0, 31 | layer.shift.y = 0, 32 | layer.space = 1.5, 33 | FOV = 30 34 | ) 35 | } 36 | \arguments{ 37 | \item{g.list}{A list of \code{igraph} objects, each representing a layer of the multiplex network.} 38 | 39 | \item{layer.colors}{A vector of colors corresponding to each network layer.} 40 | 41 | \item{as.undirected}{Logical; if \code{TRUE}, converts each layer to undirected for visualization purposes (default: \code{TRUE}).} 42 | 43 | \item{layer.layout}{A layout matrix shared by all layers, or \code{"auto"} to use \code{layoutMultiplex()} (default: \code{"auto"}).} 44 | 45 | \item{layer.labels}{A character vector for labeling layers. \code{"auto"} assigns "Layer 1", "Layer 2", etc. (default: \code{"auto"}).} 46 | 47 | \item{layer.labels.cex}{Numeric; text size for layer labels (default: \code{2}).} 48 | 49 | \item{edge.colors}{Edge colors (a single color or vector per layer), or \code{"auto"} to match layer color (default: \code{"auto"}).} 50 | 51 | \item{edge.normalize}{Logical; if \code{TRUE}, normalizes edge weights using logarithmic scale (default: \code{FALSE}).} 52 | 53 | \item{edge.size.scale}{Numeric or vector to control edge thickness (default: \code{1}).} 54 | 55 | \item{node.colors}{\code{"auto"} to use layer colors, or an \verb{N x L} matrix for node colors per layer.} 56 | 57 | \item{node.size.values}{\code{"auto"} to size nodes by strength, or a fixed numeric value (default: \code{0.5}).} 58 | 59 | \item{node.size.scale}{Numeric or vector to scale node size (default: \code{1}).} 60 | 61 | \item{node.alpha}{Transparency for nodes (0–1; default: \code{1}).} 62 | 63 | \item{edge.alpha}{Transparency for edges (0–1; default: \code{1}).} 64 | 65 | \item{layer.alpha}{A numeric vector with layer transparency, or \code{"auto"} to set 0.5 for all layers.} 66 | 67 | \item{layout}{A character string indicating the layout method for \code{layoutMultiplex()} (default: \code{"fr"}).} 68 | 69 | \item{show.nodeLabels}{Logical; if \code{TRUE}, node labels are shown (default: \code{FALSE}).} 70 | 71 | \item{show.aggregate}{Logical; if \code{TRUE}, a layer with the aggregated network is shown (default: \code{FALSE}).} 72 | 73 | \item{aggr.alpha}{Transparency for the aggregate layer (default: \code{"auto"} which is set to 0.5).} 74 | 75 | \item{aggr.color}{Color of the background layer for the aggregate network (default: \code{"#dadada"}).} 76 | 77 | \item{node.colors.aggr}{Node color used in the aggregate network (default: \code{"#dadada"}).} 78 | 79 | \item{layer.scale}{Scale factor for the extent of each layer (default: \code{2}).} 80 | 81 | \item{layer.shift.x}{Horizontal X shift per layer (default: \code{0}).} 82 | 83 | \item{layer.shift.y}{Horizontal Y shift per layer (default: \code{0}).} 84 | 85 | \item{layer.space}{Vertical spacing between layers along Z (default: \code{1.5}).} 86 | 87 | \item{FOV}{Field of view for the \code{rgl} perspective visualization (default: \code{30}).} 88 | } 89 | \description{ 90 | This function generates a 3D visualization of a multiplex network composed of multiple layers 91 | (e.g., trophic, mutualistic, and competitive networks) using \code{rgl}. Layers are displayed stacked 92 | along the Z-axis. Node positions are shared across layers, and nodes can be colored by module 93 | membership or functional type. An optional aggregate layer (merging all layers) can also be displayed. 94 | } 95 | \details{ 96 | Each layer is visualized as a semi-transparent plane, on which the respective network is plotted. 97 | Nodes are shared across layers and maintain consistent positions. Optionally, the aggregated network 98 | (union of all layers) can be visualized at the top. This function is designed for ecological multiplex 99 | networks and facilitates interpretation of modular structure, interaction types, or perturbation effects. 100 | 101 | This function is adapted from the \code{plot_multiplex3D()} function 102 | in the \href{https://manlius.github.io/muxViz/}{\code{muxViz}} project. 103 | } 104 | \examples{ 105 | \dontrun{ 106 | library(multiweb) 107 | fileName <- system.file("extdata", package = "multiweb") 108 | dn <- list.files(fileName, pattern = "^Kefi2015.*\\\\.txt$") 109 | g_list <- readNetwork(dn, fileName, skipColumn = 2) 110 | names(g_list) <- c("Negative", "Positive", "Trophic") 111 | 112 | 113 | # Visualize with community-colored nodes 114 | plot_multi3D( 115 | g.list = g_list, 116 | layer.colors = RColorBrewer::brewer.pal(3, "Set2"), 117 | node.colors = "auto", 118 | node.size.scale = 0.5, 119 | node.size.values = "auto", 120 | node.colors.aggr = "#999999", 121 | show.aggregate = TRUE 122 | ) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /man/plot_multiplex_modules.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plot_multiplex.R 3 | \name{plot_multiplex_modules} 4 | \alias{plot_multiplex_modules} 5 | \title{Plot Modular Structure of a Multiplex Ecological Network} 6 | \usage{ 7 | plot_multiplex_modules( 8 | g_list, 9 | communities, 10 | module_palette = NULL, 11 | layout_graph = NULL, 12 | scale_factor = 500, 13 | y_by_trophic = FALSE, 14 | show_labels = FALSE 15 | ) 16 | } 17 | \arguments{ 18 | \item{g_list}{A named list of \code{igraph} objects (e.g., from \code{readNetwork()}), each representing one layer.} 19 | 20 | \item{communities}{A data frame from \code{run_infomap_multi()$communities}, containing \code{node}, \code{module}, \code{layer}, \code{flow}.} 21 | 22 | \item{module_palette}{Optional named color palette for module IDs (default uses \code{viridis::viridis}).} 23 | 24 | \item{layout_graph}{Optional igraph object for layout (default is aggregate union of all layers).} 25 | 26 | \item{scale_factor}{Numeric multiplier for node size scaling by flow (default: 500).} 27 | 28 | \item{y_by_trophic}{Logical; if TRUE, adjusts vertical layout position using trophic level from "Trophic" layer (default: FALSE).} 29 | 30 | \item{show_labels}{Logical; if TRUE, show node names using ggrepel (default: FALSE).} 31 | } 32 | \value{ 33 | A named list of \code{ggplot} objects, one for each network layer. 34 | } 35 | \description{ 36 | This function visualizes each layer of a multiplex ecological network, with nodes colored 37 | by module membership and sized by Infomap flow values. It returns a list of \code{ggplot} objects. 38 | } 39 | \examples{ 40 | fileName <- system.file("extdata", package = "multiweb") 41 | dn <- list.files(fileName, pattern = "^Kefi2015.*\\\\.txt$") 42 | g_list <- readNetwork(dn, fileName, skipColumn = 2) 43 | names(g_list) <- c("Negative", "Positive", "Trophic") 44 | 45 | res <- run_infomap_multi(g_list, layer_names = names(g_list)) 46 | plots <- plot_multiplex_modules(g_list, res$communities, y_by_trophic = TRUE) 47 | cowplot::plot_grid(plotlist = plots, ncol = 3) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /man/plot_troph_level.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plotNetwork.r 3 | \name{plot_troph_level} 4 | \alias{plot_troph_level} 5 | \alias{plotTrophLevel} 6 | \title{Plot ecological network organized by trophic level, with node size determined by the node degree, and modules.} 7 | \usage{ 8 | plot_troph_level( 9 | g, 10 | vertexLabel = FALSE, 11 | vertexSizeFactor = 5, 12 | vertexSizeMin = 5, 13 | tk = FALSE, 14 | modules = FALSE, 15 | lMat = NULL, 16 | weights = NA, 17 | community_obj = NULL, 18 | bpal = NULL, 19 | maxTL = NULL, 20 | ... 21 | ) 22 | } 23 | \arguments{ 24 | \item{vertexLabel}{logical plot vertex labels} 25 | 26 | \item{vertexSizeFactor}{numeric factor to determine the size of the label with degree} 27 | 28 | \item{vertexSizeMin}{numeric determine the minimum size of the label} 29 | 30 | \item{tk}{TRUE generate an interactive plot using tkplot and returns a matrix with coordinates from \code{\link[igraph:tkplot.getcoords]{igraph::tkplot.getcoords()}}} 31 | 32 | \item{modules}{if TRUE plot modules in the x axis, obtained with the cluster spinglass algorithm} 33 | 34 | \item{lMat}{Matrix of postions for the nodes} 35 | 36 | \item{weights}{The weights of the edges for the \code{\link[igraph:cluster_spinglass]{igraph::cluster_spinglass()}} community detection, either a numeric vector, NULL, or NA. 37 | if NULL and the network has a 'weight' attribute then that will be used, 38 | if NA then the 'weight' attributte is not considered.} 39 | 40 | \item{community_obj}{Insteado of calculating modules/communities with cluster spinglass take a community object} 41 | 42 | \item{bpal}{if NULL it uses the "RdYlGn" RColorBrewer palette, else must be a vector of colors of length 11.} 43 | 44 | \item{maxTL}{maximum trophic level to draw y-axis} 45 | 46 | \item{...}{Addittional parameters to the plot function} 47 | 48 | \item{ig}{igraph object} 49 | } 50 | \value{ 51 | returns a plot and if tk==TRUE returns a layout matrix 52 | } 53 | \description{ 54 | Plot ecological network organized by trophic level, with node size determined by the node degree, and modules. 55 | } 56 | \examples{ 57 | 58 | plot_troph_level(netData[[1]]) 59 | } 60 | -------------------------------------------------------------------------------- /man/plot_troph_level_ggplot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plotNetworkGgplot.R 3 | \name{plot_troph_level_ggplot} 4 | \alias{plot_troph_level_ggplot} 5 | \title{Plot an Ecological Network Organized by Trophic Level} 6 | \usage{ 7 | plot_troph_level_ggplot( 8 | g, 9 | vertexSizeFactor = 5, 10 | vertexSizeMin = 5, 11 | modules = FALSE, 12 | weights = NA, 13 | community_obj = NULL, 14 | maxTL = NULL, 15 | use_numbers = FALSE, 16 | label_size = 4, 17 | arrow_size = 0.15, 18 | shorten_factor = 0.005 19 | ) 20 | } 21 | \arguments{ 22 | \item{g}{An \code{igraph} object representing the ecological network.} 23 | 24 | \item{vertexSizeFactor}{Numeric factor to determine node size based on degree (default: 5).} 25 | 26 | \item{vertexSizeMin}{Numeric value for the minimum node size (default: 5).} 27 | 28 | \item{modules}{Logical; if \code{TRUE}, nodes are grouped by community modules detected using \code{cluster_spinglass()} (default: \code{FALSE}).} 29 | 30 | \item{weights}{A numeric vector, \code{NULL}, or \code{NA}, specifying edge weights for community detection. 31 | If \code{NULL}, the function uses the \code{weight} attribute of \code{g} if available. If \code{NA}, weights are ignored (default: \code{NA}).} 32 | 33 | \item{community_obj}{An optional community detection object. If \code{NULL}, the function calculates modules automatically (default: \code{NULL}).} 34 | 35 | \item{maxTL}{Numeric, maximum trophic level to display on the y-axis. If \code{NULL}, it is determined automatically (default: \code{NULL}).} 36 | 37 | \item{use_numbers}{Logical; if \code{TRUE}, nodes are labeled with numeric IDs instead of species names to reduce label overlap in large networks (default: \code{FALSE}).} 38 | 39 | \item{label_size}{Numeric, font size for node labels (default: 4).} 40 | 41 | \item{arrow_size}{Numeric, size of the arrowheads for directed edges (default: 0.15).} 42 | 43 | \item{shorten_factor}{Numeric, a small factor to adjust arrow placement by slightly shortening edges. 44 | This prevents arrowheads from overlapping with node centers. A small positive value (e.g., 0.005) works well (default: \code{0.005}).} 45 | } 46 | \value{ 47 | A \code{ggplot} object visualizing the trophic structure of the network. 48 | If \code{use_numbers = TRUE}, it also returns a \code{tibble} mapping numeric labels to species names. 49 | } 50 | \description{ 51 | This function visualizes a food web using \code{ggplot2}, where nodes represent species and edges represent interactions. 52 | Nodes are positioned by trophic level on the y-axis, and optionally, community modules on the x-axis. 53 | } 54 | \examples{ 55 | 56 | g <- netData$Angola 57 | plot_troph_level_ggplot(g, modules = TRUE, use_numbers = TRUE) 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /man/readMultiplex.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/readNetwork.r 3 | \name{readMultiplex} 4 | \alias{readMultiplex} 5 | \title{Read ecological multiplex networks using different files for each layer as a 'node-aligned' multilayer network} 6 | \usage{ 7 | readMultiplex( 8 | fileName, 9 | types = c("Negative", "Positive", "Antagonistic"), 10 | filePath = NULL, 11 | fhead = TRUE, 12 | skipColumn = 1, 13 | format = "layers" 14 | ) 15 | } 16 | \arguments{ 17 | \item{fileName}{vector of fileNames with the layers of the networks} 18 | 19 | \item{types}{vector of types that represent the layers} 20 | 21 | \item{filePath}{path of the files NULL by default} 22 | 23 | \item{fhead}{TRUE if the files have header fields, FALSE otherwise.} 24 | 25 | \item{format}{string, "layers" is the default were different layers are coded as different files 26 | there must be the same number of files as the length of the types vector as each type represent 27 | a layer. "GLV" Represent multiple interaction types as pairs of entries in a matrix so competition 28 | is represented as a\link{i,j}= -1, a\link{j,i}=-1, predation a\link{i,j}=1,a\link{j,i}=-1 where species j is 29 | the predator. Mutualism is a\link{i,j}=a\link{j,i}=1. Any negative or positive number works because intensity of interactions are not registered.} 30 | 31 | \item{skipColum}{integer, number of columns that are skiped 1 by default} 32 | } 33 | \value{ 34 | an mgraph object that is a list of igraph objects with the type attribute set to each kind of layer or interaction 35 | } 36 | \description{ 37 | This functions uses \code{\link[=readNetwork]{readNetwork()}} to read files that represent network layers that can be 38 | different interaction types, represented by the type attribute of the igraph object. 39 | } 40 | \examples{ 41 | 42 | # Read a vector of files 43 | # 44 | \dontrun{ 45 | 46 | fpath <- system.file("extdata", package = "multiweb") 47 | dn <- list.files(fpath,pattern = "^Kefi2015.*\\\\.txt$") 48 | netData <- readMultiplex(dn,c("Negative","Positive","Antagonistic"),fpath,skipColum=2) 49 | } 50 | } 51 | \seealso{ 52 | \code{\link[=readNetwork]{readNetwork()}} 53 | } 54 | -------------------------------------------------------------------------------- /man/readNetwork.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/readNetwork.r 3 | \name{readNetwork} 4 | \alias{readNetwork} 5 | \title{Read ecological networks in CSV or tab separated file format as edge list or adyacency matrix} 6 | \usage{ 7 | readNetwork( 8 | fileName, 9 | filePath = NULL, 10 | fhead = TRUE, 11 | skipColumn = 1, 12 | edgeListFormat = 1 13 | ) 14 | } 15 | \arguments{ 16 | \item{fileName}{vector of fileNames with the networks} 17 | 18 | \item{filePath}{path of the files NULL by default} 19 | 20 | \item{fhead}{TRUE if the files have header fields, FALSE otherwise.} 21 | 22 | \item{edgeListFormat}{integer, for the edge list format, if 1 the first column is the "in" (the predator), 23 | if 2 the first column is the "out" link (the prey).} 24 | 25 | \item{skipColum}{integer, number of columns that are skiped 1 by default} 26 | } 27 | \value{ 28 | an igraph object if there is only one file or a list of igraph objects named after the list without extension 29 | } 30 | \description{ 31 | If the network is in edge list format and there is a third column is treated as an edge attribute with the name of the column 32 | } 33 | \examples{ 34 | 35 | # Reads a network in edge list (interaction list) format, with predators as the first column by default 36 | # 37 | fileName <- system.file("extdata", "WeddellSea_FW.csv", package = "multiweb") 38 | g <- readNetwork(fileName) 39 | 40 | # Reads a network in adyacency matrix format, with predators as columns 41 | # 42 | fileName <- system.file("extdata", "BarentsBoreal_FW.csv", package = "multiweb") 43 | g <- readNetwork(fileName) 44 | 45 | # Read a vector of files 46 | # 47 | \dontrun{ 48 | dn <- list.files("inst/extdata",pattern = "^.*\\\\.csv$") 49 | netData <- readNetwork(dn,"inst/extdata") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /man/run_infomap.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modularity.R 3 | \name{run_infomap} 4 | \alias{run_infomap} 5 | \title{Interface R and igraph with Infomap (external binary)} 6 | \usage{ 7 | run_infomap( 8 | graph, 9 | infomap_path = "infomap", 10 | output_dir = tempdir(), 11 | directed = TRUE, 12 | two_level = TRUE, 13 | seed = 123, 14 | return_df = FALSE 15 | ) 16 | } 17 | \arguments{ 18 | \item{graph}{An igraph object.} 19 | 20 | \item{infomap_path}{Path to the Infomap binary (default assumes it's in system PATH).} 21 | 22 | \item{output_dir}{Temporary directory for Infomap results.} 23 | 24 | \item{directed}{Boolean: Treat the network as directed (default TRUE).} 25 | 26 | \item{two_level}{Boolean: Use two-level Infomap (default TRUE).} 27 | 28 | \item{seed}{Numeric: Random seed for Infomap (default 123).} 29 | 30 | \item{return_df}{Boolean: If TRUE, return a data.frame with \code{node}, \code{module}, and \code{flow}; if FALSE, return a community object (default).} 31 | } 32 | \value{ 33 | Either an igraph community object or a list with a data.frame and codelength. 34 | } 35 | \description{ 36 | This function exports an igraph network to Infomap's required format, runs Infomap, 37 | and imports the detected communities back into R as an igraph-compatible cluster object. 38 | It requires the installation of infomap in the system from https://www.mapequation.org/infomap/#Install 39 | if the network has the weight attribute, it will be used as the weight of the edges. 40 | } 41 | \examples{ 42 | # Example with the intermediate files in actual folder 43 | py_infomap <- run_infomap(netData[[1]],output_dir=".", weighted = FALSE) 44 | 45 | membership(py_infomap) 46 | modularity(py_infomap) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /man/run_infomap_multi.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modularity.R 3 | \name{run_infomap_multi} 4 | \alias{run_infomap_multi} 5 | \title{Run Infomap on Multilayer Networks with Intralayer Links} 6 | \usage{ 7 | run_infomap_multi( 8 | igraph_list, 9 | layer_names = NULL, 10 | infomap_path = "infomap", 11 | output_dir = tempdir(), 12 | directed = TRUE, 13 | two_level = TRUE, 14 | multilayer_relax_rate = 0.15, 15 | seed = 123 16 | ) 17 | } 18 | \arguments{ 19 | \item{igraph_list}{A list of \code{igraph} objects, each representing a network layer.} 20 | 21 | \item{layer_names}{A character vector with layer names corresponding to each igraph object (default: \code{NULL}).} 22 | 23 | \item{infomap_path}{Character string specifying the path to the Infomap binary (default: \code{"infomap"}, assumes it is in the system \code{PATH}).} 24 | 25 | \item{output_dir}{Character string specifying the directory for Infomap results (default: \code{tempdir()}).} 26 | 27 | \item{directed}{Logical; if \code{TRUE}, the network is treated as directed (default: \code{TRUE}).} 28 | 29 | \item{two_level}{Logical; if \code{TRUE}, uses two-level Infomap instead of hierarchical Infomap (default: \code{TRUE}).} 30 | 31 | \item{multilayer_relax_rate}{Numeric; relaxation rate for multilayer links (default: \code{0.15}).} 32 | 33 | \item{seed}{Numeric; random seed for Infomap to ensure reproducibility (default: \code{123}).} 34 | } 35 | \value{ 36 | A data frame containing the detected modules with columns: 37 | \describe{ 38 | \item{module}{Module/community assignment from Infomap.} 39 | \item{node}{The node name from the original igraph objects.} 40 | \item{layer}{The corresponding layer name from \code{layer_names}.} 41 | \item{flow}{The fraction of flow assigned to the module.} 42 | } 43 | } 44 | \description{ 45 | This function exports a multilayer network (as a list of igraph objects) into Infomap's required format, 46 | runs Infomap via an external binary, and imports the detected communities back into R as a data frame. 47 | It requires Infomap to be installed on the system. See installation instructions at 48 | \href{https://www.mapequation.org/infomap/#Install}{Infomap}. 49 | } 50 | \details{ 51 | If the network has the \code{weight} attribute, it will be used as the weight of the edges. 52 | Since the exported multilayer network structure assumes only \verb{*Intra} links (i.e., no explicit inter-layer links), 53 | Infomap internally constructs inter-layer connections for each physical node using the \code{--multilayer-relax-rate} 54 | parameter (default: 0.15). This creates inter-layer links between all instances of the same node across layers. 55 | The weights of these links are proportional to the weighted out-degree of the node in the target layer, 56 | resulting in non-uniform inter-layer transition probabilities. 57 | 58 | This implicit construction of inter-layer links enables Infomap to model node-aligned multiplex dynamics 59 | without requiring an explicit \verb{*Inter} section in the input file. 60 | } 61 | \examples{ 62 | \dontrun{ 63 | # Load network data 64 | fileName <- system.file("extdata", package = "multiweb") 65 | dn <- list.files(fileName, pattern = "^Kefi2015.*\\\\.txt$") 66 | g <- readNetwork(dn, fileName, skipColumn = 2) 67 | class(g) 68 | names(g) <- c("Negative", "Positive", "Trophic") 69 | 70 | # Run Infomap 71 | run_infomap_multi(g) 72 | } 73 | 74 | } 75 | \references{ 76 | D. Edler, A. Holmgren and M. Rosvall, \emph{The MapEquation software package}, available online at \url{https://www.mapequation.org}. 77 | } 78 | -------------------------------------------------------------------------------- /man/shuffle_network_deg.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffling.R 3 | \name{shuffle_network_deg} 4 | \alias{shuffle_network_deg} 5 | \title{Degree-Preserving Network Shuffling via Edge Swaps} 6 | \usage{ 7 | shuffle_network_deg(input_graph, delta = 100, directed = TRUE, weighted = TRUE) 8 | } 9 | \arguments{ 10 | \item{delta}{Number of edge swaps to perform.} 11 | 12 | \item{A}{A square adjacency matrix (directed, weighted or unweighted).} 13 | 14 | \item{max_attempts}{Number of times to attempt a valid swap before stopping.} 15 | } 16 | \value{ 17 | A shuffled adjacency matrix preserving in-degree and out-degree. 18 | } 19 | \description{ 20 | This function shuffles a directed network while preserving in-degree and out-degree. 21 | It follows a controlled randomization process by swapping edges iteratively. 22 | Similar to the \code{curve_ball()} function but more computationally demanding. 23 | This function is useful for generating increasingly randomized networks, 24 | to generate fully randomized networks, the \code{curbe_ball()} function is preferred. 25 | } 26 | \references{ 27 | Huaylla, C. A., Nacif, M. E., Coulin, C., Kuperman, M. N., & Garibaldi, L. A. (2021). 28 | Decoding information in multilayer ecological networks: The keystone species case. 29 | Ecological Modelling, 460, 109734. https://doi.org/10.1016/j.ecolmodel.2021.109734 30 | 31 | Strona, G., Nappo, D., Boccacci, F., Fattorini, S., & San-Miguel-Ayanz, J. (2014). 32 | A fast and unbiased procedure to randomize ecological binary matrices with fixed row and column totals. 33 | Nature Communications, 5, 4114. https://doi.org/10.1038/ncomms5114 34 | } 35 | -------------------------------------------------------------------------------- /man/shuffle_network_ws.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shuffling.R 3 | \name{shuffle_network_ws} 4 | \alias{shuffle_network_ws} 5 | \title{Random Network Rewiring Without Preserving Degree Distribution} 6 | \usage{ 7 | shuffle_network_ws(input_graph, delta = 100, directed = TRUE, weighted = TRUE) 8 | } 9 | \arguments{ 10 | \item{input_graph}{A square adjacency matrix (directed, weighted or unweighted) or an igraph object.} 11 | 12 | \item{delta}{Number of rewiring attempts to perform.} 13 | 14 | \item{directed}{Logical, whether the network is directed (default: TRUE).} 15 | 16 | \item{weighted}{Logical, whether the network is weighted (default: TRUE).} 17 | } 18 | \value{ 19 | A shuffled adjacency matrix preserving the number of links but not degree distribution. 20 | } 21 | \description{ 22 | This function randomly rewires a directed network while preserving 23 | the total number of links but NOT the degree distribution. This is based 24 | on an approach described by Watts and Strogatz (1998) for small-world networks. 25 | } 26 | \references{ 27 | Watts, D. J., & Strogatz, S. H. (1998). Collective dynamics of 'small-world' networks. 28 | Nature, 393(6684), 440-442. \doi{10.1038/30918} 29 | } 30 | -------------------------------------------------------------------------------- /man/toGLVadjMat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/readNetwork.r 3 | \name{toGLVadjMat} 4 | \alias{toGLVadjMat} 5 | \title{From multiple interaction object 'mgraph'to GLV adjacency matrix} 6 | \usage{ 7 | toGLVadjMat( 8 | mg, 9 | types = c("Negative", "Positive", "Antagonistic"), 10 | istrength = FALSE 11 | ) 12 | } 13 | \arguments{ 14 | \item{mg}{multiple interaction object, class 'mgraph'} 15 | 16 | \item{types}{vector of types that represent the layers} 17 | 18 | \item{istrength}{edge weights} 19 | } 20 | \value{ 21 | an matrix with the signs of the interactions 22 | } 23 | \description{ 24 | This functions takes a 'mgraph' object and convert it to a Generalized Lotka-Volterra adjacency matrix 25 | Position is important in fact the order is Competitive/Negative,Mutualistic/Positive,Trophic/Antagonistic 26 | If 'istrength' is TRUE the attribute weight is assumed as the strength of the interacion 27 | } 28 | \examples{ 29 | 30 | # Read a vector of files 31 | # 32 | \dontrun{ 33 | 34 | fpath <- system.file("extdata", package = "multiweb") 35 | dn <- list.files(fpath,pattern = "^Kefi2015.*\\\\.txt$") 36 | netData <- readMultiplex(dn,c("Negative","Positive","Antagonistic"),fpath,skipColum=2) 37 | toGLVadjMat(netData) 38 | } 39 | } 40 | \seealso{ 41 | \code{\link[=readMultiplex]{readMultiplex()}} 42 | } 43 | -------------------------------------------------------------------------------- /man/write_multilayer_network.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modularity.R 3 | \name{write_multilayer_network} 4 | \alias{write_multilayer_network} 5 | \title{Write a Multilayer Network to a File in Infomap-Compatible Format} 6 | \usage{ 7 | write_multilayer_network(igraph_list, file_path) 8 | } 9 | \arguments{ 10 | \item{igraph_list}{A list of \code{igraph} objects, each representing a layer.} 11 | 12 | \item{file_path}{A string specifying the path to the output file.} 13 | } 14 | \value{ 15 | A list with two data frames: 16 | \item{vertices}{A data frame of node IDs and names.} 17 | \item{intra}{A data frame of intra-layer edges with columns: \code{layer_id}, \code{node_id1}, \code{node_id2}, \code{weight}.} 18 | } 19 | \description{ 20 | This function writes a multilayer network (represented as a list of igraph objects) 21 | to a text file in Infomap's required *Intra format. 22 | } 23 | \details{ 24 | The output file consists of: 25 | \itemize{ 26 | \item A list of unique vertices with their numeric IDs. 27 | \item Intra-layer edges with weights. 28 | } 29 | } 30 | \examples{ 31 | \dontrun{ 32 | write_multilayer_network(list(layer1, layer2), "network.net") 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /multiweb.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: 005d551a-d87c-4754-a77a-a74bee1870c0 3 | 4 | RestoreWorkspace: Default 5 | SaveWorkspace: Default 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /test_multi3D.r: -------------------------------------------------------------------------------- 1 | # Test Plot multiplex adapted from muxviz 2 | # 3 | 4 | library(multiweb) 5 | library(muxViz) 6 | library(igraph) 7 | library(dplyr) 8 | library(RColorBrewer) 9 | library(viridis) 10 | # === Step 1: Load data === 11 | fileName <- system.file("extdata", package = "multiweb") 12 | dn <- list.files(fileName, pattern = "^Kefi2015.*\\.txt$") 13 | g_list <- readNetwork(dn, fileName, skipColumn = 2) 14 | names(g_list) <- c("Negative", "Positive", "Trophic") 15 | lapply(g_list, function(x){igraph::gorder(x)}) 16 | 17 | # === Step 2: Detect communities using multiweb === 18 | res <- run_infomap_multi(g_list, layer_names = names(g_list)) 19 | communities <- res$communities 20 | 21 | # === Paso 3: Crear paletas viridis por módulo === 22 | modules_multi <- sort(unique(communities$module)) 23 | pal.mux <- setNames(sample(viridis(length(modules_multi))), modules_multi) 24 | 25 | # Paleta para nodos agregados (módulo dominante por especie) 26 | aggr_df <- communities %>% 27 | group_by(node, module) %>% 28 | summarise(flow = sum(flow), .groups = "drop") %>% 29 | group_by(node) %>% 30 | slice_max(order_by = flow, n = 1) 31 | 32 | modules_aggr <- sort(unique(aggr_df$module)) 33 | pal.aggr <- setNames(sample(viridis(length(modules_aggr))), modules_aggr) 34 | 35 | # === Paso 4: Matriz de colores por nodo-capa === 36 | Layers <- length(g_list) 37 | Nodes <- length(unique(communities$node)) 38 | node_names <- sort(unique(communities$node)) 39 | node_index <- setNames(seq_along(node_names), node_names) 40 | 41 | node.colors.matrix <- matrix("#dadada", nrow = Nodes, ncol = Layers) 42 | 43 | for (l in seq_len(Layers)) { 44 | dftmp <- communities %>% filter(layer == names(g_list)[l]) 45 | idxs <- node_index[dftmp$node] 46 | node.colors.matrix[idxs, l] <- pal.mux[as.character(dftmp$module)] 47 | } 48 | 49 | # === Paso 5: Colores agregados por nodo === 50 | node.colors.aggr <- rep("#dadada", Nodes) 51 | node.colors.aggr[node_index[aggr_df$node]] <- pal.aggr[as.character(aggr_df$module)] 52 | 53 | # === Paso 6a: Crear matriz de flujo por nodo-capa === 54 | node.flow.matrix <- matrix(0, nrow = Nodes, ncol = Layers) 55 | 56 | for (l in seq_len(Layers)) { 57 | dftmp <- communities %>% filter(layer == names(g_list)[l]) 58 | idxs <- node_index[dftmp$node] 59 | node.flow.matrix[idxs, l] <- dftmp$flow 60 | } 61 | 62 | # Optional: Rescale flows for visual clarity (e.g., to radius ~ sqrt(flow)) 63 | # Avoid log(0) if flow contains zeros 64 | eps <- 1e-5 65 | scaled.flow.matrix <- sqrt(node.flow.matrix ) *100 # adjust multiplier as needed 66 | 67 | # === Paso 6: Layout y visualización === 68 | lay <- layoutMultiplex(g_list, layout = "drl", ggplot.format = FALSE, box = TRUE) 69 | 70 | plot_multi3D( 71 | g_list, 72 | layer.layout = lay, 73 | layer.colors = RColorBrewer::brewer.pal(Layers, "Dark2"), 74 | layer.shift.x = 0.5, 75 | layer.space = 2, 76 | layer.labels = names(g_list), 77 | layer.labels.cex = 1.5, 78 | layer.alpha = rep(0.001, length(g_list)), 79 | node.size.values = scaled.flow.matrix, # <- flow-based sizing 80 | node.size.scale = 1, # <- no further scaling here node.size.scale = 0.5, 81 | node.colors = node.colors.matrix, 82 | edge.colors = "#FFFFFF", 83 | edge.alpha = 0.01, 84 | node.colors.aggr = node.colors.aggr, 85 | aggr.alpha = 0.01, 86 | show.aggregate = TRUE, 87 | show.nodeLabels= TRUE 88 | ) 89 | 90 | # === Paso 7: Exportar imagen si lo deseas === 91 | rgl::snapshot3d("multiweb_infomap_3D.png", fmt = "png", width = 2024, height = 2024) 92 | 93 | 94 | # Adaptar para muxViz 95 | commResult <- convert_infomap_result_to_muxviz(communities) 96 | 97 | # Paleta de colores por módulo 98 | library(viridis) 99 | pal.mux <- sample(viridis(commResult$modules.multi)) 100 | pal.aggr <- sample(viridis(commResult$modules.aggr)) 101 | 102 | # Tabla tipo ggplot con modulos por capa y agregados 103 | gplt <- plot_multimodules(commResult, module.colors = pal.mux, show.aggregate = TRUE) 104 | 105 | 106 | #Calculate PageRank and Degree versatility 107 | sam <- convert_to_supra_adjacency(g_list,use_names = TRUE)$supra_matrix 108 | 109 | pr <- GetMultiPageRankCentrality(sam, Layers,Nodes) 110 | deg <- muxViz::GetMultiDegree(M, Layers,Nodes, isDirected=F) 111 | 112 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/tests.html 7 | # * https://testthat.r-lib.org/reference/test_package.html#special-files 8 | 9 | library(testthat) 10 | library(multiweb) 11 | 12 | test_check("multiweb") 13 | -------------------------------------------------------------------------------- /tests/testthat/test-calc_modularity.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(igraph) 3 | library(dplyr) 4 | 5 | test_that("calc_modularity returns correct output format for single network", { 6 | # Create an undirected and unweighted test graph 7 | g <- netData[[1]] 8 | 9 | # Run function 10 | result <- calc_modularity(g, cluster_function = cluster_spinglass) 11 | 12 | # Check output format 13 | expect_s3_class(result, "data.frame") 14 | expect_true("Modularity" %in% colnames(result)) 15 | expect_type(result$Modularity, "double") 16 | }) 17 | 18 | test_that("calc_modularity handles directed and weighted networks", { 19 | # Create a directed and weighted test graph 20 | g <- netData[[1]] 21 | E(g)$weight <- runif(ecount(g)) # Assign random edge weights 22 | 23 | # Run function with weights 24 | result <- calc_modularity(g, weights = E(g)$weight, cluster_function = cluster_infomap) 25 | 26 | # Check output format 27 | expect_s3_class(result, "data.frame") 28 | expect_true("Modularity" %in% colnames(result)) 29 | expect_type(result$Modularity, "double") 30 | }) 31 | 32 | test_that("calc_modularity only accepts cluster_spinglass and cluster_infomap", { 33 | g <- netData[[1]] 34 | # Expect error for an invalid clustering function 35 | expect_error(calc_modularity(g, cluster_function = cluster_louvain), 36 | "must be either 'igraph::cluster_spinglass' or 'igraph::cluster_infomap'") 37 | }) 38 | 39 | test_that("calc_modularity computes modularity in the correct range", { 40 | g <- netData[[1]] 41 | 42 | # Run function 43 | result <- calc_modularity(g, cluster_function = cluster_spinglass) 44 | 45 | # Modularity values should be in range (-0.5 to 1) 46 | expect_true(all(result$Modularity >= 0.18 & result$Modularity <= 0.19)) 47 | }) 48 | 49 | test_that("calc_modularity works with both cluster_spinglass and cluster_infomap", { 50 | g <- netData[[1]] 51 | 52 | result1 <- calc_modularity(g, cluster_function = cluster_spinglass) 53 | expect_s3_class(result1, "data.frame") 54 | 55 | result2 <- calc_modularity(g, cluster_function = cluster_infomap) 56 | expect_s3_class(result2, "data.frame") 57 | }) 58 | 59 | test_that("calc_modularity handles a list of networks", { 60 | skip_on_cran() # Avoid running on CRAN due to randomness 61 | 62 | # Generate a list of randomized networks from netData 63 | nullg <- generateERbasal(netData[[1]], 10) 64 | 65 | # Run function on list 66 | result <- calc_modularity(nullg, cluster_function = cluster_spinglass) 67 | 68 | # Check output format 69 | expect_s3_class(result, "data.frame") 70 | expect_true(nrow(result) == length(nullg)) # Should return one row per network 71 | }) 72 | 73 | test_that("calc_modularity handles parallel processing", { 74 | g <- generateERbasal(netData[[1]], 10) 75 | 76 | # Run sequential 77 | result_seq <- calc_modularity(g, ncores = 0) 78 | expect_s3_class(result_seq, "data.frame") 79 | 80 | # Run parallel 81 | result_par <- calc_modularity(g, ncores = 2) 82 | expect_s3_class(result_par, "data.frame") 83 | }) 84 | -------------------------------------------------------------------------------- /tests/testthat/test-calc_topological_indices.R: -------------------------------------------------------------------------------- 1 | 2 | test_that("calc_topological_indices", { 3 | library(igraph) 4 | g <- graph_from_literal( 1 -+ 4 -+ 7,2 -+ 5 -+7, 3-+6-+7, 7-+7, 4+-3, 2-+7, simplify = FALSE) 5 | expect_snapshot( 6 | calc_topological_indices(g) 7 | ) 8 | }) 9 | -------------------------------------------------------------------------------- /tests/testthat/test-calc_topological_roles.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | test_that("calc_topological_roles", { 4 | g <- netData[[1]] 5 | set.seed(1432) 6 | comm <- cluster_spinglass(g) 7 | expect_snapshot( 8 | calc_topological_roles(g,nsim=1,community=comm) 9 | ) 10 | }) 11 | -------------------------------------------------------------------------------- /tests/testthat/test-topological.R: -------------------------------------------------------------------------------- 1 | test_that("calc and classify topological roles", { 2 | 3 | g <- netData[[1]] 4 | set.seed(1432) 5 | tp <- calc_topological_roles(g,nsim=10,ncores=4) 6 | expect_snapshot( 7 | classify_topological_roles(tp,g,plt=FALSE)) 8 | }) 9 | -------------------------------------------------------------------------------- /tests/testthat/testReadNetwork.R: -------------------------------------------------------------------------------- 1 | # Test ReadNetwork 2 | context("Read network files") 3 | library(multiweb) 4 | 5 | test_that("Read a single csv file with adyacency matrix format and header and first column names", { 6 | fileName <- system.file("extdata", "BarentsBoreal_FW.csv", package = "multiweb") 7 | g <- readNetwork(fileName) 8 | expect_is(g,"igraph") 9 | expect_equal(igraph::vcount(g),180) 10 | expect_equal(igraph::ecount(g),1546) 11 | }) 12 | 13 | test_that("Read a single csv file with interaction list format", { 14 | fileName <- system.file("extdata", "WeddellSea_FW.csv", package = "multiweb") 15 | g <- readNetwork(fileName) 16 | expect_is(g,"igraph") 17 | expect_equal(igraph::vcount(g),442) 18 | expect_equal(igraph::ecount(g),1915) 19 | }) 20 | 21 | test_that("Read multiple files with compatible formats", { 22 | filepath <- c(system.file("extdata", package = "multiweb")) 23 | 24 | dn <- list.files(filepath,pattern = "^.*\\.csv$") 25 | g <- readNetwork(dn,filepath) 26 | expect_is(g,"list") 27 | expect_equal(length(g),2) 28 | expect_match(names(g)[2],"WeddellSea_FW") 29 | expect_is(g[[2]],"igraph") 30 | }) 31 | 32 | 33 | test_that("Read a single txt file adyacency matrix no header no species names", { 34 | fileName <- system.file("extdata", "carpinteria_FW.txt", package = "multiweb") 35 | # The file has no header but also no species names so a warning is expected 36 | expect_warning(readNetwork(fileName,fhead = FALSE)) 37 | # No header and no species names 38 | g <- readNetwork(fileName,fhead = FALSE,skipColumn = 0) 39 | expect_is(g,"igraph") 40 | expect_equal(igraph::vcount(g),273) 41 | expect_equal(igraph::ecount(g),3878) 42 | }) 43 | --------------------------------------------------------------------------------