From 04fbe394d454682656f3e7a46b0411fe0539a2ab Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 18 Jul 2024 22:18:31 +0200 Subject: [PATCH 01/26] Using alt text on README images --- README.Rmd | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/README.Rmd b/README.Rmd index 1fe77a25..81c36a1e 100644 --- a/README.Rmd +++ b/README.Rmd @@ -21,7 +21,7 @@ list_data <- function(string){ ``` # manynet - +manynet logo [![Lifecycle: maturing](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://lifecycle.r-lib.org/articles/stages.html#maturing) @@ -97,7 +97,7 @@ there are specific routines included for [GraphML](http://graphml.graphdrawing.org), and [DynetML](http://casos.cs.cmu.edu/projects/dynetml/) files, e.g.: -![](https://www.jameshollway.com/post/manynet/README-import-graph-1.png) +Graph of manynet input/output formats ```{r import-graph, echo = FALSE, dpi = 300, fig.height=2.5, eval=FALSE} library(patchwork) @@ -126,8 +126,7 @@ we include a number of classical and instructional network datasets, all thoroughly documented and ready for analysis. Here are just a few examples, all available in `{manynet}`: -![](https://www.jameshollway.com/post/manynet/README-ison_egs-1.png) - +Graphs illustrating several of the classic networks included in the package ```{r ison_egs, echo = FALSE, dpi = 250, message=FALSE, fig.height=3, eval=FALSE} graphr(to_unnamed(ison_karateka)) + ggtitle("Karateka", subtitle = "Zachary 1977") + @@ -146,7 +145,7 @@ The `create_*` group of functions create networks with a particular structure, and will always create the same format from the same inputs, e.g.: -![](https://www.jameshollway.com/post/manynet/README-create_egs-1.png) +Graphs illustrating the creation of lattices and tree networks ```{r create_egs, echo = FALSE, dpi = 300, message=FALSE, fig.height=3, eval=FALSE} (graphr(create_lattice(15)) + ggtitle("Lattice", subtitle = "create_lattice(15)") + @@ -160,7 +159,7 @@ generative mechanisms that may include some random aspect, and so will return a different output each time they are run, e.g.: -![](https://www.jameshollway.com/post/manynet/README-generate_egs-1.png) +Graphs of small-world and scale-free networks of 15 nodes ```{r generate_egs, echo = FALSE, dpi = 300, message=FALSE, fig.height=3, eval=FALSE} (graphr(generate_smallworld(15)) + ggtitle("Small-World", subtitle = "generate_smallworld(15)") + @@ -180,7 +179,7 @@ Some of these functions wrap existing algorithms in other packages, while others are unique offerings or add additional formats, e.g. two-mode networks. -![](https://www.jameshollway.com/post/manynet/README-generate_tm-1.png) +Graphs of generated one- and two-mode small-world networks ```{r generate_tm, echo = FALSE, dpi = 300, message=FALSE, fig.height=3, warning=FALSE, eval=FALSE} graphr(generate_smallworld(15, directed = TRUE), layout = "stress") + ggtitle("Small-World", subtitle = "generate_smallworld(15, directed = TRUE)") + graphr(generate_smallworld(c(10,5)), layout = "stress") + ggtitle("Small-World", subtitle = "generate_smallworld(c(10,5))") @@ -213,7 +212,7 @@ you may need to translate this data into another class for analysis. from one of many common classes into any other. Below is a directed graph showing the currently available options: -![](https://www.jameshollway.com/post/manynet/README-coercion-graph-1.png) +Graph of coercible relationships between classes ```{r coercion-graph, echo = FALSE, dpi = 300, eval=FALSE, fig.height=8, fig.width=8} graphr(igraph::graph_from_literal(edgelist:matrix:igraph:network:tidygraph+--+edgelist:matrix:igraph:network:tidygraph, @@ -244,7 +243,7 @@ and extensible by developments in those other packages too. Reformatting means changing the format of the network, e.g. from directed to undirected via `to_undirected()`. -![](https://www.jameshollway.com/post/manynet/README-directed_egs-1.png) +Graphs illustrating modification of a network's directedness ```{r directed_egs, echo = FALSE, dpi = 300, fig.height=3, warning=FALSE, message=FALSE, eval=FALSE} graphr(to_directed(ison_brandes)) + ggtitle("Directed", subtitle = "to_directed(ison_brandes)") + @@ -256,7 +255,7 @@ graphr(to_directed(ison_brandes)) + ggtitle("Directed", subtitle = "to_directed( Transforming means changing the dimensions of the network, e.g. from a two-mode network to a one-mode projection via `to_mode1()`. -![](https://www.jameshollway.com/post/manynet/README-projection_egs-1.png) +Graphs illustrating decomposition of a two-mode network into its projections ```{r projection_egs, echo = FALSE, dpi = 300, fig.height=3, warning=FALSE, message=FALSE, eval=FALSE} graphr(ison_southern_women, layout = "stress") + ggtitle("Original", subtitle = "ison_southern_women") + @@ -270,7 +269,7 @@ graphr(ison_southern_women, layout = "stress") + ggtitle("Original", subtitle = Splitting means separating a network, e.g. from a whole network to the various ego networks via `to_egos()`. -![](https://www.jameshollway.com/post/manynet/README-splitting_egs-1.png) +Graphs illustrating decomposition of a network into egonets ```{r splitting_egs, echo = FALSE, dpi = 250, fig.height=4, warning=FALSE, message=FALSE, eval=FALSE} graphr(ison_adolescents) + ggtitle("Original", subtitle = "ison_adolescents") + @@ -295,7 +294,7 @@ It includes sensible defaults so that researchers can view their network's struc or distribution quickly with a minimum of fuss. Compare the output from `{manynet}` with a similar default from `{igraph}`: -![](https://www.jameshollway.com/post/manynet/README-layout-comparison-1.png) +Example illustrating differences in default igraph and manynet graphs ```{r layout-comparison, echo = FALSE, message=FALSE, dpi = 250, fig.height=4, eval = FALSE} library(manynet) @@ -328,7 +327,7 @@ Changing the size and colors of nodes and ties is as easy as specifying the function's relevant argument with a replacement, or indicating from which attribute it should inherit this information. -![](https://www.jameshollway.com/post/manynet/README-more-options-1.png) +Graph illustrating automatic and manual use of node color and size ```{r more-options, echo = FALSE, message=FALSE, dpi = 300, fig.height=3, eval=FALSE} graphr(ison_lawfirm, node_color = "darkblue", node_size = 6) + @@ -348,7 +347,7 @@ snapping layouts to a grid, visualising partitions horizontally, vertically, or concentrically, or conforming to configurational coordinates. -![](https://www.jameshollway.com/post/manynet/README-more-layouts-1.png) +Graphs illustrating different layouts ```{r more-layouts, echo = FALSE, message=FALSE, dpi = 250, eval=FALSE} (graphr(ison_southern_women, layout = "concentric") + ggtitle("Concentric layout")) / @@ -366,7 +365,7 @@ If you want to quickly make sure your plots conform to your institution or taste then it is easy to do with themes and scales that update the basic look and color palette used in your plots. -![](https://www.jameshollway.com/post/manynet/README-more-themes-1.png) +Graphs using default, IHEID, and ETHZ themes ```{r more-themes, echo = FALSE, message=FALSE, dpi = 300, fig.height=3, eval=FALSE} p <- graphr(ison_lawfirm, node_color = "Practice") + @@ -385,7 +384,7 @@ Second, `graphs()` is used to graph multiple networks together, which can be useful for ego networks or network panels. `{patchwork}` is used to help arrange individual plots together. -![](https://www.jameshollway.com/post/manynet/README-autographs-1.png) +Example of graphs() used on longitudinal data ```{r autographs, echo = FALSE, dpi = 250, fig.height=3, eval=FALSE} ison_adolescents %>% @@ -401,7 +400,7 @@ It uses `{gganimate}` and `{gifski}` to create a gif that visualises network changes over time. It really couldn't be easier. -![](https://www.jameshollway.com/post/manynet/README-autographd-1.gif) +Example of grapht() on longitudinal data ```{r autographd, echo = FALSE, dpi = 250, fig.height=3.5, eval=FALSE} ison_adolescents %>% From e42cddd6356f86a8d401711cedca9d2bdcf9eaed Mon Sep 17 00:00:00 2001 From: James Hollway Date: Thu, 18 Jul 2024 22:19:11 +0200 Subject: [PATCH 02/26] Updated README for migrated function sets, updated favicons --- README.Rmd | 93 ++++++++--- README.md | 164 ++++++++++++------- pkgdown/favicon/apple-touch-icon-120x120.png | Bin 10712 -> 10567 bytes pkgdown/favicon/apple-touch-icon-152x152.png | Bin 14127 -> 13876 bytes pkgdown/favicon/apple-touch-icon-180x180.png | Bin 16924 -> 16795 bytes pkgdown/favicon/apple-touch-icon-60x60.png | Bin 4731 -> 4623 bytes pkgdown/favicon/apple-touch-icon-76x76.png | Bin 6292 -> 6144 bytes pkgdown/favicon/apple-touch-icon.png | Bin 16924 -> 16795 bytes pkgdown/favicon/favicon-16x16.png | Bin 1261 -> 1224 bytes pkgdown/favicon/favicon-32x32.png | Bin 2338 -> 2340 bytes pkgdown/favicon/favicon.ico | Bin 15086 -> 15086 bytes 11 files changed, 175 insertions(+), 82 deletions(-) diff --git a/README.Rmd b/README.Rmd index 81c36a1e..a3029898 100644 --- a/README.Rmd +++ b/README.Rmd @@ -50,9 +50,10 @@ And they can rely on a very different visual language (and sometimes plotting en which can mess up your pretty presentation or paper. This can make learning and using network analysis tools in R challenging. -By contrast, we build packages that offer _many_ analytic tools that work on _many_ (if not most) types of networks of all kinds. -`{manynet}` is the first package that helps researchers with Making, Modifying, and Mapping networks. -For Measures, Memberships, or Models, see [`{migraph}`](https://stocnet.github.io/migraph/). +By contrast, `{manynet}` offers _many_ analytic tools that work on _many_ (if not most) types and kinds of networks. +It helps researchers make, modify, map, mark, measure, and identify nodes' motifs and memberships in networks. +For further testing and modelling capabilities, +see [`{migraph}`](https://stocnet.github.io/migraph/) and the other [stocnet](https://github.com/stocnet) packages. - [Making](#making) - [Importing network data](#importing-network-data) @@ -67,14 +68,18 @@ For Measures, Memberships, or Models, see [`{migraph}`](https://stocnet.github.i - [Mapping](#mapping) - [Graphing](#graphing) - [More options](#more-options) - - [More layouts](#more-options) + - [More layouts](#more-layouts) - [More themes and scales](#more-themes-and-scales) - [graphs](#graphs) - [grapht](#grapht) +- [Marking](#marking) +- [Motifs](#motifs) +- [Memberships](#memberships) +- [Measuring](#measuring) +- [Tutorials](#tutorials) - [Installation](#installation) - [Stable](#stable) - [Development](#development) - - [Tutorials](#tutorials) - [Relationship to other packages](#relationship-to-other-packages) - [Funding details](#funding-details) @@ -424,6 +429,12 @@ ison_adolescents %>% ## Marking +`{manynet}` includes four special groups of functions, +each with their own pretty `print()` and `plot()` methods: +marks, measures, motifs, and memberships. +Marks are logical scalars or vectors, measures are numeric, +memberships categorical, and motifs result in tabular outputs. + `{manynet}`'s `*is_*()` functions offer fast logical tests of various properties. Whereas `is_*()` returns a single logical value for the network, `node_is_*()` returns a logical vector the length of the number of nodes in the network, @@ -452,22 +463,65 @@ indicating e.g. that the first node is a member of group "A", the second in grou - `r list_functions("_in_")` +For example `node_brokerage_census()` returns +the frequency of nodes' participation in +Gould-Fernandez brokerage roles for a one-mode network, +and the Jasny-Lubell brokerage roles for a two-mode network. + +These can be analysed alone, or used as a profile for establishing equivalence. +`{manynet}` offers both HCA and CONCOR algorithms, +as well as elbow, silhouette, and strict methods for _k_-cluster selection. + +Plot of a dendrogram of structural equivalence + +`{manynet}` also includes functions for establishing membership on other bases, +such as typical community detection algorithms, +as well as component and core-periphery partitioning algorithms. + ## Measuring -All other `net_*`, `node_*` and `tie_*` functions in `{manynet}` offer ways -for users to measure some feature, property, or quantity of the network. -This includes measures of balance and brokerage, -centrality, closure, and cohesion, -diffusion and diversity, -hierarchy and resilience. -There is a lot here, so we recommend you check out the function overview. +`{manynet}` also offers a large and growing smorgasbord of measures that +can be used at the node, tie, and network level +to measure some feature, property, or quantity of the network. +Each recognises whether the network is directed or undirected, +weighted or unweighted, one-mode or two-mode. +All return normalized values wherever possible, +though this can be overrided. +Here are some examples: + +- _Centrality_: `node_degree()`, `node_closeness()`, `node_betweenness()`, and `node_eigenvector()`, + `net_degree()`, `net_closeness()`, `net_betweenness()`, and `net_eigenvector()` +- _Cohesion_: `net_density()`, `net_reciprocity()`, `net_transitivity()`, `net_equivalency()`, and `net_congruency()` +- _Hierarchy_: `net_connectedness()`, `net_efficiency()`, `net_upperbound()` +- _Resilience_: `net_components()`, `net_cohesion()`, `net_adhesion()`, `net_diameter()`, `net_length()` +- _Innovation_: e.g. `node_redundancy()`, `node_effsize()`, `node_efficiency()`, `node_constraint()`, `node_hierarchy()` +- _Diversity_: `net_richness()`, `net_diversity()`, `net_heterophily()`, `net_assortativity()`, + `node_richness()`, `node_diversity()`, `node_heterophily()`, `node_assortativity()` +- _Topology_: e.g. `net_core()`, `net_factions()`, `net_modularity()`, `net_smallworld()`, `net_balance()` +- _Diffusion_: e.g. `net_reproduction()`, `net_immunity()`, `node_thresholds()` + +There is a lot here, +so we recommend you explore [the list of functions](https://stocnet.github.io/migraph/reference/index.html) to find out more. + +## Tutorials + +This package includes tutorials to help new and experienced users +learn how they can conduct social network analysis using the package. +These tutorials leverage the additional package `{learnr}` (see [here](https://rstudio.github.io/learnr/)), +but we have made it easy to use `{manynet}` or `{migraph}` tutorials +right out of the box: + +```{r learnr-tutes} +run_tute() +# run_tute("tutorial1") +``` ## Installation ### Stable The easiest way to install the latest stable version of `{manynet}` is via CRAN. -Simply open the R console and enter:^[Macs with Macports installed may also install from the command line [using Macports](https://ports.macports.org/port/R-manynet/).] +Simply open the R console and enter: `install.packages('manynet')` @@ -496,18 +550,11 @@ please install the `{remotes}` package from CRAN and then: - For latest development version: `remotes::install_github("stocnet/manynet@develop")` -### Tutorials +### Other sources -This package includes tutorials to help new and experienced users -learn how they can conduct social network analysis using the package. -These tutorials leverage the additional package `{learnr}` (see [here](https://rstudio.github.io/learnr/)), -but we have made it easy to use `{manynet}` or `{migraph}` tutorials -right out of the box: +Those using Mac computers may also install using Macports: -```{r learnr-tutes} -run_tute() -# run_tute("tutorial1") -``` +`sudo port install R-manynet` ## Relationship to other packages diff --git a/README.md b/README.md index 888ff68d..acd13288 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # manynet - +manynet logo @@ -36,11 +36,12 @@ rely on a very different visual language (and sometimes plotting engine), which can mess up your pretty presentation or paper. This can make learning and using network analysis tools in R challenging. -By contrast, we build packages that offer *many* analytic tools that -work on *many* (if not most) types of networks of all kinds. `{manynet}` -is the first package that helps researchers with Making, Modifying, and -Mapping networks. For Measures, Memberships, or Models, see -[`{migraph}`](https://stocnet.github.io/migraph/). +By contrast, `{manynet}` offers *many* analytic tools that work on +*many* (if not most) types and kinds of networks. It helps researchers +make, modify, map, mark, measure, and identify nodes’ motifs and +memberships in networks. For further testing and modelling capabilities, +see [`{migraph}`](https://stocnet.github.io/migraph/) and the other +[stocnet](https://github.com/stocnet) packages. - [Making](#making) - [Importing network data](#importing-network-data) @@ -55,14 +56,18 @@ Mapping networks. For Measures, Memberships, or Models, see - [Mapping](#mapping) - [Graphing](#graphing) - [More options](#more-options) - - [More layouts](#more-options) + - [More layouts](#more-layouts) - [More themes and scales](#more-themes-and-scales) - [graphs](#graphs) - [grapht](#grapht) +- [Marking](#marking) +- [Motifs](#motifs) +- [Memberships](#memberships) +- [Measuring](#measuring) +- [Tutorials](#tutorials) - [Installation](#installation) - [Stable](#stable) - [Development](#development) - - [Tutorials](#tutorials) - [Relationship to other packages](#relationship-to-other-packages) - [Funding details](#funding-details) @@ -86,7 +91,7 @@ routines included for [GraphML](http://graphml.graphdrawing.org), and [DynetML](http://casos.cs.cmu.edu/projects/dynetml/) files, e.g.: -![](https://www.jameshollway.com/post/manynet/README-import-graph-1.png) +Graph of manynet input/output formats If you cannot remember the file name/path, then just run `read_*()` with the parentheses empty, and a file selection popup will open so that you @@ -109,7 +114,7 @@ and instructional network datasets, all thoroughly documented and ready for analysis. Here are just a few examples, all available in `{manynet}`: -![](https://www.jameshollway.com/post/manynet/README-ison_egs-1.png) +Graphs illustrating several of the classic networks included in the package Here are some others: `ison_adolescents`, `ison_algebra`, `ison_brandes`, `ison_friends`, `ison_greys`, `ison_hightech`, @@ -127,7 +132,7 @@ Here are some others: `ison_adolescents`, `ison_algebra`, structure, and will always create the same format from the same inputs, e.g.: -![](https://www.jameshollway.com/post/manynet/README-create_egs-1.png) +Graphs illustrating the creation of lattices and tree networks See also `create_components()`, `create_core()`, `create_empty()`, `create_explicit()`, `create_filled()`, `create_lattice()`, @@ -137,7 +142,7 @@ The `generate_*` group of functions generate networks from generative mechanisms that may include some random aspect, and so will return a different output each time they are run, e.g.: -![](https://www.jameshollway.com/post/manynet/README-generate_egs-1.png) +Graphs of small-world and scale-free networks of 15 nodes See also `generate_configuration()`, `generate_permutation()`, `generate_random()`, `generate_scalefree()`, `generate_smallworld()`, @@ -152,7 +157,7 @@ in the first mode, and 5 nodes in the second mode. Some of these functions wrap existing algorithms in other packages, while others are unique offerings or add additional formats, e.g. two-mode networks. -![](https://www.jameshollway.com/post/manynet/README-generate_tm-1.png) +Graphs of generated one- and two-mode small-world networks #### Inventing data on networks @@ -182,7 +187,7 @@ functions can be used to coerce objects from one of many common classes into any other. Below is a directed graph showing the currently available options: -![](https://www.jameshollway.com/post/manynet/README-coercion-graph-1.png) +Graph of coercible relationships between classes These functions are designed to be as intuitive and lossless as possible, outperforming many other class-coercion packages. @@ -201,21 +206,21 @@ extensible by developments in those other packages too. Reformatting means changing the format of the network, e.g. from directed to undirected via `to_undirected()`. -![](https://www.jameshollway.com/post/manynet/README-directed_egs-1.png) +Graphs illustrating modification of a network's directedness ### Transforming Transforming means changing the dimensions of the network, e.g. from a two-mode network to a one-mode projection via `to_mode1()`. -![](https://www.jameshollway.com/post/manynet/README-projection_egs-1.png) +Graphs illustrating decomposition of a two-mode network into its projections ### Splitting and Joining Splitting means separating a network, e.g. from a whole network to the various ego networks via `to_egos()`. -![](https://www.jameshollway.com/post/manynet/README-splitting_egs-1.png) +Graphs illustrating decomposition of a network into egonets Those functions that split a network into a list of networks are distinguishable as those `to_*()` functions that are named in the @@ -246,7 +251,7 @@ their network’s structure or distribution quickly with a minimum of fuss. Compare the output from `{manynet}` with a similar default from `{igraph}`: -![](https://www.jameshollway.com/post/manynet/README-layout-comparison-1.png) +Example illustrating differences in default igraph and manynet graphs Here the `{manynet}` function recognises that the network is a two-mode network and uses a bipartite layout by default, and recognises that the @@ -264,7 +269,7 @@ Changing the size and colors of nodes and ties is as easy as specifying the function’s relevant argument with a replacement, or indicating from which attribute it should inherit this information. -![](https://www.jameshollway.com/post/manynet/README-more-options-1.png) +Graph illustrating automatic and manual use of node color and size #### More layouts @@ -274,7 +279,7 @@ additional layout algorithms for snapping layouts to a grid, visualising partitions horizontally, vertically, or concentrically, or conforming to configurational coordinates. -![](https://www.jameshollway.com/post/manynet/README-more-layouts-1.png) +Graphs illustrating different layouts #### More themes and scales @@ -286,7 +291,7 @@ quickly make sure your plots conform to your institution or taste, then it is easy to do with themes and scales that update the basic look and color palette used in your plots. -![](https://www.jameshollway.com/post/manynet/README-more-themes-1.png) +Graphs using default, IHEID, and ETHZ themes More themes are on their way, and we’re happy to take suggestions. @@ -296,7 +301,7 @@ Second, `graphs()` is used to graph multiple networks together, which can be useful for ego networks or network panels. `{patchwork}` is used to help arrange individual plots together. -![](https://www.jameshollway.com/post/manynet/README-autographs-1.png) +Example of graphs() used on longitudinal data ### grapht @@ -304,7 +309,7 @@ Third, `grapht()` is used to visualise dynamic networks. It uses `{gganimate}` and `{gifski}` to create a gif that visualises network changes over time. It really couldn’t be easier. -![](https://www.jameshollway.com/post/manynet/README-autographd-1.gif) +Example of grapht() on longitudinal data @@ -319,6 +324,11 @@ changes over time. It really couldn’t be easier. ## Marking +`{manynet}` includes four special groups of functions, each with their +own pretty `print()` and `plot()` methods: marks, measures, motifs, and +memberships. Marks are logical scalars or vectors, measures are numeric, +memberships categorical, and motifs result in tabular outputs. + `{manynet}`’s `*is_*()` functions offer fast logical tests of various properties. Whereas `is_*()` returns a single logical value for the network, `node_is_*()` returns a logical vector the length of the number @@ -368,21 +378,83 @@ member of group “A”, the second in group “B”, etc. `node_in_roulette()`, `node_in_spinglass()`, `node_in_strong()`, `node_in_structural()`, `node_in_walktrap()`, `node_in_weak()` +For example `node_brokerage_census()` returns the frequency of nodes’ +participation in Gould-Fernandez brokerage roles for a one-mode network, +and the Jasny-Lubell brokerage roles for a two-mode network. + +These can be analysed alone, or used as a profile for establishing +equivalence. `{manynet}` offers both HCA and CONCOR algorithms, as well +as elbow, silhouette, and strict methods for *k*-cluster selection. + +Plot of a dendrogram of structural equivalence + +`{manynet}` also includes functions for establishing membership on other +bases, such as typical community detection algorithms, as well as +component and core-periphery partitioning algorithms. + ## Measuring -All other `net_*`, `node_*` and `tie_*` functions in `{manynet}` offer -ways for users to measure some feature, property, or quantity of the -network. This includes measures of balance and brokerage, centrality, -closure, and cohesion, diffusion and diversity, hierarchy and -resilience. There is a lot here, so we recommend you check out the -function overview. +`{manynet}` also offers a large and growing smorgasbord of measures that +can be used at the node, tie, and network level to measure some feature, +property, or quantity of the network. Each recognises whether the +network is directed or undirected, weighted or unweighted, one-mode or +two-mode. All return normalized values wherever possible, though this +can be overrided. Here are some examples: + +- *Centrality*: `node_degree()`, `node_closeness()`, + `node_betweenness()`, and `node_eigenvector()`, `net_degree()`, + `net_closeness()`, `net_betweenness()`, and `net_eigenvector()` +- *Cohesion*: `net_density()`, `net_reciprocity()`, + `net_transitivity()`, `net_equivalency()`, and `net_congruency()` +- *Hierarchy*: `net_connectedness()`, `net_efficiency()`, + `net_upperbound()` +- *Resilience*: `net_components()`, `net_cohesion()`, `net_adhesion()`, + `net_diameter()`, `net_length()` +- *Innovation*: e.g. `node_redundancy()`, `node_effsize()`, + `node_efficiency()`, `node_constraint()`, `node_hierarchy()` +- *Diversity*: `net_richness()`, `net_diversity()`, `net_heterophily()`, + `net_assortativity()`, `node_richness()`, `node_diversity()`, + `node_heterophily()`, `node_assortativity()` +- *Topology*: e.g. `net_core()`, `net_factions()`, `net_modularity()`, + `net_smallworld()`, `net_balance()` +- *Diffusion*: e.g. `net_reproduction()`, `net_immunity()`, + `node_thresholds()` + +There is a lot here, so we recommend you explore [the list of +functions](https://stocnet.github.io/migraph/reference/index.html) to +find out more. + +## Tutorials + +This package includes tutorials to help new and experienced users learn +how they can conduct social network analysis using the package. These +tutorials leverage the additional package `{learnr}` (see +[here](https://rstudio.github.io/learnr/)), but we have made it easy to +use `{manynet}` or `{migraph}` tutorials right out of the box: + +``` r +run_tute() +#> # A tibble: 9 × 3 +#> package name title +#> +#> 1 manynet tutorial0 Intro to R +#> 2 manynet tutorial1 Data +#> 3 manynet tutorial2 Visualisation +#> 4 manynet tutorial3 Centrality +#> 5 manynet tutorial4 Community +#> 6 manynet tutorial5 Position +#> 7 manynet tutorial6 Topology +#> 8 manynet tutorial7 Diffusion +#> 9 migraph tutorial8 Diversity and Regression +# run_tute("tutorial1") +``` ## Installation ### Stable The easiest way to install the latest stable version of `{manynet}` is -via CRAN. Simply open the R console and enter:[^1] +via CRAN. Simply open the R console and enter: `install.packages('manynet')` @@ -414,34 +486,11 @@ Github, please install the `{remotes}` package from CRAN and then: - For latest development version: `remotes::install_github("stocnet/manynet@develop")` -### Tutorials - -This package includes tutorials to help new and experienced users learn -how they can conduct social network analysis using the package. These -tutorials leverage the additional package `{learnr}` (see -[here](https://rstudio.github.io/learnr/)), but we have made it easy to -use `{manynet}` or `{migraph}` tutorials right out of the box: +### Other sources -``` r -run_tute() -#> # A tibble: 10 × 3 -#> package name title -#> -#> 1 manynet tutorial0 Intro to R -#> 2 manynet tutorial1 Data -#> 3 manynet tutorial2 Visualisation -#> 4 manynet tutorial3 Centrality -#> 5 migraph tutorial3 Centrality -#> 6 manynet tutorial4 Community -#> 7 manynet tutorial5 Position -#> 8 manynet tutorial6 Topology -#> 9 manynet tutorial7 Diffusion -#> 10 migraph tutorial8 Regression -``` +Those using Mac computers may also install using Macports: -``` r -# run_tute("tutorial1") -``` +`sudo port install R-manynet` ## Relationship to other packages @@ -470,6 +519,3 @@ Development on this package has been funded by the Swiss National Science Foundation (SNSF) [Grant Number 188976](https://data.snf.ch/grants/grant/188976): “Power and Networks and the Rate of Change in Institutional Complexes” (PANARCHIC). - -[^1]: Macs with Macports installed may also install from the command - line [using Macports](https://ports.macports.org/port/R-manynet/). diff --git a/pkgdown/favicon/apple-touch-icon-120x120.png b/pkgdown/favicon/apple-touch-icon-120x120.png index 33c66da8ea22b3c368fcee96c7e0a51cc40b468d..efb5002d5f67c6068b4384f5af11f212b6aabd37 100644 GIT binary patch delta 10428 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 F{{x%^0d@cY diff --git a/pkgdown/favicon/apple-touch-icon-152x152.png b/pkgdown/favicon/apple-touch-icon-152x152.png index a4693cb660f9ff525b5f9afcc85653c2fb18830a..ca460e24debe4fc496ae4d8718a9fa60cf161a37 100644 GIT binary patch literal 13876 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 literal 14127 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 GgZ~de@YBoy diff --git a/pkgdown/favicon/apple-touch-icon-180x180.png b/pkgdown/favicon/apple-touch-icon-180x180.png index 94870488520cd8e486c9e0ae213ddeb79bf10b62..da3a39723dd8642a7804015fdf83638aa4d53fc1 100644 GIT binary patch literal 16795 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 literal 16924 zcmZ{MWmFtpuqG3mP=Iy9E#K7CgASLvZ+j;2vBCcX`dd@5lS| zU@^0h?mkkdcGa$3M5rpup`(zXfIuL0`47_Szoa*K_C|n5a`ed1QJRGfe0NkTh&B>Um%)%l#>R% zzJ2C)6ej>bL2{N?l0n)-L_h`6P79J`fIt!*^3o6u_ob68H*Y+%mm%@VF`G#{PfWTz zy80_%zu!-oHYxxTJ*VoXJ-82jCH`~45sdguk9Ec zyL@_CpsPMctYtbzym|JNF_;kHz7(1Cy;+2c

8CTjKwCt%c|GOAENLFc^(1x6a-v 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

+
+ +
-
-

Transforming network data

+
+

Transforming networks

These functions are similar to the reformatting functions, and are also named to_*(), but their operation always changes the -network’s ‘order’ (number of nodes). Good examples of this are -to_mode1() and to_mode2() for transforming a -two-mode network into one of its one-mode projections. -to_mode1() will transform (project) the network to a -one-mode network of shared ties among its first set of nodes, while -to_mode2() will project the original network to a network -of shared ties among its second set of nodes. For more information on -projection, see for example Knoke et al. (2021). Let’s try this out on a -classic two-mode network, ison_southern_women. Assign and -name the transformed networks something sensible using e.g. +network’s ‘order’ (number of nodes).

+
+

Projections

+

Good examples of this are to_mode1() and +to_mode2() for transforming a two-mode network into one of +its one-mode projections. to_mode1() will transform +(project) the network to a one-mode network of shared ties among its +first set of nodes, while to_mode2() will project the +original network to a network of shared ties among its second set of +nodes. For more information on projection, see for example Knoke et +al. (2021).

+

Let’s try this out on a classic two-mode network, +ison_southern_women. Assign and name the transformed +networks something sensible using e.g. women <- to_mode... so that we can continue working with this data afterwards. To assign and immediately print the result, wrap the line in parentheses.

@@ -573,57 +717,13 @@

Transforming network data

-
-
-

Grabbing key details from network data

-

We can ask other questions of this data too. {manynet} -(and {migraph}) use a simple function naming convention so -that you always know to what it relates.

-
    -
  • network_*() functions usually return one value for the -network or graph, whether that be a string like Evelyn, -logical value like TRUE, or some number like 3 -or -0.0035
  • -
  • node_*() functions always return a vector of values for -the network as long as the number of nodes or vertices in the network -(of any mode)
  • -
  • tie_*() functions always return a vector of values for -the network as long as the number of ties or edges in the network (of -any sign or type)
  • -
-

To find out how many nodes are in the network, use -network_nodes(). To find out how many nodes are in each -mode, use network_dims(). To find out the names of those -nodes, use node_names(). Use such functions to find -out:

+

Now use functions introduced above on the two projections you have +created to find out:

    -
  1. how many nodes are in the ison_southern_women -network
  2. -
  3. how many nodes are in each mode
  4. -
  5. how many ties are in the network
  6. -
  7. what nodal attributes there are in the network
  8. -
  9. what the names of the nodes are
  10. +
  11. how many nodes there are in each of these networks,
  12. +
  13. what the names of the nodes are, and
  14. +
  15. what tie attributes there are in the networks.
-
- -
-
-
network_nodes(ison_southern_women)
-network_dims(ison_southern_women)
-network_ties(ison_southern_women)
-network_node_attributes(ison_southern_women)
-node_names(ison_southern_women)
-
-

Now use these functions on the two projections you have created to -find out a) how many nodes there are in each of these networks, b) what -the names of the nodes are, and c) what tie attributes there are in the -networks.

