Functions

Function atau fungsi adalah sekumpulan pernyataan yang diorganisir bersama untuk mengerjakan tugas/instruksi tertentu. Selain function yang dibuat sendiri oleh user, R memiliki banyak sekali fungsi built-in yang bisa langsung diimplementasikan. Beberapa fungsi built-in yang selama ini kita sering gunakan adalah sq(), mean(), max(), min(), dan lain-lain.

Manfaat fungsi adalah agar memudahkan pemrosesan berulang yang melibatkan instruksi yang sama/tetap, agar tidak mengetikkan instruksi berkali-kali dan cukup memanggil fungsi dengan argumen yang diubah-ubah (reusable). Secara konsep, function dalam R ini mirip dengan fungsi dalam matematika –> f(x), x adalah argumennya.

Beberapa built-in function

# membuat barisan bilangan dari 32 sampai 44 loncat 2
barisan <- seq(32, 44, 2)
barisan
## [1] 32 34 36 38 40 42 44
# mencetak rata-rata dari barisan bilangan yang dibuat
mean(barisan)
## [1] 38
# mencetak jumlah dari barisan
sum(barisan)
## [1] 266

Membangun Fungsi Sendiri

R memiliki banyak fungsi yang dimanfaatkan untuk komputasi dan grafik sebagaimana dipelajari di pertemuan sebelumnya. Karena statistika merupakan alat yang selalu berkembang, diprediksi bahwa ada tools yang berkembang belum ditangani di R sehingga kita buat fungsi sendiri.

Mendefinisikan fungsi dengan struktur sebagai berikut:

<function name> <- function(<argument 1>, <argument 2>, dst) {
  <function body> 
  }

jika ditulis dalam beberapa line code, atau

<function name> <- function(<aargument1>, <argument2>, dst) <function body>

jika ditulis dalam beberapa line code.

Keterangan:

  1. function name: nama fungsi. Dia disimpan dalam environment R sebagai objek dengan nama ini.
  2. argument: opsional, bisa tidak disertakan atau disertakan lebih dari satu. Argumen memiliki default value jika didefinisikan dulu ketika membangun fungsi. Argumen bisa diganti-ganti ketika memanggil fungsi, karena dia merupakan nilai yang diterapkan dalam pengoperasian di dalam fungsi.
  3. function body: sekumpulan/serangkaian instruksi yang mendefinisikan apa yang dilakukan oleh fungsi tersebut.
  4. return value: diletakkan di akhir fungsi, dan memungkinkan line return ini yang dieksekusi terakhir karena line setelahnya di dalam fungsi itu tidak akan dieksekusi. Mengembalikan sebuah nilai hasil proses sebuah fungsi.

Isi fungsi harus berupa objek data yang sudah ada (vektor, matriks, array, factor, list, atau data frame) atau objek data yang dibuat sendiri dari OOP yang dikembangkan sebagai kembalian dari fungsi yang dibuat. Kembalian dari fungsi bisa dituliskan di dalam return() pada line terakhir, atau tanpa return (fungsi prosedur yang pengembaliannya adalah NULL). Setiap fungsi yang dipanggil harus disertakan tanda “()” karena itu yang membedakan objek fungsi dengan objek yang lain.

Fungsi tanpa Argumen

set.seed(1234)
numbers <- data.frame(unifs = runif(5),
                      norms = rnorm(5, 5, 2),
                      exps = rexp(5, 0.5),
                      pois = rpois(5, 4))
numbers
##       unifs    norms     exps pois
## 1 0.1137034 5.718578 1.349183    2
## 2 0.6222994 3.539054 1.676081    1
## 3 0.6092747 5.071460 1.520861    2
## 4 0.6233794 5.225950 3.760153    6
## 5 0.8609154 7.857104 3.192211    4
deleteLastNumbers <- function(){
  numbers <- head(numbers, -1)
  numbers
}
deleteLastNumbers()
##       unifs    norms     exps pois
## 1 0.1137034 5.718578 1.349183    2
## 2 0.6222994 3.539054 1.676081    1
## 3 0.6092747 5.071460 1.520861    2
## 4 0.6233794 5.225950 3.760153    6

Fungsi dengan Argumen dan Return

Fungsi pada umumnya memiliki argumen, dan argumen yang digunakan dapat diberikan nilai default sehingga ketika pemanggilan fungsi tidak perlu diisi kalau nilai argumennya sama dengan nilai defaultnya.

# membuat fungsi sebaran binomial (manual)
binom.pdf <- function(x, n, p){
  p <- ncol(combn(n, x))*(p^x)*((1-p)^(n-x))
  return(p)
}
binom.pdf(2, 10, 0.20)
## [1] 0.3019899
dbinom(2, 10, 0.20)
## [1] 0.3019899

Jika nilai argumennya berbeda, maka argumen harus didefinisikan dengan nama yang sama. Argumen yang didefinisikan dengan namanya boleh dituliskan dengan tidak terurut.

# membuat fungsi kumulatif sebaran binom
binom.cdf <- function(x, n=10, p=0.20){
  P <- 0
  for (i in 0:x){
    P.i <- ncol(combn(n, i))*(p^i)*((1-p)^(n-i))
    P <- P + P.i
  }
  P
}
binom.cdf(4)
## [1] 0.9672065
pbinom(4, 10, 0.2)
## [1] 0.9672065
# memanggil fungsi binom.pdf() dengan argumen yang ditukar
binom.pdf(n = 20, p = 0.35, x = 10)
## [1] 0.06861397
dbinom(size = 20, prob = 0.35, x = 10)
## [1] 0.06861397

Dengan catatan, nama argumen yang dipanggil harus sama.

# akan menghasilkan error
binom.pdf(n = 20, q = 0.35, x = 10)
## Error in binom.pdf(n = 20, q = 0.35, x = 10): unused argument (q = 0.35)

