R from the ground up

Text you can reshape

Gluing text together with paste and sprintf, measuring and slicing it, and taking it apart with strsplit, grepl, sub and gsub.

Part 4 of 12

Beginner16 min readBack to Courses

A string is a vector of characters

Text lives in double quotes, and R treats a string as a value like any other. nchar counts its characters, toupper and tolower change its case, and substr(text, 1, 9) takes the first nine characters, counting from one exactly as indexing does.

One difference from many languages: R has no separate character type, because a string is already a vector of characters, and a vector of strings is a perfectly ordinary vector. That is why length("abc") is 1: the vector has one element, and the characters live inside it, so nchar is the function that counts them.

Gluing pieces into a line

paste(a, b) joins its arguments with a space between them; paste0(a, b) joins them with nothing. When you give either one a vector, it joins the vector element by element; add collapse = " " and it joins the whole vector into a single string instead. A shorter argument is recycled across a longer one, so paste0("id-", 1:3) builds three labels from one prefix — the vectorised habit from chapter two, applied to text.

sprintf fills a template, which is the tool to reach for when a line needs to line up: %.1f is a number with one decimal place, %d is a whole number, %s is a string, and %-6s is a string padded out to six characters on the right.

Taking text apart

strsplit(text, ",") cuts a string at every comma. It gives back a list with one element, so you write [[1]] to reach the vector of pieces inside it — a shape that makes sense once you remember a function may be cutting many strings at once.

grepl(pattern, text) asks whether the pattern appears and answers TRUE or FALSE. sub replaces the first match, gsub replaces every match, and the g in the name stands for global. strsplit is vectorised too: given three strings it answers a list of three vectors, which is why sapply is the usual next step.

Measure, join, cut, replace

title <- "quarterly sales report"
nchar(title)
toupper(title)
substr(title, 1, 9)

paste("Ada", "Lovelace")
paste0("id-", 1:3)

sprintf("%s scored %.1f", "Ada", 93.456)

parts <- strsplit("red,green,blue", ",")[[1]]
parts[3]
grepl("green", "red,green,blue")
sub(",", " | ", "a,b,c")
gsub(",", " | ", "a,b,c")

One string, cut and rebuilt

Run it and match each line to the call that printed it. Then change the title to something shorter, and add a line that prints the last word by counting characters with nchar().

Output
Click "Run" to execute your code

Patterns passed to grepl, sub and gsub are regular expressions, so a dot means any character. When you want a literal dot or a bracket, escape it with two backslashes, as in \\. — worth remembering the first time a pattern matches more than you expected.

Widths, rounding and printing

sprintf is a formatter, not a calculator. sprintf("%.1f", 2.25) rounds for display and returns text; the number itself is unchanged. That distinction matters at the end of a report: round once, when you print, and keep the full value for the next calculation, or small rounding errors accumulate in a total.

The pattern has a shape for most needs. %s is a string, %d an integer, %.2f a number with two decimals, %5s a field five wide, and %-5s the same field padded on the right. A width is a minimum: a longer value is printed in full rather than cut off.

Printing and storing are separate jobs, which is why cat is not the tool for building a value. cat returns NULL after writing to the output; paste and sprintf return strings you can store, compare or pass to another call. If a line needs to be used twice, build it with sprintf and print the result, rather than printing twice.

Patterns and their rules

The pattern arguments to grepl, sub and gsub are regular expressions, so a handful of characters mean something other than themselves: . is any character, ^ anchors the start, $ anchors the end, and [0-9] is any digit. A literal dot needs two backslashes, \\., because the string parser uses the first one.

When you only need to find a fixed piece of text, grepl(pattern, text, fixed = TRUE) turns the pattern language off and is faster and harder to get wrong. ignore.case = TRUE is the other argument worth knowing: it makes the match case-insensitive without changing the text.

Three common slips with text. strsplit returns a list, so forgetting [[1]] gives you a list where you expected a vector, and paste then prints it in a shape you did not intend. A bare . in a pattern matches every character, so grepl(".", x) is TRUE for any non-empty string. And sub stops after the first replacement; reaching for it when you meant every comma is the classic half-replaced line.

Try these next

  • Format three prices in a column eight characters wide with two decimals, then add a longer item name and check the alignment.
  • Split a sentence on spaces and print the number of words and the longest one.
  • Count how many values in a vector contain "an" with sum(grepl("an", x, fixed = TRUE)).

An address book, one string per person

Run it and read the formatted table. Then add a fourth person, give them a role containing "math", and predict which two counts change before you Run again.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What is the difference between paste and paste0?