使用R语言解决半结构化数据向结构化数据的转换
网络数据时代中不仅仅产生结构化数据,同时也会产生半结构化和非结构化数据。对于半结构化和非结构化数据往往需要结构化处理,然后运用处理后的数据进行统计建模分析。本文尝试使用R语言实现半结构化数据向结构化数据的转变。
结构化数据就是常见的二维表数据,具有工整的字段、字段类型、字段大小等信息;半结构化数据常出现在XML、HTML之类及自描述,数据结构和内容混杂在一起的内容;非结构化数据就是无法用简单的数据或文字形式进行记录,常表现为图片、音频、视频等数据。
函数简介
本文将对日志文件进行结构化处理,处理中用到几个重要的函数,即gregexpr()、substring()、strsplit()、grep()和ldply()函数。下面对这几个函数做一下说明:
gregexpr()、strsplit()和grep()函数常用于正则表达式的使用,语法如下:
gregexpr(pattern, text, ignore.case = FALSE,
perl = FALSE,fixed = FALSE,
useBytes = FALSE)
strsplit(x, split, fixed = FALSE, perl = FALSE,
useBytes = FALSE)
grep(pattern, x, ignore.case = FALSE, perl = FALSE,
value = FALSE,fixed = FALSE, useBytes = FALSE,
invert = FALSE)
pattern指定正则表达式;
x指定需要处理的文本或字符;
split指定文本或字符的分隔符;
text指定需要分析的文本;
ignore.case是否忽略正则表达式的大小写,默认区分大小写;
perl使用perl语法下的正则格式,默认不使用perl语法;
fixed指定正则表达式是否作为一个整体进行匹配,默认为FALSE;
value为FALSE时返回符合正则表达式条件的索引,TRUE时返回满足条件的具体值。
substring()函数为字符串函数,用于提取字符串子集,语法如下:
substring(text, first, last = 1000000L)
text指定所要分析的字符串;
first指定从first位置开始截取母字符串text;
last控制返回子字符串的长度,子字符串长度等于:last-first+1。
ldply()函数是数据格式的整合函数,表示列表数据向数据库数据的转换,语法如下:
ldply(.data, .fun = NULL, ..., .progress = "none",
.inform = FALSE,.parallel = FALSE,
.paropts = NULL, .id = NA)
.data为向量或列表格式的数据;
.fun指定为.data中每个元素执行的函数;
应用
假设一条日志文件的格式如下图所示:
下面需要从该日志记录中截取TimeStamp、ConsumerID、ProductsCount等红色部分的值,并返回为数据框格式。下面讲解一下本人解决该问题的思路:
1)发现所要获取的数据存在一个明显的结构,即“英文字符+冒号+数字” 或 “英文字符+等号+数字”。可以使用gregexpr()函数和substring()函数返回满足这些结构内容。
str <- ‘Visitor=BBStore&TimeStamp=20150701105747350&......’
使用gregexpr()函数返回满足“英文字符+冒号+数字”结构的内容所在str中的索引(下标)
t1 <- gregexpr('[a-zA-Z]+\\:[0-9]+',str)
使用gregexpr()函数返回满足”英文字符+等号+数字“结构的内容所在str中的索引(下标)
t2 <- gregexpr('[a-zA-Z]+\\=[0-9]+',str)
第一个红框返回的是:满足正则表达式的首个字符下标;第二个红框返回的是:满足正则表达式的字符长度。
使用substring()函数获取满足条件的子字符
res1 <- substring(str,t1[[1]],t1[[1]]+
attr(t1[[1]],'match.length')-1)
res2 <- substring(str,t2[[1]],t2[[1]]+
attr(t2[[1]],'match.length')-1)
第一个结果返回的是:满足“英文字符+冒号+数字”的所有字符串;第二个结果返回的是:满足“英文字符+等号+数字”的所有字符串。
合并res1和res2的结果
res <- c(res1,res2)
2)抓取想要获取的内容
使用grep()函数抓取带有'ConsumerID:'字样的索引(下标),使用strsplit()函数将抓取出来的内容用指定分割符分割。
grep('ConsumerID:',res)
res[grep('ConsumerID:',res)]
strsplit(res[grep('ConsumerID:',res)],':')
ConsumerID <- strsplit(res[grep('ConsumerID:',res)],':')[[1]][2]
ConsumerID
同理获得TimeStamp的内容
grep('TimeStamp=',res)
res[grep('TimeStamp=',res)]
strsplit(res[grep('TimeStamp=',res)],'=')
TimeStamp <- strsplit(res[grep('TimeStamp=',res)],'=')[[1]][2]
#使用as.Date()函数将字符型数据转换为日期型数据
TimeStamp <- as.Date(substring(TimeStamp,1,8),
format = '%Y%m%d')
TimeStamp
3)根据以上的思路,自定义函数
自定义函数如下:
test <- function(str){
t1 <- gregexpr('[a-zA-Z]+\\:[0-9]+',str)
t2 <- gregexpr('[a-zA-Z]+\\=[0-9]+',str)
res1 <- substring(str,t1[[1]],t1[[1]]+attr(t1[[1]],
'match.length')-1)
res2 <- substring(str,t2[[1]],t2[[1]]+attr(t2[[1]],
'match.length')-1)
res <- c(res1,res2)
TimeStamp <- as.Date(strsplit(res[grep('TimeStamp=',
res)],'=')[[1]][2],format = '%Y%m%d')
ConsumerID <- strsplit(res[grep('ConsumerID:',res)],
':')[[1]][2]
ProductsCount <- strsplit(res[grep('ProductsCount:',
res)],':')[[1]][2]
ProductID <- matrix(unlist(strsplit(res[grep('ProductID:',
res)],':')),ncol = 2, byrow = TRUE)[,2]
#检验日志文件中是否含有”Barcode:“字样
if(length(grep('Barcode:',res)) == 0){
Barcode <- matrix('',ncol = 2,
nrow = length(ProductID),byrow = TRUE)[,2]
}
else{
Barcode <- matrix(unlist(strsplit(res[grep('Barcode:',
res)],':')),ncol = 2, byrow = TRUE)[,2]
}
#检验日志文件中是否含有”StoreID=“字样
if(length(grep('StoreID=',res)) == 0){
StoreID <- ''
}
else{
StoreID <- unlist(strsplit(res[grep('StoreID=',
res)],'='))[2]
}
#返会函数值
return(data.frame(TimeStamp=TimeStamp,
ConsumerID=ConsumerID,
StoreID=StoreID,
ProductsCount=ProductsCount,
Barcode=Barcode,
ProductID=ProductID))
}
4)将数据转换为数据框格式
library(plyr)
my.data <- ldply(.data = str, .fun = test)
my.data
根据自定义函数中的if条件判断,字符串str中没有“StoreID:”的字样时返回空值,结果中也说明了str中确实没有StoreID:。
大数据集中自定义函数性能检测
到目前为止,该自定义函数已经实现了非结构化数据到结构化数据的转换,下面应用到比较大的数据中,看看该函数的性能如何。
读取数据
library(xlsx)
setwd('C:\\Users\\admin\\Desktop')
data <- read.xlsx2('test.xlsx', sheetIndex = 1)
nrow(data)
str <- as.character(data$Parm)
length(str)
使用自定义函数,生成数据框数据
my.data <- ldply(.data = str, .fun = test)
head(my.data)
nrow(my.data)
函数性能(运行所需时间)
system.time(ldply(.data = str, .fun = test))
本电脑为4GB内存,通过运行14267条日志花费88秒时间,也许性能不算高,还望读者能够提供更好的思维方式和方法。
总结:本文所涉及到的R包和函数
xlsx包
read.xlsx2()
stats包
as.Date()
gregexpr()
substring()
strsplit()
grep()
unlist()
matrix()
plyr包
ldply()