Introduction to Data Science

TUGAS 3 : Classification and Predicting


Kontak : \(\downarrow\)
Email :
Instagram : https://www.instagram.com/ferd_nw/
RPubs : https://rpubs.com/ferdnw/

What is Classification ?

Dalam statistika, klasifikasi adalah engidentifikasi yang mana dari kumpulan kategori (sub-populasi) yang menjadi suatu bahan observasi baru, berdasarkan kumpulan data pelatihan yang berisi observasi (atau contoh) yang keanggotaan kategorinya diketahui. Contohnya adalah menetapkan email tertentu ke kelas “spam” atau “non-spam”. Bisa Juga dalam dunia medis untuk mengetahui kondisi atau gejala tertentu berdasarkan karakteristiknya.

Basically, Classification adalah mengelompokkan atau membagi-bagi data yang dimiliki dengan memperhatikan suatu faktor yang menjadi acuan kita dalam membedakan dan mengelompokkan data tersebut.

Kali ini, Clasification dilakukan dengan menggunakan KNN (K-Nearest Neighbour) adalah sebuah algoritma pelatihan untuk mengklasifikasikan suatu data baru berdasarkan kategori mayoritas banyaknya K data pelatihan yang terdekat dengannya (nearest neighbor). Tujuan utama dari algoritma ini adalah untuk mengklasifikasikan suatu obyek baru berdasarkan atribut dan sampel pelatihan.

KNN dapat membantu mengklasifikasikan data yang dimiliki sesuai dengan faktor yang kita tentukan, contohnya adalah permasalahan mengenai Karakteristik Kanker yang bisa diklasifikasikan Ganas atau Jinak di Dataset berikut

Exploring Data Set

Data yang di impor dan explore adalah dataset yang bernama wdbc.csv dimana isinya tentang data kanker payudara. Setiap barisnya memiliki sampel gambaran tumor pada kankernya, dan juga diagnosis nya (Jinak dan Ganas) serta pengukuran nya.

Jika ingin melakukan sebuah diagnosa terhadap suatu penyakit, dibutuhkan cara yang detail sehingga hasilnya bisa akurat. Seorang dokter Tidak bisa mendiagnosa sebuah penyakit hanya dengan feeling saja. Salah Diagnosa bisa mengakibatkan salah penanganan dan pengobatan yang malah bisa memperparah penyakit atau yang lebih fatal. Ada berbagai pertimbangan dan pengecekan yang dilakukan dalam mendiagnosa penyakit agar hasilnya lebih akurat juga klasifikasi penyakit tersebut bersifat jinak atau justru bersifat ganas.

Import Data

library(tidyverse)
cancer <- read.csv("https://raw.githubusercontent.com/UBC-DSCI/introduction-to-datascience/master/data/wdbc.csv")
cancer

Describing the Variables


Setelah kita mengimpor dataset yang kita inginkan, alangkah baiknya bila kita mengetahui data yang memiliki banyak kolom dengan posisi menyamping dibandingkan posisi menurun. Untuk melakukan hal ini, kita perlu menggunakan fungsi glimpse agar memudahkan kita dalam melihat dataset nya.

glimpse(cancer)
## Rows: 569
## Columns: 12
## $ ID                <int> 842302, 842517, 84300903, 84348301, 84358402, 843786~
## $ Class             <chr> "M", "M", "M", "M", "M", "M", "M", "M", "M", "M", "M~
## $ Radius            <dbl> 1.0960995, 1.8282120, 1.5784992, -0.7682333, 1.74875~
## $ Texture           <dbl> -2.0715123, -0.3533215, 0.4557859, 0.2535091, -1.150~
## $ Perimeter         <dbl> 1.26881726, 1.68447255, 1.56512598, -0.59216612, 1.7~
## $ Area              <dbl> 0.98350952, 1.90703027, 1.55751319, -0.76379174, 1.8~
## $ Smoothness        <dbl> 1.56708746, -0.82623545, 0.94138212, 3.28066684, 0.2~
## $ Compactness       <dbl> 3.28062806, -0.48664348, 1.05199990, 3.39991742, 0.5~
## $ Concavity         <dbl> 2.65054179, -0.02382489, 1.36227979, 1.91421287, 1.3~
## $ Concave_Points    <dbl> 2.53024886, 0.54766227, 2.03543978, 1.45043113, 1.42~
## $ Symmetry          <dbl> 2.215565542, 0.001391139, 0.938858720, 2.864862154, ~
## $ Fractal_Dimension <dbl> 2.25376381, -0.86788881, -0.39765801, 4.90660199, -0~

