Bab 5 Eksplorasi dan Visualisasi Data

//TODO: Membahas cara eksplorasi data secara numerik maupun visual menggunakan grafik dasar R dan package {ggplot2}.

Seperti yang sudah disinggung di awal bahwa R adalah sebuah bahasa pemrograman yang penggunaan utamanya untuk kebutuhan pengolahan data dan analisis statistik. Saat ini lebih terkenal dengan istilah analisis data dan data science. Kegiatan analisis data ini tentunya tidak akan terlepas dari eksplorasi data. Terutama ketika baru pertama kali mendapatkan data tersebut. Kegiatan eksplorasi ini bertujuan untuk mengenal lebih dalam data yang kita gunakan. Metode yang umumnya digunakan ketika melakukan eksplorasi data adalah secara numerik/tabulasi atau secara visual dengan memanfaatkan grafik. Kegiatan eksplorasi dan menyiapkan data untuk kebutuhan analisis data umumnya memerlukan waktu 60%-80% dari seluruh waktu yang dibutuhkan untuk analisis data.

Kita akan bekerja dari sebuah dataframe karena umumnya kegiatan analisis data bersumber dari data yang berbentuk tabular (baris sebagai observasi dan kolom sebagai variabel). Data iris sudah tersedia di R, namun kita akan mulai dari import data karena hampir semua data akan diimport terlebih dahulu, baik itu dari file data atau database. Data iris mempunyai dua tipe data, yaitu numeric dan character.

5.1 Eksplorasi Tabulasi

Kita import terlebih dahulu data yang akan digunakan dari file iris.csv di dalam folder data. Kita gunakan fungsi read_csv() dari package {readr} sebagai latihan menggunakan package.

library(readr)
iris_csv <- read_csv("data/iris.csv")
## 
## -- Column specification --------------------------------------------------------
## cols(
##   Sepal.Length = col_double(),
##   Sepal.Width = col_double(),
##   Petal.Length = col_double(),
##   Petal.Width = col_double(),
##   Species = col_character()
## )

Untuk mengetahui banyaknya baris data pada iris_csv kita dapat gunakan fungsi nrow(), sedangkan untuk mengetahui banyaknya kolom kita gunakan fungsi ncol().

nrow(iris_csv)
## [1] 150
ncol(iris_csv)
## [1] 5

Jadi ada 150 observasi (baris) dan 5 variabel (kolom) dari data yang sudah diimport. Hal ini perlu kita sesuaikan dengan banyaknya baris dan kolom dari data asli yang ada di file data tersebut. Jika sudah sama maka kita dapat lanjut ke tahapan lain. Jika masih ada yang tidak sesuai maka perlu diketahui dahulu letak kesalahannya dan perbaiki sehingga semuanya sudah sesuai. Setelah itu kita dapat megetahui nama-nama variabel yang ada pada dataframe tersebut dengan menggunakan fungsi colnames() atau names().

colnames(iris_csv)
## [1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"
names(iris_csv)
## [1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"

Pada kasus ini kedua fungsi tersebut menghasilkan output yang sama, yaitu nama kolom dari dataframe. Atau kita juga dapat menggunakan fungsi str() untuk melihat informasi itu semua ditambah dengan preview beberapa baris pertama dataframe tersebut.

str(iris_csv)
## spec_tbl_df [150 x 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Sepal.Length: num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num [1:150] 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num [1:150] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num [1:150] 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : chr [1:150] "setosa" "setosa" "setosa" "setosa" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Sepal.Length = col_double(),
##   ..   Sepal.Width = col_double(),
##   ..   Petal.Length = col_double(),
##   ..   Petal.Width = col_double(),
##   ..   Species = col_character()
##   .. )

Sekarang mari kita eksplorasi masing-masing varabel.

Kita mulai dengan variabel pertama, yaitu Sepal.Length. Seperti yang sudah dibahas pada bagian 2.9 bahwa ada beberapa cara agar kita dapat mengakses nilai dari suatu variabel di dataframe. Salah satunya adalah dengan menggunakan tanda dollar $. Jika Anda menggunakan RStudio versi terbaru, Anda dapat mengetik iris_csv kemudian diikuti tanda $ maka akan muncul nama-nama variabel dar dataframe tersebut.

