如何让视觉相同的字符串计算相等,实现tibble的left_join连接?
解决字符串看似相同但
==不相等的连接问题 这种情况几乎都是不可见字符或者Unicode编码差异导致的——两个字符串看起来一模一样,但底层的字节表示不同。stringi::stri_cmp()返回0是因为它做的是语义等价比较(比如忽略某些不可见控制字符、统一Unicode规范形式),而==和dplyr::left_join用的是严格字节匹配,所以才会出现这种矛盾。
第一步:先找出差异在哪里
先搞清楚两个字符串到底差了什么,用这些代码排查:
# 查看字符串的Unicode转义,暴露不可见字符(比如空格、换行、零宽空格) stringi::stri_escape_unicode(str1) stringi::stri_escape_unicode(str2) # 对比字符长度,长度不同肯定有差异 nchar(str1) nchar(str2) # 拆分每个字符,查看它们的ASCII/Unicode编码 strsplit(str1, "")[[1]] |> lapply(utf8ToInt) strsplit(str2, "")[[1]] |> lapply(utf8ToInt)
第二步:标准化字符串,让它们严格相等
根据排查出的差异,选择对应的清理方式,这里提供通用的清洗流程,覆盖绝大多数情况:
library(stringr) library(stringi) # 通用清洗函数:处理空白、控制字符、Unicode规范 clean_string <- function(x) { x |> # 去除前后所有空白(包括换行、制表符) str_trim() |> # 把所有连续空白(包括多个空格、制表符)替换成单个普通空格 str_replace_all("\\s+", " ") |> # 统一Unicode编码形式(解决同字符不同编码的问题) stri_trans_nfc() |> # 移除所有控制字符(比如零宽空格、软连字符这类看不见的字符) str_replace_all("[[:cntrl:]]", "") } # 应用到你的字符串上 clean_str1 <- clean_string(str1) clean_str2 <- clean_string(str2) # 现在再检查是否相等 clean_str1 == clean_str2
第三步:应用到tibble的连接上
把这个清洗逻辑用到你的两个tibble的目标列,就能正常执行left_join了:
library(dplyr) tibble1_clean <- tibble1 |> mutate(charVector1 = clean_string(charVector1)) tibble2_clean <- tibble2 |> mutate(charVector2 = clean_string(charVector2)) # 现在可以顺利连接 left_join(tibble1_clean, tibble2_clean, by = c("charVector1" = "charVector2"))
如果清洗后还是不相等,那大概率是存在视觉相似但编码不同的字符(比如全角/半角数字字母、中文的引号和英文引号),这时候可以给清洗函数加上全角转半角的逻辑:
clean_string <- function(x) { x |> str_trim() |> str_replace_all("\\s+", " ") |> stri_trans_nfc() |> str_replace_all("[[:cntrl:]]", "") |> # 全角转半角,统一符号/字母/数字的格式 stri_trans_general("Any-Latin; Latin-ASCII; Lower") }
内容的提问来源于stack exchange,提问作者Captain Hat
相关产品推荐
相关产品推荐

