Rreth data.table

Ky kjo shĂ«nim do tĂ« jetĂ« i dobishĂ«m pĂ«r ata qĂ« pĂ«rdorin bibliotekĂ«n pĂ«rpunuese tĂ« tĂ« dhĂ«nave tabelare pĂ«r R — data.table, dhe ndoshta do tĂ« jenĂ« tĂ« kĂ«naqur tĂ« shohin fleksibilitetin e aplikimit tĂ« saj nĂ« shembuj tĂ« ndryshĂ«m.

Të frymëzuar nga një shembull i mirë koleget, dhe duke shpresuar që tashmë keni lexuar artikullin e tij, propozoj të thellojmë më shumë në optimizimin e kodit dhe performancën bazuar në data.table.

Hyrje: nga vjen data.table?

Më së miri është të filloni njohjen me bibliotekën nga pak larg, pra, nga struktura e të dhënave, nga e cila mund të krijohet objekti data.table (në vazhdim, DT).

Array

Kodi

## arrays ---------

arrmatr <- array(1:20, c(4,5))

class(arrmatr)

typeof(arrmatr)

is.array(arrmatr)

is.matrix(arrmatr)

Një nga ato struktura është array (?base::array). Ashtu si në gjuhët e tjera, array-t këtu janë shumë-dimensionale. Megjithatë, është interesante se, për shembull, array dy-dimensional fillon të trashëgojë cilësi nga klasa e matricës (?base::matrix), ndërsa array një-dimensional, çka është gjithashtu e rëndësishme, nuk trashëgon nga vektori (?base::vector).

NĂ« kĂ«tĂ« kontekst, Ă«shtĂ« e nevojshme tĂ« kuptohet se lloji i tĂ« dhĂ«nave qĂ« pĂ«rmban ndonjĂ« objekt duhet tĂ« kontrollohet nga funksioni base::typeof, i cili kthen pĂ«rshkrimin e brendshĂ«m tĂ« llojit sipas R Internals — protokollin e pĂ«rgjithshĂ«m tĂ« gjuhĂ«s, i lidhur me origjinĂ«n C.

Një komandë tjetër, për të përcaktuar klasën e objektit, base::class, kthen në rastin e vektorëve llojin vektorial (ai ndryshon emrin nga ai i brendshëm, por gjithashtu lejon të kuptohet lloji i të dhënave).

Lista

Nga array dy-dimensional, që është matrica, mund të kaloni në listë (?base::list).

Kodi

## lists ------------------

mylist <- as.list(arrmatr)

is.vector(mylist)

is.list(mylist)

Këtu ndodhin disa gjëra njëherësh:

  • Dimensioni i dytĂ« i matrices shkurtohet, domethĂ«nĂ«, ne marrim njĂ«kohĂ«sisht njĂ« listĂ« dhe njĂ« vektor.
  • Lista, kĂ«shtu, trashĂ«gon nga kĂ«to klasa. Duhet pasur parasysh se njĂ«rit element tĂ« listĂ«s do t'i pĂ«rgjigjet njĂ« vlerĂ« (skalar) nga qelia e matrices-array.

Duke qenë se lista është gjithashtu një vektor, disa funksione për vektorët mund t'i aplikohen.

Dataframe

Nga lista, matrica ose vektori mund të kaloni 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 nĂ« tĂ«: dataframe trashĂ«gon nga lista! Kolonat e dataframe janĂ« qeliza tĂ« listĂ«s. Kjo do tĂ« jetĂ« e rĂ«ndĂ«sishme mĂ« tej, kur do tĂ« pĂ«rdorim funksione tĂ« aplikuara nĂ« lista.

data.table

Mund të merrni DT (?data.table::data.table) nga dataframe, lista, vektori ose matrica. Për shembull, kështu (in place).

Kodi

## data.tables -----------------------
library(data.table)

data.table::setDT(df)

is.list(df)

is.data.frame(df)

is.data.table(df)

E dobishme është që, ashtu si dataframe, DT trashëgon cilësitë e listës.

DT dhe memorja

Ndryshe nga të gjitha objektet e tjera në R base, DT kalojnë përmes lidhjes. Nëse duhet të bëni kopjim në një hapësirë të re kufij, nevojitet funksioni data.table::copy ose nevojitet të bëni selektim 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)

Këtu përfundon hyrja. DT është një vazhdim i zhvillimit të strukturave të dhënave në R, të cilat ndodhin kryesisht përmes zgjerimit dhe përshpejtimit të operacioneve që kryhen mbi objektet e klasës data.frame. Këtu ruhet trashëgimia nga primitivet e tjera.

Disa shembuj të përdorimit të pronave data.table

Si njĂ« listë 