iris_csv$Sepal.Length
##   [1] 5.1 4.9 4.7 4.6 5.0 5.4 4.6 5.0 4.4 4.9 5.4 4.8 4.8 4.3 5.8 5.7 5.4 5.1
##  [19] 5.7 5.1 5.4 5.1 4.6 5.1 4.8 5.0 5.0 5.2 5.2 4.7 4.8 5.4 5.2 5.5 4.9 5.0
##  [37] 5.5 4.9 4.4 5.1 5.0 4.5 4.4 5.0 5.1 4.8 5.1 4.6 5.3 5.0 7.0 6.4 6.9 5.5
##  [55] 6.5 5.7 6.3 4.9 6.6 5.2 5.0 5.9 6.0 6.1 5.6 6.7 5.6 5.8 6.2 5.6 5.9 6.1
##  [73] 6.3 6.1 6.4 6.6 6.8 6.7 6.0 5.7 5.5 5.5 5.8 6.0 5.4 6.0 6.7 6.3 5.6 5.5
##  [91] 5.5 6.1 5.8 5.0 5.6 5.7 5.7 6.2 5.1 5.7 6.3 5.8 7.1 6.3 6.5 7.6 4.9 7.3
## [109] 6.7 7.2 6.5 6.4 6.8 5.7 5.8 6.4 6.5 7.7 7.7 6.0 6.9 5.6 7.7 6.3 6.7 7.2
## [127] 6.2 6.1 6.4 7.2 7.4 7.9 6.4 6.3 6.1 7.7 6.3 6.4 6.0 6.9 6.7 6.9 5.8 6.8
## [145] 6.7 6.7 6.3 6.5 6.2 5.9
typeof(iris_csv$Sepal.Length)
## [1] "double"

Seperti yang kita lihat, variabel Sepal.Length bertipe numeric atau double. Pertama, kita akan lakukan eksplorasi untuk mengetahui nilai rata-rata dari variabel ini. Kita dapat gunakan fungsi mean() seperti berikut.

mean(iris_csv$Sepal.Length)
## [1] 5.843333

Kita dapatkan nilai rata-rata dari variabel Sepal.Length adalah sekitar 5.843333. Kenapa “sekitar?” Karena mungkin saja nilai yang ditampilkan hanyalah pembulatan dengan 6 (enam) digit setelah tanda desimal. Selanjutnya kita masukkan nilai-nilai dari variabel Sepal.Length ke dalam object bernama sepal_length yang berupa vector.

sepal_length <- iris_csv$Sepal.Length

Kita juga dapat menghitung nilai tengah atau median (Q2) dengan fungsi median(). Untuk mendapatkan nilai ragam (variance) dan simpangan baku (standard deviation)? Kita gunakan fungsi var() dan sd().

median(sepal_length)
## [1] 5.8
var(sepal_length)
## [1] 0.6856935
sd(sepal_length)
## [1] 0.8280661

Untuk mendapatkan nilai minimum dan maksimum kita gunakan fungsi min() dan max(). Utuk mendapatkan keduanya sekaligus kita gunakan fungsi range().

min(sepal_length)
## [1] 4.3
max(sepal_length)
## [1] 7.9
range(sepal_length)
## [1] 4.3 7.9

Kita juga dapat gunakan fungsi quantile() untuk mendapatkan nilai statistik Q1 (data pada urutan ke 25%) dan Q3 (data pada urutan ke 75%).

quantile(sepal_length, probs = c(0.25, 0.75))
## 25% 75% 
## 5.1 6.4

Untuk menampilkan statistik lima serangkai (Min, Q1, Q2, Q3, dan max) bersamaan kita dapat gunakan fungsi summary().

summary(sepal_length)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   4.300   5.100   5.800   5.843   6.400   7.900

Selain dapat digunakan untuk sebuah vector numeric, fungsi summary() juga dapat kita gunakan untuk beberapa variabel numeric sekaligus pada sebuah dataframe.

summary(iris_csv)
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##    Species         
##  Length:150        
##  Class :character  
##  Mode  :character  
##                    
##                    
## 

Seperti kita lihat, nilai yang dihasilkan oleh fungsi summary() untuk variabel Species sangat berbeda dibandingkan variabel yang lain. Hal ini karena Species adalah varabel character, sehingga fungsi summary() hanya menampilkan banyaknya observasi (Length), Class dan Mode.

typeof(iris_csv$Species)
## [1] "character"

