99

What are the R equivalents for these Python list comprehensions:

[(i,j) for i,j in zip(index, Values)]
[(i,j) for i,j in enumerate(Values)]
[(i,j) for i,j in enumerate(range(10,20))]   %MWE, indexing or enumerating to 
                                            %keep up with the index, there may 
                                            %be some parameter to look this up

Example with Output

>>> [(i,j) for i,j in enumerate(range(10,20))]
[(0, 10), (1, 11), (2, 12), (3, 13), (4, 14), (5, 15), (6, 16), (7, 17), (8, 18), (9, 19)]

I have solved this problem earlier with some trick in R but cannot remember anymore, the first idea was itertools -pkg but I am hoping to find a more idiomatic way of doing things.

hhh
  • 50,788
  • 62
  • 179
  • 282
  • 2
    if you could give a small worked example for those of us who aren't familiar with Python it might increase the population of potential answerers. I'm guessing that the last one is `expand.grid(i=10:20,j=10:20)` – Ben Bolker Feb 14 '12 at 17:39
  • @BenBolker: added an output -- now clear? It could be more challenging but the logic is important... – hhh Feb 14 '12 at 17:59
  • 1
    I agree with @DWin. It's unreasonable to expect a one-to-one mapping between data structures in R and Python. If you want good answers you should specify how you want the result to look _in R_ not in Python. – joran Feb 14 '12 at 18:54
  • By the way, here's a neat way to zip and flatten the two lists: `as.vector(rbind(1:10, 11:20))` – smci Mar 31 '15 at 07:11

9 Answers9

59

Answer for python enumerate:

In R, a list is ordered (see this answer). Thus, all you need is to index either keys (using names()[i]) or values (using [[i]]).

Using seq_along (alternatively can do for(i in 1:length(mylist)){...}):

> mylist <- list('a'=10,'b'=20,'c'=30)
> for (i in seq_along(mylist)){
+   print(paste(i,names(mylist)[i],mylist[[i]]))
+ }
[1] "1 a 10"
[1] "2 b 20"
[1] "3 c 30"

Answer for python zip:

See one of the above answers to mimic the list of tuples. My preference is towards a data frame as shown in BondedDust's answer:

> x <- 1:3
> y <- 4:6
> data.frame(x=x, y=y)
  x y
1 1 4
2 2 5
3 3 6
Community
  • 1
  • 1
  • 1
    To continue your first example into the second, `data.frame(names=labels(mylist),values=unlist(mylist),row.names = 1:length(mylist))` – Josiah Yoder Jul 24 '19 at 18:07
  • a question about performance: does calling names(mylist)[i] have to do work every time through or is it a trivial operation? I'm wondering if it would be best to assign it to name_list before the loop – markgalassi Dec 20 '20 at 00:14
  • 2
    R syntax is so ugly if we want to do something out of normal syntax range, this language is so unfelexible in some parts, bu tin other is great (NSE & SE for example) – Qbik Feb 13 '21 at 08:44
44

There have been some discussions around list comprehension for R, e.g. here or there. The hash package even offers dictionary-like structure. However, as others said, it is difficult to try to map one language facilities onto another (even if this is what Comparison of programming languages actually offers) without a clear understanding of what it is supposed to be used to. For example, I can mimic Python zip() in R as follows:

Python

In [1]: x = [1,2,3]
In [2]: y = [4,5,6]
In [3]: zip(x, y)
Out[3]: [(1, 4), (2, 5), (3, 6)]

R

> x <- 1:3
> y <- 4:6
> list(x, y)                     # gives a simple list
> as.list(paste(x, y))           # three tuples, as a list of characters
> mapply(list, x, y, SIMPLIFY=F) # gives a list of 3 tuples
> rbind(x, y)                    # gives a 2x3 matrix 

As can be seen, this really depends on what you want to do with the result afterwards.

chl
  • 27,771
  • 5
  • 51
  • 71
17

zip and enumerate are not particularly difficult to implement in R:

#' zip(1:5,1:10)
zip <- function(...) {
  mapply(list, ..., SIMPLIFY = FALSE)
}

Enumerate is simple to define in terms of zip:

