Network analysis and plotting packages such as 'igraph', 'tidygraph', and 'ggraph' expect the edges and nodes of a graph as flat tables. Methods are provided to build those tables from data frames (column-pair edges chosen with 'tidyselect' semantics), tree-based model objects ('randomForest', 'tree', 'rpart', 'xgboost', 'gbm'), lists (recursive parent-child edges), and atomic vectors (sequential edges). Graphs can also be constructed in one step via as.igraph() and as_tbl_graph() methods for tree models.
networkformat converts R objects into network edgelist/nodelist
format for visualization and analysis with packages like igraph,
tidygraph, and ggraph. It works with tree-based ML models
(randomForest, tree, rpart, xgboost, gbm), data frames, lists,
and vectors.
Full documentation at https://jessebrandtdata.github.io/networkformat/.
# install.packages("devtools")
devtools::install_github("jessebrandtdata/networkformat")
library(networkformat)
# Vectors --- sequential chain of edges
edgelist(c("A", "B", "C", "D"))
#> from to
#> 1 A B
#> 2 B C
#> 3 C D
nodelist(c("A", "B", "A", "C"))
#> name n
#> 1 A 2
#> 2 B 1
#> 3 C 1
# Data frames --- column pairs become edges
edgelist(courses, source_cols = course, target_cols = prereq)
# Tree models --- one-step graph conversion
library(tree)
tr <- tree(Species ~ ., data = iris)
as.igraph(tr)
| Input | edgelist() |
nodelist() |
as.igraph() / as_tbl_graph() |
|---|---|---|---|
| vector (character, numeric, …) | sequential edges i -> i+1 |
unique values with frequency | — |
| list | recursive parent-child edges | node metadata (type, depth, children) | — |
| data.frame | column-pair edges | reorder with id_col first |
— |
| randomForest | parent-child splits | node attributes with labels | single or multi-tree graph |
| tree | parent-child splits | node attributes with labels | full tree graph |
| rpart | parent-child splits | node attributes with labels | full tree graph |
| xgb.Booster (xgboost) | parent-child splits | node attributes with labels | single or multi-tree graph |
| gbm | parent-child splits | node attributes with labels | single or multi-tree graph |
Any atomic vector becomes a sequential edgelist: element i connects to
element i + 1.
edgelist(c("intro", "basics", "advanced", "project"))
#> from to
#> 1 intro basics
#> 2 basics advanced
#> 3 advanced project
# Collapse duplicate edges with a count
edgelist(c("A", "B", "A", "B", "C"), weights = TRUE)
#> from to weight
#> 1 A B 2
#> 2 B A 1
#> 3 B C 1
# Node list gives unique values and frequencies
nodelist(c("A", "B", "A", "B", "C"))
#> name n
#> 1 A 2
#> 2 B 2
#> 3 C 1
Any list becomes a recursive parent-child edgelist. Nested lists create deeper edges with path-style IDs.
edgelist(list(a = list(b = 1, c = 2), d = 3))
#> from to depth
#> 1 root root/a 1
#> 2 root/a root/a/b 2
#> 3 root/a root/a/c 2
#> 4 root root/d 1
# S3 objects without a dedicated method are decomposed as plain lists
edgelist(lm(y ~ x, data.frame(x = 1:3, y = 1:3)))
Specify which columns are source/target nodes. All other columns are carried as edge attributes by default.
# Basic edgelist from two columns
edgelist(courses, source_cols = course, target_cols = prereq)
# Multiple target columns (Cartesian product of source x target)
edgelist(courses, source_cols = course, target_cols = c(prereq, crosslist))
# Keep only specific attribute columns
edgelist(courses, source_cols = course, target_cols = prereq,
attr_cols = c(dept, credits))
# Symmetric (undirected) edges with deduplication
edgelist(courses, source_cols = course,
target_cols = c(prereq, crosslist),
symmetric_cols = crosslist)
# Collapse duplicate rows and count them
edgelist(courses, source_cols = course, target_cols = prereq,
weights = TRUE)
Key parameters:
na.rm = TRUE (default) — remove edges where from or to is NAsymmetric_cols — mark target columns as undirected; adds a
directed columndedupe = TRUE (default) — when using symmetric_cols, keep only one
direction (from <= to)weights = TRUE — collapse identical rows and add a weight count
columnlibrary(randomForest)
rf <- randomForest(Species ~ ., data = iris, ntree = 5)
# Edgelist for all trees
el <- edgelist(rf)
head(el)
#> Columns: from, to, split_var, split_point, prediction, direction, treenum, split_var_name
# Extract specific trees
el_1 <- edgelist(rf, treenum = 1)
el_13 <- edgelist(rf, treenum = c(1, 3))
# Node list
nl <- nodelist(rf)
head(nl)
#> Columns: name, is_leaf, split_var, split_var_name, split_point, prediction, treenum, label
library(tree)
tr <- tree(Species ~ Sepal.Length + Sepal.Width, data = iris)
# Edgelist with parsed split components
el <- edgelist(tr)
head(el)
#> Columns: from, to, label, split_var, split_op, split_point
# Node list
nl <- nodelist(tr)
head(nl)
#> Columns: name, var, n, dev, yval, is_leaf, label
Skip the edgelist/nodelist step and go straight to an igraph or tbl_graph:
library(tree)
library(igraph)
library(tidygraph)
tr <- tree(Species ~ ., data = iris)
# igraph
g <- as.igraph(tr)
# tidygraph
tg <- as_tbl_graph(tr)
# randomForest (single tree or multiple as disconnected components)
library(randomForest)
rf <- randomForest(Species ~ ., data = iris, ntree = 3)
g_rf <- as.igraph(rf, treenum = 1)
library(ggraph)
library(tidygraph)
library(tree)
tr <- tree(Species ~ ., data = iris)
as_tbl_graph(tr) |>
ggraph(layout = "tree") +
geom_edge_link(arrow = arrow(length = unit(2, "mm")),
end_cap = circle(3, "mm")) +
geom_node_point(aes(color = is_leaf), size = 4) +
geom_node_text(aes(label = label), size = 3, vjust = -0.8) +
theme_graph()
To add support for a new model class:
R/edgelist.newclass.R with
edgelist.newclass(input_object, ...)@export roxygen tagdevtools::document() to update NAMESPACEtests/testthat/R/edgelist.randomForest.R as a referenceMIT