*several*diffrent statistics of the data. For example assume that we want to calculate minimum, maximum and mean value of each variable in data frame.

The simplest solution for this is to write a function that does all the calculations and returns a vector. The sample code is:

multi.fun

**<-****function****(**x**)****{** c

**(**min**=**min**(**x**)**, mean**=**mean**(**x**)**, max**=**max**(**x**))****}**

> sapply

**(**cars, multi.fun**)** speed dist

min 4.0 2.00

mean 15.4 42.98

max 25.0 120.00

However, when I work in interactive mode I would prefer to have a function that would accept

*multiple*functions as arguments. I came up with the following solution to this problem:

multi.sapply

**<-****function****(**...**)****{** arglist

**<-**match.call**(**expand.dots**=****FALSE****)$**... var.names

**<-**sapply**(**arglist, deparse**)** has.name

**<-****(**names**(**arglist**)****!=**""**)** var.names

**[**has.name**]****<-**names**(**arglist**)[**has.name**]** arglist

**<-**lapply**(**arglist, eval.parent, n**=**2**)** x

**<-**arglist**[[**1**]]** arglist

**[[**1**]]****<-****NULL** result

**<-**sapply**(**arglist,**function****(**FUN, x**)**sapply**(**x, FUN**)**, x**)** colnames

**(**result**)****<-**var.names**[-**1**]** return

**(**result**)****}**

> multi.sapply

**(**cars, min, mean, max**)** min mean max

speed 4 15.40 25

dist 2 42.98 120

If function argument is given name it will be used as column name instead of deparsed expression. This functionality is shown by the following example summarizing several statistics of EuStockMarkets data set:

> log.returns

**<-**data.frame**(**diff**(**log**(**EuStockMarkets**)))**> multi.sapply

**(**log.returns, sd, min,> VaR10

**=****function****(**x**)**quantile**(**x, 0.1**))** sd min VaR10

DAX 0.010300837 -0.09627702 -0.010862458

SMI 0.009250036 -0.08382500 -0.009696908

CAC 0.011030875 -0.07575318 -0.012354424

FTSE 0.007957728 -0.04139903 -0.009139666

Very cool. It would be really helpful if you added comments in your multi.sapply() function to explain what each step is doing.

ReplyDeleteNice idea.

ReplyDeleteI think your code could be simple with a first argument: function(x,...)

Then you could easily check the mode of x and have i work for vectors also.

if(mode(x) == "list") { what you do now} else

{a bit simpler}

Nice!!! Im gonna use it, thanks

ReplyDeleteEran

Steen,

ReplyDeleteI ommit naming first argument because then this name would not be allowed as name for function. For example if function was defined as

function(x,...)then call likemulti.sapply(1:10, x = function(x) paste("call", x))would not work properly.

Also this code shows that function works properly on vectors as its result is:

x

[1,] "Call: 1"

[2,] "Call: 2"

[3,] "Call: 3"

[4,] "Call: 4"

[5,] "Call: 5"

[6,] "Call: 6"

[7,] "Call: 7"

[8,] "Call: 8"

[9,] "Call: 9"

[10,] "Call: 10"

Erik,

ReplyDeleteThe code works as follows:

# extract function arguments as list

arglist <- match.call(expand.dots = FALSE)$...

# deparses the expressions defining

# arguments as given in multi.apply call

var.names <- sapply(arglist, deparse)

# if any argument was given name then its name is nonempty

# if no argument names were given then has.name is NULL

has.name <- (names(arglist) != "")

# for all arguments that had name substitue deparsed

# expression by given name

var.names[has.name] <- names(arglist)[has.name]

# now evaluate the expressions given in arguments

# go two generations back as we apply eval.parent

# witinh lapply function

arglist <- lapply(arglist, eval.parent, n = 2)

# first argument contains data set

x <- arglist[[1]]

# and here we remove it from the list

arglist[[1]] <- NULL

# we use sapply twice - outer traverses functions and inner data set

# because x is a defined argument name in sapply definition

# we have to reorder arguments in function (FUN, x)

result <- sapply(arglist, function (FUN, x) sapply(x, FUN), x)

# in defining column names

# we remove first element as it was name of data set argument

colnames(result) <- var.names[-1]

return(result)

In package doBy there is function summaryBy although function should be defined before function call.

ReplyDeleteVar10 <- function(x) quantile(x,0.1)

summaryBy(.~1,data=cars,FUN=c(mean,sd,min,VaR10))

Nice piece of code. You inspired me to also have a go at it. I tried to avoid writing a function myself and ended up using the reshape and plyr packages:

ReplyDeletelibrary(reshape)

library(plyr)

ddply(melt(cars), .(variable), summarise, min = min(value), mean = mean(value), max = max(value))

Although your solution is much more elegant for interactive use.

...and the result looks like this:

ReplyDeletevariable min mean max

1 speed 4 15.40 25

2 dist 2 42.98 120

In plyr there is function each to combine multiply functions into one.

ReplyDeletesapply(cars,each(mean, sd, min, max,

Var10=function(x)

unname(quantile(x,0.1))))

I've learned so much with this post. Thanks for share you knowledge.

ReplyDelete