@@ -632,12 +732,12 @@

Grabbing key details from network data

-
network_nodes(s_women)
-network_nodes(s_events)
+
net_nodes(s_women)
+net_nodes(s_events)
 node_names(s_women)
 node_names(s_events)
-network_tie_attributes(s_women)
-network_tie_attributes(s_events)
+net_tie_attributes(s_women) +net_tie_attributes(s_events)

So we can see that the to_mode*() functions have created a network of only one of the modes in the network. The ties in these @@ -646,14 +746,6 @@

Grabbing key details from network data

attributes, or could be retrieved using tie_weights(), but can also be checked with the simple logical check is_weighted().

-

There are a bunch of logical checks for many common properties or -features of networks. For example, one can check whether a network -is_twomode(), is_directed(), or -is_labelled(). Remember, all these to_*() and -is_*() functions work on any compatible class; the -to_*() functions will also attempt to return that same -class of object, making it even easier to manipulate networks into shape -for analysis.

Retrieve the tie weights from your women projection. Find the average (mean) of this vector of tie weights, and the average (mean) tie weight overall.

@@ -716,18 +808,14 @@

Grabbing key details from network data

only tied to one of its previously existing ties such that the network’s cardinality is maximised. +

Remember, all these to_*() functions work on any +compatible class; the to_*() functions will also attempt to +return that same class of object, making it even easier to manipulate +networks into shape for analysis.

-
-
-
    -
  1. There are a few exceptions to this in the -{manynet} package, or when attributes of the network are -listed (see below).↩︎

  2. -
-
-

Adding data

+
+

Adding data

If you import one or more edgelists and nodelists, it can be useful to bind these together in an igraph, tidygraph, or network class object.

@@ -741,8 +829,8 @@

Adding data

data-diagnostics="1" data-startover="1" data-lines="0" data-pipe="|>">
ison_adolescents %>% 
-  mutate(color = "red",
-         degree = 1:8) %>% 
+  mutate_nodes(color = "red",
+               degree = 1:8) %>% 
   mutate_ties(weight = 1:10)
@@ -754,6 +842,7 @@

Adding data

@@ -787,12 +876,12 @@

Adding data

+ + + + + + + + + + + + + + + + + + + + @@ -894,23 +1165,23 @@

Adding data

+ + + + + @@ -1083,9 +1398,9 @@

Adding data

+ + + - - + + + + + + + - - + - + + - - - - - - - + + - - + - + + - - + - + + - - +
-
- From 2aa0b87f327e38b9dfe47c9ee607ff6a33089961 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 19 Jul 2024 18:17:35 +0200 Subject: [PATCH 09/26] Started separating out the modifying section of the Function Overview --- pkgdown/_pkgdown.yml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index fe45e96a..f09458c8 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -71,14 +71,27 @@ reference: with other dimensions, such as from a two-mode network into a one-mode network. There are also functions for splitting networks, e.g. into a list of ego networks, and rejoining them from such lists. - There are also tidy-style and igraph-style functions for adding or joining - new data on nodes or ties to networks. contents: - - starts_with("as_") - - starts_with("add_") - starts_with("to_") - starts_with("from_") + - subtitle: "Coercion" + desc: | + Functions for modifying networks into other classes. + contents: + - starts_with("as_") + - subtitle: "Attributes" + desc: | + Functions for modifying nodal and tie attributes. + These include tidy-style and igraph-style functions for adding or joining + new data on nodes or ties to networks. + contents: + - starts_with("add_") + - subtitle: "Missing" + desc: | + Functions for modifying how missing data is treated. + contents: - starts_with("na_to_") + - title: "Marking" desc: | Functions for identifying properties of networks, nodes, or ties, From 5a13d6669cba6366a46a7dac43a5c0214a95d0cc Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 19 Jul 2024 19:28:01 +0200 Subject: [PATCH 10/26] Added section on adding/deleting nodes and ties, as well as on deleting attributes --- inst/tutorials/tutorial1/data.Rmd | 47 ++++- inst/tutorials/tutorial1/data.html | 285 ++++++++++++++++++++++------- 2 files changed, 267 insertions(+), 65 deletions(-) diff --git a/inst/tutorials/tutorial1/data.Rmd b/inst/tutorials/tutorial1/data.Rmd index 38c27f78..b0c33344 100644 --- a/inst/tutorials/tutorial1/data.Rmd +++ b/inst/tutorials/tutorial1/data.Rmd @@ -652,10 +652,41 @@ Remember, all these `to_*()` functions work on any compatible class; the `to_*()` functions will also attempt to return that same class of object, making it even easier to manipulate networks into shape for analysis. -## Adding data +## Modifying data -If you import one or more edgelists and nodelists, -it can be useful to bind these together in an igraph, tidygraph, or network class object. +After choosing and/or importing some network data, +and alongside other modifications, +you may need to add or delete particular nodes or ties, +or add or delete specific nodal or tie attributes. + +### Adding/deleting nodes and ties + +Sometimes you may wish to add particular nodes or ties. +This can be achieved easily enough with the following functions. +Note that one (1) additional node is added via the first command, +and one (1) additional tie (between the nodes indexed 1 and 3) +is added using the second. + +```{r add, exercise = TRUE} +add_nodes(ison_adolescents, 1) +add_ties(ison_adolescents, list(1,3)) +``` + +Other times you may wish to delete particular nodes or ties, +and there the syntax is similar. +Note here though that when a single value is used in the second argument, +this is interpreted as an index, +whereas one can also use the node's name for `delete_nodes()`, +or name the tie(s) to be deleted using `|` as a separator. + +```{r delete, exercise = TRUE} +delete_nodes(ison_adolescents, 1) +delete_nodes(ison_adolescents, "Sue") +delete_ties(ison_adolescents, 1) +delete_ties(ison_adolescents, "Carol|Tina") +``` + +### Adding/deleting attributes Adding nodal attributes to a given network is relatively straightforward. `{manynet}` offers a more `{igraph}`-like syntax, e.g. `add_node_attribute()`, @@ -669,6 +700,16 @@ ison_adolescents %>% mutate_ties(weight = 1:10) ``` +One can also delete nodal attributes in a similar way, +by assigning `NULL` to a particular attribute. + +```{r mutnull, exercise = TRUE} +ison_southern_women +ison_southern_women %>% + mutate_nodes(Surname = NULL, + Title = NULL) +``` + Note that to use `{dplyr}`-like functions like `mutate()`, `rename()`, `filter()`, `select()`, or `join()` on network ties, you will need to append the function name with `_ties`. diff --git a/inst/tutorials/tutorial1/data.html b/inst/tutorials/tutorial1/data.html index 9768614f..474304c7 100644 --- a/inst/tutorials/tutorial1/data.html +++ b/inst/tutorials/tutorial1/data.html @@ -814,11 +814,41 @@

Projections

networks into shape for analysis.

-
-

Adding data

-

If you import one or more edgelists and nodelists, it can be useful -to bind these together in an igraph, tidygraph, or network class -object.

+
+

Modifying data

+

After choosing and/or importing some network data, and alongside +other modifications, you may need to add or delete particular nodes or +ties, or add or delete specific nodal or tie attributes.

+
+

Adding/deleting nodes and ties

+

Sometimes you may wish to add particular nodes or ties. This can be +achieved easily enough with the following functions. Note that one (1) +additional node is added via the first command, and one (1) additional +tie (between the nodes indexed 1 and 3) is added using the second.

+
+
add_nodes(ison_adolescents, 1)
+add_ties(ison_adolescents, list(1,3))
+ +
+

Other times you may wish to delete particular nodes or ties, and +there the syntax is similar. Note here though that when a single value +is used in the second argument, this is interpreted as an index, whereas +one can also use the node’s name for delete_nodes(), or +name the tie(s) to be deleted using | as a separator.

+
+
delete_nodes(ison_adolescents, 1)
+delete_nodes(ison_adolescents, "Sue")
+delete_ties(ison_adolescents, 1)
+delete_ties(ison_adolescents, "Carol|Tina")
+ +
+
+
+

Adding/deleting attributes

Adding nodal attributes to a given network is relatively straightforward. {manynet} offers a more {igraph}-like syntax, @@ -834,6 +864,17 @@

Adding data

mutate_ties(weight = 1:10)
+

One can also delete nodal attributes in a similar way, by assigning +NULL to a particular attribute.

+
+
ison_southern_women
+ison_southern_women %>% 
+  mutate_nodes(Surname = NULL,
+               Title = NULL)
+ +

Note that to use {dplyr}-like functions like mutate(), rename(), filter(), select(), or join() on network ties, you will @@ -987,19 +1028,19 @@

Adding data

@@ -1018,22 +1059,22 @@

Adding data

