Pakiet tidyverse to zestaw pakietów do kompleksowego przetwarzania i wizualizacji danych. Ładuje następujące pakiety:
ggplot2 - tworzenie wykresów,
dplyr - przetwarzanie danych,
tidyr - zmiana reprezentacji danych,
readr - wczytywanie danych tekstowych,
purrr - programowanie funkcyjne
tibble - sposób przechowywania danych,
stringr - przetwarzanie tekstów,
forcats - przetwarzanie faktorów
lubridate - operacje na datach
Manifest tidyverse ustala następujące zasady:
powtórne użycie istniejących struktur danych,
tworzenie czytelnych kodów z operatorem pipe %>% (ang. rura, przewód, łącznik).
Wobec tego załadujmy pakiet tidyverse:
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.2 ✔ readr 2.1.4
✔ forcats 1.0.0 ✔ stringr 1.5.0
✔ ggplot2 3.4.2 ✔ tibble 3.2.1
✔ lubridate 1.9.2 ✔ tidyr 1.3.0
✔ purrr 1.0.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
W konsoli pojawi się informacja o wersji załadowanych pakietów oraz o konfliktach występujących pomiędzy pakietami. Konflikty te wynikają z takich samych nazw funkcji w różnych pakietach. Kolejność wczytywania pakietów ma znaczenie - kolejny pakiet przykryje funkcje z wcześniej wczytanego. Wywołanie przykrytej funkcji jest możliwe poprzez zapis nazwa_pakietu::nazwa_funkcji.
Korzystanie z pakietu i zasad tidyverse to dużo bardziej czytelny kod w porównaniu do wbudowanych funkcji. Poniżej przedstawiony jest przykład przetwarzania danych polegający na filtrowaniu, wyborze kolumn oraz utworzeniu nowej zmiennej.
data("ChickWeight")# bez pakietu tidyversechick_15 <- ChickWeight[ChickWeight$Chick=="15",]chick_15 <- chick_15[c("weight", "Time", "Diet"),]chick_15$weight_kg <- chick_15$weight/1000# z pakietem tidyversechick_15 <- ChickWeight %>%filter(Chick=="15") %>%select(-Chick) %>%mutate(weight_kg=weight/1000)
Rozwiązanie z wykorzystaniem wbudowanych funkcji to 133 znaki, natomiast wykorzystanie tidyverse to 30% oszczędność miejsca i tylko 92 znaki.
3.2 Import danych
Wczytywanie danych do R jest możliwe z wielu różnych źródeł. Funkcje, które to umożliwiają zwykle mają nazwę rozpoczynającą się od read.
Będziemy korzystać z następujących zbiorów danych:
movies - plik tekstowy zawierający informacje o filmach,
bank - plik excel zawierający dane dot. kampanii marketingowej banku, opis zmiennych,
rossmann - plik excel zawierający dane ze sklepów Rossmann,
lotto - plik tekstowy zawierający dane z losowań Lotto.
3.2.1 Pliki CSV
Do wczytywania plików csv można wykorzystać wbudowaną funkcję read.csv() lub tą pochodzącą z pakietu readr - read_csv(). W obu przypadkach wynik wczytania będzie podobny.
movies <-read.csv("data/movies.csv")movies2 <-read_csv("data/movies.csv")
Rows: 2961 Columns: 11
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (3): title, genre, director
dbl (8): year, duration, gross, budget, cast_facebook_likes, votes, reviews,...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Jeśli nas plik ma nietypową strukturę to w funkcji read.csv() możemy określić dodatkowe argumenty informując o nazwach kolumn obecnych w pliku (header =), separatorze kolumn (sep =) lub separatorze miejsc dziesiętnych (dec =)
movies <-read.csv(file ="data/movies.csv", header = T, sep=",", dec=".")
3.2.2 Pliki excel
Do wczytywania plików z Excela niezbędny jest dodatkowy pakiet readxl. W funkcji read_xlsx() podajemy jako argument nazwę pliku. Możemy także dodać nazwę lub numer arkusza w argumencie (sheet =) oraz zakres komórek jako wartość argumentu range =.
library(readxl)bank <-read_xlsx("data/bank.xlsx")# bank <- read_xlsx("data/bank.xlsx", sheet = "dane")# bank <- read_xlsx("data/bank.xlsx", sheet = 1)bank_a1i30 <-read_xlsx("data/bank.xlsx", range ="A1:I30")rossmann <-read_xlsx("data/rossmann.xlsx")
3.2.3 Pliki tekstowe
Z kolei do wczytywania plików tekstowych wykorzystuje się funkcję read.table(). Wczytywany plik nie musi być zlokalizowany na dysku twardym - może to być link internetowy.
Do przetwarzania danych służą funkcje z pakietu dplyr. Większość z nich jako pierwszy argument przyjmuje przetwarzany zbiór danych, ale można tego uniknąć wykorzystując symbole %>%.
Filtrowanie polega na wybraniu obserwacji, które spełniają określony warunek lub warunki. Ze zbioru movies wybierzmy wszystkie komedie:
komedie <-filter(movies, genre=="Comedy")
lub alternatywnie:
komedie <- movies %>%filter(genre=="Comedy")
Po zmiennej, która jest filtrowana musimy podać operator porównania czyli podwójny znak równości ==. Jeśli chcemy filtrować po większej liczbie zmiennych to kolejne warunki dodajemy po przecinku:
komedie_2012 <- movies %>%filter(genre=="Comedy", year==2012)
Wówczas oba warunki muszą zostać spełnione czyli pomiędzy nimi zachodzi relacja i. Równoważny zapis jest następujący:
komedie_2012 <- movies %>%filter(genre=="Comedy"& year==2012)
Pomiędzy warunkami może także zachodzić relacja lub. Wybieramy filmy, które są komediami lub miały swoją premierę w 2012 roku.
komedie_l_2012 <- movies %>%filter(genre=="Comedy"| year==2012)
Możliwy jest także wybór wielu kryteriów filtrowania poprzez operator %in%:
komedie_familijne <- movies %>%filter(genre %in%c("Comedy", "Family"))movies_2000_2010 <- movies %>%filter(year %in%2000:2010)
3.4 Wybieranie kolumn
Do wyboru kolumn służy funkcja select(). Zmodyfikujemy wcześniej utworzony zbiór komedie:
komedie <- movies %>%filter(genre=="Comedy") %>%select(title, year, duration, budget, rating)
Ten sam kod możemy zapisać zagnieżdżając funkcje, ale traci on w ten sposób na czytelności:
Możemy także wskazać, które zmienne nie mają znaleźć się w zbiorze wynikowym:
komedie <- movies %>%filter(genre=="Comedy") %>%select(-genre)
Natomiast jeśli zmiennych jest więcej to musimy jest umieścić w wektorze, żeby nie pisać przed każdą zmienną znaku minus:
komedie <- movies %>%filter(genre=="Comedy") %>%select(-genre, -director, -gross, -budget)komedie <- movies %>%filter(genre=="Comedy") %>%select(-c(genre, director, gross, budget))
Z wykorzystaniem znaku dwukropka możemy także wskazywać zakresy zmiennych:
komedie <- movies %>%filter(genre=="Comedy") %>%select(-genre, -c(gross:reviews))
3.5 Tworzenie nowych zmiennych
Do utworzenia nowej zmiennej wykorzystuje się funkcję mutate(). Utwórzmy w naszym zbiorze nową zmienną, która będzie zawierała czas trwania filmu w godzinach:
komedie <- movies %>%filter(genre=="Comedy") %>%select(-genre, -c(gross:reviews)) %>%mutate(dur_hour = duration/60)
Rozsądnie będzie zaokrąglić otrzymaną wartość do jednego miejsca po przecinku - służy do tego funkcja round():
komedie <- movies %>%filter(genre=="Comedy") %>%select(-genre, -c(gross:reviews)) %>%mutate(dur_hour =round(duration/60,1))
Z kolei funkcja transmute() tworzy zbiór w którym jest tylko nowo utworzona kolumna:
komedie_t <- movies %>%filter(genre=="Comedy") %>%select(-genre, -c(gross:reviews)) %>%transmute(dur_hour =round(duration/60,1))
3.6 Zmiana nazwy zmiennej
Do zmiany nazw zmiennych służy funkcja rename(). Najpierw podajemy nazwę nowej zmiennej, a po znaku równości starą nazwę:
bank <- bank %>%rename(karta=kredyt)
Zmiany nazwy można także dokonać z wykorzystaniem funkcji select:
bank_nowy <- bank %>%select(lokata=wynik)
W takim przypadku trzeba jednak pamiętać o wypisaniu wszystkich zmiennych, które mają się znaleźć w zbiorze wynikowym.
3.7 Podsumowanie danych
Funkcja summarise() służy do podsumowań danych w formie zagregowanej:
bank %>%summarise(saldo_srednia=mean(saldo),saldo_mediana=median(saldo))
Jedną z kategorii zmiennej wykształcenie jest brak danych (NA). Zamienimy tą wartość na kategorię nieustalone z wykorzystaniem funkcji mutate() oraz if_else(). Funkcja if_else() przyjmuje trzy argumenty - pierwszy (condition =) to warunek, który jest weryfikowany, następnie podajemy wartość, która ma być wprowadzona w przypadku spełnienia warunku (true =), a na końcu wartość dla niespełnionego warunku (false =). Jest to odpowiednik funkcji JEŻELI z Excela.
W omawianym przykładzie warunkiem jest sprawdzenie czy wartości zmiennej wykszt są równe NA. Jeśli tak to na ich miejsce wprowadzany jest tekst nieustalone, a w przeciwnym przypadku pozostaje oryginalna wartość.
bank %>%mutate(wykszt=if_else(is.na(wykszt), "nieustalone", wykszt)) %>%group_by(wykszt) %>%count()
Sortowanie jest możliwe z wykorzystaniem funkcji arrange(). Jako argument podajemy zmienną według, której chcemy posortować zbiór. Domyślne zbiór sortowany jest rosnąco - od wartości najmniejszych do największych:
bank_sort <- bank %>%arrange(saldo)
Zmiana kierunku sortowania jest możliwa po zastosowaniu funkcji desc():
bank_sort <- bank %>%arrange(desc(saldo))
Sortowanie możemy także zastosować do wyników podsumowania danych:
bank %>%group_by(wykszt) %>%summarise(liczebnosc=n(),saldo_srednia=mean(saldo),saldo_mediana=median(saldo)) %>%arrange(saldo_srednia)
W celu zaprezentowania funkcji łączących dane przygotujemy kilka zbiorów pomocniczych:
praca_czas <- bank %>%group_by(praca) %>%summarise(sr_czas=mean(czas))praca_saldo <- bank %>%group_by(praca) %>%summarise(sr_saldo=mean(saldo))zawod_saldo <- bank %>%rename(zawod=praca) %>%group_by(zawod) %>%summarise(sr_saldo=mean(saldo))
Do łączenia dwóch zbiorów danych służy funkcja inner_join(), która jako argumenty przyjmuje nazwy zbiorów danych oraz klucz łączenia. Jeśli w obu zbiorach występują kolumny o takich samych nazwach to zostaną potraktowane jako klucz łączenia:
Jeśli w jednym ze zbiorów nie ma wszystkich identyfikatorów, które znajdują się w drugim zbiorze to zastosowanie funkcji inner_join() będzie skutkowało zbiorem, w którym znajdą się tylko te obserwacje, które udało się połączyć.
Jeśli chcemy pozostawić niedopasowane obserwacje to należy wykorzystać jedną z funkcji - left_join() lub right_join() w zależności od tego dla którego zbioru chcemy pozostawić wszystkie informacje.
# A tibble: 11 × 3
praca sr_czas sr_saldo
<dbl> <dbl> <dbl>
1 1 247. NA
2 2 289. 1522.
3 3 254. 1764.
4 4 246. NA
5 5 256. 1521.
6 6 263. NA
7 7 268. 1648.
8 8 287. 1984.
9 9 253. NA
10 10 257. NA
11 NA 238. 1772.
3.11 Szeroka i wąska reprezentacja danych
Do wyjaśnienia kwestii szerokiej i wąskiej reprezentacji danych posłużymy się danymi z GUS dotyczącymi przeciętnego miesięcznego spożycie wybranych artykułów żywnościowych na 1 osobę w 2016 roku - plik.
spozycie <-read_xlsx("data/spozycie.xlsx")
Taka tabela jest przykładem szerokiej reprezentacji danych. Z kolei w niektórych sytuacjach wygodnie jest korzystać z wąskiej reprezentacji danych, a niektóre pakiety wręcz wymagają takich zbiorów wejściowych.
Do transformacji danych z reprezentacji szerokiej na wąską służy funkcja gather() (pol. gromadzić). Kluczowe są w niej dwa argumenty - pierwszy (key) określa nazwę nowej kolumny, która będzie zawierała nazwy zmiennych, a drugi (value) określa nazwę nowej kolumny, która będzie zawierała wartości zmiennych. Jako kolejne argumenty podaje się nazwy kolumn, które mają być transformowane lub nazwy kolumn ze znakiem minus -, które nie mają być transformowane.
Transformacja z wąskiej do szerokiej reprezentacji danych jest możliwa z zastosowaniem funkcji spread() (pol. rozprzestrzeniać). W przypadku tej funkcji niezbędne są dwa argumenty - pierwszy (key) wskazuje kolumnę zawierającą nazwy dla nowych zmiennych, a drugi argument (value) wskazuje kolumnę zawierającą wartości dla nowych zmiennych.
Zapis zbioru danych do zewnętrznego pliku jest możliwy z wykorzystaniem funkcji write.table(). Jako argumenty tej funkcji określamy: zbiór danych (x), docelowe miejsce na dysku i nazwę pliku (file), separator kolumn (sep), separator miejsc dziesiętnych (dec) oraz argument row.names = FALSE, dzięki któremu unikniemy dodatkowych numerów wierszy.
Taki plik jest plikiem csv, który możemy otworzyć w Excelu i zapisać go z rozszerzeniem .xlsx. Teoretycznie istnieje pakiet xlsx, który umożliwia zapisywanie zbiorów od razu do Excela, ale działa w oparciu o Javę, co bywa problematyczne.
Ile było sklepów o asortymencie rozszerzonym w dniu 25-02-2014?
W jaki dzień tygodnia średnia liczba klientów była największa w sklepie nr 101?
Sklep jakiego typu charakteryzuje się największą medianą sprzedaży?
Czy w ciągu roku odległość do najbliższego sklepu konkurencji zmieniła się dla jakiegokolwiek sklepu Rossmann?
Połącz dane ze sklepów Rossmann z danymi o średnim kursie EUR/PLN z 2014 roku, który można pobrać ze strony NBP. Przelicz wielkość sprzedaży na złotówki.