data.table ĂŒmber

See mÀrkus huvitab neid, kes kasutavad R-i tabeliandmete töötlemise raamatukogu data.table, ja vÔib-olla on rÔÔm nÀha selle paindlikkust erinevates nÀidetes.

HĂ€id nĂ€iteid inspireerituna kolleegid, ja loodes, et olete juba tema artikli lĂ€bi lugenud, pakun sĂŒgavamalt uurida koodi ja tootlikkuse optimeerimist, lĂ€htudes data.table.

Sissejuhatus: kust data.table pÀrineb?

Parim on alustada raamatukoguga tutvumist veidi kaugemalt, nimelt andmestruktuuridest, millest vÔib saada data.table objekti (edaspidi DT).

MaatrikssĂŒsteem

Kood

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

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

class(arrmatr)

typeof(arrmatr)

is.array(arrmatr)

is.matrix(arrmatr)

Üks selliseid struktuure on maatriks (?base::array). Nagu teistes keeltes, on maatrit siin mitmemÔÔtmelised. Kuid huvitav on see, et nĂ€iteks kahe mÔÔtmega maatriks hakkab pĂ€rima omadusi maatriksi klassilt (?base::matrix), kuid ĂŒhe mÔÔtmega maatriks, mis on samuti oluline, ei pĂ€rige vektoritelt (?base::vector).

Samuti tuleb mĂ”ista, et mingis objekti sisaldavate andmete tĂŒĂŒp tuleks kontrollida funktsiooniga base::typeof, mis tagastab sisemise tĂŒĂŒbi kirjelduse vastavalt R Internals — ĂŒldine keeleprotokoll, mis on seotud algse C.

Veel ĂŒks kĂ€sk objekti klassi mÀÀramiseks, base::class, tagastab vektorite puhul vektori tĂŒĂŒbi (see erineb sisemisest nimest, kuid vĂ”imaldab samuti mĂ”ista andmetĂŒĂŒpi).

Loend

Kaks mÔÔdet, millele jÀrgneb maatriks, saab muundada nimekirjaks (?base::list).

Kood

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

mylist <- as.list(arrmatr)

is.vector(mylist)

is.list(mylist)

Samas juhtuvad mitu asja korraga:

  • Maatriksi teine mÔÔde kokkutĂ”mbub, s.t. saame korraga nii nimekirja kui vektori.
  • Nimekiri pĂ€rib seega neist klassidest. Tuleb arvestada, et nimekirja elemendile vastab ĂŒks (skalaarne) vÀÀrtus maatriksi-massiivist.

Kuna nimekiri on ka vektor, saab sellele rakendada teatud vektorite funktsioone.

Andmeraam

Nimekirjast, maatriksist vÔi vektorist saab luua andmeraami (?base::data.frame).

Kood

## data.frames ------------

df <- as.data.frame(arrmatr)
df2 <- as.data.frame(mylist)

is.list(df)

df$V6 <- df$V1 + df$V2

Mida on huvitav: andmeraam pÀrib nimekirjast! Andmeraami veerud on nimekirja lahtrid. See on oluline hiljem, kui hakkame kasutama nimekirjadele rakendatavaid funktsioone.

data.table

Saada DT (?data.table::data.table) saab andmeraamilt, nimekirjast, vektorist vÔi maatriksist. NÀiteks, nii (in place).

Kood

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

data.table::setDT(df)

is.list(df)

is.data.frame(df)

is.data.table(df)

Kasulik on see, et, nagu andmeraam, pÀrib DT nimekirja omadusi.

DT ja mÀlu

Erinevalt teistest objektidest R pÔhijas, edastatakse DT viidatud. Kui on vajalik kopeerida uude mÀluala, on vajalik funktsioon data.table::copy vÔi on vajalik valiku tegemine vanalt objektilt.

Kood

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)

Selle sissejuhatus jÔuab lÔpule. DT on R andmestruktuuride arendamise jÀtk, mis suuresti toimub objektide klassi andframe kohta tehtavate toimingute laiendamise ja kiirendamise kaudu. Samuti sÀilib pÀrimine teistelt primitiividelt.

MÔned nÀited data.table omaduste kasutamisest

Nagu nimekiri


Itereerimine andframe vĂ”i DT ridade kaudu pole parim idee, kuna tsĂŒklikeel on R oluliselt aeglasem C, kuid tsĂŒklites veergude kaudu liikuda, mida on tavaliselt oluliselt vĂ€hem, on tĂ€iesti vĂ”imalik. Kui liigelda veergude kaudu, peame meeles, et iga veerg on nimekirja element, mis sisaldab tavaliselt vektorit. Ja operatsioonid vektoritega on hĂ€sti vektoreeritud keele pĂ”hifunktsioonide seas. Samuti on vĂ”imalik kasutada valikutehnikaid, mis on iseloomulikud nimekirjadele ja vektoritele: `[[`, `$`.

