Pilih / tetapkan ke data.table ketika nama variabel disimpan dalam vektor karakter


92

Bagaimana Anda merujuk ke variabel di data.tablejika nama variabel disimpan dalam vektor karakter? Misalnya, ini berfungsi untuk data.frame:

df <- data.frame(col1 = 1:3)
colname <- "col1"
df[colname] <- 4:6
df
#   col1
# 1    4
# 2    5
# 3    6

Bagaimana saya bisa melakukan operasi yang sama untuk data.table, baik dengan atau tanpa :=notasi? Hal yang jelas dt[ , list(colname)]tidak berhasil (saya juga tidak mengharapkannya).

Jawaban:


133

Dua cara untuk memilih variabel secara terprogram :

  1. with = FALSE:

     DT = data.table(col1 = 1:3)
     colname = "col1"
     DT[, colname, with = FALSE] 
     #    col1
     # 1:    1
     # 2:    2
     # 3:    3
    
  2. 'dot dot' ( ..) awalan:

     DT[, ..colname]    
     #    col1
     # 1:    1
     # 2:    2
     # 3:    3
    

Untuk penjelasan lebih lanjut tentang ..notasi 'titik titik' ( ), lihat Fitur Baru di 1.10.2 (saat ini tidak dijelaskan dalam teks bantuan).

Untuk menetapkan ke variabel, bungkus LHS :=di dalam tanda kurung:

DT[, (colname) := 4:6]    
#    col1
# 1:    4
# 2:    5
# 3:    6

Yang terakhir ini dikenal sebagai kolom plonk , karena Anda mengganti vektor seluruh kolom dengan referensi. Jika ada subset, subset iakan dibuat berdasarkan referensi. Parens sekitar (colname)adalah singkatan yang diperkenalkan dalam versi v1.9.4 pada CRAN Oktober 2014. Berikut adalah item beritanya :

Penggunaan with = FALSEwith :=sekarang sudah tidak digunakan lagi dalam semua kasus, mengingat bahwa membungkus kiri :=dengan tanda kurung lebih disukai untuk beberapa waktu.

colVar = "col1"
DT[, (colVar) := 1]                             # please change to this
DT[, c("col1", "col2") := 1]                    # no change
DT[, 2:4 := 1]                                  # no change
DT[, c("col1","col2") := list(sum(a), mean(b))]  # no change
DT[, `:=`(...), by = ...]                       # no change

Lihat juga bagian Detail di ?`:=`:

DT[i, (colnamevector) := value]
# [...] The parens are enough to stop the LHS being a symbol

Dan untuk menjawab pertanyaan selanjutnya dalam komentar, berikut salah satu caranya (seperti biasa ada banyak cara):

DT[, colname := cumsum(get(colname)), with = FALSE]
#    col1
# 1:    4
# 2:    9
# 3:   15 

atau, Anda mungkin menemukan lebih mudah untuk membaca, menulis dan debug hanya untuk evalsebuah paste, mirip dengan membangun sebuah pernyataan SQL dinamis untuk mengirim ke server:

expr = paste0("DT[,",colname,":=cumsum(",colname,")]")
expr
# [1] "DT[,col1:=cumsum(col1)]"

eval(parse(text=expr))
#    col1
# 1:    4
# 2:   13
# 3:   28

Jika Anda sering melakukannya, Anda dapat menentukan fungsi helper EVAL:

EVAL = function(...)eval(parse(text=paste0(...)),envir=parent.frame(2))

EVAL("DT[,",colname,":=cumsum(",colname,")]")
#    col1
# 1:    4
# 2:   17
# 3:   45

Sekarang data.table1.8.2 secara otomatis mengoptimalkan jefisiensi, mungkin lebih baik untuk menggunakan evalmetode ini. The get()dalam jmencegah beberapa optimasi, misalnya.

Atau, ada set(). Overhead rendah, bentuk fungsional :=, yang akan baik-baik saja di sini. Lihat ?set.

set(DT, j = colname, value = cumsum(DT[[colname]]))
DT
#    col1
# 1:    4
# 2:   21
# 3:   66

1
Terima kasih atas jawabannya, Matthew. With = FALSE pasti menyelesaikan sebagian dari masalah saya. Pada kenyataannya, saya ingin mengganti kolom tersebut dengan kolom cumsum. Dapatkah saya mereferensikan nama kolom dengan variabel di sisi kanan tugas?
frankc

Sebenarnya, saya baru saja menyimpan cumsum secara eksternal dengan nama berbeda yang tidak ada di dalam dt dan itu berfungsi dengan baik.
frankc

1
Tapi itu akan menjadi baris ekstra! Tidak terlalu elegan :) Tapi ok terkadang berguna. Dalam kasus tersebut paling baik untuk memulai nama variabel dengan ., atau ..untuk menghindari potensi masking jika DTpernah mengandung simbol itu sebagai nama kolom di masa depan (dan tetap berpegang pada konvensi yang tidak dimulai dengan nama kolom .). Ada beberapa permintaan fitur untuk membuatnya lebih kuat dalam mengatasi masalah seperti itu, seperti menambahkan .()dan ..().
Matt Dowle

Saya menjawab sebelum saya melihat Anda mengedit jawaban Anda. Pikiran pertama saya adalah eval (parse ()) tetapi untuk beberapa alasan saya mengalami kesulitan membuatnya berfungsi, ketika saya sadar untuk melakukannya secara eksternal. Ini adalah jawaban yang bagus dengan banyak hal yang tidak saya pikirkan. Terima kasih untuk data.table secara umum, ini adalah paket yang bagus.
frankc

2
Perhatikan bahwa Anda bisa menggunakan quasi-perl tipe string interpolasi fn$dari paket gsubfn untuk meningkatkan pembacaan solusi Tarahan: library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" ).
G. Grothendieck

8

* Ini sebenarnya bukan jawaban, tapi saya tidak punya cukup kredibilitas untuk mengirim komentar: /

Bagaimanapun, bagi siapa saja yang mungkin ingin benar-benar membuat kolom baru di tabel data dengan nama yang disimpan dalam variabel, saya punya yang berikut ini untuk bekerja. Saya tidak tahu apa-apa tentang performanya. Ada saran untuk perbaikan? Apakah aman untuk mengasumsikan kolom baru tanpa nama akan selalu diberi nama V1?

colname <- as.name("users")
# Google Analytics query is run with chosen metric and resulting data is assigned to DT
DT2 <- DT[, sum(eval(colname, .SD)), by = country]
setnames(DT2, "V1", as.character(colname))

Perhatikan bahwa saya dapat mereferensikannya dengan baik di sum () tetapi sepertinya tidak dapat membuatnya untuk ditetapkan pada langkah yang sama. BTW, alasan saya perlu melakukan ini adalah nama kolom akan didasarkan pada input pengguna di aplikasi Shiny.


+1 untuk sekadar bekerja: Saya setuju bahwa ini bukanlah "cara" untuk melakukan ini, tetapi baru saja menghabiskan waktu 45 menit untuk menuangkan setiap pos SO tentang subjek ini, ini adalah satu-satunya solusi yang sebenarnya dapat saya capai bekerja - terima kasih telah meluangkan waktu untuk menunjukkannya!
neuropsych

Senang aku dapat membantu! Sayangnya, saya tidak pernah menemukan solusi yang lebih elegan secara langsung menggunakan data.tables, meskipun 3 liner ini tidak buruk. Dalam skenario saya, saya menyadari bahwa alternatif yang lebih sederhana adalah menggunakan tidyr untuk hanya membuat data saya "panjang" daripada "lebar", karena berdasarkan masukan pengguna, saya selalu dapat memfilter pada satu kolom daripada memilih dari satu set kolom.
efh0888

2
Tidaklah aman untuk menganggap V1sebagai nama baru. Misalnya, jika Anda membaca csv dengan freaddan ada kolom tanpa nama, itu akan memiliki V1nama (dan read.csvakan memberi X). Jadi mungkin saja meja Anda sudah memiliki file V1. Mungkin hanya mendapatkan nama dengannames(DT)[length(names(DT))]
dracodoc

2

Untuk beberapa kolom dan fungsi yang diterapkan pada nilai kolom.

Saat memperbarui nilai dari suatu fungsi, RHS harus berupa objek list, jadi menggunakan loop pada .SDwith lapplyakan melakukan triknya.

Contoh di bawah ini mengonversi kolom integer menjadi kolom numerik

a1 <- data.table(a=1:5, b=6:10, c1=letters[1:5])
sapply(a1, class)  # show classes of columns
#         a           b          c1 
# "integer"   "integer" "character" 

# column name character vector
nm <- c("a", "b")

# Convert columns a and b to numeric type
a1[, j = (nm) := lapply(.SD, as.numeric ), .SDcols = nm ]

sapply(a1, class)
#         a           b          c1 
# "numeric"   "numeric" "character" 

2

Ambil beberapa kolom dari data.table melalui variabel atau fungsi:

library(data.table)

x <- data.table(this=1:2,that=1:2,whatever=1:2)

# === explicit call
x[, .(that, whatever)]
x[, c('that', 'whatever')]

# === indirect via  variable
# ... direct assignment
mycols <- c('that','whatever')
# ... same as result of a function call
mycols <- grep('a', colnames(x), value=TRUE)

x[, ..mycols]
x[, .SD, .SDcols=mycols]

# === direct 1-liner usage
x[, .SD, .SDcols=c('that','whatever')]
x[, .SD, .SDcols=grep('a', colnames(x), value=TRUE)]

yang semuanya menghasilkan

   that whatever
1:    1        1
2:    2        2

Saya menemukan .SDcolscara yang paling elegan.


1

Anda bisa mencoba ini

nama kolom <- as.name ("COL_NAME")

DT2 <- DT [, daftar (COL_SUM = jumlah (eval (nama kolom, .SD))), oleh = c (grup)]


1
Selalu disarankan untuk menambahkan penjelasan dengan kode Anda, bukan hanya kode postingan.
MBorg
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.