R/table.Performance.R

#' Generate general performance table for returns 
#' 
#' Main function to produce summary table. user can 
#' choose a set of metrics and corresponding optional arguments, 
#' modify metricsnames in output table, all together in data editor 
#' window from R console. For example, to include SharpeRatio, 
#' inside the data editor window, locate the row with SharpeRatio, 
#' and change "include" column from 0 to 1 to include the metric, and then 
#' change its optional arguments on the right that not with "#" sign. 
#' "#" sign simply means the argument (column index) is not defined 
#' for this metric (row index). 
#' @param R an xts, vector, matrix, data frame, timeSeries or zoo object of
#' asset returns
#' @param metrics a character vector of input metrics, use table.Performance.pool() to see 
#' all the condicate metrics
#' @param metricsNames options argument to specify metricsNames, default is NULL, 
#' the same as the metrics
#' @param interactive logical, default is TRUE, optional argument to trigger data editor window
#' @param arg.list optional argument to specify input optional argument for each metric, uses 
#' only interactive=FALSE
#' @param digits optional argument to specify the significant digits in printed table, default is 4
#' @param latex logical, default is FALSE, optional argument to output latex code
#' @param exportFun logical, default is NULL, optional argument to export function, see details
#' @param exportXLS logical, default is FALSE, optional argument to export resulting table to excel file
#' @param ExcelFileName The name of the Excel file to be created, default is "PerformanceReport.XLSX"
#' @details use \code{table.Performance.pool} to check available metrics. recoded SharpeRatio.
#' Both interactive and fixed input on metric set and optional arguments. Output latex code for resulting table. Export function that uses the same metrics and optional argument from interactive input. 
#' @author Kirk Li  \email{kirkli@@stat.washington.edu} 
#' @seealso \code{\link{table.Performance.pool}},\code{\link{table.Performance.pool.cran}},\code{\link{table.Arbitrary}}
#' @keywords table metrics performance measure
#' @examples
#'  \dontrun{
#' data(edhec)
#' 
#' # Example 1: start with NULL specification
#' res <- table.Performance(R=edhec,verbose=T, interactive=TRUE)
#'
#' # Example 2: start with Var and ES
#' res.ex2 <- table.Performance(edhec,metrics=c("VaR", "ES"), 
#' metricsNames=c("Modified VaR","Modified Expected 
#' Shortfall"),interactive=FALSE, verbose=T)
#' 
#' # Example 3: Non-interactive
#' arg.list <- list(
#' 		ES=list(method=c("modified","test"),
#' 				p=0.9),
#' 		VaR=list(method=c("gaussian"))
#' )
#' res.ex3 <- table.Performance(R=edhec,metrics=c("VaR", "ES"), interactive=FALSE,
#'  arg.list=arg.list, verbose=T, digits=4)
#' 
#' # Example 4: Latex code 
#' arg.list <- list(
#' 		ES=list(method=c("modified"),
#' 				p=0.9),
#' 		VaR=list(method=c("gaussian"))
#' )
# 
#' res.ex4 <- table.Performance(R=edhec,metrics=c("VaR", "ES"), interactive=FALSE, 
#' arg.list=arg.list, verbose=T, digits=4, latex=TRUE)
#'
#' # Example 5: Export function 
#' res.ex5 <- table.Performance(R=edhec,metrics=c("VaR", "ES"), interactive=TRUE, verbose=T,
#' digits=4, latex=FALSE, exportFun="myfun1", flag.pre.arg.list=FALSE)
#' myfun1(R=edhec)  
#' # myfun1 uses res.ex5's metrics and optional arguments 
#' args(myfun1)
#' 
#' # Example 6: Export XLSX 
#' res.ex6 <- table.Performance(R=edhec,metrics=c("VaR", "ES"), interactive=FALSE, 
#' arg.list=arg.list, verbose=T, digits=4, latex=TRUE, exportXLS=TRUE,ExcelFileName="PerformanceReport.xls")
#' }
#' 
#' @export
table.Performance <-
		function(R,metrics=NULL,metricsNames=NULL, verbose=FALSE, interactive=TRUE, arg.list=NULL, digits=4, latex=FALSE, exportFun=NULL, exportXLS=FALSE, ExcelFileName="PerformanceReport.xls",flag.pre.arg.list=FALSE,...){
	# FUNCTION: 47-1 different metrics
	pool <- table.Performance.pool()
	
#	extract metric functions' arguments
	ArgFormals <- lapply(pool,function(x)formals(x))
	ArgNames <- lapply(ArgFormals,function(x)names(x))
	ArgString.temp <- unique(unlist(ArgNames))
	ArgString <- sort(ArgString.temp[-which(ArgString.temp%in%c("R","x","..."))])	
	
	metrics.vec <- data.frame(
			metrics=pool,
			include=rep(0,length(pool)),
			metricsNames=pool,
			stringsAsFactors=FALSE)
	
	
#	loop through each metric and input the default values of args
	for (i in paste0("arg_",ArgString))
		eval(parse(text=paste0("metrics.vec$",i,"<- '#'")))
	
	
	for (i in 1:length(pool)){
#		i=1
		ArgFormals.i <- ArgFormals[[i]]
		ArgNames.use.i <- names(ArgFormals.i)
		
		for (ii in ArgString){
#		ii=ArgString[1]
			if(any(ArgNames.use.i%in%ii)){
				temp <- ArgFormals.i[which(ArgNames.use.i==ii)]
				temp <- ifelse(class(temp[[1]])%in%c("call","NULL"),as.character(temp),temp) 
				metrics.vec[i,paste0("arg_",ii)] <- temp
			}  
		}
	}
#	promote the order of pre-specified metric
	if(length(metrics)>0){
		metrics.vec$include[match(metrics,metrics.vec$metrics)] <- 1
		if(is.null(metrics.vec$metricsNames))
			metrics.vec$metricsNames[match(metrics,metrics.vec$metrics)] <- metricsNames
		
		metrics.vec <- metrics.vec[order(metrics.vec$include,decreasing=T),]
	}
#	open data editor	
	if(interactive & !is.null(arg.list))
		stop("Error: either uses interactive method or user input ArgList, not both")
	# TODO: some improvement can be made here
	
	if(interactive){
		metrics.vec <- fix(metrics.vec) #allow user to change the input
	} 
#   process the selected metrics and args	
	metrics.choose <- subset(metrics.vec,include==1)

# solution for same metric multiple times 
	if(nrow(metrics.choose)!=length(metrics)){
		metrics.choose <- do.call("rbind",lapply(1:nrow(metrics.choose),function(ii){do.call("rbind", replicate(table(metrics)[ii], metrics.choose[ii,], simplify = FALSE))}))
	}
	
	metrics.choose <- metrics.choose[match(metrics,metrics.choose$metrics),]
	
	
	if(nrow(metrics.choose)==0) {print("please specify as least one metric") 
		return()}
	colnames(metrics.choose) <- gsub("arg_","",colnames(metrics.choose))
	
	metrics <- as.character(metrics.choose$metrics)
	
	if(is.null(metricsNames))
		metricsNames <-  as.character(metrics.choose$metricsNames)
	
	metricsOptArgVal <- 
			lapply(1:nrow(metrics.choose[,-c(1:3),drop=FALSE]),function(x){
#						x=2
						xx <- metrics.choose[x,-c(1:3),drop=FALSE]
						xx[is.na(xx)] <- "NA"
						xy <- as.vector(xx[xx!='#'])
						names(xy) <-  names(xx)[xx!='#']
						xy})
	
	
	names(metricsOptArgVal) <- metrics
	
	
	if(!is.null(arg.list)){
		if(!is.list(arg.list)) stop("Input argument arg.list should be a list")
		if(length(setdiff(names(arg.list),names(metricsOptArgVal)))!=0) 
			stop(paste("Mismatch: input argument arg.list for",paste(names(arg.list),collapse=","), ",  but input metrics are",paste(names(metricsOptArgVal),collapse=",")))
		if(!all(unlist(lapply(arg.list,names)) %in% unlist(lapply(metricsOptArgVal,names))))
			stop("Input argument arg.list doesn't match with argument metrics")
		metricsOptArgVal <- NULL
		
		# Extract the first argument and if it is NULL, replace with _NULL_
		arg.list <- lapply(arg.list,function(x){
					lapply(x,function(xx){
								t1 <- xx[1]
								if (is.null(t1))
									t1 <-"_NULL_"	
								t1
							})
					
				})
		
		
		# Convert arg.list to metricsOptArgVal
		metricsOptArgVal <- lapply(arg.list,function(x){
					t1 <- unlist(x)
					t2 <- suppressWarnings(sapply(t1,function(xx){
										if(is.na(as.numeric(xx)))
											paste0("'",xx,"'")
										else {xx}
									}))
#			 expected warning when checking wheter input is numeric convertable or not
					names(t2) <- names(x)
					t2
				})
		
		#  replace _NULL_ with NULL
		metricsOptArgVal <- lapply(metricsOptArgVal,function(x){
					lapply(x,function(xx){
								t1 <- xx[1]
								if (t1=="'_NULL_'")
									t1 <-"NULL"	
								t1
							})
					
				})
		
	} 
	
	if(flag.pre.arg.list & !is.null(arg.list)){
		cat("###################################","\n")
		cat("use previous stored optional argument\n")
		cat("###################################","\n")
	}
	
	
#   functions to call each metric function with user input args	
	table.Arbitrary.m <- function(...){
		y = checkData(R, method = "zoo")
		columns = ncol(y)
		rows = nrow(y)
		columnnames = colnames(y)
		rownames = rownames(y)
		Arg.mat <- list()
		for (column in 1:columns) {
			x = as.matrix(y[, column])
			values = vector("numeric", 0)
			for (k in 1:length(metrics)) {
				ArgString.i <- paste(names(metricsOptArgVal[[k]]),metricsOptArgVal[[k]],sep=" = ")
				
				ArgString.i <- paste(ArgString.i,collapse =", ")
				
				Arg.mat[[k]] <- ArgString.i
				
				if(length(ArgString.i)>0 & nchar(ArgString.i)>0)
					newvalue = eval(parse(text=paste0("apply(x, MARGIN = 2, FUN = metrics[k],",ArgString.i,")"))) else
					newvalue = apply(x, MARGIN = 2, FUN = metrics[k]) #...
				values = c(values, newvalue)
			}
			if (column == 1) {
				resultingtable = data.frame(Value = values, row.names = metricsNames)
			}
			else {
				nextcolumn = data.frame(Value = values, row.names = metricsNames)
				resultingtable = cbind(resultingtable, nextcolumn)
			}
		}
		names(Arg.mat) <- metrics
		colnames(resultingtable) = columnnames
		return(list(resultingtable=round(resultingtable,digits),
						Arg.mat=Arg.mat))
	}
	
#	generating the table	
	res <- table.Arbitrary.m()
#	res <- table.Arbitrary.m(...)
#	show printout	
	
	# convert Arg.mat to arg.list
	arg.list.string <- paste("list(",paste(metrics,"=",paste("list(",res$Arg.mat,")"),collapse=","),")")
	# save arg.list to arg.list.p
	eval(parse(text=paste("arg.list.p <- ",arg.list.string)))
	# take the first argument
	res$Arg.List <- lapply(arg.list.p,function(x){
				lapply(x,function(xx){
							t1 <- xx[1]
							if (is.null(t1))
								t1 <-"NULL"	
							t1
						})
				
			})
	
	res$Arg.mat <- NULL
	if(verbose){
		cat("###################################","\n")
		cat("metrics:\n")
		print(metrics)
		cat("###################################","\n")
		cat("metricsNames:\n")
		print(metricsNames)
		cat("###################################","\n")
		cat("arg.list:\n")
		cat("Attention: for more than one element in args, \n only the first one will be used","\n")
		print(res$Arg.List)
		cat("###################################","\n")
		cat("table:\n")
		print(res$resultingtable)
	}
	
	if(latex){
		require(xtable)
		cat("###################################","\n")
		cat("Latex code:\n")
		print(xtable(res$resultingtable,digits=digits,...))
	}
	
	if(!is.null(exportFun)){
		if(!is.character(exportFun))
			warning("exportFun is mis-specified")
		else{
			cat("###################################","\n")
			cat(paste0("Exporting function ",exportFun,"\n"))
			cat("###################################","\n")
			
			string1 <- paste(exportFun,"<<-","function(R,metrics=",'c(',paste(paste0("'",metrics,"'"),collapse=','),')',", metricsNames=",'c(',paste(paste0("'",metricsNames,"'"),collapse=','),')', ", verbose=FALSE, interactive=FALSE, arg.list=",arg.list.string,", digits=4, latex = FALSE, exportFun=NULL,...)")
			
			string2 <- paste("{table.Performance(R=R,metrics=metrics,metricsNames=metricsNames,verbose=verbose,interactive=interactive,arg.list=arg.list,digits=digits,latex=latex,exportFun=exportFun,flag.pre.arg.list=TRUE,...)}")
			
			
			eval(parse(text=paste0(string1,string2)))
			
			cat(paste0(string1,string2),"\n")
			cat("###################################","\n")
			
			metricsOptArgVal.export <<- metricsOptArgVal
			
		}
		
	}
	
	if(exportXLS){
		cat("###################################","\n")
		cat(paste0("Exporting to Excel file ",ExcelFileName,"\n"))
		cat("###################################","\n")
		require(WriteXLS)
		temp <- res$resultingtable
		WriteXLS("temp",row.names = TRUE,ExcelFileName=ExcelFileName )
		
	}
	return(res)
}