#' enumerate(l=LETTERS)
enumerate <- function(...) {
  zip(ix=seq_along(..1), ...)
}

Since these are proper functions, we can use ... to make them fairly flexible and terse, and take advantage of mapply's behavior, such as recycling inputs and naming output correctly.

Neal Fultz
  • 9,282
  • 1
  • 39
  • 60
  • 1
    These have been added to the `stackoverflow` package, fwiw. – Neal Fultz Feb 02 '20 at 16:37
  • Thanks for telling me about this, but I don't see it in the docs: https://cran.r-project.org/web/packages/stackoverflow/stackoverflow.pdf – Frank May 04 '21 at 20:41
  • How can I modify enumerate to create a default variable name? I'd like index to be k and value to be v. Right now I do this: ```enumerate <- function(...){zip(k = seq_along(..1), ...)}``` and ```enumerate(v = LETTERS)```. I'd like v to be the default, ut how to change the function with a default variable? – Frank May 04 '21 at 20:44
  • Also any way to make this work with a dataframe? Default gives me the dataframe columns, but I would rather enumerate over the rows. Currently I use ```enumerate(v = split(g, seq(nrow(g))))``` where ```g = data.frame(a = c(1, 2, 3), b = c(4, 5, 6))``` – Frank May 04 '21 at 20:55
  • 1
    In the cran build it is named zip2 to not conflict with base R. – Neal Fultz May 04 '21 at 21:42
  • You often will want to add `[-1,]` which makes it more like Python in the following use case: `d <- tibble(x = c(1, 4, 2, NA, 2), y = c(NA, NA, NA, NA, NA), z = c(3, 1, NA, 12, NA), w = c(NA, NA, 6, NA, 9))` Here, `d %>% replace_na(mapply(list, colnames(.), 0)[-1,])` is equivalent to `d %>% replace_na(list(x = 0, y = 0, z = 0, w = 0))` – CubicInfinity Sep 07 '21 at 21:27
  • 1
    combinations of elements of multiple vectors are implemented in `expand.grid()` and `purrr::cross()` – ivan866 Aug 30 '22 at 17:53
9

Another option which will create a list of vectors is to use the Map function as seen here by @peterhurford: https://rdrr.io/github/peterhurford/funtools/src/R/zippers.R

> x <- 1:3
> y <- 4:6
> z <- 7:9
> Map(c, x, y, z)
[[1]]
[1] 1 4 7

[[2]]
[1] 2 5 8

[[3]]
[1] 3 6 9
wphampton
  • 504
  • 5
  • 13
  • In Python, a primary use of zip is iteration over several vectors/lists: `for xi, yi in zip(x, y): ...`. +1 for the most elegant solution I've seen so far to do this in R: `for (xi.yi in Map(c, x, y)) { xi <- xi.yi[1]; yi <- xi.yi[2]; ... }` – sgrubsmyon Oct 01 '19 at 12:04
6

If that is the Python print representation of a matrix, then this code:

j <- 10:20
matrix(c(seq_along(j), j), ncol=2)
#------------
      [,1] [,2]
 [1,]    1   10
 [2,]    2   11
 [3,]    3   12
 [4,]    4   13
 [5,]    5   14
 [6,]    6   15
 [7,]    7   16
 [8,]    8   17
 [9,]    9   18
[10,]   10   19
[11,]   11   20

You are still leaving those of us who are not Python users in the dark regarding the structure of your desired output. You use the term "list" but the output suggests an ordered set of tuples.

Given @chi's guidance we might also suggest using the very R-centric 'dataframe' structure

x <- 1:3
y <- 4:6
dfrm <- data.frame(x=x, y=y)

... which has the flexibility of a list in terms of column types and the access features of a matrix in terms of row and column indexing. Or one could use hhh's request and create the implicitly indexed values of the j-vector, 10:20, using the rownames vector that starts at "1" by default, but which could be altered to become a character vector starting at "0"

dfrm <- data.frame(j=10:20)
dfrm[3, ]
#[1] 12

 rownames(dfrm) <- 0:10
 dfrm["0",]
# [1] 10