@@ -1130,22 +1171,22 @@

Adding data

@@ -1165,23 +1206,23 @@

Adding data

@@ -1360,20 +1401,20 @@

Adding data

@@ -1515,17 +1556,17 @@

Adding data

@@ -1662,25 +1703,25 @@

Adding data

@@ -1789,27 +1830,27 @@

Adding data

+ + + + + + + + + + + + + + +

@@ -1877,6 +2037,7 @@

Adding data

+
From cd98d59bc55c928c35cc8de1e3406a321b8341d7 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 19 Jul 2024 22:51:04 +0200 Subject: [PATCH 11/26] Updated visualisation tutorial with graphs() and grapht() --- inst/tutorials/tutorial2/visualisation.Rmd | 207 ++-- inst/tutorials/tutorial2/visualisation.html | 1023 ++++++++++++++----- 2 files changed, 874 insertions(+), 356 deletions(-) diff --git a/inst/tutorials/tutorial2/visualisation.Rmd b/inst/tutorials/tutorial2/visualisation.Rmd index 3313fb4c..653d74d5 100644 --- a/inst/tutorials/tutorial2/visualisation.Rmd +++ b/inst/tutorials/tutorial2/visualisation.Rmd @@ -26,41 +26,40 @@ As Tufte (1983: 9) said: > "At their best, graphics are instruments for reasoning about quantitative information. Often the most effective way to describe, explore, and summarize a set of numbers – even a very large set – is to look at pictures of those numbers" -Brandes et al (1999) argue that visualising networks demands thinking about: +All of this is crucial with networks. +As a first step, network visualisation -- or _graphing_ -- +offers us a way to vet our data for anything strange that might be going on, +both revealing and informing our assumptions and intuitions. -- _substance_: a concise and precise delivery of insights to the researcher and/or readers -- _design_: the ergonomics of function are 98% of the purpose of good design, aesthetics only 2% -- and _algorithm_: the features of the e.g. the layout algorithm + -While there may be many dead-ends to exploratory visualisation, +It is also crucial to the further communication of the lessons that we have +learned through investigation with others. +While there may be many dead-ends and time-sinks to visualisation, it is worth taking the time to make sure that your main points are easy to appreciate. -On [her excellent and helpful website](https://kateto.net/network-visualization), -Katya Ognyanova outlines some key dimensions of control -that network researchers have to play with: +Brandes et al ([1999](https://doi.org/10.1177/0951692899011001004)) argue that visualising networks demands thinking about: -- vertex position (layout) -- vertex shape -- vertex color -- vertex size -- vertex labels -- edge color -- edge size -- edge shape -- edge arrows +- _substance_: a concise and precise delivery of insights to the researcher and/or readers +- _design_: the ergonomics of function are 98% of the purpose of good design, aesthetics only 2% +- and _algorithm_: the features of the e.g. the layout algorithm -## Approaches to visualising networks in R +## Different approaches -There are a host of packages for plotting in R, -and for plotting networks in R. +There are several main packages for plotting in R, +as well as several for plotting networks in R. Plotting in R is typically based around two main approaches: -the 'base' approach in R by default, -and the 'grid' approach made popular by the famous and very flexible `{ggplot2}` package.^['gg' stands for the Grammar of Graphics.] -Approaches to plotting _graphs_ or _networks_ in R can be similarly divided. -The two classic packages are `{igraph}` and `{sna}`, both building upon the base R graphics engine. -Newer packages [`{ggnetwork}`](https://www.r-bloggers.com/2016/03/ggnetwork-network-geometries-for-ggplot2/) and +- the 'base' approach in R by default, +and +- the 'grid' approach made popular by the famous and very flexible `{ggplot2}` package.^['gg' stands for the [Grammar of Graphics](https://doi.org/10.1007/0-387-28695-0).] + +Approaches to plotting _graphs_ or _networks_ in R can be similarly divided: + +- two classic packages, `{igraph}` and `{sna}`, +both build upon the base R graphics engine, +- newer packages [`{ggnetwork}`](https://www.r-bloggers.com/2016/03/ggnetwork-network-geometries-for-ggplot2/) and [`{ggraph}`](https://ggraph.data-imaginist.com/index.html) build upon a grid approach.^[ Others include: 'Networkly' for creating 2-D and 3-D interactive networks that can be rendered with plotly and can be easily integrated into @@ -70,35 +69,84 @@ shiny apps or markdown documents; 'networkD3' interacts with javascript (D3) to make interactive networks (https://www.r-bloggers.com/2016/10/network-visualization-part-6-d3-and-r-networkd3/). ] -This vignette introduces some functions in the `{manynet}` package for plotting and -visualising network data. -`{manynet}` builds upon the ggplot2/ggraph engine for plotting. -## Using `{manynet}` to quickly plot network graphs +While the coercion routines available in `{manynet}` make it easy to use +any of these packages' graphing capabilities, +`{manynet}` itself builds upon the ggplot2/ggraph engine +to help quickly and informatively graph networks. + +### Dimensions of visualisation + +On [her excellent and helpful website](https://kateto.net/network-visualization), +Katya Ognyanova outlines some key dimensions of control +that network researchers have to play with: + +- vertex position (layout) +- vertex shape (e.g. circles, squares) +- vertex color +- vertex size +- vertex labels +- edge shape (e.g. straight, bends) +- edge type (e.g. solid, dashed) +- edge color +- edge size +- edge arrows + +In the following sections, +we will learn how `{manynet}` provides sensible defaults on many of these elements, +and offers ways to extend or customise them. + +## Graphing + +### graphr To get a basic visualisation of the network before adding various specifications, the `graphr()` function in `{manynet}` is a quick and easy way to obtain a clear first look of the network for preliminary investigations and understanding of the network. Let's quickly visualise one of the `ison_` datasets included in the package. -```{r manyneteg, exercise=TRUE, purl = FALSE} - -``` - -```{r manyneteg-solution} -library(manynet) -graphr(ison_brandes) +```{r manyneteg, exercise=TRUE} +graphr(ison_lotr) ``` -We can also specify the colours, groups, shapes, and sizes of nodes in +We can also specify the colours, groups, shapes, and sizes of nodes and edges in the `graphr()` function using the following parameters: * `node_colour` -* `node_group` * `node_shape` * `node_size` +* `node_group` +* `edge_color` +* `edge_size` + +```{r speceg, exercise=TRUE} +graphr(ison_lotr, node_color = "Race") +``` + +### graphs + +`graphr()` is not the only graphing function included in `{manynet}`. +To graph _sets_ of networks together, `graphs()` makes sure that two +or more networks are plotted together. +This might be a set of ego networks, subgraphs, or waves of a longitudinal network. + +```{r graphs, exercise=TRUE} +graphs(to_subgraphs(ison_lotr, "Race"), + waves = c(1,2,3,4)) +``` + +### grapht + +`grapht()` is another option, rendering network changes as a gif. + +```{r grapht, exercise=TRUE} +ison_lotr %>% + mutate_ties(year = sample(1:12, 66, replace = TRUE)) %>% + to_waves(attribute = "year", cumulative = TRUE) %>% + grapht() +``` -### Adding titles and subtitles +## Titles and subtitles Append `ggtitle()` to add a title. `{manynet}` works well with both `{ggplot2}` and `{ggraph}` functions @@ -110,12 +158,12 @@ of the network. ``` ```{r manynetexample2-solution} -graphr(ison_adolescents, - labels = TRUE, - node_size = 1.5) + +graphr(ison_adolescents) + ggtitle("Visualisation") ``` +## Arrangements + `{manynet}` also uses the `{patchwork}` package for arranging graphs together, e.g. side-by-side or above one another. The syntax is quite straight forward and is used throughout these vignettes. @@ -129,53 +177,39 @@ graphr(ison_adolescents) + graphr(ison_algebra) graphr(ison_adolescents) / graphr(ison_algebra) ``` -### Adding legends +## Legends -By default, `{manynet}` doesn't add legends automatically, -as often the colours are pretty self-explanatory, -for example in the following figure. +While `{manynet}` attempts to provide legends where necessary, +in some cases the legends offer insufficient detail, +such as in the following figure, or are absent. -```{r maxbet} -ison_adolescents %>% - mutate(maxbet = node_is_max(node_betweenness(ison_adolescents))) %>% +```{r maxbet, exercise = TRUE} +ison_lotr %>% + mutate(maxbet = node_is_max(node_betweenness(ison_lotr))) %>% graphr(node_color = "maxbet") ``` -Alternatively, group labels are added automatically when using the 'node_group' argument to highlight groups in a network. - -```{r group, exercise = TRUE, purl = FALSE} - -``` - -```{r group-solution} -ison_networkers %>% - graphr(node_group = "Discipline") -``` - -But other times it is important to add in a legend. `{manynet}` supports the `{ggplot2}` way of adding legends after the main plot has been constructed, using `guides()` to add in the legends, and `labs()` for giving those legends particular titles. +Note that we can use `"\n"` within the legend title to make the title +span multiple lines. -```{r discipline, exercise = TRUE, purl = FALSE} - -``` - -```{r discipline-solution} -ison_networkers %>% - graphr(node_color = "Discipline") + +```{r legend, exercise=TRUE} +ison_lotr %>% + mutate(maxbet = node_is_max(node_betweenness(ison_lotr))) %>% + graphr(node_color = "maxbet") + guides(color = "legend") + - labs(color = "Discipline") + labs(color = "Maximum\nBetweenness") ``` -```{r discq, purl = FALSE} -question("What discipline is Charles Kadushin in according to this graph?", - answer("Anthropology"), - answer("Mathematics/Statistics"), - answer("Other"), - answer("Sociology", correct = TRUE), - allow_retry = TRUE, random_answer_order = TRUE) +An alternative to colors and legends is to use the 'node_group' argument +to highlight groups in a network. +This works best for quite clustered distributions of attributes. + +```{r group, exercise = TRUE} +graphr(ison_lotr, node_group = "Race") ``` ### Layouts @@ -226,6 +260,7 @@ graphr(mpn_elite_mex, ``` ## Using `{ggraph}` for more flexibility +## Further flexibility For more flexibility with visualizations, `{manynet}` users are encouraged to use the excellent `{ggraph}` package. @@ -236,12 +271,8 @@ a particular plot from the ground up, adding explicit layers to visualise the nodes and edges. ```{r ggrapheg, exercise=TRUE, purl = FALSE} - -``` - -```{r ggrapheg-solution} library(ggraph) -ggraph(mpn_elite_mex, layout = "fr") + +ggraph(ison_greys, layout = "fr") + geom_edge_link(edge_colour = "dark grey", arrow = arrow(angle = 45, length = unit(2, "mm"), @@ -256,7 +287,8 @@ ggraph(mpn_elite_mex, layout = "fr") + As we can see in the code above, we can specify various aspects of the plot to tailor it to our network. -Firstly, we can alter the **layout** of the network using the `layout =` argument + +First, we can alter the **layout** of the network using the `layout =` argument to create a clearer visualisation of the ties between nodes. This is especially important for larger networks, where nodes and ties are more easily obscured or misrepresented. @@ -268,7 +300,7 @@ More layouts can be found in the `{graphlayouts}` and `{igraph}` R packages. To use a layout from the `{igraph}` package, enter only the last part of the layout algorithm name (eg. `layout = "mds"` for "layout_with_mds"). -Secondly, using `geom_node_point()` which draws the nodes as geometric shapes +Second, using `geom_node_point()` which draws the nodes as geometric shapes (circles, squares, or triangles), we can specify the presentation of **nodes** in the network in terms of their *shape* (`shape=`, choose from 1 to 21), *size* (`size=`), or *colour* (`colour=`). We can also use `aes()` to match to @@ -276,7 +308,7 @@ node attributes. To add labels, use `geom_node_text()` or `geom_node_label()` (draws labels within a box). The font (`family=`), font size (`size=`), and colour (`colour=`) of the labels can be specified. -Thirdly, we can also specify the presentation of **edges** in the network. +Third, we can also specify the presentation of **edges** in the network. To draw edges, we use `geom_edge_link0()` or `geom_edge_link()`. Using the latter function makes it possible to draw a straight line with a gradient. @@ -291,9 +323,10 @@ edge attributes using `aes()`: * *opacity*: `edge_alpha=` -For directed graphs, arrows can be drawn using the `arrow=` argument and the -`arrow()` function from `{ggplot2}`. The angle, length, arrowhead type, and -padding between the arrowhead and the node can also be specified. +For directed graphs, arrows can be drawn using the `arrow=` argument +and the `arrow()` function from `{ggplot2}`. +The angle, length, arrowhead type, +and padding between the arrowhead and the node can also be specified. To change the position of the legend, add the `theme()` function from `{ggplot2}`. The legend can be positioned at the top, bottom, left, or right, diff --git a/inst/tutorials/tutorial2/visualisation.html b/inst/tutorials/tutorial2/visualisation.html index 437430ad..15ac7393 100644 --- a/inst/tutorials/tutorial2/visualisation.html +++ b/inst/tutorials/tutorial2/visualisation.html @@ -120,8 +120,18 @@

Why we graph

explore, and summarize a set of numbers – even a very large set – is to look at pictures of those numbers”

-

Brandes et al (1999) argue that visualising networks demands thinking -about:

+

All of this is crucial with networks. As a first step, network +visualisation – or graphing – offers us a way to vet our data +for anything strange that might be going on, both revealing and +informing our assumptions and intuitions.

+

+

It is also crucial to the further communication of the lessons that +we have learned through investigation with others. While there may be +many dead-ends and time-sinks to visualisation, it is worth taking the +time to make sure that your main points are easy to appreciate.

+

Brandes et al (1999) argue that +visualising networks demands thinking about:

  • substance: a concise and precise delivery of insights to the researcher and/or readers
  • @@ -130,50 +140,59 @@

    Why we graph

  • and algorithm: the features of the e.g. the layout algorithm
-

While there may be many dead-ends to exploratory visualisation, it is -worth taking the time to make sure that your main points are easy to -appreciate.

+ +
+

Different approaches

+

There are several main packages for plotting in R, as well as several +for plotting networks in R. Plotting in R is typically based around two +main approaches:

+
    +
  • the ‘base’ approach in R by default, and
  • +
  • the ‘grid’ approach made popular by the famous and very flexible +{ggplot2} package.1
  • +
+

Approaches to plotting graphs or networks in R can +be similarly divided:

+
    +
  • two classic packages, {igraph} and {sna}, +both build upon the base R graphics engine,
  • +
  • newer packages {ggnetwork} +and {ggraph} +build upon a grid approach.2
  • +
+

While the coercion routines available in {manynet} make +it easy to use any of these packages’ graphing capabilities, +{manynet} itself builds upon the ggplot2/ggraph engine to +help quickly and informatively graph networks.

+
+

Dimensions of visualisation

On her excellent and helpful website, Katya Ognyanova outlines some key dimensions of control that network researchers have to play with:

  • vertex position (layout)
  • -
  • vertex shape
  • +
  • vertex shape (e.g. circles, squares)
  • vertex color
  • vertex size
  • vertex labels
  • +
  • edge shape (e.g. straight, bends)
  • +
  • edge type (e.g. solid, dashed)
  • edge color
  • edge size
  • -
  • edge shape
  • edge arrows
-
-
-

Approaches to visualising networks in R

-

There are a host of packages for plotting in R, and for plotting -networks in R. Plotting in R is typically based around two main -approaches: the ‘base’ approach in R by default, and the ‘grid’ approach -made popular by the famous and very flexible {ggplot2} -package.1 Approaches to plotting -graphs or networks in R can be similarly divided.

-

The two classic packages are {igraph} and -{sna}, both building upon the base R graphics engine. Newer -packages {ggnetwork} -and {ggraph} -build upon a grid approach.2 This vignette introduces some -functions in the {manynet} package for plotting and -visualising network data. {manynet} builds upon the -ggplot2/ggraph engine for plotting.

-
+

In the following sections, we will learn how {manynet} +provides sensible defaults on many of these elements, and offers ways to +extend or customise them.


    -
  1. ‘gg’ stands for the Grammar of Graphics.

    ‘gg’ stands for the Grammar of Graphics.↩︎

  2. Others include: ‘Networkly’ for creating 2-D and 3-D interactive networks that can be rendered with plotly and can be @@ -188,9 +207,12 @@

    Approaches to visualising networks in R

    href="#section-fnref2" class="footnote-back">↩︎

-
-

Using {manynet} to quickly plot network graphs

+
+
+
+

Graphing

+
+

graphr

To get a basic visualisation of the network before adding various specifications, the graphr() function in {manynet} is a quick and easy way to obtain a clear first @@ -200,25 +222,59 @@

Using {manynet} to quickly plot network graphs

+
graphr(ison_lotr)
-
-
library(manynet)
-graphr(ison_brandes)
-

We can also specify the colours, groups, shapes, and sizes of nodes -in the graphr() function using the following +and edges in the graphr() function using the following parameters:

  • node_colour
  • -
  • node_group
  • node_shape
  • node_size
  • +
  • node_group
  • +
  • edge_color
  • +
  • edge_size
-
-

Adding titles and subtitles

+
+
graphr(ison_lotr, node_color = "Race")
+ +
+
+
+

graphs

+

graphr() is not the only graphing function included in +{manynet}. To graph sets of networks together, +graphs() makes sure that two or more networks are plotted +together. This might be a set of ego networks, subgraphs, or waves of a +longitudinal network.

+
+
graphs(to_subgraphs(ison_lotr, "Race"),
+       waves = c(1,2,3,4))
+ +
+
+
+

grapht

+

grapht() is another option, rendering network changes as +a gif.

+
+
ison_lotr %>%
+  mutate_ties(year = sample(1:12, 66, replace = TRUE)) %>%
+  to_waves(attribute = "year", cumulative = TRUE) %>%
+  grapht()
+ +
+
+
+
+

Titles and subtitles

Append ggtitle() to add a title. {manynet} works well with both {ggplot2} and {ggraph} functions that can be appended to create more tailored visualisations of @@ -232,11 +288,12 @@

Adding titles and subtitles

data-label="manynetexample2-solution" data-completion="1" data-diagnostics="1" data-startover="1" data-lines="0" data-pipe="|>"> -
graphr(ison_adolescents,
-       labels = TRUE,
-       node_size = 1.5) + 
+
graphr(ison_adolescents) + 
   ggtitle("Visualisation")
+
+
+

Arrangements

{manynet} also uses the {patchwork} package for arranging graphs together, e.g. side-by-side or above one another. The syntax is quite straight forward and is used throughout these @@ -253,115 +310,209 @@

Adding titles and subtitles

graphr(ison_adolescents) / graphr(ison_algebra)
-
-

Adding legends

-

By default, {manynet} doesn’t add legends automatically, -as often the colours are pretty self-explanatory, for example in the -following figure.

-
## Warning: There was 1 warning in `mutate()`.
-## ℹ In argument: `maxbet = node_is_max(node_betweenness(ison_adolescents))`.
-## Caused by warning:
-## ! 'node_mode' is deprecated.
-## Use 'node_is_mode' instead.
-## See help("Deprecated") and help("migraph-deprecated").
-
## Please make sure you spelled node color variable correctly.
-

-

Alternatively, group labels are added automatically when using the -‘node_group’ argument to highlight groups in a network.

-
+

Legends

+

While {manynet} attempts to provide legends where +necessary, in some cases the legends offer insufficient detail, such as +in the following figure, or are absent.

+
+
ison_lotr %>% 
+  mutate(maxbet = node_is_max(node_betweenness(ison_lotr))) %>% 
+  graphr(node_color = "maxbet")
-
-
ison_networkers %>% 
-  graphr(node_group = "Discipline")
-
-

But other times it is important to add in a legend. -{manynet} supports the {ggplot2} way of adding -legends after the main plot has been constructed, using +

{manynet} supports the {ggplot2} way of +adding legends after the main plot has been constructed, using guides() to add in the legends, and labs() for -giving those legends particular titles.

-
- -
-
-
ison_networkers %>% 
-  graphr(node_color = "Discipline") +
+giving those legends particular titles. Note that we can use
+"\n" within the legend title to make the title span
+multiple lines.

+
+
ison_lotr %>% 
+  mutate(maxbet = node_is_max(node_betweenness(ison_lotr))) %>% 
+  graphr(node_color = "maxbet") +
   guides(color = "legend") + 
-  labs(color = "Discipline")
-
-
-
-
-
-
- + labs(color = "Maximum\nBetweenness")
+
+

An alternative to colors and legends is to use the ‘node_group’ +argument to highlight groups in a network. This works best for quite +clustered distributions of attributes.

+
+
graphr(ison_lotr, node_group = "Race")
+
-
-

Layouts

+
+

Layouts

+

The aim of graph layouts is to position nodes in a (usually) +two-dimensional space to maximise some analytic and aesthetically +pleasing function. Quality measures might include:

+
    +
  • minimising the crossing number of edges/ties in the graph +(planar graphs +require no crossings)
  • +
  • minimising the slope number of distinct edge slopes in the +graph (where vertices are represented as points on a Euclidean +plane)
  • +
  • minimising the bend number in all edges in the graph (every +graph has a right angle crossing (RAC) drawing with three bends per +edge)
  • +
  • minimising the total edge length
  • +
  • minimising the maximum edge length
  • +
  • minimising the edge length variance
  • +
  • maximising the angular resolution or sharpest angle of +edges meeting at a common vertex
  • +
  • minimising the bounding box of the plot
  • +
  • evening the aspect ratio of the plot
  • +
  • displaying symmetry groups (subgraph automorphisms)
  • +

A range of graph layouts are available across the {igraph}, {graphlayouts}, and {manynet} packages that can be used together with graphr().

+
+

Force-directed layouts

+

Force-directed layouts updates some initial placement of vertices +through the operation of some system of metaphorically-physical forces. +These might include attractive and repulsive forces.

+
+
(graphr(ison_southern_women, layout = "fr") + ggtitle("Fruchterman-Reingold") |
+graphr(ison_southern_women, layout = "kk") + ggtitle("Kamada-Kawai"))
+ +
+
+
+

Layered layouts

+

Layered layouts arrange nodes into horizontal (or vertical) layers, +positioning them so that they reduce crossings. These layouts are best +suited for directed acyclic graphs or similar.

+
(graphr(ison_southern_women, layout = "bipartite") + ggtitle("Bipartite") |
+graphr(ison_southern_women, layout = "hierarchy") + ggtitle("Hierarchy")) /
+(graphr(ison_southern_women, layout = "railway") + ggtitle("Railway") |
+graphr(ison_southern_women, layout = "alluvial") + ggtitle("Alluvial"))
-
-
(graphr(ison_southern_women, layout = "bipartite") + ggtitle("bipartite") |
-graphr(ison_southern_women, layout = "kk") + ggtitle("kk"))
+
+
+

Circular layouts

+

Circular layouts arrange nodes around (potentially concentric) +circles, such that crossings are minimised and adjacent nodes are +located close together. In some cases, location or layer can be +specified by attribute or mode.

+
+
graphr(ison_southern_women, layout = "concentric") + ggtitle("Concentric")
+ +
+
+
+

Other layouts

+

Other layouts include:

+
    +
  • spectral layouts
  • +
  • grid layouts
  • +
  • orthogonal layouts
  • +
  • tree layouts
  • +
  • arc layouts
  • +
  • dominance layouts
  • +
-
-

Using different colours

-

Because the graphr() function is based on the grammar of -graphics, it’s easy to extend or alter aesthetic aspects. Here let’s try -and change the colors assigned to the different regions in the -mpn_elite_mex dataset.

+
+

Colors

+
+

Who’s hue?

+

By default, graphr() will use a color palette that +offers fairly good contrast and, since v1.0.0 of {manynet}, +better accessibility. However, a different hue might offer a better +aesthetic or identifiability for some nodes. Because the +graphr() function is based on the grammar of graphics, it’s +easy to extend or alter aesthetic aspects. Here let’s try and change the +colors assigned to the different races in the ison_lotr +dataset.

+
graphr(ison_lotr,
+           node_color = "Race")
+
+graphr(ison_lotr,
+           node_color = "Race") +
+  ggplot2::scale_colour_hue()
-
+
+

Grayscale

+

Other times color may not be desired. Some publications require +grayscale images. To use a grayscale color palette, replace +_hue from above with _grey:

+
-
graphr(mpn_elite_mex,
-           node_color = "region")
-
-graphr(mpn_elite_mex,
-           node_color = "region") +
-  ggplot2::scale_colour_hue()
-
-graphr(mpn_elite_mex,
-           node_color = "region") +
-  ggplot2::scale_colour_grey()
-
-graphr(mpn_elite_mex,
-           node_color = "region") +
+
graphr(ison_lotr,
+           node_color = "Race") +
+  ggplot2::scale_colour_grey()
+ +
+
+
+

Manual override

+

Or we may want to choose particular colors for each category. This is +pretty straightforward to do with +ggplot2::scale_colour_manual(). Some common color names are +available, but otherwise hex color codes can be used for more specific +colors. Unspecified categories are coloured (dark) grey.

+
+
graphr(ison_lotr,
+           node_color = "Race") +
   ggplot2::scale_colour_manual(
-    values = c("1" = "red",
-               "3" = "blue",
-               "2" = "green")) +
+    values = c("Dwarf" = "red",
+               "Hobbit" = "orange",
+               "Maiar" = "#DEC20B",
+               "Human" = "lightblue",
+               "Elf" = "lightgreen",
+               "Ent" = "darkgreen")) +
   labs(color = "Color")