table.Performance.input.shiny <- function(metrics=NULL,metricsNames=NULL, verbose=FALSE,...){
	# FUNCTION: 47-1 different metrics
	
#	extract metric functions' arguments
	ArgFormals <- lapply(metrics,function(x)formals(x))
	ArgNames <- lapply(ArgFormals,function(x)names(x))
	ArgString.temp <- unique(unlist(ArgNames))
	ArgString <- sort(ArgString.temp[-which(ArgString.temp%in%c("R","x","..."))])	
#	ArgNames.use <- lapply(ArgNames,function(x)ArgString.temp[ArgString.temp%in%x])
#	ArgValue <- lapply(ArgNames,function(x)ArgString[ArgString%in%x])
	
	metrics.vec <- data.frame(
			metrics=metrics,
			include=rep(0,length(metrics)),
			metricsNames=metrics,
			stringsAsFactors=FALSE)
	
	
#	loop through each metric and input the default values of args
	for (i in paste0("arg_",ArgString))
		eval(parse(text=paste0("metrics.vec$",i,"<- '#'")))
	
	
	for (i in 1:length(metrics)){
#		i=1
		ArgFormals.i <- ArgFormals[[i]]
		ArgNames.use.i <- names(ArgFormals.i)
		
		for (ii in ArgString){
#		ii=ArgString[1]
			if(any(ArgNames.use.i%in%ii)){
				temp <- ArgFormals.i[which(ArgNames.use.i==ii)]
				temp <- ifelse(class(temp[[1]])%in%c("call","NULL"),as.character(temp),temp) 
				metrics.vec[i,paste0("arg_",ii)] <- temp
			}  
		}
	}
#	promote the order of pre-specified metric
	if(length(metrics)>0){
		metrics.vec$include[match(metrics,metrics.vec$metrics)] <- 1
		if(is.null(metricsNames)) metricsNames=metrics 
		metrics.vec$metricsNames[match(metrics,metrics.vec$metrics)] <- metricsNames
		metrics.vec <- metrics.vec[order(metrics.vec$include,decreasing=T),]
	}
#	open data editor	
#	metrics.vec <- fix(metrics.vec) #allow user to change the input
	
#   process the selected metrics and args	
	metrics.choose <- subset(metrics.vec,include==1)
	
	
# solution for same metric multiple times 
	if(nrow(metrics.choose)!=length(metrics)){
		metrics.choose <- do.call("rbind",lapply(1:nrow(metrics.choose),function(ii){do.call("rbind", replicate(table(metrics)[ii], metrics.choose[ii,], simplify = FALSE))}))
	}
	
	metrics.choose <- metrics.choose[match(metrics,metrics.choose$metrics),]
	
	
	if(nrow(metrics.choose)==0) {print("please specify as least one metric next time")
		return()}
	colnames(metrics.choose) <- gsub("arg_","",colnames(metrics.choose))
	metrics <- as.character(metrics.choose$metrics)
	
	if(is.null(metricsNames))
		metricsNames <-  as.character(metrics.choose$metricsNames)
#	metricsOptArg <- as.list(apply(metrics.choose[,-c(1:3)],1,function(x){
	##						x <- metrics.choose[1,-c(1:3)]
#						x[is.na(x)] <- "NA"
#						names(x)[x!='#']
#					}
#	))
#	metrics.choose[,-c(1:3),drop=FALSE]
	metricsOptArgVal <- 
			lapply(1:nrow(metrics.choose[,-c(1:3),drop=FALSE]),function(x){
#						x=2
						xx <- metrics.choose[x,-c(1:3),drop=FALSE]
						xx[is.na(xx)] <- "NA"
						xy <- as.vector(xx[xx!='#'])
						names(xy) <-  names(xx)[xx!='#']
						xy})
	names(metricsOptArgVal) <- metrics
	return(metricsOptArgVal)
}