Note: Disini terdapat beberapa kolom yang ada di dalam data. Kolom-kolom itu adalah 1. ID: Nomor Identitas Pasien
2. Class: Diagnosa (Ganas dan Jinak)
3. Radius: rata-rata jarak dari pusat ke titik-titik pada keliling
4. Texture: standar deviasi nilai skala abu-abu
5. Perimeter: seberapa panjang kontur sekitarnya
6. Area: besarnya area di dalam kontur
7. Smoothness: variasi lokal dalam panjang radius
8. Compactness: rasio keliling kuadrat dan luas
9. Concavity: Kecekungannya
10. Concave_points: jumlah bagian cekung dari kontur
11. Symmetry: seberapa mirip inti ketika dicerminkan
12. Fractal_Dimension: menunjukkan seberapa kasar kelilingnya


Dari data di atas kita bisa melihat bahwa kolom Class adalah character. Karena dalam kondisi ini kolom Class merupakan kolom dari jenis yang akan kita analisis dari data, maka dari itu kita perlu mengubah menjadi factor dengan fungsi as_factor.

cancer <- cancer |>
  mutate(Class = as_factor(Class))
glimpse(cancer)
## Rows: 569
## Columns: 12
## $ ID                <int> 842302, 842517, 84300903, 84348301, 84358402, 843786~
## $ Class             <fct> M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M~
## $ Radius            <dbl> 1.0960995, 1.8282120, 1.5784992, -0.7682333, 1.74875~
## $ Texture           <dbl> -2.0715123, -0.3533215, 0.4557859, 0.2535091, -1.150~
## $ Perimeter         <dbl> 1.26881726, 1.68447255, 1.56512598, -0.59216612, 1.7~
## $ Area              <dbl> 0.98350952, 1.90703027, 1.55751319, -0.76379174, 1.8~
## $ Smoothness        <dbl> 1.56708746, -0.82623545, 0.94138212, 3.28066684, 0.2~
## $ Compactness       <dbl> 3.28062806, -0.48664348, 1.05199990, 3.39991742, 0.5~
## $ Concavity         <dbl> 2.65054179, -0.02382489, 1.36227979, 1.91421287, 1.3~
## $ Concave_Points    <dbl> 2.53024886, 0.54766227, 2.03543978, 1.45043113, 1.42~
## $ Symmetry          <dbl> 2.215565542, 0.001391139, 0.938858720, 2.864862154, ~
## $ Fractal_Dimension <dbl> 2.25376381, -0.86788881, -0.39765801, 4.90660199, -0~


Untuk melihat kategori yang ada di dalam kolom Class, kita perlu menggunakan fungsi pull untuk mengesktrak 1 kolom lalu fungsi levels untuk melihat kategori apa saja yang ada di dalam kolom itu. Dan seperti yang kita lihat, terdapat kategori M dan B dalam kolom Class.

cancer |>
  pull(Class) |>
  levels()
## [1] "M" "B"

Exploring the Dataset


Kita juga perlu untuk mengetahui frekuensi dan proporsi dari kolom Class agar kita mengetahui data menunjukkan berapa jumlah tumor ganas dan berapa jumlah tumor jinak nya. Dalam hal ini kita menggunakan fungsi group_by.Setelah itu, kita juga perlu mengetahui presentase setiap jenis tumor nya, disini kita bisa kalkulasikan dengan menggunakan count yang dimana kita akan membagi frekuensi jenis tumor dengan kolom yang dikali dengan 100.

num_obs <- nrow(cancer)
cancer |>
  group_by(Class) |>
  summarize(
    count = n(),
    percentage = n() / num_obs * 100
  )


Langkah selanjutnya, membuat grafik scatterplot nya.Dengan memplot variabel Perimeter dan Concavity.