Untuk variabel tipe character umumnya eksplorasi yang dilakukan adalah dengan membuat tabulasi atau tabel frekuensi. kita dapat gunakan fungsi table() untuk menghitung banyaknya observasi masing-masing kategori pada variable tersebut.

table(iris_csv$Species)
## 
##     setosa versicolor  virginica 
##         50         50         50

Kita bisa lihat bahwa masing-masing kategori pada variabel Species mempunyai jumlah yang sama. Tentu saja ketika kita menghitung proporsi atau persentase jumlah masing-masing kategori terhadap banyaknya observasi seluruh data (baris) maka nilainya akan sama untuk semua kategori. Untuk menghitung nilai proporsi kita gunakan fungsi prop.table() dengan inputnya adalah tabulasi hasil dari fugsi table().

frekuensi <- table(iris_csv$Species)
prop.table(frekuensi)
## 
##     setosa versicolor  virginica 
##  0.3333333  0.3333333  0.3333333

Selajutnya kita juga dapat menghitung nilai statistik dari suatu variabel numerik berdasarkan kategori suatu variabel bertipe character. Misalnya kita hitung nilai rata-rata dari variabel Sepal.Length berdasarkan kategori dari Species. Kita gunakan fungsi aggregate()

aggregate(Sepal.Length ~ Species, data = iris_csv, FUN = mean)
##      Species Sepal.Length
## 1     setosa        5.006
## 2 versicolor        5.936
## 3  virginica        6.588

Selanjutnya Anda dapat mencoba eksplorasi seperti di atas dengan menggunakan variabel lain.

5.2 Grafik Dasar di R

5.2.1 Barplot

frekuensi <- table(iris_csv$Species)
barplot(frekuensi)

rataan <- aggregate(Sepal.Length ~ Species, data = iris_csv, FUN = mean)
barplot(Sepal.Length ~ Species, data = rataan)

barplot(Sepal.Length ~ Species, data = rataan, col = "skyblue")

5.2.2 Histogram & Density

Histogram adalah sebuah visualisasi terkait sebaran data numerik. Histogram juga dapat digunakan untuk melihat pola kisaran nilai yang banyak muncul dari data numerik tersebut. Untuk membuat histogram menggunakan grafik dasar di R kta dapat gunakan fungsi hist() dengan argumen datanya berupa vector numeric.

hist(sepal_length)

hist(sepal_length, breaks = 15, 
     main = "Distribution of Sepal Length", 
     xlab = "Sepal Length", col = "skyblue")

dens <- density(sepal_length)
dens
## 
## Call:
##  density.default(x = sepal_length)
## 
## Data: sepal_length (150 obs.);   Bandwidth 'bw' = 0.2736
## 
##        x               y            
##  Min.   :3.479   Min.   :0.0001495  
##  1st Qu.:4.790   1st Qu.:0.0341599  
##  Median :6.100   Median :0.1534105  
##  Mean   :6.100   Mean   :0.1905934  
##  3rd Qu.:7.410   3rd Qu.:0.3792237  
##  Max.   :8.721   Max.   :0.3968365
plot(dens)

plot(dens, main = "Density of Sepal Length")
polygon(dens, col = "skyblue")

5.2.3 Boxplot

boxplot(sepal_length)

boxplot(Sepal.Length ~ Species, data = iris_csv)

boxplot(Sepal.Length ~ Species, data = iris_csv, horizontal = TRUE)

5.2.4 Scatter plot

x <- iris_csv$Sepal.Length
y <- iris_csv$Sepal.Width
plot(x, y)

plot(x, y, pch = 19)

plot(x, y, pch = 19, col = "red")

5.2.5 Line chart

set.seed(1001)
x <- 1:100
y <- rnorm(100, mean = 100, 12)
plot(x, y, type = "l")

5.3 Visualisasi Package {ggplot2}

install.packages("ggplot2")
library(ggplot2)

5.3.1 Dasar-dasar {ggplot2}

Package {ggplot2} adalah salah satu package yang sangat terkenal dan sering digunakan oleh pengguna R saat ini. Package ini menghasilkan visualisasi berupa grafik yang sangat bagus dengan cara yang cukup mudah. Dasar dari {ggplot2} adalah grammar of graphics yang juga menjadi singkatan untuk dua huruf g di nama package ini. Sumber bacaan utama dari bagian ini adalah https://ggplot2-book.org/index.html. Anda dapat mempelajari lebih detail tentang Grammar of Graphics di buku tersebut dan referensi yang digunakan.

Pembuatan grafik menggunakan {ggplot2} terdiri dari beberapa bagian. Bagian pertama selalu diawali dengan fungsi ggplot(). Fungsi ini akan menyiapkan layer dasar untuk grafik yang akan dibuat. Ketika Anda ketik ggplot() dan jalankan di console, maka di bagian tab Plots di RStudio akan muncul kanvas berwarna abu-abu. Pada fungsi ini Anda dapat menggunakan data yang Anda miliki dengan menyebutkannya pada argumen data =. Data yang digunakan umumnya berupa dataframe atau yang semacamnya (tibble, data.table, dst). Argumen mapping = aes()untuk memberitahukan kepada {ggplot2} variabel apa saja yang akan menjadi aesthetic dengan menyebutkan masing-masing dalam fungsi aes() tersebut.

Setelah fungsi ggplot() pasti akan diikuti oleh tanda + sebagai tanda untuk menambahkan layer berikutnya dan minimal satu geometrik atau geom. Misalnya geom_point() untuk membuat scatterplot, geom_histogram() untuk membuat histogram, geom_density() untuk membuat plot kepekatan peluang, geom_bar() atau geom_col() untuk membuat barchart, dan lain-lain. Jika Anda menggunakan RStudio, Anda dapat ketik geom_ kemudian akan muncul pilihan geometrik-geometrik yang sudah disiapkan oleh {ggplot2}. Jika tidak muncul pilihannya, Anda dapat tekan tombol Tab di keyboard Anda.

Bentuk minimal untuk membuat grafik menggunakan {ggplot2} adalah sebagai berikut.

ggplot(data = <dataframe>, mapping = aes(x = <var-x>, <y = <var-y>>) + 
  geom_<name>()

Ada beberapa cara yang umum digunakan ketika menggunakan {ggplot2}. Data yang digunakan hanya satu dan semua variabel yang dibutuhkan untuk aesthetic pada bagian geom sama.

Package {ggplot2} juga menyediakan beberapa dataframe yang dapat digunakan untuk latihan ataupun kebutuhan mengajarkan {ggplot2}. Tentu saja Anda juga dapat menggunakan dataframe Anda sendiri jika diinginkan. Salah satu dataframe yang disediakan adalah diamonds, yaitu data tentang harga dan karakter dari 53.940 berlian. Anda dapat membaca keterangan tentang data ini lebih lanjut dengan mengetik ?diamonds dan akan muncul halaman help dari data tersebut.

diamonds
## # A tibble: 53,940 x 10
##    carat cut       color clarity depth table price     x     y     z
##    <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
##  1  0.23 Ideal     E     SI2      61.5    55   326  3.95  3.98  2.43
##  2  0.21 Premium   E     SI1      59.8    61   326  3.89  3.84  2.31
##  3  0.23 Good      E     VS1      56.9    65   327  4.05  4.07  2.31
##  4  0.29 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
##  5  0.31 Good      J     SI2      63.3    58   335  4.34  4.35  2.75
##  6  0.24 Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
##  7  0.24 Very Good I     VVS1     62.3    57   336  3.95  3.98  2.47
##  8  0.26 Very Good H     SI1      61.9    55   337  4.07  4.11  2.53
##  9  0.22 Fair      E     VS2      65.1    61   337  3.87  3.78  2.49
## 10  0.23 Very Good H     VS1      59.4    61   338  4     4.05  2.39
## # ... with 53,930 more rows

5.3.2 Barplot

ggplot(data = diamonds, mapping = aes(cut)) + 
  geom_bar()

freqtab <- as.data.frame(table(diamonds$cut))
freqtab
##        Var1  Freq
## 1      Fair  1610
## 2      Good  4906
## 3 Very Good 12082
## 4   Premium 13791
## 5     Ideal 21551
ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_bar(stat = "identity")

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col()

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(fill = "coral")

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(fill = "coral", alpha = 0.7)

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col() + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi")

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col() + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi") + 
  geom_text(aes(label = Freq), vjust = -0.25)

freqtab$persen <- freqtab$Freq/sum(freqtab$Freq)

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col() + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi") + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25)

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(fill = "coral", alpha = 0.7) + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25) + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi")

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(aes(fill = Var1), alpha = 0.7) + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25) + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi")

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(aes(fill = Var1), alpha = 0.7) + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25) + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi") + 
  theme(legend.position = "none")

