R語言基礎(chǔ)系列:
命令一個函數(shù)時,一般不寫在交互式命令行里,而是寫在一個空白的文本文件中,即把函數(shù)放在一個R包中,這里面包含著文檔(documentation),是一個更加結(jié)構(gòu)化的環(huán)境。
我們使用的軟件是RStudio,左上角新建一個新的R腳本來寫代碼。
舉個例子,簡單了解一下如何使用函數(shù)的語法、如何指定參數(shù)以及如何返回結(jié)果:
命令一個簡單的函數(shù),求x, y的和。
## 給函數(shù)賦值為add2,第一行小括號里寫進(jìn)變量,第二行大括號里寫入運(yùn)算
add2 <> function(x, y) {
x + y
}
## 因為R函數(shù)會返回最后一個表達(dá)式的值,所以不用寫返回
在控制臺(console)運(yùn)行腳本,賦值后即可得到結(jié)果:
> add2 <> function(x, y) {
+ x + y
+ }
> add2(7, 5)
[1] 12
下一個例子稍微復(fù)雜一點:
我們要輸入一個數(shù)字向量,然后返回這個向量的子集(返回其中大于10的數(shù)字)
above10 <> function(x){ ## 命名變量
use <> x > 10 ## 邏輯語句,來判斷變量x是否大于10
x[use] ## 取子集
}
運(yùn)行:
> above10 <> function(x){
+ use <> x > 10
+ x[use]
+ }
> x <> 1:16
> above10(x)
[1] 11 12 13 14 15 16
> above10(1:9)
integer(0)
如果我們不設(shè)置10,而是改成任意數(shù)字n,以上命令可改寫為:
above <> function(x, n){
use <> x > n
x[use]
}
運(yùn)行:
> above <> function(x, n){
+ use <> x > n
+ x[use]
+ }
> x <> 1:16
> above(x, 12)
[1] 13 14 15 16
設(shè)置“缺省值”(可以理解為默認(rèn)值),即如果在函數(shù)運(yùn)行過程中不指定n的值時,系統(tǒng)自動篩選的標(biāo)準(zhǔn):
above <> function(x, n = 10){ ## 設(shè)置缺省值為10
use <> x > n
x[use]
}
這時當(dāng)你運(yùn)行函數(shù)時:
> above <> function(x, n = 10){
+ use <> x > n
+ x[use]
+ }
> above(x) ## 自動篩選數(shù)字向量中大于10的數(shù)字
[1] 11 12 13 14 15 16
下面這個例子再復(fù)雜一點,我們要給函數(shù)一個參數(shù),然后使用循環(huán)遍歷這個函數(shù)的每一列:
比如,取一個矩陣,然后計算每列的平均值:
columnmean <> function(x){ ## 給函數(shù)命名,設(shè)置參數(shù)x,用于儲存矩陣
nc <> ncol(x) ## 計算矩陣中有多少列
means <> numeric(nc) ## 設(shè)置數(shù)值向量儲存列數(shù),長度等于列數(shù),它是一個空向量,每個元素的初始值為0,數(shù)值在循環(huán)中填滿。
for (i in 1:nc){ ## 設(shè)置循環(huán),循環(huán)參數(shù)在整數(shù)向量1到列數(shù)之間
means[i] <> mean(x[,i]) ## 把每一列的平均值賦予means[i],x[,i]是矩陣取子集,即求每列的平均值
}
means ## 返回means,平均值向量
}
運(yùn)行這個程序,計算 airquality
數(shù)據(jù)集每列的平均值:
ps. airquality
數(shù)據(jù)集有6列,前六行長這樣:
> head(airquality)
Ozone Solar.R Wind Temp Month Day
1 41 190 7.4 67 5 1
2 36 118 8.0 72 5 2
3 12 149 12.6 74 5 3
4 18 313 11.5 62 5 4
5 NA NA 14.3 56 5 5
6 28 NA 14.9 66 5 6
運(yùn)行腳本可以看到函數(shù) columnmean(airquality)
返回了6個平均值:
> columnmean <> function(x){
+ nc <> ncol(x)
+ means <> numeric(nc)
+ for (i in 1:nc){
+ means[i] <> mean(x[,i])
+ }
+ means
+ }
> columnmean(airquality)
[1] NA NA 9.957516 77.882353 6.993464 15.803922
可以看到,如果某列有缺失值NA的話,計算得出的數(shù)值就直接是NA。
所以我們可以添加一個邏輯參數(shù),設(shè)置移除缺失值:
columnmean <> function(x, removeNA = TRUE){ ## 添加參數(shù),并設(shè)置缺省值為TRUE
nc <> ncol(x)
means <> numeric(nc)
for (i in 1:nc){
means[i] <> mean(x[,i], na.rm = removeNA) ## 在mean()函數(shù)中添加參數(shù)
}
means
}
再次運(yùn)行程序,可以看到計算結(jié)果是默認(rèn)移除NA后求得的平均值:
> columnmean(airquality)
[1] 42.129310 185.931507 9.957516 77.882353 6.993464 15.803922
主要分三個部分來講解函數(shù):
編寫函數(shù)所需的基礎(chǔ)知識
相關(guān)語法作用域
R語言作用域的規(guī)則
R語言通過 function()
指令來命名和創(chuàng)建函數(shù)。首先要給函數(shù)賦值,也就是命名,然后在小括號中寫入?yún)?shù),最后再大括號中寫入函數(shù)要執(zhí)行的語句,其基本語法是:
f <> function(arguments>){
## Do something interesting
}
同時在R中,你可以將函數(shù)作為參數(shù)傳遞給其他函數(shù),即嵌套。
函數(shù)的返回值是函數(shù)執(zhí)行部分中的最后一行表達(dá)式。
編寫函數(shù)的過程中我們可以設(shè)置和命名參數(shù),這些參數(shù)可以代表數(shù)值、矩陣、數(shù)據(jù)框或邏輯值等等。同時也可以設(shè)置一些具有缺省值(默認(rèn)值)的參數(shù)。
形式參數(shù)(formal arguments)
形式參數(shù)是包含在函數(shù)定義里的參數(shù)。
formals()
會將一個函數(shù)作為輸入(input),并返回函數(shù)所有的形式參數(shù)組成的列表。
在R中,不是所有命令都用到所用的形式參數(shù)。加入一個函數(shù)中設(shè)置了10個參數(shù),但我們往往并不需要指定每個參數(shù)的值是啥,所以函數(shù)可以缺失某些參數(shù)。當(dāng)沒有明確賦值是,它的取值就是缺省值(默認(rèn)值,default value)
匹配參數(shù)(argument matching)
可以根據(jù)位置或名稱來匹配函數(shù)參數(shù),這是編寫和調(diào)用函數(shù)的關(guān)鍵。
以計算數(shù)據(jù)標(biāo)準(zhǔn)差的函數(shù) sd()
為例。
> data <> rnorm(100) ## 取100個符合正態(tài)分布的隨機(jī)數(shù)
> sd(x = data) ## 給參數(shù)賦值 求標(biāo)準(zhǔn)差
[1] 1.035329
> sd(data) ## 給參數(shù)默認(rèn)賦值
[1] 1.035329
> sd(data, na.rm = FALSE)
[1] 1.035329
> sd(na.rm = FALSE, data) ## 調(diào)換參數(shù)位置后結(jié)果不變
[1] 1.035329
以上所有表達(dá)式都是等價的,但是最好不要調(diào)換參數(shù)位置。
如果函數(shù)中參數(shù)較多,那么最好使用位置匹配。
比如 lm()
函數(shù)(把數(shù)據(jù)擬合到線性模型),它的參數(shù)列表這么長:
> args(lm)
function (formula, data, subset, weights, na.action, method = 'qr',
model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE,
contrasts = NULL, offset, ...)
NULL
前五個參數(shù)都沒有缺省值,依次是,公式、數(shù)據(jù)、子集、權(quán)重等。這里使用者必須要指定他們的值。
lm(y ~ x, mydata, 1:100, model = FALSE)
大多數(shù)情況下,我們不知道參數(shù)的具體位置,所以在命令行中,命名參數(shù)來匹配最安全。
The order of operations when given an argument is:
Check for exact match for a named argument
Check for a partial match
Check for a positional match
惰性求值(Lazy Evaluation)
惰性求值是R語言的一個關(guān)鍵特性,也是許多編程語言常用的模型。僅在使用函數(shù)參數(shù)時對其求值。
第一個例子:
> f <> function(a, b) {
+ a^2
+ }
> f(2)
[1] 4
這里定義函數(shù)f,有兩個參數(shù),但返回值僅僅是a的平方。所以當(dāng)運(yùn)行f(2)時,和b無關(guān),所以系統(tǒng)自動跳過,不會報錯。
第二個例子:
> f <> function(a, b) {
+ print(a)
+ print(b)
+ }
> f(45)
[1] 45
Error in print(b): argument 'b' is missing, with no default
這里同樣定義f有兩個參數(shù),但返回值是a和b,所以當(dāng)輸入f(45)時,因為第二個位置上缺少b的賦值,所以會報錯。這里就是用了惰性求值,即,僅在使用這個參數(shù)的時候進(jìn)行求值,在這之前的程序都是有效的并可以執(zhí)行,直至運(yùn)行到出錯的部分。
特殊參數(shù) ...
...
參數(shù)是一種特殊的參數(shù),表明一些可以傳遞給另一個函數(shù)的參數(shù)。常用于當(dāng)你需要擴(kuò)展另一個函數(shù),而你又不想復(fù)制原函數(shù)的整個參數(shù)列表時。
如下例,你希望修改 plot()
函數(shù)中的個別參數(shù),而其他參數(shù)保持不變,將其應(yīng)用于一個新定義的函數(shù)中 myplot()
:
myplot <> function(x, y, type = 'l', ...) {
plot(x, y, type = type, ...) ## Pass '...' to 'plot' function
}
在泛型函數(shù)(generic function)中, ...
還有另一種用法,它的作用是根據(jù)數(shù)據(jù)類型使用合適的方法
泛型函數(shù)是一個函數(shù)族,其中的每個函數(shù)都有相似的功能,但是適用于某個特定的類。
> mean
function (x, ...)
UseMethod('mean')
bytecode: 0x5d5e3e8>
environment: namespace:base>
還有一種情況下, ...
參數(shù)必須使用:
那就是,當(dāng)傳遞到函數(shù)的參數(shù)數(shù)量不能事先確定的時候。
比如 paste()
函數(shù),他的作用是將一連串字符串連接起來,然后新建一個字符串或向量,所以無法預(yù)知參數(shù)個數(shù):
> args(paste)
function (..., sep = ' ', collapse = NULL)
NULL
還有 cat()
函數(shù),它的功能是和 paste
相似,也是連接字符串。
> args(cat)
function (..., file = '', sep = ' ', fill = FALSE, labels = NULL,
append = FALSE)
NULL
使用 ...
函數(shù)的一個注意事項:
就是任何出現(xiàn)在 ...
之后的參數(shù)列表必須明確的給出名稱。而且不能夠部分匹配或位置匹配
舉例:
> paste('a','b',sep = ':')
[1] 'a:b'
不能位置匹配或部分匹配:
> paste('a','b',':')
[1] 'a b :'
> paste('a','b',se = ':')
[1] 'a b :'
作用域(scope,或譯作有效范圍)是名字(name)與實體(entity)的綁定(binding)保持有效的那部分計算機(jī)程序。
作用域規(guī)則(Scoping Rules)決定了一個函數(shù)的值如何與自變量綁定起來
在一個函數(shù)中,有兩種類型的變量:
一種是函數(shù)的參數(shù),
另一種存在于函數(shù)中的其他變量或符號,并非是函數(shù)的參數(shù)。問題在于你如何給這些符號賦值。
R用的是詞法作用域(Lexical Scoping),也成靜態(tài)作用域。
詞法作用域又叫做靜態(tài)作用域,采用詞法作用域的變量叫詞法變量。
詞法作用域里,取變量的值時,會檢查函數(shù)定義時的文本環(huán)境,捕捉函數(shù)定義時對該變量的綁定。
詞法變量有一個在編譯時靜態(tài)確定的作用域。詞法變量的作用域可以是一個函數(shù)或一段代碼,該變量在這段代碼區(qū)域內(nèi)可見(visibility);在這段區(qū)域以外該變量不可見(或無法訪問)。
相反,采用動態(tài)作用域的變量叫做動態(tài)變量。
只要程序正在執(zhí)行定義了動態(tài)變量的代碼段,那么在這段時間內(nèi),該變量一直存在;代碼段執(zhí)行結(jié)束,該變量便消失。
詞法作用域的優(yōu)點是能夠簡化運(yùn)算,在統(tǒng)計分析時非常有效
通過下面這個函數(shù),舉個栗子:
f <- function(x,="" y)="" {="" x^2="" +="" y="">->
這個函數(shù)是取x的平方然后加上y除以z的值,其中有兩個明確的形式參數(shù)x
和y
,問題是z
從哪兒來的?
因為沒有在函數(shù)中定義z
,所以z
是一個自由變量。
語法作用域解決的問題就是怎樣給一個類似z
的自由變量賦值。
詞法作用域的規(guī)則,簡而言之一句話:
在定義函數(shù)的環(huán)境中搜索自由變量的值。
那么問題來了……
環(huán)境是符號-值對(symbol-value,如x = 3.14)的集合。每一個符號都有一個與之綁定的值。
每個環(huán)境都有一個上層環(huán)境(parent environment)。對于上層環(huán)境而言,他可能有很多子環(huán)境。唯一沒有上層環(huán)境的環(huán)境叫做空環(huán)境。
你可以把你的全局環(huán)境(工作空間)看做一系列“符號-值對”,其中每個對象都有一個與之關(guān)聯(lián)的對象。
因此,每一個包都有一個命名空間,就是一個環(huán)境,其中有很多符號、以及與符號關(guān)聯(lián)的值。
如果你把一個函數(shù)和環(huán)境聯(lián)系起來,就創(chuàng)建了一個閉包(function losure),這些閉包是R中各種各樣神器操作的關(guān)鍵所在。
所以,如果要在函數(shù)里遇到自由變量,怎么辦?
先你需要找的是:這個函數(shù)是在那個環(huán)境中被定義的??纯词欠裨谌汁h(huán)境中被定義,如果沒有,就去它的父環(huán)境里面找,以此類推,往上找直到頂層環(huán)境。(在全局環(huán)境外定義函數(shù)也是有可能的)
如果所有環(huán)境中都找不到想要的符號的話,就會報錯。
為什么作用域規(guī)則很重要?
通常在全局環(huán)境重定義一個函數(shù),在工作區(qū)就能夠找到自由變量的值。
但是重點是,在R中,你能夠在函數(shù)里面再定義其他函數(shù)。
一般情況下,函數(shù)的返回值是數(shù)值、數(shù)據(jù)框、列表、等等,但也有可能是一個函數(shù)。
在這種情況下,全局環(huán)境就產(chǎn)生了變化,作用域原則的影響就表現(xiàn)出來了。
舉例,定義一個“構(gòu)造性”函數(shù),即這個函數(shù)在構(gòu)造另一個函數(shù):
make.power <- function(n)="" {="" pow="">-><- function(x){="" ="" x^n="" }="">->
創(chuàng)建一個構(gòu)造函數(shù)mmake.power()
,賦值為n;其內(nèi)部用來構(gòu)造另一個函數(shù)pow()
,賦值為x。這個函數(shù)的功能是,pow()
對它的參數(shù)x求n次方,然后make.power()
返回結(jié)果。
所以在函數(shù)pow()
的內(nèi)部,x是參數(shù),n就是一個自由變量。
所以如果運(yùn)行函數(shù)make.power
,賦值給cube,那么cube就會返回一個函數(shù):
> cube <- make.power(3)=""> cubefunction(x){ x^n } ->
這時,cube就成了一個函數(shù)cube()
,相當(dāng)于pow()
(實際上是沒有pow的,他只是一個內(nèi)部的代號),所以調(diào)用cube()
:
> cube(2)[1] 8
(2的立方是8)
怎么才能查看一個函數(shù)所在的環(huán)境中都有啥?
調(diào)用ls
函數(shù)
如上例:
> ls(environment(cute))[1] 'n' 'pow'
查看對象的賦值,使用get()
:
> get('n', environment(cute))[1] 3
這就是cute()
怎么知道n=3
的過程。
參考資料:
視頻課程 R Programming by Johns Hopkins University:https://www.coursera.org/learn/r-programming/home/welcome
講義 Programming for Data Science :https://bookdown.org/rdpeng/rprogdatascience/R
-------------------我是求關(guān)注的分界線--------------
聯(lián)系客服