Kood

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

Vektoreerimine

Kui on vajadus töötada suure andmetabeli ridadega, on parim lahendus funktsiooni kirjutamine vektorisusega. Kuid kui see ei Ă”nnestu, tuleb meeles pidada, et tsĂŒkkel sisemine andmetabel on siiski kiirem kui tsĂŒkkel R, sest see toimub C.

Proovime suuremal nÀitel, kus on 100 000 rida. Vaatame, kuidas vÀlja tuua esimene tÀht sÔnadest, mis kuuluvad vektorveergu w.

Uuendatud

Kood

library(magrittr)
library(microbenchmark)

## Suurem nÀide ----

rown <- 100000

dt %
	.[, d := 1 + b + c + rnorm(nrow(.))]

# vektorisus

microbenchmark({
	dt[
		, first_l := unlist(strsplit(w, split = ' ', fixed = T))[1]
		, by = 1:nrow(dt)
	   ]
})

# teine

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

dt[, first_l := NULL]

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

# kolmas

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

dt[, first_l := NULL]

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

Esimene jooks ridade jÀrgi iteratsiooniga:

Ühik: millisekundid
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

Teine katse, kus vektoriseerimine toimub nimekirja maatriksiks muutmise kaudu ja elementide vÔtmisega lÔikes indeksiga 1 (see ongi tegelikult vektoriseerimine). TÀpsustan: vektoriseerimine funktsiooni tasemel strsplit, mis suudab vastu vÔtta vektori. Selgub, et nimekirja maatriksiks muutmise protseduur on palju keerulisem kui ise vektoriseerimine, kuid ka sel juhul on see palju kiirem kui vektoriseerimata variant.

Ühik: millisekundid
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

Keskmise jÀrgi kiirus 3 korda.

Kolmas katse, kus on muudetud maatriksiks muutmise skeemi.

Ühik: millisekundid
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

Keskmise jÀrgi kiirus 13 korda.

Selle asjaga tuleb katsetada, mida rohkem — seda parem saab olema.

Veel ĂŒks nĂ€ide vektoriseerimisest, kus samuti tekst, kuid see on lĂ€hedane reaalsetele tingimustele: erineva sĂ”na pikkusega, erinev sĂ”nade arv. On vaja vĂ€lja vĂ”tta esimesed 3 sĂ”na. Nii:

data.table ĂŒmber

Siin eelmine funktsioon ei tööta, kuna vektorid on erineva pikkusega, ja me mÀÀrasime maatriksi suuruse. Teeme selle ĂŒmber, uurides internetist.

Kood

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

Ühik: millisekundid
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

Skript töötas keskmise kiirus 1 sekund. Mitte paha.

Seotud ketiga


Objektidega DĐą saab töötada, kasutades chaining. See nĂ€eb vĂ€lja nagu sulgude Ă”igelt kĂŒlgede kinni panemine, sisuliselt on see magusam.

Kood

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

Voolab torude kaudu


Sarnaseid operatsioone saab teha lĂ€bi piping, see nĂ€eb vĂ€lja sarnane, kuid on funktsionaalselt rikkam, kuna saab kasutada mis tahes meetodeid, mitte ainult DĐą-d. VĂ€ljastame logistikaregressiooni koefitsiendid meie sĂŒnteetiliste andmete jaoks koos mitmete filtritega DĐą-l.

Kood

# 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, masinÔpe ja muu Dй sees

Saab kasutada lambda-funktsioone, kuid mĂ”nikord on parem luua need eraldi, kirjutada kogu andmeanalĂŒĂŒsi pipeline, ja edasi — need töötavad DĐą sees. NĂ€ide on rikastatud kĂ”igi ĂŒlaltoodud funktsioonidega, pluss mĂ”ned kasulikud asjad DĐą arsenalist (nĂ€iteks viitamine DĐą-le oma DĐą-sse, mĂ”nikord jĂ€rjestikku mitte, aga et see oleks).

Kood

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

KokkuvÔte

Loodan, et olen suutnud luua tervikliku, kuid kindlasti mitte tĂ€ieliku, ĂŒlevaate sellisest objektist nagu data.table, alates selle omadustest, mis on seotud R klassidelt pĂ€rimisega, kuni selle enda eripĂ€rade ja tidyverse elementidega. Loodan, et see aitab teil paremini uurida ja kasutada seda teeki töötamiseks ja meelelahutuseks.

data.table ĂŒmber

AitÀh!

TĂ€ielik kood

Kood

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

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster