{data.table}


Przygotujmy sobie przestrzeń do pracy. Najpierw ustawmy odpowiednią ścieżkę poleceniemsetwd(), a następnie wczytajmy (jeśli nie mamy zainstalujmy) niezbędne pakiety. W kolejnym kroku wczytajmy dane, które posłużą nam do nauki kolejnych poleceń (funkcji) w R tym razem z pakietu data.table

Dlaczego warto rozważyć nauczenie się data.table? Powodów w sieci jest sporo.

Wydajność i szybkość data.table jest jednym z najszybszych narzędzi do przetwarzania danych w R, często wielokrotnie szybszym od base R czy dplyr. Operacje na dużych zbiorach danych (miliony wierszy) wykonują się błyskawicznie dzięki optymalizacji na poziomie C.

Efektywność pamięciowa Pakiet modyfikuje dane “w miejscu” (by reference), co oznacza, że nie tworzy niepotrzebnych kopii tabeli. To ogromna zaleta przy pracy z dużymi danymi - oszczędzasz pamięć RAM i czas.

Zwięzła składnia Choć na początku może wydawać się nietypowa, składnia DT[i, j, by] (gdzie filtrujemy, wybieramy/transformujemy i grupujemy) jest niezwykle ekspresywna. Złożone operacje można zapisać w jednej linijce kodu.

Wszechstronność data.table łączy w sobie funkcjonalność filtrowania, grupowania, agregacji, łączenia tabel i reshapingu danych. To kompleksowe narzędzie do praktycznie wszystkich operacji na danych.

Przydatność w przemyśle Wiele firm, szczególnie pracujących z big data, preferuje data.table właśnie ze względu na wydajność. To ceniona umiejętność na rynku pracy. Stabilność i wsparcie społeczności

Pakiet jest dojrzały, aktywnie rozwijany i ma świetną dokumentację oraz aktywną społeczność użytkowników. Warto dodać, że data.table i tidyverse nie wykluczają się wzajemnie - wiele osób używa obu w zależności od potrzeb projektu.


Wczytanie danych


Przypomnijmy sobie funkcję do wczytywania danych w formacie .csv z pakietu data.table.


# install.packages('data.table')
library(data.table)

loty <- fread(
  "https://raw.githubusercontent.com/Rdatatable/data.table/master/vignettes/flights14.csv"
  )

str(loty)
## Classes 'data.table' and 'data.frame':   253316 obs. of  11 variables:
##  $ year     : int  2014 2014 2014 2014 2014 2014 2014 2014 2014 2014 ...
##  $ month    : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ day      : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ dep_delay: int  14 -3 2 -8 2 4 -2 -3 -1 -2 ...
##  $ arr_delay: int  13 13 9 -26 1 0 -18 -14 -17 -14 ...
##  $ carrier  : chr  "AA" "AA" "AA" "AA" ...
##  $ origin   : chr  "JFK" "JFK" "JFK" "LGA" ...
##  $ dest     : chr  "LAX" "LAX" "LAX" "PBI" ...
##  $ air_time : int  359 363 351 157 350 339 338 356 161 349 ...
##  $ distance : int  2475 2475 2475 1035 2475 2454 2475 2475 1089 2422 ...
##  $ hour     : int  9 11 19 7 13 18 21 15 15 18 ...
##  - attr(*, ".internal.selfref")=<externalptr>


I pamiętajmy, że mamy również ramki “zaszyte” w pakietach

# install.packages('bikeshare14')
library(bikeshare14)
data(batrips) # zbiór anonimowych danych dotyczących wypożyczeń rowerów w rejonie Zatoki San Francisco z roku 2014
# ?batrips
str(batrips)
## 'data.frame':    326339 obs. of  11 variables:
##  $ trip_id          : int  139545 139546 139547 139549 139550 139551 139552 139553 139554 139555 ...
##  $ duration         : int  435 432 1523 1620 1617 779 784 721 624 574 ...
##  $ start_date       : POSIXct, format: "2014-01-01 00:14:00" "2014-01-01 00:14:00" ...
##  $ start_station    : chr  "San Francisco City Hall" "San Francisco City Hall" "Embarcadero at Sansome" "Steuart at Market" ...
##  $ start_terminal   : int  58 58 60 74 74 74 74 74 57 57 ...
##  $ end_date         : POSIXct, format: "2014-01-01 00:21:00" "2014-01-01 00:21:00" ...
##  $ end_station      : chr  "Townsend at 7th" "Townsend at 7th" "Beale at Market" "Powell Street BART" ...
##  $ end_terminal     : int  65 65 56 39 39 46 46 46 68 68 ...
##  $ bike_id          : int  473 395 331 605 453 335 580 563 358 365 ...
##  $ subscription_type: chr  "Subscriber" "Subscriber" "Subscriber" "Customer" ...
##  $ zip_code         : chr  "94612" "94107" "94112" "92007" ...
batrips <- data.table(batrips)


Jeśli innymi metodami wczytujemy dane, to otoczmy wówczas nasz obiekt funkcją DT <- data.table(df), bądź DT <- as.data.table(df).


Uwagi Ogólne


Jak zrobić obiekt data.table’owy from scratch? Podobnie jak w przypadku data.frame’a!


data.frame - przypomnienie

df <- data.frame(year = rep(x = 2014, times = 5),
                 origin = c(rep("JFK",3), "BOS", "LAX"),
                 hour = c(3L,17L,4L,9L,6L))
str(df)
## 'data.frame':    5 obs. of  3 variables:
##  $ year  : num  2014 2014 2014 2014 2014
##  $ origin: chr  "JFK" "JFK" "JFK" "BOS" ...
##  $ hour  : int  3 17 4 9 6

data.table - bardzo analogicznie

DT <- data.table(year = rep(2014, 5),
                 origin = c(rep("JFK",3), "BOS", "LAX"),
                 hour = c(3L,17L,4L,9L,6L))
str(DT)
## Classes 'data.table' and 'data.frame':   5 obs. of  3 variables:
##  $ year  : num  2014 2014 2014 2014 2014
##  $ origin: chr  "JFK" "JFK" "JFK" "BOS" ...
##  $ hour  : int  3 17 4 9 6
##  - attr(*, ".internal.selfref")=<externalptr>

Syntax


Tak jak między nawiasami w data.frame mieliśmy sposobność manipulowania dwoma wymiarami tabeli (wiersze i kolumny), tak w data.table otrzymujemy dostęp do 3. wymiaru. Dobrze podsumowuje to grafika poniżej.


“Source: DataCamp.com”
“Source: DataCamp.com”


Jako, że data.table to też data.frame, działają na obiekcie wszystkie znane już Państwu podstawowe funkcje, np. do badania wielkości zbioru…


NROW(DT) # zauważyli Państwo, że działa z wielkich liter? Ale to wyjątki
## [1] 5
NCOL(DT)
## [1] 3
dim(DT)
## [1] 5 3


Różnice? Choćby taka, że w data.table nigdy nie zadziała…


rownames(DT) <- paste0("lot :", 1:5); DT
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
## 4:  2014    BOS     9
## 5:  2014    LAX     6

a tu owszem…

rownames(df) <- paste0("lot :", 1:5); df
##        year origin hour
## lot :1 2014    JFK    3
## lot :2 2014    JFK   17
## lot :3 2014    JFK    4
## lot :4 2014    BOS    9
## lot :5 2014    LAX    6


Wiersze


A wyciąganie przez warunki logiczne? Rozpatrzmy kilka przykładów…


DT[origin == "JFK"] # nie zadziała na ramce df
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
DT[origin == "JFK",] # nie zadziała na ramce df
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
df[df$origin == "JFK"] # zadziałało na df
##        year origin hour
## lot :1 2014    JFK    3
## lot :2 2014    JFK   17
## lot :3 2014    JFK    4
## lot :4 2014    BOS    9
## lot :5 2014    LAX    6
DT[DT$origin == "JFK"] # i na DT
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
df[df$origin == "JFK",] # zadziałało na df
##        year origin hour
## lot :1 2014    JFK    3
## lot :2 2014    JFK   17
## lot :3 2014    JFK    4
DT[DT$origin == "JFK",] # i na DT
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
DT[origin == "JFK" & hour != 3L] # żadnych przecinków jak w funkcji filter() w dplyr, tylko &
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK    17
## 2:  2014    JFK     4
DT[DT$origin == "JFK" & DT$hour != 3L, ] # zadziała na df, po wskazaniu przecinkiem, że działamy n wierszach
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK    17
## 2:  2014    JFK     4
df[df$origin == "JFK" & df$hour != 3L, ]
##        year origin hour
## lot :2 2014    JFK   17
## lot :3 2014    JFK    4


