########################################### # This version: adjusted on 2022-07-09 # ########################################### # it includes all the auxiliary functions that should only be called inside as well as OUTSIDE the bms function ####################### # FUNCTIONS FOR USERS # ######################################################################### # SEE ALSO PLOTTING FUCNTIONS AT THE END # beta.draws.bma, pmp.bma, estimates.bma, info.bma # cf. as well the functions combine_chains & [.bma farther below # moreover: bin2hex, hex2bin, f21simple, fullmodel.ssq, is.bma, print.bma, summary.bma, print.topmod, is.topmod # plotting: plotModelsize, plotComp, plotConv, plotDensity, plot.bma, image.bma #' Coefficients of the Best Models #' #' Returns a matrix whose columns are the (expected value or standard #' deviations of) coefficients for the best models in a bma object. #' #' #' @param bmao a 'bma' object (as e.g. resulting from \code{\link{bms}}) #' @param stdev if \code{stdev=FALSE} then \code{beta.draws.bma} returns the #' (conditional) posterior expected values of the coefficients (i.e. 'Bayesian #' coefficients'). If \code{stdev=TRUE} it returns their posterior standard #' deviations. #' @return Each column presents the coefficients for the model indicated by its #' column name. The zero coefficients are the excluded covariates per model. #' Note that the coefficients returned are only those of the best (100) models #' encountered by the \code{bma} object (cf. argument \code{nmodels} of #' \code{\link{bms}}). #' #' For aggregate coefficients please refer to \code{\link{coef.bma}}. #' @note Note that the elements of \code{beta.draws.bma(bmao)} correspond to #' \code{bmao$topmod$betas()} #' @seealso \code{\link{bms}} for creating bms objects, \code{\link{coef.bma}} #' for aggregate coefficients #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' #' #sample a bma object: #' data(datafls) #' mm=bms(datafls,burn=500,iter=5000,nmodel=20) #' #' #coefficients for all #' beta.draws.bma(mm) #' #' #standard deviations for the fourth- to eight best models #' beta.draws.bma(mm[4:8],TRUE); #' #' @export beta.draws.bma <- function(bmao,stdev=FALSE) { # constructs a nice matrix of the betas of the best models stored in topmods # bmao: bma object if (!is.bma(bmao)) {stop("you need to provide a BMA object"); return()} resmat=.post.beta.draws(bmao$topmod, bmao$reg.names,FALSE) if (stdev) { mom2=.post.beta.draws(bmao$topmod, bmao$reg.names,TRUE) resmat=sqrt(mom2-resmat^2) } return(resmat) } #' Posterior Model Probabilities #' #' Returns the posterior model probabilites for the best models encountered by #' a 'bma' object #' #' A call to bms with an MCMC sampler (e.g. #' \code{bms(datafls,mcmc="bd",nmodel=100)} uses a Metropolis-Hastings #' algorithm to sample through the model space - and the frequency of how often #' models are drawn converges to the distribution of their posterior marginal #' likelihoods. While sampling, each 'bma' object stores the best models #' encountered by its sampling chain with their marginal likelihood and their #' MCMC frequencies. #' #' \code{pmp.bma} then allows for comparing the posterior model probabilities #' (PMPs) for the two different methods, similar to \code{\link{plotConv}}. It #' calculates the PMPs based on marginal likelihoods (first column) and the #' PMPs based on MCMC frequencies (second column) for the best x models stored #' in the bma object. #' #' The correlation of the two columns is an indicator of how well the MCMC #' sampler has converged to the actual PMP distribution - it is therefore also #' given in the output of \code{\link{summary.bma}}. #' #' The second column is slightly different in case the \code{\link{bms}} #' argument \code{mcmc} was set to \code{mcmc="enumeration"}: In this case, the #' second column is also based on marginal likelihoods. The correlation between #' the two columns is therefore one. #' #' @param bmao A bma object (see argument \code{nmodel} in \code{\link{bms}}), #' alternatively an object of class \code{\link{topmod}} #' @param oldstyle For normal use, leave this at \code{FALSE}. It is an #' argument for compatibility with older BMS versions - see section 'Notes' #' @return the result is a matrix, its row names describe the model binaries\cr #' There are two columns in the matrix: \item{PMP (Exact)}{posterior model #' probabilities based on the posterior likelihoods of the best models in #' \code{bmao} } \item{PMP (MCMC)}{posterior model probabilities of the best #' models in \code{bmao} based on their MCMC frequencies, relative to all #' models encountered by \code{bmao} - see 'Details' } #' @note The second column thus shows the PMPs of the best models relative to #' all models the call to \code{\link{bms}} has sampled through (therefore #' typically the second column adds up to less than one). The first column #' relates to the likelihoods of the best models, therefore it would add up to #' 1. In order estimate for their marginal likelihoods with respect to the #' other models (the ones not retained in the best models), these PMP aadding #' up to one are multiplied with the sum of PMP of the best models accroding to #' MCMC frequencies. Therefore, the two columns have the same column sum. #' #' CAUTION: In package versions up to \code{BMS 0.2.5}, the first column was #' indeed set always equal to one. This behaviour can still be mimicked by #' setting \code{oldstyle=TRUE}. #' #' @seealso \code{\link{plotConv}} for plotting \code{pmp.bma}, #' \code{\link{pmpmodel}} to obtain the PMP for any individual model, #' \code{\link{bms}} for sampling bma objects #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' #' ## sample BMA for growth dataset, MCMC sampler #' data(datafls) #' mm=bms(datafls[,1:10],nmodel=20, mcmc="bd") #' #' ## mmodel likelihoods and MCMC frequencies of best 20 models #' print(mm$topmod) #' #' pmp.bma(mm) #' #first column: posterior model prob based on model likelihoods, #' # relative to best models in 'mm' #' #second column: posterior model prob based MCMC frequencies, #' # relative to all models encountered by 'mm' #' #' #consequently, first column adds up to one #' #second column shows how much of the sampled model space is #' # contained in the best models #' colSums(pmp.bma(mm)) #' #' #' #correlation betwwen the two shows how well the sampler converged #' cor(pmp.bma(mm)[,1],pmp.bma(mm)[,2]) #' #' #is the same as given in summary.bma #' summary(mm)["Corr PMP"] #' #' #plot the two model probabilites #' plotConv(mm) #' #' #equivalent to the following chart #' plot(pmp.bma(mm)[,2], type="s") #' lines(pmp.bma(mm)[,1],col=2) #' #' #' #moreover, note how the first column is constructed #' liks=exp(mm$top$lik()) #' liks/sum(liks) #' pmp.bma(mm)[,1] #these two are equivalent #' #' #' #' #the example above does not converge well, #' #too few iterations and best models #' # this is already better, but also not good #' mm=bms(datafls[,1:10],burn=2000,iter=5000,nmodel=200) #' #' #' # in case the sampler has been 'enumeration' instead of MCMC, #' # then both matrix columns are of course equivalent #' mm=bms(datafls[,1:10],nmodel=512,mcmc="enumeration") #' cor(pmp.bma(mm)[,1],pmp.bma(mm)[,2]) #' colSums(pmp.bma(mm)) #' #' #' @export pmp.bma <- function(bmao, oldstyle=FALSE) { # constructs nice matrix with PMP analytical and PMP MCMC for best models stored in topmods # Prob of top "nmodel" models, analytical (after deleting rest of models)' # bmao: either the topmodel object or a "bma" object if (!(is.bma(bmao)||is.topmod(bmao))) stop("bmao needs to be a 'bma' object!") if (is.topmod(bmao)) { topmods=bmao was.enum=FALSE cumsumweights=sum(topmods$ncount()) log.null.lik=0 } else { topmods=bmao$topmod log.null.lik=(1-bmao$info$N)/2*log(as.vector(crossprod(bmao$arguments$X.data[,1]-mean(bmao$arguments$X.data[,1])))) cumsumweights=bmao$info$cumsumweights was.enum=(bmao$arguments$mcmc=="enum") } lt1=suppressWarnings(topmods$lik() - max(topmods$lik())) # do this to get only positive probabilities lt1=exp(lt1)/sum(exp(lt1)) if (was.enum) { lt2=exp(topmods$lik()-log.null.lik)/cumsumweights #Prob of top "nmodel" models, (loglik based) } else { lt2=topmods$ncount()/cumsumweights #MCMC Prob of top "nmodel" models, numerical } cpoint=min(length(lt1),length(lt2)) lt1=lt1[1:cpoint]; lt2=lt2[1:cpoint] if (!oldstyle) lt1 <- lt1*sum(lt2) #rbind the probs to the topmodmatrix topmodout=rbind(lt1, lt2) rownames(topmodout)=c("PMP (Exact)","PMP (MCMC)") colnames(topmodout)=topmods$bool() return(t(topmodout)) } #' Posterior Model Probability for any Model #' #' Returns the posterior model probability for any model based on bma results #' #' If the model as provided in \code{model} is the null or the full model, or #' is contained in \code{bmao}'s topmod object (cf. argument \code{nmodel} in #' \code{\link{bms}}), \cr then the result is the same as in #' \code{\link{pmp.bma}}.\cr If not and \code{exact=TRUE}, then \code{pmpmodel} #' estimates the model based on comparing its marginal likelihood (times model #' prior) to the likelihoods in the \code{topmod} object and multiplying by #' their sum of PMP according to MCMC frequencies, #' #' @param bmao A bma object as created by \code{\link{bms}}. #' @param model A model index - either variable names, or a logical with model #' binaries, or the model hexcode (cf. \code{\link{hex2bin}}, or a numeric with #' positions of the variables to be included. #' @param exact If \code{TRUE}, then the resulting PMP is based on analytical #' model likelihoods (works for any model). \cr If \code{FALSE}, the the #' resulting PMP is derived from MCMC frequencies (works only for the null and #' fullmodel, as well as for models contained in \code{bmao}'s topmod #' object.\cr If \code{bmao} is based on enumeration (cf. argument \code{mcmc} #' in \code{\link{bms}}, then \code{exact} does not matter. #' @return A scalar with (an estimate of) the posterior model probability for #' \code{model} #' @seealso \code{\link{pmp.bma}} for similar #' functions #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' #' ## sample BMA for growth dataset, enumeration sampler #' data(datafls) #' mm=bms(datafls[,1:10],nmodel=5) #' #' #show the best 5 models: #' pmp.bma(mm) #' #first column: posterior model prob based on model likelihoods, #' #second column: posterior model prob based MCMC frequencies, #' #' ### Different ways to get the same result: ######### #' #' #PMP of 2nd-best model (hex-code representation) #' pmpmodel(mm,"00c") #' #' #PMP of 2nd-best model (binary representation) #' incls=as.logical(beta.draws.bma(mm)[,2]) #' pmpmodel(mm,incls) #' #' #PMP of 2nd-best model (via variable names) #' #names of regressors in model "00c": #' names(datafls[,2:10])[incls] #' pmpmodel(mm,c("SubSahara", "LatAmerica")) #' #' #PMP of 2nd-best model (via positions) #' pmpmodel(mm,c(6,7)) #' #' ####PMP of another model ######### #' pmpmodel(mm,1:5) #' #' #' @export pmpmodel= function(bmao, model=numeric(0), exact=TRUE) { #returns the PMP of any model #if model is null, full or contained in topmod, returns the PMP as in pmp.bma #if model is out of topmod but bmao is enumeration, calculates the marg-lik and compares #if model is out of topmod and bmao is MCMC-sampled, then estimates PMP by comparing marg-lik and ncounts if (!is.bma(bmao)) stop("bmao needs to be a bma object") if (!is.vector(model)) stop("model needs to be vector denoting a single model.") K=bmao$info$K was.enum=(bmao$arguments$mcmc=="enum") emptyindex=logical(K) modelhex="" #Conversion of user input if (length(model)==0L) model=numeric(0) if ((is.character(model))&&(all(model %in% bmao$reg.names))) { mix=match(model, bmao$reg.names) if (any(is.na(mix))) stop("Provided variable names do not conform to bma object") emptyindex[mix]=TRUE; model=emptyindex } else if ((length(model)==1L)&&all(is.character(model))) { modelhex=model model=as.logical(hex2bin(model)); if (length(model)>K) model=model[-(1:(length(model)-K))] } else if (is.logical(model)||((length(model)==K)&&(is.numeric(model)&& max(model)<2))) { if (length(model)>K) model=model[-(1:(length(model)-K))] model=as.logical(model) } else if (is.numeric(model)) { emptyindex[model]=TRUE; model=emptyindex } else stop("model needs to be an integer, logical or character model index representation (hexcode or variable names)") if (any(is.na(model))) stop("Provided model index does not seem to exist.") if (modelhex=="") modelhex=bin2hex(model) # now model is a logical vector, modelhex its hex representation #last user check fixed.pos = bmao$mprior.info$fixed.pos if (is.null(fixed.pos)) fixed.pos=numeric(0) if (any(model[fixed.pos]!=TRUE)) stop("Such a model was excluded by bmao's argument fixed.reg") # prior calculations bools=bmao$topmod$bool() liks=bmao$topmod$lik() ncounts=bmao$topmod$ncount() cumsumweights=bmao$info$cumsumweights yty=as.vector(crossprod(bmao$arguments$X.data[,1,drop=TRUE]-mean(bmao$arguments$X.data[,1,drop=TRUE]))) log.null.lik= bmao$gprior.info$lprobcalc$just.loglik(ymy=yty,k=0) #look up whether model is in topmodels mix=match(modelhex,bools) # first treat case when MCMC sampler and exact =FALSE if ((!exact) && (!was.enum)) { if (!is.na(mix)) { return(ncounts[[mix]]/cumsumweights) } else if (!any(model)||all(model)) { return(bmao$info$k.vec[sum(model)+1]/cumsumweights) } else { stop("Model MCMC-based PMP cannot be found. Try exact=TRUE .") } } # now treat the cases where exact = TRUE or was.enum=TRUE if (!is.na(mix)) { loglik=liks[mix] #if yes and exact return its PMP } else if (was.enum && (!any(model)||all(model))) { loglik = log(bmao$info$k.vec[sum(model)+1])+log.null.lik # stuff in info is saved as sum(Lik_model / Lik_null) } else { if (!was.enum && (length(liks)==0L)) stop("bmao needs to contain more than 0 top models to provide an estimate for your model index.") if (sum(model)==0L) { loglik= log.null.lik + bmao$mprior.info$pmp(ki=0, mdraw=rep(0,K), ymy=yty) } else { zz=zlm(bmao$arguments$X.data[,c(TRUE,model),drop=FALSE], g=bmao$gprior.info) loglik=zz$marg.lik+bmao$mprior.info$pmp(ki=sum(model), mdraw=as.numeric(model), ymy=zz$olsres$ymy) } } if (was.enum) { return(exp(loglik-log.null.lik)/cumsumweights) } #estimate pmp of model compared to top models # then multply with a factor by comparing ncount and liks so to estimate overall pmp of model pmp_withintop=exp(loglik-log.null.lik)/sum(exp(liks-log.null.lik)) return(pmp_withintop*sum(ncounts)/cumsumweights) } #' @rdname coef.bma #' @export estimates.bma <- function(bmao,exact=FALSE,order.by.pip=TRUE,include.constant=FALSE,incl.possign=TRUE,std.coefs=FALSE,condi.coef=FALSE) { # constructs a nice estimates matrix with 5 columns: 1) PIP, 2) E(beta|Y), 3) Var(beta|Y), 4) pos. coef. sign (cond. on inclusion, optional), 5) Index in X.data # bmao: bma object; # exact: if True, then calcs posterior stats based on analytical PMPs (likelihoods) in topmods object; if False, then does it as a weighted average (typically with MCMC frequencies) # order.by.pip: if true then the matrix is sorted according to the PIPs (first column) # include.constant: if true, then add the constant in the last row of resultant matrix # incl.possign: if true, then the fouth column details how often the coefficent sign was positive (conditional on inclusion) # std.coefs: if true, then coefficents are standardized # condi.coefs: if true, then the coefficents and standard deviations are not given as overall expected values, but as expected values given inclusion if (!is.bma(bmao)) {stop("you need to provide a BMA object"); return()} if (exact) { #if (bmao$arguments$beta.save==FALSE) stop("exact=TRUE needs betas from the draws: Run estimation again and save betas via setting beta.save=TRUE") if (bmao$topmod$nbmodels==0) stop("exact=TRUE needs at least one 'top model': Run estimation again and set nmodel>0") } bmaest=.post.estimates(bmao$info$b1mo,bmao$info$b2mo,bmao$info$cumsumweights,bmao$info$inccount,bmao$topmod,bmao$arguments$X.data,bmao$reg.names,bmao$info$pos.sign,exact,order.by.pip,include.constant,incl.possign,std.coefs,condi.coef) return(bmaest) } #' @rdname summary.bma #' @export info.bma <- function(bmao) { # constructs an 'info' character matrix with 1 row and the following columns: # bmao: bma object # output: a list that contains information about the "Mean nr. of Regressors" (not counting the constant term), # "Draws"=posterior draws, "Burnins"=nr. of burnins taken, "Time" denotes total time elapsed # since calling the "bms" function, "Nr. of models visited" counts each time a model is accepted. # Note that we do not take into account the case of revisiting models by the sampler. # Modelspace is simply indicating the whole model space (2^K) and percentage visited is # the nr. of models visited as a percentage of 2^K. "Corr PMP" is the correlation between # analytic and MCMC posterior model probabilites, where a correlation of 0.99 indicates # excellent convergence. For "nmodel=100" the best 100 models are considered for the correlation # analysis. Finally, Nr. of Observations is given in the output as well. foo=bmao$info iter=foo$iter; burn=foo$burn; timed=foo$timed; models.visited=foo$models.visited; corr.pmp=foo$corr.pmp; K=foo$K; N=foo$N; msize=foo$msize; cumsumweights=foo$cumsumweights if (is.element("mprior.info",names(bmao))) { prior= paste(bmao$mprior.info$mp.mode, "/", bmao$mprior.info$mp.msize) } else { if (is.element("theta",names(bmao$arguments))&&is.element("prior.msize",names(bmao$arguments))) { if (!is.null(bmao$arguments$theta)&!is.null(bmao$arguments$prior.msize)) prior=paste(bmao$arguments$theta, "/", bmao$arguments$prior.msize) else prior=NA } else { prior=paste(bmao$arguments$mprior, "/", bmao$arguments$mprior.size) } } gprior.info=bmao$gprior.info gprior.choice=gprior.info$gtype model.space=2^K fraction.model=models.visited/model.space*100 fraction.topmodel=sum(bmao$topmod$ncount())/iter*100 # this is the number of visits the topmodels accounted for relative to if (gprior.info$gtype=="hyper") {gprior.choice=paste(gprior.choice," (a=",2+signif(gprior.info$hyper.parameter-2,digits=4),")",sep="")} nr.reg=msize/cumsumweights info<-as.character(c(format(round(nr.reg,4),nsmall=4),format(iter,nsmall=0),format(burn,nsmall=0), format(timed,nsmall=4),models.visited,format(model.space,digits=2), format(fraction.model,digits=2),format(fraction.topmodel,digits=2),format(round(.cor.topmod(bmao$topmod),4),nsmall=4), format(N,nsmall=4),prior, gprior.choice)) names(info)<-c("Mean no. regressors", "Draws","Burnins", "Time", "No. models visited", "Modelspace 2^K", "% visited","% Topmodels","Corr PMP","No. Obs.", "Model Prior", "g-Prior") if (gprior.info$return.g.stats) { gpriorav=gprior.info$shrinkage.moments[1] gstatsprint= paste("Av=", format(gpriorav,digits=4),sep="") if (length(gprior.info$shrinkage.moments)>1) { gpriorstdev=sqrt(gprior.info$shrinkage.moments[2]-gprior.info$shrinkage.moments[1]^2) gstatsprint = paste(gstatsprint, ", Stdev=", format(gpriorstdev,digits=2),sep="") } info <- c(info, gstatsprint) names(info)[13]<- "Shrinkage-Stats" } return(info) } #' Predict Method for bma Objects #' #' Expected value of prediction based on 'bma' object #' #' #' @param object a bma object - see \code{\link{bms}} #' @param newdata An optional data.frame, matrix or vector containing variables #' with which to predict. If omitted, then (the expected values of) the fitted #' values are returned. #' @param exact If \code{FALSE} (default), then prediction is based on all #' models (i.e. on their MCMC frequencies in case the \code{\link{bms}} #' parameter \code{mcmc} was set to an mcmc sampler.\cr If \code{TRUE}, then #' prediction is based on analytical likelihoods of the best models retained in #' \code{object} - cf. \code{\link{bms}} parameter \code{nmodel}. #' @param topmodels index of the models with whom to predict: for instance, #' \code{topmodels=1} predicts based solely on the best model, whereas #' \code{topmodels=1:5} predicts based on a combination of the five best #' models.\cr Note that setting \code{topmodels} triggers \code{exact=TRUE}. #' @param \dots further arguments passed to or from other methods. #' @return A vector with (expected values of) fitted values. #' @seealso \code{\link{coef.bma}} for obtaining coefficients, #' \code{\link{bms}} for creating bma objects, \code{\link{predict.lm}} for a #' comparable function #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' mm=bms(datafls,user.int=FALSE) #' #' predict(mm) #fitted values based on MCM frequencies #' predict(mm, exact=TRUE) #fitted values based on best models #' #' predict(mm, newdata=1:41) #prediction based on MCMC frequencies #' #' predict(mm, newdata=datafls[1,], exact=TRUE) #prediction based on a data.frame #' #' # the following two are equivalent: #' predict(mm, topmodels=1:10) #' predict(mm[1:10], exact=TRUE) #' #' #' @export predict.bma <- function(object, newdata=NULL, exact=FALSE, topmodels=NULL, ...) { # does basic fitting in expected values # object: a bma object # newdata: newdata to be supplied (just eas in predict.lm) # topmodels: The index of the bestmodels to be included (e.g. 1 for best, 1:3 for three best); setting this parameter triggers exact=TRUE # exact: TRUE if fit should be based on exact likelihood of best models, FALSE if should be based on MCMC freqs # output: a vector with fitted values if (!is.bma(object)) {stop("you need to provide a BMA object"); return()} # check the topmodels argument if (!is.null(topmodels)) { if (!(is.numeric(topmodels)&&is.vector(topmodels))) { stop("topmodels must denote the models to take into account, e.g. 1:5 for the best five.") } else if (object$topmod$nbmodels < max(topmodels)) { stop(paste("Only",object$topmod$nbmodels,"best models are available, but you asked to take the", max(topmodels), "-best model into account.")) } object=object[unique(topmodels)] } if ((!missing(topmodels))&&missing(exact)) exact=TRUE #get the betas as required betas=estimates.bma(object,exact=exact,order.by.pip=FALSE,include.constant=FALSE,std.coefs=FALSE,condi.coef=FALSE)[,2] #check the newdata argument if (is.null(newdata)) { newX<-as.matrix(object$arguments$X.data[,-1, drop=FALSE]) } else { newX=as.matrix(newdata) if (!is.numeric(newX)) stop("newdata must be numeric!") if (is.vector(newdata)) newX=matrix(newdata,1) if (ncol(newX)!=length(betas)) { if (ncol(newX)==length(betas)+1) { newX=newX[,-1,drop=FALSE] # this is to achieve a bevavior similar to predict.lm in this case } else { stop("newdata must be a matrix or data.frame with", length(betas), "columns.") } } orinames=colnames(object$arguments$X.data[,-1, drop=FALSE]) if (!is.null(colnames(newX))&& !is.null(orinames)) { #this is a user check whether columns had been submitted in the wrong order if (all(orinames %in% colnames(newX) ) && !all(orinames == colnames(newX)) ) { warning("argument newdata had to be reordered according to its column names. Consider submitting the columns of newdata in the right order.") newX=newX[,orinames, drop=FALSE] } } } cons=.post.constant(object$arguments$X.data,betas) return(as.vector(newX%*%betas)+cons) } #' OLS Statistics for the Full Model Including All Potential Covariates #' #' A utility function for reference: Returns a list with R2 and sum of squares #' for the OLS model encompassing all potential covariates that are included in #' a bma object. #' #' #' @param yX.data a bma object (cf. \code{\link{bms}}) - alternatively a #' \link{data.frame} or \link{matrix} whose first column is the dependent #' variable #' @return Returns a list with some basic OLS statistics \item{R2}{The #' R-squared of the full model} \item{ymy}{The sum of squares of residuals of #' the full model} \item{ypy}{The explained sum of squares of the full model} #' \item{yty}{The sum of squares of the (demeaned) dependent variable} #' \item{Fstat}{The F-statistic of the full model} #' @note This function is just for quick comparison; for proper OLS estimation #' consider \code{\link{lm}} #' @seealso \code{\link{bms}} for creating bma objects, \code{\link{lm}} for #' OLS estimation #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' mm=bms(datafls) #' #' fullmodel.ssq(mm) #' #' #equivalent: #' fullmodel.ssq(datafls) #' #' #' @export fullmodel.ssq <- function(yX.data) { # yX.data: a dataframe # returns the OLS sums of sqares for yX.data, where the first column is the dependent: # R2: r-squared; ymy: resid SS; ypy: explained SS; yty: (y-ymean)'(y-ymean) if (is.bma(yX.data)) {yX.data <- yX.data$arguments$X.data} y<-as.matrix(yX.data[,1]) X<-as.matrix(yX.data[,2:ncol(yX.data)]) N<-nrow(X) K=ncol(X) y.mean=mean(y) y<-y-matrix(y.mean,N,1,byrow=TRUE) X.mean=colMeans(X) X<-X-matrix(X.mean,N,K,byrow=TRUE) Xqr<-qr(X) yty=as.numeric(crossprod(y)) ymy=as.numeric(crossprod(qr.resid(Xqr,y))) ypy=as.numeric(crossprod( qr.fitted(Xqr,y))) R2=ypy/yty return(list(R2=R2,ymy=ymy,ypy=ypy,yty=yty,Fstat=(R2/(1-R2))*(N-K-1)/K)) } #' @export print.bma <- function(x,...) { #defines how to print a bmao object (e.g. to the console) if (!is.bma(x)) {return(print(x))} print(estimates.bma(x),include.constant=TRUE,...) cat("\n") print(info.bma(x),...) cat("\n") } #' Summary Statistics for a 'bma' Object #' #' Returns a vector with summary statistics for a 'bma' object #' #' \code{info.bma} is equivalent to \code{summary.bma}, its argument #' \code{bmao} conforms to the argument \code{object} #' #' @aliases summary.bma info.bma #' @param object a list/object of class 'bma' that typically results from the #' function \code{bms} (see \code{\link{bms}} for details) #' @param \dots further arguments passed to or from other methods #' @param bmao same as \code{object} #' @return A character vector summarizing the results of a call to \code{bms} #' \item{Mean no. of Regressors}{ the posterior mean of model size} #' \item{Draws}{the number of iterations (ex burn-ins)} \item{Burnins}{the #' number of burn-in iterations} \item{Time}{the time spent on iterating #' through the model space} \item{No. of models visited}{the number of times a #' model was accepted (including burn-ins)} \item{Modelspace }{the total model #' space \eqn{2^K}}\item{list(list("2^K"))}{the total model space \eqn{2^K}} #' \item{Percentage visited}{\code{No. of models visited/Modelspace*100}} #' \item{Percentage Topmodels}{number of times the best models were drawn in #' percent of \code{Draws}} \item{Corr. PMP}{the correlation between the MCMC #' frequencies of the best models (the number of times they were drawn) and #' their marginal likelihoods.} \item{No. Obs.}{Number of observations} #' \item{Model Prior}{a character conforming to the argument \code{mprior} of #' \code{bms}, and the expected prior model size} \item{g-prior}{a character #' corresponding to argument \code{g} of function \code{bms}} #' \item{Shrinkage-Stats}{Posterior expected value und standard deviation (if #' applicable) of the shrinkage factor. Only included if argument #' \code{g.stats} of function \code{bms} was set to TRUE} #' @note All of the above statistics can also be directly extracted from the #' bma object (\code{bmao}). Therefore \code{summary.bma} only returns a #' character vector. #' @seealso \code{\link{bms}} and \code{\link{c.bma}} for functions creating #' bma objects, \code{print.bma} makes use of \code{summary.bma}. #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' #' m_fixed=bms(datafls,burn=1000,iter=3000,user.int=FALSE, ) #' summary(m_fixed) #' #' m_ebl=bms(datafls,burn=1000,iter=3000,user.int=FALSE, g="EBL",g.stats=TRUE) #' info.bma(m_ebl) #' #' @export summary.bma <-function(object,...) { #just an alias for info.bma info.bma(object) } #' Posterior Inclusion Probabilities and Coefficients from a 'bma' Object #' #' Returns a matrix with aggregate covariate-specific Bayesian model Averaging: #' posterior inclusion probabilites (PIP), post. expected values and standard #' deviations of coefficients, as well as sign probabilites #' #' More on the argument \code{exact}: \cr In case the argument #' \code{exact=TRUE}, the PIPs, coefficient statistics and conditional sign #' probabilities are computed on the basis of the (500) best models the #' sampling chain encountered (cf. argument \code{nmodel} in #' \code{\link{bms}}). Here, the weights for Bayesian model averaging (BMA) are #' the posterior marginal likelihoods of these best models. \cr In case #' \code{exact=FALSE}, then these statistics are based on all accepted models #' (except burn-ins): If \code{mcmc="enumerate"} then this are simply all #' models of the traversed model space, with their marginal likelihoods #' providing the weights for BMA.\cr If, however, the bma object \code{bmao} #' was based on an MCMC sampler (e.g. when \code{\link{bms}} argument #' \code{mcmc="bd"}), then BMA statistics are computed differently: In contrast #' to above, the weights for BMA are MCMC frequencies, i.e. how often the #' respective models were encountered by the MCMC sampler. (cf. a comparison of #' MCMC frequencies and marginal likelihoods for the best models via the #' function \code{\link{pmp.bma}}). #' #' @aliases estimates.bma coef.bma #' @param object,bmao a 'bma' object (cf. \code{\link{bms}}) #' @param exact if \code{exact=FALSE}, then PIPs, coefficients, etc. will be #' based on aggregate information from the sampling chain with posterior model #' distributions based on MCMC frequencies (except in case of enumeration - cf. #' 'Details');\cr if \code{exact=TRUE}, estimates will be based on the #' \code{\link[=bms]{nmodel}} best models encountered by the sampling chain, #' with the posterior model distribution based on their \emph{exact} marginal #' likelihoods - cf. 'Details' below. #' @param order.by.pip \code{order.by.pip=TRUE} orders the resulting matrix #' according to posterior inclusion probabilites, \code{order.by.pip=FALSE} #' ranks them according to the original data (order of the covariates as in #' provided in \code{X.data} to \code{\link{bms}}), default \code{TRUE} #' @param include.constant If \code{include.constant=TRUE} then the resulting #' matrix includes the expected value of the constant in its last row. Default #' \code{FALSE} #' @param incl.possign If \code{incl.possign=FALSE}, then the sign probabilites #' column (cf. 'Values' below) is omitted from the result. Default \code{TRUE} #' @param std.coefs If \code{std.coefs=TRUE} then the expected values and #' standard deviations are returned in standardized form, i.e. as if the #' original data all had mean zero and variance 1. If \code{std.coefs=FALSE} #' (default) then both expected values and standard deviations are returned 'as #' is'. #' @param condi.coef If \code{condi.coef=FALSE} (default) then coefficients #' \eqn{\beta_i} and standard deviations are unconditional posterior expected #' values, as in standard model averaging; if \code{condi.coef=FALSE} then they #' are given as conditional on inclusion (equivalent to \eqn{\beta_i / PIP_i}). #' @param ... further arguments for other \code{\link{coef}} methods #' @return A matrix with five columns (or four if \code{incl.possign=FALSE}) #' \item{Column 'PIP'}{Posterior inclusion probabilities \eqn{\sum p(\gamma|i #' \in \gamma, Y) / sum p(\gamma|Y) }} \item{Column 'Post Mean'}{posterior #' expected value of coefficients, unconditional \eqn{E(\beta|Y)=\sum #' p(\gamma|Y) E(\beta|\gamma,Y)}, where \eqn{E(\beta_i|\gamma,i \notin \gamma, #' Y)=0} if \code{condi.coef=FALSE}, or conditional on inclusion #' (\eqn{E(\beta|Y) / \sum p(\gamma|Y, i \in \gamma) } ) if #' \code{condi.coef=TRUE}} \item{Column 'Post SD'}{posterior standard deviation #' of coefficients, unconditional or conditional on inclusion, depending on #' \code{condi.coef}} \item{Column 'Cond.Pos.Sign'}{The ratio of how often the #' coefficients' expected values were positive conditional on inclusion. (over #' all visited models in case \code{exact=FALSE}, over the best models in case #' \code{exact=TRUE})} \item{Column 'Idx'}{the original order of covariates as #' the were used for sampling. (if included, the constant has index 0)} #' #' @seealso \code{\link{bms}} for creating bma objects, \code{\link{pmp.bma}} #' for comparing MCMC frequencies and marginal likelihoods. #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords models #' @examples #' #' #sample, with keeping the best 200 models: #' data(datafls) #' mm=bms(datafls,burn=1000,iter=5000,nmodel=200) #' #' #standard BMA PIPs and coefficients from the MCMC sampling chain, based on #' # ...how frequently the models were drawn #' coef(mm) #' #' #standardized coefficients, ordered by index #' coef(mm,std.coefs=TRUE,order.by.pip=FALSE) #' #' #coefficients conditional on inclusion: #' coef(mm,condi.coef=TRUE) #' #' #same as #' ests=coef(mm,condi.coef=FALSE) #' ests[,2]/ests[,1] #' #' #PIPs, coefficients, and signs based on the best 200 models #' estimates.bma(mm,exact=TRUE) #' #' #... and based on the 50 best models #' coef(mm[1:50],exact=TRUE) #' #' #' @method coef bma #' @export coef.bma <-function(object,exact = FALSE, order.by.pip = TRUE, include.constant = FALSE, incl.possign = TRUE, std.coefs = FALSE, condi.coef = FALSE, ...) { #just an alias for estimates.bma estimates.bma(object, exact=exact, order.by.pip = order.by.pip, include.constant = include.constant, incl.possign = incl.possign, std.coefs = std.coefs, condi.coef = condi.coef) } #' Tests for a 'bma' Object #' #' tests for objects of class "bma" #' #' #' @param bmao a 'bma' object: see 'value' #' @return Returns \code{TRUE} if bmao is of class 'bma', \code{FALSE} #' otherwise. #' #' @seealso 'Output' in \code{\link{bms}} for the structure of a 'bma' object #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords classes #' @examples #' #' data(datafls) #' mm=bms(datafls,burn=1000, iter=4000) #' is.bma(mm) #' @export is.bma <-function(bmao) { #returns true if the class of the object is either bma or a related class if (any(is.element(class(bmao),c("bma","bma.fcast","bma.sar","oldbma","bmav0")))) return(TRUE) else return(FALSE) } #' @export is.topmod <-function(tmo) { #returns true if the class of the object is a "topmod" list if (is.element("topmod",class(tmo))) return(TRUE) else return(FALSE) } #' Gaussian Hypergeometric Function F(a,b,c,z) #' #' Computes the value of a Gaussian hypergeometric function \eqn{ F(a,b,c,z) } #' for \eqn{-1 \leq z \leq 1} and \eqn{a,b,c \geq 0} #' #' The function \code{f21hyper} complements the analysis of the 'hyper-g prior' #' introduced by Liang et al. (2008).\cr For parameter values, compare cf. #' \url{https://en.wikipedia.org/wiki/Hypergeometric_function}. #' #' @param a The parameter \code{a} of the Gaussian hypergeometric function, #' must be a positive scalar here #' @param b The parameter \code{b} of the Gaussian hypergeometric function, #' must be a positive scalar here #' @param c The parameter \code{c} of the Gaussian hypergeometric function, #' must be a positive scalar here #' @param z The parameter \code{z} of the Gaussian hypergeometric function, #' must be between -1 and 1 here #' @return The value of the Gaussian hypergeometric function \eqn{ F(a,b,c,z) } #' @note This function is a simple wrapper function of sped-up code that is #' intended for sporadic application by the user; it is neither efficient nor #' general; for a more general version cf. the package '\code{hypergeo}' #' #' @seealso package \code{hypergeo} for a more proficient implementation. #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @references Liang F., Paulo R., Molina G., Clyde M., Berger J.(2008): #' Mixtures of g-priors for Bayesian variable selection. J. Am. Statist. Assoc. #' 103, p. 410-423 #' #' \url{https://en.wikipedia.org/wiki/Hypergeometric_function} #' @keywords utilities #' @examples #' #' #' f21hyper(30,1,20,.8) #returns about 165.8197 #' #' f21hyper(30,10,20,0) #returns one #' #' f21hyper(10,15,20,-0.1) # returns about 0.4872972 #' @export f21hyper = function(a,b,c,z) { if ((length(a)!=1)|(length(b)!=1)|(length(c)!=1)|(length(z)!=1)) stop("All function arguments need to be scalars") if ((a<0)|(b<0)|(c<0)) stop("Arguments a, b, and c need to be non-negative") if ((z>1)|(z<=(-1))) stop("Argument z needs to be between -1 and 1") nmx=max(100,3*floor(((a+b)*z-c-1)/(1-z))) if (nmx>10000) warning("Power series probably does not converge") serie=0:nmx return(1+sum(cumprod((a+serie)/(c+serie)*(b+serie)/(1+serie)*z))) } ######################################################################## # Auxilary functions on the final bma object or its components ######### ######################################################################## .post.constant <- function(X.data,Ebeta) { # calculates E(constant|Y): X.data is dataframe, Ebeta a vector Xmeans= colMeans(X.data) cons=Xmeans[1]-crossprod(Ebeta,Xmeans[-1]) return(as.vector(cons)) } .post.beta.draws <- function(topmods,reg.names,moment2=FALSE) { # constructs a nice matrix of the betas of the best models stored in topmods # topmods: topmod-object; reg.names: character vector (like colnames(X)) # moment2: TRUE: return betas2(), FALSE: return betas() if(moment2) beta.draws=as.matrix(topmods$betas2()) else beta.draws=as.matrix(topmods$betas()) if(sum(beta.draws)==0){ stop("The tompod object provided does not have saved betas. cf. bbeta argument in function topmod") } if(nrow(beta.draws)!=length(reg.names)){ rownames(beta.draws)=c(reg.names,"W-Index") } else{ rownames(beta.draws)=c(reg.names) } beta.names=topmods$bool() if(length(which(beta.names=="0"))>0){ colnames(beta.draws)=beta.names[-c(which(beta.names=="0"))] } else { colnames(beta.draws)=beta.names } return(beta.draws) } .post.topmod.includes <- function(topmods,reg.names) { # constructs nice 0-1-matrix with ionclusion vectors for the best models stored in topmods # topmods: topmodel object, reg.names: character-vector (like colnames(X)) topmod = topmods$bool_binary() colnames(topmod)<-topmods$bool() rownames(topmod)=reg.names return(topmod) } .post.topmod.bma <- function(topmods,reg.names=numeric(0)) { # constructs nice 0-1 matrix for regressors in model, with the last two rows # being the analytical and MCMC PMPs # topmods: either the topmodel object or a "bma" object pmps = pmp.bma(topmods) if (is.bma(topmods)) { reg.names=topmods$reg.names; topmods=topmods$topmod } # constructs nice matrix combining the 0-1- includes matrix with info on PMP rbind(.post.topmod.includes(topmods,reg.names),t(pmps)) } #' Model Binaries and their Posterior model Probabilities #' #' Returns a matrix whose columns show which covariates were included in the best models in a 'bma' object. The last two columns detail posterior model probabilities. #' #' @param bmao an object of class 'bma' - see \code{\link{bma-class}} #' @return Each column in the resulting matrix corresponds to one of the 'best' models in bmao: the first column for the best model, the second for the second-best model, etc. #' The model binaries have elements 1 if the regressor given by the row name was included in the respective models, and 0 otherwise. #' The second-last row shows the model's posterior model probability based on marginal likelihoods (i.e. its marginal likelihood over the sum of likelihoods of all best models) #' The last row shows the model's posterior model probability based on MCMC frequencies (i.e. how often the model was accepted vs sum of acceptance of all models) #' Note that the column names are hexcode representations of the model binaries (e.g. "03" for c(0,0,0,1,0,0)) #' #' #' @details #' Each bma class (the result of bms) contains 'top models', the x models with tthe best #' analytical likelihood that bms had encountered while sampling #' #' See \code{\link{pmp.bma}} for an explanation of likelihood vs. MCMC #' frequency concepts #' #' #' #' @seealso \code{\link{topmod}} for creating topmod objects, \code{\link{bms}} #' for their typical use, \code{\link{pmp.bma}} for comparing posterior model #' probabilities #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @examples #' #' data(datafls) #' #sample with a limited data set for demonstration #' mm=bms(datafls[,1:12],nmodel=20) #' #' #show binaries for all #' topmodels.bma(mm) #' #' #show binaries for 2nd and 3rd best model, without the model probs #' topmodels.bma(mm[2:3])[1:11,] #' #' #access model binaries directly #' mm$topmod$bool_binary() #' #' @export topmodels.bma <-function(bmao) {# function alias if (!is.bma(bmao)) {stop("you need to provide a bma object")} return(.post.topmod.bma(bmao)) } .post.estimates <- function(b1mo=NULL,b2mo=NULL,cumsumweights=NULL,inccount=NULL,topmods=NULL,X.data=NULL,reg.names=NULL,pos.sign=NULL,exact=FALSE,order.by.pip=TRUE,include.constant=FALSE,incl.possign=TRUE,std.coefs=FALSE,condi.coef=FALSE) { # b1mo: (weighted) cumsum of first moment of beta # b2mo: (weighted) cumsum of second moment of beta # cumsumweights: sum of the weights/model probs - typically just the number of draws "iter" # inccount: (weighted) number of inclusions per coefficient # reg.names: character vector like colnames(X.data) # topmods: topmodl object (best n models) # exact: if True, then calcs posterior stats based on analytical PMPs (likelihoods) in topmods object; if False, then does it as a weighted average (typically with MCMC frequencies) # order.by.pip: if true then the matrix is sorted according to the PIPs (first column) # include.constant: if true, then add the constant in the last row of resultant matrix idx=1:(length(b1mo)) if(exact){ #calculate with analytical PMPs from topmods object (best n models) lt1=topmods$lik() - max(topmods$lik()) # do this to get only positive probabilities exact.pmp=as.vector(exp(lt1)/sum(exp(lt1))) pip=as.vector(topmods$bool_binary()%*%exact.pmp) idx=1:(length(pip)) betas=topmods$betas() betas2=topmods$betas2() K=nrow(betas) Eb1=tcrossprod(betas,t(exact.pmp))[,1] #multiply betas with corr. pmp's and sum up Eb2=tcrossprod(betas2,t(exact.pmp))[,1] Ebsd=sqrt(Eb2-Eb1^2) possign=round(tcrossprod((betas>0),t(exact.pmp))[,1]/pip,8) possign[is.nan(possign)]=NA } else{ # Posterior mean and stand dev of each coefficient' # calculate by taking b1mo etc. as a weighted sum (typically MCMC draws), divided by the sum of the weights (typically number of runs - "iter") pip=inccount/cumsumweights Eb1 = b1mo/cumsumweights Eb2 = b2mo/cumsumweights Ebsd = sqrt(Eb2-Eb1^2) possign=round(pos.sign/inccount,8) possign[is.nan(possign)]=NA } if (include.constant) constterm=.post.constant(X.data,Eb1) if (condi.coef) { Eb1=Eb1/pip; Eb2=Eb2/pip; Ebsd=sqrt(Eb2-Eb1^2); Eb1[is.nan(Eb1)]=0; Ebsd[is.nan(Ebsd)]=0; } if (std.coefs) { #if standardized coefficients, then adjust them by SDs #important that this is done after all other Eb1 and Ebsd computations sddata=apply(as.matrix(X.data),2,stats::sd) Eb1=Eb1/sddata[1]*sddata[-1] Ebsd=Ebsd/sddata[1]*sddata[-1] if (include.constant) constterm=constterm/sddata[1] } if (incl.possign) { post.mean<-cbind(pip,Eb1,Ebsd,possign,idx) rownames(post.mean)<-reg.names colnames(post.mean)<-c("PIP","Post Mean", "Post SD","Cond.Pos.Sign","Idx") } else { post.mean<-cbind(pip,Eb1,Ebsd,idx) #post.mean<-cbind(pip,Eb1,Ebsd) rownames(post.mean)<-reg.names colnames(post.mean)<-c("PIP","Post Mean", "Post SD","Idx") #colnames(post.mean)<-c("PIP","Post Mean", "Post SD") } if (order.by.pip) { post.mean<-post.mean[order(-post.mean[,1]),] #order the results table according to PIP } if (include.constant) { constrow=matrix(c(1,constterm,NA,rep(NA,incl.possign),0),1) rownames(constrow)="(Intercept)" post.mean=rbind(post.mean,constrow) } return(post.mean) } ##################################################################################################### ######################################################################## # functions on retrieving a FULL sys.call(0) ########################### ######################################################################## .construct.arglist <- function(funobj,envir=NULL) { # evaluates all function arguments at envir (default: calling environment) # (they might have changed during running the function, or may be in variables) # construct.arglist gets rid of the variables and so on, and returns the argument list as a list namedlist=formals(funobj) argnames=names(namedlist) if (!is.environment(envir)) envir=sys.frame(-1) for (argn in 1:length(namedlist)) { # the following test is to cater for non.existent arguments testval=as.logical(try(exists(argnames[argn],envir=envir),silent=TRUE)); if (is.na(testval)) testval=FALSE if (testval) { namedlist[[argn]]=try(get(argnames[argn],envir=envir)) } } return(namedlist) } ##################################################################################################### ######################################################################## # functions for combining bma objects ################################## ######################################################################## .top10=function(nmaxregressors=10,nbmodels=10,bbeta=FALSE,lengthfixedvec=0,bbeta2=FALSE,..., inivec_lik=numeric(0),inivec_bool=character(0),inivec_count=numeric(0),inivec_vbeta=numeric(0), inivec_vbeta2=numeric(0),inivec_veck=0,inivec_fixvec=numeric(0)){ #object used by bms to save the best models #set up the variables to be augmented in the process #use .top10(...) to initalise this object # top-level INPUT arguments: # nmaxregressors: maximum number of regreswsors possible in the models (ncol(X.data)-1) for bms() # nmodels: integer >=0, maximum number of models to store # bbeta: logical whether additionally, the models` beta coefficents should be stored as well # lengthfixedvec: integer >=0; a vector of fixed length =lengthfixedvec will be stored for each model (e.g. posterior moments of g, etc.); # if lengthfixedvec=0, nothing will be stored # inivec_...: for the advanced user: vectors to initialize the top10 object in case one wants to 'add' several models right at the beginning: # inivec_lik: vector of likelihoods (length: nb of models) corresponds to: lik() # inivec_bool: vector of model binaries in hexcode (length: nb of models); althernatively, a numeric vector of 1s and 0s (length: nmaxregressors) # correspoinds to: bool() # inivec_count: vector of ncounts (length: nb of models), corresponds to: ncount() # inivec_vbeta: vector of betas (stripped of zeroes), corresponds to betas_raw() # inivec_veck: vector of ks (length: nb of models), i.e. the number of coefs per model, corresponds to: kvec_raw() # inivec_fixedvec: vector of fixed vectors (length: nb of models times lengthfixedvec) # OUTPUT: list with follwoing elements # addmodel(mylik,vec01,vbeta=numeric(0),fixedvec=numeric(0)): Function that adds a model to be stored in the object. # if the model is already there, only the model counter will be incremented: Reffeerence: see below # lik(), bool(), ncount(), nbmodels, nregs, betas_raw(), kvec_raw(), bool_binary(), betas(), fixed_vector(): # these are final output functions, for a reference, refer to these directly findex=function() { seq_incl=seq_len(nbmodel) if (nbmodel==nbmodels) { seq_incl[indices]=seq_incl } else { truncindex=indices; truncindex[(nbmodel+1):nbmodels]=0L seq_incl[truncindex]=seq_incl } return(seq_incl) } betamat = function(top10_betavec) { # return a matrix: rows: betas per model (including zeros); columns: model bins=(sapply(as.list(top10_bool[findex()]),hexobject$as.binvec)) betamatx=matrix(0,nmaxregressors,nbmodel) if (length(top10_betavec)>0) {betamatx[which(bins==1)]=top10_betavec} else betamatx=betamatx[,1] return(betamatx) } # sortall = function() { # if (!is_sorted) { # if (nbmodel0) { top10_fixvec <<- as.vector(matrix(lastm_fixvec,lengthfixedvec)[,order_index]) } # last_visited <<- c(lastnewmodel:1,length(lastm_lik):(lastnewmodel+1))[orderindex] # is_sorted <<- TRUE # } # } hexobject<-.hexcode.binvec.convert(nmaxregressors) #initialize object for hexcode to logical vector conversion if (nbmodels<0) {nbmodels=0} #declare needed objects in full length now to optimize memory allocation later indices=integer(nbmodels); top10_lik=rep(-Inf,nbmodels) top10_bool=character(nbmodels); top10_count=integer(nbmodels) top10_fixvec=numeric(lengthfixedvec*nbmodels) if (bbeta) lbetas=vector("list",nbmodels) if (bbeta2) lbetas2=vector("list",nbmodels) seq_nbmodel=seq_len(nbmodels); ix_of_mybool=logical(nbmodels) #is_sorted = FALSE # read in initial data nbmodel = length(inivec_lik) top10_lik[seq_len(nbmodel)]=inivec_lik; top10_count[seq_len(nbmodel)]=inivec_count; #read in initial binaries: character, or a list of vectors, a single vectors or a matrix, whose columns are the binaries if (is.character(inivec_bool)) {top10_bool[seq_len(nbmodel)]=inivec_bool} else { if (is.vector(inivec_bool)&(length(inivec_bool)==nmaxregressors)) { top10_bool[seq_len(nbmodel)]=hexobject$as.hexcode(inivec_bool) } else if (is.list(inivec_bool)) { top10_bool[seq_len(nbmodel)]=sapply(inivec_bool, hexobject$as.hexcode) } else if (is.matrix(inivec_bool)) { top10_bool[seq_len(nbmodel)]=sapply(as.list(as.data.frame(inivec_bool)), hexobject$as.hexcode) } else stop("inivec_bool is wrong format!") } top10_fixvec=inivec_fixvec; if (is.na(inivec_veck[1])) {inivec_veck=0} #read in initial beta information if (bbeta|bbeta2) { veck_ix=c(0,cumsum(inivec_veck)) veckix_aux=as.list(seq_len(nbmodel)); veckix_aux=lapply(veckix_aux,function(x) { if (veck_ix[[x]]==veck_ix[[x+1]]) c(0,0) else c(veck_ix[[x]]+1,veck_ix[[x+1]]) } ) } if (bbeta) { lbetas[seq_len(nbmodel)]=lapply(veckix_aux,function(x) inivec_vbeta[x[[1]]:x[[2]]]) } else lbetas=list(numeric(0)) if (bbeta2) { lbetas2[seq_len(nbmodel)]=lapply(veckix_aux,function(x) inivec_vbeta2[x[[1]]:x[[2]]]) } else lbetas2=list(numeric(0)) lastvec01=integer(nmaxregressors); modidx=length(top10_lik); indices[seq_len(nbmodel)]=order(inivec_lik,decreasing=TRUE) min.index = which.max(indices) if (length(min.index)>0) { min.top10_lik=top10_lik[[min.index]] } else { if (nbmodels>0) min.top10_lik=-Inf else min.top10_lik=Inf } index.of.mybool = function(mybool) { ix_of_mybool <<- (mybool==top10_bool) } check4dupl = index.of.mybool; dupl.possible=TRUE; retlist=list( addmodel=function(mylik,vec01,vbeta=numeric(0),vbeta2=numeric(0),fixedvec=numeric(0)) { #mylik: scalar likelihood, vec01: numeric vector of 0s and 1s, vbeta: small vector of betas (if bbeta was set to TRUE) that does NOT contain restricted zeros, vbeta2: small vector of betas^2 (is assumed to have same length as bvbeta) #use this function to add a model: #if its already among the best "nbmodels" models, its counter will be incremented by one #if it is not already in the best "nbmodels" models though its likelihood justifies that, the model will be added to the list if (mylik>=min.top10_lik|nbmodel add model to list if (nbmodel0) { top10_fixvec[(modidx-1)*lengthfixedvec+seq_len(lengthfixedvec)] <<- fixedvec } if (bbeta) { lbetas[[modidx]] <<- vbeta } if (bbeta2) { lbetas2[[modidx]] <<- vbeta2 } } else { #the model is already contained in the bestof list -> just adjust counter modidx <<- seq_nbmodel[ix_of_mybool] top10_count[[modidx]]<<-top10_count[[modidx]]+1 } } } }, lik = function(){return(top10_lik[findex()])}, #return a vector of the best "nbmodels" likelihoods bool = function(){return(top10_bool[findex()])}, #return a vector of the best "nbmodels" codes as hexadecimal (e.g. model c(0,1,0,0) as "4") ncount = function(){return(top10_count[findex()])}, #return a vector of how each of the best models was chosen #counters = function(){return(c(tcalls,inlik,added,maintained))}, # for programming checks nbmodels = nbmodels, # return the maximum number of best mdoels saved in this object nregs = nmaxregressors, # return K, the maximum number of regressors overall betas_raw = function(){return(unlist(lbetas[findex()]))}, # return the vector of beta coefs. of the best models in one line without the zeros betas2_raw = function(){return(unlist(lbetas2[findex()]))}, # return the vector of beta^2 coefs. of the best models in one line without the zeros kvec_raw = function(){return(sapply(lbetas,length)[findex()])}, #return a vector that details how many coefs. each of the best models has bool_binary = function(){return(sapply(as.list(top10_bool[findex()]),hexobject$as.binvec))}, #return a matrix: each column: one of the best models; rows: logical of coeficcient inclusion betas = function() { betamat(unlist(lbetas[findex()])) }, betas2 = function() { betamat(unlist(lbetas2[findex()])) }, fixed_vector = function(){ if (lengthfixedvec<=0) {return(matrix(0,0,0))} else { findex_base=(findex()-1)*lengthfixedvec findex_fixvec=numeric(0) for (xx in 1:lengthfixedvec) findex_fixvec=rbind(findex_fixvec,findex_base+xx) return(matrix(top10_fixvec[c(findex_fixvec)],lengthfixedvec)) } }, # return the fixed vector that may contain additional statistics duplicates_possible = function(possible=NULL) { if (!is.logical(possible)) return(dupl.possible) if (possible) { check4dupl <<- index.of.mybool; dupl.possible <<- TRUE; ix_of_mybool <<- logical(nbmodels) } else { check4dupl <<- function(mybool){}; dupl.possible <<- FALSE; ix_of_mybool <<- FALSE } } ) class(retlist)="topmod" return(retlist) } ############################################################ #auxiliary functions for topmod object #' @export "[.topmod" <- function(tm,idx) { # this function (applied as topmod[idx] ) provides a topmodel object with only the models indicated by idx # e.g. topmod[1] contains only the best model, topmod[-(90:100)] eliminates the models ranked 90 to 100 if (any(is.na(suppressWarnings(as.integer(idx))))) idx=1:length(tm$lik()) if (length(tm$betas_raw())>1) { bbeta=TRUE bet=as.vector(tm$betas()[,idx]) bet=bet[bet!=0] } else { bbeta=FALSE bet=numeric(0) } if (length(tm$betas2_raw())>1) { bbeta2=TRUE bet2=as.vector(tm$betas2()[,idx]) bet2=bet2[bet2!=0] } else { bbeta2=FALSE bet2=numeric(0) } fixvec=tm$fixed_vector() if (!length(as.vector(fixvec))) fixvec=numeric(0) else fixvec=as.vector(t(fixvec[,idx])) .top10(nmaxregressors=tm$nregs,nbmodels=tm$nbmodels,bbeta=bbeta,lengthfixedvec=nrow(tm$fixed_vector()),bbeta2=bbeta2, inivec_lik=tm$lik()[idx],inivec_bool=tm$bool()[idx],inivec_count=tm$ncount()[idx],inivec_vbeta=bet, inivec_vbeta2=bet2,inivec_veck=tm$kvec_raw()[idx],inivec_fixvec=fixvec) } #' @export "[.bma" <- function(bmao,idx) { # bma[idx] should have the same effect as applying the index to the topmod, for convenience bmao$topmod <- bmao$topmod[idx] return(bmao) } #' Printing topmod Objects #' #' Print method for objects of class 'topmod', typically the best models stored #' in a 'bma' object #' #' See \code{\link{pmp.bma}} for an explanation of likelihood vs. MCMC #' frequency concepts #' #' @param x an object of class 'topmod' - see \code{\link{topmod}} #' @param \dots additional arguments passed to \code{link{print}} #' @return if \code{x} contains more than one model, then the function returns #' a 2-column matrix: \item{Row Names}{show the model binaries in hexcode #' } \item{Column 'Marg.Log.Lik'}{shows the #' marginal log-likelihoods of the models in \code{x}} \item{Column 'MCMC #' Freq'}{shows the MCMC frequencies of the models in \code{x}} #' #' if \code{x} contains only one model, then more detailed information is shown #' for this model: \item{first line}{'Model Index' provides the model binary in #' hexcode, 'Marg.Log.Lik' its marginal log likelhood, 'Sampled Freq.' how #' often it was accepted (function \code{ncount()} in \code{\link{topmod}})} #' \item{Estimates}{first column: covariate indices included in the model, #' second column: posterior expected value of the coefficients, third column: #' their posterior standard deviations (excluded if no coefficients were stored #' in the topmod object - cf. argument \code{bbeta} in \code{\link{topmod}}) } #' \item{Included Covariates}{the model binary} \item{Additional #' Statistics}{any custom additional statistics saved with the model} #' #' @seealso \code{\link{topmod}} for creating topmod objects, \code{\link{bms}} #' for their typical use, \code{\link{pmp.bma}} for comparing posterior model #' probabilities #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords print #' @examples #' #' # do some small-scale BMA for demonstration #' data(datafls) #' mm=bms(datafls[,1:10],nmodel=20) #' #' #print info on the best 20 models #' print(mm$topmod) #' print(mm$topmod,digits=10) #' #' #equivalent: #' cbind(mm$topmod$lik(),mm$topmod$ncount()) #' #' #' #' #now print info only for the second-best model: #' print(mm$topmod[2]) #' #' #compare 'Included Covariates' to: #' topmodels.bma(mm[2]) #' #' #and to #' as.vector(mm$topmod[2]$bool_binary()) #' #' #' @export print.topmod <- function(x,...) { #this function prints the matrix of logliks and MCMC frequencies #if topmod contains only one model, more detiled infomration is given # try e.g. topmod, or topmod[1:3] or topmod[1] tm=x if (length(tm$lik())==1) { infomat=c(tm$bool(), tm$lik(),tm$ncount()) names(infomat)=c("Model Index","Marg.Log.Lik.","Sampled Freq.") print(infomat) betamat=cbind(as.vector(tm$betas_raw()),sqrt(as.vector(tm$betas2_raw())-as.vector(tm$betas_raw())^2)) if (nrow(betamat)!=0) { if (ncol(betamat)==1) {colnames(betamat)="Coef."} else {colnames(betamat)=c("Coef.","Std.Dev.")} rownames(betamat)=which(as.logical(as.vector(tm$bool_binary()))) cat("\nEstimates:\n") print(betamat) } bin=as.vector(tm$bool_binary()) names(bin)=1:length(bin) cat("\nIncluded Covariates:\n") print(bin) cat("\nAdditional Statistics:\n") print(as.vector(tm$fixed_vector())) } else { mout=cbind(tm$lik(),tm$ncount()) colnames(mout)=c("Marg.Log.Lik", "MCMC Freq") rownames(mout)=tm$bool() print(mout,...) } } #' Topmodel Object #' #' Create or use an updateable list keeping the best x models it encounters #' (for advanced users) #' #' A 'topmod' object (as created by \code{topmod}) holds three basic vectors: #' \code{lik} (for the (log) likelihood of models or similar), \code{bool()} #' for a hexcode presentation of the model binaries (cf. \code{\link{bin2hex}}) #' and ncount() for the times the models have been drawn.\cr All these vectors #' are sorted descendantly by \code{lik}, and are of the same length. The #' maximum length is limited by the argument \code{nbmodels}. #' #' If \code{tmo} is a topmod object, then a call to \code{tmo$addmodel} (e.g. #' \code{tmo$addmodel(mylik=4,vec01=c(T,F,F,T))} updates the object \code{tmo} #' by a model represented by \code{vec01} (here the one including the first and #' fourth regressor) and the marginal (log) likelihood \code{lik} (here: 4). #' #' If this model is already part of \code{tmo}, then its respective #' \code{ncount} entry is incremented by one; else it is inserted into a #' position according to the ranking of \code{lik}. #' #' In addition, there is the possibility to save (the first moments of) #' coefficients of a model (\code{betas}) and their second moments #' (\code{betas2}), as well as an arbitrary vector of statistics per model #' (\code{fixed_vector}). #' #' \code{is.topmod} returns \code{TRUE} if the argument is of class 'topmod' #' #' @aliases topmod is.topmod #' @param nbmodels The maximum number of models to be retained by the topmod #' object #' @param nmaxregressors The maximum number of covariates the models in the #' topmod object are allowed to have #' @param bbeta if \code{bbeta=TRUE}, then first and second moments of model #' coefficients are stored in addition to basic model statistics (Note: if #' \code{bbeta<0} then only the first moments are saved) #' @param lengthfixedvec The length of an optional fixed vector adhering to #' each model (for instance R-squared, etc). If \code{lengthfixedvec=0} then no #' additonal fixed vector will be stored. #' @param liks optional vector of log-likelihoods to initialize topmod object #' with (length must be \code{<=nbmodels}) - see example below #' @param ncounts optional vector of MCMC frequencies to initialize topmod #' object with (same length as \code{liks}) - see example below #' @param modelbinaries optional matrix whose columns detail model binaries to #' initialize topmod object with (same nb columns as \code{liks}, nb rows as #' \code{nmaxregressors}) - see example below #' @param betas optional matrix whose columns are coefficients to initialize #' topmod object with (same dimensions as \code{modelbinaries}) - see example #' below #' @param betas2 optional matrix whose columns are coefficients' second moments #' to initialize topmod object with (same dimensions as \code{modelbinaries}) - #' see example below #' @param fixed_vector optional matrix whose columns are a fixed vector #' initialize topmod object with (same \code{ncol} as \code{modelbinaries}) - #' see example below #' @return a call to \code{topmod} returns a list of class "topmod" with the #' following elements: #' \item{addmodel(mylik,vec01,vbeta=numeric(0),vbeta2=numeric(0),fixedvec=numeric(0))}{function #' that adjusts the list of models in the 'topmod' object (see Details). #' \code{mylik} is the basic selection criterion (usually log likelihood), #' \code{vec01} is the model binary (logical or numeric) indicating which #' regressors are included.\cr \code{vbeta} is a vector of length equal to #' \code{sum(vec01)}, contianing only the non-zero coefficients (only accounted #' for if \code{bbeta!=FALSE}). \code{vbeta2} is a similar vector of second #' moments etc. (only accounted for if \code{bbeta=TRUE}); \code{fixedvec} is #' an arbitrary vector of length \code{lengthfixedvec} (see above)} #' \item{lik()}{A numeric vector of the best models (log) likelihoods, in #' decreasing order} \item{bool()}{A character vector of hexmode expressions #' for the model binaries (cf. \code{\link{bin2hex}}), sorted by \code{lik()} } #' \item{ncount()}{A numeric vector of MCMC frequencies for the best models #' (i.e. how often the respective model was introduced by \code{addmodel})} #' \item{nbmodels}{Returns the argument \code{nbmodel}} \item{nregs}{Returns #' the argument \code{nmaxregressors}} \item{bool_binary()}{Returns a matrix #' whose columns present the models conforming to \code{lik()} in binary form} #' \item{betas()}{a matrix whose columns are the coefficients conforming to #' \code{bool_binary()} (Note that these include zero coefficients due to #' non-inclusion of covariates); Note: if \code{bbeta=FALSE} this returns an #' empty matrix} \item{betas2()}{similar to \code{betas} , for the second #' moments of coefficients Note: if \code{bbeta<=0}, this returns an empty #' matrix} \item{fixed_vector()}{The columns of this matrix return the #' \code{fixed_vector} statistics conforming to \code{lik()} (see Details); #' Note: if \code{lengthfixedvec=0} this returns an empty matrix} #' @note \code{topmod} is rather intended as a building block for programming; #' it has no direct application for a user of the BMS package. #' #' @seealso the object resulting from \code{\link{bms}} includes an element of #' class 'topmod' #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords models #' @examples #' #' #standard use #' tm= topmod(2,4,TRUE,0) #should keep a maximum two models #' tm$addmodel(-2.3,c(1,1,1,1),1:4,5:8) #update with some model #' tm$addmodel(-2.2,c(0,1,1,1),1:3,5:7) #add another model #' tm$addmodel(-2.2,c(0,1,1,1),1:3,5:7) #add it again -> adjust ncount #' tm$addmodel(-2.5,c(1,0,0,1),1:2,5:6) #add another model #' #' #read out #' tm$lik() #' tm$ncount() #' tm$bool_binary() #' tm$betas() #' #' is.topmod(tm) #' #' #extract a topmod oobject only containing the second best model #' tm2=tm[2] #' #' #' #' #advanced: should return the same result as #' #initialize #' tm2= topmod(2,4,TRUE,0, liks = c(-2.2,-2.3), ncounts = c(2,1), #' modelbinaries = cbind(c(0,1,1,1),c(1,1,1,1)), betas = cbind(0:3,1:4), #' betas2 = cbind(c(0,5:7),5:8)) #' #' #update #' tm$addmodel(-2.5,c(1,0,0,1),1:2,5:6) #add another model #' #' #read out #' tm$lik() #' tm$ncount() #' tm$bool_binary() #' tm$betas() #' #' @export topmod <- function(nbmodels,nmaxregressors=NA,bbeta=FALSE,lengthfixedvec=0,liks=numeric(0), ncounts=numeric(0),modelbinaries=matrix(0,0,0),betas=matrix(0,0,0), betas2=matrix(0,0,0),fixed_vector=matrix(0,0,0)) { #user-friendly function to create a 'topmod' object #nbmodels: integer, maxmium number of models to be retained #nmaxregressors: integer, maximum possible number of covariates (optional if arguments modelbinaries or betas are provided) #bbeta: If TRUE, model coefficients (first & second moments) are to saved by the resulting object; If FALSE not; (If bbeta<0 then only the first mometns are to be saved) #lengthfixedvec: If lengthfixedvec>0 then the resulting topmod object saves a numeric of length 'lengthfixedvec' for each added model; if lengthfixedvec=0, it does not; can be omitted if fixed_vector is supplied # #optional arguments to initialize the topmod object directly #liks: a vector of (log) likelihoods (does not need to be sorted) #ncounts: an (optional) vector of MCMC frrequencies for each model (if provided, needs to have the same length as liks) #modelbinaries: a logical or numeric matrix denoting the models to initialize the topmod object with; With nrow(modelbinaries) equal to nmaxregressors and ncol(modelbinaries) equal to length(liks) (can be omitted if argument betas is provided) #betas: a numeric matrix with the (expecteed values of) coefficents of the models; With nrow(betas) equal to nmaxregressors and ncol(betas) equal to length(liks); Note that providing betas automatically sets bbeta=-1 #betas2: a numeric matrix with the second moments coefficents of the models; With nrow(betas2) equal to nmaxregressors and ncol(betas2) equal to length(liks); Note that providing betas automatically sets bbeta=-1 #fixed_vector: an optional fixed vector to be saved with each model if (!is.numeric(nbmodels)) stop("argument 'nbmodels' needs to be an integer>0") nbmodels=floor(nbmodels[[1]]) if (nbmodels[[1]]<0) stop("argument 'nbmodels' needs to be an integer>0") if (bbeta>0) bbeta2=TRUE else bbeta2=FALSE bbeta=as.logical(bbeta) if (!bbeta&(length(betas)>0)) bbeta=TRUE if (!bbeta2&(length(betas2)>0)) bbeta2=TRUE if (is.na(lengthfixedvec[1])) lengthfixedvec=0 if ((lengthfixedvec==0)&length(fixed_vector)>0) {lengthfixedvec=nrow(fixed_vector)} if (length(liks)>nbmodels) stop("liks longer than nbmodels allows") if (length(ncounts)>nbmodels) stop("ncounts longer than nbmodels allows") if ((length(modelbinaries)==0)&(length(betas)>0)) {modelbinaries=as.logical(betas);dim(modelbinaries)=dim(betas) } if (ncol(modelbinaries)>nbmodels) stop("modelbinaries has more columns than than nbmodels allows") bindim=dim(modelbinaries); modelbinaries=as.logical(modelbinaries); dim(modelbinaries)=bindim; if ((is.na(nmaxregressors[1]))&(length(modelbinaries)>0)) {nmaxregressors=nrow(modelbinaries) } if (is.na(nmaxregressors)) stop("argument 'nmaxregressors' is missing") nmaxregressors=floor(nmaxregressors[[1]]) if (nmaxregressors<=0) stop("argument 'nmaxregressors' needs to be a positive integer") if ((length(ncounts)==0)&(length(liks)>0)) {ncounts=rep(1,length(liks)) } #check conformance with K if (length(modelbinaries)>0) if (nmaxregressors!=nrow(modelbinaries)) stop("nrow() of modelbinaries does not match nmaxregressors") if (bbeta&(length(betas)>0)) if (nmaxregressors!=nrow(betas)) stop("nrow() of betas does not match nmaxregressors") if (bbeta2&(length(betas2)>0)) if (nmaxregressors!=nrow(betas2)) stop("nrow() of betas2 does not match nmaxregressors") #check that all have the same model length N=length(liks) if (length(ncounts)!=length(liks)) stop("lengths of arguments 'liks' and 'ncounts' do not conform") if (ncol(modelbinaries)!=length(liks)) stop("nrow of argument 'modelbinaries' does not conform to length of argument 'liks'") if (bbeta) if (ncol(betas)!=length(liks)) stop("nrow of argument 'betas' does not conform to length of argument 'liks'") if (bbeta2) if (ncol(betas2)!=length(liks)) stop("nrow of argument 'betas2' does not conform to length of argument 'liks'") if (lengthfixedvec) if (ncol(fixed_vector)!=length(liks)) stop("nrow of argument 'fixed_vector' does not conform to length of argument 'liks'") morder=order(liks,decreasing=TRUE) liks=liks[morder] modelbinaries=modelbinaries[,morder,drop=FALSE] ncounts=ncounts[morder] if (bbeta) { betas=betas[,morder,drop=FALSE] } if (bbeta2) { betas2=betas2[,morder,drop=FALSE] } if (lengthfixedvec) { fixed_vector=fixed_vector[,morder,drop=FALSE] } hexobj=.hexcode.binvec.convert(nmaxregressors) bools=as.vector(sapply(as.list(as.data.frame(modelbinaries)),hexobj$as.hexcode)) if (length(bools)==0) {bools=character(0)} veck=numeric(0); betas_raw=numeric(0); betas2_raw=numeric(0); if (bbeta&(length(bbeta)>0)) { veck=colSums(modelbinaries) betas_raw=as.vector(betas)[as.vector(modelbinaries)] } if (bbeta2&(length(bbeta2)>0)) { veck=colSums(modelbinaries) betas2_raw=as.vector(betas2)[as.vector(modelbinaries)] } fixedvec=as.vector(fixed_vector) .top10(nmaxregressors=nmaxregressors,nbmodels=nbmodels,bbeta=bbeta,lengthfixedvec=lengthfixedvec,bbeta2=bbeta2, inivec_lik=liks,inivec_bool=bools,inivec_count=ncounts,inivec_vbeta=betas_raw, inivec_vbeta2=betas2_raw,inivec_veck=veck,inivec_fixvec=fixedvec) } .cor.topmod <- function(tmo) { if (is.bma(tmo)) tmo=tmo$topmod pmp.10=pmp.bma(tmo,oldstyle=TRUE) if (nrow(pmp.10)==1|suppressWarnings(length(grep("error",class(try(cor(pmp.10[,1],pmp.10[,2]),silent=TRUE)))))) { corr.pmp=NA } else { if (var(pmp.10[,2])==0) corr.pmp=NA else corr.pmp=cor(pmp.10[,1],pmp.10[,2]) } return(corr.pmp) } .topmod.as.bbetaT <- function (tm,gprior.info=NULL,yXdata=NULL, addr2=FALSE) { #this is a small function to convert a topmod object with bbeta=FALSE into on ewith bbeta=TRUE #CAUTION: this is not a generic function, but tailored to very specific topmod objects in conjunction with bms() # tm: a topmod object as the one resulting from a call to bms() # gprior.info: an object as the one resulting from a call to bms(); # optionally you can easily create one with the function .choose.gprior() # yXdata: the original data, just like the object X.data resulting from a call to bms() # if addr2=TREU, then the fixed_vector is appended to a first row containing the R-squareds # returns amn adjusted topmod object # # Optionally, tm can be a bma object, then this function returns an adjusted bma object is.bmao=FALSE if (is.bma(tm)) { #in case tm is a bma object... is.bmao=TRUE bmao=tm; yXdata=bmao$arguments$X.data; gprior.info=bmao$gprior.info; tm =bmao$topmod } #retrieve necessary info yXdata=as.matrix(yXdata); N=nrow(yXdata); K=ncol(yXdata)-1 yXdata=yXdata-matrix(colMeans(yXdata),N,K+1,byrow=TRUE) if (length(tm$lik())<1) { if (is.bmao) return(bmao) else return(tm)} if (!addr2) if ((length(tm$betas_raw())>0)&(ncol(as.matrix(tm$betas()))==length(tm$lik()))) { if (is.bmao) return(bmao) else return(tm)} bools=(tm$bool_binary()) yty=c(crossprod(yXdata[,1])) positions=lapply(lapply(as.list(as.data.frame(bools)),as.logical),which) #calculate all the OLS results for all the models olsmodels=lapply(lapply(positions,.ols.terms2,yty=yty,N=N,K=K,XtX.big=crossprod(yXdata[,-1]),Xty.big=c(crossprod(yXdata[,-1],yXdata[,1]))),function (x) x$full.results()) #initialize the right gprior-function # if (gprior.info$is.constant) { # lprobo=.lprob.constg.init(g=gprior.info$g,N=N,K=K,yty=yty) # } else if (gprior.info$gtype=="EBL") { # lprobo=.lprob.eblocal.init(g=gprior.info$g,N=N,K=K,yty=yty) # } else if (gprior.info$gtype=="hyper") { # lprobo=.lprob.hyperg.init(g=gprior.info$g,N=N,K=K,yty=yty,f21a=gprior.info$hyper.parameter) # } else { # stop("gprior not recognizeable") # } lprobo=gprior.info$lprobcalc #calculate the posterior statistics lpl=lapply(olsmodels,function(x) lprobo$lprob(x$ymy,length(x$bhat),x$bhat,x$diag.inverse)) # caution: lprobs are not the same due to missing prior model probs veck=as.vector(unlist(lapply(lapply(lpl,"[[","b1new"),length))) b1raw=as.vector(unlist(lapply(lpl,"[[","b1new"))) b2raw=as.vector(unlist(lapply(lpl,"[[","b2new"))) fixedvecmat=tm$fixed_vector() if (addr2) { # add R-squared as the first elements among the fixed vectors r2=1-sapply(olsmodels,function(x) x$ymy)/yty if (nrow(fixedvecmat)==0) { fixedvecmat=matrix(0,0,length(veck)) } else if ( mean(abs(r2-fixedvecmat[1,]))< 1e-17 ) { fixedvecmat=fixedvecmat[-1,,drop=FALSE] } fixedvecmat = rbind(r2,fixedvecmat) } lengthfixedvec=nrow(fixedvecmat) #now create new topmod with bbeta=TRUE tm<-.top10(nmaxregressors=tm$nregs,nbmodels=tm$nbmodels,bbeta=TRUE,lengthfixedvec=lengthfixedvec,bbeta2=TRUE, inivec_lik=tm$lik(),inivec_bool=tm$bool(),inivec_count=tm$ncount(),inivec_vbeta=b1raw, inivec_vbeta2=b2raw,inivec_veck=veck,inivec_fixvec=c(fixedvecmat)) if (is.bmao) { bmao$topmod <- tm # bmao$arguments$beta.save=TRUE return(bmao) } return(tm) } combine_chains <- function(...) { #to combine outputs of the the function bms #EXAMPLE: #bma1<-bms(X.data=t5.within[,1:20],burn=100,iter=1000,g=TRUE,nmodel=10,logfile=TRUE,beta.save=FALSE,start.value=41,step=1000) #bma2<-bms(X.data=X.data,burn=1000,iter=10000,g=TRUE,nmodel=10,logfile=TRUE,beta.save=FALSE,start.value=41,step=1000) #out=combine_chains(bma1,bma2) ### or: combine_chains(bma1,bma2,bma3,bma4)... # output is a standard bma object combine_topmods <- function(topmodobj1,topmodobj2) { #to combine top10 models objects of function bms() #e.g. ppp=combine_topmods(test1$topmod,test2$topmod) # output: a topmodel object #retrieve the necessary properties nregs1=topmodobj1$nregs nregs2=topmodobj2$nregs if (nregs1!=nregs2) {stop("The number of regressors in both BMA chains has to be the same!")} k1=length(topmodobj1$ncount()) k2=length(topmodobj2$ncount()) #read out the importnant stuff nbmodels1=topmodobj1$nbmodels nbmodels2=topmodobj2$nbmodels ncount1=topmodobj1$ncount() ncount2=topmodobj2$ncount() lik1=topmodobj1$lik() lik2=topmodobj2$lik() bool1=topmodobj1$bool() bool2=topmodobj2$bool() betas1=topmodobj1$betas() betas2=topmodobj2$betas() betas2_1=topmodobj1$betas2() betas2_2=topmodobj2$betas2() fv1=topmodobj1$fixed_vector() fv2=topmodobj2$fixed_vector() if (all(betas1==0)|all(betas2==0)) {dobetas=FALSE} else {dobetas=TRUE} if (all(betas2_1==0)|all(betas2_2==0)) {dobetas2=FALSE} else {dobetas2=TRUE} #first look which models of 1 are already in 2 and #for these just update the ncounts (note: this is quite easy, since this subset in 1 has the same order as in 2) idxin2_boolof1in2=match(bool1,bool2) idxin1_boolof1in2=which(!is.na(idxin2_boolof1in2)) idxin2_boolof1in2=idxin2_boolof1in2[!is.na(idxin2_boolof1in2)] ncount2[idxin2_boolof1in2]=ncount2[idxin2_boolof1in2]+ncount1[idxin1_boolof1in2] if (any(idxin1_boolof1in2)) { # in case there are models in 1 that also show up in 2 #strip 1 of all the models that were already in 2 ncount1=ncount1[-idxin1_boolof1in2] lik1=lik1[-idxin1_boolof1in2] bool1=bool1[-idxin1_boolof1in2] } #now do A u (B\(AnB)) lika=c(lik2,lik1) orderlika=order(lika,decreasing=TRUE) lika=lika[orderlika] ncounta=c(ncount2,ncount1)[orderlika] boola=c(bool2,bool1)[orderlika] if (dobetas) { # if there are betas, do the same for betas if (any(idxin1_boolof1in2)) betas1=betas1[,-idxin1_boolof1in2] betasa=cbind(betas2,betas1)[,orderlika] betasa_not0=betasa!=0 vecka=colSums(betasa_not0) vbetaa=as.vector(betasa[as.vector(betasa_not0)]) } else { vecka=0;vbetaa=numeric(0) } if (dobetas2) { # if there are betas^2, do the same for betas^2 if (any(idxin1_boolof1in2)) betas2_1=betas2_1[,-idxin1_boolof1in2] betasa2=cbind(betas2_2,betas2_1)[,orderlika] vbetaa2=as.vector(betasa2[as.vector(betasa_not0)]); } else { vbetaa2=numeric(0) } fva=numeric(0); lfv=0; if ( (nrow(fv1)==nrow(fv2)) & ((nrow(fv1)>0) & (nrow(fv2)>0)) ) { # if there is a fixed vector then combine it the same way if (any(idxin1_boolof1in2)) fv1=fv1[,-idxin1_boolof1in2] if (!is.matrix(fv1)) fv1=t(fv1) fva=as.vector(cbind(fv2,fv1)[,orderlika]) lfv=nrow(fv1) } return(.top10(nmaxregressors=nregs1,nbmodels=length(lika),bbeta=dobetas,lengthfixedvec=lfv,bbeta2=dobetas2,inivec_lik=lika,inivec_bool=boola,inivec_count=ncounta,inivec_vbeta=vbetaa,inivec_vbeta2=vbetaa2,inivec_veck=vecka,inivec_fixvec=fva)) } combine_2chains <- function(flso1,flso2) { # if (!exists("sPath")) sPath="" # source(paste(sPath,"aux_inner.r",sep=""),local=TRUE) # if (flso1$arguments$beta.save & flso2$arguments$beta.save) { # beta.save = TRUE # } else { # beta.save = FALSE # } # combine the topmodel opbjects topmod.combi=combine_topmods(flso1$topmod,flso2$topmod) #prepare the gprior.info obejct for post.calc gpi <- flso1$gprior.info gpi$shrinkage.moments=numeric(length(gpi$shrinkage.moments)) # use post.calc to compute info, gprior.info, and reg.names io1=flso1$info; io2 = flso2$info obj.combi=.post.calc(gprior.info=gpi,add.otherstats=io1$add.otherstats + io2$add.otherstats,k.vec=(io1$k.vec[-1]+io2$k.vec[-1]),null.count=(io1$k.vec[1]+io2$k.vec[1]), flso1$arguments$X.data,topmods=topmod.combi,b1mo=io1$b1mo + io2$b1mo,b2mo=io1$b2mo + io2$b2mo,iter=io1$iter + io2$iter,burn=io1$burn + io2$burn, inccount=io1$inccount + io2$inccount,models.visited=io1$models.visited + io2$models.visited,K=io1$K,N=io1$N,msize=io1$msize + io2$msize, timed=io1$timed + io2$timed,cumsumweights=io1$cumsumweights + io2$cumsumweights,mcmc=flso1$arguments$mcmc,possign=io1$pos.sign+io2$pos.sign) # concatenate start.positions by cbinding them stpos1=as.matrix(flso1$start.pos);stpos2=as.matrix(flso2$start.pos) startpos.combi =cbind(rbind(stpos1,matrix(0,max(0,nrow(stpos2)-nrow(stpos1)),ncol(stpos1))),rbind(stpos2,matrix(0,max(0,nrow(stpos1)-nrow(stpos2)),ncol(stpos2)))) # concatenate bms.calls in list call.combi= c(flso1$bms.call,flso2$bms.call) # combine arguments args.combi = flso1$arguments; args2=flso2$arguments args.combi$burn = args.combi$burn + args2$burn args.combi$iter = args.combi$iter + args2$iter if ((length(args.combi$mprior.size)==1)|(length(args.combi$mprior.size)==1)) {args.combi$mprior.size = mean(c(args.combi$mprior.size,args2$mprior.size))} args.combi$nmodel = topmod.combi$nbmodels # args.combi$beta.save = (args.combi$beta.save & args2$beta.save) args.combi$user.int = (args.combi$user.int & args2$user.int) args.combi$g.stats = (args.combi$g.stats & args2$g.stats) #model prior object mp1=flso1$mprior.info; mp2=flso2$mprior.info if (mp1$mp.mode!=mp2$mp.mode) {mpall=list()} else { mpall=mp1 mpall$mp.msize = .5*mp1$mp.msize+.5*mp2$mp.msize mpall$origargs$mpparam = .5*mp1$origargs$mpparam+.5*mp2$origargs$mpparam mpall$mp.Kdist= .5*mp1$mp.Kdist + .5*mp2$mp.Kdist } result=list(info=obj.combi$info,arguments=args.combi, topmod=topmod.combi,start.pos=startpos.combi, gprior.info=obj.combi$gprior.info, mprior.info=mpall, X.data=flso1$arguments$X.data,reg.names=obj.combi$reg.names,bms.call=call.combi) class(result)="bma" return(result) } ############################################################################################################# #this is the rest of the combine function; the combine function is iteratively used to combine as many chains #as are specified by (...) ############################################################################################################ arglist=list(...) if ( !all(unlist(lapply(arglist,is.bma)))) stop("All of the input arguments must be BMA objects!") if ( !all(lapply(arglist,function (xx) xx$info$K)==arglist[[1]]$info$K) ) stop("All of the input BMA objects must have an equal number of max regressors (i.e. equal (X.data))!") if ( !all(lapply(arglist,function (xx) xx$info$N)==arglist[[1]]$info$N) ) stop("All of the input BMA objects must have equal X.data!") if ( !all(lapply(arglist,function (xx) xx$gprior.info$gtype)==arglist[[1]]$gprior.info$gtype) ) stop("All of the input BMA objects must have the same type of g-prior (bms-argument g)") if ( length(arglist)==1) return(arglist[[1]]) combined_output <- combine_2chains(arglist[[1]],arglist[[2]]) if (nargs()>2) { for (inarg in 3:nargs()) { combined_output <- combine_2chains(arglist[[inarg]],combined_output) } } ############################################################################################################ return(combined_output) } #' Concatenate bma objects #' #' Combines bma objects (resulting from \code{\link{bms}}). Can be used to #' split estimation over several machines, or combine the MCMC results obtained #' from different starting points. #' #' Aggregates the information obtained from several chains. The result is a #' 'bma' object (cf. 'Values' in \code{\link{bms}}) that can be used just as a #' standard 'bma' object.\cr Note that \code{combine_chains} helps in #' particular to paralllelize the enumeration of the total model space: A model #' with \eqn{K} regressors has \eqn{2^K} potential covariate combinations: With #' \eqn{K} large (more than 25), this can be pretty time intensive. With the #' \code{\link{bms}} arguments \code{start.value} and \code{iter}, sampling can #' be done in steps: cf. example 'enumeration' below. #' #' @aliases combine_chains c.bma #' @param \dots At least two 'bma' objects (cf. \code{\link{bms}}) #' @param recursive retained for compatibility with \code{\link{c}} method #' #' @seealso \code{\link{bms}} for creating bma objects #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords models #' @examples #' #' data(datafls) #' #' #MCMC case ############################ #' model1=bms(datafls,burn=1000,iter=4000,mcmc="bd",start.value=c(20,30,35)) #' model2=bms(datafls,burn=1500,iter=7000,mcmc="bd",start.value=c(1,10,15)) #' #' model_all=c(model1,model2) #' coef(model_all) #' plot(model_all) #' #' #' #' #splitting enumeration ######################## #' #' #standard case with 12 covariates (4096 differnt combinations): #' enum0=bms(datafls[,1:13],mcmc="enumerate") #' #' # now split the task: #' # enum1 does everything from model zero (the first model) to model 1999 #' enum1=bms(datafls[,1:13],mcmc="enumerate",start.value=0,iter=1999) #' #' # enum2 does models from index 2000 to the index 3000 (in total 1001 models) #' enum2=bms(datafls[,1:13],mcmc="enumerate",start.value=2000,iter=1000) #' #' # enum3 does models from index 3001 to the end #' enum3=bms(datafls[,1:13],mcmc="enumerate",start.value=3001) #' #' enum_combi=c(enum1,enum2,enum3) #' coef(enum_combi) #' coef(enum0) #' #both enum_combi and enum0 have exactly the same results #' #(one difference: enum_combi has more 'top models' (1500 instead of 500)) #' #' @export c.bma <- function(...,recursive=FALSE) { #simple wrapper, recursive has no meaning and ist retained for compatibility combine_chains(...) } ######################################################################## # auxiliary functions for the topmodel object ########################## ######################################################################## .hexcode.binvec.convert <- function(length.of.binvec) { #function to initialise conversion betwwen logical vector (such as c(1,0,0,0)) and character hexcode vector (such as "f") #length.of.binvec is the desired length of the inserted and resulting logical vectors; #the initialisation will fit some leading zeros to make it convertible into hexcode (length of bin. vector as a multiple of 4) if (length(length.of.binvec)>1) length.of.binvec=length(length.of.binvec) addpositions=4-length.of.binvec%%4; positionsby4=(length.of.binvec+addpositions)/4; hexvec=c(0:9,"a","b","c","d","e","f"); #lookup list for converting from binary to hexadecimal hexcodelist=list("0"=numeric(4),"1"=c(0,0,0,1),"2"=c(0,0,1,0),"3"=c(0,0,1,1),"4"=c(0,1,0,0),"5"=c(0,1,0,1),"6"=c(0,1,1,0), "7"=c(0,1,1,1),"8"=c(1,0,0,0),"9"=c(1,0,0,1),"a"=c(1,0,1,0),"b"=c(1,0,1,1),"c"=c(1,1,0,0),"d"=c(1,1,0,1),"e"=c(1,1,1,0),"f"=c(1,1,1,1)); #lookup list for converting from hexadecimal to binary return(list( as.hexcode = function(binvec) { #convert logical vector to hexcode character incl=c(numeric(addpositions),binvec);dim(incl)=c(4,positionsby4); #split into elements of four positions return(paste(hexvec[crossprod(incl,2L^(3:0))+1],collapse="")); }, as.binvec = function(hexcode) { #convert hexcode character to numeric vector (e.g. "a" to c(1,0,1,0)) return(unlist(hexcodelist[unlist(strsplit(hexcode,"",fixed=TRUE),recursive=FALSE,use.names=FALSE)],recursive=FALSE,use.names=FALSE)[-(1:addpositions)]) })) } #' @rdname bin2hex #' @export hex2bin<-function(hexcode) { #user-friendly function to convert some hexcode character to numeric vector (e.g. "a" to c(1,0,1,0)) if (!is.character(hexcode)) stop("please input a character like '0af34c'"); hexcode <- paste("0",tolower(hexcode),sep="") hexobj<-.hexcode.binvec.convert(length(hexcode)*16L) return(hexobj$as.binvec(hexcode)) } #' Converting Binary Code to and from Hexadecimal Code #' #' A simple-to-use function for converting a logical ('binary') vector into hex #' code and reverse. #' #' The argument is an integer in binary form (such as "101"), provided as a #' logical (\code{c(T,F,T)}) or numeric vector (\code{c(1,0,1)}).\cr #' \code{bin2hex} then returns a character denoting this number in hexcode (in #' this case "5"). #' #' The function \code{hex2bin} does the reverse operation, e.g. #' \code{hex2bin("5")} gives (\code{c(1,0,1)}). #' #' @aliases bin2hex hex2bin #' @param binvec a logical vector (alternatively a vector coercible into #' logical) #' @param hexcode a single-element character denoting an integer in hexcode #' (admissible character: 0 to 9, ato f) #' @return \code{bin2hex} returns a single element character; \code{hex2bin} #' returns a numeric vector equivalent to a logical vector #' #' @seealso \code{\link{hex2bin}} for converting hexcode into binary vectors, #' \code{\link{format.hexmode}} for a related R function. #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords arith #' @examples #' #' bin2hex(c(TRUE,FALSE,TRUE,FALSE,TRUE,TRUE)) #' bin2hex(c(1,0,1,0,1,1)) #' hex2bin("b8a") #' bin2hex(hex2bin("b8a")) #' #' @export bin2hex<-function(binvec) { #user-friendly function to convert some logical vector to hexcode character(e.g. c(1,0,1,0) or c(T,F,T,F) to "a") if (!is.logical(binvec)) {if (is.numeric(binvec)) {binvec=as.logical(binvec)} else {stop("need to supply a logical vector like c(T,F) or c(1,0)")} } hexobj<-.hexcode.binvec.convert(length(binvec)) hexcode=hexobj$as.hexcode(binvec) if (nchar(hexcode)>(floor((length(binvec)-1)/4)+1)) {hexcode=substring(hexcode,2)} return(hexcode) } ####################### # FUNCTIONS FOR USERS # ######################################################################### #' Plot Model Size Distribution #' #' Plots posterior and prior model size distribution #' #' #' @param bmao a 'bma' object (cf. \code{\link{bms}}) #' @param exact if \code{TRUE}, then the posterior model distribution is based #' on the best models of \code{bmao} and their marginal likelihoods;\cr if #' \code{FALSE} (default) then the distribution is based on all encountered #' models and their MCMC frequencies (cf. 'Details' in \code{\link{coef.bma}}) #' @param ksubset integer vector detailing for which model sizes the plot #' should be done #' @param include.legend if \code{TRUE}, a small legend is included via the #' low-level command \code{\link{legend}} #' @param do.grid if \code{TRUE}, a \code{\link{grid}} is added to the plot #' (with a simple \code{grid()}). #' @param \dots parameters passed on to \code{\link{matplot}} with sensible #' defaults #' @return As a default, \code{plotModelsize} plots the posterior model size #' distribution as a blue line, and the prior model distribution as a dashed #' red line.\cr In addition, it returns a list with the following elements: #' \item{mean}{The posterior expected value of model size} \item{var}{The #' variance of the posterior model size distribution} \item{dens}{A vector #' detailing the posterior model size distribution from model size \eqn{0} (the #' first element) to \eqn{K} (the last element)} #' #' @seealso See also \code{\link{bms}}, \code{\link{image.bma}}, #' \code{\link{density.bma}}, \code{\link{plotConv}} #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords hplot #' @examples #' #' data(datafls) #' mm=bms(datafls,burn=1500, iter=5000, nmodel=200,mprior="fixed",mprior.size=6) #' #' #plot Nb.1 based on aggregate results #' postdist= plotModelsize(mm) #' #' #plot based only on 30 best models #' plotModelsize(mm[1:30],exact=TRUE,include.legend=FALSE) #' #' #plot based on all best models, but showing distribution only for model sizes 1 to 20 #' plotModelsize(mm,exact=TRUE,ksubset=1:20) #' #' # create a plot similar to plot Nb. 1 #' plot(postdist$dens,type="l") #' lines(mm$mprior.info$mp.Kdist) #' #' #' @export plotModelsize<-function(bmao,exact=FALSE,ksubset=NULL,include.legend=TRUE, do.grid=TRUE, ...) { #,lwd=1.5,xaxt="n",col=c("steelblue3","tomato"),main=NULL,cex.main=0.8,xlab="Model Size",ylab=""){ #plots posterior vs. prior model size distribution # bma: bma object # exact: whether posterior distribution is to be based on MC3 results or on best 'top' models (as in bmao$topmod) # ksubset: an integer vector detailing for which model sizes the plot should be done # include.legend: if TRUE, a small legend is included via legend() # ... parameters passed on to ?matplot (Note: defaults are as above, default 'main' is 'Posterior Model Size Distribution Mean: x' where x is the actual mean) # besides plotting, the function returns a list with the mean and variance of the posterior model size distribution (not altered by ksubset) dotargs = match.call(expand.dots=FALSE)$... if (length(exact)>1) { topmodidx=exact; exact=TRUE} else {topmodidx=NA} # get required information K=bmao$info$K if (is.element("mprior.info",names(bmao))) m=bmao$mprior.info$mp.msize else m=bmao$arguments$prior.msize pmp.10=pmp.bma(bmao$topmod[topmodidx],oldstyle=TRUE) if(exact){ modelSmean=sum(apply(.post.topmod.bma(bmao$topmod[topmodidx]),2,function(x) length(which(x==1)))*pmp.10[,1]) modelS.var=sum(apply(.post.topmod.bma(bmao$topmod[topmodidx]),2,function(x) length(which(x==1)))^2*pmp.10[,1])-modelSmean^2 x = apply(.post.topmod.bma(bmao$topmod[topmodidx]),2,function(x) length(which(x==1))) y = pmp.10[,1] result = c() for( i in sort(unique(x)) ) result = c(result, sum(y[which(x==i)])) names(result) = sort(unique(x)) kvec=rep(0,(K+1)) kvec[(as.numeric(names(result))+1)]=result } else{ k.vec=bmao$info$k.vec #calculate expected value summi=sum(k.vec) #modelSmean -1 because we have to deduct the constant from the mean model size modelSmean=sum((1:length(k.vec))*(k.vec/summi))-1 kvec=k.vec/sum(k.vec) modelSmean.sq=sum(((1:length(k.vec))^2)*(k.vec/summi)) modelS.var=modelSmean.sq-modelSmean^2 #var(x)=E(X^2)-(E(X))^2 } upper=min(ceiling(modelSmean+5*modelS.var), K) lower=max(floor(modelSmean-5*modelS.var),0) if (is.element("mp.Kdist",names(bmao$mprior.info))) { prior = bmao$mprior.info$mp.Kdist } else if (is.element("theta", names(bmao$arguments))) { #kept for historical reasons theta=bmao$arguments$theta if(theta=="random"){ beta.bin=function(a=1,b=(K-m)/m,K=K,w=0:K){ return(lgamma(a+b)-(lgamma(a)+lgamma(b)+lgamma(a+b+K))+log(choose(K,w))+lgamma(a+w)+lgamma(b+K-w)) } prior=exp(beta.bin(a=1,b=(K-m)/m,K=K,w=0:K)) } if(theta!="random"){ prior=stats::dbinom(x=0:K,size=K,prob=m/K,log=FALSE)} } else { prior = rep(NA,length(kvec)) } mat=cbind(kvec,prior) #do the plot upper.ylim=max(kvec,prior, na.rm=TRUE) if(is.null(ksubset)){ksubset=(lower:upper)} dotargs=.adjustdots(dotargs,type="l",ylim=c(0,1.1*upper.ylim),lwd=1.5,xaxt="n",col=c("steelblue3","tomato"),main=paste("Posterior Model Size Distribution","\n","Mean:",round(modelSmean,4)),cex.main=0.8,xlab="Model Size",ylab="", lty=1:2, pch=4, cex.axis=.9) matsubset=mat[ksubset+1,] eval(as.call(c(list(as.name("matplot"),as.name("matsubset")),as.list(dotargs)))) #matplot(mat[ksubset,],type="l",xaxt=xaxt,col=col, # main=ifelse(is.null(main),paste("Posterior Model Size Distribution","\n","Mean:", # round(modelSmean,4)),main),cex.main=cex.main,xlab=xlab, # ylim=c(0,1.1*upper.ylim),lwd=lwd,ylab=ylab,...) if (as.logical(do.grid)) graphics::grid() graphics::points(kvec[ksubset+1],cex=0.8,pch=eval(dotargs$pch)) #if(lower==0){ # graphics::axis(1, las=1, at =1:length(lower:upper), label = c(0:K)[lower:(upper+1)],cex.axis=0.7) #} #else{ # axis(1, las=1, at =1:length(lower:upper), label = c(0:K)[lower:upper],cex.axis=0.7) #} graphics::axis(1, las=1, at =1:length(ksubset), labels = ksubset, cex.axis=eval(dotargs$cex.axis)) if (include.legend) { if (is.null(prior)||all(is.na(prior))) { graphics::legend(x="topright",lty=eval(dotargs$lty),legend=c("Posterior"),col=eval(dotargs$col),ncol=1,bty="n",lwd=eval(dotargs$lwd)) } else { graphics::legend(x="topright",lty=eval(dotargs$lty),legend=c("Posterior","Prior"),col=eval(dotargs$col),ncol=2,bty="n",lwd=eval(dotargs$lwd)) } } return(invisible(list(mean=modelSmean,var=modelS.var,dens=kvec))) } #' Coefficient Marginal Posterior Densities #' #' Calculates the mixture marginal posterior densities for the coefficients #' from a BMA object and plots them #' #' The argument \code{addons} specifies what additional information should be #' added to the plot(s) via the low-level commands \code{\link{lines}} and #' \code{\link{legend}}:\cr \code{"e"} for the posterior expected value (EV) of #' coefficients conditional on inclusion (see argument \code{exact=TRUE} in #' \code{\link{coef.bma}}),\cr \code{"s"} for 2 times posterior standard #' deviation (SD) bounds,\cr \code{"m"} for the posterior median,\cr \code{"b"} #' for posterior expected values of the individual models whom the density is #' averaged over,\cr \code{"E"} for posterior EV under MCMC frequencies (see #' argument \code{exact=FALSE} in \code{\link{coef.bma}}),\cr \code{"S"} for #' the corresponding SD bounds (MCMC),\cr \code{"p"} for plotting the Posterior #' Inclusion Probability above the density plot,\cr \code{"l"} for including a #' \code{\link{legend}}, \code{"z"} for a zero line, \code{"g"} for adding a #' \code{\link{grid}} #' #' Any combination of these letters will give the desired result. Use #' \code{addons=""} for not using any of these.\cr In case of #' \code{density.zlm}, only the letters \code{e}, \code{s}, \code{l}, \code{z}, #' and \code{g} will have an effect. #' #' @aliases density.bma density.zlm #' @param x A bma object (see \code{\link{bms}}) or a \code{\link{zlm}} object. #' @param reg A scalar integer or character detailing which covariate's #' coefficient should be plotted. If \code{reg=NULL} (default), then all #' regressors are plotted one after the other, waiting for user interaction. #' @param addons character. Specifies which additional information should be #' added to the plot via low-level commands (see 'Details' below). #' @param std.coefs logical. If \code{TRUE} then the posterior density is #' estimated for standardized coefficients (representing the case where all #' variables have mean zero and standard deviation 1) - default is #' \code{FALSE}. #' @param n numeric. the number of equally spaced points at which the density #' is to be estimated. #' @param plot logical. If \code{TRUE} (default), the density is plotted; if #' \code{FALSE} then \code{density.bma} only returns the estimated posterior #' densities without plotting. #' @param hnbsteps even integer, default 30. The number of numerical #' integration steps to be used in case of a hyper-g prior (cf. argument #' \code{g} in \code{\link{bms}}). Increase this number to increase accuracy. #' @param addons.lwd scalar, default 1.5. Line width to be used for the #' low-level plotting commands specified by \code{addons}. Cf. argument #' \code{lwd} in \code{\link{par}} #' @param \dots Additional arguments for \code{\link{plot.default}} with #' sensible defaults #' @return The function returns a list containing objects of the class #' \code{\link{density}} detailing the marginal posterior densities for each #' coefficient provided in \code{reg}.\cr In case of \code{density.zlm}, simple #' marginal posterior coefficient densities are computed, while #' \code{density.bma} calculates there mixtures over models according to #' posterior model probabilities.\cr These densities contain only the density #' points apart from the origin. (see 'Note' below) #' #' As long as \code{plot=TRUE}, the densities are plotted too. Note that (for #' \code{density.bma}) if the posterior inclusion probability of a covariate is #' zero, then it will not be plotted, and the returned density will be #' \code{list(x=numeric(n),y=numeric(n))}. #' @note The computed marginal posterior densities from \code{density.bma} are #' a Bayesian Model Averaging mixture of the marginal posterior densities of #' the individual models. The accuracy of the result therefore depends on the #' number of 'best' models contained in \code{x} (cf. argument \code{nmodel} in #' \code{\link{bms}}). #' #' The marginal posterior density can be interpreted as 'conditional on #' inclusion': If the posterior inclusion probability of a variable is smaller #' than one, then some of its posterior density is Dirac at zero. Therefore #' the integral of the returned density vector adds up to the posterior #' inclusion probability, i.e. the probability that the coefficient is not #' zero. #' #' Correspondingly, the posterior EV and SD specified by \code{addons="es"} are #' based on 'best' model likelihoods ('exact') and are conditional on #' inclusion. They correspond to the results from command #' \code{coef.bma(x,exact=TRUE,condi.coef=TRUE,order.by.pip=FALSE)} (cf. the #' example below). #' #' The low-level commands enacted by the argument \code{addons} rely on colors #' of the \code{\link{palette}}: color 2 for \code{"e"} and \code{"s"}, color 3 #' for \code{"m"}, color 8 for \code{"b"}, color 4 for \code{"E"} and #' \code{"S"}. The default colors may be changed by a call to #' \code{\link{palette}}. #' #' Up to BMS version 0.3.0, \code{density.bma} may only cope with built-in #' \code{gprior}s, not with any user-defined priors. #' #' @seealso \code{\link{quantile.coef.density}} for extracting quantiles, #' \code{\link{coef.bma}} for similar concepts, \code{\link{bms}} for creating #' bma objects #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords aplot utilities #' @examples #' #' #' data(datafls) #' mm=bms(datafls) #' #' density(mm,reg="SubSahara") #' density(mm,reg=7,addons="lbz") #' density(mm,1:9) #' density(mm,reg=2,addons="zgSE",addons.lwd=2,std.coefs=TRUE) #' #' # plot the posterior density only for the very best model #' density(mm[1],reg=1,addons="esz") #' #' #' #using the calculated density for other purposes... #' dd=density(mm,reg="SubSahara") #' plot(dd) #' #' dd_list=density(mm,reg=1:3,plot=FALSE,n=400) #' plot(dd_list[[1]]) #' #' #' #Note that the shown density is only the part that is not zero #' dd=density(mm,reg="Abslat",addons="esl") #' pip_Abslat=sum(dd$y)*diff(dd$x)[1] #' #' #this pip and the EV conform to what is done by the follwing command #' coef(mm,exact=TRUE,condi.coef=TRUE)["Abslat",] #' #' @export density.bma <- function(x,reg=NULL,addons="lemsz",std.coefs=FALSE,n=300,plot=TRUE,hnbsteps=30,addons.lwd=1.5,...) { # x: a bma object # reg: the covariate to be calculated; can be character or integer # addons: low-level additions to the plot: "e" for post. expected value, "s" for 2 times Std.Dev. bounds, # "l" for including a legend, "m" for the median, "z" for a zero line # "b" for posterior exp. values of the individual models # "E" for post. exp. value under MCMC frequencies, "S" for the corresponding SD bounds (MCMC) # ?"g" for adding a grid() # "p" for an additional box drawing the PIP # n: the number of points for which the density should be estimated # std.coefs: If TRUE then plot post. dist. of standardized coefficients # plot: if FALSE, then only the density is returned; # hnbsteps: steps for numerical integration in case of a hyper-g prior # addons.lwd: lty for addons lines #...: parameters passed on to plot # #returns a list of class "density" (cf. ?density) dtcm=function(x,df,ncp,varp) { #a wrapper for univariate t-dist with non-centrality parameter and variance parameter # (variance is df/(df-2)*varp) sqvarp=sqrt(varp) stats::dt((x-ncp)/sqvarp,df=df)/sqvarp } dsgivenykernel <- function(sf,kpa,N,z) { #the post. density of the shrinkge factor f(s|Y)*F((N-1)/2,1,(k+a)/2,R2) (kpa-2)/2*(1-sf)^((kpa-4)/2)*(1-sf*z)^(-(N-1)/2) } #check user input and get basic info dotargs = match.call(expand.dots=FALSE)$... bmao=x if (!is.bma(bmao)) stop("Argument bmao needs to be a bma object") if (hnbsteps%%2) stop("Argument nbsteps needs to be an even integer") nbsteps=max(hnbsteps,2) n=max(ceiling(n),1) N=bmao$info$N; K=bmao$info$K if(is.null(reg)) reg=1:K nameix=1:K; names(nameix)=bmao$reg.names; reg=nameix[reg] #if (is.na(reg)) stop("Argument reg is out of bounds") ishyper=(bmao$gprior$gtype=="hyper") tm=bmao$topmod bools=(tm$bool_binary()) betas=tm$betas() betas2=tm$betas2() if (std.coefs) { #if standardized coefficients are wanted, then adjust moments accordingly sddata=apply(as.matrix(bmao$arguments$X.data),2,stats::sd) betas=diag(sddata[-1])%*%betas/sddata[1] betas2=diag(sddata[-1]^2)%*%betas2/sddata[1]^2 } sigmadiag=(betas2-betas^2)*(N-3)/(N-1) #the variance parameters for the t-dist pmps=pmp.bma(bmao$topmod,oldstyle=TRUE)[,1] #calc post model probs (exact) #the stuff below is similar to estimates.bma(bmao, condi.coef=TRUE,exact=TRUE) pips=c(tcrossprod(bools,t(pmps))) Eb1=c(tcrossprod(betas,t(pmps)))/pips Ebsd=sqrt(c(tcrossprod(betas2,t(pmps)))/pips-Eb1^2); Ebsd[is.nan(Ebsd)]=0; Eb1[is.nan(Eb1)]=0 Eball=cbind(Eb1,Ebsd) #conditional coefficients if ((any(grep("E",addons,ignore.case=FALSE)))|(any(grep("S",addons,ignore.case=FALSE)))) { #in case the user wants to include MCMC results (see estimates.bma(,exact=FALSE), #then compute them here Eb1.mcmc = bmao$info$b1mo/bmao$info$inccount Ebsd.mcmc = sqrt(bmao$info$b2mo/bmao$info$inccount-Eb1.mcmc^2) if (std.coefs) { sddata=apply(as.matrix(bmao$arguments$X.data),2,stats::sd) Eb1.mcmc=Eb1.mcmc*sddata[-1]/sddata[1]; Ebsd.mcmc=Ebsd.mcmc*sddata[-1]/sddata[1]; } } if (ishyper) { #in case of hyper-g, we cannot rely on sigmadiag, but have to numerically integrate over #differnt shrinkages, for that we need this yXdata=as.matrix(bmao$arguments$X.data); yXdata=yXdata-matrix(colMeans(yXdata),N,K+1,byrow=TRUE) if (std.coefs) yXdata=yXdata%*%diag(1/sddata) yty=c(crossprod(yXdata[,1])) positions=lapply(lapply(as.list(as.data.frame(bools)),as.logical),which) olsmodels=lapply(lapply(positions,.ols.terms2,yty=yty,N=N,K=K,XtX.big=crossprod(yXdata[,-1]),Xty.big=c(crossprod(yXdata[,-1],yXdata[,1]))),function (x) x$full.results()) f21a=bmao$gprior.info$hyper.parameter } plotndens <- function(ix,doplot=FALSE) { #this function does marginal density and plot for a specific covariate #depends heavily on parent scope! sss=function(lbound,uboundp1,nbsteps) { #simple simpson integration over shrinkage factor #this is just a convenience function and depends on variables in parent scope, only used in case of hyper-g s.seq=seq(lbound,uboundp1,(uboundp1-lbound)/nbsteps)[-nbsteps] tmat=sapply(as.list(s.seq),function(ss) { dtcm(seqs,N-1,ss*bhati,invdiagi*ss*(1-ss*z)/(N-1)*yty)}) #matrix of t-densities for different s smat=sapply(as.list(s.seq),dsgivenykernel, kpa=k+f21a,N=N,z=z) #vector of posterior densities for the differnet s if (any(is.infinite(smat))) smat[is.infinite(smat)]=0 intconst=(4*sum(smat[c(FALSE,TRUE)])+2*sum(smat[c(TRUE,FALSE)])-3*smat[nbsteps]-smat[1])*(s.seq[nbsteps]-s.seq[1])/nbsteps/3 #calc the value of F((N-1)/2,1,(k+a)/2,R2) return(list(dv=c(4*tmat[,c(FALSE,TRUE)]%*%smat[c(FALSE,TRUE)]+2*tmat[,c(TRUE,FALSE)]%*%smat[c(TRUE,FALSE)]-3*tmat[,nbsteps]*smat[nbsteps]-tmat[,1]*smat[1])*(s.seq[nbsteps]-s.seq[1])/nbsteps/3, ic=intconst)) #return the estimated density and a normalization constant #is done in parts because it may be recomposed later } if (pips[ix]==0) { reslist=list(x=numeric(n),y=numeric(n),n=n,call=sys.call(),data.name=names(nameix)[ix],has.na=FALSE); class(reslist)=c("density", "coef.density") return(reslist) } #the x vector lbound=min(betas[ix,as.logical(bools[ix,])])-3*Eball[ix,2]; ubound=max(betas[ix,as.logical(bools[ix,])])+3*Eball[ix,2]; seqs=seq(lbound,ubound,(ubound-lbound)/(n-1)) densvec=numeric(length(seqs)) #loop through models and calc post dens for (m in 1:length(pmps)) { if (bools[ix,m]) { if (ishyper) { ixadj=sum(bools[1:ix,m]) bhati=olsmodels[[m]]$bhat[[ixadj]]; invdiagi=olsmodels[[m]]$diag.inverse[[ixadj]]; k=sum(bools[,m]); Esf=betas[ix,m]/bhati; z=1-olsmodels[[m]]$ymy/yty; midpoint=1-(1-Esf)*4 if (midpoint<.5) { dvl=sss(.0001,.9999999,nbsteps*2); addvec=dvl$dv/dvl$ic } else { dvl1=sss(.0001,midpoint,nbsteps); dvl2=sss(midpoint,1,nbsteps) addvec=(dvl1$dv+dvl2$dv)/(dvl1$ic+dvl2$ic) } } else { addvec=dtcm(seqs,N-1,betas[ix,m],sigmadiag[ix,m]) } densvec=densvec+pmps[m]*addvec } } reslist=list(x=seqs,y=densvec,bw=NULL,n=n,call=sys.call(),data.name=names(nameix)[ix],has.na=FALSE); class(reslist)="density" if (!doplot) { return(reslist) } #plot stuff main_default=paste("Marginal Density:",names(nameix)[ix],"(PIP",round(c(crossprod(pmps,bools[ix,]))*100,2),"%)") if (any(grep("p",addons,ignore.case=TRUE))) { decr=.12; parplt=graphics::par()$plt; parplt_temp=parplt; parplt_temp[4]=(1-decr)*parplt[4] +decr*parplt[3]; graphics::par(plt=parplt_temp) main_temp=main_default; main_default=NULL # graphics::layout(1:2,heights=c(.1,1)) # opm=par()$mar # par(mar=c(0,opm[2],1,opm[4])) # plot(0,type="n",xlim=0:1,ylim=0:1,xaxt="n",yaxt="n",bty="n",xlab="",ylab="") # mtext("PIP",side=2,las=2) # rect(0,0,1,1,col=8) # rect(0,0,pips[ix],1,col=9) # par(mar=opm) } dotargs=.adjustdots(dotargs,type="l", col="steelblue4", main=main_default, xlab=if (std.coefs) "Standardized Coefficient" else "Coefficient", ylab = "Density"); # if (!is.element('type',names(dotargs))) { dotargs$type <- "l" } # if (!is.element('col',names(dotargs))) { dotargs$col <- "steelblue4" } # if (!is.element('main',names(dotargs))) { dotargs$main=paste("Marginal Density:",names(nameix)[ix],"(PIP",round(c(crossprod(pmps,bools[ix,]))*100,2),"%)") } # if (!is.element('xlab',names(dotargs))) { dotargs$xlab <- if (std.coefs) "Standardized Coefficient" else "Coefficient" } # if (!is.element('ylab',names(dotargs))) { dotargs$ylab <- "Density" } # eval(as.call(c(list(as.name("plot"),x=as.name("seqs"),y=as.name("densvec")),as.list(dotargs)))) leg.col=numeric(0);leg.lty=numeric(0); leg.legend=character(0) if (any(grep("g",addons,ignore.case=TRUE))) { # grid graphics::grid() } if (any(grep("b",addons,ignore.case=TRUE))) { # post exp values of the individual models for (m in 1:length(pmps)) { Ebm=betas[ix,m] ; if (as.logical(Ebm)) { Ebheight=min(densvec[max(sum(seqs0)) { #legend leg.pos="topright"; if (Eball[ix,1]>seqs[floor(n/2)]) leg.pos="topleft"; graphics::legend(x=leg.pos,lty=leg.lty,col=leg.col,legend=leg.legend,box.lwd=0,bty="n",lwd=addons.lwd) } if (any(grep("p",addons,ignore.case=TRUE))) { pusr=graphics::par()$usr graphics::rect(pusr[1],pusr[4]*(1+decr*.2), pusr[2], pusr[4]*(1+decr),xpd=TRUE,col=8) graphics::rect(pusr[1],pusr[4]*(1+decr*.2), pips[ix]*pusr[2]+(1-pips[ix])*pusr[1], pusr[4]*(1+decr),xpd=TRUE,col=9) graphics::mtext("PIP:",side=2, las=2,line=1, at=pusr[4]*(1+decr*.6)) graphics::par(plt=parplt) graphics::title(main_temp) } return(reslist) } densres=list() oldask=graphics::par()$ask plots=0 for (vbl in 1:length(reg)) { doplot=(if (as.logical(pips[reg[vbl]])) plot else FALSE) plots=plots+doplot if (plots==2) {graphics::par(ask=TRUE)} densres[[nameix[vbl]]]=plotndens(reg[vbl],doplot) densres[[nameix[vbl]]]$call=sys.call() #call("density.bma",bmao=bmao,reg=reg,n=300,hnbsteps=30) } graphics::par(ask=oldask) if (length(densres)==1) densres=densres[[1]] else class(densres) = c("coef.density",class(densres)) if (!plot) return(densres) if (plot&(plots==0)) {warning("No plot produced as PIPs of provided variables are zero under 'exact' estimation.")} return(invisible(densres)) } #' Posterior Density of the Shrinkage Factor #' #' Calculates the mixture marginal posterior density for the shrinkage factor #' (g/(1+g)) from a BMA object under the hyper-g prior and plots it #' #' The function \code{gdensity} estimates and plots the posterior density for #' the shrinkage factor \eqn{g/(1+g)}\cr This is evidently only possible if the #' shrinkage factor if not fixed, i.e. if the bma object \code{x} was estimated #' with a hyper-g prior - cf. argument \code{g} in \code{\link{bms}}\cr The #' density is based only on the best models retained in the bma object #' \code{x}, cf. argument \code{nmodel} in \code{\link{bms}}\cr A note on #' argument \code{n}: The points at which the density is estimated start at #' \eqn{max(0,E-5*SD)}, where \eqn{E} and \eqn{SD} are the expected value and #' standard deviation of the shrinkage factor, respectively. For plotting the #' entire domain \eqn{(0,1)} use \code{xlim=c(0,1)} as an argument for #' \code{gdensity}. #' #' The argument \code{addons} specifies what additional information should be #' added to the plot(s) via the low-level commands \code{\link{lines}} and #' \code{\link{legend}}:\cr \code{"e"} for the posterior expected value (EV) of #' the shrinkage factor,\cr \code{"s"} for 2 times posterior standard deviation #' (SD) bounds,\cr \code{"m"} for the posterior median,\cr \code{"f"} for #' posterior expected values of the individual models whom the density is #' averaged over,\cr \code{"z"} for a zero line, \code{"l"} for including a #' \code{\link{legend}}\cr The following two are only possible if the bma #' object collected statistics on shrinkage, cf. argument \code{g.stats} in #' \code{\link{bms}} \code{"E"} for posterior expected value under MCMC #' frequencies (see argument \code{exact} in \code{\link{coef.bma}}),\cr #' \code{"S"} for the corresponding 2 times standard deviation bounds #' (MCMC),\cr #' #' Any combination of these letters will give the desired result. Use #' \code{addons=""} for not using any of these. #' #' @param x A bma object (see \code{\link{bms}}). #' @param n The integer number of equally spaced points at which the density is #' to be estimated. see 'Details' below #' @param addons character, defaulting to \code{"zles"}. Specifies which #' additional information should be added to the plot via low-level commands #' (see 'Details' below). #' @param plot logical. If \code{TRUE} (default), the density is plotted; if #' \code{FALSE} then \code{gdensity} only returns the estimated posterior #' density without plotting. #' @param addons.lwd scalar, default 1.5. Line width to be used for the #' low-level plotting commands specified by \code{addons}. Cf. argument #' \code{lwd} in \code{\link{par}} #' @param \dots Additional arguments for \code{\link{plot.default}} with #' sensible defaults #' @return \code{gdensity} returns an object of the class \code{\link{density}} #' detailing the posterior mixture density of the shrinkage factor. #' @note The computed marginal posterior density is a Bayesian Model Averaging #' mixture of the marginal posterior densities of the shrinkage factor under #' individual models. The accuracy of the result therefore depends on the #' number of 'best' models contained in \code{x} (cf. argument \code{nmodel} in #' \code{\link{bms}}). #' #' Correspondingly, the posterior EV and SD specified by \code{addons="es"} are #' based on 'best' model likelihoods ('exact') and are conditional on #' inclusion. #' #' The low-level commands enacted by the argument \code{addons} rely on colors #' of the \code{\link{palette}}: color 2 for \code{"e"} and \code{"s"}, color 3 #' for \code{"m"}, color 8 for \code{"f"}, color 4 for \code{"E"} and #' \code{"S"}. The default colors may be changed by a call to #' \code{\link{palette}}. #' #' @seealso \code{\link{density.bma}} for computing coefficient densities, #' \code{\link{bms}} for creating bma objects, \code{\link{density}} for the #' general method #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords aplot utilities #' @examples #' #' #' data(datafls) #' mm=bms(datafls,g="hyper=UIP") #' #' gdensity(mm) # default plotting #' #' # the grey bars represent expected shrinkage factors of the individual models #' gdensity(mm,addons="lzfes") #' #' # #plotting the median 'm' and the posterior mean and bounds based on MCMC results: #' gdensity(mm,addons="zSEm",addons.lwd=2) #' #' # plot the posterior shrinkage density only for the very best model #' gdensity(mm[1],addons="esz") #' #' #' #using the calculated density for other purposes... #' dd=gdensity(mm,plot=FALSE) #' plot(dd) #' #' @export gdensity <- function(x,n=512,plot=TRUE,addons="zles",addons.lwd=1.5,...) { #main="Posterior Density of the Shrinkage Factor",type="l",xlab="Shrinkage factor",ylab="Density",col="steelblue4" # plots posterior density of shrinkage factor for hyper-g bma objects # INPUTS: # x: bma object # n: number of equally spaced points where density is computed # plot: whether to do a plot in addition or not # addons: low-level plot commands: draw "z"=zero line, "l"=legend, "e"=exact exp. val, "s"=2x St dev bounds (exact), "E"=MCMC exp. val., "S"=MCMC SD bounds, "g"=shrinkage Exp vals for individual models # addons.lwd: linwe width for addons stuff # ... commands pased on to plot.default dsgivenykernel <- function(kpazvec,sf,N) { #the post. density of the shrinkge factor f(s|Y)*F((N-1)/2,1,(k+a)/2,R2) #kpazvec is a vector with two elements: first element is k+a, second is z (the R-squared) #sf: a vector of shrinkage values #N: sample size (kpazvec[[1]]-2)/2*(1-sf)^((kpazvec[[1]]-4)/2)*(1-sf*kpazvec[[2]])^(-(N-1)/2) #mode is at sf=(N-k-a+3)/(N-1-z*(k+a-4)) } #user checks if (!is.bma(x)) stop("argument needs to an object of class 'bma'") if (!(x$gprior$gtype=="hyper")) stop("g prior density makes only sense for hyper-g prior.") if (n<2) stop("n needs to be at least 2") n=floor(n); #if (!(n%%2)) {n=n+1} dotargs = match.call(expand.dots=FALSE)$... #extract info N=x$info$N; K=x$info$K tm=x$topmod bools=tm$bool_binary() betas=tm$betas() betas2=tm$betas2() smoments = tm$fixed_vector() #re-compute the R-squareds and other stuff for the topmodels yXdata=as.matrix(x$arguments$X.data); yXdata=yXdata-matrix(colMeans(yXdata),N,K+1,byrow=TRUE) yty=c(crossprod(yXdata[,1])) positions=lapply(lapply(as.list(as.data.frame(bools)),as.logical),which) #vector of who is in where ymyvec=unlist(lapply(lapply(positions,.ols.terms2,yty=yty,N=N,K=K,XtX.big=crossprod(yXdata[,-1]),Xty.big=c(crossprod(yXdata[,-1],yXdata[,1]))),function (x) x$full.results()$ymy)) # vector of SSResid kvec=tm$kvec_raw() #vector of parameter number zvec=1-ymyvec/yty #vector of r.-squared pmpexact=pmp.bma(x,oldstyle=TRUE)[,1] #vector of 'exact' posterior model probs f21a=x$gprior.info$hyper.parameter #hyper parameter if (length(smoments)==0) { #if not there, re-compute individual moments for models #lprob=.lprob.hyperg.init(N=N,K=K,yty=yty,f21a=f21a, return.gmoments=TRUE) lprob = x$gprior.info$lprobcalc smoments = sapply(lapply(as.list(as.data.frame(rbind(kvec,ymyvec))),function(x) lprob$lprob.all(ymy=x[2],k=x[1],bhat=numeric(x[1]),diag.inverse=rep(1,x[1]))),"[[","otherstats") } Es=c(crossprod(smoments[1,],pmpexact)) #exp val Es2=c(crossprod(smoments[2,],pmpexact)) Esd = sqrt(Es2-Es^2) #st dev nbsteps=n cutoff=max(0,Es-5*Esd) #this is to concentrate on where the mass is sdiff=(1-cutoff)/(nbsteps+1) s.seq=seq(sdiff+cutoff,cutoff+nbsteps*sdiff,sdiff) sdensl=lapply(as.list(as.data.frame(rbind(kvec+f21a,zvec))),dsgivenykernel,sf=s.seq,N=N) intconsts=lapply(lapply(sdensl,sum),"*",sdiff) # a crude numerical integration to save time sdensvecs=mapply("/",sdensl,intconsts) #normalize by integration constants sdens=sdensvecs%*%pmpexact #mixture density reslist = list(x=s.seq,y=sdens,bw=NULL,n=n,call=sys.call(),data.name="Shrinkage",has.na=FALSE); class(reslist)="density" if (!plot) {return(reslist)} ##### PLOTTING ######################### #Main plot dotargs=.adjustdots(dotargs,ylab="Density", xlab = "Shrinkage factor", main = "Posterior Density of the Shrinkage Factor", type="l", col="steelblue4") eval(as.call(c(list(as.name("plot"),as.name("s.seq"),as.name("sdens")),as.list(dotargs)))) #preparing stuff for addons leg.col=numeric(0);leg.lty=numeric(0); leg.legend=character(0) if (any(grep("f",addons,ignore.case=TRUE))) { # post exp values of the individual models for (m in 1:length(pmpexact)) { Esm=smoments[1,m] ; if (as.logical(Esm)) { ixlower=max(sum(s.seq1) graphics::abline(v=Es+2*Esd,col=2,lty=2,lwd=addons.lwd) leg.col=c(leg.col,2);leg.lty=c(leg.lty,2);leg.legend=c(leg.legend,"2x SD") } if (any(grep("m",addons,ignore.case=TRUE))) { # posterior median median_index=sum(cumsum(sdens)0) graphics::abline(v=ES-2*SDs,col=4,lty=2,lwd=addons.lwd) if (ES+2*SDs<1) graphics::abline(v=ES+2*SDs,col=4,lty=2,lwd=addons.lwd) leg.col=c(leg.col,4);leg.lty=c(leg.lty,2); leg.legend=c(leg.legend,"2x SD (MCMC)") } } if (any(grep("l",addons,ignore.case=TRUE))&(length(leg.col)>0)) { #legend leg.pos="topleft"; graphics::legend(x=leg.pos,lty=leg.lty,col=leg.col,legend=leg.legend,box.lwd=0,bty="n") } return(invisible(reslist)) } #' @rdname quantile.pred.density #' @export quantile.density = function(x, probs=seq(.25,.75,.25), names=TRUE, normalize=TRUE, ...) { # a generic function for objects of class density or lists whose elements are densities # the actual subfunction for object of class "density" my.quantile.density = function(x, probs, names, normalize, ...) { ycs=(cumsum(x$y)-(x$y-x$y[[1]])/2)*diff(x$x[1:2]) if (normalize) ycs=ycs/(ycs[[length(ycs)]]) xin=x$x; maxi=length(ycs) qqs=sapply(as.list(probs), function(qu) {iii=sum(ycs<=qu); if (iii==maxi) return(Inf) else if (iii==0L) return(-Inf) else { return( xin[[iii+1]] + ( (ycs[[iii+1]]-qu)/(ycs[[iii+1]]-ycs[[iii]]) ) *(xin[[iii]]-xin[[iii+1]]) ) }}) if (as.logical(names)) names(qqs)= paste(format(100 * probs, trim = TRUE, digits = max(2L, getOption("digits"))), "%", sep = "")#paste(signif(probs,5),"%",sep="") return(qqs) } # user checks probs=as.vector(probs) if (is.element("density",class(x))) return(my.quantile.density(x=x, probs=probs, names=names, normalize=normalize)) if (!all(sapply(x,function(dd) is.element("density",class(dd))))) stop("x needs to be a density or list of densities") if (length(x)==1L) return(my.quantile.density(x=x[[1]], probs=probs, names=names, normalize=normalize)) #combining a list of denisties qout=sapply(x, my.quantile.density, probs=probs, names=FALSE, normalize=normalize) #formatting the output into a matrix if (!is.matrix(qout)) { #some formatting if (length(probs)>1) return(qout) qout=as.matrix(qout) } else qout=t(qout) if (as.logical(names)) colnames(qout)= paste(format(100 * probs, trim = TRUE, digits = max(2L, getOption("digits"))), "%", sep = "") return(qout) } .quantile.density=quantile.density #' @rdname quantile.pred.density #' @export quantile.coef.density = function(x, probs=seq(.25,.75,.25), names=TRUE, ...) { #customizing quantile.density to stuff resulting from density.bma quout= .quantile.density(x, probs=probs, names=names, normalize=TRUE) if (is.matrix(quout)&&as.logical(names)) rownames(quout) <- sapply(x, function(lx) lx[["data.name"]]) return(quout) } #' Extract Quantiles from 'density' Objects #' #' Quantiles for objects of class "density", "pred.density" or "coef.density" #' #' The methods \code{quantile.coef.density} and \code{quantile.pred.density} #' both apply \code{quantile.density} to densities nested with object of class #' \code{coef.density} or \code{pred.density}.\cr The function #' \code{quantile.density} applies generically to the built-in class #' \code{\link{density}} (as least for versions where there is no such method #' in the pre-configured packages).\cr Note that \code{quantile.density} relies #' on trapezoidal integration in order to compute the cumulative densities #' necessary for the calculation of quantiles. #' #' @aliases quantile.pred.density quantile.coef.density quantile.density #' @param x a object of class \code{\link{pred.density}}, \code{coef.density}, #' \code{\link{density}}, or a list of densities. #' @param probs numeric vector of probabilities with values in [0,1] - elements #' very close to the boundaries return \code{Inf} or \code{-Inf} #' @param names logical; if \code{TRUE}, the result has a \code{names} #' attribute, resp. a \code{rownames} and \code{colnames} attributes. Set to #' \code{FALSE} for speedup with many probs. #' @param normalize logical; if \code{TRUE} then the values in \code{x$y} are #' multiplied with a factor such that their integral is equal to one. #' @param \dots further arguments passed to or from other methods. #' @return If \code{x} is of class \code{density} (or a list with exactly one #' element), a vector with quantiles.\cr If \code{x} is a \code{\link{list}} of #' densities with more than one element (e.g. as resulting from #' \code{pred.density} or \code{coef.density}), then the output is a matrix of #' quantiles, with each matrix row corresponding to the respective density. #' @author Stefan Zeugner #' @seealso \code{\link{quantile.default}} for a comparable function, #' \code{\link{pred.density}} and \code{\link{density.bma}} for the #' BMA-specific objects. #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' mm = bms(datafls[1:70,], user.int=FALSE) #' #' #predict last two observations with preceding 70 obs: #' pmm = pred.density(mm, newdata=datafls[71:72,], plot=FALSE) #' #'standard error' quantiles #' quantile(pmm, c(.05, .95)) #' #' #Posterior density for Coefficient of "GDP60" #' cmm = density(mm, reg="GDP60", plot=FALSE) #' quantile(cmm, probs=c(.05, .95)) #' #' #' #application to generic density: #' dd1 = density(rnorm(1000)) #' quantile(dd1) #' #'\dontrun{ #' #application to list of densities: #' quantile.density( list(density(rnorm(1000)), density(rnorm(1000))) ) #' } #' #' @export quantile.pred.density = function(x, probs=seq(.25,.75,.25), names=TRUE, ...) { #customizing quantile.density to stuff resulting from pred.density quout= .quantile.density(x$densities(), probs=probs, names=names, normalize=FALSE) if (is.matrix(quout)&&as.logical(names)) rownames(quout) <- names(x$fit) return(quout) } #' Plot Convergence of BMA Sampler #' #' Plots the posterior model probabilites based on 1) marginal likelihoods and #' 2) MCMC frequencies for the best models in a 'bma' object and details the #' sampler's convergence by their correlation #' #' A call to bms with a MCMC sampler (e.g. #' \code{bms(datafls,mcmc="bd",nmodel=100)} uses a Metropolis-Hastings #' algorithm to sample through the model space: the frequency of how often #' models are drawn converges to the distribution of their posterior marginal #' likelihoods.\cr While sampling, each 'bma' object stores the best models #' encountered by its sampling chain with their marginal likelihood and their #' MCMC frequencies.\cr \code{plotConv} compares the MCMC frequencies to #' marginal likelihoods, and thus visualizes how well the sampler has #' converged. #' #' @param bmao an object of class 'bma' - see \code{\link{bms}} #' @param include.legend whether to include a \code{\link{legend}} in the plot #' @param add.grid whether to include a \code{\link{grid}} in the plot #' @param \dots other parameters for \code{\link{matplot}} #' @note \code{plotConv} is also used by \code{\link{plot.bma}} #' #' @seealso \code{\link{pmp.bma}} for posterior model probabilites based on the #' two concepts, \code{\link{bms}} for creating objects of class 'bma' #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords aplot #' @examples #' #' #' data(datafls) #' mm=bms(datafls[,1:12],user.int=FALSE) #' #' plotConv(mm) #' #' #is similar to #' matplot(pmp.bma(mm),type="l") #' #' @export plotConv<-function(bmao,include.legend=TRUE,add.grid=TRUE,...){ #function that compares MCMC and exact PMP's for the models in bmao$topmod # bmao: bma object # include.legend: TRUE or FALSE # main: if NULL then default is "Posterior Model Probabilites (Corr: x)", where x is the correlation betw. exact PMPs and MCMC freqs # other parameters: cf. help(plot.default) if (!is.bma(bmao)) stop("submit an object of class bma") # now get PMP analytical and MCMC mat=pmp.bma(bmao,oldstyle=TRUE) norm_const = sum(mat[,1])/sum(mat[,2]) mat=cbind(mat[,2]*norm_const,mat[,1]) if (length(bmao$topmod$lik())==0L) { stop("plotConv needs at least one model stored in topmod in order to produce a plot") } cor.pmp=format(round(.cor.topmod(bmao$topmod),4),nsmall=4) dotargs = match.call(graphics::plot,expand.dots=FALSE)$... dotargs=.adjustdots(dotargs, lwd=2,main=paste("Posterior Model Probabilities\n(Corr: ",cor.pmp,")",sep=""),lty=1,col=c("steelblue3","tomato"),cex.main=0.8,xlab="Index of Models",ylab="",type="l") eval(as.call(c(list(as.name("matplot"),as.name("mat")),as.list(dotargs)))) # if (is.null(main)) main=paste("Posterior Model Probabilities\n(Corr: ",cor.pmp,")",sep="") # matplot(mat,type="l",lty=lty,col=col,lwd=lwd,main=main,xlab=xlab,cex.main=cex.main,ylab=ylab,...) if (as.logical(add.grid)) graphics::grid() if (as.logical(include.legend)) graphics::legend("topright",lty=eval(dotargs$lty),legend=c("PMP (MCMC)", "PMP (Exact)"),col=eval(dotargs$col),ncol=2,bty="n",cex=1,lwd=eval(dotargs$lwd)); } #' Compare Two or More bma Objects #' #' Plots a comparison of posterior inclusion probabilites, coefficients or #' their standard deviation between various bma objects #' #' #' @param \dots one or more objects of class 'bma' to be compared. #' \code{plotComp} passes on any other parameters in \code{\dots{}} to #' \code{\link{matplot}}. #' @param varNr optionally, covariate indices to be included in the plot, can #' be either integer vector or character vector - see examples #' @param comp a character denoting what should be compared: \code{comp="PIP"} #' (default) for posterior inclusion probabilities, \code{comp="Post Mean"} for #' coefficients, \code{comp="Post SD"} for their standard deviations, #' \code{comp="Std Mean"} or standardized coefficients, or \code{comp="Std SD"} #' for standardized standard deviations #' @param exact if \code{FALSE}, the statistics to be compared are based on #' aggregate bma statistics, if \code{TRUE}, they are based solely on the best #' models retained in the bma objects #' @param include.legend whether to include a default legend in the plot #' (custom legends can be added with the command \code{\link{legend}}) #' @param add.grid whether to add a \code{\link{grid}} to the plot #' @param do.par whether to adjust \code{par("mar")} in order to fit in the #' tick labels on the x-axis #' @param cex.xaxis font size scaling parameter for the x-axis - cf. argument #' \code{cex.axis} in \code{\link{par}} #' #' @seealso \code{\link{coef.bma}} for the underlying function #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords hplot #' @examples #' #' ## sample two simple bma objects #' data(datafls) #' mm1=bms(datafls[,1:15]) #' mm2=bms(datafls[,1:15]) #' #' #compare PIPs #' plotComp(mm1,mm2) #' #' #compare standardized coefficeitns #' plotComp(mm1,mm2,comp="Std Mean") #' #' #...based on the lieklihoods of best models #' plotComp(mm1,mm2,comp="Std Mean",exact=TRUE) #' #' #plot only PIPs for first four covariates #' plotComp(mm1,mm2,varNr=1:4, col=c("black","red")) #' #' #plot only coefficients for covariates 'GDP60 ' and 'LifeExp' #' plotComp(mm1,mm2,varNr=c("GDP60", "LifeExp"),comp="Post Mean") #' #' #' #' @export plotComp <-function(...,varNr=NULL,comp="PIP",exact=FALSE,include.legend=TRUE,add.grid=TRUE,do.par=TRUE,cex.xaxis=0.8) { #,main=NULL,type="p",lty=1:5, lwd=1.5, pch=NULL,col=NULL,cex=NULL,bg=NA,xlab="",ylab=NULL){ # this plot compares results from different bma specifications, for example different W matrices # for the SAR bma or if a new computer routine was coded we want to compare results of it with that # of former computer routines # in case you plug in results for different sets of regressors (e.g. once you use interaction sampling with # some interacted variables and compare that to results without interaction sampling) the plot looks only # at the coefficients / SD's for the set of variables that is contained in both specifications; so you can # answer questions like, if I include this set of variables, how do they impact on remaining coefficients if # the set is not included. # you can specify bmaList (list of bma results), varNr is the number of variables you want to see in the plot, # comp is one of "PIP", "Post Mean", "Post SD" or "Std Mean" or "Std SD" (standardized coefficients), # bmaNames are the names the routines get assigned in the legend (e.g. "new Model", "old Model" or "inv Dist.", "inv Dist Squared"), # main is the title of the graph and cexM is the size of the labeling of the regressor names col_default =c("#1B9E77", "#D95F02", "#7570B3", "#E7298A", "#66A61E" ,"#E6AB02", "#A6761D", "#666666") #bmaList = list(...) bmaList=list(...); bmaix=sapply(bmaList,is.bma); bmaList=bmaList[bmaix] #user check: bmaNr=length(bmaList) if(!all(sapply(bmaList,is.bma))){ stop("Submit only bma objects to compare results (no other objects)") } dotargs = match.call(expand.dots=FALSE)$...; dotargs=.adjustdots(dotargs,ylab=paste(comp),pch=1:bmaNr,col=col_default,type="p",lty=1:5,lwd=1.5,xlab="",xaxt="n") dotargs=dotargs[!c(bmaix,logical(length(dotargs)-length(bmaix)))] # # take care of name order and look whether we compare results for same set of variables! xMat=lapply(bmaList,function(x) rownames(estimates.bma(x,exact=exact))) xNames=xMat[[1]] ind=as.numeric(unlist(lapply(xMat,function(x) length(x)))) # in case we do not have the same set of vars in each submitted specification if(length(unique(ind)>1)){ smallestSet=which.min(ind) indMat=array(0:0,dim=c(length(xMat[[smallestSet]]),length(ind))) for(i in 1:length(ind)){ indMat[,i]=as.numeric(xMat[[smallestSet]] %in% xMat[[i]]) } xNamesInd=which(rowSums(indMat)==bmaNr) xNames=xMat[[smallestSet]][xNamesInd] } compNames=c(colnames(estimates.bma(bmaList[[1]])),"Std Mean", "Std Coef") if(is.null(xNames)){ stop("the bma objects have to have (the same) rownames attached to them") } if(!(comp %in% compNames)){ stop("Please specify comp as one of PIP, Post Mean, Post SD, Std Mean, or Std Coef") } if(comp=="Std Mean"){ compMatrix=sapply(bmaList,function(x) estimates.bma(x,std.coefs=TRUE,exact=exact)[xNames,"Post Mean"]) comp="Standardized Coefficients" } else if (comp=="Std SD") { compMatrix=sapply(bmaList,function(x) estimates.bma(x,std.coefs=TRUE,exact=exact)[xNames,"Post SD"]) comp="Standardized SD" } else{ compMatrix=sapply(bmaList,function(x) estimates.bma(x,exact=exact)[xNames,comp]) } bmaNames=names(list(...))[bmaix] colnames(compMatrix)=paste("Model", 1:bmaNr) if(!is.null(bmaNames) && (length(bmaNames)==ncol(compMatrix))) { for (bix in 1:bmaNr) { colnames(compMatrix)[[bix]] <- ifelse(bmaNames[[bix]]=="",paste("Model", bix), bmaNames[[bix]]) } } # in case you do not want to plot the whole stuff but only the first varNr regressors if(!is.null(varNr)){ compMatrix=compMatrix[varNr,,drop=FALSE] } # do the plot #################### if (as.logical(do.par)) { oldmar=graphics::par()$mar spaceforxaxis=graphics::strwidth(rownames(compMatrix)[which.max(nchar(rownames(compMatrix)))],units="inches", cex=cex.xaxis)*(graphics::par("mar")/graphics::par("mai"))[[2]] tempmar=oldmar; tempmar[1]=min(max(oldmar[1],spaceforxaxis+oldmar[1]/3), .5*graphics::par("fin")[[2]]*(graphics::par("mar")/graphics::par("mai"))[[1]]) graphics::par(mar=tempmar) } eval(as.call(c(list(as.name("matplot"),as.name("compMatrix")),as.list(dotargs)))) #matplot(compMatrix,main=main,type=type,col=col,cex=cex,bg=bg,ylab=ylab,xlab=xlab,xaxt="n",pch=pch,lty=lty,lwd=lwd) if (as.logical(include.legend)) { extractfromdotargs=function(...) { dal=list(...); return(list(col=dal$col,pch=dal$pch)) } myargs=eval(as.call(c(list(as.name("extractfromdotargs")),as.list(dotargs)))) graphics::legend("topright", colnames(compMatrix),pch=myargs$pch,col=myargs$col,bty="n") } if (as.logical(add.grid)) graphics::grid() graphics::axis(1, las=2, at = 1:nrow(compMatrix), labels = rownames(compMatrix),cex.axis=cex.xaxis) #graphics::layout(matrix(1)) if (as.logical(do.par)) graphics::par(mar=oldmar) } #' Plot Posterior Model Size and Model Probabilities #' #' Produces a combined plot: upper row shows prior and posterior model size #' distribution, lower row shows posterior model probabilities for the best #' models #' #' #' @param x an object of class 'bma' #' @param \dots additional arguments for \code{\link{matplot}} #' @return combines the plotting functions \code{\link{plotModelsize}} and #' \code{\link{plotConv}} #' @note The upper plot shows the prior and posterior distribution of model #' sizes (\code{\link{plotModelsize}}).\cr The lower plot is an indicator of #' how well the bma object has converged (\code{\link{plotConv}}). #' and Paul #' @seealso \code{\link{plotModelsize}} and \code{\link{plotConv}} #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords hplot #' @examples #' #' data(datafls) #' mm=bms(datafls,user.int=FALSE) #' #' plot(mm) #' @export plot.bma <-function(x,...) { # does a combined plot of plotConv and plotModelsize for bma object bmao # bmao: bma object # ... passes on parameters to these two functions if (!is.bma(x)) stop("Need to provide object of class 'bma'!") if (x$arguments$nmodel<3) { try(plotModelsize(x,...),silent=TRUE) } else { graphics::layout(matrix(1:2,2,1)) try(plotModelsize(x,...),silent=TRUE) try(plotConv(x,...),silent=TRUE) graphics::layout(1) } } #' Plot Signs of Best Models #' #' Plots a grid with signs and inclusion of coefficients vs. posterior model #' probabilities for the best models in a 'bma' object: #' #' Under default settings, blue corresponds to positive sign, red to a negative #' sign, white to non-inclusion. #' #' @param x a list of class bma (cf. \code{\link{bms}} for further details) #' @param yprop2pip if \code{yprop2pip=TRUE} then the grid lines on the #' vertical axis are scaled according to the coefficients' inclusion #' probabilites.\cr If \code{yprop2pip=FALSE} (default) then the grid lines on #' the vertical axis are equidistant. #' @param order.by.pip with \code{order.by.pip=TRUE} (default), coefficients #' are sorted according to their posterior inclusion probabilites along the #' vertical axis. If \code{order.by.pip=FALSE} they are ordered as they were #' provided to \code{\link{bms}}. #' @param do.par Defaults to \code{do.par=TRUE}, which adjusts #' \code{\link{par}()$mar} for optimal positioning. Set \code{do.par=FALSE} for #' customizing \code{par} yourself. #' @param do.grid \code{do.grid=TRUE} (default) plots grid lines among the #' chart's boxes, akin to the low level command \code{\link{grid}}. #' \code{do.grid=FALSE} omits the grid lines. #' @param do.axis \code{do.axis=TRUE} (default) plots axis tick marks and #' labels (cf. \code{\link{axis}}). \code{do.axis=FALSE} omits them. #' @param cex.axis font size for the axes (cf. \code{\link{axis}}), defaults to #' 1 #' @param \dots Parameters to be passed on to \code{\link{image.default}}. #' #' @seealso \link{coef.bma} for the coefficients in matrix form, \link{bms} for #' creating 'bma' objects. #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords hplot #' @examples #' #' data(datafls) #' #' model=bms(datafls,nmodel=200) #' #' #plot all models #' image(model,order.by.pip=FALSE) #' image(model,order.by.pip=TRUE,cex.axis=.8) #' #' #plot best 7 models, with other colors #' image(model[1:7],yprop2pip=TRUE,col=c("black","lightgrey")) #' #' @export image.bma <- function(x,yprop2pip=FALSE,order.by.pip=TRUE,do.par=TRUE,do.grid=TRUE,do.axis=TRUE,cex.axis=1,...) { #,main=NULL,col=c("tomato","blue"),xlab="Cumulative Model Probabilities",ylab="" #does a 'grid plot' of bmao's best models (contained in bmao$topmod) #putting the variables' (on the y-axis) coefficent signs per model vs. the post. model probs (x-axis) # bmao: bma object # yprop2pip: if TRUE, then horizontal grid lines (on the y axis) are proportional to the variables PIPs; if FALSE, they are equidistanrt # do.par: if TRUE adjust par temporarily to fit labels properly; if FALSE, please take care of the par()$mar parameter yourself # do.grid: if TRUE, highlights the boundaries between coefficient areas via the grid() function; if FALSE, it does not # do.axis: if TRUE, plots default axis tickmarks and labels; if FALSE, it does not (consider using axis() afterwards) # cex.axis: denotes label font size (cf. ?axis ) # ... parameters passed on to image.default (Note: default colors are col=c(4,2), default axis labels are empty strings) dotargs = match.call(expand.dots=FALSE)$... ests=estimates.bma(x,exact=TRUE,order.by.pip=order.by.pip,include.constant=FALSE) ests=ests[nrow(ests):1,] pips=ests[,"PIP"] idx=ests[,"Idx"] pmp.res=pmp.bma(x,oldstyle=TRUE) pmps=pmp.res[,1] normali_factor=sum(pmp.res[,2]) betasigns=beta.draws.bma(x)[idx,,drop=FALSE] betasigns=betasigns[as.logical(pips),] betasigns=sign(betasigns)/2+.5 betasigns[betasigns==.5]=NA pips=pips[as.logical(pips)] if (yprop2pip) { pipbounds=(c(0,cumsum(pips))) } else { pipbounds=0:length(pips) names(pipbounds)=c("",names(pips)) } pmpbounds=(c(0,cumsum(pmps))) if (do.par) { oldmar=graphics::par()$mar spaceforyaxis=graphics::strwidth(names(pipbounds)[which.max(nchar(names(pipbounds)))],units="inches")*(graphics::par("mar")/graphics::par("mai"))[[2]] tempmar=oldmar; tempmar[2]=min(spaceforyaxis+oldmar[2]/2, .5*graphics::par("fin")[[1]]*(graphics::par("mar")/graphics::par("mai"))[[2]]) graphics::par(mar=tempmar) } dotargs=.adjustdots(dotargs, ylab="", xlab="Cumulative Model Probabilities", col=c("tomato", "blue"), main = paste("Model Inclusion Based on Best ",length(pmps), " Models")) dotargs$axes <- FALSE # if (!is.element("ylab",names(dotargs))) { dotargs$ylab <- "" } # if (!is.element("xlab",names(dotargs))) { dotargs$xlab <- "Cumulative Model Probabilities" } # if (!is.element("col",names(dotargs))) { dotargs$col <- c("tomato", "blue") } # if (!is.element("main",names(dotargs))) { dotargs$main <- paste("Model Inclusion Based on Best ",length(pmps), " Models") } tbetasigns=t(betasigns) eval(as.call(c(list(as.name("image.default"),as.name("pmpbounds"),as.name("pipbounds"),as.name("tbetasigns")),as.list(dotargs)))) #if (is.null(main)) { main=paste("Model Inclusion Based on Best ",length(pmps), " Models") } #image.default(pmpbounds,pipbounds,t(betasigns),col=col,axes=FALSE,xlab=xlab,ylab=ylab,main=main,...) if (do.axis) { graphics::axis(1,at=pmpbounds, labels=round(normali_factor*pmpbounds,2),cex.axis=cex.axis) graphics::axis(2,at=pipbounds,labels=FALSE,line=FALSE) graphics::axis(2,at=pipbounds[-1]-diff(pipbounds)/2,labels=names(pipbounds[-1]),tick=FALSE,las=1,cex.axis=cex.axis) } if (do.grid) { graphics::abline(v=round(pmpbounds,2),lty="dotted",col="grey") graphics::abline(h=round(pipbounds,2),lty="dotted",col="grey") } if (do.par) {graphics::par(mar=oldmar)} } ########################## # LINEAR MODELS (NO BMA) # ########################################################################### #' Bayesian Linear Model with Zellner's g #' #' Used to fit the Bayesian normal-conjugate linear model with Zellner's g #' prior and mean zero coefficient priors. Provides an object similar to the #' \code{\link{lm}} class. #' #' \code{zlm} estimates the coefficients of the following model \eqn{y = \alpha #' + X \beta + \epsilon} where \eqn{\epsilon} ~ \eqn{N(0,\sigma^2)} and \eqn{X} #' is the design matrix\cr The priors on the intercept \eqn{\alpha} and the #' variance \eqn{\sigma} are improper: \eqn{alpha \propto 1}, \eqn{sigma #' \propto \sigma^{-1}} \cr Zellner's g affects the prior on coefficients: #' \eqn{beta} ~ \eqn{N(0, \sigma^2 g (X'X)^{-1})}. \cr Note that the prior mean #' of coefficients is set to zero by default and cannot be adjusted. Note #' moreover that \code{zlm} always includes an intercept. #' #' @param formula an object of class "formula" (or one that can be coerced to #' that class), such as a data.frame - cf. \code{\link{lm}} #' @param data an optional \code{\link{data.frame}} (or one that can be coerced #' to that class): cf. \code{\link{lm}} #' @param subset an optional vector specifying a subset of observations to be #' used in the fitting process. #' @param g specifies the hyperparameter on Zellner's g-prior for the #' regression coefficients.\cr \code{g="UIP"} corresponds to \eqn{g=N}, the #' number of observations (default); \code{g="BRIC"} corresponds to the #' benchmark prior suggested by Fernandez, Ley and Steel (2001), i.e #' \eqn{g=max(N, K^2)}, where K is the total number of covariates;\cr #' \code{g="EBL"} estimates a local empirical Bayes g-parameter (as in Liang et #' al. (2008));\cr \code{g="hyper"} takes the 'hyper-g' prior distribution (as #' in Liang et al., 2008) with the default hyper-parameter \eqn{a=3}; This #' hyperparameter can be adjusted (between \eqn{21) { gi.sd=sqrt(gi$shrinkage.moments[[2]]-gi$shrinkage.moments[[1]]^2) gi.sdtext=paste(" St.Dev.:", round(gi.sd,3)) } rownames(ests) = c("(Intercept)",attr(object$terms,"term.labels")) colnames(ests)=c("Exp.Val.","St.Dev.") cat("Coefficients\n") print(ests) cat("\n Log Marginal Likelihood:\n") cat(object$marg.lik) cat(paste("\n g-Prior:", gi.choice,"\n")) cat(paste("Shrinkage Factor",ifelse(gi$is.constant,": ", " Exp.Val: "), round(gi$shrinkage.moments[[1]],3), gi.sdtext, "\n",sep="")) res=list() res$residuals <- object$residuals res$coefficients <- object$coefficients res$coef.sd <- sds res$gprior <- gi.choice res$E.shrinkage <- gi$shrinkage.moments[[1]] if (gi.sd>-1) {res$SD.shrinkage <- gi.sd} res$log.lik <- object$marg.lik return(invisible(res)) } #' Extract a Model from a bma Object #' #' Extracts a model out of a \code{bma} object's saved models and converts it #' to a \code{\link{zlm}} linear model #' #' A bma object stores several 'best' models it encounters (cf. argument #' \code{nmodel} in \code{\link{bms}}). \code{as.zlm} extracts a single model #' and converts it to an object of class \code{\link{zlm}}, which represents a #' linear model estimated under Zellner's g prior.\cr The utility #' \code{\link{model.frame}} allows to transfrom a \code{zlm} model into an OLS #' model of class \code{\link{lm}}. #' #' @param bmao A \code{bma} object, e.g. resulting from a call to #' \code{\link{bms}} #' @param model The model index, in one of the following forms:\cr An integer, #' denoting the rank of the model (1 for best, 2 for second-best, ...)\cr A #' numeric or logical vector of length K describing which covariates are #' contained in the model\cr A hexcode character describing which covariates #' are contained in the model #' @return a list of class \code{\link{zlm}} #' @author Stefan Zeugner #' @seealso \code{\link{bms}} for creating \code{bma} objects, #' \code{\link{zlm}} for creating \code{zlm} objects, #' \code{\link{pmp.bma}} for displaying the #' topmodels in a \code{bma} object #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords models #' @examples #' #' data(datafls) #' #' mm=bms(datafls[,1:6],mcmc="enumeration") # do a small BMA chain #' topmodels.bma(mm)[,1:5] #display the best 5 models #' #' m2a=as.zlm(mm,4) #extract the fourth best model #' summary(m2a) #' #' # Bayesian Model Selection: #' # transform the best model into an OLS model: #' lm(model.frame(as.zlm(mm))) #' #' # extract the model only containing the 5th regressor #' m2b=as.zlm(mm,c(0,0,0,0,1)) #' #' # extract the model only containing the 5th regressor in hexcode #' print(bin2hex(c(0,0,0,0,1))) #' m2c=as.zlm(mm,"01") #' #' #' #' #' @export as.zlm <- function(bmao, model=1) { #this function extracts a single topmodel from a bma object and converts it to zlm format #Inputs: # bmao: bma object # model: index of the model #Output: zlm object thiscall=match.call() if (!is.bma(bmao)) stop("bmao needs to be a bma object") bools=bmao$topmod$bool() if (all(is.character(model))&&length(model)==1) { model=(1:length(bools))[bools==model[[1]]] if (length(model)==0) stop("Provided model hex-index was not found in bmao object topmodels") } else if (all(is.character(model))&&(length(model)>1) ) { mix=match(model, bmao$reg.names) if (any(is.na(mix))) stop("Provided variable names do not conform to bma object") ll=logical(bmao$info$K); ll[mix]=TRUE; model=(1:length(bools))[bools==bin2hex(ll)]; rm(ll,mix) if (length(model)==0) stop("Model conforming to provided variable names was not found in bmao object topmodels") } else if ((length(model)==bmao$info$K)&&(is.numeric(model)||is.logical(model))) { model=(1:length(bools))[bools==bin2hex(model)] if (length(model)==0) stop("Provided binary model index was not found in bmao object topmodels") } else if ((length(model)==1)&&(is.numeric(model)||is.logical(model))) { if (model<1|model>length(bools)) stop("Provided numeric model index was not found in bmao object topmodels") } else stop("model needs to be an integer, logical or character model index representation (hexcode or variable names)") inclvbls= as.logical(bmao$topmod$bool_binary()[,model, drop=TRUE]) yXdf =as.data.frame(bmao$arguments$X.data) zlmres=zlm(as.formula(yXdf[,c(TRUE,inclvbls)]),data=yXdf,g=bmao$gprior.info) zlmres$call <- thiscall return(zlmres) } #' Predict Method for zlm Linear Model #' #' Expected value (And standard errors) of predictions based on 'zlm' linear #' Bayesian model under Zellner's g prior #' #' #' @param object a zlm linear model object - see \code{\link{zlm}} #' @param newdata An optional data.frame, matrix or vector containing variables #' with which to predict. If omitted, then (the expected values of) the fitted #' values are returned. #' @param se.fit A switch indicating if the standard deviations for the #' predicted varaibles are required. #' @param \dots further arguments passed to or from other methods. #' @return A vector with (expected values of) fitted values.\cr If #' \code{se.fit} is \code{TRUE}, then the output is a list with the following #' elements: \item{fit}{ a vector with the expected values of fitted values} #' \item{std.err}{ a vector with the standard deviations of fitted values} #' \item{se.fit}{ a vector with the standard errors without the residual scale #' akin to \code{se.fit} in \code{\link{predict.lm}} } \item{residual.scale}{ #' The part from the standard deviations that involves the identity matrix. #' Note that \code{sqrt(se.fit^2+residual.scale^2)} yields \code{std.err}. } #' #' @seealso \code{\link{bms}} for creating zlm objects, #' \code{\link{predict.lm}} for a comparable function, #' \code{\link{predict.bma}} for predicting with bma objects #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' mm=zlm(datafls,g="EBL") #' #' predict(mm) #fitted values #' predict(mm, newdata=1:41) #prediction based on a 'new data point' #' #' #prediction based on a 'new data point', with 'standard errors' #' predict(mm, newdata=datafls[1,], se.fit=TRUE) #' #' @export predict.zlm <- function(object, newdata=NULL, se.fit=FALSE, ...) { # does basic fitting in expected values, cf. predict.lm # object: a zlm object # newdata: newdata to be supplied (just eas in predict.lm) # se.fit: whether standard erros should be calculated # output: a vector with fitted values, if se.fit=T, then a list with fit, standard errors and residual scale if (!is(object,"zlm")) {stop("you need to provide a zlm object"); return()} #get the betas as required betas=object$coefficients[-1,drop=FALSE] alpha=object$coefficients[[1]] #check the newdata argument if (is.null(newdata)) { newX<-as.matrix(object$model[,-1,drop=FALSE]) } else { newX=as.matrix(newdata) if (!is.numeric(newX)) stop("newdata must be numeric!") if (is.vector(newdata)) newX=matrix(newdata,1) if (ncol(newX)!=length(betas)) { if (ncol(newX)==length(betas)+1) { newX=newX[,-1,drop=FALSE] # this is to achieve a behavior similar to predict.lm in this case } else { stop("newdata must be a matrix or data.frame with ", length(betas), " columns.") } } } #if se.fit==FALSE, we are done now: if (!se.fit) return(as.vector(newX%*%betas)+alpha) ################################################# #if se.fit==TRUE, get additional info to compute the standard errors yXdata <- as.matrix(object$model) oldXraw <- yXdata[,-1,drop=FALSE] if (!is.null(colnames(newX))&& !is.null(colnames(oldXraw))) { if ( all(colnames(oldXraw) %in% colnames(newX)) && !all(colnames(oldXraw) == colnames(newX)) ) { #this is another user check, when the stuff came in malformed warning("argument newdata had to be reordered according to its column names. Consider submitting the columns of newdata in the right order.") newX=newX[,colnames(oldXraw), drop=FALSE] } } yraw <- yXdata[,1,drop=TRUE] N <- length(yraw) k <- ncol(oldXraw) oldXmeans <- colMeans(oldXraw) oldXdm <- oldXraw-matrix(oldXmeans,N,k,byrow=TRUE) newXdm <- newX-matrix(oldXmeans,nrow(newX),k,byrow=TRUE) #compute x_f' (X'X)^-1 x_f xtxinv=chol2inv(chol(crossprod(oldXdm))) xtxinv_xf= tcrossprod(xtxinv,newXdm) xf_xx_xf=unlist(lapply(1:nrow(newXdm),function(x) {crossprod(newXdm[x,],xtxinv_xf[,x])[[1L]]} ) ) #these are some factors multipliying the above bvar=object$coef2moments[-1]-object$coefficients[-1]^2 bvar_factor=bvar[[1L]]/xtxinv[[1L]] yty=as.vector(crossprod(yraw)-N*mean(yraw)^2) r2 = 1-object$olsres$ymy/yty if (object$gprior.info$gtype=="hyper") { f21a=object$gprior.info$hyper.parameter f21_recover = exp( (object$marg.lik) + (N-1)/2*log(yty) + log((k+f21a-2)/(f21a-2)) ) #this might be not so accurate due to numerical errors, but in this case the part involving F21 is usually very very small... res_scale = yty/(N-3)/(N-1-k-f21a)*( (N-3)*(1-r2) - (k+f21a-2)/f21_recover) svar_woscale = res_scale/N + bvar_factor*xf_xx_xf #svar_woscale is the standard error without the residual scale (the identiy matrix part) } else { sf=object$gprior.info$shrinkage.moments[[1]] res_scale = (1-sf*r2)*yty/(N-3) svar_woscale = res_scale/N + bvar_factor*xf_xx_xf } #produce output reslist=list() reslist$fit <- as.vector(newX%*%betas)+alpha reslist$std.err <- sqrt(svar_woscale+res_scale) reslist$se.fit <- sqrt(svar_woscale) reslist$residual.scale <- sqrt(res_scale) return(reslist) } #' @export density.zlm <- function(x,reg=NULL,addons="lesz",std.coefs=FALSE,n=300,plot=TRUE,hnbsteps=30,addons.lwd=1.5,...) { #this function does just the same as density.bma, but for an object of class zlm #permitted values for addons: e, s, l, z, g addons=gsub("E","",addons,ignore.case=FALSE) addons=gsub("S","",addons,ignore.case=FALSE) addons=gsub("b","",addons,ignore.case=FALSE) addons=gsub("m","",addons,ignore.case=FALSE) addons=gsub("p","",addons,ignore.case=FALSE) N=length(x$residuals); K=length(x$coefficients)-1 tmo=topmod(1,nmaxregressors=K, bbeta=TRUE,liks=x$marg.lik, ncounts=1, modelbinaries=matrix(rep(1,K),K,1), betas=matrix(as.vector(x$coefficients[-1]),K), betas2=matrix(as.vector(x$coef2moments[-1]),K)) tokenbma=list(info=list(K=K,N=N),arguments=list(),topmod=tmo,start.pos=integer(0), gprior.info=x$gprior.info, X.data=x$model, reg.names=names(x$coefficients)[-1], bms.call=new("call")) class(tokenbma)="bma" return(density.bma(tokenbma,reg=reg, addons=addons, std.coefs=std.coefs, n=n, plot=plot, hnbsteps=hnbsteps, addons.lwd=addons.lwd,...)) } .adjustdots = function(dotargs,...) { # helper function to adjust default arguments for passing on to other functions # in particular to adjust type, col, main etc. when passing on to plotting functions defargs=list(...); defargnames=names(defargs); dotargs=as.list(dotargs); if (is.null(dotargs)) { dotargs= list() } for (di in seq_len(length(defargs))) { if (!is.element(defargnames[[di]],names(dotargs))) { dotargs[[defargnames[[di]]]] <- defargs[[di]]; } } return(dotargs); } ##################################### # PREDICTIVE DENSITY ###### ##################################### #' Predictive Densities for bma Objects #' #' Predictive densities for conditional forecasts #' #' The predictive density is a mixture density based on the \code{nmodels} best #' models in a \code{bma} object (cf. \code{nmodel} in \code{\link{bms}}).\cr #' The number of 'best models' to retain is therefore vital and should be set #' quite high for accuracy. #' #' @aliases pred.density pred.density-class print.pred.density #' @param object a bma object - see \code{\link{bms}}, alternativel a #' \code{\link{zlm}} object #' @param newdata A data.frame, matrix or vector containing variables with #' which to predict. #' @param n The integer number of equally spaced points at which the density is #' to be estimated. #' @param hnbsteps The number of numerical integration steps to be used in case #' of a hyper-g prior (cf. argument \code{g} in \code{\link{bms}}). Increase #' this number to increase accuracy. Must be an even integer. #' @param \dots arguments to be passed on to \code{\link{plot.density}}. #' @return \code{pred.density} returns a list of class \code{pred.density} with #' the following elements \item{densities()}{a list whose elements each contain #' the estimated density for each forecasted observation} \item{fit}{a vector #' with the expected values of the predictions (the 'point forecasts')} #' \item{std.err}{a vector with the standard deviations of the predictions (the #' 'standard errors')} \item{dyf(realized.y, predict_index=NULL)}{Returns the #' densities of realized response variables provided in \code{realized.y}. \cr #' If \code{realized.y} is a matrix, then each row corresponds to a forecast #' observation in \code{newdata}\cr if not left empty, \code{predict.index} #' specifies to which observations in newdata the realized.y should apply} #' \item{lps(realized.y, predict_index=NULL)}{Computes the log predictive score #' for the response varaible provided in \code{realized.y} (cf. #' \code{\link{lps.bma}}) -\cr Note that the LPS equals minus the mean of the #' logarithmized results from \code{dyf}) } \item{plot((x, predict_index = #' NULL, addons = "eslz", realized.y = NULL, addons.lwd = 1.5, ...)}{the same #' as \code{plot.pred.density}} \item{n}{The number of equally spaced #' points for which the density (under \code{densities()} was computed.} #' \item{nmodel}{The number of best models predictive densities are based #' upon.} \item{call}{the call that created this \code{pred.density} object} #' @note In BMS version 0.3.0, \code{pred.density} may only cope with built-in #' \code{gprior}s, not with any user-defined priors. #' #' @seealso \code{\link{predict.bma}} for simple point forecasts, #' \code{plot.pred.density} for plotting predictive densities, #' \code{\link{lps.bma}} for calculating the log predictive score #' independently, \code{\link{quantile.pred.density}} for extracting quantiles #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' mm=bms(datafls,user.int=FALSE) #' #' #predictive densityfor two 'new' data points #' pd=pred.density(mm,newdata=datafls[1:2,]) #' #' #' #fitted values based on best models, same as predict(mm, exact=TRUE) #' pd$fit #' #' #plot the density for the first forecast observation #' plot(pd,1) #' #' # the same plot ' naked' #' plot(pd$densities()[[1]]) #' #' #' #predict density for the first forecast observation if the dep. variable is 0 #' pd$dyf(0,1) #' #' #predict densities for both forecasts for the realizations 0 and 0.5 #' pd$dyf(rbind(c(0,.5),c(0,.5))) #' #' # calc. Log Predictive Score if both forecasts are realized at 0: #' lps.bma(pd,c(0,0)) #' #' #' @export pred.density <- function(object, newdata=NULL, n=300, hnbsteps=30, ...) { dtcm=function(x,df,ncp,varp) { #a wrapper for univariate t-dist with non-centrality parameter and variance parameter # (variance is df/(df-2)*varp) sqvarp=sqrt(varp) stats::dt((x-ncp)/sqvarp,df=df)/sqvarp } dsgivenykernel <- function(sf,kpa,N,z) { #the post. density of the shrinkge factor f(s|Y)*F((N-1)/2,1,(k+a)/2,R2) (kpa-2)/2*(1-sf)^((kpa-4)/2)*(1-sf*z)^(-(N-1)/2) } #user checks nbsteps=max(hnbsteps,2) n=max(ceiling(n),1) #read out data from object is.hyper=(object$gprior.info$gtype=="hyper") if (is.hyper) f21a=object$gprior.info$hyper.parameter if (is.bma(object)) { K=object$info$K; N=object$info$N yXdata=as.matrix(object$arguments$X.data) tmo <- object$topmod } else if (is(object,"zlm")) { yXdata=as.matrix(object$model) K=ncol(yXdata)-1; N=nrow(yXdata) tmo <- topmod(1,nmaxregressors=K, bbeta=TRUE,liks=object$marg.lik, ncounts=1, modelbinaries=matrix(rep(1,K),K,1), betas=matrix(as.vector(object$coefficients[-1]),K), betas2=matrix(as.vector(object$coef2moments[-1]),K)) } else stop("argument 'object' requires class 'bma' or 'zlm'") rm(object) #check the newdata argument, checks are small as newdata has already survived predict.zlm if (missing(newdata)) { stop("You must provide the argument newdata") } else { newX=as.matrix(newdata) if (!is.numeric(newX)) stop("newdata must be numeric!") if (is.vector(newdata)) newX=matrix(newdata,1) if (ncol(newX)!=K) { if (ncol(newX)==K+1) { newX=newX[,-1,drop=FALSE] # this is to achieve a bevavior similar to predict.lm in this case } else { stop("newdata must be a matrix or data.frame with ", K, " columns.") } } orinames=colnames(yXdata[,-1,drop=FALSE]) #this is a user check whether columns had been submitted in the wrong order if (!is.null(colnames(newX)) && !is.null(orinames)) { if (all(orinames %in% colnames(newX) ) && !all(orinames == colnames(newX)) ) { warning("argument newdata had to be reordered according to its column names. Consider submitting the columns of newdata in the right order.") newX=newX[,orinames, drop=FALSE] } } } if(!is.null(rownames(newX))) { newXnames=rownames(newX) } else { newXnames=as.character(1:nrow(newX)) } rnew = nrow(newX) y.mean=mean(yXdata[,1]) y<-yXdata[,1]-matrix(y.mean,N,1,byrow=TRUE) X<-yXdata[,-1,drop=FALSE]-matrix(colMeans(yXdata[,-1,drop=FALSE]),N,K,byrow=TRUE) XtX.big=crossprod(X) Xty.big=as.vector(crossprod(X,y)) yty = crossprod(y)[[1]] newXdm= newX-matrix(colMeans(yXdata[,-1,drop=FALSE]),rnew,K,byrow=TRUE) hexobject<-.hexcode.binvec.convert(K) make_xfxxxf = function(hex) { syminv <- function(symmat, ndim=ncol(symmat)) { #this does the same as chol2inv(chol.default(x)), but is stripped-down for speed purposes # Caution: symmat must always have length(symmat)>0!!! if (!is.matrix(symmat)) {symmat=as.matrix(symmat)} if (dim(symmat)[[1]]==0) return(matrix(numeric(0),0,0)) return( chol2inv(chol(symmat), size=ndim) ) } boolvec = as.logical(hexobject$as.binvec(hex)) if (!any(boolvec)) return(c(numeric(rnew),numeric(rnew),Inf,Inf,0)) newXsub= newXdm[,boolvec,drop=FALSE] xtxinv = syminv(XtX.big[boolvec,boolvec,drop=FALSE]) xty=Xty.big[boolvec] betas = as.vector(crossprod(xtxinv,xty),mode="numeric") r2=crossprod(xty,betas)[[1]]/yty xtxinv_xf = tcrossprod(xtxinv,newXsub) xf_xx_xf = unlist(lapply(1:nrow(newXsub),function(x) {crossprod(newXsub[x,],xtxinv_xf[,x])[[1L]]} ) ) xf_bhat = as.vector(newXsub%*%betas) return(c(xf_xx_xf, xf_bhat, xtxinv[[1L]], betas[[1L]],r2)) } pmps=pmp.bma(tmo,oldstyle=TRUE)[,1,drop=TRUE] bools=tmo$bool() nmodel=length(bools) linvres=lapply(bools,make_xfxxxf) mat_xfxxxf = array(unlist(lapply(linvres,"[",1:rnew)),dim=c(rnew,nmodel)) mat_xfbhat = array(unlist(lapply(linvres,"[",rnew+(1:rnew))),dim=c(rnew,nmodel)) xtxinv_elem1 = unlist(lapply(linvres,"[[",rnew*2+1)) betahat_elem1 = unlist(lapply(linvres,"[[",rnew*2+2)) r2 = unlist(lapply(linvres,"[[",rnew*2+3)) kvec=tmo$kvec_raw() kvec_cs=c(1,cumsum(kvec)+1); kvec_cs=kvec_cs[-length(kvec_cs)] firstbetas=tmo$betas_raw()[kvec_cs] firstbetas2=tmo$betas2_raw()[kvec_cs] Es = firstbetas / betahat_elem1 varmult = (firstbetas2-firstbetas^2) / xtxinv_elem1 if (is.hyper) { first_factor= yty/(N-3)*(N+1)/N* (1+2/(N-kvec-f21a-1)-r2*Es ) } else { first_factor= yty/(N-3)*(1-Es*r2)*(N+1)/N } Sigmas = (matrix((N-3)/(N-1)*first_factor,rnew,nmodel,byrow=TRUE)+t(t(mat_xfxxxf)*((N-3)/(N-1)*varmult))) #by recyling this multplies each column with varmult Evals_minusy = t(t(mat_xfbhat)*Es) Eyf = as.vector(Evals_minusy %*%pmps+y.mean) Varyf = as.vector(Sigmas%*%pmps)*(N-1)/(N-3) premultfactor=yty/(N-1) #needed for hyper interceptfactor=(N+1)/N #needed for hyper #################### calcdensvec = function(xf_index, seqy, m_index) { sss=function(lbound,uboundp1,nbsteps,seqs,xf.index) { #simple simpson integration over shrinkage factor #this is just a convenience function and depends on variables in parent scope, only used in case of hyper-g #caution: seqs needs to have at least two elements! s.seq=seq(lbound,uboundp1,(uboundp1-lbound)/nbsteps)[-nbsteps] tmat=array(unlist(lapply(as.list(s.seq),function(ss) { dtcm(seqs,N-1,y.mean+ss*myev,premultfactor*(1-ss*myr2)*(interceptfactor+ss*myxfxxxf))})),dim=c(length(seqs),nbsteps)) #matrix of t-densities for different s smat=sapply(as.list(s.seq),dsgivenykernel, kpa=myk+f21a,N=N,z=myr2) #vector of posterior densities for the different s if (any(is.infinite(smat))) smat[is.infinite(smat)]=0 intconst=(4*sum(smat[c(FALSE,TRUE)])+2*sum(smat[c(TRUE,FALSE)])-3*smat[nbsteps]-smat[1])*(s.seq[nbsteps]-s.seq[1])/nbsteps/3 #calc the integration constant return(list(dv=c(4*tmat[,c(FALSE,TRUE)]%*%smat[c(FALSE,TRUE)]+2*tmat[,c(TRUE,FALSE)]%*%smat[c(TRUE,FALSE)]-3*tmat[,nbsteps]*smat[nbsteps]-tmat[,1]*smat[1])*(s.seq[nbsteps]-s.seq[1])/nbsteps/3, ic=intconst)) #return the estimated density and a normalization constant #is done in parts because it may be recomposed later } if (any(is.na(newX[xf_index,]))) { densvec=numeric(0) } if (is.hyper) { myev=mat_xfbhat[xf_index,m_index]; myxfxxxf=mat_xfxxxf[xf_index,m_index]; myk=kvec[[m_index]]; myr2=r2[[m_index]] midpoint=1-(1-Es[[m_index]])*4 if (midpoint<.5) { dvl=sss(.0001,.9999999,nbsteps*2,seqy,xf_index); densvec=dvl$dv/dvl$ic } else { dvl1=sss(.0001,midpoint,nbsteps,seqy,xf_index); dvl2=sss(midpoint,1,nbsteps,seqy,xf_index) densvec=(dvl1$dv+dvl2$dv)/(dvl1$ic+dvl2$ic) } } else { densvec=dtcm(seqy,N-1,Evals_minusy[xf_index,m_index]+y.mean,Sigmas[xf_index,m_index]) } return(densvec) } ########################## ########################## dens_yf = function(yfr,xf_indices=NULL) { if (is.null(xf_indices)) xf_indices=seq_len(rnew) yfdens=array(NA,dim=dim(yfr)) for (myxf in 1:length(xf_indices)) { allm_dens=sapply(seq_len(nmodel), function(x) calcdensvec(xf_indices[[myxf]],yfr[myxf,],x)) yfdens[myxf,]=as.vector(allm_dens%*%pmps) } yfdens[!is.finite(yfdens)]=NA if (ncol(yfdens)==1) dim(yfdens) <- NULL return(yfdens) } ################################################ emptydens=list(x=numeric(0), y=numeric(0), bw=NULL, n=0, has.na=TRUE) class(emptydens)="density" dlist = lapply(vector("list",nrow(newX)),function (x) emptydens) densities_calculated <- FALSE #if (!exists("xf_index")) xf_index=NULL calc_alldens = function() { if (densities_calculated) return(NULL) for (xf.index in 1:rnew) { if (!any(is.na(newX[xf.index,]))) { #assign("xf_index",xf.index,envir=env) # IS THIS NECESSARY? #xf_index <<- xf.index lbound=Eyf[[xf.index]]-sqrt(Varyf[[xf.index]])*4; ubound=Eyf[[xf.index]]+sqrt(Varyf[[xf.index]])*4 seqs=seq(lbound,ubound,(ubound-lbound)/(n-1)) allm_dens=sapply(seq_len(nmodel), function(x) calcdensvec(xf.index,seqs,x)) myy=as.vector(tcrossprod(t(as.matrix(pmps)),allm_dens)) mydens=list(x=seqs,y=myy,bw=NULL, n=n, has.na=FALSE) class(mydens)="density" dlist[[xf.index]] <<- mydens } } densities_calculated <<- TRUE } ######################################## consistent.yf = function(yf, xf.indices=NULL) { #a user check function whether a realized y conforms to requirements xf_series=seq_len(rnew) wasnull=FALSE if (is.null(xf.indices)) { wasnull=TRUE xf.indices=xf_series } else { if (!all(xf.indices %in% xf_series)) stop(paste("predict_index needs to be an integer between 1 and ",rnew,"!",sep="")) } if (!is.numeric(yf)) stop("realized.y must be a numeric matrix or vector!") if (!is.matrix(yf)) yf <- as.matrix(yf) if ((length(xf.indices)==1)&(nrow(yf)>1)&(ncol(yf)==1)) yf <- t(yf) if (nrow(newX[xf.indices,,drop=FALSE])!=nrow(yf)) { if (wasnull) stop(paste("realized.y must have", rnew , "elements/rows corresponding to newdata")) else stop("The number of rows/elements in realized.y must have the same length as predict_index!") } return(yf) } consistent_predict_index =function(pix) { # a user check function to convert characgter predict_index into numeric if (is.character(pix)) { if (all(pix %in% newXnames)) { return( match( pix, newXnames) ) } else { stop("Forecast IDs provided in predict_index do not conform to rownames of predicted data"); } } else return(pix) } ######################################## plot.preddens = function(xf.index=1, addons="eslz", yf.addons=NULL, predict_index=NULL, addons.lwd=1.5, ...) { #, main=NULL,col="steelblue4", xlab="Response variable") { dotargs = match.call(expand.dots=FALSE)$... if (rnew>1) { main_default <- paste("Predictive Density Obs ", newXnames[[xf.index]], " (", nmodel, " Models)", sep="") } else { main_default <- paste("Predictive Density", " (", nmodel, " Models)",sep="") } dotargs=.adjustdots(dotargs,xlab="Response variable",main=main_default,col=4, zero.line=FALSE) # if (is.null(main)) { # if (rnew>1) { # main<- paste("Predictive Density Obs ", newXnames[[xf.index]], " (", nmodel, " Models)", sep="") # } else { # main<- paste("Predictive Density", " (", nmodel, " Models)",sep="") # }} # # plot.density(dlist[[xf.index]], main=main, xlab=xlab, col=col, zero.line=any(grep("z",addons,ignore.case=TRUE)), ...) thingy=dlist[[xf.index]] eval(as.call(c(list(as.name("plot"),as.name("thingy")),as.list(dotargs)))) leg.col=numeric(0);leg.lty=numeric(0); leg.legend=character(0) if (any(grep("g",addons,ignore.case=TRUE))) { # grid graphics::grid() } if (any(grep("e",addons,ignore.case=FALSE))) { # posterior mean graphics::abline(v=fit[[xf.index]],col=2,lwd=addons.lwd) leg.col=c(leg.col,2);leg.lty=c(leg.lty,1); leg.legend=c(leg.legend,"Exp. Value") } if (any(grep("s",addons,ignore.case=FALSE))) { # standard error bounds graphics::abline(v=fit[[xf.index]]-2*stderrs[[xf.index]],col=2,lty=2,lwd=addons.lwd) graphics::abline(v=fit[[xf.index]]+2*stderrs[[xf.index]],col=2,lty=2,lwd=addons.lwd) leg.col=c(leg.col,2);leg.lty=c(leg.lty,2);leg.legend=c(leg.legend,"2x Std.Errs") } if (any(grep("z",addons,ignore.case=TRUE))) { #zero line graphics::abline(h=0,col="gray",lwd=addons.lwd) } if (!is.null(yf.addons)&&is.numeric(yf.addons)) { #yf actual y realization line yfs=as.vector(yf.addons) #if (length(yfs)==rnew) { if (!is.na(yfs[[xf.index]])) { graphics::abline(v=yfs[[xf.index]],col=1,lwd=addons.lwd, lty=2) leg.col=c(leg.col,1);leg.lty=c(leg.lty,2);leg.legend=c(leg.legend,"Realized y") } else warning("yf.addons must be a vector with the same number of elements as rows in newdata!") #} } if (any(grep("l",addons,ignore.case=TRUE))&(length(leg.col)>0)) { #legend graphics::legend(x="topright",lty=leg.lty,col=leg.col,legend=leg.legend,box.lwd=0,bty="n",lwd=addons.lwd) } } ####################################### fit=Eyf; names(fit)=newXnames stderrs=sqrt(Varyf); names(stderrs)=newXnames reslist= list() reslist$densities = function() { calc_alldens(); return(dlist) } reslist$fit = fit reslist$std.err = stderrs reslist$dyf = function(realized.y, predict_index=NULL) { predict_index=consistent_predict_index(predict_index) if (missing(realized.y)) { stop("You must provide a realization of the dependent variable in realized.y")} return(dens_yf(consistent.yf(realized.y, predict_index), predict_index)) } reslist$lps = function(realized.y, predict_index=NULL) { predict_index=consistent_predict_index(predict_index) if (missing(realized.y)) { stop("You must provide a realization of the dependent variable in realized.y")} yf=consistent.yf(realized.y, predict_index); if (ncol(yf)!=1) stop("realized.y must have only one column!") yf.dens=dens_yf(yf, predict_index) return(-sum(log(yf.dens[!is.na(yf.dens)]))/length(yf)) } reslist$plot = function(predict_index=NULL, addons="eslz", realized.y=NULL, addons.lwd=1.5, ...) { dotargs = match.call(expand.dots=FALSE)$... xf_series=seq_len(rnew) #user checks predict_index=consistent_predict_index(predict_index) if (is.null(predict_index)) { predict_index=xf_series } else if (!all(predict_index%in%xf_series)) stop(paste("predict_index needs to be an integer between 1 and ",rnew,"!",sep="")) if (!(is.null(realized.y))) { if (length(realized.y)!=length(predict_index)) { stop("realized.y must be a vector with the same number of elements as rows in newdata (or predict_index)!") } } if (!is.null(realized.y)) realized.y <- consistent.yf(realized.y,predict_index); calc_alldens() #temp=reslist$densities() oldask = graphics::par()$ask plotnb=0 for (xf_index in predict_index) { doplot=!dlist[[xf_index]]$has.na; plotnb=plotnb+doplot if (plotnb==2) graphics::par(ask=TRUE) dotargs=.adjustdots(dotargs,main=NULL,col="steelblue4", xlab="Response variable") if (doplot) { eval(as.call(c(list(as.name("plot.preddens"),as.name("xf_index"),addons=as.name("addons"),yf.addons=as.name("realized.y"),addons.lwd=as.name("addons.lwd")),as.list(dotargs)))) #plot.preddens(xf_index, addons=addons, yf.addons=realized.y, addons.lwd = addons.lwd, ..., main=main, col = col, xlab=xlab) } } graphics::par(ask=oldask) } reslist$n=n reslist$nmodel=nmodel reslist$call=sys.call(0) class(reslist) ="pred.density" rm(betahat_elem1, bools, emptydens, firstbetas, firstbetas2, linvres) return(reslist) } #' Log Predictive Score #' #' Computes the Log Predictive Score to evaluate a forecast based on a bma #' object #' #' The log predictive score is an indicator for the likelihood of several #' forecasts.\cr It is defined as minus the arithmethic mean of the logarithms #' of the point densities for \code{realized.y} given \code{newdata}.\cr Note #' that in most cases is more efficient to first compute the predictive density #' object via a call to \code{\link{pred.density}} and only then pass the #' result on to \code{lps.bma}. #' #' @param object an object of class \code{\link{pred.density}}, or class #' \code{bma} (cf. \code{\link{bms}}), or class \code{\link{zlm}} #' @param realized.y a vector with realized values of the dependent variables #' to be plotted in addition to the predictive density, must have its length #' conforming to \code{newdata} #' @param newdata Needs to be provided if \code{object} is not of class #' \code{\link{pred.density}}: a data.frame, matrix or vector containing #' variables with which to predict. #' @return A scalar denoting the log predictive score #' #' @seealso \code{\link{pred.density}} for constructing predictive densities, #' \code{\link{bms}} for creating \code{bma} objects, \code{\link{density.bma}} #' for plotting coefficient densities #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' mm=bms(datafls,user.int=FALSE,nmodel=100) #' #' #LPS for actual values under the used data (static forecast) #' lps.bma(mm, realized.y=datafls[,1] , newdata=datafls[,-1]) #' #' #the same result via predicitve.density #' pd=pred.density(mm, newdata=datafls[,-1]) #' lps.bma(pd,realized.y=datafls[,1]) #' #' # similarly for a linear model (not BMA) #' zz = zlm(datafls) #' lps.bma(zz, realized.y=datafls[,1] , newdata=datafls[,-1]) #' #' @export lps.bma <- function(object, realized.y, newdata=NULL) { if (!any(class(object) %in% c("pred.density","bma","zlm"))) stop("object must be of class 'pred.density', 'bma' or 'zlm'!") if (any(class(object) %in% c("bma","zlm"))) { if (is.null(newdata)) stop("newdata must be provided if object is of class 'bma' or 'zlm'.") object=pred.density(object,newdata=newdata) } return(object$lps(realized.y)) } #' @export plot.pred.density <- function(x, predict_index=NULL, addons="eslz", realized.y=NULL, addons.lwd=1.5, ...) { if (!is(x,"pred.density")) stop("x must be of class 'pred.density'!") x$plot(predict_index, realized.y=realized.y, addons=addons, addons.lwd=addons.lwd, ...) } #' @export print.pred.density <-function(x, digits=NULL, ...) { outmat=matrix(numeric(0),length(x$fit),2) colnames(outmat)=c("Exp.Val.","Std.Err.") rownames(outmat)=names(x$fit) outmat[,1]=x$fit; outmat[,2]=x$std.err cat("Call:\n") print(x$call) cat(paste("\nDensities for conditional forecast(s)\n",x$n, " data points, based on ", x$nmodel, " models;\n",sep="")) print(outmat, digits=digits, ...) } ##################################### # NEW UTILITIES ###### ##################################### #' @export deviance.bma = function(object, exact=FALSE, ...) { #calculates (N-1)*posterior variance of a bma object = effective residual sum of squares # also works for objects of class zlm (and in principle for lm) #akin to method 'deviance' if (is.bma(object)) { xx=as.matrix(object$arguments$X.data); ebeta = estimates.bma(object,order.by.pip=FALSE,exact=exact)[,2,drop=TRUE] } else if (is(object,"lm")) { xx=as.matrix(object$model) ebeta = coef(object); if (length(ebeta)==ncol(xx) ) ebeta=ebeta[-1] } else stop("Required input is an object of class 'bma' or 'lm'/'zlm'.") xx =xx - matrix(colMeans(xx), nrow(xx), ncol(xx),byrow=TRUE) ess=as.vector(crossprod(ebeta,as.vector(crossprod(xx[,-1,drop=FALSE],xx[,1])))) return((as.vector(crossprod(xx[,1,drop=TRUE]))-ess)) } #' @export deviance.zlm=function(object, ...) deviance.bma(object) #' @export model.frame.bma = function(formula, ...) { #akin to method 'model.frame' if (!is.bma(formula)) stop("argument 'formula' needs to be a bma object") return(as.data.frame(formula$arguments$X.data)) } #' Variable names and design matrix #' #' Simple utilities retrieving variable names and design matrix from a bma #' object #' #' All functions are \code{bma}-functions for the generic methods #' \code{\link{variable.names}}, \code{\link{deviance}}, and #' \code{\link{model.frame}}. #' #' @aliases variable.names.bma model.frame.bma #' @param object A \code{bma} object (as produced by \code{\link{bms}}) #' @param ... further arguments passed to or from other methods #' #' @seealso \code{\link{bms}} for creating bma objects #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' bma_enum=bms(datafls[1:20,1:10]) #' #' model.frame(bma_enum) # similar to #' bma_enum$arguments$X.data #' #' variable.names(bma_enum)[-1] # is equivalent to #' bma_enum$reg.names #' #' @export variable.names.bma = function(object, ...) { #akin to method 'variable.names' if (!is.bma(object)) stop("argument 'object' needs to be a bma object") return(c("(Intercept)", object$reg.names)) } #' Variable names and design matrix #' #' Simple utilities retrieving variable names and design matrix from a bma #' object #' #' \code{variable.names.zlm}: method \code{\link{variable.names}} for a #' \code{\link{zlm}} model. \cr \code{vcov.zlm}: the posterior #' variance-covariance matrix of the coefficients of a \code{\link{zlm}} model #' - cf. \code{\link{vcov}} \cr \code{logLik.zlm}: a \code{\link{zlm}} model's #' log-likelihood \code{p(y|M)} according to the implementation of the #' respective coefficent prior \cr #' #' @aliases variable.names.zlm vcov.zlm logLik.zlm #' @param object A \code{bma} object (as produced by \code{\link{bms}}) #' @param ... further arguments passed to or from other methods #' #' @seealso \code{\link{zlm}} for creating \code{zlm} objects #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' #' zz=zlm(datafls) #' variable.names(zz) #' vcov(zz) #' logLik(zz) #' #' @export variable.names.zlm = function(object, ...) { #akin to method 'variable.names' if (!is(object,"zlm")) stop("argument 'object' needs to be zlm object") return(names(object$coefficients)) } #' @export logLik.zlm = function(object,...) { #marginal likelihood of a 'zlm' model, akin to method 'logLik' if (!is(object,"zlm")) stop("argument 'formula' needs to be zlm object") ret = object$marg.lik attr(ret, "df") = object$rank+1 attr(ret, "nbobs") = object$rank+object$df.residual class(ret)="logLik" return(ret) } #' @export vcov.zlm = function(object, include.const = FALSE, ...) { #akin to vcov.lm #get initial stuff Xmat=as.matrix(model.frame(object)[,-1,drop=FALSE]); if (ncol(Xmat)<1) stop("Needs at least one non-constant regressor") regnames= colnames(Xmat) Xmat=Xmat-matrix(colMeans(Xmat),nrow(Xmat),ncol(Xmat),byrow=TRUE) #complement the diagonal VCOV with off-diagonal elements, which are proportional to OLS-VCOV xxinv=chol2inv(chol(crossprod(Xmat))) outmat=((object$coef2moments[[2]]-object$coefficients[[2]]^2)/xxinv[[1]]) * xxinv if (include.const) { outmat=rbind(rep(NA,nrow(outmat)+1), cbind(rep(NA,ncol(outmat)),outmat)) regnames=c("(Intercept)", regnames) } colnames(outmat)<-rownames(outmat) <- regnames return(outmat) } #' Posterior Variance and Deviance #' #' Returns posterior residual variance, deviance, or pseudo R-squared, #' according to the chosen prior structure #' #' \code{post.var}: Posterior residual variance as according to the prior #' definitions contained in \code{object} \cr \code{post.pr2}: A #' pseudo-R-squared corresponding to unity minus posterior variance over #' dependent variance. \cr \code{deviance.bma}: returns the #' \code{\link{deviance}} of a \code{bma} model as returned from #' \code{\link{bms}}. \cr \code{deviance.zlm}: returns the #' \code{\link{deviance}} of a \code{\link{zlm}} model. #' #' @aliases post.var post.pr2 deviance.bma deviance.zlm #' @param object A \code{bma} object (as produced by \code{\link{bms}}) or a #' \code{\link{zlm}} object. #' @param exact When \code{exact=FALSE}, then \code{deviance} will be based on #' MCMC frequencies, if \code{exact=TRUE} then it will be based on\cr #' analytical posterior model probabilities - cf. argument \code{exact} in #' \code{\link{coef.bma}}. #' @param ... further arguments passed to or from other methods #' #' @seealso \code{\link{bms}} for creating \code{bma} objects and priors, #' \code{\link{zlm}} object. #' #' Check \url{http://bms.zeugner.eu} for additional help. #' @keywords utilities #' @examples #' #' data(datafls) #' #' mm=bms(datafls[,1:10]) #' deviance(mm)/nrow(datafls) # is equivalent to #' post.var(mm) #' #' post.pr2(mm) # is equivalent to #' 1 - post.var(mm) / ( var(datafls[,1])*(1-1/nrow(datafls)) ) #' #' @export post.var= function(object,exact=FALSE,...) { #calculates the expected posterior standard error based on effective Residual sum of squares, for objects of class 'bma', 'zlm', 'lm', ... if (!(is.bma(object) | is(object,"lm"))) stop("Required input is an object of class 'bma' or 'lm'/'zlm'.") od=deviance(object, exact=exact) oy=model.frame(object)[,1,drop=TRUE] ret=od/length(oy) attr(ret,"nobs") = length(oy) return(ret) } #' @export post.pr2= function(object,exact=FALSE) { #calculates a pseudo-R-squared based on effective Residual sum of squares, for objects of class 'bma', 'zlm', 'lm', ... if (!(is.bma(object) | is(object,"lm"))) stop("Required input is an object of class 'bma' or 'lm'/'zlm'.") od=deviance(object, exact=exact) oy=model.frame(object)[,1,drop=TRUE] return(1-(od/crossprod(oy-mean(oy))[[1]])) }