Atau memanggil fungsi tanpa memanggil argumen yang tidak diset default valuenya juga akan menghasilkan error.

binom.pdf(n = 20, p=0.2)
## Error in combn(n, x): argument "x" is missing, with no default

Sementara fungsi yang sudah diset default valuenya, maka ketika pemanggilan bisa dengan tidak mendefinisikan argumen apapun.

Misal membuat fungsi \[\begin{equation} z = 3x^2y^4 + 12x - y^3; x=3, y=4 \end{equation}\]

z.result <- function(x=3, y=4){
  return(3*(x^3)*(y^4)+(12*x)-(y^3))
}
z.result()
## [1] 20708

Argumen fungsi bisa menerima fungsi R yang lain misalkan pada fungsi lapply() dan sapply().

set.seed(1223)
x <- rpois(10, 10)
x
##  [1] 11  8  7 11 15  7 10 10 10  9
cbind(x, lapply(x, sqrt))
##       x          
##  [1,] 11 3.316625
##  [2,] 8  2.828427
##  [3,] 7  2.645751
##  [4,] 11 3.316625
##  [5,] 15 3.872983
##  [6,] 7  2.645751
##  [7,] 10 3.162278
##  [8,] 10 3.162278
##  [9,] 10 3.162278
## [10,] 9  3

Fungsi tanpa Return

lowercase <- function(text){
  text <- tolower(text)
}
x <- "HalLo"
lowercase(x)
x
## [1] "HalLo"

Error Handling

Di dalam R, terdapat 2 jenis kesalahan yang umum ditemukan: error dan warning. Error menunjukkan bahwa kesalahan dalam code menyebabkan eksekusi tidak bisa dilanjutkan, sementara warning memungkinkan R telah menyelesaikan seluruh eksekusi, tetapi membutuhkan langkah penanganan lebih lanjut yang perlu diperhatikan.

Penanganan error adalah sebuah proses dimana kita “berteman” dengan anomali-anomali atau error yang mungkin akan menyebabkan proses eksekusi menjadi terhenti selama menjalankan program. Dan ketika kita terapkan error handling, maka error diharapkan tidak menghentikan proses eksekusi program. Beberapa fungsi dasar yang bisa dilakukan untuk menangani error antara lain stop(), try(), tryCatch(), stopifnot(), dan warning().

Error tidak ter-handle

log.function <- function(a){
  log(a)
  # memberikan peringatan jika error 
  print("error! you entered invalid argument.")
  return(a)
}
log.function("ab+")
## Error in log(a): non-numeric argument to mathematical function

Tidak mengeluarkan warning yang dibuat karena itu bukan cara penanganan error yang benar.

stop()

Digunakan untuk menampilkan pesan error yang di-custom oleh user.

addition <- function(a, b){
  # menambahkan validasi apakah input bertipe numerik atau tidak, jika tidak maka di-stop
  if(!is.numeric(a) || !is.numeric(b)){
    error("one or both of your input is in wrong format!")
  } else{
    a+b
  }
}
# memanggil fungsi yang error
addition("a", b)
## Error in error("one or both of your input is in wrong format!"): could not find function "error"

Catatan: warning() memiliki penggunaan yang sama seperti stop() tapi dia menampilkan pesan “Warning” bukan “Error”.

substraction <- function(a, b){
  # menambahkan validasi apakah input bertipe numerik atau tidak, jika tidak maka di-stop
  if(!is.numeric(a) || !is.numeric(b)){
    warning("one or both of your input is in wrong format!")
  } else{
    a-b
  }
}
# memanggil fungsi error
substraction("a", b)
## Warning in substraction("a", b): one or both of your input is in wrong format!

try()

Digunakan untuk menangkap/menyimpan pesan error dan bisa mengabaikan error

# menyimpan pesan error dalam objek errorMessage
errorMessage <- try(addition("a",3))
## Error in error("one or both of your input is in wrong format!") : 
##   could not find function "error"
errorMessage
## [1] "Error in error(\"one or both of your input is in wrong format!\") : \n  could not find function \"error\"\n"
## attr(,"class")
## [1] "try-error"
## attr(,"condition")
## <simpleError in error("one or both of your input is in wrong format!"): could not find function "error">
# membuat fungsi yang mengeksekusi perintah walaupun ada error
printErrorObjects <- function(a,b){
  msg <- try(a+b, silent = TRUE) #silent agar pesan errornya tidak keluar di eksekusi line sini
  if (is.numeric(msg)==FALSE){
    cat(a,b, "\n")
    cat("The error is, ", msg)
  } else {
    cat("a=", a, "b=", b, "a+b=", a+b)
  }
}
printErrorObjects(2, "aku")
## 2 aku 
## The error is,  Error in a + b : non-numeric argument to binary operator

Dengan fungsi ini kita bisa menangani error dan melanjutkan eksekusi dengan mengabaikan error tersebut, melalui argumen silent.

try(log("as"), silent = TRUE) # ketika dieksekusi tidak menghasilkan erorr
print("success ignoring error.")
## [1] "success ignoring error."

stopifnot()

Digunakan untuk memeriksa argumen fungsi yang menyebabkan error

division <- function(a, b) {
  stopifnot(is.numeric(a), is.numeric(b), b!=0)
  a/b
}
division(9,0)
## Error in division(9, 0): b != 0 is not TRUE

Yang menyebabkan error adalah pada argumen kedua (b) dengan sebab ketiga, bahwa b=0.

tryCatch()

Digunakan untuk menghandle berbagai jenis error.

Disusun dengan:

