R: R 字符串处理
最后更新:2026-08-26
真实项目里 80% 的数据清洗时间花在字符串处理——清洗手机号、提取邮箱、替换关键词、分割拼接。R 的
stringr包是 tidyverse 官方推荐,比基础 R 的grep/gsub更一致、更易用。
读完这一课你就能用 R 处理任何文本清洗任务:手机号校验、邮箱提取、文本分词、URL 解析。
1. 你将学到
- 字符串基础(创建、拼接、长度、连接)
- stringr 5 大核心函数(str_c/str_sub/str_detect/str_replace/str_split)
- str_trim/str_pad 空白处理
- 字符串向量化操作
- 字符编码(UTF-8 vs GBK)
- 实战:清洗手机号、邮箱、地址
2. 一个数据清洗的故事
(1) 痛点:脏数据
小陈拿到一份客户数据,发现:
TEXT
📖 仅展示
姓名 手机号 邮箱
Alice 138-0000-1234 zhangsan@163.COM
Bob +86 139 0000 5678 lisi@example.com
Charlie (13900001111) wangwu@gmail.com
赵六 138.0000.2222 zhaoliu@ qq.com
钱七 13900003333
要清洗成:
TEXT
📖 仅展示
姓名 手机号 邮箱
Alice 13800001234 zhangsan@163.com
Bob 13900005678 lisi@example.com
Charlie 13900001111 wangwu@gmail.com
赵六 13800002222 zhaoliu@qq.com
钱七 13900003333 NA
如果用 Excel 公式 + 手动改,要半小时;用 Python re 库 20 行;用 R stringr——
(2) R 的解法
R
library(stringr)
# 1. 清洗手机号(去空格、-、.、+86、())
clean_phone <- str_replace_all(phones, "[\\s\\-\\.\\(\\)\\+86]", "")
# 2. 清洗邮箱(去空格、转小写)
clean_email <- str_trim(emails) |> str_to_lower()
# 3. 验证手机号格式
is_valid_phone <- str_detect(clean_phone, "^1[3-9]\\d{9}$")
3 行代码搞定 5 个清洗任务。
graph LR
A[脏字符串<br/>' 138-0000-1234 '] --> B[str_trim 去空白]
B --> C[str_replace_all 去特殊字符]
C --> D[str_to_lower 统一小写]
D --> E[str_detect 验证格式]
E --> F[干净字符串<br/>'13800001234']
style A fill:#f8d7da
style B fill:#fff3cd
style C fill:#d4edda
style D fill:#cce5ff
style E fill:#e1d4ff
style F fill:#d4edda
3. 字符串基础
(1) 字符串创建
R
# 1. 单引号
s1 <- 'Hello'
# 2. 双引号
s2 <- "World"
# 3. 字符向量
fruits <- c("苹果", "香蕉", "樱桃")
class(fruits)
# [1] "character"
# 4. nchar 字符数
nchar("Hello")
# [1] 5
nchar("你好") # UTF-8 一个汉字 1 个字符
# [1] 2
(2) paste() / paste0() 拼接
R
# paste 默认 sep = " "
paste("Hello", "World")
# [1] "Hello World"
# paste0 无分隔符
paste0("Hello", "World")
# [1] "HelloWorld"
# 向量化拼接
paste("第", 1:3, "名")
# [1] "第 1 名" "第 2 名" "第 3 名"
# collapse 把向量合并成单个字符串
paste(c("A", "B", "C"), collapse = "-")
# [1] "A-B-C"
4. stringr 包:5 大核心函数
mindmap
root((stringr<br/>5 大核心函数))
拼接
str_c
str_glue
子串
str_sub
str_length
检测
str_detect
str_count
替换
str_replace
str_replace_all
分割
str_split
辅助
str_trim
str_pad
str_to_upper
str_to_lower
优势
自动处理 NA
API 一致
管道友好
tidyverse 生态
(1) stringr vs 基础 R 对比
| 基础 R | stringr | 优势 |
|---|---|---|
nchar() |
str_length() |
stringr 自动处理 NA |
paste() |
str_c() |
一致 + 向量化 |
substr() |
str_sub() |
支持负索引 |
grepl() |
str_detect() |
更直观 |
gsub() |
str_replace_all() |
更简洁 |
strsplit() |
str_split() |
更易用 |
(2) str_c() 拼接
R
library(stringr)
# 1. 基本拼接
str_c("Hello", "World", sep = " ")
# [1] "Hello World"
# 2. 多参数拼接
str_c("a", "b", "c", sep = "-")
# [1] "a-b-c"
# 3. 向量化拼接
str_c("第", 1:3, "名", sep = "")
# [1] "第1名" "第2名" "第3名"
# 4. collapse 合并向量
str_c(c("A", "B", "C"), collapse = " | ")
# [1] "A | B | C"
# 5. 处理 NA
str_c("Hello", NA, "World")
# [1] NA
str_c("Hello", NA, "World", na.rm = TRUE)
# [1] "HelloWorld"
(3) str_length() 长度
R
# 字符数(不是字节数)
str_length("Hello")
# [1] 5
str_length("你好")
# [1] 2
# 字符串向量
str_length(c("abc", "你好", "Hello World"))
# [1] 3 2 12
# NA 处理
str_length(NA)
# [1] NA ← stringr 显式返回 NA
(4) str_sub() 子串
R
# 1. 提取子串(1-based,含两端)
str_sub("Hello World", 1, 5)
# [1] "Hello"
# 2. 负索引(从末尾数)
str_sub("Hello World", -5) # 最后 5 个
# [1] "World"
# 3. 修改子串
s <- "Hello World"
str_sub(s, 1, 5) <- "Hi"
s
# [1] "Hi World"
# 4. 向量化
str_sub(c("apple", "banana", "cherry"), 1, 3)
# [1] "app" "ban" "che"
(5) str_detect() 检测
R
# 1. 是否包含子串
str_detect("Hello World", "World")
# [1] TRUE
# 2. 用正则
str_detect(c("apple", "banana", "cherry"), "^a") # 以 a 开头
# [1] TRUE FALSE FALSE
# 3. 计数(包含几次)
str_count("ababab", "ab")
# [1] 3
(6) str_replace() / str_replace_all() 替换
R
# 1. 替换第一个匹配
str_replace("Hello World World", "World", "R")
# [1] "Hello R World"
# 2. 替换所有匹配
str_replace_all("Hello World World", "World", "R")
# [1] "Hello R R"
# 3. 用正则
str_replace_all("abc 123 def 456", "\\d+", "X")
# [1] "abc X def X"
5. 空白与格式处理
(1) str_trim() 去空白
R
# 1. 去两端空白
str_trim(" Hello World ")
# [1] "Hello World"
# 2. 去左端
str_trim(" Hello", side = "left")
# [1] "Hello"
# 3. 去右端
str_trim("Hello ", side = "right")
# [1] "Hello"
(2) str_pad() 填充
R
# 1. 左侧填充
str_pad("42", width = 5, side = "left", pad = "0")
# [1] "00042"
# 2. 右侧填充
str_pad("Hi", width = 5, side = "right", pad = "-")
# [1] "Hi---"
# 3. 两侧填充
str_pad("Hi", width = 6, side = "both", pad = "-")
# [1] "--Hi--"
(3) str_to_* 大小写
R
# 1. 全大写
str_to_upper("Hello World")
# [1] "HELLO WORLD"
# 2. 全小写
str_to_lower("Hello World")
# [1] "hello world"
# 3. 首字母大写
str_to_title("hello world")
# [1] "Hello World"
# 4. 句首大写
str_to_sentence("hello world")
# [1] "Hello world"
(4) str_trunc() 截断
R
# 截断过长字符串(加 ...)
str_trunc("This is a very long sentence.", width = 10)
# [1] "This is..."
6. str_split() 分割
R
# 1. 基本分割
str_split("a,b,c", ",")
# [[1]]
# [1] "a" "b" "c"
# 2. 限制分割数
str_split("a,b,c", ",", n = 2)
# [[1]]
# [1] "a" "b,c"
# 3. 简化结果(向量而非列表)
str_split("a,b,c", ",", simplify = TRUE)
# [,1] [,2] [,3]
# [1,] "a" "b" "c"
7. 实战:客户数据清洗
下面是一个完整工作流示例,把本课所有字符串知识串起来。
▶ 示例:客户数据深度清洗
R
📖 仅展示
# ============================================
# 客户数据深度清洗
# 功能:清洗手机号、邮箱、姓名、地址
# ============================================
library(stringr)
library(dplyr)
# 1. 准备脏数据
customers <- tibble(
name = c(" Alice ", "Bob", "wangwu", "ZHAO liu", "钱七"),
phone = c("138-0000-1234", "+86 139 0000 5678", "(13900001111)",
"138.0000.2222", "13900003333"),
email = c("zhangsan@163.COM", " lisi@example.com ",
"wangwu@gmail.com", "zhaoliu@ qq.com", NA)
)
cat("=== 原始数据 ===\n")
print(customers)
# 2. 清洗姓名
customers <- customers |>
mutate(
# 去空白 + 首字母大写(英文名)
name_clean = str_trim(name) |>
str_to_title() |>
str_replace_all("\\s+", " ") # 多空格变单空格
)
# 3. 清洗手机号(去空格、-、.、+86、())
customers <- customers |>
mutate(
phone_clean = str_replace_all(phone, "[\\s\\-\\.\\(\\)\\+86]", ""),
# 验证 11 位手机号
phone_valid = str_detect(phone_clean, "^1[3-9]\\d{9}$")
)
# 4. 清洗邮箱
customers <- customers |>
mutate(
email_clean = str_trim(email) |> str_to_lower(),
# 验证邮箱格式
email_valid = str_detect(email_clean, "^[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}$")
)
# 5. 输出清洗结果
cat("\n=== 清洗后数据 ===\n")
print(customers |> select(name_clean, phone_clean, phone_valid,
email_clean, email_valid))
# 6. 提取数据特征
customers <- customers |>
mutate(
# 从手机号提取运营商
operator = case_when(
str_detect(phone_clean, "^1(3[0-9]|4[5-9]|5[0-35-9]|66|7[2-35-8]|8[0-9]|9[0-35-9])\\d{8}$") ~ "中国移动",
str_detect(phone_clean, "^1(3[0-2]|4[5-7]|5[3-5-7]|6[2567]|7[0-3]|8[0-3])\\d{8}$") ~ "中国联通",
str_detect(phone_clean, "^1(33|34|49|53|7[37]|8[0-2])\\d{8}$") ~ "中国电信",
TRUE ~ "未知"
),
# 邮箱域名
email_domain = str_extract(email_clean, "@[a-z0-9.]+"),
# 手机号前 3 位
phone_prefix = str_sub(phone_clean, 1, 3)
)
cat("\n=== 数据特征 ===\n")
print(customers |> select(name_clean, operator, phone_prefix, email_domain))
# 7. 文本统计
cat("\n=== 邮箱域名分布 ===\n")
print(table(customers$email_domain))
cat("\n=== 运营商分布 ===\n")
print(table(customers$operator))
# 8. 找出无效数据
cat("\n=== 无效数据 ===\n")
invalid <- customers |> filter(!phone_valid | !email_valid)
print(invalid |> select(name_clean, phone_clean, phone_valid,
email_clean, email_valid))
预期输出(节选):
TEXT
📖 仅展示
=== 清洗后数据 ===
# A tibble: 5 × 6
name_clean phone_clean phone_valid email_clean email_valid
<chr> <chr> <lgl> <chr> <lgl>
1 Alice 13800001234 TRUE zhangsan@163.com TRUE
2 Bob 13900005678 TRUE lisi@example.com TRUE
3 Wangwu 13900001111 TRUE wangwu@gmail.com TRUE
4 Zhao Liu 13800002222 TRUE zhaoliu@qq.com TRUE
5 钱七 13900003333 TRUE <NA> FALSE
=== 运营商分布 ===
中国电信 中国联通 中国移动
1 2 2
❓ 常见问题
Q stringr 和基础 R 字符串函数怎么选?
A 新项目用 stringr
Q
str_sub() 的负索引怎么用?A 负数从末尾数。
str_sub(x, -3) 取最后 3 个字符;str_sub(x, 1, -2) 取从头到倒数第 2 个。Q 怎么提取数字?
A
str_extract_all(x, "\\d+") 提取所有数字字符串(列表)。str_extract(x, "\\d+") 提取第一个数字(向量)。Q
str_c() 和 paste() 区别?A 基本等价。但
str_c() 一致性更好(处理 NA 默认会传染),且配合管道更易读。📖 小节
- stringr 是 tidyverse 字符串处理标准,比基础 R 字符串函数一致、易用
- 5 大核心函数:
str_c拼接 /str_sub子串 /str_detect检测 /str_replace(_all)替换 /str_split分割 - 辅助函数:
str_length长度 /str_trim去空白 /str_pad填充 /str_to_*大小写 - 所有 stringr 函数自动处理 NA(不抛错)——比基础 R 友好
str_c()默认用sep = ""(无分隔符),需要空格显式sep = " "str_replace()替换第一个,str_replace_all()替换所有- 字符串处理常配合正则(
\\d+数字、\\s+空白、^...$首尾锚点)——下一课深入
📝 作业
-
基础题:用
str_c()拼接向量c("苹果", "香蕉", "樱桃")和c(1, 2, 3),输出"苹果 1 元","香蕉 2 元","樱桃 3 元"。 -
基础题:用
str_length()计算"Hello World"、"你好世界"、""的字符数。用str_sub()提取 "Hello World" 的第 7-11 个字符。 -
基础题:用
str_detect()判断c("apple", "banana", "cherry")中哪些以 "a" 结尾,输出逻辑向量。 -
进阶题:清洗下列脏数据:① 邮箱去空格转小写 ② 提取邮箱域名 ③ 验证邮箱格式(
@+域名+.+2 字母以上)。用mutate()加 3 列新数据。 -
挑战题:模拟 100 行客户数据(含各种脏手机号、邮箱、姓名),完整清洗流程:① 清洗姓名 ② 清洗手机号 ③ 验证手机号 ④ 清洗邮箱 ⑤ 提取运营商 ⑥ 统计无效数据比例。把清洗前后的数据框都打印出来,截图保存。