data.table'ın etrafında

Bu not, R - data.table için tablolu veri işleme kitaplığını kullananların ilgisini çekecektir ve çeşitli örneklerde kullanımının esnekliğini görmekten memnun olabilir.

İyi bir örnekten ilham aldık Meslektaşlarımve makalesini zaten okumuş olduğunuzu umuyorum, kod optimizasyonuna ve performansa dayalı olarak daha derinlere inmeyi öneriyorum. veri tablosu.

Giriş: data.table nereden geliyor?

Kütüphaneyi biraz uzaktan, yani data.table nesnesinin (bundan sonra DT olarak anılacaktır) elde edilebileceği veri yapılarını tanımaya başlamak en iyisidir.

sıra

Kod

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

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

class(arrmatr)

typeof(arrmatr)

is.array(arrmatr)

is.matrix(arrmatr)

Böyle bir yapı bir dizidir (?temel::dizi). Diğer dillerde olduğu gibi burada da diziler çok boyutludur. Ancak ilginç olan, örneğin iki boyutlu bir dizinin, matris sınıfından özellikler devralmaya başlamasıdır. (?taban::matris) ve tek boyutlu bir dizi, ki bu da önemlidir, bir vektörden miras almaz (?temel::vektör).

Herhangi bir nesnede bulunan veri türünün, işlev kullanılarak kontrol edilmesi gerektiği anlaşılmalıdır. temel::typeofgöre dahili tür açıklamasını döndüren R Dahili - orijinalle ilişkili dilin genel protokolü C.

Bir nesnenin sınıfını belirlemek için başka bir komut temel::sınıf, vektörler söz konusu olduğunda, vektör türünü döndürür (adı dahili olandan farklıdır, ancak aynı zamanda veri türünü anlamanıza da olanak tanır).

Liste

Matris olarak da bilinen iki boyutlu bir diziden listeye gidebilirsiniz (?temel::liste).

Kod

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

mylist <- as.list(arrmatr)

is.vector(mylist)

is.list(mylist)

Aynı anda birkaç şey olur:

  • Matrisin ikinci boyutu çöker, yani aynı anda hem liste hem de vektör elde ederiz.
  • Böylece liste bu sınıflardan miras alır. Bir liste öğesinin dizi matrisinin bir hücresindeki bir (skaler) değere karşılık geleceği akılda tutulmalıdır.

Liste aynı zamanda bir vektör olduğundan bazı vektör fonksiyonları ona uygulanabilir.

Veri çerçevesi

Bir listeden, matristen veya vektörden veri çerçevesine (?base::data.frame).

Kod

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

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

is.list(df)

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

İlginç olan şey: veri çerçevesi listeden miras alıyor! Veri çerçevesi sütunları liste hücreleridir. Bu daha sonra listelere uygulanan işlevleri kullandığımızda önemli olacaktır.

veri tablosu

DT'yi alın (?veri.tablo::veri.tablo) itibaren olabilir veri çerçevesi, liste, vektör veya matris. Mesela şöyle (yerinde).

Kod

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

data.table::setDT(df)

is.list(df)

is.data.frame(df)

is.data.table(df)

Bir veri çerçevesi gibi bir DT'nin de bir listenin özelliklerini devralması kullanışlıdır.

CE ve bellek

R tabanındaki diğer tüm nesnelerin aksine, CE'ler referans yoluyla iletilir. Yeni bir hafıza alanına kopyalama yapmanız gerekiyorsa, bir işleve ihtiyacınız vardır. veri.tablo::kopyala ya da eski nesneden seçim yapmanız gerekiyor.

Kod

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)

Bu, girişi tamamlıyor. DT, esas olarak veri çerçevesi sınıfının nesneleri üzerinde gerçekleştirilen işlemlerin genişletilmesi ve hızlandırılması nedeniyle ortaya çıkan R'deki veri yapılarının gelişiminin bir devamıdır. Aynı zamanda diğer ilkellerden miras da korunur.

data.table özelliklerinin kullanımına ilişkin bazı örnekler

Liste gibi...

