Bydd y nodyn hwn o ddiddordeb i'r rhai sy'n defnyddio'r llyfrgell prosesu data tablau ar gyfer R - data.table, ac efallai y byddant yn falch o weld hyblygrwydd ei ddefnydd mewn amrywiol enghreifftiau.
Wedi'i ysbrydoli gan esiampl dda , a gobeithio eich bod eisoes wedi darllen ei erthygl, rwy'n cynnig cloddio'n ddyfnach tuag at optimeiddio cod a pherfformiad yn seiliedig ar data.tabl.
Cyflwyniad: O ble mae data.table yn dod?
Mae'n well dechrau dod yn gyfarwydd â'r llyfrgell ychydig o bell, sef, gyda'r strwythurau data y gellir cael y gwrthrych data.table ohonynt (y cyfeirir ati yma wedi hyn fel DT).
Array
Cod
## arrays ---------
arrmatr <- array(1:20, c(4,5))
class(arrmatr)
typeof(arrmatr)
is.array(arrmatr)
is.matrix(arrmatr)
Un strwythur o'r fath yw arae (?Sylfaen::arae). Fel mewn ieithoedd eraill, mae araeau yma yn aml-ddimensiwn. Fodd bynnag, y peth diddorol yw, er enghraifft, bod amrywiaeth dau ddimensiwn yn dechrau etifeddu eiddo o'r dosbarth matrics. (? sylfaen::matrics), ac nid yw arae un dimensiwn, sydd hefyd yn bwysig, yn etifeddu o fector (?Sylfaen::fector).
Dylid deall y dylid gwirio'r math o ddata a gynhwysir mewn unrhyw wrthrych gan ddefnyddio'r swyddogaeth sylfaen::typeof, sy'n dychwelyd y disgrifiad math mewnol yn ôl R Mewnolion - protocol cyffredinol yr iaith sy'n gysylltiedig â'r gwreiddiol C.
Gorchymyn arall i benderfynu dosbarth gwrthrych yw sylfaen::class, yn achos fectorau, yn dychwelyd y math fector (mae'n wahanol o ran enw i'r un mewnol, ond hefyd yn caniatáu ichi ddeall y math o ddata).
Rhestr
O arae dau ddimensiwn, a elwir hefyd yn fatrics, gallwch fynd i'r rhestr (?Sylfaen::rhestr).
Cod
## lists ------------------
mylist <- as.list(arrmatr)
is.vector(mylist)
is.list(mylist)
Mae sawl peth yn digwydd ar unwaith:
- Mae ail ddimensiwn y matrics yn cwympo, hynny yw, rydyn ni'n cael rhestr a fector ar yr un pryd.
- Felly mae'r rhestr yn etifeddu o'r dosbarthiadau hyn. Rhaid cofio y bydd elfen rhestr yn cyfateb i un gwerth (scalar) o gell y matrics arae.
Oherwydd bod rhestr hefyd yn fector, gellir cymhwyso rhai swyddogaethau fector iddo.
Ffrâm data
Gallwch fynd o restr, matrics neu fector i ffrâm ddata (?Sylfaen::data.frame).
Cod
## data.frames ------------
df <- as.data.frame(arrmatr)
df2 <- as.data.frame(mylist)
is.list(df)
df$V6 <- df$V1 + df$V2
Beth sy'n ddiddorol amdano: mae'r ffrâm ddata yn etifeddu o'r rhestr! Celloedd rhestr yw colofnau ffrâm data. Bydd hyn yn bwysig yn ddiweddarach pan fyddwn yn defnyddio swyddogaethau a gymhwysir i restrau.
data.tabl
Cael DT (?tabl.data::tabl) gall fod o ffrâm data, rhestr, fector neu fatrics. Er enghraifft, fel hyn (yn ei le).
Cod
## data.tables -----------------------
library(data.table)
data.table::setDT(df)
is.list(df)
is.data.frame(df)
is.data.table(df)
Mae'n ddefnyddiol, fel ffrâm ddata, bod DT yn etifeddu priodweddau rhestr.
DT a'r cof
Yn wahanol i'r holl wrthrychau eraill yn sylfaen R, mae DTs yn cael eu pasio trwy gyfeiriad. Os oes angen i chi wneud copi i ardal cof newydd, mae angen swyddogaeth arnoch chi data.table::copi neu mae angen i chi wneud detholiad o'r hen wrthrych.
Cod
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)
Mae hyn yn cloi'r cyflwyniad. Mae DT yn barhad o ddatblygiad strwythurau data yn R, sy'n digwydd yn bennaf oherwydd ehangu a chyflymu gweithrediadau a gyflawnir ar wrthrychau'r dosbarth ffrâm data. Ar yr un pryd, cedwir etifeddiaeth oddi wrth y cyntefig eraill.
Rhai enghreifftiau o ddefnyddio priodweddau data.table
Fel rhestr...
Nid yw ailadrodd dros y rhesi o ffrâm data neu DT yn syniad da, gan fod y cod dolen yn yr iaith R llawer arafach C, ond mae'n eithaf posibl dolennu trwy'r colofnau, sydd fel arfer yn llawer llai. Wrth fynd drwy'r colofnau, cofiwch fod pob colofn yn elfen o restr, fel arfer yn cynnwys fector. Ac mae gweithrediadau ar fectorau wedi'u fectoreiddio'n dda yn swyddogaethau sylfaenol yr iaith. Gallwch hefyd ddefnyddio gweithredwyr dethol sy'n gyffredin i restrau a fectorau: `[[`, `$`.
Cod
## 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)))
Fectoreiddio
Os oes angen mynd trwy linellau DT mawr, yr ateb gorau fyddai ysgrifennu ffwythiant gyda fectoreiddio. Ond os nad yw hyn yn gweithio, yna dylech gofio bod y cylch y tu mewn Mae DT yn dal yn gyflymach na'r cylch R, gan ei fod yn cael ei berfformio ar C.
Gadewch i ni roi cynnig arni ar enghraifft fwy gyda rhesi 100K. Byddwn yn tynnu'r llythyren gyntaf o'r geiriau sydd wedi'u cynnwys yn y golofn fector w.
Diweddarwyd
Cod
library(magrittr)
library(microbenchmark)
## 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
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))
]
})
Rhedeg gyntaf yn ailadrodd dros resi:
Uned: milieiliadau
expr min
{ dt[, `:=`(first_l, unlist(strsplit(w, split = " ", sefydlog = T))[1]), gan = 1:nrow(dt)] } 439.6217
lq cymedrig canolrif uq max neval
451.9998 460.1593 456.2505 460.9147 621.4042 100
Yr ail rediad, lle mae fectoreiddio yn digwydd trwy droi'r rhestr yn fatrics a chymryd elfennau ar y sleisen gyda mynegai 1 (yr olaf yw'r fectoreiddio ei hun). Cywiro: fectoreiddio ar lefel swyddogaeth strsplit, sy'n gallu derbyn fector fel mewnbwn. Mae'n ymddangos bod y weithdrefn ar gyfer troi rhestr yn fatrics yn llawer anoddach na fectoreiddio ei hun, ond yn yr achos hwn mae'n llawer cyflymach na'r fersiwn heb fector.
Uned: milieiliadau
expr min lq cymedrig canolrif uq max neval
{ dt[, `:=`(cyntaf_l, .(cyntaf_l_f(w)))) } 93.07916 112.1381 161.9267 149.6863 185.9893 442.5199 100
Cyflymiad yn ôl canolrif yn 3 gwaith.
Y trydydd rhediad, lle newidiwyd y cynllun trawsnewid i'r matrics.
Uned: milieiliadau
expr min lq cymedrig canolrif uq max neval
{ dt[, `:=`(cyntaf_l, .(cyntaf_l_f2(w)))) } 32.60481 34.13679 40.4544 35.57115 42.11975 222.972 100
Cyflymiad yn ôl canolrif yn 13 gwaith.
Mae angen i chi arbrofi gyda'r mater hwn, y mwyaf, y gorau fydd hi.
Enghraifft arall gyda fectoreiddio, lle mae testun hefyd, ond mae'n agos at amodau real: hyd gwahanol eiriau, nifer gwahanol o eiriau. Mae angen i chi gael y 3 gair cyntaf. Fel hyn:

