Rozdział 8 dplyr - część I

Wiele operacji wykonywanych na ramkach danych ma bardzo podobną strukturę. Bardzo często zbiory danych łączymy, filtrujemy, rozdzielamy, grupujemy, obliczamy wybrane statystyki i wizualizujemy w poszukiwaniu istoty problemu. Wiele z tych operacji wykonywanych na ramkach danych można wykorzystać za pomocą natywnie dostępnych funkcji R. Można także skorzystać z kilku-, kilkunastu pakietów bardzo popularnych wśród analityków danych, które skracają czas obliczeń o 80% i pozwalają na zawarcie istoty problemu w zaledwie kilku liniach kodu…

Jedną z najczęściej wykorzystywanych bibliotek do analizy danych jest biblioteka dplyr (Wickham and Francois 2016), która w dużym stopniu zrewolucjonizowała analizę danych w środowisku R. Poniżej zostanie przedstawionych kilka praktycznych przykładów związanych z wykorzystaniem wybranych funkcji z tego pakietu.

Pakiet dplyr nie jest dostępny w domyślnym środowisku R, stąd też wymagana jest jego wcześniejsza, jednorazowa instalacja oraz aktywacja.

8.1 Łączenie ramek danych - left_join()

Do połączenia dwóch ramek danych na podstawie wspólnego identyfikatora można użyć natywnie dostępnej w R funkcji merge() lub skorzystać z jednej z odmian funkcji join dostępnej w bibliotece dplyr. Najczęściej stosowana komenda to left_join(), która zwraca wszystkie elementy z pierwszej ramki danych oraz wszystkie kolumny z obu łączonych ramek danych.

Spójrzmy na schemat działania tej funkcji na podstawie poniższego przykładu z dwiema ramkami danych zawierających

df1 <- data.frame(id_stacji = c(1:4,3), pomiar1 = c(2.5,1.25,2,3,2))
df2 <- data.frame(id_stacji = c(1, 3, 5), pomiar2 = c(10.2,9.6, 12.3), inne = letters[1:3])
print(df1)
##   id_stacji pomiar1
## 1         1    2.50
## 2         2    1.25
## 3         3    2.00
## 4         4    3.00
## 5         3    2.00
print(df2)
##   id_stacji pomiar2 inne
## 1         1    10.2    a
## 2         3     9.6    b
## 3         5    12.3    c

Jeśli chcemy złączyć obie ramki danych: df1 oraz df2 na podstawie kolumny id_stacji najszybciej wynik działania dostaniemy działając funkcją left_join():

library(dplyr)
left_join(df1,df2)
## Joining, by = "id_stacji"
##   id_stacji pomiar1 pomiar2 inne
## 1         1    2.50    10.2    a
## 2         2    1.25      NA <NA>
## 3         3    2.00     9.6    b
## 4         4    3.00      NA <NA>
## 5         3    2.00     9.6    b

Jak widać na powyższym przykładzie funkcja domyślnie poszukała kolumny w obu zbiorach danych o takiej samej nazwie i na tej podstawie dokonała złączenia. Jednocześnie identyfikatory stacji, które nie istniały w drugiej ramce danych zostały uzupełnione jako braki danych (NA). Jeśli chcielibyśmy uzyskać inne możliwe kombinacje możemy zastosować inne warianty rodziny funkcji join opisanej w systemie pomocy R

Brak wspólnej nazwy kolumny łączącej

Funkcja left_join() domyślnie szuka wspólnych nazw kolumn na podstawie których łączy dwie ramki danych. Jeśli nazwy kolumn po których chcemy dokonać złączenia są różne możemy:

  • odpowiednio wcześniej zunifikować te nazwy (np. za pomocą colnames())
  • lub wskazać funkcji left_join() argument by= z kolumnami po których chcemy złączać

W celu sprawdzenia takiego schematu postępowania pobierz dane z adresu http://enwo.pl/przetwarzanie/dane/przyklad1_join.Rdata i załaduj do środowiska R. W zakładce Environment powinny pojawić się 2 nowe obiekty:

  • xym - zawiera współrzędne geograficzne, wysokości stacji, międzynarodowe kody stacji i nazwy stacji meteorologicznych
  • wynik- zawiera podsumowanie dobowe dla temperatury maksymalnej, minimalnej i średniej wg depesz SYNOP z godz. 6:00 UTC
