Rreth data.table

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ë kolegu, 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:

Rreth data.table

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.

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

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster