13  R中的字符串处理

13.1 本章学习目标

通过本章学习,你将能够:

  • 掌握字符串长度、截取、大小写等基本操作
  • 熟练使用 paste()paste0() 拼接字符串
  • 使用 strsplit() 拆分和 trimws() 清洗字符串
  • 使用 sprintf() 进行格式化输出
  • 掌握 grep()sub()gsub() 进行模式匹配与替换
  • 使用 regexpr() + regmatches() 提取匹配内容
  • 能够独立完成字符串数据清洗任务

13.2 字符串长度与字符统计

你已经见过 nchar()

Code
nchar("hello")
[1] 5

👉 nchar() 返回字符数;type = "bytes" 返回字节数(中文字符通常占 2-3 个字节)。


nchar()length() 是两回事:

Code
x <- c("a", "abc", "abcde")
length(x)     # 3 —— 向量里有几个元素
[1] 3
Code
nchar(x)      # 1 3 5 —— 每个元素有几个字符
[1] 1 3 5

👉 一个问”向量多长”,一个问”每个字符串多长”。不要搞混。


13.3 字符串截取与替换

substr(x, start, stop) 按位置截取子串:

Code
substr("hello", 2, 4)           # "ell"
[1] "ell"

substr() 还能用于原地修改

Code
x <- "hello"
substr(x, 1, 1) <- "H"
x                              # "Hello"
[1] "Hello"

13.4 大小写转换

Code
toupper("hello")               # "HELLO"
[1] "HELLO"
Code
tolower("HELLO")               # "hello"
[1] "hello"
Code
casefold("HELLO")              # "hello"(默认 upper = FALSE)
[1] "hello"
Code
casefold("hello", upper = TRUE) # "HELLO"
[1] "HELLO"

chartr() 可以按字符一一对应地翻译:

Code
chartr("a-z", "A-Z", "hello")   # "HELLO"
[1] "HELLO"
Code
chartr("aeiou", "AEIOU", "hello")  # "hEllO"
[1] "hEllO"

13.5 字符串拼接

paste()paste0() 是 R 中最常用的拼接函数:

Code
paste("A", "B", "C")            # "A B C"(默认空格分隔)
[1] "A B C"
Code
paste("A", "B", sep = "-")      # "A-B"
[1] "A-B"
Code
paste0("A", "B", "C")           # "ABC"(等价于 paste(..., sep = ""))
[1] "ABC"

👉 记住:paste0() = paste(sep = ""),不带分隔符。


向量化拼接——不同长度的向量会自动循环:

Code
paste("sample", 1:3, sep = "_")   # "sample_1" "sample_2" "sample_3"
[1] "sample_1" "sample_2" "sample_3"

collapse 参数把向量折叠成单个字符串

Code
paste(c("A", "B", "C"), collapse = ", ")  # "A, B, C"
[1] "A, B, C"
Code
paste(letters[1:5], collapse = "-")       # "a-b-c-d-e"
[1] "a-b-c-d-e"

👉 sep 是多个参数之间的分隔符,collapse 是把向量元素合并成一个。


13.6 字符串拆分

strsplit() 按分隔符拆分,返回列表

Code
strsplit("a,b,c", split = ",")            # 返回列表
[[1]]
[1] "a" "b" "c"
Code
strsplit("a,b,c", split = ",")[[1]]       # c("a", "b", "c")
[1] "a" "b" "c"

👉 为什么返回列表?因为拆分后每段长度可能不同,只有列表能容纳。


Code
# 拆分为单个字符
strsplit("abcdef", split = "")
[[1]]
[1] "a" "b" "c" "d" "e" "f"
Code
# 向量化拆分
strsplit(c("a,b", "c,d,e"), split = ",")
[[1]]
[1] "a" "b"

[[2]]
[1] "c" "d" "e"
Code
# unlist 拉平
unlist(strsplit(c("a,b", "c,d,e"), split = ","))
[1] "a" "b" "c" "d" "e"

13.7 去除空白

trimws() 去除首尾空白:

Code
trimws("  hello  ")                  # "hello"
[1] "hello"
Code
trimws("  hello  ", which = "left")  # "hello  "
[1] "hello  "
Code
trimws("  hello  ", which = "right") # "  hello"
[1] "  hello"

