Lesson 3 of 3

Split, Unite & Fuzzy Joins

In Lesson 2 you changed a table's shape. But real data arrives dirty in a different way: a single column crams two facts together, or two tables that should line up have keys that only nearly agree.

Maya, who runs a small bakery, now pulls sales from an online-orders export and prices from a supplier sheet someone typed by hand. Below are her items on the left and the supplier's price sheet on the right. To you they read as the same four products. Click inner: an exact join finds only two matches. The other two are spelled just differently enough to be invisible to the join.

By the end of this lesson you will be able to:

  • Split one crammed column into several with separate() (set into and sep)
  • Combine several columns into one with unite(), the exact inverse of separate()
  • Spot keys that only nearly match, surface them with anti_join(), and repair them by cleaning and by string distance (a fuzzy join)

Prerequisites: you can run R, you know a tibble and the dplyr verbs, and you met the inner, left and anti joins and the join key in Lesson 1.

One column, two facts

separate() splits a crammed column

Maya's online store exports one row per order, but it crams the product name and its size into a single product field, like "Sourdough/Large". That breaks a rule of tidy data: one variable per column. To analyse sales by item, and separately by size, those two facts need their own columns.

Each lesson runs in a fresh R session, so we build her export right here as a tibble, then never touch the disk again:

RInteractive R
library(dplyr) # the verbs and the pipe library(tidyr) # separate(), unite() library(tibble) # tribble() library(stringr) # str_to_lower(), str_squish() orders <- tribble( ~order, ~product, ~ordered_on, "A1", "Sourdough/Large", "2024-03-01", "A2", "Bagel/Small", "2024-03-02", "A3", "Croissant/Large", "2024-03-02" )

  

tribble() from tibble just lets us type a small table by hand: the ~order, ~product, ~ordered_on entries are the column names, and the values follow row by row underneath.

separate(), from tidyr, takes one column and cuts each value into pieces at a separator. You tell it three things: which column to cut (product), what to name the pieces (into), and the character to cut on (sep):

RInteractive R
orders %>% separate(product, into = c("item", "size"), sep = "/") #> # A tibble: 3 x 4 #> order item size ordered_on #> <chr> <chr> <chr> <chr> #> 1 A1 Sourdough Large 2024-03-01 #> 2 A2 Bagel Small 2024-03-02 #> 3 A3 Croissant Large 2024-03-02

  

The one product column became two, item and size, split at the /. Read it as a picture: the crammed column on the left opens out into the two clean columns on the right.

order product ordered_on order item size ordered_on
A1 Sourdough/Large 2024-03-01 -> A1 Sourdough Large 2024-03-01
A2 Bagel/Small 2024-03-02 -> A2 Bagel Small 2024-03-02
A3 Croissant/Large 2024-03-02 -> A3 Croissant Large 2024-03-02
Note
separate() still works, but recent tidyr marks it superseded in favour of the clearer separate_wider_delim(product, delim = "/", names = c("item", "size")). Same idea, newer name. We use separate() here because it is the verb you will see most often in the wild.