Sekitar data.tabel

Catatan ini akan menarik bagi mereka yang menggunakan pustaka pemrosesan data tabel untuk R - data. Tabel, dan, mungkin, akan senang melihat fleksibilitas aplikasinya dengan berbagai contoh.

Terinspirasi oleh contoh yang baik dari seorang kolega , dan berharap Anda telah membaca artikelnya, saya sarankan menggali lebih dalam untuk mengoptimalkan kode dan kinerja berdasarkan data.table .

Pendahuluan: dari mana data.tabel berasal?


Cara terbaik untuk mulai berkenalan dengan perpustakaan sedikit dari jauh, yaitu, dari struktur data dari mana data.tabel objek (selanjutnya, DT) dapat diperoleh.

Himpunan


Kode
## arrays ---------

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

class(arrmatr)

typeof(arrmatr)

is.array(arrmatr)

is.matrix(arrmatr)



Salah satu struktur ini adalah array ( ? Base :: array ). Seperti dalam bahasa lain, array bersifat multidimensi di sini. Namun, menarik bahwa, misalnya, array dua dimensi mulai mewarisi properti dari kelas matriks (? Basis :: matriks ), dan array satu dimensi, yang juga penting, tidak mewarisi dari vektor ( ? Basis :: vektor ).

Harus dipahami bahwa tipe data yang terkandung dalam fasilitas harus diperiksa fungsi dasarnya :: typeof , yang mengembalikan deskripsi internal dari tipe tersebut sesuai dengan R Internals - protokol bahasa umum yang terkait dengan C yang pertama kali lahir .

Perintah lain untuk menentukan kelas suatu objek adalah base :: class, mengembalikan dalam kasus vektor tipe vektor (berbeda dari nama internal, tetapi juga memungkinkan Anda untuk memahami tipe data).

Daftar


Dari array dua dimensi, ini adalah matriks, Anda dapat pergi ke daftar ( ? Basis :: daftar ).

Kode
## lists ------------------

mylist <- as.list(arrmatr)

is.vector(mylist)

is.list(mylist)


Dalam hal ini, beberapa hal terjadi sekaligus:

  • Dimensi kedua dari matriks itu runtuh, yaitu, kita mendapatkan daftar dan vektor.
  • Daftar demikian mewarisi dari kelas-kelas ini. Harus diingat bahwa nilai (skalar) tunggal dari sel array matriks akan sesuai dengan item daftar.

Karena kenyataan bahwa daftar tersebut juga merupakan vektor, beberapa fungsi untuk vektor dapat diterapkan padanya.

Kerangka data


Dari daftar, matriks atau vektor, Anda dapat pergi ke bingkai data ( ? Base :: data.frame ).

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

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

is.list(df)

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


Apa yang menarik di dalamnya: bingkai data mewarisi dari daftar! Kolom bingkai data adalah sel daftar. Ini akan menjadi penting di masa mendatang ketika kami menggunakan fungsi yang diterapkan pada daftar.

tabel data


Anda bisa mendapatkan DT ( ? Data.table :: data.table ) dari bingkai data , daftar, vektor atau matriks. Misalnya, seperti ini (di tempat).

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

data.table::setDT(df)

is.list(df)

is.data.frame(df)

is.data.table(df)


Berguna bahwa, seperti kerangka data, DT mewarisi properti daftar.

DT dan memori


Tidak seperti semua objek lain dalam basis R, DT dilewatkan dengan referensi. Jika Anda perlu menyalin ke area memori baru, Anda memerlukan fungsi data.table :: copy atau Anda perlu memilih dari objek yang lama.

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


Tentang pengantar ini berakhir. DT adalah kelanjutan dari pengembangan struktur data dalam R, yang terutama terjadi karena ekspansi dan percepatan operasi yang dilakukan pada objek-objek kelas bingkai data. Dalam hal ini, warisan dari primitif lain dipertahankan.

Beberapa contoh menggunakan properti data.table


Seperti daftar ...


Iterasi di atas baris bingkai data atau DT bukan ide yang baik, karena kode loop dalam bahasa R jauh lebih lambat daripada C , dan sangat mungkin untuk berjalan melalui kolom dalam kolom yang biasanya jauh lebih sedikit. Berjalan melalui kolom, ingat bahwa setiap kolom adalah item daftar, yang biasanya berisi vektor. Dan operasi pada vektor dengan baik vektor dalam fungsi dasar bahasa. Anda juga dapat menggunakan operator pemilihan khusus untuk daftar dan vektor: `[[`, `$` .

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


Vektorisasi