perim_concav <- cancer |>
  ggplot(aes(x = Perimeter, y = Concavity, color = Class)) +
  geom_point(alpha = 0.6) +
  labs(x = "Perimeter (standardized)", 
       y = "Concavity (standardized)",
       color = "Diagnosis") +
  scale_color_manual(labels = c("Malignant", "Benign"), 
                     values = c("red", "cyan")) +
  theme(text = element_text(size = 12))
perim_concav

Seperti pada grafik di atas, sebaran jenis tumor jinak atau Benign yang memiliki nilai kecekungan dan keliling yang rendah, dilihat dari letak sebarannya di bawah kiri. Sedangkan pada sebaran kanan atas, menunjukkan tumor jenis ganas atau Malignant yang memiliki tingkatan yang tinggi.

Classification with K-Nearest Neighbour (KNN)

Untuk membuat prediksi yanglebih kuat dan akurat , kita perlu untuk melakukan langkah pengklasifikasian atau metode K-Nearest Neighbor. Step by step dalam KNN

Distance Between Points


Langkah selanjutnya, kita perlu mencoba membuat grafiknya agar kita mengetahui hubungan antara perimeter dengan concavity nya. Warna kuning berarti diagnosis Malignant dan warna hijau untuk diagnosis Benign. Disini kita menggunakan grafik scatterplot agar lebih mudah untuk melihat jenis diagnosis nya.

new_obs_Perimeter <- 0
new_obs_Concavity <- 3.5
cancer |>
  select(ID, Perimeter, Concavity, Class) |>
  mutate(dist_from_new = sqrt((Perimeter - new_obs_Perimeter)^2 + 
                              (Concavity - new_obs_Concavity)^2)) |>
  arrange(dist_from_new) |>
  slice(1:5) # take the first 5 rows

More than two explanatory variables

Pada langkah ini menggunakan rumus dengan lebih banyak dimensi. Seperti dalam peritungan kita ingin menghitung 3 variabel. yaitu perimeter, concavity, dan symmetry. Pada contoh sebelumnya, kita hanya menggunakan 2 variabel yang menjumlahkan selisih kuadrat antara kedua variabelnya, dan kemudian di akar kuadratkan.
Mari kita coba hitung untuk mencari K=5 neighbors ketika kita memiliki 3 prediktor dibawah ini.

new_obs_Perimeter <- 0
new_obs_Concavity <- 3.5
new_obs_Symmetry <- 1

cancer |>
  select(ID, Perimeter, Concavity, Symmetry, Class) |>
  mutate(dist_from_new = sqrt((Perimeter - new_obs_Perimeter)^2 + 
                              (Concavity - new_obs_Concavity)^2 +
                                (Symmetry - new_obs_Symmetry)^2)) |>
  arrange(dist_from_new) |>
  slice(1:5) # take the first 5 rows

Summarize


Berdasarkan K=5 dgn 3 prediktor yang kita pakai di atas, pengamatan baru menghasilkan pengklasifikasian yan menunjukkan kelas malignant.

KNN with tidymodels


Langkah berikut ini kita ingin menampilkan data yang berisi kolom Class, Perimeter, Concavity saja untuk memfokuskan pengklasifikasian pada K-Nearest Neighbors. Kita menggunakan fungsi select untuk menampilkan kolom yang ingin kita tampilkan.

library(tidymodels)
cancer_train <- cancer |>
  select(Class, Perimeter, Concavity)
cancer_train


Pada step ini , mulailah untuk membuat model klasifikasi KNN nya dengan menggunakan fungsi nearest_neighbor. dimana K nya adalah 5. Kita juga memakai jarak garis lurus berupa rectangular. Lalu Set_engine digunakan untuk menentukan model apa yang akan dipakai dalam mengklasifikasi. dan set_mode dengan classification untuk melakukan klasifikasi.

knn_spec <- nearest_neighbor(weight_func = "rectangular", neighbors = 5) |>
  set_engine("kknn") |>
  set_mode("classification")
knn_spec
## K-Nearest Neighbor Model Specification (classification)
## 
## Main Arguments:
##   neighbors = 5
##   weight_func = rectangular
## 
## Computational engine: kknn


