{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.
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).
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>
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.
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
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…
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
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%
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
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
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"
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…
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.
:= 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:
dt[, mean(kolumna, na.rm = TRUE)] # na.rm to argument funkcji
:= (walrus operator) To modyfikacja przez
referencję - najbardziej charakterystyczna cecha data.table:
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
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ą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)
datacamp.com Data Manipulation with data.table in R
https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html
datacamp.com Introduction to the Tidyverse
datacamp.com Data Manipulation with dplyr
Materiały z warsztatów Data Science w zastosowaniach biznesowych - Warsztaty z wykorzystaniem programu R Uniwersytet Warszawski, Wydział Nauk Ekonomicznych
DataCamp.com Introduction to R
DataCamp.com Writing Efficient R Code
Eugene O’Loughlin tutorial
Materiały dr Bartosza Maćkiewicza:
Biecek, Przemysław, “Przewodnik po pakiecie R” http://www.biecek.pl/R/ https://cran.r-project.org/doc/contrib/Biecek-R-basics.pdf
Gągolewski, Marek, “Programowanie w języku R”, “Deep R Programming”, etc. https://deepr.gagolewski.com/index.html
Crawley, Michael J. The R book. John Wiley & Sons, 2012 https://onlinelibrary.wiley.com/doi/book/10.1002/9781118448908
Kabacoff, Robert. R in action: data analysis and graphics with R. Manning Publications Co., 2015 https://www.cs.uni.edu/~jacobson/4772/week11/R_in_Action.pdf
Fox, John, Michael Friendly, and Sanford Weisberg. “Hypothesis tests for multivariate linear models using the car package.” R J 5.1 (2013) https://www.researchgate.net/publication/285736465_Hypothesis_Tests_for_Multivariate_Linear_Models_Using_the_car_Package
Hothorn, Torsten, et al. “Package ‘multcomp’.” Simultaneous inference in general parametric models. Project for Statistical Computing, Vienna, Austria (2016) https://cran.r-project.org/web/packages/multcomp/vignettes/generalsiminf.pdf
claude.ai
chatgpt.com