R语言教程:写给高级入门者的数据打理攻略

译文
开发 开发工具 后端
如果大家还未完全熟悉R语言、甚至不能轻松利用它实现最基本的处理任务,我建议各位先查阅其它指导文章、帮助自己积累对R语言的认识。但如果大家已经拥有一定的背景知识,希望能够进一步提升自己的开发技能——或者单纯只是想看看R语言如何完成文章中罗列的四项任务——那么请跟着我继续阅读。

学习如何添加column、计算总和、对结果排序以及数据改造。

强大的能力在带来责任之外,也给我们增添了恼人的复杂性——这一点在R语言身上就表现得淋漓尽致。作为专门针对统计计算工作的开源项目,R语言出色的调查、处理以及分析实力足以把数据驾驭得服服贴贴。然而由于其语法有时候太过复杂,入门者们可能发现自己在掌握了基础知识之后很难进一步提升个人技能水平。

如果大家还未完全熟悉R语言、甚至不能轻松利用它实现最基本的处理任务,我建议各位先查阅其它指导文章、帮助自己积累对R语言的认识。但如果大家已经拥有一定的背景知识,希望能够进一步提升自己的开发技能——或者单纯只是想看看R语言如何完成文章中罗列的四项任务——那么请跟着我继续阅读。

我创建了一套样本数据集,其中包含最近三年以来苹果、谷歌以及微软公司的营收与利润数额。(统计数据来自这三家公司公布的财报结果,其中fy代表财年。)如果大家想一步步跟随本文进行尝试,那么请将下列内容输入(或者直接复制加粘贴)到自己的R终端窗口当中:

  1. fy <- c(2010,2011,2012,2010,2011,2012,2010,2011,2012)  
  2. company <- c("Apple","Apple","Apple","Google","Google","Google","Microsoft",  
  3. "Microsoft","Microsoft")  
  4. revenue <- c(65225,108249,156508,29321,37905,50175,62484,69943,73723)  
  5. profit <- c(14013,25922,41733,8505,9737,10737,18760,23150,16978)  
  6. 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
Google
29321
8505
5
2011
Google
37905
9737
6
2012
Google
50175
10737
7
2010
Microsoft
62484
18760
8
2011
Microsoft
69943
23150
9
2012
Microsoft
73723
16978

(如果大家没有为各行命名,那么R会为其自动添加行数。)

如果大家想在数据框中运行str()函数来查看其结构,则会看到其中的“year”被当作单独的数字来处理,而无法代表应有的“年”这一含义:

  1. str(companiesData)  
  2. 'data.frame': 9 obs. of 4 variables:  
  3. $ fy : num 2010 2011 2012 2010 2011 ...  
  4. $ company: Factor w/ 3 levels "Apple","Google",..: 1 1 1 2 2 2 3 3 3  
  5. $ revenue: num 65225 108249 156508 29321 37905 ...  
  6. $ profit : num 14013 25922 41733 8505 9737 ... 

我可能希望把自己的数据按年度进行分组,但大家别误会——我并不打算针对时间进行特殊分析。因此,我会将fy数列转化为一个包含有Rcategory(称之为factor)的column以取代日期,如以下命令所示:

  1. companiesData$fy <- as.factor(companiesData$fy) 

现在我们已经做好了各项准备工作。

向现有数据框中添加column

在R语言中,最简单的任务执行方式是向基于一个或多个column的数据框中添加新的column。大家可能希望添加几项现有column以获取平均值或者根据各行现有数据计算出某项特定“result”。

在R语言中我们可以通过多种方式实现这一目标。对于这样一项简单的任务,某些做法显得有些太过复杂——但请大家记住我的建议,对于那些需要处理更高难任务的高级用户来说,看似复杂的做法却往往能收到奇效。

语法一:为新column简单创建一个变量名称,并将其添加到计算公式中作为赋值——举例来说,我们希望在新的column中计算两个现有column的总和:

  1. dataFrame$newColumn <- dataFrame$oldColumn1 + dataFrame$oldColumn2 

大家可能已经猜到了,这个新增colume名为“newColumn”,其数值为oldColumn1与oldColumn2各行数值的总和。

我们的这套示例数据框名为“data”,大家可以通过将利润除以营收再乘以100的方式添加一个“margin”(利润率)column:

  1. 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
Google
29321
8505
29.00651
5
2011
Google
37905
9737
25.68790
6
2012
Google
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列中的数字保留小数点后一位:

  1. 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
