07使用 stringr 整理字符串

使用 stringr 与正则表达式完成大小写转换、空白清理、检测、提取、替换、截取、拆分与拼接。

2026-04-06
RstringrTidyverseStringsRegular Expressions
本章目录 · 12

字符串清洗经常需要批量修改一整列文本。stringr 为常见操作提供统一的 str_ 前缀,函数以字符向量为输入,能够直接放进 dplyr::mutate() 或管道中。

library(stringr)

转换大小写

大小写转换主要使用三个函数:

任务 函数 示例结果
全部转为小写 str_to_lower() "Hello""hello"
全部转为大写 str_to_upper() "Hello""HELLO"
单词首字母大写 str_to_title() "hello world""Hello World"
x <- c("bIoINFO", "hello world", "R语言", "张三")

str_to_lower(x)
# [1] "bioinfo" "hello world" "r语言" "张三"

str_to_upper(x)
# [1] "BIOINFO" "HELLO WORLD" "R语言" "张三"

str_to_title(x)
# [1] "Bioinfo" "Hello World" "R语言" "张三"

大小写规则与语言有关。需要按特定语言处理时,显式提供 locale

str_to_upper("straße", locale = "de")
# [1] "STRASSE"

中文字符没有大小写,转换时通常保持不变。str_to_title() 适合英文标题或姓名,但它只执行通用的标题化规则,不能替代领域特定的书写规范。

在数据框中批量转换

单列可以直接在 mutate() 中处理:

library(dplyr)

people <- tibble(
  name = c("zhang san", "LI Si"),
  city = c("beijing", "SHANGHAI")
)

people |>
  mutate(city = str_to_title(city))

多个字符列使用 across(),并通过 where(is.character) 避免误改数值等其他类型:

people |>
  mutate(across(where(is.character), str_to_title))

大小写标准化会改变原始文本。若大小写本身可能携带意义,例如基因符号、样本编号或密码,不应不加区分地处理整个数据框。

清理空白

str_trim() 只移除字符串两端的空白;str_squish() 还会把内部连续空白压缩成一个空格。

str_trim("  hello  ")
# [1] "hello"

str_squish("  hello   world \n test  ")
# [1] "hello world test"

前者适合只清理边界,后者适合规范自由输入的文本。对需要保留换行或对齐空格的内容,不要使用 str_squish()

检测与替换

检测函数返回逻辑向量,可以用于筛选或创建标记列:

files <- c("sample.csv", "notes.txt", "report.csv")

str_starts(files, "sample")
str_ends(files, ".csv")
str_detect(files, "report|notes")

替换第一个匹配使用 str_replace(),替换所有匹配使用 str_replace_all()

str_replace("apple apple", "apple", "pear")
# [1] "pear apple"

str_replace_all("apple apple", "apple", "pear")
# [1] "pear pear"

这些函数默认把模式当作正则表达式。要匹配字面文本,可使用 fixed() 明确表达意图:

str_replace_all("a.b.c", fixed("."), "-")
# [1] "a-b-c"

正则表达式速查

正则表达式(regular expression)描述一类字符串模式。str_detect()str_extract()str_replace()str_split() 等函数都可以把正则作为 pattern。

模式 含义 示例
. 任意单字符 "a.c"
[abc] abc "a[bc]d"
[a-z] 小写字母范围 "[a-z]+"
[^abc] abc 外的字符 "[^abc]"
\\d / \\D 数字 / 非数字 "\\d+"
\\w / \\W 单词字符 / 非单词字符 "\\w+"
\\s / \\S 空白 / 非空白 "\\s+"
^ / $ 字符串开头 / 结尾 "^sample", "csv$"
a|b ab "cat|dog"
(ab) 分组 "(ab)+"
{n} 恰好重复 n 次 "a{3}"
{n,m} 重复 n 到 m 次 "a{2,4}"
? 重复 0 或 1 次 "a?"
+ 重复 1 次或更多 "a+"
* 重复 0 次或更多 "a*"

正则中的反斜杠和 R 字符串中的转义叠加,因此匹配数字的正则 \d+ 在 R 代码里写作:

pattern <- "\\d+"
str_extract_all("2025年1月30日", pattern)
# [[1]] "2025" "1" "30"

检测、提取与替换

x <- c("R language", "Python", "Ruby")

str_detect(x, "^R")
# [1] TRUE FALSE TRUE

