Nick
Nick

Reputation: 9061

List comprehension in R

Is there a way to implement list comprehension in R?

Like python:

sum([x for x in range(1000) if x % 3== 0 or x % 5== 0])

same in Haskell:

sum [x| x<-[1..1000-1], x`mod` 3 ==0 || x `mod` 5 ==0 ]

What's the practical way to apply this in R?

Nick

Upvotes: 52

Views: 40717

Answers (10)

G. Grothendieck
G. Grothendieck

Reputation: 269905

This is many years later but there are three list comprehension packages now on CRAN. Each has slightly different syntax. All of these work well but listcompr supports convenient custom column naming (not needed here) and eList supports parallel processing (not shown here) in conjunction with the parallel package. In alphabetical order:

library(comprehenr)
sum(to_vec(for(x in 1:1000) if (x %% 3 == 0 | x %% 5 == 0) x))
## [1] 234168

library(eList)
Sum(for(x in 1:1000) if (x %% 3 == 0 | x %% 5 == 0) x else 0)
## [1] 234168

library(listcompr)
sum(gen.vector(x, x = 1:1000, x %% 3 == 0 | x %% 5 == 0))
## [1] 234168

In addition the following is on github only.

# devtools::install.github("mailund/lc")
library(lc)
sum(unlist(lc(x, x = seq(1000), x %% 3 == 0 | x %% 5 == 0)))
## [1] 234168

Upvotes: 5

Tripartio
Tripartio

Reputation: 2199

For a strict mapping from Python to R, this might be the most direct equivalence:

Python:

sum([x for x in range(1000) if x % 3== 0 or x % 5== 0])

R:

sum((x <- 0:999)[x %% 3 == 0 | x %% 5 == 0])

One important difference: the R version works like Python 2 where the x variable is globally scoped outside of the expression. (I call it an "expression" here since R does not have the notion of "list comprehension".) In Python 3, the iterator is restricted to the local scope of the list comprehension. In other words:

  • In R (as in Python 2), the x variable persists after the expression. If it existed before the expression, then its value is changed to the final value of the expression.
  • In Python 3, the x variable exists only within the list comprehension. If there was an x variable created before the list comprehension, the list comprehension does not change it at all.

Upvotes: 1

Patrick Roocks
Patrick Roocks

Reputation: 3259

I hope it's okay to self-promote my package listcompr which implements a list comprehension syntax for R.

The example from the question can be solved in the following way:

library(listcompr)
sum(gen.vector(x, x = 1:1000, x %% 3 == 0 || x %% 5 == 0))

## Returns: 234168

As listcompr does a row-wise (and not a vector-vise) evaluation of the conditions, it makes no difference if || or | is used a logical operator. It accepts arbitrary many arguments: First, a base expression which is transformed into the list or vector entries. Next, arbitrary many arguments which specify the variable ranges and the conditions.

More examples can be found on the readme page on the github repository of listcompr: https://github.com/patrickroocks/listcompr

Upvotes: 1

Adam Lee Perelman
Adam Lee Perelman

Reputation: 890

The foreach package by Revolution Analytics gives us a handy interface to list comprehensions in R. https://www.r-bloggers.com/list-comprehensions-in-r/

Example

Return numbers from the list which are not equal as tuple:

Python

list_a = [1, 2, 3]
list_b = [2, 7]

different_num = [(a, b) for a in list_a for b in list_b if a != b]

print(different_num) 

# Output: 
[(1, 2), (1, 7), (2, 7), (3, 2), (3, 7)]

R

require(foreach)

list_a = c(1, 2, 3)
list_b = c(2, 7)

different_num  <- foreach(a=list_a ,.combine = c ) %:% foreach(b=list_b) %:% when(a!=b) %do% c(a,b)

print(different_num)

# Output:
         [[1]]
         [1] 1 2

         [[2]]
         [1] 1 7

         [[3]]
         [1] 2 7

         [[4]]
         [1] 3 2

         [[5]]
         [1] 3 7

EDIT:
The foreach package is very slow for certain tasks. A faster list comprehension implementation is given at List comprehensions for R

. <<- structure(NA, class="comprehension")

comprehend <- function(expr, vars, seqs, guard, comprehension=list()){
  if(length(vars)==0){  # base case of recursion
    if(eval(guard)) comprehension[[length(comprehension)+1]] <- eval(expr)
  } else {
    for(elt in eval(seqs[[1]])){
      assign(vars[1], elt, inherits=TRUE)
      comprehension <- comprehend(expr, vars[-1], seqs[-1], guard, 
                                  comprehension)
    }
  }
  comprehension
}

## List comprehensions specified by close approximation to set-builder notation:
##
##   { x+y | 0<x<9, 0<y<x, x*y<30 } ---> .[ x+y ~ {x<-0:9; y<-0:x} |  x*y<30 ]
##

