Pada bagian ini saya belajar tentang functions.
library(tidyverse)
Keuntungan peggunaan function adalah sebagai berikut:
“Codes” menjadi lebih mudah untuk dimengerti.
Jika ingin mengganti sesuatu dalam “codes” kita hany mengedit “codes” tersebut dalam satu tempat.
Meminimalisir kesalahan ketika kita menggunakan “copy paste”, seperti typo atau mngganti variabl pada “codes” tersebut.
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
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.
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).
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 {}
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:
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 -----------------------------------------------------------
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