Convert a character string with an index to an object reference - r

I am parsing the left-hand side of an R formula. In my specific case, this can be a variable or object with an index (something like myvariable[[3]]). I would like to access the third sub-object of this object and store it in another object. The following example starts at the point where I have the string of the indexed object, but I need the reference.
mychars <- c("a", "b", "c")
mystring <- "mychars[2]"
get(mystring) # does not work
eval(as.name(mystring)) # does not work either
I could of course parse the number using regular expressions and use as.numeric to convert it to a real index. But in some cases, there may be named indices, like mystring["second"]. So how can I extract the sub-object?

You can parse and then eval this expression.
mychars <- c("a", "b", "c")
mystring <- "mychars[2]"
eval(parse(text = mystring))
[1] "b"
It works for named indices too
names(mychars) <- c("first", "second", "third")
eval(parse(text = 'mychars["second"]'))
second
"b"

Related

converting names in a call to strings in r, c(a,b) -> c("a","b")

I want a function taking an unevaluated expression containing variable names into a new expression containing string for the variable names.
c(a,b)
should be converted to
c("a","b")
How can this be done?
Let me explain in more detail what I want to achieve.
my_tibble <-
tibble(a=rep(c("a","b"),each=4),
b=rep(rep(c("x","y"),each=2),2),
x=seq_along(b)
)
summary_groupwise <- function(data,group_var,var_to_summarise,result_var){
data |>
group_by({{group_var}}) |>
summarise({{result_var}} := sum({{var_to_summarise}}))
}
This function works
summary_groupwise(my_tibble,a,x,group_sum)
Now I want to group by multiple variables
summary_groupwise_multiple <- function(data,group_vars,var_to_summarise,result_var){
data |>
group_by(across(all_of({{group_vars}}))) |>
summarise({{result_var}} := sum({{var_to_summarise}}))
}
This works:
summary_groupwise_multiple(my_tibble,c("a","b"),x,group_sums)
To make summary_groupwise_multiple work, the names of the grouping variables have to be given as strings.
For consistency of the syntax, I would like the following call to work
summary_groupwise_multiple(my_tibble,c(a,b),x,group_sums)
How can is fiddle with the parameter group_vars so the call works without giving the names of the variables as strings.
If the expressions are passed as a vector i.e. with c(), an option is
f1 <- function(expr) {dput(sapply(as.list(substitute(expr))[-1], deparse))}
> f1(c(a, b))
c("a", "b")
fun <- function(x)dput(as.character(substitute(x)[-1]))
fun(c(a, b, c))
c("a", "b", "c")

How can I pass a line of code in textInput in R shiny?

I want to pass a line of code in textInput function, like suppose I write list(a = c("b", "c")) in textInput. But textInput would take it as a character string.
Is there any way to do it?
You can use eval and parse from base R. Please note that it can be dangerous to evaluate user defined code, so think twice.
string <- 'list(a = c("b", "c"))'
eval(parse(text = string))
#$a
#[1] "b" "c"

Using a list's assigned name from a character string in a vector

