本篇来谈一个比较基础的问题:如何正确使用R语言的函数?
R的函数和数学上的函数类似——函数名后跟着圆括号,圆括号内是参数,参数可以赋值——因此用户能很容易理解。但也会有些读者对“数据结构”没有概念,对函数的使用也只是一知半解。
首先提出两个问题:
- R环境中存在同名函数,R环境对此如何区分?
- 同名函数意味着只是名称相同,实际不是一个函数,那判断同一函数和同名函数的依据是什么?反过来看,什么时候R环境会把同名函数误认为同一函数呢?
很多读者也会私信学堂君询问某些特定函数的用法,对此学堂君也一般建议读者去看函数的帮助文档。虽然看起来比较“敷衍”,但其实是一种正确学习R语言的方法。本篇就通过几个函数例子来证明这一点。
1 summary()
函数
1.1 例1.1
summary()
属于基础包中的函数,那究竟是哪个基础包呢?利用RStudio的自动联想功能,可以看出它来自base
工具包。
这其实有点违背直觉,因为使用summary()
函数大多数情况是输出数学模型的结果,它理应在stats
工具包里。
事实上,用来查看线性模型结果的summary()
函数确实来自stats
工具包。通过查看帮助文档可以验证这一点,如下图:
尽管现在还没有明确区分同一函数和同名函数的依据,但如果两个函数不在一个工具包里,那应该不会是同一个函数,而只能是同名函数。
其实,base
工具包的summary()
函数也十分常用。比如我们查看数据框变量的分布时会用到它:
summary(mtcars)
## mpg cyl disp hp
## Min. :10.40 Min. :4.000 Min. : 71.1 Min. : 52.0
## 1st Qu.:15.43 1st Qu.:4.000 1st Qu.:120.8 1st Qu.: 96.5
## Median :19.20 Median :6.000 Median :196.3 Median :123.0
## Mean :20.09 Mean :6.188 Mean :230.7 Mean :146.7
## 3rd Qu.:22.80 3rd Qu.:8.000 3rd Qu.:326.0 3rd Qu.:180.0
## Max. :33.90 Max. :8.000 Max. :472.0 Max. :335.0
可以不加区分地使用,也不需要加载工具包,读者会潜意识地认为上面这个summary()
函数和下面这个是同一个函数:
model <- lm(mpg ~ qsec, data = mtcars)
summary(model)
## Call:
## lm(formula = mpg ~ qsec, data = mtcars)
##
## Residuals:
## Min 1Q Median 3Q Max
## -9.8760 -3.4539 -0.7203 2.2774 11.6491
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -5.1140 10.0295 -0.510 0.6139
## qsec 1.4121 0.5592 2.525 0.0171 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
1.2 例1.2
base
和stats
都是R的基础包,都随R启动而自动加载,它们的函数也会始终处在R的环境中等待调用。从这个意义上说,上例中两个summary()
函数和同在一个工具包也没什么区别。那真正意义上的同一工具包会存在同名但并不相同的函数吗?
以base
工具包为例,我们在帮助文档中找到summary()
函数的语法结构:
通过第一个红框的内容可以看到,函数执行的功能取决于它第一个参数的“类型”,也就是“数据结构”。针对数据框、因子向量、矩阵等类型的对象,它执行的不是一个语法结构。
函数对应的英文是function
,从这个意义说,这些summary()
函数并不是同一个函数。不过考虑到可以在函数内部定义不同的功能,我们也可以将其看作是同一个函数,而它和定义一系列单一功能的summary()
函数也没什么区别——因为R环境会根据第一个参数的数据结构加以区分。这也可以解释为什么base
和stats
中的summary()
函数在使用时不用区分了——因为它们针对的是不同类型的数据结构。
1.3 例1.3
我们再看stats
包中的summary()
函数。
之所以通过RStudio的自动联想功能查不到该包有summary()
函数,是因为帮助文档中使用了后缀进行区分。
比如查看线性模型结果的是summary.lm()
函数,查看广义线性模型的是summary.glm()
函数,此外还有summary.aov()
、summary.nls()
等函数。如下图所示:
尽管在使用时用的是同一个函数名,但帮助文档中对其进行区分,一定程度上也是有意将它们看作是不同的函数。原因在于,尽管都是“模型”,但是线性模型和广义线性模型所对应的对象并非是同一种数据结构,针对不同的模型要分别定义一个summary()
函数。
回到推文标题,为什么有的模型结果不能使用summary()
函数查询呢?是因为工具包的作者没有为这种类型的“模型”定义summary()
函数。一个例子就是使用GWmodel
工具包运行地理加权模型,通过summary()
函数查询的结果如下所示(GWmodel | 地理加权模型(Ⅱ-1):地理加权主成分分析(GWPCA)):
library(GWmodel)
library(sf)
library(sp)
library(tidyverse)
data(Georgia)
data(GeorgiaCounties)
Gedu.df$AreaKey <- factor(Gedu.df$AreaKey)
dta <- Gedu.counties %>% st_as_sf() %>%
left_join(Gedu.df, by = c("AREAKEY" = "AreaKey")) %>%
as("Spatial")
formula <- PctPov ~ PctBlack + PctBach
bw.1 <- bw.gwr(formula, data = dta,
approach = "aic",
adaptive = T,
kernel = "gaussian")
model.1 <- gwr.basic(formula, data = dta,
bw = bw.1,
adaptive = T,
kernel = "gaussian")
summary(model.1)
## Length Class Mode
## GW.arguments 11 -none- list
## GW.diagnostic 8 -none- list
## lm 14 lm list
## SDF 159 SpatialPolygonsDataFrame S4
## timings 5 -none- list
## this.call 6 -none- call
## Ftests 0 -none- list
很明显,这不是我们想象中模型结果该有的样子。通过查看GWmodel
工具包的帮助文档也找不到summary()
函数,而基础包stats
不能运行地理加权模型,自然也不会为这种“模型”定义summary()
函数。
那为什么在这里summary(model.1)
语句还能输出结果呢(尽管不是所期望的结果)?因为这里执行的其实是base
包中的summary()
函数:
ls = list()
summary(ls)
## Length Class Mode
## 0 list list
GWmodel
的作者实际上是定义了一系列的print()
函数来查询该模型的信息:
print(model.1)
## 输出内容较长,有省略
## ***********************************************************************
## * Results of Geographically Weighted Regression *
## ***********************************************************************
##
## *********************Model calibration information*********************
## Kernel function: gaussian
## Adaptive bandwidth: 22 (number of nearest neighbours)
## Regression points: the same locations as observations are used.
## Distance metric: Euclidean distance metric is used.
##
## ****************Summary of GWR coefficient estimates:******************
## Min. 1st Qu. Median 3rd Qu. Max.
## Intercept 10.38272 13.01826 14.76120 19.30086 20.6314
## PctBlack 0.19714 0.23396 0.26714 0.29816 0.3677
## PctBach -0.65183 -0.55045 -0.37867 -0.28073 -0.1597
## ************************Diagnostic information*************************
## Number of data points: 159
## Effective number of parameters (2trace(S) - trace(S'S)): 14.82297
## Effective degrees of freedom (n-2trace(S) + trace(S'S)): 144.177
## AICc (GWR book, Fotheringham, et al. 2002, p. 61, eq 2.33): 867.0877
## AIC (GWR book, Fotheringham, et al. 2002,GWR p. 96, eq. 4.22): 852.3931
## BIC (GWR book, Fotheringham, et al. 2002,GWR p. 61, eq. 2.34): 736.8195
## Residual sum of squares: 1853.565
## R-square value: 0.7770154
## Adjusted R-square value: 0.75393
GWmodel
工具包中的print()
函数:
另外,我们知道base
包中也有一个print()
函数,但这里model.1
的数据结构是gwrm
类型,因此print(model)
执行的是上图中print.gwrm()
函数。
class(model.1)
## [1] "gwrm"
当然,考虑到用户习惯,许多工具包会为它的模型定义一个summary()
函数,比如运行生存模型的survival
工具包,运行多层模型的lme4
工具包等,但这也会给人一种基础包的summary()
函数包打一切的错觉,碰到不按常规出牌的工具包可以会感到困惑。
2 filter()
函数
现在知道,R环境能根据对象的数据结构区分同名函数。那如果函数名相同、对象数据结构也相同呢?
2.1 例2.1
dplyr
工具包和基础包有一些同名的函数。当首次加载dplyr
工具包时会出现如下提示:
library(dplyr)
## 载入程辑包:'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
在加载dplyr
后,R环境会将基础包的一些同名函数覆盖掉(或者说屏蔽),这样出现同名函数调用的就只能来自dplyr
包。那为什么在上节的例子里,summary()
、print()
等函数不会被覆盖呢?其实就是因为这里函数不仅同名,而且连对象数据结构也可以相同,R环境无法对其区分。
比如,来自dplyr
包的filter()
函数用于样本筛选,是一个很常用的函数,而stats
包中也有这么一个函数,并且这两个函数的数据结构还有重叠。读者可以尝试在加载dplyr
工具包前后分别运行下列代码:
x <- 1:100
filter(x, rep(1, 3))
加载前(重启R再运行):
x <- 1:100
filter(x, rep(1, 3))
## Time Series:
## Start = 1
## End = 100
## Frequency = 1
## [1] NA 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54
## [19] 57 60 63 66 69 72 75 78 81 84 87 90 93 96 99 102 105 108
## [37] 111 114 117 120 123 126 129 132 135 138 141 144 147 150 153 156 159 162
## [55] 165 168 171 174 177 180 183 186 189 192 195 198 201 204 207 210 213 216
## [73] 219 222 225 228 231 234 237 240 243 246 249 252 255 258 261 264 267 270
## [91] 273 276 279 282 285 288 291 294 297 NA
- 此时,R环境执行
stats
工具包中的函数,程序正常运行。
加载后:
library(dplyr)
x <- 1:100
filter(x, rep(1, 3))
# Error in UseMethod("filter") :
# no applicable method for 'filter' applied to an object of class "c('integer', 'numeric')"
- 此时,R环境会认为该函数来自
dplyr
工具包,但由于后面的参数无法对应,程序运行出错。
解决办法是在函数名前加上工具包名和两个冒号:
x <- 1:100
stats::filter(x, rep(1, 3))
## Time Series:
## Start = 1
## End = 100
## Frequency = 1
## [1] NA 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54
## [19] 57 60 63 66 69 72 75 78 81 84 87 90 93 96 99 102 105 108
## [37] 111 114 117 120 123 126 129 132 135 138 141 144 147 150 153 156 159 162
## [55] 165 168 171 174 177 180 183 186 189 192 195 198 201 204 207 210 213 216
## [73] 219 222 225 228 231 234 237 240 243 246 249 252 255 258 261 264 267 270
## [91] 273 276 279 282 285 288 291 294 297 NA
2.2 例2.2
上例中,程序运行出错会提示用户修改代码。但在一些情况下,程序能正常运行,但运行结果未必如预期。比如当运行语句lag(1:10, 2
时,程序执行下面哪种情况取决于否加载了dplyr
工具包:
stats::lag(1:10,2)
## [1] 1 2 3 4 5 6 7 8 9 10
## attr(,"tsp")
## [1] -1 8 1
dplyr::lag(1:10, 2)
## [1] NA NA 1 2 3 4 5 6 7 8
3 总结
R环境能根据对象的数据结构区分同名函数;当数据结构相同时,R环境无法区分二者。
另外,学R,要多看帮助文档。