林嶔 (Lin, Chin)
Lesson 6 列表簡介與應用
– 如果你有注意到的話,迴圈運行速度其實是越來越慢,這個問題其實是出在我們的函數「rbind」,這個函數雖然能夠方便的把兩個資料表合併成一個,但他的過程其實對記憶體很不友善的!
– 列表(List)層分為列表(list)、S3物件(S3 class)及S4物件(S4 class):
列表(list):在R裡面,向量的上層是陣列層物件。若是我們希望在一個物件內放置很多陣列層物件,我們會用到列表。値得一提的是,列表裡面可以同時包含數個陣列層物件及變數層物件。
S3物件(S3 class):S3物件是一種特殊的列表物件,他的變化會在後面慢慢介紹。
S4物件(S4 class):S4物件與前面兩種有非常大的不同,相關的函數也不一樣,在本節課我們不會教到。
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 5 9 13 17
## [2,] 2 6 10 14 18
## [3,] 3 7 11 15 19
## [4,] 4 8 12 16 20
# 再產生一個文字矩陣物件
x2 = c("A", "B", "C", "A", "C", "B", "B", "B", "A")
M2 = matrix(x2, nrow = 3, ncol = 3)
M2
## [,1] [,2] [,3]
## [1,] "A" "A" "B"
## [2,] "B" "C" "B"
## [3,] "C" "B" "A"
## [1] TRUE FALSE TRUE FALSE
## [[1]]
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 5 9 13 17
## [2,] 2 6 10 14 18
## [3,] 3 7 11 15 19
## [4,] 4 8 12 16 20
##
## [[2]]
## [,1] [,2] [,3]
## [1,] "A" "A" "B"
## [2,] "B" "C" "B"
## [3,] "C" "B" "A"
##
## [[3]]
## [1] TRUE FALSE TRUE FALSE
函數「length()」可以協助我們了解物件長度
函數「class()」可以查詢該物件的屬性
函數「names()」可以協助我們命名物件
函數「ls()」可以協助我們看看物件中有哪些東西
## [1] 3
## [1] "list"
## $A
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 5 9 13 17
## [2,] 2 6 10 14 18
## [3,] 3 7 11 15 19
## [4,] 4 8 12 16 20
##
## $B
## [,1] [,2] [,3]
## [1,] "A" "A" "B"
## [2,] "B" "C" "B"
## [3,] "C" "B" "A"
##
## $C
## [1] TRUE FALSE TRUE FALSE
## [1] "A" "B" "C"
## [,1] [,2] [,3]
## [1,] "A" "A" "B"
## [2,] "B" "C" "B"
## [3,] "C" "B" "A"
## [,1] [,2] [,3]
## [1,] "A" "A" "B"
## [2,] "B" "C" "B"
## [3,] "C" "B" "A"
## [,1] [,2] [,3]
## [1,] "A" "A" "B"
## [2,] "B" "C" "B"
## [3,] "C" "B" "A"
## [1] "B"
## [1] "C"
## [1] "A"
經過了上述的示範後,我們了解到列表(list)是一個很方便的物件,它可以把很多很雜的東西丟在同個物件內。但東西多了以後會遇到問題,那就是該列表物件會變的非常非常大,但也許我們想要呈現的東西是很有限的,在R裡面,列表有一種擴展型態叫做S3物件(S3 class),它可以解決這個問題。
S3物件(S3 class)的產生方式如下
## $A
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 5 9 13 17
## [2,] 2 6 10 14 18
## [3,] 3 7 11 15 19
## [4,] 4 8 12 16 20
##
## $B
## [,1] [,2] [,3]
## [1,] "A" "A" "B"
## [2,] "B" "C" "B"
## [3,] "C" "B" "A"
##
## $C
## [1] TRUE FALSE TRUE FALSE
## [1] "list"
## [1] "test"
## $A
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 5 9 13 17
## [2,] 2 6 10 14 18
## [3,] 3 7 11 15 19
## [4,] 4 8 12 16 20
##
## $B
## [,1] [,2] [,3]
## [1,] "A" "A" "B"
## [2,] "B" "C" "B"
## [3,] "C" "B" "A"
##
## $C
## [1] TRUE FALSE TRUE FALSE
##
## attr(,"class")
## [1] "test"
– 小提示:當你使用函數「class()」可以查詢該物件的屬性,若非常見的幾種屬性名稱,那就非常有可能是S3物件(S3 class)或S4物件(S4 class)
#先寫一個自訂函數「print.test()」
print.test = function(test) {
cat("此列表共有",length(test),"個物件\n")
cat("物件名稱分別為:\n")
cat(paste(names(test), collapse = ", "), "\n")
}
#再看看請R列印出L1會變什麼
L1
## 此列表共有 3 個物件
## 物件名稱分別為:
## A, B, C
– 列表(list)的幾個常見函數還是能夠使用:
## [1] "A" "B" "C"
## [1] 3
## [1] "test"
## 此列表共有 3 個物件
## 物件名稱分別為:
## D, E, F
– 在寫之前我們先看看直接對L1使用函數「summary()」會怎樣
## Length Class Mode
## D 20 -none- numeric
## E 9 -none- character
## F 4 -none- logical
– 現在我們可以讓函數「summary()」使用後產生不同的結果
#先寫一個自訂函數「summary.test()」
summary.test = function(test) {
cat("此列表共有",length(test),"個物件\n")
cat("物件名稱分別為:\n")
cat(paste(names(test), collapse = ", "), "\n")
for (i in 1:length(test)) {
cat(names(test)[i], "之物件屬性為", class(test[[i]]), "\n")
}
}
#再看看使用函數「summary()」後會變什麼
summary(L1)
## 此列表共有 3 個物件
## 物件名稱分別為:
## D, E, F
## D 之物件屬性為 matrix
## E 之物件屬性為 matrix
## F 之物件屬性為 logical
我們已經學會如何將想要的資訊放在列表(list)物件中,並透過將這個物件轉換為一個特定的S3物件(S3 class) 後,就可以透過自訂函數「print.XXX()」呈現想要的結果。
我們現在希望能把這個列表轉為S3物件,並且讓他的輸出改為這種格式:
## $student
## [1] "小明" "小華" "小愛"
##
## $score
## [1] 80 90 75
## 小明 的分數為 80
## 小華 的分數為 90
## 小愛 的分數為 75
指定一個物件名稱
編寫特定的「print」函數
Test_list = list(student = c('小明', '小華', '小愛'),
score = c(80, 90, 75))
class(Test_list) = 'My_list'
print.My_list = function(Test_list) {
for (i in 1:length(Test_list[[1]])) {
cat(Test_list[[1]][i], "的分數為", Test_list[[2]][i], "\n")
}
}
Test_list
## 小明 的分數為 80
## 小華 的分數為 90
## 小愛 的分數為 75
– 讓我們做個小測試,假設我們不斷的將一個完全相同的資料表用rbind指令合併,和先使用列表儲存,看看時間差異有多大:
– 這是連續合併1000次的耗時:
t0 = Sys.time()
base_dat = data.frame(X = rnorm(20), Y = rnorm(20))
final_dat = NULL
for (i in 1:1000) {
final_dat = rbind(final_dat, base_dat)
}
Sys.time() - t0 #用現在時間減去開始時間
## Time difference of 0.3860843 secs
– 這是連續合併2000次的耗時:
t0 = Sys.time()
base_dat = data.frame(X = rnorm(20), Y = rnorm(20))
final_dat = NULL
for (i in 1:2000) {
final_dat = rbind(final_dat, base_dat)
}
Sys.time() - t0 #用現在時間減去開始時間
## Time difference of 1.588761 secs
– 這是連續合併4000次的耗時:
t0 = Sys.time()
base_dat = data.frame(X = rnorm(20), Y = rnorm(20))
final_dat = NULL
for (i in 1:4000) {
final_dat = rbind(final_dat, base_dat)
}
Sys.time() - t0 #用現在時間減去開始時間
## Time difference of 8.261805 secs
– 這是連續合併1000次的耗時:
t0 = Sys.time()
base_dat = data.frame(X = rnorm(20), Y = rnorm(20))
dat_list = list()
for (i in 1:1000) {
dat_list[[i]] = base_dat
}
Sys.time() - t0 #用現在時間減去開始時間
## Time difference of 0.006219149 secs
– 這是連續合併2000次的耗時:
t0 = Sys.time()
base_dat = data.frame(X = rnorm(20), Y = rnorm(20))
dat_list = list()
for (i in 1:2000) {
dat_list[[i]] = base_dat
}
Sys.time() - t0 #用現在時間減去開始時間
## Time difference of 0.006496668 secs
– 這是連續合併4000次的耗時:
t0 = Sys.time()
base_dat = data.frame(X = rnorm(20), Y = rnorm(20))
dat_list = list()
for (i in 1:4000) {
dat_list[[i]] = base_dat
}
Sys.time() - t0 #用現在時間減去開始時間
## Time difference of 0.007715464 secs
但這樣還遠遠不夠,我們還是需要把這個列表合併成同一個資料表,我們該怎麼做呢?
請在這裡下載資料
– 但這裡需要用到函數「do.call」,他的用法是把list裡面的物件當作參數:
dat1 = read.csv("laboratory_2.csv", header = TRUE, fileEncoding = 'CP950')
param_list = list(file = "laboratory_2.csv", header = TRUE, fileEncoding = 'CP950')
dat2 = do.call("read.csv", param_list)
all.equal(dat1, dat2)
## [1] TRUE
– 函數「rbind」與函數「do.call」的搭配將能完成任務:
t0 = Sys.time()
base_dat = data.frame(X = rnorm(20), Y = rnorm(20))
dat_list = list()
for (i in 1:4000) {
dat_list[[i]] = base_dat
}
my_data = do.call("rbind", dat_list)
Sys.time() - t0 #用現在時間減去開始時間
## Time difference of 0.1262248 secs
## [1] 80000
## X Y
## 1 0.14226899 -0.7272419
## 2 -0.61010852 2.3682651
## 3 -0.04489173 0.2426155
## 4 0.98061624 1.3891229
## 5 0.52803578 0.6951405
## 6 -0.33132006 -0.3813124
– 這次請你學著使用列表特性加速過程!這是上節課的答案
dat = read.csv("laboratory_2.csv", header = TRUE, fileEncoding = 'CP950')
levels.TESTNAME = levels(dat[,'TESTNAME'])
n.TESTNAME = length(levels.TESTNAME)
levels.PATNUMBER = levels(as.factor(dat[,'PATNUMBER']))
n.PATNUMBER = length(levels.PATNUMBER)
final.data = NULL
pb = txtProgressBar(max = n.PATNUMBER, style=3)
for (i in 1:n.PATNUMBER) {
subdat = dat[dat[,'PATNUMBER']==levels.PATNUMBER[i],]
subdat[,'COLLECTIONDATE'] = as.factor(as.character(subdat[,'COLLECTIONDATE']))
levels.COLLECTIONDATE = levels(subdat[,'COLLECTIONDATE'])
n.COLLECTIONDATE = length(levels.COLLECTIONDATE)
submatrix = matrix(NA, nrow = n.COLLECTIONDATE, ncol = n.TESTNAME+2)
colnames(submatrix) = c("PATNUMBER", "COLLECTIONDATE", levels.TESTNAME)
submatrix[,1] = levels.PATNUMBER[i]
submatrix[,2] = levels.COLLECTIONDATE
for (j in 1:n.COLLECTIONDATE) {
subsubdat = subdat[subdat[,'COLLECTIONDATE']==levels.COLLECTIONDATE[j],]
for (k in 1:nrow(subsubdat)) {
NAME = subsubdat[k,'TESTNAME']
position = which(NAME == levels.TESTNAME) + 2
VALUE = subsubdat[k,'RESVALUE']
MINIMUM = subsubdat[k,'MINIMUM']
MAXIMUM = subsubdat[k,'MAXIMUM']
if (is.na(MINIMUM)) {MINIMUM = -Inf}
if (is.na(MAXIMUM)) {MAXIMUM = Inf}
submatrix[j, position] = (VALUE >= MINIMUM & VALUE <= MAXIMUM)
}
}
final.data = rbind(final.data, submatrix)
setTxtProgressBar(pb, i)
}
close(pb)
head(final.data)
levels.TESTNAME = levels(dat[,'TESTNAME'])
n.TESTNAME = length(levels.TESTNAME)
levels.PATNUMBER = levels(as.factor(dat[,'PATNUMBER']))
n.PATNUMBER = length(levels.PATNUMBER)
final.data_list = list() # 改這裡
pb = txtProgressBar(max = n.PATNUMBER, style=3)
for (i in 1:n.PATNUMBER) {
subdat = dat[dat[,'PATNUMBER']==levels.PATNUMBER[i],]
subdat[,'COLLECTIONDATE'] = as.factor(as.character(subdat[,'COLLECTIONDATE']))
levels.COLLECTIONDATE = levels(subdat[,'COLLECTIONDATE'])
n.COLLECTIONDATE = length(levels.COLLECTIONDATE)
submatrix = matrix(NA, nrow = n.COLLECTIONDATE, ncol = n.TESTNAME+2)
colnames(submatrix) = c("PATNUMBER", "COLLECTIONDATE", levels.TESTNAME)
submatrix[,1] = levels.PATNUMBER[i]
submatrix[,2] = levels.COLLECTIONDATE
for (j in 1:n.COLLECTIONDATE) {
subsubdat = subdat[subdat[,'COLLECTIONDATE']==levels.COLLECTIONDATE[j],]
for (k in 1:nrow(subsubdat)) {
NAME = subsubdat[k,'TESTNAME']
position = which(NAME == levels.TESTNAME) + 2
VALUE = subsubdat[k,'RESVALUE']
MINIMUM = subsubdat[k,'MINIMUM']
MAXIMUM = subsubdat[k,'MAXIMUM']
if (is.na(MINIMUM)) {MINIMUM = -Inf}
if (is.na(MAXIMUM)) {MAXIMUM = Inf}
submatrix[j, position] = (VALUE >= MINIMUM & VALUE <= MAXIMUM)
}
}
final.data_list[[i]] = submatrix # 改這裡
setTxtProgressBar(pb, i)
}
close(pb)
final.data = do.call("rbind", final.data_list) # 改這裡
head(final.data)
列表僅僅是索引,因此為其增加物件並不需要額外複製的動作,因此原則上使用這個物件格式進行操作
如果一定要在資料表內完成任務,那預先創造好一個空的資料表,讓程式為其填數字較有效率