GraphSpace accessors and transformations
Sysbiolab Team
2026-10-19
Source:vignettes/articles/graphspace-accessors.Rmd
graphspace-accessors.Rmd
Package: RGraphSpace 1.5.7
# Check required version
if (packageVersion("RGraphSpace") < "1.5.7"){
message("Need to update 'RGraphSpace' for this vignette")
remotes::install_github("sysbiolab/RGraphSpace")
}Overview
RGraphSpace is primarily designed for graph rendering and expects graphs in their final form before they enter its workflow. More extensive graph transformations are therefore best performed upstream, while preparing the input data.
Nevertheless, downstream analyses may require accessing and modifying
a GraphSpace object to explore alternative graph
configurations or highlight specific subsets of the data.
In the following, we demonstrate the GraphSpace
accessors for graph manipulation. All general accessors use the
gs_* prefix to reduce the chance of masking functions from
other packages, particularly graph-analysis packages that are commonly
used side-by-side.
Setting basic input data
# Make a toy modular graph
set.seed(42)
g <- sample_islands(
islands.n = 3, # number of modules
islands.size = 30, # nodes per module
islands.pin = 0.25, # probability of edges within modules
n.inter = 2) # edges between modules
# Assign module membership to nodes
V(g)$module <- rep(1:3, each = 30)
# Assign colors to nodes
V(g)$nodeFillColor <- rainbow(3)[V(g)$module]
# Assign a categorical variable to nodes
V(g)$node_group <- c("A", "B", "C")[V(g)$module]
# Assign numeric variables to nodes and edges
V(g)$node_var <- rnorm(vcount(g))
E(g)$edge_var <- rnorm(ecount(g))
# Create a GraphSpace object
gs <- GraphSpace(g, simplify = FALSE)Manipulating attributes
The gs_vertex_attr() and gs_edge_attr()
functions provide a single interface to access, add, modify, and delete
node and edges attributes, respectively. This interface wraps the
corresponding igraph methods, applying them to the internal
graph representation and propagating the result to all
GraphSpace components.
Node attributes
# Access all vertex attributes
# gs_vertex_attr(gs)
# Access a specific vertex attribute
gs_vertex_attr(gs, "node_group")[1:5]
#> n1 n2 n3 n4 n5
#> "A" "A" "A" "A" "A"
# Add or replace an entire vertex attribute
gs_vertex_attr(gs, "nodeSize") <- 5
# Modify a single value within a vertex attribute
gs_vertex_attr(gs, "nodeSize")["n1"] <- 10
# Add a new vertex variable
gs_vertex_attr(gs, "new_node_var") <- rnorm(gs_vcount(gs))
# Delete a vertex attribute or variable by assigning NULL
gs_vertex_attr(gs, "new_node_var") <- NULL
gs
#> A GraphSpace-class object for:
#> IGRAPH 874b116 UN-- 90 329 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [-8, 11] (cols)
#> | y: [-10, 8] (rows)Edge attributes
# Access a specific edge attribute
gs_edge_attr(gs, "arrowType")[1:3]
#> [1] "---" "---" "---"
# Add or replace an entire edge attribute
gs_edge_attr(gs, "edgeColor") <- "grey"
# Add a new edge variable
gs_edge_attr(gs, "new_edge_var") <- rnorm(gs_ecount(gs))
# Delete an edge attribute or variable by assigning NULL
gs_edge_attr(gs, "new_edge_var") <- NULL
gs
#> A GraphSpace-class object for:
#> IGRAPH 874b116 UN-- 90 329 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | edgeColor (e/c), arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [-8, 11] (cols)
#> | y: [-10, 8] (rows)Protected attributes
Some node and edge attributes are protected from certain edits. The
name and vertex attributes are read-only: the
package relies on them internally, so they cannot be modified, and
attempting to assign to one raises an error. A second group,
x, y, nodeLabel, and
nodeSize, can be modified but not deleted, since they are
required for essential operations. Reading any of these is unaffected.
For edges, arrowType is the only protected attribute
exposed to the user; it can be modified but not deleted. The remaining
protected edge attributes, vertex1, vertex2,
name1, and name2, are read-only and hidden
from the interface, since they are derived from the node data.
# Return the vertex attribute `name`
gs_vertex_attr(gs, "name")[1:5]
#> [1] "n1" "n2" "n3" "n4" "n5"
# Protected attribute that cannot be modified
# gs_vertex_attr(gs, "name") <- 1
#> error: 'name' is a read-only node attribute.
# Protected attribute that cannot be deleted
# gs_vertex_attr(gs, "x") <- NULL
#> error: 'x' is a protected node attribute.See the Building a GraphSpace tutorial for a detailed list of RGraphSpace attributes.
Adding nodes
The gs_add_nodes() function adds one or more nodes to a
GraphSpace object. Attributes present on existing nodes but
absent in the input value are filled from package defaults.
Standard node attributes (such as nodeSize and
nodeColor) are kept consistent across old and new nodes;
the @graph, @nodes, and @fdata
slots are updated consistently. Because new nodes introduce coordinates
into the existing layout, the normalized state is invalidated and
normalizeGraphSpace() must be re-run afterwards.
# Functional form (pipe-friendly) returns a modified copy
gs <- gs |> gs_add_nodes(data.frame(name = "new1", x = 0, y = -2))
# Assignment form modifies gs in place
gs_add_nodes(gs) <- data.frame(name = "new2", x = 0, y = 2)
# Add multiple nodes with visual attributes
gs <- gs_add_nodes(gs, data.frame(
name = c("new3", "new4"),
x = c(-2, 2),
y = c(0, 0),
nodeSize = c(8, 5),
nodeFillColor = c("steelblue", "tomato")) )
# Add two nodes; x and y are assigned random values
gs_add_nodes(gs) <- c("new5", "new6")Adding edges
The gs_add_edges() function adds one or more edges to a
GraphSpace object. Both endpoints of every new edge must
already exist in the node set. The @graph and
@edges slots, along with all derived edge quantities, are
updated consistently; the node set and the normalized coordinate state
are not affected.
# Functional form (pipe-friendly) returns a modified copy
gs <- gs |> gs_add_edges(data.frame(from = "new1", to = "new2"))
# Assignment form modifies gs in place
gs_add_edges(gs) <- data.frame(from = "new3", to = "new4")
# Add multiple edges with a numeric attribute
gs <- gs_add_edges(gs, data.frame(
from = c("new1", "new3"),
to = c("new2", "new4"),
edge_var = c(10, 20)) )For objects built with simplify = TRUE (the default),
loop edges (from == to), parallel edges, and duplicate rows
within value are dropped with a warning. To allow loops or
parallel edges, rebuild the object with
GraphSpace(g, simplify = FALSE).
Subsetting nodes
The gs_subset_nodes() function retains a subset of nodes
and automatically removes any edge whose endpoint is no longer present.
The result is propagated to all GraphSpace components.
# Subset by node name (character vector)
gs2 <- gs_subset_nodes(gs, c("n1", "n2", "n3"))
# Subset by integer position
gs2 <- gs_subset_nodes(gs, 1:5)
# Subset by predicate (data masking against @nodes columns)
gs2 <- gs_subset_nodes(gs, node_group != "A")
# Subset by pre-evaluated logical vector
keep <- gs$node_var > 0
gs2 <- gs_subset_nodes(gs, keep)
# Combining with pipes
gs2 <- gs |>
gs_subset_nodes(node_group == "C") |>
gs_subset_edges(edge_var > 0)Subsetting edges
The gs_subset_edges() retains a subset of edges without
modifying the node set, and the result is propagated to all
GraphSpace components.
# Subset by predicate on an edge attribute
gs2 <- gs_subset_edges(gs, edge_var > 0)
# Subset by endpoint names: 'name1' and 'name2' are columns in
# @edges and can be used directly inside any predicate expression
gs2 <- gs_subset_edges(gs, name1 == "new1" & name2 == "new2")
# Combining endpoint and attribute conditions
gs2 <- gs_subset_edges(gs, name1 == "new1" & edge_var > 0)
# By integer position
gs2 <- gs_subset_edges(gs, 1:3)
# By logical vector
gs2 <- gs_subset_edges(gs, gs_edges(gs)$edge_var > 0)Subscript operators
The [ operator subsets a GraphSpace object
along two independent dimensions: nodes (i) and edges
(j). This differs from the usual data-frame convention,
where [i, j] refers to rows and columns of a single table.
Here, neither index subsets columns; both select graph entities
directly. Omitting an index retains all elements along that
dimension.
Synchronization rules:
-
x[i, ]Node-induced subgraph. Nodes are selected byi, then edges are automatically pruned to those whose endpoints both survived. Normalized coordinates are preserved. -
x[, j]Edge selection. Edges are selected byj; the node set is untouched and no node pruning occurs. -
x[i, j]Combined selection. Node filtering is applied first, thenjis evaluated against the original, unfiltered edge table; an edge survives only if it satisfiesjand both its endpoints survived node filtering.
# Node-induced subgraph: keep named nodes, prune dangling edges
gs[c("n1", "n2", "n3"), ]
#> A GraphSpace-class object for:
#> IGRAPH af6ebfd UN-- 3 1 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | edgeColor (e/c), arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [4, 7] (cols)
#> | y: [3, 4] (rows)
# Node-induced subgraph by integer position
gs[1:4, ]
#> A GraphSpace-class object for:
#> IGRAPH 2b5f623 UN-- 4 3 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | edgeColor (e/c), arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [4, 7] (cols)
#> | y: [2, 4] (rows)
# Node-induced subgraph by pre-evaluated logical mask
gs[gs$node_var > 0, ]
#> A GraphSpace-class object for:
#> IGRAPH 5612021 UN-- 39 70 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | edgeColor (e/c), arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [-8, 8] (cols)
#> | y: [-9, 7] (rows)
# Edge selection only: keep all nodes
gs[, gs_edges(gs)$edge_var > 0]
#> A GraphSpace-class object for:
#> IGRAPH 723d52f UN-- 96 161 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | edgeColor (e/c), arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [-8, 11] (cols)
#> | y: [-10, 8] (rows)
# Edge selection by endpoint: predicates must be pre-evaluated
gs[, gs_edges(gs)$name1 == "n1"]
#> A GraphSpace-class object for:
#> IGRAPH 78da3b4 UN-- 96 10 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | edgeColor (e/c), arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [-8, 11] (cols)
#> | y: [-10, 8] (rows)
# Alternatively, wrap the predicate in quote()
gs[, quote(name1 == "n1" & edge_var > 0)]
#> A GraphSpace-class object for:
#> IGRAPH 5d0986e UN-- 96 3 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | edgeColor (e/c), arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [-8, 11] (cols)
#> | y: [-10, 8] (rows)
# Combined: node filter first, then edge intersection
gs[quote(node_group == "A"), gs_edges(gs)$edge_var > 0]
#> A GraphSpace-class object for:
#> IGRAPH e209d1d UN-- 30 49 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | edgeColor (e/c), arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [3, 11] (cols)
#> | y: [-2, 5] (rows)The [[ operator, by contrast, is a simple accessor:
x[["nodes"]], x[["edges"]],
x[["graph"]], and x[["fdata"]] return the
corresponding component unmodified, equivalent to
getGraphSpace(x, "nodes") and so on. No subsetting or
synchronization logic applies.
gs[["nodes"]] # same as getGraphSpace(gs, "nodes")
gs[["edges"]] # same as getGraphSpace(gs, "edges")
gs[["graph"]] # same as getGraphSpace(gs, "graph")
gs[["fdata"]] # same as getGraphSpace(gs, "fdata")The $ accessor
The $ operator provides direct, attribute-style access
to a single vertex attribute of a
GraphSpace object, similar to how $ works on a
data frame or list. A companion .DollarNames method enables
tab-completion after gs$ in interactive sessions
(e.g. via RStudio console), listing all columns currently in
the node table.
Arbitrary attributes created via $<- are stored as
payload: they are added to the node table but not
expanded into vertex attributes in the underlying igraph
object. Geometries use this same storage path
to avoid expanding large, complex objects into vertex attributes.
# Set an existing vertex attribute
gs$nodeSize <- 5
# Modify one element
gs$nodeSize[1] <- 10
# Add a new RGraphSpace vertex attribute
gs$nodeLineColor <- "grey"
# Add a new node variable (payload)
gs$new_node_var <- 1
gs
#> A GraphSpace-class object for:
#> IGRAPH 7f372e8 UN-- 96 333 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), nodeLineColor (v/c), module (v/n), node_group
#> | (v/c), node_var (v/n), edgeColor (e/c), arrowType (e/c), edge_var
#> | (e/n)
#> + node payload: 1 (new_node_var)
#> + node spatial boundaries: raw graph
#> | x: [-8, 11] (cols)
#> | y: [-10, 8] (rows)Assigning NULL deletes an attribute, whether stored as a
vertex attribute or as payload.
# Delete a vertex attribute
gs$nodeLineColor <- NULL
# Delete a payload
gs$new_node_var <- NULL
gs
#> A GraphSpace-class object for:
#> IGRAPH 7f372e8 UN-- 96 333 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | nodeFillColor (v/c), module (v/n), node_group (v/c), node_var (v/n),
#> | edgeColor (e/c), arrowType (e/c), edge_var (e/n)
#> + node spatial boundaries: raw graph
#> | x: [-8, 11] (cols)
#> | y: [-10, 8] (rows)Other accessors
Here we show typical usage of other accessors used across the tutorials. Output is omitted for brevity; run the calls interactively to see each result.
Feature-related accessors
# Make a sparse Matrix aligned to nodes
mtx <- Matrix::Matrix(0, gs_vcount(gs), 2)
rownames(mtx) <- names(gs)
colnames(mtx) <- c("feature1", "feature2")
# Add a sparse matrix
gs_fdata(gs) <- mtx
# Feature names
gs_features(gs)
# Number of features
gs_nfeatures(gs)Image-related accessors
# Images may be provided as raster or numeric matrices;
# 'SpatRaster' objects are supported when the optional
# 'terra' package is available
gs_image(gs) <- as_colorraster(volcano)
# Set a pixel budget for image operations
gs_image_maxpixels(gs) <- 4e+06
# Scale node coordinates into image coordinate space
# (e.g. when node/image use different units/resolution)
gs_scale_factor(gs) <- 0.1
# Undo scaling
gs_scale_factor(gs) <- 1Spatial transformations
Spatial transformation functions are specialized accessors that
operate on a reference frame, either graph or image space, rather than
on the graph structure alone. They transform the spatial representation
of a GraphSpace object while preserving its underlying
graph topology and associated attributes. Combined, these
transformations provide an interface for accessing all eight symmetries
of a square, including support for cropping and resizing operations.
-
normalizeGraphSpace()normalizes the graph and, when present, the background image into a common reference frame. Theimage.spaceargument controls which is used as the reference map. -
cropGraphSpace()subsets a normalizedGraphSpaceto a specific region defined by cropping boundaries. It recalculates node positions and background image boundaries to maintain spatial consistency after cropping, and drops nodes (and edges) that fall outside the cropping window. -
rotateGraphSpace(),flipGraphSpace(), andtransposeGraphSpace()are all exact coordinate/pixel permutations, with no re-sampling or interpolation. Applied to a normalizedGraphSpace, these operations transform the normalized coordinates, leaving the raw coordinates intact so the original orientation can be restored without loss. Applied to aGraphSpacethat has not been normalized, they transform the raw coordinates permanently. Thepersistargument controls this behavior:persist = TRUEwrites the transformation into the raw coordinates regardless of normalization state.
# Create a GraphSpace
gs <- GraphSpace(make_full_graph(30))
# Normalize coordinates
# The 'image.space' argument controls whether the image
# (when present) is used as the reference map
gs <- normalizeGraphSpace(gs, image.space = FALSE)
# Crop
gs_crop <- cropGraphSpace(gs, ymax = 0.5)
# Rotate
gs_rot90 <- rotateGraphSpace(gs)
# Flip
gs_flip <- flipGraphSpace(gs)
# Transpose
gs_t <- transposeGraphSpace(gs)
# Set 'persist' to TRUE to rotate, flip, or transpose
# the raw coordinates directly; the transformation
# will persist through re-normalization
gs_rot90 <- rotateGraphSpace(gs, persist = TRUE)Geometries
Geometry columns from the sf package (class
sfc) are specialized list-columns, where each element
contains a spatial object, such as a point, line, or polygon.
RGraphSpace accepts these objects as node payloads.
Unlike numeric, character, and logical attributes, geometry list-columns add overhead that increases with graph size when stored as vertex attributes. Node payload avoids this: geometries remain in the node table and are not expanded into vertex attributes, so assigning or updating them is a standard data-frame operation.
# Add an 'sfc' geometry column (requires the optional 'sf' package)
# 'sf' is optional to avoid introducing a heavy dependency
if (requireNamespace("sf", quietly = TRUE)) {
gs_geometry(gs) <- sfshape_ngons(n = gs_vcount(gs))
}
gs
#> A GraphSpace-class object for:
#> IGRAPH dfe7108 UN-- 30 435 --
#> + attr: x (v/n), y (v/n), name (v/c), nodeLabel (v/c), nodeSize (v/n),
#> | arrowType (e/c)
#> + node payload: 1 (geometry)
#> + node spatial boundaries: normalized to graph space
#> | x: [-1, 1] -> [0, 1] (cols)
#> | y: [-1, 1] -> [0, 1] (rows)Other geometry accessors are cover in the geometries and nodes tutorial.
Applying igraph functions
The gs_compute() function runs any igraph function on
the graph carried by a GraphSpace, without needing a
dedicated gs_* wrapper for each one. It extracts the
underlying igraph via as.igraph(), applies .f,
and returns the result unchanged. This is the read-only lane onto the
broader igraph ecosystem: measures such as degree(),
betweenness(), and coreness(), all work
through this single entry point.
# Apply igraph functions
gs_compute(gs, igraph::degree)[1:5]
#> n1 n2 n3 n4 n5
#> 29 29 29 29 29
gs_compute(gs, "betweenness", directed = FALSE)[1:5]
#> n1 n2 n3 n4 n5
#> 0 0 0 0 0
# Fold a per-vertex result back as a node attribute
gs$degree <- gs_compute(gs, igraph::degree)It is deliberately not a GraphSpace-modification path. If
.f returns a graph (e.g. simplify(),
induced_subgraph()), this cannot be reintegrated to the
gs object, as graph modifications may invalidate the
correspondence between the graph and other components of the object. A
modified graph must instead go through the graph-modification checks of
the GraphSpace() constructor.
Session information
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.5 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: America/Sao_Paulo
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] igraph_2.3.4 RGraphSpace_1.5.7 ggplot2_4.0.3
#>
#> loaded via a namespace (and not attached):
#> [1] sass_0.4.10 generics_0.1.4 tidyr_1.3.2 class_7.3-24
#> [5] KernSmooth_2.23-27 lattice_0.23-1 digest_0.6.39 magrittr_2.0.5
#> [9] evaluate_1.0.5 grid_4.6.1 RColorBrewer_1.1-3 fastmap_1.2.0
#> [13] jsonlite_2.0.0 Matrix_1.7-6 e1071_1.7-17 ggrastr_1.0.2
#> [17] DBI_1.3.0 purrr_1.2.2 scales_1.4.0 textshaping_1.0.5
#> [21] jquerylib_0.1.4 cli_3.6.6 rlang_1.3.0 units_1.0-1
#> [25] tidygraph_1.3.1 withr_3.0.3 cachem_1.1.0 yaml_2.3.12
#> [29] otel_0.2.0 ggbeeswarm_0.7.3 tools_4.6.1 dplyr_1.2.1
#> [33] vctrs_0.7.3 R6_2.6.1 proxy_0.4-29 classInt_0.4-11
#> [37] lifecycle_1.0.5 fs_2.1.0 htmlwidgets_1.6.4 vipor_0.4.7
#> [41] ragg_1.5.2 pkgconfig_2.0.3 beeswarm_0.4.0 desc_1.4.3
#> [45] pkgdown_2.2.0 pillar_1.11.1 bslib_0.11.0 gtable_0.3.6
#> [49] Rcpp_1.1.2 glue_1.8.1 sf_1.1-2 systemfonts_1.3.2
#> [53] xfun_0.59 tibble_3.3.1 tidyselect_1.2.1 rstudioapi_0.19.0
#> [57] knitr_1.51 dichromat_2.0-1 farver_2.1.2 htmltools_0.5.9
#> [61] rmarkdown_2.32 compiler_4.6.1 S7_0.2.2