Unfortunately, the unwary will find that dfrm[0, ] is not a happy call, returning vector of length 0.

IRTFM
  • 258,963
  • 21
  • 364
  • 487
  • +1 for an elegant solution. (No, those are not Python matrices but as you already guessed it [list of tuples](http://docs.python.org/library/functions.html#zip).) – chl Feb 14 '12 at 20:53
4

In order to use Python style list comprehensions with enumerations, such as enumerated lists, one way is to install List-comprehension package LC (developed 2018) and itertools package (developed 2015).

List comprehensions in R

You can find the LC package here.

install.packages("devtools")
devtools::install_github("mailund/lc")

Example

> library(itertools); library(lc)
> lc(paste(x$index, x$value), x=as.list(enumerate(rnorm(5))), )
[[1]]
[1] "1 -0.715651978438808"

[[2]]
[1] "2 -1.35430822605807"

[[3]]
[1] "3 -0.162872340884235"

[[4]]
[1] "4 1.42909760816254"

[[5]]
[1] "5 -0.880755983937781"

where the programming syntax is not yet as clean and polished as in Python but functionally working and its help outlines:

"The syntax is as follows: lc(expr, lists, predicates) where expr is some expression to be evaluated for all elements in the lists, where lists are one or more named lists, where these are specified by a name and an expression name = list_expr, and where predicates are expressions that should evaluated to a boolean value. For example, to get a list of all even numbers, squared, from a list x we can write lc(x ** 2, x = x, x %% 2 == 0). The result of a call to lc is a list constructed from the expressions in expr, for all elements in the input lists where the predicates evaluate to true."

where notice that you can leave the predicates empty for example in the above example.

Python-style itertools and enumerations

You can use R's itertools that is very similar to Python's itertools, further in Cran here

library(itertools)

where described

"Various tools for creating iterators, many patterned after functions in the Python itertools module, and others patterned after functions in the 'snow' package."

Example. enumeration

> for (a in as.list(enumerate(rnorm(5)))) { print(paste(a$index, "index:", a$value))}
[1] "1 index: 1.63314811372568"
[1] "2 index: -0.983865948988314"
[1] "3 index: -1.27096072277818"
[1] "4 index: 0.313193212706331"
[1] "5 index: 1.25226639725357"

Example. enumeration with ZIP

> for (h in as.list(izip(a=1:5, b=letters[1:5]))) { print(paste(h$a, "index:", h$b))}
[1] "1 index: a"
[1] "2 index: b"
[1] "3 index: c"
[1] "4 index: d"
[1] "5 index: e"
hhh
  • 50,788
  • 62
  • 179
  • 282
2

This can be achieved using two paste statements:

str1 <- paste(1:11, 10:20, sep=",", collapse='), (')
paste("(", str1, ")", sep = "")

Output will like following:

'(1,10), (2,11), (3,12), (4,13), (5,14), (6,15), (7,16), (8,17), (9,18), (10,19), (11,20)'
kravi
  • 747
  • 1
  • 8
  • 13
1

For python, 'enumerate' equivalent in R. Storing the vectors in the list and iterating over them with an index should work fine.

vect1 <- c('A', 'B', 'C')
vect2 <- c('a', 'b', 'c')

# eqiv to zip values:
idx_list <- list(vect1, vect2)
idx_vect <- c(1:length(idx_list[[1]]))

for(i in idx_vect){
    x <- idx_list[[1]][i]
    j <- idx_list[[2]][i]
    print(c(i, x, j))
}

Output:

[1] "1" "A" "a"
[1] "2" "B" "b"
[1] "3" "C" "c"

R 'list' is a nice bank for depositing the vectors and retain with an index.

Surya
  • 11,002
  • 4
  • 57
  • 39
0
# similar to python. return a list of list. Short sequences get recycled.
zip <- function(...){ 
    all.list <- list(...)
    ele.names <- names(all.list)
    max.length <- max(sapply(all.list, length))
    lapply(0:(max.length - 1), function(i) {
        res <- lapply(all.list, function(l) l[i %% length(l) + 1]) 
        names(res) <- ele.names
        res
    })
}
Leaf
  • 11