Ky kjo shĂ«nim do tĂ« jetĂ« i dobishĂ«m pĂ«r ata qĂ« pĂ«rdorin bibliotekĂ«n e pĂ«rpunimit tĂ« tĂ« dhĂ«nave tabulare pĂ«r R â data.table, dhe ndoshta do tĂ« jenĂ« tĂ« kĂ«naqur tĂ« shohin fleksibilitetin e saj nĂ« aplikimin e shembujve tĂ« ndryshĂ«m.
Të inspiruar nga një shembull i mirë , dhe shpresojmë se tashmë e keni lexuar artikullin e tij, ofroj një thellim në drejtim të optimizimit të kodit dhe performancës mbi bazën e data.table.
Hyrje: nga vjen data.table?
Më së miri fillon njohjen me bibliotekën nga pak më larg, sidomos nga strukturat e të dhënave, nga të cilat mund të krijohet objekti data.table (më pas, DT).
Matiç
Kodi
## arrays ---------
arrmatr <- array(1:20, c(4,5))
class(arrmatr)
typeof(arrmatr)
is.array(arrmatr)
is.matrix(arrmatr)
Një nga këto struktura është matiçi (?base::array). Siç ndodh edhe në gjuhë të tjera, maticat këtu janë shumëdimensionale. Megjithatë, e veçantë është se, për shembull, një matricë dy-dimensionale fillon të trashëgojë pronat nga klasa e matices (?base::matrix), dhe një matricë një-dimensionale, që gjithashtu është e rëndësishme, nuk trashëgon nga vektori (?base::vector).
Duhet tĂ« kuptohet se tipi i tĂ« dhĂ«nave qĂ« pĂ«rmbahen nĂ« cilindo objekt duhet tĂ« kontrollohet me funksionin base::typeof, i cili kthen pĂ«rshkrimin e brendshĂ«m tĂ« tipit sipas R Internals â protokolli i pĂ«rgjithshĂ«m i gjuhĂ«s, i lidhur me origjinĂ«n C.
Një komandë tjetër për të përcaktuar klasën e një objekti, base::class, kthen në rastin e vektorëve tipin vektorial (ai ndryshon emrin nga ai i brendshëm, por gjithashtu lejon të kuptohet tipi i të dhënave).
Lista
Nga një matricë dy-dimensionale, që është edhe matricë, mund të kalosh në listë (?base::list).
Kodi
## lists ------------------
mylist <- as.list(arrmatr)
is.vector(mylist)
is.list(mylist)
Ndërkohë, ndodhin disa gjëra njëkohësisht:
- Dimensions e dytë të matices shkurtohet, dmth, ne marrim njëkohësisht edhe listën edhe vektorin.
- Lista, në këtë mënyrë, trashëgon nga këto klasa. Duhet të merret parasysh se një elementi të listës do t'i përgjigjet një (vlerë skalar) nga qeliza e matices-matiçi.
Falë faktit se lista është gjithashtu një vektor, mund t'i aplikohen disa funksione për vektorët.
Dataframe
Nga lista, matica ose vektori mund të kalosh në dataframe (?base::data.frame).
Kodi
## data.frames ------------
df <- as.data.frame(arrmatr)
df2 <- as.data.frame(mylist)
is.list(df)
df$V6 <- df$V1 + df$V2
ĂfarĂ« Ă«shtĂ« interesante: dataframe trashĂ«gon nga lista! Kolonat e dataframe-it janĂ« qeliza tĂ« listĂ«s. Kjo do tĂ« jetĂ« e rĂ«ndĂ«sishme mĂ« pas, kur do tĂ« pĂ«rdorim funksione tĂ« aplikueshme nĂ« lista.
data.table
Mund të krijosh DT (?data.table::data.table) nga dataframe-i, lista, vektori ose matiçja. Për shembull, kështu (në vend).
Kodi
## data.tables -----------------------
library(data.table)
data.table::setDT(df)
is.list(df)
is.data.frame(df)
is.data.table(df)
E dobishme është se, si dhe dataframe-i, DT trashëgon pronat e listes.
DT dhe kujtesa
Ndryshe nga të gjithë objektet e tjera në R base, DT dërgohen me referencë. Nëse nevojitet të bëhet një kopje në një hapësirë të re memorie, nevojitet funksioni data.table::copy ose duhet të bëhet një përzgjedhje nga objekti i vjetër.
Kodi
df2 <- df
df[V1 == 1, V2 := 999]
data.table::fsetdiff(df, df2)
df2 <- data.table::copy(df)
df[V1 == 2, V2 := 999]
data.table::fsetdiff(df, df2)
Me kĂ«tĂ«, hyrja pĂ«rfundon. DT â Ă«shtĂ« njĂ« vazhdimĂ«si e zhvillimit tĂ« strukturave tĂ« tĂ« dhĂ«nave nĂ« R, e cila kryesisht ndodh pĂ«rmes zgjerimit dhe pĂ«rshpejtimit tĂ« operacioneve qĂ« kryhen mbi objektet e klasĂ«s dataframe. NĂ« tĂ« njĂ«jtĂ«n kohĂ«, trashĂ«gohet nga primitivĂ« tĂ« tjerĂ«.
Disa shembuj të përdorimit të pronave data.table
Si lista...
Të iterosh përmes rreshtave të dataframe-it ose DT nuk është ideali, pasi kodi i ciklit në gjuhën R është ndjeshëm më i ngadalshëm C, ndersa të kalosh nëpër kolona, të cilat zakonisht janë shumë më të pakta, është plotësisht e mundur. Kur kalojmë përmes kolonave, kujtojmë se çdo kolonë është një element i listës, që zakonisht përmban një vektor. Operacionet mbi vektorët janë mirë të vektorizuara në funksionet bazë të gjuhës. Mund të përdorim gjithashtu operatorët e zgjedhjes, të cilat i përkasin listave dhe vektorëve: `[[`, `$`.
Kodi
## operations on data.tables ------------
#using list properties
df$'V1'[1]
df[['V1']]
df[[1]][1]
sapply(df, class)
sapply(df, function(x) sum(is.na(x)))
Vektorizimi
Nëse ka nevojë të kalosh përmes rreshtave të një DT të madh, zgjidhja më e mirë do të ishte shkrimi i një funksioni me vektorizim. Por nëse kjo nuk është e mundur, duhet të mbahen mend se cikli brenda DT gjithsesi është më i shpejtë se cikli në R, pasi ekzekutohet në C.
Le të provosh në një shembull më të madh me 100K rreshta. Do të nxjerrim fillin e parë nga fjalët që janë pjesë e kolonës vektor w.
Përditësuar
Kodi
library(magrittr)
library(microbenchmark)
## Shembulli më i Madh ----
rown <- 100000
dt %
.[, d := 1 + b + c + rnorm(nrow(.))]
# vektorizimi
microbenchmark({
dt[
, first_l := unlist(strsplit(w, split = ' ', fixed = T))[1]
, by = 1:nrow(dt)
]
})
# e dyta
first_l_f %
do.call(rbind, .) %>%
`[`(,1)
}
dt[, first_l := NULL]
microbenchmark({
dt[
, first_l := .(first_l_f(w))
]
})
# e treta
first_l_f2 %
unlist %>%
matrix(nrow = 3) %>%
`[`(1,)
}
dt[, first_l := NULL]
microbenchmark({
dt[
, first_l := .(first_l_f2(w))
]
})
Prova e parë me iterimin përmes rreshtave:
Njësi: milisekonda
shprehja min
{ dt[, `:=`(first_l, unlist(strsplit(w, split = " ", fixed = T))[1]), by = 1:nrow(dt)] } 439.6217
lq mesatare mediane uq maksimumi neval
451.9998 460.1593 456.2505 460.9147 621.4042 100
Prova e dytë, ku vektorizimi shkon përmes kthimit të listes në matricë dhe marrjen e elementeve në prerjen me indeksin 1 (kjo është në fakt vektorizimi). Do të korrigjoj: vektorizimi në nivelin e funksionit strsplit, që mund të pranojë një vektor si hyrje. Duket se procedura e transformimit të një liste në një matricë është ndjeshëm më e rëndë se vetë vektorizimi, por në këtë rast është ende shumë më e shpejtë se varianti pa vektorizim.
Njësi: milisekonda
expr min lq mean median uq max neval
{ dt[, `:=`(first_l, .(first_l_f(w)))] } 93.07916 112.1381 161.9267 149.6863 185.9893 442.5199 100
Përshpejtimi sipas medianës është 3 herë.
Prova e tretë, ku skema e transformimit në matricë është ndryshuar.
Njësi: milisekonda
expr min lq mean median uq max neval
{ dt[, `:=`(first_l, .(first_l_f2(w)))] } 32.60481 34.13679 40.4544 35.57115 42.11975 222.972 100
Përshpejtimi sipas medianës është 13 herë.
Me kĂ«tĂ« çështje duhet tĂ« eksperimentoni, sa mĂ« shumĂ« â aq mĂ« mirĂ« do tĂ« jetĂ«.
Një tjetër shembull me vektorizimin, ku ka gjithashtu tekst, por është i afërt me kushtet reale: ndryshimi në gjatësi të fjalëve, sasia e fjalëve. Duhet të nxirren 3 fjalët e para. Ja kështu:

