Attribuer plusieurs nouvelles variables sur LHS sur une seule ligne


89

Je souhaite 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 souhaite attribuer environ 5 à 6 variables sur une seule ligne, au lieu d'avoir plusieurs lignes. Y a-t-il une alternative?


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

@Tomas T - Je pense que ma vassignsuggestion ci-dessous se rapproche ... :)
Tommy

Remarque: les points-virgules ne sont pas nécessaires pour ce morceau de R.
Itérateur

1
Si vous essayez ceci dans un environnement approprié, ce serait aussi simple que X <- list();X[c('a','b')] <- values[c(2,4)]. OK, vous ne les attribuez pas dans l'espace de travail, mais conservez-les bien ensemble dans une liste. Je préfère le faire de cette façon.
Joris Meys

7
j'aime python, juste a, b = 1,2. toutes les réponses ci-dessous sont 100x plus difficiles
appleLover

Réponses:


39

Il y a une excellente réponse sur le blog 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)
}


Puis 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"

34

J'ai créé un zeallot de package R pour résoudre ce problème. zeallot inclut un opérateur ( %<-%) pour les affectations de déballage, de multiple et de déstructuration. La LHS de l'expression d'affectation est créée à 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 une destructureimplé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, vous pouvez consulter la vignette du package .


c'est exactement ce que j'espérais trouver, quelque chose qui active la syntaxe de type python que l'OP demandait, implémentée dans un package R
jafelds

1
Qu'en est-il de l'attribution d'une matrice à chaque nom de variable?
StatsSorceress

33

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

Par exemple, créez un dataframe à 1 ligne (disons V) et initialisez vos variables dedans. Vous pouvez désormais affecter plusieurs variables à la fois V[,c("a", "b")] <- values[c(2, 4)], les appeler par leur nom ( V$a) ou en utiliser plusieurs 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 du dataframe, 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

4
+10 si je pouvais. Je me demande pourquoi les gens refusent d'utiliser des listes dans des cas aussi évidents, mais jonchent plutôt l'espace de travail avec des tonnes de variables sans signification. (vous utilisez des listes, car data.frame est un type spécial de liste. J'utiliserais simplement une liste plus générale.)
Joris Meys

mais vous ne pouvez pas avoir différents types d'éléments dans la même colonne, ni stocker des dataframes ou des listes dans votre dataframe
skan

1
En fait, vous pouvez stocker des listes dans un bloc de données - "colonne de liste" google.

Ce n'est pas une mauvaise approche, elle présente certaines commodités, mais il n'est pas non plus difficile d'imaginer pourquoi de nombreux utilisateurs ne voudraient pas avoir à gérer la syntaxe data.frame à chaque fois qu'ils essaient d'utiliser ou d'accéder à des variables affectées de cette manière.
Brandon

13

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.


2
Koshke, me semble très gentil :-) Mais je suis un peu inquiet pour la priorité des opérateurs: les opérateurs% something% sont assez élevés, donc le comportement de eg c(c, d) %tin% c(1, 2) + 3(=> c = 1, d = 1, renvoie numérique ( 0)) peut être considéré comme surprenant.
cbeleites mécontent de SX

10

Une assignoption potentiellement dangereuse (dans la mesure où l'utilisation est risquée) 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 pouvez le vectoriser vous-même manuellement avec votre propre fonction en utilisant mapplypeut-être une valeur par défaut raisonnable pour l' envirargument. Par exemple, Vectorizeretournera une fonction avec les mêmes propriétés d'environnement que assign, ce qui dans ce cas est namespace:base, ou vous pouvez simplement définir envir = parent.env(environment(assignVec)).


8

Comme d'autres l'ont expliqué, il ne semble y avoir rien de intégré. ... mais vous pouvez concevoir une vassignfonction 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 comment gérer les cas où vous spécifiez par exemple 3 variables et 5 valeurs ou l'inverse. Ici, je répète (ou tronque) simplement 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

J'aime cela, mais je crains que cela ne casse dans certains cas où il a été appelé à partir d'une fonction (bien qu'un simple test de cela ait fonctionné, à ma légère surprise). Pouvez-vous m'expliquer ...(), ce qui ressemble à de la magie noire pour moi ...?
Ben Bolker

1
@Ben Bolker - Oui, ...()c'est de la magie noire extrême ;-). Il se trouve que lorsque "l'appel de fonction" ...()est remplacé, il devient une liste de paires qui peut être transmise as.characteret le tour est joué, vous avez les arguments sous forme de chaînes ...
Tommy

1
@Ben Bolker - Et il devrait fonctionner correctement même lorsqu'il est appelé depuis une fonction car il utilise envir=parent.frame()- Et vous pouvez spécifier par exemple envir=globalenv()si vous le souhaitez.
Tommy

Encore plus cool aurait cela comme fonction de remplacement: `vassign<-` <- function (..., envir = parent.frame (), value)et ainsi de suite. Cependant, il semble que le premier objet à attribuer devrait déjà exister. Des idées?
cbeleites mécontent de SX le

@cbeleites - Oui, ce serait plus cool mais je ne pense pas que vous puissiez contourner la limitation selon laquelle le premier argument doit exister - c'est pourquoi on l'appelle une fonction de remplacement :) ... mais faites-moi savoir si vous découvrez le contraire !
Tommy

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

A servi mon objectif, c'est-à-dire attribuer cinq 2 en cinq premières lettres.



4

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

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

3

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

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

2
cherchait une déclaration succincte mais je suppose qu'il n'y en a pas
user236215

Je suis sur le même bateau que @ user236215. lorsque le côté droit est une expression compliquée renvoyant un vecteur, la répétition du code semble très erronée ...
frappe aérienne

1

J'ai 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 puisse penser est celle-ci:

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

ou si les avertissements vous dérangent, 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
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

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

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é ceci pour ajouter la section R ici: http://rosettacode.org/wiki/Sort_three_variables#R

Attention: cela ne fonctionne que pour assigner des variables globales (comme <<-). S'il y a une meilleure solution plus générale, pls. Dites-moi dans les commentaires.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.