R語言編程入門--replicate()函數比較有意思!

I. 導論

簡單來講,編程是藉助計算機來解決某個問題。學習編程的就是訓練我們解決問題的能力。有這樣一種說法:在未來,不會編程的人即是文盲。

1 爲什麼要學習R編程

大部分情況下解決某些問題還需要依賴一些事實或數據,結合數據分析的框架和計算工具來幫助我們決策和判斷。這時候R語言編程就會派上用場。例如從大的方面來看,投資方要決定在何處建立風力發電場,就需要採集天氣數據加以建模分析,評估各項目方案。從小的方面來看,個人是否應該購買某個理財產品,你需要獲取過去的市場信息,模擬未來可能的變化,計算該項資產未來的期望收益和標準差。所以說學習R編程就是學習在數據環境中解決問題,從中磨練技術、鍛鍊智力,還能得到滿足的快感。

  • 學會R編程,才能理解高手的代碼,並從中領會其用意併成爲真正的高手。
  • 學會R編程,才能深入瞭解函數背後的理論,從而進一步解決從未有過的新問題。

2 如何學習R編程

  • 讀代碼
  • 寫代碼

編程無法在課堂或書本中學到,在游泳池裏學游泳是最佳的方法,也是唯一的方法。Learn Python The Hard Way一書的作者Zed A. Shaw曾說過“The Hard Way Is Easier”。所以就算是按照教材重複打一遍代碼,也會有相當的收穫。此外還要按照規範來編寫代碼,養成良好的習慣,包括各種符號的用法和良好的註釋。在註釋裏作筆記是也一個好的學習方法,很多時候你只需要將舊代碼略作修改就可以用到其它地方。

3 學習R編程的資源

  • 書籍

S Programming

The Art of R Programming

A First Course in Statistical Programming with R

software for data analysis programming with R

Introduction to Scientific Programming and Simulation Using R

  • 論壇和博客

http://cos.name/cn/forum/15

http://www.r-bloggers.com/

http://www.statmethods.net/index.html

http://zoonek2.free.fr/UNIX/48_R/all.html

http://www.rdatamining.com/

http://www.r-statistics.com/

http://www.inside-r.org/

http://r-ke.info/

http://wiki.stdout.org/rcookbook/

 

4 如何獲得幫助

R中的幫助文檔非常有用,其中有四種型的幫助

  • help(functionname) 對已經加載包所含的函數顯示其幫助文檔,用?號也是一樣的。
  • help.search('keyword') 對已經安裝的包搜索關鍵詞,用??號功能一樣。
  • help(package='packagename') 顯示已經安裝的包的描述和函數說明
  • RSiteSearch('keyword') 在官方網站上聯網搜索

5 R語言的啓動

  • R語言啓動後會首先查找有無.Rprofile文檔,用戶可通過編輯.Rprofile文檔來自定義R啓動環境,該文件可放在工作目錄或安裝目錄中。
  • 之後R會查找在工作目錄有無.RData文檔,若有的話將自動加載恢復之前的工作內容。
  • 在R中所有的默認輸入輸出文件都會在工作目錄中。getwd() 報告工作目錄,setwd() 負責設置工作目錄。在win窗口下也可以點擊Change Working Directory來更改。
  • Sys.getenv('R_HOME') 會報告R主程序安裝目錄
  • ?Startup可以得到更多關於R啓動時的幫助

 

II. 對象

 

R是一種基於對象(Object)的語言,所以你在R語言中接觸到的每樣東西都是一個對象,一串數值向量是一個對象,一個函數是一個對象,一個圖形也是一個對象。基於對象編程(OOP)就是在定義的基礎上,創建與操作對象

對象中包含了我們需要的數據,同時對象也具有很多屬性(Attribute)。其中一種重要的屬性就是它的(Class),R語言中最爲基本的包括了數值(numeric)、邏輯(logical)、字符(character)、列表(list),在此基礎上構成了一些複合型的,包括矩陣(matrix)、數組(array)、因子(factor)、數據框(dataframe)。除了這些內置的外還有很多其它的,用戶還可以自定義新的,但所有的都是建立在這些基本的之上的。

