Призначте кілька нових змінних на LHS в один рядок


89

Я хочу призначити кілька змінних в одному рядку в R. Чи можна зробити щось подібне?

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'

Зазвичай я хочу призначити близько 5-6 змінних в одному рядку, замість того, щоб мати кілька рядків. Чи є альтернатива?


ви маєте на увазі щось на зразок PHP list($a, $b) = array(1, 2)? Це було б чудово! +1.
TMS

@Tomas T - Думаю, моя vassignпропозиція нижче наближається ... :)
Томмі

Примітка: крапка з комою не потрібна для цього біта R.
Ітератор

1
Якби ви спробували це у відповідному середовищі, це було б так само просто X <- list();X[c('a','b')] <- values[c(2,4)]. Добре, ви не призначаєте їх у робочій області, але добре тримайте їх у списку. Я волів би зробити це так.
Joris Meys

7
мені подобається python, просто a, b = 1,2. всі відповіді нижче є в 100
разів

Відповіді:


39

Існує чудова відповідь у блозі "Протиборство через проблеми"

Це взято звідти, з дуже незначними змінами.

ВИКОРИСТАННЯ НАСТУПНИХ ТРИ ФУНКЦІЙ (Плюс одна для включення списків різних розмірів)

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


Потім виконати:

Групуйте ліву частину, використовуючи нову функцію g() . Права сторона повинна бути вектором або списком. Використовуйте нещодавно створений двійковий оператор%=%

# 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


Приклад використання списків різних розмірів:

Довший лівий бік

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"

Довший правий бік

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

Я зібрав фанатик пакету R, щоб вирішити цю саму проблему. zeallot включає оператор ( %<-%) для розпакування, множинного призначення та деструктуризації. LHS виразу присвоєння будується за допомогою викликів до c(). RHS виразу присвоєння може бути будь-яким виразом, який повертає або є вектором, списком, вкладеним списком, фреймом даних, рядком символів, об'єктом дати або спеціальними об'єктами (за умови, що існуєdestructure реалізація).

