2016年9月9日 星期五

R 的物件導向:S3、S4


R 的物件導向

物件導向(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 執行的泛型函數,其運作過程是:

  1. 找到 x 的 class 屬性是什麼 >>> 是一個 vector
  2. 把這個 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 的使用自由度更高也更安全了,主要有幾個重要工具:

  1. 使用 setClass() 來建立一個新的 class
  2. setGeneric() 來設定屬於此 class 的泛型函數
  3. 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 呢?

  1. @data :取得地圖圖資(屬性資料表)
  2. @polygons:取得地圖中的所有 polygons(其下每個 polygons 也都擁有自己的 slots)
  3. @plotOrder:取得地圖中每個 polygons 的繪製順序
  4. @bbbox:取得地圖的圖幅(minX, minY, maxX, maxY)
  5. @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 語言是有相當助益的,大家一起加油囉!

參考資料:


1 則留言:

  1. 看了這麼多 R 的 OOP 解釋,就你的解釋我一看就懂。

    回覆刪除