library(tidyverse)12 Strings
1 Getting started
- A string is the generic word for character type variables.
- String operations are particularly useful when cleaning names, survey responses, labels, and other text variables.
- The
stringrpackage (a coretidyversepackage) provides a consistent set of functions for working with strings. Most of its functions begin withstr_.
1.1 Creating strings
We can use either single (' ') or double (" ") quotation marks. Both work in the same way.
x <- "R for Data Science"
y <- 'AST230'
x[1] "R for Data Science"
y[1] "AST230"
class(x)[1] "character"
Use quotation marks that make the string easier to write.
"It's time for the lab."[1] "It's time for the lab."
'I said, "Welcome to AST230!"'[1] "I said, \"Welcome to AST230!\""
If both types of quotation marks occur in a string, use a backslash (\) to escape the quotation mark that would otherwise end it.
"It's good to say, \"Welcome!\""[1] "It's good to say, \"Welcome!\""
cat("It's good to say, \"Welcome!\"")It's good to say, "Welcome!"
Note: R’s printed representation of a string may show escape characters. Use cat() when you want to see how the text actually appears.
1.2 Special characters
A backslash introduces some special characters inside strings.
| Code | Meaning |
|---|---|
\n |
New line |
\t |
Tab |
\\ |
A backslash |
\" |
A double quotation mark |
\' |
A single quotation mark |
cat("First line\nSecond line")First line
Second line
cat("Name\tRoll")Name Roll
cat("C:\\Data\\AST230")C:\Data\AST230
1.3 Character vectors and built-in constants
A character vector contains one or more strings.
students <- c("Anika", "Rafi", "Nusrat")
students[1] "Anika" "Rafi" "Nusrat"
length(students)[1] 3
R also provides some ready-made character vectors.
head(letters)[1] "a" "b" "c" "d" "e" "f"
head(LETTERS)[1] "A" "B" "C" "D" "E" "F"
head(month.name)[1] "January" "February" "March" "April" "May" "June"
month.abb[1:4][1] "Jan" "Feb" "Mar" "Apr"
The stringr package provides examples such as words, sentences, and fruit.
head(words)[1] "a" "able" "about" "absolute" "accept" "account"
head(fruit)[1] "apple" "apricot" "avocado" "banana" "bell pepper"
[6] "bilberry"
2 Basic stringr functions
Here is a summary of the main functions used in this lecture.
| Task | Functions |
|---|---|
| Change case | str_to_lower(), str_to_upper(), str_to_title() |
| Measure and extract | str_length(), str_sub(), str_locate() |
| Clean or format | str_trim(), str_squish(), str_pad(), str_dup() |
| Combine or split | str_c(), str_split(), word() |
| Sort | str_sort() |
| Find matches | str_detect(), str_count() |
| Replace matches | str_replace(), str_replace_all() |
We will learn these functions using simple examples before applying them to data frames.
3 Changing and measuring strings
3.1 Change the case
The following functions convert strings to lower case, upper case, or title case.
x <- "r for DATA science"
str_to_lower(x)[1] "r for data science"
str_to_upper(x)[1] "R FOR DATA SCIENCE"
str_to_title(x)[1] "R For Data Science"
These functions also work on vectors.
names <- c("anika rahman", "RAFI AHMED", "Nusrat jahan")
str_to_title(names)[1] "Anika Rahman" "Rafi Ahmed" "Nusrat Jahan"
Note: str_to_title() capitalises the beginning of each word. It does not know whether a word is an acronym or how a particular person’s name should be capitalised.
3.2 Count characters
Do not confuse length() with str_length():
x <- "Data Science"
length(x) # Number of elements in the vector[1] 1
str_length(x) # Number of characters in the string[1] 12
Spaces are characters, but an empty string has zero characters.
str_length("AST230")[1] 6
str_length(" ")[1] 1
str_length("")[1] 0
str_length(c("R", "AST230", "Statistics"))[1] 1 6 10
3.3 Get substrings with str_sub()
str_sub() extracts a part of a string using its starting and ending positions. Positions start from 1.
course <- "AST230"
str_sub(course, 1, 3) # First three characters[1] "AST"
str_sub(course, 4, 6) # Characters 4 to 6[1] "230"
str_sub(course, -3, -1) # Last three characters[1] "230"
Negative positions count backwards from the end. If the requested end is beyond the string, R returns the available characters.
str_sub("AST230", 1, 20)[1] "AST230"
We can also replace characters at selected positions.
x <- "ast230"
str_sub(x, 1, 3) <- "AST"
x[1] "AST230"
3.4 Find location of match with str_locate()
Sometimes we know the word to extract but not its position. str_locate() gives the starting and ending positions of the first match.
x <- "Welcome to the AST230 lab"
position <- str_locate(x, "AST230")
position start end
[1,] 16 21
str_sub(x, position[1, "start"], position[1, "end"])[1] "AST230"
If there is no match, the returned positions are NA.
3.5 Exercise 1: case, length, and extraction
- Create
x <- "Institute of Applied Statistics and Data Science". Find the number of characters inx. - Convert
"uNIVERSITY OF dHAKA"to lower case and to title case. - From
"AST230", extract"AST"and"230"separately. - Extract the last four characters from
"Bangladesh". - Find the starting and ending positions of
"Data"in"R for Data Science".
4 Cleaning and formatting strings
4.1 Remove whitespace
str_trim() removes spaces at the beginning and end of a string.
x <- " Dhaka "
str_trim(x)[1] "Dhaka"
str_trim(x, side = "left")[1] "Dhaka "
str_trim(x, side = "right")[1] " Dhaka"
str_squish() goes one step further: it also reduces repeated internal whitespace to a single space.
names <- c(" Anika Rahman ", " Rafi Ahmed ", "Nusrat Jahan")
str_trim(names)[1] "Anika Rahman" "Rafi Ahmed" "Nusrat Jahan"
str_squish(names)[1] "Anika Rahman" "Rafi Ahmed" "Nusrat Jahan"
A common cleaning task is to combine these with a case-conversion function.
raw_names <- c(" anika RAHMAN", "RAFI ahmed ")
clean_names <- str_to_title(str_squish(raw_names))
clean_names[1] "Anika Rahman" "Rafi Ahmed"
4.2 Pad a string to minimum width
str_pad() adds characters until a string reaches a chosen total width.
str_pad("R", width = 5)[1] " R"
str_pad("R", width = 5, side = "right")[1] "R "
str_pad("R", width = 5, side = "both", pad = "-")[1] "--R--"
This is useful when creating consistently formatted roll or ID numbers.
roll <- c("7", "24", "105")
str_pad(roll, width = 3, pad = "0")[1] "007" "024" "105"
str_pad() does not shorten a string if it already exceeds the requested width.
str_pad("AST230", width = 3)[1] "AST230"
4.3 Duplicate a string
str_dup() joins repeated copies into a single string, whereas rep() repeats elements in a vector.
str_dup("R", 4)[1] "RRRR"
rep("R", 4)[1] "R" "R" "R" "R"
4.4 Exercise 2: cleaning student records
student_names <- c(" aNika rahman ", "RAFI ahmed", " nusrat JAHAN ")
rolls <- c("3", "18", "102")- Remove unnecessary spaces from each student name.
- Convert the cleaned names to title case.
- Format all roll numbers to have three digits (for example,
"003"). - Find the number of characters in each cleaned student name.
5 Combining and splitting strings
5.1 Combine strings with str_c()
Use str_c() to join strings. The sep argument specifies what appears between arguments.
str_c("AST", "230")[1] "AST230"
str_c("AST", "230", sep = "-")[1] "AST-230"
str_c("R", "for", "Data", "Science", sep = " ")[1] "R for Data Science"
If you have vectors, str_c() combines the corresponding elements.
first_name <- c("Anika", "Rafi", "Nusrat")
last_name <- c("Rahman", "Ahmed", "Jahan")
str_c(first_name, last_name, sep = " ")[1] "Anika Rahman" "Rafi Ahmed" "Nusrat Jahan"
Use collapse when you want to combine all elements of a vector into one string.
courses <- c("AST230", "AST305", "PSY206")
str_c(courses, collapse = ", ")[1] "AST230, AST305, PSY206"
str_c(letters[1:5], collapse = "-")[1] "a-b-c-d-e"
Remember: sep separates arguments element by element; collapse joins the elements of the result into one string.
5.2 Split strings with str_split()
str_split() divides text wherever it finds the specified separator.
course_label <- "AST_230_Lab_2026"
str_split(course_label, "_")[[1]]
[1] "AST" "230" "Lab" "2026"
str_split() returns a list of character vectors. For one input string, use [[1]] to get the vector inside the first list element.
parts <- str_split(course_label, "_")[[1]]
parts[1] "AST" "230" "Lab" "2026"
parts[1][1] "AST"
parts[2][1] "230"
With several input strings, the result has one list element for each string.
labels <- c("AST-230-Lab", "AST-305-Theory", "PSY-206-Lab")
str_split(labels, "-")[[1]]
[1] "AST" "230" "Lab"
[[2]]
[1] "AST" "305" "Theory"
[[3]]
[1] "PSY" "206" "Lab"
Splitting on "" returns individual characters.
str_split("AST230", "")[[1]][1] "A" "S" "T" "2" "3" "0"
5.3 Extract words from a sentence
word() extracts words by position. A negative position counts from the end.
sentence <- "Institute of Applied Statistics and Data Science"
word(sentence, 1) # First word[1] "Institute"
word(sentence, 3) # Third word[1] "Applied"
word(sentence, -1) # Last word[1] "Science"
word(sentence, 3, 4) # Third through fourth words[1] "Applied Statistics"
5.4 Exercise 3: course labels
courses <- c("AST230", "AST305", "PSY206", "CSE331")
label <- "IASDS_University_of_Dhaka"
rolls <- c("3", "18", "102")- Combine the course codes into one string, separated by
" | ". - Use
str_split()to turnlabelinto separate words. - Extract
"University"from the split result. - Create
"Course: AST230","Course: AST305", etc., using onestr_c()call. - Use
str_pad()andstr_c()to create the IDs"AST230-003","AST230-018", and"AST230-102". - Find the last word in
"R for Data Science"usingword().
6 Sorting and comparing strings
6.1 Sort a vector alphabetically
str_sort() sorts the values.
districts <- c("Sylhet", "Dhaka", "Rajshahi", "Barishal")
str_sort(districts)[1] "Barishal" "Dhaka" "Rajshahi" "Sylhet"
str_sort(districts, decreasing = TRUE)[1] "Sylhet" "Rajshahi" "Dhaka" "Barishal"
6.2 Comparing strings
R is case-sensitive. Extra spaces also affect equality comparisons.
"Dhaka" == "dhaka"[1] FALSE
"Dhaka" == " Dhaka "[1] FALSE
Clean the strings before comparing when case or accidental spaces should not matter.
a <- " DHAKA "
b <- "dhaka"
str_to_lower(str_trim(a)) == str_to_lower(str_trim(b))[1] TRUE
7 Finding patterns in strings
A pattern is the text we want to find. str_detect() tests whether the pattern occurs within each string and returns TRUE or FALSE.
7.1 Detect a match
courses <- c("AST230", "AST305", "PSY206", "CSE331")
str_detect(courses, "AST")[1] TRUE TRUE FALSE FALSE
str_detect(courses, "230")[1] TRUE FALSE FALSE FALSE
For case-insensitive searching, convert the text to a common case first.
responses <- c("Agree", "agree", "DISAGREE", "Neutral")
str_detect(str_to_lower(responses), "agree")[1] TRUE TRUE TRUE FALSE
Here, "agree" also occurs inside "disagree". If we mean exactly "agree", we need a more specific pattern.
7.2 Match the beginning or end
Two useful pattern symbols are:
^means start of string.$means end of string.
courses <- c("AST230", "AST305", "PSY206", "CSE331", "AST230-Lab")
str_detect(courses, "^AST") # Starts with AST[1] TRUE TRUE FALSE FALSE TRUE
str_detect(courses, "230$") # Ends with 230[1] TRUE FALSE FALSE FALSE FALSE
Use both symbols to match a whole string rather than just part of it.
responses <- c("Agree", "Disagree", "Agree strongly", "agree")
str_detect(str_to_lower(responses), "agree")[1] TRUE TRUE TRUE TRUE
str_detect(str_to_lower(responses), "^agree$")[1] TRUE FALSE FALSE TRUE
These start/end symbols are basic examples of regular expressions. We will not cover more complex regular expressions in this lecture.
7.3 Count matches
Because logical values act like 1 (TRUE) and 0 (FALSE) in calculations, we can use sum() and mean() to find the number and proportion of strings containing a pattern.
districts <- c("Dhaka", "Gazipur", "Dhaka", "Sylhet", "Dhaka")
matches <- str_detect(districts, "^Dhaka$")
matches[1] TRUE FALSE TRUE FALSE TRUE
sum(matches) # Number of matching observations[1] 3
mean(matches) # Proportion of matching observations[1] 0.6
To count how many times a pattern occurs within each string, use str_count().
str_count(c("banana", "papaya", "apple"), "a")[1] 3 3 1
If the input contains missing values, str_detect() returns NA for those elements. Use na.rm = TRUE when appropriate.
x <- c("Dhaka", NA, "Gazipur")
str_detect(x, "a")[1] TRUE NA TRUE
sum(str_detect(x, "a"), na.rm = TRUE)[1] 2
7.4 Exercise 4: matching survey responses
responses <- c("Agree", "Disagree", "Neutral", "agree", "Strongly agree", "Agree")- Find the responses that contain
"agree", ignoring case. - Find the responses that are exactly
"Agree", ignoring case. - Count the number and proportion of exact
"Agree"responses. - Find which of the following course codes start with
"AST":"AST230","PSY206","AST305", and"CSE331".
8 Replacing text
str_replace() replaces the first match in each string; str_replace_all() replaces every match.
x <- c("banana", "papaya", "apple")
str_replace(x, "a", "-")[1] "b-nana" "p-paya" "-pple"
str_replace_all(x, "a", "-")[1] "b-n-n-" "p-p-y-" "-pple"
This is useful when standardising labels in data.
labels <- c("AST230_Lab", "AST305_Theory", "PSY206_Lab")
str_replace_all(labels, "_", "-")[1] "AST230-Lab" "AST305-Theory" "PSY206-Lab"
Note: As with other R functions, the original object remains unchanged unless you assign the result.
labels <- str_replace_all(labels, "_", "-")
labels[1] "AST230-Lab" "AST305-Theory" "PSY206-Lab"
8.1 Exercise 5: standardising text
items <- c("Data-Science", "R-Programming", "Statistical-Computing")- Replace the first hyphen in each string with a space.
- Replace all hyphens with spaces.
- Convert the resulting strings to lower case.
9 Working with character columns
Most stringr functions are vectorised: they can process an entire character vector without a loop. This also makes them convenient inside dplyr::mutate().
We will create a small example data frame representing student information.
library(dplyr)
students <- tibble::tibble(
name = c(" anika RAHMAN", "RAFI ahmed ", "nusrat jahan"),
roll = c("7", "24", "105"),
course = c("AST230", "AST230", "AST305")
)
students# A tibble: 3 × 3
name roll course
<chr> <chr> <chr>
1 " anika RAHMAN" 7 AST230
2 "RAFI ahmed " 24 AST230
3 "nusrat jahan" 105 AST305
Clean the names and make a consistent student ID.
students_clean <- students |>
mutate(
name = str_to_title(str_squish(name)),
student_id = str_c(course, str_pad(roll, 3, pad = "0"), sep = "-")
)
students_clean# A tibble: 3 × 4
name roll course student_id
<chr> <chr> <chr> <chr>
1 Anika Rahman 7 AST230 AST230-007
2 Rafi Ahmed 24 AST230 AST230-024
3 Nusrat Jahan 105 AST305 AST305-105
We can also use str_detect() with filter() to keep rows based on character data.
students_clean |>
filter(str_detect(course, "^AST230$"))# A tibble: 2 × 4
name roll course student_id
<chr> <chr> <chr> <chr>
1 Anika Rahman 7 AST230 AST230-007
2 Rafi Ahmed 24 AST230 AST230-024
9.1 Exercise 6: a small survey dataset
survey <- tibble::tibble(
id = c("1", "2", "3", "4", "5"),
district = c(" dhaka", "GAZIPUR ", "Dhaka", " sylhet ", "gazipur"),
response = c("Agree", "agree ", "Disagree", "Neutral", "AGREE")
)
survey# A tibble: 5 × 3
id district response
<chr> <chr> <chr>
1 1 " dhaka" "Agree"
2 2 "GAZIPUR " "agree "
3 3 "Dhaka" "Disagree"
4 4 " sylhet " "Neutral"
5 5 "gazipur" "AGREE"
- Create
district_cleanby removing extra spaces and converting the district names to title case. - Create
response_cleanby removing extra spaces and converting responses to title case. - Create an ID with the prefix
"S-"and three-digit numbers, such as"S-001". - Keep only the rows where
district_cleanis"Dhaka". - Calculate the number and proportion of responses that are exactly
"Agree"after cleaning.
10 Summary
| If you want to… | Use… |
|---|---|
| Change character case | str_to_lower(), str_to_upper(), str_to_title() |
| Count characters or extract part of a string | str_length(), str_sub() |
| Find the location of a substring | str_locate() |
| Remove unnecessary spaces | str_trim(), str_squish() |
| Format IDs with a fixed width | str_pad() |
| Join strings or split them | str_c(), str_split() |
| Extract words | word() |
| Sort strings | str_sort() |
| Test or count matches | str_detect(), str_count() |
| Replace text | str_replace(), str_replace_all() |