I have some lists:
my_list1 <- list("data" = list(c("a", "b", "c")), "meta" = list(c("a", "b")))
my_list2 <- list("data" = list(c("x", "y", "z")), "meta" = list(c("x", "y")))
I'd like to be able to perform some operations on these lists but I need to use the names of the lists stored in a vector as I'm creating them dynamically from an API call. Such a vector might be:
list_vec <- c("my_list1", "my_list2")
I'm running into problems evaluating the character string in the vector into the name of the list. I know this topic's been covered but the part I'm stuck on specifically is being able to extract just the data sublist when running functions within assign. Essentially a situation like this:
library(purrr)
for(i in seq_along(1:length(list_vec))){
assign(list_vec[[i]], map_df(list_vec[[i]][["data"]], unlist))
}
Which would give a result of:
# A tibble: 3 x 1
data
<chr>
1 a
2 b
3 c
I could also do something like:
my_list1$meta <- NULL
with
list_vec[[1]][["meta"]] <- NULL
To reduce the list to just the data sublist, but I can't within dynamically assigned names.
I've also wrapping things with eval but can't get that to work.
So specifically I need to evaluate the list's name from a string so I can extract a sublist from it.
We can pass the vector list_vec to mget, which returns a nested list. We use lapply to extract ([[) the data element and use unlist to convert this nested list to a list.
unlist(lapply(mget(list_vec), `[[`, "data"), recursive = FALSE)
Result
#$my_list1
#[1] "a" "b" "c"
#$my_list2
#[1] "x" "y" "z"

Convert a replicated list to named list in r

I create named lists manually such as:
FD_OesophagitisIntro<-list(x="LA Grade A",
x="LA Grade B",
x="LA Grade C",
x="LA Grade D")
but this is repetitive so a neater version is
FD_OesophagitisIntro<-list(unique(append(FD_OesophagitisIntro,replicate(4,paste("LA Grade ",sample(c("A","B","C","D"),replace=F))))))
however this creates a non named list. How can I create the list above with the neater code
If your question is how do you use replicate to create a named list, you can only do that if the expression has a name.
replicate is a wrapper to sapply with the expression evaluated as an anonymous function like this:
sapply(integer(4), function(...){
paste("LA Grade ", sample(c("A", "B", "C", "D"), replace = F)
})
There is no ... argument for replicate, but thankfully, sapply's USE.NAMES argument is set to TRUE by default. So to get names from this, you need to either have X be "character" (it isn't, it's "integer"), or have the return value of expr have names. It isn't. It's the return value of a call to paste(), which calls as.character() on all it's arguments, so removes attributes, including names. You can see this in the following example:
paste(c(a = "x", b = "x"), c(a = "y", b = "y"))
[1] "x y" "x y"
This means your solution will involve separating the call to replicate out, THEN assigning names to the object it returns. Sadly, it then becomes a fake one-liner with curly braces, or not a one liner at all.
You're also going to have to pass the product of replicate to append as a list, so that its names are retained, and not use unique either (since it strips names).
Here's an example:
repd <- replicate(4, paste("LA Grade ", sample(c("A", "B", "C", "D"), replace = FALSE)))
names(repd) <- rep("x", length(repd))
long <- append(FD_OesophagitisIntro, as.list(repd))
FD_OesophagitisIntro <- long[!duplicated(long)]
names(FD_OesophagitisIntro)
# [1] "x" "x" "x" "x" "x" "x" "x" "x"

Manipulating the quotes on strings when coding in R

This is actually a series of questions about the referencing character type of values in R. Would add more bullets when I recalled any other related questions I believe which is interesting and related to this topic. For simplification, here I shall use some simple random examples to explain my questions. Hope this helps:
When building up a set of datasets using for loops and wanted to output a series of vectors with names restored in a list called name_list = ("a", "b", "c", "d", "e", "f") in the loop we would like to define as
for(i in 1:4){
a <- data[data$Year == 2010,]
b <- unique(data$Name)
c <- summarise(group_by(data,Year,Name), avg = mean(quantity))
...
f <- left_join(data,data1, by = c("Year", "Names)
}
Is there any function that allows me to use function(name_list[1]) through function(name_list[6]) to replace the a through f in the for loop? This question also goes for trying to create columns using column names in some tables/data frames embedded a chunk of code. (as.name and noquote function work when just referencing the vector/dataset but don't work when attempting to assign values to the target variable, if possible could anyone share why this happens?)
When we extract some information from SQL or other data sources we might have some information separated by comma or some other delimiters as one variable. How could we test if certain values is among one of the values separated by commas? See the example below:
1567 %in% c(1567,1456,123)
TRUE
a <- "c(1567,1456,123)"
noquote(a)
c(1567,1456,123)
1567 %in% noquote(a)
FALSE
1567 %in% list(noquote(a))
FALSE
b <- "1567,1456,123"
noquote(b)
1567,1456,123
1567 %in% noquote(strsplit(a,","))
FALSE
1567 %in% list(noquote(strsplit(a,",")))
FALSE
I kind of get why the %in% here doesn't work, seems like R is taking 1567,1456,123 as one element. So I used the strsplit to separate them. But seems that it's still not working. Wondering is there any way that allows us to get R taking the string as commands?
If all you need to do is convert comma-separated lists like "1567,1456,123" into R vectors like c(1567, 1456, 123), you definitely do not need to wrap them in c(...) and try to evaluate them directly as vectors. You should just use strsplit to split the data:
data_str <- "1567,1456,123"
data_vec <- as.integer(strsplit(string_data, ","))
stopifnot(1567 %in% data_vec)
Note that strsplit returns a list, because it can also character vectors of length greater than one:
stopifnot(
all.equal(
list(c("a", "b"), c("x", "y")),
strsplit(c("a,b", "x,y"), ",")) == TRUE)
which makes it useful for operating on columns of SQL output:
| id | concatenated_field |
|----|--------------------|
| 1 | 5362,395,9000,7 |
| 2 | 319,75624,63 |
(etc.)
d <- data.frame(
id = c(1, 2),
concatenated_field = c("5362,395,9000,7", "319,75624,63"))
d$split_field <- strsplit(d$concatenated_field, ",")
sapply(d, class)
# id concatenated_field split_field
# "numeric" "character" "list"
d$split_field[[1]]
# [1] "5362" "395" "9000" "7"
Alternatively, if you're reading in one big stream of comma-separated data, you can use scan:
data_vec <- scan(
what = 0, # arcane way to say "expect numeric input"
sep = ",",
text = "1,2,3,4,5,6,7,8,9,10")
stopifnot(all.equal(data_vec, 1:10) == TRUE)
scan is more heavy-duty than strsplit and can handle more complicated inputs as well, such as data with quoted fields:
weird_data <- scan(what="", sep=",", text='marvin,ruby,"joe,joseph",dean')
print(weird_data)
# [1] "marvin" "ruby" "joe,joseph" "dean"
If you are really really sure you need to be able to accept and evaluate R code passed as an input (this can be VERY DANGEROUS since it means you will be executing arbitrary unverified R code), you can use
r_code_string <- 'c("a", "b"), c("x", "y"))'
stopifnot(
all.equal(
c("a", "b"), c("x", "y")),
eval(parse(r_code_string))) == TRUE)
parse converts raw text into an unevaluated "expression", which is a representation of R code in the form of a special R object, eval passes the expression to the interpreter for execution.
As for noquote, it doesn't do what you think it does. It doesn't actually modify the string, it just adds a flag to the variable so that it will print without quotation marks. You can emulate this behavior with print(..., quote = FALSE).

Resources