load("/home/bartosz/github/przetwarzanie/dane/przyklad1_join.Rdata")
head(xym)
##        lon      lat alt  code         name
## 1 27.95000 55.81667 133 26554 Verhnedvinsk
## 2 27.46667 55.36667 131 26643 Sarcovschina
## 3 26.31667 55.05000 209 26645      Lyntupy
## 4 28.76667 55.46667 133 26653       Polock
## 5 27.75000 54.88333 197 26657    Dokshitsy
## 6 28.70000 54.88333 174 26659        Lepel
head(wynik)
##      stacja tmax tmin tavg
## 1 Kolobrzeg  6.3  1.4  3.8
## 2  Koszalin  6.6 -2.4  2.1
## 3     Ustka  5.3  0.6  3.6
## 4      Leba  6.1  1.4  3.4
## 5  Darlowek  6.5 -0.4  2.5
## 6    Lebork  6.3 -3.9  1.7

Jak widzimy wspólnym polem w obu zbiorach danych są nazwy stacji zawarte w polach: name oraz stacja. Jeśli potraktujemy zbiór wynik jako podstawowy do którego chcemy dołączyć współrzędne geograficzne i kod WMO stacji wówczas samo wpisanie komendy left_join(xym, wynik) powinno dać błąd. Konieczne jest wskazanie nazw kolumn w zbiorze pierwszym i odpowiadającej mu nazwy kolumny w zbiorze drugim w dość nieintuicyjnej składni argumentu by= :

calosc <- left_join(wynik, xym, by = c("stacja" = "name"))
## Warning: Column `stacja`/`name` joining factors with different levels,
## coercing to character vector
head(calosc)
##      stacja tmax tmin tavg      lon      lat alt  code
## 1 Kolobrzeg  6.3  1.4  3.8 15.58333 54.18333   3 12100
## 2  Koszalin  6.6 -2.4  2.1 16.15000 54.20000  32 12105
## 3     Ustka  5.3  0.6  3.6 16.86667 54.58333   6 12115
## 4      Leba  6.1  1.4  3.4 17.53333 54.75000   2 12120
## 5  Darlowek  6.5 -0.4  2.5 16.40000 54.40000   2 12124
## 6    Lebork  6.3 -3.9  1.7 17.75000 54.55000  17 12125

Często po złączeniu dwóch ramek danych nasz nowy zbiór zawiera braki. Jeśli chcemy się ich pozbyć możemy użyć funkcji na.omit(), która usunie wszystkie rzędy z wartościami NA.

calosc <- na.omit(calosc)

W ten sposób nasza baza danych powinna zawierać tylko poprawne wartości. Możemy w szybki sposób zwizualizować nasz zbiór danych za pomocą wcześniej poznanej funkcji plot, gdzie jako współrzędnej x i y podamy wartości odpowiednio długości (lon) i szerokości geograficznej (lat). Możemy także dodać dowolną informację w postaci tekstowej, np. temperaturę minimalną za pomocą funkcji text(). Działa ona analogicznie jak funkcja plot() przy czym konieczne jest podanie dodatkowego argumentu labels=, który ma być wyświetlony we wskazanych koordynatach. Jeśli chcemy wyświetlić dodatkowo nazwy stacji wówczas możemy zastosować zarówno poniższy kod:

plot(x = calosc$lon, y = calosc$lat); text(x = calosc$lon, y = calosc$lat, labels=calosc$stacja)

  • Można dodatkowo dodać fragment kodu, który doda kontury krajów. Jeśli nie mamy nigdzie w pobliżu odpowiednio przygotowanej warstwy w postaci pliku GISowego, możemy wykorzystać pakiet mapdata, w którym znajdują się podstawowe dane z granicami administracyjnymi dla całego świata:
#install.packages("mapdata") # jesli chcemy uzyc po raz pierwszy musimy ja zainstalowac
library(mapdata) # aktywacja pakietu
plot(x = calosc$lon, y = calosc$lat)
text(x = calosc$lon, y = calosc$lat, labels=calosc$stacja) # rysujemy to co wczesniej
map("world", add=TRUE, lwd=2) # wazne aby ustawic opcje 'add'; reszta parametrow jak dla funkcji plot()
text(x = calosc$lon, y = calosc$lat+0.2, labels=calosc$tmin, col="blue") # dodajmy jeszcze np. temp. min nieco powyzej etykiet

8.1.1 Łączenie danych po dacie

W katalogu http://enwo.pl/przetwarzanie/dane/opady/ znajdują się pliki do dzisiejszego ćwiczenia. Zawierają one dobowe sumy opadów atmosferycznych z kilku wybranych polskich stacji. Każdy z plików ma taką samą strukturę zawierającą w kolejnych kolumnach: numer stacji, nazwę stacji, datę oraz sumę opadu. Wartości są rozdzielone znakami tabulacji, a miejsca dziesiętne są oddzielone kropkami.

