List Comprehensions in R

Package provides Python-style list comprehensions for R. List comprehension expressions use usual loops (for, while and repeat) and usual if as list producers. Syntax is very similar to Python. The difference is that returned value should be at the end of the loop body.

There are three main functions:

Rather unpractical example - squares of even numbers:

library(comprehenr)
to_vec(for(i in 1:10) if(i %% 2==0) i*i)
#> [1]   4  16  36  64 100

Pythagorean triples:

to_list(for (x in 1:20) for (y in x:20) for (z in y:20) if (x^2 + y^2 == z^2) c(x, y, z))
#> [[1]]
#> [1] 3 4 5
#> 
#> [[2]]
#> [1]  5 12 13
#> 
#> [[3]]
#> [1]  6  8 10
#> 
#> [[4]]
#> [1]  8 15 17
#> 
#> [[5]]
#> [1]  9 12 15
#> 
#> [[6]]
#> [1] 12 16 20

More examples:

colours = c("red", "green", "yellow", "blue")
things = c("house", "car", "tree")
to_vec(for(x in colours) for(y in things) paste(x, y))
#>  [1] "red house"    "red car"      "red tree"     "green house" 
#>  [5] "green car"    "green tree"   "yellow house" "yellow car"  
#>  [9] "yellow tree"  "blue house"   "blue car"     "blue tree"

# prime numbers
noprimes = to_vec(for (i in 2:7) for (j in seq(i*2, 99, i)) j)
primes = to_vec(for (x in 2:99) if(!x %in% noprimes) x)
primes
#>  [1]  2  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83
#> [24] 89 97

You can iterate over multiple lists if you provide several loop variables in backticks:

to_vec(for(`i, j` in numerate(letters)) if(i %% 2==0) paste(i, j))
#>  [1] "2 b"  "4 d"  "6 f"  "8 h"  "10 j" "12 l" "14 n" "16 p" "18 r" "20 t"
#> [11] "22 v" "24 x" "26 z"

set.seed(123)
rand_sequence = runif(20)
# gives only locally increasing values
to_vec(for(`i, j` in lag_list(rand_sequence)) if(j>i) j)
#>  [1] 0.7883051 0.8830174 0.9404673 0.5281055 0.8924190 0.9568333 0.6775706
#>  [8] 0.8998250 0.3279207 0.9545036

alter examples:

data(iris)
# scale numeric variables
res = alter(for(i in iris) if(is.numeric(i)) scale(i))
str(res)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num [1:150, 1] -0.898 -1.139 -1.381 -1.501 -1.018 ...
#>   ..- attr(*, "scaled:center")= num 5.84
#>   ..- attr(*, "scaled:scale")= num 0.828
#>  $ Sepal.Width : num [1:150, 1] 1.0156 -0.1315 0.3273 0.0979 1.245 ...
#>   ..- attr(*, "scaled:center")= num 3.06
#>   ..- attr(*, "scaled:scale")= num 0.436
#>  $ Petal.Length: num [1:150, 1] -1.34 -1.34 -1.39 -1.28 -1.34 ...
#>   ..- attr(*, "scaled:center")= num 3.76
#>   ..- attr(*, "scaled:scale")= num 1.77
#>  $ Petal.Width : num [1:150, 1] -1.31 -1.31 -1.31 -1.31 -1.31 ...
#>   ..- attr(*, "scaled:center")= num 1.2
#>   ..- attr(*, "scaled:scale")= num 0.762
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

# convert factors to characters
res = alter(for(i in iris) if(is.factor(i)) as.character(i))
str(res)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : chr  "setosa" "setosa" "setosa" "setosa" ...

# drop factors
res = alter(for(i in iris) if(is.factor(i)) exclude())
str(res)
#> 'data.frame':    150 obs. of  4 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...

# 'data' argument example
# specify which columns to map with a numeric vector of positions:
res = alter(
    for(`i, value` in numerate(mtcars)) if(i %in% c(1, 4, 5)) as.character(value),
    data = mtcars
)
str(res)
#> 'data.frame':    32 obs. of  11 variables:
#>  $ mpg : chr  "21" "21" "22.8" "21.4" ...
#>  $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
#>  $ disp: num  160 160 108 258 360 ...
#>  $ hp  : chr  "110" "110" "93" "110" ...
#>  $ drat: chr  "3.9" "3.9" "3.85" "3.08" ...
#>  $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
#>  $ qsec: num  16.5 17 18.6 19.4 17 ...
#>  $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
#>  $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
#>  $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
#>  $ carb: num  4 4 1 1 2 1 4 2 2 4 ...

# or with a vector of names:
res = alter(
    for(`name, value` in mark(mtcars)) if(name %in% c("cyl", "am")) as.character(value),
    data = mtcars
)
str(res)
#> 'data.frame':    32 obs. of  11 variables:
#>  $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
#>  $ cyl : chr  "6" "6" "4" "6" ...
#>  $ disp: num  160 160 108 258 360 ...
#>  $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
#>  $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
#>  $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
#>  $ qsec: num  16.5 17 18.6 19.4 17 ...
#>  $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
#>  $ am  : chr  "1" "1" "1" "0" ...
#>  $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
#>  $ carb: num  4 4 1 1 2 1 4 2 2 4 ...