千寻的计算机字符,R语千寻 | 非结构化数据--文本数据的读入

原标题:R语千寻 | 非结构化数据--文本数据的读入

cd3b0aefafb6d4123662a45cdd7cdd09.png

上一期(),我们介绍了普通数据读入的方法,一般来说,如果你的数据够规矩,够整齐,按照我们上期介绍的方法,注意注意路径、分隔符等常见问题就能顺利读入R开始分析啦!

然而,现实是残酷的,干净整齐的数据往往可遇不可求,再加上我们周围出现的数据形式越来越丰富,当你想出手玩玩全新出现的数据时,第一个需要解决的问题就是:这些杂乱无章,高度非结构化的数据,我们要怎么把它读入软件。

这就是我们本期要介绍的内容。非结构化数据形式有很多,我们本期重点关注文本数据的读入,并会展示一些常见坑的原因和解决办法。

a47ea128ed5beaf50ae4c519b6359093.png

生活中的文本数据

首先要说明的是,文本数据并不都是非结构化的,有时候虽然你拿到的数据中包含文本,但它们却是整整齐齐归在一个表里,存储为相对规范的结构化形式,比如狗熊会精品案例中为大家提供了各种包含文本的数据,它们大部分都被贴心地规整成csv格式了,这时,我们只需按照上节介绍的读入csv等标准式数据的方法读入,即可进行后续的分析啦!唯一需要注意的就是,如果包含中文文本,往往需要关注一下文件的编码问题,如下所示:

b93aba669c21c6ee4bda74731a23746a.png

前面说到,这整齐的数据总是太少,当我们拿到的数据不太理想该怎么办?这部分就以一个微博数据为例,看看当我们的数据远看像表格,一读密密麻麻小问题不断时,该如何收住它们的小脾气。

b00bc00edbebef0e9c52d23808c50121.png

微博数据示意图

1

用read.table读入

这份weibo数据有9个指标:脱敏的微博名、所在地、性别、粉丝数、关注人数、微博数、创建时间以及个人描述。用文本编辑软件打开是这样的:

2c4db5eac080dff9f57c41afcc472cde.png

(注:数据来源于网络)

你看到时会不会心花怒放,上期介绍的read.table马上可以派上用场了。于是你把文件放入工作空间,并观察它的分隔符后,输入这么一条命令:read.table(“weibo.txt”,sep=“t”) 一运行,发现R给我们先来一个开门红:

这可能是童鞋们在读入数据时经常遇到的Error。这个错误的产生通常是由于read.table只能读入完整的横行竖列的数据,当某些行存在空字段时,就无法构成完整的表格,因此R告诉你某些行并没有该表格每行所需要的8个元素。(问,R咋知道一行应该有几个元素?答,它可以通过数据的前5行判断出来!)所以这个问题的解决需要设置一个参数,fill=T,意思就是让R在所有空字段部分补上一个空格,从而填满这个表格。即输入read.table(“weibo.txt”,sep=“t”,fill=T)

当你输入这个命令时会发现,哇,工作空间终于有了test这个对象啦!它似乎是一个92行8列的数据。万分欣喜,没想到这时候又蹦出一个Warning:

cf9f543016a7281c702b6bd335fc6b8a.png

对待Warning message,不知道是否有些童鞋会选择直接忽略它。你看既然数据已经读入空间,也没有报错,只是出现了Warning,是不是就没有大碍可以放心的用了呢?不然不然。无视Warning的存在很可能会为你整个分析埋下隐患,除非你真的了解这个Warning无关紧要。

比如这里出现的警示,它给我们在拉响什么警报呢?EOF 是end of file的缩写,这个错误大致在告诉我们在文件的末尾有一个被引号括起来的字符串有问题。当我们再去查看这个看似被正常读入的文件的最后一行,是不是有密集恐惧症的童鞋会被吓上一跳:

fdc1db9daa2734ddafc13b1c77e228c3.png

这是什么鬼?!!

再仔细一看,它里面包含了很多nt的换行制表符,而且似乎,原本应该读入到下面行的记录被莫名其妙统统挤在了这一个格子里,是不是R在分着分着抽抽了,只能帮我们到这儿啦?非也非也。如果你把EOF within quoted string这个错误输入Stackoverflow中你会马上看到大家提供的解决办法:将read.table参数中的quote重新设置为空,即加入quote=“”就行。这样完整命令是:weibo= read.table("weibo.txt", sep = "t",fill = T,quote = “”)