Këtu funksioni i mëparshëm nuk punon, pasi vektorët kanë gjatësi të ndryshme, ndërsa ne keni caktuar madhësinë e matrices. Do ta rregullojmë këtë, duke kërkuar në internet.
Kodi
# fourth
rown <- 100000
words <-
sapply(
seq_len(rown)
, function(x){
nwords <- rbinom(1, 10, 0.5)
paste(
sapply(
seq_len(nwords)
, function(x){
paste(sample(letters, rbinom(1, 10, 0.5), replace = T), collapse = '')
}
)
, collapse = ' '
)
}
)
dt <-
data.table(
w = words
, a = sample(letters, rown, replace = T)
, b = runif(rown, -3, 3)
, c = runif(rown, -3, 3)
, e = rnorm(rown)
) %>%
.[, d := 1 + b + c + rnorm(nrow(.))]
first_l_f3 <- function(sd, n)
{
l <- strsplit(sd, split = ' ', fixed = T)
maxl <- max(lengths(l))
sapply(l, "length<-", maxl) %>%
`[`(n,) %>%
as.character
}
microbenchmark({
dt[
, (paste0('w_', 1:3)) := lapply(1:3, function(x) first_l_f3(w, x))
]
})
dt[
, (paste0('w_', 1:3)) := lapply(1:3, function(x) first_l_f3(w, x))
]
Njësi: milisekonda
expr min lq mean median
{ dt[, `:=`((paste0(«w_», 1:3)), strsplit(w, split = " ", fixed = T))] } 851.7623 916.071 1054.5 1035.199
uq max neval
1178.738 1356.816 100
Skripti punoi me një shpejtësi mesatare prej 1 sekonde. Jo keq.
Të lidhura me një zinxhir...
Me objektet DT mund të punoni duke përdorur chaining. Kjo duket si bashkimin e sintaksës së kllapave nga e djathta, në thelb, eshte ëmbëlsirë.
Kodi
# chaining
res1 <- dt[a == 'a'][sample(.N, 100)]
res2 <- dt[, .N, a][, N]
res3 <- dt[, coefficients(lm(e ~ d))[1], a][, .(letter = a, coef = V1)]
Përshkonpër tubat...
Të njëjtat operacione mund të bëhen përmes piping, duket e ngjashme, por funksionalisht më e pasur, pasi mund të përdoren çdo metodë, jo vetëm DT. Do të nxjerrim koeficientët e regresionit logjistik për të dhënat tona sintetike me disa filtra në DT.
Kodi
# piping
samplpe_b <- dt[a %in% head(letters), sample(b, 1)]
res4 <-
dt %>%
.[a %in% head(letters)] %>%
.[,
{
dt0 <- .SD[1:100]
quants <-
dt0[, c] %>%
quantile(seq(0.1, 1, 0.1), na.rm = T)
.(q = quants)
}
, .(cond = b > samplpe_b)
] %>%
glm(
cond ~ q -1
, family = binomial(link = "logit")
, data = .
) %>%
summary %>%
.[[12]]
Statistika, mësimi i makinave dhe të tjera brenda DT
Mund tĂ« pĂ«rdoren funksione lambda, por ndonjĂ«herĂ« Ă«shtĂ« mĂ« mirĂ« tâi krijoni ato veçmas, tĂ« shkruani tĂ« gjithĂ« linjĂ«n e analizĂ«s sĂ« tĂ« dhĂ«nave, dhe pĂ«rpara â ato punojnĂ« brenda DT. Shembulli Ă«shtĂ« pasuruar me tĂ« gjitha veçoritĂ« e mĂ«sipĂ«rme, plus disa gjera tĂ« dobishme nga arsenali i DT (tĂ« tilla si referimi te vetĂ« DT brenda DT me lidhje, tĂ« futur ndonjĂ«herĂ« jo nĂ« rend, por pĂ«r tĂ« qenĂ«).
Kodi
# function
rm(lm_preds)
lm_preds <- function(
sd, by, n
)
{
if(
n < 100 |
!by[['a']] %in% head(letters, 4)
)
{
res <-
list(
low = NA
, mean = NA
, high = NA
, coefs = NA
)
} else {
lmm <-
lm(
d ~ c + b
, data = sd
)
preds <-
stats::predict.lm(
lmm
, sd
, interval = "prediction"
)
res <-
list(
low = preds[, 2]
, mean = preds[, 1]
, high = preds[, 3]
, coefs = coefficients(lmm)
)
}
res
}
res5 <-
dt %>%
.[e < 0] %>%
.[.[, .I[b > 0]]] %>%
.[, `:=` (
low = as.numeric(lm_preds(.SD, .BY, .N)[[1]])
, mean = as.numeric(lm_preds(.SD, .BY, .N)[[2]])
, high = as.numeric(lm_preds(.SD, .BY, .N)[[3]])
, coef_c = as.numeric(lm_preds(.SD, .BY, .N)[[4]][1])
, coef_b = as.numeric(lm_preds(.SD, .BY, .N)[[4]][2])
, coef_int = as.numeric(lm_preds(.SD, .BY, .N)[[4]][3])
)
, a
] %>%
.[!is.na(mean), -'e', with = F]
# plot
plo <-
res5 %>%
ggplot +
facet_wrap(~ a) +
geom_ribbon(
aes(
x = c * coef_c + b * coef_b + coef_int
, ymin = low
, ymax = high
, fill = a
)
, size = 0.1
, alpha = 0.1
) +
geom_point(
aes(
x = c * coef_c + b * coef_b + coef_int
, y = mean
, color = a
)
, size = 1
) +
geom_point(
aes(
x = c * coef_c + b * coef_b + coef_int
, y = d
)
, size = 1
, color = 'black'
) +
theme_minimal()
print(plo)
Përfundimi
Shpresoj të kem krijuar një pamje të plotë, por sigurisht jo të plotë, të këtij objekti si data.table, duke filluar nga vetitë e tij të lidhura me trashëgiminë nga klasat R dhe duke përfunduar me tiparet e tij dhe mjedisin e elementeve tidyverse. Shpresoj se kjo do t'ju ndihmojë të studioni dhe përdorni më mirë këtë bibliotekë për punë dhe argëtim.

