相关文章推荐
个性的跑步机  ·  褚遂良 - 知乎·  1 年前    · 
英俊的猴子  ·  凌宝汽车_搜索·  2 年前    · 

R语言正则表达式在工程技术数据分析中的应用

正则表达式,由于其优秀的批量字符串搜索和替换功能,几乎所有编程语言都能调用。近年来,随着python,R等统计分析语言在工程领域应用越来越广泛,正则表达式也在不断的渗透到该领域。接下来,我在R语言环境下,用真实的案例来介绍一下正则表达式在工程技术数据分析中的应用。

案例背景说明

某公司需要对两个部门提供的钢板数据按钢板种类对比分析。这两组数据分别是套料数据 product_data 和出库数据 warehouse_data 。通过观察数据发现,两组数据对钢板种类的记录方式不同,并且每组数据内部也不一致,直接应用不会产生有价值的的结果,因此需要将钢板的材质和种类提取出来,整理成统一的格式才行。先看数据:

`library(tidyverse)
product_data <- read.table("data",header = T,sep = "\t")
warehouse_data <- read.table("temp",header = T,sep = "\t")`
head(product_data)
  月份 材质 厚度     重量
1 5月份 Q500   10  6923.700
2 1月份 Q550    5   164.300000
3 7月份  960    8   2449.2000
4 5月份 Q960   10   3061.500
5 9月份 Q960E   5  765.3750
6 9月份 BS700    7  1066.08495
unique(product_data$材质)
[1] "Q345"    "Q460"    "Q550"    "BS700"   "Q960E"   "Q890D"   "Q960"
[8] "进口700" "Q500"    "Q800"    "Q890"    "800"     "960"
head(warehouse_data)
过帐日期            物料描述     重量
1      1月 热轧厚钢板 t10 Q345 51184.60
2      1月 热轧厚钢板 t10 Q345 56142.03
3      1月 热轧厚钢板 t10 Q345 61469.39
4      1月  热轧厚钢板 t5 Q345 49674.61
5      1月 热轧厚钢板 t20 Q345 26376.00
6      1月  热轧厚钢板 t8 Q345  5572.14
 unique(warehouse_data$物料描述)
 [1] "热轧厚钢板 t10 Q345"
 [2] "热轧厚钢板 t6 Q345" 
 [3] "热轧厚钢板 t15 Q345B (1800X9450)"
 [4] "热轧薄钢板 t4 DOMEX700(瑞典SSAB)"
 [5] "热轧厚钢板 T6.5 BS960E"
 [6] "热轧厚钢板 t16 WQ690E"
 [7] "热轧薄钢板 t4 BS700MCK2-XF"
 [8] "热轧厚钢板 t35 Q890D"
 ……

从输出结果看,需要做以下清洗操作:

  • product_data 共有月份、材质、厚度和重量四个字段, warehouse_data 共有过帐日期、物料描述和重量四个字段,其中 warehouse_data 中的物料描述与 product_data 中的材质和厚度对应,但格式又不相同。为了能够使用 full_join() 将两组数据合并到一张表中,需要把两组数据的厚度和材质整合成 t10_Q345 的样式。
  • product_data 厚度规格17种,记录格式为数字,需要加字符 t
  • product_data 材质规格有13种,其中 Q500 属于记录错误,需要更改为 Q550 Q960E Q960 960 属于记录问题,需要统一调整成 Q960 ;同理, Q800 800 统一调整成 Q800 Q890D Q890 需要调整成 Q890
  • warehouse_data 物料描述字段中的“热轧厚钢板”和“热轧薄钢板”都不是我们需要的,需要去掉;有些材质使用 () 备注了一些内容,也不是我们需要的,需要去掉;和 product_data 数据一样,钢板的等级 A|B|C|D|E|MCK2 和一些特殊标识 -XF 也不是我们需要的,都需要删除。

R语言中的正则表达式简介

在数据清洗之前,先对正则表达式作一个简单的介绍。正则表达式是使用一组逻辑公式,通过调用正则表达引擎,对字符串进行查找和替换的操作。其中逻辑公式是由一组特殊字符组成,这些字符包括:

  • 元字符,用于匹配字符,常见的元字符有:
    • .:匹配除 "\n" 之外的任何单个字符。
    • [abc]或[a-c]:字符集,匹配[]内的任一单个字符。
    • [^abc]:匹配除[]内的任一字符。
    • |:或字符。
    • \\d或[0-9]或[[:digit]]:匹配任意一阿拉伯数字。
    • \\D:匹配非阿拉伯数字。
    • \\s:匹配任何空白字符,包括空格、制表符、换页符等等。
  • 转义字符\\,特殊字符和普通字符之间相互转换,例如.为元字符,\\.表示普通字符“.”,\\d是元字符,而d是普通字符“d”。
  • 位置字符:用于匹配字符串中的位置信息,常见的位置字符有:
    • ^:匹配字符串的开头,出现在[]内的除外。
    • $: 匹配字符串的末尾,在 \n 之前。
    • \\b:单词的边界。
    • (?<=),(?<!),(?=),(?!):断言,指出现在某个字符(串)之后(?<=),不出现在某个字符(串)之后(?<!),出现在某个字符串之前(?=),不出现在某个字符(串)之前(?!)。
  • 成组匹配与反向引用
    • ():出现成()内的字符串作为一个字符串组进行匹配
    • \\1-\\9,反向引用,反向引用前面()中的内容,其中\\1对应第一个(),\\9对应第9个(),反向引用最多为9个。
  • 数量字符,用于指定连续匹配的次数,主要有:
    • ?:区配?前面的字符(串)0次或1次。
    • +:匹配+前面的字符(串)1次或多次。
    • *:匹配*前面的字符(串)0次或多次。
    • {m}:匹配{m}前面的字符(串)m次。
    • {m,n}:匹配{m,n}前面的字符m-n次。