## "V1" "V2"    "V3"    "V4"
## 249180120    "SKOCZÓW"   19501101    2.1
## 249180120    "SKOCZÓW"   19501105    2.1
## 249180120    "SKOCZÓW"   19501107    2.9
## 249180120    "SKOCZÓW"   19501108    0.5
## 249180120    "SKOCZÓW"   19501111    0.6
## 249180120    "SKOCZÓW"   19501114    12.6
## 249180120    "SKOCZÓW"   19501115    12.3
## 249180120    "SKOCZÓW"   19501116    0.6
## 249180120    "SKOCZÓW"   19501117    0.4

Zwróć uwagę, że pliki zawierają informację jedynie o dniach, w których wystąpiły opady na danej stacji (jeśli opadu w danym dniu nie było wówczas jest on pominięty). Naszym celem będzie utworzenie jednolitej, pełnej bazy, ze wszystkimi datami w zakresie występujących dat (bez względu na to czy padało), a wartości opadów dla każdej kolejnej stacji będą umieszczane w kolejnych kolumnach, jak na poniższym schemacie:

##         daty brenna chalupki cieszyn
## 1 1951-01-01     NA     12.2     7.8
## 2 1951-01-02     NA      4.8     9.6
## 3 1951-01-03    0.1      0.5     4.3
## 4 1951-01-04    0.5      0.9     7.1
## 5 1951-01-05    5.2       NA     4.0
## 6 1951-01-06   12.9       NA     3.3

Dla wielu osób pracujących dotychczas w arkuszach kalkulacyjnych taka postać bazy danych jest najbardziej intuicyjna w obsłudze.

Zanim przystąpisz do tworzenia bazy danych utwórz katalog opady (np. na pulpicie) i zapisz do niego pliki znajdujące się pod adresem http://enwo.pl/przetwarzanie/dane/opady. Następnie ustaw katalog roboczy RStudio aby pliki były dostępne bez konieczności wpisywania pełnej ścieżki.

W poniższej tabeli wypisano nazwy stacji oraz liczbę wierszy w każdym z plików. Dlaczego nie możemy połączyć plików od razu do postaci macierzy / ramki danych za pomocą komendy cbind() / cbind.data.frame()?

##    1603 BRENNA.
##    1198 CHALUPKI.
##    1198 CHAŁUPKI.
##    2053 CIESZYN.
##    1270 GOCZALKOWICE-ZDROJ.
##    1270 GOCZAŁKOWICE-ZDRÓJ.
##    1921 ISTEBNA-MLODAGORA.
##    1921 ISTEBNA-MŁODAGÓRA.
##    1947 ISTEBNA-STECOWKA.
##    1947 ISTEBNA-STECÓWKA.
##    1492 JAWISZOWICE.
##    1717 RUDZICA.
##    1763 SKOCZOW.
##    1763 SKOCZÓW.
##    1493 SZCZYRK.
##    1233 TRZEMESNIA.
##    1233 TRZEMEŚNIA.
##    1856 USTROŃ-RÓWNICA-WIEŚ.
##    1856 USTRON.
##    2032 WAPIENICA.
##    1237 WARSZOWICE.
##    1817 WISŁA-CENTRUM.
##    1268 WISLA-GLEBCE.
##    1268 WISŁA-GŁĘBCE.
##     695 WISŁAWIELKA.
##   39051

Do połączenia 2 ramek danych na podstawie wspólnego identyfikatora można użyć natywnie dostępnej w R funkcji merge() lub skorzystać z pakietu plyr, który oferuje nieco bardziej wydajny algorytm łączenia baz danych. W celu przetestowania jego funkcjonalności niezbędne będzie wykonanie poniższych kroków:

  1. Stwórz obiekt data z datami od 1. stycznia 1950 r. do 31. grudnia 1960 r.
  2. Stwórz ramkę danych wynik z jedną kolumną nazwaną data, w której będą przechowywane wartości dat (z poprzedniego punktu).
  3. Wczytaj pierwszy (dowolny) plik z danymi opadowymi i nazwij go dane. Za pomocą funkcji colnames() nazwij w intuicyjny sposób kolumny (np.: “id”,“stacja”,“data”,“opad”).
  4. Kolumnę zawierającą datę przekonwertuj do klasy Date, aby komputer nie miał problemów ze zrozumieniem, że wartości w tej kolumnie przechowują czas a nie wartości liczbowe.
  5. Złącz wynikową ramkę danych z wczytanym plikiem za pomoca funkcji left_join() z pakietu dplyr.
  6. Ponów kroki 3-5 wczytując kolejny plik do istniejącej wynikowej ramki danych

Zadanie domowe

Po opanowaniu złączania ramek danych kontynuuj treść poleceń 1-6 poprzez stworzenie pętli for, która będzie wczytywać kolejne pliki z danymi opadowymi oraz dopisywać do wynikowej ramki danych. Finalny wynik zapisz do pliku arkuszu kalkulacyjnego z rozszerzeniem .xls.