+ +
+
+
+

Theming

+

Perhaps you are preparing a presentation, representing your +institution, department, or research centre at home or abroad. In this +case, you may wish to theme the whole network with institutional colors +and fonts. Here we demonstrate one of the color scales available in +{manynet}, the colors of the Sustainable Development +Goals:

+
+
graphr(ison_lotr, node_color = "Race") + 
+  scale_color_sdgs()
+
+

More institutional scales and themes are available, and more can be +implemented upon pull request.

-
-

Using {ggraph} for more flexibility

+
+

Further flexibility

For more flexibility with visualizations, {manynet} users are encouraged to use the excellent {ggraph} package. {ggraph} is built upon the venerable {ggplot2} @@ -372,13 +523,8 @@

Using {ggraph} for more flexibility

- -
-
library(ggraph)
-ggraph(mpn_elite_mex, layout = "fr") + 
+ggraph(ison_greys, layout = "fr") + 
   geom_edge_link(edge_colour = "dark grey", 
                   arrow = arrow(angle = 45,
                                 length = unit(2, "mm"),
@@ -389,23 +535,24 @@ 

Using {ggraph} for more flexibility

scale_edge_width(range = c(0.3,1.5)) + theme_graph() + theme(legend.position = "none")
+

As we can see in the code above, we can specify various aspects of -the plot to tailor it to our network. Firstly, we can alter the -layout of the network using the layout = -argument to create a clearer visualisation of the ties between nodes. -This is especially important for larger networks, where nodes and ties -are more easily obscured or misrepresented. In {ggraph}, -the default layout is the “stress” layout. The “stress” layout is a safe -choice because it is deterministic and fits well with almost any graph, -but it is also a good idea to explore and try out other layouts on your -data. More layouts can be found in the {graphlayouts} and -{igraph} R packages. To use a layout from the -{igraph} package, enter only the last part of the layout -algorithm name (eg. layout = "mds" for -“layout_with_mds”).

-

Secondly, using geom_node_point() which draws the nodes -as geometric shapes (circles, squares, or triangles), we can specify the +the plot to tailor it to our network.

+

First, we can alter the layout of the network using +the layout = argument to create a clearer visualisation of +the ties between nodes. This is especially important for larger +networks, where nodes and ties are more easily obscured or +misrepresented. In {ggraph}, the default layout is the +“stress” layout. The “stress” layout is a safe choice because it is +deterministic and fits well with almost any graph, but it is also a good +idea to explore and try out other layouts on your data. More layouts can +be found in the {graphlayouts} and {igraph} R +packages. To use a layout from the {igraph} package, enter +only the last part of the layout algorithm name (eg. +layout = "mds" for “layout_with_mds”).

+

Second, using geom_node_point() which draws the nodes as +geometric shapes (circles, squares, or triangles), we can specify the presentation of nodes in the network in terms of their shape (shape=, choose from 1 to 21), size (size=), or colour (colour=). We can @@ -414,12 +561,12 @@

Using {ggraph} for more flexibility

(draws labels within a box). The font (family=), font size (size=), and colour (colour=) of the labels can be specified.

-

Thirdly, we can also specify the presentation of -edges in the network. To draw edges, we use -geom_edge_link0() or geom_edge_link(). Using -the latter function makes it possible to draw a straight line with a -gradient. The following features can be tailored either globally or -matched to specific edge attributes using aes():

+

Third, we can also specify the presentation of edges +in the network. To draw edges, we use geom_edge_link0() or +geom_edge_link(). Using the latter function makes it +possible to draw a straight line with a gradient. The following features +can be tailored either globally or matched to specific edge attributes +using aes():

  • colour: edge_colour=

  • width: edge_width=

  • @@ -494,11 +641,10 @@

    Exporting plots to PDF

    "library(manynet)", "library(migraph)", "library(patchwork)", "knitr::opts_chunk$set(echo = FALSE)"), chunk_opts = list(label = "setup", include = FALSE)), setup = NULL, chunks = list(list(label = "manyneteg", - code = "", opts = list(label = "\"manyneteg\"", exercise = "TRUE", - purl = "FALSE"), engine = "r")), code_check = NULL, error_check = NULL, - check = NULL, solution = structure(c("library(manynet)", - "graphr(ison_brandes)"), chunk_opts = list(label = "manyneteg-solution")), - tests = NULL, options = list(eval = FALSE, echo = TRUE, results = "markup", + code = "graphr(ison_lotr)", opts = list(label = "\"manyneteg\"", + exercise = "TRUE"), engine = "r")), code_check = NULL, + error_check = NULL, check = NULL, solution = NULL, tests = NULL, + options = list(eval = FALSE, echo = TRUE, results = "markup", tidy = FALSE, tidy.opts = NULL, collapse = FALSE, prompt = FALSE, comment = NA, highlight = FALSE, size = "normalsize", background = "#F7F7F7", strip.white = TRUE, cache = 0, @@ -513,14 +659,136 @@

    Exporting plots to PDF

    fig.retina = 2, external = TRUE, sanitize = FALSE, interval = 1, aniopts = "controls,loop", warning = TRUE, error = FALSE, message = TRUE, render = NULL, ref.label = NULL, child = NULL, - engine = "r", split = FALSE, include = TRUE, purl = FALSE, + engine = "r", split = FALSE, include = TRUE, purl = TRUE, max.print = 1000, label = "manyneteg", exercise = TRUE, - code = "", out.width.px = 624, out.height.px = 384, params.src = "manyneteg, exercise=TRUE, purl = FALSE", - fig.num = 0L, exercise.df_print = "paged", exercise.checker = "NULL"), + code = "graphr(ison_lotr)", out.width.px = 624, out.height.px = 384, + params.src = "manyneteg, exercise=TRUE", fig.num = 0, + exercise.df_print = "paged", exercise.checker = "NULL"), + engine = "r", version = "4"), class = c("r", "tutorial_exercise" +))) + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + - - + - + + + + + + + + + + + + + + + + - + +

From 8363662205ff1deab48559e8db2ee3f0dff628c9 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 19 Jul 2024 22:51:45 +0200 Subject: [PATCH 12/26] Added more details on layouts to viz tutorial --- inst/tutorials/tutorial2/visualisation.Rmd | 65 ++++++++++++++++++++-- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/inst/tutorials/tutorial2/visualisation.Rmd b/inst/tutorials/tutorial2/visualisation.Rmd index 653d74d5..12318e83 100644 --- a/inst/tutorials/tutorial2/visualisation.Rmd +++ b/inst/tutorials/tutorial2/visualisation.Rmd @@ -212,21 +212,74 @@ This works best for quite clustered distributions of attributes. graphr(ison_lotr, node_group = "Race") ``` -### Layouts +## Layouts + +The aim of graph layouts is to position nodes in a (usually) two-dimensional space +to maximise some analytic and aesthetically pleasing function. +Quality measures might include: + +- minimising the _crossing number_ of edges/ties in the graph +([planar graphs](https://www.jasondavies.com/planarity/) require no crossings) +- minimising the _slope number_ of distinct edge slopes in the graph +(where vertices are represented as points on a Euclidean plane) +- minimising the _bend number_ in all edges in the graph +(every graph has a right angle crossing (RAC) drawing with three bends per edge) +- minimising the _total edge length_ +- minimising the _maximum edge length_ +- minimising the _edge length variance_ +- maximising the _angular resolution_ or sharpest angle of edges meeting at a common vertex +- minimising the _bounding box_ of the plot +- evening the _aspect ratio_ of the plot +- displaying _symmetry groups_ (subgraph automorphisms) A range of graph layouts are available across the `{igraph}`, `{graphlayouts}`, and `{manynet}` packages that can be used together with `graphr()`. -```{r layoutseg, exercise=TRUE, purl = FALSE} +### Force-directed layouts +Force-directed layouts updates some initial placement of vertices +through the operation of some system of metaphorically-physical forces. +These might include attractive and repulsive forces. + +```{r forcedir, exercise=TRUE} +(graphr(ison_southern_women, layout = "fr") + ggtitle("Fruchterman-Reingold") | +graphr(ison_southern_women, layout = "kk") + ggtitle("Kamada-Kawai")) +``` + +### Layered layouts + +Layered layouts arrange nodes into horizontal (or vertical) layers, +positioning them so that they reduce crossings. +These layouts are best suited for directed acyclic graphs or similar. + +```{r layoutseg, exercise=TRUE} +(graphr(ison_southern_women, layout = "bipartite") + ggtitle("Bipartite") | +graphr(ison_southern_women, layout = "hierarchy") + ggtitle("Hierarchy")) / +(graphr(ison_southern_women, layout = "railway") + ggtitle("Railway") | +graphr(ison_southern_women, layout = "alluvial") + ggtitle("Alluvial")) ``` -```{r layoutseg-solution} -(graphr(ison_southern_women, layout = "bipartite") + ggtitle("bipartite") | -graphr(ison_southern_women, layout = "kk") + ggtitle("kk")) +### Circular layouts + +Circular layouts arrange nodes around (potentially concentric) circles, +such that crossings are minimised and adjacent nodes are located close together. +In some cases, location or layer can be specified by attribute or mode. + +```{r circular, exercise=TRUE} +graphr(ison_southern_women, layout = "concentric") + ggtitle("Concentric") ``` -### Using different colours +### Other layouts + +Other layouts include: + +- spectral layouts +- grid layouts +- orthogonal layouts +- tree layouts +- arc layouts +- dominance layouts + Because the `graphr()` function is based on the grammar of graphics, From 8853ca096f015922df00503d1129a6a03dae47be Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 19 Jul 2024 22:52:00 +0200 Subject: [PATCH 13/26] Added more details on colors and theming --- inst/tutorials/tutorial2/visualisation.Rmd | 73 +++++++++++++++++----- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/inst/tutorials/tutorial2/visualisation.Rmd b/inst/tutorials/tutorial2/visualisation.Rmd index 12318e83..c55c708c 100644 --- a/inst/tutorials/tutorial2/visualisation.Rmd +++ b/inst/tutorials/tutorial2/visualisation.Rmd @@ -280,39 +280,80 @@ Other layouts include: - arc layouts - dominance layouts +## Colors +### Who's hue? + +By default, `graphr()` will use a color palette that offers fairly good contrast +and, since v1.0.0 of `{manynet}`, better accessibility. +However, a different hue might offer a better aesthetic or +identifiability for some nodes. Because the `graphr()` function is based on the grammar of graphics, it's easy to extend or alter aesthetic aspects. Here let's try and change the colors -assigned to the different regions in the `mpn_elite_mex` dataset. +assigned to the different races in the `ison_lotr` dataset. -```{r colorch, exercise=TRUE, purl = FALSE} +```{r colorch, exercise=TRUE} +graphr(ison_lotr, + node_color = "Race") +graphr(ison_lotr, + node_color = "Race") + + ggplot2::scale_colour_hue() ``` -```{r colorch-solution} -graphr(mpn_elite_mex, - node_color = "region") +### Grayscale -graphr(mpn_elite_mex, - node_color = "region") + - ggplot2::scale_colour_hue() +Other times color may not be desired. +Some publications require grayscale images. +To use a grayscale color palette, +replace `_hue` from above with `_grey`: -graphr(mpn_elite_mex, - node_color = "region") + +```{r greyscale, exercise=TRUE} +graphr(ison_lotr, + node_color = "Race") + ggplot2::scale_colour_grey() +``` + +### Manual override + +Or we may want to choose particular colors for each category. +This is pretty straightforward to do with `ggplot2::scale_colour_manual()`. +Some common color names are available, +but otherwise hex color codes can be used for more specific colors. +Unspecified categories are coloured (dark) grey. -graphr(mpn_elite_mex, - node_color = "region") + +```{r mancolor, exercise=TRUE} +graphr(ison_lotr, + node_color = "Race") + ggplot2::scale_colour_manual( - values = c("1" = "red", - "3" = "blue", - "2" = "green")) + + values = c("Dwarf" = "red", + "Hobbit" = "orange", + "Maiar" = "#DEC20B", + "Human" = "lightblue", + "Elf" = "lightgreen", + "Ent" = "darkgreen")) + labs(color = "Color") ``` -## Using `{ggraph}` for more flexibility +### Theming + +Perhaps you are preparing a presentation, +representing your institution, department, or research centre at home or abroad. +In this case, you may wish to theme the whole network with institutional colors +and fonts. +Here we demonstrate one of the color scales available in `{manynet}`, +the colors of the Sustainable Development Goals: + +```{r theming, exercise=TRUE} +graphr(ison_lotr, node_color = "Race") + + scale_color_sdgs() +``` + +More institutional scales and themes are available, +and more can be implemented upon pull request. + ## Further flexibility For more flexibility with visualizations, From 20c16bdc8dc9c9dfd4f9e830ef14a3d112992b7c Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 19 Jul 2024 22:52:30 +0200 Subject: [PATCH 14/26] Moved mapping up in function overview --- pkgdown/_pkgdown.yml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index f09458c8..ee3cad1a 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -92,6 +92,19 @@ reference: contents: - starts_with("na_to_") + - title: "Mapping" + desc: | + Functions for plotting and visualising graphs of different types. + `graphr()` graphs any manynet-compatible class object automagically. + `graphs()` and `grapht()` do the same for multiple networks + and dynamic networks, respectively. + contents: + - starts_with("graph") + - starts_with("layout") + - starts_with("theme") + - starts_with("scale") + - ends_with("palettes") + - title: "Marking" desc: | Functions for identifying properties of networks, nodes, or ties, @@ -126,18 +139,6 @@ reference: contents: - contains("_by_") - contains("_in_") - - title: "Mapping" - desc: | - Functions for plotting and visualising graphs of different types. - `graphr()` graphs any manynet-compatible class object automagically. - `graphs()` and `grapht()` do the same for multiple networks - and dynamic networks, respectively. - contents: - - starts_with("graph") - - starts_with("layout") - - starts_with("theme") - - starts_with("scale") - - ends_with("palettes") - title: "Methods" desc: "Methods used in other functions but documented here:" contents: From 2e29204d43136e8b6396d7d0c1b2a1b124a7d0b7 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 19 Jul 2024 22:53:03 +0200 Subject: [PATCH 15/26] #minor bump --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 8cbb4710..40753cd6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: manynet Title: Many Ways to Make, Modify, Map, Mark, and Measure Myriad Networks -Version: 1.0.1 -Date: 2024-07-08 +Version: 1.0.2 +Date: 2024-07-19 Description: Many tools for making, modifying, mapping, marking, measuring, and motifs and memberships of many different types of networks. All functions operate with matrices, edge lists, and 'igraph', 'network', and 'tidygraph' objects, From e9372ab77468d51087e0c891863f06a6feaaf6b0 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 11:38:53 +0200 Subject: [PATCH 16/26] Added more on visualisation --- inst/tutorials/tutorial2/visualisation.Rmd | 41 ++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/inst/tutorials/tutorial2/visualisation.Rmd b/inst/tutorials/tutorial2/visualisation.Rmd index c55c708c..10731fcc 100644 --- a/inst/tutorials/tutorial2/visualisation.Rmd +++ b/inst/tutorials/tutorial2/visualisation.Rmd @@ -241,11 +241,46 @@ Force-directed layouts updates some initial placement of vertices through the operation of some system of metaphorically-physical forces. These might include attractive and repulsive forces. -```{r forcedir, exercise=TRUE} -(graphr(ison_southern_women, layout = "fr") + ggtitle("Fruchterman-Reingold") | -graphr(ison_southern_women, layout = "kk") + ggtitle("Kamada-Kawai")) +```{r forcedir, exercise=TRUE, fig.width=9} +(graphr(ison_southern_women, layout = "kk") + ggtitle("Kamada-Kawai") | + graphr(ison_southern_women, layout = "fr") + ggtitle("Fruchterman-Reingold") | + graphr(ison_southern_women, layout = "stress") + ggtitle("Stress Minimisation")) ``` +The _Kamada-Kawai_ method inserts a spring between all pairs of vertices +that is the length of the graph distance between them. +This means that edges with a large weight will be longer. +KK offers a good layout for lattice-like networks, +because it will try to space the network out evenly. + +The _Fruchterman-Reingold_ method uses an attractive force between directly connected vertices, +and a repulsive force between all vertex pairs. +The attractive force is proportional to the edge's weight, +thus edges with a large weight will be shorter. +FR offers a good baseline for most types of networks. + +The _Stress Minimisation_ method is related to the KK algorithm, +but offers better runtime, quality, and stability and so is generally preferred. +Indeed, `{manynet}` uses it as the default for most networks. +It has the advantage of returning the same layout each time it is run on the same network. + +```{r layoutinterp-Q, echo=FALSE, purl = FALSE} +question("Can we interpret the distance between nodes in force-directed layouts conclusively?", + answer("No", + correct = TRUE, + message = "That's right, they are illustrative and not to be used for hard conclusions."), + answer("Yes"), + allow_retry = FALSE +) +``` + +Other force-directed layouts available include: + +- Simulated annealing (Davidson and Harel 1993): `"dh"` +- Graph embedder (Frick et al. 1995): `"gem"` +- Graphopt (Schmuhl): `"graphopt"` +- Distributed recursive graph layout (Martin et al. 2008): `"drl"` + ### Layered layouts Layered layouts arrange nodes into horizontal (or vertical) layers, From 23de3684f41de70fd01d60d1cd2abbb503860dd0 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 11:39:07 +0200 Subject: [PATCH 17/26] Added more on layered layouts --- inst/tutorials/tutorial2/visualisation.Rmd | 27 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/inst/tutorials/tutorial2/visualisation.Rmd b/inst/tutorials/tutorial2/visualisation.Rmd index 10731fcc..9bc21a22 100644 --- a/inst/tutorials/tutorial2/visualisation.Rmd +++ b/inst/tutorials/tutorial2/visualisation.Rmd @@ -287,13 +287,30 @@ Layered layouts arrange nodes into horizontal (or vertical) layers, positioning them so that they reduce crossings. These layouts are best suited for directed acyclic graphs or similar. -```{r layoutseg, exercise=TRUE} -(graphr(ison_southern_women, layout = "bipartite") + ggtitle("Bipartite") | -graphr(ison_southern_women, layout = "hierarchy") + ggtitle("Hierarchy")) / -(graphr(ison_southern_women, layout = "railway") + ggtitle("Railway") | -graphr(ison_southern_women, layout = "alluvial") + ggtitle("Alluvial")) +```{r bipartite, exercise=TRUE, fig.width=9} +(graphr(ison_southern_women, layout = "bipartite") + ggtitle("Bipartite")) / +(graphr(ison_southern_women, layout = "hierarchy") + ggtitle("Hierarchy")) / +(graphr(ison_southern_women, layout = "railway") + ggtitle("Railway")) ``` +Note that `"hierarchy"` and `"railway"` use a different algorithm to +`{igraph}`'s `"bipartite"`, and generally performs better, +especially where there are multiple layers. +Whereas `"hierarchy"` tries to position nodes to minimise overlaps, +`"railway"` sequences the nodes in each layer to a grid +so that nodes are matched as far as possible. +If you want to flip the horizontal and vertical, +you could flip the coordinates, or use something like the following layout. + +```{r alluvial, exercise=TRUE, fig.align='center'} +graphr(ison_southern_women, layout = "alluvial") + ggtitle("Alluvial") +``` + +Other layered layouts include: + +- Tree: `"tree"` +- Dominance layouts + ### Circular layouts Circular layouts arrange nodes around (potentially concentric) circles, From c2df93657a3e7e83916addd4059e366dd1fea23a Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 11:39:33 +0200 Subject: [PATCH 18/26] Added spectral layouts --- inst/tutorials/tutorial2/visualisation.Rmd | 39 +++++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/inst/tutorials/tutorial2/visualisation.Rmd b/inst/tutorials/tutorial2/visualisation.Rmd index 9bc21a22..8f4c2e1a 100644 --- a/inst/tutorials/tutorial2/visualisation.Rmd +++ b/inst/tutorials/tutorial2/visualisation.Rmd @@ -317,20 +317,41 @@ Circular layouts arrange nodes around (potentially concentric) circles, such that crossings are minimised and adjacent nodes are located close together. In some cases, location or layer can be specified by attribute or mode. -```{r circular, exercise=TRUE} +```{r circular, exercise=TRUE, fig.align='center'} graphr(ison_southern_women, layout = "concentric") + ggtitle("Concentric") ``` -### Other layouts +Other such layouts include: -Other layouts include: +- circular: `"circle"` +- sphere: `"sphere"` +- star: `"star"` +- arc or linear layouts: `"linear"` + +### Spectral layouts + +Spectral layouts arrange nodes according to the eigenvalues +of the Laplacian matrix of a graph. +These layouts tend to exaggerate clustering of like-nodes and the +separation of less similar nodes in two-dimensional space. + +```{r eigen, exercise=TRUE, fig.align='center'} +graphr(ison_southern_women, layout = "eigen") + ggtitle("Eigenvector") +``` + +Somewhat similar are multidimensional scaling techniques, +that visualise the similarity between nodes in terms of their proximity +in a two-dimensional (or more) space. + +```{r mds, exercise=TRUE, fig.align='center'} +graphr(ison_southern_women, layout = "mds") + ggtitle("Multidimensional Scaling") +graphr(ison_southern_women, layout = "mds") + ggtitle("Multidimensional Scaling") +``` + +Other such layouts include: + +- Pivot multidimensional scaling: `"pmds"` -- spectral layouts -- grid layouts -- orthogonal layouts -- tree layouts -- arc layouts -- dominance layouts ## Colors From a6c0f3b6f0a1df4f1558f1d893496deb473304e9 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 11:39:43 +0200 Subject: [PATCH 19/26] Added grid layouts --- inst/tutorials/tutorial2/visualisation.Rmd | 15 + inst/tutorials/tutorial2/visualisation.html | 425 ++++++++++++++++---- 2 files changed, 363 insertions(+), 77 deletions(-) diff --git a/inst/tutorials/tutorial2/visualisation.Rmd b/inst/tutorials/tutorial2/visualisation.Rmd index 8f4c2e1a..d0eeb2b4 100644 --- a/inst/tutorials/tutorial2/visualisation.Rmd +++ b/inst/tutorials/tutorial2/visualisation.Rmd @@ -352,6 +352,21 @@ Other such layouts include: - Pivot multidimensional scaling: `"pmds"` +### Grid layouts + +Grid layouts arrange nodes based on some cartesion coordinates. +These can be useful for making sure all nodes' labels are visible, +but horizontal and vertical lines can overlap, +making it difficult to distinguish whether some nodes are tied or not. + +```{r grid, exercise=TRUE, fig.align='center'} +graphr(ison_southern_women, layout = "grid") + ggtitle("Grid") +``` + +Other grid layouts include: + +- orthogonal layouts for e.g. printed circuit boards +- grid snapping for other layouts ## Colors diff --git a/inst/tutorials/tutorial2/visualisation.html b/inst/tutorials/tutorial2/visualisation.html index 15ac7393..09b9e1f6 100644 --- a/inst/tutorials/tutorial2/visualisation.html +++ b/inst/tutorials/tutorial2/visualisation.html @@ -385,25 +385,76 @@