check = tryCatch({
    expression
}, warning = function(w){
    code that handles the warnings
}, error = function(e){
    code that handles the errors
}, finally = function(f){
    clean-up code
})
errorChecking <- function(a, b){
  tryCatch({
    print(a/b)
    print("success executing.")
  },
  error = function(e){
    print("There is an error.")
  },
  warning = function(w){
    print("There is a warning.")
  },
  finally = {
    print("It's executed.")
  }
  )
}
errorChecking(2,"a")
## [1] "There is an error."
## [1] "It's executed."

Keterangan:

  • argumen finally memungkinkan pada setiap hasil yang didapat, dia akan dieksekusi.
  • argumen error dieksekusi jika terjadi error, tanpa menampilkan error itu sendiri.
  • argumen warning dieksekusi jika terjadi error.
  • sedangkan jika tidak terjadi error dan warning, maka akan dieksekusi ekspresinya.

Object Oriented Programming in R

Sebelum muncul konsep pemrograman berbasis objek, pemrograman dilakukan secara struktural. Di mana program disusun secara line per line dan objek per objek sehingga eksekusi dari awal hingga akhir jika terdapat kesalahan di satu line code maka setelahnya juga terjadi kesalahan. Sementara dalam OOP, objek-objek dibuat secara terpisah dengan method-methodnya dan berjalan secara mandiri, jika ada salah satu objek yang gagal maka objek yang lain masih berjalan dan yang diperbaiki, bukan keseluruhan program tapi hanya objek yang salah saja.

OOP atau Object Oriented Programming adalah suatu paradigma pemrograman berdasarkan objek, di mana setiap objek dapat berisi data dalam bentuk atribut/properti dan memiliki behavior dalam bentuk fungsi/method. Pada dasarnya, kita mengambil objek dari dunia nyata dan merepresentasikannya dalam code. Dalam konsep OOP, semua proses melibatkan objek yang mana setiap objek memiliki atribut dan perilakunya masing-masing. Konsep OOP telah menjadi paradigma yang dipilih berbagai bahasa pemrograman seperti C, Java, Ruby, C++, dan lain-lain. OOP dikatakan bagus untuk mengembangkan tools untuk analisis data tetapi buruk bagi data itu sendiri. Hal ini dikarenakan OOP memiliki kelebihan dan kekurangan sebagai berikut:

Kelebihan Kekurangan
- OOP memungkinkan kode dapat digunakan berkali-kali dan bisa dikembangkan lebih lanjut (di-extend). Class dan methods dapat dipakai ulang di analisis lain, atau membuat project baru yang memanfaatkan sebagian code yang telah dibuat sehingga mengurangi waktu proses analisis. - Memungkinkan basis code memiliki ukuran yang besar dengan banyak part yang tidak terlalu dibutuhkan.
- Lebih mudah menemukan bugs/kelemahan program jika code ditulis dengan bersih (clean code) dan bukan smell code. - Tidak bisa menjalankan duplikasi kode selama implementasi objek.
- Lebih mudah untuk test dan debug ketika ada bagian code yang error/perlu perbaikan - Membutuhkan waktu yang cukup lama untuk mendalami OOP sebelum benar-benar menerapkan OOP itu sendiri
- OOP memungkinkan pengembangan program secara paralel oleh lebih dari satu data scientist yang ingin bekerja dengan code yang sama untuk masing-masing project mereka - Dapat membuat waktu running code menjadi lebih lama karena ada lebih banyak line code yang harus dieksekusi.

Sebagaimana kita ketahui bahwa R merupakan bahasa yang dikembangkan dari bahasa S, pada tahun 1990-an S versi ketiga (S3) bersamaan dengan berkembangnya OOP di berbagai bahasa pemrograman pada kala itu memperkenalkan atribut kelas dengan argumen tunggal dalam bahasa pemrogramannya. Banyak fungsi R (seperti fungsi-fungsi pemodelan statistik) diimplementasikan menggunakan metode S3, sehingga metode S3 masih ada sampai sekarang. Dalam S versi 4 (dinamai S4), kelas dan metode formal diperkenalkan yang memungkinkan banyak argumen dan pewarisan yang lebih canggih. Banyak paket baru diimplementasikan menggunakan metode S4. (Baca lebih lanjut: R in a Nutshell, 2nd Edition by Joseph Adler)

Terdapat empat prinsip dari OOP (disingkat A PIE), antara lain:

  1. Abstraction / Abstraksi Prinsp kelas abstrak dari OOP berarti menyembunyikan/tidak menampakkan implementasi instruksi secara detail dari sebuah proses dan mengemasnya dalam sebuah kelas dan method. Ketika objek dibuat, maka wujud objek secara abstrak dijelaskan oleh kelas dan ketika objek menggunakan method tersebut, tidak diketahui apa saja detail instruksi yang dilakukan.

  2. Encapsulation / Pengkhususan Enkapsulasi adalah sebuah mekanisme membungkus sebuah objek dalam kelas dan ketika objek menjadi bagian dari suatu kelas, maka dia menerapkan karakteristik dan method-method yang didefinisikan dalam kelas tersebut.

  3. Inheritance / Pewarisan Inheritance memungkinkan sebuah code dapat dipakai berulang-ulang. Inheritance dalam OOP memungkinkan user bisa membentuk sebuah kelas baru yang mirip dengan kelas yang sudah ada dan menambahkan fitur/elemen baru di dalamnya. Kelas yang mewarisi properti dan method-method dari kelas lain disebut Sub-class, sedangkan kelas yang mewariskan properti dan methodnya kepada sub-class disebut Base Class atau **Super Class*.

  4. Polymorphism / Polimorfisme Sebagaimana definisi polimorfisme yang berarti “banyak bentuk”, dalam OOP, polymorphism berarti di dalam OOP memungkinkan pendefinisian beberapa fungsi dengan nama yang sama tapi bisa menghasilkan output yang berbeda-beda tergantung kelas mana yang menerapkan method tersebut. Misalnya pada fungsi plot() dan summary(), di mana keduanya dapat menghasilkan plot dan ringkasan dari berbagai macam jenis plot dan analisis dengan parameter yang berbeda-beda, namun dikemas dengaan nama fungsi yang sama, yaitu plot() dan summary().

