电子表格可能是使用最广泛的PC应用之一,其理由是:电子表格很容易对表格数据进行计算和操作。但是,电子表格也会有一些劣势和风险:程序容易奔溃,很难调试。好消息是,可以用R实现电子表格的许多功能。在R语言中,使用数据框来表示表格数据。另外,R提供了很多函数、运算符和方法,允许对数据框进行操作和计算。这意味着几乎所有可以在Microsoft Excel、LibreOffice等电子表格中完成的任务都可以借助R来完成。
大家可以试试下面这些例子,并养成遇到问题时求助R帮助文档的习惯。
使用电子表格经常做的一个任务就是对行或者列进行求和。要做到这一点最简单的方法是使用“rowSums()”和“colSums()”函数。类似地,使用“rowMeans()” 和“colMeans()”可以计算平均值。
下面来试试内置数据集iris。首先,删除第五列,因为它包含描述鸢尾花品种的文本:
> iris.num <- iris[, -5]
然后计算每列的总和和均值:
> colSums(iris.num) > colMeans(iris.num)
这两个函数都非常方便,但有时可能需要为每行或列计算其他的统计汇总,这就需要用到对数组或数据框中行或列数据的遍历:apply()函数。例如,要获得一列数组的最小值,相当于在数据的第二维度上应用min()函数:
> apply(iris.num, 2, min)
> apply(iris.num, 2, max)
“apply()”函数特别适合处理数组,因为处理行或列数据都很直观且直接。如果数据存在于数据框中,则最好使用sapply()而非apply()。所以,试试下面的代码来计算列统计:
> sapply(iris.num, min)
> sapply(iris.num, max)
当编写一份报表时,希望所有数据看起来统一而整齐,这需要对它们进行格式化。例如,希望让数字以小数点对齐,或者指定固定的列宽;再如,想在某些 表示金额的数字前面加上货币符号(如 $100.0 ),或者在某些百分数字后面添加百分号(如 35.7% )。
format()函数可以将数字变成美观的文本,它通过若干个参数来控制输出结果的格式。下面列出了其中的 3 个。
trim: 逻辑值。FALSE表示通过添加空格实现文本的右对齐,TRUE则会 去掉前面的空格。
digite: 控制显示的有效数字的个数。
nsmall: 小数点后最少保留多少个数字。
此外,可以通过decimal.mark来控制小数点的格式,通过big.mark来设置小数点前整数部分的分段符,通过small.mark则设置小数点后小数部分的分段符。
例如,假设要输出数字 12345.6789,并且以逗号为小数点,空格作为整数部分的分段符,点号作为小数部分的分段符:
> format(12345.6789, digits = 9, decimal.mark = ",",
+ big.mark = " ", small.mark = ".", small.interval = 3)
[1] "12 345,678.9"
举个更具体的例子,计算mtcars中某几列的平均值,并且以两位小数的形式将其打印,如下所示:
> x <- colMeans(mtcars[, 1:4])
> format(x, digits = 2, nsmall = 2)
mpg cyl disp hp
"20.09" " 6.19" "230.72" "146.69"
注意,结果已经不是数字,而是字符串。所以,在使用格式化数字的时候要特别小心,这应是制作报表的最后一步。
如果熟悉类似于C或C++这类编程语言,会知道sprintf()函数是非常有用的,它实际上是对C语言“sprintf()”的封装,允许将格式化后的数值存储到一个字符串中。
下面这段示例代码将一个数值转换成百分数:
> x <- seq(0.5, 0.55, 0.01)
> sprintf("%.1f %%", 100 * x)
[1] "50.0 %" "51.0 %" "52.0 %" "53.0 %" "54.0 %" "55.0 %"
这一神奇之处对于C程序员而言非常熟悉,但对其他人而言,还是需要了解一下它的实现过程:sprintf()函数的第一参数用来指定格式,即这里的“%.1F%%”。格式化参数使用一种特殊的描述方式,告诉函数将其中的哪些部分替换成变量,并应用某些格式。上面例子中描述的单元总是以“%”符号开头。在这种情况下,“%.1F”表示传入的第一个值进行格式化,要求显示小数点后一位数字,“%%”则表示输出一个“%”。
再如,要对货币进行格式化,在前面添加美元符号,可以使用下面这段代码:
> set.seed(1)
> x <- 1000 * runif(5)
> sprintf("$ %3.2f", x)
[1] "$ 265.51" "$ 372.12" "$ 572.85" "$ 908.21" "$ 201.68"
正如前面看到的,“%3.2f”意味着数值格式化为一个定点数,保留小数点后两位数,且小数点前显示 3 位数字。
函数sprintf()比我们想象的还要强大,它为将任何变量值转换成字符串提供了 一种有用的途径。
> stuff <- c("bread", "cookies")
> price <- c(2.1, 4)
> sprintf("%s cost $ %3.2f ", stuff, price)
[1] "bread cost $ 2.10 " "cookies cost $ 4.00 "
上面这段代码向“sprintf()”传入两个向量(每个都包含两个元素),所以返回结果也是一个包含两个元素的向量。在此过程中,R将对各个元素应用回收机制,所以“%s”(表示将传入值格式化为字符串)第一次获得值“bread”,第二次的值为“cookies”。
由于“paste()”和“format()”函数结合起来可以完成“sprintf()”函数的全部功 能,因此可以不去了解它。但当读者熟悉之后,会发现这个函数非常方便,而且能简化代码。
在R中对数据进行排序可以使用“sort()”或者“order()”函数。
例如,以递增或递减的顺序对 mtcars 数据框的hp列进行排序,如下所示:
> with(mtcars, mtcars[order(hp), ])
> with(mtcars, mtcars[order(hp, decreasing = TRUE), ])
电子表格提供了各类“What if?”语句的判断,这是通过if()函数实现的。
R同样也有if()函数,但一般用于脚本的程序流控制。由于在R中一般对整个向量进行计算,因此使用ifelse()函数会更方便。
下面是使用“ifelse()”函数在数据集mtcars中判断高油耗和低油耗的车:
> mtcars <- transform(mtcars,
+ mpgClass = ifelse(mpg < mean(mpg),"Low","High"))
> mtcars[mtcars$mpgClass == "High", ]
Excel还提供的一个功能是基于条件进行求和,以及通过“sumif()”和“countif()”函数进行条件计数。
在R中有两种实现方案:
使用ifelse()函数;
直接提取数据子集进行统计汇总。
举个例子,假如想要计算mtcars在不同案例段中油耗的平均值,可以使用 “mean()”函数。在阈值为 150 马力时的情况下,请尝试以下操作:
> with(mtcars, mean(mpg))
[1] 20.09062
> with(mtcars, mean(mpg[hp < 150]))
[1] 24.22353
> with(mtcars, mean(mpg[hp >= 150]))
[1] 15.40667
对向量元素进行计数实际上就是获取它的长度,这意味着Excel中的“countif”等价于R中的“length()”函数:
> with(mtcars, length(mpg[hp > 150]))
[1] 13
有时需要将数据中的行和列对调。在R中,转置函数“t()”可以完成矩阵转置操作:
> x <- matrix(1:12, ncol = 3) > x [,1] [,2] [,3]
[1,] 1 5 9
[2,] 2 6 10
[3,] 3 7 11
[4,] 4 8 12
使用函数“t()”获取转置:
> t(x) [,1] [,2] [,3] [,4]
[1,] 1 2 3 4
[2,] 5 6 7 8
[3,] 9 10 11 12
可以使用“t()”转置数据框,但要小心,转置的结果始终是一个矩阵(或数组)。因为数组只能有一种变量类型,比如数值或字符,因此结果中变量类型可能与所期望的不符。
请注意,mtcars的转置是一个字符数组:
> t(mtcars[1:4, ])
Mazda RX4 Mazda RX4 Wag Datsun 710 Hornet 4 Drive
mpg "21.0" "21.0" "22.8" "21.4"
cyl "6" "6" "4" "6"
disp "160" "160" "108" "258"
hp "110" "110" " 93" "110"
drat "3.90" "3.90" "3.85" "3.08"
wt "2.620" "2.875" "2.320" "3.215"
qsec "16.46" "17.02" "18.61" "19.44"
vs "0" "0" "1" "1"
am "1" "1" "1" "0"
gear "4" "4" "4" "3"
carb "4" "4" "1" "1"
mpgClass "High" "High" "High" "High"
要找到数据中的所有唯一值,可以使用“unique()”函数。尝试寻找在mtcars中不同的汽缸数:
> unique(mtcars$cyl)
[1] 6 4 8
有时想要知道数据中哪些值是重复的,结合具体情况,有时数据重复是正常的,但有时,重复条目可能表明数据录入的错误。
查找重复数据的函数是“duplicated()”。在内置数据集iris中,第 143 行有重复值,如下所示:
> dupes <- duplicated(iris)
> head(dupes)
[1] FALSE FALSE FALSE FALSE FALSE FALSE
> which(dupes)
[1] 143
> iris[dupes, ]
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
143 5.8 2.7 5.1 1.9 virginica
因为duplicated()函数返回的是一个逻辑向量,因此可以把它作为索引,从数据中删除重复行。为此,需要使用求反运算符—叹号(就像 !):
> iris[!dupes, ]
> nrow(iris[!dupes, ])
[1] 149
在Excel电子表格应用程序中,可以用vlookup函数或者将index和match结合起来创建检索表。
在R中,可以很方便地使用“merge()”或“match()”函数。match()函数返回一个向量,指示与检索表值匹配元素的位置。
例如,想找到元素“Toyota Corolla”在mtcars数据集中相对应的行名称,可以使用下面的代码:
> index <- match("Toyota Corolla", rownames(mtcars))
> index
[1] 20
> mtcars[index, 1:4]
mpg cyl disp hp
Toyota Corolla 33.9 4 71.1 65
可以看到,返回索引位置为 20,而第 20 行确实是我们想找的内容。
在Excel中,数据透视表是操作和分析数据的强大工具。
对于R 中的简单表,可以使用“tapply()”函数来实现类似结果。下面是使用“tapply()”计算不同气缸数和挡位数汽车平均hp的示例:
> with(mtcars, tapply(hp, list(cyl, gear), mean))
3 4 5
4 97.0000 76.0 102.0
6 107.5000 116.5 175.0
8 194.1667 NA 299.5
对于稍微复杂一点的表—两个以上的交叉分类因子—则需要使用“aggregate()”函数:
> aggregate(hp ~ cyl + gear + am, mtcars, mean)
cyl gear am hp
1 4 3 0 97.00000
2 6 3 0 107.50000
3 8 3 0 194.16667
4 4 4 0 78.50000
5 6 4 0 123.00000
6 4 4 1 75.16667
7 6 4 1 110.00000
8 4 5 1 102.00000
9 6 5 1 175.00000
10 8 5 1 299.50000
如果经常用Excel表格工作,应该好好研究一下“dplyr”和“tidyr”扩展包。这两个包提供了一系列的函数,实现常用的数据处理功能。
Excel的一个强大的功能在于,它拥有一个求解器,可以轻松地计算一定条件约束下函数的最大值和最小值。
对各类问题求最优化的解是数学中的一个重要的问题。在R中,“ optimize()”函数提供一个相对简单的优化机制。
假设你是一家公司的销售总监,需要为产品定一个合适的价格。换句话说,要找到一个价格,使产品获得最大的收益。
经济学的一个基本模型是随着价格上涨,产品的销售量会下降,利用下面这个简单的函数来表达这个模型:
> sales <- function(price) { 100 - 0.5 * price }
那么,预期收益就是产品价格与销售量的乘积:
> revenue <- function(price) { price * sales(price) }
可以用“curve()”函数将这两个函数的图画出来。它接收一个函数作为参数,并绘出一定范围内函数的图像。假设定价范围在 50 美元到 150 美元之间,绘出销量和收益函数的图表:
> oldpar <- par(mfrow = c(1, 2), bty = "l")
> curve(sales, from = 50, to = 150, xname = "price", main = "Sales")
> curve(revenue, from = 50, to = 150, xname = "price", main = "Revenue")
> par(oldpar)
结果如图 1 所示。
图1 销售量和收益的期望模型
现在有了销售和收益的一个可用模型。从图中可以看出,收益显然有一个最大的收益点。所以接下来,可以使用R的“optimize()”函数来找到最大值。要使用optimize()时,首先需要告诉所使用的目标函数(在本例中,是revenue()),以及取值区间(这里是 $50 ~ $150)。在默认情况下,“optimize()”函数将查找最小值,所以,还需要告诉它获取的是最大值:
> optimize(revenue, interval = c(50, 150), maximum = TRUE)
$maximum
[1] 100$
objective
[1] 5000
定价 100 美元,期望收益为 5000 美元。
Excel的求解器使用广义简约梯度算法来求解非线性最优化问题。与Excel求解器不同的是,R的“optimize()”函数使用的是黄金分割搜索和连续抛物线插值的结合。幸运的是,有各种不同的包提供了对最优化问题求解的不同算法。事实上,在CARN上还专门有一个关于最优化和数学编程的专题页面。
(本文节选自:【法】Andrie de Vries,【比利时】Joris Meys作品《R语言可以很简单(第2版)》)
(点击图片,立即阅读)
作者:【法】Andrie de Vries(安德里·德弗里斯) ,
【比利时】Joris Meys(乔里斯·梅斯)
R语言轻松入门与提高,统计和数据分析人员首选。
本书作为业内外一致好评的 Dummies系列书籍之一,是供R语言初学者学习的经典力作。