Yma nid yw'r ffwythiant blaenorol yn gweithio, gan fod y fectorau o wahanol hyd, ac rydym yn gosod maint y matrics. Gadewch i ni ail-wneud hyn trwy gloddio o gwmpas ar y Rhyngrwyd.
Cod
# 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))
]
Uned: milieiliadau
expr min lq cymedrig canolrif
{ dt[, `:=`((past0("w_", 1:3)), strsplit(w, hollt = " ", sefydlog = T))] } 851.7623 916.071 1054.5 1035.199
uq nefol max
1178.738 1356.816 100
Roedd y sgript yn rhedeg ar fuanedd cyfartalog o 1 eiliad. Ddim yn ddrwg.
Wedi'i gysylltu gan un gadwyn...
Gallwch weithio gyda gwrthrychau DT gan ddefnyddio cadwyno. Mae'n edrych fel atodi cystrawen braced i'r dde, yn y bôn siwgr.
Cod
# 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)]
Yn llifo drwy'r pibellau...
Gellir gwneud yr un gweithrediadau trwy bibellau, mae'n edrych yn debyg, ond mae'n gyfoethocach yn swyddogaethol, oherwydd gallwch chi ddefnyddio unrhyw ddulliau, nid DT yn unig. Dewch i ni gael cyfernodau atchweliad logistaidd ar gyfer ein data synthetig gyda nifer o hidlwyr ar DT.
Cod
# 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]]
Ystadegau, dysgu peirianyddol a mwy y tu mewn i DT
Gallwch ddefnyddio swyddogaethau lambda, ond weithiau mae'n well eu creu ar wahân, ysgrifennu'r biblinell dadansoddi data cyfan, a mynd ymlaen - maen nhw'n gweithio y tu mewn i'r DT. Mae'r enghraifft wedi'i chyfoethogi â'r holl nodweddion uchod, ynghyd â sawl peth defnyddiol o'r arsenal DT (fel cyrchu'r DT ei hun y tu mewn i'r DT trwy ddolen, weithiau'n cael ei fewnosod nid yn ddilyniannol, ond fel ei fod).
Cod
# 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)
Casgliad
Rwy’n gobeithio fy mod wedi gallu creu darlun cyflawn, ond, wrth gwrs, nid yn gyflawn, o wrthrych o’r fath â data.table, gan ddechrau o’i briodweddau sy’n gysylltiedig ag etifeddiaeth o ddosbarthiadau R ac yn gorffen gyda’i nodweddion a’i amgylchedd ei hun o elfennau taclus. . Rwy'n gobeithio y bydd hyn yn eich helpu i ddysgu a defnyddio'r llyfrgell hon yn well ar gyfer gwaith a adloniant.

Diolch yn fawr!
Cod llawn
Cod
## 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)
Ffynhonnell: hab.com