Class (Kelas) dan Object (Objek)

Kelas dan objek merupakan konsep dasar di dalam OOP. Kelas merupakan kerangna / sketsa / blueprint dari objek yang akan diciptakan. Ketika objek sudah terbentuk, maka ia memiliki method/perilaku dan karakteristik/properti mengikuti bentuk rangka/blueprint-nya dalam kelas. Misalkan kelas Phone, yang memiliki karakteristik seperti RAM speed, Storage capacity, Processor, Camera, Battery, Size, Weight dan memiliki method-method semacam Turn On, Charge, Turn Off, Play Game. Dan objek Phone1 dibuat mengikuti karakteristik dan menerapkan method sesuai dengan kelasnya. Berbeda dengan kebanyakan bahasa pemrograman lainnya, R memiliki Sistem 3 Kelas (3-Class System) yang terdiri dari S3, S4, dan Reference Classes.

Sebagai pengantar, terdapat perbedaan-perbedaan antara sistem kelas S3 dan S4 dalam R, antara lain:

S3 S4
- Kelas didefinisikan setelah mendefinisikan objek - Kelas didefinisikan sebelum objek dibuat
- Kelas didefinisikan dengan me-assign kelas objek dengan nama kelasnya - Kelas didefinisikan secara formal dengan setClass
- Pembuatan objek dalam fungsi konstruktor dengan membuat objek biasa bertipe list Pembuatan objek dalam fungsi konstruktor dengan menggunakan new()
- Data dalam masing-masing properti objek bisa diisi dengan tipe apapun - Tipe data diatur ketika membuat fungsi konstruktor
- Method print untuk menampilkan data - Method show untuk menampilkan data
- Akses data secara langsung dengan tanda dollar ($) - Akses data secara langsung dengan method slot atau tanda @
- Membuat fungsi generik dengan definisi ulang fungsi generik yang sudah ada dalam R dengan nama: namamethod.class - Membuat fungsi generik dari fungsi generik yang sudah ada dalam R dengan setMethod()
- Membuat fungsi non generik agar menjadi fungsi generik dengan UseMethod() - Membuat fungsi non generik menjadi generik dengan setGeneric()

Sistem Kelas S3

Merupakan sistem OOP yang paling sederhana di R. Pada sistem ini, tidak terdapat banyak batasan-batasan dan kita dapat membuat objek dengan sederhana melalui penambahan atribut-atribut di dalamnya. Kelas S3 tidak memiliki struktur pendefinisian secara formal, dan method-method yang didefinisikan dalam S3 sama seperti fungsi pada umumnya. Berikut adalah contoh kasus OOP dalam sistem S3, dengan rincian instruksi sebagai berikut:

  1. Ingin dibuat objek bernama pts yang merupakan point atau titik yang berisi dua properti, x (properti dalam sumbu x) dan y (properti dalam sumbu y).
  2. Memasukkan objek pts dalam kelas coords atau koordinat
  3. Mencetak koordinat objek pts dengan format tertentu menggunakan method print.
  4. Melihat panjang atau jumlah titik yang tersimpan dalam objek dengan method length.
  5. Mengetahui batas minimal dan maksimal titik-titik tersebut dengan method bbox.
  6. Memetakan titik dengan method plot.
  7. Nantinya, ingin dibuat objek baru dari kelas coords dengan penambahan properti nilai untuk masing-masing titik.

Mendefinisikan Objek

Objek didefinisikan dalam kelas list terlebih dahulu dengan properti-properti tertentu.

set.seed(1)
pts <- list (x=round(rnorm(5), 2), y=round(rnorm(5), 2))
pts
## $x
## [1] -0.63  0.18 -0.84  1.60  0.33
## 
## $y
## [1] -0.82  0.49  0.74  0.58 -0.31
class(pts)
## [1] "list"

Sebagaimana terlihat bahwa kelas objek pts adalah list.

Membuat Kelas Baru

Dilakukan dengan me-assign pts sebagai kelas baru, dalam hal ini coords.

class(pts) <- "coords"
class(pts)
## [1] "coords"

Kelas objek pts sudah berganti menjadi coords.

Membuat Fungsi Konstruktor

Fungsi konstruktor digunakan sebagai alat bantu untuk membuat objek sesuai dengan kelas yang sudah didefinisikan. Penggunaan fungsi konstruktor memungkinkan user dapat melakukan pemeriksaan tambahan terkait kriteria value tertentu dari properti yang menyusun kelas. Misalkan nilainya harus numerik, atau nilainya tidak boleh negatif, panjang karakter > 5, dan lain-lain. Dalam hal ini, fungsi konstruktor coords dibuat untuk memastikan bahwa objek yang terbentuk harus memenuhi beberapa kriteria sebagai berikut:

  • x dan y harus berupa numerik
  • vektor tidak boleh NA, NaN, Inf
  • vektor harus memiliki panjang yang sama
coords <- function(x, y){
  # memastikan tipe data input harus numerik
  if(!is.numeric(x) || !is.numeric(y) || !all(is.finite(x)) || !all(is.finite(y))) stop("Titik koordinat tidak tepat!")
  # memastikan panjang x dan y sama karena titik-titiknya berpasangan
  if(length(x) != length(y)) stop("Panjang koordinat berbeda!")
  # membuat objek bertipe list
  pts <- list(x=x, y=y)
  # re-assign kelas objek dengan nama kelas baru
  class(pts) <- "coords"
  # return objek
  pts
}
# menggunakan fungsi konstruktor untuk membuat objek baru
set.seed(1)
pts <- coords(x = round(rnorm(5), 2),
              y = round(rnorm(5), 2))