Ось первинне запитання, перероблене за допомогою Zeallot (остання версія, 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

Для отримання додаткових прикладів та інформації можна ознайомитися з віньєткою пакету .


це саме те, що я сподівався знайти, щось таке, що уможливлює
пітоноподібний

1
А як щодо присвоєння матриці кожному імені змінної?
StatsSorceress

33

Подумайте про використання функціональних можливостей, що входять до бази R.

Наприклад, створіть 1 рядок даних (скажімо V) та ініціалізуйте в ньому свої змінні. Тепер ви можете призначати декілька змінних одночасно V[,c("a", "b")] <- values[c(2, 4)], називати кожну по імені ( V$a) або використовувати багато з них одночасно ( values[c(5, 6)] <- V[,c("a", "b")]).

Якщо ви полінуєтесь і не хочете ходити по виклику змінних з фрейму даних, ви могли б attach(V)(хоча я особисто ніколи цього не роблю).

# 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

4
+10 якби міг. Цікаво, чому люди відмовляються використовувати списки в таких очевидних випадках, а навпаки, засмічують робочу область тоннами безглуздих змінних. (ви використовуєте списки, оскільки data.frame - це особливий вид списку. Я б просто використав більш загальний.)
Джоріс Мейс,

але ви не можете мати різні типи елементів в одному стовпці, а також не можете зберігати фрейми даних або списки всередині вашого фрейму даних
skan

1
Насправді ви можете зберігати списки у фреймі даних - google "стовпець списку".

Це не поганий підхід, він має деякі зручності, але також не так вже й важко уявити, чому багато користувачів не хотіли б мати справу з синтаксисом data.frame щоразу, коли вони намагалися використовувати або отримати доступ до змінних, призначених таким чином.
Брендон

13

ось моя ідея. Можливо, синтаксис досить простий:

`%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)

дає так:

> 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

проте це не дуже добре перевірено.


2
Кошке, мені це виглядає дуже приємно :-) Але мене трохи турбує пріоритет оператора: оператори% something% досить високі, тому поведінка eg c(c, d) %tin% c(1, 2) + 3(=> c = 1, d = 1, повертає числовий ( 0)) можна вважати дивним.
кбелеїти незадоволені SX

10

Потенційно небезпечним (настільки, наскільки використання assignє ризикованим) буде 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

Або я припускаю, що ви можете векторизувати його самостійно вручну за допомогою власної функції, використовуючи mapply, можливо, використовуючи розумний типовий envirаргумент. Наприклад, Vectorizeповерне функцію з тими ж властивостями середовища assign, які в цьому випадку є namespace:base, або ви можете просто встановити envir = parent.env(environment(assignVec)).


8

Як пояснили інші, здається, нічого не вбудовано ... але ви можете спроектувати vassignфункцію наступним чином:

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

Однак слід врахувати одне, як вирішити випадки, коли, наприклад, ви вказуєте 3 змінні та 5 значень, або навпаки. Тут я просто повторюю (або скорочую) значення, що мають однакову довжину зі змінними. Можливо, попередження буде розумним. Але це дозволяє наступне:

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

Мені це подобається, але я б переживав, що це може зламатися в деяких випадках, коли його викликали зсередини функції (хоча простий тест цього спрацював, на мій легкий подив). Можете пояснити ...(), що мені здається чорною магією ...?
Ben Bolker

1
@ Бен Болкер - Так, ...()це надзвичайна чорна магія ;-). Так трапляється, що коли "функціональний виклик" ...()замінюється, він стає парним списком, який можна передати as.characterта voila, ви отримали аргументи як рядки ...
Tommy

1
@Ben Bolker - І він повинен працювати коректно, навіть коли викликається зсередини функції, оскільки вона використовує envir=parent.frame()- І ви можете вказати, наприклад, envir=globalenv()якщо хочете.
Tommy

Навіть кулер мав би використовувати цю функцію як заміну: `vassign<-` <- function (..., envir = parent.frame (), value)тощо. Однак, здається, перший призначений об'єкт повинен уже існувати. Будь-які ідеї?
кбелеїти незадоволені SX

@cbeleites - Так, це було б крутіше, але я не думаю, що ти можеш обійти обмеження, яке має існувати перший аргумент - ось чому це називається функцією заміни :) ... але дай мені знати, якщо дізнаєшся інакше !
Томмі,

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

Служив моїй меті, тобто присвоїв п’ять 2-х першим п’яти літерам.



4

Нещодавно мала подібну проблему, і ось моя спроба використовувати purrr::walk2

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

3

Якщо ваша єдина вимога - мати один рядок коду, то як щодо:

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

2
шукав стислий вислів, але, мабуть, його немає
user236215

Я на тому самому човні, що і @ user236215. коли права сторона є складним виразом, що повертає вектор, повторюваний код здається дуже неправильним ...
авіаудар

1

Я боюся, що елементарного рішення, яке ви шукаєте (подобається c(a, b) = c(2, 4)), на жаль, не існує. Але не здавайтесь, я не впевнений! Найближче рішення, яке я можу придумати, це таке:

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

або якщо вас турбують попередження, вимкніть їх:

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

Але я припускаю, що ви не задоволені цим рішенням, я б теж не був ...



0

Інша версія з рекурсією:

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

приклад:

> 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

Моя версія:

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

приклад:

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

0

Поєднуючи деякі з наведених тут відповідей + трохи солі, як щодо цього рішення:

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

Я використав це, щоб додати тут розділ R: http://rosettacode.org/wiki/Sort_three_variables#R

Увага: він працює лише для призначення глобальних змінних (наприклад <<-). Якщо є краще, більш загальне рішення, будь ласка. скажіть мені в коментарях.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.