Google
29321
8505
29.0
5
2011
Google
37905
9737
25.7
6
2012
Google
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()的基本语法:

  1. dataFrame <- transform(dataFrame, newColumnName =所需公式) 

因此,要利用transform()求得两column之和并将结果保存为新column,大家可以利用以下代码来实现:

  1. dataFrame <- transform(dataFrame, newColumn = oldColumn1 + oldColumn2) 

要利用transform()向我们的数据框中添加利润率column,大家需要这样操作:

  1. companiesData <- transform(companiesData, margin = (profit/revenue) * 100) 

接下来,我们可以利用round()函数将新column中的数值调整为只取小数点后一位。或者,我们也可以采取一步到位的方法,直接创建一个仅保留小数点后一位的新column:

  1. 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() 的基本格式为:

  1. 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的内容并非数字,就可能引发问题。

我们的财务统计样本数据正好符合这种情况。对于数据变量来说,以下代码无法正常生效:

  1. apply(companiesData, 1, function(x) sum(x)) 

为什么会这样?因为apply()会试图将每一行中的条目进行相加,而公司名称是无法被计入公式的。

为了让apply()只作用于数据框中的特定column中,例如将营收与利润中的数值相加(当然,我承认这样的算法在现实世界的财务分析工作中不太可能发生),我们需要将对应的数据框子集作为我们的第一项参数。也就是说,我们不再让apply()作用于整套数据框,而只让它针对营收与利润两列起效,具体代码如下所示:

  1. apply(companiesData[,c('revenue', 'profit')], 1, function(x) sum(x)) 

请大家注意其中的[c('revenue', 'profit')],这部分内容位于数据框名称之外,它的作用是强调求和操作“只作用于营收与利润两列”。

下面我们要做的是将apply的计算结果保存在新的column当中,代码如下:

  1. companiesData$sums <- apply(companiesData[,c('revenue', 'profit')], 1, function(x) sum(x)) 

对于单纯的求和函数来说,这样的效果已经合格了。不过让我们再想想前面提到过的例子——根据每一行中的条目计算公司利润率。在这种情况下,我们需要将利润与营收以特定顺序加以排列——也就是用利润除以营收,而不是相反——然后再乘以100。

我们该如何利用匿名function(x)中让apply()以特定顺序处理多个条目?答案是将我们的匿名函数分别命名为x[1]、x[2]并以此类推,利用它们指代不同的条目:

  1. 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,我们需遵循以下格式:

  1. dataFrame$newColumn <- mapply(someFunction, dataFrame$column1, dataFrame$column2, dataFrame$column3) 

以上代码会将函数someFunction()应用到数据框各行column1、column2以及column3中的数据身上。

请注意,mapply()的第一个参数为“函数名称”,而非公式或者等式。因此如果我们希望像前面提到的那样计算“利润除以营收再乘以100”的结果,则需要首先在自己的函数中写入这一计算,然后再将其交给mapply()。

