Praktikum Pemrograman Statistika

Build Your Own Tools: Pemrograman Fungsi & OOP di R

Author

Muhammad Yusran

Published

September 23, 2025

Saatnya Membuat Fungsi dan Objek Sendiri!

Dalam dunia analisis data, kita sering mengandalkan fungsi-fungsi bawaan R untuk memproses, menganalisis, atau memvisualisasikan data. Namun, bagaimana jika kamu ingin membuat fungsi sendiri yang disesuaikan dengan kebutuhan unik? Atau bahkan, bagaimana jika kamu ingin mendefinisikan struktur objek baru yang bisa menyimpan data dan berperilaku seperti objek di dunia nyata?

Praktikum ini akan membawamu melampaui penggunaan fungsi standar menuju dunia pemrograman fungsional dan berorientasi objek (OOP) di R. Kamu akan belajar bagaimana:

  • Menulis fungsi custom sesuai kebutuhanmu sendiri,
  • Mengelola class dan objek S3 dengan fleksibilitas tinggi,
  • Mendesain class S4 dengan struktur formal dan validasi ketat,
  • Mengembangkan method khusus,
  • Mewariskan class dan membangun hierarki objek.

Di luar kemampuan eksplorasi data, kemampuan membuat fungsi dan objek sendiri akan membantumu membangun pustaka kode reusable, scalable, dan elegan. Baik untuk penelitian, proyek besar, atau bahkan mengembangkan package R sendiri

Fungsi

Fungsi adalah kumpulan pernyataan yang diatur secara bersama untuk melakukan suatu pekerjaan yang spesifik, seperti perhitungan matematis, pembacaan data, analisis statistik, visualisasi, dan berbagai operasi lainnya.

Dalam bahasa pemrograman R, terdapat dua macam fungsi utama:

  1. Fungsi built-in (fungsi bawaan) – yaitu fungsi yang sudah disediakan dan terintegrasi langsung dalam R.
  2. Fungsi user-defined (fungsi buatan) – yaitu fungsi yang dapat didefinisikan atau dibuat sendiri oleh pemrogram untuk kebutuhan tertentu.

Penggunaan fungsi sangat penting karena memungkinkan kita untuk:

  • Menulis kode yang modular dan efisien,
  • Menghindari pengulangan perintah yang sama,
  • Meningkatkan keterbacaan dan fleksibilitas program,
  • Membuat alur analisis menjadi lebih terstruktur dan mudah dipelihara.

Built-in Function

Fungsi built-in adalah fungsi yang telah disediakan oleh R dan siap digunakan tanpa perlu didefinisikan ulang. Fungsi-fungsi ini mencakup berbagai keperluan umum mulai dari perhitungan statistik, transformasi data, hingga visualisasi.

Berikut beberapa fungsi built-in yang umum digunakan dalam analisis data menggunakan R:

Fungsi Deskripsi Contoh Penggunaan
mean() Menghitung nilai rata-rata mean(c(1, 2, 3, 4, 5))
sd() Menghitung simpangan baku sd(c(10, 12, 15))
sum() Menjumlahkan seluruh elemen dalam vektor sum(1:10)
length() Menghitung jumlah elemen length(c(4, 5, 6))
min() Nilai terkecil dari vektor min(c(5, 3, 9))
max() Nilai terbesar dari vektor max(c(5, 3, 9))
summary() Ringkasan statistik deskriptif summary(mtcars$mpg)
range() Rentang (min & max) dari data range(c(7, 2, 9, 4))
plot() Membuat grafik dasar plot(1:10)
hist() Membuat histogram hist(mtcars$mpg)

Berikut beberapa contoh penggunaan langsung fungsi bawaan R:

# Vektor data
data <- c(10, 12, 15, 14, 11)

# Rata-rata dan simpangan baku
mean(data)
[1] 12.4
sd(data)
[1] 2.073644
# Jumlah elemen dan total nilai
length(data)
[1] 5
sum(data)
[1] 62
# Nilai minimum dan maksimum
min(data)
[1] 10
max(data)
[1] 15
# Ringkasan statistik
summary(data)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   10.0    11.0    12.0    12.4    14.0    15.0 
range(data)
[1] 10 15
# Visualisasi sederhana
x <- 1:10
y <- x^2

# Plot garis
plot(x, y, type = "l", col = "blue", main = "Plot Kuadrat")

# Histogram distribusi mpg di dataset mtcars
hist(mtcars$mpg, col = "lightblue", main = "Histogram MPG Mobil")

Catatan Penting

Hampir semua fungsi built-in di R memiliki dokumentasi lengkap.
Gunakan ?nama_fungsi atau help(nama_fungsi) di console untuk membaca petunjuk penggunaannya.

Contoh:

?mean
help(summary)

Salah satu fasilitas yang menarik dalam penggunaan fungsi built-in di R adalah bahwa argumen fungsi dapat berupa fungsi lain. Dengan kata lain, kamu bisa menjalankan fungsi di dalam fungsi, sehingga sangat fleksibel dan powerful. Contohnya banyak digunakan dalam fungsi apply(), lapply(), dan sejenisnya.

# Data matriks
mat <- matrix(1:12, nrow = 3)

# Menghitung rata-rata tiap baris
apply(mat, 1, mean)
[1] 5.5 6.5 7.5
# Menghitung jumlah tiap kolom
apply(mat, 2, sum)
[1]  6 15 24 33

Konsep ini disebut juga sebagai higher-order function — yaitu fungsi yang dapat menerima fungsi lain sebagai input, atau menghasilkan fungsi sebagai output.

User-Defined Function

Tidak semua fungsi yang dibutuhkan ada di dalam R. Jika fungsi bawaan belum dapat memenuhi kebutuhan analisis yang spesifik, maka kita bisa membuat fungsi buatan sendiri, yang disebut juga user-defined function.

Fungsi ini memungkinkan kita untuk: - Menyusun kode agar lebih modular dan reusable, - Menyimpan blok instruksi yang bisa dipanggil berulang kali, - Menyesuaikan proses dengan kebutuhan analisis yang lebih kompleks.

