Tetapkan beberapa variabel baru di LHS dalam satu baris


90

Saya ingin menetapkan beberapa variabel dalam satu baris di R. Apakah mungkin untuk melakukan sesuatu seperti ini?

values # initialize some vector of values
(a, b) = values[c(2,4)] # assign a and b to values at 2 and 4 indices of 'values'

Biasanya saya ingin menetapkan sekitar 5-6 variabel dalam satu baris, daripada memiliki banyak baris. Apakah ada alternatif lain?


maksud Anda sesuatu seperti di PHP list($a, $b) = array(1, 2)? Itu akan menyenangkan! +1.
TMS

@Tomas T - Saya pikir vassignsaran saya di bawah hampir mendekati ... :)
Tommy

Catatan: Titik koma tidak diperlukan untuk R.
Iterator

1
Jika Anda mencoba ini dalam lingkungan yang sesuai, itu akan semudah X <- list();X[c('a','b')] <- values[c(2,4)]. Oke, Anda tidak menetapkannya di ruang kerja, tetapi simpan dengan baik di dalam daftar. Saya lebih suka melakukannya dengan cara itu.
Joris Meys

7
saya suka python, hanya a, b = 1,2. semua jawaban di bawah ini 100x lebih sulit
appleLover

Jawaban:


40

Ada jawaban bagus di Blog Berjuang Melalui Masalah

Ini diambil dari sana, dengan sedikit modifikasi.

MENGGUNAKAN TIGA FUNGSI BERIKUT (Ditambah satu untuk memungkinkan daftar ukuran yang berbeda)

# Generic form
'%=%' = function(l, r, ...) UseMethod('%=%')

# Binary Operator
'%=%.lbunch' = function(l, r, ...) {
  Envir = as.environment(-1)

  if (length(r) > length(l))
    warning("RHS has more args than LHS. Only first", length(l), "used.")

  if (length(l) > length(r))  {
    warning("LHS has more args than RHS. RHS will be repeated.")
    r <- extendToMatch(r, l)
  }

  for (II in 1:length(l)) {
    do.call('<-', list(l[[II]], r[[II]]), envir=Envir)
  }
}

# Used if LHS is larger than RHS
extendToMatch <- function(source, destin) {
  s <- length(source)
  d <- length(destin)

  # Assume that destin is a length when it is a single number and source is not
  if(d==1 && s>1 && !is.null(as.numeric(destin)))
    d <- destin

  dif <- d - s
  if (dif > 0) {
    source <- rep(source, ceiling(d/s))[1:d]
  }
  return (source)
}

# Grouping the left hand side
g = function(...) {
  List = as.list(substitute(list(...)))[-1L]
  class(List) = 'lbunch'
  return(List)
}


Kemudian untuk mengeksekusi:

Kelompokkan sisi kiri menggunakan fungsi baru g() Sisi kanan harus berupa vektor atau daftar Gunakan operator biner yang baru dibuat%=%

# Example Call;  Note the use of g()  AND  `%=%`
#     Right-hand side can be a list or vector
g(a, b, c)  %=%  list("hello", 123, list("apples, oranges"))

g(d, e, f) %=%  101:103

# Results: 
> a
[1] "hello"
> b
[1] 123
> c
[[1]]
[1] "apples, oranges"

> d
[1] 101
> e
[1] 102
> f
[1] 103


Contoh menggunakan daftar dengan ukuran berbeda:

Sisi Kiri Lebih Panjang

g(x, y, z) %=% list("first", "second")
#   Warning message:
#   In `%=%.lbunch`(g(x, y, z), list("first", "second")) :
#     LHS has more args than RHS. RHS will be repeated.
> x
[1] "first"
> y
[1] "second"
> z
[1] "first"

Sisi Kanan Lebih Panjang

g(j, k) %=% list("first", "second", "third")
#   Warning message:
#   In `%=%.lbunch`(g(j, k), list("first", "second", "third")) :
#     RHS has more args than LHS. Only first2used.
> j
[1] "first"
> k
[1] "second"

34

Pertimbangkan untuk menggunakan fungsionalitas yang termasuk dalam basis R.

Misalnya, buat 1 baris dataframe (katakanlah V) dan inisialisasi variabel Anda di dalamnya. Sekarang Anda dapat menetapkan ke beberapa variabel sekaligus V[,c("a", "b")] <- values[c(2, 4)], memanggil masing-masing dengan name ( V$a), atau menggunakan banyak di antaranya pada saat yang sama ( values[c(5, 6)] <- V[,c("a", "b")]).

Jika Anda malas dan tidak ingin berkeliling memanggil variabel dari dataframe, Anda bisa attach(V)(meskipun saya pribadi tidak pernah melakukannya).

# Initialize values
values <- 1:100

# V for variables
V <- data.frame(a=NA, b=NA, c=NA, d=NA, e=NA)

# Assign elements from a vector
V[, c("a", "b", "e")] = values[c(2,4, 8)]

