Назначьте несколько новых переменных на 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 переменных в одной строке вместо нескольких строк. Есть ли альтернатива?

user236215
источник
вы имеете в виду что-то вроде PHP list($a, $b) = array(1, 2)? Это было бы чудесно! +1.
TMS
@Tomas T - Я думаю, что мое vassignпредложение ниже подходит ... :)
Tommy
Примечание: точка с запятой не нужна для этого фрагмента R.
Iterator
1
Если бы вы попробовали это в подходящей среде, это было бы так же просто X <- list();X[c('a','b')] <- values[c(2,4)]. Хорошо, вы не назначаете их в рабочей области, а храните их вместе в списке. Я бы предпочел сделать это так.
Джорис Мейс
7
мне нравится питон, просто a, b = 1,2. все ответы ниже в 100 раз сложнее
appleLover

Ответы:

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

Я собрал zeallot пакета R для решения этой самой проблемы. zeallot включает оператор ( %<-%) для распаковки, множественного и деструктурирования присваивания. LHS выражения присваивания строится с использованием вызовов c(). Правая часть выражения присваивания может быть любым выражением, которое возвращает или является вектором, списком, вложенным списком, фреймом данных, символьной строкой, объектом даты или настраиваемыми объектами (при условии, что существует 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

Дополнительные примеры и информацию можно найти на виньетке на упаковке .

nteetor
источник
это именно то, что я надеялся найти, что-то, что позволяет использовать синтаксис типа Python, о котором просил OP, реализованный в пакете R
jafelds
1
А как насчет присвоения матрицы каждому имени переменной?
StatsSorceress
33

Рассмотрите возможность использования функций, включенных в базовый R.

Например, создайте фрейм данных с одной строкой (скажем 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 - это особый вид списка. Я бы просто использовал более общий.)
Джорис Мейс
но у вас не может быть разных типов элементов в одном столбце, а также вы не можете хранить фреймы данных или списки внутри своего
фрейма данных
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% довольно высоки, поэтому поведение, например, c(c, d) %tin% c(1, 2) + 3(=> c = 1, d = 1, возвращает числовое ( 0)) можно считать удивительным.
cbeleites недовольны 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)).

Joran
источник
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
Томми
источник
Мне это нравится, но я бы побеспокоился, что он может сломаться в некоторых случаях, когда он был вызван из функции (хотя простой тест этого сработал, к моему легкому удивлению). Вы можете объяснить ...(), что мне кажется черной магией ...?
Бен Болкер
1
@ Бен Болкер - Да, ...()это экстремальная черная магия ;-). Бывает так, что когда "вызов функции" ...()заменяется, он становится паирлистом, который можно передать, as.characterи вуаля, вы получили аргументы в виде строк ...
Томми
1
@Ben Bolker - И он должен работать правильно даже при вызове из функции, поскольку он использует envir=parent.frame()- И вы можете указать, например, envir=globalenv()если хотите.
Tommy
Еще круче было бы иметь это в качестве функции замены: `vassign<-` <- function (..., envir = parent.frame (), value)и так далее. Однако кажется, что первый назначаемый объект должен уже существовать. Любые идеи?
cbeleites недовольны SX
@cbeleites - Да, это было бы круче, но я не думаю, что вы можете обойти ограничение, что должен существовать первый аргумент - поэтому он называется функцией замены :) ... но дайте мне знать, если вы узнаете иначе !
Tommy
6
list2env(setNames(as.list(rep(2,5)), letters[1:5]), .GlobalEnv)

Это послужило моей цели, т. Е. Присвоение пяти двоек первым пяти буквам.

Рафикул Хайдер
источник
4

Недавно была аналогичная проблема, и вот я попытался использовать purrr::walk2

purrr::walk2(letters,1:26,assign,envir =parent.frame()) 
Леронг
источник
3

Если ваше единственное требование - иметь одну строку кода, как насчет:

> a<-values[2]; b<-values[4]
NPE
источник
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)

Но я полагаю, вас не устраивает это решение, я бы тоже не стал ...

ТМС
источник
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
ничто101
источник
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

Предостережение: он работает только для назначения глобальных переменных (например, <<-). Если есть лучшее, более общее решение, пожалуйста. Ответь мне в комментариях.

vonjd
источник