Jika ada kebutuhan untuk melewati garis-garis DT besar, menulis fungsi dengan vektorisasi adalah solusi terbaik. Tetapi jika tidak bekerja, harus diingat bahwa siklus dalam DT masih siklus lebih cepat di R , karena berjalan pada C .

Mari kita coba contoh yang lebih besar dengan 100 ribu baris. Kami akan menarik huruf pertama dari kata-kata yang termasuk dalam vektor kolom w .

Diperbarui

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


Run pertama iterates melalui baris:

Satuan: milidetik
expr min
{dt [, `: =` (first_l, unlist (strsplit (w, split = "", fixed = T)) [1]), dengan = 1: nrow (dt)]} 439.6217 lq
mean median uq maks neval
451.9998 460.1593 456.2505 460.9147 621.4042 100

Jalankan kedua, di mana vektorisasi dilakukan dengan mengubah daftar menjadi matriks dan mengambil elemen pada slice dengan indeks 1 (yang terakhir sebenarnya vektorisasi) . Saya akan memulihkan: vektorisasi pada tingkat fungsi strsplit , yang dapat mengambil vektor sebagai input. Ternyata proses mengubah daftar menjadi matriks jauh lebih sulit daripada vektorisasi itu sendiri, tetapi dalam hal ini jauh lebih cepat daripada versi non-vektor.

Unit: milidetik
expr mnt lq berarti median uq maks neval
{dt [, `: =` (first_l ,. (First_l_f (w)))]} 93.07916 112.1381 161.9267 149.6863 185.9893 442.5199 100

Akselerasi median adalah 3 kali .

Jalankan ketiga, di mana skema transformasi menjadi matriks diubah.
Unit: milidetik,
expr, min lq, berarti median uq maks neval
{dt [, `: =` (first_l ,. (First_l_f2 (w)))]} 32.60481 34.13679 40.4544 35.57115 42.11975 222.972 100

Akselerasi median adalah 13 kali .

Anda harus bereksperimen dengan masalah ini, semakin banyak - semakin baik.

Contoh lain dengan vektorisasi, di mana ada juga teks, tetapi dekat dengan kondisi nyata: panjang kata berbeda, jumlah kata berbeda. Diperlukan untuk mendapatkan 3 kata pertama. Seperti ini:



Di sini fungsi sebelumnya tidak berfungsi, karena vektor-vektor memiliki panjang yang berbeda, dan kami mengatur ukuran matriks. Ulangi ini dengan mempelajari Internet.

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



Unit: milidetik,
expr, min lq, berarti 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


Script berjalan dengan kecepatan rata-rata 1 detik. Tidak buruk.

Cara lain yang lebih ekonomis ditemukankablag:

Kode
# fifth

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(.))]

microbenchmark({
	
	w_split <- dt[
		, data.table::tstrsplit(w, split = ' ', fixed = T, keep = 1L:3L)
		]
	
	dt[
		, `:=` (
			w_1 = as.character(w_split[[1]])
			, w_2 = as.character(w_split[[2]])
			, w_3 = as.character(w_split[[3]])
		)
		]

})



Median 186, 5 kali lebih murah ...

Terhubung oleh satu rantai ...


Anda dapat bekerja dengan objek DT menggunakan rantai. Sepertinya mengaitkan sintaks kurung ke kanan, pada kenyataannya, gula.

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



Mengalir melalui pipa ...


Operasi yang sama dapat dilakukan melalui perpipaan, terlihat serupa, tetapi secara fungsional lebih kaya, karena Anda dapat menggunakan metode apa pun, bukan hanya DT. Kami memperoleh koefisien regresi logistik untuk data sintetis kami dengan sejumlah filter pada bahan bakar diesel.

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


Statistik, pembelajaran mesin, dll. Di dalam DT


Anda dapat menggunakan fungsi lambda, tetapi kadang-kadang lebih baik untuk membuatnya secara terpisah, mendaftarkan seluruh pipa analisis data, dan kemudian mereka bekerja di dalam DT. Contoh diperkaya dengan semua fitur di atas, ditambah beberapa hal yang berguna dari arsenal DT (seperti mengakses DT itu sendiri di dalam DT dengan referensi, kadang-kadang tidak dimasukkan secara berurutan, tetapi menjadi).

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



Kesimpulan


Saya berharap bahwa saya dapat membuat gambar yang lengkap, tetapi, tentu saja, tidak lengkap, dari objek seperti data. Saya harap ini membantu Anda untuk lebih belajar dan menggunakan perpustakaan ini untuk bekerja dan bersenang - senang .



Terima kasih!

Kode lengkap


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


All Articles