我們下面來用一個簡單線性迴歸的例子來了解一下對象和的處理。

1 # 創建兩個數值向量
2 x <- runif(100)
3 y <- rnorm(100)+5*x
4 # 用線性迴歸創建模型,存入對象model
5 model <- lm(y~x)

好了,現在我們手頭上有一個不熟悉的對象model,那麼首先來看看它裏面藏着什麼好東西。最有用的函數命令就是attributes(model),用來提取對象的各種屬性,結果如下:

< attributes(model)
$names
 [1] "coefficients"  "residuals"     "effects"     
 [4] "rank"          "fitted.values" "assign"     
 [7] "qr"            "df.residual"   "xlevels"     
[10] "call"          "terms"         "model"       

$class
[1] "lm"

可以看到這個對象的是“lm”,這意味着什麼呢?我們知道對於不同的有不同的處理方法,那麼對於modle這個對象,就有專門用來處理lm對象的函數,例如plot.lm()。但如果你用普通的函數plot()也一樣能顯示其圖形,Why?因爲plot()這種函數會自動識別對象的,從而選擇合適的函數來對付它,這種函數就稱爲泛型函數(generic function)。你可以用methods(class=lm)來了解有哪些函數可適用於lm對象。

好了,我們已經知道了model的底細了,你還想知道x的信息吧。如果運行attributes(x),會發現返回了空值。這是因爲x是一個向量,對於向量這種內置的基本,attributes是沒有什麼好顯示的。此時你可以運行mode(x),可觀察到向量的是數值型。如果運行mode(model)會有什麼反應呢?它會顯示lm的基本構成是由list組成的。當然要了解對象的,也可以直接用class(),如果要消除對象的則可用unclass()

從上面的結果我們還看到names這個屬性,這如同你到一家餐廳問服務生要一份菜單,輸入names(model)就相當於問model這個對象:Hi,你能提供什麼好東西嗎?如果你熟悉迴歸理論的話,就可以從names裏頭看到它提供了豐富的迴歸結果,包括迴歸係數(coefficients)、殘差(residuals)等等,調用這些信息可以就象處理普通的數據框一樣使用$符號,例如輸出殘差可以用model$residuals。當然用泛型函數可以達到同樣的效果,如residuals(model),但在個別情況下,這二者結果是有少許差別的。

我們已經知道了attributes的威力了,那麼另外一個非常有用的函數是str(),它能以簡潔的方式顯示對象的數據結構及其內容,試試看,非常有用的。

 

III. 輸入與輸出

 

如同ATM機一樣,你首先得輸入銀行卡,才能輸出得到鈔票。數據分析也是如此,輸入輸出數據在分析工作中有重要的地位。下面對R語言中一些重要的輸入輸出函數進行小結,而其它的函數請參考官方指南

1 讀取鍵盤輸入

如果只有很少的數據量,你可以直接用變量賦值輸入數據。若要用交互方式則可以使用readline()函數輸入單個數據,但要注意其默認輸入格爲字符型。scan()函數中如果不加參數則也可以用來手動輸入數據。如果加上文件名則是從文件中讀取數據。

2 讀取表格文件

讀取本地表格文件的主要函數是read.table(),其中的file參數設定了文件路徑,注意路徑中斜槓的正確用法(如"C:/data/sample.txt"),header參數設定是否帶有表頭。sep參數設定了列之間的間隔方式。該函數讀取數據後將存爲data.frame格式,而且所有的字符將被轉爲因子格式,如果你不想這麼做需要記得將參數stringsAsFactors設爲FALSE。與之似的函數是read.csv()專門用來讀取csv格式。

如果是想抓去網頁上的某個表格,那麼可以使用XML包中的readHTMLTable()函數。例如我們想獲得google統計的訪問最多的1000名網站數據,則可以象下面這樣做。

2 data <- readHTMLTable(url)
3 names(data)
4 head(data[[2]])

3 讀取文本文件