Disini mengguakan library kknn untuk melakukan klasifikasi KNN. Langkah pertama adalah menyesuaikan pemodelan dengan dataset nya menggunakan fungsi fit. Di langkah ini kita juga harus menentukan variabel apa yang diinginkan untuk dijadikan target dan prediktor. Pada kali ini, kita akan menjadikan variabel Class sebagai target serta perimeter dan concavity menjadi predictor.

library(kknn)
## Warning: package 'kknn' was built under R version 4.1.2
knn_fit <- knn_spec |>
  fit(Class ~ Perimeter + Concavity, data = cancer_train)


Kita juga dapat menggunakan sintaks singkatan menggunakan titik, Class ~ ., untuk menunjukkan bahwa kita ingin menggunakan setiap variabel kecuali Class sebagai prediktor dalam model.

knn_fit <- knn_spec |>
  fit(Class ~ ., data = cancer_train)
knn_fit
## parsnip model object
## 
## Fit time:  31ms 
## 
## Call:
## kknn::train.kknn(formula = Class ~ ., data = data, ks = min_rows(5,     data, 5), kernel = ~"rectangular")
## 
## Type of response variable: nominal
## Minimal misclassification: 0.07557118
## Best kernel: rectangular
## Best k: 5


Di sini Anda dapat melihat ringkasan model terlatih terakhir. Ini menegaskan bahwa mesin komputasi yang digunakan untuk melatih model adalah kknn::train.kknn.


Pada langkah dibawah ini, kita akan memprediksi datanya. Kira menggunakan fungsi predict() untuk memprediksi data yang ingin kita prediksi. Dan ketika kita run, kita bisa melihat hasilnya. Hasilnya menunjukkan hasil klasifikasi yang memprediksi adanya pengamatan baru sebagai M atau tipe ganas.

new_obs <- tibble(Perimeter = 0, Concavity = 3.5)
predict(knn_fit, new_obs)


Akan tetapi, sebuah prediksi tidaklah 100% benar, selalu ada ruang untuk kesalahan. Prediksi pengklasifikasi mungkin besar kemungkinannya untuk benar, maka dari itu tidak ada salahnya akan membuat prediksi kita lebih akurat untuk memperbesar peluang benar.

Data preprocessing with tidymodels

Centering and scaling


Untuk melakukan scaling dan pemusatan data, kita perlu mean dari variabel kita (rata-rata, yang mengkuantifikasi nilai “pusat” dari serangkaian angka) dan standar deviasi (angka yang mengukur seberapa tersebar nilai). Untuk setiap nilai variabel yang diamati, kita kurangi mean (yaitu, pusatkan variabel) dan bagi dengan standar deviasi (yaitu, skala variabel). Ketika kita melakukan ini, data dikatakan terstandarisasi, dan semua variabel dalam kumpulan data akan memiliki rata-rata 0 dan simpangan baku 1. Metode ini mirip dengan menghitung Zscores, atau jarak penyebaran data tersebut dari pusatnya.

Kita akan menggunakan variabel Area, Smoothness, dan Class dalam dataset unscaled_wdbc.csv.

Seperti tadi, kita menjadikan variabel Class menjadi factor

data.unscaled <- read.csv("https://raw.githubusercontent.com/UBC-DSCI/introduction-to-datascience/master/data/unscaled_wdbc.csv") |>
  mutate(Class = as_factor(Class)) |>
  select(Class, Area, Smoothness)
data.unscaled


Melihat data yang unscaled dan uncentered ,TErlihat bahwa perbedaan antara nilai untuk pengukuran area jauh lebih besar daripada untuk kehalusan. Apakah ini akan memengaruhi prediksi? Untuk mengetahuinya, scatter dari dua prediktor ini (diwarnai oleh diagnosis) untuk data tidak standar yang baru saja kami muat, dan versi standar dari data yang sama. Tapi pertama-tama, kita perlu membakukan kumpulan data unscaled_cancer dengan model rapi.

uc_recipe <- recipe(Class ~ ., data = data.unscaled)
print(uc_recipe)
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor          2


