I am trying to plot maps for three different dependent variables (each plotted separately), over multiple metro areas. To do this, I am trying to do 2 nested maps. I start by doing it just over the 3 different variables foe one metro area - defining the function and running it through map and it works. The code for this part is below:
plot_fun <- function(x){
base_plot_2015$`14460` +
data_2015 %>% subset(met2013 == 14460) %>% subset(onetsoccode == "151021") %>%
geom_sf(mapping = aes_string(fill = x)) +
scale_fill_viridis_c(option = "plasma")
}
expl <- names(data_2015)[17:19]
expl <- set_names(expl)
plot_Boston <- map(expl, ~plot_fun(.x))
This gives me a list of 3 maps and so far so good. (Please note that the base_plot is a list containing maps of each of the metro areas saved as the metro area id, which is stored as double).
Next, I want to do the same, but also iterate it over different metro codes. I try doing this by defining the following function:
plot_fun <- function(x,y){
base_plot_2015$`y` +
data_2015 %>% subset(met2013 == y) %>% subset(onetsoccode == "151021") %>%
geom_sf(mapping = aes_string(fill = x)) +
scale_fill_viridis_c(option = "plasma")
}
The idea is to map it over a vector of metro areas (y) for each dependent variable to be plotted(x). However, before I do that, I can't get this function to work. When I run this function using plot_fun("work_home", 14460), I get the following error:
Error in base_plot_2015$y + data_2015 %>% subset(met2013 == y) %>% subset(onetsoccode == :
non-numeric argument to binary operator
Would someone be kind enough to point out what I am doing wrong here?
Also, to do the nested map, does the code map(met, ~map(expl, ~plot_fun, y=.x)) (where met is a vector of metro area codes) make sense?
TLDR: When you subsetting your base_plot list, you need to use the [[ operator instead of $
In your second version, the function sees it as "y" a string, not the variable y. Since, presumably, there is no metro code "y", NULL is returned. The binary operator is + and NULL is non-numeric. Here's a simplified example of what's happening:
base_data <- list(
a = letters,
b = LETTERS,
c = rnorm(100)
)
getMyData <- function(y){
base_data$`x`
}
getMyData("a")
#> NULL
base_data$x <- "QSBjbHVlIHBlcmhhcHM="
val <- getMyData("a") %>% print()
#> [1] "QSBjbHVlIHBlcmhhcHM="
base64enc::base64decode(val) %>% rawToChar()
#> [1] "A clue perhaps"
If you change your function to use the [[ operator, you can use the string value in the variable y to subset:
actuallyGetMyData <- function(x){
base_data[[x]]
}
actuallyGetMyData("a")[1:10]
#> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
EDIT: Regarding the mapping over the permutations (because it's hard to read code in comments)
the call map(expl, ~plot_fun) creates an anonymous function that returns the function plot_fun. You either need to drop the anonymous function
map(met, ~map(expl, plot_fun, y=.x))
or call the function
map(met, ~map(expl, ~plot_fun(.x, .y), .y=.x))
I find the nested maps a little hard to read (.x becoming .y and what not) so alternatively you could just first enumerate all the combinations and then map over those
plotVars <- expand.grid(expl, met)
plot_final <- map2(plotVars[[1]], plotsVars[[2]], plot_fun) %>%
set_names(paste(plotVars[[1]], plotVars[[2]], sep = "_"))
Note that your plot_final list will no longer be nested though.
Related
I want to make many plots using multiple pairs of variables in a dataframe, all with the same x. I store the plots in a named list. For simplicity, below is an example with only 1 variable in each plot.
Key to this function is a select() call that is clearly not necessary here but is with my actual data.
The body of the function works fine on each variable, but when I loop through a list of variables, the last one in the list always produces
Error in get(ll): object 'd' not found.
(or whatever the last variable, if not 'd'). Replacing data <- df %>% select(x,ll) with data <- df avoids the error.
## make data
df2 <- data.frame(x = 1:10,
a = 1:10,
b = 2:11,
c = 101:110,
d = 10*(1:10))
## make function
testfun <- function(df = df2, vars = letters[1:4]){
## initialize list to store plots
plotlist <- list()
for (ll in vars){
## subset data
data <- df %>% select(x, ll) ## comment out select() to get working function
# print(data) ## uncomment to check that dataframe subset works correctly
## plot variable vs. x
p <- ggplot(data,
aes(x = x, y = get(ll))) +
geom_point() +
ylab(ll)
## add plot to named list
plotlist[[ll]] <- p
# print(p) ## uncomment to see that each plot is being made
}
return(plotlist) ## unnecessary, being explicit for troubleshooting
}
## use function
pl <- testfun(df2)
## error ?
pl
I have a work-around that avoids select() by renaming variables in my actual dataframe, but I am curious why this does not work? Any ideas?
get() could work, but not with ll directly. Try y = get(!!ll) or y = {{ll}}.
ggplot (or maybe aes, it's hard to tell) waits to run this code until its plot object is referenced, as the error in the provided code demonstrates. By the time each ggplot evaluates get(ll), the for loop has already finished. So ll evaluates to the last value of the loop variable, "d", for all four ggplots. ll being "d" in the error makes it seem like it's the final ggplot object that fails, but it's actually evaluating the first one that causes this error.
In the body of the loop we'd like a way to evaluate the ll variable and stick that resulting string ("a", "b", "c", or "d") into this code, the rest of which won't run until later. Changing y = get(ll) to y = get(!!ll) is one way to do this: !! performs "surgery" on the unevaluated expression (called a "blueprint for code" in Tidyverse docs) so that the expression passed into ggplot contains a literal string like "a" instead of the variable reference ll.
testfun <- function(df = df2, vars = letters[1:4]){
plotlist <- list()
for (ll in vars){
data <- df %>% select(x, ll)
p <- ggplot(data,
aes(x = x, y = get(!!ll))) +
geom_point() +
ylab(ll)
plotlist[[ll]] <- p
}
return(plotlist)
}
Read on for explanation and an alternate solution.
The loop problem: late binding
In a given function or in the global scope in R, there's just one variable of any given name. A for (x in xs) loop repeatedly rebinds that variable to a new value. That means that after a for loop has finished, that variable still exists and retains the last value it was assigned. Here's a way this can trip you up:
vars <- c("a", "b", "c", "d")
results <- list()
for (ll in vars){
message("in for loop, ll: ", ll)
func <- function () { ll }
results[[ll]] <- c(ll, func)
}
message("after for loop, ll: ", ll)
# after for loop, now ll is "d"
for (vec in results) {
message(vec[[1]], " ", vec[[2]]())
}
This outputs
in for loop, ll: a
in for loop, ll: b
in for loop, ll: c
in for loop, ll: d
after for loop, ll: d
a d
b d
c d
d d
Each of the four functions constructed here use the same outer scope variable ll which, by the time the functions are actually called after the for loop, is "d". The late binding part is that the value of the variable at function call time (late) is used when looking up its value, not the value of the variable when the function is defined (early).
The NSE problem
The OP isn't creating functions in a loop though, they're calling ggplot. ggplot does something similar to creating a function: it takes some code as an argument that it doesn't evaluate until later. ggplot (or maybe aes) "captures" code from some of arguments instead of running them. In OP's case, get(ll) isn't evaluated until later.
When this code is evaluated it's in a new context with a "data mask" that allows names of a data frame to be referenced directly. This part is great, it's what we want — this is what makes get("a") work at all. But the fact that the evaluation happens later is a problem for the OP: ll in get(ll) evaluated to "d", like get("d"), because the code is evaluated after the for-loop iteration where ll had the expected value.
Ignoring the data mask part, here's a function called run.later that, like ggplot, doesn't run one of its arguments. When we run that code later, we again find that ll evaluates to "d" for all four of the saved expressions.
vars <- c("a", "b", "c", "d")
unevaluated.exprs <- list();
run.later <- function(name, something) {
expr <- substitute(something)
unevaluated.exprs[[name]] <<- c(name, expr)
}
for (ll in vars){
run.later(ll, ll)
}
for (vec in unevaluated.exprs) {
message(c(vec[[1]], " ", eval(vec[[2]])))
}
prints
a d
b d
c d
d d
That's the ll part of the problem. The rule of thumb from languages like Python of "Don't define functions in a loop (if they reference loop variables)" could be generalized for R to "don't define functions or otherwise write code that won't be immediately evaluated in a loop (if that code references loop variables)."
Fixing the scope problem instead of metaprogramming
The !! solution provided at the top uses metaprogramming to evaluate the ll variable in the loop instead of evaluating it later.
Theoretically, one could instead dynamically create variables in each iteration of a loop, then carefully reference that dynamically created variable name with metaprogramming. But a more elegant way would be to use the same variable name but in different scopes. This is what Nithin's answer does with a function: every function creates a new scope and tada, you can use the same variable name in each. Here's another version of that, closer to OP's code:
testfun <- function(df = df2, vars = letters[1:4]){
plotlist <- list()
plot.fn <- function(var) {
data <- df %>% select(x, var)
p <- ggplot(data,
aes(x = x, y = get(var))) +
geom_point() +
ylab(var)
plotlist[[ll]] <<- p
}
for (ll in vars){
plot.fn(ll)
}
return(plotlist)
}
pl <- testfun(df2)
pl
There are 4 distinct variables called var in this code, and each iteration of the loop references a different one.
Prettier metaprogramming
I think (haven't tested) that get(!!ll) is equivalent to {{ll}} here — get() looks up a string as a variable, but that's also what sticking the symbol of the string that ll evaluates to into the expression does. Double curlies seem more common and can roughly be understood as "evaluate the result of this expression as a variable in the other context," or as "template this string into the expression."
write a custom function like this
plot_fn<- function(df,y){
df %>% ggplot(aes(x=x,
y=get(y))+
geom_point()+
ylab(y)
}
Iterate over plots with purrr:::map
map(letters[1:4],~plot_fn(df=df2,y=.x))
The issue is that we cannot use get to access dplyr/tidyverse data in a "programming" paradigm. Instead, we should use non standard evaluation to access the data. I offer a simplified function below (originally I thought it was a function masking issue as I quickly skimmed the question).
testfun <- function(df = df2, vars = letters[1:4]){
lapply(vars, function(y) {
ggplot(df,
aes(x = x, y = .data[[y]] )) +
geom_point() +
ylab(y)
})
}
Calling
plots <- testfun(df2)
plots[[1]]
EDIT
Since OP would like to know what the issue is, I have used a traditional loop as requested
testfun2 <- function(df = df2, vars = letters[1:4]){
## initialize list to store plots
plotlist <- list()
for (ll in vars){
## subset data
d_t <- df %>% select(x, ll) ## comment out select() to get working function
# print(data) ## uncomment to check that dataframe subset works correctly
## plot variable vs. x
p <- ggplot(d_t,
aes(x = x, y = .data[[ll]])) +
geom_point() +
ylab(ll)
## add plot to named list
plotlist[[ll]] <- p
## uncomment to see that each plot is being made
}
plotlist
}
pl <- testfun2(df2)
pl[[1]]
The reason get does not work is that we need to use non-standard evaluation as the docs state. Related questions on using get may be useful.
First plot
Before you mark as dup, I know about Use character string as function argument, but my use case is slightly different. I don't need to pass a parameter INSIDE the function, I would like to pass a dynamic number of parameters after a + (think ggplot2).
(Note: Please don't format and remove the extra-looking ####, I have left them in so people can copy paste the code into R for simplicity).
This has been my process:
#### So let's reproduce this example:
library(condformat)
condformat(iris[c(1:5,70:75, 120:125),]) +
rule_fill_discrete(Species) +
rule_fill_discrete(Petal.Width)
#### I would like to be able to pass the two rule_fill_discrete() functions dynamically (in my real use-case I have a variable number of possible inputs and it's not possible to hardcode these in).
#### First, create a function to generalize:
PlotSeries <- function(x){
b=NULL
for (i in 1:length(x)){
a <- paste('rule_fill_discrete(',x[i],')',sep="")
b <- paste(paste(b,a,sep="+"))
}
b <- gsub("^\\+","",b)
eval(parse(text = b))
}
#### Which works with one argument
condformat(iris[c(1:5,70:75, 120:125),]) +
PlotSeries("Species")
#### But not if we pass two arguments:
condformat(iris[c(1:5,70:75, 120:125),]) +
PlotSeries(c("Species","Petal.Width"))
Error in rule_fill_discrete(Species) + rule_fill_discrete(Petal.Width) :
non-numeric argument to binary operator
#### It will work if we call each individually
condformat(iris[c(1:5,70:75, 120:125),]) +
PlotSeries("Species") +
PlotSeries("Petal.Width")
#### Which gives us an indication as to what the problem is... the fact that it doesn't like when the rule_fill_discrete statements are passed in as one statement. Let's test this:
condformat(iris[c(1:5,70:75, 120:125),]) +
eval(rule_fill_discrete(Species) +
rule_fill_discrete(Petal.Width) )
Error in rule_fill_discrete(Species) + rule_fill_discrete(Petal.Width) :
non-numeric argument to binary operator
#### Fails. But:
condformat(iris[c(1:5,70:75, 120:125),]) +
eval(rule_fill_discrete(Species)) +
eval(rule_fill_discrete(Petal.Width) )
#### This works. But we need to be able to pass in a GROUP of statements (that's kinda the whole point). So let's try to get the eval statements in:
Nasty <- "eval(rule_fill_discrete(Species)) eval(rule_fill_discrete(Petal.Width))"
condformat(iris[c(1:5,70:75, 120:125),]) + Nasty #### FAIL
Error in +.default(condformat(iris[c(1:5, 70:75, 120:125), ]), Nasty) :
non-numeric argument to binary operator
condformat(iris[c(1:5,70:75, 120:125),]) + eval(Nasty) #### FAIL
Error in +.default(condformat(iris[c(1:5, 70:75, 120:125), ]), eval(Nasty)) :
non-numeric argument to binary operator
condformat(iris[c(1:5,70:75, 120:125),]) + parse(text=Nasty) #### FAIL
Error in +.default(condformat(iris[c(1:5, 70:75, 120:125), ]), parse(text = Nasty)) :
non-numeric argument to binary operator
condformat(iris[c(1:5,70:75, 120:125),]) + eval(parse(text=Nasty)) #### FAIL
Error in eval(rule_fill_discrete(Species)) + eval(rule_fill_discrete(Petal.Width)) :
non-numeric argument to binary operator
So how can we do it?
Thanks to this stackoverflow question and thanks to the bug report from #amit-kohli, I was made aware that there was a bug in the condformat package.
Update: Answer updated to reflect the new condformat API introduced in condformat 0.7.
Here I show how to (using condformat 0.7.0). Note that the syntax I use in the standard evaluation function is derived from the rlang package.
Install condformat:
install.packages("condformat)"
A simple example, asked in the question:
# Reproduce the example
library(condformat)
condformat(iris[c(1:5,70:75, 120:125),]) %>%
rule_fill_discrete(Species) %>%
rule_fill_discrete(Petal.Width)
# With variables:
col1 <- rlang::quo(Species)
col2 <- rlang::quo(Petal.Width)
condformat(iris[c(1:5,70:75, 120:125),]) %>%
rule_fill_discrete(!! col1) %>%
rule_fill_discrete(!! col2)
# Or even with character strings to give the column names:
col1 <- "Species"
col2 <- "Petal.Width"
condformat(iris[c(1:5,70:75, 120:125),]) %>%
rule_fill_discrete(!! col1) %>%
rule_fill_discrete(!! col2)
# Do it programmatically (In a function)
#' #importFrom magrittr %>%
some_color <- function(data, col1, col2) {
condformat::condformat(data) %>%
condformat::rule_fill_discrete(!! col1) %>%
condformat::rule_fill_discrete(!! col2)
}
some_color(iris[c(1:5,70:75, 120:125),], "Species", "Petal.Width")
A more general example, using an expression:
# General example, using an expression:
condformat(iris[c(1:5,70:75, 120:125),]) %>%
rule_fill_gradient(Species, expression = Sepal.Width - Sepal.Length)
# General example, using a column given as character and an
# expression given as character as well:
expr <- rlang::parse_expr("Sepal.Width - Sepal.Length")
condformat(iris[c(1:5,70:75, 120:125),]) %>%
rule_fill_gradient("Species", expression = !! expr)
# General example, in a function, everything given as a character:
two_column_difference <- function(data, col_to_colour, col1, col2) {
expr1 <- rlang::parse_expr(col1)
expr2 <- rlang::parse_expr(col2)
condformat::condformat(data) %>%
condformat::rule_fill_gradient(
!! col_to_colour,
expression = (!!expr1) - (!!expr2))
}
two_column_difference(iris[c(1:5,70:75, 120:125),],
col_to_colour = "Species",
col1 = "Sepal.Width",
col2 = "Sepal.Length")
Custom discretized scales for continuous values
Custom discrete color values can be specified with a function that preprocesses a continuous column into a discrete scale:
discretize <- function(column) {
sapply(column,
FUN = function(value) {
if (value < 4.7) {
return("low")
} else if (value < 5.0) {
return("mid")
} else {
return("high")
}
})
}
And we can specify the colors for each of the levels of the scale using colours =:
condformat(head(iris)) %>%
rule_fill_discrete(
"Sepal.Length",
expression = discretize(Sepal.Length),
colours = c("low" = "red", "mid" = "yellow", "high" = "green"))
If we want, the discretize function can return colours:
discretize_colours <- function(column) {
sapply(column,
FUN = function(value) {
if (value < 4.7) {
return("red")
} else if (value < 5.0) {
return("yellow")
} else {
return("green")
}
})
}
The code to use it:
condformat(head(iris)) %>%
rule_fill_discrete(
"Sepal.Length",
expression = discretize_colours(Sepal.Length),
colours = identity)
Note that as expression returns the colours we use colours = identity. identity is just function(x) x.
Finally, using some rlang tidy evaluation we can create a function:
colour_based_function <- function(data, col1) {
col <- rlang::parse_expr(col1)
condformat::condformat(data) %>%
condformat::rule_fill_discrete(
columns = !! col1,
expression = discretize_colours(!! col),
colours = identity)
}
colour_based_function(head(iris), "Sepal.Length")
NOTE: This answer provides a workaround for a bug in an old version of condformat. The bug has since been fixed, see #zeehio's answer for the current version after this bug was fixed.
I think you have two mostly separate questions. That are all mixed together in your post. I will attempt to restate and answer them individually, and then put things together - which doesn't work all the way at this point but gets close.
First, let's save some typing by defining a couple variables:
ir = iris[c(1:5,70:75, 120:125), ]
cf = condformat(ir)
Q1: How do I use + on a vector or list of inputs?
This is the easy question. The base answer is Reduce. The following are all equivalent:
10 + 1 + 2 + 5
"+"("+"("+"(10, 1), 2), 5)
Reduce("+", c(1, 2, 5), init = 10))
More pertinent to your case, we can do this to replicate your desired output:
fills = list(rule_fill_discrete(Species), rule_fill_discrete(Petal.Width))
res = Reduce(f = "+", x = fills, init = cf)
res
Q2: How do I use string inputs with rule_fill_discrete?
This is my first time using condformat, but it looks to be written in the lazyeval paradigm with rule_fill_discrete_ as a standard-evaluating counterpart to the non-standard-evaluating rule_fill_discrete. This example is even given in ?rule_fill_discrete, but it doesn't work as expected
cf + rule_fill_discrete_(columns = "Species")
# bad: Species column colored entirely red, not colored by species
# possibly a bug? At the very least misleading documentation...
cf + rule_fill_discrete_(columns = "Species", expression = expression(Species))
# bad: works as expected, but still uses an unquoted Species
# other failed attempts
cf + rule_fill_discrete_(columns = "Species", expression = expression("Species"))
cf + rule_fill_discrete_(columns = "Species", expression = "Species")
# bad: single color still single color column
There is also an env environment argument in the SE function, but I had no luck with that either. Maybe someone with more lazyeval/expression experience can point out something I'm overlooking or doing wrong.
Work-around: What we can do is pass the column directly. This works because we're not doing any fancy functions of the column, just using it's values directly to determine the coloring:
cf + rule_fill_discrete_(columns = c("Species"), expression = ir[["Species"]])
# hacky, but it works
Putting it together
Using the NSE version with Reduce is easy:
fills = list(rule_fill_discrete(Species), rule_fill_discrete(Petal.Width))
res = Reduce(f = "+", x = fills, init = cf)
res
# works!
Using SE with input strings, we can use the hacky workaround.
input = c("Species", "Petal.Width")
fills_ = lapply(input, function(x) rule_fill_discrete_(x, expression = ir[[x]]))
res_ = Reduce(f = "+", x = fills_, init = cf)
res_
# works!
And this, of course, you could wrap up into a custom function that takes a data frame and a string vector of column names as input.
#Gregor's answer was perfect. A bit hacky, but works excellently.
In my use-case, I needed a bit more complication, I will post it here in case it's useful to somebody else.
In my use-case, I needed to be able to color multiple columns based on the values of one column. condformat allows us to do this already, but again we run into the parametrization problem. Here's my solution to that, based on the response by Gregor:
CondFormatForInput <- function(Table,VectorToColor,VectorFromColor) {
cf <- condformat(Table)
input = data.frame(Val=VectorToColor,
Comp=VectorFromColor)
fills2_ = map2(input$Val,.y = input$Comp,.f = function(x,y) rule_fill_discrete_(x, expression =
iris[[y]]))
res_ = Reduce(f = "+", x = fills2_, init = cf)
res_
}
CondFormatForInput(iris,
c("Sepal.Length","Sepal.Width","Petal.Length","Petal.Width"),
c("Sepal.Width","Sepal.Width","Petal.Width","Petal.Width"))
Lets say I have a function named Fun1 within which I am using many different in-built functions of R for different different processes. Then how can I get a list of in-built functions used inside this function Fun1
Fun1 <- function(x,y){
sum(x,y)
mean(x,y)
c(x,y)
print(x)
print(y)
}
So My output should be like list of characters i.e. sum, mean, c, print. Because these are the in-built functions I have used inside function Fun1.
I have tried using grep function
grep("\\(",body(Fun1),value=TRUE)
# [1] "sum(x, y)" "mean(x, y)" "c(x, y)" "print(x)" "print(y)"
It looks ok, but arguments should not come i.e. x and y. Just the list of function names used inside body of function Fun1 here.
So my overall goal is to print the unique list of in-built functions or any create functions inside a particular function, here Fun1.
Any help on this is highly appreciated. Thanks.
You could use all.vars() to get all the variable names (including functions) that appear inside the body of Fun1, then compare that with some prepared list of functions. You mention in-built functions, so I will compare it with the base package object names.
## full list of variable names inside the function body
(vars <- all.vars(body(Fun1)[-1], functions = TRUE))
# [1] "sum" "x" "y" "mean" "c" "print"
## compare it with the base package object names
intersect(vars, ls(baseenv()))
# [1] "sum" "mean" "c" "print"
I removed the first element of the function body because presumably you don't care about {, which would have been matched against the base package list.
Another possibility, albeit a bit less reliable, would be to compare the formal arguments of Fun1 to all the variable names in the function. Like I said, likely less reliable though because if you make assignments inside the function you will end up with incorrect results.
setdiff(vars, names(formals(Fun1)))
# [1] "sum" "mean" "c" "print"
These are fun though, and you can fiddle around with them.
Access to the parser tokens is available with functions from utils.
tokens <- utils::getParseData(parse(text=deparse(body(Fun1))))
unique(tokens[tokens[["token"]] == "SYMBOL_FUNCTION_CALL", "text"])
[1] "sum" "mean" "c" "print"
This should be somewhat helpful - this will return all functions however.
func_list = Fun1 %>%
body() %>% # extracts function
toString() %>% # converts to single string
gsub("[{}]", "", .) %>% # removes curly braces
gsub("\\s*\\([^\\)]+\\)", "", .) %>% # removes all contents between brackets
strsplit(",") %>% # splits strings at commas
unlist() %>% # converts to vector
trimws(., "both") # removes all white spaces before and after`
[1] "" "sum" "mean" "c" "print" "print"
> table(func_list)
func_list
c mean print sum
1 1 1 2 1
This is extremely limited to your example... you could modify this to be more robust. It will fall over where a function has brackets nesting other functions etc.
this is not so beautiful but working:
Fun1 <- function(x,y){
sum(x,y)
mean(x,y)
c(x,y)
print(x)
print(y)
}
getFNamesInFunction <- function(f.name){
f <- deparse(body(get(f.name)))
f <- f[grepl(pattern = "\\(", x = f)]
f <- sapply(X = strsplit(split = "\\(", x = f), FUN = function(x) x[1])
unique(trimws(f[f != ""]))
}
getFNamesInFunction("Fun1")
[1] "sum" "mean" "c" "print"
as.list(Fun1)[3]
gives you the part of the function between the curly braces.
{
sum(x, y)
mean(x, y)
c(x, y)
print(x)
print(y)
}
Hence
gsub( ").*$", "", as.list(Fun1)[3])
gives you everything before the first " ) " appears which is presumable the name of the first function.
Taking this as a starting point you should be able to include a loop which gives you the other functions and not the first only the first one.
Attempting to update an alpha character column to respective alphanumeric characters: meaning update "A" to "1".
passing this in the console updates the sdt dataframe for column 5
x <- 5
y <- "K"
z <- "1000"
sdt[[x]] <- ifelse(sdt[[x]] == y, z, sdt[[x]])
thought to scale it by building this function:
texp <- function (x, y, z) {
x <- as.integer(x)
y <- as.character(y)
y <- as.character(z)
sdt[[x]] <- ifelse(sdt[[x]] == y, z, sdt[[x]] )
}
then passing this to the console:
texp(5, "A", "1")
no updates were made. Attempted a variety of variants, none worked (meaning no updates made).
What am I missing?
# Update column
sdt[[5]] <- match(tolower(sdt[[5]]), letters)
# transform to numeric
sdt[[5]] <- as.numeric(sdt[[5]])
Notes about your code:
Inside a function, if you want to modify an existing object, you
need to use <<- instead of <- (although this should be used with
caution).
As docendo pointed out, you'd generally want a function to return
something instead of modifying existing objects. So for instance you
could have return(ifelse([your args])), and then use your function for
assigning new values.
I created the following function with six args:
nDone <- function(under,strike,ttoe,vol,rf,dy) {
pnorm(((log(under/strike)+ (rf-dy+(vol^2)/2)*ttoe)/(vol*(ttoe^0.5))))
}
nDone(90,100,3,0.17,0.05,0)
# Result:
[1] 0.6174643
Now I create a vector with the same values in an object, and try to call the function using the vector, but get the following error:
d <- c(90,100,3,0.17,0.05,0)
nDone(d)
Error in under/strike : 'strike' is missing
What am I doing wrong and how to fix?
Try this
do.call(nDone, as.list(d))
Explanation of what's happening in your first attempt by #joran from the comments:
R is seeing you pass a single argument to nDone, namely the vector d, which is handed off to the first function argument, under. Since you haven't specified a default value for the others, they are missing and hence the error
Maybe worth to add:
If your function can accept arguments that are vectors of length >1 and generates output of the same length, do.call can handle that, too, and you will need list():
x <- c("a", "b", "c")
y <- c(1, 2, 3)
> do.call(paste0,c(list(x),list(y)))
[1] "a1" "b2" "c3"
watch out that this won't fail or warn for vectors of unequal lengths:
x <- c("a", "b")
> do.call(paste0,c(list(x),list(y)))
[1] "a1" "b2" "a3"
Of course paste0(x,y) would work here just as well, but I'm using this e.g. for rgb():
# whichever complex functions to generate vector of floats:
x <- seq(1,6) %>% exp()
# rescale for rgb
x <- scales::rescale(x)
# make a list of vectors
# note that as.list() would not give the desired output here
x <- rep(list(x),3)
# call
> do.call(rgb, x)
[1] "#000000" "#030303" "#0B0B0B" "#212121" "#5D5D5D" "#FFFFFF"
or a tidy one line:
> seq(1,6) %>% exp() %>% scales::rescale() %>% list() %>% rep(3) %>% do.call(rgb,.)
[1] "#000000" "#030303" "#0B0B0B" "#212121" "#5D5D5D" "#FFFFFF"