Custom tab completion in R function - r

I am currently writing a function which only accepts certain inputs (in the example only "a" and "b"). For all other inputs the function will return an error.
test <- function(x) {
allowedX <- c("a","b")
if(x %in% allowedX) print("Good choice!")
else stop("wrong input!")
}
To help users of the function I would like to supply the allowed values for x (stored in allowedX) using the tab completion feature in R and replace the default file name completion which is typically applied after a quote. So pressing TAB should give something like:
test(x="<TAB>
a b
However, I couldn't find a solution so far how to map the vector allowedX to the tab completion in R. Can somebody tell me how to do that?
Thanks in advance!

You could try something like the following:
test <- function() {
allowedX <- c("a","b")
x = readline('Please enter your choice of parameters (either "a" or "b"): ')
if(x %in% allowedX) print("Good choice!")
else stop("wrong input!")
}

Related

IF statements inside function do not recognize conditions

I want to adjust my function so that my if and else if statements recognize the name of the dataframe used and execute the correct plotting function. These are some mock data structured the same as mine:
df1<-data.frame(A=c(1,2,2,3,4,5,1,1,2,3),
B=c(4,4,2,3,4,2,1,5,2,2),
C=c(3,3,3,3,4,2,5,1,2,3),
D=c(1,2,5,5,5,4,5,5,2,3),
E=c(1,4,2,3,4,2,5,1,2,3),
dummy1=c("yes","yes","no","no","no","no","yes","no","yes","yes"),
dummy2=c("high","low","low","low","high","high","high","low","low","high"))
df1[colnames(df1)] <- lapply(df1[colnames(df1)], factor)
vals <- colnames(df1)[1:5]
dummies <- colnames(df1)[-(1:5)]
step1 <- lapply(dummies, function(x) df1[, c(vals, x)])
step2 <- lapply(step1, function(x) split(x, x[, 6]))
names(step2) <- dummies
tbls <- unlist(step2, recursive=FALSE)
tbls<-lapply(tbls, function(x) x[(names(x) %in% names(df1[c(1:5)]))])
A<-lapply(tbls,"[", c(1,2))
B<-lapply(tbls,"[", c(3,4))
C<-lapply(tbls,"[", c(3,4))
list<-list(A,B,C)
names(list)<-c("A","B","C")
And this is my function:
plot_1<-function (section, subsample) {
data<-list[grep(section, names(list))]
data<-data[[1]]
name=as.character(names(data))
if(section=="A" && subsample=="None"){plot_likert_general_section(df1[c(1:2)],"A")}
else if (section==name && subsample=="dummy1"){plot_likert(data$dummy1.yes, title=paste("How do the",name,"topics rank?"));plot_likert(data$Ldummy1.no, title = paste("How do the",name,"topics rank?"))}
}
Basically what I want it to do is plot a certain graph by specifying section and subsample I'm interested in if, for example, I want to plot section C and subsample dummy.1, I just write:
plot_1(section="C", subsample="dummy1)
I want to avoid writing this:
else if (section=="A" && subsample=="dummy1"){plot_likert(data$dummy1.yes, title=paste("How do the A topics rank?"));plot_likert(data$Ldummy1.no, title = paste("How do the A topics rank?"))}
else if (section=="B" && subsample=="dummy1"){plot_likert(data$dummy1.yes, title=paste("How do the B topics rank?"));plot_likert(data$Ldummy1.no, title = paste("How do the B topics rank?"))}
else if (section=="C" && subsample=="dummy1"){plot_likert(data$dummy1.yes, title=paste("How do the c topics rank?"));plot_likert(data$Ldummy1.no, title = paste("How do the C topics rank?"))}
else if (section=="C" && subsample=="dummy2")...
.
.
}
So I tried to extract the dataframe used from the list so that it matches the string of the section typed in the function (data<-list[grep(section, names(list))]) and store its name as a character (name=as.character(names(data))), because I thought that in this way the function would have recognized the string "A", "B" or "C" by itself, without the need for me to specify each condition.
However, if I run it, I get this error: Warning message: In section == name && subsample == "dummy1" : 'length(x) = 4 > 1' in coercion to 'logical(1)', that, from what I understand, is due to the presence of a vector in the statement. But I have no idea how to correct for this (I'm still quite new to R).
How can I fix the function so that it does what I want? Thanks in advance!
Well, I can't really test your code without the plot_likert_general_section function or the plot_likert function, but I've done a bit of simplifying and best practices--passing list in as an argument, consistent spaces and assignment operators, etc.--and this is my best guess as to what you want:
plot_1 = function(list, section, subsample) { ## added `list` as an argument
data = list[[grep(section, names(list))]] # use [[ to extract a single item
name = as.character(names(data))
if(subsample == "None"){
plot_likert_general_section(df1[c(1:2)], section)
} else {
yesno = paste(subsample, c("yes", "no"), sep = ".")
plot_likert(data[[yesno[1]]], title = paste("How do the", name, "topics rank?"))
plot_likert(data[[yesno[2]]], title = paste("How do the", name, "topics rank?"))
}
}
plot_1(list, section = "C", subsample = "dummy1)
I'm not sure if your plot_likert functions use base or grid graphics--but either way you'll need to handle the multiple plots. With base, probably use mfrow() to display both of them, if grid I'd suggest putting them in a list to return them both, and then maybe using gridExtra::grid.arrange() (or similar) to plot both of them.
You're right that the error is due to passing a vector where a single value is expected. Try inserting print statements before the equality test to diagnose why this is.
Also, be careful with choosing variable names like name which are baseR functions (e.g. ?name). I'd also recommend following the tidyverse style guide here: https://style.tidyverse.org/.

How to include a not-yet indexed parameter into if-else

I have the following function:
foo <- function(...){
dots <- list(...)
response <- dots[[1]]
if(is(dots[[2]],'list') == TRUE){print('yes')} else print('no')
}
This produces the following output:
foo('yes'):
Error in dots[[2]] : subscript out of bounds
How can I use a 'not-yet' indexed parameter so that I can stall the function when it's TRUE or when its FALSE. For example, when it's TRUE I would do some stuff based on this, otherwise when it is FALSE the part of the function that uses it won't run.
However, R want's me to at-least index dots with some list values.
For example, If I wanted to use just:
foo('yes')
>Error in dots[[2]] : subscript out of bounds
#otherwise
foo('yes',c('some','list'))
>'yes'
I want to be able to run foo('yes') and for it to print no. Essentially, some parameters won't get used in the function, and so in this case when it's not assigned anything then run the else statement.
Picking up on #Rui Barradas and #Allan Camerons comments, I can achieve the same expectation with function(pred=NULL,...) by using:
foo <- function(...){
dots <- list(...)
response <- dots[[1]]
print(response)
if(length(dots) > 1){
if(is(dots[[2]],'list') == TRUE){
print('yes')
} else print('no')
} else if (length(dots) == 1){
dots[[2]] = NULL
}
}
Results:
> foo('yes',list(1, 2, 3))
[1] "yes"
> foo('yes')
[1] "yes"
Are there any cleaner alternatives to this that reduce the amount of code? My approach produces quite some clutter. The only issue I have with this is that If I wanted dots[[3]], I would have to implement further conditionals to access this or set it to NULL.

Updating a nested list object in the global environment from within a function in R

Here are the set of circumstances that have gotten me stuck:
The Problem
I have written a function in R that will need to execute within a for loop with the goal of manually adjusting some values and then updating a large nested list in the global environment. I have 2 functions more.points() and get.num.pts() that ask for user input. These are part of a larger function add.points() which runs everything and will be wrapped in a for loop. Unfortunately, I cannot figure out how to update the nested list to the correct place in the list's hierarchy from within the function. I must do it from within the function in order to make sure I dont run lines of code in the for loop after the function because this will cause readlines() to fail and take the next line of code as user input. How do I update the hierarchical list object in the correct place from within the add.points() function? assign() does not appear to be up to the task, at least to my limited knowledge. Any help is greatly appreciated. I am making a pipeline for aligning an atlas to brain images so I can localize cells that fluoresce to their respective brain regions.
more.points <- function(){
more.pts <- readline(prompt = "Do you need to add correspondence points to adjust the atlas registration? (y/n): ")
}
get.num.pts <- function(){
num.pts <- readline(prompt = "How many additional points are required? (You will be able to add additional points later if you need to): ")
}
add.points <- function(){
mo.pts <- as.character(more.points());
if(mo.pts == "y" || mo.pts == "Y" || mo.pts == "Yes" || mo.pts == "yes"){
while(mo.pts == "y" || mo.pts == "Y" || mo.pts == "Yes" || mo.pts == "yes") {
#ask for user input about number of new points to be created
n.pts <- as.integer(get.num.pts());
reg.fun.obj <- paste0(n.pts," updated!");
print(reg.fun.obj)
#do other stuff
#assign totally works here just fine because it isnt a hierarchical list being updated
assign("reg.obj", reg.fun.obj, envir = .GlobalEnv);
#Need to update the correct position in the list object hierarchy with new info.
assign(i.data[[reference.df$i[[i]]]][[reference.df$j[[i]]]][[reference.df$k[[i]]]], reg.obj, envir = .GlobalEnv);
#But this cannot take `i.data[[reference.df$i[[i]]]][[reference.df$j[[i]]]][[reference.df$k[[i]]]]` for the name argument. it must be a string.
mo.pts = as.character(more.points())
}
}
}
Reproducible example:
Here is an example of the global environment hierarchical list I need to update from an object within the add.points() function:
#Hierarchical List Object Example
#The image objects have more complexity in my real implementation i.e. image_1 is itself a list object with multiple attributes.
list.i <- c("channel1", "channel2", "channel3")
list.j <- c("m1", "m2", "m3")
list.k <- c("image_1", "image_2", "image_3")
k.tmp <- list()
j.tmp <- list()
i.data <- list()
for(i in seq_along(list.i)){
for(j in seq_along(list.j)){
for(k in seq_along(list.k)){
k.tmp[[k]] <- list.k[[k]]
names(k.tmp)[[k]] <- paste0("img", k)
}
j.tmp[[j]] <- k.tmp
names(j.tmp)[[j]] <- paste0("m", j)
k.tmp <- list()
}
i.data[[i]] <- j.tmp
names(i.data)[[i]] <- paste0("channel", i)
j.tmp <- list
}
remove(k.tmp,j.tmp)
#Additional example list I am using to know which elements of the hierarchy need to be updated/adjusted as the for loop cycles.
reference.df <- data.frame(i = c(rep(1, 9), rep(2, 9), rep(3, 9)), j = c(rep(c(1, 1, 1, 2, 2, 2, 3, 3, 3),3)), k = c(rep(c(1, 2, 3),9)))
Code to run function:
reg.obj <- i.data[[reference.df$i[[i]]]][[reference.df$j[[i]]]][[reference.df$k[[i]]]]
for(i in seq_along(reference.df$k)){
add.points()
}
Remember: I am unable to run anything after the function within the for loop because R will interpret the next line as the user input being fed to readlines(). Thus, the whole point of this loop and function - getting user input, saving, and cycling to the next image for the user to provide input on - will not occur.
For anyone else who runs into an issue like this. Don't be stupid like me. Use the return() function within your function to convert your variable into an output that you can feed into your nested list thusly:
in the function:
myfun(){
#do stuff to make object containing update
return(update.obj)
}
#run the function:
list[[x]][[y]][[z]] <- myfun()
#is equivalent to below occurring outside a function:
list[[x]][[y]][[z]] <- update.obj
Yes this was stupid but hopefully I helped someone avoid my fundamental mistake here. If you can avoid it, don't use assign() in a function.

Overwrite variable inside function

I'm sure this question may have been asked already, but I couldnt find an answer to my satisfaction.
So my Problem I defined a function (See below) which should take a Variable (x) and check if its part of a dataframe (y). The function should than ask for a promt until it is part of said dataframe.
However when I let it run it wont overwrite the variable inside the function so that the global enviroment variable gets also changed.
Thus var1 should store the value I gave through the prompt inside the function.
Thx :)
#Function
fn_Valid_prompt <- function(x, y, boolOP= FALSE){
while(is.element(x, colnames(y)) == boolOP){
cat("A")
x <<- readline(prompt="Please enter variable: ")
}
if (is.element(x, colnames(y)) != boolOP){
cat(green(bold("Success!")))}
}
#
var1 <- "V1"
data <- c(1:9)
metadata <- as.data.frame(matrix(data,3,3))
fn_Valid_prompt(var1, metadata, boolOP= FALSE)
The following version works, although i'm not sure of your intent with this code :
#Function
fn_Valid_prompt <- function(x, y, boolOP= FALSE){
while(is.element(x, colnames(y)) == boolOP){
x <- readline(prompt="Please enter variable: ")
}
if (is.element(x, colnames(y)) != boolOP){
cat("Success!")}
return(x)
}
#
var1 <- "V1"
data <- c(1:9)
metadata <- as.data.frame(matrix(data,3,3))
result = fn_Valid_prompt("V10", metadata, boolOP= FALSE)
cat(result)
Your mistake was to use <<- instead of <-. Furthermore, i assume you wanted to return the result ?

R - creating variable from reading line

I would like to create a variable XO from user's answer on a quick question. And I also would like system to write, what user has selected. The code looks like this:
fun1 <- function() {
XO <- readline(prompt = "Do you want X, or O? ")
if (substr(XO, 1, 1) == "X")
cat("You have chosen X.\n") & XO = "X"
else
cat("You have chosen O.\n") & XO = "O"
}
The function fun1 is created properly, but after answering the question (my answer is e.g. "X"), system shows error:
Error in cat("You have chosen X.\n") & XO = "X" :
target of assignment expands to non-language object
And XO is not created.
Please, could you help me, what am I doing wrong? Thanks in advance.
In R, & is just used in logical assignments, not for joining sentences.
What you wanna do is to put that piece of code in a chunks inside curly brackets {} and split them in different lines. If the condition is true R will run the hole chunk inside the curly brackets.
fun1 <- function() {
XO <- readline(prompt = "Do you want X, or O? ")
if (substr(XO, 1, 1) == "X") {
cat("You have chosen X.\n")
XO <<- "X"
} else {
cat("You have chosen O.\n")
XO <<- "O"
}
}
You're using = to assign the XO variable inside the fun1 function. Take a look at this question to be sure that's what you want. If you want it to be available also in the global environment, use <<- instead.

Resources