class(pts)
## [1] "coords"

Membuat Fungsi Aksesor

Untuk mengakses properti/data dalam suatu kelas, sebagaimana dalam list, bisa menggunakan tanda $. Namun pengaksesan properti secara langsung tidak dianjurkan, sehingga properti diakses melalui fungsi. Dalam hal ini, membuat fungsi aksesor untuk mengakses kelas coords yang mengembalikan nilai propertinya dalam objek.

# karena ada 2 properti dalam kelas, maka dibuat 2 fungsi aksesor
xcoords <- function(obj) obj$x
ycoords <- function(obj) obj$y
xcoords(pts)
## [1] -0.63  0.18 -0.84  1.60  0.33
ycoords(pts)
## [1] -0.82  0.49  0.74  0.58 -0.31

Fungsi Generik

Fungsi generik merupakan suatu method dari suatu class objek dalam R. Fungsi generik bertindak untuk beralih memilih fungsi tertentu atau metode tertentu yang dijalankan sesuai dengan classnya. Fungsi generik mengevaluasi terlebih dahulu argumennya masuk kelas mana, kalau masuk kelas coords maka menggunakan method coords. Hal ini disebut polymorphism.

  • Mendefinisikan fungsi generik baru dari method yang sudah ada dalam R adalah dengan: method.namakelas <- function() ekspresibaru
  • Membuat fungsi generik baru dari method yang bukan fungsi generik, maka untuk menjadikan method itu sebagai fungsi generik dengan menggunakan fungsi setMethod("nama method").
Method print

Method print digunakan sebagaimana aslinya:

print(pts)
## $x
## [1] -0.63  0.18 -0.84  1.60  0.33
## 
## $y
## [1] -0.82  0.49  0.74  0.58 -0.31
## 
## attr(,"class")
## [1] "coords"

Method print yang dibuat fungsi generik mengikuti kelas coords:

print.coords <- function(obj){
  print(paste("(", format(xcoords(obj)), ",",
              format(ycoords(obj)), ")", sep=" "), quote = F) #quote dihilangkan ketika print
}
print.coords(pts)
## [1] ( -0.63 , -0.82 ) (  0.18 ,  0.49 ) ( -0.84 ,  0.74 ) (  1.60 ,  0.58 )
## [5] (  0.33 , -0.31 )
pts 
## [1] ( -0.63 , -0.82 ) (  0.18 ,  0.49 ) ( -0.84 ,  0.74 ) (  1.60 ,  0.58 )
## [5] (  0.33 , -0.31 )
# karena method print sudah digenerik dengan kelas coords maka tanpa ditulis print-nya, pemanggilan pts sudah ikut aturan main dari kelas coords tadi
Method length

Jika menggunakan method length biasa tanpa didefinisikan sebagai fungsi generik:

length(pts)
## [1] 2

Padahal ada 5 pasang titik koordinat dalam pts. Penggunaan length dalam list (karena sebelum coords, pts masuk kelas list), adalah menghitung banyak properti di dalamnya. Sehingga harus didefinisikan ulang:

length.coords <- function(obj) {
  length(xcoords(obj))
}
length(pts)
## [1] 5
Method bbox yang dibuat sebagai fungsi generik baru

Untuk membuat suatu method yang dapat diwariskan, maka method tersebut harus dijadikan fungsi generik.

# membuat bbox sebagai fungsi generik
bbox <- function(obj) {
  UseMethod("bbox")
}
# membuat fungsi generik bbox untuk kelas coords
bbox.coords <- function(obj){
  # mencari rentang min sampai max dari masing-masing x dan y, menyimpan dalam matriks
  matrix(c(range(xcoords(obj)),
           range(ycoords(obj))),
         # memberi label matriks
         nc = 2, dimnames = list(
           c("min", "max"),
           c("x:", "y")
         ))
}
bbox(pts)
##        x:     y
## min -0.84 -0.82
## max  1.60  0.74
Method plot

Ingin membuat plot khusus untuk kelas coords.

plot.coords <- function(obj, bbox = FALSE, ...){ # titik titik untuk menambakan argumen jika argumen > 1
  if(bbox) { # jika bbox bernilai TRUE akan dikasih garis outline yang melewati batas-batas terluar titik
    plot(xcoords(obj), ycoords(obj), ...);
    x <- c(bbox(obj)[1], bbox(obj)[2], bbox(obj)[2], bbox(obj)[1])
    y <- c(bbox(obj)[3], bbox(obj)[3], bbox(obj)[4], bbox(obj)[4])
    polygon(x,y)
  } else {
    plot(xcoords(obj), ycoords(obj), ...)
  }
  
}
plot(pts)

plot(pts, bbox = T, pch = 19, col="red")

Pewarisan Kelas (Inheritance)

Ingin dibuat objek baru yang dari kelas sama namun terdapat penambahan properti baru. Sehingga kelas coords diwariskan ke kelas baru, dalam hal ini vcoords. Membuat fungsi konstruktor baru kelas vcoords:

vcoords <- function(x, y, v){
  # memastikan tipe data input harus numerik
  if(!is.numeric(x) || !is.numeric(y) || !is.numeric(v) || !all(is.finite(x)) || !all(is.finite(y))) stop("Titik koordinat tidak tepat!")
  # memastikan panjang x, y, dan v sama karena titik-titiknya berpasangan
  if(length(x) != length(y) || length(x) != length(v)) stop("Panjang koordinat berbeda!")
  # membuat objek bertipe list
  pts <- list(x=x, y=y, v=v)
  # re-assign kelas objek dengan nama kelas baru dan kelas lama yang diwarisi
  class(pts) <- c("vcoords", "coords")
  # return objek
  pts
}