Të iterosh mbi rreshtat e data.frame ose DT nuk është ideja më e mirë, pasi kodi i ciklit në gjuhën R është shumë më i ngadalshëm C, ndërsa të kalosh nëpër kolona, të cilat zakonisht janë shumë më të pakta, është plotësisht e mundshme. Duke ecur përmes kolonave, kujtojmë se çdo kolonë është një element liste, që përmban, zakonisht, një vektor. Dhe operacionet mbi vektorët janë mirë të vektorizuara në funksionet bazë të gjuhës. Gjithashtu, mund të përdoren operatorë seleksioni, të veçantë për listat dhe vektorët: `[[`, `$`.

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)))

Vektoriza

Nëse ka nevojë për të kaluar përmes rreshtave të një DT të madh, zgjidhja më e mirë do të ishte të shkruhet një funksion me vektorizim. Por nëse kjo nuk është e mundur, duhet të kujtojmë se cikli brenda DT është gjithsesi më i shpejtë se cikli në R, pasi ekzekutohet në C.

Le të provojmë në një shembull më të madh me 100K rreshta. Do të nxjerrim shkronjën e parë nga fjalët, që përfshihen në kolonën vektor w.

Përditësuar

Kodi

library(magrittr)
library(microbenchmark)

## Shembuj më të mëdhenj ----

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)
	   ]
})

# i dyti

first_l_f %
		do.call(rbind, .) %>%
		`[`(,1)
}

dt[, first_l := NULL]

microbenchmark({
	dt[
		, first_l := .(first_l_f(w))
		]
})

# i tretë

first_l_f2 %
		unlist %>%
		matrix(nrow = 3) %>%
		`[`(1,)
}

dt[, first_l := NULL]

microbenchmark({
	dt[
		, first_l := .(first_l_f2(w))
		]
})

Prova e parë me iterim mbi rreshtat:

Njësi: milisekonda
expr min
{ dt[, `:=`(first_l, unlist(strsplit(w, split = " ", fixed = T))[1]), by = 1:nrow(dt)] } 439.6217
lq mean median uq max neval
451.9998 460.1593 456.2505 460.9147 621.4042 100

Rastër i dytë, ku vektorizimi bëhet përmes kthimit të listës në matricë dhe marrjes së elementeve në prerjen me indeksin 1 (kjo është vetë vektorizimi). Do të korrigjohem: vektorizimi në nivelin e funksionit strsplit, që di të pranojë një vektor si input. Duket se procedura e shndërrimit të listës në matricë është shumë më e rëndë se vetë vektorizimi, por në këtë rast është 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 në mesatare 3 herë.

Rast i tretë, ku është ndryshuar skema e shndërrimit në matricë.

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 në mesatare 13 herë.

Me kĂ«tĂ« gjĂ« duhet eksperimentuar, sa mĂ« shumĂ« — aq mĂ« mirĂ« do tĂ« jetĂ«.

Një tjetër shembull me vektorizimin, ku gjithashtu ka tekst, por është më afër kushteve reale: gjatësi të ndryshme fjalësh, numër i ndryshëm fjalësh. Duhet të nxirren fjalët e para 3. Ja si:

Rreth data.table

Këtu funksioni i mëparshëm nuk funksionon më, pasi vektorët kanë gjatësi të ndryshme, ndërsa ne kishim përcaktuar madhësinë e matricës. Do ta riparojmë 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

Skedari funksionoi me një shpejtësi mesatare prej 1 sekonde. Jo keq.

Të lidhura me një zinxhir


Me objektet DT mund të punosh duke përdorur chaining. Kjo duket si bashkim sintaksë brenda kllapave të djathta, në thelb, është një sheqer.

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ërmes tubave


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 makinës dhe gjëra të tjera brenda DT

Mund tĂ« pĂ«rdoren funksione lambda, por ndonjĂ«herĂ« Ă«shtĂ« mĂ« mirĂ« t'i krijosh ato veçmas, tĂ« shkruash tĂ« gjithĂ« pipeline-in e analizĂ«s sĂ« tĂ« dhĂ«nave, dhe pĂ«rpara — ato punojnĂ« brenda DT. Shembulli Ă«shtĂ« pasuruar me tĂ« gjitha veçoritĂ« e lartpĂ«rmendura, plus disa gjĂ«ra tĂ« dobishme nga arsenali DT (tĂ« tilla si qasja nĂ« vetĂ« DT brenda DT me lidhje, tĂ« vendosura ndonjĂ«herĂ« jo nĂ« radhĂ«, 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ërfundim

Shpresoj se kam arritur të krijoj një pamje të plotë, por sigurisht jo të plotë, të një objekti si data.table, duke filluar nga pronat e tij të lidhura me trashëgiminë nga klasa R dhe duke përfunduar me karakteristikat e tij të veta dhe ambientin nga elementët tidyverse. Shpresoj se kjo do t'ju ndihmojë të studioni dhe aplikoni më mirë këtë bibliotekë për punë dhe argëtim.

Rreth data.table

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

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster