This question already has answers here:
How to use R's ellipsis feature when writing your own function?
(5 answers)
Split up `...` arguments and distribute to multiple functions
(4 answers)
Closed 6 years ago.
Is there a way to pass arbitrary arguments to more than one command inside a function? The following function clearly does not work but I hope it explains what I am trying to achieve.
test = function(x = rnorm(20), y = rnorm(20), ..., ---){
plot(x, y, type = "p", ...)
lines(x, y, ---)
}
The goal is to be able to write a function that creates plot with say lines and points and polygon and can take arbitrary arguments for each command and pass them to the respective commands without me having to explicitly specify arguments for each command.
Here is a hackish approach:
.. <- "/////" #or anything which won't be used as a valid parameter
f <- function(...){
arguments <- list(...)
if(.. %in% arguments){
i <- which(arguments == ..)
terms <- unlist(arguments[1:(i-1)])
factors <- unlist(arguments[(i+1):length(arguments)])
c(sum(terms),prod(factors))
}
}
Then, for example,
> f(2,3,4,..,7,8,10)
[1] 9 560
You could obviously extend the idea to multiple ... fields, each delimited with ..
OPTION 1
Function
test = function(x = rnorm(20), y = rnorm(20), plot_options = NA, ...){
if (is.na(plot_options) == FALSE){
eval(parse(text = paste0("plot(x, y, ", plot_options, ")")))
} else {
plot(x, y, type = "n")
}
lines(x, y, ...)
}
USAGE
test()
set.seed(42)
m = rnorm(20)
n = rnorm(20)
test(x = m, y = n,
plot_options = "type = 'p', col = 'red', pch = 19, xlab = 'Test Plot', ylab = 'Y-axis'")
OPTION 2 (#Gregor's Solution)
Function
test2 = function(x = rnorm(20), y = rnorm(20), ..., line_options){
plot(x, y, ...)
if (missing(line_options)) {
lines(x, y)
} else {
do.call(lines, c(list(x = x, y = y), line_options))
}
}
USAGE
par(mfrow = c(2, 2), mar = c(2, 2, 1, 1))
test2(main = 'default')
test2(line_options = list(lty = 2), main = 'line')
test2(col = 'red', main = 'plot')
test2(col = 'red', line_options = list(lty = 2, col = 'blue'), main = 'line and plot')
Related
I wonder how you can simplify these two :
plot (payroll,wins)
id = identify(payroll, wins,labels = code, n = 5)
plot (payroll,wins)
with(data, text(payroll, wins, labels = code, pos = 1, cex=0.5))
using other alternatives - pch() dan as.numeric()?
Not sure it's easier but you change pch during identification as below (taken from the R-help). Every time you click empty point change to filled-in dot.
# data simulation
data <- data.frame(payroll = rnorm(10), wins = rnorm(10), code = letters[1:10])
identifyPch <- function(x, y = NULL, n = length(x), plot = FALSE, pch = 19, ...)
{
xy <- xy.coords(x, y)
x <- xy$x
y <- xy$y
sel <- rep(FALSE, length(x))
while (sum(sel) < n) {
ans <- identify(x[!sel], y[!sel], labels = which(!sel), n = 1, plot = plot, ...)
if(!length(ans)) {
break
}
ans <- which(!sel)[ans]
points(x[ans], y[ans], pch = pch)
sel[ans] <- TRUE
}
## return indices of selected points
which(sel)
}
if(dev.interactive()) { ## use it
with(data, plot(payroll,wins))
id = with(data, identifyPch(payroll, wins))
}
I have a function called all.priors (see R code below). My goal is to get the x and y from the curve() call inside the for loop, and save these xs and ys as object h.
(I want to have 101 rows, and 2*length(d) columns in h. This way, each 2 columns, contain x and y from a curve() run in the for loop.)
Question:
how can I correctly save the xs and ys from the curve() call? [I get the error: incorrect number of subscripts on matrix]
all.priors = function(a, b, lo, hi, d, Bi = 55, n = 1e2){
h = matrix(NA, 101, 2*length(d))
for(i in 1:length(d)){
p = function(x) get(d[i])(x, a, b)
prior = function(x) p(x)/integrate(p, lo, hi)[[1]]
likelihood = function(x) dbinom(Bi, n, x)
posterior = function(x) prior(x)*likelihood(x)
h[i,] = curve(posterior, ty = "n", ann = FALSE, yaxt = "n", xaxt = "n", add = i!= 1, bty = "n")
}
}
#Example of use:
all.priors(lo = 0, hi = 1, a = 2, b = 3, d = c("dgamma", "dnorm", "dcauchy", "dlogis"))
You just need to carefully place the values in the matrix, and then return the matrix from your function. try this
all.priors = function(a, b, lo, hi, d, Bi = 55, n = 1e2){
h = matrix(NA, 101, 2*length(d))
for(i in 1:length(d)){
p = function(x) get(d[i])(x, a, b)
prior = function(x) p(x)/integrate(p, lo, hi)[[1]]
likelihood = function(x) dbinom(Bi, n, x)
posterior = function(x) prior(x)*likelihood(x)
cv <- curve(posterior, ty = "n", ann = FALSE, yaxt = "n", xaxt = "n", add = i!= 1, bty = "n")
h[,i*2-1] <- cv$x
h[,i*2] <- cv$y
}
h
}
all.priors(lo = 0, hi = 1, a = 2, b = 3, d = c("dgamma", "dnorm", "dcauchy", "dlogis"))
A different way to solve this might be to save the answers in a list rather than a matrix. I think that your function complicates the picture of what is going on, so I will use a simpler example.
h = list()
for(i in 1:5) {
h[i] = list(curve(sin(i*x), xlim=c(0,6.3))) }
The resulting data structure should be easy to use.
He is my dataframe and its plot
my_df <- data.frame(var_1= as.factor(sample(c(0,1), 10, replace = TRUE)),
var_2 = sample(1:20, 10, replace = TRUE),
var_3 = as.factor(sample(c('a','b', 'c'), 10, replace = TRUE)))
plot(my_df)
So factors are getting converted to numeric values. How can I figure out map between factor value and its numeric representation? For example it looks like var_3 have the following conversion is in place a -> 1, b -> 2, c -> 3.
Also can I display this conversion map on the graph?
plot.data.frame passes ... arguments to ?pairs which has a labels argument
my_df <- data.frame(var_1= as.factor(sample(c(0,1), 10, replace = TRUE)),
var_2 = sample(1:20, 10, replace = TRUE),
var_3 = as.factor(sample(c('a','b', 'c'), 10, replace = TRUE)))
plot(my_df, labels = LETTERS[1:3])
So just get a vector of labels and use that
f <- function(data, default = names(data), use.varname = TRUE) {
default <- rep_len(default, ncol(data))
sapply(seq_along(data), function(ii) {
x <- data[, ii]
if (is.factor(x)) {
lbl <- paste(levels(x), seq.int(nlevels(x)), sep = ' -> ', collapse = '\n')
if (use.varname)
paste(default[ii], lbl, sep = '\n') else lbl
} else default[ii]
})
}
f(my_df, use.varname = FALSE)
# [1] "0 -> 1\n1 -> 2" "var_2" "a -> 1\nb -> 2\nc -> 3"
f(my_df, use.varname = TRUE)
# [1] "var_1\n0 -> 1\n1 -> 2" "var_2" "var_3\na -> 1\nb -> 2\nc -> 3"
plot(my_df, labels = f(my_df))
A better option might be to use ggpairs that gives more information
library(GGally)
ggpairs(my_df)
The integers corresponds to the levels of your factor. If you want a scatterplot as that reported in your example, you can simply set the labels for your axis using the axis function.
plot(as.numeric(my_df$var_3), as.numeric(as.vector(my_df$var_1)), axes = F)
axis(side = 1, labels = levels(my_df$var_3), at = 1:length(levels(my_df$var_3)))
axis(side = 2)
box()
Now, if you want a multi-plot result, you can do as follows.
par(mfrow=c(3,3))
for (i in 1:ncol(my_df)){
for (j in 1:ncol(my_df)){
if (i == j) {
plot(1, cex = 0, ylim = c(0,2), xlim = c(0,2))
text(1, 1, labels = paste(names(my_df)[j]))
} else {
plot(as.numeric(my_df[,i]), as.numeric(my_df[,j]), axes = F,
xlab = names(my_df)[i], ylab = names(my_df)[j])
if (is.factor(my_df[,i])){
axis(side = 1, labels = levels(my_df[,i]), at = 1:length(levels(my_df[,i])))
} else {
axis(side = 1)
}
if (is.factor(my_df[,j])){
axis(side = 2, labels = levels(my_df[,j]), at = 1:length(levels(my_df[,j])))
} else {
axis(side = 2)
}
box()
}
}
}
par(mfrow=c(1,1))
Definitely verbose and not very pretty, but as you can see the variable levels (class names) are retained and plotted at the axis ticks...
How do I label the y-axis, using timeSeries::plot, with Greek letters? i.e. change SB, SP, etc. to \alpha, \beta etc., I'm am aware I need expression(), in some way. However I can't even get to the labels (I normally use ggplot2). Code below.
# install.packages("xtable", dependencies = TRUE)
library("timeSeries")
## Load Swiss Pension Fund Benchmark Data -
LPP <- LPP2005REC[1:12, 1:4]
colnames(LPP) <- abbreviate(colnames(LPP), 2)
finCenter(LPP) <- "GMT"
timeSeries::plot(LPP, type = "o")
It have been pointed out that the object structure, obtained with str(), is quite particular in LPP compared to say this object z
z <- ts(matrix(rnorm(300), 100, 3), start = c(1961, 1), frequency = 12)
plot(z)
If any one has an answer to both or any I would appreciate it. I realize I can convert the data and plot it with ggplot2, I have seen that here on SO, but I am interested in doing in directly on the timeSeries object LPP and the stats (time-series object) z
[ REVISION & Edited ]
When plot.type is "multiple", we can't define ylab directly. Both plot(ts.obj) (S3 method) and plot(timeSeries.obj) (S4 method) take colnames(obj) as ylab, and I don't know any methods of using Greek letters as colname. (The difference in structure mainly comes from the difference of S3 and S4; colnames(timeSeries.obj) is equivalent to timeSeries.obj#units; the defaults is Series i and TS.i).
We can step in ylab using the arugument, panel (It wants a function and the default is lines). It is used in for(i in 1:ncol(data)). I couldn't give panel.function a suitable "i" (I guess it can in some way, but I didn't think up), so I got "i" using which col the data matches.
for timeSeries
ylabs <- expression(alpha, beta, gamma, delta)
row1 <- LPP[1,]
timeSeries.panel.f <- function(x, y, ...) {
lines(x, y, ...)
mtext(ylabs[which(row1 %in% y[1])], 2, line = 3)
}
plot(LPP, panel = timeSeries.panel.f, type = "o", ann = F)
title("Title")
mtext("Time", 1, line = 3)
## If you aren't so concerned about warnings, here is more general.
## (Many functions read `...` and they return warnings).
timeSeries.panel.f2 <- function(x, y, ..., ylabs = ylabs, row1 = row1) {
lines(x, y, ...)
mtext(ylabs[which(row1 %in% y[1])], 2, line = 3)
}
plot(LPP, panel = timeSeries.panel.f2, type = "o", ann = F,
ylabs = expression(alpha, beta, gamma, delta), row1 = LPP[1,])
title("Title")
mtext("Time", 1, line = 3)
for ts
ylabs <- expression(alpha, beta, gamma)
row1 <- z[1,]
ts.panel.f <- function(y, ...) {
lines(y, ...)
mtext(ylabs[which(row1 %in% y[1])], 2, line = 3)
}
plot(z, panel = ts.panel.f, ann = F)
title("Title")
mtext("Time", 1, line = 3)
Of course you can archieve it using new functions made from the original (mostly the same as the original). I showed only the modified points.
modified plot(ts.obj) (made from plot.ts)
my.plot.ts <- function(~~~, my.ylab = NULL) {
:
nm <- my.ylab # before: nm <- colnames(x)
:
}
# use
my.plot.ts(z, my.ylab = expression(alpha, beta, gamma), type = "o")
modified plot(timeSeries.obj)
# made from `.plot.timeSeries`
my.plot.timeSeries <- function(~~~, my.ylab = NULL) {
:
my.plotTimeSeries(~~~, my.ylab = my.ylab)
}
# made from `timeSeries:::.plotTimeSeries`
my.plotTimeSeries <- function(~~~, my.ylab) {
:
nm <- my.ylab # before: nm <- colnames(x)
:
}
#use
my.plot.timeSeries(LPP, my.ylab = expression(alpha, beta, gamma, delta), type="o")
I need to intercept the value of an optional xlim in a function so that I can change the units of it before plotting. The following function confirms that xlim was passed, but I can't access the value.
foo <- function(x, y, ...) {
if ("xlim" %in% names(list(...))) {
print(xlim) # not found/can't use value!
}
# modify xlim and pass to plotting functions
return()
}
But foo(x = 1:5, y = 1:5, xlim = c(2,4)) gives:
Error in print(xlim) : object 'xlim' not found
What trick do I need use the value? Seems like it should just work, but I see from looking around on SO that the dots can be vexing. I've played a bit with exists, deparse etc but I don't really 'get' the proper use of those functions.
EDIT: so here is the final snippet which was the leanest way to access the value:
dots <- list(...)
if (any(names(dots) == "xlim")) {
xlim <- dots$xlim
print(xlim)
}
This is because xlim is actually a list element, and is not (yet) an actual object in the function's environment. You could do
foo <- function(x, y, ...) {
m <- match.call(expand.dots = FALSE)$...
if(any(names(m) == "xlim")) m[["xlim"]]
else stop("no xlim value")
}
foo(x = 1:5, y = 1:5, xlim = c(2,4))
# c(2, 4)
foo(x = 1:5, y = 1:5, ylim = c(2,4))
# Error in foo(x = 1:5, y = 1:5, ylim = c(2, 4)) : no xlim value
You can see what match.call is doing if we examine the function as
f <- function(x, y, ...) {
match.call(expand.dots = FALSE)$...
}
It is a list of all the entered dot arguments with their respective expressions, so there are many different ways to get the values, the above is just one way.
f(x = 1:5, y = 1:5, xlim = c(2,4))
# $xlim
# c(2, 4)
Alternatively, you could do
g <- function(x, y, ...) {
dots <- list(...)
any(names(dots) == "xlim")
}
g(x = 1:5, y = 1:5, xlim = c(2,4))
# [1] TRUE
Also keep in mind that match.call keeps the argument as an unevaluated call, while list(...) evaluates the argument. This might be important for you passing the argument to other functions.