2.1.4 数据框
前面介绍了矩阵这种数据结构,接下来介绍另外一种长相类似但更常见、功能更丰富的数据结构——数据框(data.frame)。如果说矩阵是数值运算的“明星”,那么数据框就是存储、处理实际数据的“主角”,是实战中的核心主力,有着丰富的变换技法。下面就从基本的创建引用操作出发,展示它变大、变小、变序、变形的各类操作。
数据框是最常见的数据结构,一般来讲,从csv或txt文件读入时就会自动存储为数据框对象。该结构同样拥有表格状,但与矩阵不同的是,矩阵只可以存储一种数据类型(比如,数值型与字符型数据不能同时存在于矩阵中)。而实际中,我们看到的数据表格往往有很多类型,如表2-9所示的电影数据集,不同的数据类型——字符、日期、数值包含在不同列中。想要在R中读入并表示这种数据,数据框就可以派上用场了。需要特别注意的是:数据框的每一列必须是同一种数据类型。如果不符合规定,R会在一定范围内强制转换数据类型,比如输入的一列里既有文本又有数值,它会把该列强制转换成文本格式。
以下就以前面介绍过的movie数据集为例,详细讲解针对数据框对象常用的操作,看看如何创建一个数据框,又如何让它变大、变小、变序、变形“随心玩”。
1.创建
如果已有外部数据源,将外部数据读入R中并赋值给一个对象就可以了;如果需要自己创建数据框,输入数据也很方便,data.frame( )就是专门构造数据框的函数。它的语法是data.frame(col1, col2, col3),即先定义好每列的向量,然后组合成一个数据框即可。
2.汇总
拿到一个陌生的数据集,我们通常想先睹为快,快速了解一下数据情况,这时使用一定的汇总类函数就可满足需求。比如用函数head( )提取数据前6行,就可以看到数据概貌;用函数str( )来展示每列的数据类型,就可以确定是连续的数值还是离散的因子;如果不仅想看部分数据、了解数据类型,还想知道每列数据整体情况、整体的取值范围,这时就可以使用summary( )。 summary( )是清洗数据的必备之选,可自动根据数据类型调整输出结果。具体来说,对于连续数据,它能给出输出数据的分位数值,这样,那些异常值就无处遁形;对于类别数据(以factor存储形式),它能给出输出每个类别的数目统计,这样,各类数据样本数是否平衡就一目了然。
这里要特别提醒的是:这些函数看似简单,但在实操过程中非常重要。当对不同数据源做各种合并、匹配、排序、删除等操作时,时刻要有一种“看到新数据先summary( ),看看是不是符合自己的预期,有没有新异常”的习惯,也就是要有“时刻把握你的数据状态、进度”的意识,否则,待到后面报错时再重新回来查看就费时费力,万一本来匹配有误最后程序还没报错,后果会很严重。所以我们要记得不断查看处理的每一步到底对数据做了什么。
下面是str( )和summary( )函数的演示结果。可以清楚看到movie数据集中哪些变量是数值型num,哪些变量是因子型factor,以及它们各自的取值范围。
实践中,我们拿到的原始数据可能并不满足需要,常常需要增加新列,甚至合并新表进来扩充信息,下面就介绍一些可为你的数据“加瓦添砖”的操作。
首先是在数据框后面增加新列,语法为dat $ column_name=vector,比如如下操作:
以上方法虽然实现简单,但并不适用于大规模增加、合并信息,这种方法更适合数据处理中“新变量生成”阶段。当需要把描述同一对象(即至少有一个共同列)但包含不同信息的几张表合在一起时,就需要另外一个强大的函数——merge( )来实现。该函数的基本用法是:merge(x, y, by),其中x,y分别是要合并的两个数据框,by是它们共有的列。如果这个共有的列在两个数据框中的名字不同,还需要通过by.x,by.y分别定义识别。另外,和数据库中的操作类似,我们经常会发现两个数据框中匹配列的值域并不相同,这时还需要用all类的参数设置以哪个所包含的值域为准,如以下示范代码:
4.变小——数据框的筛选引用
信息稀缺是问题,信息过载也是障碍。对一个数据框缩小聚焦也是常用的技能,主要运用的是引用、筛选功能。基本的引用语法与矩阵类似,使用A[i, j]就可以提取出A中第i行第j个元素,即通过行列号来引用。筛选分为选列和选行,选列很简单,通过符号$配列名即可实现(例如,用movie $ name可以提取出name这一列);选行则一般通过行号,或者条件语句返回一个逻辑结果向量,而后R把其中为TRUE的行摘出来。
5.变序——数据框的内部排序
为了使数据框更加整齐有序,我们拿到数据后可能需要对它先进行排序工作。前面提到对一个向量排序很简单,用sort( )整理就行,可是对于一个大的数据框该如何操作呢?在Excel中,我们经常用到先按某一列排序,再按另一列排序的功能,这样的功能在R中也能实现吗?可以按照下面的做法,其中decreasing参数用来设置是按升序还是降序排列。
6.变形——长表宽表互换
有丰富数据处理经验的读者可能见过这种情形:拿到一个数据集,虽然也是规规整整的表格形状,但它与常见的一行一个观测、一列一个变量的表有点差别,比如表2-10中,每一列分别记录了熊大和水妈在2015—2017年粉丝数(表中数据纯属虚构)。
表2-10中后三列都在时间这个维度上,那么可以直接添加时间维度变量并列出取值吗?这就是著名的宽表变长表问题,变换之后如表211所示。
如何才能把宽表整理成长表呢?可以使用reshape2包中的melt( )函数。下面先来看从表2-10变身为表2-11的详细代码。
melt( )是一个专门把宽表收起来,把多个列变成一个变量下的属性函数,其中,参数id.vars用来设定要把哪列定住不动,然后其他列就会自动收入这一列中;参数variable_name用来设定这个新列的列名。以上的代码就是以id.vars为基准,其他所有的原始变量都排成一个新列,然后原始列下面的数值就会被记录在一列新的value列中。
了解了从宽表变为长表,那么如何从长表变成宽表呢?相对应的函数是dcast( ),它同样在reshape2包中。 dcast( )函数的第一个参数是要变形的数据框,表2-11中就是m Long。第二个采用了公式参数。公式的左边每个变量都会作为结果中的一列,而右边的变量被当成因子类型,每个水平值都会在结果中新生成一个单独列,由此表2-11就变成了表2-10。
7.R中的数据透视表——神奇的ddply( )
在数据分析实战中,Excel最常用的功能应该是vlookup和数据透视表,尤其是后者。那么在R中,什么函数可以完成类似数据透视表的分组计算不同量的功能呢?那就是ddply( )。 ddply( )是plyr包中的函数,常用于数据整理汇总等。下面就介绍一下ddply( )的使用方式。
从以上代码可知,ddply( )是一个把数据框按某种属性分组,然后分别应用同一函数的操作。其基本用法是ddply (.data, .variables, .fun=NULL),第一个参数是要处理的数据框;第二个参数是分组标记,这个标记可以是多个分类变量,例如上面的代码中就同时加入了类型和时长;第三个参数便是一个函数。 ddply( )处理数据的逻辑是按照第二个参数定义的分组变量把数据框分组成多个子数据框,然后作为第三个函数的输入。如果第三个函数需要有额外的参数输入,需要在第四个参数的位置设定,感兴趣的读者可以详细阅读ddply( )的帮助文档。关于这类分组计算统计量的函数还有很多,比如base包中的by( )函数,它运用与ddply( )相似的语法可以完成一些分组计算,但最后会返回一个“by”类对象,它可以被转换为向量但却无法轻易变成数据框对象,大家可以根据个人情况选择合适的函数进行数据处理。感兴趣的读者还可以继续查看其他相关函数。[3]
数据框作为最常用的数据结构,处理解决它的函数多不胜数,完成同一个功能的函数也不一而足,读者在熟悉了前面介绍的基本函数后,还要养成持续关注R相关网站、博客的习惯,了解新包、新功能的开发与应用,思考和比较不同函数的异同,以及时补充自己的函数库,不断优化、简化自己的程序。