下面我们看来如何创建名为profitMargin()的函数,其中包含两个变量——在本文的示例中,我们将其称为netIncome(净收入)与revenue(营收)——第一个变量除以第二个变更再乘以100,结果取小数点后一位:

  1. profitMargin <- function(netIncome, revenue)  
  2. { mar <- (netIncome/revenue) * 100  
  3. mar <- round(mar, 1)  
  4. return(mar)  

现在我们可以利用这项由用户自己创建的命名函数与mapply()协作了:

  1. companiesData$margin <- mapply(profitMargin, companiesData$profit, companiesData$revenue) 

或者,我们也可以在mapply()当中创建一项匿名函数:

  1. companiesData$margin <- mapply(function(x, y) round((x/y) * 100, 1), companiesData$profit, companiesData$revenue) 

与transform()相比,mapply()的优势之一在于我们可以使用来自不同数据框的column(请注意,如果各列的长度不同,则可能无法正确起效)。另一项优势是,mapply()在某函数拥有多个参数时,其将函数应用于数据vector时的语法更为精致,如下所示:

  1. mapply(someFunction, vector1, vector2, vector3) 

sapply()与mapply()在语法上略有不同,而且如今R语言中的应用类函数也相当丰富。我不打算在本文中再多加讨论,但看到这里,相信大家应该会明白为什么R语言大师Hadley Wickham会创建出一套名为plyr的定制软件包。很显然,其中所选取的函数都使用相当的语法,从而使R语言的功能更趋合理化。(我们在下一节中将谈到plyr。)

如果大家需要进一步了解R语言中的各种应用类选项,我推荐各位阅读由Neil Saunders撰写的《R语言中的‘apply’简介》。

#p#

对分组数据进行摘要

在我们的数据框中,大家可以轻松利用max(companiesData$margin)找到最高利润率数字。要将最高利润数值分配给名为highestMargin的变量,我们只需使用以下简单代码:

  1. highestMargin <- max(companiesData$margin) 

返回的结果为:

[1] 33.09838

不过大家还不知道除了该数值之外、同一行内的其它变量都有什么,例如年份及公司名称。

为了在查看最高利润率所在行的全部内容而不只是是数值,我们可以采用以下代码:

  1. highestMargin <- companiesData[companiesData$margin == max(companiesData$margin),] 

或者尝试另一种方式:

  1. highestMargin <- subset(companiesData, margin==max(margin)) 

不过如果我们打算查找每一家公司最高利润率数值所在行的内容,又该怎么办呢?这就涉及到分组进行函数应用了——在R语言中这被称为factor。

由Hadley Wickham创建的plyr软件包将这类任务以“分割-应用-结合”这种三步方式进行处理:通过一种或多种factor将数据集进行分割,而后应用某项函数,最后将结果整合回数据集当中。

plyr的ddply()函数能够在数据框内实现“分割-应用-结合”三步操作,而后再根据结果生成一个新的独立数据框。这也正是ddply()函数前两个字母“dd”的含义:输入一个data frame,再得到另一个data frame。Plyr包中囊括了一整套“ply”函数,其中包括:用于输入数组、获取清单的alply,输入清单、获取数据框的idply等等。

要使用ddply(),大家首先需要安装安装plyr软件包(如果此前尚未安装的话),方式如下:

  1. install.packages("plyr") 

接下来,如果大家还没有在当前R会话中载入plyr,那么输入以下命令:

  1. library("plyr") 

利用ddply根据不同factor分割数据框并应用函数的具体格式为:

  1. ddply(mydata, c('column name of a factor to group by', 'column name of the second factor to group by'), summarize OR transform, newcolumn = myfunction(column name(s) I want the function to act upon)) 

下面具体来解读上述内容。函数ddply()的第一个参数是原始数据框名称,第二个参数则是我们打算划分为子集的column名称。第三个参数用于告知ddply()应该返回数据点结果(即总和)还是在整套数据框的新column中根据factor的具体要求显示每行数据点内容。最后,第四项参数用于命名新column并列出我们希望ddply()使用的函数清单。

如果大家不想在引号中填写那么多column名称,也可以采用另一种备用语法——在column名称之前加个点(.),相信很多朋友都熟悉这种处理方式:

  1. myresult <- ddply(mydata, .(column name of factor I'm splitting by, column name second factor I'm splitting by), summarize OR transform, newcolumn = myfunction(column name I want the function to act upon)) 

要获取每一家公司的最高利润率,我们只需要根据一项factor对数据框进行分割——company。由于只需要获取各company的最高利润率与公司名称,我们可以在第三项参数中采用summarize:

  1. highestProfitMargins <- ddply(companiesData, .(company), summarize, bestMargin = max(margin)) 

(这里我们将结果分配给变量highestProfiMargins。)

语法备注: 即便我们只使用了一项factor,为了避免将column名称纳入引用,我们也需要将其放置在“点”之后的括号里。不过如果大家使用的是引号且factor只有一项,则无需使用括号:

  1. highestProfitMargins <- ddply(companiesData, 'company', summarize, bestMargin = max(margin)) 

无论采用哪种方式,最后我们都会获得一份全新数据框,其中列出每家公司的最高利润率:

 
company
bestMargin
1
Apple
26.7
2
Google
29.0
3
Microsoft
33.1

Summarize不会提供来自原始数据框中其它列中的任何信息。各家公司的最高利润率出现在哪一年?单纯依靠summarize,我们无法得到这一答案。

如果大家需要列出其它column数据,则可以把“summarize”替换为“transform”。这样一来,我们的现有数据框中将出现一个新column,且重复列举每一家公司的最高利润率:

  1. highestProfitMargins <- ddply(companiesData, 'company', transform, bestMargin = max(margin)) 

 
fy
company
revenue
profit
margin
bestMargin
1
2010
Apple
65225
14013
21.5
26.7
2
2011
Apple
108248
25922
23.9
26.7
3
2012
Apple
156508
41733
26.7
26.7
4
2010
Google
29321
8505
29.0
29.0
5
2011
Google
37905
9737
25.7
29.0
6
2012
Google
50175
10737
21.4
29.0
7
2010
Microsoft
62484
18760
30.0
33.1
8
2011
Microsoft
69943
23150
33.1
33.1
9
2012
Microsoft
73723
16978
23.0
33.1

请注意,这里的margin column中显示的是每家公司每年的实际利润率,而bestMargin中则重复显示各公司最高年份获得的利润率。

要在上面这份数据框中找出哪一年利润率最高,我们只能逐行比较来亲自发现答案。

ddply()允许大家一次应用多项函数,实例如下:

  1. myResults <- ddply(companiesData, 'company', transform, highestMargin = max(margin), lowestMargin = min(margin)) 

我们得到的结果是:

 
fy
company
revenue
profit
margin
highestMargin
lowestMargin
1
2010
Apple
65225
14013
21.5
26.7
21.5
2
2011
Apple
108248
25922
23.9
26.7
21.5
3
2012
Apple
156508
41733
26.7
26.7
21.5
4
2010
Google
29321
8505
29.0
29.0
21.4
5
2011
Google
37905
9737
25.7
29.0
21.4
6
2012
Google
50175
10737
21.4
29.0
21.4
7
2010
Microsoft
62484
18760
30.0
33.1
23.0
8
2011
Microsoft
69943
23150
33.1
33.1
23.0
9
2012
Microsoft
73723
16978
23.0
33.1
23.0

不过在某些情况下,我们想要的是一份全新的数据框,其中整行内容都符合最高利润率这一factor。要实现这一目的,我们先来看一种较为复杂的语法表达方式:

  1. highestProfitMargins <- ddply(companiesData, 'company', function(x) x[x$margin==max(x$margin),]) 

 
fy
company
revenue
profit
margin
1
2012
Apple
156508
41733
26.7
2
2010
Google
29321
8505
29.0
3
2011
Microsoft
69943
23150
33.1

看起来好像有点吓人,但只要我们逐步拆分解读,大家就会发现其实它还是挺容易理解的。

以上代码中的ddply(companiesData, “company”, function(x))部分相信大家已经很熟悉了:companiesData是指原始数据框,function(x)表示的是匿名(暂时未命名)的函数。因此,惟一不太眼熟的部分就只有:

  1. x[x$margin==max(x$margin),] 

这部分代码是在提取x中的一个子集。在示例中,x代表的是已经被传递到匿名函数内部的数据框。括号内的公式意思是:我希望匹配x$margin数值与x$margin最大数值相等的行。x$margin==max(x$margin)后面的逗号是在要求R语言返回匹配行中的所有列内容,因为这里没有指定任何特定列。作为替代方案,我们也可以要求只返回一列或者几列,而非所有列。

需要注意的是:

  1. companiesData[companiesData$margin==max(companiesData$margin),] 

这部分代码本身并没有使用ddply(),因此得到的是最高全局利润率而非各公司最高利润率。不过由于匿名函数被加入到ddply()声明当中,而ddply()又已经按照公司名称对数据框进行了分割,所以返回的匹配行仍然符合按不同公司计算的要求。

关于ddply(),另外需要说明的一点是:尽管其设计初衷在于“分割-应用-结合”——也就是将一项函数应用到不同数据category当中——大家还可以利用它一次性在整套数据框中应用一项函数。顺着这个思路,我们可以再次尝试利用ddply()声明对各公司最高利润率进行汇总:

  1. highestProfitMargins <- ddply(companiesData, 'company', summarize, bestMargin = max(margin)) 

要利用ddply()查看整个数据集中的最高利润率,而非根据公司category划分的子集,我们可以在第二个参数中输入NULL使分割factor变为无效:

  1. highestProfitMargin <- ddply(companiesData, NULL, summarize, bestMargin = max(margin)) 

这种处理方式与max(companiesData$margin)相比显然要复杂得多,但有时候大家可能会发现plyr中的“ply”家族不听使唤,这时上述代码就非常重要了。虽然在整套数据结构中应用多种函数并保持语法一致性非常诱人,但代码能够发挥效果才是最重要的,因此我们必须留上这么一手。

#p#

奖励关:按日期范围分组

如果大家手头有一系列日期外加相关数值,这里介绍一种按照日期范围——例如周、月、季度或者年——对其进行分组的超简便处理方式:R语言的cut()函数。

假设vector中存在以下示例数据:

  1. vDates <- as.Date(c("2013-06-01", "2013-07-08", "2013-09-01", "2013-09-15")) 

那么执行后的结果为:

  1. [1] "2013-06-01" "2013-07-08" "2013-09-01" "2013-09-15" 

这里as.Data()函数的作用非常重要;如果没有它,R语言会认为以上内容仅仅是数字串而非日期对象。

如果大家希望获得按月排序的另一个vector,则可以根据以下基本语法使用cut()函数:

  1. vDates.bymonth <- cut(vDates, breaks = "month"

执行后的结果为:

[1] 2013-06-01 2013-07-01 2013-09-01 2013-09-01

Levels: 2013-06-01 2013-07-01 2013-08-01 2013-09-01

将二者在数据框中相结合可能会让结果看起来更为清晰:

  1. dfDates <- data.frame(vDates, vDates.bymonth) 

执行后的结果为:

 
vDates
vDates.bymonth
1
2013-06-01
2013-06-01
2
2013-07-08
2013-07-01
3
2013-09-01
2013-09-01
4
2013-09-15
2013-09-01

新的column给出每个月的开始日期,这使得按月划分变得更容易。

上面提到的简便方法来自在读博士生Mollie Taylor发表的博文《如何在R语言中按周或按月划分数据》,大家光靠阅读cut()帮助文档肯定学不着这样的妙招。如果各位曾经分析或者划分过以日期为基础的数据,那么这篇非常实用的博文绝对值得一读。感兴趣的朋友还可以点击此处下载她的代码成果。

对结果进行排序

要对某列数值进行简单排序,大家可以利用order()函数达到目的,具体方式如下:

  1. companyOrder <- order(companiesData$margin) 

这些代码描述了我们该如何重新排列自己的行并生成一份关于行号的列表:

6 1 9 2 5 3 4 7 8

大家可能并不打算利用行号来重新排序,而是希望以具体数据作为排序依据。在这种情况下,各位可以利用以下代码对数据框内的各行进行重新排序:

  1. companiesOrdered <- companiesData[companyOrder,] 

这里的companyOrder是我们先前所创建的排列结果。或者,大家也可以通过单独代码行显然先前排列结果(但看起来可能不太直观):

  1. companiesOrdered <- companiesData[order(companiesData$margin),] 

如果大家忘记了在新排序之后加上“逗号”,那么很可能发生执行错误——这是因为R语言需要了解返回的结果显示在哪些列当中。我要再次强调,单单一个逗号(后面什么都不加)在默认情况下代表“所有列”。当然,大家也可以像下列代码那样为其指定特殊列:

  1. companiesOrdered <- companiesData[order(companiesData$margin),c("fy", "company")] 

如果想要实现降序排列,我们只需在companyOrder中的列排序表达之前加个“减号”:

  1. companyOrder <- order(-companiesData$margin) 

接下来:

  1. companiesOrdered <- companiesData[companyOrder,] 

将以上两者结合,我们就得到了下面这条单独的声明:

  1. companiesOrdered <- companiesData[order(-companiesData$margin),] 

 
fy
company
revenue
profit
margin
8
2011
Microsoft
69943
23150
33.1
7
2010
Microsoft
62484
18760
30.0
4
2010
Google
29321
8505
29.0
3
2012
Apple
156508
41733
26.7
5
2011
Google
37905
9737
25.7
2
2011
Apple
108249
25922
23.9
9
2012
Microsoft
73723
16978
23.0
1
2010
Apple
65225
14013
21.5
6
2012
Google
50175
10737
21.4

请注意,现在我们能够看到最左侧的原始列数已经被重新排序。

如果大家希望将某一列按升序排列、另一列按降序排列,那么只需在降序对应的列前面加个“减号”。通过这种方式,我们可以先按年份对数据进行升序排列,然后再通过降序排列看看哪家公司当年获得了最高利润率:

  1. companiesData[order(companiesData$fy, -companiesData$margin),] 

如果大家不希望顶着美元大标记为每个column输入数据框名称,R语言的with()函数可以将数据框名称作为第一项参数并通过以下命令让它自动填充到随后的对应参数当中:

  1. companiesOrdered <- companiesData[with(companiesData, order(fy, -margin)),] 

尽管这能有效减少输入内容,但也可能会让我们的代码变得难以理解,特别是对于那些经验不多的R用户而言。

#p#

数据改造:wide与long

R语言中包含多种不同分析工具——其中包括一些图形包——它们对数据的格式有着特殊要求。举个最常见(也是最重要)的例子:我们经常需要在R数据操作任务当中切换“wide”与“long”格式,从而满足特定分析或者图形功能的要求。例如,在long格式下,我们能够更轻松地利用热门图形包ggplot2()实现数据可视化。wide意味着大家可以在每一行中对多个column进行测算,如下图所示:

 
fy
company
revenue
profit
margin
1
2010
Apple
65225
14013
21.5
2
2011
Apple
108249
25922
23.9
3
2012
Apple
156508
41733
26.7
4
2010
Google
29321
8505
29.0
5
2011
Google
37905
9737
25.7
6
2012
Google
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

每一行中都包含营收数值列、利润列以及经过计算得出的利润率列。

long意味着我们只能测算每列中的一项数值,但可能跨越多种category,具体结果如下所示:

 
fy
company
variable
value
1
2010
Apple
revenue
65225.0
2
2011
Apple
revenue
108249.0
3
2012
Apple
revenue
156508.0
4
2010
Google
revenue
29321.0
5
2011
Google
revenue
37905.0
6
2012
Google
revenue
50175.0
7
2010
Microsoft
revenue
62484.0
8
2011
Microsoft
revenue
69943.0
9
2012
Microsoft
revenue
73723.0
10
2010
Apple
profit
14013.0
11
2011
Apple
profit
25922.0
12
2012
Apple
profit
41733.0
13
2010
Google
profit
8505.0
14
2011
Google
profit
9737.0
15
2012
Google
profit
10737.0
16
2010
Microsoft
profit
18760.0
17
2011
Microsoft
profit
23150.0
18
2012
Microsoft
profit
16978.0
19
2010
Apple
margin
21.5
20
2011
Apple
margin
23.9
21
2012
Apple
margin
26.7
22
2010
Google
margin
29.0
23
2011
Google
margin
25.7
24
2012
Google
margin
21.4
25
2010
Microsoft
margin
30.0
26
2011
Microsoft
margin
33.1
27
2012
Microsoft
margin
23.0

请相信我(我也是经过艰难探索才发现这一结论的):一旦大家彻底弄清了wide与long的概念,那么在R语言中实现相关功能就变得非常轻松了。

如果大家还没明白什么是category、什么是measurement,我在这里提供一点建议:不要把太多精力放在定义上——什么“long”数据框应该只在每一行中包含惟一一个“值”,这都是屁话。为什么?对于那些拥有丰富其它编程语言经验的用户来说,所有东西看起来都像是“值”。如果年份为2011,公司名称为Google,那么2011不就是year值、Google不就是company值么?

不过对于数据改造来说,“值”这一术语的意义确实有所区别。

我希望认为“long”数据框中每行只拥有一项“可用于对自身进行划分的measurement”。对于某些财务统计结果,“long”数据框中的measurement能否用于划分2010、2011或者2012等年份数值?不能,因为年份属于我们提前设置好的特殊category,而category决定了measurement所能影响到的对象。

即使我将财务结果按季度进行划分——而季度1、2、3、4看起来很像是数字或者叫“值”——将其视为“值”并根据季度数字的变化对数据框进行划分同样是没有意义的。季度属于category,也就是R语言中的一种factor——虽然它是数据分组的依据之一,但我们不会利用它对其自身进行划分。

这一点在科学实验领域表现得尤为明显。如果大家正在对一种新型降胆固醇药物进行测试,那么预先设置的category可能包括患者的年龄、性别以及服用的是药物还是安慰剂。在这里,measurement(或者根据measurement计算出的结果)就是我们的最终结果:整体胆固醇水平变动、低密度胆固醇情况与高密度胆固醇情况等。不过无论具体数据是什么,大家都应该至少划定一个category与一个measurement,这样才能创建出long数据框。

在我们所使用的实验数据中,我设定的category分别为fy与company,而measurement则分别为revenue、profit与margin。

现在我们提出的是大家在由wide向long进行数据改造时需要理解的下一个概念:由于大家希望每行只存在一个measurement,因此需要通过添加一个column来表明每个值的measurement类型。

在我的现有wide格式当中,column标题已经注明了measurement类型,包括revenue、profit以及margin。不过由于我只能为每行选择一个measurement而不是三个,因此需要添加一个新column以标明使用的具体是哪种measurement。

通过举例的方式能够更清晰地表达我的意思。下面来看“wide”行示例:

fy
company
revenue
profit
margin
2010
Apple
65225
14013
21.48409

下面是如何让每行只存在一个measurement——通过创建三个“long”行:

fy
company
financialCategory
value
2010
Apple
revenue
65225
2010
Apple
profit
14013
2010
Apple
margin
21.5

现在financialCategory列告诉我们每个值的具体measurement类型。这样一来,“值”这一术语就变得更有意义了。

最后,我们准备了一些代码将数据框由“wide”式改造为“long”式。与R语言中的其它操作方式一样,我们同样可以通过多种途径实现这一目标。要使用reshape2,大家首先需要安装该软件包:

install.packages("reshape2")

通过以下代码进行加载:

library(reshape2)

接下来使用reshape2提供的melt()函数。Melt()通过以下格式将结果分配给名为longData的变量:

longData <- melt(your original data frame, a vector of your category variables)

这就是melt()的全部要求:数据框名称、category变量名称。不过大家也可以选择性添加其它几种变量,包括measurement变量(如果没有添加此变量,melt()会假设所有剩余列都属于measurement列)以及为新category列起好的名称。

好了,再次使用数据框中的样本数据,wide-long转换代码如下:

companiesLong <- melt(companiesData, c("fy", "company"))

现在的执行结果:

 
fy
company
variable
value
1
2010
Apple
revenue
65225.0
2
2011
Apple
revenue
108249.0
3
2012
Apple
revenue
156508.0
4
2010
Google
revenue
29321.0
5
2011
Google
revenue
37905.0
6
2012
Google
revenue
50175.0
7
2010
Microsoft
revenue
62484.0
8
2011
Microsoft
revenue
69943.0
9
2012
Microsoft
revenue
73723.0
10
2010
Apple
profit
14013.0
11
2011
Apple
profit
25922.0
12
2012
Apple
profit
41733.0
13
2010
Google
profit
8505.0
14
2011
Google
profit
9737.0
15
2012
Google
profit
10737.0
16
2010
Microsoft
profit
18760.0
17
2011
Microsoft
profit
23150.0
18
2012
Microsoft
profit
16978.0
19
2010
Apple
margin
21.5
20
2011
Apple
margin
23.9
21
2012
Apple
margin
26.7
22
2010
Google
margin
29.0
23
2011
Google
margin
25.7
24
2012
Google
margin
21.4
25
2010
Microsoft
margin
30.0
26
2011
Microsoft
margin
33.1
27
2012
Microsoft
margin
23.0

在大家理解了基本概念之后,整个转换过程还是非常简单的。现在,代码已经将除了fy与company之外的所有列都默认视为measurement——也就是我们希望进行划分的条目。

如果愿意,大家也可以把代码写得更长,特别是对于那些习惯于通过这种方式帮助自己捋顺思路的朋友。下面的声明列出了数据框中的全部列,将它们分配给id.vars或者measure.vars,而且新column名称也不再是默认的“variable”以及“value”:

我感觉reshape2调用category变量“id.vars”(简称ID变量)而非category或者factor的处理方式有点令人困惑,但经过一段时间相信大家会慢慢适应。Reshape2中的measurement变量可以更直观地被称为measure.vars。

companiesLong <- melt(companiesData, id.vars=c("fy", "company"),

measure.vars=c("revenue", "profit", "margin"),

variable.name="financialCategory", value.name="amount")

执行结果如下所示:

 
fy
company
financialCategory
amount
1
2010
Apple
revenue
65225.0
2
2011
Apple
revenue
108249.0
3
2012
Apple
revenue
156508.0
4
2010
Google
revenue
29321.0
5
2011
Google
revenue
37905.0
6
2012
Google
revenue
50175.0
7
2010
Microsoft
revenue
62484.0
8
2011
Microsoft
revenue
69943.0
9
2012
Microsoft
revenue
73723.0
10
2010
Apple
profit
14013.0
11
2011
Apple
profit
25922.0
12
2012
Apple
profit
41733.0
13
2010
Google
profit
8505.0
14
2011
Google
profit
9737.0
15
2012
Google
profit
10737.0
16
2010
Microsoft
profit
18760.0
17
2011
Microsoft
profit
23150.0
18
2012
Microsoft
profit
16978.0
19
2010
Apple
margin
21.5
20
2011
Apple
margin
23.9
21
2012
Apple
margin
26.7
22
2010
Google
margin
29.0
23
2011
Google
margin
25.7
24
2012
Google
margin
21.4
25
2010
Microsoft
margin
30.0
26
2011
Microsoft
margin
33.1
27
2012
Microsoft
margin
23.0

数据改造:long到wide

数据框在经过“回炉”之后,也可以被“改造”成我们想要的任何形式。Reshape2的dcast()函数将“long”数据框作为输入源,且允许大家在返回结果中创建一个经过改造的数据框。(这有点类似于能够返回数组、向量或者矩阵的acast()函数。)我见过的对利用dcast()实现long到wide转化的最好解释来自Winston Chang所著的《R图形烹饪指南》:

“指定ID变量(仍然存在于列当中)以及variable变量(已经‘移动到顶端’)。要实现这一目的,我们需要利用一项公式,其中ID变量位于波浪线(~)之前,而variable变量则位于波浪线之后。”

换句话来说,我们可以通过更简单的方式看待自己创建的数据结构。我们希望在每一行中重复的变量就是“ID变量”,而那些应该成为列标题的就是“variable变量”。

现在来看原始数据框中的这一行,下面列出的是“wide”版本:

fy
company
revenue
profit
margin
2010
Apple
65225
14013
21.5

在财年与公司下面列出的都是与特定年份及公司名称相关的measurement。正因为如此,fy与company属于ID变量;而revenue、profit以及margin属于被“移动到顶端”作为列标题的“variable变量”。

如何根据数据的long版本重新创建wide数据框?

如果大家拥有两列ID变量与一列variable变量,那么代码如下所示:

  1. wideDataFrame <- dcast(longDataFrame, idVariableColumn1 + idVariableColumn2 ~ variableColumn, value.var="Name of column with the measurement values"

dcast()会将long数据框的名称作为第一项参数。大家需要遵循以下语法创建公式来充当第二项参数:

id variables ~ variable variables

其中ID与measurement变量由波浪线隔开。如果波浪线两端的变量不止一个,那么各变量彼此之间要用“加号”分隔。

第三项参数用于让dcast()将包含measurement值的列名称分配给value.var。

因此,要利用dcast()根据companiesLong生成原始wide数据框,我们需要使用以下代码:

  1. companiesWide <- dcast(companiesLong, fy + company ~ financialCategory, value.var="amount"

下面我们来分步解读以上代码内容:companiesLong是我们long数据框的名称;fy与company是我们想要在新wide数据框中继续作为每行条目所保留的列;我希望在financialCategory列中为每个不同category创建一个新column,正如Chang所说;我还需要财务category的所有实际measurement都来自数量列。

总结

希望这份攻略能帮助大家在利用R语言打理数据工作的征途上解决一些实际问题。当然,借靠这些是远远不够的,祝愿各位在R语言的学习之路上高歌猛进。

原文链接:4 data wrangling tasks in R for advanced beginners

责任编辑:林师授 来源: 51CTO
相关推荐

2011-07-25 17:31:49

iPhone Objective-

2019-03-20 14:44:53

数据库MySQLExcel

2021-03-15 13:58:42

微软开源Go

2010-03-09 11:15:28

Python语言教程

2010-03-09 10:59:42

Python语言教程

2020-07-09 15:21:58

大数据RStudioR语言

2023-07-04 07:45:11

gogRPC服务

2019-09-27 14:33:34

2024-11-22 10:45:20

2010-06-18 16:56:50

UML建模语言

2013-03-28 10:22:33

数据库关系型数据库数据库设计

2019-07-29 16:05:48

前端DockerNode.js

2020-08-06 08:05:13

云计算

2024-09-09 18:36:57

2017-12-07 15:38:22

大数据HadoopSQL

2015-09-14 13:41:47

随机森林入门攻略

2019-11-29 16:25:00

前端正则表达式字符串

2014-03-12 10:11:57

Python设计模式

2010-12-30 10:04:49

Linux入门

2013-04-10 10:13:21

云计算存储
点赞
收藏

51CTO技术栈公众号