# membuat fungsi aksesor tambahan untuk properti v
value <- function(obj) obj$v

Membuat objek baru pada kelas vcoords:

set.seed(1)
vpts <- vcoords(x = round(rnorm(5), 2),
                y = round(rnorm(5), 2),
                v = round(runif(5, 0, 100)))
vpts
## [1] ( -0.63 , -0.82 ) (  0.18 ,  0.49 ) ( -0.84 ,  0.74 ) (  1.60 ,  0.58 )
## [5] (  0.33 , -0.31 )
# akses data
ycoords(vpts)
## [1] -0.82  0.49  0.74  0.58 -0.31
xcoords(vpts)
## [1] -0.63  0.18 -0.84  1.60  0.33
value(vpts)
## [1] 93 21 65 13 27
# memetakan data
bbox(vpts)
##        x:     y
## min -0.84 -0.82
## max  1.60  0.74

Terlihat bahwa hasil penampilan data pada vcoords mengikuti format yang dibuat dalam fungsi generik kelas coords. Untuk method print, harus didefinisikan ulang agar properti v juga ikut ditampilkan.

print.vcoords <- function(obj){
  print(paste("(", format(xcoords(obj)), ", ",
              format(ycoords(obj)),"; ",
              format(value(obj)), ")", sep=""), quote = F)
}
vpts
## [1] (-0.63, -0.82; 93) ( 0.18,  0.49; 21) (-0.84,  0.74; 65) ( 1.60,  0.58; 13)
## [5] ( 0.33, -0.31; 27)

Method plot juga harus didefinisikan ulang agar value-nya tampil:

plot.vcoords <- function(obj, txt = FALSE, bbox = FALSE, ...){ 
  if(bbox) {
    if(!txt) {
      plot(xcoords(obj), ycoords(obj), ...)
    } else {
      plot(xcoords(obj), ycoords(obj), type="n", ...);
      text(xcoords(obj), ycoords(obj), value(obj) , ...);
    }
    x <- c(bbox(obj)[1], bbox(obj)[2], bbox(obj)[2], bbox(obj)[1])
    y <- c(bbox(obj)[3], bbox(obj)[3], bbox(obj)[4], bbox(obj)[4])
    polygon(x,y)
  } else {
    if(!txt){
      plot(xcoords(obj), ycoords(obj), ...)
    } else {
      plot(xcoords(obj), ycoords(obj), type="n", ...);
      text(xcoords(obj), ycoords(obj), value(obj) , ...);
    }
  }
}

plot(vpts)

plot(vpts, txt=T, bbox=T, col="red")

Method Subset

Digunakan untuk mengakses sebagian data pada objek, sebagaimana instruksi:

vpts[xcoords(vpts) < 0 & ycoords(vpts) < 0]
## $x
## [1] -0.63  0.18 -0.84  1.60  0.33

Dapat ditangani dengan:

`[.vcoords ` <- function(x, i) {
  vcoords(xcoords(x)[i], ycoords(x)[i], value(x)[i])
}
vpts[1:3]
## $x
## [1] -0.63  0.18 -0.84  1.60  0.33
## 
## $y
## [1] -0.82  0.49  0.74  0.58 -0.31
## 
## $v
## [1] 93 21 65 13 27

Pemeriksaan Suatu Class Objek

Untuk mengecek apakah suatu objek merupakan suatu class digunakan fungsi inherits:

inherits(pts, "coords")
## [1] TRUE
inherits(vpts, "coords")
## [1] TRUE
inherits(pts, "vcoords")
## [1] FALSE
inherits(vpts, "vcoords")
## [1] TRUE

Penutup

Sistem kelas S3 sudah memberikan berbagai fasilitas dalam pengimplementasian S3, tetapi banyak isu-isu teknis. Misalnya pada ekspresi berikut diperbolehkan dalam R:

model <- 1:10
class(model)
## [1] "integer"
plot(model)

sqrt(model)
##  [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427
##  [9] 3.000000 3.162278
model^2
##  [1]   1   4   9  16  25  36  49  64  81 100
# me-reassign kelas model dengan kelas baru
class(model) <- "lm"
class(model)
## [1] "lm"

Harusnya, karena asal mula objek model masuk dalam kelas numerik, maka model bisa diterapkan sebagaimana objek bertipe numerik yaitu di-plotkan dan dilakukan operasi aritmatika. Tetapi ketika sudah masuk ke kelas lm ini, operasi-operasi tersebut menghasilkan error.

plot(model)
## Error in object$residuals: $ operator is invalid for atomic vectors
sqrt(model)
## Error in x$call: $ operator is invalid for atomic vectors
model^2
## Error in x$call: $ operator is invalid for atomic vectors

Sistem Kelas S4

Kelas S4 memiliki struktur pendefinisian tersendiri dan lebih formal, sebagai solusi dari issue-issue pada S3. Sehingga setiap kelas yang dibentuk dalam sistem S4 memiliki struktur yang mirip. Terdapat fungsi setClass() untuk membentuk kelas baru dan new() untuk membuat objeknya. Dalam S4, kelas terdiri dari slot dengan tipe data yang spesifik.

Mendefinisikan Kelas dengan setClass()

setClass("coords",
         representation(x = "numeric", 
                           y = "numeric"))

representation adalah fungsi argumen untuk menentukan tipe data dari masing-masing slot dalam kelas.

Fungsi Konstruktor

Membentuk objek pada kelas coords dengan fungsi konstruktor sebagai berikut:

