# Install graphfast from GitHub if it isn't already available...# if (!requireNamespace("graphfast", quietly = TRUE)) {# if (!requireNamespace("remotes", quietly = TRUE)) install.packages("remotes")# remotes::install_github("KyleHaynes/graphfast")# }# # ...along with the optional (Suggests) packages these examples use.# for (pkg in c("data.table", "visNetwork")) {# if (!requireNamespace(pkg, quietly = TRUE)) install.packages(pkg)# }print(.libPaths())
This page walks through every exported function with small, self-contained examples. The chunks are live — the output you see is produced when the site is rendered.
A graph in graphfast is just a two-column edge list: a matrix or data.table where each row is an edge from -> to. We’ll reuse this little graph with three components throughout:
The workhorse. Returns one component id per node, the size of each component, and the total count.
cc <-find_connected_components(edges)cc$n_components
[1] 3
cc$component_sizes
[1] 4 2 3
cc$components # component id for node 1, 2, 3, ...
[1] 1 1 1 1 2 2 3 3 3
Node 5 and node 6 share component 2; node 1 is in component 1:
cc$components[c(1, 5, 6)]
[1] 1 2 2
compress = FALSE keeps the raw Union–Find root labels instead of tidy consecutive ids (the counts are still correct):
raw <-find_connected_components(edges, compress =FALSE)raw$n_components
[1] 3
head(raw$components)
[1] 0 0 0 0 4 4
You can pass n_nodes explicitly to skip the scan for the maximum id (handy when you already know it, or to include isolated nodes with no edges):
find_connected_components(edges, n_nodes =12)$n_components # nodes 10,11,12 are singletons
[1] 6
4.2find_connected_components_safe()
When node ids are sparse or large, a dense parent array up to the maximum id would waste memory. This version remaps ids to 1..(unique nodes) with fastmatch, runs the algorithm, then maps the answer back. The returned components vector is named by the original ids.
group_edges() is the convenience alias returning one component id per edge row. edge_components() does the same but takes a data.table and column names, so it drops straight into a data.table expression.
Degree summary and density in a single pass — no adjacency matrix.
stats <-graph_statistics(edges)stats$n_nodes
[1] 9
stats$n_edges
[1] 6
stats$density
[1] 0.1666667
stats$degree_stats
$min
[1] 1
$max
[1] 2
$mean
[1] 1.333333
7 Entity resolution / grouping
These solve “group rows that share a value across any of these columns” as a connected-components problem.
7.1group_id()
The general engine. Give it a data.frame/data.table and the columns to match on (regex by default), plus values that should never glue rows together (incomparables).
An alternative grouping engine using the bipartite edge reduction (melt → edges → components). It requires an id column and writes the group id back by reference.
7.3.1 Basic grouping
Two rows land in the same group when they share any value across the chosen columns. Here rows 1 and 3 share phone "123", and rows 1 and 4 share phone "111", so all three collapse into one group.
cols is a regular expression matched against the column names, so one pattern can pull in several value columns at once. Here "phone|email" grabs all three.
incomparables lists values that are ignored when forming groups — empty strings and NA by default, so blank cells never tie records together. Add your own sentinels (e.g. "Unknown") as needed.
dt_inc <-data.table(id =1:4,phone =c("Unknown", "Unknown", "555-1234", "555-1234"),alt =c("", "", "", "555-9999"))# Without excluding "Unknown", rows 1 and 2 would be linked by it.set_group_id(dt_inc, cols ="phone|alt",var_output_name ="gid",incomparables =c(NA, "", "Unknown"))
id
phone
alt
gid
1
Unknown
NA
2
Unknown
NA
3
555-1234
1
4
555-1234
555-9999
1
dt_inc
id
phone
alt
gid
1
Unknown
NA
2
Unknown
NA
3
555-1234
1
4
555-1234
555-9999
1
7.3.5 Treating column sets in isolation
By default every selected column shares one value namespace, so a phone number appearing in an email column would link the two. Pass isolate — a list of column sets — to keep each set’s values separate: identical text only links rows within the same set. The columns to group on come from the union of the sets, and cols is ignored.
Note row 5: its phone number "123-456-7890" sits in the email column. Because email is isolated from the phone columns, that value does not pull row 5 into the phone group.
Pass return_edges = TRUE to get back the record-to-record edge list — every pair of ids that share a value, with the shared value itself. The group-id column is still written to dt by reference, so you get both the grouping and the underlying connections in one call.
dt_edges <-copy(dt_iso)[, gid :=NULL]edges <-set_group_id(dt_edges, isolate =list(c("phone1", "phone2"), "email"),return_edges =TRUE)edges # from / to / value
from
to
value
1
3
123-456-7890
1
4
123-456-7890
3
4
123-456-7890
1
4
john@email.com
dt_edges # gid still added by reference
id
phone1
phone2
email
gid
1
123-456-7890
john@email.com
1
2
987-654-3210
987-654-3210
jane@email.com
2
3
123-456-7890
555-1234
bob@email.com
1
4
123-456-7890
john@email.com
1
5
555-0123
123-456-7890
3
7.3.7 Plotting the linkage with visNetwork
The edge list drops straight into visNetwork: use the id/gid columns for the nodes (colouring by group) and the returned edges for the connections. The shared value becomes the edge tooltip.
library(visNetwork)nodes <-data.frame(id = dt_edges$id,label =paste0("rec ", dt_edges$id),group = dt_edges$gid)vis_edges <-data.frame(from = edges$from,to = edges$to,title = edges$value # hover shows the value that links the pair)visNetwork(nodes, vis_edges, width ="100%", height ="320px") |>visNodes(shape ="dot", size =22) |>visOptions(highlightNearest =TRUE) |>visLegend()