Force-directed layouts

-
(graphr(ison_southern_women, layout = "fr") + ggtitle("Fruchterman-Reingold") |
-graphr(ison_southern_women, layout = "kk") + ggtitle("Kamada-Kawai"))
+
(graphr(ison_southern_women, layout = "kk") + ggtitle("Kamada-Kawai") |
+   graphr(ison_southern_women, layout = "fr") + ggtitle("Fruchterman-Reingold") |
+   graphr(ison_southern_women, layout = "stress") + ggtitle("Stress Minimisation"))
+

The Kamada-Kawai method inserts a spring between all pairs +of vertices that is the length of the graph distance between them. This +means that edges with a large weight will be longer. KK offers a good +layout for lattice-like networks, because it will try to space the +network out evenly.

+

The Fruchterman-Reingold method uses an attractive force +between directly connected vertices, and a repulsive force between all +vertex pairs. The attractive force is proportional to the edge’s weight, +thus edges with a large weight will be shorter. FR offers a good +baseline for most types of networks.

+

The Stress Minimisation method is related to the KK +algorithm, but offers better runtime, quality, and stability and so is +generally preferred. Indeed, {manynet} uses it as the +default for most networks. It has the advantage of returning the same +layout each time it is run on the same network.

+
+
+
+
+
+ +
+
+

Other force-directed layouts available include:

+
    +
  • Simulated annealing (Davidson and Harel 1993): +"dh"
  • +
  • Graph embedder (Frick et al. 1995): "gem"
  • +
  • Graphopt (Schmuhl): "graphopt"
  • +
  • Distributed recursive graph layout (Martin et al. 2008): +"drl"
  • +

Layered layouts

Layered layouts arrange nodes into horizontal (or vertical) layers, positioning them so that they reduce crossings. These layouts are best suited for directed acyclic graphs or similar.

-
-
(graphr(ison_southern_women, layout = "bipartite") + ggtitle("Bipartite") |
-graphr(ison_southern_women, layout = "hierarchy") + ggtitle("Hierarchy")) /
-(graphr(ison_southern_women, layout = "railway") + ggtitle("Railway") |
-graphr(ison_southern_women, layout = "alluvial") + ggtitle("Alluvial"))
+
(graphr(ison_southern_women, layout = "bipartite") + ggtitle("Bipartite")) /
+(graphr(ison_southern_women, layout = "hierarchy") + ggtitle("Hierarchy")) /
+(graphr(ison_southern_women, layout = "railway") + ggtitle("Railway"))
+ +
+

Note that "hierarchy" and "railway" use a +different algorithm to {igraph}’s "bipartite", +and generally performs better, especially where there are multiple +layers. Whereas "hierarchy" tries to position nodes to +minimise overlaps, "railway" sequences the nodes in each +layer to a grid so that nodes are matched as far as possible. If you +want to flip the horizontal and vertical, you could flip the +coordinates, or use something like the following layout.

+
+
graphr(ison_southern_women, layout = "alluvial") + ggtitle("Alluvial")
+

Other layered layouts include:

+
    +
  • Tree: "tree"
  • +
  • Dominance layouts
  • +

Circular layouts

@@ -417,17 +468,57 @@

Circular layouts

graphr(ison_southern_women, layout = "concentric") + ggtitle("Concentric")
+

Other such layouts include:

+
    +
  • circular: "circle"
  • +
  • sphere: "sphere"
  • +
  • star: "star"
  • +
  • arc or linear layouts: "linear"
  • +
+
+
+

Spectral layouts

+

Spectral layouts arrange nodes according to the eigenvalues of the +Laplacian matrix of a graph. These layouts tend to exaggerate clustering +of like-nodes and the separation of less similar nodes in +two-dimensional space.

+
+
graphr(ison_southern_women, layout = "eigen") + ggtitle("Eigenvector")
+ +
+

Somewhat similar are multidimensional scaling techniques, that +visualise the similarity between nodes in terms of their proximity in a +two-dimensional (or more) space.

+
+
graphr(ison_southern_women, layout = "mds") + ggtitle("Multidimensional Scaling")
+graphr(ison_southern_women, layout = "mds") + ggtitle("Multidimensional Scaling")
+ +
+

Other such layouts include:

+
    +
  • Pivot multidimensional scaling: "pmds"
  • +
-
-

Other layouts

-

Other layouts include:

+
+

Grid layouts

+

Grid layouts arrange nodes based on some cartesion coordinates. These +can be useful for making sure all nodes’ labels are visible, but +horizontal and vertical lines can overlap, making it difficult to +distinguish whether some nodes are tied or not.

+
+
graphr(ison_southern_women, layout = "grid") + ggtitle("Grid")
+ +
+

Other grid layouts include:

    -
  • spectral layouts
  • -
  • grid layouts
  • -
  • orthogonal layouts
  • -
  • tree layouts
  • -
  • arc layouts
  • -
  • dominance layouts
  • +
  • orthogonal layouts for e.g. printed circuit boards
  • +
  • grid snapping for other layouts
@@ -1004,49 +1095,72 @@

Exporting plots to PDF

"library(manynet)", "library(migraph)", "library(patchwork)", "knitr::opts_chunk$set(echo = FALSE)"), chunk_opts = list(label = "setup", include = FALSE)), setup = NULL, chunks = list(list(label = "forcedir", - code = "(graphr(ison_southern_women, layout = \"fr\") + ggtitle(\"Fruchterman-Reingold\") |\ngraphr(ison_southern_women, layout = \"kk\") + ggtitle(\"Kamada-Kawai\"))", - opts = list(label = "\"forcedir\"", exercise = "TRUE"), engine = "r")), - code_check = NULL, error_check = NULL, check = NULL, solution = NULL, - tests = NULL, options = list(eval = FALSE, echo = TRUE, results = "markup", - tidy = FALSE, tidy.opts = NULL, collapse = FALSE, prompt = FALSE, - comment = NA, highlight = FALSE, size = "normalsize", - background = "#F7F7F7", strip.white = TRUE, cache = 0, - cache.path = "visualisation_cache/html/", cache.vars = NULL, - cache.lazy = TRUE, dependson = NULL, autodep = FALSE, - cache.rebuild = FALSE, fig.keep = "high", fig.show = "asis", - fig.align = "default", fig.path = "visualisation_files/figure-html/", + code = "(graphr(ison_southern_women, layout = \"kk\") + ggtitle(\"Kamada-Kawai\") |\n graphr(ison_southern_women, layout = \"fr\") + ggtitle(\"Fruchterman-Reingold\") |\n graphr(ison_southern_women, layout = \"stress\") + ggtitle(\"Stress Minimisation\"))", + opts = list(label = "\"forcedir\"", exercise = "TRUE", fig.width = "9"), + engine = "r")), code_check = NULL, error_check = NULL, check = NULL, + solution = NULL, tests = NULL, options = list(eval = FALSE, + echo = TRUE, results = "markup", tidy = FALSE, tidy.opts = NULL, + collapse = FALSE, prompt = FALSE, comment = NA, highlight = FALSE, + size = "normalsize", background = "#F7F7F7", strip.white = TRUE, + cache = 0, cache.path = "visualisation_cache/html/", + cache.vars = NULL, cache.lazy = TRUE, dependson = NULL, + autodep = FALSE, cache.rebuild = FALSE, fig.keep = "high", + fig.show = "asis", fig.align = "default", fig.path = "visualisation_files/figure-html/", dev = "png", dev.args = NULL, dpi = 192, fig.ext = "png", - fig.width = 6.5, fig.height = 4, fig.env = "figure", - fig.cap = NULL, fig.scap = NULL, fig.lp = "fig:", fig.subcap = NULL, - fig.pos = "", out.width = 624, out.height = NULL, out.extra = NULL, + fig.width = 9, fig.height = 4, fig.env = "figure", fig.cap = NULL, + fig.scap = NULL, fig.lp = "fig:", fig.subcap = NULL, + fig.pos = "", out.width = 864, out.height = NULL, out.extra = NULL, fig.retina = 2, external = TRUE, sanitize = FALSE, interval = 1, aniopts = "controls,loop", warning = TRUE, error = FALSE, message = TRUE, render = NULL, ref.label = NULL, child = NULL, engine = "r", split = FALSE, include = TRUE, purl = TRUE, max.print = 1000, label = "forcedir", exercise = TRUE, - code = c("(graphr(ison_southern_women, layout = \"fr\") + ggtitle(\"Fruchterman-Reingold\") |", - "graphr(ison_southern_women, layout = \"kk\") + ggtitle(\"Kamada-Kawai\"))" - ), out.width.px = 624, out.height.px = 384, params.src = "forcedir, exercise=TRUE", + code = c("(graphr(ison_southern_women, layout = \"kk\") + ggtitle(\"Kamada-Kawai\") |", + " graphr(ison_southern_women, layout = \"fr\") + ggtitle(\"Fruchterman-Reingold\") |", + " graphr(ison_southern_women, layout = \"stress\") + ggtitle(\"Stress Minimisation\"))" + ), out.width.px = 864, out.height.px = 384, params.src = "forcedir, exercise=TRUE, fig.width=9", fig.num = 0, exercise.df_print = "paged", exercise.checker = "NULL"), engine = "r", version = "4"), class = c("r", "tutorial_exercise" ))) + + + + + + + - + + - - + + + + + + + + + + + + + + + + + - - + - + + - - + - + + - - + - + + - - + - + + - - +
From 4570b940bf56021d9290924249f0fe23fccb8df7 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 13:21:35 +0200 Subject: [PATCH 20/26] Added more centrality interpretation --- inst/tutorials/tutorial3/centrality.Rmd | 60 ++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/inst/tutorials/tutorial3/centrality.Rmd b/inst/tutorials/tutorial3/centrality.Rmd index 08fa5640..1db7ff4d 100644 --- a/inst/tutorials/tutorial3/centrality.Rmd +++ b/inst/tutorials/tutorial3/centrality.Rmd @@ -115,6 +115,19 @@ question("Are the row sums the same as the column sums?", ) ``` +```{r degreeinterpQ, purl = FALSE} +question("In what ways are higher degree nodes more 'central'?", + answer("They have more power than other nodes", + message = "Not necessarily"), + answer("They are more active than other nodes", + correct = TRUE, message = learnr::random_praise()), + answer("They would be located in the centre of the graph", + message = "Not necessarily"), + answer("They would be able to control information flow in the network", + message = "Not necessarily"), + allow_retry = TRUE, + random_answer_order = TRUE) +``` Often we are interested in the distribution of (degree) centrality in a network. `{migraph}` offers a way to get a pretty good first look at this distribution, though there are more elaborate ways to do this in base and grid graphics. @@ -129,8 +142,18 @@ plot(node_degree(ison_brandes)) ``` What's plotted here by default is both the degree distribution as a histogram, -as well as a density plot overlaid on it. -What kind of shape does this have? +as well as a density plot overlaid on it in red. + +```{r degdistshapeQ, purl = FALSE} +question("What kind of shape does this degree distribution have?", + answer("Exponential"), + answer("Hard to tell", + correct = TRUE, message = learnr::random_praise()), + answer("Uniform"), + answer("Normal"), + allow_retry = TRUE, + random_answer_order = TRUE) +``` ### Other centralities @@ -165,17 +188,42 @@ node_eigenvector(ison_brandes) node_betweenness(ison_brandes) node_closeness(ison_brandes) node_eigenvector(ison_brandes) -# TASK: Can you create degree distributions for each of these? ``` What is returned here are vectors of betweenness, closeness, and eigenvector scores for the nodes in the network. But what do they mean? + +```{r closeinterpQ, purl = FALSE} +question("Why does a node with the smallest sum of geodesic distances to all other nodes be 'central'?", + answer("They have more power than other nodes", + message = "Not necessarily"), + answer("They can distribute a message to all other nodes in the network most quickly", + correct = TRUE, message = learnr::random_praise()), + answer("They would be located in the centre of the graph", + message = "Not necessarily"), + answer("They would be able to control information flow in the network", + message = "Not necessarily"), + allow_retry = TRUE, + random_answer_order = TRUE) +``` + +```{r betinterpQ, purl = FALSE} +question("Why would a node lying 'between' many other nodes be 'central'?", + answer("They have more power than other nodes", + message = "Not necessarily"), + answer("They would be able to control information flow in the network", + correct = TRUE, message = learnr::random_praise()), + answer("They would be located in the centre of the graph", + message = "Not necessarily"), + answer("They can distribute a message to all other nodes in the network most quickly", + message = "Not necessarily"), + allow_retry = TRUE, + random_answer_order = TRUE) +``` + Try to answer the following questions for yourself: -- in what ways is a higher degree actor more 'central'? -- can you explain why a node that has the smallest sum of geodesic distances to all other nodes is said to be 'central'? -- why would an actor lying 'between' two other actors be 'central'? - what does Bonacich mean when he says that power and influence are not the same thing? - can you think of a real-world example when an actor might be central but not powerful, or powerful but not central? From ee2800cc7b43f199b89c7e35bed8b3b82e422f50 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 13:22:02 +0200 Subject: [PATCH 21/26] Updated structure and functions --- inst/tutorials/tutorial3/centrality.Rmd | 90 ++-- inst/tutorials/tutorial3/centrality.html | 508 +++++++++++++++++------ 2 files changed, 425 insertions(+), 173 deletions(-) diff --git a/inst/tutorials/tutorial3/centrality.Rmd b/inst/tutorials/tutorial3/centrality.Rmd index 1db7ff4d..58366a98 100644 --- a/inst/tutorials/tutorial3/centrality.Rmd +++ b/inst/tutorials/tutorial3/centrality.Rmd @@ -7,19 +7,24 @@ output: runtime: shiny_prerendered description: > This tutorial aims to show how to measure and map degree, betweenness, - closeness, eigenvector and other types of centrality and centralization. + closeness, eigenvector, and other types of centrality and centralization. --- ```{r setup, include=FALSE} library(learnr) library(manynet) -library(migraph) library(patchwork) knitr::opts_chunk$set(echo = FALSE) ison_brandes2 <- ison_brandes %>% rename(type = twomode_type) ``` -## Calculating centrality +## Today's target + +The aim of this tutorial is to show how we can measure and map +degree, betweenness, closeness, eigenvector, and other types of centrality, +explore their distributions and calculate the corresponding centralisation. + +### Setting up For this exercise, we'll use the `ison_brandes` dataset in `{manynet}`. This dataset is in a 'tidygraph' format, @@ -27,7 +32,7 @@ but `manynet` makes it easy to coerce this into other forms to be compatible with other packages. We can create a two-mode version of the dataset by renaming the nodal attribute "twomode_type" to just "type". -Let's begin by graphing these datasets using `manynet::graphr()`. +Let's begin by graphing these datasets using `graphr()`. ```{r coercion, exercise = TRUE, purl = FALSE} @@ -44,17 +49,17 @@ ison_brandes2 <- ison_brandes %>% rename(type = twomode_type) graphr(____) ``` -```{r coercion-solution, purl = FALSE} +```{r coercion-solution} # plot the one-mode version graphr(ison_brandes) ison_brandes2 <- ison_brandes %>% rename(type = twomode_type) # plot the two-mode version -graphr(ison_brandes2) +graphr(ison_brandes2, layout = "bipartite") ``` The network is anonymous, but I think it would be nice to add some names, even if it's just pretend. -Luckily, `{manynet}` has a function for this. +Luckily, `{manynet}` has a function for this: `to_named()`. This makes plotting the network just a wee bit more accessible and interpretable: ```{r addingnames, exercise = TRUE, purl = FALSE} @@ -72,12 +77,14 @@ ison_brandes <- to_named(ison_brandes) graphr(ison_brandes) ``` -Note that you will likely get a different set of names, +Note that you will likely get a different set of names (though still alphabetical), as they are assigned randomly from a pool of (American) first names. -### Degree centrality +## Degree centrality -Let's start with calculating degree, as it is easy to calculate yourself. +Let's start with calculating degree. +Remember that degree centrality is just the number of incident edges/ties to each node. +It is therefore easy to calculate yourself. Just sum the rows or columns of the matrix! ```{r degreesum, exercise = TRUE, exercise.setup = "addingnames", purl = FALSE} @@ -92,7 +99,7 @@ rowSums(mat) == colSums(mat) ``` ```{r degreesum-hint-2, purl = FALSE} -# Or by using a built in command in migraph like this: +# Or by using a built in command in manynet like this: node_degree(ison_brandes, normalized = FALSE) ``` @@ -101,7 +108,7 @@ node_degree(ison_brandes, normalized = FALSE) mat <- as_matrix(ison_brandes) degrees <- rowSums(mat) rowSums(mat) == colSums(mat) -# You can also just use a built in command in migraph though: +# You can also just use a built in command in manynet though: node_degree(ison_brandes, normalized = FALSE) ``` @@ -128,8 +135,11 @@ question("In what ways are higher degree nodes more 'central'?", allow_retry = TRUE, random_answer_order = TRUE) ``` + +## Degree distributions + Often we are interested in the distribution of (degree) centrality in a network. -`{migraph}` offers a way to get a pretty good first look at this distribution, +`{manynet}` offers a way to get a pretty good first look at this distribution, though there are more elaborate ways to do this in base and grid graphics. ```{r distrib, exercise = TRUE, exercise.setup = "addingnames", purl = FALSE} @@ -155,10 +165,10 @@ question("What kind of shape does this degree distribution have?", random_answer_order = TRUE) ``` -### Other centralities +## Other centralities Other measures of centrality can be a little trickier to calculate by hand. -Fortunately, we can use functions from `{migraph}` to help calculate the +Fortunately, we can use functions from `{manynet}` to help calculate the betweenness, closeness, and eigenvector centralities for each node in the network. Let's collect the vectors of these centralities for the `ison_brandes` dataset: @@ -227,13 +237,25 @@ Try to answer the following questions for yourself: - what does Bonacich mean when he says that power and influence are not the same thing? - can you think of a real-world example when an actor might be central but not powerful, or powerful but not central? -Note that all centrality measures in `{migraph}` return normalized +Note that all centrality measures in `{manynet}` return normalized scores by default -- for the raw scores, include `normalized = FALSE` in the function as an extra argument. +Now, can you create degree distributions for each of these? + +```{r otherdist, exercise=TRUE, purl=FALSE} + +``` + +```{r otherdist-solution} +plot(node_betweenness(ison_brandes)) +plot(node_closeness(ison_brandes)) +plot(node_eigenvector(ison_brandes)) +``` + ## Plotting centrality -It is straightforward in `{migraph}` to highlight nodes and ties +It is straightforward in `{manynet}` to highlight nodes and ties with maximum or minimum (e.g. degree) scores. If the vector is numeric (i.e. a "measure"), then this can be easily converted into a logical vector that @@ -276,19 +298,19 @@ What can you see? ```{r ggid_twomode-solution} ison_brandes2 %>% add_node_attribute("color", node_is_max(node_degree(ison_brandes2))) %>% - graphr(node_color = "color") + graphr(node_color = "color", layout = "bipartite") ison_brandes2 %>% add_node_attribute("color", node_is_max(node_betweenness(ison_brandes2))) %>% - graphr(node_color = "color") + graphr(node_color = "color", layout = "bipartite") ison_brandes2 %>% add_node_attribute("color", node_is_max(node_closeness(ison_brandes2))) %>% - graphr(node_color = "color") + graphr(node_color = "color", layout = "bipartite") ison_brandes2 %>% add_node_attribute("color", node_is_max(node_eigenvector(ison_brandes2))) %>% - graphr(node_color = "color") + graphr(node_color = "color", layout = "bipartite") ``` ```{r brandes2quiz, purl = FALSE} @@ -301,9 +323,11 @@ question("Select all that are true for the two-mode Brandes network.", random_answer_order = TRUE) ``` -## Calculating centralization +## Centralization + + -`{migraph}` also implements network centralization functions. +`{manynet}` also implements network centralization functions. Here we are no longer interested in the level of the node, but in the level of the whole network, so the syntax replaces `node_` with `net_`: @@ -316,13 +340,13 @@ so the syntax replaces `node_` with `net_`: net_degree(ison_brandes) net_betweenness(ison_brandes) net_closeness(ison_brandes) -net_eigenvector(ison_brandes) +print(net_eigenvector(ison_brandes), digits = 5) ``` -By default, scores are printed to 3 decimal places, +By default, scores are printed up to 3 decimal places, but this can be modified and, in any case, the unrounded values are retained internally. -This means that even if rounded values are printed, +This means that even if rounded values are printed (to respect console space), as much precision as is available is used in further calculations. Note that for centralization in two-mode networks, @@ -334,20 +358,16 @@ What if we want to have a single image/figure with multiple plots? This can be a little tricky with gg-based plots, but fortunately the `{patchwork}` package is here to help. -```{r multiplot, exercise = TRUE, exercise.setup = "addingnames", purl = FALSE} +```{r multiplot, exercise = TRUE, exercise.setup = "addingnames", purl = FALSE, fig.width=9} ``` ```{r multiplot-solution} ison_brandes <- ison_brandes %>% - add_node_attribute("degree", - node_is_max(node_degree(ison_brandes))) %>% - add_node_attribute("betweenness", - node_is_max(node_betweenness(ison_brandes))) %>% - add_node_attribute("closeness", - node_is_max(node_closeness(ison_brandes))) %>% - add_node_attribute("eigenvector", - node_is_max(node_eigenvector(ison_brandes))) + add_node_attribute("degree", node_is_max(node_degree(ison_brandes))) %>% + add_node_attribute("betweenness", node_is_max(node_betweenness(ison_brandes))) %>% + add_node_attribute("closeness", node_is_max(node_closeness(ison_brandes))) %>% + add_node_attribute("eigenvector", node_is_max(node_eigenvector(ison_brandes))) gd <- graphr(ison_brandes, node_color = "degree") + ggtitle("Degree", subtitle = round(net_degree(ison_brandes), 2)) gc <- graphr(ison_brandes, node_color = "closeness") + diff --git a/inst/tutorials/tutorial3/centrality.html b/inst/tutorials/tutorial3/centrality.html index 225911c3..be779080 100644 --- a/inst/tutorials/tutorial3/centrality.html +++ b/inst/tutorials/tutorial3/centrality.html @@ -110,15 +110,20 @@
-
-

