web-dev-qa-db-fra.com

Attribuer plusieurs nouvelles variables sur LHS sur une seule ligne

Je veux affecter plusieurs variables sur une seule ligne dans R. Est-il possible de faire quelque chose comme ça?

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'

En général, je veux affecter environ 5-6 variables sur une seule ligne, au lieu d'avoir plusieurs lignes. Y a-t-il une alternative?

77
user236215

Il y a une excellente réponse sur le Blog de lutte contre les problèmes

Ceci est pris à partir de là, avec des modifications très mineures.

UTILISATION DES TROIS FONCTIONS SUIVANTES (plus une pour permettre des listes de tailles différentes)

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


Ensuite pour exécuter:

Regroupez le côté gauche à l'aide de la nouvelle fonction g() Le côté droit doit être un vecteur ou une liste Utilisez l'opérateur binaire nouvellement créé %=%

# 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

Exemple utilisant des listes de différentes tailles:

Côté gauche plus long

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"

Côté droit plus long

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"
30
Ricardo Saporta

Pensez à utiliser les fonctionnalités incluses dans la base R.

Par exemple, créez un cadre de données à 1 ligne (disons V) et initialisez vos variables dedans. Vous pouvez maintenant affecter plusieurs variables à la fois V[,c("a", "b")] <- values[c(2, 4)], appeler chacune par son nom (V$a), Ou utiliser plusieurs d'entre elles en même temps (values[c(5, 6)] <- V[,c("a", "b")]).

Si vous devenez paresseux et que vous ne voulez pas appeler des variables à partir de la trame de données, vous pouvez attach(V) (bien que personnellement je ne le fasse jamais).

# 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
28
Oscar de León

J'ai mis en place un package R zeallot pour résoudre ce problème même. zeallot inclut un opérateur (%<-%) pour le déballage, l'affectation multiple et la déstructuration. Le LHS de l'expression d'affectation est construit à l'aide d'appels à c(). Le RHS de l'expression d'affectation peut être toute expression qui renvoie ou est un vecteur, une liste, une liste imbriquée, un bloc de données, une chaîne de caractères, un objet de date ou des objets personnalisés (en supposant qu'il existe un destructure implémentation).

Voici la question initiale retravaillée avec zeallot (dernière version, 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

Pour plus d'exemples et d'informations, on peut consulter le package vignette .

24
nteetor

voici mon idée. La syntaxe est probablement assez simple:

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

donne comme ça:

> 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

ce n'est pas bien testé cependant.

13
kohske

Une option potentiellement dangereuse (dans la mesure où l'utilisation de assign est risquée) serait de Vectorizeassign:

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

Ou je suppose que vous pouvez le vectoriser vous-même manuellement avec votre propre fonction en utilisant mapply qui utilise peut-être une valeur par défaut raisonnable pour l'argument envir. Par exemple, Vectorize renverra une fonction avec les mêmes propriétés d'environnement que assign, qui dans ce cas est namespace:base, Ou vous pouvez simplement définir envir = parent.env(environment(assignVec)) .

10
joran

Comme d'autres l'ont expliqué, il ne semble rien y avoir de construit. ... mais vous pouvez concevoir une fonction vassign comme suit:

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

Une chose à considérer cependant est de savoir comment gérer les cas où vous par exemple spécifiez 3 variables et 5 valeurs ou l'inverse. Ici, je répète (ou tronque) simplement les valeurs de la même longueur que les variables. Peut-être qu'un avertissement serait prudent. Mais cela permet ce qui suit:

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

https://stat.ethz.ch/R-manual/R-devel/library/base/html/list2env.html :

list2env(
        list(
            a=1,
            b=2:4,
            c=rpois(10,10),
            d=gl(3,4,LETTERS[9:11])
            ),
        envir=.GlobalEnv
        )
4
isomorphismes
list2env(setNames(as.list(rep(2,5)), letters[1:5]), .GlobalEnv)

A servi mon but, c'est-à-dire assigner cinq 2 aux cinq premières lettres.

4
Rafiqul Haider

J'ai eu un problème similaire récemment et voici mon essai en utilisant purrr::walk2

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

Si votre seule exigence est d'avoir une seule ligne de code, que diriez-vous:

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

J'ai bien peur que la solution élégante que vous recherchez (comme c(a, b) = c(2, 4)) n'existe malheureusement pas. Mais n'abandonnez pas, je ne suis pas sûr! La solution la plus proche à laquelle je peux penser est celle-ci:

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

ou si vous êtes gêné par des avertissements, désactivez-les:

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

Mais je suppose que vous n'êtes pas satisfait de cette solution, je ne le serais pas non plus ...

1
TMS
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
1
naught101

Une autre version avec récursivité:

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

exemple:

> 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

Ma version:

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

exemple:

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

En combinant certaines des réponses données ici + un peu de sel, que diriez-vous de cette solution:

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

J'ai utilisé cela pour ajouter la section R ici: http://rosettacode.org/wiki/Sort_three_variables#R

Avertissement: cela ne fonctionne que pour l'attribution de variables globales (comme <<-). S'il existe une meilleure solution, plus générale, veuillez. Dites-moi dans les commentaires.

0
vonjd