103 votes

Assigner plusieurs nouvelles variables sur la LHS dans une seule ligne

Je veux assigner 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 assigner environ 5-6 variables sur une seule ligne, au lieu d'avoir plusieurs lignes. Existe-t-il une alternative ?

0 votes

Vous voulez dire quelque chose comme en PHP list($a, $b) = array(1, 2) ? Ce serait bien ! +1.

0 votes

@Tomas T - Je pense que mon vassign La suggestion ci-dessous s'en approche... :)

0 votes

Note : Les points-virgules ne sont pas nécessaires pour cette partie de R.

45voto

Ricardo Saporta Points 22951

Il y a une excellente réponse sur le site Blog Struggling Through Problems

Ceci est tiré de là, avec des modifications très mineures.

EN UTILISANT LES 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)
}

Puis d'exécuter :

Regroupez le côté gauche en utilisant la nouvelle fonction g() La partie droite 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 d'utilisation de listes de tailles différentes :

Plus long côté gauche

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"

Plus long côté droit

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"

38voto

Oscar de León Points 822

Envisagez d'utiliser la fonctionnalité incluse dans la base R.

Par exemple, créez un cadre de données à 1 ligne (disons V ) et initialiser vos variables dans celui-ci. Maintenant, vous pouvez assigner à plusieurs variables à la fois V[,c("a", "b")] <- values[c(2, 4)] appelle chacun d'eux par son nom ( V$a ), ou utiliser plusieurs d'entre eux en même temps ( values[c(5, 6)] <- V[,c("a", "b")] ).

Si vous êtes paresseux et que vous ne voulez pas appeler les variables du cadre de données, vous pourriez attach(V) (bien que je ne le fasse jamais personnellement).

# 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

7 votes

+10 si je pouvais. Je me demande pourquoi les gens refusent d'utiliser des listes dans des cas aussi évidents, mais préfèrent joncher l'espace de travail de tonnes de variables sans signification. (Vous utilisez effectivement des listes, car un data.frame est un type spécial de liste. J'en utiliserais simplement une plus générale).

0 votes

Mais vous ne pouvez pas avoir différents types d'éléments dans la même colonne, et vous ne pouvez pas non plus stocker des cadres de données ou des listes dans votre cadre de données.

1 votes

En fait, vous pouvez stocker des listes dans un cadre de données - recherchez "colonne de liste" sur Google.

14voto

kohske Points 30437

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

Mais cela n'est pas bien testé.

3 votes

Koshke, ça m'a l'air très bien :-) Mais je suis un peu inquiet au sujet de la précédence des opérateurs : les opérateurs %quelquechose% sont assez haut placés, donc le comportement de par ex. c(c, d) %tin% c(1, 2) + 3 (=> c = 1, d = 1, retour numérique (0)) peut être considéré comme surprenant.

10voto

joran Points 68079

Un danger potentiel (dans la mesure où l'utilisation assign est risqué) l'option serait de 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

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

9voto

Tommy Points 16323

Comme d'autres l'ont expliqué, il semble que rien ne soit intégré. ...mais vous pourriez concevoir une 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 la façon de gérer les cas où vous spécifiez par exemple 3 variables et 5 valeurs ou l'inverse. Ici, je répète simplement (ou je tronque) les valeurs pour qu'elles soient 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

0 votes

J'aime bien cette idée, mais je crains qu'elle ne se casse dans le cas où elle est appelée à partir d'une fonction (bien qu'un test simple ait fonctionné, à ma légère surprise). Pouvez-vous expliquer ...() ce qui ressemble à de la magie noire pour moi... ?

1 votes

@Ben Bolker - Oui, ...() est de la magie noire extrême ;-). Il se trouve que lorsque l'"appel de fonction" ...() est substitué, il devient une liste de paires qui peut être transmise à as.character et voilà, vous avez les arguments sous forme de chaînes...

1 votes

@Ben Bolker - Et il devrait fonctionner correctement, même lorsqu'il est appelé à l'intérieur d'une fonction, puisqu'il utilise la fonction envir=parent.frame() - Et vous pouvez spécifier par exemple envir=globalenv() si vous voulez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X