
ERDbuilder creates Entity-Relationship Diagrams (ERDs)
directly from R data frames.
The package provides tools to:
You can install the development version of ERDbuilder
from GitHub:
remotes::install_github("gbasulto/ERDbuilder")Then load the package:
library(ERDbuilder)A typical ERDbuilder workflow consists of six steps:
suggest_relationships() to identify likely
relationships.create_erd().validate_erd().Define dataframes for example:
# Create dataframe "employees"
employees <- data.frame(
emp_id = c(1, 2, 3),
name = c("Alice", "Bob", "Charlie")
)
# Create dataframe "departments"
departments <- data.frame(
dept_id = c(1, 2),
dept_name = c("HR", "Engineering")
)
# Create dataframe "assignments"
assignments <- data.frame(
emp_id = c(1, 3),
dept_id = c(1, 2)
)df_list <- list(
employees = employees,
departments = departments,
assignments = assignments
)
relationships <- suggest_relationships(df_list)
#> Suggested 2 relationship(s).
erd_object <- create_erd(
df_list = df_list,
relationships = relationships
)
validate_erd(erd_object)
#> ERD validation: valid
#> Errors: 0 | Warnings: 0render_erd(erd_object)Relationship suggestions are based on the available data and should always be reviewed before they are treated as database constraints.
| Function | Purpose |
|---|---|
suggest_relationships() |
Suggest relationships between data frames |
format_relationships() |
Convert a relationship specification into paste-ready R code |
create_erd() |
Create an ERD object |
validate_erd() |
Validate the ERD structure and optionally inspect observed cardinalities |
is_valid_erd() |
Return whether an ERD passes validation |
render_erd() |
Render an ERD |
perform_join() |
Join tables using relationships stored in an ERD |
suggest_relationships() examines pairs of data frames
and evaluates possible join columns using information such as:
Start by creating a named list of data frames:
employees <- data.frame(
emp_id = c(1, 2, 3),
name = c("Alice", "Bob", "Charlie")
)
departments <- data.frame(
dept_id = c(1, 2),
dept_name = c("Human Resources", "Engineering")
)
assignments <- data.frame(
assignment_id = 1:3,
emp_id = c(1, 1, 3),
dept_id = c(1, 2, 2)
)
df_list <- list(
employees = employees,
departments = departments,
assignments = assignments
)Generate the suggested relationships:
relationships <- suggest_relationships(
df_list,
quiet = TRUE
)
relationships
#> $employees
#> $employees$assignments
#> $employees$assignments$emp_id
#> [1] "emp_id"
#>
#> $employees$assignments$relationship
#> [1] "||" "0<"
#>
#>
#>
#> $departments
#> $departments$assignments
#> $departments$assignments$dept_id
#> [1] "dept_id"
#>
#> $departments$assignments$relationship
#> [1] "||" "|<"
#>
#>
#>
#> attr(,"suggestions")
#> from_table to_table from_columns to_columns relationship_left
#> 1 employees assignments emp_id emp_id ||
#> 2 departments assignments dept_id dept_id ||
#> relationship_right score coverage_from_to coverage_to_from coverage_mean
#> 1 0< 0.925 0.6666667 1 0.8333333
#> 2 |< 1.000 1.0000000 1 1.0000000
#> from_unique to_unique n_columns
#> 1 TRUE FALSE 1
#> 2 TRUE FALSE 1The returned object uses the same relationship structure expected by
create_erd().
Diagnostic information about the suggestions is stored in the
"suggestions" attribute:
attr(relationships, "suggestions")
#> from_table to_table from_columns to_columns relationship_left
#> 1 employees assignments emp_id emp_id ||
#> 2 departments assignments dept_id dept_id ||
#> relationship_right score coverage_from_to coverage_to_from coverage_mean
#> 1 0< 0.925 0.6666667 1 0.8333333
#> 2 |< 1.000 1.0000000 1 1.0000000
#> from_unique to_unique n_columns
#> 1 TRUE FALSE 1
#> 2 TRUE FALSE 1These diagnostics can be used to review the proposed join columns, relationship direction, key uniqueness, value coverage, and confidence score.
Use format_relationships() to convert the suggested
object into R code:
cat(format_relationships(relationships))
#> relationships <- list(employees = list(assignments = list(emp_id = "emp_id", relationship = c("||",
#> "0<"))), departments = list(assignments = list(dept_id = "dept_id",
#> relationship = c("||", "|<"))))To copy that code directly to the system clipboard during an interactive R session, use:
relationships <- suggest_relationships(
df_list,
copy = TRUE
)Clipboard support requires the optional clipr
package:
install.packages("clipr")After reviewing the suggested relationships, create the ERD object:
erd_object <- create_erd(
df_list = df_list,
relationships = relationships
)
validation <- validate_erd(erd_object)
validation
#> ERD validation: valid
#> Errors: 0 | Warnings: 0The validation result contains an issue table:
validation$issues
#> [1] severity code from_table to_table n_affected message
#> <0 rows> (or 0-length row.names)It also contains a logical indicator of whether the ERD is valid:
validation$valid
#> [1] TRUEFor a direct logical result, use:
is_valid_erd(erd_object)
#> [1] TRUEBy default, validate_erd() distinguishes between
structural errors and warnings based on the observed data.
Structural errors include problems such as:
When check_data = TRUE, the function also compares the
declared cardinalities with the values observed in the data:
validate_erd(
erd_object,
check_data = TRUE
)
#> ERD validation: valid
#> Errors: 0 | Warnings: 0Observed cardinality violations are warnings by default because a data sample may not fully represent the intended database constraints.
Use strict = TRUE to treat warnings as validation
failures:
validate_erd(
erd_object,
check_data = TRUE,
strict = TRUE
)
#> ERD validation: valid
#> Errors: 0 | Warnings: 0During progressive ERD construction, incomplete relationships can be permitted explicitly:
validate_erd(
erd_object,
check_data = FALSE,
allow_incomplete = TRUE
)
#> ERD validation: valid
#> Errors: 0 | Warnings: 0The following example manually defines the relationships among employees, departments, and assignments.
library(ERDbuilder)
employees <- data.frame(
emp_id = c(1, 2, 3),
name = c("Alice", "Bob", "Charlie")
)
departments <- data.frame(
dept_id = c(1, 2),
dept_name = c("Human Resources", "Engineering")
)
assignments <- data.frame(
assignment_id = 1:3,
emp_id = c(1, 1, 3),
dept_id = c(1, 2, 2)
)
relationships <- list(
assignments = list(
employees = list(
emp_id = "emp_id",
relationship = c(">|", "||")
),
departments = list(
dept_id = "dept_id",
relationship = c(">0", "||")
)
)
)
erd_object <- create_erd(
df_list = list(
employees = employees,
departments = departments,
assignments = assignments
),
relationships = relationships
)
validation <- validate_erd(
erd_object,
check_data = TRUE
)
validation
#> ERD validation: valid with warnings
#> Errors: 0 | Warnings: 1
#> severity code from_table to_table n_affected
#> warning observed_cardinality assignments employees 1
#> message
#> The `assignments` side is declared `>|` (one or more), but 1 row(s) in `employees` violate that constraint.Render the ERD:
erd_plot <- render_erd(
erd_object,
label_distance = 0,
label_angle = -25
)
# erd_plotTo export the diagram, packages such as DiagrammeRsvg,
rsvg, and tiff can be used:
library(DiagrammeRsvg)
library(rsvg)
library(tiff)
dpi <- 600
width_cm <- 38
height_cm <- 38
erd_plot |>
DiagrammeRsvg::export_svg() |>
charToRaw() |>
rsvg::rsvg(
width = width_cm * (dpi / 2.54),
height = height_cm * (dpi / 2.54)
) |>
tiff::writeTIFF("erd_plot.tiff")This example creates a larger ERD and uses it to join four related tables.
library(ERDbuilder)
library(readr)
data_url <- paste0(
"https://raw.githubusercontent.com/",
"jwood-iastate/DataFiles/main/"
)
occupant_tbl <- read_csv(
paste0(data_url, "OCC.csv"),
show_col_types = FALSE
)
crash_tbl <- read_csv(
paste0(data_url, "CRASH.csv"),
show_col_types = FALSE
)
distract_tbl <- read_csv(
paste0(data_url, "DISTRACT.csv"),
show_col_types = FALSE
)
vehicle_tbl <- read_csv(
paste0(data_url, "GV.csv"),
show_col_types = FALSE
)
df_list <- list(
Crash = crash_tbl,
Vehicle = vehicle_tbl,
Occupant = occupant_tbl,
Distract = distract_tbl
)The relationships can be suggested initially:
suggested_relationships <- suggest_relationships(df_list)
attr(suggested_relationships, "suggestions")For this example, the reviewed relationship specification is:
relationships <- list(
Crash = list(
Vehicle = list(
CASENUMBER = "CASENUMBER",
relationship = c("||", "|<")
),
Occupant = list(
CASENUMBER = "CASENUMBER",
relationship = c("||", "|<")
),
Distract = list(
CASENUMBER = "CASENUMBER",
relationship = c("||", "0<")
)
),
Vehicle = list(
Occupant = list(
CASENUMBER = "CASENUMBER",
VEHNO = "VEHNO",
relationship = c("|0", "0<")
),
Distract = list(
CASENUMBER = "CASENUMBER",
VEHNO = "VEHNO",
relationship = c("||", "0<")
)
)
)Create and validate the ERD:
erd_object <- create_erd(
df_list = df_list,
relationships = relationships
)
validate_erd(
erd_object,
check_data = TRUE
)Join the tables:
# A many-to-many relationship may arise when Distract is joined after Crash,
# Vehicle, and Occupant have already been combined.
joined_data <- perform_join(
erd_object,
c("Crash", "Vehicle", "Occupant", "Distract")
)Render the ERD:
erd_plot <- render_erd(
erd_object,
label_distance = 0,
label_angle = 15,
n = 20
)
erd_plotMore detailed examples are available in the package vignettes:
browseVignettes("ERDbuilder")The package website is available at: