Aplink duomenis.lentelę

Ši pastaba bus įdomi tiems, kurie naudoja lentelių duomenų apdorojimo biblioteką R - data.table, ir gali būti malonu matyti jos naudojimo lankstumą įvairiuose pavyzdžiuose.

Įkvėptas gero pavyzdžio kolegos, ir tikėdamasis, kad jau perskaitėte jo straipsnį, siūlau gilintis į kodo optimizavimą ir našumą remiantis duomenys. lentelė.

Įvadas: iš kur atsiranda data.table?

Pažintį su biblioteka geriausia pradėti šiek tiek iš tolo, būtent su duomenų struktūromis, iš kurių galima gauti objektą data.table (toliau – DT).

Masyvas

Kodas

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

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

class(arrmatr)

typeof(arrmatr)

is.array(arrmatr)

is.matrix(arrmatr)

Viena iš tokių struktūrų yra masyvas (?base::masyvas). Kaip ir kitomis kalbomis, čia masyvai yra daugiamačiai. Tačiau įdomu tai, kad, pavyzdžiui, dvimatis masyvas pradeda paveldėti savybes iš matricos klasės (?base::matrica), o vienmatis masyvas, kuris taip pat svarbus, nepaveldimas iš vektoriaus (?bazė::vektorius).

Reikėtų suprasti, kad bet kuriame objekte esančių duomenų tipas turėtų būti patikrintas naudojant funkciją bazė::typeof, kuris grąžina vidinio tipo aprašą pagal R Vidiniai - bendrasis su originalu susijusios kalbos protokolas C.

Kita komanda objekto klasei nustatyti yra bazė::klasė, vektorių atveju grąžina vektoriaus tipą (pavadinimu jis skiriasi nuo vidinio, bet leidžia suprasti ir duomenų tipą).

Sąrašas

Iš dvimačio masyvo, dar žinomo kaip matrica, galite pereiti į sąrašą (?base::sąrašas).

Kodas

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

mylist <- as.list(arrmatr)

is.vector(mylist)

is.list(mylist)

Keli dalykai vyksta vienu metu:

  • Antrasis matricos matmuo žlunga, tai yra, gauname ir sąrašą, ir vektorių vienu metu.
  • Taigi sąrašas paveldimas iš šių klasių. Reikia nepamiršti, kad sąrašo elementas atitiks vieną (skaliarinę) reikšmę iš masyvo matricos langelio.

Kadangi sąrašas taip pat yra vektorius, jam gali būti taikomos kai kurios vektorinės funkcijos.

Duomenų rėmelis

Iš sąrašo, matricos ar vektoriaus galite pereiti prie duomenų rėmelio (?base::data.frame).

Kodas

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

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

is.list(df)

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

Kas čia įdomaus: duomenų rėmelis paveldimas iš sąrašo! Duomenų rėmelių stulpeliai yra sąrašo langeliai. Tai bus svarbu vėliau, kai naudosime sąrašams taikomas funkcijas.

duomenys. lentelė

Gaukite DT (?duomenys.lentelė::duomenų.lentelė) gali būti iš duomenų rėmelis, sąrašas, vektorius arba matrica. Pavyzdžiui, kaip šis (vietoje).

Kodas

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

data.table::setDT(df)

is.list(df)

is.data.frame(df)

is.data.table(df)

Naudinga, kad DT, kaip ir duomenų rėmelis, paveldi sąrašo savybes.

DT ir atmintis

Skirtingai nuo visų kitų R bazės objektų, DT perduodami pagal nuorodą. Jei reikia nukopijuoti į naują atminties sritį, reikia funkcijos data.table::copy arba reikia pasirinkti iš seno objekto.

Kodas

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)

Tuo įžanga baigiama. DT yra R duomenų struktūrų kūrimo tęsinys, kuris daugiausia atsiranda dėl operacijų, atliekamų su duomenų rėmelių klasės objektais, išplėtimo ir pagreitinimo. Tuo pačiu išsaugomas paveldėjimas iš kitų primityvų.

Keli data.table ypatybių naudojimo pavyzdžiai

Kaip sąrašas...

Iteruoti duomenų rėmelio arba DT eilutes nėra gera idėja, nes ciklo kodas kalba R daug lėčiau C, tačiau visiškai įmanoma pereiti per stulpelius, kurie paprastai yra daug mažesni. Eidami per stulpelius atminkite, kad kiekvienas stulpelis yra sąrašo elementas, kuriame paprastai yra vektorius. Ir operacijos su vektoriais yra gerai vektorizuotos pagrindinėse kalbos funkcijose. Taip pat galite naudoti sąrašams ir vektoriams bendrus pasirinkimo operatorius: `[[`, "$"..

Kodas

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

Vektorizacija

Jei reikia eiti per didelio DT eilutes, geriausias sprendimas būtų parašyti funkciją su vektorizavimu. Bet jei tai neveikia, turėtumėte atsiminti, kad ciklas per DT vis dar greitesnis už ciklą R, nes jis atliekamas C.

Pabandykime tai didesniame pavyzdyje su 100 XNUMX eilučių. Pirmąją raidę ištrauksime iš žodžių, įtrauktų į vektoriaus stulpelį w.

Atnaujinta

Kodas

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

Pirmiausia paleiskite kartojimą eilutėse:

Vienetas: milisekundės
min
{ dt[, `:=`(pirmas_l, unlist(strsplit(w, split = " ", fiksuotas = T))[1]), by = 1:nrow(dt)] } 439.6217
lq vidurkis mediana uq max neval
451.9998 460.1593 456.2505 460.9147 621.4042 100

Antrasis paleidimas, kai vektorizavimas įvyksta paverčiant sąrašą į matricą ir paimant elementus iš pjūvio su indeksu 1 (pastarasis yra pats vektorizavimas). Pataisymas: vektorizavimas funkcijos lygiu strsplit, kuris gali priimti vektorių kaip įvestį. Pasirodo, sąrašo pavertimo matrica procedūra yra daug sunkesnė nei pati vektorizacija, tačiau šiuo atveju ji yra daug greitesnė nei ne vektorizuota versija.

Vienetas: milisekundės
expr min lq vidurkis mediana uq max neval
{ dt[, `:=`(pirmas_l, .(pirmas_l_f(w)))] } 93.07916 112.1381 161.9267 149.6863 185.9893 442.5199 100

Pagreitis pagal medianą in 3 kartas.

Trečiasis važiavimas, kai buvo pakeista transformacijos į matricą schema.

Vienetas: milisekundės
expr min lq vidurkis mediana uq max neval
{ dt[, `:=`(pirmas_l, .(pirmas_l_f2(w)))] } 32.60481 34.13679 40.4544 35.57115 42.11975 222.972 100

Pagreitis pagal medianą in 13 kartas.

Reikia eksperimentuoti su šiuo klausimu, kuo daugiau, tuo geriau.

Kitas pavyzdys su vektorizavimu, kur yra ir tekstas, bet jis artimas realioms sąlygoms: skirtingas žodžių ilgis, skirtingas žodžių skaičius. Turite gauti pirmuosius 3 žodžius. Kaip šitas:

Aplink duomenis.lentelę

Čia ankstesnė funkcija neveikia, nes vektoriai yra skirtingo ilgio, o mes nustatome matricos dydį. Pakartokime tai naršydami internete.

Kodas

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

Vienetas: milisekundės
expr min lq vidutinė mediana

{ dt[, `:=`((įklijuoti0("w_", 1:3)), strsplit(w, split = " ", fiksuota = T))] } 851.7623 916.071 1054.5 1035.199
uq max neval
1178.738 1356.816 100

Scenarijus veikė vidutiniu 1 sekundės greičiu. Neblogai.

Sujungta viena grandine...

Galite dirbti su DT objektais naudodami grandininę funkciją. Panašu, kad dešinėje pusėje pridedama skliausto sintaksė, iš esmės cukrus.

Kodas

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

Teka per vamzdžius...

Tas pačias operacijas galima atlikti per vamzdyną, jis atrodo panašiai, bet yra funkcionalesnis, nes galite naudoti bet kokius metodus, ne tik DT. Išveskime savo sintetinių duomenų logistinės regresijos koeficientus naudodami daugybę DT filtrų.

Kodas

# 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, mašininis mokymasis ir daugiau DT viduje

Galite naudoti lambda funkcijas, tačiau kartais geriau jas sukurti atskirai, parašyti visą duomenų analizės dujotiekį ir pirmyn – jos veikia DT viduje. Pavyzdys praturtintas visomis aukščiau paminėtomis funkcijomis ir keletu naudingų dalykų iš DT arsenalo (pavyzdžiui, prieiga prie paties DT DT viduje per nuorodą, kartais įterpiama ne nuosekliai, o taip, kad būtų).

Kodas

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

išvada

Tikiuosi, kad man pavyko sukurti išsamų, bet, žinoma, ne pilną tokio objekto, kaip data.table, vaizdą, pradedant nuo jo savybių, susijusių su paveldėjimu iš R klasių, ir baigiant jo savybėmis ir aplinka iš tvarkingų elementų. . Tikiuosi, kad tai padės jums geriau išmokti ir naudoti šią biblioteką darbe ir pramoga.

Aplink duomenis.lentelę

Dėkojame!

Pilnas kodas

Kodas

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

Šaltinis: www.habr.com

Pirkite patikimą prieglobą svetainėms su DDoS apsauga, VPS VDS serveriais 🔥 Įsigykite patikimą svetainių talpinimą su DDoS apsauga, VPS VDS serveriais | ProHoster