# Also other class
V[, "d"] <- "R"

# Use your variables
V$a
V$b
V$c  # OOps, NA
V$d
V$e

5
+10 jika saya bisa. Saya bertanya-tanya mengapa orang menolak menggunakan daftar dalam kasus yang jelas seperti itu, melainkan mengotori ruang kerja dengan banyak variabel yang tidak berarti. (Anda memang menggunakan daftar, karena data.frame adalah jenis daftar khusus. Saya hanya akan menggunakan daftar yang lebih umum.)
Joris Meys

tetapi Anda tidak dapat memiliki jenis elemen yang berbeda dalam kolom yang sama, Anda juga tidak dapat menyimpan kerangka data atau daftar di dalam kerangka data Anda
skan

1
Sebenarnya, Anda dapat menyimpan daftar dalam bingkai data - "kolom daftar" google.

Ini bukan pendekatan yang buruk, ini memiliki beberapa kemudahan, tetapi juga tidak sulit untuk membayangkan mengapa banyak pengguna tidak ingin berurusan dengan sintaks data.frame setiap kali mereka mencoba menggunakan atau mengakses variabel yang ditetapkan dengan cara ini.
Brandon

34

Saya mengumpulkan zeallot paket R untuk mengatasi masalah ini. zeallot menyertakan operator ( %<-%) untuk tugas membongkar, menggandakan , dan merusak. LHS ekspresi penetapan dibuat menggunakan panggilan ke c(). RHS ekspresi tugas dapat berupa ekspresi apa pun yang mengembalikan atau merupakan vektor, daftar, daftar bersarang, bingkai data, string karakter, objek tanggal, atau objek khusus (dengan asumsi ada destructureimplementasi).

Berikut adalah pertanyaan awal yang dikerjakan ulang menggunakan zeallot (versi terbaru, 0.0.5).

library(zeallot)

values <- c(1, 2, 3, 4)     # initialize a vector of values
c(a, b) %<-% values[c(2, 4)]  # assign `a` and `b`
a
#[1] 2
b
#[1] 4

Untuk contoh dan informasi lebih lanjut, seseorang dapat melihat sketsa paket .


ini persis seperti yang saya harapkan, sesuatu yang memungkinkan sintaks mirip python yang diminta OP, diimplementasikan dalam paket R
jafelds

1
Bagaimana dengan menetapkan matriks ke setiap nama variabel?
StatsSorceress

14

ini ide saya. Mungkin sintaksnya cukup sederhana:

`%tin%` <- function(x, y) {
    mapply(assign, as.character(substitute(x)[-1]), y,
      MoreArgs = list(envir = parent.frame()))
    invisible()
}

c(a, b) %tin% c(1, 2)

memberi seperti ini:

> a
Error: object 'a' not found
> b
Error: object 'b' not found
> c(a, b) %tin% c(1, 2)
> a
[1] 1
> b
[1] 2

ini tidak diuji dengan baik.


3
Koshke, terlihat sangat baik untuk saya :-) Tapi saya agak khawatir tentang prioritas operator: operator% sesuatu% cukup tinggi, jadi perilaku eg c(c, d) %tin% c(1, 2) + 3(=> c = 1, d = 1, mengembalikan numerik ( 0)) mungkin dianggap mengejutkan.
cbeleites tidak senang dengan SX

10

assignOpsi yang berpotensi berbahaya (sebanyak penggunaan itu berisiko) adalah Vectorize assign:

assignVec <- Vectorize("assign",c("x","value"))
#.GlobalEnv is probably not what one wants in general; see below.
assignVec(c('a','b'),c(0,4),envir = .GlobalEnv)
a b 
0 4 
> b
[1] 4
> a
[1] 0

Atau saya kira Anda bisa memvektorisasi sendiri secara manual dengan fungsi Anda sendiri menggunakan mapplyyang mungkin menggunakan default yang masuk akal untuk envirargumen. Misalnya, Vectorizeakan mengembalikan fungsi dengan properti lingkungan yang sama assign, yang dalam hal ini adalah namespace:base, atau Anda bisa menyetelnya envir = parent.env(environment(assignVec)).


9

Seperti yang dijelaskan orang lain, sepertinya tidak ada bawaan apa pun. ... tetapi Anda dapat mendesain vassignfungsi sebagai berikut:

vassign <- function(..., values, envir=parent.frame()) {
  vars <- as.character(substitute(...()))
  values <- rep(values, length.out=length(vars))
  for(i in seq_along(vars)) {
    assign(vars[[i]], values[[i]], envir)
  }
}

# Then test it
vals <- 11:14
vassign(aa,bb,cc,dd, values=vals)
cc # 13

Satu hal yang perlu dipertimbangkan adalah bagaimana menangani kasus di mana Anda misalnya menentukan 3 variabel dan 5 nilai atau sebaliknya. Di sini saya hanya mengulangi (atau memotong) nilai-nilai menjadi sama panjangnya dengan variabel. Mungkin peringatan akan lebih bijaksana. Tapi itu memungkinkan yang berikut:

vassign(aa,bb,cc,dd, values=0)
cc # 0

Saya suka ini, tapi saya khawatir itu mungkin rusak dalam beberapa kasus di mana itu dipanggil dari dalam suatu fungsi (meskipun tes sederhana ini berhasil, saya sedikit terkejut). Bisakah Anda menjelaskan ...(), mana yang tampak seperti ilmu hitam bagi saya ...?
Ben Bolker

1
@ Ben Bolker - Ya, ...()itu sihir hitam yang ekstrim ;-). Kebetulan ketika "panggilan fungsi" ...()diganti, itu menjadi daftar pasangan yang dapat diteruskan ke as.characterdan voila, Anda mendapat argumen sebagai string ...
Tommy

1
@ Ben Bolker - Dan itu harus bekerja dengan benar bahkan ketika dipanggil dari dalam suatu fungsi sejak digunakan envir=parent.frame()- Dan Anda dapat menentukan misalnya envir=globalenv()jika Anda mau.
Tommy

Yang lebih keren lagi adalah memiliki ini sebagai fungsi pengganti: `vassign<-` <- function (..., envir = parent.frame (), value)dan seterusnya. Namun, tampaknya objek pertama yang ditugaskan harus sudah ada. Ada ide?
Cbeleites tidak senang dengan SX

@cbeleites - Ya, itu akan lebih keren tetapi saya rasa Anda tidak dapat mengatasi batasan bahwa argumen pertama harus ada - itulah mengapa ini disebut fungsi pengganti :) ... tetapi beri tahu saya jika Anda mengetahui sebaliknya !
Tommy

6
list2env(setNames(as.list(rep(2,5)), letters[1:5]), .GlobalEnv)

Melayani tujuan saya, yaitu, menugaskan lima 2s menjadi lima huruf pertama.



5

Punya masalah serupa baru-baru ini dan inilah percobaan saya menggunakan purrr::walk2

purrr::walk2(letters,1:26,assign,envir =parent.frame()) 

3

Jika satu-satunya persyaratan Anda adalah memiliki satu baris kode, lalu bagaimana dengan:

> a<-values[2]; b<-values[4]

2
sedang mencari pernyataan singkat tapi saya kira tidak ada
user236215

Saya berada di perahu yang sama dengan @ user236215. ketika sisi kanan adalah ekspresi rumit yang mengembalikan vektor, pengulangan kode tampaknya sangat salah ...
serangan udara

1

Saya khawatir solusi elegan yang Anda cari (seperti c(a, b) = c(2, 4)) sayangnya tidak ada. Tapi jangan menyerah, saya tidak yakin! Solusi terdekat yang dapat saya pikirkan adalah yang ini:

attach(data.frame(a = 2, b = 4))

atau jika Anda terganggu dengan peringatan, matikan:

attach(data.frame(a = 2, b = 4), warn = F)

Tetapi saya kira Anda tidak puas dengan solusi ini, saya juga tidak akan ...


1
R> values = c(1,2,3,4)
R> a <- values[2]; b <- values[3]; c <- values[4]
R> a
[1] 2
R> b
[1] 3
R> c
[1] 4

0

Versi lain dengan rekursi:

let <- function(..., env = parent.frame()) {
    f <- function(x, ..., i = 1) {
        if(is.null(substitute(...))){
            if(length(x) == 1)
                x <- rep(x, i - 1);
            stopifnot(length(x) == i - 1)
            return(x);
        }
        val <- f(..., i = i + 1);
        assign(deparse(substitute(x)), val[[i]], env = env);
        return(val)
    }
    f(...)
}

contoh:

> let(a, b, 4:10)
[1]  4  5  6  7  8  9 10
> a
[1] 4
> b
[1] 5
> let(c, d, e, f, c(4, 3, 2, 1))
[1] 4 3 2 1
> c
[1] 4
> f
[1] 1

Versi saya:

let <- function(x, value) {
    mapply(
        assign,
        as.character(substitute(x)[-1]),
        value,
        MoreArgs = list(envir = parent.frame()))
    invisible()
}

contoh:

> let(c(x, y), 1:2 + 3)
> x
[1] 4
> y
[1] 

0

Menggabungkan beberapa jawaban yang diberikan di sini + sedikit garam, bagaimana dengan solusi ini:

assignVec <- Vectorize("assign", c("x", "value"))
`%<<-%` <- function(x, value) invisible(assignVec(x, value, envir = .GlobalEnv))

c("a", "b") %<<-% c(2, 4)
a
## [1] 2
b
## [1] 4

Saya menggunakan ini untuk menambahkan bagian R di sini: http://rosettacode.org/wiki/Sort_three_variables#R

Peringatan: Ini hanya berfungsi untuk menetapkan variabel global (seperti <<-). Jika ada solusi yang lebih baik dan lebih umum, mohon. beri tahu saya di komentar.

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.