Untuk membuat fungsi sendiri di R, terdapat beberapa komponen penting yang perlu diperhatikan, yaitu:

  1. Nama fungsi – penamaan fungsi sesuai kebutuhan (hindari nama yang sudah digunakan di R),
  2. Argumen fungsi – input yang diterima fungsi untuk diproses,
  3. Tubuh fungsi (body) – berisi instruksi atau blok kode yang dijalankan,
  4. Nilai kembali (return value) – hasil akhir yang dikembalikan oleh fungsi.

Bentuk umum ketika membuat fungsi sendiri adalah

nama_fungsi <- function(arg1, arg2, ...) {
  # baris kode
  # ...
  hasil
}

Atau bisa juga secara eksplisit menggunakan return() untuk mengembalikan hasil:

nama_fungsi <- function(arg1, arg2, ...) {
  # baris kode
  # ...
  return(hasil)
}

Berikut beberapa contoh fungsi buatan (user-defined function) untuk memahami variasi cara mendefinisikan fungsi di R:

# Fungsi 1: Mengembalikan satu output numerik (z)
transform_power <- function(n, power) {
  x <- runif(n)
  y <- runif(n)
  z <- (x + y)^power
  return(z)
}

# Contoh penggunaan
transform_power(10, 2)
 [1] 0.48051267 0.06818170 1.26250328 0.96515149 0.03637363 0.25156439
 [7] 0.55614606 2.86538606 2.63262568 1.42101504
# Fungsi 2: Mengembalikan list berisi x, y, dan hasil z
generate_components <- function(n, power) {
  x <- runif(n)
  y <- runif(n)
  z <- (x + y)^power
  return(list(x = x, y = y, result = z))
}

generate_components(10, 2)
$x
 [1] 0.69378428 0.42321757 0.60171259 0.24454416 0.37662937 0.78729579
 [7] 0.03032039 0.31896212 0.91859676 0.44027873

$y
 [1] 0.48930178 0.98795150 0.91631298 0.71422229 0.20538767 0.09026153
 [7] 0.37914798 0.75805441 0.03511455 0.70751029

$result
 [1] 1.3996926 1.9913982 2.3044016 0.9192331 0.3387438 0.7701068 0.1676643
 [8] 1.1599646 0.9095653 1.3174196
# Fungsi 3: Diberi nilai default untuk argumen n dan power
generate_default <- function(n = 1, power = 2) {
  x <- runif(n)
  y <- runif(n)
  z <- (x + y)^power
  return(z)
}

generate_default()      # menggunakan default
[1] 0.6995677
generate_default(5, 3)  # override default
[1] 0.8029451 0.3660964 0.2823785 0.3875657 0.3867205
# Fungsi 4: Tidak memiliki argumen, gunakan variabel global (kurang disarankan)
generate_global <- function() {
  x <- runif(n)
  y <- runif(n)
  z <- (x + y)^power
  return(z)
}

# Harus tetapkan n dan power terlebih dahulu
n <- 5; power <- 3
generate_global()
[1] 0.18118199 0.80014354 0.05391259 0.51320008 5.74455291
# Fungsi 5: Gunakan ... untuk fleksibilitas parameter runif
generate_flexible <- function(n, power, ...) {
  x <- runif(n, ...)
  y <- runif(n, ...)
  z <- (x + y)^power
  return(z)
}

generate_flexible(10, 2, min = 2, max = 5)
 [1] 44.53649 55.09521 55.90279 40.53816 67.28840 35.12916 76.13844 50.60678
 [9] 55.73468 42.60816

Fungsi generate_flexible() memungkinkan pengguna mengatur argumen tambahan seperti batas bawah (min) dan atas (max) langsung melalui parameter ..., yang sangat berguna saat bekerja dengan fungsi internal seperti runif(), rnorm(), atau paste().

Anonymous Function

Selain membuat fungsi dengan nama tertentu, kita juga bisa membuat fungsi langsung di tempat tanpa nama. Fungsi seperti ini disebut anonymous function. Biasanya, fungsi ini kita gunakan saat ingin melakukan operasi cepat—tanpa perlu menyimpan fungsi tersebut untuk digunakan ulang.

Fungsi jenis ini sering muncul saat kita memakai fungsi lain seperti apply(), lapply(), atau sapply(), dan kita hanya butuh proses satu langkah sederhana.

Bentuk umumnya:

function(argumen) ekspresi

Berikut beberapa contoh anonymous function:

# Mengkuadratkan setiap elemen vektor menggunakan sapply() + anonymous function
bilangan <- 1:5
sapply(bilangan, function(x) x^2)
[1]  1  4  9 16 25
# Menambahkan 100 ke setiap elemen matriks kolom demi kolom
mat <- matrix(1:9, nrow = 3)
apply(mat, 2, function(x) x + 100)
     [,1] [,2] [,3]
[1,]  101  104  107
[2,]  102  105  108
[3,]  103  106  109
# Fungsi lebih dari satu baris dalam anonymous function
sapply(1:5, function(x) {
  y <- x^2
  y + 10
})
[1] 11 14 19 26 35

Kita bisa memakai fungsi tanpa nama ini ketika:

  • Kita hanya butuh fungsi sekali pakai,
  • Ingin menulis kode secara ringkas langsung di tempatnya,
  • Tidak ingin menambah terlalu banyak nama fungsi ke environment kita.

Namun, kalau kode kita mulai rumit atau fungsinya akan dipakai berulang kali, sebaiknya kita definisikan dulu sebagai fungsi biasa (user-defined) agar lebih rapi dan mudah dirawat.

Error Handling and Debugging

Dalam pemrograman, kita tidak bisa selalu menghindari kesalahan. Namun, kita bisa mengantisipasi dan menangani error agar program tetap berjalan dengan baik dan mudah dilacak. Pada bagian ini, kita akan membahas:

  • Bagaimana menangani error dengan elegan,
  • Cara memberikan peringatan atau informasi kepada pengguna,
  • Strategi menelusuri dan memperbaiki error (debugging).