"[.comprehension" <- function(x, f,rectangularizing=T){
  f <- substitute(f)
  ## First, we pluck out the optional guard, if it is present:
  if(is.call(f) && is.call(f[[3]]) && f[[3]][[1]]=='|'){
    guard <- f[[3]][[3]]
    f[[3]] <- f[[3]][[2]]
  } else {
    guard <- TRUE
  }
  ## To allow omission of braces around a lone comprehension generator,
  ## as in 'expr ~ var <- seq' we make allowances for two shapes of f:
  ##
  ## (1)    (`<-` (`~` expr
  ##                   var)
  ##              seq)
  ## and
  ##
  ## (2)    (`~` expr
  ##             (`{` (`<-` var1 seq1)
  ##                  (`<-` var2 seq2)
  ##                      ...
  ##                  (`<-` varN <- seqN)))
  ##
  ## In the former case, we set gens <- list(var <- seq), unifying the
  ## treatment of both shapes under the latter, more general one.
  syntax.error <- "Comprehension expects 'expr ~ {x1 <- seq1; ... ; xN  <- seqN}'."
  if(!is.call(f) || (f[[1]]!='<-' && f[[1]]!='~'))
    stop(syntax.error)
  if(is(f,'<-')){ # (1)
    lhs <- f[[2]]
    if(!is.call(lhs) || lhs[[1]] != '~')
      stop(syntax.error)
    expr <- lhs[[2]]
    var <- as.character(lhs[[3]])
    seq <- f[[3]]
    gens <- list(call('<-', var, seq))
  } else { # (2)
    expr <- f[[2]]
    gens <- as.list(f[[3]])[-1]
    if(any(lapply(gens, class) != '<-'))
      stop(syntax.error)
  }
  ## Fill list comprehension .LC
  vars <- as.character(lapply(gens, function(g) g[[2]]))
  seqs <- lapply(gens, function(g) g[[3]])
  .LC <- comprehend(expr, vars, seqs, guard)

  ## Provided the result is rectangular, convert it to a vector or array
  if(!rectangularizing) return(.LC)
  tryCatch({
     if(!length(.LC))
      return(.LC)
  dim1 <- dim(.LC[[1]])
  if(is.null(dim1)){
    lengths <- sapply(.LC, length)
    if(all(lengths == lengths[1])){ # rectangular
      .LC <- unlist(.LC)
      if(lengths[1] > 1) # matrix
        dim(.LC) <- c(lengths[1], length(lengths))
    } else { # ragged
      # leave .LC as a list
    }
  } else { # elements of .LC have dimension
    dim <- c(dim1, length(.LC))
    .LC <- unlist(.LC)
    dim(.LC) <- dim
  }
  return(.LC)
  }, error = function(err) {
    return(.LC)
  })

}

This implementation is faster then foreach, it allows nested comprehension, multiple parameters and parameters scoping.

 N <- list(10,20)
.[.[c(x,y,z)~{x <- 2:n;y <- x:n;z <- y:n} | {x^2+y^2==z^2 & z<15}]~{n <- N}]

[[1]]
[[1]][[1]]
[1] 3 4 5

[[1]][[2]]
[1]  6  8 10


[[2]]
[[2]][[1]]
[1] 3 4 5

[[2]][[2]]
[1]  5 12 13

[[2]][[3]]
[1]  6  8 10

Upvotes: 3

John
John

Reputation: 641

You could convert a sequence of random numbers to a binary sequence as follows:

x=runif(1000)
y=NULL
for (i in x){if (i>.5){y<-c(y,1)}else{y=c(y,-1)}}

this could be generalized to operate on any list to another list based on:

x = [item for item in x if test == True]

where the test could use the else statement to not append the list y.

For the problem at hand:

x <- 0:999 
y <- NULL 
for (i in x){ if (i %% 3 == 0 | i %% 5 == 0){ y <- c(y, i) }}
sum( y )

Upvotes: 0

Wassadamo
Wassadamo

Reputation: 1386

This list comprehension of the form:

[item for item in list if test]

is pretty straightforward with boolean indexing in R. But for more complex expressions, like implementing vector rescaling (I know this can be done with scales package too), in Python it's easy:

x = [1, 3, 5, 7, 9, 11] # -> [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
[(xi - min(x))/(max(x) - min(x)) for xi in x]

But in R this is the best I could come up with. Would love to know if there's something better:

sapply(x, function(xi, mn, mx) {(xi-mn)/(mx-mn)}, mn = min(x), mx = max(x))

Upvotes: 0

zawhtut
zawhtut

Reputation: 8561

Another way

sum(l<-(1:1000)[l %% 3 == 0 | l %% 5 == 0])

Upvotes: 1

pedrobtz
pedrobtz

Reputation: 67

And, (kind of) the for-comprehension of scala:

for(i in {x <- 1:100;x[x%%2 == 0]})print(i)

Upvotes: 3

Ciar&#225;n Tobin
Ciar&#225;n Tobin

Reputation: 7536

Something like this?

l <- 1:1000
sum(l[l %% 3 == 0 | l %% 5 == 0])

Upvotes: 44

Nishanth
Nishanth

Reputation: 7130

Yes, list comprehension is possible in R:

sum((1:1000)[(1:1000 %% 3) == 0 | (1:1000 %% 5) == 0])

Upvotes: 10

Related Questions