清洗数据框中的字符列:

Code
df <- data.frame(name = c(" 张三 ", " 李四  "), score = 1:2)
df$name <- trimws(df$name)
df
  name score
1 张三     1
2 李四     2

13.8 格式化输出

sprintf() 用格式占位符生成指定格式的字符串:

Code
sprintf("编号%03d", 7)            # "编号007"(零填充到3位)
[1] "编号007"
Code
sprintf("%.2f", 3.14159)          # "3.14"(两位小数)
[1] "3.14"
Code
sprintf("%s:%d", "样本", 1:3)    # "样本:1" "样本:2" "样本:3"
[1] "样本:1" "样本:2" "样本:3"
Code
sprintf("%10s", "abc")            # "       abc"(右对齐,宽度10)
[1] "       abc"
Code
sprintf("%-10s", "abc")           # "abc       "(左对齐,宽度10)
[1] "abc       "
Code
sprintf("%05d", 42)               # "00042"(整数零填充)
[1] "00042"

常用格式符:

格式符 含义
%s 字符串
%d 整数
%f 浮点数
%e 科学计数法

for (i in 1:10) {
  
  filename <- sprintf("Fig_%02d.png", i)
  
  png(filename, width = 800, height = 600)
  
  x <- 1:20
  y <- rnorm(20, mean = i * 2, sd = 3)
  
  plot(x, y, type = "b", pch = 16, col = i + 1, lwd = 2,
       main = paste0("Figure_", i),
       xlab = "x", ylab = "y")
  
  dev.off()
}

13.9 模式匹配:查找

grep() 返回匹配元素的索引,grepl() 返回逻辑向量:

Code
x <- c("apple", "banana", "grape", "pear", "apricot")

grep("^a", x)                     # 1 5(索引)
[1] 1 5
Code
grep("^a", x, value = TRUE)       # "apple" "apricot"(值)
[1] "apple"   "apricot"
Code
grepl("^a", x)                    # TRUE FALSE FALSE FALSE TRUE
[1]  TRUE FALSE FALSE FALSE  TRUE

👉 grepl() 返回逻辑向量,可以直接用于 [ ] 筛选或 subset()


Code
# 逻辑向量筛选
x[grepl("a", x)]                  # 所有含 a 的元素
[1] "apple"   "banana"  "grape"   "pear"    "apricot"
Code
# 忽略大小写
grep("A", c("apple", "Apple"), ignore.case = TRUE)
[1] 1 2
Code
# 反向匹配
grep("a", x, invert = TRUE)       # pear(不含 a 的元素)
integer(0)

13.10 模式匹配:替换

sub() 替换首次匹配,gsub() 替换全部匹配:

Code
sub("a", "X", "banana")           # "bXnana"(仅第一个)
[1] "bXnana"
Code
gsub("a", "X", "banana")          # "bXnXnX"(全部)
[1] "bXnXnX"

👉 工作中绝大多数情况用 gsub(),因为通常要把所有匹配都替换掉。


Code
# 删除多余空格
gsub("\\s+", " ", "a  b   c")    # "a b c"
[1] "a b c"
Code
# 向量化替换
fruits <- c("apple", "pear")
gsub("p", "P", fruits)            # "aPPle" "Pear"
[1] "aPPle" "Pear" 
Code
# 删除所有数字
gsub("\\d", "", "abc123def456")   # "abcdef"
[1] "abcdef"

13.11 模式匹配:提取

regexpr() / gregexpr() 返回匹配位置,配合 regmatches() 提取匹配文本:

Code
# 提取首次匹配
x <- "ID编号007"
m <- regexpr("\\d+", x)
regmatches(x, m)                  # "007"
[1] "007"

Code
# 提取全部匹配
x <- "a1b22c333"
m <- gregexpr("\\d+", x)
regmatches(x, m)                  # list("1", "22", "333")
[[1]]
[1] "1"   "22"  "333"
Code
# 提取邮箱域名(需要 perl = TRUE 支持后顾断言)
email <- "user@example.com"
m <- regexpr("(?<=@)\\S+", email, perl = TRUE)
regmatches(email, m)              # "example.com"
[1] "example.com"