Pamiętamy jak wyciągaliśmy wiersze z df’a? Działa tak samo na DT, a nawet lepiej…


# spróbujmy df[3:4]
df[3:4, ] # dopiero zadziała
##        year origin hour
## lot :3 2014    JFK    4
## lot :4 2014    BOS    9
DT[3:4] # a tu i tak 
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     4
## 2:  2014    BOS     9
DT[3:4,] # i siak
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     4
## 2:  2014    BOS     9
DT[-(4:5)] # rezygnacja z dwóch ostatnich wierszy, zadziała również na df'ie
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
DT[!(4:5)] # rezygnacja z dwóch ostatnich wierszy
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
# ale np. 
df[!(4:5)] # ?
## data frame with 0 columns and 5 rows


.N to specjalny znak zwracający liczbę wierszy, gdy jest wstawiony w sekcji i (co to sekcja i? Rysunek powyżej)


nrow(DT) # liczba wierszy
## [1] 5
DT[5] # wiersz piąty
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    LAX     6
DT[.N] # wiersz piąty
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    LAX     6
DT[1:(.N-2)] # bez dwóch ostatnich wierszy
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4


Helper Functions


%like& służy do wyszukiwania “wzoru”, który spełniony jest w wartości danego wiersza, zobaczmy…

“Source: DataCamp.com”
“Source: DataCamp.com”


DT[origin %like% "FK"]
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
DT[origin %like% "^J"] # ^ - początek string'a
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
DT[origin %like% "K$"] # ^ - koniec string'a
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
# z użyciem wyrażeń regularnych
DT[grepl("FK", origin)]
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4


%between% służy do wyciągania ze zmiennej numerycznej zakresu wartości z zakresu

“Source: DataCamp.com”
“Source: DataCamp.com”


DT[hour %between% c(3, 6)]
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK     4
## 3:  2014    LAX     6
# inaczej
DT[hour  >= 3 & hour <= 6]
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK     4
## 3:  2014    LAX     6


%chin% służy do wyciągania wartości z zakresu zmiennych w typie character, szybsza niż %in%

“Source: DataCamp.com”
“Source: DataCamp.com”


DT[origin %chin% c("JFK", "BOS")]
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
## 4:  2014    BOS     9
# szybsza niż 
DT[origin %in% c("JFK", "BOS")]
##     year origin  hour
##    <num> <char> <int>
## 1:  2014    JFK     3
## 2:  2014    JFK    17
## 3:  2014    JFK     4
## 4:  2014    BOS     9



Kolumny


Przycinanie zbiorów do niezbędnych kolumn…


DT[, c("origin", "hour")] # operacja po przecinku
##    origin  hour
##    <char> <int>
## 1:    JFK     3
## 2:    JFK    17
## 3:    JFK     4
## 4:    BOS     9
## 5:    LAX     6
# w data.frame tak samo byśmy wyciągnęli

# ciekawostka, gdy wyciągamy tylko jedną kolumnę wówczas wynik będzie
str(DT[,"hour"]) # -> ‘data.table’ and 'data.frame'
## Classes 'data.table' and 'data.frame':   5 obs. of  1 variable:
##  $ hour: int  3 17 4 9 6
##  - attr(*, ".internal.selfref")=<externalptr>
# w data.frame?
str(df[, "hour"]) # -> vector
##  int [1:5] 3 17 4 9 6
# jednak 
str(DT[,hour]) # -> vector
##  int [1:5] 3 17 4 9 6
# chcąc uniknąć zamiany na vector i pozostawić nowy obiekt jako data.table?
str(DT[, list(hour)])  # -> ‘data.table’ and 'data.frame'
## Classes 'data.table' and 'data.frame':   5 obs. of  1 variable:
##  $ hour: int  3 17 4 9 6
##  - attr(*, ".internal.selfref")=<externalptr>
# działa również wyciąganie kolumn przez indeks
DT[, c(2,3)]
##    origin  hour
##    <char> <int>
## 1:    JFK     3
## 2:    JFK    17
## 3:    JFK     4
## 4:    BOS     9
## 5:    LAX     6
#oraz
cols <- c("year", "origin")
DT[, ..cols]  # Dwie kropki pozwalają wybrać kolumny z listy
##     year origin
##    <num> <char>
## 1:  2014    JFK
## 2:  2014    JFK
## 3:  2014    JFK
## 4:  2014    BOS
## 5:  2014    LAX


DT[, -c("year")] # nie zadziała: df[, -c("year")]
##    origin  hour
##    <char> <int>
## 1:    JFK     3
## 2:    JFK    17
## 3:    JFK     4
## 4:    BOS     9
## 5:    LAX     6
# pamiętamy jak zrobić to klasycznie w data.frame?
df[, -c(1)] # zadziała
##        origin hour
## lot :1    JFK    3
## lot :2    JFK   17
## lot :3    JFK    4
## lot :4    BOS    9
## lot :5    LAX    6
df[ ,!(colnames(df) == "origin")] # ale to jakaś rzeźba
##        year hour
## lot :1 2014    3
## lot :2 2014   17
## lot :3 2014    4
## lot :4 2014    9
## lot :5 2014    6
df[, -which(names(df) == "origin")] # jeszcze gorsza rzeźba
##        year hour
## lot :1 2014    3
## lot :2 2014   17
## lot :3 2014    4
## lot :4 2014    9
## lot :5 2014    6
# albo
DT[, !c("year")]# nie pyknie: df[, !c("year")]
##    origin  hour
##    <char> <int>
## 1:    JFK     3
## 2:    JFK    17
## 3:    JFK     4
## 4:    BOS     9
## 5:    LAX     6


Proste podsumowywanie zmiennych..


DT[, unique(year)]
## [1] 2014
DT[, mean(hour)]
## [1] 7.8
# w konwencji data.frame'owej?
mean(df[, "hour"])
## [1] 7.8
# lub 
mean(df$hour)
## [1] 7.8


Wyciąganie kolumn z wraz z nadawaniem im nowych nazw


DT[, list(year, airport.abrevation = origin)]
##     year airport.abrevation
##    <num>             <char>
## 1:  2014                JFK
## 2:  2014                JFK
## 3:  2014                JFK
## 4:  2014                BOS
## 5:  2014                LAX


i teraz, w data.table, aby uniknąć ciągłego wpisywania części dotyczącej kolumn w listę, zaproponowano otaczanie tej części kodu .() (to samo co list())
Używając zamiast funkcji tworzącej wektor c()własnie .() oszczędzamy sobie konieczności umieszczania nazw zmiennych w cudzysłowy "..."


# czyli zamiast
DT[, c("origin", "hour")] 
##    origin  hour
##    <char> <int>
## 1:    JFK     3
## 2:    JFK    17
## 3:    JFK     4
## 4:    BOS     9
## 5:    LAX     6
# po prostu
DT[, .(origin, hour)] 
##    origin  hour
##    <char> <int>
## 1:    JFK     3
## 2:    JFK    17
## 3:    JFK     4
## 4:    BOS     9
## 5:    LAX     6



Wiersze + kolumny


DT[origin == "JFK", mean(hour)] # vector
## [1] 8
# zadziała ponieważ najpierw realizowany jest warunek na wiersze,
# a nasępnie z tego output't wyciągana jest średnia godzinowa


Ile lotów miało swój początek w USA na JFK airport?


DT[origin == "JFK", .N] # <- przykład użycia .N w miejscu dotyczącym kolumn
## [1] 3
# przypominająco, 'data.frame way'
nrow(df[df$origin == "JFK", ])
## [1] 3


Nowa zmienna w sekcji j, przykład


