Hi,
first of all, thanks for tidygraph, which I find very useful for network analysis within the tidyverse ecosystem.
I would like to propose a small helper for edge mutation in tidygraph, aimed at making common node-based edge operations more readable and closer to a dplyr-style expression.
Motivation
When mutating edges based on node attributes, the current idiom typically relies on .N() together with explicit indexing via from / to. For example:
flo_tidy %>%
activate("edges") %>%
mutate(
to_medici = (.N()$name[from] == "Medici" | .N()$name[to] == "Medici"))
This approach is flexible and expressive, but can feel somewhat redundant, since node attributes need to be accessed explicitly through .N() and indexing.
Proposal
I experimented with a minimal helper that precomputes the two edge endpoints and exposes a small set of functions inside mutate(), such as:
from(var) / to(var) to access node attributes at edge endpoints
either(expr), both(expr), one(expr), neither(expr) for symmetric nodes logic
Using this approach, the example above becomes:
flo_tidy %>%
proposed_edge_mutate(
to_medici = either(name == "Medici")
)
Example using a node-level variable
flo_tidy %>%
activate("nodes") %>%
mutate(centrality = centrality_power()) %>%
activate("edges") %>%
mutate(
mean_centrality = (.N()$centrality[from] + .N()$centrality[to]) / 2)
Equivalent proposed syntax:
flo_tidy %>%
proposed_edge_mutate(
mean_centrality = (from(centrality) + to(centrality)) / 2)
Results are identical (verified via all.equal()).
Scope
I would be interested in your thoughts on whether an interface like this could fit within tidygraph, or if it would be considered out of scope.
Best,
Alessandro
Minimal proof-of-concept implementation
proposed_edge_mutate <- function(graph, ...) {
if (!inherits(graph, "tbl_graph")) {
rlang::abort("`graph` must be a tidygraph `tbl_graph`.")
}
local_env <- environment()
nodes <- graph %>%
tidygraph::activate("nodes") %>%
tibble::as_tibble() %>%
dplyr::mutate(.node_id = dplyr::row_number())
edges <- graph %>%
tidygraph::activate("edges") %>%
tibble::as_tibble()
if (!all(c("from", "to") %in% names(edges))) {
rlang::abort("Edge table must contain `from` and `to` columns.")
}
tbl_from <- edges %>%
dplyr::select(from) %>%
dplyr::left_join(nodes, by = dplyr::join_by(from == .node_id))
tbl_to <- edges %>%
dplyr::select(to) %>%
dplyr::left_join(nodes, by = dplyr::join_by(to == .node_id))
from <- function(var) {
var_sym <- rlang::ensym(var)
dplyr::pull(tbl_from, !!var_sym)
}
to <- function(var) {
var_sym <- rlang::ensym(var)
dplyr::pull(tbl_to, !!var_sym)
}
either <- function(expr) {
expr_quo <- rlang::enquo(expr)
rlang::eval_tidy(expr_quo, data = tbl_from) |
rlang::eval_tidy(expr_quo, data = tbl_to)
}
both <- function(expr) {
expr_quo <- rlang::enquo(expr)
rlang::eval_tidy(expr_quo, data = tbl_from) &
rlang::eval_tidy(expr_quo, data = tbl_to)
}
neither <- function(expr) {
expr_quo <- rlang::enquo(expr)
!(rlang::eval_tidy(expr_quo, data = tbl_from) |
rlang::eval_tidy(expr_quo, data = tbl_to))
}
one <- function(expr) {
expr_quo <- rlang::enquo(expr)
rlang::eval_tidy(expr_quo, data = tbl_from) !=
rlang::eval_tidy(expr_quo, data = tbl_to)
}
quos <- rlang::enquos(...)
quos <- lapply(quos, function(q) {
rlang::new_quosure(
expr = rlang::quo_get_expr(q),
env = local_env
)
})
graph %>%
tidygraph::activate("edges") %>%
dplyr::mutate(!!!quos)
}
Hi,
first of all, thanks for tidygraph, which I find very useful for network analysis within the tidyverse ecosystem.
I would like to propose a small helper for edge mutation in tidygraph, aimed at making common node-based edge operations more readable and closer to a dplyr-style expression.
Motivation
When mutating edges based on node attributes, the current idiom typically relies on
.N()together with explicit indexing viafrom/to. For example:This approach is flexible and expressive, but can feel somewhat redundant, since node attributes need to be accessed explicitly through
.N()and indexing.Proposal
I experimented with a minimal helper that precomputes the two edge endpoints and exposes a small set of functions inside
mutate(), such as:from(var)/to(var)to access node attributes at edge endpointseither(expr),both(expr),one(expr),neither(expr)for symmetric nodes logicUsing this approach, the example above becomes:
Example using a node-level variable
Equivalent proposed syntax:
Results are identical (verified via
all.equal()).Scope
I would be interested in your thoughts on whether an interface like this could fit within tidygraph, or if it would be considered out of scope.
Best,
Alessandro
Minimal proof-of-concept implementation