lakukan penskalaan (step_scale) dan pemusatan (step_center) untuk semua prediktor sehingga masing-masing memiliki rata-rata 0 dan standar deviasi 1. Perhatikan bahwa cleanverse sebenarnya menyediakan step_normalize, yang melakukan pemusatan dan penskalaan dalam satu langkah resep ; dalam buku ini kita akan memisahkan step_scale dan step_center untuk menekankan secara konseptual bahwa ada dua langkah yang terjadi. Fungsi persiapan menyelesaikan resep dengan menggunakan data (di sini, unscaled_cancer) untuk menghitung apa pun yang diperlukan untuk menjalankan resep (dalam hal ini, rata-rata kolom dan simpangan baku)-

uc_recipe <- uc_recipe |>
  step_scale(all_predictors()) |>
  step_center(all_predictors()) |>
  prep()
uc_recipe
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor          2
## 
## Training data contained 569 data points and no missing data.
## 
## Operations:
## 
## Scaling for Area, Smoothness [trained]
## Centering for Area, Smoothness [trained]

Ketika kita ingin menambahkan langkah menuju ke recipe. Menggunakan fungsi all_predictors() untuk menentukan bahwa setiap langkah harus diterapkan ke semua variabel prediktor. Namun, ada juga argumen serta sintaks lain yang dapat digunakan dan memiliki fungsi sebagai berikut: 1. all_nominal() dan all_numeric(): tentukan semua variabel kategori atau semua numerik
2. all_predictors() dan all_outcomes(): tentukan semua prediktor atau semua variabel target
3. Area, Smoothness: tentukan variabel Area dan Smoothness
4. Class: tentukan semuanya kecuali variabel Class
Anda dapat menemukan set lengkap semua langkah dan fungsi pemilihan variabel di halaman referensi resep.

Pada langkah sebelumnya, kita telah menghitung statistik data dengan input data ke recipe. Tetapi datanya nya belum terskala dan terpusat. Untuk benar-benar menskalakan dan memusatkan datanya, kita gunakan fungsi bake()

scaled_cancer <- bake(uc_recipe, data.unscaled)
scaled_cancer

Balancing


Masalah potensial lain dalam kumpulan data untuk pengklasifikasi adalah ketidakseimbangan kelas, yaitu ketika satu label jauh lebih umum daripada yang lain (outliers). Karena saat melakukan KNN menggunakan label titik terdekat untuk memprediksi label titik baru, jika ada lebih banyak titik data dengan satu label secara keseluruhan, algoritme lebih cenderung memilih label itu secara umum (bahkan jika “pola” dari data menunjukkan sebaliknya), Merusak pola predictionnya.

rare_cancer <- bind_rows(
      filter(cancer, Class == "B"),
      cancer |> filter(Class == "M") |> slice_head(n = 3)
    ) |>
    select(Class, Perimeter, Concavity)

rare_plot <- rare_cancer |>
  ggplot(aes(x = Perimeter, y = Concavity, color = Class)) +
  geom_point(alpha = 0.5) +
  labs(x = "Perimeter (standardized)", 
       y = "Concavity (standardized)",
       color = "Diagnosis") +
  scale_color_manual(labels = c("Malignant", "Benign"), 
                     values = c("darkgoldenrod2", "lightgreen")) +
  theme(text = element_text(size = 12))

rare_plot


Dalam kode berikut ini, tujuannya untuk balancing data. Tapi dengan melakukan oversampling Class nya. Disini kita akan menggunakan library themis dalam menggunakan fungsi step_upsample.

library(themis)
## Warning: package 'themis' was built under R version 4.1.2
## Registered S3 methods overwritten by 'themis':
##   method                  from   
##   bake.step_downsample    recipes
##   bake.step_upsample      recipes
##   prep.step_downsample    recipes
##   prep.step_upsample      recipes
##   tidy.step_downsample    recipes
##   tidy.step_upsample      recipes
##   tunable.step_downsample recipes
##   tunable.step_upsample   recipes
## 
## Attaching package: 'themis'
## The following objects are masked from 'package:recipes':
## 
##     step_downsample, step_upsample
ups_recipe <- recipe(Class ~ ., data = rare_cancer) |>
  step_upsample(Class, over_ratio = 1, skip = FALSE) |>
  prep()

ups_recipe
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor          2
## 
## Training data contained 360 data points and no missing data.
## 
## Operations:
## 
## Up-sampling based on Class [trained]
upsampled_cancer <- bake(ups_recipe, rare_cancer)