Code
# 批量提取
emails <- c("a@gmail.com", "b@163.com", "c@qq.com")
m <- gregexpr("(?<=@)\\S+", emails, perl = TRUE)
unlist(regmatches(emails, m))     # "gmail.com" "163.com" "qq.com"
[1] "gmail.com" "163.com"   "qq.com"   

👉 perl = TRUE 启用 PCRE 引擎,支持后顾断言 (?<=...) 等高级特性。


13.12 正则表达式速查

R 默认使用 TRE 正则引擎,设置 perl = TRUE 可使用 PCRE 引擎。

模式 含义 示例
. 任意单个字符 a.b 匹配 “aab”
^ 行首 ^a 以 a 开头
$ 行尾 a$ 以 a 结尾
\\d 数字 [0-9] \\d+ 提取数字
\\w 单词字符 \\w+ 提取单词
\\s 空白字符 \\s+ 匹配连续空格
[abc] 字符类 a、b 或 c
[^abc] 否定字符类 非 a、b、c
* 零次或多次 a*
+ 一次或多次 a+
? 零次或一次 a?
{n} 恰好 n 次 \\d{4} 4位数字
{n,m} n 到 m 次 \\d{2,4}
() 捕获分组 (\\d+)-(\\d+)
| apple\|banana
Code
grep("^\\d{3}-\\d{4}", c("010-1234", "01-1234"))  # 匹配电话区号
[1] 1
Code
gsub("[[:punct:]]", "", "Hello, World!")            # 去掉所有标点
[1] "Hello World"
Code
gsub("[^0-9]", "", "电话:138-1234-5678")           # 只保留数字
[1] "13812345678"

13.13 综合应用示例

批量处理文件名——提取日期并转格式:

Code
files <- c("report_20240115.csv", "data_20231201.pdf",
           "summary_20240320.xlsx")

# 提取 8 位日期
m <- regexpr("\\d{8}", files)
dates <- regmatches(files, m)
dates  # "20240115" "20231201" "20240320"
[1] "20240115" "20231201" "20240320"
Code
# 转为标准格式
gsub("(\\d{4})(\\d{2})(\\d{2})", "\\1-\\2-\\3", dates)
[1] "2024-01-15" "2023-12-01" "2024-03-20"
Code
# "2024-01-15" "2023-12-01" "2024-03-20"

数据框清洗——统一清理字符列:

Code
df <- data.frame(
  name  = c(" 张三 ", "李  四", "王五"),
  phone = c("138-1234-5678", "(010)82345678", "13912345678"),
  stringsAsFactors = FALSE
)

df$name <- trimws(df$name)                         # 去首尾空白
df$name <- gsub("\\s+", "", df$name)               # 去内部空格
df$phone <- gsub("[^0-9]", "", df$phone)           # 只保留数字
df
  name       phone
1 张三 13812345678
2 李四 01082345678
3 王五 13912345678

文本分词与词频统计:

Code
text <- c("R is a language for statistical computing",
          "Python is a general purpose language")

words <- unlist(strsplit(tolower(text), " "))
freq <- sort(table(words), decreasing = TRUE)
head(freq, 8)
words
        a        is  language computing       for   general   purpose    python 
        2         2         2         1         1         1         1         1 

13.14 本章总结

  • 长度nchar() 字符数,length() 元素数
  • 截取substr() / substring() 按位置提取,substr() 可赋值修改
  • 大小写toupper()tolower()casefold()chartr() 字符翻译
  • 拼接paste() / paste0()sep 分隔参数,collapse 折叠向量
  • 拆分strsplit(),返回列表,用 [[ ]]unlist() 提取
  • 空白trimws() 去首尾空白
  • 格式化sprintf()%s%d%f 生成格式化字符串
  • 查找grep() 返回索引,grepl() 返回逻辑值
  • 替换sub() 换首个,gsub() 换全部
  • 提取regexpr() / gregexpr() + regmatches() 提取匹配内容

👉 字符串处理是数据清洗的基本功。 打开 RStudio,把每个函数的例子都动手跑一遍。