这次运行完命令后你就可以终于舒一口气,发现再没有什么error和warning,数据被成功读入了!

所以,回头我们再来看这个quote到底是干啥的呢?为什么它一出场就立刻横扫顽疾呢?其实这是一个设置引用字符串的参数,其默认取值是quote = ""'"(备注:这里单双引号全部为英文半角,下同)表明默认情况下采用单、双引号表示引用字符串,我们将其设置为quote = ""的意思是需要R完全禁用引用。这是啥子意思呢?大家仔细看原始数据,会发现这样一个现象,数据中有些行的最后一列描述中会出现带着单引号的字样,比如下面这样:

99176b648775ae2c88797a00aaddb71d.png

(看来大家很喜欢用英文鸡汤作为自己的描述呀!)单引号有什么特殊性嘛?别忘了,在R中,单引号被认为是字符串开始或结束的标记哟,所以只要遇到单引号,R就会将其后的所有文字当做一个字符,这样就不难理解,我们上面看到的密密麻麻的文字,其实就是因为第92行微博信息的描述中出现了“I'm gonna make him an offer he cannot refuse. ”(貌似是一个《教父》的真爱粉哟)所以后面的文字不管三七二十一就都被当做一个字符圈进来了。当我们设置quote=“”告诉R完全禁用引用时,它就不再把’识别为一个字符开始或结束的标记了,而是乖乖看着换行符换行,看着分隔符分列就好啦!

知道了原因修改了程序,我们就能顺利把数据读入了。如果大家实际操作数据可能会发现,这时候读入的数据行数是103,而不是之前的92行,如果直接忽略Warning就开手分析的话,后果……你懂得。

【题外话】

话说小妹最初在查阅这些错误信息时,曾发现一个惊悚事实:有时R竟然会默默插你一刀,发生这个错误时并不提示Error和Warnings,但你实际上读入的数据比真实的数据行数要少,参见这里的博客[1] 这提示我们,在将一份数据读入R时,最好能在其他软件中先看看它的行列数等基本情况,然后再读入,或许一定程度上能让我们提前察觉某些隐秘错误的产生。

到此为止,我们终于把数据全部读进来了,细心的童鞋可能还记得,第80行数据曾给我们挖过第一个坑,它现在怎么样了?看看下图:

8d48b17164b835bb97a7d15368cd7a89.png

没错,果然是这里出现了异常数据才让我们的表格数据不规整,由于这些行与其他数据有很大不同,我们略施小计就能把它们处理啦!这点留给大家开动脑筋处理,如需参考代码直戳下面的“阅读原文“哟!

2

用readLines读入

看了上面的一大段分析,你是不是对 read.table这个函数对读入数据格式要求之高有些体会,它又需要表格齐整,又需要注意各种符号,有没有比它更懂文本的函数呢?下面要介绍的readLines没准会更得你的心。它必需的参数只有一个文件名,即可实现将文本按行读入,然后每一行作为一个字符存储起来,所以整个文本文件读入就是一个大大的字符串。

37dc4b21f9de8f2f72c1713b3c43c423.png

经过上一小节处处踩雷的过程,看到如此简洁就顺利读入的程序是不是让童鞋们心生喜爱呢?没错,这就是它的优势所在,另外,我们还可以从这里直观的看到各个文字之间的分隔符,方便对各种分隔符空白符不熟悉的童鞋们查看。

很显然,这里的文字是用t(制表符)分隔的,因此我们首先要将文字按该分隔符分开。这就涉及到文本处理中的一个重要分隔函数:strsplit,它的基础用法是strplit(x, split),即可实现根据split将x分割,最终分隔结果以列表形式输出,如下图所示。

b7c2e08cf23206369c3ca4eba3c85fcd.png

大家可以看到,经过函数strsplit的处理,已经把数据文件变成一个大的列表啦。原先每一行的数据变成了列表的一级元素,而该行内的各个指标就成为每个一级元素下面的二级元素了,这就初步完成了把指标分开的任务。

