Pada bagian ini saya belajar tentang functions.

library(tidyverse)

Keuntungan peggunaan function adalah sebagai berikut:

  1. “Codes” menjadi lebih mudah untuk dimengerti.

  2. Jika ingin mengganti sesuatu dalam “codes” kita hany mengedit “codes” tersebut dalam satu tempat.

  3. Meminimalisir kesalahan ketika kita menggunakan “copy paste”, seperti typo atau mngganti variabl pada “codes” tersebut.

Kapan kita menggunakan function ?

Penulisan function digunakan saat suatu blok kode di “copy paste” lebih dari dua kali. Contoh:

df <- tibble::tibble(
  a = rnorm(10),
  b = rnorm(10),
  c = rnorm(10),
  d = rnorm(10)
)

df
## # A tibble: 10 x 4
##          a       b       c      d
##      <dbl>   <dbl>   <dbl>  <dbl>
##  1  0.0582 -0.153   1.02    1.01 
##  2 -0.453  -1.02   -1.85    0.422
##  3  0.934  -0.613   0.0405  1.20 
##  4  1.12   -0.0970 -0.760  -2.09 
##  5  0.221   0.928   1.69   -0.533
##  6 -1.36    0.554   0.106  -0.953
##  7 -2.07    2.00   -0.911   2.04 
##  8 -1.13    0.139  -0.995  -0.127
##  9  0.413   1.18    0.159  -2.01 
## 10  0.612   0.592  -0.210  -1.08
df$a <- (df$a - min(df$a, na.rm = TRUE)) /
  (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$b <- (df$b - min(df$b, na.rm = TRUE)) /
  (max(df$b, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$c <- (df$c - min(df$c, na.rm = TRUE)) /
  (max(df$c, na.rm = TRUE) - min(df$c, na.rm = TRUE))
df$d <- (df$d - min(df$d, na.rm = TRUE)) /
  (max(df$d, na.rm = TRUE) - min(df$d, na.rm = TRUE))

df
## # A tibble: 10 x 4
##        a     b     c      d
##    <dbl> <dbl> <dbl>  <dbl>
##  1 0.668 0.435 0.812 0.750 
##  2 0.508 0     0     0.608 
##  3 0.943 0.205 0.534 0.796 
##  4 1     0.463 0.308 0     
##  5 0.719 0.975 1     0.377 
##  6 0.224 0.788 0.552 0.275 
##  7 0     1.51  0.265 1     
##  8 0.295 0.581 0.241 0.475 
##  9 0.779 1.10  0.567 0.0181
## 10 0.842 0.808 0.463 0.243

Pada saat menggunakan “copy paste” blok kode, kita kemungkinan Typo/lupa mengganti variabel. Pada contoh diatas saya sengaja menuliskan blok kode yang lupa mengganti variabel. Terlihat pada blok kode kedua, seharusnya “-min(dfb…)”, namun pada kode tertulis “-min(dfa…)”.

Untuk menuliskan suatu function, pertama-tama kita menganalisis “codes” tersebut. Berapa banyak inputnya, misal pada ontoh yang diatas.

(df$a - min(df$a, na.rm = TRUE)) /
 (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
##  [1] 0.6680230 0.5075804 0.9427677 1.0000000 0.7189352 0.2243503 0.0000000
##  [8] 0.2946629 0.7792600 0.8415102

“codes” diatas hanya memerlukan satu input, yaitu df$a. Biar lebih jelas kita tulis lagi “codes” dengan mengganti input menjadi x.

x <- df$a
(x - min(x, na.rm = TRUE)) /(max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
##  [1] 0.6680230 0.5075804 0.9427677 1.0000000 0.7189352 0.2243503 0.0000000
##  [8] 0.2946629 0.7792600 0.8415102

Pada “codes” diatas penulisan min dan max dapat diubah menjadi lebih singkat dan mudah.

range1<-range(x, na.rm = T)
range1
## [1] 0 1

Dapat dilihat bahwa nilai minimumnya adalah 0 dan nilai maksimumnya adalah 1.

Jadi, "codes’ dapat ditulis sebagai berikut:

(x-range1[1])/ (range1[2]-range1[1])
##  [1] 0.6680230 0.5075804 0.9427677 1.0000000 0.7189352 0.2243503 0.0000000
##  [8] 0.2946629 0.7792600 0.8415102

Nah, jadi penulisan “codesnya” seperti ini:

rescale1<-function(x){
  range1<-range(x, na.rm=T)
  (x-range1[1])/(range1[2]-range1[1])
}


rescale1(c(-10, 0, 10))
## [1] 0.0 0.5 1.0
rescale1(c(1, 2, 3, NA, 5))
## [1] 0.00 0.25 0.50   NA 1.00
df$a<-rescale1(df$a)
df$b<-rescale1(df$b)
df$c<-rescale1(df$c)
df$d<-rescale1(df$d)

df
## # A tibble: 10 x 4
##        a     b     c      d
##    <dbl> <dbl> <dbl>  <dbl>
##  1 0.668 0.288 0.812 0.750 
##  2 0.508 0     0     0.608 
##  3 0.943 0.136 0.534 0.796 
##  4 1     0.306 0.308 0     
##  5 0.719 0.645 1     0.377 
##  6 0.224 0.522 0.552 0.275 
##  7 0     1     0.265 1     
##  8 0.295 0.385 0.241 0.475 
##  9 0.779 0.728 0.567 0.0181
## 10 0.842 0.534 0.463 0.243
x1 <- c(1:10, Inf)
x1
##  [1]   1   2   3   4   5   6   7   8   9  10 Inf
rescale1(x1)
##  [1]   0   0   0   0   0   0   0   0   0   0 NaN

Namun, pada kasus tersebut terjadi kesalahan. kesalahan tersebut dapat teratasi dengan “codes” di bawah:

rescale2 <- function(x) {
 range2 <- range(x, na.rm = TRUE, finite = TRUE)
 (x - range2[1]) / (range2[2] - range2[1])
}
rescale2(x1)
##  [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667
##  [8] 0.7777778 0.8888889 1.0000000       Inf

Function are for humans and computers

fuction digunakan bukan hanya untuk computer, namun digunakan juga untuk manusia. Berikut merupakan sesuatu yang harus dilakukan saat menulis suatu function agar function tersebut dapat dimengerti oleh manusia.

Hal yang penting adalah pada nama function. Nama function harus singkat dan jelas. Secara umum nama function adalah variabel dan argumentnya adalah sebuah noun. Contoh:

Terlalu singkat

f()


Bukan suatu Variabel

my_awesome_function()


Panjang tapi jelas

impute_missing()

collapse_years()


Never do this!

col_mins <- function(x, y) {}

rowMaxes <- function(y, x) {}


Apabila nama function dari dua kata, penulisan harus konsisten mau pakai underscore atau menggunakan huruf kecil dan huruf besar.

Jika kita mempunyai nama functions dengan suatu persamaan,contoh kita mempunyai beberapa function input seperti dibawah.


lakukan seperti ini

input_select()

input_checkbox()

input_text()


Jangan juga menuliskan suatu yang sudah diketahui di R menjadi variabel, seperti:

T <- FALSE

c <- 10

mean <- function(x) sum(x)

Kan “T” mah R udah tau kalau itu artinya ’true" kalau buat seperti diatas nanti bakalan chaos.

Suatu hal penting lain adalah menggunakan “Comment” pada script dan memberi nama chunk pada markdown , seperti:

# Load data --------------------------------------

# Plot data --------------------------------------

Untuk memudahkan gunakan Ctrl+Shift+R.


Conditional Execution

umumnya “codes”nya seperti ini:

if (condition) {

# code executed when condition is TRUE

} else {

# code executed when condition is FALSE }

Berikut contoh function menggunakan if, yang bertujuan untuk melihat apakah input diberi nama atau tidak.

has_name <- function(x) {
  nms <- names(x)
  if (is.null(nms)) {
    rep(FALSE, length(x))
  } else {
    !is.na(nms) & nms != ""
  }
}

z1<- list(a = 1, b = "c", c = 1:3,9)
z1
## $a
## [1] 1
## 
## $b
## [1] "c"
## 
## $c
## [1] 1 2 3
## 
## [[4]]
## [1] 9
z2<- c(a=1,b=2,"c")
z2
##   a   b     
## "1" "2" "c"
has_name(z1)
## [1]  TRUE  TRUE  TRUE FALSE
has_name(z2)
## [1]  TRUE  TRUE FALSE

Untuk menggunakan beberapa logical bisa menggunakan || (or) dan && (dan).


Multiple Conditions

Umumnya codesnya seperti ini:

if (this) {

# do that

} else if (that) {

# do something else

} else {

# do another something else

}

Berikut adalah contoh penggunaannya

cek <- function(y,x){
  if (y > x ) {
    "Y is grater than X"
    }
  else if (y == x) {
    "Y is equal X"
    } else {
      "Y is lower than X"
      }

}

cek(9, 2)
## [1] "Y is grater than X"
cek(9,9)
## [1] "Y is equal X"
cek(3.14, pi)
## [1] "Y is lower than X"

Usahakan selalu menulis kondisi didalam {}


Function Arguments

Argumen atau input pada suatu function terbagi dalam dua jenis, yaitu pertama data argument kedau detail argument.

Contoh:

Perhitungan interval kepercayaan disekitar rata-rata menggunakan normal approksimai.

mean_ci <- function(x, conf = 0.95) {
  se <- sd(x) / sqrt(length(x))
  alpha <- 1 - conf
  mean(x) + se * qnorm(c(alpha / 2,
                         1 - alpha / 2))
}

x <- runif(100)
mean_ci(x)
## [1] 0.4514287 0.5701252
mean_ci(x, conf = 0.99)
## [1] 0.4327801 0.5887738
mean_ci(x, 0.99)
## [1] 0.4327801 0.5887738

Berdasarkan contoh diatas dapat kita lihat kedua bentuk argument. Pertama data argument, pada function diatas x merupakan data argument. Dan kedua, detail argument adalah conf. Pada penulisan argument, umumnya data argument diletakkan diawal dan detail argument berada diakhir.

Pada saat kita memanggil suatu functions usahakan kita menulis detail argument secara lengkap. Walaupun hasilnya sama, namun itu tidak umum dan kemungkinan bisa teradi suatu kesalahan.

# Good
mean_ci(x, conf = 0.99)
## [1] 0.4327801 0.5887738
# Bad
mean_ci(x, 0.99)
## [1] 0.4327801 0.5887738
# Good
mean(1:10, na.rm = TRUE)
## [1] 5.5
# Bad
mean(x = 1:10, , FALSE)
## [1] 5.5
mean(, TRUE, x = c(1:10, NA))
## [1] 5.5

Selain itu satu hal lagi yang saya abaru tahu, bahwa pada penulisan code usahakan selalu menempatkan spasi setelah penulisan simbol. Contoh:

feet<- 20
inches<- 200
# Good
average <- mean(feet / 12 + inches, na.rm = TRUE)
# Bad
average<-mean(feet/12+inches,na.rm=TRUE)

Choosing Names

Nama suatu argumen merupakan suatu hal penting juga. Berikut merupakan beberapa contoh penamaan yang biasanya dilakukan dan orang-orang pun akan mengetahuinya:

  1. x, y, z: vectors.
  2. w: a vector of weights.
  3. df: a data frame.
  4. i, j: numeric indices (typically rows and columns).
  5. n: length, or number of rows.
  6. p: number of columns.

Checking Value

As you start to write more functions, you’ll eventually get to the point where you don’t remember exactly how your function works. At this point it’s easy to call your function with invalid inputs. To avoid this problem, it’s often useful to make constraints explicit. For example, imagine you’ve written some functions for computing weighted summary statistics:

wt_mean <- function(x, y) {
 sum(x * y) / sum(x)
}
wt_var <- function(x, y) {
 mu <- wt_mean(x, y)
 sum(y * (x - mu) ^ 2) / sum(y)
}
wt_sd <- function(x, y) {
 sqrt(wt_var(x, y))
}

Apa yang terjadi bila panjang x tidak sama dengan y ?

wt_mean(1:6, 1:3)
## [1] 2.190476

Padahal harusnya error kan. 1 dikali 1, 2 dikali 2, 3 dikali 3, 4 dikali???

Nah bagusnya sebelum perintah pada function, ditliskan terlebih dahulu suatu kondisi, dan apabila kondisi tidak dipenuhi di stop dan diberi peringatan.

seperti contoh dibawah:

wt_mean <- function(x, w) {
 if (length(x) != length(w)) {
 stop("`x` and `w` must be the same length", call. = FALSE)
 }
 sum(w * x) / sum(x)
}

Coba run lagi ini wt_mean(1:6, 1:3) pasti error.

Begini juga bisa, Kalau ada kondisi yang tak terpenuhi akan terjadi error dengan keterangan kondisi yang gagal “is not true”

wt_mean <- function(x, w, na.rm = FALSE) {
 stopifnot(is.logical(na.rm), length(na.rm) == 1)
 stopifnot(length(x) == length(w))
 if (na.rm) {
 miss <- is.na(x) | is.na(w)
 x <- x[!miss]
 w <- w[!miss]
 }
 sum(w * x) / sum(x)
}
wt_mean(1:6, 6:1, na.rm = T)
## [1] 2.666667

Dot-Dot-Dot(…)

Masih bingung juga sih, tapi ini contohnya

commas <- function(...) stringr::str_c(..., collapse = ", ")
commas(letters[1:10])
## [1] "a, b, c, d, e, f, g, h, i, j"
#> [1] "a, b, c, d, e, f, g, h, i, j"
rule <- function(..., pad = "-") {
 title <- paste0(...)
 width <- getOption("width") - nchar(title) - 5
 cat(title, " ", stringr::str_dup(pad, width), "\n", sep = "")
}
rule("Important output")
## Important output -----------------------------------------------------------

Return Values

There are two things you should consider when returning a value: 1. Does returning early make your function easier to read? 2. Can you make your function pipeable?

Explicit Return Statements

Pada function nilai atau value yang dikembalikan biasanya merupakan pernyataan atau perintah terakhir pada function, namun kita dapat memilih untuk mengembalikan nilai atau value lebih awal menggunakan return().

Berikut merupakan contoh penggunaan return().

complicated_function <- function(x, y, z) {
 if (length(x) == 0 || length(y) == 0) {
 return(0)
 }
 # Complicated code here
}

Atau bisa ditulis begini juga.

f <- function() {
 if (x) {
 # Do
 # something
 # that
 # takes
 # many
 # lines
 # to
 # express
 } else {
 # return something short
 }
}

Writing Pipeable Functions

show_missings1 <- function(df) {
  n <- sum(is.na(df))
  cat("Missing values: ", n, "\n", sep = "")

}
show_missings <- function(df) {
  n <- sum(is.na(df))
  cat("Missing values: ", n, "\n", sep = "")
  invisible(df)
}

show_missings(mtcars)
## Missing values: 0
x <- show_missings(mtcars)
## Missing values: 0
x1<-show_missings1(mtcars)
## Missing values: 0
class(x1)
## [1] "NULL"
class(x)
## [1] "data.frame"
dim(x1)
## NULL
dim(x)
## [1] 32 11

Jika kita akan menggunakan suatu functions pada pipe (%>%), gunakan invisible() pada function. perintah ini tidak akan menulis apapun namun inputnya tetap ada disitu.

mtcars %>%
 show_missings() %>%
 mutate(mpg = ifelse(mpg < 20, NA, mpg)) %>%
 show_missings()
## Missing values: 0
## Missing values: 18

Semoga Bermanfaat