Spis treści:
Eksploracja danych z bibliotekami dplyr, tidyr oraz stringr
- Podzbiory kolumn
- Filtrowanie wierszy
- Operatory logiczne, algebra Boola, prawa de Morgana
- Tworzenie nowych kolumn (1x Challenge)
- Wartości brakujące
- Manipulowanie tekstem (3x Challenge)
- Agregacja danych (1x Challenge)
- Tabele przestawne, dane w formacie long oraz wide
- Łączenie tabel
Przydatne materiały:
- dplyr cheatsheet
- tidyr cheatsheet
- stringr cheatsheet
- ggplot2 cheatsheet
- A. Kassambara - Guide to Create Beautiful Graphics in R.
Dane pochodzą ze strony https://flixgem.com/ (wersja zbioru danych z dnia 12 marca 2021). Dane zawierają informacje na temat 9425 filmów i seriali dostępnych na Netlix.
Eksploracja danych z bibliotekami dplyr oraz tidyr
Podzbiory kolumn
Kolumny wybieramy po ich nazwach za pomocą funkcji select(). Możemy też usuwać kolumny, poprzedzając nazwę danej kolumny symbolem -.
dane %>%
select(X.U.FEFF.Title, Runtime, IMDb.Score, Release.Date) %>%
head(5)
dane %>%
select(-Netflix.Link, -IMDb.Link, -Image, -Poster, -TMDb.Trailer)%>%
head(5)
dane %>%
select(1:10)%>%
head(5)
dane %>%
select(X.U.FEFF.Title:Runtime)%>%
head(5)
Przydatne funkcje podczas wybierania/usuwania kolumn: - starts_with() - wybieramy lub usuwamy kolumny zaczynające się danym ciągiem znaków - ends_with() - wybieramy lub usuwamy kolumny kończące się danym ciągiem znaków - contains() - wybieramy lub usuwamy kolumny zawierające dany ciąg znaków.
dane %>%
select(starts_with('IMDb'))%>%
head(10)
dane %>%
select(ends_with('Score'))%>%
head(10)
dane %>%
select(contains('Date'))%>%
head(10)
Za pomocą funkcji matches() wybieramy lub usuwamy kolumny zawierające dane wyrażenie regularne. Przydatne narzędzie w budowaniu i testowaniu wyrażeń regularnych jest pod linkiem https://regex101.com/.
dane %>%
select(matches('^[a-z]{5,6}$')) %>%
head(10)
dane %>%
select(-matches('\\.'))%>% #wszystkie oprócz zawierające '.'
head(10)
Funkcja select() zawsze zwraca ramkę danych, natomiast mamy też możliwość zwrócenia wektora za pomocą funkcji pull().
dane %>%
select(IMDb.Score)%>%
head(10)
# dane %>%
# select(IMDb.Score) %>%
# unlist(use.names = FALSE)
dane %>%
pull(IMDb.Score)%>%
head(10)
dane %>%
pull(IMDb.Score, X.U.FEFF.Title)%>%
head(10)
Filtrowanie wierszy
Wiersze filtrujemy za pomocą funkcji filter() korzystając z operatorów ==, !=, >, >=, <, <=, between().
dane %>%
filter(Series.or.Movie == "Series")%>%
head(10)
dane %>%
filter(IMDb.Score > 8)%>%
head(10)
Operatory logiczne, algebra Boola, prawa de Morgana
Operator logiczny AND oznaczany symbolem & - FALSE & FALSE = FALSE - FALSE & TRUE = FALSE - TRUE & FALSE = FALSE - TRUE & TRUE = TRUE
dane %>%
filter(IMDb.Score >= 8 & Series.or.Movie == 'Series')%>%
head(10)
Operator logiczny OR oznaczany symbolem | - FALSE | FALSE = FALSE - FALSE | TRUE = TRUE - TRUE | FALSE = TRUE - TRUE | TRUE = TRUE
dane %>%
filter(IMDb.Score >= 9 | IMDb.Votes < 1000)%>%
head(10)
Prawa de Morgana mówią, że gdy wchodzimy z negacją pod nawias, to OR zamienia się na AND (i na odwrót). not (A & B) = (not A) | (not B) not (A | B) = (not A) & (not B)
dane %>%
filter(!(IMDb.Score >= 9 | IMDb.Votes < 1000))%>%
head(10)
dane %>%
filter(!(IMDb.Score >= 9) & !(IMDb.Votes < 1000))%>%
head(10)
Tworzenie nowych kolumn
Za pomocą funkcji mutate() dodajemy nowe kolumny do ramki danych albo edytujemy już istniejące kolumny.
dane %>%
mutate(score_category = if_else(IMDb.Score >= 5, 'Good', 'Poor')) %>%
select(X.U.FEFF.Title, IMDb.Score, score_category)%>%
head(10)
dane %>%
transmute(
Release = Release.Date %>% as.Date(format = '%m/%d/%y')
,Netflix.Release = Netflix.Release.Date %>% as.Date(format = '%m/%d/%y')
)
CHALLENGE 1: Jaki jest najstarszy film Woody’ego Allena dostępny na Netflixie?
dane %>%
filter(Director=='Woody Allen',Series.or.Movie=='Movie') %>%
arrange(Release.Date) %>%
select(X.U.FEFF.Title,Release.Date) %>%
head(1)
W przypadku funkcji case_when() nie musimy pisać warunków tworzących zbiory wzajemnie rozłączne. Ewaluacja następuje po spełnieniu pierwszego z warunków, po czym natychmiastowo następuje kolejna iteracja.
dane %>%
mutate(score_category = case_when(
IMDb.Score <= 2 ~ 'Very Poor'
,IMDb.Score <= 4 ~ 'Poor'
,IMDb.Score <= 6 ~ 'Medium'
,IMDb.Score <= 8 ~ 'Good'
,IMDb.Score <= 10 ~ 'Very Good'
)) %>%
select(X.U.FEFF.Title, IMDb.Score, score_category)%>%
head(10)
Działania matematyczne wykonywane dla każdego wiersza i bazujące na kilku kolumnach wykonujemy przy pomocy funkcji rowwise().
dane %>%
mutate(avg_score = mean(c(IMDb.Score * 10
,Hidden.Gem.Score * 10
,Rotten.Tomatoes.Score
,Metacritic.Score)
,na.rm = TRUE) %>%
round(2)) %>%
select(X.U.FEFF.Title, avg_score)%>%
head(10)
dane %>%
rowwise() %>%
mutate(avg_score = mean(c(IMDb.Score * 10
,Hidden.Gem.Score * 10
,Rotten.Tomatoes.Score
,Metacritic.Score)
,na.rm = TRUE) %>%
round(2)) %>%
select(X.U.FEFF.Title, avg_score)%>%
head(10)
Domyślnie kolumny tworzone są pomocą mutate() są na końcu tabeli. Za pomocą relocate() możemy zmieniać pozycje poszczególnych kolumn w tabeli.
dane %>%
mutate(Popularity = if_else(IMDb.Votes > quantile(IMDb.Votes, 0.90, na.rm = TRUE), 'High', 'Not High')) %>%
relocate(Popularity, .after = X.U.FEFF.Title)
Zmieniamy nazwy kolumn za pomocą funkcji rename().
dane %>%
rename(
Tytul = X.U.FEFF.Title
,Gatunek = Genre
)
Wartości brakujące
Za pomocą funkcji z biblioteki tidyr możemy okiełznać wartości brakujące: - drop_na() - usuwamy wiersze zawierające wartości brakujące we wskazanych kolumnach - replace_na() - zastępujemy wartości brakujące określoną stałą - fill() - zastępujemy wartości brakujące poprzednią lub następną dostępną wartością.
dane %>%
sapply(function(x) is.na(x) %>% sum())
dane %>%
drop_na(Hidden.Gem.Score)
dane %>%
mutate(Hidden.Gem.Score = replace_na(Hidden.Gem.Score, median(Hidden.Gem.Score, na.rm = TRUE))) %>%
sapply(function(x) is.na(x) %>% sum())
dane %>%
replace_na(list(Hidden.Gem.Score = median(dane$Hidden.Gem.Score, na.rm = TRUE))) %>%
sapply(function(x) is.na(x) %>% sum())
Manipulowanie tekstem
Biblioteka stringr zawiera dużo przydatnych funkcji do manipulacji tekstem oraz wyrażeniami regularnymi. Większość funkcji z tej biblioteki zaczyna się od str_.
Q: Co można poprawić w poniższym kodzie, aby była zachowana konwencja stylu tidyverse?
gatunki = dane$Genre %>%
paste0(collapse = ', ') %>%
str_extract_all('[A-Za-z]+') %>%
unlist() %>%
table() %>%
as.data.frame()
gatunki %>%
arrange(-Freq)
dane %>%
mutate(poland_available = str_detect(Country.Availability, 'Poland')) %>%
filter(poland_available == TRUE) %>%
pull(X.U.FEFF.Title)%>%
head(10)
Za pomocą separate() możemy rozdzielać jedną kolumną na kilką oraz łączyć kilka kolumn w jedną za pomocą funkcji unite().
dane %>%
unite(
col = 'Scores'
,c('Hidden.Gem.Score', 'IMDb.Score', 'Rotten.Tomatoes.Score', 'Metacritic.Score')
,sep = ', '
) %>%
select(X.U.FEFF.Title, Scores)%>%
head(10)
CHALLENGE 2: Jakie są trzy najwyżej oceniane komedie dostępne w języku polskim?
dane %>%
select(X.U.FEFF.Title,Languages,Genre,Hidden.Gem.Score) %>%
filter(Languages=="Polish",Genre=="Comedy") %>%
arrange(desc(Hidden.Gem.Score)) %>%
head(3)
CHALLENGE 3: Dla produkcji z lat 2019 oraz 2020 jaki jest średni czas między premierą a pojawieniem się na Netflixie?
dane$Release.Date <- as.Date(dane$Release.Date, format = "%m/%d/%Y")
dane$Netflix.Release.Date <- as.Date(dane$Netflix.Release.Date, format = "%m/%d/%Y")
dane %>%
drop_na(Release.Date, Netflix.Release.Date) %>%
mutate(year = lubridate::year(Release.Date), differ = Netflix.Release.Date - Release.Date) %>%
filter(year %>% between(2019,2020)) %>%
group_by(year) %>%
summarize(movie_mean = mean(differ))
CHALLENGE 4: Jakie są najpopularniejsze tagi dla produkcji dostępnych w języku polskim?
dane %>%
filter(Languages %>% str_detect('Polish')) %>%
pull(Tags) %>%
str_c(collapse=',') %>%
str_split(',') %>%
table() %>%
as.data.frame()%>%
arrange(-Freq) %>%
top_n(3)
Agregacja danych
Za pomocą funkcji group_by() oraz summarize() wykonujemy operacje na zagregowanych danych.
dane %>%
group_by(Series.or.Movie) %>%
summarize(
count = n()
,avg_imdb_score = mean(IMDb.Score, na.rm = TRUE) %>% round(2)
,avg_imdb_votes = mean(IMDb.Votes, na.rm = TRUE) %>% round(0)
,sum_awards = sum(Awards.Received, na.rm = TRUE)
)
dane %>%
group_by(Series.or.Movie, Runtime) %>%
summarize(n = n()) %>%
arrange(-n)
CHALLENGE 5: Jakie są średnie oceny filmów wyprodukowanych w poszczególnych dekadach (tzn. lata 60, 70, 80, 90 etc.)?
dane$Release.Date <- as.Date(dane$Release.Date, format = "%m/%d/%Y")
dane %>%
mutate(rok = lubridate::year(Release.Date), decade = floor(rok/10)*10) %>%
group_by(decade) %>%
summarize(count = n(),
avg_imdb_score = mean(IMDb.Score, na.rm = TRUE) %>% round(2))
Tabele przestawne, dane w formacie long oraz wide
Dane w formacie wide: - wiersze reprezentują pojedyncze obserwacje - kolumny reprezentują atrybuty tych obserwacji - w komórkach znajdują się wartości poszczególnych atrybutów dla poszczególnych obserwacji.
Dane w formacie long: - w pierwszej kolumnie mamy obserwacje (klucz obserwacji może składać się też z więcej niż jednej kolumny) - w drugiej kolumnie mamy atrybuty - w trzeciej kolumnie mamy wartości.
Format long jest przydatny m. in. przy tworzeniu wykresów w bibliotece ggplot2.
#dane_pivot = dane %>%
# select(X.U.FEFF.Title, ends_with('Score'))
#dane_pivot = dane_pivot %>%
# pivot_longer(
# cols = 2:5
#,names_to = 'Attribute'
#,values_to = 'Value'
# )
#dane_pivot = dane_pivot %>%
# pivot_wider(
#id_cols = 1
#,names_from = 'Attribute'
#,values_from = 'Value'
# )
Łączenie tabel
#oceny_metacritic = dane %>%
# select(X.U.FEFF.Title, Metacritic.Score) %>%
#[1:100,] %>%
# drop_na()
#oceny_rotten_tomatoes = dane %>%
#select(X.U.FEFF.Title, Rotten.Tomatoes.Score) %>%
#.[1:100,] %>%
#drop_na()
Tabele łączymy po odpowiednich kluczach tak samo, jak robimy to w SQL.
#oceny_metacritic %>%
#left_join(oceny_rotten_tomatoes, by = c('Title' = 'Title'))
#oceny_metacritic %>%
#right_join(oceny_rotten_tomatoes, by = c('Title' = 'Title'))
#oceny_metacritic %>%
#inner_join(oceny_rotten_tomatoes, by = c('Title' = 'Title'))
#oceny_metacritic %>%
#full_join(oceny_rotten_tomatoes, by = c('Title' = 'Title'))
#oceny_metacritic %>%
#anti_join(oceny_rotten_tomatoes, by = c('Title' = 'Title'))
#oceny_rotten_tomatoes %>%
# anti_join(oceny_metacritic, by = c('Title' = 'Title'))