# użyjmy jednego z wczytanych wcześniej zbiorów
batrips <- data.table(batrips)
batrips$czas_wyp <- batrips[, difftime(end_date, start_date, units = "min")]


Można rysować w sekcji j


batrips[start_station == "Townsend at 7th" & duration < 500, 
        hist(duration)]

## $breaks
##  [1]  50 100 150 200 250 300 350 400 450 500
## 
## $counts
## [1]   28   15  792 2042  920  314  314  497  538
## 
## $density
## [1] 1.025641e-04 5.494505e-05 2.901099e-03 7.479853e-03 3.369963e-03
## [6] 1.150183e-03 1.150183e-03 1.820513e-03 1.970696e-03
## 
## $mids
## [1]  75 125 175 225 275 325 375 425 475
## 
## $xname
## [1] "duration"
## 
## $equidist
## [1] TRUE
## 
## attr(,"class")
## [1] "histogram"



Grupowanie


Załóżmy, że mamy taki problem: Ile użyć miały poszczególne stacje?


stacje_uzycie <- batrips[, .N, by = "start_station"]
head(stacje_uzycie, 7)
##                           start_station     N
##                                  <char> <int>
## 1:              San Francisco City Hall  2145
## 2:               Embarcadero at Sansome 12879
## 3:                    Steuart at Market 11579
## 4:                        5th at Howard  6297
## 5: Harry Bridges Plaza (Ferry Building) 15536
## 6:                      Beale at Market  7978
## 7:                Embarcadero at Folsom  6748


btw


stacje_uzycie <- batrips[, .N, by = .(start_station)] # jest równoważne poprzedniemu zapisowi

# ale używając tej konwencji możemy zmienić nazwę zmiennej grupującej
stacje_uzycie <- batrips[, 
                         .(Liczba.wyjazdow = .N), 
                         by = .(Stacja = start_station)]
head(stacje_uzycie, 7)
##                                  Stacja Liczba.wyjazdow
##                                  <char>           <int>
## 1:              San Francisco City Hall            2145
## 2:               Embarcadero at Sansome           12879
## 3:                    Steuart at Market           11579
## 4:                        5th at Howard            6297
## 5: Harry Bridges Plaza (Ferry Building)           15536
## 6:                      Beale at Market            7978
## 7:                Embarcadero at Folsom            6748


Elastyczność sekcji by


stacje_uzycie_miesieczne <- batrips[, 
                                    .N, 
                                    by = .(start_station,
                                           mon = month(start_date))] # nie było tej zmiennej, zrobiliśmy ją on-the-fly
head(stacje_uzycie_miesieczne, 7)
##                           start_station   mon     N
##                                  <char> <int> <int>
## 1:              San Francisco City Hall     1   193
## 2:               Embarcadero at Sansome     1   985
## 3:                    Steuart at Market     1   813
## 4:                        5th at Howard     1   402
## 5: Harry Bridges Plaza (Ferry Building)     1  1217
## 6:                      Beale at Market     1   537
## 7:                Embarcadero at Folsom     1   548


Inny przykład. Średni czas użycia roweru per każda stacja początkowa


stacje_srednia <- batrips[, 
                          .(mean_duration = mean(duration)),
                          by = "start_station"]
head(stacje_srednia, 6)
##                           start_station mean_duration
##                                  <char>         <num>
## 1:              San Francisco City Hall     1893.9361
## 2:               Embarcadero at Sansome     1418.1822
## 3:                    Steuart at Market      956.9007
## 4:                        5th at Howard      845.0584
## 5: Harry Bridges Plaza (Ferry Building)     1516.3534
## 6:                      Beale at Market      856.6453


Średni czas użycia i liczba wypożyczeń rowerów per każda stacja początkowa i końcowa.


stacje_srednia <- batrips[, 
                          .(mean_duration = mean(duration), 
                            total_trips = .N), 
                          by = .(start_station, end_station )]
head(stacje_srednia, 6)
##                           start_station
##                                  <char>
## 1:              San Francisco City Hall
## 2:               Embarcadero at Sansome
## 3:                    Steuart at Market
## 4:                    Steuart at Market
## 5:                        5th at Howard
## 6: Harry Bridges Plaza (Ferry Building)
##                                      end_station mean_duration total_trips
##                                           <char>         <num>       <int>
## 1:                               Townsend at 7th      678.6364         121
## 2:                               Beale at Market      651.2367         545
## 3:                            Powell Street BART      883.9379         145
## 4:                         Washington at Kearney     1553.3333           9
## 5: Yerba Buena Center of the Arts (3rd @ Howard)     1810.8305          59
## 6:                             Steuart at Market     3576.1667          60


Może to jest pomysł na projekt zaliczeniowy? Zdobyć (ściągnąć?) dane o warszawskim Veturilo i ocenić czy rozlokowanie wszystkich stacji ma sens ekonomiczny? Albo jak zmieniło się użycie stacji w pandemii? Sporo analiz już jest w sieci, zatem prośba o nie kopiowanie ich jeden do jeden, tylko twórcze wykorzystanie i adaptację. Rzucam w eter…



Chaining


Taka sytuacja w postaci uogólnionej x[...][...][...]