Calculating centrality

+
+

Today’s target

+

The aim of this tutorial is to show how we can measure and map +degree, betweenness, closeness, eigenvector, and other types of +centrality, explore their distributions and calculate the corresponding +centralisation.

+
+

Setting up

For this exercise, we’ll use the ison_brandes dataset in {manynet}. This dataset is in a ‘tidygraph’ format, but manynet makes it easy to coerce this into other forms to be compatible with other packages. We can create a two-mode version of the dataset by renaming the nodal attribute “twomode_type” to just “type”. -Let’s begin by graphing these datasets using -manynet::graphr().

+Let’s begin by graphing these datasets using graphr().

@@ -144,12 +149,12 @@

Calculating centrality

graphr(ison_brandes) ison_brandes2 <- ison_brandes %>% rename(type = twomode_type) # plot the two-mode version -graphr(ison_brandes2)
+graphr(ison_brandes2, layout = "bipartite")

The network is anonymous, but I think it would be nice to add some names, even if it’s just pretend. Luckily, {manynet} has a -function for this. This makes plotting the network just a wee bit more -accessible and interpretable:

+function for this: to_named(). This makes plotting the +network just a wee bit more accessible and interpretable:

@@ -169,12 +174,17 @@

Calculating centrality

# plot network with names graphr(ison_brandes)
-

Note that you will likely get a different set of names, as they are -assigned randomly from a pool of (American) first names.

-
-

Degree centrality

-

Let’s start with calculating degree, as it is easy to calculate -yourself. Just sum the rows or columns of the matrix!

+

Note that you will likely get a different set of names (though still +alphabetical), as they are assigned randomly from a pool of (American) +first names.

+
+
+
+

Degree centrality

+

Let’s start with calculating degree. Remember that degree centrality +is just the number of incident edges/ties to each node. It is therefore +easy to calculate yourself. Just sum the rows or columns of the +matrix!

@@ -191,7 +201,7 @@

Degree centrality

-
# Or by using a built in command in migraph like this:
+
# Or by using a built in command in manynet like this:
 node_degree(ison_brandes, normalized = FALSE)
Degree centrality mat <- as_matrix(ison_brandes) degrees <- rowSums(mat) rowSums(mat) == colSums(mat) -# You can also just use a built in command in migraph though: +# You can also just use a built in command in manynet though: node_degree(ison_brandes, normalized = FALSE)
@@ -212,8 +222,19 @@

Degree centrality

+
+
+
+
+
+ +
+
+
+
+

Degree distributions

Often we are interested in the distribution of (degree) centrality in -a network. {migraph} offers a way to get a pretty good +a network. {manynet} offers a way to get a pretty good first look at this distribution, though there are more elaborate ways to do this in base and grid graphics.

Degree centrality plot(node_degree(ison_brandes))

What’s plotted here by default is both the degree distribution as a -histogram, as well as a density plot overlaid on it. What kind of shape -does this have?

+histogram, as well as a density plot overlaid on it in red.

+
+
+
+
+
+ +
-
-

Other centralities

+
+
+

Other centralities

Other measures of centrality can be a little trickier to calculate by -hand. Fortunately, we can use functions from {migraph} to +hand. Fortunately, we can use functions from {manynet} to help calculate the betweenness, closeness, and eigenvector centralities for each node in the network. Let’s collect the vectors of these centralities for the ison_brandes dataset:

@@ -269,32 +297,55 @@

Other centralities

data-lines="0" data-pipe="|>">
node_betweenness(ison_brandes)
 node_closeness(ison_brandes)
-node_eigenvector(ison_brandes)
-# TASK: Can you create degree distributions for each of these?
+node_eigenvector(ison_brandes)

What is returned here are vectors of betweenness, closeness, and -eigenvector scores for the nodes in the network. But what do they mean? -Try to answer the following questions for yourself:

+eigenvector scores for the nodes in the network. But what do they +mean?

+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+

Try to answer the following questions for yourself:

    -
  • in what ways is a higher degree actor more ‘central’?
  • -
  • can you explain why a node that has the smallest sum of geodesic -distances to all other nodes is said to be ‘central’?
  • -
  • why would an actor lying ‘between’ two other actors be -‘central’?
  • what does Bonacich mean when he says that power and influence are not the same thing?
  • can you think of a real-world example when an actor might be central but not powerful, or powerful but not central?
-

Note that all centrality measures in {migraph} return +

Note that all centrality measures in {manynet} return normalized scores by default – for the raw scores, include normalized = FALSE in the function as an extra argument.

+

Now, can you create degree distributions for each of these?

+
+ +
+
+
plot(node_betweenness(ison_brandes))
+plot(node_closeness(ison_brandes))
+plot(node_eigenvector(ison_brandes))

Plotting centrality

-

It is straightforward in {migraph} to highlight nodes +

It is straightforward in {manynet} to highlight nodes and ties with maximum or minimum (e.g. degree) scores. If the vector is numeric (i.e. a “measure”), then this can be easily converted into a logical vector that identifies the node/tie with the maximum/minimum @@ -340,19 +391,19 @@

Plotting centrality

data-pipe="|>">
ison_brandes2 %>%
   add_node_attribute("color", node_is_max(node_degree(ison_brandes2))) %>%
-  graphr(node_color = "color")
+  graphr(node_color = "color", layout = "bipartite")
 
 ison_brandes2 %>%
   add_node_attribute("color", node_is_max(node_betweenness(ison_brandes2))) %>%
-  graphr(node_color = "color")
+  graphr(node_color = "color", layout = "bipartite")
 
 ison_brandes2 %>%
   add_node_attribute("color", node_is_max(node_closeness(ison_brandes2))) %>%
-  graphr(node_color = "color")
+  graphr(node_color = "color", layout = "bipartite")
 
 ison_brandes2 %>%
   add_node_attribute("color", node_is_max(node_eigenvector(ison_brandes2))) %>%
-  graphr(node_color = "color")
+ graphr(node_color = "color", layout = "bipartite")
@@ -363,12 +414,13 @@

Plotting centrality

-
-

Calculating centralization

-

{migraph} also implements network centralization +

+

Centralization

+

+

{manynet} also implements network centralization functions. Here we are no longer interested in the level of the node, but in the level of the whole network, so the syntax replaces -node_ with network_:

+node_ with net_:

@@ -377,15 +429,16 @@

Calculating centralization

-
network_degree(ison_brandes)
-network_betweenness(ison_brandes)
-network_closeness(ison_brandes)
-network_eigenvector(ison_brandes)
+
net_degree(ison_brandes)
+net_betweenness(ison_brandes)
+net_closeness(ison_brandes)
+print(net_eigenvector(ison_brandes), digits = 5)
-

By default, scores are printed to 3 decimal places, but this can be -modified and, in any case, the unrounded values are retained internally. -This means that even if rounded values are printed, as much precision as -is available is used in further calculations.

+

By default, scores are printed up to 3 decimal places, but this can +be modified and, in any case, the unrounded values are retained +internally. This means that even if rounded values are printed (to +respect console space), as much precision as is available is used in +further calculations.

Note that for centralization in two-mode networks, two values are given (as a named vector), since normalization typically depends on the (asymmetric) number of nodes in each mode.

@@ -401,22 +454,18 @@

Calculating centralization

data-completion="1" data-diagnostics="1" data-startover="1" data-lines="0" data-pipe="|>">
ison_brandes <- ison_brandes %>%
-  add_node_attribute("degree",
-                              node_is_max(node_degree(ison_brandes))) %>%
-  add_node_attribute("betweenness",
-                              node_is_max(node_betweenness(ison_brandes))) %>%
-  add_node_attribute("closeness",
-                              node_is_max(node_closeness(ison_brandes))) %>%
-  add_node_attribute("eigenvector",
-                              node_is_max(node_eigenvector(ison_brandes)))
+  add_node_attribute("degree", node_is_max(node_degree(ison_brandes))) %>%
+  add_node_attribute("betweenness", node_is_max(node_betweenness(ison_brandes))) %>%
+  add_node_attribute("closeness", node_is_max(node_closeness(ison_brandes))) %>%
+  add_node_attribute("eigenvector", node_is_max(node_eigenvector(ison_brandes)))
 gd <- graphr(ison_brandes, node_color = "degree") + 
-  ggtitle("Degree", subtitle = round(network_degree(ison_brandes), 2))
+  ggtitle("Degree", subtitle = round(net_degree(ison_brandes), 2))
 gc <- graphr(ison_brandes, node_color = "closeness") + 
-  ggtitle("Closeness", subtitle = round(network_closeness(ison_brandes), 2))
+  ggtitle("Closeness", subtitle = round(net_closeness(ison_brandes), 2))
 gb <- graphr(ison_brandes, node_color = "betweenness") + 
-  ggtitle("Betweenness", subtitle = round(network_betweenness(ison_brandes), 2))
+  ggtitle("Betweenness", subtitle = round(net_betweenness(ison_brandes), 2))
 ge <- graphr(ison_brandes, node_color = "eigenvector") + 
-  ggtitle("Eigenvector", subtitle = round(network_eigenvector(ison_brandes), 2))
+  ggtitle("Eigenvector", subtitle = round(net_eigenvector(ison_brandes), 2))
 (gd | gb) / (gc | ge)
 # ggsave("brandes-centralities.pdf")
@@ -447,7 +496,6 @@

Tasks

+ + - + + + + + + + + + + + + + + @@ -859,8 +1091,8 @@

Tasks

@@ -988,22 +1220,22 @@

Tasks

@@ -1023,12 +1255,12 @@

Tasks