#	names(metricsOptArg) <- metrics

#   functions to call each metric function with user input args	






table.Performance.output.shiny <- function(R,metricsOptArgVal, metrics=NULL,metricsNames=NULL, verbose=FALSE,...){
#	metrics=names(metricsOptArgVal)
	if(is.null(metricsNames))
		metricsNames=metrics
	table.Arbitrary.m <- function(...){
		y = checkData(R, method = "zoo")
		columns = ncol(y)
		rows = nrow(y)
		columnnames = colnames(y)
		rownames = rownames(y)
		Arg.mat <- list()
		for (column in 1:columns) {
#			 column=1
			x = as.matrix(y[, column])
			values = vector("numeric", 0)
			for (metric in metrics) {
#			 metric=metrics[1]
				ArgString.i <- paste(names(metricsOptArgVal[[metric]]),metricsOptArgVal[[metric]],sep=" = ")
#				ArgString.i[1] <- "p=0.9"
				Arg.mat[[metric]] <- ArgString.i
#			newvalue = apply(x, MARGIN = 2, FUN = metric)
				ArgString.i <- paste(ArgString.i,collapse =", ")
				if(length(ArgString.i)>0 & nchar(ArgString.i)>0)
					newvalue = eval(parse(text=paste0("apply(x, MARGIN = 2, FUN = metric,",ArgString.i,")"))) else
					newvalue = apply(x, MARGIN = 2, FUN = metric) #...
				values = c(values, newvalue)
			}
			if (column == 1) {
				resultingtable = data.frame(Value = values, row.names = metricsNames)
			}
			else {
				nextcolumn = data.frame(Value = values, row.names = metricsNames)
				resultingtable = cbind(resultingtable, nextcolumn)
			}
		}
		names(Arg.mat) <- metrics
		colnames(resultingtable) = columnnames
		rownames(resultingtable) = metricsNames
		return(list(resultingtable=resultingtable,
						Arg.mat=Arg.mat))
	}
	
#	table.Arbitrary.m()
	
#	generating the table	
	res <- table.Arbitrary.m(...)
#	show printout	
	if(verbose){
		cat("###################################","\n")
		cat("metrics:\n")
		print(metrics)
		cat("###################################","\n")
		cat("metricsNames:\n")
		print(metricsNames)
		cat("###################################","\n")
		cat("metricsOptArg:\n")
		cat("Attention: for more than one element in args, \n only the first one will be used","\n")
		print(res$Arg.mat)
		cat("###################################","\n")
		cat("table:\n")
		print(res$resultingtable)
	}
	res$resultingtable
	
}
kecoli/mpo documentation built on May 20, 2019, 8:34 a.m.