有時候需要讀取的數據存放在非結構化的文本文件中,例如電子郵件數據或微博數據。這種情況下只能依靠readLines()函數,將文檔轉爲以行爲單位存放的list格式。例如我們希望讀取wikipedia的主頁html文件的前十行。

1 data <- readLines('http://en.wikipedia.org/wiki/Main_Page',n=10)

另外,scan()也有豐富的參數用來讀取非結構化文檔。

 

4 批量讀取本地文件

在批量讀取文檔時一般先將其存放在某一個目錄下。先用dir()函數獲取目錄中的文件名,然後用paste()將路徑合成,最後用循環或向量化方法處理文檔。例如:

1 doc.names <- dir("path")
2 doc.path <- sapply(doc.names,function(names) paste(path,names,sep='/'))
3 doc <- sapply(doc.path, function(doc) readLines(doc))

5 寫入文件

write.table()write.csv()函數可以很方便的寫入表格型數據文檔,而cat()函數除了可以在屏幕上輸出之外,也能夠輸出成文件。

另外若要與MySQL數據庫交換數據,則可以使用RMySLQ包。

 

IV. 字符串處理

 

儘管R語言的主要處理對象是數字,而字符串有時候也會在數據分析中佔到相當大的份量。特別是在文本數據挖掘日趨重要的背景下,在數據預處理階段你需要熟練的操作字符串對象。當然如果你擅長其它的處理軟件,比如Python,可以讓它來負責前期的髒活。

獲取字符串長度:nchar()能夠獲取字符串的長度,它也支持字符串向量操作。注意它和length()的結果是有區別的。

字符串粘合:paste()負責將若干個字符串相連結,返回成單獨的字符串。其優點在於,就算有的處理對象不是字符型也能自動轉爲字符型。

字符串分割:strsplit()負責將字符串按照某種分割形式將其進行劃分,它正是paste()的逆操作。

字符串截取:substr()能對給定的字符串對象取出子集,其參數是子集所處的起始和終止位置。

字符串替代:gsub()負責搜索字符串的特定表達式,並用新的內容加以替代。sub()函數是似的,但只替代第一個發現結果。

字符串匹配:grep()負責搜索給定字符串對象中特定表達式 ,並返回其位置索引。grepl()函數與之類似,但其後面的"l"則意味着返回的將是邏輯值。

一個例子:

我們來看一個處理郵件的例子,目的是從該文本中抽取發件人的地址。該文本在此可以下載到。郵件的全文如下所示:

----------------------------
Return-Path: [email protected]
Delivery-Date: Sat Sep  7 05:46:01 2002
From: [email protected] (Skip Montanaro)
Date: Fri, 6 Sep 2002 23:46:01 -0500
Subject: [Spambayes] speed
Message-ID: <[email protected]>

If the frequency of my laptop's disk chirps are any indication, I'd say
hammie is about 3-5x faster than SpamAssassin.

Skip
----------------------------

 

01 # 用readLines函數從本地文件中讀取郵件全文。
02 data <- readLines('data'
03 # 判斷對象的類,確定是一個文本型向量,每行文本是向量的一個元素。
04 class(data) 
05 # 從這個文本向量中找到包括有"From:"字符串的那一行
06 email <- data[grepl('From:',data)]
07 #將其按照空格進行分割,分成一個包括四個元素的字符串向量。
08 from <- strsplit(email,' ')
09 # 上面的結果是一個list格式,轉成向量格式。
10 from <- unlist(from)
11 # 最後搜索包含'@'的元素,即爲發件人郵件地址。
12 from <- from[grepl('@',from)]

在字符串的複雜操作中通常會包括正則表達式(Regular Expressions),關於這方面內容可以參考?regex

 

V. 向量化運算

 

和matlab一樣,R語言以向量爲基本運算對象。也就是說,當輸入的對象爲向量時,對其中的每個元素分別進行處理,然後以向量的形式輸出。R語言中基本上所有的數據運算均能允許向量操作。不僅如此,R還包含了許多高效的向量運算函數,這也是它不同於其它軟件的一個顯著特徵。向量化運算的好處在於避免使用循環,使代碼更爲簡潔、高效和易於理解。本文來對apply族函數作一個簡單的歸納,以便於大家理解其中的區別所在。

所謂apply族函數包括了apply,sapply,lappy,tapply等函數,這些函數在不同的情況下能高效的完成複雜的數據處理任務,但角色定位又有所不同。

 

apply()函數的處理對象是矩陣或數組,它逐行或逐列的處理數據,其輸出的結果將是一個向量或是矩陣。下面的例子即對一個隨機矩陣求每一行的均值。要注意的是apply與其它函數不同,它並不能明顯改善計算效率,因爲它本身內置爲循環運算。

1 m.data <- matrix(rnorm(100),ncol=10)
2 apply(m.data,1,mean)

lappy()的處理對象是向量、列表或其它對象,它將向量中的每個元素作爲參數,輸入到處理函數中,最後生成結果的格式爲列表。在R中數據框是一種特殊的列表,所以數據框的列也將作爲函數的處理對象。下面的例子即對一個數據框按列來計算中位數與標準差。

1 f.data <- data.frame(x=rnorm(10),y=runif(10))
2 lapply(f.data,FUN=function(x) list(median=median(x),sd=sd(x))


sapply()可能是使用最爲頻繁的向量化函數了,它和lappy()是非常相似的,但其輸出格式則是較爲友好的矩陣格式。

1 sapply(f.data,FUN=function(x)list(median=median(x),sd=sd(x)))
2 class(test)

tapply()的功能則又有不同,它是專門用來處理分組數據的,其參數要比sapply多一個。我們以iris數據集爲例,可觀察到Species列中存放了三種花的名稱,我們的目的是要計算三種花瓣萼片寬度的均值。其輸出結果是數組格式。

 

1 head(iris)
2 attach(iris)
3 tapply(Sepal.Width,INDEX=Species,FUN=mean)

與tapply功能非常相似的還有aggregate(),其輸出是更爲友好的數據框格式。而by()和上面兩個函數是同門師兄弟。

另外還有一個非常有用的函數replicate(),它可以將某個函數重複運行N次,常常用來生成較複雜的隨機數。下面的例子即先建立一個函數,模擬扔兩個骰子的點數之和,然後重複運行10000次。

1 game <- function() {
2 n <- sample(1:6,2,replace=T)
3 return(sum(n))
4 }
5 replicate(n=10000,game())

最後一個有趣的函數Vectorize(),它能將一個不能進行向量化運算的函數進行轉化,使之具備向量化運算功能。

 

VI. 循環與條件

 

循環

for (n in x) {expr}

R中最基本的是for循環,其中n爲循環變量,x通常是一個序列。n在每次循環時從x中順序取值,代入到後面的expr語句中進行運算。下面的例子即是以for循環計算30個Fibonacci數。

1 x <- c(1,1)
2 for (i in 3:30) {
3 x[i] <- x[i-1]+x[i-2]
4 }

while (condition) {expr}

當不能確定循環次數時,我們需要用while循環語句。在condition條件爲真時,執行大括號內的expr語句。下面即是以while循環來計算30個Fibonacci數。

1 x <- c(1,1)
2 i <- 3
3 while (i &lt;= 30) {
4 x[i] <- x[i-1]+x[i-2]
5 i <- i +1
6 }

條件

if (conditon) {expr1} else {expr2}

if語句用來進行條件控制,以執行不同的語句。若condition條件爲真,則執行expr1,否則執行expr2。ifesle()函數也能以簡潔的方式構成條件語句。下面的一個簡單的例子是要找出100以內的質數。

1 x <- 1:100
2 y <- rep(T,100)
3 for (i in 3:100) {
4 if (all(i%%(2:(i-1))!=0)){
5 y[i] <- TRUE
6 else {y[i] <- FALSE
7 }
8 }
9 print(x[y])

在上面例子裏,all()函數的作用是判斷一個邏輯序列是否全爲真,%%的作用是返回餘數。在if/else語句中一個容易出現的錯誤就是else沒有放在}的後面,若你執行下面的示例就會出現錯誤。

1 logic = 3
2 x<- c(2,3)
3 if (logic == 2){
4 y <- x^2
5 }
6 else {
7 y<-x^3
8 }
9 show(y)

一個例子

本例來自於"introduction to Scientific Programming and Simulatoin Using R"一書的習題。有這樣一種賭博遊戲,賭客首先將兩個骰子隨機拋擲第一次,如果點數和出現7或11,則贏得遊戲,遊戲結束。如果沒有出現7或11,賭客繼續拋擲,如果點數與第一次扔的點數一樣,則贏得遊戲,遊戲結束,如果點數爲7或11則輸掉遊戲,遊戲結束。如果出現其它情況,則繼續拋擲,直到贏或者輸。用R編程來計算賭客贏的概率,以決定是否應該參加這個遊戲。

01 craps <- function() {
02 #returns TRUE if you win, FALSE otherwise
03 initial.roll <- sum(sample(1:6,2,replace=T))
04 if (initial.roll == 7 || initial.roll == 11) return(TRUE)
05 while (TRUE) {
06 current.roll <- sum(sample(1:6,2,replace=T))
07 if (current.roll == 7 || current.roll == 11) {
08 return(FALSE)
09 else if (current.roll == initial.roll) {
10 return(TRUE)
11 }
12 }
13 }
14 mean(replicate(10000, craps()))

從最終結果來看,賭客贏的概率爲0.46,長期來看只會往外掏錢,顯然不應該參加這個遊戲了。最後要說的是,本題也可以用遞歸來做。

 

VII. 程序查錯

寫程序難免會出錯,有時候一個微小的錯誤需要花很多時間來調試程序來修正它。所以掌握必要的調試方法能避免很多的無用功。

基本的除錯方法是跟蹤重要變量的賦值情況。在循環或條件分支代碼中加入顯示函數能完成這個工作。例如cat('var',var,'\n')。在確認程序運行正常後,可以將這行代碼進行註釋。好的編程風格也能有效的減少出錯的機會。在編寫代碼時先寫出一個功能最爲簡單的功能,然後在此基礎上逐步添加其它複雜的功能。對輸出結果進行繪圖或統計彙總也能揭示一些潛在的問題。

另一種避免出錯的方法是儘量使用函數。使用函數能將一個大的程序分解成幾個小型的模塊。一個函數模塊只負責實現某一種功能的實現。這樣容易理解程序,而且容易針對各函數的輸入、計算、輸出分別進行查錯調試。R語言中函數的運行不會影響到全局變量,所以使用函數基本上不會有什麼副作用。

但是在使用函數時需要注意的問題是輸入參數的不可預測性。未預料到的輸入參數會產生奇怪的或是錯誤的輸出,所以在函數起始部分就要用條件語句來檢查參數的正確與否。如果輸入參數不正確,可以用下面的語句來停止程序執行stop('your message here.')

對函數進行調試的重要工具是browser(),它可以使我們進入調試模式逐行運行代碼。在函數中的某一行插入browser()後,在函數執行時會在這一行暫停中斷,並顯示一個提示符。此時我們可以在提示符後輸入任何R語言的交互式命令進行檢查調試。輸入n則會逐行運行程序,並提示下一行將運行的語句。輸入c會直接跳到下一個中斷點。而輸入Q則會直接跟出調試模式。

debug()函數和browser()是相似的,如果你認爲某個函數,例如fx(x),有問題的話,使用debug(fx(x))即可進入調試模式。它本質上是在函數的第一行加入了browser,所以其它提示和命令都是相同的。其它與程序調試有關的函數還包括:trace(),setBreakpoint(),traceback(),recover()

 

參考資料:
 

http://xccds1977.blogspot.com/2012/02/r_28.html

如何成爲一名黑客 :http://dongxi.net/b14rH
How to be a Programmer : http://samizdat.mines.edu/howto/HowToBeAProgrammer.html
Teach Yourself Programming in Ten Years : http://norvig.com/21-days.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章