物件導向(Object-Oriented Programming,OOP)很紅,大家都把物件導向掛在嘴上。 R 語言也在 OOP 的潮流下,逐漸往物件導向的方向演化。
在 R 裡面,每一樣「東西」都叫做「物件(object)」,也就是,不管是向量(vector)、矩陣(matrix)、陣列(array)、列表(list)、資料框架(data frame)……都是 object 的不同類型(type)。我們可以透過 typeof()
函式來查看物件的類別(class)
物件的類別(Class)
上一篇介紹了物件屬性(attributes)的概念,而一種很特別的物件屬性叫做物件的「類別(class)」。
例如,某個物件的類別是 data frame(沒錯,data frame 就是用 list 物件所做出來的 special class),那麼當我們使用 plot()
函式去跑這個 data frame ,則這個物件會以特別的形式被印出來。
如果許多 objects 被定義為相同的 class ,那麼這些 objects 將會擁有相同的 attributes 。
S3 Class
R 語言中提供了一種泛型函數(generic function),它可以接受屬於各種不同 class 的 objects,為這些 objects 調用合適的方法(而此泛型函數本身並不做計算)。
舉例而言,
x = c(1,2,3)
print(x)
## [1] 1 2 3
print(x)
就是一個對 x
執行的泛型函數,其運作過程是:
- 找到
x
的 class 屬性是什麼 >>> 是一個 vector - 把這個 class 屬性裡的每個元素(
1, 2, 3
)都透過.
連接起來
其中在第 2 步對於這種 class 所設計而執行的動作,就是針對它而做的 method 。 我們可以使用 methods(classname)
來看看該特定 classname 可以用的泛型函數有哪些
methods(print)
## [1] print.acf*
## [2] print.AES*
## [3] print.anova*
## [4] print.aov*
## [5] print.aovlist*
## [6] print.ar*
## [7] print.Arima*
## [8] print.arima0*
## [9] print.AsIs
## [10] print.aspell*
## [11] print.aspell_inspect_context*
## [12] print.bibentry*
## [13] print.Bibtex*
## [14] print.browseVignettes*
## [15] print.by
## [16] print.bytes*
## [17] print.changedFiles*
## [18] print.check_code_usage_in_package*
## [19] print.check_compiled_code*
## [20] print.check_demo_index*
## [21] print.check_depdef*
## [22] print.check_dotInternal*
## [23] print.check_make_vars*
## [24] print.check_nonAPI_calls*
## [25] print.check_package_code_assign_to_globalenv*
## [26] print.check_package_code_attach*
## [27] print.check_package_code_data_into_globalenv*
## [28] print.check_package_code_startup_functions*
## [29] print.check_package_code_syntax*
## [30] print.check_package_code_unload_functions*
## [31] print.check_package_compact_datasets*
## [32] print.check_package_CRAN_incoming*
## [33] print.check_package_datasets*
## [34] print.check_package_depends*
## [35] print.check_package_description*
## [36] print.check_package_description_encoding*
## [37] print.check_package_license*
## [38] print.check_packages_in_dir*
## [39] print.check_packages_in_dir_changes*
## [40] print.check_packages_used*
## [41] print.check_po_files*
## [42] print.check_Rd_contents*
## [43] print.check_Rd_line_widths*
## [44] print.check_Rd_metadata*
## [45] print.check_Rd_xrefs*
## [46] print.check_so_symbols*
## [47] print.check_T_and_F*
## [48] print.check_url_db*
## [49] print.check_vignette_index*
## [50] print.checkDocFiles*
## [51] print.checkDocStyle*
## [52] print.checkFF*
## [53] print.checkRd*
## [54] print.checkReplaceFuns*
## [55] print.checkS3methods*
## [56] print.checkTnF*
## [57] print.checkVignettes*
## [58] print.citation*
## [59] print.codoc*
## [60] print.codocClasses*
## [61] print.codocData*
## [62] print.colorConverter*
## [63] print.compactPDF*
## [64] print.condition
## [65] print.connection
## [66] print.data.frame
## [67] print.Date
## [68] print.default
## [69] print.dendrogram*
## [70] print.density*
## [71] print.difftime
## [72] print.dist*
## [73] print.Dlist
## [74] print.DLLInfo
## [75] print.DLLInfoList
## [76] print.DLLRegisteredRoutines
## [77] print.dummy_coef*
## [78] print.dummy_coef_list*
## [79] print.ecdf*
## [80] print.factanal*
## [81] print.factor
## [82] print.family*
## [83] print.fileSnapshot*
## [84] print.findLineNumResult*
## [85] print.formula*
## [86] print.fseq*
## [87] print.ftable*
## [88] print.function
## [89] print.getAnywhere*
## [90] print.glm*
## [91] print.hclust*
## [92] print.help_files_with_topic*
## [93] print.hexmode
## [94] print.HoltWinters*
## [95] print.hsearch*
## [96] print.hsearch_db*
## [97] print.htest*
## [98] print.html*
## [99] print.html_dependency*
## [100] print.infl*
## [101] print.integrate*
## [102] print.isoreg*
## [103] print.kmeans*
## [104] print.knitr_kable*
## [105] print.Latex*
## [106] print.LaTeX*
## [107] print.libraryIQR
## [108] print.listof
## [109] print.lm*
## [110] print.loadings*
## [111] print.loess*
## [112] print.logLik*
## [113] print.ls_str*
## [114] print.medpolish*
## [115] print.MethodsFunction*
## [116] print.mtable*
## [117] print.NativeRoutineList
## [118] print.news_db*
## [119] print.nls*
## [120] print.noquote
## [121] print.numeric_version
## [122] print.object_size*
## [123] print.octmode
## [124] print.packageDescription*
## [125] print.packageInfo
## [126] print.packageIQR*
## [127] print.packageStatus*
## [128] print.pairwise.htest*
## [129] print.PDF_Array*
## [130] print.PDF_Dictionary*
## [131] print.pdf_doc*
## [132] print.pdf_fonts*
## [133] print.PDF_Indirect_Reference*
## [134] print.pdf_info*
## [135] print.PDF_Keyword*
## [136] print.PDF_Name*
## [137] print.PDF_Stream*
## [138] print.PDF_String*
## [139] print.person*
## [140] print.POSIXct
## [141] print.POSIXlt
## [142] print.power.htest*
## [143] print.ppr*
## [144] print.prcomp*
## [145] print.princomp*
## [146] print.proc_time
## [147] print.raster*
## [148] print.Rd*
## [149] print.recordedplot*
## [150] print.restart
## [151] print.RGBcolorConverter*
## [152] print.rle
## [153] print.roman*
## [154] print.sessionInfo*
## [155] print.shiny.tag*
## [156] print.shiny.tag.list*
## [157] print.simple.list
## [158] print.smooth.spline*
## [159] print.socket*
## [160] print.srcfile
## [161] print.srcref
## [162] print.stepfun*
## [163] print.stl*
## [164] print.StructTS*
## [165] print.subdir_tests*
## [166] print.summarize_CRAN_check_status*
## [167] print.summary.aov*
## [168] print.summary.aovlist*
## [169] print.summary.ecdf*
## [170] print.summary.glm*
## [171] print.summary.lm*
## [172] print.summary.loess*
## [173] print.summary.manova*
## [174] print.summary.nls*
## [175] print.summary.packageStatus*
## [176] print.summary.ppr*
## [177] print.summary.prcomp*
## [178] print.summary.princomp*
## [179] print.summary.table
## [180] print.summaryDefault
## [181] print.table
## [182] print.tables_aov*
## [183] print.terms*
## [184] print.ts*
## [185] print.tskernel*
## [186] print.TukeyHSD*
## [187] print.tukeyline*
## [188] print.tukeysmooth*
## [189] print.undoc*
## [190] print.vignette*
## [191] print.warnings
## [192] print.xgettext*
## [193] print.xngettext*
## [194] print.xtabs*
## see '?methods' for accessing help and source code
再舉個例子,例如一個很好用的函式 summary()
,它可以用在很多不同 class 的 objects 上面
# mtcars 是一個 data frame
mtcars %>% str
## 'data.frame': 32 obs. of 11 variables:
## $ mpg : num 21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
## $ cyl : num 6 6 4 6 8 6 8 4 4 6 ...
## $ disp: num 160 160 108 258 360 ...
## $ hp : num 110 110 93 110 175 105 245 62 95 123 ...
## $ drat: num 3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
## $ wt : num 2.62 2.88 2.32 3.21 3.44 ...
## $ qsec: num 16.5 17 18.6 19.4 17 ...
## $ vs : num 0 0 1 1 0 1 0 1 1 1 ...
## $ am : num 1 1 1 0 0 0 0 0 0 0 ...
## $ gear: num 4 4 4 3 3 3 3 4 4 4 ...
## $ carb: num 4 4 1 1 2 1 4 2 2 4 ...
# seq.date 是一連串 Date format
seq.date = seq(as.Date("2016/4/1"), as.Date("2016/6/30"), "days")
seq.date %>% str
## Date[1:91], format: "2016-04-01" "2016-04-02" "2016-04-03" "2016-04-04" ...
但是這兩個不同 class 的物件,都可以使用 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
## drat wt qsec vs
## Min. :2.760 Min. :1.513 Min. :14.50 Min. :0.0000
## 1st Qu.:3.080 1st Qu.:2.581 1st Qu.:16.89 1st Qu.:0.0000
## Median :3.695 Median :3.325 Median :17.71 Median :0.0000
## Mean :3.597 Mean :3.217 Mean :17.85 Mean :0.4375
## 3rd Qu.:3.920 3rd Qu.:3.610 3rd Qu.:18.90 3rd Qu.:1.0000
## Max. :4.930 Max. :5.424 Max. :22.90 Max. :1.0000
## am gear carb
## Min. :0.0000 Min. :3.000 Min. :1.000
## 1st Qu.:0.0000 1st Qu.:3.000 1st Qu.:2.000
## Median :0.0000 Median :4.000 Median :2.000
## Mean :0.4062 Mean :3.688 Mean :2.812
## 3rd Qu.:1.0000 3rd Qu.:4.000 3rd Qu.:4.000
## Max. :1.0000 Max. :5.000 Max. :8.000
summary(seq.date)
## Min. 1st Qu. Median Mean 3rd Qu.
## "2016-04-01" "2016-04-23" "2016-05-16" "2016-05-16" "2016-06-07"
## Max.
## "2016-06-30"
這是為什麼呢?我們可以來看一下 summary 泛型函式擁有哪些 method
methods(summary)
## [1] summary.aov summary.aovlist*
## [3] summary.aspell* summary.check_packages_in_dir*
## [5] summary.connection summary.data.frame
## [7] summary.Date summary.default
## [9] summary.ecdf* summary.factor
## [11] summary.glm summary.infl*
## [13] summary.lm summary.loess*
## [15] summary.manova summary.matrix
## [17] summary.mlm* summary.nls*
## [19] summary.packageStatus* summary.PDF_Dictionary*
## [21] summary.PDF_Stream* summary.POSIXct
## [23] summary.POSIXlt summary.ppr*
## [25] summary.prcomp* summary.princomp*
## [27] summary.proc_time summary.srcfile
## [29] summary.srcref summary.stepfun
## [31] summary.stl* summary.table
## [33] summary.tukeysmooth*
## see '?methods' for accessing help and source code
我們看到其中有 summary.data.frame
還有 summary.Date
。那麼來做個實驗
summary.data.frame(mtcars) # 出來的結果和 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
## drat wt qsec vs
## Min. :2.760 Min. :1.513 Min. :14.50 Min. :0.0000
## 1st Qu.:3.080 1st Qu.:2.581 1st Qu.:16.89 1st Qu.:0.0000
## Median :3.695 Median :3.325 Median :17.71 Median :0.0000
## Mean :3.597 Mean :3.217 Mean :17.85 Mean :0.4375
## 3rd Qu.:3.920 3rd Qu.:3.610 3rd Qu.:18.90 3rd Qu.:1.0000
## Max. :4.930 Max. :5.424 Max. :22.90 Max. :1.0000
## am gear carb
## Min. :0.0000 Min. :3.000 Min. :1.000
## 1st Qu.:0.0000 1st Qu.:3.000 1st Qu.:2.000
## Median :0.0000 Median :4.000 Median :2.000
## Mean :0.4062 Mean :3.688 Mean :2.812
## 3rd Qu.:1.0000 3rd Qu.:4.000 3rd Qu.:4.000
## Max. :1.0000 Max. :5.000 Max. :8.000
summary.Date(seq.date) # 出來的結果和 summary(seq.date)一樣
## Min. 1st Qu. Median Mean 3rd Qu.
## "2016-04-01" "2016-04-23" "2016-05-16" "2016-05-16" "2016-06-07"
## Max.
## "2016-06-30"
summary.data.frame(seq.date) # 這樣就出錯了
## Error in substring(blanks, 1, pad): invalid substring arguments
summary.Date(mtcars) # QQ 不要亂用
## < table of extent 0 x 33 >
S3 class 是 R 語言朝向物件導向演化時所產出的第一波結果。因此,S3 class 規範了 R 語言所擁有的許多可用工具,基本上都是封裝好的,我們可以去改動(例如做一個可以分析自己專屬資料的 summary.xxxxxx ,但是盡量還是不要改動比較安全。
S4 Class
相對於 S3 ,對於使用者而言 S4 的使用自由度更高也更安全了,主要有幾個重要工具:
- 使用
setClass()
來建立一個新的 class - 用
setGeneric()
來設定屬於此 class 的泛型函數 - 用
setMethod()
和removeMethod()
來增加或刪除 method
如果你之前有看過用 R 來玩玩地圖吧! 文章,當時是否覺得這些地圖格式的結構很複雜很奇怪?其實,地圖資料就是 S4 class 的實例(instance)。先不多說,上資料來看看。
# 載入專門處理地圖資料的套件 rgdal
library(rgdal)
## Loading required package: sp
## rgdal: version: 1.1-10, (SVN revision 622)
## Geospatial Data Abstraction Library extensions to R successfully loaded
## Loaded GDAL runtime: GDAL 1.11.3, released 2015/09/16
## Path to GDAL shared files: /usr/local/Cellar/gdal/1.11.3_1/share/gdal
## Loaded PROJ.4 runtime: Rel. 4.9.2, 08 September 2015, [PJ_VERSION: 492]
## Path to PROJ.4 shared files: (autodetected)
## Linking to sp version: 1.1-0
TWN <- readOGR(dsn = "data/Taiwan", layer = "Taiwan_TB")
## OGR data source with driver: ESRI Shapefile
## Source: "data/Taiwan", layer: "Taiwan_TB"
## with 352 features
## It has 11 fields
來看一下台灣鄉鎮地圖 TWN 的複雜結構
str(TWN, max.level = 2)
## Formal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slots
## ..@ data :'data.frame': 352 obs. of 11 variables:
## ..@ polygons :List of 352
## .. .. [list output truncated]
## ..@ plotOrder : int [1:352] 301 144 145 303 106 240 286 41 40 302 ...
## ..@ bbox : num [1:2, 1:2] 146784 2422315 350859 2799342
## .. ..- attr(*, "dimnames")=List of 2
## ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
首先, str()
函式告訴我們,這是一個由 package “sp” 所訂定的正式類別 SpatialPolygonsDataFrame,它包含了擁有 5 個元素,在 S4 中叫做 slots,這 5 個 slot 分別叫做 data, polygons, plotOrder, bbox 和 proj4string。
那麼要怎麼取得物件中的 slot 呢?答案是用 @
TWN@data %>% head
## FID_1 COUN_CODE COUNTY TOWN FULLNAME Count_ ABOR_P INCOME
## 0 0 1000101 台北縣 板橋市 台北縣板橋市 1 0.004595 189.1450
## 1 1 1000102 台北縣 三重市 台北縣三重市 1 0.003200 162.9353
## 2 2 1000103 台北縣 中和市 台北縣中和市 1 0.003837 204.5399
## 3 3 1000104 台北縣 永和市 台北縣永和市 1 0.002428 256.6338
## 4 4 1000105 台北縣 新莊市 台北縣新莊市 1 0.009076 184.2582
## 5 5 1000106 台北縣 新店市 台北縣新店市 1 0.009339 255.8700
## TBINCI3 TBINCI6 TBINCI
## 0 0.0019 0.0019 0.00378
## 1 0.0020 0.0018 0.00378
## 2 0.0020 0.0019 0.00385
## 3 0.0015 0.0015 0.00303
## 4 0.0016 0.0015 0.00318
## 5 0.0020 0.0019 0.00389
沒錯,我們拿到了其中一個 slot 叫做 data ,它是一個 data frame 。 在地圖資料中, @data 取得的就是地圖的圖資資料。
那其他的 slots 呢?
- @data :取得地圖圖資(屬性資料表)
- @polygons:取得地圖中的所有 polygons(其下每個 polygons 也都擁有自己的 slots)
- @plotOrder:取得地圖中每個 polygons 的繪製順序
- @bbbox:取得地圖的圖幅(minX, minY, maxX, maxY)
- @proj4string:取得地圖的座標系統
這樣對地圖圖資有比較清楚了嗎? (其實我寫這篇原本是為了要講地圖圖資的結構只是失控了)
丟個這張圖讓大家複習一下 SpatialPolygons 的資料結構:
那麼回到正題,再來解釋一下如何摔打揉捏 S4 類別的物件。
建立一個新的 class: setClass()
我們來試著成為神奇寶貝大師好了。(堅持抵制寶可夢)
# 建立一套標準,定義 pokemon 這個類別的應該要擁有哪些特質
setClass("pokemon",
representation(
Species = "character", # 神奇寶貝的名字,要用 character
CP = "numeric",
HP = "numeric",
Dust = "numeric"
)
)
# 建立好這套 pokemon 的標準之後,
# 讓我~~豪想抓都抓不到的~~卡比獸活起來吧!!!!
# 為了避免混淆,我把這第一隻抓到的卡比獸命名為卡卡獸
KaKaShou = new("pokemon", Species = "Snorlax", CP=1147, HP=163, Dust=1600)
# 為了滿足個人私慾一定要幻想自己抓到小火馬⇽這個人到現在還抓不到小火馬
MyLittlePony = new("pokemon", Species = "Ponyta", CP=9999, HP = 999, Dust= 5000) #看這數值就知道這女人超愛小火馬
來看一下卡卡獸先生和親愛小馬的個人資料,可以用 show()
函式來看
show(KaKaShou)
## An object of class "pokemon"
## Slot "Species":
## [1] "Snorlax"
##
## Slot "CP":
## [1] 1147
##
## Slot "HP":
## [1] 163
##
## Slot "Dust":
## [1] 1600
show(MyLittlePony)
## An object of class "pokemon"
## Slot "Species":
## [1] "Ponyta"
##
## Slot "CP":
## [1] 9999
##
## Slot "HP":
## [1] 999
##
## Slot "Dust":
## [1] 5000
也可以看看卡卡獸的特定資料
KaKaShou@Species
## [1] "Snorlax"
KaKaShou@CP
## [1] 1147
KaKaShou@HP
## [1] 163
設定泛型函數:setGeneric()
抓到了第一隻卡比獸之後,超級想要知道他的 IV 值(手很賤就是想知道), 所以我來寫一個可以輸出 IV 值的函式
setGeneric("ShowIV",
function(obj){
IV = round(obj@CP*obj@HP/obj@Dust, 2)
paste0("這隻 ",obj@Species , " 的 IV 值是:", IV )
}
)
## [1] "ShowIV"
ShowIV(KaKaShou)
## [1] "這隻 Snorlax 的 IV 值是:116.85"
ShowIV(MyLittlePony)
## [1] "這隻 Ponyta 的 IV 值是:1997.8"
# 哇哈哈哈哈我親愛的小馬比卡比獸還強XDDDDDDDDDD
增加或刪除 method :setMethod()
和 removeMethod()
讀者反映你把我當白癡,你 IV 根本亂算啊, 這部分嘛,可以透過 setMethod()
來修改:
setMethod("ShowIV", "pokemon",
function(obj){
IV = round(obj@CP*obj@HP*100/obj@Dust, 2)
paste0("這隻 ",obj@Species , " 的 IV 值應該是:", IV , " 才對。")
}
)
## [1] "ShowIV"
ShowIV(KaKaShou)
## [1] "這隻 Snorlax 的 IV 值應該是:11685.06 才對。"
ShowIV(MyLittlePony)
## [1] "這隻 Ponyta 的 IV 值應該是:199780.02 才對。"
哇~IV值爆表惹顆顆。 不要逼我真的寫 IV Caculator 啦我寧可把時間拿去睡覺。 自己去看文章雖然作者廢話跟我一樣多。
Anyway,物件導向其實是一件很複雜的事情,充滿了各種定義,我也是為了寫這篇文章才弄清楚一些基本概念。但了解物件導向之後對於撰寫 R 語言是有相當助益的,大家一起加油囉!
參考資料:
看了這麼多 R 的 OOP 解釋,就你的解釋我一看就懂。
回覆刪除