upsampled_cancer |>
  group_by(Class) |>
  summarize(n = n())


Dan seperti yang bisa kita lihat, Class dari M dan B telah seimbang.

Putting it together in a workflow

Library tidymodels juga menyediakan workflow. Kali ini kita akan melakukan analisis dengan langkah yang tidak perlu kode yang banyak. Disini kita akan menggunakan dataset unscaled_wdbc.csv. Pertama kita akan memuat data, membuat model, dan menentukan resep bagaimana data harus diproses sebelumnya.

# load the unscaled cancer data 
# and make sure the target Class variable is a factor
unscaled_cancer <- read_csv("https://raw.githubusercontent.com/UBC-DSCI/introduction-to-datascience/master/data/unscaled_wdbc.csv") |>
  mutate(Class = as_factor(Class))
## Rows: 569 Columns: 12
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr  (1): Class
## dbl (11): ID, Radius, Texture, Perimeter, Area, Smoothness, Compactness, Con...
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
# create the KNN model
knn_spec <- nearest_neighbor(weight_func = "rectangular", neighbors = 7) |>
  set_engine("kknn") |>
  set_mode("classification")

# create the centering / scaling recipe
uc_recipe <- recipe(Class ~ Area + Smoothness, data = unscaled_cancer) |>
  step_scale(all_predictors()) |>
  step_center(all_predictors())


Sekarang, menempatkan langkah-langkah ini dalam alur kerja menggunakan fungsi add_recipe dan add_model, dan terakhir menggunakan fungsi fit untuk menjalankan seluruh alur kerja pada data unscaled_cancer. Perhatikan perbedaan lain dari sebelumnya di sini:

knn_fit <- workflow() |>
  add_recipe(uc_recipe) |>
  add_model(knn_spec) |>
  fit(data = unscaled_cancer)

knn_fit
## == Workflow [trained] ==========================================================
## Preprocessor: Recipe
## Model: nearest_neighbor()
## 
## -- Preprocessor ----------------------------------------------------------------
## 2 Recipe Steps
## 
## * step_scale()
## * step_center()
## 
## -- Model -----------------------------------------------------------------------
## 
## Call:
## kknn::train.kknn(formula = ..y ~ ., data = data, ks = min_rows(7,     data, 5), kernel = ~"rectangular")
## 
## Type of response variable: nominal
## Minimal misclassification: 0.112478
## Best kernel: rectangular
## Best k: 7
new_observation <- tibble(Area = c(500, 1500), Smoothness = c(0.075, 0.1))
prediction <- predict(knn_fit, new_observation)

prediction

Library tidymodels juga menyediakan workflow. Kali ini kita akan melakukan analisis dengan langkah yang tidak perlu kode yang banyak. Disini kita akan menggunakan

# create the grid of area/smoothness vals, and arrange in a data frame
are_grid <- seq(min(unscaled_cancer$Area), 
                max(unscaled_cancer$Area), 
                length.out = 100)
smo_grid <- seq(min(unscaled_cancer$Smoothness), 
                max(unscaled_cancer$Smoothness), 
                length.out = 100)
asgrid <- as_tibble(expand.grid(Area = are_grid, 
                                Smoothness = smo_grid))

# use the fit workflow to make predictions at the grid points
knnPredGrid <- predict(knn_fit, asgrid)

# bind the predictions as a new column with the grid points
prediction_table <- bind_cols(knnPredGrid, asgrid) |> 
  rename(Class = .pred_class)
# plot:
# 1. the colored scatter of the original data
# 2. the faded colored scatter for the grid points
wkflw_plot <-
  ggplot() +
  geom_point(data = unscaled_cancer, 
             mapping = aes(x = Area, 
                           y = Smoothness, 
                           color = Class), 
             alpha = 0.75) +
  geom_point(data = prediction_table, 
             mapping = aes(x = Area, 
                           y = Smoothness, 
                           color = Class), 
             alpha = 0.02, 
             size = 5) +
  labs(color = "Diagnosis", 
       x = "Area (standardized)", 
       y = "Smoothness (standardized)") +
  scale_color_manual(labels = c("Malignant", "Benign"), 
                     values = c("darkgoldenrod2", "lightgreen")) +
  theme(text = element_text(size = 12))

wkflw_plot