Spróbujmy odpowiedzieć na tak postawione pytanie.
Na początek rozważmy czego możemy się dowiedzieć z ogólnodostępnych źródeł? Weźmy na przykład bazę filmów z seriwsu IMBd.com. Mamy tam takie informacje:
Spróbujmy zatem na początek pobrać potrzebne informacje wprost z IMBd.com.
Te biblioteki nam się przydadzą:
library(ggplot2)
library(dplyr)
library(rvest)
library(randomForest)
Poniższa funkcja pobierze informacje o nominacjach oraz wygranych w kategorii “Best Picutre” wprost ze stron IMBd.com. Na początek wystarczą nam linki do stron poszczególnych filmów:
GetBestMoviesList <- function(rok) {
# czytamy odpowiednią stronę - kolejną edycję Oscarów
page <- read_html(paste0("http://www.imdb.com/event/ev0000003/", rok))
# proszę zajrzeć w kod HTML strony - potrzebujemy tylko fragmentów
best_picture <- page %>%
html_nodes("div.award") %>%
html_node("blockquote") %>%
html_node("blockquote")
# "Best Picture" jest w pierwszym bloku
best_picture <- best_picture[1]
# pobieramy wszystkie linki z wybranego bloku
best_picture <- best_picture %>%
html_nodes("div") %>%
html_nodes("a") %>%
html_attr("href") %>%
unique()
# wykluczenie zbędnych linków
# interesują nas tylko filmy, a nie link do osoby lub wytwórnia
# usuwamy to co zbędne
best_picture <- best_picture[substr(best_picture, 1, 9)!="/company/"]
best_picture <- best_picture[substr(best_picture, 1, 8)!="/name/nm"]
# wszystkie linki włóżmy w jedną ramkę
df <- data.frame(Year=rok, MovieLink=best_picture, Won=FALSE)
# pierwszy film na liście wygrywał - oznaczmy to
df[1,3] <- TRUE
return(df)
}
Skoro mamy gotową funkcję - możemy pobrać z jej pomocą informacje z kolejnych edycji Oscarów. W przykładzie ograniczamy się do lat 2000-2016.
movies_list <- data.frame()
for(y in 2000:2016) {
movies_list <- rbind(movies_list, GetBestMoviesList(y))
}
Uwaga - jeśli chcemy pobrać lata wcześniejsze (np. wszystkie edycje nagrody - od 1929 roku) trzeba uważać na niespodzianki. Są dwie:
Kiedy mamy już linki do wszystkich filmów z oznaczeniem czy film wygrał w kategorii “Best Picture” oraz w którym to było roku - warto uzupełnić linki do pełnego adresu URL. Na stronie linki są w postaci względnej (chociaż nie zawsze).
# zbudowanie pełnego urla
movies_list$MovieLink <- ifelse(substr(movies_list$MovieLink, 1, 7) == "http://",
movies_list$MovieLink,
paste0("http://www.imdb.com", movies_list$MovieLink))
Mamy historię, ale co z nominowanymi w 2017 roku? To dodajmy ręcznie. Tak - ręcznie wyszukałem te filmy i je wpisałem poniżej.
# filmy nominowane w 2017
movies_list <- rbind(movies_list, data.frame(Year=2017,
MovieLink=c("http://www.imdb.com/title/tt2543164/",
"http://www.imdb.com/title/tt3783958/",
"http://www.imdb.com/title/tt2671706/",
"http://www.imdb.com/title/tt3741834/",
"http://www.imdb.com/title/tt2119532/",
"http://www.imdb.com/title/tt4034228/",
"http://www.imdb.com/title/tt4975722/",
"http://www.imdb.com/title/tt2582782/",
"http://www.imdb.com/title/tt4846340/"),
Won=FALSE))
Mamy “namiary” na wszystkie interesujące nas filmy. Czas zdobyć ich “dane szczegółowe”. Posłużymy się poniższą funkcją, którą wywołamy 122 razy (tyle ile mamy filmów).
GetMovieDetalis <- function(movie_url) {
# wczytajmy stronę z filmem
page <- read_html(movie_url)
# tytuł - interesuje nas tytuł oryginalny
title <- page %>%
html_node("div.title_wrapper") %>%
html_node("div.originalTitle") %>%
html_text()
# jeśli nie ma tytułu oryginalnego w stosownym polu - weźmy tytuł "główny"
# IMDb.com w tytule podaje tytuł zlokalizowany - w Polsce zobaczymy polskie tytuły
# dodatkowo w nawiasach po tytule jest np. rok produkcji filmu - usuwamy to
if(is.na(title)) {
title <- page %>%
html_node("div.title_wrapper") %>%
html_node("h1") %>%
html_text()
sub <- page %>%
html_node("div.title_wrapper") %>%
html_node("h1") %>%
html_node("span") %>%
html_text()
} else {
sub <- page %>%
html_node("div.title_wrapper") %>%
html_node("div.originalTitle") %>%
html_node("span") %>%
html_text()
}
title <- gsub(sub, "", title, fixed = TRUE)
title <- trimws(title)
# ocena użytkowników
rating <- page %>%
html_node("div.imdbRating") %>%
html_nodes("span") %>%
html_text()
# liczba głosóW
votes <- as.integer(gsub(",", "", rating[4]))
# ocena
rating <- as.numeric(rating[1])
# czas trwania filmu
duration <- page %>%
html_node("div.title_wrapper") %>%
html_node("div.subtext") %>%
html_node("time") %>%
html_attr("datetime")
duration <- gsub("PT", "", duration)
duration <- as.integer(gsub("M", "", duration))
# kategoria wiekowa
pgrating <- page %>%
html_node("div.title_wrapper") %>%
html_node("div.subtext") %>%
html_node("meta") %>%
html_attr("content")
# gatunki - pobieramy wszystko, na końcu wykorzystamy tylko pierwszy wskazany
genres <- page %>%
html_node("div.title_wrapper") %>%
html_node("div.subtext") %>%
html_nodes("a") %>%
html_text()
genres <- genres[1:(length(genres)-1)]
# credits
credits <- page %>% html_nodes("div.credit_summary_item")
director <- credits[1] %>% html_nodes("span.itemprop") %>% html_text()
writers <- credits[2] %>% html_nodes("span.itemprop") %>% html_text()
stars <- credits[3] %>% html_nodes("span.itemprop") %>% html_text()
# metascore
metascore <- page %>% html_node("div.metacriticScore")
# dla starszych filmów nie ma tej wartości
# aby funkcja zadziałała dla filmów z Oscarów 1929 robimy obejście :)
if(!is.na(metascore)) {
metascore <- metascore %>% html_node("span") %>% html_text() %>% as.integer()
} else {
metascore <- NA
}
# budżet
movie_details <- page %>%
html_node("div#titleDetails") %>%
html_nodes("div.txt-block") %>%
html_text()
movie_details <- trimws(gsub("\\n", "", movie_details))
movie_details <- gsub(" +", " ", movie_details)
budget <- movie_details[substr(movie_details, 1, 8)=="Budget: "]
if(length(budget)>0) {
budget <- substr(budget, 9, nchar(budget))
budget <- strsplit(budget, " ")[[1]][1]
budget <- gsub(",", "", budget)
# niektóre wartości są w funtach - przeliczmy to na dolary
# GBP -> USD, 1 GBP = 1.5 USD
budget <- ifelse(substr(budget, 1, 1)=="$",
as.integer(substr(budget, 2, nchar(budget))),
1.5*as.integer(substr(budget, 2, nchar(budget))))
} else {
# czasem nie ma informacji o budżecie - obejście
budget <- NA
}
# przychód w weekend otwarcia
# plus obejście dla brakujących danych
opening <- movie_details[substr(movie_details, 1, 17)=="Opening Weekend: "]
if(length(opening)>0) {
opening <- substr(opening, 18, nchar(opening))
opening <- strsplit(opening, " ")[[1]][1]
opening <- gsub(",", "", opening)
opening <- ifelse(substr(opening, 1, 1)=="$",
as.integer(substr(opening, 2, nchar(opening))),
1.5*as.integer(substr(opening, 2, nchar(opening))))
} else {
opening <- NA
}
# całkowity przychód
gross <- movie_details[substr(movie_details, 1, 7)=="Gross: "]
if(length(gross)>0) {
gross <- substr(gross, 8, nchar(gross))
gross <- strsplit(gross, " ")[[1]][1]
gross <- gsub(",", "", gross)
gross <- ifelse(substr(gross, 1, 1)=="$",
as.integer(substr(gross, 2, nchar(gross))),
1.5*as.integer(substr(gross, 2, nchar(gross))))
} else {
gross <- NA
}
# łączymy wszystkie dane w jedną ramkę, a właciwie jeden wiersz
df <- data.frame(Title=title,
Rating=rating,
Votes=votes,
Duration=duration,
PG=pgrating,
Genres=genres[1],
Director=director[1],
Writers=writers[1],
StarA=stars[1],
StarB=stars[2],
MetaScore=metascore,
Budget=budget,
Openning=opening,
Gross=gross)
return(df)
}
Uff - mamy czym pobierać dane o filmach. Czas to zrobić!
# pobranie danych do jednej ramki
movies_dets <- data.frame()
len <- nrow(movies_list)
for(i in 1:len) {
db <- data.frame(movies_list[i,])
mdet <- GetMovieDetalis(movies_list[i,2])
db <- cbind(db, mdet)
movies_dets <- rbind(movies_dets, db)
}
Proces może trochę potrwać - w końcu mamy do przekopania się przez 122 stron. Ale na koniec zgromadzimy ciekawe dane. O takie (pierwsze 10 pobranych rekordów):
| Year | MovieLink | Won | Title | Rating | Votes | Duration | PG | Genres | Director | Writers | StarA | StarB | MetaScore | Budget | Openning | Gross |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2000 | http://www.imdb.com/title/tt0169547/ | TRUE | American Beauty | 8.4 | 854519 | 122 | 15 | Drama | Sam Mendes | Alan Ball | Kevin Spacey | Annette Bening | 86 | 1.50e+07 | 861531 | 130058047 |
| 2000 | http://www.imdb.com/title/tt0124315/ | FALSE | The Cider House Rules | 7.4 | 80678 | 126 | PG-13 | Drama | Lasse Hallström | John Irving | Tobey Maguire | Charlize Theron | 75 | 2.40e+07 | 110098 | 57536361 |
| 2000 | http://www.imdb.com/title/tt0120689/ | FALSE | The Green Mile | 8.5 | 824505 | 189 | R | Crime | Frank Darabont | Stephen King | Tom Hanks | Michael Clarke Duncan | 61 | 6.00e+07 | 18017152 | 136801374 |
| 2000 | http://www.imdb.com/title/tt0140352/ | FALSE | The Insider | 7.9 | 136694 | 157 | R | Biography | Michael Mann | Marie Brenner | Russell Crowe | Al Pacino | 84 | 9.00e+07 | 6712361 | 28965197 |
| 2000 | http://www.imdb.com/title/tt0167404/ | FALSE | The Sixth Sense | 8.1 | 730317 | 107 | PG-13 | Drama | M. Night Shyamalan | M. Night Shyamalan | Bruce Willis | Haley Joel Osment | 64 | 4.00e+07 | 26681262 | 293501675 |
| 2001 | http://www.imdb.com/title/tt0172495/ | TRUE | Gladiator | 8.5 | 1024354 | 155 | R | Action | Ridley Scott | David Franzoni | Russell Crowe | Joaquin Phoenix | 64 | 1.03e+08 | 34819017 | 187670866 |
| 2001 | http://www.imdb.com/title/tt0241303/ | FALSE | Chocolat | 7.3 | 153929 | 121 | PG-13 | Drama | Lasse Hallström | Joanne Harris | Juliette Binoche | Judi Dench | 64 | 2.50e+07 | 157624 | 71309760 |
| 2001 | http://www.imdb.com/title/tt0190332/ | FALSE | Wo hu cang long | 7.9 | 221985 | 120 | PG-13 | Action | Ang Lee | Du Lu Wang | Yun-Fat Chow | Michelle Yeoh | 93 | 1.70e+07 | 663205 | 128067808 |
| 2001 | http://www.imdb.com/title/tt0195685/ | FALSE | Erin Brockovich | 7.3 | 139347 | 131 | R | Biography | Steven Soderbergh | Susannah Grant | Julia Roberts | Albert Finney | 73 | 5.20e+07 | 28138465 | 125548685 |
| 2001 | http://www.imdb.com/title/tt0181865/ | FALSE | Traffic | 7.6 | 173658 | 147 | R | Crime | Steven Soderbergh | Simon Moore | Michael Douglas | Benicio Del Toro | 86 | 4.80e+07 | 15517549 | 124107476 |
Na początek zobaczmy jak oceniane przez społeczność IMBd.com są filmy, które zdobyły Oscara.
# minimalna i maksymalna ocena - do skalowania wykresów
ymin <- floor(min(movies_dets$Rating))
ymax <- ceiling(max(movies_dets$Rating))
movies_dets %>%
filter(Year != 2017) %>%
ggplot() +
theme_bw() +
geom_point(aes(Year, Rating, col=Won), size=5) +
geom_smooth(aes(Year, Rating, col=Won), se=FALSE, method = "loess") +
ylim(ymin, ymax) +
labs(title="Ocena IMDb.com a Oscar")
Widać, że lepiej oceniane są filmy z Oscarem. Czyli nagroda odpowiada gustom publiczności :)
A jak mają się do nagrody informacje o pieniądzach?
Najpierw budżet:
movies_dets %>%
filter(Year != 2017) %>%
ggplot() +
theme_bw() +
geom_point(aes(Year, Budget, col=Won), size=5) +
geom_smooth(aes(Year, Budget, col=Won), se=FALSE, method = "loess") +
labs(title="Budżet a Oscar - produkcja")
Filmy z Oscarem są ostatnio tańsze w produkcji - taka tendencja widoczna jest od 2008 roku. Czyli Akademia preferuje kino kameralne nad blockbustery? Sprawdźmy to po przychodach:
movies_dets %>%
filter(Year != 2017) %>%
ggplot() +
theme_bw() +
geom_point(aes(Year, Openning, col=Won), size=5) +
geom_smooth(aes(Year, Openning, col=Won), se=FALSE, method = "loess") +
labs(title="Budżet a Oscar - weekend otwarcia")
movies_dets %>%
filter(Year != 2017) %>%
ggplot() +
theme_bw() +
geom_point(aes(Year, Gross, col=Won), size=5) +
geom_smooth(aes(Year, Gross, col=Won), se=FALSE, method = "loess") +
labs(title="Budżet a Oscar - całkowity przychód")
Widzieliśmy już, że publiczność wyżej ocenia filmy Oscarowe. A krytycy (oceny MetaScore)?
movies_dets %>%
filter(Year != 2017) %>%
ggplot() +
theme_bw() +
geom_point(aes(Year, MetaScore, col=Won), size=5) +
geom_smooth(aes(Year, MetaScore, col=Won), se=FALSE, method = "loess") +
labs(title="MetaScore a Oscar")
Spróbujmy teraz przewidzieć który film wygra w kategorii “Best Picture”?
Zbudujemy model typu random forest zasilony danymi z lat 2000-2016.
Najpierw jednak trzeba podzielić dane na część uczącą i testową - próbka treningowa to będą poprzednie lata, zaś predykcję będziemy robić na roku 2017.
# wybieramy tylko potrzebne kolumny
movies_train <- movies_dets[, c(1,3,4,5,6,7,8,9, 14,15,16,17)]
# test - 2017 rok
movies_test <- movies_train[movies_train$Year == 2017, -2]
# trening - wszystko pozostałe
movies_train <- movies_train[movies_train$Year != 2017, -3]
movies_train$Won <- ifelse(movies_train$Won, 1, 0)
# dane treningowe to tylko filmy, które mają wszystkie informacje
movies_train <- na.omit(movies_train)
Zbudujemy model. Banalnie - wywołując po prostu jedną fukncję. Będziemy przewidywać wartość “Won” na podstawie pozostałych danych.
# randomForest
model_rf <- randomForest(Won ~ ., movies_train, importance=TRUE)
Przy okazji, sprawdźmy jakie znaczenie w modelu mają poszczególne dane opisujące film:
varImpPlot(model_rf)
Dobra, dobra - czas na przewidywania!
pred_rf <- predict(model_rf, newdata = movies_test[,-2])
movies_test_rf <- cbind(movies_test, pred_rf)
# posortujmy od razu tabelę po predykcji
movies_rf_list <- movies_test_rf %>%
arrange(desc(pred_rf)) %>%
select(Title, Rating, Votes, Genres, MetaScore, pred_rf)
print(movies_rf_list)
| Title | Rating | Votes | Genres | MetaScore | pred_rf |
|---|---|---|---|---|---|
| La La Land | 8.7 | 95928 | Comedy | 93 | 0.4037333 |
| Manchester by the Sea | 8.4 | 21889 | Drama | 96 | 0.2887333 |
| Moonlight | 8.3 | 17467 | Drama | 99 | 0.2323333 |
| Hell or High Water | 7.7 | 65574 | Crime | 88 | 0.1608333 |
| Arrival | 8.2 | 142955 | Drama | 81 | 0.1150000 |
| Hacksaw Ridge | 8.5 | 57891 | Drama | 71 | 0.1100333 |
| Lion | 8.0 | 8213 | Drama | 69 | 0.0982333 |
| Hidden Figures | 8.0 | 13169 | Biography | 74 | 0.0905333 |
| Fences | 7.7 | 9506 | Drama | 78 | 0.0499000 |
Na pierwszym miejscu (IMHO) fawort. Czy tak będzie?
Krytycy różnią się w opiniach.
Masz pytania, albo uwagi? Napisz