library(readr)
library(dplyr)
library(tibble)
library(stringr)
library(stringi)
library(purrr)
sentences <- read_csv(
"data/workforce/workforce_sentences.csv",
na = character(),
col_types = cols(
sentence_id = col_character(),
document_id = col_character(),
source_line = col_character(),
text = col_character(),
reference_label = col_character(),
uncertainty = col_character(),
annotator_id = col_character(),
rationale = col_character(),
codebook_version = col_character(),
codebook_hash = col_character(),
derived = col_character(),
transformation = col_character()
)
)
feedback <- read_csv(
"data/customer_feedback.csv",
na = character(),
col_types = cols(
feedback_id = col_character(),
submitted_at = col_character(),
channel = col_character(),
rating = col_character(),
comment = col_character()
)
)Making text comparable
Normalize carefully and keep the display text intact
Related phrases across the 28 text units can differ in formatting. The flyer heading RIVERTON SKILLS OPEN HOUSE uses capitals, while job details use ordinary case. Pasted text may also contain extra spacing and accented characters. The team examines each normalization stepβs effect before applying it.
Before counting matches, the team has to decide which small differences to ignore. Each edit can make comparison easier and also erase information.
Normalization applies edits before text comparison. Common operations change case, trim spaces, remove punctuation, or convert Unicode characters to a consistent form.
The author created the fictional Riverton Workforce Lab, its job board, and its training flyer for teaching. The text files are local and fixed.
By the end of this lesson, you will be able to:
- explain the purpose of normalization before comparison.
- examine the effects of lowercasing and space repair.
- examine punctuation removal with stringrβs ICU regex engine.
- compare two Unicode forms of the same visible word.
Load text with an accent check
The readr package opens CSV files, and dplyr and tibble handle tables. The stringr package handles text operations, while stringi normalizes Unicode. The purrr package applies checks to each item. Inputs contain the Riverton text and feedback with the word cafΓ©.
The accent check matters because normalization can change text that looks stable on screen. The visible word may stay the same while the stored code points change.
Lowercase text and repair spaces
Case folding prepares text for case-insensitive comparison. This example uses lowercasing, which differs from full Unicode case folding. The table contains a Riverton string and a copy of s002 with tripled spaces to simulate pasted text. It also compares US with us.
padded_requirement <- str_replace_all(
sentences$text[sentences$sentence_id == "s002"],
" ",
" "
)
padded_requirement <- paste0(" ", padded_requirement, " ")
case_space_examples <- tibble(
example = c(
"Flyer heading",
"Padded requirement sentence",
"Warning pair, capital",
"Warning pair, lower"
),
raw_text = c(
sentences$text[sentences$sentence_id == "s023"],
padded_requirement,
"US",
"us"
),
lowercased = str_to_lower(raw_text),
squished = str_squish(raw_text)
)| Example | Raw text | Lowercased | Spacing normalized |
|---|---|---|---|
| Flyer heading | RIVERTON SKILLS OPEN HOUSE | riverton skills open house | RIVERTON SKILLS OPEN HOUSE |
| Padded requirement sentence | No prior data experience is required. | no prior data experience is required. | No prior data experience is required. |
| Warning pair, capital | US | us | US |
| Warning pair, lower | us | us | us |
Lowercasing makes RIVERTON SKILLS OPEN HOUSE easier to compare with ordinary case. It also makes US and us the same string. str_squish() removes leading and trailing spaces and replaces repeated spaces with one space.
Remove punctuation only when it is safe
Punctuation removal can make matching easier, but it can damage units such as 12-week. The next table uses Riverton strings that contain hyphens, apostrophes, and periods.
punctuation_examples <- tibble(
sentence_id = c("s001", "s015", "s017"),
raw_text = sentences$text[match(sentence_id, sentences$sentence_id)],
without_punctuation = str_remove_all(raw_text, "[[:punct:]]")
)
unicode_punctuation_check <- str_remove_all("βpaidβtrainingβ", "[[:punct:]]")| Sentence ID | Raw text | After punctuation removal |
|---|---|---|
| s001 | Paid 12-week training is provided. | Paid 12week training is provided |
| s015 | On-the-job training is provided. | Onthejob training is provided |
| s017 | A valid driverβs license is required. | A valid drivers license is required |
Punctuation removal deletes the hyphen in 12-week and the apostrophe in driver's. The function str_remove_all() uses stringrβs ICU engine. Its [[:punct:]] class covers Unicode punctuation, such as curly quotes and em dashes from word processors.
Normalize Unicode forms
Unicode stores text as code points. In cafΓ©, the letter Γ© can occupy one code point or combine e with a separate accent. NFC keeps the composed form. NFD separates the accent into its own code point.
cafe_word <- str_extract(feedback$comment[2], "cafΓ©")
cafe_nfc <- stri_trans_nfc(cafe_word)
cafe_nfd <- stri_trans_nfd(cafe_word)
unicode_forms <- tibble(
form = c("NFC", "NFD"),
text = c(cafe_nfc, cafe_nfd),
code_point_count = str_length(text),
bytes_utf8 = nchar(text, type = "bytes"),
code_points = map_chr(text, \(value) paste(utf8ToInt(value), collapse = ", "))
)| Form | Text | Code points | Bytes as UTF-8 | Code point values |
|---|---|---|---|---|
| NFC | cafΓ© | 4 | 5 | 99, 97, 102, 233 |
| NFD | cafeΜ | 5 | 6 | 99, 97, 102, 101, 769 |
The forms look the same, but identical() detects different stored strings. Both forms show four letters. NFC uses 4 code points and 5 UTF-8 bytes. NFD uses 5 code points and 6 bytes. Its accent occupies a separate code point after e.
Keep two columns
Store at least two versions: raw text for display and normalized text for comparison. Normalize case, spaces, or Unicode form as the task requires.
display_and_match <- sentences |>
filter(sentence_id %in% c("s001", "s023")) |>
transmute(
sentence_id,
display_text = text,
comparison_text = text |>
stri_trans_nfc() |>
str_squish() |>
str_to_lower()
)| Sentence ID | Display text | Comparison text |
|---|---|---|
| s001 | Paid 12-week training is provided. | paid 12-week training is provided. |
| s023 | RIVERTON SKILLS OPEN HOUSE | riverton skills open house |
Normalize text for comparison. Keep raw text for display. Some normalization operations are irreversible. After lowercasing, us does not reveal whether the source was US or us. Choose NFC for the comparison column. This recommended web-text form gives equivalent character sequences a consistent representation across systems.
What to remember
- Normalization makes text easier to compare.
- Lowercasing can merge strings that mean different things.
- ICU punctuation classes contain more than keyboard punctuation.
- Unicode forms can look the same while storing different code points.
- Keep the display text and comparison text separate.
- Raw text preserves information that normalization removes.
A normalized column assists matching, not display. Show readers the original Riverton text that the Lab collected.