【陆勤践行】写给高级入门者的数据打理攻略(上)
学习如何添加column、计算总和、对结果排序以及数据改造。
强大的能力在带来责任之外,也给我们增添了恼人的复杂性——这一点在R语言身上就表现得淋漓尽致。作为专门针对统计计算工作的开源项目,R语言出色的调查、处理以及分析实力足以把数据驾驭得服服贴贴。然而由于其语法有时候太过复杂,入门者们可能发现自己在掌握了基础知识之后很难进一步提升个人技能水平。
如果大家还未完全熟悉R语言、甚至不能轻松利用它实现最基本的处理任务,我建议各位先查阅其它指导文章、帮助自己积累对R语言的认识。但如果大家已经拥有一定的背景知识,希望能够进一步提升自己的开发技能——或者单纯只是想看看R语言如何完成文章中罗列的四项任务——那么请跟着我继续阅读。
我创建了一套样本数据集,其中包含最近三年以来苹果、谷歌以及微软公司的营收与利润数额。(统计数据来自这三家公司公布的财报结果,其中fy代表财年。)如果大家想一步步跟随本文进行尝试,那么请将下列内容输入(或者直接复制加粘贴)到自己的R终端窗口当中:
fy <- c(2010,2011,2012,2010,2011,2012,2010,2011,2012)
company <- c("Apple","Apple","Apple","Google","Google","Google","Microsoft",
"Microsoft","Microsoft")
revenue <- c(65225,108249,156508,29321,37905,50175,62484,69943,73723)
profit <- c(14013,25922,41733,8505,9737,10737,18760,23150,16978)
companiesData <- data.frame(fy, company, revenue, profit)
以上代码将创建出如下所示的数据框,所有变量都保存在“companiedsData”当中:
fy |
company |
revenue |
profit |
|
1 |
2010 |
Apple |
65225 |
14013 |
2 |
2011 |
Apple |
108249 |
25922 |
3 |
2012 |
Apple |
156508 |
41733 |
4 |
2010 |
|
29321 |
8505 |
5 |
2011 |
|
37905 |
9737 |
6 |
2012 |
|
50175 |
10737 |
7 |
2010 |
Microsoft |
62484 |
18760 |
8 |
2011 |
Microsoft |
69943 |
23150 |
9 |
2012 |
Microsoft |
73723 |
16978 |
(如果大家没有为各行命名,那么R会为其自动添加行数。)
如果大家想在数据框中运行str()函数来查看其结构,则会看到其中的“year”被当作单独的数字来处理,而无法代表应有的“年”这一含义:
str(companiesData)
'data.frame': 9 obs. of 4 variables:
$ fy : num 2010 2011 2012 2010 2011 ...
$ company: Factor w/ 3 levels "Apple","Google",..: 1 1 1 2 2 2 3 3 3
$ revenue: num 65225 108249 156508 29321 37905 ...
$ profit : num 14013 25922 41733 8505 9737 ...
我可能希望把自己的数据按年度进行分组,但大家别误会——我并不打算针对时间进行特殊分析。因此,我会将fy数列转化为一个包含有Rcategory(称之为factor)的column以取代日期,如以下命令所示:
companiesData$fy <- as.factor(companiesData$fy)
现在我们已经做好了各项准备工作。
向现有数据框中添加column
在R语言中,最简单的任务执行方式是向基于一个或多个column的数据框中添加新的column。大家可能希望添加几项现有column以获取平均值或者根据各行现有数据计算出某项特定“result”。
在R语言中我们可以通过多种方式实现这一目标。对于这样一项简单的任务,某些做法显得有些太过复杂——但请大家记住我的建议,对于那些需要处理更高难任务的高级用户来说,看似复杂的做法却往往能收到奇效。
语法一:为新column简单创建一个变量名称,并将其添加到计算公式中作为赋值——举例来说,我们希望在新的column中计算两个现有column的总和:
dataFrame$newColumn <- dataFrame$oldColumn1 + dataFrame$oldColumn2
大家可能已经猜到了,这个新增colume名为“newColumn”,其数值为oldColumn1与oldColumn2各行数值的总和。
我们的这套示例数据框名为“data”,大家可以通过将利润除以营收再乘以100的方式添加一个“margin”(利润率)column:
companiesData$margin <- (companiesData$profit / companiesData$revenue) * 100
运行结果如下:
fy |
company |
revenue |
profit |
margin |
|
1 |
2010 |
Apple |
65225 |
14013 |
21.48409 |
2 |
2011 |
Apple |
108248 |
25922 |
23.94664 |
3 |
2012 |
Apple |
156508 |
41733 |
26.66509 |
4 |
2010 |
|
29321 |
8505 |
29.00651 |
5 |
2011 |
|
37905 |
9737 |
25.68790 |
6 |
2012 |
|
50175 |
10737 |
21.39910 |
7 |
2010 |
Microsoft |
62484 |
18760 |
30.02369 |
8 |
2011 |
Microsoft |
69943 |
23150 |
33.09838 |
9 |
2012 |
Microsoft |
73723 |
16978 |
23.02945 |
哇哦——大家可以看到,margin列中数字的小数点后取值有点太夸张了。
我们可以利用round()函数让计算结果只保留小数点后一位;round()的格式为:
round(number(s)这里填写大家想要保留的小数点位数,数字会自动进行四舍五入)
此,我们打算为margin列中的数字保留小数点后一位:
companiesData$margin <- round(companiesData$margin, 1)
下面就是我们得到的最新结果:
fy |
company |
revenue |
profit |
margin |
|
1 |
2010 |
Apple |
65225 |
14013 |
21.5 |
2 |
2011 |
Apple |
108248 |
25922 |
23.9 |
3 |
2012 |
Apple |
156508 |
41733 |
26.7 |
4 |
2010 |
|
29321 |
8505 |
29.0 |
5 |
2011 |
|
37905 |
9737 |
25.7 |
6 |
2012 |
|
50175 |
10737 |
21.4 |
7 |
2010 |
Microsoft |
62484 |
18760 |
30.0 |
8 |
2011 |
Microsoft |
69943 |
23150 |
33.1 |
9 |
2012 |
Microsoft |
73723 |
16978 |
23.0 |
语法二: R语言的transform()函数是我们达成目标的另一条途径。以下为transform()的基本语法:
dataFrame <- transform(dataFrame, newColumnName =所需公式)
因此,要利用transform()求得两column之和并将结果保存为新column,大家可以利用以下代码来实现:
dataFrame <- transform(dataFrame, newColumn = oldColumn1 + oldColumn2)
要利用transform()向我们的数据框中添加利润率column,大家需要这样操作:
companiesData <- transform(companiesData, margin = (profit/revenue) * 100)
接下来,我们可以利用round()函数将新column中的数值调整为只取小数点后一位。或者,我们也可以采取一步到位的方法,直接创建一个仅保留小数点后一位的新column:
companiesData <- transform(companiesData, margin = round((profit/revenue) * 100, 1))
下面我们来总结round()函数的使用方法:大家可以通过负数形式表达“小数点后的保留位数”。举例来说,round(73842.421,1)保留的就是一位——结果为73842.4,而round(73842.421,-3)则代表取最接近的千位整数,也就是74000。
语法三:R语言的apply()函数顾名思义,会将某个函数“应用”在数据框(或者多种其它R数据结构,但我们目前姑且只关注数据框这一种)当中。它的语法与前两种函数相比要复杂一些,但在某些难度较高的计算过程中会起到重要作用。
apply() 的基本格式为:
dataFrame$newColumn <- apply(dataFrame, 1, function(x) { . . . } )
以上代码行的作用是在数据框内创建一个名为“newColumn”的新column;其中的内容将由{…}的具体代码决定。
下面我们来分别解释以上代码行中各apply()参数的具体含义。第一项apply()参数代表着现有数据框。第二项参数,在本示例中为“1”,意思是“在row中应用一项函数”。如果该参数为2,则代表“在column中应用一项函数”。如果大家打算对当前column而非row进行求和或者求平均值,那么直接修改这条参数就能轻松达到目的。
第三项参数为function(x),很明显具体内容有待写入。具体来说,在其它情况下function()部分将保持不变,但“x”则可以是任何变量名称。那在我们的示例中,x代表着什么呢?它的意思是将所有条目(row或者column)都将由apply()进行遍历。
最后,{…}代表我们要对遍历的每项条目进行何种操作。
请注意,apply()会对所有row或者column内的每一项条目进行查找发实现函数应用。如果大家应用的函数只能作用于数字条目、而当前数据框中某些column的内容并非数字,就可能引发问题。
我们的财务统计样本数据正好符合这种情况。对于数据变量来说,以下代码无法正常生效:
apply(companiesData, 1, function(x) sum(x))
为什么会这样?因为apply()会试图将每一行中的条目进行相加,而公司名称是无法被计入公式的。
为了让apply()只作用于数据框中的特定column中,例如将营收与利润中的数值相加(当然,我承认这样的算法在现实世界的财务分析工作中不太可能发生),我们需要将对应的数据框子集作为我们的第一项参数。也就是说,我们不再让apply()作用于整套数据框,而只让它针对营收与利润两列起效,具体代码如下所示:
apply(companiesData[,c('revenue', 'profit')], 1, function(x) sum(x))
请大家注意其中的[c('revenue', 'profit')],这部分内容位于数据框名称之外,它的作用是强调求和操作“只作用于营收与利润两列”。
下面我们要做的是将apply的计算结果保存在新的column当中,代码如下:
companiesData$sums <- apply(companiesData[,c('revenue', 'profit')], 1, function(x) sum(x))
对于单纯的求和函数来说,这样的效果已经合格了。不过让我们再想想前面提到过的例子——根据每一行中的条目计算公司利润率。在这种情况下,我们需要将利润与营收以特定顺序加以排列——也就是用利润除以营收,而不是相反——然后再乘以100。
我们该如何利用匿名function(x)中让apply()以特定顺序处理多个条目?答案是将我们的匿名函数分别命名为x[1]、x[2]并以此类推,利用它们指代不同的条目:
companiesData$margin <- apply(companiesData[,c('revenue', 'profit')], 1, function(x) { (x[2]/x[1]) * 100 } )
以上代码行创建出一项利用第二条目除以第一条目的匿名函数——由于在companiesData[,c('revenue', 'profit')]当中,营收在前而利润在后,因此第二条目就指代利润而第一条目指代营收。这种方式在我们的示例中是可地的,因为其中只涉及两项条目,即营收与利润——请记住,我们一定要让apply()只作用于这两个对应column。
语法四。mapply()与更简单的sapply()也可以实现将函数应用在数据框某列中的效果——但不一定对所有列都有效——而且最重要的是,跟它们两位打交道时我们不用再考虑x[1]和x[2]这种麻烦事。要利用mapply()在数据框中生成一个新column,我们需遵循以下格式:
dataFrame$newColumn <- mapply(someFunction, dataFrame$column1, dataFrame$column2, dataFrame$column3)
以上代码会将函数someFunction()应用到数据框各行column1、column2以及column3中的数据身上。
请注意,mapply()的第一个参数为“函数名称”,而非公式或者等式。因此如果我们希望像前面提到的那样计算“利润除以营收再乘以100”的结果,则需要首先在自己的函数中写入这一计算,然后再将其交给mapply()。
下面我们看来如何创建名为profitMargin()的函数,其中包含两个变量——在本文的示例中,我们将其称为netIncome(净收入)与revenue(营收)——第一个变量除以第二个变更再乘以100,结果取小数点后一位:
profitMargin <- function(netIncome, revenue)
{ mar <- (netIncome/revenue) * 100
mar <- round(mar, 1)
return(mar)
}
现在我们可以利用这项由用户自己创建的命名函数与mapply()协作了:
companiesData$margin <- mapply(profitMargin, companiesData$profit, companiesData$revenue)
或者,我们也可以在mapply()当中创建一项匿名函数:
companiesData$margin <- mapply(function(x, y) round((x/y) * 100, 1), companiesData$profit, companiesData$revenue)
与transform()相比,mapply()的优势之一在于我们可以使用来自不同数据框的column(请注意,如果各列的长度不同,则可能无法正确起效)。另一项优势是,mapply()在某函数拥有多个参数时,其将函数应用于数据vector时的语法更为精致,如下所示:
mapply(someFunction, vector1, vector2, vector3)
sapply()与mapply()在语法上略有不同,而且如今R语言中的应用类函数也相当丰富。我不打算在本文中再多加讨论,但看到这里,相信大家应该会明白为什么R语言大师Hadley Wickham会创建出一套名为plyr的定制软件包。很显然,其中所选取的函数都使用相当的语法,从而使R语言的功能更趋合理化。(我们在下一节中将谈到plyr。)
如果大家需要进一步了解R语言中的各种应用类选项,我推荐各位阅读由Neil Saunders撰写的《R语言中的‘apply’简介》。
请关注“恒诺新知”微信公众号,感谢“R语言“,”数据那些事儿“,”老俊俊的生信笔记“,”冷🈚️思“,“珞珈R”,“生信星球”的支持!