R-base, R-dplyr, R-data.table i Python

Podstawy manipulacji danymi w tabelkach. W R (trzy wersje) oraz w Pythonie. Bo to pierwszy post z cyklu idziemy w stronę Pythona.

W dzisiejszym wpisie zajmiemy się podstawowymi operacjami na tabelach (data frame) z danymi. Dlaczego to jest ważne? Są to najczęściej wykonywane operacje w trakcie analizy danych, bo przecież:

  • dane trzeba wczytać do komputera
  • czasem zmodyfikować, na przykład policzyć wartość na podstawie dwóch lub więcej kolumn
  • wybrać określone wiersze lub określone kolumny
  • połączyć jedną tabelę z inną

Z tak przygotowanych danych można później na przykład narysować wykres czy też wrzucić je do modelu. I to jest wystarczające, na co dowodem jest pewnie z 70% moich wpisów z ostatnich 12 miesięcy (albo i więcej). Kolejne 20% wpisów to sposób pozyskania danych. 10% było pewnie o modelach wszelakich.

Osobiście uważam, że w R nauczyłem się na tyle dużo, aby przejść na kolejny poziom. W pracy są to aplikacje w Shiny czy też panele w wykorzystaniem flexdashboard (znowu: w obu przypadkach 70% pracy to manipulacja danymi w tabelkach), ale ciągnie mnie do Pythona. Gdybym zaczynał przygodę z machine learning czy data science to wybrałbym Pythona. Jednocześnie mam nadzieję, że dla osób znających jeden z języków niniejszy wpis będzie ciekawym wprowadzeniem w drugi język.

Jeśli chodzi o R to zobaczymy jak wykonać podstawowe operacje wykorzystując standardowe możliwości R (R-base), wykorzystując pakiety dplyr (wchodzący w skład ekosystemu tidyverse) oraz data.table. W przykładach dla Pythona będzie to pandas.

Dlaczego data.table? Porównanie prędkości pokazuje, że dla bardzo dużych tabel data.table jest najszybszy.

Zaczniemy od wczytania odpowiednich bibliotek. W R oczywiście:

a w Pythonie niewiele inaczej:

Za dane przykładowe posłuży nam lista państw europejskich zaczerpnięta z Wikipedii i upchnięta w plik CSV.

Wczytanie danych z CSV

W R skorzystamy z bazowej funkcji read.csv() z parametrami mówiącymi, że kolumny rozdziela średnik, a liczby całkowite od części ułamkowych kropka:

as.data.table() konwertuje nam data.frame o obiektu o typie używanym przez pakiet data.table. Zobaczmy co mamy w danych źródłowych:

Identyczny wynik uzyskamy dla r_europa_dt.

W Pythonie dane wczytamy funkcją z pakietu pandas, i od razu zobaczmy jaki jest wynik:

Jak widać wyniki są jednakowe. Super. Dla bardziej zaawansowanych pythonowców polecam ostatni wpis Mateusza Grzyba, który opowiada o tym jak poradzić sobie z dużymi danymi (mam na myśli przede wszystkim punkt drugi wpisu Mateusza).

Podsumowanie tabeli

Podsumowanie powie nam przede wszystkim o tym jak wyglądają dane, w szczególności kolumny z wartościami liczbowymi. W R dostajemy informacje o wszystkich kolumnach:

W Pythonie tylko o tych zawierających liczby:

Jak widać domyślnie Python (pandas) w inny sposób pokazuje liczby dziesiętne (zapis Xe+y oznacza X*10^Y) oraz pokazuje 50-percentyl zamiast pisania wprost, że mamy do czynienia z medianą. Wartości oczywiście są takie same. Gratisowo mamy liczbę elementów (tutaj: wierszy) i odchylenie standardowe.

Filtrowanie tabeli

czyli wybieranie wierszy spełniających określone kryteria.

Znowu zaczniemy od bazowego R (można to zrobić na wiele sposobów):

W dplyr mamy intuicyjną funkcję filter():

zaś data.table wygląda podobnie do R-base:

W każdym przypadku wynik jest identyczny. Zwróćcie uwagę, że mniej klepania jest w wersji DT (od razu podajemy nazwę kolumny), ale potrzebna była wcześniejsza konwersja typu (as.data.table() gdzieś wcześniej). Osobiście najchętniej korzystam z funkcji z dplyr – kod jest bardziej intuicyjny.

Wersja pythonowa jest podobna, chociaż łatwo zapodziać się w tych nawiasach:

Zwróćcie uwagę na liczby w pierwszej kolumnie po lewej we wszystkich powyższych przykładach. To w tym odcinku nieistotne, ale w ogólności bardzo ważne. Są to indeksy kolejnych wierszy. Wszystkie przykłady poza dplyr zachowują oryginalne indeksy przy filtrowaniu. Co więcej – R liczy indeksy od 1, a Python od 0 (jak większość języków programowania, co jest w zasadzie nienaturalne).

A jeśli potrzebujemy wybrać wiersze gdzie kryteria są w dwóch (lub więcej) kolumnach? Albo w jednej tylko wynikają z jakiejś logiki (np. większe od 5 i jednocześnie mniejsze od 10)?

Filtrowanie dwóch kolumn na raz

R-base:

R-dplyr:

i R-data.table:

wiele od siebie się nie różnią. W przypadku dplyr możemy zamiast & wstawić przecinek (czyli byłoby filter(r_europa, UE == "T", Euro != "T")) który zadziała identycznie.

Czy w Pythonie jest inaczej? Nie może być, bo w zasadzie gramatyka każdego języka programowania jest podobna. Jedyna różnica to te nieszczęsne nawiasy (które swoją drogą sprawiają, że warunki logiczne są bardziej czytelne):

Wiemy jak coś znaleźć, czas na to, aby coś policzyć:

Dodanie kolumny z obliczeń

Jaka będzie gęstość zaludnienia? Ile osób przypada na kilometr kwadratowy w danym kraju?

R-base:

R-dplyr:

W data.table nie ma specjalnego podejścia – można zastosować oba powyższe (bo dplyr działa również na obiektach typu data.table, ha!). Zatem przygotujmy sobie tylko tabelkę wynikową w odpowiednim typie:

Python jest podobny do R-base w tym przypadku:

Sortowanie

czyli zobaczmy wynik w odpowiedniej kolejności. R-dplyr:

R-data.table:

W Pythonie mamy metodę sort_values(), którą wywołujemy na obiekcie:

Filtrowanie i sortowanie w jednym

Na początek coś najbardziej (wg mnie) logicznego – R-dplyr:

Kod zapisany jest dosłownie tak, jak robimy: tabelę filtrujemy i sortujemy. W przypadku R-data.table odpowiednio zastosowane nawiasy też to tłumaczą:

W Pythonie zaś mamy kombinację: wybór wierszy w ramach nawiasów, sortowanie jako metoda na tym co wyszło:

Średnia wartośc z kolumny

Spróbujmy teraz wybrać państwa, które mają powierzchnię większą niż średnia powierzchnia kraju europejskiego. Potrzebujemy zatem poznać średnią powierzchnię.

W R najprostszy sposób to:

który jest podobny do Pythona, gdzie wywołujemy odpowiednią metodę na interesującej nas kolumnie:

Mając zapisaną wartość średnią z wybranej kolumny do oddzielnej zmiennej możemy wyszukać państw o powierzchni większej stosując

filtrowanie według zmiennej

R-base:

R-dplyr:

R-data.table:

Właściwie wszędzie jednakowo. W Pythonie też, pamiętając o nawiasach:

Rozdzielenie tabel na dwie (albo wybór kolumn)

Czasem z dużej tabeli potrzebujemy wybrać tylko konkretne kolumny. Tym sposobem rozdzielimy sobie naszą tabelę o Europie na dwie – jedną z informacjami o ludności, drugą – z informacjami o powierzchni:

R-base:

R-dplyr:

w R-data.table analogicznie do wersji bazowej:

To samo robimy dla tabelki z powierzchnią:

Python – jak zdążyliśmy się chyba przyzwyczaić – niewiele różni się od R:

Różnica zasadnicza to podwójne nawiasy kwadratowe. Takie drobiazgi są najgorsze – najtrudniej się od nich odzwyczaić…

Łączenie tabel

Załóżmy (jak w naszym przykładzie), że mamy dwie tabelki, w każdej z nich znajduje się kolumna łącząca (klucz; w naszym przykładzie jest to nazwa państwa). Możemy chcieć je z jakiegoś powodu połączyć. Kilka razy we wpisach związanymi z prezentacją danych z GUS na mapach takim kluczem łączącym był kod TERYT.

Łączenie tabel z R-base to merge():

w R-dplyr to znany z SQL join (w tym przypadku left):

a R-data.table to zgrabny zapis:

W pythonowym pandas mamy funkcję merge z odpowiednim wskazaniem kolumny-klucza:

Znamy już najważniejsze sposoby manipulacji danymi w R i Pythonie. Sposobów na to samo jest cała masa. Przy przesiadce z jednego języka na drugi najtrudniejsza jest zmiana gramatyki – te nawiasy, przecinki itd. Sama składnia jest podobna. Dla jednych czytelniejszy jest jeden język (albo w pewnym sensie wariant: czysty R jest moim zdaniem nieco mniej przejrzysty niż wzbogacony w dplyr i pipe %>%), a dla innych – drugi.

W swoim życiu pisałem w Basicu, AMOSie (to taki Basic dla Amigi, bardzo popularny swego czasu), C, PHP i teraz w R. Po drodze na studiach był też Fortran. Czytam jeszcze kilka języków. Ale to tylko języki, najważniejsze jest myślenie, sposoby rozwiązania problemów i dobrane do nich algorytmy. Reszta przychodzi sama, razem z praktyką.

1 komentarz do wpisu “R-base, R-dplyr, R-data.table i Python

  1. Dzięki za wpis – zwłaszcza za część o dplyr (właśnie jestem w trakcie „przesiadki” na dplyr). Parę uwag do data.table:
    Niezależnie od znacznej różnicy w czasie analizy danych, dużą zaletą data.table jest czas odczytu danych (funkcja fread). Czyli odczytanie danych wyglądało by jakoś tak:
    r_europa_dt <- fread("europa.csv")
    Drugi drobiazg – składnia data.table:
    "W data.table nie ma specjalnego podejścia – można zastosować oba powyższe" [do obliczenia wartości nowej kolumny] – owszem, można oba powyższe, ale jest specjalne podejście (i też chyba szybsze, choć czasem dplyr może być wygodniejszy):
    r_ue_bez_euro_dt[, Gestosc_zaludnienia := Ludnosc / Powierzchnia]
    co można łączyć też z filtrowaniem danych np:
    r_ue_bez_euro_dt[UE == "T", Gestosc_zaludnienia := Ludnosc / Powierzchnia]
    Mam nadzieję, że się przyda do ewentualnych uzupełnień.
    A tego z kolei nie znałem: r_ludnosc_dt[r_powierzchnia_dt, on="Panstwo"] a takie wygodne!
    Jeszcze raz dzięki!

Dodaj komentarz