For this assignment, I chose three books on learning R and statistics: R for Data Science, OpenIntro Statistics, and Hands-On Programming with R. Two of the three have multiple authors, and two authors (Mine Çetinkaya-Rundel and Garrett Grolemund) each appear on more than one book. For each book, I recorded the title, authors, edition, publication year, and publisher.
I wrote both data files by hand using Claude AI as a guide and saved them in my GitHub repository:
books.html with the data in an HTML table
books.json with the same data as a JSON array of book objects
The goal is to load each file into its own data frame and see whether the two come out the same.
David Diez, Mine Çetinkaya-Rundel, Christopher Barr
4
2019
OpenIntro
Hands-On Programming with R
Garrett Grolemund
1
2014
O’Reilly Media
Code
str(books_html)
tibble [3 × 5] (S3: tbl_df/tbl/data.frame)
$ title : chr [1:3] "R for Data Science" "OpenIntro Statistics" "Hands-On Programming with R"
$ authors : chr [1:3] "Hadley Wickham, Mine Çetinkaya-Rundel, Garrett Grolemund" "David Diez, Mine Çetinkaya-Rundel, Christopher Barr" "Garrett Grolemund"
$ edition : int [1:3] 2 4 1
$ year : int [1:3] 2023 2019 2014
$ publisher: chr [1:3] "O'Reilly Media" "OpenIntro" "O'Reilly Media"
read_html() reads the page, html_element("table") finds the table, and html_table() turns it into a data frame, using the header row as the column names. Even though everything in an HTML file is text, html_table() guessed that the edition and year columns are numbers and converted them to integers. Because a table cell can only hold one value, the authors for each book are stored together in a single text string separated by commas.
title
1 R for Data Science
2 OpenIntro Statistics
3 Hands-On Programming with R
authors edition year
1 Hadley Wickham, Mine Çetinkaya-Rundel, Garrett Grolemund 2 2023
2 David Diez, Mine Çetinkaya-Rundel, Christopher Barr 4 2019
3 Garrett Grolemund 1 2014
publisher
1 O'Reilly Media
2 OpenIntro
3 O'Reilly Media
Code
str(books_json)
'data.frame': 3 obs. of 5 variables:
$ title : chr "R for Data Science" "OpenIntro Statistics" "Hands-On Programming with R"
$ authors :List of 3
..$ : chr "Hadley Wickham" "Mine Çetinkaya-Rundel" "Garrett Grolemund"
..$ : chr "David Diez" "Mine Çetinkaya-Rundel" "Christopher Barr"
..$ : chr "Garrett Grolemund"
$ edition : int 2 4 1
$ year : int 2023 2019 2014
$ publisher: chr "O'Reilly Media" "OpenIntro" "O'Reilly Media"
fromJSON() converts the array of book objects into a data frame with one row per book. In the JSON file I wrote the edition and year as numbers without quotes, so they load as integers here too. The main difference is the authors column. In the JSON file each book’s authors are stored as an array, so R loads that column as a list, where each row holds a vector of one to three names.
Comparing the Two Data Frames
Code
identical(books_html, books_json)
[1] FALSE
Code
all.equal(books_html, books_json)
[1] "Attributes: < Component \"class\": Lengths (3, 1) differ (string compare on first 1) >"
[2] "Attributes: < Component \"class\": 1 string mismatch >"
[3] "Component \"authors\": Modes: character, list"
[4] "Component \"authors\": target is character, current is list"
The two data frames are not identical. all.equal() shows two reasons:
Class:html_table() returns a tibble, while fromJSON() returns a base R data frame.
Authors: the HTML version stores authors as one text string per book, while the JSON version stores them as a list.
The titles, editions, years, and publishers all match so values are the same. The differences come from the way each format stores the data.
Making the Two Match
To check that the content really is the same, I converted the JSON version to a tibble and combined each book’s authors into one comma separated string, the same way they appear in the HTML table.
After these two changes, the data frames are identical.
One Row per Author
Joining the authors into one string makes the two versions match but it isn’t the most useful format for analysis. The JSON structure keeps each author as a separate value which makes it easy to reshape the data so each row is one book and one author.
books_by_author %>%count(author, name ="books") %>%arrange(desc(books)) %>%kable()
author
books
Garrett Grolemund
2
Mine Çetinkaya-Rundel
2
Christopher Barr
1
David Diez
1
Hadley Wickham
1
This shows that Çetinkaya-Rundel and Grolemund each wrote two of the three books. Getting the same result from the HTML version would mean splitting the authors text on the commas first.
Conclusions
Both files held the same information but they did not load into identical data frames. The values matched but the structure differed in two ways: the HTML table loaded as a tibble with authors stored as text and the JSON file loaded as a base data frame with authors stored as a list. Once I matched the class and the authors format, the two were identical.
Writing the files by hand also demonstrated a difference between the formats. HTML tables allow a field with several values to be squeezed into one cell. On the other hand, JSON can nest a list inside a record, which keeps multiple authors separate and makes the data easier to reshape later.
AI Citation
Anthropic. (2026). Sonnet 5.5 [Large language model]. https://claude.ai. Accessed October 2026.