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.
# 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
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:
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.
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 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
lowercase <- function(text){
text <- tolower(text)
}
x <- "HalLo"
lowercase(x)
x
## [1] "HalLo"
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().
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.
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!
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."
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.
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:
finally memungkinkan pada setiap hasil yang
didapat, dia akan dieksekusi.error dieksekusi jika terjadi error, tanpa
menampilkan error itu sendiri.warning dieksekusi jika terjadi error.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:
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.
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.
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*.
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().
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() |
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:
print.length.bbox.plot.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.
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.
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:
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"
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 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.
method.namakelas <- function() ekspresibarusetMethod("nama method").printMethod 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
lengthJika 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
bbox yang dibuat sebagai fungsi generik
baruUntuk 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
plotIngin 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")
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")
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
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
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
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.
setClass()setClass("coords",
representation(x = "numeric",
y = "numeric"))
representation adalah fungsi argumen untuk menentukan
tipe data dari masing-masing slot dalam kelas.
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.
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
showJika 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)
bboxSebagaimana 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
plotsetMethod ("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?
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")
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)
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)
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
Bisa juga dilakukan coerce atau memindahkan suatu objek ke kelas lain
dan mengikuti karakteristik kelas tersebut, dengan menggunakan syntax
as(objek, "kelas tujuan"). Misalkan:
as(vpts, "coords")
## [1] (-0.63, -0.82) ( 0.18, 0.49) (-0.84, 0.74) ( 1.60, 0.58) ( 0.33, -0.31)
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; )
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.