# takie operacje
(krok_1 <- batrips[duration > 3600])
##        trip_id duration          start_date     start_station start_terminal
##          <int>    <int>              <POSc>            <char>          <int>
##     1:  139559     3691 2014-01-01 00:32:00 Steuart at Market             74
##     2:  139560     3793 2014-01-01 00:32:00 Steuart at Market             74
##     3:  139561     3788 2014-01-01 00:32:00 Steuart at Market             74
##     4:  139562     3626 2014-01-01 00:33:00 Steuart at Market             74
##     5:  139587    52577 2014-01-01 02:15:00     Howard at 2nd             63
##    ---                                                                      
## 10396:  588757    15777 2014-12-31 15:20:00 Steuart at Market             74
## 10397:  588772    12493 2014-12-31 15:35:00 Steuart at Market             74
## 10398:  588779    11859 2014-12-31 15:46:00 Steuart at Market             74
## 10399:  588850     9523 2014-12-31 17:36:00   Clay at Battery             41
## 10400:  588849     9540 2014-12-31 17:36:00   Clay at Battery             41
##                   end_date            end_station end_terminal bike_id
##                     <POSc>                 <char>        <int>   <int>
##     1: 2014-01-01 01:33:00      Steuart at Market           74     619
##     2: 2014-01-01 01:35:00      Steuart at Market           74     311
##     3: 2014-01-01 01:35:00      Steuart at Market           74     577
##     4: 2014-01-01 01:33:00      Steuart at Market           74     271
##     5: 2014-01-01 16:51:00        2nd at Townsend           61     433
##    ---                                                                
## 10396: 2014-12-31 19:43:00          Market at 4th           76     509
## 10397: 2014-12-31 19:04:00 Embarcadero at Sansome           60     328
## 10398: 2014-12-31 19:03:00 Embarcadero at Sansome           60     313
## 10399: 2014-12-31 20:15:00        Clay at Battery           41     439
## 10400: 2014-12-31 20:15:00        Clay at Battery           41     593
##        subscription_type zip_code   czas_wyp
##                   <char>   <char> <difftime>
##     1:          Customer    94070    61 mins
##     2:          Customer    55417    63 mins
##     3:          Customer    55417    63 mins
##     4:          Customer    94070    60 mins
##     5:          Customer    94107   876 mins
##    ---                                      
## 10396:          Customer     <NA>   263 mins
## 10397:          Customer     <NA>   209 mins
## 10398:          Customer     <NA>   197 mins
## 10399:          Customer     <NA>   159 mins
## 10400:          Customer     <NA>   159 mins
(krok_2 <- krok_1[order(duration)])
##        trip_id duration          start_date
##          <int>    <int>              <POSc>
##     1:  295912     3601 2014-05-23 11:18:00
##     2:  347471     3602 2014-07-01 19:33:00
##     3:  536050     3602 2014-11-08 15:41:00
##     4:  162053     3603 2014-01-22 16:00:00
##     5:  306651     3603 2014-06-02 09:08:00
##    ---                                     
## 10396:  237942   644771 2014-04-06 03:37:00
## 10397:  361321   715339 2014-07-13 05:50:00
## 10398:  323594   716480 2014-06-13 16:57:00
## 10399:  522337   720454 2014-10-30 08:29:00
## 10400:  568474 17270400 2014-12-06 21:59:00
##                               start_station start_terminal            end_date
##                                      <char>          <int>              <POSc>
##     1: Harry Bridges Plaza (Ferry Building)             50 2014-05-23 12:18:00
##     2:                      Clay at Battery             41 2014-07-01 20:33:00
##     3:                       Market at 10th             67 2014-11-08 16:41:00
##     4:              San Mateo County Center             23 2014-01-22 17:01:00
##     5:               Embarcadero at Sansome             60 2014-06-02 10:08:00
##    ---                                                                        
## 10396:             South Van Ness at Market             66 2014-04-13 14:44:00
## 10397:             Arena Green / SAP Center             14 2014-07-21 12:32:00
## 10398: Harry Bridges Plaza (Ferry Building)             50 2014-06-21 23:59:00
## 10399:        Redwood City Caltrain Station             22 2014-11-07 15:36:00
## 10400:             South Van Ness at Market             66 2015-06-24 20:18:00
##                                 end_station end_terminal bike_id
##                                      <char>        <int>   <int>
##     1: Harry Bridges Plaza (Ferry Building)           50     512
##     2:               Embarcadero at Sansome           60     288
##     3: Harry Bridges Plaza (Ferry Building)           50     332
##     4:              San Mateo County Center           23     231
##     5:               Embarcadero at Sansome           60     570
##    ---                                                          
## 10396:                      Clay at Battery           41     369
## 10397:                     Adobe on Almaden            5     251
## 10398:    Civic Center BART (7th at Market)           72     633
## 10399:             Stanford in Redwood City           25     692
## 10400:                        2nd at Folsom           62     535
##        subscription_type zip_code    czas_wyp
##                   <char>   <char>  <difftime>
##     1:          Customer    95757     60 mins
##     2:          Customer     7009     60 mins
##     3:          Customer    64112     60 mins
##     4:          Customer    94086     61 mins
##     5:          Customer        1     60 mins
##    ---                                       
## 10396:          Customer    94014  10747 mins
## 10397:          Customer     <NA>  11922 mins
## 10398:        Subscriber    94131  11942 mins
## 10399:          Customer    94010  12007 mins
## 10400:          Customer    95531 287839 mins
(krok_3 <- krok_2[1:5, 1:2])
##    trip_id duration
##      <int>    <int>
## 1:  295912     3601
## 2:  347471     3602
## 3:  536050     3602
## 4:  162053     3603
## 5:  306651     3603
# równoważne takim
batrips[duration > 3600] # == krok_1 <- batrips[duration > 3600]
##        trip_id duration          start_date     start_station start_terminal
##          <int>    <int>              <POSc>            <char>          <int>
##     1:  139559     3691 2014-01-01 00:32:00 Steuart at Market             74
##     2:  139560     3793 2014-01-01 00:32:00 Steuart at Market             74
##     3:  139561     3788 2014-01-01 00:32:00 Steuart at Market             74
##     4:  139562     3626 2014-01-01 00:33:00 Steuart at Market             74
##     5:  139587    52577 2014-01-01 02:15:00     Howard at 2nd             63
##    ---                                                                      
## 10396:  588757    15777 2014-12-31 15:20:00 Steuart at Market             74
## 10397:  588772    12493 2014-12-31 15:35:00 Steuart at Market             74
## 10398:  588779    11859 2014-12-31 15:46:00 Steuart at Market             74
## 10399:  588850     9523 2014-12-31 17:36:00   Clay at Battery             41
## 10400:  588849     9540 2014-12-31 17:36:00   Clay at Battery             41
##                   end_date            end_station end_terminal bike_id
##                     <POSc>                 <char>        <int>   <int>
##     1: 2014-01-01 01:33:00      Steuart at Market           74     619
##     2: 2014-01-01 01:35:00      Steuart at Market           74     311
##     3: 2014-01-01 01:35:00      Steuart at Market           74     577
##     4: 2014-01-01 01:33:00      Steuart at Market           74     271
##     5: 2014-01-01 16:51:00        2nd at Townsend           61     433
##    ---                                                                
## 10396: 2014-12-31 19:43:00          Market at 4th           76     509
## 10397: 2014-12-31 19:04:00 Embarcadero at Sansome           60     328
## 10398: 2014-12-31 19:03:00 Embarcadero at Sansome           60     313
## 10399: 2014-12-31 20:15:00        Clay at Battery           41     439
## 10400: 2014-12-31 20:15:00        Clay at Battery           41     593
##        subscription_type zip_code   czas_wyp
##                   <char>   <char> <difftime>
##     1:          Customer    94070    61 mins
##     2:          Customer    55417    63 mins
##     3:          Customer    55417    63 mins
##     4:          Customer    94070    60 mins
##     5:          Customer    94107   876 mins
##    ---                                      
## 10396:          Customer     <NA>   263 mins
## 10397:          Customer     <NA>   209 mins
## 10398:          Customer     <NA>   197 mins
## 10399:          Customer     <NA>   159 mins
## 10400:          Customer     <NA>   159 mins
batrips[duration > 3600][order(duration)] # == krok_2 <- krok_1[duration > 3600][order(duration)]
##        trip_id duration          start_date
##          <int>    <int>              <POSc>
##     1:  295912     3601 2014-05-23 11:18:00
##     2:  347471     3602 2014-07-01 19:33:00
##     3:  536050     3602 2014-11-08 15:41:00
##     4:  162053     3603 2014-01-22 16:00:00
##     5:  306651     3603 2014-06-02 09:08:00
##    ---                                     
## 10396:  237942   644771 2014-04-06 03:37:00
## 10397:  361321   715339 2014-07-13 05:50:00
## 10398:  323594   716480 2014-06-13 16:57:00
## 10399:  522337   720454 2014-10-30 08:29:00
## 10400:  568474 17270400 2014-12-06 21:59:00
##                               start_station start_terminal            end_date
##                                      <char>          <int>              <POSc>
##     1: Harry Bridges Plaza (Ferry Building)             50 2014-05-23 12:18:00
##     2:                      Clay at Battery             41 2014-07-01 20:33:00
##     3:                       Market at 10th             67 2014-11-08 16:41:00
##     4:              San Mateo County Center             23 2014-01-22 17:01:00
##     5:               Embarcadero at Sansome             60 2014-06-02 10:08:00
##    ---                                                                        
## 10396:             South Van Ness at Market             66 2014-04-13 14:44:00
## 10397:             Arena Green / SAP Center             14 2014-07-21 12:32:00
## 10398: Harry Bridges Plaza (Ferry Building)             50 2014-06-21 23:59:00
## 10399:        Redwood City Caltrain Station             22 2014-11-07 15:36:00
## 10400:             South Van Ness at Market             66 2015-06-24 20:18:00
##                                 end_station end_terminal bike_id
##                                      <char>        <int>   <int>
##     1: Harry Bridges Plaza (Ferry Building)           50     512
##     2:               Embarcadero at Sansome           60     288
##     3: Harry Bridges Plaza (Ferry Building)           50     332
##     4:              San Mateo County Center           23     231
##     5:               Embarcadero at Sansome           60     570
##    ---                                                          
## 10396:                      Clay at Battery           41     369
## 10397:                     Adobe on Almaden            5     251
## 10398:    Civic Center BART (7th at Market)           72     633
## 10399:             Stanford in Redwood City           25     692
## 10400:                        2nd at Folsom           62     535
##        subscription_type zip_code    czas_wyp
##                   <char>   <char>  <difftime>
##     1:          Customer    95757     60 mins
##     2:          Customer     7009     60 mins
##     3:          Customer    64112     60 mins
##     4:          Customer    94086     61 mins
##     5:          Customer        1     60 mins
##    ---                                       
## 10396:          Customer    94014  10747 mins
## 10397:          Customer     <NA>  11922 mins
## 10398:        Subscriber    94131  11942 mins
## 10399:          Customer    94010  12007 mins
## 10400:          Customer    95531 287839 mins
batrips[duration > 3600][order(duration)][1:5, 1:2]# zupełnie jakby był tam, między kwadratowymi nawiasami niewidzialny dplyr'owy pipe
##    trip_id duration
##      <int>    <int>
## 1:  295912     3601
## 2:  347471     3602
## 3:  536050     3602
## 4:  162053     3603
## 5:  306651     3603