和awk,perl等脚本语言等使用d,s,g,i等修饰符进行删除,替换等操作不同,R语言使用字符串函数查找和删除。

product_data 数据清洗

1、将材质和厚度合并到字段,连接字符使用“_”,格式为 t10_Q345

product_data <- product_data %>% 
mutate(物料描述=str_c("t",厚度,"_",材质))%>% 
select(物料描述,重量)
unique(product_data$物料描述)
[1] "t8_960"     "t10_Q960"  "t5_Q960E"   "t7_BS700"   "t10_Q500"  
[6] "t5_Q550"   "t10_Q960"   "t10_Q960E"  "t12_BS700"  "t12_Q460"  
……

2、清洗材质规格

比较简单的方法是方法是,用R语言 str_replace() 函数进行逐项替换,代码语句如下。

product_data$物料描述 <- str_replace(product_data$物料描述,"Q?960[DE]?","Q960")
product_data$物料描述 <- str_replace(product_data$物料描述,"Q500","Q550")
product_data$物料描述 <- str_replace(product_data$物料描述,"Q890D?","Q890")
product_data$物料描述 <- str_replace(product_data$物料描述,"Q?800","Q800")

在这段代码中,第一句使用了正则表达式 Q?960[DE]? ,用于匹配字符串 960 Q960 Q960D Q960E 。第三句使用了正则表达式 Q890D? 匹配字符串 Q890 Q890D 。第四句使用正则表达式 Q?800 匹配字符串 Q800 800 。查看运行结果,共有65种规格,结果唯一,清洗成功。

sort(unique(product_data$物料描述))
[1] "t10_BS700"  "t10_Q345"   "t10_Q460"   "t10_Q550"   "t10_Q960"  
[6] "t12_BS700"  "t12_Q460"   "t12_Q550"   "t12_Q960"   "t14_Q345"  
[11] "t14_Q460"   "t14_Q550"   "t15_BS700"  "t15_Q345"   "t15_Q460"  
[16] "t15_Q550"   "t15_Q890"   "t16_BS700"  "t16_Q345"   "t16_Q460"  
[21] "t16_Q550"   "t18_Q890"   "t20_BS700"  "t20_Q345"   "t20_Q460"  
[26] "t20_Q550"   "t20_Q890"   "t20_Q960"   "t25_Q345"   "t25_Q460"  
[31] "t25_Q550"   "t25_Q890"   "t3_BS700"   "t3_Q550"    "t30_Q345"  
[36] "t30_Q460"   "t30_Q550"   "t30_Q890"   "t30_Q960"   "t4_进口700"
[41] "t4_BS700"   "t4_Q550"    "t4_Q960"    "t40_Q890"   "t5_BS700"  
[46] "t5_Q345"    "t5_Q460"    "t5_Q550"    "t5_Q960"    "t6_BS700"  
[51] "t6_Q345"    "t6_Q460"    "t6_Q550"    "t6_Q800"    "t6_Q960"   
[56] "t6.5_Q960"  "t7_BS700"   "t7_Q550"    "t7_Q960"    "t8_BS700"  
[61] "t8_Q345"    "t8_Q460"    "t8_Q550"    "t8_Q800"    "t8_Q960"

上面清洗虽然成功了,但效率和准确性还不如 Excel ,完全没有发挥出正则表达式的作用。用于举例尚可,但要真的应用到工作当中去,那真是要骂娘了。那么如何发挥正则表达式呢? 这就要用到正则的含婪模式和反向引用等功能。在应用前,首先对原始字符串分析:

