1.3 函數
要想實現類似隨機抽樣這樣較為復雜的任務,可以用R自帶的一些函數。比如,round函數可以實現數字的四舍五入操作,factorial函數可以實現階乘操作。R中函數的使用方法非常簡單,只需把函數的名稱敲出來并在其后的括號中鍵入相應的數據即可。
round(3.1415) ## 3 factorial(3) ## 6
傳遞到函數中的數據稱為該函數的參數(argument)。參數可以是原始數據、R對象,甚至是另一個R函數的返回結果。在最后一種情況下,R函數的執行方式是從內到外,如圖1-5所示。

圖1-5:在R中使用函數嵌套時,R將從最內層的運算開始解析,直到最外層的運算為止。在這個例子中,R首先找到die這個對象,然后計算所有6個數值的平均值,再進行四舍五入
mean(1:6) ## 3.5 mean(die) ## 3.5 round(mean(die)) ## 4
好在有一個R函數可以幫助我們完成“擲骰子”的任務。這個函數便是sample函數,它可以模擬擲骰子。sample函數有兩個參數:一個名為x的向量和一個名為size的數字。sample函數的作用便是從向量x中抽取size個元素并返回。
sample(x = 1:4, size = 2)
## 3 2
要想擲骰子并得到一個點數,可將x設置為die,然后從die中抽取一個元素。每次擲都會得到一個新的(可能不同的)點數。
sample(x = die, size = 1) ## 2 sample(x = die, size = 1) ## 1 sample(x = die, size = 1) ## 6
R中的許多函數都包含多個參數以幫助其完成任務。可以給一個R函數設置多個參數,只要用逗號將它們隔開。
你可能已經注意到了,在上面的代碼示例中,我用等號將die和1分別與sample函數中的參數名x和size連了起來。你可以設置應該將哪個數據對象賦值給該函數的哪個參數,方法是將這個數據對象的名稱與參數名用等號連起來。在給有著多個參數的函數設置參數值的時候,這一點尤為重要;將數據對象明確指定給某個參數名可以避免錯誤傳遞數據。然而,對于R函數來說,并不一定要明確指定參數名。你會注意到,R用戶大都不會指定R函數的第一個參數的名稱。因此上面的代碼很可能會寫成下面的形式。
sample(die, size = 1)
## 2
通常來說,R函數的第一個參數的名稱都比較簡單,乍看很難知道它代表什么意思,但是第一個數據對象的含義往往是顯而易見的。
那么你如何知道應該調用哪個參數呢?如果你使用了一個該函數不能識別的參數名,那么很可能會得到一條錯誤的輸出信息。
round(3.1415, corners = 2) ## Error in round(3.1415, corners = 2) : unused argument(s) (corners = 2)
對于一個函數,如果你不確定應該如何設置其中的參數,可以通過args函數查看這個函數的所有參數名。具體來說,只要將函數的名稱放在args函數的括號中即可。從下面的例子可以看出,round函數有兩個參數,一個參數名是x,另一個是digits。
args(round) ## function (x, digits = 0) ## NULL
不知道大家注意到沒有,args函數顯示round的digits參數已被設置為0。因為它本身有一個默認值,所以是可選參數。R函數中經常會有digits這樣的可選參數。只要需要,你可以將一個不一樣的值賦給一個可選參數。如果不明確賦值,該可選參數就會使用其默認值。比如說,round函數會默認將數值四舍五入到小數點后的0位。要替代該默認值,可以為digits提供不同的值,如下所示。
round(3.1415, digits = 2)
## 3.14
在調用一個包含多個參數的函數時,從第二個參數或者第三個參數開始,應該寫出每個參數的名稱。為什么呢?首先,這有助于你自己或者其他的代碼閱讀者理解你的代碼。通常來說,第一個參數的含義指向是十分明確的(有時候第二個參數也如此)。但是,要記住每一個R函數的第三個或者第四個參數代表什么,是一件十分困難的事情。其次,更為重要的一點是,詳細地寫出參數名稱可以防止出現錯誤。
如果你沒有寫出參數名稱,那么R會按順序將你的值與函數中的參數匹配。例如,在下面的代碼中,第一個值是die,該值會與sample函數的第一個參數x匹配。第二個值是1,該值會與第二個參數size匹配。
sample(die, 1)
## 2
如果一個函數包含多個參數,那么你所使用的順序很可能與R的順序不一致。這可能會導致值被傳遞給錯誤的參數。而詳細地寫出參數名稱可以防止這樣的情況發生。R會始終將某個值與其參數名稱相匹配,而無論其參數順序如何。
sample(size = 1, x = die)
## 2
1.4 可放回抽樣
在上面的sample函數中,如果設定size = 2,就幾乎可以模擬一對骰子了。在運行代碼之前,花一分鐘想想為什么會是這樣。sample函數會返回兩個數字,分別對應每個骰子的點數。
sample(die, size = 2)
## 3 4
我剛才之所以說“幾乎”是因為這個方法的輸出結果有可疑之處。如果我們多次運行這樣的模擬,會發現第二個點數永遠不同于第一個點數,也就是說,你永遠不可能擲出兩個三點或者兩個一點。兩個骰子的點數不可能一模一樣。這到底是為什么呢?
原來,sample函數在抽樣時默認使用了不可放回抽樣(without replacement)。要理解這是什么意思,試想一下sample函數將die數據對象中所有可能的點數都放在了一個罐子中,然后將這些點數一個一個地拿出來,從而排出最終的點數樣本。一旦某個點數被取出來,sample函數就把它放在一邊,不會重新放進罐中,因此在接下來的抽樣中不可能再取到該點數。因此,如果sample函數第一次取的點數為6,那么它第二次就不會再取到同一個點數,因為點數6已經被從罐中拿了出來。雖然sample函數的取樣是計算機執行的,但是它遵循了這種物理規律。
這種抽樣方法的一個副作用就是每一次取樣的結果都與前一次取樣的結果息息相關。然而,在現實世界里,當我們擲骰子時,每個骰子之間都是相互獨立的。如果第一個骰子的點數是6,這并不妨礙第二個骰子的點數也是6。也就是說,第一次擲骰子的結果不應該對第二次的結果有任何影響。這樣的取樣邏輯也可以用sample函數實現,只不過需要額外設定參數replace = TRUE。
sample(die, size = 2, replace = TRUE) ## 5 5
參數replace = TRUE的作用就是將sample函數的抽樣類型設定為可放回抽樣(with replacement)。之前將骰子點數放入罐中的例子可以很好地解釋可放回抽樣與不可放回抽樣的區別。進行可放回抽樣時,sample函數從罐中取出一個點數并記錄下該點數的值,然后將該點數放回罐中。也就是說,sample函數在每次點數取樣之后都將該點數放回(replace)到原罐中。這樣的放回操作使得兩次擲骰子出現相同的點數成為可能。因為6個點數在每次抽樣中都有可能被取到,所以每一次抽樣都跟第一次抽樣沒有區別。
可放回抽樣法是創建獨立隨機樣本(independent random sample)的一種簡單方法。可以將樣本中的每一個值視為一個樣本量為1的獨立樣本,并且它與樣本中的其他數值是相互獨立的。這才是模擬擲一對骰子的正確方法。
sample(die, size = 2, replace = TRUE) ## 2 4
恭喜你,你已經用R嘗試運行了第一個模擬。現在,你已經掌握了模擬擲一對骰子的實現方法。如果你想知道兩個骰子的總點數,只需要將模擬的結果直接交給sum函數即可。
dice <- sample(die, size = 2, replace = TRUE) dice ## 2 4 sum(dice) ## 6
在dice生成之后,如果我們反復調用它會有什么樣的效果呢?R會在每次調用時都生成一對新的點數嗎?我們不妨試一下。
dice ## 2 4 dice ## 2 4 dice ## 2 4
顯然R并不會每次都生成一對新的點數。每次調用dice, R返回的都是同樣的數值,也就是之前用sample函數抽樣然后把抽樣的點數賦給dice的那一對數值。R并不會重新運行一次sample(die, 2, replace = TRUE)以生成一對新的點數。這其實是有一定益處的。每當將一組結果保存到一個R對象中之后,這些結果就不會改變了。如果對象的值在每次調用時都會發生改變,編程工作將變成一場噩夢。
然而,若有一個對象在每次調用時都重新擲一次骰子,可能會很方便。要實現這樣的目的,你需要編寫一個自定義的R函數。
- INSTANT OpenCV Starter
- JavaScript Unlocked
- ASP.NET動態網頁設計教程(第三版)
- Java程序設計
- SQL Server實用教程(SQL Server 2008版)
- Java Web開發實例大全(基礎卷) (軟件工程師開發大系)
- Angular Design Patterns
- Java EE架構設計與開發實踐
- 零基礎學編程系列(全5冊)
- Python計算機視覺與深度學習實戰
- 軟硬件綜合系統軟件需求建模及可靠性綜合試驗、分析、評價技術
- Jakarta EE Cookbook
- OpenStack Networking Cookbook
- C語言解惑
- Processing與Arduino互動編程