From 6ec280fb870760880f0cce334a4406bd6e0ea029 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 13:42:10 +0200 Subject: [PATCH 22/26] Updated structure and functions --- inst/tutorials/tutorial4/community.Rmd | 77 +-- inst/tutorials/tutorial4/community.html | 645 ++++++++++++------------ 2 files changed, 354 insertions(+), 368 deletions(-) diff --git a/inst/tutorials/tutorial4/community.Rmd b/inst/tutorials/tutorial4/community.Rmd index 0889068c..934345f6 100644 --- a/inst/tutorials/tutorial4/community.Rmd +++ b/inst/tutorials/tutorial4/community.Rmd @@ -1,5 +1,5 @@ --- -title: "Community" +title: "Cohesion and Community" author: "by James Hollway" output: learnr::tutorial: @@ -14,7 +14,6 @@ description: > library(learnr) library(patchwork) library(manynet) -library(migraph) knitr::opts_chunk$set(echo = FALSE) friends <- to_uniplex(ison_algebra, "friends") @@ -161,20 +160,20 @@ Because this is a directed network, we can calculate the density as: ```{r dens-explicit-solution} # calculating network density manually according to equation -network_ties(tasks)/(network_nodes(tasks)*(network_nodes(tasks)-1)) +net_ties(tasks)/(net_nodes(tasks)*(net_nodes(tasks)-1)) ``` -but we can also just use the `{migraph}` function... +but we can also just use the `{manynet}` function... ```{r dens, exercise=TRUE, exercise.setup = "separatingnets", purl = FALSE} ``` ```{r dens-solution} -network_density(tasks) +net_density(tasks) ``` -Note that the various measures in `{migraph}` print results to three decimal points +Note that the various measures in `{manynet}` print results to three decimal points by default, but the underlying result retains the same recurrence. So same result... @@ -188,11 +187,11 @@ question("Is this network's density high or low?", ) ``` -### Closure +## Closure Next let's calculate _reciprocity_ in the task network. While one could do this by hand, -it's more efficient to do this using the `{migraph}` package. +it's more efficient to do this using the `{manynet}` package. Can you guess the correct name of the function? ```{r recip, exercise=TRUE, exercise.setup = "separatingnets", purl = FALSE} @@ -200,7 +199,7 @@ Can you guess the correct name of the function? ``` ```{r recip-solution} -network_reciprocity(tasks) +net_reciprocity(tasks) # this function calculates the amount of reciprocity in the whole network ``` @@ -212,7 +211,7 @@ Again, can you guess the correct name of this function? ``` ```{r trans-solution} -network_transitivity(tasks) +net_transitivity(tasks) # this function calculates the amount of transitivity in the whole network ``` @@ -237,11 +236,11 @@ question("What can we say about task closure in this network? Choose all that ap ) ``` -### Components +## Components Now let's look at the friendship network, 'friends'. We're interested here in how many _components_ there are. -By default, the `network_components()` function will +By default, the `net_components()` function will return the number of _strong_ components for directed networks. For _weak_ components, you will need to first make the network undirected. Remember the difference between weak and strong components? @@ -260,7 +259,7 @@ question("Weak components...", ``` ```{r comp-no-hint-1, purl = FALSE} -network_components(friends) +net_components(friends) # note that friends is a directed network # you can see this by calling the object 'friends' # or by running `manynet::is_directed(friends)` @@ -271,13 +270,13 @@ network_components(friends) # Note: to_undirected() returns an object with all tie direction removed, # so any pair of nodes with at least one directed edge # will be connected by an undirected edge in the new network. -network_components(to_undirected(friends)) +net_components(to_undirected(friends)) ``` ```{r comp-no-solution} # note that friends is a directed network -network_components(friends) -network_components(to_undirected(friends)) +net_components(friends) +net_components(to_undirected(friends)) ``` ```{r comp-interp, echo = FALSE, purl = FALSE} @@ -343,7 +342,7 @@ question("Why is there a difference between the weak and strong components resul ) ``` -## Community Detection +## Communities Ok, the friendship network has 3-4 components, but how many 'groups' are there? Just visually, it looks like there are two denser clusters within the main component. @@ -387,7 +386,7 @@ There is no one single best community detection algorithm. Instead there are several, each with their strengths and weaknesses. Since this is a rather small network, we'll focus on the following methods: walktrap, edge betweenness, and fast greedy. -(Others are included in `{migraph}`/`{igraph}`) +(Others are included in `{manynet}`/`{igraph}`) As you use them, consider how they portray communities and consider which one(s) afford a sensible view of the social world as cohesively organized. @@ -448,7 +447,7 @@ which(friend_wt == 2) ```{r walk-hint-3, purl = FALSE} # resulting in a modularity of -network_modularity(friends, friend_wt) +net_modularity(friends, friend_wt) ``` ```{r walk-solution} @@ -462,7 +461,7 @@ which(friend_wt == 1) # and the other which(friend_wt == 2) # resulting in a modularity of -network_modularity(friends, friend_wt) +net_modularity(friends, friend_wt) ``` We can also visualise the clusters on the original network @@ -494,9 +493,9 @@ graphr(friends, node_color = "walk_comm", node_group = "walk_comm") + ggtitle("Walktrap", - subtitle = round(network_modularity(friends, friend_wt), 3)) + subtitle = round(net_modularity(friends, friend_wt), 3)) # the function `round()` rounds the values to a specified number of decimal places -# here, we are telling it to round the network_modularity score to 3 decimal places, +# here, we are telling it to round the net_modularity score to 3 decimal places, # but the score is exactly 0.27 so only two decimal places are printed. ``` @@ -511,7 +510,7 @@ graphr(friends, node_color = "walk_comm", node_group = "walk_comm") + ggtitle("Walktrap", - subtitle = round(network_modularity(friends, friend_wt), 3)) + subtitle = round(net_modularity(friends, friend_wt), 3)) ``` This can be helpful when polygons overlap to better identify membership @@ -564,7 +563,7 @@ graphr(friends, node_color = "eb_comm", node_group = "eb_comm") + ggtitle("Edge-betweenness", - subtitle = round(network_modularity(friends, friend_eb), 3)) + subtitle = round(net_modularity(friends, friend_eb), 3)) ``` ```{r ebplot-solution} @@ -574,7 +573,7 @@ graphr(friends, node_color = "eb_comm", node_group = "eb_comm") + ggtitle("Edge-betweenness", - subtitle = round(network_modularity(friends, friend_eb), 3)) + subtitle = round(net_modularity(friends, friend_eb), 3)) ``` For more on this algorithm, see M Newman and M Girvan: Finding and @@ -599,7 +598,7 @@ although I personally find it both useful and in many cases quite "accurate". ```{r fg-hint-1, purl = FALSE} friend_fg <- node_in_greedy(friends) friend_fg # Does this result in a different community partition? -network_modularity(friends, friend_fg) # Compare this to the edge betweenness procedure +net_modularity(friends, friend_fg) # Compare this to the edge betweenness procedure ``` ```{r fg-hint-2, purl = FALSE} @@ -610,14 +609,14 @@ graphr(friends, node_color = "fg_comm", node_group = "fg_comm") + ggtitle("Fast-greedy", - subtitle = round(network_modularity(friends, friend_fg), 3)) + subtitle = round(net_modularity(friends, friend_fg), 3)) # ``` ```{r fg-solution} friend_fg <- node_in_greedy(friends) friend_fg # Does this result in a different community partition? -network_modularity(friends, friend_fg) # Compare this to the edge betweenness procedure +net_modularity(friends, friend_fg) # Compare this to the edge betweenness procedure # Again, we can visualise these communities in different ways: friends <- friends %>% @@ -626,7 +625,7 @@ graphr(friends, node_color = "fg_comm", node_group = "fg_comm") + ggtitle("Fast-greedy", - subtitle = round(network_modularity(friends, friend_fg), 3)) + subtitle = round(net_modularity(friends, friend_fg), 3)) ``` See A Clauset, MEJ Newman, C Moore: @@ -643,7 +642,9 @@ question("What is the difference between communities and components?", allow_retry = TRUE) ``` -## Two-mode network: Southern women +## Projection + +### A two-mode network The next dataset, 'ison_southern_women', is also available in `{manynet}`. Let's load and graph the data. @@ -744,22 +745,22 @@ Let's return to the question of cohesion. ``` ```{r twomode-cohesion-hint-1, purl = FALSE} -# network_equivalency(): Calculate equivalence or reinforcement in a (usually two-mode) network +# net_equivalency(): Calculate equivalence or reinforcement in a (usually two-mode) network -network_equivalency(ison_southern_women) +net_equivalency(ison_southern_women) ``` ```{r twomode-cohesion-hint-2, purl = FALSE} -# network_transitivity(): Calculate transitivity in a network +# net_transitivity(): Calculate transitivity in a network -network_transitivity(women_graph) -network_transitivity(event_graph) +net_transitivity(women_graph) +net_transitivity(event_graph) ``` ```{r twomode-cohesion-solution} -network_equivalency(ison_southern_women) -network_transitivity(women_graph) -network_transitivity(event_graph) +net_equivalency(ison_southern_women) +net_transitivity(women_graph) +net_transitivity(event_graph) ``` What do we learn from this? diff --git a/inst/tutorials/tutorial4/community.html b/inst/tutorials/tutorial4/community.html index b2f88d0b..9b6f2027 100644 --- a/inst/tutorials/tutorial4/community.html +++ b/inst/tutorials/tutorial4/community.html @@ -15,7 +15,7 @@ -Community +Cohesion and Community @@ -266,9 +266,9 @@

Density

data-diagnostics="1" data-startover="1" data-lines="0" data-pipe="|>">
# calculating network density manually according to equation
-network_ties(tasks)/(network_nodes(tasks)*(network_nodes(tasks)-1))
+net_ties(tasks)/(net_nodes(tasks)*(net_nodes(tasks)-1))
-

but we can also just use the {migraph} function…

+

but we can also just use the {manynet} function…

@@ -277,9 +277,9 @@

Density

-
network_density(tasks)
+
net_density(tasks)
-

Note that the various measures in {migraph} print +

Note that the various measures in {manynet} print results to three decimal points by default, but the underlying result retains the same recurrence. So same result…

@@ -291,11 +291,12 @@

Density

-
-

Closure

+
+
+

Closure

Next let’s calculate reciprocity in the task network. While one could do this by hand, it’s more efficient to do this using the -{migraph} package. Can you guess the correct name of the +{manynet} package. Can you guess the correct name of the function?

Closure
-
network_reciprocity(tasks)
+
net_reciprocity(tasks)
 # this function calculates the amount of reciprocity in the whole network

And let’s calculate transitivity in the task network. Again, @@ -318,7 +319,7 @@

Closure

-
network_transitivity(tasks)
+
net_transitivity(tasks)
 # this function calculates the amount of transitivity in the whole network

We have collected measures of the task network’s reciprocity and @@ -333,11 +334,11 @@

Closure

-
-

Components

+
+

Components

Now let’s look at the friendship network, ‘friends’. We’re interested here in how many components there are. By default, the -network_components() function will return the number of +net_components() function will return the number of strong components for directed networks. For weak components, you will need to first make the network undirected. Remember the difference between weak and strong components?

@@ -357,7 +358,7 @@

Components

-
network_components(friends)
+
net_components(friends)
 # note that friends is a directed network
 # you can see this by calling the object 'friends'
 # or by running `manynet::is_directed(friends)`
@@ -369,14 +370,14 @@

Components

# Note: to_undirected() returns an object with all tie direction removed, # so any pair of nodes with at least one directed edge # will be connected by an undirected edge in the new network. -network_components(to_undirected(friends))
+net_components(to_undirected(friends))
# note that friends is a directed network
-network_components(friends)
-network_components(to_undirected(friends))
+net_components(friends) +net_components(to_undirected(friends))
@@ -432,9 +433,8 @@

Components

-
-
-

Community Detection

+
+

Communities

Ok, the friendship network has 3-4 components, but how many ‘groups’ are there? Just visually, it looks like there are two denser clusters within the main component.

@@ -480,7 +480,7 @@

Community Detection

there are several, each with their strengths and weaknesses. Since this is a rather small network, we’ll focus on the following methods: walktrap, edge betweenness, and fast greedy. (Others are included in -{migraph}/{igraph}) As you use them, consider +{manynet}/{igraph}) As you use them, consider how they portray communities and consider which one(s) afford a sensible view of the social world as cohesively organized.

@@ -542,7 +542,7 @@

Walktrap

data-completion="1" data-diagnostics="1" data-startover="1" data-lines="0" data-pipe="|>">
# resulting in a modularity of 
-network_modularity(friends, friend_wt)
+net_modularity(friends, friend_wt)
Walktrap # and the other which(friend_wt == 2) # resulting in a modularity of -network_modularity(friends, friend_wt) +net_modularity(friends, friend_wt)

We can also visualise the clusters on the original network How does the following look? Plausible?

@@ -593,9 +593,9 @@

Walktrap

node_color = "walk_comm", node_group = "walk_comm") + ggtitle("Walktrap", - subtitle = round(network_modularity(friends, friend_wt), 3)) + subtitle = round(net_modularity(friends, friend_wt), 3)) # the function `round()` rounds the values to a specified number of decimal places -# here, we are telling it to round the network_modularity score to 3 decimal places, +# here, we are telling it to round the net_modularity score to 3 decimal places, # but the score is exactly 0.27 so only two decimal places are printed.
Walktrap node_color = "walk_comm", node_group = "walk_comm") + ggtitle("Walktrap", - subtitle = round(network_modularity(friends, friend_wt), 3)) + subtitle = round(net_modularity(friends, friend_wt), 3))

This can be helpful when polygons overlap to better identify membership Or you can use node color and size to indicate other @@ -670,7 +670,7 @@

Edge Betweenness

node_color = "eb_comm", node_group = "eb_comm") + ggtitle("Edge-betweenness", - subtitle = round(network_modularity(friends, friend_eb), 3)) + subtitle = round(net_modularity(friends, friend_eb), 3))
Edge Betweenness node_color = "eb_comm", node_group = "eb_comm") + ggtitle("Edge-betweenness", - subtitle = round(network_modularity(friends, friend_eb), 3)) + subtitle = round(net_modularity(friends, friend_eb), 3))

For more on this algorithm, see M Newman and M Girvan: Finding and evaluating community structure in networks, Physical Review E 69, 026113 @@ -708,7 +708,7 @@

Fast Greedy

data-lines="0" data-pipe="|>">
friend_fg <- node_in_greedy(friends)
 friend_fg # Does this result in a different community partition?
-network_modularity(friends, friend_fg) # Compare this to the edge betweenness procedure
+net_modularity(friends, friend_fg) # Compare this to the edge betweenness procedure
Fast Greedy node_color = "fg_comm", node_group = "fg_comm") + ggtitle("Fast-greedy", - subtitle = round(network_modularity(friends, friend_fg), 3)) + subtitle = round(net_modularity(friends, friend_fg), 3)) #
Fast Greedy data-lines="0" data-pipe="|>">
friend_fg <- node_in_greedy(friends)
 friend_fg # Does this result in a different community partition?
-network_modularity(friends, friend_fg) # Compare this to the edge betweenness procedure
+net_modularity(friends, friend_fg) # Compare this to the edge betweenness procedure
 
 # Again, we can visualise these communities in different ways:
 friends <- friends %>% 
@@ -737,7 +737,7 @@ 

Fast Greedy

node_color = "fg_comm", node_group = "fg_comm") + ggtitle("Fast-greedy", - subtitle = round(network_modularity(friends, friend_fg), 3))
+ subtitle = round(net_modularity(friends, friend_fg), 3))

See A Clauset, MEJ Newman, C Moore: Finding community structure in very large networks, Fast Greedy

-
-

Two-mode network: Southern women

+
+

Projection

+
+

A two-mode network

The next dataset, ‘ison_southern_women’, is also available in {manynet}. Let’s load and graph the data.

Two-mode network: Southern women ison_southern_women graphr(ison_southern_women, node_color = "type")
+

Project two-mode network into two one-mode networks

@@ -865,26 +867,26 @@

Project two-mode network into two one-mode networks

data-label="twomode-cohesion-hint-1" data-completion="1" data-diagnostics="1" data-startover="1" data-lines="0" data-pipe="|>"> -
# network_equivalency(): Calculate equivalence or reinforcement in a (usually two-mode) network
+
# net_equivalency(): Calculate equivalence or reinforcement in a (usually two-mode) network
 
-network_equivalency(ison_southern_women)
+net_equivalency(ison_southern_women)
-
# network_transitivity(): Calculate transitivity in a network
+
# net_transitivity(): Calculate transitivity in a network
 
-network_transitivity(women_graph)
-network_transitivity(event_graph)
+net_transitivity(women_graph) +net_transitivity(event_graph)
-
network_equivalency(ison_southern_women)
-network_transitivity(women_graph)
-network_transitivity(event_graph)
+
net_equivalency(ison_southern_women)
+net_transitivity(women_graph)
+net_transitivity(event_graph)

What do we learn from this?

@@ -903,7 +905,6 @@

Task/Unit Test

library(learnr) library(patchwork) library(manynet) -library(migraph) knitr::opts_chunk$set(echo = FALSE) friends <- to_uniplex(ison_algebra, "friends") @@ -941,14 +942,13 @@

Task/Unit Test

@@ -1198,19 +1197,18 @@

Task/Unit Test

@@ -1330,12 +1327,12 @@

Task/Unit Test

@@ -1362,20 +1359,19 @@

Task/Unit Test

@@ -1442,19 +1438,18 @@

Task/Unit Test

@@ -1526,19 +1521,18 @@

Task/Unit Test

@@ -1863,16 +1851,16 @@

Task/Unit Test

@@ -2120,7 +2104,8 @@

Task/Unit Test

-

Community

+

Cohesion and +Community

by James Hollway

From d0128619e17b5d319be53cd0ca4a8940571b466e Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 13:42:24 +0200 Subject: [PATCH 23/26] Updated structure and functions in position tutorial --- inst/tutorials/tutorial5/position.Rmd | 29 ++- inst/tutorials/tutorial5/position.html | 262 ++++++++++++------------- 2 files changed, 140 insertions(+), 151 deletions(-) diff --git a/inst/tutorials/tutorial5/position.Rmd b/inst/tutorials/tutorial5/position.Rmd index 7cafcd12..3f26a590 100644 --- a/inst/tutorials/tutorial5/position.Rmd +++ b/inst/tutorials/tutorial5/position.Rmd @@ -1,5 +1,5 @@ --- -title: "Position" +title: "Position and Equivalence" author: "by James Hollway" output: learnr::tutorial: @@ -13,7 +13,6 @@ description: > ```{r setup, include = FALSE} library(learnr) library(manynet) -library(migraph) library(patchwork) knitr::opts_chunk$set(echo = FALSE) ``` @@ -114,7 +113,7 @@ question("If we interpret ties with higher weights as strong ties, and lesser we ) ``` -## Structural Holes and Constraint +## Structural Holes Our first question for this network, is where innovation and creative ideas might be expected to appear. @@ -161,7 +160,7 @@ question("There are a number of measures that might be used to approximate the c Let's take a look at which actors are least _constrained_ by their position in the *task* network to begin with. -`{migraph}` makes this easy enough with the `node_constraint()` function. +`{manynet}` makes this easy enough with the `node_constraint()` function. ```{r objects-setup, purl=FALSE} alge <- to_named(ison_algebra) @@ -248,7 +247,7 @@ a uniplex subgraph thereof. ### Finding structurally equivalent classes -In `{migraph}`, finding how the nodes of a network can be partitioned +In `{manynet}`, finding how the nodes of a network can be partitioned into structurally equivalent classes can be as easy as: ```{r find-se, exercise = TRUE, exercise.setup = "data"} @@ -266,8 +265,8 @@ how these classes are identified and how to interpret them. ### Step one: starting with a census All equivalence classes are based on nodes' similarity across some profile of motifs. -In `{migraph}`, we call these motif *censuses*. -Any kind of census can be used, and `{migraph}` includes a few options, +In `{manynet}`, we call these motif *censuses*. +Any kind of census can be used, and `{manynet}` includes a few options, but `node_in_structural()` is based off of the census of all the nodes' ties, both outgoing and incoming ties, to characterise their relationships to tie partners. @@ -324,7 +323,7 @@ ison_algebra %>% Note that `node_tie_census()` does not need to be passed to `node_in_structural()` --- this is done automatically! However, the more generic `node_in_equivalence()` is available and can be used with whichever tie census is desired. -Feel free to explore using some of the other censuses available in `{migraph}`, +Feel free to explore using some of the other censuses available in `{manynet}`, though some common ones are already used in the other equivalence convenience functions, e.g. `node_triad_census()` in `node_in_regular()` and `node_path_census()` in `node_in_automorphic()`. @@ -341,7 +340,7 @@ so that help page should be consulted for more details. By default `"euclidean"` is used. Second, we can also set the type of clustering algorithm employed. -By default, `{migraph}`'s equivalence functions use hierarchical clustering, `"hier"`, +By default, `{manynet}`'s equivalence functions use hierarchical clustering, `"hier"`, but for compatibility and enthusiasts, we also offer `"concor"`, which implements a CONCOR (CONvergence of CORrelations) algorithm. @@ -366,7 +365,7 @@ question("Do you see any differences?", allow_retry = TRUE) ``` -So plotting a `membership` vector from `{migraph}` returns a dendrogram +So plotting a `membership` vector from `{manynet}` returns a dendrogram with the names of the nodes on the _y_-axis and the distance between them on the _x_-axis. Using the census as material, the distances between the nodes is used to create a dendrogram of (dis)similarity among the nodes. @@ -397,7 +396,7 @@ But where does this red line come from? Or, more technically, how do we identify the number of clusters into which to assign nodes? -`{migraph}` includes several different ways of establishing `k`, +`{manynet}` includes several different ways of establishing `k`, or the number of clusters. Remember, the further to the right the red line is (the lower on the tree the cut point is) @@ -433,7 +432,7 @@ then we might expect there to be a relatively rapid increase in correlation as we move from, for example, 3 clusters to 4 clusters, but a relatively small increase from, for example, 13 clusters to 14 clusters. By identifying the inflection point in this line graph, -`{migraph}` selects a number of clusters that represents a trade-off +`{manynet}` selects a number of clusters that represents a trade-off between fit and parsimony. This is the `k = "elbow"` method. @@ -461,12 +460,12 @@ Either is probably fine here, and there is much debate around how to select the number of clusters anyway. However, the silhouette method seems to do a better job of identifying how unique the 16th node is. -The silhouette method is also the default in `{migraph}`. +The silhouette method is also the default in `{manynet}`. Note that there is a somewhat hidden parameter here, `range`. Since testing across all possible numbers of clusters can get computationally expensive (not to mention uninterpretable) for large networks, -`{migraph}` only considers up to 8 clusters by default. +`{manynet}` only considers up to 8 clusters by default. This however can be modified to be higher or lower, e.g. `range = 16`. Finally, one last option is `k = "strict"`, @@ -584,7 +583,7 @@ and pretty happy to socialise with everyone. - The nerd is a loner, no friends, but everyone hangs out with them for task advice. -### Reduced graph +## Reduced graphs Lastly, we can consider how _classes_ of nodes relate to one another in a blockmodel. Let's use the 4-cluster solution on the valued network (though binary is possible too) diff --git a/inst/tutorials/tutorial5/position.html b/inst/tutorials/tutorial5/position.html index 547e7363..cfc21330 100644 --- a/inst/tutorials/tutorial5/position.html +++ b/inst/tutorials/tutorial5/position.html @@ -15,7 +15,7 @@ -Position +Position and Equivalence @@ -206,9 +206,8 @@

Separating multiplex networks

-
-

Structural Holes and Constraint

+
+

Structural Holes

Our first question for this network, is where innovation and creative ideas might be expected to appear.

@@ -231,7 +230,7 @@

Measuring structural holes

Let’s take a look at which actors are least constrained by their position in the task network to begin with. -{migraph} makes this easy enough with the +{manynet} makes this easy enough with the node_constraint() function.

Structural Equivalence

Finding structurally equivalent classes

-

In {migraph}, finding how the nodes of a network can be +

In {manynet}, finding how the nodes of a network can be partitioned into structurally equivalent classes can be as easy as:

Finding structurally equivalent classes class="section level3">

Step one: starting with a census

All equivalence classes are based on nodes’ similarity across some -profile of motifs. In {migraph}, we call these motif +profile of motifs. In {manynet}, we call these motif censuses. Any kind of census can be used, and -{migraph} includes a few options, but +{manynet} includes a few options, but node_in_structural() is based off of the census of all the nodes’ ties, both outgoing and incoming ties, to characterise their relationships to tie partners.

@@ -411,7 +410,7 @@

Step one: starting with a census

However, the more generic node_in_equivalence() is available and can be used with whichever tie census is desired. Feel free to explore using some of the other censuses available in -{migraph}, though some common ones are already used in the +{manynet}, though some common ones are already used in the other equivalence convenience functions, e.g. node_triad_census() in node_in_regular() and node_path_census() in @@ -430,7 +429,7 @@

Step two: growing a tree of similarity

help page should be consulted for more details. By default "euclidean" is used.

Second, we can also set the type of clustering algorithm employed. By -default, {migraph}’s equivalence functions use hierarchical +default, {manynet}’s equivalence functions use hierarchical clustering, "hier", but for compatibility and enthusiasts, we also offer "concor", which implements a CONCOR (CONvergence of CORrelations) algorithm.

@@ -459,7 +458,7 @@

Step two: growing a tree of similarity

So plotting a membership vector from -{migraph} returns a dendrogram with the names of the nodes +{manynet} returns a dendrogram with the names of the nodes on the y-axis and the distance between them on the x-axis. Using the census as material, the distances between the nodes is used to create a dendrogram of (dis)similarity among the nodes. @@ -487,7 +486,7 @@

Step three: identifying the number of clusters

to the branches (clusters) present at that cut-point.

But where does this red line come from? Or, more technically, how do we identify the number of clusters into which to assign nodes?

-

{migraph} includes several different ways of +

{manynet} includes several different ways of establishing k, or the number of clusters. Remember, the further to the right the red line is (the lower on the tree the cut point is) the more dissimilar we’re allowing nodes in the same cluster @@ -519,7 +518,7 @@

Step three: identifying the number of clusters

be a relatively rapid increase in correlation as we move from, for example, 3 clusters to 4 clusters, but a relatively small increase from, for example, 13 clusters to 14 clusters. By identifying the inflection -point in this line graph, {migraph} selects a number of +point in this line graph, {manynet} selects a number of clusters that represents a trade-off between fit and parsimony. This is the k = "elbow" method.

The other option is to evaluate a candidate for k based @@ -545,11 +544,11 @@

Step three: identifying the number of clusters

around how to select the number of clusters anyway. However, the silhouette method seems to do a better job of identifying how unique the 16th node is. The silhouette method is also the default in -{migraph}.

+{manynet}.

Note that there is a somewhat hidden parameter here, range. Since testing across all possible numbers of clusters can get computationally expensive (not to mention -uninterpretable) for large networks, {migraph} only +uninterpretable) for large networks, {manynet} only considers up to 8 clusters by default. This however can be modified to be higher or lower, e.g. range = 16.