Faleminderit!
Kodi i plotë
Kodi
## load libs ----------------
library(data.table)
library(ggplot2)
library(magrittr)
library(microbenchmark)
## arrays ---------
arrmatr <- array(1:20, c(4,5))
class(arrmatr)
typeof(arrmatr)
is.array(arrmatr)
is.matrix(arrmatr)
## lists ------------------
mylist <- as.list(arrmatr)
is.vector(mylist)
is.list(mylist)
## data.frames ------------
df <- as.data.frame(arrmatr)
is.list(df)
df$V6 <- df$V1 + df$V2
## data.tables -----------------------
data.table::setDT(df)
is.list(df)
is.data.frame(df)
is.data.table(df)
df2 <- df
df[V1 == 1, V2 := 999]
data.table::fsetdiff(df, df2)
df2 <- data.table::copy(df)
df[V1 == 2, V2 := 999]
data.table::fsetdiff(df, df2)
## operations on data.tables ------------
#using list properties
df$'V1'[1]
df[['V1']]
df[[1]][1]
sapply(df, class)
sapply(df, function(x) sum(is.na(x)))
## Bigger example ----
rown <- 100000
dt <-
data.table(
w = sapply(seq_len(rown), function(x) paste(sample(letters, 3, replace = T), collapse = ' '))
, a = sample(letters, rown, replace = T)
, b = runif(rown, -3, 3)
, c = runif(rown, -3, 3)
, e = rnorm(rown)
) %>%
.[, d := 1 + b + c + rnorm(nrow(.))]
# vectorization
# zero - for loop
microbenchmark({
for(i in 1:nrow(dt))
{
dt[
i
, first_l := unlist(strsplit(w, split = ' ', fixed = T))[1]
]
}
})
# first
microbenchmark({
dt[
, first_l := unlist(strsplit(w, split = ' ', fixed = T))[1]
, by = 1:nrow(dt)
]
})
# second
first_l_f <- function(sd)
{
strsplit(sd, split = ' ', fixed = T) %>%
do.call(rbind, .) %>%
`[`(,1)
}
dt[, first_l := NULL]
microbenchmark({
dt[
, first_l := .(first_l_f(w))
]
})
# third
first_l_f2 <- function(sd)
{
strsplit(sd, split = ' ', fixed = T) %>%
unlist %>%
matrix(nrow = 3) %>%
`[`(1,)
}
dt[, first_l := NULL]
microbenchmark({
dt[
, first_l := .(first_l_f2(w))
]
})
# fourth
rown <- 100000
words <-
sapply(
seq_len(rown)
, function(x){
nwords <- rbinom(1, 10, 0.5)
paste(
sapply(
seq_len(nwords)
, function(x){
paste(sample(letters, rbinom(1, 10, 0.5), replace = T), collapse = '')
}
)
, collapse = ' '
)
}
)
dt <-
data.table(
w = words
, a = sample(letters, rown, replace = T)
, b = runif(rown, -3, 3)
, c = runif(rown, -3, 3)
, e = rnorm(rown)
) %>%
.[, d := 1 + b + c + rnorm(nrow(.))]
first_l_f3 <- function(sd, n)
{
l <- strsplit(sd, split = ' ', fixed = T)
maxl <- max(lengths(l))
sapply(l, "length<-", maxl) %>%
`[`(n,) %>%
as.character
}
microbenchmark({
dt[
, (paste0('w_', 1:3)) := lapply(1:3, function(x) first_l_f3(w, x))
]
})
dt[
, (paste0('w_', 1:3)) := lapply(1:3, function(x) first_l_f3(w, x))
]
# chaining
res1 <- dt[a == 'a'][sample(.N, 100)]
res2 <- dt[, .N, a][, N]
res3 <- dt[, coefficients(lm(e ~ d))[1], a][, .(letter = a, coef = V1)]
# piping
samplpe_b <- dt[a %in% head(letters), sample(b, 1)]
res4 <-
dt %>%
.[a %in% head(letters)] %>%
.[,
{
dt0 <- .SD[1:100]
quants <-
dt0[, c] %>%
quantile(seq(0.1, 1, 0.1), na.rm = T)
.(q = quants)
}
, .(cond = b > samplpe_b)
] %>%
glm(
cond ~ q -1
, family = binomial(link = "logit")
, data = .
) %>%
summary %>%
.[[12]]
# function
rm(lm_preds)
lm_preds <- function(
sd, by, n
)
{
if(
n < 100 |
!by[['a']] %in% head(letters, 4)
)
{
res <-
list(
low = NA
, mean = NA
, high = NA
, coefs = NA
)
} else {
lmm <-
lm(
d ~ c + b
, data = sd
)
preds <-
stats::predict.lm(
lmm
, sd
, interval = "prediction"
)
res <-
list(
low = preds[, 2]
, mean = preds[, 1]
, high = preds[, 3]
, coefs = coefficients(lmm)
)
}
res
}
res5 <-
dt %>%
.[e < 0] %>%
.[.[, .I[b > 0]]] %>%
.[, `:=` (
low = as.numeric(lm_preds(.SD, .BY, .N)[[1]])
, mean = as.numeric(lm_preds(.SD, .BY, .N)[[2]])
, high = as.numeric(lm_preds(.SD, .BY, .N)[[3]])
, coef_c = as.numeric(lm_preds(.SD, .BY, .N)[[4]][1])
, coef_b = as.numeric(lm_preds(.SD, .BY, .N)[[4]][2])
, coef_int = as.numeric(lm_preds(.SD, .BY, .N)[[4]][3])
)
, a
] %>%
.[!is.na(mean), -'e', with = F]
# plot
plo <-
res5 %>%
ggplot +
facet_wrap(~ a) +
geom_ribbon(
aes(
x = c * coef_c + b * coef_b + coef_int
, ymin = low
, ymax = high
, fill = a
)
, size = 0.1
, alpha = 0.1
) +
geom_point(
aes(
x = c * coef_c + b * coef_b + coef_int
, y = mean
, color = a
)
, size = 1
) +
geom_point(
aes(
x = c * coef_c + b * coef_b + coef_int
, y = d
)
, size = 1
, color = 'black'
) +
theme_minimal()
print(plo)
Burimi: habr.com