coords <- function(x, y){
  if(length(x) != length(y)) stop("Panjang x dan y harus sama!")
  new("coords", x = as.vector(x), y = as.vector(y))
}
# error karena salah tipe input 
pts <- coords(round(rnorm(5), 2), c("A", 2, 3, 4, 5))
## Error in validObject(.Object): invalid class "coords" object: invalid object for slot "y" in class "coords": got class "character", should be or extend class "numeric"
# input benar
set.seed(1)
pts <- coords(round(rnorm(5), 2), round(rnorm(5),2))
pts
## An object of class "coords"
## Slot "x":
## [1] -0.63  0.18 -0.84  1.60  0.33
## 
## Slot "y":
## [1] -0.82  0.49  0.74  0.58 -0.31

Terlihat bahwa tanpa harus mengatur kriteria tipe data pada fungsi konstruktor, jika user menginput data ke slot dengan tipe yang tidak sesuai maka akan menghasilkan error. Hal ini terjadi karena tipe data slot objek sudah didefinisikan ketika membuat kelas.

Fungsi Aksesor

Akses terhadap slot pada sistem S4 dilakukan dengan operator @ atau menggunakan method slot().

slot(pts, "x")
## [1] -0.63  0.18 -0.84  1.60  0.33
pts@y
## [1] -0.82  0.49  0.74  0.58 -0.31

Tetapi disarankan untuk membuat fungsi aksesor sebagai berikut:

xcoords <- function(obj) obj@x
ycoords <- function(obj) obj@y

Fungsi Generik

Method show

Jika dalam sistem S3 fungsi generik untuk menampilkan objek adalah print, maka di S4 fungsi generiknya adalah show. Membuat fungsi generik dilakukan dengan menggunakan setMethod().

setMethod(show, signature = (object = "coords"), definition = 
          function(object) {
            print(paste("(", format(xcoords(object)),
                        ", ", format(ycoords(object)),
                        ")", sep = ""),
                  quote = F)})
# menampilkan objek
pts
## [1] (-0.63, -0.82) ( 0.18,  0.49) (-0.84,  0.74) ( 1.60,  0.58) ( 0.33, -0.31)

Method bbox

Sebagaimana pada S3, bbox bukan merupakan fungsi generik bawaan di R sehingga untuk mendefinisikan bbox sebagai fungsi generik baru, pada S4 digunakam setGeneric().

setGeneric("bbox",
           function(obj)
             standardGeneric("bbox"))
## [1] "bbox"
# mendefinisikan ulang fungsi generik bbox
setMethod("bbox", signature(obj = "coords"), definition = 
          function(obj) {
            matrix(c(range(xcoords(obj)),
                     range(ycoords(obj))),
                   nc = 2,
                   dimnames = list(
                     c("min", "max"),
                     c("x:", "y:")
                   ))})
bbox(pts)
##        x:    y:
## min -0.84 -0.82
## max  1.60  0.74
Method plot
setMethod ("plot", signature (x="coords"),
           function (x, bbox =FALSE , ...) {
             if (bbox) {
               plot (xcoords(x),ycoords(x),...) ;
               x.1 <- c(bbox(x)[1] ,bbox(x)[2], bbox(x)[2] ,bbox(x)[1]) ;
               y.1 <- c(bbox(x)[3] ,bbox(x)[3] ,bbox(x)[4] ,bbox(x)[4]) ;
               polygon (x.1,y.1)
               } else {
                 plot (xcoords(x),ycoords(x), ...)
                 }
             })
plot(pts)

plot(pts,bbox=T, pch=19, col="red",xlab="x", ylab="y")

# kalau namanya object/obj/a kenapa error?

Pewarisan Kelas

Sebagaimana pada sistem S3, pewarisan kelas pada S4 dilakukan dengan membuat fungsi konstruktor baru namun dengan properti yang sudah ada, namun dalam hal ini ditambahkan properti v.

setClass("vcoords", 
         representation(value = "numeric"),
         contains = "coords") #argumen contains ditambahkan karena kelas vcoords merupakan pewarisan dari kelas coords
# membuat fungsi konstruktor baru
vcoords <- function(x, y, value) {
  if ((length(x) != length(y)) || (length(x) != length(value))) {
    stop("Panjang x, y, dan value harus sama!")
  }
  new("vcoords", x = as.vector(x), y = as.vector(y), value = as.vector(value))
}
# membuat fungsi aksesor baru
value <- function(obj) obj@value

Kemudian membentuk objek baru dalam kelas vcoords dengan:

set.seed(1)
vpts <- vcoords(xcoords(pts), ycoords(pts), round(100*runif(5)))
# ketika menggunakan method show, ikut method kelas coords
vpts
## [1] (-0.63, -0.82) ( 0.18,  0.49) (-0.84,  0.74) ( 1.60,  0.58) ( 0.33, -0.31)

Karena method show mengikuti kelas coords, maka value-nya tidak muncul sehingga harus didefinisikan ulang (sebagaimana print pada S3).

setMethod(show, signature(object = "vcoords"),
          definition = function(object){
            print(paste("(", format(xcoords(object)),
                        ", ", format(ycoords(object)),
                        "; ", format(value(object)),
                        ")", sep=""),
                  quote = F)
          })
vpts
## [1] (-0.63, -0.82; 27) ( 0.18,  0.49; 37) (-0.84,  0.74; 57) ( 1.60,  0.58; 91)
## [5] ( 0.33, -0.31; 20)

Demikian pula pendefisian ulang pada method plot

setMethod("plot", signature(x = "vcoords"), 
          function(x, txt = FALSE, bbox = FALSE, ...) {
            if (bbox) {
              if (!txt) {
                plot(xcoords(x), ycoords(x), ...)
              } else {
                plot(xcoords(x), ycoords(x), type = "n", ...)
                text(xcoords(x), ycoords(x), value(x), ...)
              }
              x.1 <- c(bbox(x)[1], bbox(x)[2], bbox(x)[2], bbox(x)[1])
              y.1 <- c(bbox(x)[3], bbox(x)[3], bbox(x)[4], bbox(x)[4])
              polygon(x.1, y.1)
            } else {
              if (!txt) {
                plot(xcoords(x), ycoords(x), ...)
              } else {
                plot(xcoords(x), ycoords(x), type = "n", ...)
                text(xcoords(x), ycoords(x), value(x), ...)
              }
            }
          })
plot(vpts)

plot(vpts,txt=T,bbox=T,pch=19,col="red")

Operasi Aritmetika

Menerapkan operasi aritmetika pada objek di dalam S4 dengan menggunakan method non-generik Arith yang dijadikan fungsi generik.

# menyimpan kondisi bahwa kedua objek yang dioperasikan memiliki panjang yang sama
sameloc <- function(e1, e2) (
  length(value(e1)) == length(value(e2))
    || any(xcoords(e1) == xcoords(e2))
    || any(ycoords(e1) == ycoords(e2)));

setMethod("Arith", signature(e1 = "vcoords",
                             e2 = "vcoords"),
          definition = function(e1, e2){
            if(!sameloc(e1, e2)) {
              stop("Butuh titik identik yang sama panjang!")
            }
            vcoords(xcoords(e1), ycoords(e2),
                    callGeneric(value(e1),
                                value(e2)))
          })
vpts
## [1] (-0.63, -0.82; 27) ( 0.18,  0.49; 37) (-0.84,  0.74; 57) ( 1.60,  0.58; 91)
## [5] ( 0.33, -0.31; 20)
vpts+vpts
## [1] (-0.63, -0.82;  54) ( 0.18,  0.49;  74) (-0.84,  0.74; 114)
## [4] ( 1.60,  0.58; 182) ( 0.33, -0.31;  40)
# karena argumen Arith di atas diatur operasi pada 2 objek kelas vcoords, sehingga line berikut akan menghasilkan error
3*vpts
## Error in 3 * vpts: non-numeric argument to binary operator

Agar bisa dioperasikan dengan skalar/nilai tunggal maka argumen e1 atau e2 diganti tipenya.

setMethod("Arith", signature(e1 = "numeric",
                             e2 = "vcoords"),
          definition = function(e1, e2){
            if(length(e1) > length(e2)) {
              stop("Length tidak benar!")
            }
            vcoords(xcoords(e2), ycoords(e2),
                    callGeneric(as.vector(e1),
                                value(e2)))
          })
vpts
## [1] (-0.63, -0.82; 27) ( 0.18,  0.49; 37) (-0.84,  0.74; 57) ( 1.60,  0.58; 91)
## [5] ( 0.33, -0.31; 20)
# mengalikan nilai pada vpts dengan skalar
3*vpts
## [1] (-0.63, -0.82;  81) ( 0.18,  0.49; 111) (-0.84,  0.74; 171)
## [4] ( 1.60,  0.58; 273) ( 0.33, -0.31;  60)
# jika nilai yang dikalikan vektor dengan length > 1, diambil elemen pertama
c(2,4)*vpts
## Warning in as.vector(e1) * value(e2): longer object length is not a multiple of
## shorter object length
## [1] (-0.63, -0.82;  54) ( 0.18,  0.49; 148) (-0.84,  0.74; 114)
## [4] ( 1.60,  0.58; 364) ( 0.33, -0.31;  40)

Method Subset

Sama seperti sebelumnya pada S3, diharapkan metode subset dengan operator kurung siku bisa dipakai untuk mengambil sebagian data pada objek S4.

setMethod("[",
          signature(x = "vcoords",
                    i = "ANY",
                    j = "missing", 
                    # karena tidak ada keterangan kolom dalam slot maka j (indeks kolom didefinisikan "missing")
                    drop = "missing"),
          definition = function(x, i){
            vcoords(xcoords(x)[i],
                    ycoords(x)[i],
                    value(x)[i])
          })
vpts[1:3]
## [1] (-0.63, -0.82; 27) ( 0.18,  0.49; 37) (-0.84,  0.74; 57)

Memeriksa Objek

Untuk memeriksa objek apakah masuk di kelas tertentu, digunakan syntax: is(objek, "kelas").

is(pts, "coords")
## [1] TRUE
is(pts, "vcoords")
## [1] FALSE
is(vpts, "coords")
## [1] TRUE
is(vpts, "vcoords")
## [1] TRUE

Coerce Object

Bisa juga dilakukan coerce atau memindahkan suatu objek ke kelas lain dan mengikuti karakteristik kelas tersebut, dengan menggunakan syntax as(objek, "kelas tujuan"). Misalkan:

  • Memindah vpts dari kelas vcoords ke kelas coords, maka slot value akan hilang karena di coords tidak memiliki slot tersebut.
as(vpts, "coords")
## [1] (-0.63, -0.82) ( 0.18,  0.49) (-0.84,  0.74) ( 1.60,  0.58) ( 0.33, -0.31)
  • Memindah pts ke kelas vcoords yang mana memiliki slot value, maka value-nya akan ada tapi tidak ada nilainya (nilai kosong).
as(pts, "vcoords")
## [1] (-0.63, -0.82; ) ( 0.18,  0.49; ) (-0.84,  0.74; ) ( 1.60,  0.58; )
## [5] ( 0.33, -0.31; )

Reference Classes (RC)

Sistem RC di R mirip dengan sistem OOP pada bahasa peemrograman lain. Pada dasarnya RC merupakan S4 namun dengan penambahan environment. Untuk membuat reference kelas, digunakan fungsi setRefClass() dan method-method pada reference class terikat dengan kelas tersebut secara khusus.