diff --git a/.Rbuildignore b/.Rbuildignore index 91114bf..b80fbe7 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,2 +1,20 @@ +^CODE_OF_CONDUCT\.md$ +^LICENSE\.md$ ^.*\.Rproj$ ^\.Rproj\.user$ +^working$ +^\.github$ +^data-raw$ +data.csv +vignettes/*\.Rmd\.orig +vignettes/precompile\.R +^docs$ +^cache$ +^pkgdown$ +^cran-comments\.md$ +^CRAN-RELEASE$ +^doc$ +^Meta$ +^CRAN-SUBMISSION$ +^README\.Rmd$ +^.mailmap$ \ No newline at end of file diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..2d19fc7 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..24aa0a3 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,25 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for +everyone, regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. + +Examples of unacceptable behavior by participants include the use of sexual language or +imagery, derogatory comments or personal attacks, trolling, public or private harassment, +insults, or other unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this +Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed +from the project team. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by +opening an issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the Contributor Covenant +(http://contributor-covenant.org), version 1.0.0, available at +http://contributor-covenant.org/version/1/0/0/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..703c456 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,123 @@ +# Contributing + +Contributions to `netrics`, +whether in the form of issue identification, bug fixes, new code or documentation +are encouraged and welcome. + +## Aims + +Here is some things that Guy Kawasaki, Silicon Valley venture capitalist, +learned from Steve Jobs: + +- "Experts" are clueless. Especially self-declared ones. +- Customers cannot tell you what they need. They can help with evolution, but not revolution. +- Biggest challenges beget the best work. +- Design counts. Users will see the skin/UI of your product, not the great algorithms. +- Big graphics, big fonts. +- Jump curves---do things 10 times better, not 10 percent. +- All that truly matters is whether something works or doesn't work. Open or close, iPhone or Android, car or train, doesn't matter---make +it work. +- "Value" is different from "price". There is a class of people who do care about value. Ease of use -> less support costs. You have to create a unique and valuable product as an engineer. +- Real CEOs can demo. If you can't demo your own product, then quit. +- Real entrepreneurs ship, not slip. +- Some things need to be believed to be seen. + +## Git + +`stocnet` projects are maintained using the git version control system. +A plain-English introduction to git can be found [here](https://blog.red-badger.com/2016/11/29/gitgithub-in-plain-english). +I recommend you read this before continuing. +A more recent motivation can be found [here](https://www.r-bloggers.com/2024/04/git-gud-version-control-best-practices/). +It will explain the basics of git version control, committing and repos, pulling and pushing, +branching and merging. + +Using git from the command line on your lap- or desktop can be intimidating, +but I recommend [Fork](https://git-fork.com) software for Mac and Windows. +This allows mostly visual management of commits, diffs, branches, etc. +There are various other git software packages available, but this one is fairly fully featured. + +The GitHub page allows to access the issues assigned to you and check the commits. +You can also access the documents in the repository, +although this won't be necessary after you have cloned it on your computer via Fork. + +## Style + +In terms of style, we are aiming for pleasant predictability in terms of user experience. +To that end, we have a regular syntax that users can rely on producing expected effects. + +## Fork + +### Cloning +Once you have downloaded Fork, the first thing you have to do is to +clone the remote repository on your computer. +Before cloning, you will be able to choose on which `branch` you want to work: +develop or main. + +### Pull +This command allows you to `pull` changes from the remote repository to your local repository on Sourcetree. +Make sure you do that before starting working on your files so you have the newest versions. +When pulling, make sure you choose master or develop, +depending on the branch you decided to work with. +Once you pulled, you have now all the new commits and files and +you can start working on your assigned tasks. +Note that you can access and open the files either from the Finder or from Fork. +Some documents might be stored using Large File Storage (LFS) to save space on the repository. + +### Commit and Push + +Once you have made modifications on a file and saved them, it will appear in your `commit` window. +Here you can control one last time your file, write the commit message with the +issue reference (see below) and commit. +Once your commit is ready, you can `push` them to the origin/main repository. +Note that you can click the "push immediately" box in the commit window +if you don't want to do it in two steps. +If you are working on a separate branch, +it is important to select this branch when pushing to origin/main. + +## Issues and tests + +Please use the issues tracker on GitHub to identify any function-related issues. +You can use these issues to track progress on the issue and +to comment or continue a conversation on that issue. +Currently issue tracking is only open to those involved in the project. + +The most useful issues are ones that precisely identify an error, +or propose a test that should pass but instead fails. +This package uses the `testthat` package for testing functions. +Please see the [testthat website](https://testthat.r-lib.org) for more details. + +## Bug fixing or adding new code + +Independent or assigned code contributions are most welcome. +When writing new code, please follow +[standard R guidelines](https://www.r-bloggers.com/🖊-r-coding-style-guide/). +It can help to use packages such as `lintr`, `goodpractice` and `formatR` +to ensure these are followed. + +Currently, commits can only be pushed to GitHub where they reference an existing issue. +If no issue exists for the code you have developed, please add an issue first before pushing. +Once the issue exists, you will need to mention the issue number (preceded by a hash symbol: #) +in the commit description: + +` Resolved #31 by adding a new function that does things, also updated documentation ` + +Where the issue hash (i.e. #31) is preceded by +`resolve`, `resolves`, `resolved`, `close`, `closes`, `closed`, `fix`, `fixes`, or `fixed` +(capitalised or not), +Github will automatically updated the status of the issue(s) mentioned. + +Our current syntactical standard is to mention the issue first and then +provide a short description of what the committed changes do +in relation to that issue. +Any ancillary changes can be mentioned after a comma. + +## Documentation + +A final way of contributing to the package is in developing the +vignettes/articles that illustrate the value added in the package. +Please contact me with any proposals here. + +Please note that the `netrics` project is released with a +[Contributor Code of Conduct](CODE_OF_CONDUCT.md). +By contributing to this project, you agree to abide by its terms. + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d5d8f18 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Provide system information** + - OS: [e.g. MacOS Mojave 10.14.6] + - R version: [e.g. 3.5.3] + - netrics version: [e.g. 1.4.0] + +**To Reproduce** +Please consider providing a [reprex](https://reprex.tidyverse.org) (a reproducible example), +or outline the steps taken to reproduce the behaviour, e.g.: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error +If applicable, add screenshots to help explain your problem. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/documentation_request.md b/.github/ISSUE_TEMPLATE/documentation_request.md new file mode 100644 index 0000000..8c4e661 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_request.md @@ -0,0 +1,12 @@ +--- +name: Documentation request +about: Create a report to help us improve +title: '' +labels: documentation +assignees: '' + +--- + +**Name the function or functions that require further documentation here and in the title** + +Please describe what is not clear in the current documentation that can be expanded upon. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..cb310f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: feature_request +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/test_request.md b/.github/ISSUE_TEMPLATE/test_request.md new file mode 100644 index 0000000..35bf9ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/test_request.md @@ -0,0 +1,10 @@ +--- +name: Test request +about: Create a report to help us improve +title: '' +labels: tests +assignees: '' + +--- + +**Name the function or functions that require the tests here and in the title** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..74c60e3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +# Description + +# Checklist: + +- Documentation + - [ ] DESCRIPTION file version is bumped by the appropriate increment (major, minor, patch) + - [ ] Date in DESCRIPTION is correct + - [ ] Longer functions are commented inline or broken down into helper functions to help debugging +- PR form + - [ ] Title indicates expected version number + - [ ] PR description above and the NEWS.md file are aligned + - [ ] Description above itemizes changes under subsection titles, e.g. "## Data" + - [ ] Closed, fixed, or related issues are referenced and explained in the description above, e.g. "Fixed #0 by adding A" diff --git a/.github/workflows/prchecks.yml b/.github/workflows/prchecks.yml new file mode 100644 index 0000000..7ad888f --- /dev/null +++ b/.github/workflows/prchecks.yml @@ -0,0 +1,74 @@ +on: + pull_request: + branches: + - main + +name: Binary checks + +jobs: + + build: + name: Build for ${{ matrix.config.os }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - {os: macOS-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: macOS} + - {os: windows-latest, r: 'release', artifact_name: '*.zip', asset_name: winOS} + - {os: ubuntu-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: linuxOS} + + env: + R_REMOTES_NO_ERRORS_FROM_WARNINGS: true + RSPM: ${{ matrix.config.rspm }} + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + cache-version: 2 + needs: check, build + extra-packages: | + any::rcmdcheck + + - uses: r-lib/actions/check-r-package@v2 + env: + _R_CHECK_FORCE_SUGGESTS_: false + with: + upload-snapshots: true + + - name: Binary + run: | + pkgbuild::clean_dll() + binary <- pkgbuild::build(binary = TRUE, needs_compilation = TRUE, compile_attributes = TRUE) + dir.create("build") + file.copy(binary, "build") + shell: Rscript {0} + + - name: Save binary artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.config.asset_name }} + path: build/ + + - name: Calculate code coverage + if: runner.os == 'macOS' + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: Rscript -e 'covr::codecov(token = Sys.getenv("CODECOV_TOKEN"))' + + - name: Lint + run: lintr::lint_package() + shell: Rscript {0} + + - name: Spell check + run: spelling::spell_check_package() + shell: Rscript {0} diff --git a/.github/workflows/pushrelease.yml b/.github/workflows/pushrelease.yml new file mode 100644 index 0000000..bd4a22b --- /dev/null +++ b/.github/workflows/pushrelease.yml @@ -0,0 +1,157 @@ +on: + push: + branches: + - main + +name: Check and release + +jobs: + + build: + name: Build for ${{ matrix.config.os }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - {os: macOS-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: macOS} + - {os: windows-latest, r: 'release', artifact_name: '*.zip', asset_name: winOS} + - {os: ubuntu-latest, r: 'release', artifact_name: '*.tar.gz', asset_name: linuxOS} + + env: + R_REMOTES_NO_ERRORS_FROM_WARNINGS: true + RSPM: ${{ matrix.config.rspm }} + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + cache-version: 2 + needs: check, build + extra-packages: | + any::rcmdcheck + any::remotes + + - uses: r-lib/actions/check-r-package@v2 + env: + _R_CHECK_FORCE_SUGGESTS_: false + with: + upload-snapshots: true + + - name: Binary + run: | + pkgbuild::clean_dll() + binary <- pkgbuild::build(binary = TRUE, needs_compilation = TRUE, compile_attributes = TRUE) + dir.create("build") + file.copy(binary, "build") + shell: Rscript {0} + + - name: Save binary artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.config.asset_name }} + path: build/ + + - name: Calculate code coverage + if: runner.os == 'macOS' + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: Rscript -e 'covr::codecov(token = Sys.getenv("CODECOV_TOKEN"))' + + release: + name: Bump version and release + if: ${{ always() }} + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout one + uses: actions/checkout@v4 + with: + fetch-depth: '0' + - name: Bump version and push tag + id: newtag + uses: anothrNick/github-tag-action@1.39.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WITH_V: true + DEFAULT_BUMP: patch + RELEASE_BRANCHES: main + - name: Checkout two + uses: actions/checkout@v4 + + - name: Extract version + run: | + echo "PACKAGE_VERSION=$(grep '^Version' DESCRIPTION | sed 's/.*: *//')" >> $GITHUB_ENV + echo "PACKAGE_NAME=$(grep '^Package' DESCRIPTION | sed 's/.*: *//')" >> $GITHUB_ENV + + - name: Download binaries + uses: actions/download-artifact@v4 + + - name: Rename binaries release + shell: bash + run: | + ls -R + cp ./macOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.tgz . + cp ./linuxOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.tar.gz . + cp ./winOS/${{ env.PACKAGE_NAME }}_${{ env.PACKAGE_VERSION }}*.zip . + echo "Renamed files" + ls netrics_* + + - name: Create Release and Upload Assets + id: create_release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.newtag.outputs.tag }} + name: Release ${{ steps.newtag.outputs.tag }} + draft: false + prerelease: false + fail_on_unmatched_files: true + # Specify the assets you want to upload + files: | + netrics_*.tgz + netrics_*.tar.gz + netrics_*.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + pkgdown: + name: Build and deploy website + if: ${{ always() }} + needs: release + runs-on: macOS-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v2 + + - uses: r-lib/actions/setup-r@v2 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + cache-version: 2 + extra-packages: | + any::rcmdcheck + any::pkgdown + any::rsconnect + needs: check + + - name: Install package + run: R CMD INSTALL . + + - name: Deploy package + run: | + git config --local user.email "actions@github.com" + git config --local user.name "GitHub Actions" + Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' diff --git a/.gitignore b/.gitignore index d915b79..d1b11a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,22 @@ -# History files +inst/doc +.Rproj.user .Rhistory -.Rapp.history - -# Session Data files .RData -.RDataTmp - -# User-specific files .Ruserdata - -# Example code in package build process -*-Ex.R - -# Output files from R CMD build -/*.tar.gz - -# Output files from R CMD check -/*.Rcheck/ - -# RStudio files -.Rproj.user/ - -# produced vignettes -vignettes/*.html -vignettes/*.pdf - -# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 -.httr-oauth - -# knitr and R markdown default cache directories -*_cache/ -/cache/ - -# Temporary files created by R markdown -*.utf8.md -*.knit.md - -# R Environment Variables -.Renviron - -# pkgdown site +.DS_Store +data-raw/* docs/ - -# translation temp files -po/*~ - -# RStudio Connect folder -rsconnect/ -*.DS_Store +working/* +/doc/ +/Meta/ +tests/testthat/Rplots.pdf +.DS_Store +CRAN-SUBMISSION +man/figures/unnamed-chunk-1-1.png +inst/tutorials/community/community_data/* +inst/tutorials/equivalence/equivalence_data/* +inst/tutorials/tutorial4/community_data/* +inst/tutorials/tutorial5/equivalence_data/* +toadd/* +.data.csv +cache/* diff --git a/DESCRIPTION b/DESCRIPTION index ced8554..5c8f140 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,11 +1,13 @@ Package: netrics Title: Many Ways to Measure and Classify Membership for Networks, Nodes, and Ties -Version: 0.0.1 -Date: 2025-12-05 -Description: Many tools for marking, measuring, motifs and memberships of - many different types of networks. - All functions operate with matrices, edge lists, and 'igraph', 'network', and 'tidygraph' objects, - on directed, multiplex, multimodal, signed, and other networks. +Version: 0.1.0 +Date: 2026-03-15 +Description: Many tools for calculating network, node, or tie + marks, measures, motifs and memberships of many different types of networks. + Marks identify structural positions, measures quantify network properties, + memberships classify nodes into groups, and motifs tabulate substructure participation. + All functions operate with all classes of network data covered in 'manynet', + and on directed, undirected, multiplex, multimodal, signed, and other networks. URL: https://stocnet.github.io/netrics/ BugReports: https://github.com/stocnet/netrics/issues License: MIT + file LICENSE @@ -42,4 +44,4 @@ Config/Needs/website: pkgdown Config/testthat/parallel: true Config/testthat/edition: 3 -Config/testthat/start-first: member_community +Config/testthat/start-first: measure_net diff --git a/NAMESPACE b/NAMESPACE index d9ddd65..04cd795 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -29,7 +29,6 @@ export(net_by_equivalency) export(net_by_factions) export(net_by_harmonic) export(net_by_heterophily) -export(net_by_hierarchy) export(net_by_homophily) export(net_by_immunity) export(net_by_indegree) @@ -59,6 +58,7 @@ export(net_by_waves) export(net_x_brokerage) export(net_x_dyad) export(net_x_hazard) +export(net_x_hierarchy) export(net_x_mixed) export(net_x_tetrad) export(net_x_triad) @@ -165,7 +165,6 @@ export(tie_by_eigenvector) export(tie_is_bridge) export(tie_is_cyclical) export(tie_is_feedback) -export(tie_is_forbidden) export(tie_is_imbalanced) export(tie_is_loop) export(tie_is_max) diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..5c38977 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,121 @@ +# netrics 0.1.0 + +## Release notes + +`{netrics}` 0.1.0 is the first formal release of the package as a standalone +analytic engine for the [stocnet](https://github.com/stocnet) ecosystem. +The analytic functions — marks, measures, motifs, and memberships — have been +extracted from `{manynet}` and `{migraph}` into this dedicated package, with +consistent naming conventions and a range of bug fixes. + +## New naming conventions + +All functions now follow a consistent verb–object–qualifier naming scheme: + +- **Marks** (`node_is_*()`, `tie_is_*()`): logical vectors identifying which nodes + or ties hold a particular structural property. +- **Measures** (`*_by_*()`): numeric vectors at the network (`net_by_*()`), + node (`node_by_*()`), or tie (`tie_by_*()`) level. +- **Motifs** (`*_x_*()`): tabular counts of nodes' or networks' participation + in structural sub-patterns. +- **Memberships** (`*_in_*()`): categorical vectors assigning nodes to groups + (components, communities, equivalence classes, etc.). + +Functions previously named with other prefixes (e.g. `node_centrality_*`, +`net_cohesion_*`, `node_equivalency_*`) have been renamed to follow the +`*_by_*()` / `*_x_*()` / `*_in_*()` convention. +`tie_by_cohesion()` now correctly returns a `tie_measure` class object. + +## Functions moved from `{manynet}` / `{migraph}` + +The following groups of functions have been moved into `{netrics}`: + +### Marks +- `node_is_core()`, `node_is_cutpoint()`, `node_is_exposed()`, + `node_is_fold()`, `node_is_independent()`, `node_is_infected()`, + `node_is_isolate()`, `node_is_latent()`, `node_is_max()`, + `node_is_mean()`, `node_is_mentor()`, `node_is_min()`, + `node_is_neighbor()`, `node_is_pendant()`, `node_is_random()`, + `node_is_recovered()`, `node_is_universal()` +- `tie_is_bridge()`, `tie_is_cyclical()`, `tie_is_feedback()`, + `tie_is_imbalanced()`, `tie_is_loop()`, `tie_is_max()`, + `tie_is_min()`, `tie_is_multiple()`, `tie_is_path()`, + `tie_is_random()`, `tie_is_reciprocated()`, `tie_is_simmelian()`, + `tie_is_transitive()`, `tie_is_triangular()`, `tie_is_triplet()` + +### Measures +- Network-level: `net_by_adhesion()`, `net_by_assortativity()`, + `net_by_balance()`, `net_by_betweenness()`, `net_by_change()`, + `net_by_closeness()`, `net_by_cohesion()`, `net_by_components()`, + `net_by_congruency()`, `net_by_connectedness()`, `net_by_core()`, + `net_by_correlation()`, `net_by_degree()`, `net_by_density()`, + `net_by_diameter()`, `net_by_diversity()`, `net_by_efficiency()`, + `net_by_eigenvector()`, `net_by_equivalency()`, `net_by_factions()`, + `net_by_harmonic()`, `net_by_heterophily()`, `net_by_hierarchy()`, + `net_by_homophily()`, `net_by_immunity()`, `net_by_indegree()`, + `net_by_independence()`, `net_by_infection_complete()`, + `net_by_infection_peak()`, `net_by_infection_total()`, + `net_by_length()`, `net_by_modularity()`, `net_by_outdegree()`, + `net_by_reach()`, `net_by_reciprocity()`, `net_by_recovery()`, + `net_by_reproduction()`, `net_by_richclub()`, `net_by_richness()`, + `net_by_scalefree()`, `net_by_smallworld()`, `net_by_spatial()`, + `net_by_stability()`, `net_by_strength()`, `net_by_toughness()`, + `net_by_transitivity()`, `net_by_transmissibility()`, + `net_by_upperbound()`, `net_by_waves()` +- Node-level: `node_by_adoption_time()`, `node_by_alpha()`, + `node_by_authority()`, `node_by_betweenness()`, `node_by_bridges()`, + `node_by_brokering_activity()`, `node_by_brokering_exclusivity()`, + `node_by_closeness()`, `node_by_constraint()`, `node_by_coreness()`, + `node_by_deg()`, `node_by_degree()`, `node_by_distance()`, + `node_by_diversity()`, `node_by_eccentricity()`, + `node_by_efficiency()`, `node_by_effsize()`, `node_by_eigenvector()`, + `node_by_equivalency()`, `node_by_exposure()`, `node_by_flow()`, + `node_by_harmonic()`, `node_by_heterophily()`, `node_by_hierarchy()`, + `node_by_homophily()`, `node_by_hub()`, `node_by_indegree()`, + `node_by_induced()`, `node_by_information()`, `node_by_kcoreness()`, + `node_by_leverage()`, `node_by_multidegree()`, + `node_by_neighbours_degree()`, `node_by_outdegree()`, + `node_by_pagerank()`, `node_by_posneg()`, `node_by_power()`, + `node_by_randomwalk()`, `node_by_reach()`, `node_by_reciprocity()`, + `node_by_recovery()`, `node_by_redundancy()`, `node_by_richness()`, + `node_by_stress()`, `node_by_subgraph()`, `node_by_thresholds()`, + `node_by_transitivity()`, `node_by_vitality()` +- Tie-level: `tie_by_betweenness()`, `tie_by_closeness()`, + `tie_by_cohesion()`, `tie_by_degree()`, `tie_by_eigenvector()` + +### Motifs +- `net_x_brokerage()`, `net_x_dyad()`, `net_x_hazard()`, + `net_x_mixed()`, `net_x_tetrad()`, `net_x_triad()` +- `node_x_brokerage()`, `node_x_dyad()`, `node_x_exposure()`, + `node_x_path()`, `node_x_tetrad()`, `node_x_tie()`, `node_x_triad()` + +### Memberships +- `node_in_adopter()`, `node_in_automorphic()`, + `node_in_betweenness()`, `node_in_brokering()`, + `node_in_community()`, `node_in_component()`, `node_in_core()`, + `node_in_eigen()`, `node_in_equivalence()`, `node_in_fluid()`, + `node_in_greedy()`, `node_in_infomap()`, `node_in_leiden()`, + `node_in_louvain()`, `node_in_optimal()`, `node_in_partition()`, + `node_in_regular()`, `node_in_roulette()`, `node_in_spinglass()`, + `node_in_strong()`, `node_in_structural()`, `node_in_walktrap()`, + `node_in_weak()` + +## Bug fixes + +- `node_is_isolate()` and `node_is_pendant()` now work correctly with signed networks. +- `tie_is_random()` now correctly returns a `tie_mark` class object (previously returned a node mark). +- `node_by_authority()` and `node_by_hub()` updated to use current `{igraph}` API. +- `node_by_brokering_activity()` and `node_by_brokering_exclusivity()` now handle unlabelled networks correctly. +- `node_by_homophily()` no longer resolves the attribute to a vector prematurely. +- `node_by_pagerank()` updated to correctly extract the vector output from `{igraph}`. +- `node_by_power()` reverts to a lower exponent (closer to degree centrality) when there is no degree variation. +- `node_by_randomwalk()` now works with two-mode networks. +- `net_by_degree()`, `net_by_harmonic()`, and `net_by_reach()` now consistently include the function call in the returned object. +- `net_by_richclub()` returns 0 (rather than erroring) when all nodes have equivalent degree. +- `net_by_smallworld()` and `node_by_bridges()` now use internal `{netrics}` functions rather than `{manynet}` equivalents. +- `net_by_waves()` returns 1 for cross-sectional networks and correctly returns a network measure class. +- `net_x_hierarchy()` correctly classified as a motif function. +- `node_in_community()` now delegates to `{netrics}` membership functions internally. +- Dyad census fixed to handle two-mode networks. +- Equivalence *k*-assignment fixed for the degenerate case where every node is placed in the same cluster. +- `tie_by_cohesion()` now correctly returns a `tie_measure` class object. diff --git a/R/mark_nodes.R b/R/mark_nodes.R index d9c0d65..730647e 100644 --- a/R/mark_nodes.R +++ b/R/mark_nodes.R @@ -1,6 +1,6 @@ -# Structural properties #### +# Degree properties #### -#' Marking nodes based on structural properties +#' Marking nodes based on degree properties #' #' @description #' These functions return logical vectors the length of the @@ -8,28 +8,25 @@ #' #' - `node_is_isolate()` marks nodes that are isolates, #' with neither incoming nor outgoing ties. -#' - `node_is_independent()` marks nodes that are members of the largest independent set, -#' aka largest internally stable set. -#' - `node_is_cutpoint()` marks nodes that cut or act as articulation points in a network, -#' increasing the number of connected components when removed. -#' - `node_is_core()` marks nodes that are members of the network's core. -#' - `node_is_fold()` marks nodes that are in a structural fold between two or more -#' triangles that are only connected by that node. -#' - `node_is_mentor()` marks a proportion of high indegree nodes as 'mentors' (see details). +#' - `node_is_pendant()` marks nodes that are pendants, +#' with exactly one incoming or outgoing tie. +#' - `node_is_universal()` identifies whether nodes are adjacent to all other +#' nodes in the network. #' @param .data A network object of class `mnet`, `igraph`, `tbl_graph`, `network`, or similar. #' For more information on the standard coercion possible, #' see [manynet::as_tidygraph()]. #' @family marks -#' @name mark_nodes +#' @family degree +#' @name mark_degree NULL -#' @rdname mark_nodes +#' @rdname mark_degree #' @examples #' node_is_isolate(ison_brandes) #' @export node_is_isolate <- function(.data){ .data <- manynet::expect_nodes(.data) - mat <- manynet::as_matrix(.data) + mat <- abs(manynet::as_matrix(.data)) if(manynet::is_twomode(.data)){ out <- c(rowSums(mat)==0, colSums(mat)==0) } else { @@ -39,11 +36,11 @@ node_is_isolate <- function(.data){ make_node_mark(out, .data) } -#' @rdname mark_nodes +#' @rdname mark_degree #' @export node_is_pendant <- function(.data){ .data <- manynet::expect_nodes(.data) - mat <- manynet::as_matrix(.data) + mat <- abs(manynet::as_matrix(.data)) if(manynet::is_twomode(.data)){ out <- c(rowSums(mat)==1, colSums(mat)==1) } else { @@ -52,6 +49,47 @@ node_is_pendant <- function(.data){ make_node_mark(out, .data) } +#' @rdname mark_degree +#' @section Universal/dominating node: +#' A universal node is adjacent to all other nodes in the network. +#' It is also sometimes called the dominating vertex because it represents +#' a one-element dominating set. +#' A network with a universal node is called a cone, and its universal node +#' is called the apex of the cone. +#' A classic example of a cone is a star graph, +#' but friendship, wheel, and threshold graphs are also cones. +#' @examples +#' node_is_universal(create_star(11)) +#' @export +node_is_universal <- function(.data){ + .data <- manynet::expect_nodes(.data) + net <- manynet::to_undirected(manynet::to_unweighted(.data)) + make_node_mark(node_by_deg(net)==(manynet::net_nodes(net)-1), .data) +} + +# Structural properties #### + +#' Marking nodes based on structural properties +#' +#' @description +#' These functions return logical vectors the length of the +#' nodes in a network identifying which hold certain properties or positions in the network. +#' +#' - `node_is_independent()` marks nodes that are members of the largest independent set, +#' aka largest internally stable set. +#' - `node_is_cutpoint()` marks nodes that cut or act as articulation points in a network, +#' increasing the number of connected components when removed. +#' - `node_is_fold()` marks nodes that are in a structural fold between two or more +#' triangles that are only connected by that node. +#' - `node_is_mentor()` marks a proportion of high indegree nodes as 'mentors' (see details). +#' - `node_is_neighbor()` marks nodes that are neighbours of a given node. +#' @param .data A network object of class `mnet`, `igraph`, `tbl_graph`, `network`, or similar. +#' For more information on the standard coercion possible, +#' see [manynet::as_tidygraph()]. +#' @family marks +#' @name mark_nodes +NULL + #' @rdname mark_nodes #' @importFrom igraph largest_ivs #' @references @@ -195,6 +233,7 @@ node_is_neighbor <- function(.data, node){ #' - `node_is_recovered()` marks nodes that are recovered at a particular time point. #' @inheritParams mark_nodes #' @family marks +#' @family diffusion #' @name mark_diff NULL @@ -366,7 +405,7 @@ node_is_exposed <- function(.data, mark, time = 0){ # Selection properties #### -#' Marking nodes for selection based on measures +#' Marking nodes based on measures #' #' @description #' These functions return logical vectors the length of the @@ -403,7 +442,7 @@ node_is_random <- function(.data, size = 1){ #' three scores. #' By default, `ranks = 1`. #' @examples -#' #node_is_max(migraph::node_degree(ison_brandes)) +#' #node_is_max(node_by_degree(ison_brandes)) #' @export node_is_max <- function(node_measure, ranks = 1){ if(!inherits(node_measure, "node_measure")) @@ -429,7 +468,7 @@ node_is_max <- function(node_measure, ranks = 1){ #' @rdname mark_select #' @examples -#' #node_is_min(migraph::node_degree(ison_brandes)) +#' #node_is_min(node_by_degree(ison_brandes)) #' @export node_is_min <- function(node_measure, ranks = 1){ if(!inherits(node_measure, "node_measure")) diff --git a/R/mark_ties.R b/R/mark_ties.R index 80db090..d237656 100644 --- a/R/mark_ties.R +++ b/R/mark_ties.R @@ -101,7 +101,7 @@ tie_is_path <- function(.data, from, to, all_paths = FALSE){ # Triangular properties #### -#' Marking ties based on structural properties +#' Marking ties based on triangular properties #' #' @description #' These functions return logical vectors the length of the ties @@ -114,7 +114,7 @@ tie_is_path <- function(.data, from, to, all_paths = FALSE){ #' and fully reciprocated. #' - `tie_is_imbalanced()` marks ties that are part of imbalanced triads. #' - `tie_is_transitive()` marks ties that complete transitive closure. -#' - `tie_is_forbidden()` marks ties that complete forbidden triads. +# #' - `tie_is_forbidden()` marks ties that complete forbidden triads. #' #' They are most useful in highlighting parts of the network that #' are cohesively connected. @@ -218,37 +218,39 @@ tie_is_simmelian <- function(.data){ make_tie_mark(out, .data) } -#' @rdname mark_triangles -#' @examples -#' generate_random(8, directed = TRUE) %>% -#' mutate_ties(forbid = tie_is_forbidden()) -#' #graphr(edge_color = "forbid") -#' @export -tie_is_forbidden <- function(.data){ - .data <- manynet::expect_ties(.data) - dists <- igraph::distances(.data, mode = "out")==2 - ends <- which(dists * t(dists)==1, arr.ind = TRUE) - ends <- t(apply(ends, 1, function(x) sort(x))) - ends <- ends[!duplicated(ends),] - tris <- apply(ends, 1, function(x){ - y <- unlist(igraph::all_shortest_paths(.data, x[1], x[2], mode = "out")$res) - y <- matrix(y, ncol = 3, byrow = TRUE) - y <- do.call("paste", c(as.data.frame(y)[, , drop = FALSE], sep = "-")) - z <- unlist(igraph::all_shortest_paths(.data, x[1], x[2], mode = "in")$res) - z <- matrix(z, ncol = 3, byrow = TRUE) - z <- do.call("paste", c(as.data.frame(z)[, , drop = FALSE], sep = "-")) - as.numeric(unlist(strsplit(y[y %in% z], "-"))) - }) - out <- matrix(unlist(tris), ncol = 3, byrow = TRUE) - out <- unique(c(apply(out, 1, function(x){ - c(paste0(x[1],"->",x[2]), - paste0(x[2],"->",x[1]), - paste0(x[2],"->",x[3]), - paste0(x[3],"->",x[2])) - } ))) - out <- names(tie_is_reciprocated(.data)) %in% out - make_tie_mark(out, .data) -} +# #' @rdname mark_triangles +# #' @examples +# #' generate_random(8, directed = TRUE) %>% +# #' mutate_ties(forbid = tie_is_forbidden()) +# #' #graphr(edge_color = "forbid") +# #' @export +# tie_is_forbidden <- function(.data){ +# .data <- manynet::expect_ties(.data) +# if(!manynet::is_weighted(.data)) +# snet_abort("This function only works with weighted networks.") +# dists <- igraph::distances(.data, mode = "out")==2 +# ends <- which(dists * t(dists)==1, arr.ind = TRUE) +# ends <- t(apply(ends, 1, function(x) sort(x))) +# ends <- ends[!duplicated(ends),] +# tris <- apply(ends, 1, function(x){ +# y <- unlist(igraph::all_shortest_paths(.data, x[1], x[2], mode = "out")$res) +# y <- matrix(y, ncol = 3, byrow = TRUE) +# y <- do.call("paste", c(as.data.frame(y)[, , drop = FALSE], sep = "-")) +# z <- unlist(igraph::all_shortest_paths(.data, x[1], x[2], mode = "in")$res) +# z <- matrix(z, ncol = 3, byrow = TRUE) +# z <- do.call("paste", c(as.data.frame(z)[, , drop = FALSE], sep = "-")) +# as.numeric(unlist(strsplit(y[y %in% z], "-"))) +# }) +# out <- matrix(unlist(tris), ncol = 3, byrow = TRUE) +# out <- unique(c(apply(out, 1, function(x){ +# c(paste0(x[1],"->",x[2]), +# paste0(x[2],"->",x[1]), +# paste0(x[2],"->",x[3]), +# paste0(x[3],"->",x[2])) +# } ))) +# out <- names(tie_is_reciprocated(.data)) %in% out +# make_tie_mark(out, .data) +# } #' @rdname mark_triangles #' @examples @@ -313,7 +315,7 @@ tie_is_imbalanced <- function(.data){ # Selection properties #### -#' Marking ties for selection based on measures +#' Marking ties based on measures #' #' @description #' These functions return logical vectors the length of the ties in a network: @@ -335,13 +337,13 @@ tie_is_random <- function(.data, size = 1){ n <- manynet::net_ties(.data) out <- rep(FALSE, n) out[sample.int(n, size)] <- TRUE - make_node_mark(out, .data) + make_tie_mark(out, .data) } #' @rdname mark_tie_select #' @param tie_measure An object created by a `tie_` measure. #' @examples -#' # tie_is_max(migraph::tie_betweenness(ison_brandes)) +#' # tie_is_max(tie_by_betweenness(ison_brandes)) #' @export tie_is_max <- function(tie_measure){ out <- as.numeric(tie_measure) == max(as.numeric(tie_measure)) @@ -351,7 +353,7 @@ tie_is_max <- function(tie_measure){ #' @rdname mark_tie_select #' @examples -#' #tie_is_min(migraph::tie_betweenness(ison_brandes)) +#' #tie_is_min(tie_by_betweenness(ison_brandes)) #' @export tie_is_min <- function(tie_measure){ out <- as.numeric(tie_measure) == min(as.numeric(tie_measure)) diff --git a/R/measure_centrality.R b/R/measure_centrality.R index 6fa51d1..60b5db4 100644 --- a/R/measure_centrality.R +++ b/R/measure_centrality.R @@ -29,6 +29,7 @@ #' @name measure_central_degree #' @family centrality #' @family measures +#' @family degree #' @inheritParams mark_nodes #' @param normalized Logical scalar, whether the centrality scores are normalized. #' Different denominators are used depending on whether the object is one-mode or two-mode, @@ -81,7 +82,7 @@ #' _Social Networks_ 32, 245-251. #' \doi{10.1016/j.socnet.2010.03.006} #' @examples -#' node_degree(ison_southern_women) +#' node_by_degree(ison_southern_women) #' @return Depending on how and what kind of an object is passed to the function, #' the function will return a `tidygraph` object where the nodes have been updated NULL @@ -248,11 +249,11 @@ net_by_degree <- function(.data, normalized = TRUE, out$nodes1 <- sum(max(allcent[!mode]) - allcent)/((nrow(mat) + ncol(mat))*ncol(mat) - 2*(ncol(mat) + nrow(mat) - 1)) out$nodes2 <- sum(max(allcent[mode]) - allcent)/((nrow(mat) + ncol(mat))*nrow(mat) - 2*(ncol(mat) + nrow(mat) - 1)) } else if (normalized) { - allcent <- node_by_degree(mat, normalized = TRUE) + allcent <- node_by_degree(.data, normalized = TRUE) out$nodes1 <- sum(max(allcent[!mode]) - allcent)/((nrow(mat) + ncol(mat) - 1) - (ncol(mat) - 1) / nrow(mat) - (ncol(mat) + nrow(mat) - 1)/nrow(mat)) out$nodes2 <- sum(max(allcent[mode]) - allcent)/((ncol(mat) + nrow(mat) - 1) - (nrow(mat) - 1) / ncol(mat) - (nrow(mat) + ncol(mat) - 1)/ncol(mat)) } - } else if (direction == "in") { + } else if (direction == "in" | direction == "out") { out$nodes1 <- sum(max(rowSums(mat)) - rowSums(mat))/((ncol(mat) - 1)*(nrow(mat) - 1)) out$nodes2 <- sum(max(colSums(mat)) - colSums(mat))/((ncol(mat) - 1)*(nrow(mat) - 1)) } @@ -303,6 +304,7 @@ net_by_indegree <- function(.data, normalized = TRUE){ #' @name measure_central_between #' @family centrality #' @family measures +#' @family betweenness #' @inheritParams measure_central_degree #' @param cutoff The maximum path length to consider when calculating betweenness. #' If negative or NULL (the default), there's no limit to the path lengths considered. @@ -439,7 +441,7 @@ node_by_stress <- function(.data, normalized = TRUE){ #' @rdname measure_central_between #' @importFrom igraph edge_betweenness #' @examples -#' (tb <- tie_betweenness(ison_adolescents)) +#' (tb <- tie_by_betweenness(ison_adolescents)) #' ison_adolescents %>% mutate_ties(weight = tb) #' @export tie_by_betweenness <- function(.data, normalized = TRUE){ @@ -454,7 +456,7 @@ tie_by_betweenness <- function(.data, normalized = TRUE){ #' @rdname measure_central_between #' @examples -#' net_betweenness(ison_southern_women, direction = "in") +#' net_by_betweenness(ison_southern_women, direction = "in") #' @export net_by_betweenness <- function(.data, normalized = TRUE, direction = c("all", "out", "in")) { @@ -515,24 +517,27 @@ net_by_betweenness <- function(.data, normalized = TRUE, #' These functions calculate common closeness-related centrality measures #' that rely on path-length for one- and two-mode networks: #' -#' - `node_closeness()` measures the closeness centrality of nodes in a +#' - `node_by_closeness()` measures the closeness centrality of nodes in a #' network. -#' - `node_reach()` measures nodes' reach centrality, -#' or how many nodes they can reach within _k_ steps. -#' - `node_harmonic()` measures nodes' harmonic centrality or valued +#' - `node_by_harmonic()` measures nodes' harmonic centrality or valued #' centrality, which is thought to behave better than reach centrality #' for disconnected networks. -#' - `node_information()` measures nodes' information centrality or +#' - `node_by_reach()` measures nodes' reach centrality, +#' or how many nodes they can reach within _k_ steps. +#' - `node_by_information()` measures nodes' information centrality or #' current-flow closeness centrality. -#' - `node_eccentricity()` measures nodes' eccentricity or maximum distance +#' - `node_by_eccentricity()` measures nodes' eccentricity or maximum distance #' from another node in the network. -#' - `node_distance()` measures nodes' geodesic distance from or to a +#' - `node_by_distance()` measures nodes' geodesic distance from or to a #' given node. -#' - `tie_closeness()` measures the closeness of each tie to other ties +#' - `node_by_vitality()` measures a network's closeness vitality centrality, +#' or the change in closeness centrality between networks with and without a +#' given node. +#' - `tie_by_closeness()` measures the closeness of each tie to other ties #' in the network. -#' - `net_closeness()` measures a network's closeness centralization. -#' - `net_reach()` measures a network's reach centralization. -#' - `net_harmonic()` measures a network's harmonic centralization. +#' - `net_by_closeness()` measures a network's closeness centralization. +#' - `net_by_reach()` measures a network's reach centralization. +#' - `net_by_harmonic()` measures a network's harmonic centralization. #' #' All measures attempt to use as much information as they are offered, #' including whether the networks are directed, weighted, or multimodal. @@ -771,7 +776,8 @@ node_by_vitality <- function(.data, normalized = TRUE){ .data <- manynet::expect_nodes(.data) .data <- manynet::as_igraph(.data) out <- vapply(manynet::snet_progress_nodes(.data), function(x){ - sum(igraph::distances(.data)) - sum(igraph::distances(manynet::delete_nodes(.data, x))) + sum(igraph::distances(.data)) - + sum(igraph::distances(manynet::delete_nodes(.data, x))) }, FUN.VALUE = numeric(1)) if(normalized) out <- out/max(out) make_node_measure(out, .data) @@ -797,7 +803,7 @@ node_by_vitality <- function(.data, normalized = TRUE){ node_by_randomwalk <- function(.data, normalized = TRUE){ .data <- manynet::expect_nodes(.data) # adjacency and degree matrices - A <- manynet::as_matrix(.data) + A <- manynet::as_matrix(manynet::to_multilevel(.data)) degs <- node_by_deg(.data) D <- diag(degs) @@ -847,7 +853,7 @@ node_by_randomwalk <- function(.data, normalized = TRUE){ #' @rdname measure_central_close #' @examples -#' (ec <- tie_closeness(ison_adolescents)) +#' (ec <- tie_by_closeness(ison_adolescents)) #' ison_adolescents %>% mutate_ties(weight = ec) #' @export tie_by_closeness <- function(.data, normalized = TRUE){ @@ -930,7 +936,7 @@ net_by_reach <- function(.data, normalized = TRUE, cutoff = 2){ reaches <- node_by_reach(.data, normalized = FALSE, cutoff = cutoff) out <- sum(max(reaches) - reaches) if(normalized) out <- out / sum(manynet::net_nodes(.data) - reaches) - make_network_measure(out, .data) + make_network_measure(out, .data, call = deparse(sys.call())) } #' @rdname measure_central_close @@ -940,7 +946,7 @@ net_by_harmonic <- function(.data, normalized = TRUE, cutoff = 2){ harm <- node_by_harmonic(.data, normalized = FALSE, cutoff = cutoff) out <- sum(max(harm) - harm) if(normalized) out <- out / sum(manynet::net_nodes(.data) - harm) - make_network_measure(out, .data) + make_network_measure(out, .data, call = deparse(sys.call())) } # Eigenvector-like centralities #### @@ -1071,6 +1077,11 @@ node_by_power <- function(.data, normalized = TRUE, scale = FALSE, exponent = 1) manynet::tie_weights(.data), NA) graph <- manynet::as_igraph(.data) + if(var(node_by_deg(graph))==0){ + manynet::snet_minor_info("All nodes have the same degree, so power centrality equals degree centrality.") + exponent <- 0 + } + # Do the calculations if (!manynet::is_twomode(graph)){ out <- igraph::power_centrality(graph = graph, @@ -1144,7 +1155,7 @@ node_by_alpha <- function(.data, alpha = 0.85){ #' @export node_by_pagerank <- function(.data){ .data <- manynet::expect_nodes(.data) - make_node_measure(igraph::page_rank(manynet::as_igraph(.data)), + make_node_measure(igraph::page_rank(manynet::as_igraph(.data))$vector, .data) } @@ -1158,7 +1169,7 @@ node_by_pagerank <- function(.data){ #' @export node_by_authority <- function(.data){ .data <- manynet::expect_nodes(.data) - make_node_measure(igraph::authority_score(manynet::as_igraph(.data))$vector, + make_node_measure(igraph::hits_scores(manynet::as_igraph(.data))$authority, .data) } @@ -1166,7 +1177,7 @@ node_by_authority <- function(.data){ #' @export node_by_hub <- function(.data){ .data <- manynet::expect_nodes(.data) - make_node_measure(igraph::hub_score(manynet::as_igraph(.data))$vector, + make_node_measure(igraph::hits_scores(manynet::as_igraph(.data))$hub, .data) } diff --git a/R/measure_change.R b/R/measure_change.R new file mode 100644 index 0000000..a8471ee --- /dev/null +++ b/R/measure_change.R @@ -0,0 +1,90 @@ +# Change #### + +#' Measures of network change +#' @description +#' These functions measure certain topological features of networks: +#' +#' - `net_by_waves()` measures the number of waves in longitudinal network data. +#' - `net_by_change()` measures the Hamming distance between two or more networks. +#' - `net_by_stability()` measures the Jaccard index of stability between two or more networks. +#' - `net_by_correlation()` measures the product-moment correlation between two networks. +#' +#' These `net_*()` functions return a numeric vector the length of the number +#' of networks minus one. E.g., the periods between waves. +#' @inheritParams mark_nodes +#' @name measure_periods +#' @family measures +NULL + +#' @rdname measure_periods +#' @export +net_by_waves <- function(.data){ + .data <- manynet::expect_nodes(.data) + tie_waves <- length(unique(manynet::tie_attribute(.data, "wave"))) + if(manynet::is_changing(.data)){ + chltime <- manynet::as_changelist(.data)$time + chg_waves <- (max(chltime)+1) - max(min(chltime)-1, 0) + } else chg_waves <- 1 + make_network_measure(max(tie_waves, chg_waves), + .data, call = deparse(sys.call())) +} + +#' @rdname measure_periods +#' @param object2 A network object. +#' @export +net_by_change <- function(.data, object2){ + net <- manynet::expect_nodes(.data) + if(!missing(object2)){ + net <- list(net, object2) + } else if(manynet::is_longitudinal(net)){ + net <- manynet::to_waves(net) + } + if(!manynet::is_list(net)) + manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") + periods <- length(net)-1 + out <- vapply(seq.int(periods), function(x){ + net1 <- manynet::as_matrix(net[[x]]) + net2 <- manynet::as_matrix(net[[x+1]]) + sum(net1 != net2) + }, FUN.VALUE = numeric(1)) + make_network_measure(out, .data, call = deparse(sys.call())) +} + +#' @rdname measure_periods +#' @export +net_by_stability <- function(.data, object2){ + net <- manynet::expect_nodes(.data) + if(!missing(object2)){ + net <- list(net, object2) + } else if(manynet::is_longitudinal(net)){ + net <- manynet::to_waves(net) + } + if(!manynet::is_list(net)) + manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") + periods <- length(net)-1 + out <- vapply(seq.int(periods), function(x){ + net1 <- manynet::as_matrix(net[[x]]) + net2 <- manynet::as_matrix(net[[x+1]]) + n11 <- sum(net1 * net2) + n01 <- sum(net1==0 * net2) + n10 <- sum(net1 * net2==0) + n11 / (n01 + n10 + n11) + }, FUN.VALUE = numeric(1)) + make_network_measure(out, .data, call = deparse(sys.call())) +} + +#' @rdname measure_periods +#' @export +net_by_correlation <- function(.data, object2){ + .data <- manynet::expect_nodes(.data) + comp1 <- manynet::as_matrix(.data) + comp2 <- manynet::as_matrix(object2) + if(!manynet::is_complex(.data)){ + diag(comp1) <- NA + } + if(!manynet::is_directed(.data)){ + comp1[upper.tri(comp1)] <- NA + } + out <- cor(c(comp1), c(comp2), use = "complete.obs") + make_network_measure(out, .data, call = deparse(sys.call())) +} \ No newline at end of file diff --git a/R/measure_closure.R b/R/measure_closure.R index 54fe819..a190f7a 100644 --- a/R/measure_closure.R +++ b/R/measure_closure.R @@ -120,13 +120,13 @@ net_by_equivalency <- function(.data) { #' @rdname measure_closure #' @examples -#' node_by_equivalency(ison_southern_women) +#' # node_by_equivalency(ison_southern_women) #' @export node_by_equivalency <- function(.data) { .data <- manynet::expect_nodes(.data) # if(is_weighted(.data)) # snet_info("Using unweighted form of the network.") - out <- vapply(manynet::snet_progress_along(seq_nodes(.data)), function(i){ + out <- vapply(manynet::snet_progress_seq(.data), function(i){ threepaths <- igraph::all_simple_paths(.data, i, cutoff = 3, mode = "all") onepaths <- threepaths[vapply(threepaths, length, diff --git a/R/measure_diffusion.R b/R/measure_diffusion.R index 75442a8..0c55e4a 100644 --- a/R/measure_diffusion.R +++ b/R/measure_diffusion.R @@ -216,7 +216,7 @@ net_by_immunity <- function(.data, normalized = TRUE){ #' @examples #' smeg <- generate_smallworld(15, 0.025) #' smeg_diff <- play_diffusion(smeg) -#' net_infection_complete(smeg_diff) +#' net_by_infection_complete(smeg_diff) #' @export net_by_infection_complete <- function(.data){ diff_model <- manynet::as_diffusion(.data) diff --git a/R/measure_features.R b/R/measure_features.R index 7990b4e..703531d 100644 --- a/R/measure_features.R +++ b/R/measure_features.R @@ -4,22 +4,22 @@ #' @description #' These functions measure certain topological features of networks: #' -#' - `net_core()` measures the correlation between a network +#' - `net_by_core()` measures the correlation between a network #' and a core-periphery model with the same dimensions. -#' - `net_richclub()` measures the rich-club coefficient of a network. -#' - `net_factions()` measures the correlation between a network +#' - `net_by_richclub()` measures the rich-club coefficient of a network. +#' - `net_by_factions()` measures the correlation between a network #' and a component model with the same dimensions. #' If no 'membership' vector is given for the data, #' `node_partition()` is used to partition nodes into two groups. -#' - `net_modularity()` measures the modularity of a network +#' - `net_by_modularity()` measures the modularity of a network #' based on nodes' membership in defined clusters. -#' - `net_smallworld()` measures the small-world coefficient for one- or +#' - `net_by_smallworld()` measures the small-world coefficient for one- or #' two-mode networks. Small-world networks can be highly clustered and yet #' have short path lengths. -#' - `net_scalefree()` measures the exponent of a fitted +#' - `net_by_scalefree()` measures the exponent of a fitted #' power-law distribution. An exponent between 2 and 3 usually indicates #' a power-law distribution. -#' - `net_balance()` measures the structural balance index on +#' - `net_by_balance()` measures the structural balance index on #' the proportion of balanced triangles, #' ranging between `0` if all triangles are imbalanced and #' `1` if all triangles are balanced. @@ -106,8 +106,8 @@ net_by_richclub <- function(.data){ .data <- manynet::expect_nodes(.data) coefs <- vector() temp <- .data - for(k in seq_len(max(node_by_degree(temp, normalized = FALSE)))){ - richclub <- manynet::to_subgraph(temp, node_by_degree(temp, normalized = FALSE) >= k) + for(k in seq_len(max(node_by_deg(temp)))){ + richclub <- manynet::to_subgraph(temp, node_by_deg(temp) >= k) nk <- manynet::net_nodes(richclub) ek <- ifelse(manynet::is_directed(temp), manynet::net_ties(richclub), @@ -138,8 +138,9 @@ net_by_richclub <- function(.data){ } coefs[is.nan(coefs)] <- 1 - out <- coefs[.elbow_finder(seq_along(coefs), coefs)] - # max(coefs, na.rm = TRUE) + if(length(which(coefs == 1)) == 0) out <- 0 else + out <- coefs[.elbow_finder(seq_along(coefs), coefs)] + # max(coefs, na.rm = TRUE) make_network_measure(out, .data, call = deparse(sys.call())) } @@ -185,9 +186,9 @@ net_by_factions <- function(.data, #' The higher this parameter, the more smaller communities will be privileged. #' The lower this parameter, the fewer larger communities are likely to be found. #' @examples -#' net_modularity(ison_adolescents, +#' net_by_modularity(ison_adolescents, #' node_in_partition(ison_adolescents)) -#' net_modularity(ison_southern_women, +#' net_by_modularity(ison_southern_women, #' node_in_partition(ison_southern_women)) #' @references #' ## On modularity @@ -248,7 +249,7 @@ net_by_modularity <- function(.data, #' with the same dimensions. #' \eqn{SWI} also ranges between 0 and 1 with the same interpretation, #' but where there may not be a network for which \eqn{SWI = 1}. -#' @seealso [net_transitivity()] and [net_equivalency()] +#' @seealso [net_by_transitivity()] and [net_by_equivalency()] #' for how clustering is calculated #' @references #' ## On small-worldliness @@ -295,12 +296,12 @@ net_by_smallworld <- function(.data, } } - lo <- manynet::net_length(.data) + lo <- net_by_length(.data) lr <- mean(vapply(1:times, - function(x) manynet::net_length(manynet::generate_random(.data)), + function(x) net_by_length(manynet::generate_random(.data)), FUN.VALUE = numeric(1))) if(method == "SWI"){ - ll <- manynet::net_length(manynet::create_ring(.data)) + ll <- net_by_length(manynet::create_ring(.data)) } out <- switch(method, @@ -366,7 +367,7 @@ net_by_scalefree <- function(.data){ #' _Psychological Review_, 63(5): 277-293. #' \doi{10.1037/h0046049} #' @examples -#' net_by_balance(fict_marvel) +#' net_by_balance(to_uniplex(fict_marvel, "relationship")) #' @export net_by_balance <- function(.data) { @@ -431,87 +432,3 @@ net_by_balance <- function(.data) { call = deparse(sys.call())) } - -# Change #### - -#' Measures of network change -#' @description -#' These functions measure certain topological features of networks: -#' -#' - `net_waves()` measures the number of waves in longitudinal network data. -#' - `net_change()` measures the Hamming distance between two or more networks. -#' - `net_stability()` measures the Jaccard index of stability between two or more networks. -#' - `net_correlation()` measures the product-moment correlation between two networks. -#' -#' These `net_*()` functions return a numeric vector the length of the number -#' of networks minus one. E.g., the periods between waves. -#' @inheritParams mark_nodes -#' @name measure_periods -#' @family measures -NULL - -#' @rdname measure_periods -#' @export -net_by_waves <- function(.data){ - .data <- manynet::expect_nodes(.data) - tie_waves <- length(unique(manynet::tie_attribute(.data, "wave"))) - if(manynet::is_changing(.data)){ - chltime <- manynet::as_changelist(.data)$time - chg_waves <- (max(chltime)+1) - max(min(chltime)-1, 0) - } else chg_waves <- 0 - max(tie_waves, chg_waves) -} - -#' @rdname measure_periods -#' @param object2 A network object. -#' @export -net_by_change <- function(.data, object2){ - .data <- manynet::expect_nodes(.data) - if(manynet::is_list(.data)){ - - } else if(!missing(object2)){ - .data <- list(.data, object2) - } else manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") - periods <- length(.data)-1 - vapply(seq.int(periods), function(x){ - net1 <- manynet::as_matrix(.data[[x]]) - net2 <- manynet::as_matrix(.data[[x+1]]) - sum(net1 != net2) - }, FUN.VALUE = numeric(1)) -} - -#' @rdname measure_periods -#' @export -net_by_stability <- function(.data, object2){ - .data <- manynet::expect_nodes(.data) - if(manynet::is_list(.data)){ - - } else if(!missing(object2)){ - .data <- list(.data, object2) - } else manynet::snet_abort("`.data` must be a list of networks or a second network must be provided.") - periods <- length(.data)-1 - vapply(seq.int(periods), function(x){ - net1 <- manynet::as_matrix(.data[[x]]) - net2 <- manynet::as_matrix(.data[[x+1]]) - n11 <- sum(net1 * net2) - n01 <- sum(net1==0 * net2) - n10 <- sum(net1 * net2==0) - n11 / (n01 + n10 + n11) - }, FUN.VALUE = numeric(1)) -} - -#' @rdname measure_periods -#' @export -net_by_correlation <- function(.data, object2){ - .data <- manynet::expect_nodes(.data) - comp1 <- manynet::as_matrix(.data) - comp2 <- manynet::as_matrix(object2) - if(!manynet::is_complex(.data)){ - diag(comp1) <- NA - } - if(!manynet::is_directed(.data)){ - comp1[upper.tri(comp1)] <- NA - } - out <- cor(c(comp1), c(comp2), use = "complete.obs") - make_network_measure(out, .data, call = deparse(sys.call())) -} \ No newline at end of file diff --git a/R/measure_heterogeneity.R b/R/measure_heterogeneity.R index c8614a8..3f3280c 100644 --- a/R/measure_heterogeneity.R +++ b/R/measure_heterogeneity.R @@ -1,3 +1,5 @@ +# Diversity #### + #' Measures of network diversity #' #' @description @@ -136,7 +138,7 @@ node_by_richness <- function(.data, attribute){ #' Princeton: Princeton University Press. #' \doi{10.1515/9781400835140} #' @examples -#' marvel_friends <- to_unsigned(fict_marvel, "positive") +#' marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") #' net_by_diversity(marvel_friends, "Gender") #' net_by_diversity(marvel_friends, "Appearances") #' @export @@ -209,7 +211,31 @@ node_by_diversity <- function(.data, attribute, make_node_measure(out, .data) } -#' @rdname measure_heterogeneity +# Assortativity #### + +#' Measures of network assortativity +#' +#' @description +#' These functions offer ways to measure the distribution or assortativity +#' of ties in a network: +#' +#' - `net_heterophily()` measures how embedded nodes in the network +#' are within groups of nodes with the same attribute. +#' - `node_heterophily()` measures each node's embeddedness within groups +#' of nodes with the same attribute. +#' - `net_assortativity()` measures the degree assortativity in a network. +#' - `net_spatial()` measures the spatial association/autocorrelation +#' (global Moran's I) in a network. +#' +#' @inheritParams mark_nodes +#' @inheritParams measure_heterogeneity +#' @param attribute Name of a nodal attribute or membership vector +#' to use as categories for the diversity measure. +#' @name measure_assortativity +#' @family measures +NULL + +#' @rdname measure_assortativity #' @section Homophily: #' Given a partition of a network into a number of mutually exclusive groups then #' The E-I index is the number of ties between (or _external_) nodes @@ -230,6 +256,7 @@ node_by_diversity <- function(.data, attribute, #' _Annual Review of Sociology_, 27(1): 415-444. #' \doi{10.1146/annurev.soc.27.1.415} #' @examples +#' marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") #' net_by_heterophily(marvel_friends, "Gender") #' net_by_heterophily(marvel_friends, "Attractive") #' @export @@ -249,7 +276,7 @@ net_by_heterophily <- function(.data, attribute){ make_network_measure(ei, .data, call = deparse(sys.call())) } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @examples #' node_by_heterophily(marvel_friends, "Gender") #' node_by_heterophily(marvel_friends, "Attractive") @@ -275,7 +302,7 @@ node_by_heterophily <- function(.data, attribute){ make_node_measure(ei, .data) } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @examples #' net_by_homophily(marvel_friends, "Gender") #' @export @@ -373,14 +400,14 @@ attr_mode <- function(.data, attribute){ } else NULL } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @export node_by_homophily <- function(.data, attribute, method = c("ie","ei","yule","geary")){ .data <- manynet::expect_nodes(.data) - if (length(attribute) == 1 && is.character(attribute)) { - attribute <- manynet::node_attribute(.data, attribute) - } + # if (length(attribute) == 1 && is.character(attribute)) { + # attribute <- manynet::node_attribute(.data, attribute) + # } method <- match.arg(method) if(is.numeric(attribute) && method %in% c("ie","ei","yule")){ manynet::snet_info("{.val {method}} index is not appropriate for numeric attributes.") @@ -400,7 +427,7 @@ node_by_homophily <- function(.data, attribute, make_node_measure(out, .data) } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @importFrom igraph assortativity_degree #' @references #' ## On assortativity @@ -418,7 +445,7 @@ net_by_assortativity <- function(.data){ .data, call = deparse(sys.call())) } -#' @rdname measure_heterogeneity +#' @rdname measure_assortativity #' @references #' ## On spatial autocorrelation #' Moran, Patrick Alfred Pierce. 1950. diff --git a/R/measure_hierarchy.R b/R/measure_hierarchy.R index 25e5e6f..fd4fc4e 100644 --- a/R/measure_hierarchy.R +++ b/R/measure_hierarchy.R @@ -1,18 +1,25 @@ -#' Graph theoretic dimensions of hierarchy +# Motifs #### + +#' Motifs of hierarchy #' #' @description -#' These functions, together with `net_reciprocity()`, are used jointly to -#' measure how hierarchical a network is: -#' +#' This function collects the measures of hierarchy into a single motif, +#' which can be used to compare the relative hierarchy of different networks. +#' The measures of hierarchy are: #' - `net_connectedness()` measures the proportion of dyads in the network -#' that are reachable to one another, +#' that are reachable to one another, #' or the degree to which network is a single component. #' - `net_efficiency()` measures the Krackhardt efficiency score. -#' - `net_upperbound()` measures the Krackhardt (least) upper bound score. +#' - `net_upperbound()` measures the Krackhardt (least) upper bound +#' score. +#' - `net_reciprocity()` measures the proportion of ties in the network that +#' are reciprocated, +#' which is a measure of the degree to which the network is non-hierarchical. #' #' @inheritParams mark_nodes -#' @name measure_hierarchy -#' @family measures +#' @name motif_hierarchy +#' @family motifs +#' @family hierarchy #' @references #' ## On hierarchy #' Krackhardt, David. 1994. @@ -25,15 +32,12 @@ #' _Social Networks_, 34: 159-163. #' \doi{10.1016/j.socnet.2011.10.006} #' @examples -#' net_connectedness(ison_networkers) -#' 1 - net_reciprocity(ison_networkers) -#' net_efficiency(ison_networkers) -#' net_upperbound(ison_networkers) +#' net_x_hierarchy(ison_networkers) NULL -#' @rdname measure_hierarchy +#' @rdname motif_hierarchy #' @export -net_by_hierarchy <- function(.data){ +net_x_hierarchy <- function(.data){ .data <- manynet::expect_nodes(.data) out <- data.frame(Connectedness = net_by_connectedness(.data), InvReciprocity = 1 - net_by_reciprocity(.data), @@ -42,6 +46,42 @@ net_by_hierarchy <- function(.data){ make_network_motif(out, .data) } +# Measures #### + +#' Measures of hierarchy +#' +#' @description +#' These functions, together with `net_reciprocity()`, are used jointly to +#' measure how hierarchical a network is: +#' +#' - `net_connectedness()` measures the proportion of dyads in the network +#' that are reachable to one another, +#' or the degree to which network is a single component. +#' - `net_efficiency()` measures the Krackhardt efficiency score. +#' - `net_upperbound()` measures the Krackhardt (least) upper bound score. +#' +#' @inheritParams mark_nodes +#' @name measure_hierarchy +#' @family measures +#' @family hierarchy +#' @references +#' ## On hierarchy +#' Krackhardt, David. 1994. +#' Graph theoretical dimensions of informal organizations. +#' In Carley and Prietula (eds) _Computational Organizational Theory_, +#' Hillsdale, NJ: Lawrence Erlbaum Associates. Pp. 89-111. +#' +#' Everett, Martin, and David Krackhardt. 2012. +#' “A second look at Krackhardt's graph theoretical dimensions of informal organizations.” +#' _Social Networks_, 34: 159-163. +#' \doi{10.1016/j.socnet.2011.10.006} +#' @examples +#' net_by_connectedness(ison_networkers) +#' 1 - net_by_reciprocity(ison_networkers) +#' net_by_efficiency(ison_networkers) +#' net_by_upperbound(ison_networkers) +NULL + #' @rdname measure_hierarchy #' @export net_by_connectedness <- function(.data){ diff --git a/R/measure_holes.R b/R/measure_holes.R index 8225019..1ca28ac 100644 --- a/R/measure_holes.R +++ b/R/measure_holes.R @@ -51,7 +51,7 @@ node_by_bridges <- function(.data){ g <- manynet::as_igraph(.data) .inc <- NULL out <- vapply(igraph::V(g), function(ego){ - length(igraph::E(g)[.inc(ego) & manynet::tie_is_bridge(g)==1]) + length(igraph::E(g)[.inc(ego) & tie_is_bridge(g)==1]) }, FUN.VALUE = numeric(1)) make_node_measure(out, .data) } @@ -190,8 +190,7 @@ node_by_constraint <- function(.data) { nodes = igraph::V(.data), weights = NULL) } - res <- make_node_measure(res, .data) - res + make_node_measure(res, .data) } #' @rdname measure_holes @@ -246,5 +245,5 @@ tie_by_cohesion <- function(.data){ neigh_nodes <- length(unique(c(neigh1, neigh2)))-2 shared_nodes / neigh_nodes } ) - make_node_measure(out, .data) + make_tie_measure(out, .data) } diff --git a/R/member_community.R b/R/member_community.R index 065f465..feb08d8 100644 --- a/R/member_community.R +++ b/R/member_community.R @@ -1,6 +1,6 @@ -# Non-hierarchical community partitioning #### +# Non-hierarchical community clustering #### -#' Non-hierarchical community partitioning algorithms +#' Non-hierarchical community clustering algorithms #' #' @description #' These functions offer algorithms for partitioning @@ -30,6 +30,7 @@ #' @inheritParams mark_nodes #' @name member_community_non #' @family memberships +#' @family community NULL #' @rdname member_community_non @@ -47,7 +48,7 @@ node_in_community <- function(.data){ # don't use node_in_betweenness because slow and poorer quality to optimal manynet::snet_success("{.fn node_in_optimal} available and", "will return the highest modularity partition.") - manynet::node_in_optimal(.data) + netrics::node_in_optimal(.data) } else { manynet::snet_info("Excluding {.fn node_in_optimal} because network rather large.") poss_algs <- c("node_in_infomap", @@ -355,9 +356,9 @@ node_in_leiden <- function(.data, resolution = 1){ make_node_member(out, .data) } -# Hierarchical community partitioning #### +# Hierarchical community clustering #### -#' Hierarchical community partitioning algorithms +#' Hierarchical community clustering algorithms #' #' @description #' These functions offer algorithms for hierarchically clustering @@ -379,6 +380,7 @@ node_in_leiden <- function(.data, resolution = 1){ #' @inheritParams member_community_non #' @name member_community_hier #' @family memberships +#' @family community NULL #' @rdname member_community_hier diff --git a/R/member_components.R b/R/member_components.R index 556bbe2..17e2cfb 100644 --- a/R/member_components.R +++ b/R/member_components.R @@ -1,4 +1,4 @@ -#' Component partitioning algorithms +#' Membership in components #' #' @description #' These functions create a vector of nodes' memberships in components: diff --git a/R/member_core.R b/R/member_core.R index 66c7356..f3dfae8 100644 --- a/R/member_core.R +++ b/R/member_core.R @@ -2,8 +2,6 @@ #' @description #' These functions identify nodes belonging to (some level of) the core of a network: #' -#' - `node_is_universal()` identifies whether nodes are adjacent to all other -#' nodes in the network. #' - `node_is_core()` identifies whether nodes belong to the core of the #' network, as opposed to the periphery. #' - `node_in_core()` categorizes nodes into two or more core/periphery @@ -23,24 +21,6 @@ #' @family memberships NULL -#' @rdname mark_core -#' @section Universal/dominating node: -#' A universal node is adjacent to all other nodes in the network. -#' It is also sometimes called the dominating vertex because it represents -#' a one-element dominating set. -#' A network with a universal node is called a cone, and its universal node -#' is called the apex of the cone. -#' A classic example of a cone is a star graph, -#' but friendship, wheel, and threshold graphs are also cones. -#' @examples -#' node_is_universal(create_star(11)) -#' @export -node_is_universal <- function(.data){ - .data <- manynet::expect_nodes(.data) - net <- manynet::to_undirected(manynet::to_unweighted(.data)) - make_node_mark(node_by_deg(net)==(manynet::net_nodes(net)-1), .data) -} - #' @rdname mark_core #' @section Core-periphery: #' This function is used to identify which nodes should belong to the core, diff --git a/R/member_equivalence.R b/R/member_equivalence.R index f619d59..d357218 100644 --- a/R/member_equivalence.R +++ b/R/member_equivalence.R @@ -68,6 +68,7 @@ node_in_equivalence <- function(.data, census, strict = k_strict(hc, .data), elbow = k_elbow(hc, .data, census, range), silhouette = k_silhouette(hc, .data, range)) + if(length(k)==0) k <- 1 # in the case of all nodes being in the same cluster out <- make_node_member(stats::cutree(hc, k), .data) attr(out, "hc") <- hc @@ -108,12 +109,12 @@ node_in_regular <- function(.data, .data <- manynet::expect_nodes(.data) if(manynet::is_twomode(.data)){ manynet::snet_info("Since this is a two-mode network,", - "using {.fn node_by_tetrad} to", + "using {.fn node_x_tetrad} to", "profile nodes' embedding in local structures.") mat <- as.matrix(node_x_tetrad(.data)) } else { manynet::snet_info("Since this is a one-mode network,", - "using {.fn node_by_triad} to", + "using {.fn node_x_triad} to", "profile nodes' embedding in local structures.") mat <- node_x_triad(.data) } diff --git a/R/motif_brokerage.R b/R/motif_brokerage.R index 4fa6980..cc97f35 100644 --- a/R/motif_brokerage.R +++ b/R/motif_brokerage.R @@ -1,4 +1,4 @@ -# Brokerage #### +# Motifs #### #' Motifs of brokerage #' @@ -15,6 +15,7 @@ #' #' @name motif_brokerage #' @family motifs +#' @family brokerage #' @inheritParams motif_node #' @param membership A vector of partition membership as integers. #' @param standardized Whether the score should be standardized @@ -80,7 +81,24 @@ net_x_brokerage <- function(.data, membership, standardized = FALSE){ make_network_motif(out, .data) } -#' @rdname motif_brokerage +# Measures #### + +#' Measures of brokerage +#' +#' @description +#' These functions include ways to measure nodes' brokerage activity and +#' exclusivity in a network: +#' +#' - `node_brokering_activity()` measures nodes' brokerage activity. +#' - `node_brokering_exclusivity()` measures nodes' brokerage exclusivity. +#' +#' @name measure_brokerage +#' @family measures +#' @family brokerage +#' @inheritParams motif_brokerage +NULL + +#' @rdname measure_brokerage #' @references #' ## On brokerage activity and exclusivity #' Hamilton, Matthew, Jacob Hileman, and Orjan Bodin. 2020. @@ -91,7 +109,6 @@ net_x_brokerage <- function(.data, membership, standardized = FALSE){ #' @export node_by_brokering_activity <- function(.data, membership){ .data <- manynet::expect_nodes(.data) - from <- to.y <- to_memb <- from_memb <- NULL twopaths <- .to_twopaths(.data) if(!missing(membership)){ twopaths$from_memb <- manynet::node_attribute(.data, membership)[`if`(manynet::is_labelled(.data), @@ -104,20 +121,23 @@ node_by_brokering_activity <- function(.data, membership){ } # tabulate brokerage out <- c(table(twopaths$to)) - # correct ordering for named data - if(manynet::is_labelled(.data)) out <- out[match(manynet::node_names(.data), names(out))] + # correct ordering + if(manynet::is_labelled(.data)) out <- out[match(manynet::node_names(.data), names(out))] else { + temp <- rep(0, manynet::net_nodes(.data)) + temp[as.numeric(names(out))] <- out + out <- temp + } # missings should be none out[is.na(out)] <- 0 make_node_measure(out, .data) } -#' @rdname motif_brokerage +#' @rdname measure_brokerage #' @examples #' node_by_brokering_exclusivity(ison_networkers, "Discipline") #' @export node_by_brokering_exclusivity <- function(.data, membership){ .data <- manynet::expect_nodes(.data) - from <- to.y <- to_memb <- from_memb <- NULL twopaths <- .to_twopaths(.data) if(!missing(membership)){ twopaths$from_memb <- manynet::node_attribute(.data, membership)[`if`(manynet::is_labelled(.data), @@ -133,12 +153,18 @@ node_by_brokering_exclusivity <- function(.data, membership){ # tabulate brokerage out <- c(table(out$to)) # correct ordering for named data - if(manynet::is_labelled(.data)) out <- out[match(manynet::node_names(.data), names(out))] + if(manynet::is_labelled(.data)) out <- out[match(manynet::node_names(.data), names(out))] else { + temp <- rep(0, manynet::net_nodes(.data)) + temp[as.numeric(names(out))] <- out + out <- temp + } # missings should be none out[is.na(out)] <- 0 make_node_measure(out, .data) } +# Memberships #### + #' Memberships of brokerage #' #' @description @@ -150,6 +176,7 @@ node_by_brokering_exclusivity <- function(.data, membership){ #' #' @name member_brokerage #' @family memberships +#' @family brokerage #' @inheritParams motif_brokerage NULL diff --git a/R/motif_census.R b/R/motif_census.R index 9bdd64d..b712dc1 100644 --- a/R/motif_census.R +++ b/R/motif_census.R @@ -301,10 +301,21 @@ node_x_path <- function(.data){ #' @name motif_net #' @family motifs #' @inheritParams motif_node -#' @param object2 A second, two-mode migraph-consistent object. +#' @param object2 A second, two-mode network object. NULL #' @rdname motif_net +#' @section Dyad census: +#' The dyad census counts the number of mutual, asymmetric, and null dyads +#' in a network. +#' For directed networks, +#' - Mutual dyads have ties in both directions +#' - Asymmetric dyads have a tie in one direction only +#' - Null dyads have no ties +#' +#' Note that for undirected and two-mode networks, +#' only mutual and null dyads are possible, +#' as the concept of an asymmetric dyad does not apply. #' @references #' ## On the dyad census #' Holland, Paul W., and Samuel Leinhardt. 1970. @@ -320,18 +331,46 @@ NULL #' @export net_x_dyad <- function(.data) { .data <- manynet::expect_nodes(.data) - if (manynet::is_twomode(.data)) { - manynet::snet_unavailable("A twomode or multilevel option for a dyad census is not yet implemented.") - } else { - out <- suppressWarnings(igraph::dyad_census(manynet::as_igraph(.data))) - out <- unlist(out) - names(out) <- c("Mutual", "Asymmetric", "Null") - if (!manynet::is_directed(.data)) out <- out[c(1, 3)] - make_network_motif(out, .data) - } + out <- suppressWarnings(igraph::dyad_census(manynet::as_igraph(.data))) + out <- unlist(out) + names(out) <- c("Mutual", "Asymmetric", "Null") + if (!manynet::is_directed(.data)) out <- out[c(1, 3)] + make_network_motif(out, .data) } -#' @rdname motif_net +#' @rdname motif_net +#' @section Triad census: +#' The triad census counts the number of three-node configurations in the network. +#' The function returns a matrix with a special naming convention: +#' - 003: This is an empty triad; no ties +#' - 012: This triad includes one tie +#' - 102: This triad includes two ties, but they are not reciprocated +#' - 021D: This triad includes two ties, one of which is reciprocated, and the other is directed towards the reciprocated tie +#' - 021U: This triad includes two ties, one of which is +#' reciprocated, and the other is directed away from the reciprocated tie +#' - 021C: This triad includes two ties, one of which is reciprocated, and the other is directed between the two non-reciprocated nodes +#' - 111D: This triad includes three ties, two of which are +#' reciprocated, and the other is directed towards the reciprocated ties +#' - 111U: This triad includes three ties, two of which are +#' reciprocated, and the other is directed away from the reciprocated ties +#' - 030T: This triad includes three ties, all of which are +#' directed in a transitive manner (i.e. A->B, B->C, A->C) +#' - 030C: This triad includes three ties, all of which are +#' directed in a cyclic manner (i.e. A->B, B->C +#' A->C) +#' - 201: This triad includes three ties, all of which are reciproc +#' ated (i.e. A<->B, B<->C, A<->C) +#' - 120D: This triad includes four ties, three of which are +#' reciprocated, and the other is directed towards the reciprocated ties +#' - 120U: This triad includes four ties, three of which are +#' reciprocated, and the other is directed away from the reciprocated ties +#' - 120C: This triad includes four ties, three of which are +#' reciprocated, and the other is directed between the two non-reciprocated +#' - 210: This triad includes five ties, four of which are reciprocated, and the other is directed between the two non-reciprocated +#' - 300: This triad includes six ties, all of which are reciprocated +#' +#' Note that for undirected and two-mode networks, only 003, 102, and 201 are possible, +#' as the other configurations rely on the concept of directionality. #' @references #' ## On the triad census #' Davis, James A., and Samuel Leinhardt. 1967. @@ -442,14 +481,13 @@ net_x_tetrad <- function(.data){ #' _Network Science_ 5(2): 187–212. #' \doi{10.1017/nws.2017.8} #' @examples -#' marvel_friends <- to_unsigned(fict_marvel, "positive") -#' (mixed_cen <- net_x_mixed(marvel_friends, ison_marvel_teams)) +#' net_x_mixed(fict_marvel) #' @export net_x_mixed <- function (.data, object2) { .data <- manynet::expect_nodes(.data) if(missing(object2) && manynet::is_multiplex(.data)) { - # object2 <- to_uniplex(.data, ) - + object2 <- manynet::to_uniplex(.data, unique(manynet::tie_attribute(.data, "type"))[2]) + .data <- manynet::to_uniplex(.data, unique(manynet::tie_attribute(.data, "type"))[1]) } if(manynet::is_twomode(.data)) manynet::snet_abort("First object should be a one-mode network") @@ -499,6 +537,7 @@ net_x_mixed <- function (.data, object2) { #' infection/adoption by time step. #' #' @family motifs +#' @family diffusion #' @inheritParams motif_node #' @inheritParams measure_diffusion_net #' @name motif_diffusion diff --git a/R/netrics-utils.R b/R/netrics-utils.R index 74bdbac..a97ab23 100644 --- a/R/netrics-utils.R +++ b/R/netrics-utils.R @@ -3,6 +3,7 @@ # defining global variables more centrally utils::globalVariables(c(".data", "obs", "from", "to", "name", "weight","sign","wave", + "from_memb","to_memb","to.y", "nodes","event","exposure", "student","students","colleges", "node","value","var","active","time", diff --git a/R/zzz.R b/R/zzz.R index 8d3f887..6d5b5b0 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -18,7 +18,7 @@ greet_startup_cli <- function() { tips <- c( - "i" = "There are lots of ways to contribute to {.pkg manynet} at {.url https://github.com/stocnet/netrics/}.", + "i" = "There are lots of ways to contribute to {.pkg netrics} at {.url https://github.com/stocnet/netrics/}.", "i" = "Please share bugs, issues, or feature requests at {.url https://github.com/stocnet/netrics/issues}. It's really helpful!", # "i" = "To suppress package startup messages, use: `suppressPackageStartupMessages(library({.pkg netrics}))`.", # "i" = "Changing the theme of all your graphs is straightforward with `set_manynet_theme()`", diff --git a/README.Rmd b/README.Rmd index cd41700..8134e15 100644 --- a/README.Rmd +++ b/README.Rmd @@ -29,38 +29,29 @@ list_data <- function(string){ ![GitHub release (latest by date)](https://img.shields.io/github/v/release/stocnet/netrics) ![GitHub Release Date](https://img.shields.io/github/release-date/stocnet/netrics) [![Codecov test coverage](https://codecov.io/gh/stocnet/netrics/branch/main/graph/badge.svg)](https://app.codecov.io/gh/stocnet/netrics?branch=main) - - - - - ## About the package -While many awesome packages for network analysis exist for R, -all with their own offerings and advantages, -they also all have their own vocabulary, syntax, -and expected formats for data inputs and analytic outputs. -Many of these packages only work on _some_ types of networks -(usually one-mode, simple, directed or undirected networks) for _some_ types of analysis; -if you want to analyse a different type of network or try a different analysis, -a different package is needed. -And they can 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, `{netrics}` offers _many_ analytic tools that work on _many_ (if not most) types and kinds of networks. -It helps researchers make, modify, mark, measure, and identify nodes' motifs and memberships in networks. -For graph drawing see [`{autograph}`](https://stocnet.github.io/autograph/), +`{netrics}` is the analytic engine of the [stocnet](https://github.com/stocnet) ecosystem. +It provides _many_ tools for marking, measuring, and identifying nodes' motifs and memberships +in _many_ (if not most) types and kinds of networks. +All functions operate on matrices, `{igraph}`, `{network}`, `{tidygraph}`, and `mnet` class objects, +and recognise directed, weighted, multiplex, multimodal, signed, and other types of networks. + +`{netrics}` depends on [`{manynet}`](https://stocnet.github.io/manynet/), +which handles working with different network classes, converting between them, and modifying them. +Network-level logical tests (e.g. `is_directed()`, `is_twomode()`) remain in `{manynet}`, +while node- and tie-level analytic marks (e.g. `node_is_*()`, `tie_is_*()`) are in `{netrics}`. +For graph drawing, see [`{autograph}`](https://stocnet.github.io/autograph/), and for further testing and modelling capabilities -see [`{migraph}`](https://stocnet.github.io/migraph/) and the other [stocnet](https://github.com/stocnet) packages. +see [`{migraph}`](https://stocnet.github.io/migraph/). - [Marking](#marking) -- [Motifs](#motifs) +- [Measures](#measures) - [Memberships](#memberships) -- [Measuring](#measuring) -- [Tutorials](#tutorials) +- [Motifs](#motifs) +- [Analysis](#analysis) - [Installation](#installation) - [Stable](#stable) - [Development](#development) @@ -75,10 +66,11 @@ 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, +`{netrics}`'s `node_is_*()` and `tie_is_*()` functions offer fast logical tests +of node- and tie-level properties. `node_is_*()` returns a logical vector the length of the number of nodes in the network, and `tie_is_*()` returns a logical vector the length of the number of ties in the network. +Note that network-level tests such as `is_directed()` and `is_twomode()` are in `{manynet}`. - `r list_functions("^node_is_")` - `r list_functions("^tie_is_")` @@ -86,85 +78,77 @@ and `tie_is_*()` returns a logical vector the length of the number of ties in th The `*is_max()` and `*is_min()` functions are used to identify the maximum or minimum, respectively, node or tie according to some measure (see below). -## Motifs +## Measures -`{manynet}`'s `*by_*()` functions tabulate nodes' frequency in various motifs. +`{netrics}`'s `*_by_*()` functions offer numeric measures at the network, node, and tie level. These include: - `r list_functions("_by_")` +The measures are organised into several broad categories, including: +_Centrality_, _Cohesion_, _Hierarchy_, _Innovation_ (structural holes), +_Diversity_ (heterogeneity), _Topology_ (features), and _Diffusion_. +Each measure recognises whether the network is directed or undirected, +weighted or unweighted, one-mode or two-mode, +and returns normalized values wherever possible. +We recommend you explore [the list of functions](https://stocnet.github.io/netrics/reference/index.html) to find out more. + ## Memberships -`{manynet}`'s `*in_*()` functions identify nodes' membership in some grouping, +`{netrics}`'s `*_in_*()` functions identify nodes' membership in some grouping, such as a community or component. These functions always return a character vector, indicating e.g. that the first node is a member of group "A", the second in group "B", etc. - `r list_functions("_in_")` -For example `node_brokerage_census()` returns +For example `node_in_brokering()` 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, +`{netrics}` 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, +`{netrics}` 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 - -`{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") -``` +## Motifs + +`{netrics}`'s `*_x_*()` functions tabulate nodes' and networks' frequency in various motifs. +These include: + +- `r list_functions("_x_")` + +## Analysis + +The functions in `{netrics}` are designed to answer a wide variety of analytic questions about networks. +For example, you might want to know about: + +- _Centrality_: `r list_functions("degree|betweenness|closeness|eigenvector")` +- _Cohesion_: `r list_functions("density|reciprocity|transitivity|equivalency|congruency")` +- _Hierarchy_: `r list_functions("hierarchy|connectedness|upper|efficiency|reciprocity")` +- _Innovation_: `r list_functions("hole|redundancy|constraint|effsize")` +- _Diversity_: `r list_functions("diversity|phily|richness|assort")` +- _Topology_: `r list_functions("core|factions|modularity|smallworld|balance|richclub")` +- _Resilience_: `r list_functions("cutpoint|bridge|hesion|articul")` +- _Brokerage_: `r list_functions("broke")` +- _Diffusion_: `r list_functions("adopt|infect|expos")` ## Installation ### Stable -The easiest way to install the latest stable version of `{manynet}` is via CRAN. +The easiest way to install the latest stable version of `{netrics}` is via CRAN. Simply open the R console and enter: -`install.packages('manynet')` +`install.packages('netrics')` -`library(manynet)` will then load the package and make the data and tutorials (see below) contained within the package available. +`library(netrics)` will then load the package and make the functions contained within the package available. ### Development @@ -173,46 +157,57 @@ for slightly earlier access to new features or for testing, you may wish to download and install the binaries from Github or install from source locally. The latest binary releases for all major OSes -- Windows, Mac, and Linux -- -can be found [here](https://github.com/stocnet/manynet/releases/latest). +can be found [here](https://github.com/stocnet/netrics/releases/latest). Download the appropriate binary for your operating system, and install using an adapted version of the following commands: -- For Windows: `install.packages("~/Downloads/manynet_winOS.zip", repos = NULL)` -- For Mac: `install.packages("~/Downloads/manynet_macOS.tgz", repos = NULL)` -- For Unix: `install.packages("~/Downloads/manynet_linuxOS.tar.gz", repos = NULL)` +- For Windows: `install.packages("~/Downloads/netrics_winOS.zip", repos = NULL)` +- For Mac: `install.packages("~/Downloads/netrics_macOS.tgz", repos = NULL)` +- For Unix: `install.packages("~/Downloads/netrics_linuxOS.tar.gz", repos = NULL)` -To install from source the latest main version of `{manynet}` from Github, +To install from source the latest main version of `{netrics}` from Github, please install the `{remotes}` package from CRAN and then: - For latest stable version: -`remotes::install_github("stocnet/manynet")` +`remotes::install_github("stocnet/netrics")` - For latest development version: -`remotes::install_github("stocnet/manynet@develop")` +`remotes::install_github("stocnet/netrics@develop")` ### Other sources Those using Mac computers may also install using Macports: -`sudo port install R-manynet` +`sudo port install R-netrics` ## Relationship to other packages -This package stands on the shoulders of several incredible packages. - -In terms of the objects it works with, -this package aims to provide an updated, more comprehensive replacement for `{intergraph}`. -As such it works with objects in `{igraph}` and `{network}` formats, -but also equally well with base matrices and edgelists (data frames), -and formats from several other packages. - -The user interface is inspired in some ways by Thomas Lin Pedersen's excellent `{tidygraph}` package, -though makes some different decisions, -and uses the quickest `{igraph}` or `{network}` routines where available. - -`{manynet}` has inherited most of its core functionality from its maternal package, `{migraph}`. -`{migraph}` continues to offer more analytic and modelling functions that builds upon -the architecture provided by `{manynet}`. -For more, please check out `{migraph}` directly. +`{netrics}` is part of the [stocnet](https://github.com/stocnet) ecosystem of R packages +for network analysis. +The packages are designed to be modular, with clear roles and dependencies: + +- [`{manynet}`](https://stocnet.github.io/manynet/): + The foundation package for working with network data. + It handles network classes (matrices, `{igraph}`, `{network}`, `{tidygraph}`, `mnet`), + coercion between them, modification, and network-level logical tests (`is_*()` functions). +- **`{netrics}`**: + The analytic package containing all measures (`*_by_*()` functions), + memberships (`*_in_*()` functions), motifs (`*_x_*()` functions), + and node- and tie-level marks (`node_is_*()`, `tie_is_*()` functions). + `{netrics}` depends on `{manynet}`. +- [`{autograph}`](https://stocnet.github.io/autograph/): + The graph drawing package. + `{autograph}` depends on both `{manynet}` (for network classes) + and `{netrics}` (for analytic results to visualise), + since it would typically be used with both. +- [`{migraph}`](https://stocnet.github.io/migraph/): + The modelling and testing package, + building on both `{manynet}` and `{netrics}`. + +Node- and tie-level marks such as `node_is_cutpoint()` and `tie_is_bridge()` are kept +in `{netrics}` rather than `{manynet}` because they are analytic functions that identify +structural positions in the network. +Network-level property tests like `is_directed()` remain in `{manynet}` +because they describe the type of data rather than an analytic result. ## Funding details diff --git a/README.md b/README.md index b7b0180..d33818f 100644 --- a/README.md +++ b/README.md @@ -16,40 +16,33 @@ Release Date](https://img.shields.io/github/release-date/stocnet/netrics) [![Codecov test coverage](https://codecov.io/gh/stocnet/netrics/branch/main/graph/badge.svg)](https://app.codecov.io/gh/stocnet/netrics?branch=main) - - - - - ## About the package -While many awesome packages for network analysis exist for R, all with -their own offerings and advantages, they also all have their own -vocabulary, syntax, and expected formats for data inputs and analytic -outputs. Many of these packages only work on *some* types of networks -(usually one-mode, simple, directed or undirected networks) for *some* -types of analysis; if you want to analyse a different type of network or -try a different analysis, a different package is needed. And they can -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, `{netrics}` offers *many* analytic tools that work on -*many* (if not most) types and kinds of networks. It helps researchers -make, modify, mark, measure, and identify nodes’ motifs and memberships -in networks. For graph drawing see -[`{autograph}`](https://stocnet.github.io/autograph/), and for further -testing and modelling capabilities see -[`{migraph}`](https://stocnet.github.io/migraph/) and the other -[stocnet](https://github.com/stocnet) packages. +`{netrics}` is the analytic engine of the +[stocnet](https://github.com/stocnet) ecosystem. It provides *many* +tools for marking, measuring, and identifying nodes’ motifs and +memberships in *many* (if not most) types and kinds of networks. All +functions operate on matrices, `{igraph}`, `{network}`, `{tidygraph}`, +and `mnet` class objects, and recognise directed, weighted, multiplex, +multimodal, signed, and other types of networks. + +`{netrics}` depends on +[`{manynet}`](https://stocnet.github.io/manynet/), which handles working +with different network classes, converting between them, and modifying +them. Network-level logical tests (e.g. `is_directed()`, `is_twomode()`) +remain in `{manynet}`, while node- and tie-level analytic marks +(e.g. `node_is_*()`, `tie_is_*()`) are in `{netrics}`. For graph +drawing, see [`{autograph}`](https://stocnet.github.io/autograph/), and +for further testing and modelling capabilities see +[`{migraph}`](https://stocnet.github.io/migraph/). - [Marking](#marking) -- [Motifs](#motifs) +- [Measures](#measures) - [Memberships](#memberships) -- [Measuring](#measuring) -- [Tutorials](#tutorials) +- [Motifs](#motifs) +- [Analysis](#analysis) - [Installation](#installation) - [Stable](#stable) - [Development](#development) @@ -63,11 +56,12 @@ 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, and `tie_is_*()` returns a logical vector the -length of the number of ties in the network. +`{netrics}`’s `node_is_*()` and `tie_is_*()` functions offer fast +logical tests of node- and tie-level properties. `node_is_*()` returns a +logical vector the length of the number of nodes in the network, and +`tie_is_*()` returns a logical vector the length of the number of ties +in the network. Note that network-level tests such as `is_directed()` +and `is_twomode()` are in `{manynet}`. - `node_is_core()`, `node_is_cutpoint()`, `node_is_exposed()`, `node_is_fold()`, `node_is_independent()`, `node_is_infected()`, @@ -76,8 +70,8 @@ length of the number of ties in the network. `node_is_neighbor()`, `node_is_pendant()`, `node_is_random()`, `node_is_recovered()`, `node_is_universal()` - `tie_is_bridge()`, `tie_is_cyclical()`, `tie_is_feedback()`, - `tie_is_forbidden()`, `tie_is_imbalanced()`, `tie_is_loop()`, - `tie_is_max()`, `tie_is_min()`, `tie_is_multiple()`, `tie_is_path()`, + `tie_is_imbalanced()`, `tie_is_loop()`, `tie_is_max()`, + `tie_is_min()`, `tie_is_multiple()`, `tie_is_path()`, `tie_is_random()`, `tie_is_reciprocated()`, `tie_is_simmelian()`, `tie_is_transitive()`, `tie_is_triangular()`, `tie_is_triplet()` @@ -85,10 +79,10 @@ The `*is_max()` and `*is_min()` functions are used to identify the maximum or minimum, respectively, node or tie according to some measure (see below). -## Motifs +## Measures -`{manynet}`‘s `*by_*()` functions tabulate nodes’ frequency in various -motifs. These include: +`{netrics}`’s `*_by_*()` functions offer numeric measures at the +network, node, and tie level. These include: - `net_by_adhesion()`, `net_by_assortativity()`, `net_by_balance()`, `net_by_betweenness()`, `net_by_change()`, `net_by_closeness()`, @@ -97,8 +91,8 @@ motifs. These include: `net_by_degree()`, `net_by_density()`, `net_by_diameter()`, `net_by_diversity()`, `net_by_efficiency()`, `net_by_eigenvector()`, `net_by_equivalency()`, `net_by_factions()`, `net_by_harmonic()`, - `net_by_heterophily()`, `net_by_hierarchy()`, `net_by_homophily()`, - `net_by_immunity()`, `net_by_indegree()`, `net_by_independence()`, + `net_by_heterophily()`, `net_by_homophily()`, `net_by_immunity()`, + `net_by_indegree()`, `net_by_independence()`, `net_by_infection_complete()`, `net_by_infection_peak()`, `net_by_infection_total()`, `net_by_length()`, `net_by_modularity()`, `net_by_outdegree()`, `net_by_reach()`, `net_by_reciprocity()`, @@ -128,9 +122,18 @@ motifs. These include: `tie_by_betweenness()`, `tie_by_closeness()`, `tie_by_cohesion()`, `tie_by_degree()`, `tie_by_eigenvector()` +The measures are organised into several broad categories, including: +*Centrality*, *Cohesion*, *Hierarchy*, *Innovation* (structural holes), +*Diversity* (heterogeneity), *Topology* (features), and *Diffusion*. +Each measure recognises whether the network is directed or undirected, +weighted or unweighted, one-mode or two-mode, and returns normalized +values wherever possible. We recommend you explore [the list of +functions](https://stocnet.github.io/netrics/reference/index.html) to +find out more. + ## Memberships -`{manynet}`‘s `*in_*()` functions identify nodes’ membership in some +`{netrics}`‘s `*_in_*()` functions identify nodes’ membership in some grouping, such as a community or component. These functions always return a character vector, indicating e.g. that the first node is a member of group “A”, the second in group “B”, etc. @@ -144,91 +147,84 @@ member of group “A”, the second in group “B”, etc. `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’ +For example `node_in_brokering()` 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 +equivalence. `{netrics}` 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 +`{netrics}` 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 - -`{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. +## Motifs -## 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() -#> Checking tutorials in stocnet packages ■■■■■■■■■■■ 33% | … -#> # A tibble: 10 × 3 -#> package name title -#> -#> 1 manynet tutorial0 Intro to R -#> 2 manynet tutorial1 Data -#> 3 autograph tutorial2 Visualisation -#> 4 manynet tutorial3 Centrality -#> 5 manynet tutorial4 Cohesion and Community -#> 6 manynet tutorial5 Position and Equivalence -#> 7 manynet tutorial6 Topology and Resilience -#> 8 migraph tutorial7 Diffusion and Learning -#> 9 migraph tutorial8 Diversity and Regression -#> 10 migraph tutorial9 Modelling with ERGMs -#> ℹ You can run a tutorial by typing e.g `run_tute('tutorial1')` or `run_tute('Data')` into the console. -# run_tute("tutorial1") -``` +`{netrics}`‘s `*_x_*()` functions tabulate nodes’ and networks’ +frequency in various motifs. These include: + +- `net_x_brokerage()`, `net_x_dyad()`, `net_x_hazard()`, + `net_x_hierarchy()`, `net_x_mixed()`, `net_x_tetrad()`, + `net_x_triad()`, `node_x_brokerage()`, `node_x_dyad()`, + `node_x_exposure()`, `node_x_path()`, `node_x_tetrad()`, + `node_x_tie()`, `node_x_triad()` + +## Analysis + +The functions in `{netrics}` are designed to answer a wide variety of +analytic questions about networks. For example, you might want to know +about: + +- *Centrality*: `net_by_betweenness()`, `net_by_closeness()`, + `net_by_degree()`, `net_by_eigenvector()`, `net_by_indegree()`, + `net_by_outdegree()`, `node_by_betweenness()`, `node_by_closeness()`, + `node_by_degree()`, `node_by_eigenvector()`, `node_by_indegree()`, + `node_by_multidegree()`, `node_by_neighbours_degree()`, + `node_by_outdegree()`, `node_in_betweenness()`, + `tie_by_betweenness()`, `tie_by_closeness()`, `tie_by_degree()`, + `tie_by_eigenvector()` +- *Cohesion*: `net_by_congruency()`, `net_by_density()`, + `net_by_equivalency()`, `net_by_reciprocity()`, + `net_by_transitivity()`, `node_by_equivalency()`, + `node_by_reciprocity()`, `node_by_transitivity()` +- *Hierarchy*: `net_by_connectedness()`, `net_by_efficiency()`, + `net_by_reciprocity()`, `net_by_upperbound()`, `net_x_hierarchy()`, + `node_by_efficiency()`, `node_by_hierarchy()`, `node_by_reciprocity()` +- *Innovation*: `node_by_constraint()`, `node_by_effsize()`, + `node_by_redundancy()` +- *Diversity*: `net_by_assortativity()`, `net_by_diversity()`, + `net_by_heterophily()`, `net_by_homophily()`, `net_by_richness()`, + `node_by_diversity()`, `node_by_heterophily()`, `node_by_homophily()`, + `node_by_richness()` +- *Topology*: `net_by_balance()`, `net_by_core()`, `net_by_factions()`, + `net_by_modularity()`, `net_by_richclub()`, `net_by_smallworld()`, + `node_by_coreness()`, `node_by_kcoreness()`, `node_in_core()`, + `node_is_core()`, `tie_is_imbalanced()` +- *Resilience*: `net_by_adhesion()`, `net_by_cohesion()`, + `node_by_bridges()`, `node_is_cutpoint()`, `tie_by_cohesion()`, + `tie_is_bridge()` +- *Brokerage*: `net_x_brokerage()`, `node_by_brokering_activity()`, + `node_by_brokering_exclusivity()`, `node_in_brokering()`, + `node_x_brokerage()` +- *Diffusion*: `net_by_infection_complete()`, `net_by_infection_peak()`, + `net_by_infection_total()`, `node_by_adoption_time()`, + `node_by_exposure()`, `node_in_adopter()`, `node_is_exposed()`, + `node_is_infected()`, `node_x_exposure()` ## Installation ### Stable -The easiest way to install the latest stable version of `{manynet}` is +The easiest way to install the latest stable version of `{netrics}` is via CRAN. Simply open the R console and enter: -`install.packages('manynet')` +`install.packages('netrics')` -`library(manynet)` will then load the package and make the data and -tutorials (see below) contained within the package available. +`library(netrics)` will then load the package and make the functions +contained within the package available. ### Development @@ -236,51 +232,59 @@ For the latest development version, for slightly earlier access to new features or for testing, you may wish to download and install the binaries from Github or install from source locally. The latest binary releases for all major OSes – Windows, Mac, and Linux – can be found -[here](https://github.com/stocnet/manynet/releases/latest). Download the +[here](https://github.com/stocnet/netrics/releases/latest). Download the appropriate binary for your operating system, and install using an adapted version of the following commands: - For Windows: - `install.packages("~/Downloads/manynet_winOS.zip", repos = NULL)` + `install.packages("~/Downloads/netrics_winOS.zip", repos = NULL)` - For Mac: - `install.packages("~/Downloads/manynet_macOS.tgz", repos = NULL)` + `install.packages("~/Downloads/netrics_macOS.tgz", repos = NULL)` - For Unix: - `install.packages("~/Downloads/manynet_linuxOS.tar.gz", repos = NULL)` + `install.packages("~/Downloads/netrics_linuxOS.tar.gz", repos = NULL)` -To install from source the latest main version of `{manynet}` from +To install from source the latest main version of `{netrics}` from Github, please install the `{remotes}` package from CRAN and then: - For latest stable version: - `remotes::install_github("stocnet/manynet")` + `remotes::install_github("stocnet/netrics")` - For latest development version: - `remotes::install_github("stocnet/manynet@develop")` + `remotes::install_github("stocnet/netrics@develop")` ### Other sources Those using Mac computers may also install using Macports: -`sudo port install R-manynet` +`sudo port install R-netrics` ## Relationship to other packages -This package stands on the shoulders of several incredible packages. - -In terms of the objects it works with, this package aims to provide an -updated, more comprehensive replacement for `{intergraph}`. As such it -works with objects in `{igraph}` and `{network}` formats, but also -equally well with base matrices and edgelists (data frames), and formats -from several other packages. - -The user interface is inspired in some ways by Thomas Lin Pedersen’s -excellent `{tidygraph}` package, though makes some different decisions, -and uses the quickest `{igraph}` or `{network}` routines where -available. - -`{manynet}` has inherited most of its core functionality from its -maternal package, `{migraph}`. `{migraph}` continues to offer more -analytic and modelling functions that builds upon the architecture -provided by `{manynet}`. For more, please check out `{migraph}` -directly. +`{netrics}` is part of the [stocnet](https://github.com/stocnet) +ecosystem of R packages for network analysis. The packages are designed +to be modular, with clear roles and dependencies: + +- [`{manynet}`](https://stocnet.github.io/manynet/): The foundation + package for working with network data. It handles network classes + (matrices, `{igraph}`, `{network}`, `{tidygraph}`, `mnet`), coercion + between them, modification, and network-level logical tests (`is_*()` + functions). +- **`{netrics}`**: The analytic package containing all measures + (`*_by_*()` functions), memberships (`*_in_*()` functions), motifs + (`*_x_*()` functions), and node- and tie-level marks (`node_is_*()`, + `tie_is_*()` functions). `{netrics}` depends on `{manynet}`. +- [`{autograph}`](https://stocnet.github.io/autograph/): The graph + drawing package. `{autograph}` depends on both `{manynet}` (for + network classes) and `{netrics}` (for analytic results to visualise), + since it would typically be used with both. +- [`{migraph}`](https://stocnet.github.io/migraph/): The modelling and + testing package, building on both `{manynet}` and `{netrics}`. + +Node- and tie-level marks such as `node_is_cutpoint()` and +`tie_is_bridge()` are kept in `{netrics}` rather than `{manynet}` +because they are analytic functions that identify structural positions +in the network. Network-level property tests like `is_directed()` remain +in `{manynet}` because they describe the type of data rather than an +analytic result. ## Funding details diff --git a/cran-comments.md b/cran-comments.md new file mode 100644 index 0000000..d90f044 --- /dev/null +++ b/cran-comments.md @@ -0,0 +1,16 @@ +## Test environments + +* local R installation, aarch64-apple-darwin20, R 4.5.2 +* macOS 15.7.4 (on Github), R 4.5.2 +* Microsoft Windows Server 2025 10.0.26100 (on Github), R 4.5.2 +* Ubuntu 24.04.3 (on Github), R 4.5.2 + +## R CMD check results + +0 errors | 0 warnings | 0 notes + +- This package is ready for submission to CRAN. +The check results are clean, with no errors, warnings, or notes across all tested environments. +Note that there are some conflicts with manynet functions, +but these are not causing any issues with the package itself and +will be resolved by removing them from manynet once netrics is available on CRAN. diff --git a/man/mark_core.Rd b/man/mark_core.Rd index 3ef024c..857f2fd 100644 --- a/man/mark_core.Rd +++ b/man/mark_core.Rd @@ -2,15 +2,12 @@ % Please edit documentation in R/member_core.R \name{mark_core} \alias{mark_core} -\alias{node_is_universal} \alias{node_is_core} \alias{node_by_kcoreness} \alias{node_by_coreness} \alias{node_in_core} \title{Core-periphery clustering algorithms} \usage{ -node_is_universal(.data) - node_is_core(.data, method = c("degree", "eigenvector")) node_by_kcoreness(.data) @@ -41,8 +38,6 @@ or "kmeans" (k-means clustering). Default is "bins".} \description{ These functions identify nodes belonging to (some level of) the core of a network: \itemize{ -\item \code{node_is_universal()} identifies whether nodes are adjacent to all other -nodes in the network. \item \code{node_is_core()} identifies whether nodes belong to the core of the network, as opposed to the periphery. \item \code{node_in_core()} categorizes nodes into two or more core/periphery @@ -52,17 +47,6 @@ resembles a typical core node. \item \code{node_kcoreness()} assigns nodes to their level of k-coreness. } } -\section{Universal/dominating node}{ - -A universal node is adjacent to all other nodes in the network. -It is also sometimes called the dominating vertex because it represents -a one-element dominating set. -A network with a universal node is called a cone, and its universal node -is called the apex of the cone. -A classic example of a cone is a star graph, -but friendship, wheel, and threshold graphs are also cones. -} - \section{Core-periphery}{ This function is used to identify which nodes should belong to the core, @@ -97,7 +81,6 @@ quantile-based bins, or k-means clustering. } \examples{ -node_is_universal(create_star(11)) node_is_core(ison_adolescents) #ison_adolescents \%>\% # mutate(corep = node_is_core()) \%>\% diff --git a/man/mark_degree.Rd b/man/mark_degree.Rd new file mode 100644 index 0000000..d19c5a3 --- /dev/null +++ b/man/mark_degree.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/mark_nodes.R +\name{mark_degree} +\alias{mark_degree} +\alias{node_is_isolate} +\alias{node_is_pendant} +\alias{node_is_universal} +\title{Marking nodes based on degree properties} +\usage{ +node_is_isolate(.data) + +node_is_pendant(.data) + +node_is_universal(.data) +} +\arguments{ +\item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. +For more information on the standard coercion possible, +see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} +} +\description{ +These functions return logical vectors the length of the +nodes in a network identifying which hold certain properties or positions in the network. +\itemize{ +\item \code{node_is_isolate()} marks nodes that are isolates, +with neither incoming nor outgoing ties. +\item \code{node_is_pendant()} marks nodes that are pendants, +with exactly one incoming or outgoing tie. +\item \code{node_is_universal()} identifies whether nodes are adjacent to all other +nodes in the network. +} +} +\section{Universal/dominating node}{ + +A universal node is adjacent to all other nodes in the network. +It is also sometimes called the dominating vertex because it represents +a one-element dominating set. +A network with a universal node is called a cone, and its universal node +is called the apex of the cone. +A classic example of a cone is a star graph, +but friendship, wheel, and threshold graphs are also cones. +} + +\examples{ +node_is_isolate(ison_brandes) +node_is_universal(create_star(11)) +} +\seealso{ +Other marks: +\code{\link{mark_diff}}, +\code{\link{mark_nodes}}, +\code{\link{mark_select}}, +\code{\link{mark_tie_select}}, +\code{\link{mark_ties}}, +\code{\link{mark_triangles}} + +Other degree: +\code{\link{measure_central_degree}} +} +\concept{degree} +\concept{marks} diff --git a/man/mark_diff.Rd b/man/mark_diff.Rd index 140d404..2f18f47 100644 --- a/man/mark_diff.Rd +++ b/man/mark_diff.Rd @@ -60,10 +60,19 @@ in that diffusion. } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_nodes}}, \code{\link{mark_select}}, \code{\link{mark_tie_select}}, \code{\link{mark_ties}}, \code{\link{mark_triangles}} + +Other diffusion: +\code{\link{measure_diffusion_infection}}, +\code{\link{measure_diffusion_net}}, +\code{\link{measure_diffusion_node}}, +\code{\link{member_diffusion}}, +\code{\link{motif_diffusion}} } +\concept{diffusion} \concept{marks} diff --git a/man/mark_nodes.Rd b/man/mark_nodes.Rd index b4a09e9..e878af7 100644 --- a/man/mark_nodes.Rd +++ b/man/mark_nodes.Rd @@ -2,8 +2,6 @@ % Please edit documentation in R/mark_nodes.R \name{mark_nodes} \alias{mark_nodes} -\alias{node_is_isolate} -\alias{node_is_pendant} \alias{node_is_independent} \alias{node_is_cutpoint} \alias{node_is_fold} @@ -11,10 +9,6 @@ \alias{node_is_neighbor} \title{Marking nodes based on structural properties} \usage{ -node_is_isolate(.data) - -node_is_pendant(.data) - node_is_independent(.data) node_is_cutpoint(.data) @@ -50,20 +44,17 @@ described in Valente and Davis (1999).} These functions return logical vectors the length of the nodes in a network identifying which hold certain properties or positions in the network. \itemize{ -\item \code{node_is_isolate()} marks nodes that are isolates, -with neither incoming nor outgoing ties. \item \code{node_is_independent()} marks nodes that are members of the largest independent set, aka largest internally stable set. \item \code{node_is_cutpoint()} marks nodes that cut or act as articulation points in a network, increasing the number of connected components when removed. -\item \code{node_is_core()} marks nodes that are members of the network's core. \item \code{node_is_fold()} marks nodes that are in a structural fold between two or more triangles that are only connected by that node. \item \code{node_is_mentor()} marks a proportion of high indegree nodes as 'mentors' (see details). +\item \code{node_is_neighbor()} marks nodes that are neighbours of a given node. } } \examples{ -node_is_isolate(ison_brandes) node_is_independent(ison_adolescents) node_is_cutpoint(ison_brandes) node_is_fold(create_explicit(A-B, B-C, A-C, C-D, C-E, D-E)) @@ -102,6 +93,7 @@ Valente, Thomas, and Rebecca Davis. 1999. } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_select}}, \code{\link{mark_tie_select}}, diff --git a/man/mark_select.Rd b/man/mark_select.Rd index 9e89019..c629763 100644 --- a/man/mark_select.Rd +++ b/man/mark_select.Rd @@ -6,7 +6,7 @@ \alias{node_is_max} \alias{node_is_min} \alias{node_is_mean} -\title{Marking nodes for selection based on measures} +\title{Marking nodes based on measures} \usage{ node_is_random(.data, size = 1) @@ -44,12 +44,13 @@ are key because they minimise or, more often, maximise some measure. } \examples{ node_is_random(ison_brandes, 2) -#node_is_max(migraph::node_degree(ison_brandes)) -#node_is_min(migraph::node_degree(ison_brandes)) +#node_is_max(node_by_degree(ison_brandes)) +#node_is_min(node_by_degree(ison_brandes)) #node_is_mean(node_degree(ison_brandes)) } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_nodes}}, \code{\link{mark_tie_select}}, diff --git a/man/mark_tie_select.Rd b/man/mark_tie_select.Rd index 87e471e..debb95e 100644 --- a/man/mark_tie_select.Rd +++ b/man/mark_tie_select.Rd @@ -5,7 +5,7 @@ \alias{tie_is_random} \alias{tie_is_max} \alias{tie_is_min} -\title{Marking ties for selection based on measures} +\title{Marking ties based on measures} \usage{ tie_is_random(.data, size = 1) @@ -33,11 +33,12 @@ are key because they minimise or, more often, maximise some measure. } } \examples{ -# tie_is_max(migraph::tie_betweenness(ison_brandes)) -#tie_is_min(migraph::tie_betweenness(ison_brandes)) +# tie_is_max(tie_by_betweenness(ison_brandes)) +#tie_is_min(tie_by_betweenness(ison_brandes)) } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_nodes}}, \code{\link{mark_select}}, diff --git a/man/mark_ties.Rd b/man/mark_ties.Rd index 20f5853..c064e58 100644 --- a/man/mark_ties.Rd +++ b/man/mark_ties.Rd @@ -61,6 +61,7 @@ ison_adolescents \%>\% } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_nodes}}, \code{\link{mark_select}}, diff --git a/man/mark_triangles.Rd b/man/mark_triangles.Rd index abe50d1..8a1ef60 100644 --- a/man/mark_triangles.Rd +++ b/man/mark_triangles.Rd @@ -7,9 +7,8 @@ \alias{tie_is_triplet} \alias{tie_is_cyclical} \alias{tie_is_simmelian} -\alias{tie_is_forbidden} \alias{tie_is_imbalanced} -\title{Marking ties based on structural properties} +\title{Marking ties based on triangular properties} \usage{ tie_is_triangular(.data) @@ -21,8 +20,6 @@ tie_is_cyclical(.data) tie_is_simmelian(.data) -tie_is_forbidden(.data) - tie_is_imbalanced(.data) } \arguments{ @@ -41,7 +38,6 @@ in a network identifying which hold certain properties or positions in the netwo and fully reciprocated. \item \code{tie_is_imbalanced()} marks ties that are part of imbalanced triads. \item \code{tie_is_transitive()} marks ties that complete transitive closure. -\item \code{tie_is_forbidden()} marks ties that complete forbidden triads. } They are most useful in highlighting parts of the network that @@ -63,13 +59,11 @@ ison_adolescents \%>\% to_directed() \%>\% ison_monks \%>\% to_uniplex("like") \%>\% mutate_ties(simmel = tie_is_simmelian()) #graphr(edge_color = "simmel") -generate_random(8, directed = TRUE) \%>\% - mutate_ties(forbid = tie_is_forbidden()) - #graphr(edge_color = "forbid") fict_marvel \%>\% to_uniplex("relationship") \%>\% tie_is_imbalanced() } \seealso{ Other marks: +\code{\link{mark_degree}}, \code{\link{mark_diff}}, \code{\link{mark_nodes}}, \code{\link{mark_select}}, diff --git a/man/measure_assortativity.Rd b/man/measure_assortativity.Rd new file mode 100644 index 0000000..5b96a06 --- /dev/null +++ b/man/measure_assortativity.Rd @@ -0,0 +1,126 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/measure_heterogeneity.R +\name{measure_assortativity} +\alias{measure_assortativity} +\alias{net_by_heterophily} +\alias{node_by_heterophily} +\alias{net_by_homophily} +\alias{node_by_homophily} +\alias{net_by_assortativity} +\alias{net_by_spatial} +\title{Measures of network assortativity} +\usage{ +net_by_heterophily(.data, attribute) + +node_by_heterophily(.data, attribute) + +net_by_homophily(.data, attribute, method = c("ie", "ei", "yule", "geary")) + +node_by_homophily(.data, attribute, method = c("ie", "ei", "yule", "geary")) + +net_by_assortativity(.data) + +net_by_spatial(.data, attribute) +} +\arguments{ +\item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. +For more information on the standard coercion possible, +see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} + +\item{attribute}{Name of a nodal attribute or membership vector +to use as categories for the diversity measure.} + +\item{method}{Which method to use for \code{net_diversity()}. +Either "blau" (Blau's index) or "teachman" (Teachman's index) for +categorical attributes, or "variation" (coefficient of variation) +or "gini" (Gini coefficient) for numeric attributes. +Default is "blau". +If an incompatible method is chosen for the attribute type, +a suitable alternative will be used instead with a message.} +} +\description{ +These functions offer ways to measure the distribution or assortativity +of ties in a network: +\itemize{ +\item \code{net_heterophily()} measures how embedded nodes in the network +are within groups of nodes with the same attribute. +\item \code{node_heterophily()} measures each node's embeddedness within groups +of nodes with the same attribute. +\item \code{net_assortativity()} measures the degree assortativity in a network. +\item \code{net_spatial()} measures the spatial association/autocorrelation +(global Moran's I) in a network. +} +} +\section{Homophily}{ + +Given a partition of a network into a number of mutually exclusive groups then +The E-I index is the number of ties between (or \emph{external}) nodes +grouped in some mutually exclusive categories +minus the number of ties within (or \emph{internal}) these groups +divided by the total number of ties. +This value can range from 1 to -1, +where 1 indicates ties only between categories/groups and -1 ties only within categories/groups. +} + +\examples{ +marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") +net_by_heterophily(marvel_friends, "Gender") +net_by_heterophily(marvel_friends, "Attractive") +node_by_heterophily(marvel_friends, "Gender") +node_by_heterophily(marvel_friends, "Attractive") +net_by_homophily(marvel_friends, "Gender") +net_by_assortativity(ison_networkers) +net_by_spatial(ison_lawfirm, "age") +} +\references{ +\subsection{On heterophily}{ + +Krackhardt, David, and Robert N. Stern. 1988. +Informal networks and organizational crises: an experimental simulation. +\emph{Social Psychology Quarterly} 51(2): 123-140. +\doi{10.2307/2786835} + +McPherson, Miller, Lynn Smith-Lovin, and James M. Cook. 2001. +"Birds of a Feather: Homophily in Social Networks". +\emph{Annual Review of Sociology}, 27(1): 415-444. +\doi{10.1146/annurev.soc.27.1.415} +} + +\subsection{On assortativity}{ + +Newman, Mark E.J. 2002. +"Assortative mixing in networks". +\emph{Physical Review Letters}, 89(20): 208701. +\doi{10.1103/physrevlett.89.208701} +} + +\subsection{On spatial autocorrelation}{ + +Moran, Patrick Alfred Pierce. 1950. +"Notes on continuous stochastic phenomena". +\emph{Biometrika} 37(1): 17-23. +\doi{10.2307/2332142} +} +} +\seealso{ +Other measures: +\code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, +\code{\link{measure_central_between}}, +\code{\link{measure_central_close}}, +\code{\link{measure_central_degree}}, +\code{\link{measure_central_eigen}}, +\code{\link{measure_closure}}, +\code{\link{measure_cohesion}}, +\code{\link{measure_diffusion_infection}}, +\code{\link{measure_diffusion_net}}, +\code{\link{measure_diffusion_node}}, +\code{\link{measure_features}}, +\code{\link{measure_fragmentation}}, +\code{\link{measure_heterogeneity}}, +\code{\link{measure_hierarchy}}, +\code{\link{measure_holes}}, +\code{\link{measure_periods}}, +\code{\link{member_diffusion}} +} +\concept{measures} diff --git a/man/measure_breadth.Rd b/man/measure_breadth.Rd index c86798d..17c9608 100644 --- a/man/measure_breadth.Rd +++ b/man/measure_breadth.Rd @@ -30,6 +30,8 @@ net_by_length(to_giant(fict_marvel)) } \seealso{ Other measures: +\code{\link{measure_assortativity}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_brokerage.Rd b/man/measure_brokerage.Rd new file mode 100644 index 0000000..a3f71b5 --- /dev/null +++ b/man/measure_brokerage.Rd @@ -0,0 +1,67 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/motif_brokerage.R +\name{measure_brokerage} +\alias{measure_brokerage} +\alias{node_by_brokering_activity} +\alias{node_by_brokering_exclusivity} +\title{Measures of brokerage} +\usage{ +node_by_brokering_activity(.data, membership) + +node_by_brokering_exclusivity(.data, membership) +} +\arguments{ +\item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. +For more information on the standard coercion possible, +see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} + +\item{membership}{A vector of partition membership as integers.} +} +\description{ +These functions include ways to measure nodes' brokerage activity and +exclusivity in a network: +\itemize{ +\item \code{node_brokering_activity()} measures nodes' brokerage activity. +\item \code{node_brokering_exclusivity()} measures nodes' brokerage exclusivity. +} +} +\examples{ +node_by_brokering_exclusivity(ison_networkers, "Discipline") +} +\references{ +\subsection{On brokerage activity and exclusivity}{ + +Hamilton, Matthew, Jacob Hileman, and Orjan Bodin. 2020. +"Evaluating heterogeneous brokerage: New conceptual and methodological approaches +and their application to multi-level environmental governance networks" +\emph{Social Networks} 61: 1-10. +\doi{10.1016/j.socnet.2019.08.002} +} +} +\seealso{ +Other measures: +\code{\link{measure_assortativity}}, +\code{\link{measure_breadth}}, +\code{\link{measure_central_between}}, +\code{\link{measure_central_close}}, +\code{\link{measure_central_degree}}, +\code{\link{measure_central_eigen}}, +\code{\link{measure_closure}}, +\code{\link{measure_cohesion}}, +\code{\link{measure_diffusion_infection}}, +\code{\link{measure_diffusion_net}}, +\code{\link{measure_diffusion_node}}, +\code{\link{measure_features}}, +\code{\link{measure_fragmentation}}, +\code{\link{measure_heterogeneity}}, +\code{\link{measure_hierarchy}}, +\code{\link{measure_holes}}, +\code{\link{measure_periods}}, +\code{\link{member_diffusion}} + +Other brokerage: +\code{\link{member_brokerage}}, +\code{\link{motif_brokerage}} +} +\concept{brokerage} +\concept{measures} diff --git a/man/measure_central_between.Rd b/man/measure_central_between.Rd index dc1c8de..4178777 100644 --- a/man/measure_central_between.Rd +++ b/man/measure_central_between.Rd @@ -101,9 +101,9 @@ nodes, and thus associated with bridging or spanning boundaries. \examples{ node_by_betweenness(ison_southern_women) node_by_induced(ison_adolescents) -(tb <- tie_betweenness(ison_adolescents)) +(tb <- tie_by_betweenness(ison_adolescents)) ison_adolescents \%>\% mutate_ties(weight = tb) -net_betweenness(ison_southern_women, direction = "in") +net_by_betweenness(ison_southern_women, direction = "in") } \references{ \subsection{On betweenness centrality}{ @@ -149,7 +149,9 @@ Other centrality: \code{\link{measure_central_eigen}} Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, \code{\link{measure_central_eigen}}, @@ -166,5 +168,6 @@ Other measures: \code{\link{measure_periods}}, \code{\link{member_diffusion}} } +\concept{betweenness} \concept{centrality} \concept{measures} diff --git a/man/measure_central_close.Rd b/man/measure_central_close.Rd index 2d33137..19d93fc 100644 --- a/man/measure_central_close.Rd +++ b/man/measure_central_close.Rd @@ -66,24 +66,27 @@ against only the centrality scores of the other nodes in that mode.} These functions calculate common closeness-related centrality measures that rely on path-length for one- and two-mode networks: \itemize{ -\item \code{node_closeness()} measures the closeness centrality of nodes in a +\item \code{node_by_closeness()} measures the closeness centrality of nodes in a network. -\item \code{node_reach()} measures nodes' reach centrality, -or how many nodes they can reach within \emph{k} steps. -\item \code{node_harmonic()} measures nodes' harmonic centrality or valued +\item \code{node_by_harmonic()} measures nodes' harmonic centrality or valued centrality, which is thought to behave better than reach centrality for disconnected networks. -\item \code{node_information()} measures nodes' information centrality or +\item \code{node_by_reach()} measures nodes' reach centrality, +or how many nodes they can reach within \emph{k} steps. +\item \code{node_by_information()} measures nodes' information centrality or current-flow closeness centrality. -\item \code{node_eccentricity()} measures nodes' eccentricity or maximum distance +\item \code{node_by_eccentricity()} measures nodes' eccentricity or maximum distance from another node in the network. -\item \code{node_distance()} measures nodes' geodesic distance from or to a +\item \code{node_by_distance()} measures nodes' geodesic distance from or to a +given node. +\item \code{node_by_vitality()} measures a network's closeness vitality centrality, +or the change in closeness centrality between networks with and without a given node. -\item \code{tie_closeness()} measures the closeness of each tie to other ties +\item \code{tie_by_closeness()} measures the closeness of each tie to other ties in the network. -\item \code{net_closeness()} measures a network's closeness centralization. -\item \code{net_reach()} measures a network's reach centralization. -\item \code{net_harmonic()} measures a network's harmonic centralization. +\item \code{net_by_closeness()} measures a network's closeness centralization. +\item \code{net_by_reach()} measures a network's reach centralization. +\item \code{net_by_harmonic()} measures a network's harmonic centralization. } All measures attempt to use as much information as they are offered, @@ -189,7 +192,7 @@ where \eqn{H_{ji}} is the hitting time from node \eqn{j} to node \eqn{i}. \examples{ node_by_closeness(ison_southern_women) node_by_reach(ison_adolescents) -(ec <- tie_closeness(ison_adolescents)) +(ec <- tie_by_closeness(ison_adolescents)) ison_adolescents \%>\% mutate_ties(weight = ec) net_by_closeness(ison_southern_women, direction = "in") } @@ -272,7 +275,9 @@ Other centrality: \code{\link{measure_central_eigen}} Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_degree}}, \code{\link{measure_central_eigen}}, diff --git a/man/measure_central_degree.Rd b/man/measure_central_degree.Rd index e94065f..c458233 100644 --- a/man/measure_central_degree.Rd +++ b/man/measure_central_degree.Rd @@ -136,7 +136,7 @@ neighbours, \eqn{J}: } \examples{ -node_degree(ison_southern_women) +node_by_degree(ison_southern_women) tie_by_degree(ison_adolescents) net_by_degree(ison_southern_women, direction = "in") } @@ -192,7 +192,9 @@ Other centrality: \code{\link{measure_central_eigen}} Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_eigen}}, @@ -208,6 +210,10 @@ Other measures: \code{\link{measure_holes}}, \code{\link{measure_periods}}, \code{\link{member_diffusion}} + +Other degree: +\code{\link{mark_degree}} } \concept{centrality} +\concept{degree} \concept{measures} diff --git a/man/measure_central_eigen.Rd b/man/measure_central_eigen.Rd index 5219735..3261673 100644 --- a/man/measure_central_eigen.Rd +++ b/man/measure_central_eigen.Rd @@ -223,7 +223,9 @@ Other centrality: \code{\link{measure_central_degree}} Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_closure.Rd b/man/measure_closure.Rd index 9befbd5..d36e57b 100644 --- a/man/measure_closure.Rd +++ b/man/measure_closure.Rd @@ -72,7 +72,7 @@ node_by_reciprocity(to_unweighted(ison_networkers)) net_by_transitivity(ison_adolescents) node_by_transitivity(ison_adolescents) net_by_equivalency(ison_southern_women) -node_by_equivalency(ison_southern_women) +# node_by_equivalency(ison_southern_women) } \references{ \subsection{On equivalency or four-cycles}{ @@ -93,7 +93,9 @@ Cambridge University Press. Cambridge University Press. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_cohesion.Rd b/man/measure_cohesion.Rd index fa56dae..1818a85 100644 --- a/man/measure_cohesion.Rd +++ b/man/measure_cohesion.Rd @@ -54,7 +54,9 @@ net_by_independence(ison_adolescents) } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_diffusion_infection.Rd b/man/measure_diffusion_infection.Rd index 048f7d3..43db7fb 100644 --- a/man/measure_diffusion_infection.Rd +++ b/man/measure_diffusion_infection.Rd @@ -43,13 +43,15 @@ highest infection rate is observed. \examples{ smeg <- generate_smallworld(15, 0.025) smeg_diff <- play_diffusion(smeg) - net_infection_complete(smeg_diff) + net_by_infection_complete(smeg_diff) net_by_infection_total(smeg_diff) net_by_infection_peak(smeg_diff) } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, @@ -67,9 +69,11 @@ Other measures: \code{\link{member_diffusion}} Other diffusion: +\code{\link{mark_diff}}, \code{\link{measure_diffusion_net}}, \code{\link{measure_diffusion_node}}, -\code{\link{member_diffusion}} +\code{\link{member_diffusion}}, +\code{\link{motif_diffusion}} } \concept{diffusion} \concept{measures} diff --git a/man/measure_diffusion_net.Rd b/man/measure_diffusion_net.Rd index 14e1977..27e9039 100644 --- a/man/measure_diffusion_net.Rd +++ b/man/measure_diffusion_net.Rd @@ -165,7 +165,9 @@ Garnett, G.P. 2005. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, @@ -183,9 +185,11 @@ Other measures: \code{\link{member_diffusion}} Other diffusion: +\code{\link{mark_diff}}, \code{\link{measure_diffusion_infection}}, \code{\link{measure_diffusion_node}}, -\code{\link{member_diffusion}} +\code{\link{member_diffusion}}, +\code{\link{motif_diffusion}} } \concept{diffusion} \concept{measures} diff --git a/man/measure_diffusion_node.Rd b/man/measure_diffusion_node.Rd index eb2469c..a4d5380 100644 --- a/man/measure_diffusion_node.Rd +++ b/man/measure_diffusion_node.Rd @@ -115,7 +115,9 @@ Valente, Tom W. 1995. \emph{Network models of the diffusion of innovations} } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, @@ -133,9 +135,11 @@ Other measures: \code{\link{member_diffusion}} Other diffusion: +\code{\link{mark_diff}}, \code{\link{measure_diffusion_infection}}, \code{\link{measure_diffusion_net}}, -\code{\link{member_diffusion}} +\code{\link{member_diffusion}}, +\code{\link{motif_diffusion}} } \concept{diffusion} \concept{measures} diff --git a/man/measure_features.Rd b/man/measure_features.Rd index 26fa8d2..4dc1322 100644 --- a/man/measure_features.Rd +++ b/man/measure_features.Rd @@ -75,22 +75,22 @@ The lower this parameter, the fewer larger communities are likely to be found.} \description{ These functions measure certain topological features of networks: \itemize{ -\item \code{net_core()} measures the correlation between a network +\item \code{net_by_core()} measures the correlation between a network and a core-periphery model with the same dimensions. -\item \code{net_richclub()} measures the rich-club coefficient of a network. -\item \code{net_factions()} measures the correlation between a network +\item \code{net_by_richclub()} measures the rich-club coefficient of a network. +\item \code{net_by_factions()} measures the correlation between a network and a component model with the same dimensions. If no 'membership' vector is given for the data, \code{node_partition()} is used to partition nodes into two groups. -\item \code{net_modularity()} measures the modularity of a network +\item \code{net_by_modularity()} measures the modularity of a network based on nodes' membership in defined clusters. -\item \code{net_smallworld()} measures the small-world coefficient for one- or +\item \code{net_by_smallworld()} measures the small-world coefficient for one- or two-mode networks. Small-world networks can be highly clustered and yet have short path lengths. -\item \code{net_scalefree()} measures the exponent of a fitted +\item \code{net_by_scalefree()} measures the exponent of a fitted power-law distribution. An exponent between 2 and 3 usually indicates a power-law distribution. -\item \code{net_balance()} measures the structural balance index on +\item \code{net_by_balance()} measures the structural balance index on the proportion of balanced triangles, ranging between \code{0} if all triangles are imbalanced and \code{1} if all triangles are balanced. @@ -132,16 +132,16 @@ net_by_core(ison_adolescents) net_by_core(ison_southern_women) net_by_richclub(ison_adolescents) net_by_factions(ison_southern_women) -net_modularity(ison_adolescents, +net_by_modularity(ison_adolescents, node_in_partition(ison_adolescents)) -net_modularity(ison_southern_women, +net_by_modularity(ison_southern_women, node_in_partition(ison_southern_women)) net_by_smallworld(ison_brandes) net_by_smallworld(ison_southern_women) net_by_scalefree(ison_adolescents) net_by_scalefree(generate_scalefree(50, 1.5)) net_by_scalefree(create_lattice(100)) -net_by_balance(fict_marvel) +net_by_balance(to_uniplex(fict_marvel, "relationship")) } \references{ \subsection{On core-periphery}{ @@ -230,11 +230,13 @@ Cartwright, D., and Frank Harary. 1956. } } \seealso{ -\code{\link[manynet:measure_closure]{manynet::net_transitivity()}} and \code{\link[manynet:measure_closure]{manynet::net_equivalency()}} +\code{\link[=net_by_transitivity]{net_by_transitivity()}} and \code{\link[=net_by_equivalency]{net_by_equivalency()}} for how clustering is calculated Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_fragmentation.Rd b/man/measure_fragmentation.Rd index 0774062..29b1427 100644 --- a/man/measure_fragmentation.Rd +++ b/man/measure_fragmentation.Rd @@ -54,7 +54,9 @@ White, Douglas R and Frank Harary. 2001. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_heterogeneity.Rd b/man/measure_heterogeneity.Rd index 05a456c..ac38934 100644 --- a/man/measure_heterogeneity.Rd +++ b/man/measure_heterogeneity.Rd @@ -6,12 +6,6 @@ \alias{node_by_richness} \alias{net_by_diversity} \alias{node_by_diversity} -\alias{net_by_heterophily} -\alias{node_by_heterophily} -\alias{net_by_homophily} -\alias{node_by_homophily} -\alias{net_by_assortativity} -\alias{net_by_spatial} \title{Measures of network diversity} \usage{ net_by_richness(.data, attribute) @@ -29,18 +23,6 @@ node_by_diversity( attribute, method = c("blau", "teachman", "variation", "gini") ) - -net_by_heterophily(.data, attribute) - -node_by_heterophily(.data, attribute) - -net_by_homophily(.data, attribute, method = c("ie", "ei", "yule", "geary")) - -node_by_homophily(.data, attribute, method = c("ie", "ei", "yule", "geary")) - -net_by_assortativity(.data) - -net_by_spatial(.data, attribute) } \arguments{ \item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. @@ -142,32 +124,14 @@ and the Lorenz curve, divided by the total area under the line of equality. } -\section{Homophily}{ - -Given a partition of a network into a number of mutually exclusive groups then -The E-I index is the number of ties between (or \emph{external}) nodes -grouped in some mutually exclusive categories -minus the number of ties within (or \emph{internal}) these groups -divided by the total number of ties. -This value can range from 1 to -1, -where 1 indicates ties only between categories/groups and -1 ties only within categories/groups. -} - \examples{ net_by_richness(ison_networkers) node_by_richness(ison_networkers, "Discipline") -marvel_friends <- to_unsigned(fict_marvel, "positive") +marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") net_by_diversity(marvel_friends, "Gender") net_by_diversity(marvel_friends, "Appearances") node_by_diversity(marvel_friends, "Gender") node_by_diversity(marvel_friends, "Attractive") -net_by_heterophily(marvel_friends, "Gender") -net_by_heterophily(marvel_friends, "Attractive") -node_by_heterophily(marvel_friends, "Gender") -node_by_heterophily(marvel_friends, "Attractive") -net_by_homophily(marvel_friends, "Gender") -net_by_assortativity(ison_networkers) -net_by_spatial(ison_lawfirm, "age") } \references{ \subsection{On richness}{ @@ -194,39 +158,12 @@ Page, Scott E. 2010. Princeton: Princeton University Press. \doi{10.1515/9781400835140} } - -\subsection{On heterophily}{ - -Krackhardt, David, and Robert N. Stern. 1988. -Informal networks and organizational crises: an experimental simulation. -\emph{Social Psychology Quarterly} 51(2): 123-140. -\doi{10.2307/2786835} - -McPherson, Miller, Lynn Smith-Lovin, and James M. Cook. 2001. -"Birds of a Feather: Homophily in Social Networks". -\emph{Annual Review of Sociology}, 27(1): 415-444. -\doi{10.1146/annurev.soc.27.1.415} -} - -\subsection{On assortativity}{ - -Newman, Mark E.J. 2002. -"Assortative mixing in networks". -\emph{Physical Review Letters}, 89(20): 208701. -\doi{10.1103/physrevlett.89.208701} -} - -\subsection{On spatial autocorrelation}{ - -Moran, Patrick Alfred Pierce. 1950. -"Notes on continuous stochastic phenomena". -\emph{Biometrika} 37(1): 17-23. -\doi{10.2307/2332142} -} } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_hierarchy.Rd b/man/measure_hierarchy.Rd index 6a03126..3f9ca79 100644 --- a/man/measure_hierarchy.Rd +++ b/man/measure_hierarchy.Rd @@ -2,14 +2,11 @@ % Please edit documentation in R/measure_hierarchy.R \name{measure_hierarchy} \alias{measure_hierarchy} -\alias{net_by_hierarchy} \alias{net_by_connectedness} \alias{net_by_efficiency} \alias{net_by_upperbound} -\title{Graph theoretic dimensions of hierarchy} +\title{Measures of hierarchy} \usage{ -net_by_hierarchy(.data) - net_by_connectedness(.data) net_by_efficiency(.data) @@ -33,10 +30,10 @@ or the degree to which network is a single component. } } \examples{ -net_connectedness(ison_networkers) -1 - net_reciprocity(ison_networkers) -net_efficiency(ison_networkers) -net_upperbound(ison_networkers) +net_by_connectedness(ison_networkers) +1 - net_by_reciprocity(ison_networkers) +net_by_efficiency(ison_networkers) +net_by_upperbound(ison_networkers) } \references{ \subsection{On hierarchy}{ @@ -54,7 +51,9 @@ Everett, Martin, and David Krackhardt. 2012. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, @@ -70,5 +69,9 @@ Other measures: \code{\link{measure_holes}}, \code{\link{measure_periods}}, \code{\link{member_diffusion}} + +Other hierarchy: +\code{\link{motif_hierarchy}} } +\concept{hierarchy} \concept{measures} diff --git a/man/measure_holes.Rd b/man/measure_holes.Rd index f8580d3..4c1230d 100644 --- a/man/measure_holes.Rd +++ b/man/measure_holes.Rd @@ -111,7 +111,9 @@ Barrat, Alain, Marc Barthelemy, Romualdo Pastor-Satorras, and Alessandro Vespign } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/measure_periods.Rd b/man/measure_periods.Rd index b687d2e..067a754 100644 --- a/man/measure_periods.Rd +++ b/man/measure_periods.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/measure_features.R +% Please edit documentation in R/measure_change.R \name{measure_periods} \alias{measure_periods} \alias{net_by_waves} @@ -26,10 +26,10 @@ see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} \description{ These functions measure certain topological features of networks: \itemize{ -\item \code{net_waves()} measures the number of waves in longitudinal network data. -\item \code{net_change()} measures the Hamming distance between two or more networks. -\item \code{net_stability()} measures the Jaccard index of stability between two or more networks. -\item \code{net_correlation()} measures the product-moment correlation between two networks. +\item \code{net_by_waves()} measures the number of waves in longitudinal network data. +\item \code{net_by_change()} measures the Hamming distance between two or more networks. +\item \code{net_by_stability()} measures the Jaccard index of stability between two or more networks. +\item \code{net_by_correlation()} measures the product-moment correlation between two networks. } These \verb{net_*()} functions return a numeric vector the length of the number @@ -37,7 +37,9 @@ of networks minus one. E.g., the periods between waves. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, diff --git a/man/member_brokerage.Rd b/man/member_brokerage.Rd index 2aa0a39..5dceab8 100644 --- a/man/member_brokerage.Rd +++ b/man/member_brokerage.Rd @@ -30,5 +30,10 @@ Other memberships: \code{\link{member_community_non}}, \code{\link{member_components}}, \code{\link{member_equivalence}} + +Other brokerage: +\code{\link{measure_brokerage}}, +\code{\link{motif_brokerage}} } +\concept{brokerage} \concept{memberships} diff --git a/man/member_community_hier.Rd b/man/member_community_hier.Rd index 546516d..f2232b7 100644 --- a/man/member_community_hier.Rd +++ b/man/member_community_hier.Rd @@ -6,7 +6,7 @@ \alias{node_in_greedy} \alias{node_in_eigen} \alias{node_in_walktrap} -\title{Hierarchical community partitioning algorithms} +\title{Hierarchical community clustering algorithms} \usage{ node_in_betweenness(.data) @@ -130,5 +130,9 @@ Other memberships: \code{\link{member_community_non}}, \code{\link{member_components}}, \code{\link{member_equivalence}} + +Other community: +\code{\link{member_community_non}} } +\concept{community} \concept{memberships} diff --git a/man/member_community_non.Rd b/man/member_community_non.Rd index 2265ad1..e1ad097 100644 --- a/man/member_community_non.Rd +++ b/man/member_community_non.Rd @@ -10,7 +10,7 @@ \alias{node_in_fluid} \alias{node_in_louvain} \alias{node_in_leiden} -\title{Non-hierarchical community partitioning algorithms} +\title{Non-hierarchical community clustering algorithms} \usage{ node_in_community(.data) @@ -229,5 +229,9 @@ Other memberships: \code{\link{member_community_hier}}, \code{\link{member_components}}, \code{\link{member_equivalence}} + +Other community: +\code{\link{member_community_hier}} } +\concept{community} \concept{memberships} diff --git a/man/member_components.Rd b/man/member_components.Rd index 19890af..156926a 100644 --- a/man/member_components.Rd +++ b/man/member_components.Rd @@ -5,7 +5,7 @@ \alias{node_in_component} \alias{node_in_weak} \alias{node_in_strong} -\title{Component partitioning algorithms} +\title{Membership in components} \usage{ node_in_component(.data) diff --git a/man/member_diffusion.Rd b/man/member_diffusion.Rd index e22b14c..d067505 100644 --- a/man/member_diffusion.Rd +++ b/man/member_diffusion.Rd @@ -48,7 +48,9 @@ Valente, Tom W. 1995. } \seealso{ Other measures: +\code{\link{measure_assortativity}}, \code{\link{measure_breadth}}, +\code{\link{measure_brokerage}}, \code{\link{measure_central_between}}, \code{\link{measure_central_close}}, \code{\link{measure_central_degree}}, @@ -66,9 +68,11 @@ Other measures: \code{\link{measure_periods}} Other diffusion: +\code{\link{mark_diff}}, \code{\link{measure_diffusion_infection}}, \code{\link{measure_diffusion_net}}, -\code{\link{measure_diffusion_node}} +\code{\link{measure_diffusion_node}}, +\code{\link{motif_diffusion}} } \concept{diffusion} \concept{measures} diff --git a/man/motif_brokerage.Rd b/man/motif_brokerage.Rd index 93693e9..bcf5502 100644 --- a/man/motif_brokerage.Rd +++ b/man/motif_brokerage.Rd @@ -4,17 +4,11 @@ \alias{motif_brokerage} \alias{node_x_brokerage} \alias{net_x_brokerage} -\alias{node_by_brokering_activity} -\alias{node_by_brokering_exclusivity} \title{Motifs of brokerage} \usage{ node_x_brokerage(.data, membership, standardized = FALSE) net_x_brokerage(.data, membership, standardized = FALSE) - -node_by_brokering_activity(.data, membership) - -node_by_brokering_exclusivity(.data, membership) } \arguments{ \item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. @@ -42,7 +36,6 @@ roles in a network. \examples{ # node_x_brokerage(ison_networkers, "Discipline") # net_x_brokerage(ison_networkers, "Discipline") -node_by_brokering_exclusivity(ison_networkers, "Discipline") } \references{ \subsection{On brokerage motifs}{ @@ -57,20 +50,17 @@ Jasny, Lorien, and Mark Lubell. 2015. \emph{Social Networks} 41:36–47. \doi{10.1016/j.socnet.2014.11.005} } - -\subsection{On brokerage activity and exclusivity}{ - -Hamilton, Matthew, Jacob Hileman, and Orjan Bodin. 2020. -"Evaluating heterogeneous brokerage: New conceptual and methodological approaches -and their application to multi-level environmental governance networks" -\emph{Social Networks} 61: 1-10. -\doi{10.1016/j.socnet.2019.08.002} -} } \seealso{ Other motifs: \code{\link{motif_diffusion}}, +\code{\link{motif_hierarchy}}, \code{\link{motif_net}}, \code{\link{motif_node}} + +Other brokerage: +\code{\link{measure_brokerage}}, +\code{\link{member_brokerage}} } +\concept{brokerage} \concept{motifs} diff --git a/man/motif_diffusion.Rd b/man/motif_diffusion.Rd index d99ce11..4a7541c 100644 --- a/man/motif_diffusion.Rd +++ b/man/motif_diffusion.Rd @@ -90,7 +90,16 @@ Cambridge: MIT Press. \seealso{ Other motifs: \code{\link{motif_brokerage}}, +\code{\link{motif_hierarchy}}, \code{\link{motif_net}}, \code{\link{motif_node}} + +Other diffusion: +\code{\link{mark_diff}}, +\code{\link{measure_diffusion_infection}}, +\code{\link{measure_diffusion_net}}, +\code{\link{measure_diffusion_node}}, +\code{\link{member_diffusion}} } +\concept{diffusion} \concept{motifs} diff --git a/man/motif_hierarchy.Rd b/man/motif_hierarchy.Rd new file mode 100644 index 0000000..ac38c62 --- /dev/null +++ b/man/motif_hierarchy.Rd @@ -0,0 +1,59 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/measure_hierarchy.R +\name{motif_hierarchy} +\alias{motif_hierarchy} +\alias{net_x_hierarchy} +\title{Motifs of hierarchy} +\usage{ +net_x_hierarchy(.data) +} +\arguments{ +\item{.data}{A network object of class \code{mnet}, \code{igraph}, \code{tbl_graph}, \code{network}, or similar. +For more information on the standard coercion possible, +see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} +} +\description{ +This function collects the measures of hierarchy into a single motif, +which can be used to compare the relative hierarchy of different networks. +The measures of hierarchy are: +\itemize{ +\item \code{net_connectedness()} measures the proportion of dyads in the network +that are reachable to one another, +or the degree to which network is a single component. +\item \code{net_efficiency()} measures the Krackhardt efficiency score. +\item \code{net_upperbound()} measures the Krackhardt (least) upper bound +score. +\item \code{net_reciprocity()} measures the proportion of ties in the network that +are reciprocated, +which is a measure of the degree to which the network is non-hierarchical. +} +} +\examples{ +net_x_hierarchy(ison_networkers) +} +\references{ +\subsection{On hierarchy}{ + +Krackhardt, David. 1994. +Graph theoretical dimensions of informal organizations. +In Carley and Prietula (eds) \emph{Computational Organizational Theory}, +Hillsdale, NJ: Lawrence Erlbaum Associates. Pp. 89-111. + +Everett, Martin, and David Krackhardt. 2012. +“A second look at Krackhardt's graph theoretical dimensions of informal organizations.” +\emph{Social Networks}, 34: 159-163. +\doi{10.1016/j.socnet.2011.10.006} +} +} +\seealso{ +Other motifs: +\code{\link{motif_brokerage}}, +\code{\link{motif_diffusion}}, +\code{\link{motif_net}}, +\code{\link{motif_node}} + +Other hierarchy: +\code{\link{measure_hierarchy}} +} +\concept{hierarchy} +\concept{motifs} diff --git a/man/motif_net.Rd b/man/motif_net.Rd index 3223627..ea267f0 100644 --- a/man/motif_net.Rd +++ b/man/motif_net.Rd @@ -24,7 +24,7 @@ net_x_mixed(.data, object2) For more information on the standard coercion possible, see \code{\link[manynet:manip_as]{manynet::as_tidygraph()}}.} -\item{object2}{A second, two-mode migraph-consistent object.} +\item{object2}{A second, two-mode network object.} } \description{ These functions include ways to take a census of the graphlets @@ -39,6 +39,59 @@ a one-mode and a two-mode network. See also \href{https://www.graphclasses.org/smallgraphs.html}{graph classes}. } +\section{Dyad census}{ + +The dyad census counts the number of mutual, asymmetric, and null dyads +in a network. +For directed networks, +\itemize{ +\item Mutual dyads have ties in both directions +\item Asymmetric dyads have a tie in one direction only +\item Null dyads have no ties +} + +Note that for undirected and two-mode networks, +only mutual and null dyads are possible, +as the concept of an asymmetric dyad does not apply. +} + +\section{Triad census}{ + +The triad census counts the number of three-node configurations in the network. +The function returns a matrix with a special naming convention: +\itemize{ +\item 003: This is an empty triad; no ties +\item 012: This triad includes one tie +\item 102: This triad includes two ties, but they are not reciprocated +\item 021D: This triad includes two ties, one of which is reciprocated, and the other is directed towards the reciprocated tie +\item 021U: This triad includes two ties, one of which is +reciprocated, and the other is directed away from the reciprocated tie +\item 021C: This triad includes two ties, one of which is reciprocated, and the other is directed between the two non-reciprocated nodes +\item 111D: This triad includes three ties, two of which are +reciprocated, and the other is directed towards the reciprocated ties +\item 111U: This triad includes three ties, two of which are +reciprocated, and the other is directed away from the reciprocated ties +\item 030T: This triad includes three ties, all of which are +directed in a transitive manner (i.e. A->B, B->C, A->C) +\item 030C: This triad includes three ties, all of which are +directed in a cyclic manner (i.e. A->B, B->C +A->C) +\item 201: This triad includes three ties, all of which are reciproc +ated (i.e. A<->B, B<->C, A<->C) +\item 120D: This triad includes four ties, three of which are +reciprocated, and the other is directed towards the reciprocated ties +\item 120U: This triad includes four ties, three of which are +reciprocated, and the other is directed away from the reciprocated ties +\item 120C: This triad includes four ties, three of which are +reciprocated, and the other is directed between the two non-reciprocated +\item 210: This triad includes five ties, four of which are reciprocated, and the other is directed between the two non-reciprocated +\item 300: This triad includes six ties, all of which are reciprocated +} + +Note that for undirected and two-mode networks, only 003, 102, and 201 are possible, +as the other configurations rely on the concept of directionality. +} + \section{Tetrad census}{ The tetrad census counts the number of four-node configurations in the network. @@ -70,8 +123,7 @@ Graphs of these motifs can be shown using net_x_dyad(manynet::ison_algebra) net_x_triad(manynet::ison_adolescents) net_x_tetrad(ison_southern_women) -marvel_friends <- to_unsigned(fict_marvel, "positive") -(mixed_cen <- net_x_mixed(marvel_friends, ison_marvel_teams)) +net_x_mixed(fict_marvel) } \references{ \subsection{On the dyad census}{ @@ -117,6 +169,7 @@ Hollway, James, Alessandro Lomi, Francesca Pallotti, and Christoph Stadtfeld. 20 Other motifs: \code{\link{motif_brokerage}}, \code{\link{motif_diffusion}}, +\code{\link{motif_hierarchy}}, \code{\link{motif_node}} } \concept{motifs} diff --git a/man/motif_node.Rd b/man/motif_node.Rd index 0140269..353f4c7 100644 --- a/man/motif_node.Rd +++ b/man/motif_node.Rd @@ -121,6 +121,7 @@ Opsahl, Tore, Filip Agneessens, and John Skvoretz. 2010. Other motifs: \code{\link{motif_brokerage}}, \code{\link{motif_diffusion}}, +\code{\link{motif_hierarchy}}, \code{\link{motif_net}} } \concept{motifs} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml new file mode 100644 index 0000000..ec08648 --- /dev/null +++ b/pkgdown/_pkgdown.yml @@ -0,0 +1,103 @@ +url: https://stocnet.github.io/netrics/ +development: + mode: auto +template: + bootswatch: superhero +authors: + James Hollway: + href: https://jameshollway.com +navbar: + structure: + left: + - home + - intro + - reference + - news + right: + - search + - github + - cran + components: + home: + icon: fa-home fa-lg + href: index.html + aria-label: Go to home + reference: + text: Function Overview + href: reference/index.html + news: + text: News + href: news/index.html + github: + icon: "fab fa-github fa-lg" + href: https://github.com/stocnet/netrics + aria-label: View on Github + # cran: + # icon: "fab fa-r-project" + # href: https://cloud.r-project.org/package=netrics + # aria-label: View on CRAN +reference: + - title: "Marking" + desc: | + Functions for identifying properties of nodes or ties, + all returning logical scalars or vectors. + - subtitle: "Nodal marks" + desc: | + `node_is_*()` functions return a vector of logical values the length + of the nodes in the network. + contents: + - starts_with("node_is_") + - subtitle: "Tie marks" + desc: | + `tie_is_*()` functions return a vector of logical values the length + of the ties in the network. + contents: + - starts_with("tie_is_") + + - title: "Measuring" + desc: | + Functions for measuring networks and returning a numeric vector or value. + `net_` measures return one or, in some cases of two-mode measures, + two values. + All `node_` and `tie_` measures return a single vector, + the length of the nodes or ties in the network, respectively. + - subtitle: "Centrality" + contents: + - starts_with("measure_central") + - measure_holes + - measure_brokerage + - measure_hierarchy + - subtitle: "Cohesion" + contents: + - measure_cohesion + - measure_closure + - measure_features + - measure_heterogeneity + - measure_assortativity + - measure_fragmentation + - measure_breadth + - subtitle: "Dynamics" + contents: + - measure_periods + - starts_with("measure_diffusion") + + - title: "Memberships" + desc: | + Motifs are functions for calculating network subgraphs, + always return a matrix or table of nodes as rows and motif or other property as columns, + and can be recognised by the `_by_` in the function name. + Memberships are functions for identifying community, cluster, or class memberships, + always return a string vector the length of the nodes in the network, + and can be recognised by the `_in_` in the function name. + - subtitle: "Motifs" + contents: + - contains("_x_") + - subtitle: "Members" + contents: + - contains("_in_") + + - title: "Methods" + desc: "Methods used in other functions but documented here:" + contents: + - starts_with("model_") + diff --git a/pkgdown/favicon/apple-touch-icon.png b/pkgdown/favicon/apple-touch-icon.png new file mode 100644 index 0000000..3f1f39a Binary files /dev/null and b/pkgdown/favicon/apple-touch-icon.png differ diff --git a/pkgdown/favicon/favicon-96x96.png b/pkgdown/favicon/favicon-96x96.png new file mode 100644 index 0000000..f40cd98 Binary files /dev/null and b/pkgdown/favicon/favicon-96x96.png differ diff --git a/pkgdown/favicon/favicon.ico b/pkgdown/favicon/favicon.ico new file mode 100644 index 0000000..379d961 Binary files /dev/null and b/pkgdown/favicon/favicon.ico differ diff --git a/pkgdown/favicon/favicon.svg b/pkgdown/favicon/favicon.svg new file mode 100644 index 0000000..d538cec --- /dev/null +++ b/pkgdown/favicon/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkgdown/favicon/site.webmanifest b/pkgdown/favicon/site.webmanifest new file mode 100644 index 0000000..4ebda26 --- /dev/null +++ b/pkgdown/favicon/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/pkgdown/favicon/web-app-manifest-192x192.png b/pkgdown/favicon/web-app-manifest-192x192.png new file mode 100644 index 0000000..d23f1c7 Binary files /dev/null and b/pkgdown/favicon/web-app-manifest-192x192.png differ diff --git a/pkgdown/favicon/web-app-manifest-512x512.png b/pkgdown/favicon/web-app-manifest-512x512.png new file mode 100644 index 0000000..1b29b7a Binary files /dev/null and b/pkgdown/favicon/web-app-manifest-512x512.png differ diff --git a/tests/testthat/helper-netrics.R b/tests/testthat/helper-netrics.R index 0353258..cc9a3c5 100644 --- a/tests/testthat/helper-netrics.R +++ b/tests/testthat/helper-netrics.R @@ -1,10 +1,6 @@ options(manynet_verbosity = "quiet") options(snet_verbosity = "quiet") -collect_functions <- function(pattern, package = "netrics"){ - getNamespaceExports(package)[grepl(pattern, getNamespaceExports(package))] -} - expect_values <- function(object, ref, toler = 3) { # 1. Capture object and label # act <- quasi_label(rlang::enquo(object), arg = "object") @@ -66,3 +62,31 @@ bot5 <- function(res, dec = 4){ unname(round(res, dec))[(lr-4):lr] } else unname(res)[(lr-2):lr] } + +collect_functions <- function(pattern, package = "netrics"){ + getNamespaceExports(package)[grepl(pattern, getNamespaceExports(package))] +} +funs_objs <- mget(ls("package:netrics"), inherits = TRUE) + +# data_objs <- mget(ls("package:manynet"), inherits = TRUE) +# # Filter to relevant objects +# # data_objs <- data_objs[grepl("ison_|fict_|irps_|mpn_", names(data_objs))] +# # data_objs <- data_objs[!grepl("starwars|physicians|potter", names(data_objs))] +# objs <- table_data() %>% dplyr::filter(!grepl("starwars|physicians|potter", dataset)) %>% +# dplyr::distinct(directed, weighted, twomode, labelled, signed, multiplex, longitudinal, dynamic, changing, .keep_all = TRUE) %>% +# dplyr::pull(dataset) %>% as.character() +# data_objs <- data_objs[objs] + +set.seed(1234) +data_objs <- list(directed = generate_random(12, directed = TRUE), + twomode = generate_random(c(6,6)), + labelled = to_signed(add_node_attribute(create_wheel(12), "name", + LETTERS[1:12])), + attribute = add_node_attribute(create_ring(12), "group", + rep(c("A","B"), each = 6)), + weighted = add_tie_attribute(create_ring(12), "weight", + rep(c(1,2), each = 6)), + diffusion = play_diffusion(create_ring(12), seeds = 1, + steps = 5, latency = 0.75, + recovery = 0.25)) + diff --git a/tests/testthat/test-mark_nodes.R b/tests/testthat/test-mark_nodes.R index c20adde..03ddee5 100644 --- a/tests/testthat/test-mark_nodes.R +++ b/tests/testthat/test-mark_nodes.R @@ -1,17 +1,39 @@ set.seed(1234) +node_marks <- funs_objs[grepl("node_is_", names(funs_objs))] +for(fn in names(node_marks)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + if(fn == "node_is_exposed"){ + expect_s3_class(node_marks[[fn]](data_objs[[ob]], mark = c(1,3)), + "node_mark") + } else if(fn == "node_is_core"){ + skip_if(fn == "node_is_core" && manynet::is_directed(data_objs[[ob]])) + expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") + } else if(fn == "node_is_neighbor"){ + expect_s3_class(node_marks[[fn]](data_objs[[ob]], node = 1), + "node_mark") + } else if(grepl("recovered|latent|infected", fn)){ + if(ob == "diffusion") + expect_s3_class(node_marks[[fn]](data_objs[[ob]]), + "node_mark") else succeed("Only used for diffusion objects") + } else if(grepl("min|max|mean", fn)){ + expect_s3_class(node_marks[[fn]](node_by_deg(data_objs[[ob]])), + "node_mark") + } else { + expect_s3_class(node_marks[[fn]](data_objs[[ob]]), "node_mark") + } + }) + } +} + test_that("node_is_cutpoint", { - expect_true(exists("node_is_cutpoint")) - test_that("returns correct type", { - expect_s3_class(node_is_cutpoint(ison_algebra), "node_mark") - }) expect_length(node_is_cutpoint(ison_southern_women), c(net_nodes(ison_southern_women))) }) test_that("node_is_isolate", { f <- node_is_isolate - expect_true(is.function(f)) test <- f(ison_brandes) test_that("returns correct values", { expect_equal(length(test), c(net_nodes(ison_brandes))) @@ -24,7 +46,6 @@ test_that("node_is_isolate", { test_that("node_is_fold works", { test <- node_is_fold(create_explicit(A-B, B-C, A-C, C-D, C-E, D-E)) expect_equal(as.logical(test), c(F,F,T,F,F)) - expect_s3_class(test, "node_mark") }) test_that("node_is_neighbor works", { @@ -34,23 +55,23 @@ test_that("node_is_neighbor works", { test_that("node_is_max works", { # skip_on_cran() # skip_on_ci() - expect_equal(length(node_is_max(node_betweenness(ison_brandes))), + expect_equal(length(node_is_max(node_by_betweenness(ison_brandes))), c(net_nodes(ison_brandes))) - expect_equal(sum(node_is_max(node_betweenness(ison_brandes)) == TRUE), 1) - expect_s3_class(node_is_max(node_betweenness(ison_brandes)), "logical") + expect_equal(sum(node_is_max(node_by_betweenness(ison_brandes)) == TRUE), 1) + expect_s3_class(node_is_max(node_by_betweenness(ison_brandes)), "logical") }) test_that("node_is_min works", { # skip_on_cran() # skip_on_ci() - expect_equal(length(node_is_min(node_betweenness(ison_brandes))), + expect_equal(length(node_is_min(node_by_betweenness(ison_brandes))), c(net_nodes(ison_brandes))) - expect_equal(sum(node_is_min(node_betweenness(ison_brandes)) == TRUE), 4) - expect_s3_class(node_is_min(node_betweenness(ison_brandes)), "logical") + expect_equal(sum(node_is_min(node_by_betweenness(ison_brandes)) == TRUE), 4) + expect_s3_class(node_is_min(node_by_betweenness(ison_brandes)), "logical") }) test_that("node_is_mean works", { - expect_s3_class(node_is_mean(node_betweenness(ison_brandes)), "logical") + expect_s3_class(node_is_mean(node_by_betweenness(ison_brandes)), "logical") }) test_that("additional node mark functions work", { diff --git a/tests/testthat/test-mark_ties.R b/tests/testthat/test-mark_ties.R index a921e65..535c4b6 100644 --- a/tests/testthat/test-mark_ties.R +++ b/tests/testthat/test-mark_ties.R @@ -1,3 +1,24 @@ +tie_marks <- funs_objs[grepl("tie_is_", names(funs_objs))] +for(fn in names(tie_marks)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("tie_is_imbalanced", fn) && ob == "twomode") + if(fn == "tie_is_path"){ + expect_s3_class(tie_marks[[fn]](data_objs[[ob]], 1, 2), "tie_mark") + } else if(grepl("infected|recovered", fn)){ + if(ob == "diffusion") + expect_s3_class(tie_marks[[fn]](data_objs[[ob]]), "tie_mark") else + success("Only used for diffusion objects") + } else if(grepl("max|min", fn)){ + skip_if_not(packageVersion("manynet") >= "1.7.3") + expect_s3_class(tie_marks[[fn]](tie_by_degree(data_objs[[ob]])), "tie_mark") + } else { + expect_s3_class(tie_marks[[fn]](data_objs[[ob]]), "tie_mark") + } + }) + } +} + graph1 <- igraph::make_directed_graph(c(1,2,1,5,2,3,2,4,3,5,4,5,5,1)) graph2 <- igraph::make_undirected_graph(c(1,1,1,2,2,4,3,4,3,4)) @@ -39,19 +60,19 @@ test_that("directed triangle tie marks work", { test_that("tie_is_max works", { skip_on_ci() skip_on_cran() - expect_equal(length(tie_is_max(tie_betweenness(graph1))), + expect_equal(length(tie_is_max(tie_by_betweenness(graph1))), c(net_ties(graph1))) - expect_equal(sum(tie_is_max(tie_betweenness(graph1)) == TRUE), 1) - expect_s3_class(tie_is_max(tie_betweenness(graph1)), "logical") + expect_equal(sum(tie_is_max(tie_by_betweenness(graph1)) == TRUE), 1) + expect_s3_class(tie_is_max(tie_by_betweenness(graph1)), "logical") }) test_that("tie_is_min works", { skip_on_ci() skip_on_cran() - expect_equal(length(tie_is_min(tie_betweenness(ison_brandes))), + expect_equal(length(tie_is_min(tie_by_betweenness(ison_brandes))), c(net_ties(ison_brandes))) - expect_equal(sum(tie_is_min(tie_betweenness(ison_brandes)) == TRUE), 1) - expect_s3_class(tie_is_min(tie_betweenness(ison_brandes)), "logical") + expect_equal(sum(tie_is_min(tie_by_betweenness(ison_brandes)) == TRUE), 1) + expect_s3_class(tie_is_min(tie_by_betweenness(ison_brandes)), "logical") }) test_that("tie_is_feedback() mark functions work", { diff --git a/tests/testthat/test-measure_centrality.R b/tests/testthat/test-measure_centrality.R index 30e9183..df85781 100644 --- a/tests/testthat/test-measure_centrality.R +++ b/tests/testthat/test-measure_centrality.R @@ -3,163 +3,127 @@ test_igr <- ison_southern_women test_mat <- as_matrix(ison_southern_women) test_that("one mode degree centrality calculated correctly",{ - expect_equal(top5(node_degree(ison_adolescents, normalized = FALSE)), c(1,4,4,2,3)) + expect_equal(top5(node_by_degree(ison_adolescents, normalized = FALSE)), c(1,4,4,2,3)) }) test_that("one mode strength centrality calculated correctly",{ - expect_equal(top5(node_degree(to_unweighted(ison_networkers), direction = "in", normalized = FALSE)), + expect_equal(top5(node_by_degree(to_unweighted(ison_networkers), direction = "in", normalized = FALSE)), c(29, 24, 11, 18, 8)) - expect_equal(top5(node_degree(ison_networkers, direction = "in", normalized = FALSE, alpha = 1)), + expect_equal(top5(node_by_degree(ison_networkers, direction = "in", normalized = FALSE, alpha = 1)), c(2495, 1212, 101, 322, 89)) }) test_that("two mode degree centrality calculated correctly",{ - expect_equal(top5(node_degree(test_mat, normalized = FALSE)), c(8,7,8,7,4)) - expect_equal(top5(node_degree(test_igr, normalized = FALSE)), c(8,7,8,7,4)) - expect_equal(top5(with_graph(test_tbl, node_degree(normalized = FALSE))), c(8,7,8,7,4)) - expect_equal(bot3(node_degree(test_mat, normalized = FALSE)), c(6,3,3)) - expect_equal(bot3(node_degree(test_igr, normalized = FALSE)), c(6,3,3)) - expect_equal(bot3(with_graph(test_tbl, node_degree(normalized = FALSE))), c(6,3,3)) - expect_equal(top5(node_degree(test_mat, normalized = TRUE)), c(0.5714, .5, .5714, .5, .2857)) - expect_equal(top5(node_degree(test_igr, normalized = TRUE)), c(0.5714, .5, .5714, .5, .2857)) - expect_equal(top5(with_graph(test_tbl, node_degree(normalized = TRUE))), c(0.5714, .5, .5714, .5, .2857)) - expect_equal(bot3(node_degree(test_mat, normalized = TRUE)), c(.3333, .1667, .1667)) - expect_equal(bot3(node_degree(test_igr, normalized = TRUE)), c(.3333, .1667, .1667)) - expect_equal(bot3(with_graph(test_tbl, node_degree(normalized = TRUE))), c(.3333, .1667, .1667)) + expect_equal(top5(node_by_degree(test_mat, normalized = FALSE)), c(8,7,8,7,4)) + expect_equal(top5(node_by_degree(test_igr, normalized = FALSE)), c(8,7,8,7,4)) + expect_equal(top5(with_graph(test_tbl, node_by_degree(normalized = FALSE))), c(8,7,8,7,4)) + expect_equal(bot3(node_by_degree(test_mat, normalized = FALSE)), c(6,3,3)) + expect_equal(bot3(node_by_degree(test_igr, normalized = FALSE)), c(6,3,3)) + expect_equal(bot3(with_graph(test_tbl, node_by_degree(normalized = FALSE))), c(6,3,3)) + expect_equal(top5(node_by_degree(test_mat, normalized = TRUE)), c(0.5714, .5, .5714, .5, .2857)) + expect_equal(top5(node_by_degree(test_igr, normalized = TRUE)), c(0.5714, .5, .5714, .5, .2857)) + expect_equal(top5(with_graph(test_tbl, node_by_degree(normalized = TRUE))), c(0.5714, .5, .5714, .5, .2857)) + expect_equal(bot3(node_by_degree(test_mat, normalized = TRUE)), c(.3333, .1667, .1667)) + expect_equal(bot3(node_by_degree(test_igr, normalized = TRUE)), c(.3333, .1667, .1667)) + expect_equal(bot3(with_graph(test_tbl, node_by_degree(normalized = TRUE))), c(.3333, .1667, .1667)) }) test_that("one mode closeness centrality calculated correctly",{ - expect_equal(top3(node_closeness(ison_adolescents, normalized = FALSE)), c(0.059, 0.091, 0.091), tolerance = 0.01) + expect_equal(top3(node_by_closeness(ison_adolescents, normalized = FALSE)), c(0.059, 0.091, 0.091), tolerance = 0.01) }) test_that("two mode closeness centrality calculated correctly",{ - expect_equal(top5(node_closeness(test_mat, normalized = FALSE)), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) - expect_equal(top5(node_closeness(test_igr, normalized = FALSE)), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) - expect_equal(top5(with_graph(test_tbl, node_closeness(normalized = FALSE))), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) - expect_equal(bot3(node_closeness(test_mat, normalized = FALSE)), c(0.0128, 0.0119, 0.0119)) - expect_equal(bot3(node_closeness(test_igr, normalized = FALSE)), c(0.0128, 0.0119, 0.0119)) - expect_equal(bot3(with_graph(test_tbl, node_closeness(normalized = FALSE))), c(0.0128, 0.0119, 0.0119)) - expect_equal(top5(node_closeness(test_mat, normalized = TRUE)), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) - expect_equal(top5(node_closeness(test_igr, normalized = TRUE)), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) - expect_equal(top5(with_graph(test_tbl, node_closeness(normalized = TRUE))), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) - expect_equal(bot3(node_closeness(test_mat, normalized = TRUE)), c(0.5641, 0.5238, 0.5238)) - expect_equal(bot3(node_closeness(test_igr, normalized = TRUE)), c(0.5641, 0.5238, 0.5238)) - expect_equal(bot3(with_graph(test_tbl, node_closeness(normalized = TRUE))), c(0.5641, 0.5238, 0.5238)) + expect_equal(top5(node_by_closeness(test_mat, normalized = FALSE)), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) + expect_equal(top5(node_by_closeness(test_igr, normalized = FALSE)), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) + expect_equal(top5(with_graph(test_tbl, node_by_closeness(normalized = FALSE))), c(0.0167, 0.0152, 0.0167, 0.0152, 0.0125)) + expect_equal(bot3(node_by_closeness(test_mat, normalized = FALSE)), c(0.0128, 0.0119, 0.0119)) + expect_equal(bot3(node_by_closeness(test_igr, normalized = FALSE)), c(0.0128, 0.0119, 0.0119)) + expect_equal(bot3(with_graph(test_tbl, node_by_closeness(normalized = FALSE))), c(0.0128, 0.0119, 0.0119)) + expect_equal(top5(node_by_closeness(test_mat, normalized = TRUE)), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) + expect_equal(top5(node_by_closeness(test_igr, normalized = TRUE)), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) + expect_equal(top5(with_graph(test_tbl, node_by_closeness(normalized = TRUE))), c(0.8000, 0.7273, 0.8000, 0.7273, 0.6000)) + expect_equal(bot3(node_by_closeness(test_mat, normalized = TRUE)), c(0.5641, 0.5238, 0.5238)) + expect_equal(bot3(node_by_closeness(test_igr, normalized = TRUE)), c(0.5641, 0.5238, 0.5238)) + expect_equal(bot3(with_graph(test_tbl, node_by_closeness(normalized = TRUE))), c(0.5641, 0.5238, 0.5238)) }) test_that("one mode betweenness centrality calculated correctly",{ - expect_equal(top3(node_betweenness(ison_adolescents, normalized = FALSE)), c(0, 7.5, 5.5), tolerance = 0.001) + expect_equal(top3(node_by_betweenness(ison_adolescents, normalized = FALSE)), c(0, 7.5, 5.5), tolerance = 0.001) }) test_that("two mode betweenness centrality calculated correctly",{ - expect_equal(top5(node_betweenness(test_mat, normalized = FALSE)), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) - expect_equal(top5(node_betweenness(test_igr, normalized = FALSE)), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) - expect_equal(top5(with_graph(test_tbl, node_betweenness(normalized = FALSE))), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) - expect_equal(bot3(node_betweenness(test_mat, normalized = FALSE)), c(8.1786, 1.0128, 1.0128)) - expect_equal(bot3(node_betweenness(test_igr, normalized = FALSE)), c(8.1786, 1.0128, 1.0128)) - expect_equal(bot3(with_graph(test_tbl, node_betweenness(normalized = FALSE))), c(8.1786, 1.0128, 1.0128)) - expect_equal(top3(node_betweenness(test_mat, normalized = TRUE),4), c(0.0972, 0.0517, 0.0882)) - expect_equal(top3(node_betweenness(test_igr, normalized = TRUE),4), c(0.0972, 0.0517, 0.0882)) - expect_equal(top3(with_graph(test_tbl, node_betweenness(normalized = TRUE)),4), c(0.0972, 0.0517, 0.0882)) - expect_equal(bot3(node_betweenness(test_mat, normalized = TRUE),4), c(0.0181, 0.0022, 0.0022)) - expect_equal(bot3(node_betweenness(test_igr, normalized = TRUE),4), c(0.0181, 0.0022, 0.0022)) - expect_equal(bot3(with_graph(test_tbl, node_betweenness(normalized = TRUE)),4), c(0.0181, 0.0022, 0.0022)) + expect_equal(top5(node_by_betweenness(test_mat, normalized = FALSE)), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) + expect_equal(top5(node_by_betweenness(test_igr, normalized = FALSE)), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) + expect_equal(top5(with_graph(test_tbl, node_by_betweenness(normalized = FALSE))), c(42.9802, 22.8541, 38.9796, 22.0215, 4.7153)) + expect_equal(bot3(node_by_betweenness(test_mat, normalized = FALSE)), c(8.1786, 1.0128, 1.0128)) + expect_equal(bot3(node_by_betweenness(test_igr, normalized = FALSE)), c(8.1786, 1.0128, 1.0128)) + expect_equal(bot3(with_graph(test_tbl, node_by_betweenness(normalized = FALSE))), c(8.1786, 1.0128, 1.0128)) + expect_equal(top3(node_by_betweenness(test_mat, normalized = TRUE),4), c(0.0972, 0.0517, 0.0882)) + expect_equal(top3(node_by_betweenness(test_igr, normalized = TRUE),4), c(0.0972, 0.0517, 0.0882)) + expect_equal(top3(with_graph(test_tbl, node_by_betweenness(normalized = TRUE)),4), c(0.0972, 0.0517, 0.0882)) + expect_equal(bot3(node_by_betweenness(test_mat, normalized = TRUE),4), c(0.0181, 0.0022, 0.0022)) + expect_equal(bot3(node_by_betweenness(test_igr, normalized = TRUE),4), c(0.0181, 0.0022, 0.0022)) + expect_equal(bot3(with_graph(test_tbl, node_by_betweenness(normalized = TRUE)),4), c(0.0181, 0.0022, 0.0022)) }) test_that("one mode eigenvector centrality calculated correctly",{ # expect_equal(top3(node_eigenvector(ison_adolescents, normalized = FALSE)), c(0.16, 0.491, 0.529), tolerance = 0.001) - expect_equal(top3(node_eigenvector(ison_adolescents)), c(0.303, 0.928, 1), tolerance = 0.001) + expect_equal(top3(node_by_eigenvector(ison_adolescents)), c(0.303, 0.928, 1), tolerance = 0.001) }) test_that("two mode eigenvector centrality calculated correctly",{ - expect_equal(top3(node_eigenvector(test_mat)), c(0.9009, 0.8497, 1)) - expect_equal(bot3(node_eigenvector(test_mat)), c(0.4764, 0.2907, 0.2907)) - expect_equal(top3(node_eigenvector(test_igr)), c(0.9009, 0.8497, 1)) - expect_equal(bot3(node_eigenvector(test_igr)), c(0.4764, 0.2907, 0.2907)) -}) - -test_that("node measure class works", { - expect_s3_class(node_degree(ison_adolescents), "node_measure") - expect_s3_class(node_betweenness(ison_adolescents), "node_measure") - expect_s3_class(node_closeness(ison_adolescents), "node_measure") - expect_s3_class(node_eigenvector(ison_adolescents), "node_measure") - expect_s3_class(node_reach(ison_adolescents), "node_measure") - expect_s3_class(node_randomwalk(ison_adolescents), "node_measure") - expect_s3_class(node_harmonic(ison_adolescents), "node_measure") + expect_equal(top3(node_by_eigenvector(test_mat)), c(0.9009, 0.8497, 1)) + expect_equal(bot3(node_by_eigenvector(test_mat)), c(0.4764, 0.2907, 0.2907)) + expect_equal(top3(node_by_eigenvector(test_igr)), c(0.9009, 0.8497, 1)) + expect_equal(bot3(node_by_eigenvector(test_igr)), c(0.4764, 0.2907, 0.2907)) }) test_that("summary node measure works", { - expect_equal(names(summary(node_degree(ison_adolescents))), + expect_equal(names(summary(node_by_degree(ison_adolescents))), c("Minimum","Maximum","Mean","StdDev","Missing")) }) test_that("summary net measure works", { - expect_match(summary(net_degree(ison_adolescents)), "z =") + expect_match(summary(net_by_degree(ison_adolescents)), "z =") }) # ####### Centralization test_that("one-mode centralisation is calculated correctly", { - expect_equal(as.numeric(net_degree(ison_adolescents)), 0.2142, tolerance = 0.001) - expect_equal(as.numeric(net_closeness(ison_adolescents)), 0.3195, tolerance = 0.001) - expect_equal(as.numeric(net_betweenness(ison_adolescents)), 0.3401, tolerance = 0.001) - expect_equal(as.numeric(net_eigenvector(ison_adolescents)), 0.5479, tolerance = 0.001) + expect_equal(as.numeric(net_by_degree(ison_adolescents)), 0.2142, tolerance = 0.001) + expect_equal(as.numeric(net_by_closeness(ison_adolescents)), 0.3195, tolerance = 0.001) + expect_equal(as.numeric(net_by_betweenness(ison_adolescents)), 0.3401, tolerance = 0.001) + expect_equal(as.numeric(net_by_eigenvector(ison_adolescents)), 0.5479, tolerance = 0.001) }) test_that("two mode degree centralisation calculated correctly", { - expect_equal(as.numeric(net_degree(ison_southern_women, normalized = FALSE)), c(0.2021, 0.5253), tolerance = 0.001) - expect_equal(as.numeric(net_degree(ison_southern_women, direction = "in")), c(0.249, 0.484), tolerance = 0.001) - expect_equal(as.numeric(net_degree(ison_southern_women, normalized = TRUE)), c(0.245, 0.493), tolerance = 0.001) + expect_equal(as.numeric(net_by_degree(ison_southern_women, normalized = FALSE)), c(0.2021, 0.5253), tolerance = 0.001) + expect_equal(as.numeric(net_by_degree(ison_southern_women, direction = "in")), c(0.249, 0.484), tolerance = 0.001) + expect_equal(as.numeric(net_by_degree(ison_southern_women, normalized = TRUE)), c(0.245, 0.493), tolerance = 0.001) }) test_that("two mode closeness centralisation calculated correctly", { - expect_equal(as.numeric(net_closeness(ison_southern_women, normalized = TRUE)), c(0.293, 0.452), tolerance = 0.001) - expect_equal(as.numeric(net_closeness(ison_southern_women, direction = "in")), c(0.224, 0.537), tolerance = 0.001) + expect_equal(as.numeric(net_by_closeness(ison_southern_women, normalized = TRUE)), c(0.293, 0.452), tolerance = 0.001) + expect_equal(as.numeric(net_by_closeness(ison_southern_women, direction = "in")), c(0.224, 0.537), tolerance = 0.001) }) test_that("two mode betweenness centralisation calculated correctly", { - expect_equal(as.numeric(net_betweenness(ison_southern_women, normalized = FALSE)), c(0.0733, 0.2113), tolerance = 0.001) - expect_equal(as.numeric(net_betweenness(ison_southern_women, direction = "in")), c(0.082, 0.202), tolerance = 0.001) - expect_equal(as.numeric(net_betweenness(ison_southern_women, normalized = TRUE)), c(0.0739, 0.2113), tolerance = 0.001) + expect_equal(as.numeric(net_by_betweenness(ison_southern_women, normalized = FALSE)), c(0.0733, 0.2113), tolerance = 0.001) + expect_equal(as.numeric(net_by_betweenness(ison_southern_women, direction = "in")), c(0.082, 0.202), tolerance = 0.001) + expect_equal(as.numeric(net_by_betweenness(ison_southern_women, normalized = TRUE)), c(0.0739, 0.2113), tolerance = 0.001) }) test_that("net_measure class works", { - expect_s3_class(net_degree(ison_algebra), "network_measure") - expect_s3_class(net_betweenness(ison_southern_women), "network_measure") - expect_s3_class(net_closeness(ison_southern_women), "network_measure") - expect_output(print(net_degree(ison_algebra))) + expect_output(print(net_by_degree(ison_algebra))) }) # ####### Edge centrality -test_that("tie_degree works", { - res <- tie_degree(ison_adolescents) - expect_s3_class(res, "tie_measure") - expect_length(res, manynet::net_ties(ison_adolescents)) - expect_output(print(res), "Betty-Sue") -}) - test_that("tie_betweenness works", { - expect_s3_class(tie_betweenness(ison_adolescents), - "tie_measure") - expect_length(tie_betweenness(ison_adolescents), - manynet::net_ties(ison_adolescents)) - expect_equal(unname(tie_betweenness(ison_adolescents)[1:3]), + expect_equal(unname(tie_by_betweenness(ison_adolescents)[1:3]), c(7,3,5), tolerance = 0.001) }) test_that("tie_closeness works", { - expect_s3_class(tie_closeness(ison_adolescents), - "tie_measure") - expect_length(tie_closeness(ison_adolescents), - manynet::net_ties(ison_adolescents)) - expect_equal(unname(tie_closeness(ison_adolescents)[1:3]), + expect_equal(unname(tie_by_closeness(ison_adolescents)[1:3]), c(0.562,0.692,0.600), tolerance = 0.001) }) - -test_that("tie_eigenvector works", { - expect_s3_class(tie_eigenvector(ison_southern_women), - "tie_measure") - expect_length(tie_eigenvector(ison_southern_women), - manynet::net_ties(ison_southern_women)) -}) - diff --git a/tests/testthat/test-measure_closure.R b/tests/testthat/test-measure_closure.R index f001179..9efe669 100644 --- a/tests/testthat/test-measure_closure.R +++ b/tests/testthat/test-measure_closure.R @@ -1,56 +1,25 @@ test_that("network density works", { - expect_s3_class(net_density(ison_southern_women), "network_measure") - expect_equal(as.numeric(net_density(create_empty(10))), 0) - expect_equal(as.numeric(net_density(create_empty(c(10,6)))), 0) - expect_equal(as.numeric(net_density(create_filled(10))), 1) - expect_equal(as.numeric(net_density(create_filled(c(10,6)))), 1) - expect_output(print(net_density(create_filled(10)))) -}) - -test_that("network reciprocity works", { - expect_s3_class(net_reciprocity(ison_networkers), "network_measure") - expect_output(print(net_reciprocity(ison_networkers))) - expect_length(net_reciprocity(ison_networkers), 1) - expect_equal(as.numeric(net_reciprocity(ison_networkers)), - igraph::reciprocity(as_igraph(ison_networkers))) + expect_equal(as.numeric(net_by_density(create_empty(10))), 0) + expect_equal(as.numeric(net_by_density(create_empty(c(10,6)))), 0) + expect_equal(as.numeric(net_by_density(create_filled(10))), 1) + expect_equal(as.numeric(net_by_density(create_filled(c(10,6)))), 1) + expect_output(print(net_by_density(create_filled(10)))) }) test_that("one-mode object clustering is reported correctly",{ - expect_equal(as.numeric(net_transitivity(ison_algebra)), + expect_equal(as.numeric(net_by_transitivity(ison_algebra)), 0.69787, tolerance = 0.001) - expect_s3_class(net_transitivity(ison_algebra), "network_measure") - expect_output(print(net_transitivity(ison_algebra))) }) test_that("two-mode object clustering is reported correctly",{ - expect_equal(as.numeric(net_equivalency(ison_southern_women)), + expect_equal(as.numeric(net_by_equivalency(ison_southern_women)), 0.4677, tolerance = 0.001) - expect_s3_class(net_equivalency(ison_southern_women), "network_measure") - expect_output(print(net_equivalency(ison_southern_women))) - expect_values(net_equivalency(ison_adolescents), 0.258) -}) - -test_that("node_equivalency works correctly",{ - expect_equal(as.numeric(node_equivalency(ison_laterals$ison_mm)), - c(0,1,1,0,0.5,0.5), tolerance = 0.001) - expect_s3_class(node_equivalency(ison_southern_women), "node_measure") + expect_values(net_by_equivalency(ison_adolescents), 0.258) }) test_that("three-mode clustering calculated correctly",{ mat1 <- manynet::create_ring(c(10,5)) mat2 <- manynet::create_ring(c(5,8)) - expect_equal(as.numeric(net_congruency(mat1, mat2)), + expect_equal(as.numeric(net_by_congruency(mat1, mat2)), 0.3684, tolerance = 0.001) - expect_s3_class(net_congruency(mat1, mat2), "network_measure") - expect_output(print(net_congruency(mat1, mat2))) -}) - -test_that("node_transitivity is reported correctly",{ - expect_length(node_transitivity(ison_algebra), net_nodes(ison_algebra)) - expect_s3_class(node_transitivity(ison_algebra), "node_measure") -}) - -test_that("node_reciprocity works",{ - expect_length(node_reciprocity(ison_networkers), net_nodes(ison_networkers)) - expect_s3_class(node_reciprocity(ison_networkers), "node_measure") }) diff --git a/tests/testthat/test-measure_cohesion.R b/tests/testthat/test-measure_cohesion.R index ec37d11..ddbf321 100644 --- a/tests/testthat/test-measure_cohesion.R +++ b/tests/testthat/test-measure_cohesion.R @@ -1,40 +1,32 @@ test_that("network components works", { - expect_s3_class(net_components(ison_adolescents), "network_measure") - expect_equal(as.numeric(net_components(ison_adolescents)), 1) + expect_equal(as.numeric(net_by_components(ison_adolescents)), 1) }) test_that("network cohesion works", { - expect_s3_class(net_cohesion(ison_southern_women), "network_measure") - expect_equal(as.numeric(net_cohesion(ison_southern_women)), 2) + expect_equal(as.numeric(net_by_cohesion(ison_southern_women)), 2) }) test_that("network adhesion works", { - expect_s3_class(net_adhesion(ison_southern_women), "network_measure") - expect_equal(as.numeric(net_adhesion(ison_southern_women)), 2) + expect_equal(as.numeric(net_by_adhesion(ison_southern_women)), 2) }) test_that("network diameter works", { - expect_s3_class(net_diameter(ison_southern_women), "network_measure") - expect_equal(as.numeric(net_diameter(ison_southern_women)), 4) + expect_equal(as.numeric(net_by_diameter(ison_southern_women)), 4) }) test_that("network length works", { - expect_s3_class(net_length(ison_southern_women), "network_measure") - expect_equal(as.numeric(net_length(ison_southern_women)), 2.306, + expect_equal(as.numeric(net_by_length(ison_southern_women)), 2.306, tolerance = 0.001) }) test_that("net_independence works", { - expect_s3_class(net_independence(ison_adolescents), "network_measure") - expect_values(net_independence(ison_adolescents), 4) + expect_values(net_by_independence(ison_adolescents), 4) }) test_that("net_strength works", { - expect_s3_class(net_strength(ison_adolescents), "network_measure") - expect_values(net_strength(ison_adolescents), 0.5) + expect_values(net_by_strength(ison_adolescents), 0.5) }) test_that("net_toughness works", { - expect_s3_class(net_toughness(ison_adolescents), "network_measure") - expect_values(net_toughness(ison_adolescents), 0.5) + expect_values(net_by_toughness(ison_adolescents), 0.5) }) \ No newline at end of file diff --git a/tests/testthat/test-measure_diffusion.R b/tests/testthat/test-measure_diffusion.R index 91df04d..5dbbf69 100644 --- a/tests/testthat/test-measure_diffusion.R +++ b/tests/testthat/test-measure_diffusion.R @@ -1,9 +1,9 @@ test <- play_diffusion(create_tree(12), steps = 2) test_that("infection total works", { - expect_values(net_infection_total(test), 0.583) + expect_values(net_by_infection_total(test), 0.583) }) test_that("infection complete works", { - expect_values(net_infection_complete(test), Inf) + expect_values(net_by_infection_complete(test), Inf) }) diff --git a/tests/testthat/test-measure_features.R b/tests/testthat/test-measure_features.R index 410530c..5fcd274 100644 --- a/tests/testthat/test-measure_features.R +++ b/tests/testthat/test-measure_features.R @@ -1,11 +1,5 @@ set.seed(123) -test_that("small-world metrics for two mode networks are calculated and displayed correctly", { - expect_s3_class(net_smallworld(ison_southern_women), "network_measure") - expect_equal(as.numeric(net_smallworld(ison_southern_women)), -0.94, tolerance = 0.02) - expect_equal(c(net_smallworld(ison_adolescents, method = "SWI")), -0.25, tolerance = 0.05) -}) - # test_that("net_balance works", { # out <- net_balance(ison_marvel_relationships) # expect_s3_class(out, "network_measure") @@ -15,52 +9,38 @@ test_that("small-world metrics for two mode networks are calculated and displaye # }) test_that("net_modularity works for two mode networks", { - out <- net_modularity(ison_southern_women, + out <- net_by_modularity(ison_southern_women, node_in_partition(ison_southern_women)) - expect_s3_class(out, "network_measure") expect_length(out, 1) }) test_that("net_core works", { - out <- net_core(ison_adolescents) - expect_s3_class(out, "network_measure") - expect_length(out, 1) + out <- net_by_core(ison_adolescents) expect_values(out, -0.133) - expect_values(net_core(ison_adolescents, method = "ident"), 6.481) - expect_values(net_core(ison_adolescents, method = "diff"), 6.094) + expect_values(net_by_core(ison_adolescents, method = "ident"), 6.481) + expect_values(net_by_core(ison_adolescents, method = "diff"), 6.094) }) test_that("net_richclub works", { - out <- net_richclub(ison_adolescents) - expect_s3_class(out, "network_measure") - expect_length(out, 1) + out <- net_by_richclub(ison_adolescents) expect_values(out, 0.833) }) -test_that("net_factions works", { - out <- net_factions(ison_adolescents) - expect_s3_class(out, "network_measure") - expect_length(out,1) -}) - test_that("net_scalefree works", { - out <- net_scalefree(ison_adolescents) - expect_s3_class(out, "network_measure") + out <- net_by_scalefree(ison_adolescents) expect_values(out,3.689) }) test_that("net_balance works", { - out <- net_balance(irps_wwi) - expect_s3_class(out, "network_measure") + out <- net_by_balance(irps_wwi) expect_values(out,1) }) wavenet <- ison_adolescents %>% - tidygraph::activate(edges) %>% - mutate(wave = c(1, 1, 1, 1, 2, 2, 2, 3, 3, 3)) + mutate_ties(wave = c(1, 1, 1, 1, 2, 2, 2, 3, 3, 3)) test_that("net_waves works", { - expect_equal(net_waves(ison_adolescents), 1) - expect_equal(net_waves(wavenet), 3) + # expect_equal(net_waves(ison_adolescents), 1) + expect_values(net_by_waves(wavenet), 3) }) diff --git a/tests/testthat/test-measure_heterogeneity.R b/tests/testthat/test-measure_heterogeneity.R index 8f35da6..d3aab50 100644 --- a/tests/testthat/test-measure_heterogeneity.R +++ b/tests/testthat/test-measure_heterogeneity.R @@ -1,32 +1,29 @@ #*************** Test the heterogeneity family of functions ******************# test_that("diversity functions works", { - expect_equal(as.numeric(net_diversity(to_uniplex(fict_marvel,"relationship"), "Gender")), + expect_equal(as.numeric(net_by_diversity(to_uniplex(fict_marvel,"relationship"), "Gender")), 0.306, tolerance = 0.001) - expect_equal(top3(node_diversity(ison_lawfirm, "gender")), + expect_equal(top3(node_by_diversity(ison_lawfirm, "gender")), c(0.285, 0.375,0), tolerance = 0.01) }) test_that("heterophily function works", { - expect_equal(as.numeric(net_heterophily(ison_networkers, "Discipline")), .1704, tolerance = 0.001) - expect_length(node_heterophily(ison_networkers, "Discipline"), + expect_equal(as.numeric(net_by_heterophily(ison_networkers, "Discipline")), .1704, tolerance = 0.001) + expect_length(node_by_heterophily(ison_networkers, "Discipline"), net_nodes(ison_networkers)) - expect_s3_class(node_heterophily(ison_networkers, "Discipline"), "node_measure") + expect_s3_class(node_by_heterophily(ison_networkers, "Discipline"), "node_measure") }) test_that("assortativity function works", { - expect_length(net_assortativity(ison_networkers), 1) - expect_s3_class(net_assortativity(ison_networkers), "network_measure") + expect_length(net_by_assortativity(ison_networkers), 1) }) test_that("richness function works", { - expect_length(net_richness(ison_networkers), 1) - expect_equal(as.numeric(net_richness(ison_networkers)), 3) - expect_s3_class(net_richness(ison_networkers), "network_measure") - expect_length(node_richness(ison_networkers, "type"), 32) - expect_s3_class(node_richness(ison_networkers, "type"), "node_measure") + expect_length(net_by_richness(ison_networkers), 1) + expect_equal(as.numeric(net_by_richness(ison_networkers)), 3) + expect_length(node_by_richness(ison_networkers, "type"), 32) }) test_that("net_spatial works", { - expect_values(net_spatial(ison_lawfirm, "age"), 0.126) + expect_values(net_by_spatial(ison_lawfirm, "age"), 0.126) }) \ No newline at end of file diff --git a/tests/testthat/test-measure_hierarchy.R b/tests/testthat/test-measure_hierarchy.R index ce085ba..ea5bdc2 100644 --- a/tests/testthat/test-measure_hierarchy.R +++ b/tests/testthat/test-measure_hierarchy.R @@ -1,9 +1,8 @@ # Test hierarchy measures test_that("net_connectedness works correctly", { - connect_judo <- net_connectedness(ison_judo_moves) + connect_judo <- net_by_connectedness(ison_judo_moves) # Basic functionality tests - expect_s3_class(connect_judo, "network_measure") # undirected # Return type and range tests expect_true(is.numeric(as.numeric(connect_judo))) @@ -11,17 +10,16 @@ test_that("net_connectedness works correctly", { expect_true(as.numeric(connect_judo) <= 1) # Test with complete graph (should be 1) - expect_equal(round(as.numeric(net_connectedness(create_filled(5))), 4), 1) - expect_equal(round(as.numeric(net_connectedness(create_empty(5))), 4), 0) + expect_equal(round(as.numeric(net_by_connectedness(create_filled(5))), 4), 1) + expect_equal(round(as.numeric(net_by_connectedness(create_empty(5))), 4), 0) # Test edge case: single node - expect_false(is.finite(as.numeric(net_connectedness(create_empty(1))))) + expect_false(is.finite(as.numeric(net_by_connectedness(create_empty(1))))) }) test_that("net_efficiency works correctly", { # Basic functionality tests - effic_judo <- net_efficiency(ison_judo_moves) - expect_s3_class(effic_judo, "network_measure") + effic_judo <- net_by_efficiency(ison_judo_moves) # Return type tests expect_true(is.numeric(as.numeric(effic_judo))) @@ -30,8 +28,7 @@ test_that("net_efficiency works correctly", { test_that("net_upperbound works correctly", { # Basic functionality tests - upper_judo <- net_efficiency(ison_judo_moves) - expect_s3_class(upper_judo, "network_measure") + upper_judo <- net_by_efficiency(ison_judo_moves) # Return type and range tests expect_true(is.numeric(as.numeric(upper_judo))) @@ -41,13 +38,12 @@ test_that("net_upperbound works correctly", { # Test with perfect hierarchy (should approach 1) # Create a tournament-like structure perfect_hierarchy <- create_tree(5) - expect_equal(as.numeric(net_upperbound(perfect_hierarchy)), 1) + expect_equal(as.numeric(net_by_upperbound(perfect_hierarchy)), 1) }) -test_that("net_by_hierarchy works correctly", { - result <- net_by_hierarchy(ison_judo_moves) +test_that("net_x_hierarchy works correctly", { + result <- net_x_hierarchy(ison_judo_moves) # Basic functionality tests - expect_s3_class(result, "network_motif") # Check that it returns a data frame with correct columns expect_true(is.data.frame(result)) diff --git a/tests/testthat/test-measure_holes.R b/tests/testthat/test-measure_holes.R index 264dbae..e22cba8 100644 --- a/tests/testthat/test-measure_holes.R +++ b/tests/testthat/test-measure_holes.R @@ -1,58 +1,52 @@ test_that("redundancy is reported correctly", { - expect_s3_class(node_redundancy(ison_brandes), "node_measure") - expect_s3_class(node_redundancy(ison_southern_women), "node_measure") - expect_equal(as.numeric(length(node_redundancy(ison_brandes))), + expect_equal(as.numeric(length(node_by_redundancy(ison_brandes))), as.numeric(net_nodes(ison_brandes))) - expect_equal(as.numeric(length(node_redundancy(ison_southern_women))), + expect_equal(as.numeric(length(node_by_redundancy(ison_southern_women))), as.numeric(net_nodes(ison_southern_women))) - expect_named(node_redundancy(ison_southern_women)) + expect_named(node_by_redundancy(ison_southern_women)) }) test_that("effective size is calculated and reported correctly", { - expect_s3_class(node_effsize(ison_brandes), "node_measure") - expect_s3_class(node_effsize(ison_southern_women), "node_measure") - expect_equal(as.numeric(length(node_effsize(ison_brandes))), + expect_equal(as.numeric(length(node_by_effsize(ison_brandes))), as.numeric(net_nodes(ison_brandes))) - expect_equal(length(node_effsize(ison_southern_women)), + expect_equal(length(node_by_effsize(ison_southern_women)), c(net_nodes(ison_southern_women))) - expect_named(node_effsize(ison_southern_women)) - expect_equal(unname(node_effsize(ison_southern_women)[1:3]), c(2.5,1.38,2.46), tolerance = 0.01) + expect_named(node_by_effsize(ison_southern_women)) + expect_equal(unname(node_by_effsize(ison_southern_women)[1:3]), c(2.5,1.38,2.46), tolerance = 0.01) }) test_that("efficiency is reported correctly", { - expect_s3_class(node_efficiency(ison_brandes), "node_measure") - expect_s3_class(node_efficiency(ison_southern_women), "node_measure") - expect_equal(length(node_efficiency(ison_brandes)), c(net_nodes(ison_brandes))) - expect_equal(length(node_efficiency(ison_southern_women)), + expect_equal(length(node_by_efficiency(ison_brandes)), c(net_nodes(ison_brandes))) + expect_equal(length(node_by_efficiency(ison_southern_women)), c(net_nodes(ison_southern_women))) }) test_that("constraint scores are reported correctly for two-mode networks",{ - res <- node_constraint(ison_southern_women) + res <- node_by_constraint(ison_southern_women) expect_equal(top3(res), c(0.2782, 0.3071, 0.2965)) expect_output(print(res), "Evelyn") # expect_named(node_constraint(ison_southern_women)[1:3], c("Evelyn", "Laura", "Theresa")) }) test_that("constraint scores are reported correctly for one-mode notworks",{ - res <- node_constraint(ison_adolescents) + res <- node_by_constraint(ison_adolescents) expect_equal(round(unname(res[1:3]),2), c(1, .43, .57)) expect_output(print(res), "Alice") }) test_that("hierarchy is reported correctly", { - expect_s3_class(node_hierarchy(ison_brandes), "node_measure") - expect_s3_class(node_hierarchy(ison_southern_women), "node_measure") - expect_equal(length(node_hierarchy(ison_brandes)), c(net_nodes(ison_brandes))) - expect_equal(length(node_hierarchy(ison_southern_women)), + expect_s3_class(node_by_hierarchy(ison_brandes), "node_measure") + expect_s3_class(node_by_hierarchy(ison_southern_women), "node_measure") + expect_equal(length(node_by_hierarchy(ison_brandes)), c(net_nodes(ison_brandes))) + expect_equal(length(node_by_hierarchy(ison_southern_women)), c(net_nodes(ison_southern_women))) - expect_named(node_hierarchy(ison_southern_women)) + expect_named(node_by_hierarchy(ison_southern_women)) }) test_that("node_neighbours_degree works", { - expect_equal(top3(node_neighbours_degree(ison_adolescents)), c(4,2.75,3)) + expect_equal(top3(node_by_neighbours_degree(ison_adolescents)), c(4,2.75,3)) }) test_that("tie_cohesion works", { - expect_equal(top3(tie_cohesion(ison_adolescents)), c(0,0.5,0.3333)) + expect_equal(top3(tie_by_cohesion(ison_adolescents)), c(0,0.5,0.3333)) }) diff --git a/tests/testthat/test-measure_net.R b/tests/testthat/test-measure_net.R new file mode 100644 index 0000000..8ddb97c --- /dev/null +++ b/tests/testthat/test-measure_net.R @@ -0,0 +1,36 @@ +net_meas <- funs_objs[grepl("net_by_", names(funs_objs))] +for(fn in names(net_meas)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("core|spatial", fn)) + skip_if(grepl("net_by_factions", fn) && ob == "twomode") + if(grepl("diversity|heterophily|homophily", fn)){ + if(ob == "attribute") + expect_s3_class(net_meas[[fn]](data_objs[[ob]], "group"), "network_measure") else + succeed("Only used for attribute objects") + } else if(grepl("balance", fn)){ + if(ob == "labelled") + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") else + succeed("Only used for signed objects") + } else if(grepl("congruency", fn)){ + if(ob == "twomode") + expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") else + succeed("Only used for multiple two-mode objects") + } else if(grepl("strength|toughness", fn)){ # why is this so slow?? + if(ob == "weighted") + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") else + succeed("Testing only once because slow") + } else if(grepl("infection|immunity|recovery|reproduction|transmiss", fn)){ + if(ob == "diffusion") + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") else + succeed("Only used for diffusion objects") + } else if(grepl("correlation|change|stability", fn)){ + if(ob == "labelled") + expect_s3_class(net_meas[[fn]](data_objs[[ob]], data_objs[[ob]]), "network_measure") else + succeed("Only used for multi objects") + } else { + expect_s3_class(net_meas[[fn]](data_objs[[ob]]), "network_measure") + } + }) + } +} diff --git a/tests/testthat/test-measure_nodes.R b/tests/testthat/test-measure_nodes.R new file mode 100644 index 0000000..f9b4082 --- /dev/null +++ b/tests/testthat/test-measure_nodes.R @@ -0,0 +1,26 @@ +node_meas <- funs_objs[grepl("node_by_", names(funs_objs))] +for(fn in names(node_meas)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("multideg", fn)) + skip_if(grepl("equivalency", fn) && ob == "labelled") + if(grepl("diversity|richness|heterophily|homophily", fn)){ + if(ob == "attribute") + expect_s3_class(node_meas[[fn]](data_objs[[ob]], "group"), "node_measure") else + succeed("Only used for attribute objects") + } else if(grepl("adoption|threshold|recovery|exposure",fn)){ + if(ob == "diffusion") + expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") else + succeed("Only used for diffusion objects") + } else if(grepl("posneg",fn)){ + if(ob == "signed") + expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") else + succeed("Only used for signed objects") + } else if(grepl("distance",fn)){ + expect_s3_class(node_meas[[fn]](data_objs[[ob]], 1, 2), "node_measure") + } else { + expect_s3_class(node_meas[[fn]](data_objs[[ob]]), "node_measure") + } + }) + } +} \ No newline at end of file diff --git a/tests/testthat/test-measure_ties.R b/tests/testthat/test-measure_ties.R new file mode 100644 index 0000000..f08f7b2 --- /dev/null +++ b/tests/testthat/test-measure_ties.R @@ -0,0 +1,9 @@ +tie_meas <- funs_objs[grepl("tie_by_", names(funs_objs))] +for(fn in names(tie_meas)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if_not(packageVersion("manynet") >= "1.7.3") + expect_s3_class(tie_meas[[fn]](data_objs[[ob]]), "tie_measure") + }) + } +} \ No newline at end of file diff --git a/tests/testthat/test-member_community.R b/tests/testthat/test-member_community.R index c7634e3..1f27e16 100644 --- a/tests/testthat/test-member_community.R +++ b/tests/testthat/test-member_community.R @@ -24,11 +24,11 @@ test_that("node_walktrap algorithm works", { }) test_that("node_in_community uses node_in_optimal on small networks", { + skip_if(format(Sys.time(), "%H") >= "09") options(manynet_verbosity = "verbose") options(snet_verbosity = "verbose") expect_message(node_in_community(manynet::create_ring(10)), "optimal") expect_message(node_in_community(manynet::create_ring(200)), "xcluding") - expect_message(node_in_community(fict_thrones), "xcluding") options(manynet_verbosity = "quiet") options(snet_verbosity = "quiet") }) \ No newline at end of file diff --git a/tests/testthat/test-member_components.R b/tests/testthat/test-member_components.R index ad795ad..877c331 100644 --- a/tests/testthat/test-member_components.R +++ b/tests/testthat/test-member_components.R @@ -3,7 +3,7 @@ test_that("node_in_component works", { node_in_component() expect_s3_class(comp, "node_member") expect_equal(length(unique(comp)), - c(net_components(to_uniplex(ison_monks, "esteem")))) + c(net_by_components(to_uniplex(ison_monks, "esteem")))) expect_equal(length(unique(comp)), length(unique(node_in_strong(to_uniplex(ison_monks, "esteem"))))) comp <- ison_monks %>% to_uniplex("esteem") %>% diff --git a/tests/testthat/test-member_core.R b/tests/testthat/test-member_core.R index 88a3942..8a57ca7 100644 --- a/tests/testthat/test-member_core.R +++ b/tests/testthat/test-member_core.R @@ -3,7 +3,7 @@ test_that("node_is_universal works", { }) test_that("node_kcoreness works", { - expect_equal(top3(node_kcoreness(ison_adolescents)), c(1,2,2)) + expect_equal(top3(node_by_kcoreness(ison_adolescents)), c(1,2,2)) }) test_that("node_in_core works", { diff --git a/tests/testthat/test-member_nodes.R b/tests/testthat/test-member_nodes.R new file mode 100644 index 0000000..56b6bc4 --- /dev/null +++ b/tests/testthat/test-member_nodes.R @@ -0,0 +1,23 @@ +node_membs <- funs_objs[grepl("node_in_", names(funs_objs))] +for(fn in names(node_membs)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + if(grepl("roulette", fn)){ + if(ob != "twomode") + expect_s3_class(node_membs[[fn]](data_objs[[ob]], num_groups = 3), + "node_member") else + succeed("Roulette doesn't work on two-mode objects") + } else if(grepl("adopter", fn)){ + if(ob == "diffusion") + expect_s3_class(node_membs[[fn]](data_objs[[ob]]), "node_member") else + succeed("Only works on diffusion objects") + } else if(grepl("equivalence", fn)){ + expect_s3_class(node_membs[[fn]](data_objs[[ob]], + node_x_tie(data_objs[[ob]])), + "node_member") + } else { + expect_s3_class(node_membs[[fn]](data_objs[[ob]]), "node_member") + } + }) + } +} \ No newline at end of file diff --git a/tests/testthat/test-model_cluster.R b/tests/testthat/test-model_cluster.R index f7f1e0d..ed5a03e 100644 --- a/tests/testthat/test-model_cluster.R +++ b/tests/testthat/test-model_cluster.R @@ -1,3 +1,8 @@ test_that("cluster_cosine works", { - expect_s3_class(cluster_cosine(node_by_triad(ison_monks), distance = "euclidean"), "hclust") + expect_s3_class(cluster_cosine(node_x_triad(ison_monks), distance = "euclidean"), "hclust") +}) + +test_that("cluster_cosine works", { + unlab_2mode <- generate_random(c(6,6)) + expect_s3_class(cluster_concor(unlab_2mode, node_x_tetrad(unlab_2mode)), "hclust") }) diff --git a/tests/testthat/test-model_play.R b/tests/testthat/test-model_play.R deleted file mode 100644 index d6cc692..0000000 --- a/tests/testthat/test-model_play.R +++ /dev/null @@ -1,14 +0,0 @@ - -test_that("play_diffusion works for named networks", { - expect_warning(named_SI <- play_diffusion(ison_adolescents, old_version = TRUE)) - expect_equal(named_SI$S + named_SI$I, named_SI$n) - expect_equal(summary(named_SI)$t[1], 0) - expect_equal(summary(named_SI)$nodes[1:4], c(1,2,3,5)) -}) - -test_that("play_diffusion works for named networks", { - expect_warning(named_SEI <- play_diffusion(ison_adolescents, latency = 1, old_version = TRUE)) - expect_equal(named_SEI$S + named_SEI$E + named_SEI$I, named_SEI$n) - expect_equal(summary(named_SEI)$t[1], 0) - expect_equal(summary(named_SEI)$nodes[1:4], c(1,2,NA,NA)) -}) diff --git a/tests/testthat/test-motif_brokerage.R b/tests/testthat/test-motif_brokerage.R index f482e03..5021c28 100644 --- a/tests/testthat/test-motif_brokerage.R +++ b/tests/testthat/test-motif_brokerage.R @@ -1,12 +1,12 @@ -test_that("node_brokering_activity works", { - test <- node_brokering_activity(ison_networkers, "Discipline") +test_that("node_by_brokering_activity works", { + test <- node_by_brokering_activity(ison_networkers, "Discipline") expect_s3_class(test, "node_measure") expect_equal(c(net_nodes(ison_networkers)), length(test)) expect_equal(top3(test), c(333,207,3)) }) test_that("node_brokering_exclusivity works", { - test <- node_brokering_exclusivity(ison_networkers, "Discipline") + test <- node_by_brokering_exclusivity(ison_networkers, "Discipline") expect_s3_class(test, "node_measure") expect_equal(c(net_nodes(ison_networkers)), length(test)) expect_equal(top3(test), c(1,0,0)) @@ -21,14 +21,3 @@ test_that("node_in_brokering works", { expect_output(print(summary(node_in_brokering(to_uniplex(fict_marvel, "affiliation")))), "Connectors") }) -test_that("node_by_brokerage works", { - test <- node_by_brokerage(ison_networkers, "Discipline") - expect_s3_class(test, "node_motif") - expect_equal(dim(test), c(32,6)) -}) - -test_that("net_by_brokerage works", { - test <- net_by_brokerage(ison_networkers, "Discipline") - expect_s3_class(test, "network_motif") - expect_equal(top3(names(test)), c("Coordinator","Itinerant","Gatekeeper")) -}) diff --git a/tests/testthat/test-motif_census.R b/tests/testthat/test-motif_census.R deleted file mode 100644 index f211806..0000000 --- a/tests/testthat/test-motif_census.R +++ /dev/null @@ -1,82 +0,0 @@ -# # Census function family tests -set.seed(123) -task_eg <- to_named(to_uniplex(ison_algebra, "tasks")) - -test_that("node_by_tie census works", { - test <- node_by_tie(task_eg) - expect_equal(test[1:4], rep(0, 4)) - expect_s3_class(test, "node_motif") - expect_output(print(test), "fromA") - expect_equal(nrow(summary(test, membership = node_in_roulette(task_eg, 3))), - 3) -}) - -test_that("node_by_dyad census works", { - test <- node_by_dyad(ison_adolescents) - expect_s3_class(test, "node_motif") - expect_equal(colnames(test)[1:2], c("Mutual", "Null")) -}) - -test_that("node_by_triad census works", { - test <- node_by_triad(task_eg) - expect_equal(top3(test[,16]), c(7,8,6)) - expect_s3_class(test, "node_motif") - expect_equal(colnames(test)[1:3], c("003", "012", "102")) -}) - -test_that("net_by_dyad census works", { - test <- net_by_dyad(ison_adolescents) - expect_equal(test[[1]], 10) - expect_equal(test[[2]], 18) - expect_equal(names(test), c("Mutual", "Null")) - expect_s3_class(test, "network_motif") - expect_output(print(test), "Mutual") -}) - -test_that("net_by_triad census works", { - test <- net_by_triad(ison_adolescents) - expect_equal(test[[1]], 13) - expect_equal(test[[3]], 29) - expect_equal(names(test), c("003", "012", "102", "201", "210", "300")) - expect_s3_class(test, "network_motif") - expect_equal(names(summary(test)), c("003", "012", "102", "201", "210", "300")) - # Error - expect_error(net_by_triad(ison_southern_women)) -}) - -test_that("net_by_tetrad census works", { - test <- net_by_tetrad(ison_southern_women) - expect_s3_class(test, "network_motif") - expect_values(c(test)[1], 12388) -}) - -test_that("node_by_tetrad census works", { - test <- node_by_tetrad(ison_southern_women) - expect_s3_class(test, "node_motif") - expect_equal(test[1,1], 1241) -}) - -test_that("net_mixed census works", { - marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") - test <- net_by_mixed(marvel_friends, to_uniplex(fict_marvel, "affiliation")) - expect_s3_class(test, "network_motif") - expect_equal(unname(test[1]), 1137) - expect_equal(names(test[1]), "22") - # Errors - expect_error(net_by_mixed(ison_southern_women, - to_uniplex(fict_marvel, "affiliation"))) - expect_error(net_by_mixed(to_uniplex(fict_marvel, "affiliation"), - ison_southern_women)) - expect_error(net_by_mixed(ison_karateka, - to_uniplex(fict_marvel, "affiliation"))) -}) - -test <- node_by_path(ison_southern_women) -test_that("node path census works", { - expect_equal(c(net_nodes(ison_adolescents)), - nrow(node_by_path(ison_adolescents))) - expect_s3_class(test, "node_motif") - expect_true(nrow(node_by_path(ison_southern_women)) == - ncol(node_by_path(ison_southern_women))) -}) - diff --git a/tests/testthat/test-motif_net.R b/tests/testthat/test-motif_net.R new file mode 100644 index 0000000..715ffc3 --- /dev/null +++ b/tests/testthat/test-motif_net.R @@ -0,0 +1,17 @@ +net_motifs <- funs_objs[grepl("net_x_", names(funs_objs))] +for(fn in names(net_motifs)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("exposure|mixed|hazard", fn)) + skip_if(grepl("triad", fn) && is_twomode(data_objs[[ob]])) + if(grepl("brokerage", fn)){ + if(ob == "attribute") + expect_s3_class(net_motifs[[fn]](data_objs[[ob]], "group"), "network_motif") else + succeed("Only used for attribute objects") + } else { + expect_s3_class(net_motifs[[fn]](data_objs[[ob]]), "network_motif") + } + }) + } +} + diff --git a/tests/testthat/test-motif_nodes.R b/tests/testthat/test-motif_nodes.R new file mode 100644 index 0000000..51315c2 --- /dev/null +++ b/tests/testthat/test-motif_nodes.R @@ -0,0 +1,102 @@ +node_motifs <- funs_objs[grepl("node_x_", names(funs_objs))] +for(fn in names(node_motifs)) { + for (ob in names(data_objs)) { + test_that(paste(fn, "works on", ob), { + skip_if(grepl("triad|dyad", fn) && is_twomode(data_objs[[ob]])) + if(grepl("brokerage", fn)){ + if(ob == "attribute") + expect_s3_class(node_motifs[[fn]](data_objs[[ob]], "group"), "node_motif") else + succeed("Only used for attribute objects") + } else if(grepl("exposure", fn)){ + if(ob == "diffusion") + expect_s3_class(node_motifs[[fn]](data_objs[[ob]]), "node_motif") else + succeed("Only used for diffusion objects") + } else { + expect_s3_class(node_motifs[[fn]](data_objs[[ob]]), "node_motif") + } + }) + } +} + +# # Census function family tests +set.seed(123) +task_eg <- to_named(to_uniplex(ison_algebra, "tasks")) + +test_that("node_x_tie census works", { + test <- node_x_tie(task_eg) + expect_equal(test[1:4], rep(0, 4)) + expect_output(print(test), "fromA") + expect_equal(nrow(summary(test, membership = node_in_roulette(task_eg, 3))), + 3) +}) + +test_that("node_x_dyad census works", { + test <- node_x_dyad(ison_adolescents) + expect_equal(colnames(test)[1:2], c("Mutual", "Null")) +}) + +test_that("node_x_triad census works", { + test <- node_x_triad(task_eg) + expect_equal(top3(test[,16]), c(7,8,6)) + expect_equal(colnames(test)[1:3], c("003", "012", "102")) +}) + +test_that("net_x_dyad census works", { + test <- net_x_dyad(ison_adolescents) + expect_equal(test[[1]], 10) + expect_equal(test[[2]], 18) + expect_equal(names(test), c("Mutual", "Null")) + expect_output(print(test), "Mutual") +}) + +test_that("net_x_triad census works", { + test <- net_x_triad(ison_adolescents) + expect_equal(test[[1]], 13) + expect_equal(test[[3]], 29) + expect_equal(names(test), c("003", "012", "102", "201", "210", "300")) + expect_equal(names(summary(test)), c("003", "012", "102", "201", "210", "300")) + # Error + expect_error(net_x_triad(ison_southern_women)) +}) + +test_that("net_x_tetrad census works", { + test <- net_x_tetrad(ison_southern_women) + expect_values(c(test)[1], 12388) +}) + +test_that("node_x_tetrad census works", { + test <- node_x_tetrad(ison_southern_women) + expect_equal(test[1,1], 1241) +}) + +test_that("net_mixed census works", { + marvel_friends <- to_unsigned(to_uniplex(fict_marvel, "relationship"), "positive") + test <- net_x_mixed(marvel_friends, to_uniplex(fict_marvel, "affiliation")) + expect_equal(unname(test[1]), 1137) + expect_equal(names(test[1]), "22") + # Errors + expect_error(net_x_mixed(ison_southern_women, + to_uniplex(fict_marvel, "affiliation"))) + expect_error(net_x_mixed(to_uniplex(fict_marvel, "affiliation"), + ison_southern_women)) + expect_error(net_x_mixed(ison_karateka, + to_uniplex(fict_marvel, "affiliation"))) +}) + +test <- node_x_path(ison_southern_women) +test_that("node path census works", { + expect_equal(c(net_nodes(ison_adolescents)), + nrow(node_x_path(ison_adolescents))) + expect_true(nrow(node_x_path(ison_southern_women)) == + ncol(node_x_path(ison_southern_women))) +}) + +test_that("node_x_brokerage works", { + test <- node_x_brokerage(ison_networkers, "Discipline") + expect_equal(dim(test), c(32,6)) +}) + +test_that("net_x_brokerage works", { + test <- net_x_brokerage(ison_networkers, "Discipline") + expect_equal(top3(names(test)), c("Coordinator","Itinerant","Gatekeeper")) +}) diff --git a/tests/testthat/test-motif_ties.R b/tests/testthat/test-motif_ties.R new file mode 100644 index 0000000..043dbb7 --- /dev/null +++ b/tests/testthat/test-motif_ties.R @@ -0,0 +1,12 @@ +# tie_motifs <- funs_objs[grepl("tie_x_", names(funs_objs))] +# for(fn in names(tie_motifs)) { +# for (ob in names(data_objs)) { +# test_that(paste(fn, "works on", ob), { +# if(fn == "x"){ +# } else { +# expect_s3_class(tie_motifs[[fn]](data_objs[[ob]]), "tie_motif") +# } +# }) +# } +# } +