Tujuannya adalah agar fungsi yang kita buat lebih tangguh, tidak mudah crash, dan mudah diperbaiki saat ada masalah.

Jika proses eksekusi program berjalan terlalu lama atau menyebabkan kesalahan (error), kita bisa menghentikannya secara manual. Di RStudio, tersedia tombol merah Stop (🟥) di pojok kanan atas konsol yang dapat kita klik untuk menginterupsi proses yang sedang berjalan.

Selain itu, kita juga bisa menggunakan shortcut keyboard seperti ESC (di Windows/Mac) atau CTRL + C (di terminal atau R biasa) untuk menghentikan eksekusi secara paksa. Ini sangat penting saat fungsi masuk ke infinite loop atau error yang membuat R “hang”.

Menangani Error: stop(), warning(), dan message()

R menyediakan tiga fungsi utama untuk komunikasi saat error atau kondisi khusus:

Fungsi Tujuan
stop() Menghentikan eksekusi program dan menampilkan pesan error
warning() Menampilkan peringatan tapi program tetap dilanjutkan
message() Memberikan informasi tanpa menghentikan atau mengganggu eksekusi
#Contoh penggunaan:
cek_positif <- function(x) {
  if (x < 0) stop("Nilai harus positif!")
  if (x == 0) warning("Nilai nol diberikan")
  message("Input valid.")
  return(sqrt(x))
}
cek_positif(-4) # error
Error in cek_positif(-4) : Nilai harus positif!
cek_positif(0) # warning
Warning in cek_positif(0): Nilai nol diberikan
Input valid.
[1] 0
cek_positif(9) # message + hasil
Input valid.
[1] 3

Menghindari Program Crash: try() dan tryCatch()

Fungsi try() dan tryCatch() berguna saat kita ingin program tetap lanjut meski ada error.

hasil <- try(log("teks"))
Error in log("teks") : non-numeric argument to mathematical function
print("Program masih jalan")
[1] "Program masih jalan"

tryCatch() memberikan kontrol yang lebih fleksibel:

safe_log <- function(x) {
  tryCatch(
    log(x),
    error = function(e) {
      message("Terjadi error: ", e$message)
      return(NA)
      }
  )
  }


safe_log(10) # berhasil
[1] 2.302585
safe_log("a") # tidak error, hasil NA
Terjadi error: non-numeric argument to mathematical function
[1] NA

Debugging: Menelusuri dan Memperbaiki Error

Saat fungsi tidak berjalan sebagaimana mestinya, kita bisa:

  • browser(): masuk ke mode interaktif dalam fungsi,
  • traceback(): melihat jejak error terakhir,
  • debug() dan undebug(): menelusuri eksekusi fungsi baris per baris,
  • print() atau cat(): mencetak nilai.
debug(cek_positif)
cek_positif(-2) # akan masuk mode debugging
undebug(cek_positif)

Error handling dan debugging adalah keterampilan penting yang akan menghemat waktu dan stres saat proyek menjadi lebih kompleks. Jadi, jangan takut error – kita latih kemampuan untuk “berdialog” dengan error tersebut.

Object-Oriented Programming

Pemrograman Berorientasi Objek (Object-Oriented Programming, OOP) adalah paradigma pemrograman yang berfokus pada objek — entitas yang memiliki data (properti) dan fungsi (method) — serta bagaimana objek tersebut berinteraksi satu sama lain.

Dalam dunia analisis data dan pengembangan perangkat lunak, OOP membantu kita untuk:

  • Mengorganisasi kode menjadi struktur yang modular dan reusable,
  • Menyusun program yang lebih natural dan intuitif,
  • Meningkatkan pemeliharaan (maintainability) dan skalabilitas kode.

Prinsip-prinsip dari OOP adalah:

Konsep Penjelasan Singkat
Abstraksi Menyembunyikan detail implementasi dan hanya menampilkan fitur penting.
Enkapsulasi Mengemas data dan method dalam satu unit (objek), membatasi akses langsung.
Pewarisan (Inheritance) Objek dapat mewarisi properti dan method dari objek lain.
Polimorfisme Fungsi yang sama bisa memiliki perilaku berbeda tergantung class objek yang digunakan.

Class dan Object

Dalam OOP, class adalah cetak biru (blueprint) atau template untuk membuat objek. Class mendefinisikan atribut (disebut properti) dan fungsi (disebut method) yang dimiliki oleh objek.

Sedangkan object adalah instansiasi dari class — yaitu wujud konkret yang dibuat berdasarkan class. Satu class bisa digunakan untuk membuat banyak object yang berbeda.

Contoh analogi:

  • class Mobil: memiliki properti warna, kecepatan; dan method jalan(), berhenti().
  • object mobilA: adalah mobil merah yang bisa berjalan dan berhenti.
  • object mobilB: adalah mobil biru dengan kecepatan lebih tinggi.

OOP di Bahasa R

Walaupun R adalah bahasa pemrograman yang secara default bersifat fungsional, R mendukung paradigma berorientasi objek. Hal ini diwujudkan melalui dua sistem utama class:

  • Class System S3: sistem class yang sederhana dan fleksibel, banyak digunakan dalam paket-paket dasar R.
  • Class System S4: sistem class yang lebih ketat dan formal, cocok untuk pengembangan perangkat lunak dan package skala besar.

Keduanya memungkinkan pengguna untuk:

  • Mendefinisikan struktur objek (class definition),
  • Membuat objek baru (instantiation),
  • Mengembangkan fungsi khusus (method) sesuai dengan class-nya,
  • Mewariskan properti dan method ke class turunan (inheritance).

Berikut perbandingan antara kedua sistem class