Finally, one last option is k = "strict", which only @@ -679,8 +678,9 @@

Summarising profiles

for task advice.
-
-

Reduced graph

+
+
+

Reduced graphs

Lastly, we can consider how classes of nodes relate to one another in a blockmodel. Let’s use the 4-cluster solution on the valued network (though binary is possible too) to create a reduced @@ -705,7 +705,6 @@

Reduced graph

@@ -740,11 +739,11 @@

Reduced graph

@@ -855,28 +854,28 @@

Reduced graph

@@ -935,9 +934,8 @@

Reduced graph

@@ -1056,19 +1053,19 @@

Reduced graph

@@ -1255,9 +1252,8 @@

Reduced graph

-
@@ -1604,7 +1593,8 @@

Reduced graph

-

Position

+

Position and +Equivalence

by James Hollway

From f67efc95c0ba98686b443274933615dc87835818 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 13:42:40 +0200 Subject: [PATCH 24/26] Updated structure and functions in topology tutorial --- inst/tutorials/tutorial6/topology.Rmd | 33 ++- inst/tutorials/tutorial6/topology.html | 330 ++++++++++++------------- 2 files changed, 168 insertions(+), 195 deletions(-) diff --git a/inst/tutorials/tutorial6/topology.Rmd b/inst/tutorials/tutorial6/topology.Rmd index db03f3e6..74f89874 100644 --- a/inst/tutorials/tutorial6/topology.Rmd +++ b/inst/tutorials/tutorial6/topology.Rmd @@ -1,6 +1,6 @@ --- -title: "Topology" -author: "by James Hollway, Andrea Biswas-Tortajada" +title: "Topology and Resilience" +author: "by James Hollway" output: learnr::tutorial: theme: journal @@ -14,7 +14,6 @@ description: > ```{r setup, include = FALSE} library(learnr) library(manynet) -library(migraph) library(patchwork) knitr::opts_chunk$set(echo = FALSE) learnr::random_phrases_add(language = "fr", @@ -45,14 +44,14 @@ learnr::random_phrases_add(language = "en", encouragement = c("Bon effort")) ``` +## Overview + In this tutorial, we'll explore: - how to create or generate different network topologies - the core-periphery structure of a network - features of a network related to its resilience -## Generate networks of different structures - This tutorial covers a range of different network topologies: trees, lattices, random, small-world, scale-free, and core-periphery networks. @@ -65,7 +64,7 @@ We'll first look at some deterministic algorithms for _creating_ networks of different structures, and then look at how the introduction of some randomness can _generate_ a variety of network structures. -### Deterministic graphs +## Creating networks To begin with, let's create a few 'empty' and full/'complete' graphs. You will want to use some of the `create_*()` group of functions from `{manynet}`, @@ -214,7 +213,7 @@ Graph three ring networks: (graphr(create_ring(50, width = 2), "stress") + ggtitle("The Ring Two v2.0")) ``` -### Probabilistic graphs +## Generating networks Next we are going to take a look at some probabilistic graphs. These involve some random element, perhaps in addition to specific rules, @@ -292,7 +291,7 @@ but with a rewiring probability of 0.25: ``` ```{r smallwtest-solution} -network_smallworld(generate_smallworld(50, 0.25)) +net_smallworld(generate_smallworld(50, 0.25)) ``` #### Scale-free graphs @@ -343,7 +342,7 @@ comes from a power-law distribution. ``` ```{r scaleftest-solution} -network_scalefree(generate_scalefree(50, 2)) +net_scalefree(generate_scalefree(50, 2)) ``` ## Core-Periphery @@ -423,14 +422,14 @@ one on the left and one on the right. But is it really all that much of a core-periphery structure? We can establish how correlated our network is compared to -a core-periphery model of the same dimension using `network_core()`. +a core-periphery model of the same dimension using `net_core()`. ```{r netcore, exercise=TRUE, purl = FALSE} ``` ```{r netcore-solution} -network_core(ison_lawfirm, node_is_core(ison_lawfirm)) +net_core(ison_lawfirm, node_is_core(ison_lawfirm)) ``` ```{r corecorr-qa, echo=FALSE, purl = FALSE} @@ -483,11 +482,11 @@ question("There a statistically significant association between the core assignm ) ``` -### Coreness values +## Coreness An alternative route is to identify 'core' nodes depending on their _k_-coreness. -In `{migraph}`, we can return nodes _k_-coreness +In `{manynet}`, we can return nodes _k_-coreness with `node_coreness()` instead of the `node_is_core()` used for core-periphery. @@ -524,7 +523,7 @@ question("Select the correct definitions:", ) ``` -## Network Resilience +## Resilience ### How cohesive is the network? @@ -538,7 +537,7 @@ First, we might be interested in whether the network is connected at all. ``` ```{r connected-solution} -network_connectedness(ison_adolescents) +net_connectedness(ison_adolescents) ``` This measure gets at the proportion of dyads that can reach each other in the network. @@ -567,7 +566,7 @@ Find out how many dropped nodes it would take to (further) fragment the network. ``` ```{r cohesion-solution} -network_cohesion(ison_adolescents) +net_cohesion(ison_adolescents) ``` ```{r cohesion-qa, echo=FALSE, purl = FALSE} @@ -631,7 +630,7 @@ Let's do something similar now, but with respect to ties rather than nodes. ``` ```{r tieside-solution} -network_adhesion(ison_adolescents) +net_adhesion(ison_adolescents) ison_adolescents |> mutate_ties(cut = tie_is_bridge(ison_adolescents)) |> graphr(edge_color = "cut") ``` diff --git a/inst/tutorials/tutorial6/topology.html b/inst/tutorials/tutorial6/topology.html index 6577327f..2f118d7a 100644 --- a/inst/tutorials/tutorial6/topology.html +++ b/inst/tutorials/tutorial6/topology.html @@ -8,14 +8,14 @@ - + -Topology +Topology and Resilience @@ -110,15 +110,14 @@
+
+

Overview

In this tutorial, we’ll explore:

  • how to create or generate different network topologies
  • the core-periphery structure of a network
  • features of a network related to its resilience
-
-

Generate networks of different structures

This tutorial covers a range of different network topologies: trees, lattices, random, small-world, scale-free, and core-periphery networks. These ideal networks exaggerate centrality, cohesion, and randomness @@ -129,8 +128,9 @@

Generate networks of different structures

deterministic algorithms for creating networks of different structures, and then look at how the introduction of some randomness can generate a variety of network structures.

-
-

Deterministic graphs

+
+
+

Creating networks

To begin with, let’s create a few ‘empty’ and full/‘complete’ graphs. You will want to use some of the create_*() group of functions from {manynet}, because they create graphs @@ -276,8 +276,8 @@

Rings

-
-

Probabilistic graphs

+
+

Generating networks

Next we are going to take a look at some probabilistic graphs. These involve some random element, perhaps in addition to specific rules, to stochastically ‘generate’ networks of certain types of topologies. As @@ -361,7 +361,7 @@

Small-world graphs

-
network_smallworld(generate_smallworld(50, 0.25))
+
net_smallworld(generate_smallworld(50, 0.25))
@@ -411,8 +411,7 @@

Scale-free graphs

-
network_scalefree(generate_scalefree(50, 2))
-
+
net_scalefree(generate_scalefree(50, 2))
@@ -470,11 +469,10 @@

Core-periphery assignment

graphr(ison_lawfirm, node_color = "school")

Next, let’s assign nodes to the core and periphery blocks using the -node_in_core() function from {migraph}. It -works pretty straightforwardly. By default it runs down the rank order -of nodes by their degree, at each step working out whether including the -next highest degree node in the core will maximise the core-periphery -structure of the network.

+node_is_core() function. It works pretty straightforwardly. +By default it runs down the rank order of nodes by their degree, at each +step working out whether including the next highest degree node in the +core will maximise the core-periphery structure of the network.

@@ -484,14 +482,14 @@

Core-periphery assignment

data-completion="1" data-diagnostics="1" data-startover="1" data-lines="0" data-pipe="|>">
ison_lawfirm %>% 
-  mutate(nc = node_in_core(ison_lawfirm)) %>% 
+  mutate(nc = node_is_core(ison_lawfirm)) %>% 
   graphr(node_color = "nc")

This graph suggests that there might even be two cores here, one on the left and one on the right.

But is it really all that much of a core-periphery structure? We can establish how correlated our network is compared to a core-periphery -model of the same dimension using network_core().

+model of the same dimension using net_core().

@@ -500,7 +498,7 @@

Core-periphery assignment

-
network_core(ison_lawfirm, node_in_core(ison_lawfirm))
+
net_core(ison_lawfirm, node_is_core(ison_lawfirm))
@@ -510,7 +508,7 @@

Core-periphery assignment

-

Note that node_in_core() also includes a method that +

Note that node_is_core() also includes a method that descends through the rank order of nodes’ eigenvector centralities instead of degree centralities. Why might that not be such a good choice here?

@@ -528,7 +526,7 @@

Core-periphery assignment

-
chisq.test(node_in_core(ison_lawfirm), node_attribute(ison_lawfirm, "Gender"))
+
chisq.test(node_is_core(ison_lawfirm), node_attribute(ison_lawfirm, "Gender"))
@@ -539,12 +537,13 @@

Core-periphery assignment

-
-

Coreness values

+
+
+

Coreness

An alternative route is to identify ‘core’ nodes depending on their -k-coreness. In {migraph}, we can return nodes +k-coreness. In {manynet}, we can return nodes k-coreness with node_coreness() instead of the -node_in_core() used for core-periphery.

+node_is_core() used for core-periphery.

@@ -574,9 +573,8 @@

Coreness values

- -
-

Network Resilience

+
+

Resilience

How cohesive is the network?

When investigating a network’s resilience, we might think of whether @@ -592,7 +590,7 @@

How cohesive is the network?

-
network_connectedness(ison_adolescents)
+
net_connectedness(ison_adolescents)

This measure gets at the proportion of dyads that can reach each other in the network. Another way to get at this would be to see how @@ -617,7 +615,7 @@

How cohesive is the network?

-
network_cohesion(ison_adolescents)
+
net_cohesion(ison_adolescents)
@@ -680,7 +678,7 @@

Identifying bridges

-
network_adhesion(ison_adolescents)
+
net_adhesion(ison_adolescents)
 ison_adolescents |> mutate_ties(cut = tie_is_bridge(ison_adolescents)) |> 
   graphr(edge_color = "cut")
@@ -703,7 +701,6 @@

Identifying bridges

@@ -984,9 +978,8 @@

Identifying bridges

@@ -1487,9 +1473,8 @@

Identifying bridges

@@ -1761,9 +1743,8 @@

Identifying bridges

@@ -1855,9 +1836,8 @@

Identifying bridges

@@ -1930,22 +1910,22 @@

Identifying bridges

@@ -1969,9 +1949,8 @@

Identifying bridges

@@ -2055,9 +2034,8 @@

Identifying bridges

@@ -2171,9 +2149,8 @@

Identifying bridges

@@ -2419,9 +2393,9 @@

Identifying bridges

-

Topology

-

by James Hollway, Andrea -Biswas-Tortajada

+

Topology and +Resilience

+

by James Hollway

From 55fb7f4080a2d5b3a4999e0404eedcc8081e5d32 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 13:43:03 +0200 Subject: [PATCH 25/26] Added spaces to intro checklist for diffusion tutorial --- inst/tutorials/tutorial7/diffusion.Rmd | 22 ++-- inst/tutorials/tutorial7/diffusion.html | 152 ++++++++++++------------ 2 files changed, 87 insertions(+), 87 deletions(-) diff --git a/inst/tutorials/tutorial7/diffusion.Rmd b/inst/tutorials/tutorial7/diffusion.Rmd index 0f9d93d2..c29205ea 100644 --- a/inst/tutorials/tutorial7/diffusion.Rmd +++ b/inst/tutorials/tutorial7/diffusion.Rmd @@ -1,5 +1,5 @@ --- -title: "Diffusion" +title: "Diffusion and Learning" author: "by James Hollway" output: learnr::tutorial: @@ -27,16 +27,16 @@ Network structures are key to how diffusion processes play out. By the end of this tutorial, you will be able to: -- [ ] 'play' a diffusion process over any kind of network -- [ ] print, plot, graph and interpret the results -- [ ] understand cascade models -- [ ] understand the role of network structure on diffusion -- [ ] understand threshold models -- [ ] understand the role of seeds for network intervention -- [ ] understand compartmental models -- [ ] understand the role of vaccination strategies -- [ ] understand learning models -- [ ] understand the role of influential nodes +- [ ]   'play' a diffusion process over any kind of network +- [ ]   print, plot, graph and interpret the results +- [ ]   understand cascade models +- [ ]   understand the role of network structure on diffusion +- [ ]   understand threshold models +- [ ]   understand the role of seeds for network intervention +- [ ]   understand compartmental models +- [ ]   understand the role of vaccination strategies +- [ ]   understand learning models +- [ ]   understand the role of influential nodes ## Influence cascade models diff --git a/inst/tutorials/tutorial7/diffusion.html b/inst/tutorials/tutorial7/diffusion.html index ceb175fa..1be3b700 100644 --- a/inst/tutorials/tutorial7/diffusion.html +++ b/inst/tutorials/tutorial7/diffusion.html @@ -15,7 +15,7 @@ -Diffusion +Diffusion and Learning @@ -117,25 +117,25 @@

This tutorial

how diffusion processes play out.

By the end of this tutorial, you will be able to:

    -
  • -
  • -
  • +
  • +
  • -
  • -
  • -
  • -
  • -
  • -
  • -
@@ -169,8 +169,7 @@

Diffusing across a lattice

need to run play_diffusion(), and assign the result. Then we can investigate the resulting object in a few ways. The first way is simply to print the result by calling the object. The other way is to -unpack the result by calling for its summary. -

+unpack the result by calling for its summary.

@@ -291,7 +290,7 @@

Varying network structure

show if and when there is complete infection, but we need to sit through each ‘movie’. But there is an easier way. Play these same diffusions again, this time nesting the call within -net_complete_infection().

+net_infection_complete().

@@ -1507,11 +1506,11 @@

Free play: Networkers

@@ -1612,19 +1611,19 @@

Free play: Networkers

@@ -1682,10 +1681,10 @@

Free play: Networkers

@@ -1746,10 +1745,10 @@

Free play: Networkers

@@ -1978,10 +1977,10 @@

Free play: Networkers

@@ -2084,10 +2083,10 @@

Free play: Networkers

@@ -2152,10 +2151,10 @@

Free play: Networkers

@@ -2263,22 +2262,22 @@

Free play: Networkers

@@ -2551,10 +2550,10 @@

Free play: Networkers

@@ -2616,16 +2615,16 @@

Free play: Networkers

@@ -2732,16 +2731,16 @@

Free play: Networkers

@@ -2761,17 +2760,17 @@

Free play: Networkers

@@ -2961,10 +2960,10 @@

Free play: Networkers

@@ -2984,20 +2983,20 @@

Free play: Networkers

@@ -3059,27 +3058,27 @@

Free play: Networkers

@@ -3142,7 +3141,7 @@

Free play: Networkers

@@ -3157,7 +3156,8 @@

Free play: Networkers

-

Diffusion

+

Diffusion and +Learning

by James Hollway

From d16db1a3d93cab06c026b6d94a65029ff67ad42c Mon Sep 17 00:00:00 2001 From: James Hollway Date: Sat, 20 Jul 2024 14:13:58 +0200 Subject: [PATCH 26/26] Updated NEWS --- DESCRIPTION | 2 +- NEWS.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 40753cd6..669d1573 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: manynet Title: Many Ways to Make, Modify, Map, Mark, and Measure Myriad Networks Version: 1.0.2 -Date: 2024-07-19 +Date: 2024-07-20 Description: Many tools for making, modifying, mapping, marking, measuring, and motifs and memberships of many different types of networks. All functions operate with matrices, edge lists, and 'igraph', 'network', and 'tidygraph' objects, diff --git a/NEWS.md b/NEWS.md index d31e3d81..667d6334 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,36 @@ +# manynet 1.0.2 + +## Package + +- Added alttext to images in the README, tutorials, and website +- Added CRAN link to homepage +- Updated favicons +- Added more structure to the function overview + +## Making + +- Updated intro tutorial with images, exercises, questions, and more explanation and structure +- Updated data tutorial with images, new function names, questions, and more explanation and structure +- Updated data tutorial with more details on adding and deleting nodes, ties, and attributes +- Updated topology tutorial with new function names and more structure + +## Mapping + +- Updated viz tutorial with examples of `graphs()` and `grapht()` +- Updated viz tutorial with more details and examples on colors and theming +- Updated viz tutorial with overview, examples, and details on layouts, +including force-directed, layered, circular, spectral, and grid layouts + +## Measures + +- Updated centrality tutorial with images, new function names, and more structure +- Updated centrality tutorial with more interpretation of centrality measures +- Updated position tutorial with new function names and more structure + +## Memberships + +- Updated community tutorial with new function names and more structure + # manynet 1.0.1 ## Package