Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

# Checklist:

- PR form
- [ ] Description above itemizes changes under subtitles, e.g. "## Data""
- [ ] Any closed, fixed, or related issues are referenced and explained in the description above, e.g. "Fixed #0 by adding A"
- [ ] Package builds on my OS without issues
- PR checks all pass for latest commit
- [ ] CodeFactor check: Package improves or maintains good style
- [ ] Package builds on Mac
- [ ] Package builds on Windows
- [ ] Package builds on Linux
- [ ] CodeCov check: Package improves or maintains good test coverage
- Documentation
- [ ] Any new or modified functions or data have roxygen style documentation in their .R scripts
- [ ] Longer functions are commented inline or broken down into helper functions so that it is easier to debug in the future
- [ ] PR description above and the NEWS.md file are aligned
- [ ] 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"
5 changes: 4 additions & 1 deletion .github/workflows/prchecks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ jobs:
path: build/

- name: Calculate code coverage
run: Rscript -e "covr::codecov()"
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()
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/pushrelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ jobs:
path: build/

- name: Calculate code coverage
if: runner.os == 'macOS-latest'
run: Rscript -e "covr::codecov()"
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
Expand Down
5 changes: 2 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: manynet
Title: Many Ways to Make, Modify, Mark, and Measure Myriad Networks
Version: 1.6.0
Date: 2025-08-22
Version: 1.6.1
Date: 2025-09-12
Description: Many tools for making, modifying, marking, measuring,
and motifs and memberships of many different types of networks.
All functions operate with matrices, edge lists, and 'igraph', 'network', and 'tidygraph' objects,
Expand Down Expand Up @@ -36,7 +36,6 @@ Suggests:
sna,
testthat (>= 3.0.0),
tibble,
tidyr,
xml2
Authors@R:
c(person(given = "James",
Expand Down
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ export(cluster_hierarchical)
export(collect_changes)
export(create_components)
export(create_core)
export(create_cycle)
export(create_degree)
export(create_ego)
export(create_empty)
Expand All @@ -347,6 +348,8 @@ export(create_motifs)
export(create_ring)
export(create_star)
export(create_tree)
export(create_wheel)
export(create_windmill)
export(delete_nodes)
export(delete_ties)
export(extract_tute)
Expand Down
32 changes: 32 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
# manynet 1.6.1

## Package

- Dropped mapping from function overview on website
- Dropped viz tutorial, moved to `{autograph}`
- Added CITATION
- Improved `run_tute()` and `extract_tute()` to search for `{autograph}`

## Printing

- Improved `describe_changes()` to work with time as well as begin/end tie attributes

## Making

- Added `create_windmill()`
- Added `create_wheel()`
- Added `create_cycle()`

## Modifying

- Fixed `to_uniplex()` test so that it doesn't rely on random sampling

## Marking

- Improved `is_multiplex()` to recognise more tie attributes

## Data

- Added `irps_nuclear_discourse` for goldfish and various testing of dynamic networks
- Added `ison_judo_moves` for judo move sequences

# manynet 1.6.0

## Package
Expand Down
10 changes: 9 additions & 1 deletion R/class_networks.R
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ describe_changes <- function(x){
if(is_longitudinal(x)){
paste(" over", max(tie_attribute(x, "wave")), "waves")
} else if (is_dynamic(x)){
paste(" from", min(tie_attribute(x, "begin")), "to", max(tie_attribute(x, "end")))

if("time" %in% net_tie_attributes(x)){
paste(" from", min(tie_attribute(x, "time"), na.rm = TRUE),
"to", max(tie_attribute(x, "time"), na.rm = TRUE))
} else if("begin" %in% net_tie_attributes(x)){
paste(" from", min(tie_attribute(x, "begin"), na.rm = TRUE),
"to", max(tie_attribute(x, "end"), na.rm = TRUE))
}

}
}
70 changes: 70 additions & 0 deletions R/data_ison.R
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,50 @@
#' ```
"ison_karateka"

## Judo moves ####

#' One-mode judo moves network (Bastazini 2025)
#'
#' @description
#' Judo is a martial art with a long history and many different techniques.
#' It involves a dynamic 'chess match' of throws, holds, locks,
#' submission techniques, and other maneuvers.
#' The techniques are often combined in sequences to create fluid and
#' effective combinations to score points or achieve victory.
#' As the author of this network describes,
#' "While individual techniques (called waza) are foundational,
#' the real artistry lies in how they are chained together --
#' through renraku-waza (combination techniques) and
#' renzoku-waza (continuous combination techniques)"
#' This network describes the relationships between 33 individual judo moves,
#' as recognised by the Kodokan (the official international governing body of judo),
#' where an arc indicates that one move can be followed by another.
#' @docType data
#' @keywords datasets
#' @name ison_judo_moves
#' @usage data(ison_judo_moves)
#' @references
#' Bastazini, Vinicius. 2025.
#' "The Dynamics of the “Gentle Way”: Exploring Judo Attack Combinations as Networks in R",
#' https://geekcologist.wordpress.com/2025/05/27/the-dynamics-of-the-gentle-way-exploring-judo-attack-combinations-as-networks-in-r/
#'
#' Kashiwazaki, Katsuhiko, and Hidetoshi Nakanishi. 1995.
#' _Attacking Judo: A Guide to Combinations and Counters_.
#' Ippon Books.
#'
#' Kawaishi, Mikinosuke. 1963.
#' _Standing judo: The combinations and counter-attacks_.
#' Budoworks.
#'
#' van Haesendonck, F.M. 1968.
#' _Judo: Ecyclopédie par l’Image_.
#' Éditions Erasme: Anvers-Bruxelles.
#' @format
#' ```{r, echo = FALSE}
#' ison_judo_moves
#' ```
"ison_judo_moves"

## Koenigsberg ####

#' One-mode Seven Bridges of Koenigsberg network (Euler 1741)
Expand Down Expand Up @@ -837,5 +881,31 @@
#' ```
"irps_revere"

## Nuclear Discourse ####

#' Two-mode dynamic discourse network of Germany's nuclear energy phase-out (Haunss and Hollway 2023)
#'
#' @description
#' Following the 11 March 2011 Fukushima nuclear disaster in Japan,
#' there was a vigorous public debate in Germany about the future of nuclear energy.
#' This network captures the discourse established by 337 actors,
#' including individual politicians, experts, parties, and the media,
#' and their claims about nuclear energy and German nuclear energy policy.
#' These claims were with respect to 54 concepts coded,
#' and could be supportive or critical, and could also be repeated.
#' @docType data
#' @keywords datasets
#' @name irps_nuclear_discourse
#' @usage data(irps_nuclear_discourse)
#' @references
#' Haunss Sebastian, James Hollway. 2023.
#' "Multimodal mechanisms of political discourse dynamics and the case of Germany’s nuclear energy phase-out".
#' _Network Science_, 11(2):205-223.
#' \doi{10.1017/nws.2022.31}
#' @format
#' ```{r, echo = FALSE}
#' irps_nuclear_discourse
#' ```
"irps_nuclear_discourse"


155 changes: 155 additions & 0 deletions R/make_create.R
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,161 @@ create_core <- function(n, directed = FALSE, mark = NULL) {
}
}

#' @rdname make_create
#' @examples
#' create_windmill(6)
#' @export
create_windmill <- function(n) {
total_nodes <- n

if (length(total_nodes) == 1) {

if (total_nodes < 3) {
snet_abort("At least 3 nodes required to form a windmill graph.")
}

# Find all valid (k, n) pairs
valid_pairs <- list()
for (k in 1:(total_nodes - 2)) {
n_candidate <- (total_nodes - 1) / k + 1
if (n_candidate == floor(n_candidate) && n_candidate >= 2) {
valid_pairs[[length(valid_pairs) + 1]] <- list(k = k, n = as.integer(n_candidate))
}
}

if (length(valid_pairs) == 0) {
snet_abort("No valid (k, n) pair found for a windmill graph with given number of nodes.")
}

# Choose the pair with minimal |k - n|
best_pair <- valid_pairs[[which.min(sapply(valid_pairs, function(p) abs(p$k - p$n)))]]
k <- best_pair$k
n <- best_pair$n
snet_info("Using k = {k} groups of n = {n} nodes (including universal node)")

total_nodes <- 1 + k * (n - 1)
g <- igraph::make_empty_graph(n = total_nodes, directed = FALSE)

universal_node <- 1
current_node <- 2

for (i in 1:k) {
group_nodes <- current_node:(current_node + n - 2)

group_edges <- utils::combn(group_nodes, 2, simplify = FALSE)
universal_edges <- lapply(group_nodes, function(x) c(universal_node, x))

g <- igraph::add_edges(g, unlist(c(group_edges, universal_edges), recursive = FALSE))
current_node <- current_node + n - 1
}

return(g)

} else if (length(total_nodes) == 2) {

a <- total_nodes[1] # Mode A
b <- total_nodes[2] # Mode B

if (a < 2 || b < 1) {
snet_abort("Need at least 2 Mode A nodes and 1 Mode B node.")
}

k <- a - 1 # Number of blades/groups
if (b %% k != 0) {
snet_abort("Mode B nodes must divide evenly across Mode A groups (excluding universal node).")
}

group_size <- b / k
mode_A_nodes <- 1:a
mode_B_nodes <- (a + 1):(a + b)

edges <- list()
current_B <- a + 1

for (i in 1:k) {
group_B_nodes <- current_B:(current_B + group_size - 1)

for (b_node in group_B_nodes) {
# Connect to universal node (Mode A node 1)
edges[[length(edges) + 1]] <- c(1, b_node)
# Connect to group-specific Mode A node
edges[[length(edges) + 1]] <- c(i + 1, b_node)
}

current_B <- current_B + group_size
}

g <- igraph::make_empty_graph(n = a + b, directed = FALSE)
g <- igraph::add_edges(g, unlist(edges))
g <- igraph::set_vertex_attr(g, "type", value = c(rep(TRUE, a), rep(FALSE, b))) # Bipartite flag

return(g)

} else snet_abort("Sorry, that's not possible.")

}

#' @rdname make_create
#' @examples
#' create_cycle(6)
#' @export
create_cycle <- function(n){
# Helper: Create edge list for unimodal cycle
unimodal_cycle <- function(n_nodes) {
edges <- data.frame(
from = 1:n_nodes,
to = c(2:n_nodes, 1)
)
as_tidygraph(edges)
}

# Helper: Create edge list for bimodal cycle
bimodal_cycle <- function(n_modes) {
if(n_modes[1] != n_modes[2]){
snet_abort("Two-mode cycles require equal number of nodes in each mode.")
}
unimodal_cycle(sum(n_modes)) %>%
mutate_nodes(type = rep_len(c(F,T), sum(n_modes)))
}

# Main logic
if (length(n) == 1) {
# Unimodal cycle
net <- unimodal_cycle(n)
} else if (length(n) == 2) {
# Bimodal cycle
net <- bimodal_cycle(n)
} else {
snet_abort("Argument 'n' must be a scalar or a vector of length 2.")
}
return(net)
}

#' @rdname make_create
#' @examples
#' create_wheel(6)
#' @export
create_wheel <- function(n) {
if (length(n) == 1) {
if (n < 4) {
stop("At least 4 nodes required to form a wheel graph.")
}
center_node <- 1
rim_nodes <- 2:n
# Create the cycle (rim)
rim_cycle <- cbind(rim_nodes, c(rim_nodes[-1], rim_nodes[1]))
# Connect center to each rim node
center_edges <- cbind(center_node, rim_nodes)
edges <- rbind(rim_cycle, center_edges)
g <- igraph::graph_from_edgelist(edges, directed = FALSE)
return(g)
} else if (length(n) == 2) {
snet_abort("Wheel graphs are undefined for two-mode networks",
"because the rim nodes cannot be adjacent to both neighbouring",
"rim nodes and the dominant node in the centre.")
} else stop("Argument 'n' must be a scalar or vector of length 2.")
}

# #' @rdname create
# #' @details Creates a nested two-mode network.
# #' Will construct an affiliation matrix,
Expand Down
Loading
Loading