Dziś wracamy do ramek danych i uczymy się tworzyć oraz usuwać kolumny i wiersze. Najpierw jednak przyjrzymy się operatorowi, z którego do tej pory korzystaliśmy bez głębszego zastanowienia.
O co chodzi z operatorem %>%
Z przykładów w poprzednich odcinkach wiemy, że za pomocą dostarczonego przez Tidyverse operatora %>%
(zwanym fajką, od angielskiego pipe) możemy przekazywać ramkę danych między kolejnymi poleceniami. Cóż, było to uproszczenie.
W rzeczywistości każda z poznanych komend (mutate
, summarise
, arrange
) przyjmuje ramkę danych jako pierwszy parametr. Za pomocą operatora %>%
przekazywaliśmy ten parametr w sposób niejawny.
Innymi słowy, polecenie z operatorem %>%
ramka %>% funkcja(parametr)
jest równoważne następującemu poleceniu bez tego operatora
funkcja(ramka, parametr)
Wróćmy do przykładu z pierwszego odcinka i pokolorujmy każdy wiersz.
Teraz spójrzmy na analogiczne wyrażenie bez operatora %>%
Czy też macie wrażenie, że widzimy tu złożoną formułę Excela?
Oba wyrażenia są sobie równoważne*, natomiast pierwsze jest bez wątpienia o wiele bardziej czytelne dla człowieka. Pozwala śledzić przepływ danych z góry na dół a nie od środka wielkiego wyrażenia ku jego obrzeżom. Komputerowi jest wszystko jedno a nam będzie wygodniej.
„Fajka” okazała się operatorem tak przydatnym i wygodnym, że po latach trafiła w bardzo podobnej postaci do języka R, gdzie występuje jako „|>
”. Ten operator, choć podobny, ma nieco inną semantykę, więc w Poradniku będziemy konsekwentnie stosować „%>%
”.
ad *) zastrzeżenie dla programistów – kolejność wykonania operacji będzie się różnić, jednak w praktyce nie ma to znaczenia
Odwołania do wierszy i kolumn
Do tej pory język R poznawaliśmy w sposób dość niestandardowy – dając nura na głęboką wodę, by od razu dostrzec korzyści płynące z pracy z ramką danych. Wróćmy bliżej brzegu i poznajmy składnię kilku prostszych poleceń:
Sprawdzamy liczbę wierszy i kolumn w ramce danych
nrow(otomoto)
ncol(otomoto)
Alternatywnie:
otomoto %>% nrow()
albo…
otomoto %>% nrow
… ponieważ przy wywołaniach funkcji bez parametrów możemy pominąć nawiasy (programistom włosy właśnie stanęły dęba).
Wyświetlamy pierwsze 10 i ostatnie 5 wierszy ramki danych
head(otomoto, n=10)
tail(otomoto, n=5)
Wyświetlamy wszystkie wiersze oprócz pierwszych 15
tail(otomoto, n = -15)
Wyświetlamy pierwszy i tysięczny wiersz ramki danych
otomoto[1,]
otomoto[1000,]
Wyświetlamy wiersze od pierwszego do tysięcznego
otomoto[1:1000,]
Wektor, w którego skład wejdą teksty z kolumny „miasto” wszystkich wierszy (w Poradniku nie mówiliśmy jeszcze niczego o listach i wektorach, ale pokazuję, że odwoływać można się nie tylko do wierszy, ale i kolumn)
otomoto$city
otomoto[,8]
Trzy sposoby na sprawdzenie przebiegu auta z siódmego wiersza
otomoto[7,"mileage"]
otomoto[7,5]
otomoto[7,]$mileage
Usuwanie kolumn
Do usuwania niepotrzebnych kolumn służy komenda select
. Oto przykładowe warianty:
Pozostawienie jedynie miasta i ceny
otomoto %>% select(city, price)
Usunięcie miasta i województwa
otomoto %>% select(-city, -province)
Pozostawienie kolumn z literą „a” w nazwie
otomoto %>% select(contains("a"))
Pozostawienie kolumn zaczynających się na literę „m”
otomoto %>% select(starts_with("m"))
Pozostawienie wybranych kolumn, począwszy od kolumny z rocznikiem do kolumny z rodzajem paliwa
otomoto %>% select(year:fuel)
Select jest użyteczny w serii komend, bo zwraca zmodyfikowaną ramkę z kopią danych, wejściowa ramka danych pozostaje niezmieniona. Jeśli chcemy zmodyfikować istniejącą ramkę danych i trwale usunąć z niej kolumnę, możemy skorzystać z wyrażeń
ramka <- ramka %>% select(-kolumna)
albo
ramka$kolumna <- NULL
Filtrowanie wierszy
Filtrowanie realizujemy komendą filter
, której argumentem jest wyrażenie logiczne. Aby nie wchodzić dziś w szczegóły dotyczącej składni wyrażeń, ograniczymy się do prostego przykładu.
otomoto %>% filter(fuel == "CNG")
Aby wyeliminować z ramki danych zduplikowane wiersze, możemy użyć funkcji distinct
lub unique
. Ich działanie jest bardzo podobne, ale różnica istnieje. Distinct przenumeruje wiersze, zaś unique pozostawi informację o tym, w którym wierszu dana wartość wystąpiła po raz pierwszy.
otomoto %>%
filter(fuel == "CNG") %>%
select(mark, model) %>%
distinct()
Tworzenie nowych kolumn
Jednym z wielu sposobów na dodanie nowych kolumn do ramki danych jest komenda mutate
, w której podajemy nazwę i zawartość nowych kolumn.
otomoto %>% mutate(zrodlo="otomoto", cena_w_milionach = price/1000000)
Także tutaj mutate stworzy kopię ramki danych. Istniejącą ramkę możemy trwale modyfikować przypisując wartość nieistniejącej dotąd kolumnie:
otomoto$cena_w_milionach <- otomoto$price/1000000
Przy wielu okazjach przydatna będzie funkcja ifelse
, pozwalająca na przypisanie wartości zależnej od wyrażenia logicznego. Jeśli docelowych wartości jest więcej, możemy użyć konstrukcji case_when
.
otomoto$stolica <- ifelse(otomoto$city=="Warszawa", TRUE, FALSE)
otomoto$czy_drogo <- case_when(
otomoto$price<10000 ~ "tanio",
otomoto$price<100000 ~ "tak sobie",
.default = "drogo" )
Do zmiany kolejności kolumn można użyć komendy relocate
:
otomoto %>% relocate(czy_drogo, .after = cena_w_milionach)
Tworzenie nowych wierszy
Jeśli potrzebujemy zmodyfikować „w locie” ramkę danych, możemy użyć komendy add_row
, w której parametrach przypiszemy zmiennym (kolumnom) nowego wiersza odpowiednie wartości.
otomoto %>%
add_row(mark = "FSO", model = "Syrena", price = 10) %>%
tail(n=3)
Możemy też łączyć wiele ramek danych o „pasujących” kolumnach w jedną większą ramkę za pomocą funkcji rbind
.
otomotopodwojone <- rbind(otomoto,otomoto)
Sortowanie wierszy
Do sortowania wierszy w ramce danych służy komenda arrange
. Jeśli podamy w niej kilka różnych nazw kolumn, będą one użyte według malejącej ważności (czyli sortowanie względem pierwszej podanej kolumny, dla równych wartości względem drugiej itd). Domyślnie używany jest porządek rosnący, odwrócimy go za pomocą słowa kluczowego desc
otomoto %>% arrange (mark, desc(model))
Uwaga – nie omówiliśmy jeszcze szczególnego typu danych, jakim są typy kategoryczne (factor
). Typu tego używamy do kolumn przechowujących wartości z zamkniętego słownika – przykładem może być „kraj”, „płeć” czy „stan cywilny”. Aby oszczędzać pamięć, ramka danych przechowuje – zamiast napisu – indeks pozycji w słowniku. A słownik wcale nie musi być posortowany – z tego powodu sortowanie wg kolumny o typie kategorycznym wymaga kilku linii kodu więcej. Także łączenie dwóch ramek danych z danymi kategorycznymi o różnych dziedzinach wymusi dodatkowe operacje. Do tego tematu wrócimy w przyszłości.
Wypróbuj te komendy samodzielnie
Wszystkie operacje z dzisiejszego odcinka czekają na wypróbowanie w tym pliku dostępnym na GitHubie.
Dwa słowa dla programistów – jeśli uważacie, że składnia R jest dziwna, niekonsekwentna a miejscami nawet nielogiczna, to… macie rację. Niestety, za projektowanie języka wzięli się statystycy i wyszło jak wyszło. Mimo tego uważam, że zalety języka R przeważają nad jego wadami, więc Poradnik jedzie dalej. Za tydzień napiszę o obliczeniach agregujących dane z wielu wierszy.
O autorze: zawodowy programista od 2003 roku, pasjonat bezpieczeństwa informatycznego. Rozwijał systemy finansowe dla NBP, tworzył i weryfikował zabezpieczenia bankowych aplikacji mobilnych, brał udział w pracach nad grą Angry Birds i wyszukiwarką internetową Microsoft Bing.