Zadanie. Wyciągnijmy 4 stacje startowe z najniższą średnią wykorzystania (zmienna duration) roweru.


# nie znając wszystkich opcji bajeranckiego data.table
krok_1 <- batrips[, .(mn_dur = mean(duration)), by = "start_station"]
krok_2 <- krok_1[order(mn_dur)]
krok_2[1:4]
##                                    start_station   mn_dur
##                                           <char>    <num>
## 1:                                 2nd at Folsom 551.0807
## 2: Temporary Transbay Terminal (Howard at Beale) 655.8563
## 3:                             2nd at South Park 697.7034
## 4:                               Townsend at 7th 700.8317
# i znając #conieco
batrips[, 
        .(mn_dur = mean(duration)),
        by = "start_station"][order(mn_dur)][1:4]
##                                    start_station   mn_dur
##                                           <char>    <num>
## 1:                                 2nd at Folsom 551.0807
## 2: Temporary Transbay Terminal (Howard at Beale) 655.8563
## 3:                             2nd at South Park 697.7034
## 4:                               Townsend at 7th 700.8317


Przypominając. Żeby policzyć liczbę unikalnych wartości w wektorze używamy dwóch funkcji zagnieżdżając je unique() oraz length()


v <- c(1,2,3,4,1,2)
length(v)
## [1] 6
unique(v)
## [1] 1 2 3 4
length(unique(v))
## [1] 4


W {data.table} mamy jedną funkcję wykonującą tę robotę


uniqueN(v)
## [1] 4


Gdybyśmy zatem chcieli policzyć liczbę unikalnych rowerów w użyciu w danym miesiącu?


l_rowerow_per_miesiac <- batrips[, 
                                 .(liczba.rowerow = uniqueN(bike_id)), 
                                 by = .(miesiac = month(start_date))]
head(l_rowerow_per_miesiac, 6)
##    miesiac liczba.rowerow
##      <int>          <int>
## 1:       1            605
## 2:       2            608
## 3:       3            631
## 4:       4            637
## 5:       5            625
## 6:       6            618


Znajdźmy tamtejszą “Syrenkę” w bazie (naszą nadwiślańską stację, która - zakładam - jest jedną z najpopularniejszych stacji Veturilo w mieście). Policzmy liczbę użyć stacji per start_station oraz end_station i posortujmy malejąco


batrips[, .N, # trips są naszą "daną jednostkową" zatem wystarczy policzyć liczbę (wierszy) po danym agregacie
        by = .(end_station)][order(-N)][1] # zmienne grupujące, porządek, i wybór pierwszej obserwacji
##                                 end_station     N
##                                      <char> <int>
## 1: San Francisco Caltrain (Townsend at 4th) 33213
# zwróćmy stację początkową, z której rozpoczęło się najwięcej podróży
batrips[, .N, 
        by = .(start_station)][order(-N)][1]
##                               start_station     N
##                                      <char> <int>
## 1: San Francisco Caltrain (Townsend at 4th) 25144


Znajdźmy pierwsze i ostatnie użycie danej stacji początkowej


batrips[order(start_date), 
        .(start_date = start_date[c(1, .N)]), 
        by = start_station]
##                        start_station          start_date
##                               <char>              <POSc>
##   1:         San Francisco City Hall 2014-01-01 00:14:00
##   2:         San Francisco City Hall 2014-12-31 22:06:00
##   3:          Embarcadero at Sansome 2014-01-01 00:17:00
##   4:          Embarcadero at Sansome 2014-12-31 22:08:00
##   5:               Steuart at Market 2014-01-01 00:23:00
##  ---                                                    
## 144: Santa Clara County Civic Center 2014-12-31 15:32:00
## 145:                     Ryland Park 2014-04-10 09:10:00
## 146:                     Ryland Park 2014-12-31 07:56:00
## 147:        Stanford in Redwood City 2014-09-03 19:41:00
## 148:        Stanford in Redwood City 2014-12-22 16:56:00



.SD -> Subset of Data


.SD jest specjalną zmienną w data.table, która reprezentuje podzbiór danych w ramach każdej grupy podczas operacji grupowania. Jest to bardzo przydatne, gdy chcesz wykonać operacje na podzbiorze kolumn w każdej grupie.


Rozważmy taki przykład


# ramka
(x <- data.table(id = c(1, 1 ,2, 2, 1, 1),
                 val1 = 1:6, 
                 val2 = letters[6:1]))
##       id  val1   val2
##    <num> <int> <char>
## 1:     1     1      f
## 2:     1     2      e
## 3:     2     3      d
## 4:     2     4      c
## 5:     1     5      b
## 6:     1     6      a


i teraz…


(wynik <- x[, 
            .SD, 
            by = id]) # w sumie wydaje się, że to ten sam obiekt co x, tylko posortowany
##       id  val1   val2
##    <num> <int> <char>
## 1:     1     1      f
## 2:     1     2      e
## 3:     1     5      b
## 4:     1     6      a
## 5:     2     3      d
## 6:     2     4      c
# chociaż jak chcemy podejrzeć wnętrze, to widzimy, że jakaś operacja grupowania się dokonała
(wynik <- x[,
            print(.SD), # print()
            by = id])
##     val1   val2
##    <int> <char>
## 1:     1      f
## 2:     2      e
## 3:     5      b
## 4:     6      a
##     val1   val2
##    <int> <char>
## 1:     3      d
## 2:     4      c
## Empty data.table (0 rows and 1 cols): id


Otrzymujemy de facto dwie tabele, dynamicznie tworzone, pogrupowane po zmiennej id. Żeby wybrać, którąś grupę, możemy sięgnąć po nią w następujący sposób.


# dobieramy się do wartości poszczególnych tabel znanymi metodami, pierwszy element każdej z grup
x[, .SD[1], by = id]
##       id  val1   val2
##    <num> <int> <char>
## 1:     1     1      f
## 2:     2     3      d


# ostatni element każdej grupy
x[, .SD[.N], by = id]
##       id  val1   val2
##    <num> <int> <char>
## 1:     1     6      a
## 2:     2     4      c


Możemy wykorzystać grupowanie inaczej


# Filtrowanie danych: możemy filtrować dane w ramach każdej grupy. Przykład:
(wynik <- x[, 
            .SD[val1 == max(val1)], 
            by = id])
##       id  val1   val2
##    <num> <int> <char>
## 1:     1     6      a
## 2:     2     4      c
# bez .SD
(wynik <- x[, 
            .(val1 == max(val1)), 
            by = id])
##       id     V1
##    <num> <lgcl>
## 1:     1  FALSE
## 2:     1  FALSE
## 3:     1  FALSE
## 4:     1   TRUE
## 5:     2  FALSE
## 6:     2   TRUE


.SDcols służy do kontrolowania, które kolumny chcemy pokazać. Możemy używać .SD w połączeniu z .SDcols do określenia, które kolumny mają być uwzględnione w .SD.


x[, 
  .SD[1], 
  by = id, 
  .SDcols = c("val1")] # tylko zmienna "val1"
##       id  val1
##    <num> <int>
## 1:     1     1
## 2:     2     3
x[, .SD[1], 
  by = id, 
  .SDcols = -c("val1")] # bez zmiennej "val1", stąd minus
##       id   val2
##    <num> <char>
## 1:     1      f
## 2:     2      d


Inny przykład.


# Załóżmy, że mamy data.table DT_ex z kolumnami A, B, C, D
(DT_ex <- data.table(A = c(1, 1, 2, 2), 
                    B = c(3, 4, 5, 6), 
                    C = c(7, 8, 9, 10), 
                    D = c(11, 12, 13, 14)))