library(scales)
## 
## Attaching package: 'scales'
## The following object is masked from 'package:readr':
## 
##     col_factor
ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(aes(fill = Var1), alpha = 0.7) + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25) + 
  scale_y_continuous(labels = comma) + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi") + 
  theme(legend.position = "none")

5.3.3 Histogram & Density

ggplot(data = diamonds, mapping = aes(x = price)) + 
  geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

ggplot(data = diamonds, mapping = aes(x = price)) + 
  geom_histogram(bins = 30)

ggplot(data = diamonds, mapping = aes(x = price)) + 
  geom_histogram(bins = 30, color = "white")

ggplot(data = diamonds, mapping = aes(x = price)) + 
  geom_histogram(bins = 30, color = "white", fill = "coral")

ggplot(data = diamonds, mapping = aes(x = price)) + 
  geom_histogram(bins = 30, color = "white", fill = "coral") + 
  scale_x_continuous(labels = comma) + 
  scale_y_continuous(labels = comma) + 
  labs(x = "Price", 
       y = "Frekuensi")

ggplot(data = diamonds, mapping = aes(x = price)) + 
  geom_density()

ggplot(data = diamonds, mapping = aes(x = price)) + 
  geom_density(fill = "coral", alpha = 0.7)

5.3.4 Boxplot

ggplot(data = diamonds, mapping = aes(x = carat)) + 
  geom_boxplot()

ggplot(data = diamonds, mapping = aes(x = carat, y = cut)) + 
  geom_boxplot()

ggplot(data = diamonds, mapping = aes(x = carat, y = cut, fill = cut)) + 
  geom_boxplot() + 
  theme(legend.position = "none")

5.3.5 Scatter plot

ggplot(data = diamonds, mapping = aes(x = carat, y = price)) + 
  geom_point()

ggplot(data = diamonds, mapping = aes(x = carat, y = price, color = cut)) + 
  geom_point()

5.3.6 Line chart

set.seed(1001)
linedata <- data.frame(x = 1:100,
                       y = rnorm(100, mean = 100, 12))
head(linedata)
##   x         y
## 1 1 126.26378
## 2 2  97.86943
## 3 3  97.77670
## 4 4  69.92157
## 5 5  93.31226
## 6 6  98.27729
ggplot(data = linedata, mapping = aes(x = x, y = y)) + 
  geom_line()

ggplot(data = linedata, mapping = aes(x = x, y = y)) + 
  geom_line() + 
  geom_smooth()
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

5.3.7 Faceting

ggplot(diamonds, aes(x = clarity)) + 
  geom_bar() + 
  facet_wrap(~cut)

ggplot(diamonds, aes(x = price)) + 
  geom_histogram() + 
  facet_wrap(~cut)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

ggplot(data = diamonds, mapping = aes(x = carat, y = price, color = cut)) + 
  geom_point() + 
  facet_wrap(~cut) + 
  theme(legend.position = "none")

5.3.8 Annotation

5.3.9 Theme

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(aes(fill = Var1), alpha = 0.7) + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25) + 
  scale_y_continuous(labels = comma) + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi") + 
  theme_grey() + 
  theme(legend.position = "none")

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(aes(fill = Var1), alpha = 0.7) + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25) + 
  scale_y_continuous(labels = comma) + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi") + 
  theme_classic() + 
  theme(legend.position = "none")

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(aes(fill = Var1), alpha = 0.7) + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25) + 
  scale_y_continuous(labels = comma) + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi") + 
  theme_dark() + 
  theme(legend.position = "none")

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(aes(fill = Var1), alpha = 0.7) + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25) + 
  scale_y_continuous(labels = comma) + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi") + 
  theme_minimal() + 
  theme(legend.position = "none")

ggplot(data = freqtab, mapping = aes(x = Var1, y = Freq)) + 
  geom_col(aes(fill = Var1), alpha = 0.7) + 
  geom_text(aes(label = paste0(round(persen*100, 2), "%")), vjust = -0.25) + 
  scale_y_continuous(labels = comma) + 
  labs(title = "Frekuensi berdasarkan 'cut'", 
       x = "Cut Level", 
       y = "Frekuensi") + 
  ggthemes::theme_stata() + 
  theme(legend.position = "none")