Fitur S3 S4 # S3 (tanpa deklarasi class formal)
Pendefinisian Class Tidak formal (langsung lewat class<-) Formal (dengan setClass())
Validasi Struktur Tidak ada Ada validasi tipe dan panjang data
Pewarisan Bisa Bisa (lebih eksplisit dan kuat)
Method function.classname() setMethod() untuk generic
Cocok untuk Proyek kecil-menengah Proyek besar / pembuatan package

Contoh sederhananya seperti berikut:

# S3 (tanpa deklarasi class formal)
obj <- list(x = 1:5, y = 6:10)
class(obj) <- "coords"
obj
$x
[1] 1 2 3 4 5

$y
[1]  6  7  8  9 10

attr(,"class")
[1] "coords"
class(obj)
[1] "coords"
# S4 (dengan deklarasi formal)
setClass("coords", representation(x = "numeric", y = "numeric"))
obj <- new("coords", x = 1:5, y = 6:10)
obj
An object of class "coords"
Slot "x":
[1] 1 2 3 4 5

Slot "y":
[1]  6  7  8  9 10
class(obj)
[1] "coords"
attr(,"package")
[1] ".GlobalEnv"
Kapan Menggunakan S3 dan S4?
  • Gunakan S3 jika proyek kita cepat, eksploratif, atau berskala kecil.
  • Gunakan S4 jika kita mengembangkan package atau sistem dengan banyak validasi formal.

Class System S3 di R

Sistem class S3 adalah sistem pemrograman berorientasi objek yang paling sederhana dan fleksibel di R. Tidak ada deklarasi class formal — cukup dengan menetapkan atribut class pada objek menggunakan class(obj) <- "nama_class".

Class S3 memungkinkan kita untuk:

  • Membuat class sendiri untuk objek buatan,
  • Mengembangkan fungsi yang berlaku khusus untuk class tertentu (method),
  • Melakukan pewarisan sederhana antar class.

Constructor: Membuat Objek Class Baru

S3 tidak memiliki mekanisme formal untuk constructor seperti bahasa lain, tetapi kita bisa membuat fungsi constructor agar input divalidasi dan objek dibuat dengan struktur konsisten.

Contoh: Membuat class coords untuk menyimpan titik koordinat (x, y).

coords <- function(x, y) {
  if (!is.numeric(x) || !is.numeric(y)) stop("Koordinat harus numerik")
  if (!all(is.finite(x)) || !all(is.finite(y))) stop("Koordinat harus valid (bukan NA, NaN, Inf)")
  if (length(x) != length(y)) stop("Panjang x dan y harus sama")
  pts <- list(x = x, y = y)
  class(pts) <- "coords"
  return(pts)
}

Penggunaan:

pts <- coords(c(1.2, 3.4, 5.6), c(7.8, 9.0, 2.3))
class(pts) # "coords"
[1] "coords"

Accessor: Mengakses Komponen Objek

Walaupun objek coords adalah list, akses langsung seperti obj$x tidak disarankan. Gunakan fungsi accessor agar lebih terkontrol dan mendukung perubahan struktur di masa depan.

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

Untuk menggunakan fungsi accessornya, cukup memanggil seperti fungsi biasanya

xcoords(pts)
[1] 1.2 3.4 5.6
ycoords(pts)
[1] 7.8 9.0 2.3

Method Generik dan Khusus

Fungsi seperti print(), length(), dan plot() adalah fungsi generik. Kita bisa membuat method khusus untuk class coords dengan menulis fungsi print.coords(), length.coords(), dll.

print.coords <- function(obj) {
  for (i in seq_along(obj$x)) {
    cat("(", obj$x[i], ", ", obj$y[i], ")\n", sep = "")
  }
}
print(pts)
(1.2, 7.8)
(3.4, 9)
(5.6, 2.3)
length.coords <- function(obj) length(obj$x)
length(pts)
[1] 3

Semisal kita ingin membuat fungsi generik baru bernama bbox untuk menghitung bounding box (rentang nilai x dan y), kita bisa mendefinisikannya sebagai berikut:

bbox <- function(obj) UseMethod("bbox")


bbox.coords <- function(obj) {
  matrix(c(range(obj$x), range(obj$y)), ncol = 2,
         dimnames = list(c("min", "max"), c("x", "y")))
}


bbox(pts)
      x   y
min 1.2 2.3
max 5.6 9.0

Output berupa matriks 2x2 dengan kolom x dan y, serta baris min dan max.

Fungsi plot() adalah fungsi generik yang bisa dibuat versi khususnya untuk class coords. Kita bisa menambahkan opsi bbox = TRUE untuk menampilkan bounding box di sekitar titik-titik.

plot.coords <- function(obj, bbox = FALSE, ...) {
  plot(obj$x, obj$y, pch = 16, col = "blue", xlab = "x", ylab = "y", ...)
  if (bbox) {
    b <- bbox(obj)
    rect(b[1, 1], b[1, 2], b[2, 1], b[2, 2], border = "red", lty = 2)
  }
}
plot(pts)                # Plot titik
plot(pts, bbox = TRUE)   # Plot dengan bounding box merah

Pewarisan Class: Membuat Subclass vcoords

Dalam OOP, class bisa mewarisi properti dan method dari class lain. Di S3, pewarisan dilakukan dengan menambahkan class baru ke dalam vektor class objek.

class(obj) <- c("anak", "induk")
Warning in class(obj) <- c("anak", "induk"): Setting class(x) to multiple
strings ("anak", "induk", ...); result will no longer be an S4 object

Misalnya, kita ingin membuat class vcoords — turunan dari coords — yang memiliki nilai tambahan v (misalnya suhu atau intensitas) untuk setiap titik:

vcoords <- function(x, y, v) {
  if (!is.numeric(x) || !is.numeric(y) || !is.numeric(v)) stop("Semua komponen harus numerik")
  if (length(x) != length(y) || length(x) != length(v)) stop("Panjang x, y, v harus sama")
  obj <- list(x = x, y = y, v = v)
  class(obj) <- c("vcoords", "coords")
  return(obj)
}

Fungsi accessor tambahan:

nilai <- function(obj) obj$v

Overriding Method

Objek vcoords otomatis akan menggunakan semua method coords (seperti xcoords(), ycoords(), bbox(), plot()) kecuali jika ada method vcoords yang secara eksplisit dituliskan untuk menimpanya (overriding).

Agar method print() untuk vcoords juga menampilkan nilai v, kita bisa menuliskan ulang fungsi print.vcoords():

print.vcoords <- function(obj) {
  for (i in seq_along(obj$x)) {
    cat("(", obj$x[i], ", ", obj$y[i], "; ", obj$v[i], ")\n", sep = "")
  }
}

Kita juga bisa memperbarui fungsi plot() untuk vcoords agar:

  • Menampilkan nilai v di titik koordinat jika txt = TRUE,
  • Menampilkan bounding box jika bbox = TRUE.
plot.vcoords <- function(obj, txt = FALSE, bbox = FALSE, ...) {
  if (!txt) {
    plot(obj$x, obj$y, pch = 16, col = "darkgreen", xlab = "x", ylab = "y", ...)
  } else {
    plot(obj$x, obj$y, type = "n", xlab = "x", ylab = "y", ...)
    text(obj$x, obj$y, labels = obj$v, col = "darkgreen", ...)
  }

  if (bbox) {
    b <- bbox(obj)
    rect(b[1, 1], b[1, 2], b[2, 1], b[2, 2], border = "red", lty = 2)
  }
}
vpts <- vcoords(1:5, 6:10, c(20, 40, 60, 80, 100))
print(vpts)
(1, 6; 20)
(2, 7; 40)
(3, 8; 60)
(4, 9; 80)
(5, 10; 100)
plot(vpts, txt = TRUE, bbox = TRUE)

Method Subset