##        A     B     C     D
##    <num> <num> <num> <num>
## 1:     1     3     7    11
## 2:     1     4     8    12
## 3:     2     5     9    13
## 4:     2     6    10    14
# Grupowanie według kolumny A i obliczanie sumy kolumn B i C dla każdej grupy (względem wartości ze zmiennej A)
DT_ex[, 
      lapply(.SD, sum), 
      by = A, 
      .SDcols = c("B", "C")] # funkcje z rodziny apply poznamy na innych zajęciach
##        A     B     C
##    <num> <num> <num>
## 1:     1     7    15
## 2:     2    11    19



Przykład 1: Oblicz średnią dla wszystkich kolumn numerycznych per typ subskrybenta

unique(batrips$subscription_type)
## [1] "Subscriber" "Customer"
batrips[, lapply(.SD, mean, na.rm = TRUE), 
         by = subscription_type,
         .SDcols = c("duration")]
##    subscription_type  duration
##               <char>     <num>
## 1:        Subscriber  588.6278
## 2:          Customer 4238.8438

Przykład 2: Policz liczbę unikalnych wartości w kolumnach kategorycznych

batrips[, lapply(.SD, uniqueN), 
         .SDcols = c("start_station", "end_station", "bike_id")]
##    start_station end_station bike_id
##            <int>       <int>   <int>
## 1:            74          74     687

Przykład 3: Pierwsze 3 obserwacje dla każdej grupy

batrips[, .SD[1:3], by = subscription_type]
##    subscription_type trip_id duration          start_date
##               <char>   <int>    <int>              <POSc>
## 1:        Subscriber  139545      435 2014-01-01 00:14:00
## 2:        Subscriber  139546      432 2014-01-01 00:14:00
## 3:        Subscriber  139547     1523 2014-01-01 00:17:00
## 4:          Customer  139549     1620 2014-01-01 00:23:00
## 5:          Customer  139550     1617 2014-01-01 00:23:00
## 6:          Customer  139551      779 2014-01-01 00:24:00
##              start_station start_terminal            end_date
##                     <char>          <int>              <POSc>
## 1: San Francisco City Hall             58 2014-01-01 00:21:00
## 2: San Francisco City Hall             58 2014-01-01 00:21:00
## 3:  Embarcadero at Sansome             60 2014-01-01 00:42:00
## 4:       Steuart at Market             74 2014-01-01 00:50:00
## 5:       Steuart at Market             74 2014-01-01 00:50:00
## 6:       Steuart at Market             74 2014-01-01 00:37:00
##              end_station end_terminal bike_id zip_code   czas_wyp
##                   <char>        <int>   <int>   <char> <difftime>
## 1:       Townsend at 7th           65     473    94612     7 mins
## 2:       Townsend at 7th           65     395    94107     7 mins
## 3:       Beale at Market           56     331    94112    25 mins
## 4:    Powell Street BART           39     605    92007    27 mins
## 5:    Powell Street BART           39     453    92007    27 mins
## 6: Washington at Kearney           46     335    94109    13 mins

Przykład 4: Statystyki dla duration w każdej grupie

batrips[, lapply(.SD, function(x) list(mean = mean(x), median = median(x))), 
        by = subscription_type,
        .SDcols = "duration"]
##    subscription_type duration
##               <char>   <list>
## 1:        Subscriber 588.6278
## 2:        Subscriber      471
## 3:          Customer 4238.844
## 4:          Customer     1127



Wyszukiwanie po kluczu.


Możemy dodać indeks(y) (klucze) do szybszego wyszukiwania elementów w zbiorze albo ustawienia, które kolumny będą służyć do łączenia z innymi zbiorami danych.


dt <- data.table(a = 1:100,
                 b = rnorm(100))
setkey(dt, a)  # Klucz na kolumnie "a"


Co nam to daje? Otóż możemy np. użyć funkcji J(). J(...) służy do przeszukiwania tabeli danych w na obiektach typu data.table, gdzie klucze są ustawione.


dt[J(50)] # Bardzo szybkie wyszukiwanie
## Key: <a>
##        a          b
##    <int>      <num>
## 1:    50 -0.6245924


Co jeśli więcej niż jedna kolumna będzie indeksem? Wówczas zachodzą następujące zależności.


setkey(x = loty, dest, air_time)
str(loty)
loty["JFK"] # można bez
loty[J("JFK")] # można z

loty[226] # można bez
loty[J(226)] # należy bez

loty["ABQ", 226] # nie
# loty[,uniqueN(dest)]
loty[J("ABQ", 226)] # tak


W tym celu użyjemy funkcji J(). J(...) służy do tworzenia tabeli danych w kontekście zapytań na data.table, gdy klucze są ustawione.



Dodawanie i modyfikacja kolumn


:= to add/update/delete columns by reference


Operator := jest specyficzny dla pakietu data.table.


Jest używany do modyfikowania kolumn w miejscu, bez potrzeby tworzenia kopii oryginalnej tabeli.


Bardzo efektywnie zarządza pamięcią i jest zalecany, gdy pracujesz z dużymi zbiorami danych.


Rozważmy


Operator = w data.table pozwala tworzyć lub modyfikować zmienne, ale działa tylko lokalnie w ramach wywołania funkcji.


Zmiany dokonane za pomocą = nie modyfikują oryginalnej tabeli w miejscu, lecz zwracają nową wersję tabeli.


head(
  batrips[, .(week_day = wday(start_date))]
  , 4)
##    week_day
##       <int>
## 1:        4
## 2:        4
## 3:        4
## 4:        4


Operator := jest charakterystyczny dla data.table i umożliwia modyfikowanie danych bezpośrednio w miejscu (in-place), co jest bardziej wydajne pamięciowo.


Używany jest wyłącznie wewnątrz [...]


# dodanie pojedynczej nowo policzonej kolumny 
batrips[, is_dur_gt_1hour1 := duration > 3600]


# dodanie dwóch własnie policzonych kolumn
batrips[, c("is_dur_gt_1hour2","week_day") := list(duration > 3600, wday(start_date))]
# lub
batrips[, c("is_dur_gt_1hour2","week_day") := .(duration > 3600, wday(start_date))]


:= to zapis operatora przypisania jako wyrażenia, co pozwala użyć go w programowaniu metadanych lub w bardziej dynamicznych sytuacjach.


Może być przydatne np. w pętlach lub funkcjach, gdzie nazwy zmiennych są konstruowane dynamicznie.


# wyrzucenie/dodanie dwóch właśnie policzonych kolumn 
batrips[,`:=`(is_dur_gt_1hour2 = NULL, start_station2 = toupper(start_station))]

# dla przypomnienia innego sposobu wyrzucenia zmiennej start_station2
batrips$start_station2 <- NULL


Zamiana czasu z sekund w część godziny


batrips[, duration_hour := duration/3600,][1:3]
##    trip_id duration          start_date           start_station start_terminal
##      <int>    <int>              <POSc>                  <char>          <int>
## 1:  139545      435 2014-01-01 00:14:00 San Francisco City Hall             58
## 2:  139546      432 2014-01-01 00:14:00 San Francisco City Hall             58
## 3:  139547     1523 2014-01-01 00:17:00  Embarcadero at Sansome             60
##               end_date     end_station end_terminal bike_id subscription_type
##                 <POSc>          <char>        <int>   <int>            <char>
## 1: 2014-01-01 00:21:00 Townsend at 7th           65     473        Subscriber
## 2: 2014-01-01 00:21:00 Townsend at 7th           65     395        Subscriber
## 3: 2014-01-01 00:42:00 Beale at Market           56     331        Subscriber
##    zip_code   czas_wyp is_dur_gt_1hour1 week_day duration_hour
##      <char> <difftime>           <lgcl>    <int>         <num>
## 1:    94612     7 mins            FALSE        4     0.1208333
## 2:    94107     7 mins            FALSE        4     0.1200000
## 3:    94112    25 mins            FALSE        4     0.4230556


Poprawienie wartości w konkretnym wierszu danej zmiennej. Przypuśćmy, że mamy taką literówkę, wówczas…


