I am trying to analyze how varying starting conditions and variable values in a set of differential equations (that describe the progression of a disease through a population) influences the dynamics of the system (as seen via graph). I have written the code and it works perfectly well, but forces me to change a value and then re-run the whole code.
I am therefore trying to put this code inside of manipulate() so I can manipulate the variables and immediately see the effect on the produced graph:
library(deSolve)
library(manipulate)
xyz <- function(time, state, parameters) {
with(as.list(c(state, parameters)), {
dt <- 1
dv <- v0*v1*cos(2*pi*t/33)
dX <- (v*N-B*X*Y-(mu+N/K)*X)
dY <- (B*X*Y-(mu+m+g+N/K)*Y)
dZ <- (g*Y-(mu+N/K)*Z)
dN <- (v-mu-N/K)*N-m*Y
return(list(c(dt,dv,dX, dY, dZ, dN)))
})
}
times <- seq(0,365*3,by = 1)
init <- c(t=0,v=0.02,X=995,Y=5,Z=0,N=1000)
parameters <- c(B=0.14,mu=.01,m=.075,g=.025,K=10000,v0=.02,v1=.5)
manipulate(
out <- as.data.frame(ode(y = init, times = seq(0,365*3,by=1), func = xyz, parms = parameters)),
matplot(times,out[4:6],type="l",xlab="Time",ylab="Susceptibles and Recovereds",main="SIR Model",lwd=1,lty=1,bty="l",col=2:4),
B=slider(0,1,initial=0.14,step=0.01)
)
I keep getting error messages regardless if I have all or part of the code inside manipulate(), define variables outside or inside of it, or anything else. Any help would be greatly appreciated!
When I first ran your code, the error I encountered was:
Error in manipulate(out <- as.data.frame(ode(y = init, times = seq(0, :
all controls passed to manipulate must be named
This error occurred because the second argument to manipulate was the matplot() command rather than a named control argument (such as a slider). So I placed the first two lines within curly braces to make them a single expression:
manipulate( {
out <- as.data.frame(ode(y = init, times = seq(0,365*3,by=1), func = xyz, parms = parameters))
matplot(times,out[4:6],type="l",xlab="Time",ylab="Susceptibles and Recovereds",main="SIR Model",lwd=1,lty=1,bty="l",col=2:4)
}, B=slider(0,1,initial=0.14,step=0.01)
)
This eliminates the error, but moving the slider doesn't do anything to the plot. Why? Because the slider named B doesn't refer to anything within the expression passed to manipulate(). I solved that by moving the parameters <- ... line into the manipulate expression and then changing that line so that there was a variable B (not just a name in the list); in other words, we need B=B instead of B=0.14. Now the plot changes when you move the slider, which I believe is what you wanted:
manipulate( {
parameters <- c(B=B,mu=.01,m=.075,g=.025,K=10000,v0=.02,v1=.5)
out <- as.data.frame(ode(y = init, times = seq(0,365*3,by=1), func = xyz, parms = parameters))
matplot(times,out[4:6],type="l",xlab="Time",ylab="Susceptibles and Recovereds",main="SIR Model",lwd=1,lty=1,bty="l",col=2:4)
}, B=slider(0,1,initial=0.14,step=0.01)
)
times <- seq(0,365*3,by = 1)
init <- c(t=0,v=0.02,X=995,Y=5,Z=0,N=1000)
plot.ode <- function(B.param) {
parameters <- c(B=B.param,mu=.01,m=.075,g=.025,K=10000,v0=.02,v1=.5)
out <- as.data.frame(ode(y = init, times = seq(0,365*3,by=1), func = xyz, parms = parameters))
matplot(times,out[4:6],type="l",xlab="Time",ylab="Susceptibles and Recovereds",main="SIR Model",lwd=1,lty=1,bty="l",col=2:4, ylim=c(0,200))
}
manipulate(plot.ode(B), B=slider(0,1,initial=0.14,step=0.01))
Seems a little odd that only the red curve is influenced by changing B.
Related
I'd like to use the censored boxplot in the R package NADA but I want to reorder the X-axis.
library(NADA)
data(Golden)
#this should reorder the factor and change the x-axis but does not
Golden$DosageGroup <-factor(Golden$DosageGroup, levels=c("Low","High"))
cenboxplot(Golden$Blood, Golden$BloodCen, Golden$DosageGroup)
The help says the output is the default boxplot method but I cannot seem to get it to work.
PS - similar to this post but no answers were given
Modifying a function in a loaded existing package consists of several steps:
get the code and store its environment
cenboxplot # just typing the name of a function should bring up its code.
# this appears
function (obs, cen, group, log = TRUE, range = 0, ...)
{
if (log)
log = "y"
else log = ""
if (missing(group))
ret = boxplot(cenros(obs, cen), log = log, range = range,
...)
else {
modeled = numeric()
groups = character()
for (i in levels(as.factor(group))) {
mod = suppressWarnings(cenros(obs[group == i], cen[group ==
i])$modeled)
grp = rep(i, length(mod))
modeled = c(modeled, mod)
groups = c(groups, grp)
}
# problem with levels of the `groups` object
boxplot(modeled ~ as.factor(groups), log = log, range = range,
...)
ret = data.frame(ros.model = modeled, group = groups)
}
abline(h = max(obs[cen]))
invisible(ret)
}
<environment: 0x55a8acb2d708>
cbp_env <- environment(cenboxplot)
figure where the function is deficient and make a copy ready to fix the problem:
It's because the constructed groups object doesn't inherit the levels from the group argument. When I'm looking at the code, I select the console output starting with the function name and ending just before the <environment ....> designation, and then paste that back to the console. I then put an assignment arrow (<-)right after the function name.
Modify teh code before hitting enter (or copy it to an editor if it's goint to require major surgery.) After code is modified, assign new value to existing name (or a new name at your discretion)
The modification that succeeds: Put this line in just below the curley-brace that is the end of the for loop. (It's also just before the boxplot call:
groups=factor(groups, levels=levels(group)) # adhere to user's intent
assign the same environment to the new version as the old version had:
environment(cenboxplot) <- cbp_env # which was stored above.
Now running your code yields:
There are other options to the fname<-old_fname; environment(fname)<-environment(old_fname) strategy. There is reassignInPackage in the R.utils package. And apparently you can do: environment(censboxplot) <- asNamespace('NADA')
I am aware that the "skip error in for loop" has been answered multiple times (see How to skip an error in a loop or Skip Error and Continue Function in R). But all answers are complex and difficult to apply to a different situation for a novice.
I am performing a Gaussian histogram fitting on 100's of datasets using a piece of code.
results = list()
for(i in 1:length(T_files)){
R = Table[i][,1]
tab = data.frame(x = seq_along(R), r = R)
res = nls(R ~ k*exp(-1/2*(x-mu)^2/sigma^2), start=c(mu=15,sigma=5, k=1) , data = tab)
v = summary(res)$parameters[,"Estimate"]
fun = function(x) v[3]*exp(-1/2*(x-v[1])^2/v[2]^2)
results[[i]] = fun(seq(0, 308, 1))/max(fun_SP(seq(0, 308, 1)))/2
}
The code works on most datasets when tested on each individual. However, the loop does not and shows the "error in nls(...): singular gradient" message. I want to skip this message and continue to the next dataset.
I know that a tryCatch function may be used, but the line containing the nls function is complex and I have not found a way to use correctly tryCatch in this line. Any advice is welcome :-)
Use the function try, it allows you save an error and then put a condition if(error==T) then "pass to next df". Something like this:
error<-try(your code...)
if(class(error)!="try-error"){pass to the next one}
In yor case, maybe must be:
results = list()
for(i in 1:length(T_files)){
R = Table[i][,1]
tab = data.frame(x = seq_along(R), r = R)
error = try(res <- nls(R ~ k*exp(-1/2*(x-mu)^2/sigma^2), start=c(mu=15,sigma=5, k=1) , data = tab))
if(class(error)!="try-error"){
v = summary(res)$parameters[,"Estimate"]
fun = function(x) v[3]*exp(-1/2*(x-v[1])^2/v[2]^2)
results[[i]] = fun(seq(0, 308, 1))/max(fun_SP(seq(0, 308, 1)))/2
}else{
pass to next data frame (or something like that)
}
}
This question differs from my original; it adheres more to a minimal reproducible example and incorporates a recommendation by be_green against silently loading entire libraries within the context of a function.
The outer function starts by defining a number of cases, default values, and a list of any case exceptions. The inner function assembles each case by using the default values in a computation unless exceptions are defined. Finally, the outer function assembles these cases into a data frame.
Here is the function:
outerfun <- function(cases, var_default, exceptions=list()){
# Inner Function to create a case
innerfun <- function(var=var_default) { # Case
result = var
return(result)
}
# Combine Cases
datlist <- list()
for(case in 1:cases){
datlist[[paste0("X",case)]] <- do.call(innerfun, as.list(exceptions[[paste0("X",case)]]))
}
casedata <- do.call(dplyr::data_frame, datlist)
return(casedata)
}
This function works fine when I define values for the inner function as exceptions:
data <- outerfun(cases = 3, var_default = 10, exceptions = list("X2" = c(var = 14)))
But not when I mix the two:
data <- outerfun(cases = 3, var_default = 10, exceptions =
list("X2" = c(var = var_default + 4)))
Being able to mix the two are important since it makes the function more intuitive and easier to program for a variety of cases.
I think the problem might result from using do.call and have seen other threads detailing this issue (having to do with environments and frames), but I haven't been able to find an optimal solution for me. I like do.call since I can pass a list of arguments into a function. I could turn the inner function into a list (think: function(...) { }) but then I would have to define every variable instead of relying on the default.
Any help or suggestions you might have would be great.
The problem is that lvl_default is not defined outside the context of the function, and yet you call it as an input to a parameter. Because there is no variable called lvl_default in the global environment, when the function tries to evaluate the parameter exceptions = list(X3 - c(lvl = lvl_default + 10), it fails to find a variable to evaluate. You are not able to specify parameters by setting them equal to the names of other unevaluated parameters.
Instead, what I would recommend doing is setting a variable outside the function associated with the value you were hoping to pass into lvl_default and then pass it into the function like so:
level <- 1000
data <- genCaseData(n_signals = 3, datestart = "2017-07-01T15:00:00",
n_cycles = 4, period_default = 10, phase_default = 0, ampl_default = 15,
lvl_default = level, exceptions = list(X1= c(lvl=980),
X3 = c(lvl = level + 10)))
Also as I noted in a comment, I would recommend against silently loading entire libraries within the context of a function. You can end up masking things you didn't mean to, and running into strange errors because the require call doesn't actually throw one if a library is unavailable. Instead I would reference the functions through pkgname::fncname.
be_green did solve this first, but I wanted to follow-up with what I actually did for my project.
As be_green pointed out, I couldn't call var_default within the exception list since it hadn't yet been defined. I didn't understand this at first since you can actually define the default of an argument to a variable defined within the function itself:
addfun <- function(x, y = z + x + 2) {
z = 20
c(x, y)
}
addfun(x = 20)
[1] 20 42
This is because function arguments in R lazily evaluated. I thought this gave me a pass to call the function like this:
addfun(x = 10, y = x + z)
Error in addfun(x = 10, y = x + z) : object 'x' not found
If you remove x then it calls an error for z. So even though the default to y is dependent on x and z, you can't call the function using x or z.
be_green suggested that I pass arguments in a string and then parse it within the function. But I was afraid that others on my team would find the resulting syntax confusing.
Instead, I used ellipsis (...) and evaluated the ellipsis arguments within my function. I did this using this line of code:
list2env(eval(substitute(alist(...))), envir = as.environment(-1))
Here the eval(substitute(alist(...))) pattern is common but results in a named list of arguments. Due to some other features, it becomes more convenient to evaluate the arguments as objects within the function. list2env(x, envir = as.environment(-1)) accomplishes this with an additional step. Once the argument is called, you need to explicitly evaluate the call. So if I wanted to change my addfun() above:
addfun <- function(x, ...) {
z = 20
list2env(eval(substitute(alist(...))),
envir = as.environment(-1))
c(x, eval(y))
}
addfun(x = 10, y = x + z)
This is a trite example: I now need to define y even though it's not an argument in the function. But now I can even re-define z within the function call:
addfun(x = 10, y = z + 2, z = 10)
This is all possible because of non-standard evaluation. There can be trade-offs but in my application of non-standard evaluation, I was able to increase the usability and flexibility of the function while making it more intuitive to use.
Final code:
outerfun <- function(caseIDs, var_default, ...){
list2env(eval(substitute(alist(...))), envir = as.environment(-1))
# Inner Function to create a case
innerfun <- function(var=var_default) { # Case
result = var
return(result)
}
# Combine Cases
datlist <- lapply(caseIDs, function(case) {
do.call(innerfun, eval(get0(case, ifnotfound = list())))
})
names(datlist) <- caseIDs
casedata <- do.call(dplyr::data_frame, datlist)
return(casedata)
}
Now both examples work with full functionality:
data <- outerfun(caseIDs = c("X1","X2","X3"), var_default = 10,
X2 = list(var = 14))
data <- outerfun(caseIDs = c("X1","X2","X3"), var_default = 10,
X2 = list(var = var_default + 4))
I hope this helps someone else! Enjoy!
i'm new to this site.
I'm implementing a script in R, and i need to utilize deSolve, in order to do that i have to define the parameters that are useful when i write the "rate of change" of my equations.
Here's my problem, for using the package i'm obliged to define those parameters, but I actually don't need them, I mean in the rate of change of my equations i don't have any parameters so i'm asking for a way to avoid their definition.
I partially solved that problem defining the parameters in this way(not very elegant):
parameters <- c(1)
but I don't like this kind of solution.
Thank you in advance for any help, if my message is not that clear please report it to me, sorry but english is not my first language!
That's the code:
parameters <- c(1)
e=0.2056
ini <- c(q1 =1-e, q2 = 0, p1 = 0, p2 = sqrt ((1+e)/(1-e)) )
rhs <- function(t, ini,parameters)
{
with(as.list(c(ini,parameters)),{
# rate of change
dq1 <- p1
dq2 <- p2
dp1 <- -q1/((q1^2+q2^2)^(3/2))
dp2 <- -q2/((q1^2+q2^2)^(3/2))
# return rate of change
list(c(dq1,dq2,dp1,dp2))
} )
}
###### EULER ######
library(deSolve)
times <- seq(0,40, by = 0.0005)
out<- ode(y = ini, times = times, func = rhs, parms = parameters,method="euler")
head(out)
'ode' needs those parameters, it says error if i don't put parms
parms is passed to the method that actually does the solving, and in turn passed to your supplied function, and thus must be supplied. But it doesn't need to have a value, and your function doesn't need to use it.
# Accept and ignore third argument
rhs <- function(t, ini,...)
{
with(as.list(c(ini)),{
# rate of change
dq1 <- p1
dq2 <- p2
dp1 <- -q1/((q1^2+q2^2)^(3/2))
dp2 <- -q2/((q1^2+q2^2)^(3/2))
# return rate of change
list(c(dq1,dq2,dp1,dp2))
} )
}
# Pass NULL to parms
out2 <- ode(y = ini, times = times, func = rhs, parms = NULL,method="euler")
identical(out, out2)
## [1] TRUE
I'd like to write a function that will create and return a set of parameters to be used in a function mySimulation I've created. Until now, I've basically been doing, e.g., mySimulation(parm1 = 3, parm2 = 4). This is now suboptimal because (1) in the actual version, the number of parameters is becoming unwieldy and (2) I'd like to keep track of different combinations of the parameters that produce the different models I'm using. So, I wrote createParms (a minimally sufficient version shown below) to do the trick. My whole approach just seems so clunky though. With all the statisticians using R, I'm sure there's a more standard way of handling my issue...right?
createParms <- function(model = "default", ...) {
# Returns a list `parms` of parameters which will then be used in
# mySimultation(parms)
#
# Args:
# model: ["default" | "mymodel"] character string representation of a model
# with known parameters
# ...: parameters of the existing `model` to overwrite.
# if nothing is supplied then the model parameters will be left as is.
# passed variables must be named.
# e.g., `parm1 = 10, parm2 = 20` is good. `10, 20` is bad.
#
# Returns:
# parms: a list of parameters to be used in mySimulation(parms)
#
parms.names <- c("parm1", "parm2")
parms <- vector(mode = "list", length = length(parms.names))
names(parms) <- parms.names
overwrite <- list(...)
overwrite.names <- names(overwrite)
if (model == "default") {
parms$parm1 <- 0
parms$parm2 <- 0
} else if (model == "mymodel") {
parms$parm1 <- 1
parms$parm2 <- 2
}
if (length(overwrite) != 0) {
parms[overwrite.names] <- overwrite
}
return(parms)
}
I think if you know the combination of parameters to be used for each model, then it is better to create a data frame of model names and parameters as shown below
# create a data frame with model names and parameters
# NOTE: i am assuming all models have equal number of parameters
# if they are unequal, then store as list of models
model = c('default', 'mymodel');
parm1 = c(0.5, 0.75);
parm2 = c(1, 2);
models.df = data.frame(model, parm1, parm2)
You can now simulate any of the models by passing it as an argument to your mySimulation function. I have used a dummy simulation example, which you can replace with your code.
# function to run simulation based on model name
mySimulation = function(model = 'default'){
# find row corresponding to model of interest
mod.row = match(model, models.df$model)
# extract parameters corresponding to model
parms = models.df[mod.row, -1]
# run dummy simulation of drawing normal random variables
sim.df = rnorm(100, mean = parms[,1], sd = parms[,2])
return(sim.df)
}
If you now want to run all your simulations in one step, you can use the excellent plyr package and invoke
library(plyr)
sim.all = ldply(models.df$model, mySimulation)
If each of your simulations returns unequal number of values then you can use the function llply instead of ldply.
If you provide more information about the return values of your simulation and details on what it does, this code can be easily tweaked to get what you want.
Let me know if this works
If the simulation function always takes the same set of arguments, then Ramnath's approach of storing them in a data frame is best. For the more general case of variable inputs to mySimulation, you should store each set of inputs in a list – probably using a list of lists for running several simluations.
The idea behind your createParms function looks sound; you can simplify the code a little bit.
createParms <- function(model = "default", ...)
{
#default case
parms <- list(
parm1 = 0,
parm2 = 0
)
#other special cases
if(model == "mymodel")
{
parms <- within(parms,
{
parm1 <- 1
parm2 <- 2
})
}
#overwrite from ...
dots <- list(...)
parms[names(dots)] <- dots
parms
}
Test this with, e.g.,
createParms()
createParms("mymodel")
createParms("mymodel", parm2 = 3)
do.call may come in handy for running your simulation, as in
do.call(mySimulation, createParms())
EDIT: What do.call does for you
If you have parms <- createParms(), then
do.call(mySimulation, parms)
is the same as
with(parms, mySimulation(parm1, parm2))
The main advantage is that you don't need to spell out each parameter that you are passing into mySimulation (or to modify that function to accept the parameters in list form).