下一步我们该做什么处理呢?是不是就该直接把列表合并成一个数据框了呢?不急,别忘了根据上一小节的分析,我们知道这原始数据里面包含着许多无用的行,我们应该先把这些杂乱的行去掉再合并成表。想想这些问题行的特征是什么呢?对,只在1,2列有文字,后面指标取值基本全是空白,而这反映到我们列表上的特征就是:这些行元素的长度“与众不同”。因此我们应该通过提取列表的元素长度来识别问题行,如下操作:(还记得sapply是什么意思吗?它是对一个列表(tmp)的每个元素施加后面的函数(length),详细用法可以回炉列表【】

a5cbf1a75d5c84bea1991814e42abdca.png

果不其然,这些列表显示原数据有的长度为0,即对应空行;有的长度为1, 即对应一个空格以及一个异常字符,而长度为7则是缺少最后一列描述的行,而长度为8的则是正常行,因此我们可以为长度为7的行后填补一个空格,就能与长度为8的行合并成一个规整的数据框啦!

073c16e686d49dd71087059e2ad0e5e5.png

最终合成的数据框长这样,实现了跟方法1一样的效果。

dabbe746179f3897e6d014a67f8b6a71.png

3

用readLines读入纯文本数据

上一节我们讲了如何将一个接近表格的文本克服重重困难整成一个规整的数据框,当然,文本的分析并不一定非要变成结构化数据,有时候我们可直接对文本做处理,比如常见的分词,分段操作等,这一小节我们就再来展示几个其他的文本预处理操作。

如果大家持续关注狗熊会,一定不会忘记R语千寻早期推出过一期非常火爆的文章《张无忌到底爱谁》()里面涉及到很多对于文本数据的有趣操作,我们这里重温一下它读取数据的过程并给予一些评注。

首先我们仍然通过readLines逐行读取小说文本进入空间,如下图所示:

d12179dcb64b0548584990f704ff8e36.png

然而段落是小说的意群,readLines函数把每行读成了一个字符,破坏了原本的段落结构。因此我们可以做的一个处理就是分隔段落,将每个段落变成一个字符。完成这个目的需要两步走:一、识别段首的标记;二、将段首之间的文本粘在一起成为一个元素。

第一步识别段落的标记。大家可以想想,什么是一个段落开始的标记呢?没错,大概大家小学写作文的时候就知道了:段落开头空两个,此处正如此,因此我们可以通过查找开始包含空格的文本来定位段首标记。这里就要用到一个在R中“查找固定模式数据“的得力助手——grep函数 ,它的用法是grep(pattern,x)即查找x向量中符合pattern模式的字符位置,pattern可以是一些固定字符,也可以采用正则表达式规则。如果不熟悉正则表达式的可以参考这里的介绍help(“regex”),此处我们采用s+这个正则表达来匹配至少包含一个空格的文本,para_head就给出了段落开始地方所在的行数。

另外需要特别注意的是:该函数最后还用到了参数 perl,它用来设定我们采用哪种正则表达式规则。R中有三类正则表达式可以选择:grep(extended=TRUE)是默认的extended正则表达式,grep(extended=FALSE):可以改用basic正则表达式,而grep(perl=TRUE)则使用perl-like正则表达式 ,它可以让R理解perl语言下的正则表达式规则。

提取出段首标记后,我们就要想办法把中间的文字粘住了,这一部分大家可以先自己想想该如何实现这个目的,下面是我们给出的参考方法。

8415e68c6ef566fdb54470cac5276596.png

分好了段,我们就可以继续研究诸如人物关系,情节发展等有趣的问题啦。看到这里还收不住手的童鞋们,可以再回味一下这里【】,看看针对文本这种“不拘一格”的数据,你还有哪些脑洞大开的分析呢?

好了~以上就是本期的内容啦。数据读入是一个说简单也简单,说难也可以很难的事,解决好这个问题可能需要你对很多R底层编码规则,实现原理的了解,如果数据够乱,遇到一片片的error,warning也很有可能,这时候大家就要学会深呼吸仔细想,Calm down, and welcome to the real world!

【附录】

[1] https://biowize.wordpress.com/2013/10/08/quotation-marks-and-rs-read-table-function/ 。

往期精彩内容

责任编辑:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值