batrips[2]
##    trip_id duration          start_date           start_station start_terminal
##      <int>    <int>              <POSc>                  <char>          <int>
## 1:  139546      432 2014-01-01 00:14:00 San Francisco City Hall             58
##               end_date     end_station end_terminal bike_id subscription_type
##                 <POSc>          <char>        <int>   <int>            <char>
## 1: 2014-01-01 00:21:00 Townsend at 7th           65     395        Subscriber
##    zip_code   czas_wyp is_dur_gt_1hour1 week_day duration_hour
##      <char> <difftime>           <lgcl>    <int>         <num>
## 1:    94107     7 mins            FALSE        4          0.12
batrips[2, start_station := "San Francisco Ratusz"]
batrips[2]
##    trip_id duration          start_date        start_station start_terminal
##      <int>    <int>              <POSc>               <char>          <int>
## 1:  139546      432 2014-01-01 00:14:00 San Francisco Ratusz             58
##               end_date     end_station end_terminal bike_id subscription_type
##                 <POSc>          <char>        <int>   <int>            <char>
## 1: 2014-01-01 00:21:00 Townsend at 7th           65     395        Subscriber
##    zip_code   czas_wyp is_dur_gt_1hour1 week_day duration_hour
##      <char> <difftime>           <lgcl>    <int>         <num>
## 1:    94107     7 mins            FALSE        4          0.12
batrips[2, start_station := "San Francisco City Hall"]
batrips[2]
##    trip_id duration          start_date           start_station start_terminal
##      <int>    <int>              <POSc>                  <char>          <int>
## 1:  139546      432 2014-01-01 00:14:00 San Francisco City Hall             58
##               end_date     end_station end_terminal bike_id subscription_type
##                 <POSc>          <char>        <int>   <int>            <char>
## 1: 2014-01-01 00:21:00 Townsend at 7th           65     395        Subscriber
##    zip_code   czas_wyp is_dur_gt_1hour1 week_day duration_hour
##      <char> <difftime>           <lgcl>    <int>         <num>
## 1:    94107     7 mins            FALSE        4          0.12


Masowo? Załóżmy, że mamy błędy w bazie i chcemy wszystkie czasy z wartościami ujemnymi zamienić na braki danych


batrips[duration < 0, duration := NA]


Rozważmy takie dwa przykłady, żeby poczuć jak zmiana (czasem błąd) składniowa, da totalnie różne rezultaty. Przykład analogiczny rozważaliśmy w trakcie zajęć 6, które dotyczyły dplyr, kiedy liczyliśmy mianownik do obliczania udziałów w jednym z przykładów ćwiczeniowych.


batrips[, 
        .(trips_N = .N), 
        by = start_station] # policzony agregat
##                                     start_station trips_N
##                                            <char>   <int>
##  1:                       San Francisco City Hall    2145
##  2:                        Embarcadero at Sansome   12879
##  3:                             Steuart at Market   11579
##  4:                                 5th at Howard    6297
##  5:          Harry Bridges Plaza (Ferry Building)   15536
##  6:                               Beale at Market    7978
##  7:                         Embarcadero at Folsom    6748
##  8:                             2nd at South Park    9448
##  9:                        Santa Clara at Almaden    1934
## 10:                            Powell Street BART    9314
## 11:                                 Howard at 2nd    6087
## 12:                               2nd at Townsend   12935
## 13:                      South Van Ness at Market    6415
## 14:                                 2nd at Folsom    7993
## 15:                                 Market at 4th    9980
## 16:                                Market at 10th    9803
## 17:                             Market at Sansome   12518
## 18:                         Embarcadero at Bryant    7066
## 19: Temporary Transbay Terminal (Howard at Beale)   12748
## 20:             Civic Center BART (7th at Market)    6521
## 21:       San Francisco Caltrain 2 (330 Townsend)   15132
## 22:               Grant Avenue at Columbus Avenue    7769
## 23:                          Paseo de San Antonio    1115
## 24:                         San Jose Civic Center     837
## 25:                        University and Emerson     782
## 26:                               Townsend at 7th   11002
## 27:                        Embarcadero at Vallejo    5000
## 28:                         Washington at Kearney     346
## 29:                               Spear at Folsom    5724
## 30:      San Francisco Caltrain (Townsend at 4th)   25144
## 31:                              Davis at Jackson    4646
## 32:                               Clay at Battery    4654
## 33:                           Golden Gate at Polk    3555
## 34: Yerba Buena Center of the Arts (3rd @ Howard)    6004
## 35:                 Powell at Post (Union Square)    6124
## 36:                  San Antonio Caltrain Station    1155
## 37:         Rengstorff Avenue / California Street     558
## 38:                          Cowper at University     725
## 39:           Mechanics Plaza (Market at Battery)    5853
## 40:                Mountain View Caltrain Station    3190
## 41:                              Adobe on Almaden     632
## 42:                      Commercial at Montgomery    5848
## 43:                    SJSU - San Salvador at 9th     768
## 44:                               Post at Kearney     662
## 45:               California Ave Caltrain Station     514
## 46:                                 St James Park     868
## 47:                       Mountain View City Hall    1430
## 48:                           San Salvador at 1st     930
## 49:                          Evelyn Park and Ride     905
## 50:             San Jose Diridon Caltrain Station    4851
## 51:                 Redwood City Caltrain Station     521
## 52:                    Palo Alto Caltrain Station     986
## 53:                            San Jose City Hall    1427
## 54:                        SJSU 4th at San Carlos     566
## 55:                                 Park at Olive     338
## 56:                      Arena Green / SAP Center     852
## 57:                              San Pedro Square    1433
## 58:                                   MLK Library     835
## 59:                                     Japantown     902
## 60:                              Broadway at Main      38
## 61:                    San Jose Government Center      23
## 62:              Castro Street and El Camino Real    1207
## 63:                       San Mateo County Center     109
## 64:                   San Antonio Shopping Center    1075
## 65:                             Franklin at Maple      85
## 66:                   Redwood City Medical Center      76
## 67:                   Redwood City Public Library     108
## 68:                     Broadway St at Battery St    5022
## 69:                                    Mezes Park     168
## 70:                          Washington at Kearny    2440
## 71:                                Post at Kearny    3838
## 72:               Santa Clara County Civic Center     496
## 73:                                   Ryland Park    1097
## 74:                      Stanford in Redwood City      50
##                                     start_station trips_N
batrips[, 
        trips_N := .N, 
        by = start_station][1:5] # policzona zmienna dla każdego wiersza zawierająca wartość agregatu
##    trip_id duration          start_date           start_station start_terminal
##      <int>    <int>              <POSc>                  <char>          <int>
## 1:  139545      435 2014-01-01 00:14:00 San Francisco City Hall             58
## 2:  139546      432 2014-01-01 00:14:00 San Francisco City Hall             58
## 3:  139547     1523 2014-01-01 00:17:00  Embarcadero at Sansome             60
## 4:  139549     1620 2014-01-01 00:23:00       Steuart at Market             74
## 5:  139550     1617 2014-01-01 00:23:00       Steuart at Market             74
##               end_date        end_station end_terminal bike_id
##                 <POSc>             <char>        <int>   <int>
## 1: 2014-01-01 00:21:00    Townsend at 7th           65     473
## 2: 2014-01-01 00:21:00    Townsend at 7th           65     395
## 3: 2014-01-01 00:42:00    Beale at Market           56     331
## 4: 2014-01-01 00:50:00 Powell Street BART           39     605
## 5: 2014-01-01 00:50:00 Powell Street BART           39     453
##    subscription_type zip_code   czas_wyp is_dur_gt_1hour1 week_day
##               <char>   <char> <difftime>           <lgcl>    <int>
## 1:        Subscriber    94612     7 mins            FALSE        4
## 2:        Subscriber    94107     7 mins            FALSE        4
## 3:        Subscriber    94112    25 mins            FALSE        4
## 4:          Customer    92007    27 mins            FALSE        4
## 5:          Customer    92007    27 mins            FALSE        4
##    duration_hour trips_N
##            <num>   <int>
## 1:     0.1208333    2145
## 2:     0.1200000    2145
## 3:     0.4230556   12879
## 4:     0.4500000   11579
## 5:     0.4491667   11579


Dodanie zmiennej ze średnią miesięczną użycia rowerów?


batrips[,
        mean_dur := mean(duration, na.rm = TRUE), # drugi argument na wypadek braków danych 
        by = month(start_date)] # ekstrakcja miesiąca