str_extract("sample_2025.csv", "\\d+")
# [1] "2025"

str_replace_all(c("abcda", "adcba"), "a", "A")
# [1] "AbcdA" "AdcbA"

str_extract() 取第一个匹配,str_extract_all() 取全部匹配;str_replace() 只替换第一个,str_replace_all() 替换全部。函数名已经表达了返回范围,不必先用位置函数再手动截取。

贪婪与非贪婪匹配

*+ 默认尽可能多地匹配:

text <- "<b>one</b><b>two</b>"

str_extract(text, "<b>.*</b>")
# [1] "<b>one</b><b>two</b>"

在量词后加 ?,让它尽可能少地匹配:

str_extract_all(text, "<b>.*?</b>")
# [[1]] "<b>one</b>" "<b>two</b>"

若目标只是固定边界内“不包含某字符”的内容,明确的字符类通常比非贪婪通配符更容易检查。

Base R 对应函数

Base R 也提供完整的正则工具:

函数 返回内容
grep() 匹配元素的位置,或设置 value = TRUE 返回值
grepl() 每个元素是否匹配的逻辑向量
sub() 替换第一个匹配
gsub() 替换全部匹配
regexpr() 第一个匹配的位置和长度
gregexpr() 全部匹配的位置和长度
regmatches() 根据位置结果提取匹配文本
strsplit() 按模式拆分文本
x <- "日期:2025年1月30日"
regmatches(x, gregexpr("\\d+", x))

grepl("^R", c("R语言", "Python", "Ruby"))
gsub("a", "A", c("abcda", "adcba"))

正则适合描述模式,不适合替代结构化解析。日期、URL、HTML、JSON 等内容若已有专门解析器,应优先使用解析器,而不是不断扩展一条难以验证的正则。

拆分、拼接与长度

str_split("A,B,C", ",")
str_c(c("sample", "control"), c("01", "02"), sep = "-")
str_length(c("R", "stringr"))

str_split() 通常返回 list,因为不同字符串可能拆出不同数量的片段。若一个数据框列需要拆成多列或多行,应使用 tidyr 的 separate_* 家族;那组函数会同时处理字符串和表格结构。

使用 str_sub() 提取子串

按字符位置截取一段文本:

x <- c("ABCDEF", "ghijkl", "123456")

str_sub(x, start = 2, end = 4)
# [1] "BCD" "hij" "234"

负数位置从字符串末尾开始计算,-1 表示最后一个字符:

str_sub(x, -3, -1)
# [1] "DEF" "jkl" "456"

这适合结构固定的样本编号、条码或文件名。如果字段长度不固定,应根据分隔符或正则模式解析,而不是假设某段内容永远位于相同字符位置。

Base R 的 substring() 使用从 1 开始的正数位置:

substring(x, first = 2, last = 4)
# [1] "BCD" "hij" "234"

str_c()paste()collapse

逐元素拼接两个向量:

str_c(c("sample", "control"), c("01", "02"), sep = "-")
# [1] "sample-01" "control-02"

Base R 中,paste() 默认以空格分隔,paste0() 默认不加分隔符:

paste("Hello", "World")
# [1] "Hello World"

paste0("Sample_", 1:5)
# [1] "Sample_1" "Sample_2" "Sample_3" "Sample_4" "Sample_5"

sep 控制每组输入之间的分隔符,collapse 则把逐元素生成的全部结果继续合成一个字符串:

paste(letters[1:3], 1:3, sep = "-", collapse = "; ")
# [1] "a-1; b-2; c-3"

输入长度不同时会使用向量回收规则:

paste(c("A", "B"), 1:3, sep = "-")
# [1] "A-1" "B-2" "A-3"

这种回收只有在意图明确时才安全。需要逐行对应的两个 ID 向量通常应先验证长度一致,不能让回收悄悄制造标签。

构造文件路径时使用 file.path(),而不是用 paste() 手动拼接斜杠;前者会处理操作系统路径分隔符。

Base R 对应方法

常见操作在 Base R 中也有对应函数:

stringr Base R
str_to_lower() tolower()
str_to_upper() toupper()
str_split() strsplit()
str_length() nchar()
str_c() paste() / paste0()

两套方法都可以向量化处理文本。项目已经使用 tidyverse 时,stringr 的统一命名和参数顺序更便于组合;只依赖 Base R 的脚本则没有必要仅为大小写转换增加依赖。