Skip to content

Commit

Permalink
'modify'-> 'alter'
Browse files Browse the repository at this point in the history
  • Loading branch information
gdemin committed Apr 8, 2019
1 parent 81402fa commit 1ea4909
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 31 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: comprehenr
Type: Package
Title: List Comprehensions
Version: 0.6.0
Version: 0.6.5
Maintainer: Gregory Demin <gdemin@gmail.com>
Authors@R: person("Gregory", "Demin", email = "gdemin@gmail.com",
role = c("aut", "cre"))
Expand Down
2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Generated by roxygen2: do not edit by hand

export(alter)
export(enumerate)
export(lag_list)
export(mark)
export(modify)
export(numerate)
export(to_list)
export(to_vec)
Expand Down
2 changes: 1 addition & 1 deletion NEWS
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
0.6.5 (09.04.2019)
================
* add for lists/data.frames modification
* add 'alter' function for conditional lists/data.frames modification

0.6.0 (18.03.2019)
================
Expand Down
53 changes: 42 additions & 11 deletions R/to_list.R
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#' List comprehensions for R
#'
#' \code{to_list} converts usual R loops expressions to list producers.
#' \itemize{\item{\code{to_list}}{ converts usual R loops expressions to list producers.
#' Expression should be started with \code{for} , \code{while} or
#' \code{repeat}. You can iterate over multiple lists if you provide several
#' loop variables in backticks. See examples.
#' loop variables in backticks. See examples.}
#' \item{\code{to_vec}}{ is the same as \code{to_list} but return vector. See examples.}
#' \item{\code{alter}}{ return the same type as its argument but with modified
#' elements. It is useful for altering existing data.frames or lists. See
#' examples.}
#' }
#' @param expr expression which starts with \code{for} , \code{while} or \code{repeat}.
#' @param recursive logical. Should unlisting be applied to list components of result? See \link[base]{unlist} for details.
#' @param use.names logical. Should names be preserved? See \link[base]{unlist} for details.
#' @param data data.frame/list/vector which we want to alter
#' @return list for \code{to_list} and vector for \code{to_vec}
#' @export
#'
Expand All @@ -33,6 +39,31 @@
#' rand_sequence = runif(20)
#' # gives only locally increasing values
#' to_vec(for(`i, j` in lag_list(rand_sequence)) if(j>i) j)
#'
#' # 'alter' examples
#' data(iris)
#' # scale numeric variables
#' res = alter(for(i in iris) if(is.numeric(i)) scale(i))
#' str(res)
#'
#' # convert factors to characters
#' res = alter(for(i in iris) if(is.factor(i)) as.character(i))
#' str(res)
#'
#' # '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)
#'
#' # 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)
to_list = function(expr){
expr = substitute(expr)
if(!is_loop(expr)) {
Expand All @@ -41,7 +72,7 @@ to_list = function(expr){

expr = expand_loop_variables(expr)
expr = add_assignment_to_final_loops(expr)
on.exit(suppressWarnings(rm(list = c(".___res", ".___counter", ".__curr"), envir = parent.frame())))
on.exit(suppressWarnings(rm(list = c(".___res", ".___counter", ".___curr"), envir = parent.frame())))
eval.parent(quote(.___res <- list()))
eval.parent(quote(.___counter <- 0)) # initial list length
eval.parent(expr)
Expand Down Expand Up @@ -131,20 +162,20 @@ add_assignment_to_loop = function(expr, result_exists = FALSE){
last_item = length(expr)
if(result_exists){
expr[[last_item]] = bquote({
.__curr = {.(expr[[last_item]])}
.___curr = {.(expr[[last_item]])}
.___counter = .___counter + 1
if(!is.null(.__curr)){
.___res[[.___counter]] = .__curr
if(!is.null(.___curr)){
.___res[[.___counter]] = .___curr
}

})
} else {
expr[[last_item]] = bquote({

.__curr = {.(expr[[last_item]])}
if(!is.null(.__curr)){
.___curr = {.(expr[[last_item]])}
if(!is.null(.___curr)){
.___counter = .___counter + 1
.___res[[.___counter]] = .__curr
.___res[[.___counter]] = .___curr
}

})
Expand All @@ -154,12 +185,12 @@ add_assignment_to_loop = function(expr, result_exists = FALSE){

#' @rdname to_list
#' @export
modify = function(expr, data = NULL){
alter = function(expr, data = NULL){
expr = substitute(expr)
if(!is_loop(expr)) {
stop(paste("argument should be expression with 'for', 'while' or 'repeat' but we have: ", deparse(expr, width.cutoff = 500)[1]))
}
on.exit(suppressWarnings(rm(list = c(".___res", ".___counter", ".__curr"), envir = parent.frame())))
on.exit(suppressWarnings(rm(list = c(".___res", ".___counter", ".___curr"), envir = parent.frame())))
if(is.null(data)) data = expr[[3]]
eval.parent(substitute(.___res <- data))
expr = expand_loop_variables(expr)
Expand Down
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,23 @@ 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.

```R
# rather useless statement - squares of even numbers
to_list(for(i in 1:10) if(i %% 2==0) i*i)
There are three main functions:

# 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))
- `to_list` converts usual R loops expressions to list producers. Expression should be started with `for`, `while` or `repeat`. You can iterate over multiple lists if you provide several loop variables in backticks. See examples.
- `to_vec` is the same as `to_list` but return vector. See examples.
- `alter` return the same type as its argument but with modified elements. It is useful for altering existing data.frames or lists. See examples.

Rather unpractical example - squares of even numbers:
```R
library(comprehenr)
to_vec(for(i in 1:10) if(i %% 2==0) i*i)
```
Pythagorean triples:
```R
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))
```
More examples:
```R
colours = c("red", "green", "yellow", "blue")
things = c("house", "car", "tree")
to_vec(for(x in colours) for(y in things) paste(x, y))
Expand All @@ -36,3 +46,30 @@ rand_sequence = runif(20)
to_vec(for(`i, j` in lag_list(rand_sequence)) if(j>i) j)

```

`alter` examples:
```R
data(iris)
# scale numeric variables
res = alter(for(i in iris) if(is.numeric(i)) scale(i))
str(res)

# convert factors to characters
res = alter(for(i in iris) if(is.factor(i)) as.character(i))
str(res)

# '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)

# 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)
```
40 changes: 36 additions & 4 deletions man/to_list.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions tests/testthat/test_to_list.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ expect_equal(
)
expect_false(exists(".___res"))
expect_false(exists(".___counter"))
expect_false(exists(".__curr"))
expect_false(exists(".___curr"))

expect_error(to_list(for(i in 1:10) i*brrrr))
expect_false(exists(".___res"))
expect_false(exists(".___counter"))
expect_false(exists(".__curr"))
expect_false(exists(".___curr"))


expect_error(to_list(1))
expect_false(exists(".___res"))
expect_false(exists(".___counter"))
expect_false(exists(".__curr"))
expect_false(exists(".___curr"))
a = 11
expect_error(to_list(a + 2))

Expand Down Expand Up @@ -119,20 +119,20 @@ true_res = paste(true_res[[2]], true_res[[1]])
expect_identical(res, true_res)


context("modify")
context("alter")
data(iris)
iris2 = modify(for(i in iris) if(is.numeric(i)) scale(i))
iris2 = alter(for(i in iris) if(is.numeric(i)) scale(i))
res_iris = iris
res_iris[,-5] = lapply(iris[,-5], scale)
expect_equal(iris2, res_iris)

iris2 = modify(for(`name, value` in mark(iris)) if(grepl("Width", name)) scale(value), data = iris)
iris2 = alter(for(`name, value` in mark(iris)) if(endsWith(name, "Width")) scale(value), data = iris)

res_iris = iris
res_iris[,c("Sepal.Width", "Petal.Width")] = lapply(iris[,c("Sepal.Width", "Petal.Width")], scale)
expect_equal(iris2, res_iris)


expect_error(alter(1))
# library(data.table)
# dt_iris = as.data.table(iris)
# dt_iris2 = modify(for(i in dt_iris) if(is.numeric(i)) scale(i))
# dt_iris2 = alter(for(i in dt_iris) if(is.numeric(i)) scale(i))
25 changes: 25 additions & 0 deletions vignettes/Introduction.R
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,28 @@ rand_sequence = runif(20)
to_vec(for(`i, j` in lag_list(rand_sequence)) if(j>i) j)


## ------------------------------------------------------------------------
data(iris)
# scale numeric variables
res = alter(for(i in iris) if(is.numeric(i)) scale(i))
str(res)

# convert factors to characters
res = alter(for(i in iris) if(is.factor(i)) as.character(i))
str(res)

# '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)

# 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)

0 comments on commit 1ea4909

Please sign in to comment.