# for datetime-related data processing
library(lubridate)
# for data wrangling
library(tidyr)
# visualization
library(ggplot2)Rotten Tomatoes Data Visualization
1 Introduction
Rotten Tomatoes merupakan situs web ulasan film dan acara TV. Nama “Rotten Tomatoes” ini berhubungan dengan istilah yang digunakan untuk menilai suatu tayangan. Ketika suatu film dinilai bagus, maka film tersebut dikatakan fresh atau segar, sementara jika dinilai jelek, film tersebut dikatakan rotten atau busuk.
Tidak hanya menampung ulasan dari penonton umum, Rotten Tomatoes juga mengumpulkan ulasan dari para kritikus terkemuka. Bahkan penilaian dari para kritikus ini memiliki penamaan sistem tersendiri, yaitu Tomatometer®. Hal ini menyebabkan Rotten Tomatoes menjadi salah satu situs ulasan industri hiburan paling terpercaya di dunia.
Kali ini kita akan membahas secara step-by-step cara membuat visualisasi dalam bahasa R menggunakan dari data ulasan Rotten Tomatoes! Data yang akan kita gunakan dapat diakses dari Kaggle: Rotten Tomatoes Top Movies Ratings and Technical. Dari data ini kita akan mencari informasi menarik terkait dunia perfilman dan kritik film, seperti perkembangan kualitas film berdasarkan ratingnya, bagaimana perbedaan penilaian kritikus dengan penilaian audiens, serta di akhir kita akan membuat rekomendasi film berdasarkan nilai rating.
2 Data Preparation
2.1 Prerequisites
2.1.1 Importing Libraries
2.1.2 Importing Dataset
rt <- read.csv("data/rotten_tomatoes_top_movies.csv")Mari kita inspeksi data kita menggunakan head().
head(rt)Dari inspeksi data di atas, dapat kita lihat bahwa data kita terdiri dari beberapa kolom berikut.
| Nama Kolom | Deskripsi |
|---|---|
| X | Indeks dari 0 (Integer) |
| title | Judul film. (Character) |
| year | Tahun rilis film. (Integer) |
| synopsis | Ringkasan singkat film. (Character) |
| critic_score | Skor film dari para kritikus. (Integer) |
| people_score | Skor film dari para penonton umum. (Integer) |
| consensus | Ringkasan ulasan dari film. (Character) |
| total_reviews | Jumlah kritikus yang mengulas. (Integer) |
| total_ratings | Jumlah penonton umum yang mengulas. (Integer) |
| type | Jenis film. (Character) |
| genre | Genre film. (Character) |
| rating | Rating film (PG, R, dsb.). (Character) |
| original_language | Bahasa asli film. (Character) |
| director | Sutradara film. (Character) |
| producer | Produser film. (Character) |
| writer | Penulis naskah film. (Character) |
| release_date_(theaters) | Tanggal rilis film di teater. (Date) |
| release_date_(streaming) | Tanggal rilis film di media streaming. (Date) |
| box_office_(gross_usa) | Total pendapatan kotor box office di AS. (Integer) |
| runtime | Durasi film dalam menit. (Integer) |
| production_co | Perusahaan produksi film. (Character) |
| sound_mix | Teknologi suara dalam film. (Character) |
| aspect_ratio | Rasio aspek film. (Character) |
| view_the_collection | Koleksi film. (Character) |
| crew | Anggota kru yang terlibat dalam film. (Character) |
| link | Tautan ke situs web Rotten Tomatoes. (Character) |
3 Data Processing
Dari deskripsi dan hasil inspeksi di atas, kita tidak akan membutuhkan kolom X dan kolom link karena isinya hanya berupa indeks dan tautan ke website Rotten Tomatoes. Kedua kolom ini tidak terlalu informatif dan dapat kita buang menggunakan fungsi subset().
rt <- subset(rt, select = -c(X, link))3.1 Duplicates
Pertama-tama, kita akan memeriksa data yang duplikat. Hal ini dapat dilakukan dengan fungsi duplicated(). Untuk melihat jumlah baris yang duplikat, kita dapat membalut fungsi duplicated() dengan fungsi sum().
sum(duplicated(rt))[1] 0
Angka 0 di atas menandakan bahwa tidak ada baris yang identik. Namun, karena satu film memiliki satu halaman review pada Rotten Tomatoes, maka seharusnya tidak ada judul film (title) yang duplikat. Mari kita lihat apakah ada judul yang duplikat dengan subset kolom title.
sum(duplicated(rt$title, ))[1] 628
Ternyata terdapat 628 judul yang duplikat. Mari kita lihat judul apa saja yang merupakan duplikat dengan memasukkan fungsi duplicated() sebagai conditional subset dari dataframe rt.
rt[duplicated(rt$title), ]Mari kita lihat dua contoh judul yang duplikat, yaitu film Spider-Man: Into the Spider-Verse dan The LEGO Movie
rt[rt$title == "Spider-Man: Into the Spider-Verse", ]rt[rt$title == "The LEGO Movie", ]Ternyata seluruh kolomnya berisi informasi yang sama, kecuali untuk kolom type, dan kolom ini telah direpresentasikan oleh kolom genre. Dari kedua contoh di atas, tipe genre yang paling sesuai adalah paling bawah, maka kita akan menyimpan data-data duplikat dengan mengambil entry terbawahnya. Hal ini dapat dicapai dengan melakukan conditional subsetting dengan kondisi sebagai berikut.
rt_clean <- rt[!duplicated(rt$title, fromLast = TRUE), ]
rt_cleanTanda ! berarti “bukan”. Ketika dimasukkan ke dalam conditional subsetting, berarti kita hanya ingin mengambil baris-baris yang “bukan duplikasi”. Parameter fromLast = TRUE berarti kita ingin mengambil baris yang paling akhir dari baris-baris yang duplikat.
Mengingat kolom type berisi informasi yang sama dengan kolom genre, maka kita hanya perlu memilih salah satu di antaranya. Kali ini kita memilih kolom type karena kolom genre berisi lebih dari satu jenis genre untuk tiap baris, data seperti ini sulit diolah dan divisualisasikan. Oleh karena itu kita akan menghapus kolom genre dengan fungsi subset().
rt_clean <- subset(rt_clean, select = -genre)
head(rt_clean)3.2 Missing Values
Mari kita lihat struktur data kita menggunakan fungsi str().
str(rt_clean)'data.frame': 982 obs. of 23 variables:
$ title : chr "Mission: Impossible Rogue Nation" "John Wick: Chapter 3 -- Parabellum" "The Peanut Butter Falcon" "Apollo 13" ...
$ year : int 2015 2019 2019 1995 2002 2016 2007 2010 2016 2015 ...
$ synopsis : chr "With the IMF now disbanded and Ethan Hunt (Tom Cruise) out in the cold, a new threat -- called the Syndicate --"| __truncated__ "After gunning down a member of the High Table -- the shadowy international assassin's guild -- legendary hit ma"| __truncated__ "After running away from a residential nursing home to pursue his dream of becoming a pro wrestler, a man who ha"| __truncated__ "This Hollywood drama is based on the events of the Apollo 13 lunar mission, astronauts Jim Lovell (Tom Hanks), "| __truncated__ ...
$ critic_score : int 94 89 94 96 96 94 96 95 93 98 ...
$ people_score : int 87 86 96 87 89 84 92 85 81 76 ...
$ consensus : chr "Mission: Impossible Rogue Nation continues the franchise's thrilling resurgence -- and proves that Tom Cruise r"| __truncated__ "John Wick: Chapter 3 - Parabellum reloads for another hard-hitting round of the brilliantly choreographed, over"| __truncated__ "A feelgood adventure brought to life by outstanding performances, The Peanut Butter Falcon finds rich modern re"| __truncated__ "In recreating the troubled space mission, Apollo 13 pulls no punches: it's a masterfully told drama from direct"| __truncated__ ...
$ total_reviews : int 325 349 217 92 202 332 162 151 166 54 ...
$ total_ratings : chr "50,000+" "25,000+" "2,500+ Verified" "250,000+" ...
$ type : chr "Action & Adventure" "Action & Adventure" "Action & Adventure" "Action & Adventure" ...
$ rating : chr "PG-13 (Sequences of Action & Violence|Brief Partial Nudity)" "R (Some Language|Pervasive Strong Violence)" "PG-13 (Smoking|Language Throughout|Some Violence|Thematic Content)" "PG" ...
$ original_language : chr "English" "English" "English" "English" ...
$ director : chr "Christopher McQuarrie" "Chad Stahelski" "Tyler Nilson, Michael Schwartz" "Ron Howard" ...
$ producer : chr "Tom Cruise, J.J. Abrams, Bryan Burk, David Ellison, Dana Goldberg, Don Granger" "Basil Iwanyk, Erica Lee" "Tim Zajaros, Christopher Lemole, Albert Berger, Ron Yerxa" "Brian Grazer" ...
$ writer : chr "Christopher McQuarrie" "Derek Kolstad, Shay Hatten, Chris Collins, Marc Abrams" "Tyler Nilson, Michael Schwartz" "William Broyles Jr., Al Reinert" ...
$ release_date_.theaters. : chr "Jul 31, 2015 wide" "May 17, 2019 wide" "Aug 23, 2019 wide" "Jun 30, 1995 wide" ...
$ release_date_.streaming.: chr "Jun 1, 2016" "Aug 23, 2019" "Aug 27, 2019" "Jun 14, 2012" ...
$ box_office_.gross_usa. : chr "" "$171.0M" "$20.5M" "$173.8M" ...
$ runtime : chr "2h 11m" "2h 11m" "1h 36m" "2h 20m" ...
$ production_co : chr "Tom Cruise, Paramount Pictures, Skydance Productions, Bad Robot" "Thunder Road Pictures, 87eleven" "Harbor Picture Company, Bona Fide Productions, Nut Bucket Films, Armory Films" "Imagine Entertainment, Universal Pictures" ...
$ sound_mix : chr "Dolby Atmos" "" "" "Surround" ...
$ aspect_ratio : chr "" "Scope (2.35:1)" "Scope (2.35:1)" "" ...
$ view_the_collection : chr "" "" "" "" ...
$ crew : chr "Tom Cruise, Jeremy Renner, Simon Pegg, Rebecca Ferguson, Ving Rhames, Sean Harris, Alec Baldwin, Simon McBurney"| __truncated__ "Keanu Reeves, Halle Berry, Ian McShane, Laurence Fishburne, Mark Dacascos, Asia Kate Dillon, Lance Reddick, Tob"| __truncated__ "Shia LaBeouf, Dakota Johnson, Zack Gottsagen, John Hawkes, Bruce Dern, Thomas Haden Church, Jon Bernthal, YelaW"| __truncated__ "Tom Hanks, Bill Paxton, Kevin Bacon, Gary Sinise, Ed Harris, Kathleen Quinlan, David Andrews, Xander Berkeley, "| __truncated__ ...
Dari hasil di atas, kita mendapatkan informasi bahwa data kita terdiri dari 982 baris dan 26 kolom (982 obs. of 23 variables). Selain itu, ternyata terdapat beberapa kolom yang berisi karakter kosong (""), hal ini dapat menyebabkan data-data tersebut tidak terbaca sebagai missing value. Kita dapat mengubah karakter kosong tersebut menjadi NA.
rt_clean[rt_clean == ""] <- NA
str(rt_clean)'data.frame': 982 obs. of 23 variables:
$ title : chr "Mission: Impossible Rogue Nation" "John Wick: Chapter 3 -- Parabellum" "The Peanut Butter Falcon" "Apollo 13" ...
$ year : int 2015 2019 2019 1995 2002 2016 2007 2010 2016 2015 ...
$ synopsis : chr "With the IMF now disbanded and Ethan Hunt (Tom Cruise) out in the cold, a new threat -- called the Syndicate --"| __truncated__ "After gunning down a member of the High Table -- the shadowy international assassin's guild -- legendary hit ma"| __truncated__ "After running away from a residential nursing home to pursue his dream of becoming a pro wrestler, a man who ha"| __truncated__ "This Hollywood drama is based on the events of the Apollo 13 lunar mission, astronauts Jim Lovell (Tom Hanks), "| __truncated__ ...
$ critic_score : int 94 89 94 96 96 94 96 95 93 98 ...
$ people_score : int 87 86 96 87 89 84 92 85 81 76 ...
$ consensus : chr "Mission: Impossible Rogue Nation continues the franchise's thrilling resurgence -- and proves that Tom Cruise r"| __truncated__ "John Wick: Chapter 3 - Parabellum reloads for another hard-hitting round of the brilliantly choreographed, over"| __truncated__ "A feelgood adventure brought to life by outstanding performances, The Peanut Butter Falcon finds rich modern re"| __truncated__ "In recreating the troubled space mission, Apollo 13 pulls no punches: it's a masterfully told drama from direct"| __truncated__ ...
$ total_reviews : int 325 349 217 92 202 332 162 151 166 54 ...
$ total_ratings : chr "50,000+" "25,000+" "2,500+ Verified" "250,000+" ...
$ type : chr "Action & Adventure" "Action & Adventure" "Action & Adventure" "Action & Adventure" ...
$ rating : chr "PG-13 (Sequences of Action & Violence|Brief Partial Nudity)" "R (Some Language|Pervasive Strong Violence)" "PG-13 (Smoking|Language Throughout|Some Violence|Thematic Content)" "PG" ...
$ original_language : chr "English" "English" "English" "English" ...
$ director : chr "Christopher McQuarrie" "Chad Stahelski" "Tyler Nilson, Michael Schwartz" "Ron Howard" ...
$ producer : chr "Tom Cruise, J.J. Abrams, Bryan Burk, David Ellison, Dana Goldberg, Don Granger" "Basil Iwanyk, Erica Lee" "Tim Zajaros, Christopher Lemole, Albert Berger, Ron Yerxa" "Brian Grazer" ...
$ writer : chr "Christopher McQuarrie" "Derek Kolstad, Shay Hatten, Chris Collins, Marc Abrams" "Tyler Nilson, Michael Schwartz" "William Broyles Jr., Al Reinert" ...
$ release_date_.theaters. : chr "Jul 31, 2015 wide" "May 17, 2019 wide" "Aug 23, 2019 wide" "Jun 30, 1995 wide" ...
$ release_date_.streaming.: chr "Jun 1, 2016" "Aug 23, 2019" "Aug 27, 2019" "Jun 14, 2012" ...
$ box_office_.gross_usa. : chr NA "$171.0M" "$20.5M" "$173.8M" ...
$ runtime : chr "2h 11m" "2h 11m" "1h 36m" "2h 20m" ...
$ production_co : chr "Tom Cruise, Paramount Pictures, Skydance Productions, Bad Robot" "Thunder Road Pictures, 87eleven" "Harbor Picture Company, Bona Fide Productions, Nut Bucket Films, Armory Films" "Imagine Entertainment, Universal Pictures" ...
$ sound_mix : chr "Dolby Atmos" NA NA "Surround" ...
$ aspect_ratio : chr NA "Scope (2.35:1)" "Scope (2.35:1)" NA ...
$ view_the_collection : chr NA NA NA NA ...
$ crew : chr "Tom Cruise, Jeremy Renner, Simon Pegg, Rebecca Ferguson, Ving Rhames, Sean Harris, Alec Baldwin, Simon McBurney"| __truncated__ "Keanu Reeves, Halle Berry, Ian McShane, Laurence Fishburne, Mark Dacascos, Asia Kate Dillon, Lance Reddick, Tob"| __truncated__ "Shia LaBeouf, Dakota Johnson, Zack Gottsagen, John Hawkes, Bruce Dern, Thomas Haden Church, Jon Bernthal, YelaW"| __truncated__ "Tom Hanks, Bill Paxton, Kevin Bacon, Gary Sinise, Ed Harris, Kathleen Quinlan, David Andrews, Xander Berkeley, "| __truncated__ ...
Sekarang mari kita lihat jumlah missing value pada data kita.
colSums(is.na(x = rt_clean)) title year synopsis
0 0 5
critic_score people_score consensus
0 1 12
total_reviews total_ratings type
0 0 0
rating original_language director
273 16 1
producer writer release_date_.theaters.
74 201 312
release_date_.streaming. box_office_.gross_usa. runtime
10 291 4
production_co sound_mix aspect_ratio
86 465 618
view_the_collection crew
907 0
Mengingat bahwa data kita terdiri dari 982 baris, maka mari kita hapus kolom dengan missing value yang terlalu banyak (di atas 200 baris), yaitu consensus, rating, writer, release_date_.theaters., box_office_.gross_usa., sound_mix, aspect_ratio, dan view_the_collection.
rt_clean <-
subset(rt_clean, select = -c(consensus, rating, writer, release_date_.theaters.,
box_office_.gross_usa., sound_mix, aspect_ratio,
view_the_collection))Mari kita lihat kembali kolom yang berisi missing values.
colSums(is.na(x = rt_clean)) title year synopsis
0 0 5
critic_score people_score total_reviews
0 1 0
total_ratings type original_language
0 0 16
director producer release_date_.streaming.
1 74 10
runtime production_co crew
4 86 0
Dan kita inspeksi kembali isi data kita.
rt_cleanSelanjutnya mari kita imputasi missing value untuk data bertipe character dengan kata “Unknown”, kecuali untuk kolom release_date_.streaming. dan runtime karena kolom tersebut berisi tanggal dan durasi waktu. Missing value dalam kolom original_language akan kita isi dengan “Unknown language” karena ternyata terdapat data “Unknown language” dalam kolom tersebut.
cols <- c("synopsis", "director", "producer", "production_co")
rt_clean[cols][is.na(rt_clean[cols])] <- "Unknown"
rt_clean["original_language"][is.na(rt_clean["original_language"])] <- "Unknown language"Cek kembali missing values
colSums(is.na(x = rt_clean)) title year synopsis
0 0 0
critic_score people_score total_reviews
0 1 0
total_ratings type original_language
0 0 0
director producer release_date_.streaming.
0 0 10
runtime production_co crew
4 0 0
Selanjutnya mari kita lakukan imputasi untuk kolom numerik, yaitu people_score. Pertama kita lihat terlebih dahulu persebaran data dari kolom people_score menggunakan fungsi summary().
summary(rt_clean$people_score) Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
10.00 77.00 85.00 81.39 90.00 98.00 1
Dapat kita lihat bahwa nilai median dan mean tidak berbeda jauh, namun ada indikasi data left skewed karena nilai minimum nya yang sangat jauh dengan kuartil pertama. Selain itu, hampir tidak mungkin seseorang memberikan nilai yang benar-benar 0 untuk sebuah film, oleh karena itu lebih baik kita isi missing value ini dengan nilai median.
rt_clean$people_score[is.na(rt_clean$people_score)] <- median(rt_clean$people_score, na.rm = TRUE)
summary(rt_clean$people_score) Min. 1st Qu. Median Mean 3rd Qu. Max.
10.0 77.0 85.0 81.4 90.0 98.0
Setelah imputasi dengan median, dapat dilihat bahwa informasi persebaran data kita tidak berubah.
3.3 Data Types
Kini mari kita ubah tipe data dalam dataset kita agar dapat divisualisasikan dengan baik. Mari kita lihat kembali struktur data kita dengan str().
str(rt_clean)'data.frame': 982 obs. of 15 variables:
$ title : chr "Mission: Impossible Rogue Nation" "John Wick: Chapter 3 -- Parabellum" "The Peanut Butter Falcon" "Apollo 13" ...
$ year : int 2015 2019 2019 1995 2002 2016 2007 2010 2016 2015 ...
$ synopsis : chr "With the IMF now disbanded and Ethan Hunt (Tom Cruise) out in the cold, a new threat -- called the Syndicate --"| __truncated__ "After gunning down a member of the High Table -- the shadowy international assassin's guild -- legendary hit ma"| __truncated__ "After running away from a residential nursing home to pursue his dream of becoming a pro wrestler, a man who ha"| __truncated__ "This Hollywood drama is based on the events of the Apollo 13 lunar mission, astronauts Jim Lovell (Tom Hanks), "| __truncated__ ...
$ critic_score : int 94 89 94 96 96 94 96 95 93 98 ...
$ people_score : int 87 86 96 87 89 84 92 85 81 76 ...
$ total_reviews : int 325 349 217 92 202 332 162 151 166 54 ...
$ total_ratings : chr "50,000+" "25,000+" "2,500+ Verified" "250,000+" ...
$ type : chr "Action & Adventure" "Action & Adventure" "Action & Adventure" "Action & Adventure" ...
$ original_language : chr "English" "English" "English" "English" ...
$ director : chr "Christopher McQuarrie" "Chad Stahelski" "Tyler Nilson, Michael Schwartz" "Ron Howard" ...
$ producer : chr "Tom Cruise, J.J. Abrams, Bryan Burk, David Ellison, Dana Goldberg, Don Granger" "Basil Iwanyk, Erica Lee" "Tim Zajaros, Christopher Lemole, Albert Berger, Ron Yerxa" "Brian Grazer" ...
$ release_date_.streaming.: chr "Jun 1, 2016" "Aug 23, 2019" "Aug 27, 2019" "Jun 14, 2012" ...
$ runtime : chr "2h 11m" "2h 11m" "1h 36m" "2h 20m" ...
$ production_co : chr "Tom Cruise, Paramount Pictures, Skydance Productions, Bad Robot" "Thunder Road Pictures, 87eleven" "Harbor Picture Company, Bona Fide Productions, Nut Bucket Films, Armory Films" "Imagine Entertainment, Universal Pictures" ...
$ crew : chr "Tom Cruise, Jeremy Renner, Simon Pegg, Rebecca Ferguson, Ving Rhames, Sean Harris, Alec Baldwin, Simon McBurney"| __truncated__ "Keanu Reeves, Halle Berry, Ian McShane, Laurence Fishburne, Mark Dacascos, Asia Kate Dillon, Lance Reddick, Tob"| __truncated__ "Shia LaBeouf, Dakota Johnson, Zack Gottsagen, John Hawkes, Bruce Dern, Thomas Haden Church, Jon Bernthal, YelaW"| __truncated__ "Tom Hanks, Bill Paxton, Kevin Bacon, Gary Sinise, Ed Harris, Kathleen Quinlan, David Andrews, Xander Berkeley, "| __truncated__ ...
Kolom dengan tipe data yang perlu diganti adalah:
total_ratings→ factortype→ factororiginal_language→ factorrelease_date_.streaming.→ date
# mengganti tipe data menjadi factor
cols <- c("total_ratings", "type", "original_language")
rt_clean[cols] <- lapply(X = rt_clean[cols], FUN = as.factor)
rt_clean[cols]# mengganti tipe data menjadi date
rt_clean$release_date_.streaming. <- mdy(rt_clean$release_date_.streaming.)
rt_clean["release_date_.streaming."]4 Exploratory Data Analysis
Mari kita lihat kembali data kita
head(rt_clean)Pertama-tama, mari kita lihat seperti apa persebaran data kita dari segi tahun penayangan. Kita dapat melihatnya dengan fungsi summary().
summary(rt_clean$year) Min. 1st Qu. Median Mean 3rd Qu. Max.
1919 1985 2007 1996 2015 2020
Data kita berisi film dari tahun 1919 hingga 2020, dan terpusat di tahun 2007. Mari kita lihat frekuensinya secara visual dengan melakukan exploratory visualization. Exploratory visualization adalah visualisasi yang kita lakukan untuk mengenal data kita. Untuk melihat distribusi frekuensi, kita dapat membuat histogram menggunakan fungsi hist().
hist(rt_clean$year)Ternyata sebagian besar film, bakan lebih dari 600 film, dibuat dari tahun 2000 hingga 2020.
Mari kita lihat jumlah film berdasarkan tipe genrenya (type) dengan fungsi table().
table(rt_clean$type)
Action & Adventure Animation Art House & International
5 20 41
Classics Comedy Documentary
13 38 52
Drama Horror Kids & Family
55 63 68
Musical & Performing Arts Mystery & Suspense Romance
70 81 89
Science Fiction & Fantasy Special Interest Sports & Fitness
91 88 69
Television Western
57 82
Tabel di atas cukup sulit dibaca, mari kita sortir agar nilainya terurutkan dari tertinggi ke terendah dengan fungsi sort() dan memasukkan parameter decreasing = T .
sort(table(rt_clean$type), decreasing = T)
Science Fiction & Fantasy Romance Special Interest
91 89 88
Western Mystery & Suspense Musical & Performing Arts
82 81 70
Sports & Fitness Kids & Family Horror
69 68 63
Television Drama Documentary
57 55 52
Art House & International Comedy Animation
41 38 20
Classics Action & Adventure
13 5
Ternyata film dengan tipe Science Fiction & Fantasy memiliki jumlah paling banyak dan Action & Adventure memiliki jumlah paling sedikit, namun informasi lainnya cukup sulit untuk didapatkan. Mari kita lakukan visualisasi dengan barchart untuk melihat ranking tiap tipe.
plot(rt_clean$type)Visualisasi di atas kurang jelas karena tidak semua tipe ditampilkan. Kita akan memperbaiki visualisasi ini pada bagian Expanatory Visualization. Sekarang, mari kita lihat apa saja film yang bertipe Science Fiction & Fantasy, yaitu tipe dengan jumlah film terbanyak.
rt_clean[rt_clean$type == "Science Fiction & Fantasy", ]Dari 10 data teratas, ternyata banyak film bertipe Science Fiction & Fantasy merupakan film Superhero! Mari kita lihat tahun berapa saja film-film ini dibuat.
summary(rt_clean[rt_clean$type == "Science Fiction & Fantasy", "year"]) Min. 1st Qu. Median Mean 3rd Qu. Max.
1922 1981 2011 1995 2017 2020
Ternyata film Science Fiction & Fantasy sudah dibuat sejak tahun 1922, dan banyak dibuat di sekitar tahun 2011.
Kini mari kita lihat bahasa apa yang paling banyak digunakan dalam film yang telah direview Rotten Tomatoes.
sort(table(rt_clean$original_language), decreasing = T)
English Japanese French (France)
791 28 25
French (Canada) Unknown language English (United Kingdom)
18 18 16
German Italian Arabic
11 9 6
Persian Spanish Swedish
6 6 6
Korean Russian Hebrew
5 5 4
Chinese English (Australia) Portuguese (Brazil)
3 3 3
Spanish (Spain) Hindi Indonesian
3 2 2
Bambara Bangla Czech
1 1 1
Danish Dutch Nepali
1 1 1
Polish Romanian Thai
1 1 1
Tibetan Turkish Wolof
1 1 1
Terlihat bahwa bahasa terbanyak adalah English (Inggris) diikuti Japanese (Jepang) dan French (Perancis). Dapat dilihat juga bahwa Rotten Tomatoes menampung film-film dari negara yang beragam, termasuk Indonesia. Bahkan beberapa film dengan bahasa yang cukup asing di telinga juga terdapat di situs Rotten Tomatoes, seperti bahasa Bambara yang merupakan bahasa penduduk Republik Mali dan Wolof yang merupakan bahasa etnik Wolof dari Afrika Barat.
Kita juga dapat mencari tahu siapa sutradara (director) dengan film terbanyak sejak 1919 hingga 2020. Agar hasil yang didapatkan tidak terlalu panjang, mari kita ambil 6 sutradara teratas dengan fungsi head().
head(sort(table(rt_clean$director), decreasing = T))
Alfred Hitchcock Steven Spielberg Howard Hawks John Ford
14 7 6 6
Akira Kurosawa Billy Wilder
5 5
Ternyata bahkan sutradara terbanyak hanya memproduksi 14 film! Hal ini menunjukkan bahwa membuat suatu film tidaklah mudah. Alfred Hitchcock merupakan sutradara dengan film terbanyak, diikuti Steven Spielberg dan Howard Hawks. Mari kita lihat film-film yang mereka buat.
direc <- c("Alfred Hitchcock", "Steven Spielberg", "Howard Hawks")
top3_direc <- rt_clean[rt_clean$director %in% direc, ]
top3_direc[order(top3_direc$year, decreasing = T),]Sebagian besar film yang mereka produksi merupakan film yang cukup lawas. Hanya ada 3 film yang diproduksi di atas tahun 2000. Hal ini menunjukkan bahwa beliau-beliau tersebut sangat produktif di tahun 90an.
5 Explanatory Visualization
Explanatory visualization merupakan tahap membuat visualisasi untuk mempresentasikan data kita. Oleh karena itu, pada tahap ini kita akan membuat visualisasi dengan tampilan yang informatif menarik.
Mari kita buat visualisasi yang lebih rapi dan menarik menggunakan library ggplot2. Pertama, mari kita coba perbaiki barchart pada bagian Exploratory Data Analysis di atas. Mari kita buat dataframe dari tabel frekuensi yang telah kita buat sebelumnya.
type_freq <- as.data.frame(table(rt_clean$type))
type_freqKemudian kita masukkan dataframe di atas ke dalam kode visualisasi kita.
ggplot(data = type_freq, mapping = aes(x = Var1, y = Freq)) +
geom_col()Panjang batang dalam visualisasi kita masih belum berurutan, mari kita urutkan dengan fungsi reorder().
ggplot(data = type_freq, mapping = aes(x = reorder(Var1, Freq), y = Freq)) +
geom_col()Sudah berurutan! Tapi tipe film kita terlihat bertumpuk, selain itu visualisasi ranking lebih baik ditampilkan dengan horizontal barplot. Mari kita tukar sumbu x dengan sumbu y menggunakan fungsi coord_flip().
ggplot(data = type_freq, mapping = aes(x = reorder(Var1, Freq), y = Freq)) +
geom_col() +
coord_flip()Jauh lebih baik! sekarang mari kita tambahkan informasi nilai frekuensi tiap tipe berupa teks pada bagian ujung batang menggunakan geom_text() dan masukkan Freq sebagai label.
ggplot(data = type_freq, mapping = aes(x = reorder(Var1, Freq), y = Freq)) +
geom_col() +
geom_text(aes(label = Freq)) +
coord_flip()Sekarang saatnya kita mengubah label pada sumbu x dan y. Kita akan mengganti label “Freq” menjadi “Frequency” dan mengganti label “reorder(Var1, Freq)” menjadi “Genre Type”. Untuk mengganti & membuat label, kita dapat menggunakan fungsi labs().
Dalam fungsi ggplot(), “Freq” merupakan data untuk y sementara “reorder(Var1, Freq)” merupakan data untuk x, oleh karena itu dalam fungsi labs() kita perlu memasukkan label baru sesuai dengan x dan y-nya.
ggplot(data = type_freq, mapping = aes(x = reorder(Var1, Freq), y = Freq)) +
geom_col() +
geom_text(aes(label = Freq)) +
coord_flip() +
labs(x = "Genre Type",
y = "Frequency")Hampir lengkap! Sekarang mari kita tambahkan judul dan subjudul agar audiens dapat mengetahui informasi apa yang terdapat dalam visualisasi ini. Seperti sebelumnya, kita akan menggunakan fungsi labs() dengan mengisi parameter title untuk judul dan subtitle untuk subjudul.
ggplot(data = type_freq, mapping = aes(x = reorder(Var1, Freq), y = Freq)) +
geom_col() +
geom_text(aes(label = Freq)) +
coord_flip() +
labs(x = "Genre Type",
y = "Frequency",
title = "Rotten Tomatoes: Critic Score Distribution",
subtitle = "Year of 1919 to 2020")Untuk membuat visualisasi lebih ✨berwarna✨, mari kita ubah isi warna batang dengan warna “tomato”. Kita dapat melakukannya dengan memasukkan warna tersebut ke parameter fill dalam geom_col().
Selain itu mari ubah pula warna teks angka frekuensi menjadi warna Burgundy dengan kode warna #8B0020 agar tidak terlalu hitam dan lebih senada dengan warna barplot. Kita bisa memasukkan kode warnanya ke parameter color dalam geom_text().
ggplot(data = type_freq, mapping = aes(x = reorder(Var1, Freq), y = Freq)) +
geom_col(fill = "tomato") +
geom_text(aes(label = Freq), color = "#8B0020") +
coord_flip() +
labs(x = "Genre Type",
y = "Frequency",
title = "Rotten Tomatoes: Critic Score Distribution",
subtitle = "Year of 1919 to 2020")Agar batang barplot kita lebih terlihat, mari kita gunakan tema yang lebih clean, yaitu theme_light().
ggplot(data = type_freq, mapping = aes(x = reorder(Var1, Freq), y = Freq)) +
geom_col(fill = "tomato") +
geom_text(aes(label = Freq), color = "#800020") +
coord_flip() +
labs(x = "Genre Type",
y = "Frequency",
title = "Rotten Tomatoes: Critic Score Distribution",
subtitle = "Year of 1919 to 2020") +
theme_light()Mantap! Dari visualisasi di atas, dapat terlihat lebih jelas bahwa Science Fiction & Fantasy merupakan tipe genre film yang paling banyak dibuat sepanjang tahun 1919 hingga 2020, diikuti Romance dan Special Interest.
Kini mari kita lihat distribusi nilai yang diberikan oleh para kritikus film (critic_score) dan para penonton awam (people_score). Pertama, mari buat histogram dari critic_score terlebih dahulu.
ggplot(data = rt_clean, mapping = aes(x = critic_score)) +
geom_histogram(fill = "tomato") +
labs(x = "Critic Score",
y = "Frequency",
title = "Rotten Tomatoes: Critic Score Distribution",
subtitle = "Year of 1919 to 2020")+
theme_light()Terlihat bahwa para kritikus condong memberikan nilai yang tinggi, yairu di sekitar 90 s.d. 100. Meskipun demikian, beberapa kritikus berani memberi nilai di bawah 25. Sekarang mari kita lihat histogram dari people_score.
ggplot(data = rt_clean, mapping = aes(x = people_score)) +
geom_histogram(fill = "tomato") +
labs(x = "People Score",
y = "Frequency",
title = "Rotten Tomatoes: People Score Distribution",
subtitle = "Year of 1919 to 2020")+
theme_light()Terlihat bahwa penilaian penonton awam justru lebih variatif dan cenderung lebih rendah, yaitu di sekitar 70 s.d. 98. Meskipun demikian, sangat jarang sekali ada penonton yang memberi nilai di bawah 25. Agar dapat membandingkan critic_score dan people_score dengan lebih terperinci, mari kita gunakan boxplot. Namun sebelum itu, kita perlu melakukan data wrangling terlebih dahulu, yaitu dengan melakukan pivot_longer agar critic_score dan people_score dapat menjadi satu kolom.
score_longer <-
pivot_longer(data = rt_clean[, c("critic_score", "people_score")],
cols = c("critic_score", "people_score"),
names_to = "variable", values_to = "value")
score_longer$variable <- ifelse(score_longer$variable == "critic_score",
yes = "Critic Score",
no = "People Score")
score_longerggplot(data = score_longer, mapping = aes(x = variable, y = value)) +
geom_boxplot(fill = "tomato", color = "#800020") +
coord_flip() +
labs(x = "",
y = "Score",
title = "Rotten Tomatoes: Score Distribution Comparison",
subtitle = "People Score vs. Critic Score\nYear of 1919 to 2020") +
theme_light()Dari visualisasi di atas, kita dapat menarik informasi:
Median, median skor dari penonton umum lebih rendah daripada skor dari kritikus
IQR/Lebar Kotak, kotak People Score lebih lebar daripada kotak Critic Score, yang berarti penilaian dari penonton umum lebih variatif sementara penilaian dari kritikus lebih terpusat
Outlier, outlier pada skor penonton umum lebih sedikit daripada outlier pada skor kritikus, yang berarti hanya ada sedikit penonton umum yang memberikan nilai rendah
Selanjutnya mari kita lihat bagaimana hubungan antara jumlah kritikus (total_reviews) dengan penilaian mereka (critic_score). Untuk melihat hubungan antara dua variabel numerik, kita dapat membuat scatter plot menggunakan geom_point().
ggplot(data = rt_clean, mapping = aes(x = total_reviews, y = critic_score)) +
geom_point(color = "tomato") +
labs(x = "Total Reviews",
y = "Critic Score",
title = "Rotten Tomatoes: Reviewer Amount and Their Score",
subtitle = "Year of 1919 to 2020") +
theme_light()Terlihat bahwa sebagian besar film memiliki jumlah reviewer kurang dari 200 orang, dan sebagian besar orang memberi nilai di atas 75. Film-film dengan nilai kecil (< 50) umumnya direview oleh jumlah orang yang sedikit (kurang dari 300 orang). Kemungkinan hal ini terjadi karena beberapa kritikus pertama yang telah menonton film-film ini memberikan review rendah, sehingga orang-orang yang membaca review sebelum menonton akhirnya tidak tertarik untuk menonton film-film tersebut. Sementara itu, film-film dengan jumlah reviewer yang banyak, jarang mendapatkan review jelek, kemungkinan karena film-film ini memang bagus dan banyak ditonton, sehingga para penontonnya juga memberikan review positif.
Selanjutnya mari kita lihat pergerakan jumlah film tiap tahunnya berdasarkan kategori apakah film tersebut fresh atau rotten. Untuk itu mari kita buat suatu kolom baru dalam dataframe kita yang bernama tomatometer. Sesuai keterangan pada laman Rotten Tomatoes mengenai Tomatometer®, kolom ini akan kita isi dengan kata “Fresh” apabila nilai critic_score > 60 dan “Rotten” jika nilai critic_score < 60.
rt_clean$tomatometer <- ifelse(test = rt_clean$critic_score >= 60,
yes = "Fresh",
no = "Rotten")
# melihat 5 data random dari tomatometer
sample(rt_clean$tomatometer, size = 5)[1] "Fresh" "Fresh" "Rotten" "Fresh" "Fresh"
Kemudian kita buat tabel frekuensi dari year dan tomatometer menggunakan fungsi table() yang kemudian kita konversi ke bentuk dataframe.
year_meter <- as.data.frame(table(rt_clean$year, rt_clean$tomatometer))
head(year_meter)Terlihat bahwa Var1 yang berisi tahun justru bertipe data factor. Jika kita langsung melakukan visualisasi dari data ini, grafik yang terbentuk akan menjadi garis vertikal.
ggplot(data = year_meter, mapping = aes(x = Var1, y = Freq)) +
geom_line()Oleh karena itu, kita perlu mengganti tipe datanya menjadi integer kembali. Kita dapat langsung mengganti tipe data di dalam parameter x.
Kemudian, karena kita akan membedakan warna plot berdasarkan isi dari Var2, maka kita akan memasukkan Var2 ke dalam parameter color dalam aes() dari geom_line().
ggplot(data = year_meter, mapping = aes(x = as.integer(as.character(Var1)), y = Freq)) +
geom_line(mapping = aes(color = Var2)) +
labs(x = "Year",
y = "Amount of Film/TV Show",
title = "Rotten Tomatoes: Amount of Film by Year",
subtitle = "Fresh vs. Rotten\nYear of 1919 to 2020") +
theme_light()Mari perbesar pula ukuran garis agar plot terlihat lebih jelas, dengan mengisi parameter size dalam geom_line(). Kemudian mari kita ubah judul legenda untuk warna garis dari “Var2” menjadi “Tomatometer®” dengan mengisi parameter color pada fungsi labs().
Selain itu, untuk lebih merepresentasikan warna fresh dan rotten, mari kita ubah warna garis pada plot kita menjadi Lime Green untuk kategori Fresh dan dark grey untuk kategori Rotten. Hal ini dapat dilakukan dengan menambahkan warna-warna tersebut dalam fungsi scale_color_manual().
ggplot(data = year_meter, mapping = aes(x = as.integer(as.character(Var1)), y = Freq)) +
geom_line(mapping = aes(color = Var2), size = 1) +
labs(x = "Year",
y = "Amount of Film/TV Show",
color = "Tomatometer®",
title = "Rotten Tomatoes: Amount of Film by Year",
subtitle = "Fresh vs. Rotten\nYear of 1919 to 2020") +
scale_color_manual(values = c("#32CD32", "dark grey")) +
theme_light()Dapat kita lihat bahwa sepanjang tahun 1919 s.d. 2020, jumlah film yang fresh selalu lebih banyak daripada jumlah film yang rotten. Selain itu, jumlah film yang fresh juga semakin meningkat seiring berjalannya tahun. Bahkan menuju tahun 2020, jumlah film yang tergolong fresh jauh melebihi jumlah film-film rotten. Hal ini menunjukkan bahwa seiring perkembangan zaman, semakin banyak film-film berkualitas yang diproduksi.
5.1 Movie Recommendation
Apakah kamu pernah ingin menonton film sendiri, atau nobar (nonton bareng) teman-teman di tongkrongan, atau movie and chill date dengan pasanganmu, tapi bingung ingin menonton film apa? Supaya tidak lagi bingung, mari kita buat rekomendasi film dari rating Rotten Tomatoes!
Pertama, mari kita buat rekomendasi film-film baru, yaitu film dari tahun 2020. Kita dapat mengurutkan data kita berdasarkan nilai critic_score dengan fungsi order(). Untuk mengurutkan dari nilai terbesar ke terkecil, kita dapat menggunakan parameter decreasing = TRUE. Kita akan mengambil 10 film teratas menggunakan head().
top_critic_2020 <- rt_clean[rt_clean$year == 2020, ]
top_critic_2020 <- top_critic_2020[order(top_critic_2020$critic_score, decreasing = T),]
top_critic_2020 <- head(top_critic_2020, 10)
top_critic_2020Mari kita visualisasikan menggunakan geom_point() berdasarkan waktu rilis film tersebut (release_date_.streaming.). Kali ini kita akan menggunakan fungsi geom_label_repel() dari library ggrepel untuk membuat label judul film. Selain itu, karena sumbu y merupakan angka, kita akan menggunakan fungsi scale_y_continuous() untuk mengatur skala pada sumbu y agar label tidak terlalu berdempetan.
library(ggrepel)
ggplot(data = top_critic_2020, mapping = aes(x = release_date_.streaming., y = critic_score)) +
geom_point(size = 3) +
geom_label_repel(aes(label = title),
size = 3.5,
segment.size = 0.1, # ukuran garis label
segment.color = "black", # warna garis label
fill = "#32CD32", # warna isi kotak label
color = "white", # warna teks
box.padding = 0.6) + # jarak antar kotak
scale_y_continuous(limits = c(98.5, 100.5), breaks = c(99, 100)) +
labs(x = "Release date",
y = "Critic score",
title = "Rotten Tomatoes: Recommended Films by Critics",
subtitle = "Year of 2020") +
theme_light()Judul-judul yang tertera pada visualisasi di atas adalah judul-judul film terbaik menurut para kritikus di tahun 2020! Dapat kita lihat bahwa sebagian besar film bernilai 100 dan hanya satu, “Totally Under Control” yang bernilai 99.
Kini coba kita lihat apa saja film-film terbaik di tahun 2020 menurut para penonton. Kita persiapkan datanya dengan tahapan yang mirip seperti tahapan di atas, hanya saja kali ini kita urutkan berdasarkan nilai people_score.
top_people_2020 <- rt_clean[rt_clean$year == 2020, ]
top_people_2020 <- top_people_2020[order(top_people_2020$people_score, decreasing = T),]
top_people_2020 <- head(top_people_2020, 10)
top_people_2020Selanjutnya kita dapat melakukan visualisasi dari 10 film terbaik versi penonton di tahun 2020.
ggplot(data = top_people_2020, mapping = aes(x = release_date_.streaming., y = critic_score)) +
geom_point(size = 3) +
geom_label_repel(aes(label = title),
size = 3.5,
segment.size = 0.1,
segment.color = "black",
fill = "#32CD32",
color = "white",
box.padding = 0.6) +
scale_y_continuous(limits = c(80, 103)) +
labs(x = "Release date",
y = "People score",
title = "Rotten Tomatoes: Recommended Films by People",
subtitle = "Year of 2020") +
theme_light()Judul-judul pada visualisasi di atas adalah judul-judul film terbaik menurut para penonton di tahun 2020! Ternyata jumlah film dengan nilai 100 oleh penonoton tidak sebanyak jumlah film dengan nilai 100 oleh kritikus.
Sekarang mari kita lihat film-film terbaik sepanjang masa (tahun 1919 s.d. 2020). Pertama mari kita ambil data dengan nilai critic_score dan people_score >= 95. Setelah itu kita buat kolom baru bernama avg_score yang berisi rata-rata critic_score dengan people_score. Selanjutnya kita urutkan data kita berdasarkan nilai avg_score.
top_overall <- rt_clean[rt_clean$critic_score >= 95 & rt_clean$people_score >= 95, ]
top_overall$avg_score <- (top_overall$critic_score + top_overall$people_score) / 2
top_overall <- top_overall[order(top_overall$avg_score, decreasing = T),]
top_overallSelanjutnya kita bisa memvisualisasikan data di atas. Kali ini kita akan membuat ukuran geom_point() berbeda berdasarkan nilai people_score. Selain itu, kita juga akan menampilkan empat judul film dengan skor terbaik, oleh karena itu kita meenggunakan head() dalam parameter data pada geom_label_repel().
ggplot(data = top_overall, mapping = aes(x = year, y = critic_score)) +
geom_point(aes(size = people_score)) +
geom_label_repel(data = head(top_overall, 4),
mapping = aes(label = title),
size = 3.5,
segment.size = 0.1,
segment.color = "black",
fill = "#32CD32",
color = "white",
box.padding = 0.7) +
scale_x_continuous(breaks = seq(1920, 2020, 20)) +
labs(x = "Year",
y = "Critic score",
size = "People score",
title = "Rotten Tomatoes: Recommended Films by Critics & People",
subtitle = "Year of 1919 to 2020") +
theme_light()Dari visualisasi di atas, dapat kita lihat bahwa terdapat empat titik besar pada garis teratas, yang berarti keempat titik ini memiliki skor tertinggi baik dari para penonton maupun para kritikus. Ternyata keempat film tersebut berjudul
- Seven Samurai: tahun 1950an
- 12 Angry Men: tahun 1950an
- Stop Making Sense: tahun 1980an
- Rewind: tahun 2019
Selamat menonton! 😄
6 Conclusion
Kali ini kita telah berhasil membuat beragam visualiasai data dari situs Rotten Tomatoes. Dari visualisasi-visualisasi tersebut, banyak informasi yang dapat kita ambil, seperti adanya perbedaan distribusi penilaian oleh kritikus dibandingkan dengan penonton umum, serta adanya tren peningkatan jumlah film berkualitas seiring perkembangan zaman. Di akhir, kita juga berhasil membuat rekomendasi film berdasarkan nilai ulasan, baik dari kritikus, dari penonton umum, maupun keduanya. Hasil visualisasi yang telah kita buat menujukkan bahwa visualisasi data sangat bermanfaat dalam mempermudah kita maupun audiens dalam menarik dan menerima informasi dari data.