I'm new to R. Reading Book Of R by Tilman Davies. An example is provided for how to use an externally defined helper function which incidentally utilizes double square brackets [[]]. Please explain what helper.call[[1]] and helper.call[[2]] are doing and use of double brackets here.
multiples_helper_ext <- function(x=foo,matrix.flags,mat=diag(2){
indexes <- which(matrix.flags)
counter <- 0
result <- list()
for(i in indexes){
temp <- x[[i]]
if(ncol(temp)==nrow(mat)){
counter <- counter+1
result[[counter]] <- temp%*%mat
}
}
return(list(result,counter))
}
multiples4 <- function(x,mat=diag(2),str1="no valid matrices",str2=str1){
matrix.flags <- sapply(x,FUN=is.matrix)
if(!any(matrix.flags)){
return(str1)
}
helper.call <- multiples_helper_ext(x,matrix.flags,mat=diag(2)
result <- helper.call[[1]] #I dont understand this use of double bracket
counter <- helper.call[[2]] #and here either
if(counter==0){
return(str2)
} else {
return(result)
}
}
foo <- list(matrix(1:4,2,2),"not a matrix","definitely not a matrix",matrix(1:8,2,4),matrix(1:8,4,2))
In R there are two basic types of objects: lists and vectors. The items of lists can be other objects, the items of of vectors are usually numbers, strings, etc.
To access items in a list, you use the double bracket [[]]. This gives back the object at that place of the list.
So
x <- 1:10
x is now a vector of integers
L <- list( x, x, "hello" )
L is a list whose first item is the vector x, its second item is the vector x, and its third item is the string "hello".
L[[2]]
This give back a vector, 1:10, which is stored in the 2nd place in L.
L[2]
This is a bit confusing, but this gives back a list whose only item is 1:10, i.e. it only contains L[[2]].
In R, when you want to return multiple values, you usually do this with a list. So, you might end you function with
f <- function() {
return( list( result1="hello", result2=1:10) )
}
x = f()
Now you can access the two results with
print( x[["result1"]] )
print( x[["result2"]] )
You can also access items of a list with ''$, so instead you can write
print( x$result1 )
print( x$result2 )
The syntax [[]] is used for list in python. Your helper.call is a list (of result and counter), so helper.cal[[1]] returns the first element of this list (result).
Have a look here: Understanding list indexing and bracket conventions in R
Related
i've non permanent vectors that i like to merge them to one data frame,
im using the following loop to create those vectors
for (i in campagin_id){
h <- basicHeaderGatherer()
doc <- getURI(paste0(automations_url,
"/",i,
"?apikey=",accessToken,
"&count=",pagination), headerfunction = h$update)
assign(paste0('web_id',i),c(i,as.integer(substring(h$value()[as.integer(grep(SearchTerm, h$value()))],
as.integer(regexpr(SearchTerm,h$value()[as.integer(grep(SearchTerm, h$value()))]))+nchar(SearchTerm)-1,as.integer(regexpr(SearchTerm,h$value()[as.integer(grep(SearchTerm, h$value()))]))+nchar(SearchTerm)+StringLength-2))))
}
i received list of vectors and i like to marge them with rbind something like that
rbind(web_id0f09cc8ddd,web_id18a71f70a8)
the issue is that i don't not how many vectors i will get but i knows only the beginning of the vector name, so i'm trying to run the following loop
for (i in campagin_id) {
web_id <- do.call("rbind",list(paste0('web_id',i)))
}
but it insert only one vector to the data frame
the campaign_id contains all the i values i need in specific time
Thanks
do.call is the right idea, but rbind is a slow operation. You should add your vectors to a list one-at-a-time, and then do a single rbind at the end, something like this (untested, obviously, as the example isn't reproducible, but it should give you the idea):
result_list = list(length = length(campagin_id))
for (i in campagin_id) {
h <- basicHeaderGatherer()
doc <- getURI(
paste0(
automations_url,
"/",
i,
"?apikey=",
accessToken,
"&count=",
pagination
),
headerfunction = h$update
)
result_list[[i]] = c(i, as.integer(
substring(
h$value()[as.integer(grep(SearchTerm, h$value()))],
as.integer(regexpr(SearchTerm, h$value()[as.integer(grep(SearchTerm, h$value()))])) +
nchar(SearchTerm) - 1,
as.integer(regexpr(SearchTerm, h$value()[as.integer(grep(SearchTerm, h$value()))])) +
nchar(SearchTerm) + StringLength - 2
)
))
}
results = do.call(rbind, result_list)
I'm looking to fill in this empty vector:
empty_vec <- rep("", times=length(number_vec))
with sequential numbers from this loop:
for (numbers in number_vec) {
sqrt <- sqrt(numbers)
empty_vec[numbers] <- sqrt
}
where numbers_vec is c(16:49).
However, when I do this, the first positions (1-15) in my empty_vec are not filled?
You can address this in two ways:
First, you can create a counter, that will register which step of the loop you are, and use this as index to empty_vect, like this:
empty_vec <- rep("", times=length(number_vec))
counter=0
for (numbers in number_vec) {
counter=counter+1
sqrt<-sqrt(numbers)
empty_vec[counter]<-sqrt
}
Or you can just create an empty vector and concatenate each new value, like this:
empty_vec <- c()
for (numbers in number_vec) {
sqrt<-sqrt(numbers)
empty_vec <- c(empty_vec,sqrt)
}
The way you were doing, is like you started to fill your vector in 16th position, that's way you had error.
First you need to understand how for loop works
General express for for loop is
for(var in seq) expr
var = A syntactical name for a variable
seq = An expression evaluating to a vector (including a list and an expression) or to a pairlist or NULL. A factor value will be coerced to a character vector.
so note it , "seq" will be the value of the "var".
In your example , you wrote
for (numbers in number_vec)
where,
numbers = Name of the variable
number_vec = c(16:49)
So here , the initial value of "numbers" will be first value of "number_vec" which is 16.
in later step in loop, the expression
empty_vec[numbers]<-sqrt
where ,
empty_vec[numbers] indicate the 16th position of the empty_vec as initially numbers started with value 16
As you start with 16th position , the previous 15 position remain empty.
Possible solution of your problem :
number_vec = c(16:49)
empty_vec <- rep("", times=length(number_vec))
for (numbers in seq_along(number_vec)) {
sqrt<-sqrt(number_vec[numbers])
empty_vec[numbers]<-sqrt
}
I started using R today, so I apologize if this is too basic.
First I construct 2 matrices, and construct a vector, whose entries are these matrices. Then, I try to loop over the elements of the vector, i.e. the matrices. However, when I do, I get a "argument of length zero" error.
cam <- 1:12
ped <- 13:24
dim(cam) <- c(3,4)
dim(ped) <- c(4,3)
mats <- c('cam','ped')
for (i in 1:2) {
rownames(mats[i]) <- LETTERS[1:dim(mats[i])[1]]
colnames(mats[i]) <- LETTERS[1:dim(mats[i])[2]]
}
The error text is as follows:
Error in 1:dim(mats[i])[1] : argument of length 0
The question: how to loop over elements of a vector, these elements being matrices? (I'm guessing I'm not calling the elements correctly). Thank you for patience.
The go-to option in R is to use lists:
cam <- 1:12 ; dim(cam) <- c(3,4)
# same as matrix(1:12, nrow = 3, ncol = 4)
ped <- 13:24 ; dim(ped) <- c(4,3)
# save the list ( '=' sign for naming purposes only here)
mats <- list(cam = cam, ped = ped)
# notice the double brackets '[[' which is used for picking the list
for (i in 1:length(mats) {
rownames(mats[[i]]) <- LETTERS[1:dim(mats[[i]])[1]]
colnames(mats[[i]]) <- LETTERS[1:dim(mats[[i]])[2]]
}
# finally you can call the whole list at once as follows:
mats
# or seperately using $ or [[
mats$cam # mats[['cam']]
mats$ped # mats[['ped']]
ALTERNATIVELY
If you really want to get crazy you can take advantage of the get() and assign() functions. get() calls an object by character, and assign() can create one.
mats <- c('cam','ped')
mats.new <- NULL # initialize a matrix placeholder
for (i in 1:length(mats)) {
mats.new <- get(mats[i]) # save as a new matrix each loop
# use dimnames with a list input to do both the row and column at once
dimnames(mats.new) <- list(LETTERS[1:dim(mats.new)[1]],
LETTERS[1:dim(mats.new)[2]])
assign(mats[i],mats.new) # create (re-write) the matrix
}
If the datasets are placed in a list we can use lapply
lst <- lapply(mget(mats), function(x) {
dimnames(x) <- list(LETTERS[seq_len(nrow(x))], LETTERS[seq_len(ncol(x))])
x})
It is better to keep it in a list. In case the original objects needs to be changed
list2env(lst, envir = .GlobalEnv)
When I have data.frame objects, I can simply do View(df), and then I get to see the data.frame in a nice table (even if I can't see all of the rows, I still have an idea of what variables my data contains).
But when I have a list object, the same command does not work. And when the list is large, I have no idea what the list looks like.
I've tried head(mylist) but my console simply cannot display all of the information at once. What's an efficient way to look at a large list in R?
Here's a few ways to look at a list:
Look at one element of a list:
myList[[1]]
Look at the head of one element of a list:
head(myList[[1]])
See the elements that are in a list neatly:
summary(myList)
See the structure of a list (more in depth):
str(myList)
Alternatively, as suggested above you could make a custom print method as such:
printList <- function(list) {
for (item in 1:length(list)) {
print(head(list[[item]]))
}
}
The above will print out the head of each item in the list.
I use str to see the structure of any object, especially complex list's
Rstudio shows you the structure by clicking at the blue arrow in the data-window:
You can also use a package called listviewer
library(listviewer)
jsonedit( myList )
If you have a really large list, you can look at part of it using
str(myList, max.level=1)
(If you don't feel like typing out the second argument, it can be written as max=1 since there are no other arguments that start with max.)
I do this often enough that I have an alias in my .Rprofile for it:
str1 <- function(x, ...) str(x, max.level=1, ...)
And a couple others that limit the printed output (see example(str) for an example of using list.len):
strl <- function(x, len=10L, ...) str(x, list.len=len, ...) # lowercase L in the func name
str1l <- function(x, len=10L, ...) str(x, max.level=1, list.len=len, ...)
you can check the "head" of your dataframes using lapply family:
lapply(yourList, head)
which will return the "heads" of you list.
For example:
df1 <- data.frame(x = runif(3), y = runif(3))
df2 <- data.frame(x = runif(3), y = runif(3))
dfs <- list(df1, df2)
lapply(dfs, head)
Returns:
> lapply(dfs, head)
[[1]]
x y
1 0.3149013 0.8418625
2 0.8807581 0.5048528
3 0.2490966 0.2373453
[[2]]
x y
1 0.4132597 0.5762428
2 0.0303704 0.3399696
3 0.9425158 0.5465939
Instead of "head" you can use any function related to the data.frames, i.e. names, nrow...
Seeing as you explicitly specify that you want to use View() with a list, this is probably what you are looking for:
View(myList[[x]])
Where x is the number of the list element that you wish to view.
For example:
View(myList[[1]])
will show you the first element of the list in the standard View() format that you will be used to in RStudio.
If you know the name of the list item you wish to view, you can do this:
View(myList[["itemOne"]])
There are several other ways, but these will probably serve you best.
This is a simple edit of giraffehere's excellent answer.
For some lists it is convenient to only print the head of a subset of the nested objects, to print the name of the given slot above the output of head().
Arguments:
#'#param list a list object name
#'#param n an integer - the the objects within the list that you wish to print
#'#param hn an integer - the number of rows you wish head to print
USAGE: printList(mylist, n = 5, hn = 3)
printList <- function(list, n = length(list), hn = 6) {
for (item in 1:n) {
cat("\n", names(list[item]), ":\n")
print(head(list[[item]], hn))
}
}
For numeric lists, output may be more readable if the number of digits is limited to 3, eg:
printList <- function(list, n = length(list), hn = 6) {
for (item in 1:n) {
cat("\n", names(list[item]), ":\n")
print(head(list[[item]], hn), digits = 3)
}
}
I had a similar problem and managed to solve it using as_tibble() on my list (dplyr or tibble packages), then just use View() as usual.
In recent versions of RStudio, you can just use View() (or alternatively click on the little blue arrow beside the object in the Global Environment pane).
For example, if we create a list with:
test_list <- list(
iris,
mtcars
)
Then either of the above methods will show you:
I like using as.matrix() on the list and then can use the standard View() command.
I am trying to come up with a variant of mapply (call it xapply for now) that combines the functionality (sort of) of expand.grid and mapply. That is, for a function FUN and a list of arguments L1, L2, L3, ... of unknown length, it should produce a list of length n1*n2*n3 (where ni is the length of list i) which is the result of applying FUN to all combinations of the elements of the list.
If expand.grid worked to generate lists of lists rather than data frames, one might be able to use it, but I have in mind that the lists may be lists of things that won't necessarily fit into a data frame nicely.
This function works OK if there are exactly three lists to expand, but I am curious about a more generic solution. (FLATTEN is unused, but I can imagine that FLATTEN=FALSE would generate nested lists rather than a single list ...)
xapply3 <- function(FUN,L1,L2,L3,FLATTEN=TRUE,MoreArgs=NULL) {
retlist <- list()
count <- 1
for (i in seq_along(L1)) {
for (j in seq_along(L2)) {
for (k in seq_along(L3)) {
retlist[[count]] <- do.call(FUN,c(list(L1[[i]],L2[[j]],L3[[k]]),MoreArgs))
count <- count+1
}
}
}
retlist
}
edit: forgot to return the result. One might be able to solve this by making a list of the indices with combn and going from there ...
I think I have a solution to my own question, but perhaps someone can do better (and I haven't implemented FLATTEN=FALSE ...)
xapply <- function(FUN,...,FLATTEN=TRUE,MoreArgs=NULL) {
L <- list(...)
inds <- do.call(expand.grid,lapply(L,seq_along)) ## Marek's suggestion
retlist <- list()
for (i in 1:nrow(inds)) {
arglist <- mapply(function(x,j) x[[j]],L,as.list(inds[i,]),SIMPLIFY=FALSE)
if (FLATTEN) {
retlist[[i]] <- do.call(FUN,c(arglist,MoreArgs))
}
}
retlist
}
edit: I tried #baptiste's suggestion, but it's not easy (or wasn't for me). The closest I got was
xapply2 <- function(FUN,...,FLATTEN=TRUE,MoreArgs=NULL) {
L <- list(...)
xx <- do.call(expand.grid,L)
f <- function(...) {
do.call(FUN,lapply(list(...),"[[",1))
}
mlply(xx,f)
}
which still doesn't work. expand.grid is indeed more flexible than I thought (although it creates a weird data frame that can't be printed), but enough magic is happening inside mlply that I can't quite make it work.
Here is a test case:
L1 <- list(data.frame(x=1:10,y=1:10),
data.frame(x=runif(10),y=runif(10)),
data.frame(x=rnorm(10),y=rnorm(10)))
L2 <- list(y~1,y~x,y~poly(x,2))
z <- xapply(lm,L2,L1)
xapply(lm,L2,L1)
#ben-bolker, I had a similar desire and think I have a preliminary solution worked out, that I've also tested to work in parallel. The function, which I somewhat confusingly called gmcmapply (g for grid) takes an arbitrarily large named list mvars (that gets expand.grid-ed within the function) and a FUN that utilizes the list names as if they were arguments to the function itself (gmcmapply will update the formals of FUN so that by the time FUN is passed to mcmapply it's arguments reflect the variables that the user would like to iterate over (which would be layers in a nested for loop)). mcmapply then dynamically updates the values of these formals as it cycles over the expanded set of variables in mvars.
I've posted the preliminary code as a gist (reprinted with an example below) and would be curious to get your feedback on it. I'm a grad student, that is self-described as an intermediately-skilled R enthusiast, so this is pushing my R skills for sure. You or other folks in the community may have suggestions that would improve on what I have. I do think even as it stands, I'll be coming to this function quite a bit in the future.
gmcmapply <- function(mvars, FUN, SIMPLIFY = FALSE, mc.cores = 1, ...){
require(parallel)
FUN <- match.fun(FUN)
funArgs <- formals(FUN)[which(names(formals(FUN)) != "...")] # allow for default args to carry over from FUN.
expand.dots <- list(...) # allows for expanded dot args to be passed as formal args to the user specified function
# Implement non-default arg substitutions passed through dots.
if(any(names(funArgs) %in% names(expand.dots))){
dot_overwrite <- names(funArgs[which(names(funArgs) %in% names(expand.dots))])
funArgs[dot_overwrite] <- expand.dots[dot_overwrite]
#for arg naming and matching below.
expand.dots[dot_overwrite] <- NULL
}
## build grid of mvars to loop over, this ensures that each combination of various inputs is evaluated (equivalent to creating a structure of nested for loops)
grid <- expand.grid(mvars,KEEP.OUT.ATTRS = FALSE, stringsAsFactors = FALSE)
# specify formals of the function to be evaluated by merging the grid to mapply over with expanded dot args
argdefs <- rep(list(bquote()), ncol(grid) + length(expand.dots) + length(funArgs) + 1)
names(argdefs) <- c(colnames(grid), names(funArgs), names(expand.dots), "...")
argdefs[which(names(argdefs) %in% names(funArgs))] <- funArgs # replace with proper dot arg inputs.
argdefs[which(names(argdefs) %in% names(expand.dots))] <- expand.dots # replace with proper dot arg inputs.
formals(FUN) <- argdefs
if(SIMPLIFY) {
#standard mapply
do.call(mcmapply, c(FUN, c(unname(grid), mc.cores = mc.cores))) # mc.cores = 1 == mapply
} else{
#standard Map
do.call(mcmapply, c(FUN, c(unname(grid), SIMPLIFY = FALSE, mc.cores = mc.cores)))
}
}
example code below:
# Example 1:
# just make sure variables used in your function appear as the names of mvars
myfunc <- function(...){
return_me <- paste(l3, l1^2 + l2, sep = "_")
return(return_me)
}
mvars <- list(l1 = 1:10,
l2 = 1:5,
l3 = letters[1:3])
### list output (mapply)
lreturns <- gmcmapply(mvars, myfunc)
### concatenated output (Map)
lreturns <- gmcmapply(mvars, myfunc, SIMPLIFY = TRUE)
## N.B. This is equivalent to running:
lreturns <- c()
for(l1 in 1:10){
for(l2 in 1:5){
for(l3 in letters[1:3]){
lreturns <- c(lreturns,myfunc(l1,l2,l3))
}
}
}
### concatenated outout run on 2 cores.
lreturns <- gmcmapply(mvars, myfunc, SIMPLIFY = TRUE, mc.cores = 2)
Example 2. Pass non-default args to FUN.
## Since the apply functions dont accept full calls as inputs (calls are internal), user can pass arguments to FUN through dots, which can overwrite a default option for FUN.
# e.g. apply(x,1,FUN) works and apply(x,1,FUN(arg_to_change= not_default)) does not, the correct way to specify non-default/additional args to FUN is:
# gmcmapply(mvars, FUN, arg_to_change = not_default)
## update myfunc to have a default argument
myfunc <- function(rep_letters = 3, ...){
return_me <- paste(rep(l3, rep_letters), l1^2 + l2, sep = "_")
return(return_me)
}
lreturns <- gmcmapply(mvars, myfunc, rep_letters = 1)
A bit of additional functionality I would like to add but am still trying to work out is
cleaning up the output to be a pretty nested list with the names of mvars (normally, I'd create multiple lists within a nested for loop and tag lower-level lists onto higher level lists all the way up until all layers of the gigantic nested loop were done). I think using some abstracted variant of the solution provided here will work, but I haven't figured out how to make the solution flexible to the number of columns in the expand.grid-ed data.frame.
I would like an option to log the outputs of the child processesthat get called in mcmapply in a user-specified directory. So you could look at .txt outputs from every combination of variables generated by expand.grid (i.e. if the user prints model summaries or status messages as a part of FUN as I often do). I think a feasible solution is to use the substitute() and body() functions, described here to edit FUN to open a sink() at the beginning of FUN and close it at the end if the user specifies a directory to write to. Right now, I just program it right into FUN itself, but later it would be nice to just pass gmcmapply an argument called something like log_children = "path_to_log_dir. and then editing the body of the function to (pseudocode) sink(file = file.path(log_children, paste0(paste(names(mvars), sep = "_"), ".txt")
Let me know what you think!
-Nate