This warm up exercise looks at the same data stored in two different file formats, HTML and JSON, and checks whether R reads them into identical data frames. I chose three books on one subject, baseball analytics, and recorded the title, authors, publication year, publisher, and ISBN for each. One of them, Analyzing Baseball Data with R, is also the book closest to the kind of work I do, and two of the three have multiple authors.
I created both files by hand. They are publicly available on GitHub:
Tom M. Tango, Mitchel G. Lichtman, Andrew E. Dolphin
2007
Potomac Books
978-1-59797-129-4
Analyzing Baseball Data with R, Second Edition
Max Marchi, Jim Albert, Benjamin S. Baumer
2018
CRC Press
978-0-8153-5351-5
HTML has no built in way to store a list inside a table cell, so in books.html the authors for each book sit in a single cell separated by semicolons. In books.json the authors are stored as a real array, which is the more natural structure for a list of names.
Step 1: Load the HTML Data
read_html() downloads and parses the page, html_element("table") pulls out the table, and html_table() turns it into a data frame.
Tom M. Tango; Mitchel G. Lichtman; Andrew E. Dolphin
2007
Potomac Books
978-1-59797-129-4
Analyzing Baseball Data with R, Second Edition
Max Marchi; Jim Albert; Benjamin S. Baumer
2018
CRC Press
978-0-8153-5351-5
str(books_html)
tibble [3 × 5] (S3: tbl_df/tbl/data.frame)
$ title : chr [1:3] "Moneyball: The Art of Winning an Unfair Game" "The Book: Playing the Percentages in Baseball" "Analyzing Baseball Data with R, Second Edition"
$ authors : chr [1:3] "Michael Lewis" "Tom M. Tango; Mitchel G. Lichtman; Andrew E. Dolphin" "Max Marchi; Jim Albert; Benjamin S. Baumer"
$ year : int [1:3] 2003 2007 2018
$ publisher: chr [1:3] "W. W. Norton & Company" "Potomac Books" "CRC Press"
$ isbn : chr [1:3] "978-0-393-05765-2" "978-1-59797-129-4" "978-0-8153-5351-5"
Step 2: Load the JSON Data
fromJSON() reads the file straight from the URL. Because each book is an object in a JSON array, it comes back as a data frame with one row per book.
title
1 Moneyball: The Art of Winning an Unfair Game
2 The Book: Playing the Percentages in Baseball
3 Analyzing Baseball Data with R, Second Edition
authors year
1 Michael Lewis 2003
2 Tom M. Tango, Mitchel G. Lichtman, Andrew E. Dolphin 2007
3 Max Marchi, Jim Albert, Benjamin S. Baumer 2018
publisher isbn
1 W. W. Norton & Company 978-0-393-05765-2
2 Potomac Books 978-1-59797-129-4
3 CRC Press 978-0-8153-5351-5
str(books_json)
'data.frame': 3 obs. of 5 variables:
$ title : chr "Moneyball: The Art of Winning an Unfair Game" "The Book: Playing the Percentages in Baseball" "Analyzing Baseball Data with R, Second Edition"
$ authors :List of 3
..$ : chr "Michael Lewis"
..$ : chr "Tom M. Tango" "Mitchel G. Lichtman" "Andrew E. Dolphin"
..$ : chr "Max Marchi" "Jim Albert" "Benjamin S. Baumer"
$ year : int 2003 2007 2018
$ publisher: chr "W. W. Norton & Company" "Potomac Books" "CRC Press"
$ isbn : chr "978-0-393-05765-2" "978-1-59797-129-4" "978-0-8153-5351-5"
Notice the authors column. In the JSON data frame it is a list column, where each cell holds a character vector of names, for example:
books_json$authors[[2]]
[1] "Tom M. Tango" "Mitchel G. Lichtman" "Andrew E. Dolphin"
Step 3: Compare the Two Data Frames
First, a direct comparison of the two data frames exactly as they were loaded.
identical(books_html, books_json)
[1] FALSE
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"
They are not identical, even though both files describe the same three books. The comparison points to two differences:
The authors column has a different structure. In the HTML data frame it is a plain text column ("Tom M. Tango; Mitchel G. Lichtman; Andrew E. Dolphin"), while in the JSON data frame it is a list column holding a vector of names for each book.
The data frames are different kinds of objects.html_table() returns a tibble, while fromJSON() returns a base R data frame.
Making the Two Data Frames Comparable
To compare the actual content rather than the formatting, I put both into the same shape. For the JSON data I collapse each authors vector into one text string using the same "; " separator as the HTML file, convert it to a tibble, and make sure the year is an integer in both.
Tom M. Tango; Mitchel G. Lichtman; Andrew E. Dolphin
2007
Potomac Books
978-1-59797-129-4
Analyzing Baseball Data with R, Second Edition
Max Marchi; Jim Albert; Benjamin S. Baumer
2018
CRC Press
978-0-8153-5351-5
identical(books_html_clean, books_json_clean)
[1] TRUE
all.equal(books_html_clean, books_json_clean)
[1] TRUE
Conclusions
Read as they came in, the HTML and JSON data frames are not identical. The content is the same, but the two formats store it differently: HTML can only hold the authors as one block of text in a table cell, while JSON can store them as a proper list, and the two R packages also return different data frame types. After collapsing the JSON authors list into a semicolon separated string and matching the column types, the two data frames are identical.
The main lesson is that the same data can look different in R depending on the format it came from, so comparing data frames usually means cleaning them into a common structure first. This matters for the next weeks, when we load HTML and JSON from the web, because multi valued fields like authors will almost always need this kind of handling.