Secara default, operator subset [ ] pada objek class S3 akan memanggil method [.list, karena objek coords atau vcoords berbentuk list. Namun, agar objek vcoords dapat di-subset dengan hasil tetap berupa objek vcoords (bukan list biasa), kita perlu mendefinisikan method [.vcoords.

`[.vcoords` <- function(obj, i) {
  vcoords(obj$x[i], obj$y[i], obj$v[i])
}

Dengan method ini, kita bisa menggunakan sintaks subset seperti:

vpts <- vcoords(1:5, 6:10, c(100, 200, 300, 400, 500))
vpts[1:3]
(1, 6; 100)
(2, 7; 200)
(3, 8; 300)

Catatan:

  • Jika obj[i] dikembalikan dalam bentuk list biasa, maka fungsi generik seperti plot() atau bbox() tidak dapat digunakan.
  • Dengan method ini, kita menjaga konsistensi struktur objek saat subset dilakukan.

Pemeriksaan Class Objek

Untuk memeriksa apakah suatu objek merupakan class tertentu (misalnya untuk keperluan debugging atau validasi internal), kita bisa menggunakan fungsi inherits().

inherits(vpts, "vcoords")  # TRUE
[1] TRUE
inherits(vpts, "coords")   # TRUE (karena pewarisan)
[1] TRUE
inherits(vpts, "lm")       # FALSE
[1] FALSE

Hal ini dilakukan ketika:

  • Membuat method generik baru dan ingin memastikan class input cocok.
  • Melakukan pengujian kondisi sebelum eksekusi method tertentu.
  • Membuat fungsi stop() atau warning() agar hanya berlaku untuk class tertentu.

Contoh validasi dalam fungsi:

print_koordinat <- function(obj) {
  if (!inherits(obj, "coords")) stop("Objek harus class 'coords'")
  cat("Jumlah titik koordinat:", length(obj$x), "\n")
}

Dengan inherits(), kita mencegah pengguna memasukkan objek sembarangan yang bisa menyebabkan error saat dieksekusi.

Contoh: Class S3 player untuk Data Pemain Sepak Bola

Bayangkan kita ingin menyimpan informasi seorang pemain sepak bola yang terdiri atas: nama, tim, posisi, dan jumlah gol yang telah dicetak sepanjang kariernya.

Konstruktor dan Method

player <- function(nama, tim, posisi, gol, main) {
  if (!is.character(nama) || !is.character(tim) || !is.character(posisi)) {
    stop("Nama, tim, dan posisi harus karakter")
  }
  if (!is.numeric(gol) || !is.numeric(main) || length(gol) != 1 || length(main) != 1) {
    stop("Gol dan pertandingan harus berupa angka tunggal")
  }
  
  obj <- list(nama = nama, tim = tim, posisi = posisi, gol = gol, main = main)
  class(obj) <- "player"
  return(obj)
}

Method print

print.player <- function(obj) {
  cat("Nama   :", obj$nama, "\n")
  cat("Tim    :", obj$tim, "\n")
  cat("Posisi :", obj$posisi, "\n")
  cat("Gol    :", obj$gol, "\n")
  cat("Main   :", obj$main, "\n")
}
mbappe <- player("Kylian Mbappé", "Real Madrid", "Forward", 51, 65)
print(mbappe)
Nama   : Kylian Mbappé 
Tim    : Real Madrid 
Posisi : Forward 
Gol    : 51 
Main   : 65 

Method goalRatio(): Rasio Gol per Pertandingan

goalRatio <- function(obj) UseMethod("goalRatio")

goalRatio.player <- function(obj) {
  obj$gol / obj$main
}
goalRatio(mbappe)
[1] 0.7846154

Method compareGoals(): Membandingkan Dua Pemain

compareGoals <- function(p1, p2) {
  UseMethod("compareGoals")
}

compareGoals.player <- function(p1, p2) {
  if (!inherits(p2, "player")) {
    stop("Argumen kedua harus class 'player'")
  }

  cat("Perbandingan Gol:\n")
  cat(p1$nama, ":", p1$gol, "gol\n")
  cat(p2$nama, ":", p2$gol, "gol\n")

  selisih <- p1$gol - p2$gol

  if (selisih > 0) {
    cat(p1$nama, "unggul", selisih, "gol dari", p2$nama, "\n")
  } else if (selisih < 0) {
    cat(p2$nama, "unggul", abs(selisih), "gol dari", p1$nama, "\n")
  } else {
    cat("Keduanya memiliki jumlah gol yang sama\n")
  }
}
yamal <- player("Lamine Yamal", "Barcelona", "Forward", 27, 109)
compareGoals(mbappe, yamal)
Perbandingan Gol:
Kylian Mbappé : 51 gol
Lamine Yamal : 27 gol
Kylian Mbappé unggul 24 gol dari Lamine Yamal 

Tambahkan compareGoals.default()

compareGoals.default <- function(p1, p2) {
  stop("compareGoals hanya tersedia untuk objek class 'player'")
}

Dengan begini, compareGoals() kini adalah fungsi S3 yang formal dan fleksibel.

Update Method compareGoals.player() dengan Argumen ratio = TRUE

Semisal, kita ingin memperbarui method compareGoals() agar bisa membandingkan rasio gol per pertandingan, bukan hanya jumlah gol total. Kita cukup menambahkan argumen ratio = TRUE.

compareGoals <- function(p1, p2, ...) {
  UseMethod("compareGoals")
}

compareGoals.player <- function(p1, p2, ratio = FALSE) {
  if (!inherits(p2, "player")) {
    stop("Argumen kedua harus class 'player'")
  }

  if (!is.logical(ratio) || length(ratio) != 1) {
    stop("Parameter 'ratio' harus TRUE atau FALSE")
  }

  if (ratio) {
    r1 <- goalRatio(p1)
    r2 <- goalRatio(p2)

    cat("Perbandingan Rasio Gol per Pertandingan:\n")
    cat(p1$nama, ":", round(r1, 3), "gol/match\n")
    cat(p2$nama, ":", round(r2, 3), "gol/match\n")

    selisih <- r1 - r2
    if (selisih > 0) {
      cat(p1$nama, "unggul", round(selisih, 3), "gol/match dari", p2$nama, "\n")
    } else if (selisih < 0) {
      cat(p2$nama, "unggul", round(abs(selisih), 3), "gol/match dari", p1$nama, "\n")
    } else {
      cat("Keduanya memiliki rasio gol per pertandingan yang sama\n")
    }
  } else {
    cat("Perbandingan Jumlah Gol:\n")
    cat(p1$nama, ":", p1$gol, "gol\n")
    cat(p2$nama, ":", p2$gol, "gol\n")

    selisih <- p1$gol - p2$gol
    if (selisih > 0) {
      cat(p1$nama, "unggul", selisih, "gol dari", p2$nama, "\n")
    } else if (selisih < 0) {
      cat(p2$nama, "unggul", abs(selisih), "gol dari", p1$nama, "\n")
    } else {
      cat("Keduanya memiliki jumlah gol yang sama\n")
    }
  }
}
compareGoals(mbappe, yamal)  # default: perbandingan jumlah gol
Perbandingan Jumlah Gol:
Kylian Mbappé : 51 gol
Lamine Yamal : 27 gol
Kylian Mbappé unggul 24 gol dari Lamine Yamal 
compareGoals(mbappe, yamal, ratio = TRUE)  # perbandingan rasio gol per match
Perbandingan Rasio Gol per Pertandingan:
Kylian Mbappé : 0.785 gol/match
Lamine Yamal : 0.248 gol/match
Kylian Mbappé unggul 0.537 gol/match dari Lamine Yamal 

Catatan Kritis tentang S3

Sistem class S3 di R sangat populer karena kemudahannya dan menawarkan fleksibilitas tinggi, namun dengan beberapa risiko jika tidak digunakan secara hati-hati. Tabel berikut merangkum kelebihan, kekurangan, serta kesalahan umum penggunaannya.

Aspek Keterangan
Kelebihan
Sederhana Tidak perlu deklarasi class formal — cukup set class(obj) <- "nama"
Ringan Cocok untuk eksplorasi dan prototipe cepat
Kompatibel Didukung luas oleh fungsi bawaan R seperti print(), summary(), plot()
Kekurangan
Tidak validasi Tidak ada pengecekan struktur internal objek
Sulit dilacak Sulit dipelihara dalam proyek besar tanpa dokumentasi yang baik
Rentan error Mudah salah assign class ke objek yang tidak sesuai

Contoh Kesalahan Umum

Kasus Contoh Kode Penjelasan
Menetapkan class yang salah class(x) <- "lm" x bukan model regresi, tapi dipaksa jadi class "lm"
Fungsi tidak sesuai class summary(x) setelah itu Bisa menghasilkan error atau output tidak relevan
Tidak pakai constructor Buat list lalu langsung set class Bisa membuat objek tidak lengkap/valid

Class System S4 di R

Class System S4 adalah sistem OOP (Object-Oriented Programming) di R yang lebih formal dan ketat dibanding S3. Sistem ini dirancang untuk mengatasi kekurangan S3 seperti tidak adanya validasi struktur, dan lebih cocok untuk proyek besar atau pengembangan package.

Beberapa karakteristik utama S4:

  • Definisi class dilakukan dengan setClass(),
  • Objek dibuat menggunakan new() atau constructor custom,
  • Slot digunakan untuk mendefinisikan struktur data secara eksplisit,
  • Method ditetapkan dengan setMethod() dan generic function didefinisikan lewat setGeneric(),
  • Validasi tipe data otomatis dilakukan.

Pendefinisian Class dengan setClass()

Contoh: mendefinisikan class coords dengan dua slot numerik x dan y:

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

Constructor: Membuat Objek S4

Buat constructor agar input divalidasi:

coords <- function(x, y){
  if (length(x) != length(y)) stop("Panjang x dan y harus sama")
  if (!is.numeric(x) || !is.numeric(y)) stop("x dan y harus numerik")
  new("coords", x = as.vector(x), y = as.vector(y))
}

pts <- coords(c(1.2, 3.4), c(4.5, 6.7))
pts
An object of class "coords"
Slot "x":
[1] 1.2 3.4

Slot "y":
[1] 4.5 6.7
class(pts)
[1] "coords"
attr(,"package")
[1] ".GlobalEnv"

Akses Slot: Menggunakan @ atau Fungsi Aksesor

pts@x
[1] 1.2 3.4
pts@y
[1] 4.5 6.7
xcoords <- function(obj) obj@x
ycoords <- function(obj) obj@y

Method show() (Setara print() di S3)

setMethod("show", signature(object = "coords"),
          function(object) {
            for (i in seq_along(object@x)) {
              cat("(", object@x[i], ", ", object@y[i], ")\n", sep = "")
              }
            })

pts
(1.2, 4.5)
(3.4, 6.7)

Fungsi Generik Baru: bbox()

setGeneric("bbox", function(obj) standardGeneric("bbox"))
[1] "bbox"
setMethod("bbox", signature(obj = "coords"),
          function(obj) {
            matrix(c(range(obj@x), range(obj@y)), ncol = 2,
                   dimnames = list(c("min", "max"), c("x", "y")))
            })

bbox(pts)
      x   y
min 1.2 4.5
max 3.4 6.7

Method plot() untuk Objek coords

setMethod("plot", signature(x = "coords"),
          function(x, bbox = FALSE, ...) {
            plot(x@x, x@y, pch = 16, col = "blue", ...)
            if (bbox) {
              b <- bbox(x)
              rect(b[1, 1], b[1, 2], b[2, 1], b[2, 2], border = "red", lty = 2)
              }
            })

plot(pts, bbox = TRUE)

Pewarisan: Class vcoords Turunan dari coords

Kita ingin menambahkan slot nilai ke class coords. Dengan S4, cukup gunakan contains.

setClass("vcoords",
         representation(nilai = "numeric"),
         contains = "coords")

vcoords <- function(x, y, nilai) {
  if (!is.numeric(x) || !is.numeric(y) || !is.numeric(nilai)) stop("Semua slot harus numerik")
  if (!(length(x) == length(y) && length(y) == length(nilai))) stop("Panjang harus sama")
  new("vcoords", x = as.vector(x), y = as.vector(y), nilai = as.vector(nilai))
}

Fungsi Aksesor untuk nilai

nilai <- function(obj) obj@nilai

Method show untuk vcoords

setMethod("show", signature(object = "vcoords"),
          function(object) {
            for (i in seq_along(object@x)) {
              cat("(", object@x[i], ", ", object@y[i], "; ", object@nilai[i], ")\n", sep = "")
              }
            })

Method plot untuk vcoords

setMethod("plot", signature(x = "vcoords"),
          function(x, txt = FALSE, bbox = FALSE, ...) {
            if (!txt) {
              plot(x@x, x@y, pch = 16, col = "darkgreen", ...)
              } else {
                plot(x@x, x@y, type = "n", ...)
                text(x@x, x@y, labels = x@nilai, col = "darkgreen", ...)
                }
            if (bbox) {
              b <- bbox(x)
              rect(b[1, 1], b[1, 2], b[2, 1], b[2, 2], border = "red", lty = 2)
              }
            })
vpts <- vcoords(1:5, 6:10, c(10, 20, 30, 40, 50))
plot(vpts, txt = TRUE, bbox = TRUE)

Method Subsetting [ ]

setMethod("[", signature(x = "vcoords", i = "ANY", j = "missing", drop = "missing"),
          function(x, i, j, drop) {
            vcoords(x@x[i], x@y[i], x@nilai[i])
            })

vpts[1:3]
(1, 6; 10)
(2, 7; 20)
(3, 8; 30)

Pemeriksaan Class: is() dan Coercion

is(vpts, "coords") # TRUE
[1] TRUE
is(vpts, "vcoords") # TRUE
[1] TRUE
as(vpts, "coords") # coercion menjadi class induk
(1, 6)
(2, 7)
(3, 8)
(4, 9)
(5, 10)

Operasi Aritmetika

Kita bisa menambahkan operasi aritmetika menggunakan setMethod("Arith").

sameloc <- function(e1, e2) {
  length(e1@nilai) == length(e2@nilai)
  }

setMethod("Arith", signature(e1 = "vcoords", e2 = "vcoords"),
          function(e1, e2) {
            if (!sameloc(e1, e2)) stop("Ukuran tidak sama")
            vcoords(e1@x, e1@y, callGeneric(e1@nilai, e2@nilai))
            })

setMethod("Arith", signature(e1 = "numeric", e2 = "vcoords"),
          function(e1, e2) {
            if (length(e1) > length(e2@nilai)) stop("Panjang numeric lebih dari nilai")
            vcoords(e2@x, e2@y, callGeneric(e1, e2@nilai))
            })


2 * vpts
(1, 6; 20)
(2, 7; 40)
(3, 8; 60)
(4, 9; 80)
(5, 10; 100)
vpts + vpts
(1, 6; 20)
(2, 7; 40)
(3, 8; 60)
(4, 9; 80)
(5, 10; 100)

Contoh: Class S4 player untuk Data Pemain Sepak Bola

Salah satu keunggulan sistem S4 adalah kemampuannya untuk mendefinisikan perilaku khusus saat objek digunakan dalam operasi aritmetika. Berikut ini kita definisikan class player untuk merepresentasikan data seorang pemain sepak bola, lalu menambahkan method aritmetika +.

Definisi Class player

setClass("player",
         representation(
           nama = "character",
           tim = "character",
           posisi = "character",
           gol = "numeric",
           main = "numeric"
         ))

Constructor: Validasi Input dan Pembuatan Objek

player <- function(nama, tim, posisi, gol, main) {
  if (!is.character(nama) || !is.character(tim) || !is.character(posisi)) {
    stop("Nama, tim, dan posisi harus karakter")
  }
  if (!is.numeric(gol) || !is.numeric(main) || length(gol) != 1 || length(main) != 1) {
    stop("Gol dan pertandingan harus berupa angka tunggal")
  }

  new("player", nama = nama, tim = tim, posisi = posisi, gol = gol, main = main)
}

Method show() untuk Menampilkan Objek

setMethod("show", "player", function(object) {
  cat("Nama   :", object@nama, "\n")
  cat("Tim    :", object@tim, "\n")
  cat("Posisi :", object@posisi, "\n")
  cat("Gol    :", object@gol, "\n")
  cat("Main   :", object@main, "\n")
})
mbappe1 <- player("Kylian Mbappé", "Real Madrid", "Forward", 10, 12)
mbappe2 <- player("Kylian Mbappé", "Real Madrid", "Forward", 7, 10)
mbappe1
Nama   : Kylian Mbappé 
Tim    : Real Madrid 
Posisi : Forward 
Gol    : 10 
Main   : 12 

Method goalRatio()

setGeneric("goalRatio", function(obj) standardGeneric("goalRatio"))
[1] "goalRatio"
setMethod("goalRatio", signature(obj = "player"),
          function(obj) {
            obj@gol / obj@main
          })
goalRatio(mbappe1)
[1] 0.8333333

Method compareGoals()

setGeneric("compareGoals", function(p1, p2, ratio = FALSE) standardGeneric("compareGoals"))
[1] "compareGoals"
setMethod("compareGoals", signature(p1 = "player", p2 = "player"),
          function(p1, p2, ratio = FALSE) {
            if (!is.logical(ratio) || length(ratio) != 1) {
              stop("Parameter 'ratio' harus TRUE atau FALSE")
            }

            if (ratio) {
              r1 <- goalRatio(p1)
              r2 <- goalRatio(p2)

              cat("Perbandingan Rasio Gol per Pertandingan:\n")
              cat(p1@nama, ":", round(r1, 3), "gol/match\n")
              cat(p2@nama, ":", round(r2, 3), "gol/match\n")

              selisih <- r1 - r2
              if (selisih > 0) {
                cat(p1@nama, "unggul", round(selisih, 3), "gol/match dari", p2@nama, "\n")
              } else if (selisih < 0) {
                cat(p2@nama, "unggul", round(abs(selisih), 3), "gol/match dari", p1@nama, "\n")
              } else {
                cat("Keduanya memiliki rasio gol per pertandingan yang sama\n")
              }
            } else {
              cat("Perbandingan Jumlah Gol:\n")
              cat(p1@nama, ":", p1@gol, "gol\n")
              cat(p2@nama, ":", p2@gol, "gol\n")

              selisih <- p1@gol - p2@gol
              if (selisih > 0) {
                cat(p1@nama, "unggul", selisih, "gol dari", p2@nama, "\n")
              } else if (selisih < 0) {
                cat(p2@nama, "unggul", abs(selisih), "gol dari", p1@nama, "\n")
              } else {
                cat("Keduanya memiliki jumlah gol yang sama\n")
              }
            }
          })
yamal1 <- player("Lamine Yamal", "Barcelona", "Forward", 27, 109)

compareGoals(mbappe1, yamal1)  # berdasarkan jumlah gol
Perbandingan Jumlah Gol:
Kylian Mbappé : 10 gol
Lamine Yamal : 27 gol
Lamine Yamal unggul 17 gol dari Kylian Mbappé 
compareGoals(mbappe1, yamal1, ratio = TRUE)  # berdasarkan rasio gol per pertandingan
Perbandingan Rasio Gol per Pertandingan:
Kylian Mbappé : 0.833 gol/match
Lamine Yamal : 0.248 gol/match
Kylian Mbappé unggul 0.586 gol/match dari Lamine Yamal 

Method Aritmetika: + untuk Menjumlahkan Gol dan Pertandingan

setMethod("Arith", signature(e1 = "player", e2 = "player"),
          function(e1, e2) {
            if (e1@nama != e2@nama) stop("Hanya bisa menjumlahkan statistik pemain yang sama")
            if (.Generic != "+") stop("Hanya operasi '+' yang diizinkan untuk 'player'")
            
            player(
              nama = e1@nama,
              tim = e1@tim,
              posisi = e1@posisi,
              gol = e1@gol + e2@gol,
              main = e1@main + e2@main
            )
          })

Menambahkan Gol dengan Nilai Numerik

setMethod("Arith", signature(e1 = "numeric", e2 = "player"),
          function(e1, e2) {
            if (.Generic != "+") stop("Hanya operasi '+' yang diizinkan untuk numeric + player")
            
            player(
              nama = e2@nama,
              tim = e2@tim,
              posisi = e2@posisi,
              gol = e1 + e2@gol,
              main = e2@main
            )
          })

setMethod("Arith", signature(e1 = "player", e2 = "numeric"),
          function(e1, e2) {
            if (.Generic != "+") stop("Hanya operasi '+' yang diizinkan untuk player + numeric")
            
            player(
              nama = e1@nama,
              tim = e1@tim,
              posisi = e1@posisi,
              gol = e1@gol + e2,
              main = e1@main
            )
          })
mbappe1 + mbappe2        # Menggabungkan statistik dua pertandingan
Nama   : Kylian Mbappé 
Tim    : Real Madrid 
Posisi : Forward 
Gol    : 17 
Main   : 22 
2 + mbappe1              # Tambah 2 gol ke statistik Mbappé
Nama   : Kylian Mbappé 
Tim    : Real Madrid 
Posisi : Forward 
Gol    : 12 
Main   : 12 
mbappe1 + 3              # Tambah 3 gol ke statistik Mbappé
Nama   : Kylian Mbappé 
Tim    : Real Madrid 
Posisi : Forward 
Gol    : 13 
Main   : 12 

Muhammad Yusran
Program Studi Magister Statistika dan Sains Data
Sekolah Sains Data, Matematika, dan Informatika
IPB University

Email: muhammadyusran@apps.ipb.ac.id
LinkedIn: Muhammad Yusran
Instagram: mhammad.yusran_