当前格式:  _Q960E或_960  规律是:_+Q(0次或1次)+数字(3个)+英文字符(一个)
期望格式:_Q960,_960 规律是_+Q+三个数字`

这里有两种方式实现,一种是将 _Q960 作为一个整体进行匹配替换,方法是:

product_data$物料描述 <- str_replace(product_data$物料描述,"(_)Q(?\\d{3})[A-E]?","\\1Q\\2")

本例中的{3}表示匹配\\d三次。这种使用量化字符匹配尽可能多字符的方法叫在正则中叫含婪模式。而使用 () 对字符串进行成组匹配,并在后面使用\\1进行引用的方法,在正则中叫反向引用。

另一种是通过匹配字符(串)位置的方式,实现字符串的匹配,正则表达式中这种方式叫断言。本例中使用的是 (?<=_) ,意思是匹配的字符必须出现在 _ 之后。

product_data$物料描述 <- str_replace(product_data$物料描述,"Q500","Q550")
product_data$物料描述 <- str_replace(product_data$物料描述,"(?<=_)Q?(\\d{3})[A-E]?","Q\\1")
sort(unique(product_data$物料描述))
[1] "t10_BS700"  "t10_Q345"   "t10_Q460"   "t10_Q550"   "t10_Q960"  
 [6] "t12_BS700"  "t12_Q460"   "t12_Q550"   "t12_Q960"   "t14_Q345"  
[11] "t14_Q460"   "t14_Q550"   "t15_BS700"  "t15_Q345"   "t15_Q460"  
[16] "t15_Q550"   "t15_Q890"   "t16_BS700"  "t16_Q345"   "t16_Q460"  
[21] "t16_Q550"   "t18_Q890"   "t20_BS700"  "t20_Q345"   "t20_Q460"  
[26] "t20_Q550"   "t20_Q890"   "t20_Q960"   "t25_Q345"   "t25_Q460"  
[31] "t25_Q550"   "t25_Q890"   "t3_BS700"   "t3_Q550"    "t30_Q345"  
[36] "t30_Q460"   "t30_Q550"   "t30_Q890"   "t30_Q960"   "t4_进口700"
[41] "t4_BS700"   "t4_Q550"    "t4_Q960"    "t40_Q890"   "t5_BS700"  
[46] "t5_Q345"    "t5_Q460"    "t5_Q550"    "t5_Q960"    "t6_BS700"  
[51] "t6_Q345"    "t6_Q460"    "t6_Q550"    "t6_Q800"    "t6_Q960"   
[56] "t6.5_Q960"  "t7_BS700"   "t7_Q550"    "t7_Q960"    "t8_BS700"  
[61] "t8_Q345"    "t8_Q460"    "t8_Q550"    "t8_Q800"    "t8_Q960"

语句运行后,查看结果,与优化前结果一致。

warehouse_data清洗

有了上面的基础,数据 warehouse_data 清洗直接应用贪婪模式和反向引用。首先观察数据: warehouse_data$物料描述 字段信息由四部分组成:第一部分是名称“热轧厚钢板或热轧薄钢板”,第二部分是厚度“T6.5/t8/t15”等,第三部分是材质和特殊字符“BS700MCK-XF”,第四部分为 () 内备注内容。而我们需要的内容是第二部分内容的全部和第三部分数字以前的内容。经过上述分析找到字符串的规律后,就可以用正则表达式进行替换清洗了。

"热轧厚钢板 t8 Q460"
"热轧厚钢板 t10 Q550D"
"热轧薄钢板 t7 BS700MCK2-XF" 
"热轧薄钢板 t4 DOMEX700 (瑞典SSAB)"
"热轧厚钢板 T6.5 BS960E" 
"热轧厚钢板 t15 Q345B (1800X9450)"
……

第一部分使用语句 ^[^\\s]*\\s ,意思是匹配字符串开头 ^ 至第一个空格 \\s 之间的所有字符,中间使用的是 [^\\s] 而非 . ,是为了防止正则表达式匹配第二、第三或第四个空格。

第二部分使用语句 [tT](\\d{1,2}(\\.\\d)?) ,意思是匹配字符串中以 t T 开始,后面是1~2个数字,再后面可能是一个小数点的字符串,其中 [tT] 后面字符是要保留的字符,使用了 () 进行组合,在替换字符串里使用 \\1 引用, \\.\\d 意思是匹配小数,由于数据中只有个别记录包含小数,因此使用 () 组合成整体后加 ? 表示或不需匹配。

第三部分使用语句 \\s(\\D+\\d{3}).* ,意思是匹配字符串中以空格开始,后面是不少于一个非数字字符,再后面是3个数字字符,最后面是一串我们不关心的字符。其中从空格后第一个字母开始到紧接着的3个数字字符结束,使用 () 进行组合,在替换字符串里使用 \\3 引用。

把三部分正则表达式组成,就形成了最终的表达则,使用 str_replace() 进行调用正则表达式引擎,运行结果和预期一致。

warehouse_data$物料描述 <- str_replace(warehouse_data$物料描述,"^[^\\s]*\\s[tT](\\d{1,2}(\\.\\d)?)\\s(\\D+\\d{3}).*","t\\1_\\3")
 sort(unique(warehouse_data$物料描述))
[1] "t10_BS700"   "t10_Q345"    "t10_Q460"    "t10_Q550"    "t10_Q960"   
[6] "t12_BS700"   "t12_Q460"    "t12_Q550"    "t12_Q960"    "t14_Q550"   
[11] "t15_Q345"    "t15_Q550"    "t15_Q890"    "t16_Q550"    "t16_WQ690"  
[16] "t18_Q890"    "t20_ Q960"   "t20_Q345"    "t20_Q460"    "t20_Q550"   
[21] "t20_Q690"    "t20_Q890"    "t25_Q345"    "t25_Q460"    "t25_Q550"   
[26] "t25_Q890"    "t3_BS700"    "t30_Q345"    "t30_Q460"    "t30_Q550"