Bir veri çerçevesinin veya DT'nin satırları üzerinde yineleme yapmak iyi bir fikir değildir, çünkü dildeki döngü kodu R çok daha yavaş C, ancak genellikle çok daha küçük olan sütunlar arasında geçiş yapmak oldukça mümkündür. Sütunları incelerken, her sütunun genellikle bir vektör içeren bir listenin öğesi olduğunu unutmayın. Ve vektörler üzerindeki işlemler, dilin temel işlevlerinde iyi bir şekilde vektörleştirilmiştir. Listelerde ve vektörlerde ortak olan seçim işleçlerini de kullanabilirsiniz: '[[', '$'.

Kod

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

vektörleştirme

Büyük bir CE'nin satırlarının üzerinden geçme ihtiyacı varsa en iyi çözüm, vektörleştirmeli bir fonksiyon yazmak olacaktır. Ancak bu işe yaramazsa, o zaman döngünün içinde DT hâlâ döngüden daha hızlı Rtarihinde gerçekleştirildiğinden C.

100K satırlı daha büyük bir örnek üzerinde deneyelim. Vektör sütununda yer alan kelimelerin ilk harfini çıkaracağız w.

Güncellenmiş

Kod

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

İlk önce satırlar üzerinde yineleme yaparak çalıştırın:

Birim: milisaniye
ifade dk.
{ dt[, `:=`(ilk_l, unlist(strsplit(w, split = " ", sabit = T))[1]), by = 1:nrow(dt)] } 439.6217
lq ortalama medyan uq max neval
+451.9998 460.1593 456.2505 460.9147 621.4042 100

Listeyi bir matrise dönüştürerek ve dilimdeki öğeleri 1 indeksiyle alarak vektörleştirmenin gerçekleştiği ikinci çalıştırma (ikincisi vektörleştirmenin kendisidir). Düzeltme: fonksiyon seviyesinde vektörleştirme strsplitbir vektörü girdi olarak kabul edebilir. Bir listeyi matrise dönüştürme prosedürünün vektörleştirmenin kendisinden çok daha zor olduğu ortaya çıktı, ancak bu durumda vektörleştirilmemiş versiyondan çok daha hızlı.

Birim: milisaniye
ifade min lq ortalama medyan uq maks neval
{ dt[, `:=`(ilk_l, .(ilk_l_f(w))))] } 93.07916 112.1381 161.9267 149.6863 +185.9893 442.5199 100

Ortancaya göre hızlanma 3 kez.

Matrise dönüşüm şemasının değiştirildiği üçüncü çalıştırma.

Birim: milisaniye
ifade min lq ortalama medyan uq maks neval
{ dt[, `:=`(ilk_l, .(ilk_l_f2(w))))] } 32.60481 34.13679 40.4544 35.57115 +42.11975 222.972 100

Ortancaya göre hızlanma 13 kez.

Bu konuyu denemeniz gerekiyor, ne kadar çok olursa o kadar iyi olur.

Metnin de bulunduğu ancak gerçek koşullara yakın olduğu vektörleştirmeye ilişkin başka bir örnek: farklı sözcük uzunlukları, farklı sayıda sözcük. İlk 3 kelimeyi bulmanız gerekiyor. Bunun gibi:

data.table'ın etrafında

Burada önceki fonksiyon çalışmıyor çünkü vektörler farklı uzunluklarda ve biz matris boyutunu ayarlıyoruz. İnterneti araştırarak bunu tekrarlayalım.

Kod

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

Birim: milisaniye
ifade min lq ortalama medyan

{ dt[, `:=`((paste0(“w_”, 1:3))), strsplit(w, split = " ", sabit = T))] } 851.7623 916.071 1054.5 1035.199
uq max neval
+1178.738 1356.816 100

Komut dosyası ortalama 1 saniyelik bir hızda çalıştı. Fena değil.

Tek zincirle bağlı...

Zincirlemeyi kullanarak DT nesneleriyle çalışabilirsiniz. Parantez sözdizimini sağa, aslında şekere eklemek gibi görünüyor.

Kod

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

Borulardan akıyor...

Aynı işlemler borulama yoluyla da yapılabilir, benzer görünür ancak işlevsel olarak daha zengindir, çünkü yalnızca DT'yi değil herhangi bir yöntemi kullanabilirsiniz. DT'de bir takım filtreler kullanarak sentetik verilerimiz için lojistik regresyon katsayılarını türetelim.

Kod

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

DT'de istatistikler, makine öğrenimi ve daha fazlası

Lambda işlevlerini kullanabilirsiniz, ancak bazen bunları ayrı ayrı oluşturmak, tüm veri analizi hattını yazmak ve devam etmek daha iyidir; bunlar DT'nin içinde çalışır. Örnek, yukarıdaki tüm özelliklerin yanı sıra DT cephaneliğinden birkaç faydalı şeyle zenginleştirilmiştir (örneğin, DT'nin kendisine DT'nin içine bir bağlantı yoluyla erişmek, bazen sıralı olarak değil, öyle olacak şekilde eklemek gibi).

Kod

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

Sonuç

Umarım data.table gibi bir nesnenin, R sınıflarından kalıtımla ilişkili özelliklerinden başlayıp, düzenli öğelerden kendi özellikleri ve ortamıyla biten eksiksiz, ancak elbette tam olmayan bir resmini oluşturabilmişimdir. . Umarım bu, bu kütüphaneyi daha iyi öğrenmenize ve iş için kullanmanıza yardımcı olur. eğlence.

data.table'ın etrafında

Teşekkürler!

Tam kod

Kod

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

Kaynak: habr.com

DDoS korumalı siteler, VPS VDS sunucuları için güvenilir hosting satın alın 🔥 DDoS korumalı, güvenilir VPS ve VDS sunucu barındırma hizmeti satın alın | ProHoster