Uzupełnianie braków danych. Załóżmy, że chcemy nadpisać braki danych średnią miesięczną (w “jednej linii” kodu).


batrips[, 
        mean_dur := mean(duration, na.rm = TRUE), 
        by = month(start_date)][is.na(duration), duration := mean_dur]


A chcąc stworzyć zmienną, użyć jej i od razu się pozbyć w jednej linijce kodu?


batrips[,
        mean_dur := mean(duration, na.rm = TRUE), 
        by = month(start_date)][is.na(duration), duration := mean_dur][, mean_dur := NULL]


# Przykład 1: Dodaj nową kolumnę
batrips[, duration_hours := duration / 60]

# Przykład 2: Dodaj wiele kolumn jednocześnie
batrips[, `:=`(
  duration_hours = duration / 60,
  duration_mins = duration
)]

# Przykład 3: Modyfikacja warunkowa
batrips[, long_trip := ifelse(duration > 60, "Yes", "No")]

# Przykład 4: Usuń kolumnę
batrips[, long_trip := NULL]

# Przykład 5: Modyfikacja w grupach - średni czas dla typu subskrypcji
batrips[, mean_duration_by_type := mean(duration), 
         by = subscription_type]

# Przykład 6: Kategorie czasu trwania
batrips[, duration_category := fcase(
  duration < 15, "short",
  duration < 30, "medium",
  duration >= 30, "long"
)]


fread() & fwrite()



Kilka komentarzy…

system.time(data.table::fread("dane/batrips.csv"))
##    user  system elapsed 
##    0.17    0.08    0.13
system.time(utils::read.csv2("dane/batrips.csv"))
##    user  system elapsed 
##    1.74    0.13    1.85
# a to są jakieś śmiesznie małe zbiory


Wydajność przy dużych zbiorach danych


library(data.table)
# Tworzymy duży zbiór danych
dt <- data.table(id = 1:1e7, value = rnorm(1e7))
system.time(
  dt[, 
     .(mean_value = mean(value)), 
     by = id %% 100] # Modulo, w tym kontekście id %% 100 grupuje dane w 100 cyklicznych grup na podstawie wartości id, do samodzielnego sprawdzenia w dokumentacji i potestowania bez
  )
##    user  system elapsed 
##    0.98    0.06    0.61
library(dplyr)
df <- as.data.frame(dt)
system.time(
  df %>% 
    group_by(id %% 100) %>%
    summarise(mean_value = mean(value))
  )
##    user  system elapsed 
##    0.42    0.10    0.52


Wyniki z funkcji system.time() w R przedstawiają, ile czasu zajęło wykonanie danego fragmentu kodu. W szczególności zwraca trzy wartości:

  • user: Czas CPU spędzony na wykonywaniu kodu przez proces użytkownika (czas “obliczeń” w kodzie R).

  • system: Czas CPU spędzony przez system operacyjny na obsłudze operacji związanych z kodem (np. alokacja pamięci, operacje dyskowe).

  • elapsed: Czas rzeczywisty, jaki upłynął od początku do końca wykonywania kodu (czas zegarowy). To może obejmować równoległość obliczeń, czekanie na zasoby itp.



W {data.table}


dt[, new_col := value * 2]  # Kolumna dodana w miejscu


W {dplyr}


df <- df %>% 
  mutate(new_col = value * 2)  # Tworzy się nowy obiekt



Grupy zagnieżdżone i wielopoziomowe.


{data.table} obsługuje wielopoziomowe grupowanie z dużą elastycznością. Rozważmy taki przykład.


dt[, 
   .(sum_value = sum(value), 
     mean_value = mean(value)),
   by = .(id %% 10, 
          id %% 2)]
##        id  id.1  sum_value    mean_value
##     <num> <num>      <num>         <num>
##  1:     1     1   606.2838  0.0006062838
##  2:     2     0  -452.9076 -0.0004529076
##  3:     3     1  1521.8221  0.0015218221
##  4:     4     0   755.1856  0.0007551856
##  5:     5     1   318.5834  0.0003185834
##  6:     6     0  -457.3130 -0.0004573130
##  7:     7     1  -410.0345 -0.0004100345
##  8:     8     0 -1377.5813 -0.0013775813
##  9:     9     1  -247.2844 -0.0002472844
## 10:     0     0   359.6012  0.0003596012


Versus w {dplyr}


df %>% 
  group_by(group1 = id %% 10,
           group2 = id %% 2) %>% 
  summarise(sum_value = sum(value),
            mean_value = mean(value))
## # A tibble: 10 × 4
## # Groups:   group1 [10]
##    group1 group2 sum_value mean_value
##     <dbl>  <dbl>     <dbl>      <dbl>
##  1      0      0      360.   0.000360
##  2      1      1      606.   0.000606
##  3      2      0     -453.  -0.000453
##  4      3      1     1522.   0.00152 
##  5      4      0      755.   0.000755
##  6      5      1      319.   0.000319
##  7      6      0     -457.  -0.000457
##  8      7      1     -410.  -0.000410
##  9      8      0    -1378.  -0.00138 
## 10      9      1     -247.  -0.000247


Obsługa pamięci. {data.table} jest niezwykle wydajny pod względem pamięci. Możesz ustawiać limity wątków:


setDTthreads(4)  # Ustawienie liczby wątków


W {dplyr} brak natywnej kontroli nad wątkami, co ogranicza wydajność.




Systematyzując myśli…

= (pojedynczy równa się) Jest to standardowe przypisanie w R. W kontekście data.table w sekcji j:

  • Używasz go przy wywołaniach funkcji, np. mean(x, na.rm = TRUE)
  • Nie tworzy nowych kolumn w data.table
  • Działa jak zwykłe przypisanie argumentów
dt[, mean(kolumna, na.rm = TRUE)]  # na.rm to argument funkcji


:= (walrus operator) To modyfikacja przez referencję - najbardziej charakterystyczna cecha data.table:

  • Modyfikuje data.table “w miejscu” (bez kopiowania)
  • Super szybkie, oszczędza pamięć
  • Tworzy/modyfikuje kolumny bezpośrednio
dt[, nowa_kolumna := stara_kolumna * 2]  # tworzy nową kolumnę
dt[, kolumna := NULL]  # usuwa kolumnę


:= (w backticku) To dokładnie to samo co :=, tylko użyte w specjalny sposób do przypisania wielu kolumn naraz:

# Wersja funkcyjna - wiele kolumn jednocześnie
dt[, `:=`(kolumna1 = wartość1,
          kolumna2 = wartość2,
          kolumna3 = wartość3)]



Syntax: Siła prostoty. Składnia {data.table} jest bardziej kompaktowa, co sprawia, że kod jest krótszy i często bardziej czytelny.


# dplyr
df %>% 
  group_by(id %% 100) %>% 
  summarise(mean_value = mean(value), .groups = "drop") %>%
  head(3)
## # A tibble: 3 × 2
##   `id%%100` mean_value
##       <dbl>      <dbl>
## 1         0  -0.000167
## 2         1  -0.00425 
## 3         2  -0.00267

Versus

dt[, .(mean_value = mean(value)), by = id %% 100][1:3]
##       id   mean_value
##    <num>        <num>
## 1:     1 -0.004245741
## 2:     2 -0.002668414
## 3:     3  0.004140578



Praca domowa

Proszę przykłady z zajęć 5 i 6, które dotyczyły dplyr, policzyć składnią data.table (7 trudniejszych), a te z zajęć 7 (dzisiejszych) policzyć składnią dplyr (7 trudniejszych). Znane są output’y przykładów, więc będą Państwo pewni rozwiązań.

Rozwiązanie

Rozwiązania proszę podsyłać na adres m.maluchnik2(at)uw.edu.pl. Osoby, które podeślą wyniki przed ostatnimi zajęciami otrzymają plusy w projekcie zaliczeniowym. Osoby, które będą miały super projekty końcowe, otrzymają zamiast plusa pojednawcze spojrzenie prowadzącego na dowód uznania (i ewentualnie uścisk dłoni, jeśli aktualna sytuacja pandemiczna na to pozwoli)






Źródła i inspiracje pomocne w przygotowaniu niniejszej prezentacji: