Overview
This assignment loads the same book dataset from two hand-generated files — an HTML table (books.html) and a JSON array (books.json) — into separate R data frames and then verifies that the two frames are identical after normalizing data types.
The dataset covers three graphic novels in the Teen Titans series by Kami Garcia and Gabriel Picolo (DC Comics), with ISBN, publication year, and publisher as additional attributes. I was particularly interested in the series after attending a panel session with the authors last weekend at Comic Con, and now inspired to begin reading the books.
Approach
Step 1 — Gather book data.
Step 2 — Create books.html by hand.
Step 3 — Create books.json by hand.
Step 4 — Load HTML into R.
Step 5 — Load JSON into R.
Step 6 — Normalize and compare.
1 · Load the HTML data
httr::GET() fetches the raw file from GitHub and httr::content() extracts the text, which is then handed to rvest::read_html().
library(httr)
library(rvest)
library(stringr)
library(rjson)
github_base <- "https://raw.githubusercontent.com/shanicesmith98/data-607-assignments/refs/heads/main/week-7-assignment/"
html_response <- tryCatch(
{
response <- GET(paste0(github_base, "/books.html"))
stop_for_status(response)
response
},
error = function(e) {
stop("Failed to fetch books.html — check the URL and that the repo is public.\n",
"Details: ", conditionMessage(e))
}
)
html_text <- content(html_response, as = "text", encoding = "UTF-8")
df_html <- read_html(html_text) |>
html_table() |>
_[[1]] # html_table() returns a list; extract the first table
# Use stringr::str_trim() to remove any leading/trailing whitespace the
# HTML parser may have captured around cell values
df_html <- as.data.frame(
lapply(df_html, str_trim),
stringsAsFactors = FALSE
)
str(df_html)
'data.frame': 3 obs. of 5 variables:
$ title : chr "Teen Titans: Robin" "Teen Titans: Starfire" "Teen Titans: Together"
$ authors : chr "Kami Garcia, Gabriel Picolo" "Kami Garcia, Gabriel Picolo" "Kami Garcia, Gabriel Picolo"
$ isbn : chr "9781779512246" "9781779517999" "9781799503064"
$ pub_year : chr "2023" "2024" "2026"
$ publisher: chr "DC Comics" "DC Comics" "DC Comics"
title authors isbn pub_year
1 Teen Titans: Robin Kami Garcia, Gabriel Picolo 9781779512246 2023
2 Teen Titans: Starfire Kami Garcia, Gabriel Picolo 9781779517999 2024
3 Teen Titans: Together Kami Garcia, Gabriel Picolo 9781799503064 2026
publisher
1 DC Comics
2 DC Comics
3 DC Comics
2 · Load the JSON data
The same httr::GET() / content() pattern fetches the JSON file from GitHub. rjson::fromJSON() then parses that text into a nested R list — one element per book — which is coerced into a data frame with as.data.frame().
json_response <- tryCatch(
{
res <- GET(paste0(github_base, "/books.json"))
stop_for_status(res)
res
},
error = function(e) {
stop("Failed to fetch books.json — check the URL and that the repo is public.\n",
"Details: ", conditionMessage(e))
}
)
json_text <- content(json_response, as = "text", encoding = "UTF-8")
# rjson::fromJSON() returns a list of lists, not a data frame directly.
# do.call(rbind, ...) binds the per-book lists into a matrix, and
# as.data.frame() converts that into a proper data frame.
df_json <- as.data.frame(
do.call(rbind, rjson::fromJSON(json_text)),
stringsAsFactors = FALSE
)
# do.call(rbind) produces list-columns; unlist each column to atomic vectors,
# then apply str_trim() to normalize whitespace and types in one pass
df_json <- as.data.frame(
lapply(df_json, function(x) str_trim(as.character(unlist(x)))),
stringsAsFactors = FALSE
)
str(df_json)
'data.frame': 3 obs. of 5 variables:
$ title : chr "Teen Titans: Robin" "Teen Titans: Starfire" "Teen Titans: Together"
$ authors : chr "Kami Garcia, Gabriel Picolo" "Kami Garcia, Gabriel Picolo" "Kami Garcia, Gabriel Picolo"
$ isbn : chr "9781779512246" "9781779517999" "9781799503064"
$ pub_year : chr "2023" "2024" "2026"
$ publisher: chr "DC Comics" "DC Comics" "DC Comics"
title authors isbn pub_year
1 Teen Titans: Robin Kami Garcia, Gabriel Picolo 9781779512246 2023
2 Teen Titans: Starfire Kami Garcia, Gabriel Picolo 9781779517999 2024
3 Teen Titans: Together Kami Garcia, Gabriel Picolo 9781799503064 2026
publisher
1 DC Comics
2 DC Comics
3 DC Comics
3 · Align column order, then compare
identical() is sensitive to column order. Reordering df_json to match df_html ensures a structural match before the value comparison.
# Guarantee column order matches
df_json <- df_json[, names(df_html)]
are_identical <- identical(df_html, df_json)
cat("Are the two data frames identical?", are_identical, "\n")
Are the two data frames identical? TRUE
# Diagnostic: surface any cell-level differences if the check fails
if (!are_identical) {
mismatches <- which(df_html != df_json, arr.ind = TRUE)
cat("\nMismatches (row, col):\n")
print(mismatches)
cat("\nHTML values:\n"); print(df_html[mismatches])
cat("\nJSON values:\n"); print(df_json[mismatches])
} else {
cat("Both data frames contain exactly the same data.\n")
}
Both data frames contain exactly the same data.
4 · Side-by-side view
cat("--- HTML data frame ---\n"); print(df_html)
title authors isbn pub_year
1 Teen Titans: Robin Kami Garcia, Gabriel Picolo 9781779512246 2023
2 Teen Titans: Starfire Kami Garcia, Gabriel Picolo 9781779517999 2024
3 Teen Titans: Together Kami Garcia, Gabriel Picolo 9781799503064 2026
publisher
1 DC Comics
2 DC Comics
3 DC Comics
cat("\n--- JSON data frame ---\n"); print(df_json)
title authors isbn pub_year
1 Teen Titans: Robin Kami Garcia, Gabriel Picolo 9781779512246 2023
2 Teen Titans: Starfire Kami Garcia, Gabriel Picolo 9781779517999 2024
3 Teen Titans: Together Kami Garcia, Gabriel Picolo 9781799503064 2026
publisher
1 DC Comics
2 DC Comics
3 DC Comics
Credit
Throughout the assignment, Claude (Sonnet 4.6) was used to help plan, explain concepts, flag data issues, and debug errors.
Anthropic. (2026). Claude (Sonnet 4.6) [Large language model].