I have the following three parameters:
a <- c("brown", "red", "purple", "yellow", "blue", "green")
b <- c("unknown", "medium", "low", "high")
c <- c("group1", "group2")
I want to loop through each combination of parameters in order to obtain a list of names of those very parameters. The result should look like the following:
$plot_group1_unknown_brown
[1] "plot_group1_unknown_brown"
$plot_group2_unknown_brown
[1] "plot_group2_unknown_brown"
$plot_group1_medium_brown
[1] "plot_group1_medium_brown"
$plot_group2_medium_brown
[1] "plot_group2_medium_brown"
$plot_group1_low_brown
[1] "plot_group1_low_brown"
.
.
.
Now I want achieve this using any of the apply family of functions or really any approach other than a for loop. I can accomplish this with a for loop like so:
for (x in a) {
for (a in b) {
for (i in c) {
plot_name <- paste( 'plot', i, a, x, sep = '_' )
plot_list[[plot_name]] <- plot_name
}
}
}
I would also like to note that this is just an example. In my actual use case, plot_name (on the RHS) would be a function that takes the same parameters as the ones used inside the paste() function above it. Something along the lines of plot_list[[plot_name]] <- some_plotting_function(x, i, a) and the result would be a list of ggplot2 objects.
We can use expand.grid on the vectors and paste
v1 <- paste0('plot_', do.call(paste, c(expand.grid(c, b, a), sep="_")))
lst1 <- setNames(as.list(v1), v1)
head(lst1)
#$plot_group1_unknown_brown
#[1] "plot_group1_unknown_brown"
#$plot_group2_unknown_brown
#[1] "plot_group2_unknown_brown"
#$plot_group1_medium_brown
#[1] "plot_group1_medium_brown"
#$plot_group2_medium_brown
#[1] "plot_group2_medium_brown"
#$plot_group1_low_brown
#[1] "plot_group1_low_brown"
#$plot_group2_low_brown
#[1] "plot_group2_low_brown"
You could use expand.grid to get all possible combinations and then use apply row-wise to pass the parameters to the function.
complete_list <- expand.grid(c, b, a)
#Can also use cross_df from purrr
#complete_list <- purrr::cross_df(list(c = c, b = b, a = a))
apply(complete_list, 1, function(x) some_plotting_function(x[1], x[2], x[3]))
Related
Given the following code:
a <- 3
colors <- ifelse(a == 3,
c("#004B93", "#389DC3", "#6DBE99"),
ifelse(a == 2, c("#004B93", "#389DC3"), c("#000000", "#000000", "#000000")))
My expectation ist to get something like
> colors
[1] "#004B93" "#389DC3" "#6DBE99"
But what I get is
> colors
[1] "#004B93"
What am I doing wrong?
You can also use if else statements for check the conditions inside R.
For that you can do the same logic as I checked for you.
a <- 3
colors <- if(a == 3) {
c("#004B93", "#389DC3", "#6DBE99")
} else if (a == 2) {
c("#004B93", "#389DC3")
} else {
c("#000000", "#000000", "#000000")
}
print(colors)
Output Result :
[1] "#004B93" "#389DC3" "#6DBE99"
Here's how I would write this:
possible_colors = list(
c("#000000", "#000000", "#000000"),
c("#004B93", "#389DC3"),
c("#004B93", "#389DC3", "#6DBE99")
)
colors = if (a < 1L || a > 3L) list[[1L]] else list[[a]]
This assumes that a only has integer values. Adjust the condition of the if accordingly if that is not the case.
If you know that a will never be any value except 1, 2 or 3, you can omit the if completely and directly write colors = possible_colors[[a]].
ifelse is a command that is usually better to avoid.
Why? Because
ifelse(TRUE, 1:3, 4:6)
returns 1 and not 1:3.
In fact, ifelse wants the output to be the same length as the test. Above TRUE is a vector of length 1, while the candidate output1:3 is of length 3. However, ifelse does not raise any warning, it just cuts the vector 1:3.
There is rationale behind this, and this example should explain it:
ifelse(c(TRUE, FALSE, TRUE), yes = 1:3, no = 4:6)
[1] 1 5 3
True values are taken from the yes argument and false values are
taken from the no argument. If you really want this sophisticate behaviour, ifelse can be extremely powerful, but this is rarely what you want.
The good news is that R is a functional language, thus everything is a function, including the if/else construct. Hence, to get the intended result from
ret <- ifelse(TRUE, 1:3, 4:6)
rewrite it like so:
ret <- if(TRUE) 1:3 else 4:6
and ret is 1:3
Do you need nested conditions? No problem.
a <- 1
ret <- if(a == 1) 1:3 else (if(a == 2) 4:6 else 7:9)
The line above emphasises the functional approach, where I replace the expression 4:6 with the bracketed one, but it can be shortened to:
ret <- if(a == 1) 1:3 else if(a == 2) 4:6 else 7:9
However, let me note that for this type of tasks, there is a dedicated R function: switch.
ret <- switch(a, 1:3, 4:6, 7:9)
Counting after a, the first, second, etc. argument is chosen depending on the value of a, thus:
a <- 2
switch(a, 1:3, 4:6, 7:9)
# 4:6
As for your case:
a <- 3
switch(a,
c("#000000", "#000000", "#000000"),
c("#004B93", "#389DC3"),
c("#004B93", "#389DC3", "#6DBE99"))
# [1] "#004B93" "#389DC3" "#6DBE99"
Continuing on my quest to work with functions and ggplot:
I sorted out basic ways on how to use lapply and ggplot to cycle through a list of y_columns to make some individual plots:
require(ggplot2)
# using lapply with ggplot
df <- data.frame(x=c("a", "b", "c"), col1=c(1, 2, 3), col2=c(3, 2, 1), col3=c(4, 2, 3))
cols <- colnames(df[2:4])
myplots <- vector('list', 3)
plot_function <- function(y_column, data) {
ggplot(data, aes_string(x="x", y=y_column, fill = "x")) +
geom_col() +
labs(title=paste("lapply:", y_column))
}
myplots <- lapply(cols, plot_function, df)
myplots[[3]])
I know what to bring in a second variable that I will use to select rows. In my minimal example I am skipping the selection and just reusing the same plots and dfs as before, I simply add 3 iterations. So I would like to generate the same three plots as above, but now labelled as iteration A, B, and C.
I took me a while to sort out the syntax, but I now get that mapply needs to vectors of identical length that get passed on to the function as matched pairs. So I am using expand.grid to generate all pairs of variable 1 and variable 2 to create a dataframe and then pass the first and second column on via mapply. The next problem to sort out was that I need to pass on the dataframe as list MoreArgs =. So it seems like everything should be good to go. I am using the same syntax for aes_string() as above in my lapply example.
However, for some reason now it is not evaluating the y_column properly, but simply taking it as a value to plot, not as an indicator to plate the values contained in df$col1.
HELP!
require(ggplot2)
# using mapply with ggplot
df <- data.frame(x=c("a", "b", "c"), col1=c(1, 2, 3), col2=c(3, 2, 1), col3=c(4, 2, 3))
cols <- colnames(df[2:4])
iteration <- c("Iteration A", "Iteration B", "Iteration C")
multi_plot_function <- function(y_column, iteration, data) {
plot <- ggplot(data, aes_string(x="x", y=y_column, fill = "x")) +
geom_col() +
labs(title=paste("mapply:", y_column, "___", iteration))
}
# mapply call
combo <- expand.grid(cols=cols, iteration=iteration)
myplots <- mapply(multi_plot_function, combo[[1]], combo[[2]], MoreArgs = list(df), SIMPLIFY = F)
myplots[[3]]
We may need to use rowwise here
out <- lapply(asplit(combo, 1), function(x)
multi_plot_function(x[1], x[2], df))
In the OP's code, the only issue is that the columns are factor for 'combo', so it is not parsed correctly. If we change it to character, it works
out2 <- mapply(multi_plot_function, as.character(combo[[1]]),
as.character(combo[[2]]), MoreArgs = list(df), SIMPLIFY = FALSE)
-testing
out2[[1]]
I would like to use a function from the apply family (in R) to apply a function of two arguments to two matrices. I assume this is possible. Am I correct? Otherwise, it would seem that I have to put the two matrices into one, and redefine my function in terms of the new matrix.
Here's an example of what I'd like to do:
a <- matrix(1:6,nrow = 3,ncol = 2)
b <- matrix(7:12,nrow = 3,ncol = 2)
foo <- function(vec1,vec2){
d <- sample(vec1,1)
f <- sample(vec2,1)
result <- c(d,f)
return(result)
}
I would like to apply foo to a and b.
(Strictly answering the question, not pointing you to a better approach for you particular use here....)
mapply is the function from the *apply family of functions for applying a function while looping through multiple arguments.
So what you want to do here is turn each of your matrices into a list of vectors that hold its rows or columns (you did not specify). There are many ways to do that, I like to use the following function:
split.array.along <- function(X, MARGIN) {
require(abind)
lapply(seq_len(dim(X)[MARGIN]), asub, x = X, dims = MARGIN)
}
Then all you have to do is run:
mapply(foo, split.array.along(a, 1),
split.array.along(b, 1))
Like sapply, mapply tries to put your output into an array if possible. If instead you prefer the output to be a list, add SIMPLIFY = FALSE to the mapply call, or equivalently, use the Map function:
Map(foo, split.array.along(a, 1),
split.array.along(b, 1))
You could adjust foo to take one argument (a single matrix), and use apply in the function body.
Then you can use lapply on foo to sample from each column of each matrix.
> a <- matrix(1:6,nrow = 3,ncol = 2)
> b <- matrix(7:12,nrow = 3,ncol = 2)
> foo <- function(x){
apply(x, 2, function(z) sample(z, 1))
}
> lapply(list(a, b), foo)
## [[1]]
## [1] 1 6
## [[2]]
## [1] 8 12
I have a list containing 3 vectors.
mylist <- list( a = c(1,2),
b = c(3,4),
c = c(5,6) )
Is there any simple way to, for instance, perform computations on the first values of the three objects with the sum() function?
I tried many things like:
sum(mylist[c(a, b, c)][1])
This line of code does not work, but it gives insight into what I am trying to do.
Thanks for your help.
use sapply
> sum(sapply(mylist, "[", 1))
[1] 9
Bonus fun fact: You can use c( ) inside of [[ ]]:
sum( sapply(seq(mylist), function(i) mylist[[ c(i, 1) ]]) )
Not very efficient solution:
sum(unlist(lapply(mylist,'[',1)))
[1] 9
From a dataframe I get a new array, sliced from a dataframe.
I want to get the amount of times a certain repetition appears on it.
For example
main <- c(A,B,C,A,B,V,A,B,C,D,E)
p <- c(A,B,C)
q <- c(A,B)
someFunction(main,p)
2
someFunction(main,q)
3
I've been messing around with rle but it counts every subrepetion also, undersirable.
Is there a quick solution I'm missing?
You can use one of the regular expression tools in R since this is really a pattern matching exercise, specifically gregexpr for this question. The p and q vectors represent the search pattern and main is where we want to search for those patterns. From the help page for gregexpr:
gregexpr returns a list of the same length as text each element of which is of
the same form as the return value for regexpr, except that the starting positions
of every (disjoint) match are given.
So we can take the length of the first list returned by gregexpr which gives the starting positions of the matches. We'll first collapse the vectors and then do the searching:
someFunction <- function(haystack, needle) {
haystack <- paste(haystack, collapse = "")
needle <- paste(needle, collapse = "")
out <- gregexpr(needle, haystack)
out.length <- length(out[[1]])
return(out.length)
}
> someFunction(main, p)
[1] 2
> someFunction(main, q)
[1] 3
Note - you also need to throw "" around your vector main, p, and q vectors unless you have variables A, B, C, et al defined.
main <- c("A","B","C","A","B","V","A","B","C","D","E")
p <- c("A","B","C")
q <- c("A","B")
I'm not sure if this is the best way, but you can simply do that work by:
f <- function(a,b)
if (length(a) > length(b)) 0
else all(head(b, length(a)) == a) + Recall(a, tail(b, -1))
Someone may or may not find a built-in function.
Using sapply:
find_x_in_y <- function(x, y){
sum(sapply(
seq_len(length(y)-length(x)),
function(i)as.numeric(all(y[i:(i+length(x)-1)]==x))
))
}
find_x_in_y(c("A", "B", "C"), main)
[1] 2
find_x_in_y(c("A", "B"), main)
[1] 3
Here's a way to do it using embed(v,n), which returns a matrix of all n-length sub-sequences of vector v:
find_x_in_y <- function(x, y)
sum( apply( embed( y, length(x)), 1,
identical, rev(x)))
> find_x_in_y(p, main)
[1] 2
> find_x_in_y(q, main)
[1] 3