diff --git a/NAMESPACE b/NAMESPACE index 0b16688..d9d5186 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ S3method(as.data.frame,iprobitData) S3method(fitted,iprobitMod) S3method(iprobit,default) S3method(iprobit,formula) +S3method(iprobit,ipriorKernel) S3method(iprobit,iprobitMod) S3method(logLik,iprobitMod) S3method(plot,iprobitData) @@ -12,39 +13,31 @@ S3method(predict,iprobitMod) S3method(print,iprobitLowerBound) S3method(print,iprobitMod) S3method(print,iprobitMod_summary) -S3method(print,iprobitPredict) -S3method(print,iprobitPredict_quant) S3method(summary,iprobitMod) S3method(update,iprobitMod) -export(convert_prob) export(expit) export(gen_circle) export(gen_mixture) export(gen_spiral) export(get_brier_score) export(get_brier_scores) -export(get_coef_se_mult) export(get_error_rate) export(get_error_rates) -export(get_kernel) +export(get_hyperparam) +export(get_intercept) export(get_lbs) -export(get_one.lam) export(iplot_dec_bound) export(iplot_error) export(iplot_fitted) export(iplot_lb) export(iplot_predict) export(iprobit) -export(iprobit_bin) -export(iprobit_mult) export(iprobit_parallel) export(is.iprobitData) +export(is.iprobitMod) export(is.iprobitMod_bin) export(is.iprobitMod_mult) export(logit) -export(predict_quant) -export(quantile_prob) -export(sample_prob_mult) import(ggplot2) import(iprior) importFrom(stats,coef) diff --git a/R/Accessor_functions.R b/R/Accessor_functions.R new file mode 100644 index 0000000..1618fce --- /dev/null +++ b/R/Accessor_functions.R @@ -0,0 +1,141 @@ +################################################################################ +# +# iprobit: Binary and Multinomial Probit Regression with I-priors +# Copyright (C) 2017 Haziq Jamil +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +################################################################################ + +#' Accessor functions for \code{ipriorMod} objects. +#' +#' @param object An \code{ipriorMod} object. +#' +#' @name Accessors +NULL + +#' @describeIn Accessors Obtain all of the hyperparameters. +#' @export +get_hyperparam <- function(object) { + check_and_get_iprobitMod(object) + object$param.full +} + +#' @describeIn Accessors Obtain the intercept. +#' @export +get_intercept <- function(object, by.class = FALSE) { + res <- get_hyperparam(object) + res <- res[grep("Intercept", rownames(res)), , drop = FALSE] + if (!isTRUE(by.class)) { + res <- c(res) + if (is.common.intercept(object)) { + res <- res[1] + names(res) <- "Intercept" + } else { + names(res) <- get_names(object, "intercept") + } + } + res +} + +get_alpha <- get_intercept + +get_lambda <- function(object, by.class = FALSE) { + res <- get_hyperparam(object) + res <- res[grep("lambda", rownames(res)), , drop = FALSE] + if (!isTRUE(by.class)) { + res <- c(res) + if (is.common.intercept(object)) { + res <- res[1] + names(res) <- get_names(object, "lambda", FALSE) + } else { + names(res) <- get_names(object, "lambda", TRUE) + } + } + res +} + +get_sd <- function(object) { + setNames(object$param.summ$S.D., rownames(object$param.summ)) +} + +get_sd_alpha <- function(object) { + res <- get_sd(object) + res[grep("Intercept", names(res))] +} + +get_sd_lambda <- function(object) { + res <- get_sd(object) + res[grep("lambda", names(res))] +} + + + +#' @export +get_error_rate <- function(x) x$fitted.values$train.error + +#' @export +get_error_rates <- function(x) { + res <- x$error + names(res) <- seq_along(res) + res +} + +#' @export +get_brier_score <- function(x) x$fitted.values$brier.score + +#' @export +get_brier_scores <- function(x) { + res <- x$brier + names(res) <- seq_along(res) + res +} + +get_m <- function(object) { + if (is.iprobitMod(object)) object <- object$ipriorKernel + length(object$y.levels) +} + + +#' Extract the variational lower bound +#' +#' @param object An object of class \code{ipriorProbit}. +#' @param ... This is not used here. +#' +#' @return The variational lower bound. +#' @export +logLik.iprobitMod <- function(object, ...) { + lb <- object$lower.bound[!is.na(object$lower.bound)] + lb <- lb[length(lb)] + class(lb) <- "iprobitLowerBound" + lb +} + +#' @export +get_lbs <- function(x) x$lower.bound + +#' @export +print.iprobitLowerBound <- function(x, ...) { + cat("Lower bound =", x) +} + +get_theta <- function(object) object$theta + +get_w <- function(object) object$w + +get_y <- function(object) { + res <- factor(object$ipriorKernel$y) + levels(res) <- object$ipriorKernel$y.levels + res +} diff --git a/R/Plots.R b/R/Plots.R index 5d84802..2bc191e 100644 --- a/R/Plots.R +++ b/R/Plots.R @@ -18,6 +18,13 @@ # ################################################################################ +# iplot_fitted() +# iplot_dec_bound() +# iplot_predict() +# iplot_lb() +# iplot_error() +# iplot_lb_and_error() + #' @export plot.iprobitMod <- function(x, niter.plot = NULL, levels = NULL, ...) { iplot_fitted(x) @@ -27,10 +34,9 @@ plot.iprobitMod <- function(x, niter.plot = NULL, levels = NULL, ...) { iplot_fitted <- function(object) { list2env(object, environment()) list2env(ipriorKernel, environment()) - list2env(model, environment()) probs <- fitted(object)$prob - if (isNystrom(ipriorKernel)) probs <- probs[order(Nystrom$Nys.samp), ] + # if (isNystrom(ipriorKernel)) probs <- probs[order(Nystrom$Nys.samp), ] df.plot <- data.frame(probs, i = 1:n) colnames(df.plot) <- c(y.levels, "i") df.plot <- reshape2::melt(df.plot, id.vars = "i") @@ -44,7 +50,7 @@ iplot_fitted <- function(object) { } #' @export -iplot_lb <- function(x, niter.plot = NULL, lab.pos = c("up", "down"), ...) { +iplot_lb <- function(x, niter.plot, lab.pos = c("up", "down"), ...) { lae.check <- FALSE extra.opt <- list(...) if (isTRUE(extra.opt$lb.and.error)) { @@ -61,7 +67,7 @@ iplot_lb <- function(x, niter.plot = NULL, lab.pos = c("up", "down"), ...) { lb.original <- x$lower.bound if (missing(niter.plot)) niter.plot <- seq_along(lb.original) - else if (length(niter.plot) == 1) niter.plot <- c(1, niter.plot) + else if (length(niter.plot) == 1) niter.plot <- seq_len(niter.plot) lb <- lb.original[niter.plot] plot.df <- data.frame(Iteration = niter.plot, lb = lb) time.per.iter <- x$time$time / x$niter @@ -191,7 +197,7 @@ iplot_dec_bound <- function(object, X.var = c(1, 2), col = "grey35", size = 0.8, geom_point(data = points.df, aes(X1, X2, col = Class)) + geom_contour(data = plot.df, aes(X1, X2, z = value, group = variable, size = "Decision\nboundary"), - bins = 2, col = col, ...) + + binwidth = 0.5 + 1e-12, col = col, ...) + coord_cartesian(xlim = mm[1, ], ylim = mm[2, ]) + scale_colour_manual(values = c(iprior::gg_col_hue(m), "grey30")) + scale_size_manual(values = size, name = NULL) + @@ -230,18 +236,44 @@ iplot_predict <- function(object, X.var = c(1, 2), grid.len = 50, } iplot_predict_bin <- function(plot.df, points.df, x, y, m, dec.bound) { - ggplot() + - geom_raster(data = plot.df, aes(X1, X2, fill = class2), alpha = 0.5) + + p <- ggplot() + + geom_raster(data = plot.df, aes(X1, X2, fill = `2`), alpha = 0.5) + scale_fill_gradient(low = "#F8766D", high = "#00BFC4", limits = c(0, 1)) + # annotate(geom = "raster", x = plot.df[, 1], y = plot.df[, 2], # alpha = 0.6 * plot.df[, 3], fill = "#F8766D") + # annotate(geom = "raster", x = plot.df[, 1], y = plot.df[, 2], # alpha = 0.6 * plot.df[, 4], fill = "#00BFC4") + - geom_point(data = points.df, aes(X1, X2, col = Class)) + - # col = "black", shape = 21, stroke = 0.8) + - coord_cartesian(xlim = x, ylim = y) + - guides(fill = FALSE) + theme_bw() + + # Add decision boundary ------------------------------------------------------ + if (isTRUE(dec.bound)) { + p <- p + + geom_contour(data = plot.df, aes(X1, X2, z = `2`, + size = "Decision\nboundary"), + binwidth = 0.5 + 1e-12, col = "grey35", + linetype = "dashed") + + scale_size_manual(values = 0.8, name = NULL) + + scale_color_discrete(name = " Class") + + guides(fill = FALSE, + size = guide_legend(override.aes = list(linetype = 2, col = "grey35")), + col = guide_legend(order = 1, override.aes = list(linetype = 0))) + + theme(legend.key.width = unit(2, "line")) + } else { + p <- p + guides(fill = FALSE) + } + + # Add points ----------------------------------------------------------------- + p <- p + + ggrepel::geom_label_repel(data = points.df, segment.colour = "grey25", + box.padding = 0.9, show.legend = FALSE, + aes(X1, X2, col = Class, label = prob)) + + geom_point(data = points.df, aes(X1, X2, col = Class)) + + geom_point(data = subset(points.df, points.df$prob != ""), aes(X1, X2), + shape = 1, col = "grey25") + + # Touch up plot and return --------------------------------------------------- + p + coord_cartesian(xlim = x, ylim = y) + } iplot_predict_mult <- function(plot.df, points.df, x, y, m, dec.bound) { @@ -262,15 +294,15 @@ iplot_predict_mult <- function(plot.df, points.df, x, y, m, dec.bound) { alpha = alpha * plot.df[, class.ind[j]], fill = fill.col[j]) } - # Add points and decision boundary, and touch up remaining plot -------------- + # Add decision boundary ------------------------------------------------------ if (isTRUE(dec.bound)) { decbound.df <- reshape2::melt(plot.df, id.vars = c("X1", "X2")) p <- p + geom_contour(data = decbound.df, aes(X1, X2, z = value, group = variable, col = variable, size = "Decision\nboundary"), - # binwidth = 0.5 + 1e-12, - bins = 2, + binwidth = 0.5 + 1e-12, + # bins = 2, linetype = "dashed") + scale_size_manual(values = 0.8, name = NULL) + scale_color_discrete(name = " Class") + @@ -303,7 +335,7 @@ iplot_predict_mult <- function(plot.df, points.df, x, y, m, dec.bound) { iplot_error <- function(x, niter.plot, plot.test = TRUE) { if (x$niter < 2) stop("Nothing to plot.") if (missing(niter.plot)) niter.plot <- seq_along(x$train.error) - else if (length(niter.plot) == 1) niter.plot <- c(1, niter.plot) + else if (length(niter.plot) == 1) niter.plot <- seq_len(niter.plot) # Prepare plotting data frame ------------------------------------------------ plot.df <- data.frame(Iteration = niter.plot, @@ -360,9 +392,9 @@ iplot_error <- function(x, niter.plot, plot.test = TRUE) { theme(legend.position = "none") } -iplot_lb_and_error <- function(x, niter.plot, lab.pos) { +iplot_lb_and_error <- function(x, niter.plot, lab.pos, plot.test = TRUE) { suppressMessages( - p2 <- iplot_error(x, niter.plot) + + p2 <- iplot_error(x, niter.plot, plot.test) + scale_x_continuous( breaks = scales::pretty_breaks(n = min(5, ifelse(x$niter == 2, 1, x$niter))) ) diff --git a/R/Utilities.R b/R/Utilities.R index 5778b10..7c2b984 100644 --- a/R/Utilities.R +++ b/R/Utilities.R @@ -78,187 +78,38 @@ is.iprobitMod_mult <- function(x) inherits(x, "iprobitMod_mult") #' @export is.iprobitData <- function(x) inherits(x, "iprobitData") -isNystrom <- function(x) { - if (inherits(x, "ipriorKernel_old")) { - if (!is.list(x$Nystrom)) res <- x$Nystrom - else res <- TRUE - } else { - if (!is.list(x$ipriorKernel$Nystrom)) res <- x$ipriorKernel$Nystrom - else res <- TRUE - } - res -} - -#' Extract the variational lower bound -#' -#' @param object An object of class \code{ipriorProbit}. -#' @param ... This is not used here. -#' -#' @return The variational lower bound. -#' @export -logLik.iprobitMod <- function(object, ...) { - lb <- object$lower.bound[!is.na(object$lower.bound)] - lb <- lb[length(lb)] - class(lb) <- "iprobitLowerBound" - lb -} - -#' @export -get_lbs <- function(x) x$lower.bound - -#' @export -print.iprobitLowerBound <- function(x, ...) { - cat("Lower bound =", x) -} - -get_theta <- function(object) object$theta - -get_w <- function(object) object$w - -get_y <- function(object) { - res <- factor(object$ipriorKernel$y) - levels(res) <- object$ipriorKernel$y.levels - res -} - - -#' @export -get_one.lam <- function(object) { - object$ipriorKernel$model$one.lam -} - -#' @export -get_kernel <- function(object, collapse = TRUE) { - kernel.used <- object$ipriorKernel$model$kernel - Hurst.used <- get_Hurst(object) - for (i in seq_along(kernel.used)) { - if (kernel.used[i] == "FBM") - kernel.used[i] <- paste0(kernel.used[i], ",", Hurst.used[i]) - } - kernel.used -} - -#' get_lambda <- function(object) { -#' lambda <- object$lambda -#' if (is.iprobitMod_bin(object)) { -#' if (length(lambda) > 1) -#' names(lambda) <- paste0("lambda[", seq_along(lambda), "]") -#' else -#' names(lambda) <- "lambda" -#' } else if (is.iprobitMod_mult(object)) { -#' if (nrow(lambda) > 1) -#' rownames(lambda) <- paste0("lambda[", seq_along(lambda[, 1]), ",]") -#' else -#' rownames(lambda) <- "lambda" -#' colnames(lambda) <- paste0("Class = ", seq_along(object$y.levels)) -#' } else { -#' stop("Input iprobitMod objects only.") -#' } -#' -#' lambda -#' } - - -# get_psi <- function(object) { -# alpha <- object$alpha -# if (is.iprobitMod_bin(object)) { -# # if (length(alpha) > 1) -# # names(alpha) <- paste0("alpha[", seq_along(alpha), "]") -# # else -# names(alpha) <- "alpha" -# } else if (is.iprobitMod_mult(object)) { -# alpha <- matrix(alpha, nrow = 1) -# rownames(alpha) <- "alpha" -# colnames(alpha) <- paste0("Class = ", seq_along(object$y.levels)) +# isNystrom <- function(x) { +# if (inherits(x, "ipriorKernel_old")) { +# if (!is.list(x$Nystrom)) res <- x$Nystrom +# else res <- TRUE # } else { -# stop("Input iprobitMod objects only.") +# if (!is.list(x$ipriorKernel$Nystrom)) res <- x$ipriorKernel$Nystrom +# else res <- TRUE # } -# alpha +# res # } -get_alpha <- function(object) { - param.full <- param.summ_to_param.full(object$param.summ) - param.full[grep("Intercept", names(param.full))] +is.common.intercept <- function(x) { + check_and_get_iprobitMod(x) + return(x$common$intercept) } -get_lambda <- function(object) { - param.full <- param.summ_to_param.full(object$param.summ) - param.full[grep("lambda", names(param.full))] +is.common.RKHS.param <- function(x) { + check_and_get_iprobitMod(x) + return(x$common$RKHS.param) } -get_sd <- function(object) { - setNames(object$param.summ$S.D., rownames(object$param.summ)) -} - -get_sd_alpha <- function(object) { - res <- get_sd(object) - res[grep("Intercept", names(res))] -} - -get_sd_lambda <- function(object) { - res <- get_sd(object) - res[grep("lambda", names(res))] -} - -#' @export -get_coef_se_mult <- function(object) { - theta <- coef(object) - m <- ncol(theta) - l <- nrow(theta) - 1 - - if (isTRUE(object$control$common.intercept)) { - alpha <- theta[1, 1] - names(alpha) <- "alpha" - alpha.se <- object$se.alpha - } else { - alpha <- theta[1, ] - names(alpha) <- paste0("alpha[", 1:m, "]") - alpha.se <- rep(object$se.alpha, m) - } - - if (isTRUE(object$control$common.RKHS.scale)) { - lambda <- theta[-1, 1] - if (length(lambda) > 1) - names(lambda) <- paste0("lambda[", seq_along(lambda), ",]") - else - names(lambda) <- "lambda" - lambda.se <- object$se.lambda[, 1] +check_and_get_iprobitMod <- function(object, assign.to.env = FALSE) { + # Helper function to check whether object is of ipriorMod class. + # + # Args: An ipriorMod or ipriorKernel object; logical assign.to.env. + # + # Returns: Nothing - just checks. Unless assign.to.env is TRUE. + if (is.iprobitMod(object)) { + if (isTRUE(assign.to.env)) list2env(object$ipriorKernel, parent.frame()) } else { - lambda <- c(t(theta[-1, ])) - lambda.names <- NULL - for (k in 1:l) { - lambda.names <- c(lambda.names, paste0("lambda[", k, ",", 1:m, "]")) - } - names(lambda) <- lambda.names - lambda.se <- c(t(object$se.lambda)) + stop("Input an iprobitMod object.", call. = FALSE) } - - list(theta = c(alpha, lambda), se = c(alpha.se, lambda.se)) -} - -#' @export -get_error_rate <- function(x) x$fitted.values$train.error - -#' @export -get_error_rates <- function(x) { - res <- x$error - names(res) <- seq_along(res) - res -} - -#' @export -get_brier_score <- function(x) x$fitted.values$brier.score - -#' @export -get_brier_scores <- function(x) { - res <- x$brier - names(res) <- seq_along(res) - res -} - -get_m <- function(object) { - if (is.iprobitMod(object)) object <- object$ipriorKernel - length(object$y.levels) } all.same <- function(v) { @@ -266,196 +117,6 @@ all.same <- function(v) { all(sapply(as.list(v[-1]), FUN = function(z) identical(z, v[1]))) } -# lambda expansion and Hlam calculation for binary models ---------------------- - -expand_lambda <- function(x, intr, intr.3plus = NULL) { - # Helper function to expand lambda or lambda.sq (scale parameters) according - # to any interactions specification. - # - # Args: lambda - # - # Returns: Expanded lambda. - if (is.vector(x)) { - # Binary models ------------------------------------------------------------ - return(iprior::.expand_Hl_and_lambda(x, x, intr, intr.3plus)$lambda) - } else { - # Multinomial models ------------------------------------------------------- - res <- NULL - m <- ncol(x) - for (j in seq_len(m)) { - res[[j]] <- iprior::.expand_Hl_and_lambda(x[, j], x[, j], intr, - intr.3plus)$lambda - } - return(matrix(unlist(res), ncol = m)) - } -} - -get_Hlam <- function(object, theta, theta.is.lambda = FALSE) { - # Obtains the kernel matrix Hlam. - # - # Args: - # - # Returns: For binary models, this calculate Hlam. For multinomial models, - # this calculates Hlam for every class---so a list is returned. - if (is.iprobit_bin(object)) { - if (is.matrix(theta)) theta <- theta[, 1] - return(iprior::.get_Hlam(object = object, theta = theta, - theta.is.lambda = theta.is.lambda)) - } else { - res <- NULL - m <- get_m(object) - for (j in seq_len(m)) { - res[[j]] <- iprior::.get_Hlam(object = object, theta = as.numeric(theta[, j]), - theta.is.lambda = theta.is.lambda) - } - return(res) - } -} - -get_Htildelam <- function(object, theta, xstar) { - if (is.iprobit_bin(object)) { - if (is.matrix(theta)) theta <- theta[, 1] - return(iprior::.get_Htildelam(object, theta, xstar)) - } else { - res <- NULL - m <- get_m(object) - for (j in seq_len(m)) { - res[[j]] <- iprior::.get_Htildelam(object, as.numeric(theta[, j]), xstar) - } - return(res) - } -} - - -get_Hlamsq <- function() { - # Calculate Hlamsq for closed-form VB. - # - # Args: mod is an ipriorKernel object defined in parent environment, along - # with lambda, lambdasq, Psql, Hsql, H2l, ind1, ind2, p, and no.int. - # - # Returns: Hlamsq mat for binary models, and list of Hlamsq matrices for - # multinomial models. - environment(Hlam_two_way_index) <- environment() - q <- p + no.int - m <- get_m(mod) - - if (is.iprobit_bin(mod)) { # BINARY MODEL - # Calculate square terms of Hlamsq ----------------------------------------- - if (is.null(Hsql)) - square.terms <- Reduce("+", mapply("*", Psql[1:q], lambdasq[1:q], - SIMPLIFY = FALSE)) - else - square.terms <- Reduce("+", mapply("*", Hsql[1:q], lambdasq[1:q], - SIMPLIFY = FALSE)) - - # Calculate two-way terms of Hlamsq ---------------------------------------- - if (is.null(ind1) && is.null(ind2)) { - two.way.terms <- 0 - } else { - lambda.two.way <- Hlam_two_way_index(lambda, lambdasq) - two.way.terms <- Reduce("+", mapply("*", H2l, lambda.two.way, - SIMPLIFY = FALSE)) - } - - return(square.terms + two.way.terms) - } else { - res <- NULL - for (j in seq_len(m)) { # MULTINOMIAL MODEL - # Calculate square terms of Hlamsq ----------------------------------------- - if (is.null(Hsql)) - square.terms <- Reduce("+", mapply("*", Psql[1:q], lambdasq[1:q, j], - SIMPLIFY = FALSE)) - else - square.terms <- Reduce("+", mapply("*", Hsql[1:q], lambdasq[1:q, j], - SIMPLIFY = FALSE)) - - # Calculate two-way terms of Hlamsq ---------------------------------------- - if (is.null(ind1) && is.null(ind2)) - two.way.terms <- 0 - else { - lambda.two.way <- Hlam_two_way_index(lambda[1:q, j], lambdasq[1:q, j]) - two.way.terms <- - Reduce("+", mapply("*", H2l, lambda.two.way, SIMPLIFY = FALSE)) - } - - res[[j]] <- square.terms + two.way.terms - } - return(res) - } -} - - -Hlam_two_way_index <- function(lam, lamsq) { - # mod <- iprior::.kernL(Species ~ . ^ 2, iris) - # iprobit.env <- environment() - # list2env(mod, iprobit.env) - # list2env(BlockBstuff, iprobit.env) - # list2env(model, iprobit.env) - - comb.ind12 <- cbind(ind1, ind2) - comb.ind12 <- split(comb.ind12, row(comb.ind12)) - - replace_ind <- function(x) { - if (any(x > p)) { - here <- which(x > p) - what <- x[here] - p - res <- c(x[-here], intr[, what]) - sort(res) - } else { - x - } - } - - lam_ind <- function(x) { - here <- which(duplicated(x)) - if (length(here > 0)) { - what <- x[here] - what.not <- x[x != what] - prod(lamsq[what]) * prod(lam[what.not]) - } else { - prod(lam[x]) - } - } - - lapply(lapply(comb.ind12, replace_ind), lam_ind) -} - -# myfun <- function() { -# # Function to test lambdaExpand_mult() and the Hlam*_mult functions -# set.seed(123) -# dat <- gen_mixture(n = 4, m = 3) -# mod <- iprior::.kernL(y ~ . ^ 2, dat) -# iprobit.env <- environment() -# list2env(mod, iprobit.env) -# list2env(BlockBstuff, iprobit.env) -# list2env(model, iprobit.env) -# environment(lambdaExpand_mult) <- iprobit.env -# environment(HlamFn_mult) <- iprobit.env -# environment(HlamsqFn_mult) <- iprobit.env -# environment(Hlam_two_way_index) <- iprobit.env -# m <- length(y.levels) -# lambda <- matrix(2:3, ncol = m, nrow = l) -# lambda.sq <- lambda ^ 2 -# lambdaExpand_mult(x = lambda, y = lambda.sq, env = iprobit.env) -# HlamFn_mult(env = iprobit.env) -# HlamsqFn_mult(env = iprobit.env) -# list(H = Hl, Hsq = Hsql, lambda = lambda, lambda.sq = lambda.sq, -# Hlam.mat = Hlam.mat[[1]], Hlam.matsq = Hlam.matsq[[1]]) -# -# # CHECK -# # 2 3 6 (lambda) -# # 4 9 36 (lambda.sq) -# # -# # H1 <- H[[1]] -# # H2 <- H[[2]] -# # H12 <- H[[1]] * H[[2]] -# # H1sq <- H1 %*% H1 -# # H2sq <- H2 %*% H2 -# # H12sq <- H12 %*% H12 -# # -# # 4 * H1sq + 9 * H2sq + 36 * H12sq + 2 * 3 * (H1 %*% H2 + H2 %*% H1) + 4 * 3 * (H1 %*% H12 + H12 %*% H1) + 9 * 2 * (H2 %*% H12 + H12 %*% H2) -# } - hyperparam_to_theta <- function(lambda, hurst, lengthscale, offset) { # In order to be compatible with the iprior helper functions, need theta (the # unconstrained versions of the hyperparameters, excluding alpha). @@ -538,7 +199,6 @@ param.full_to_coef <- function(param.full, object) { iprior::.reduce_theta(x, est.list = object$estl)$theta.reduced }) res <- rbind("Intercept" = param.full[1, ], res) - rownames(res) <- get_names(object, expand = FALSE) res } diff --git a/R/fitted_and_predict.R b/R/fitted_and_predict.R index 9ec9b6b..505747e 100644 --- a/R/fitted_and_predict.R +++ b/R/fitted_and_predict.R @@ -44,7 +44,10 @@ fitted.iprobitMod <- function(object, quantiles = FALSE, n.samp = 100, transform = identity, raw = FALSE, ...) { if (isTRUE(quantiles)) { - return(predict_quant(object, n.samp, transform, raw = raw, type = "train")) + y <- as.numeric(factor(object$ipriorKernel$y)) + + return(predict_quant(object, n.samp, transform, raw = raw, type = "train", + y = y)) } else { res <- object$fitted.values res$prob <- transform(res$prob) @@ -101,17 +104,20 @@ predict_iprobit <- function(object, xstar, y.test, quantiles = FALSE, res } -calc_ystar <- function(object, xstar, alpha, theta, w) { +calc_ystar <- function(object, xstar, alpha, theta, w, theta.is.lambda = FALSE) { # Args: An ipriorKernel object. # # Returns: For binary models, a vector of ystar. For multinomial models, this # is a matrix with columns representing the number of classes. - if (is.null(xstar)) Hlam.new <- get_Hlam(object, theta) - else Hlam.new <- get_Htildelam(object, theta, xstar) + if (is.null(xstar)) Hlam.new <- get_Hlam(object, theta, theta.is.lambda) + else Hlam.new <- get_Htildelam(object, theta, xstar, theta.is.lambda) if (is.iprobit_bin(object)) { + if (length(alpha) > 1) { + stopifnot(all.same(as.numeric(alpha))) + alpha <- alpha[1] + } return(as.numeric(alpha + Hlam.new %*% w)) } else { - res <- NULL m <- get_m(object) if (length(alpha) == 1) alpha <- rep(alpha, m) return(rep(alpha, each = nrow(Hlam.new[[1]])) + @@ -120,6 +126,7 @@ calc_ystar <- function(object, xstar, alpha, theta, w) { } probs_yhat_error <- function(y, y.levels, ystar, type = c("train", "test")) { + # Args: y is for comparison (test or training responses). m <- length(y.levels) if (m == 2) { @@ -150,7 +157,8 @@ probs_yhat_error <- function(y, y.levels, ystar, type = c("train", "test")) { levels(y.hat) <- y.levels error.rate <- mean(as.numeric(y.hat) != as.numeric(y)) * 100 - brier.score <- brier_score(as.numeric(y), as.numeric(y.hat), as.data.frame(p.hat)) + brier.score <- brier_score(as.numeric(y), as.numeric(y.hat), + as.data.frame(p.hat)) structure(list(y = y.hat, prob = as.data.frame(p.hat), type = match.arg(type, c("train", "test")), @@ -158,7 +166,6 @@ probs_yhat_error <- function(y, y.levels, ystar, type = c("train", "test")) { class = "iprobitPredict") } -#' @export predict_quant <- function(object, n.samp, transform = identity, raw = FALSE, xstar = NULL, y = NULL, type = c("train", "test")) { # Helper function to get the quantiles of the probabilities, error rate and @@ -170,49 +177,73 @@ predict_quant <- function(object, n.samp, transform = identity, raw = FALSE, tmp <- sample_prob_bin(object, n.samp, xstar, y) } if (is.iprobitMod_mult(object)) { - # tmp <- sample_prob_mult(object, n.samp, Hl, y) + tmp <- sample_prob_mult(object, n.samp, xstar, y) } phats <- convert_prob(tmp$phat.samp, transform) names(phats) <- object$ipriorKernel$y.levels + + if (all(is.nan(tmp$error.samp))) { + error.rate <- NaN + } else { + error.rate <- stats::quantile(tmp$error.samp, + probs = c(0.025, 0.25, 0.5, 0.75, 0.975)) + } + + if (all(is.nan(tmp$brier.samp))) { + brier.score <- NaN + } else { + brier.score <- stats::quantile(tmp$brier.samp, + probs = c(0.025, 0.25, 0.5, 0.75, 0.975)) + } + if (isTRUE(raw)) { return(list( - prob = phats, train.error = tmp$error.samp, brier.score = tmp$brier.samp + prob = phats, error.rate = tmp$error.samp, brier.score = tmp$brier.samp )) } else { return(structure(list( prob = quantile_prob(phats), - error.rate = stats::quantile(tmp$error.samp, probs = c(0.025, 0.25, 0.5, 0.75, 0.975)), - brier.score = stats::quantile(tmp$brier.samp, probs = c(0.025, 0.25, 0.5, 0.75, 0.975)), + error.rate = error.rate, + brier.score = brier.score, type = match.arg(type, c("train", "test")) ), class = "iprobitPredict_quant")) } } -# calc_ystar <- function(object, xstar, alpha, theta, w) - sample_prob_bin <- function(object, n.samp, xstar = NULL, y = NULL) { # Helper function to sample probabilities, error rates and brier scores for - # binary models. + # binary models. For the closed-form VB-EM, then the hyperparameters all have + # normal distributions. For the VB-EM with Metropolis sampler, then the + # samples for theta are Bootstrapped from object$theta.samp, a remnant of the + # fitting procedure. Note that results will not be so good if n.samp >> + # nrow(object$theta.samp). # # Args: An iprobitMod_x object. + # + # Returns: A list of 1) a list of class probability samples; 2) a vector of + # samples of misclassification rates; and 3) a vector of sample Brier scores. + # This is needed in predict_quant(). alpha.samp <- sample_alpha(n.samp, get_alpha(object), get_sd_alpha(object)) + til <- FALSE if (grepl("Closed-form", object$est.method)) { theta.samp <- sample_lambda(n.samp, get_lambda(object), get_sd_lambda(object)) + til <- TRUE } else { - stop("Not implemented yet.") + theta.samp <- object$theta.samp + theta.samp <- theta.samp[sample(seq_len(nrow(theta.samp)), size = n.samp), ] } w.samp <- sample_w(n.samp, object$w, object$Varw) # n.samp x n matrix + phat.samp <- list() error.samp <- brier.samp <- rep(NA, n.samp) - if (is.null(y)) y <- as.numeric(factor(object$ipriorKernel$y)) for (i in seq_len(n.samp)) { alpha <- alpha.samp[i] - theta <- hyperparam_to_theta(theta.samp[i, ]) + theta <- theta.samp[i, ] w <- w.samp[i, ] - ystar.samp <- calc_ystar(object$ipriorKernel, xstar, alpha, theta, w) + ystar.samp <- calc_ystar(object$ipriorKernel, xstar, alpha, theta, w, til) tmp <- probs_yhat_error(y, object$ipriorKernel$y.levels, ystar.samp) phat.samp[[i]] <- tmp$prob error.samp[i] <- tmp$error @@ -222,144 +253,98 @@ sample_prob_bin <- function(object, n.samp, xstar = NULL, y = NULL) { list(phat.samp = phat.samp, error.samp = error.samp, brier.samp = brier.samp) } -sample_alpha <- function(n.samp, mean, sd) { - rnorm(n.samp, mean, sd) -} - -sample_lambda <- function(n.samp, mean, sd) { - if (length(mean) > 1) { - return(mvtnorm::rmvnorm(n.samp, mean, diag(sd ^ 2))) - } else { - return(matrix(log(rnorm(n.samp, mean, sd)))) # note the log(lambda) - } -} - -sample_w <- function(n.samp, mean, variance) { - mvtnorm::rmvnorm(n.samp, mean, variance) -} - -#' calc_ystar <- function(object, xstar) { -#' # Helper function to calculate ystar values from Hl, lambda, alpha and w. If -#' # values are not supplied in the argument, these are obtained from object. -#' # -#' # Args: iprobitMod object. -#' # -#' # Returns: -#' list2env(object, environment()) -#' list2env(ipriorKernel, environment()) -#' list2env(model, environment()) -#' if (!is.null(Hl.new)) Hl <- Hl.new -#' if (!is.null(alpha.new)) alpha <- alpha.new -#' if (!is.null(lambda.new)) lambda <- lambda.new -#' if (!is.null(w.new)) w <- w.new -#' -#' if (is.iprobitMod_bin(object)) { -#' environment(lambdaExpand_bin) <- environment() -#' environment(HlamFn) <- environment() -#' lambdaExpand_bin(env = environment(), y = NULL) -#' HlamFn(env = environment()) -#' return(as.vector(alpha + (Hlam.mat %*% w))) -#' } -#' if (is.iprobitMod_mult(object)) { -#' # environment(lambdaExpand_mult) <- environment() -#' # environment(HlamFn_mult) <- environment() -#' # lambdaExpand_mult(env = environment(), y = NULL) -#' # HlamFn_mult(env = environment()) -#' # return(rep(alpha, each = nrow(Hlam.mat[[1]])) + -#' # mapply("%*%", Hlam.mat, split(w, col(w)))) -#' } -#' } - - - - - - - - - - -# predict_iprobit_mult <- function(y, y.levels, ystar, type = c("train", "test")) { -# m <- length(y.levels) -# -# p.hat <- ystar -# for (i in seq_len(nrow(ystar))) { -# for (j in seq_along(y.levels)) { -# p.hat[i, j] <- EprodPhiZ(ystar[i, j] - ystar[i, seq_along(y.levels)[-j]]) -# } -# # p.hat[i, m] <- 1 - sum(p.hat[i, 1:(m - 1)]) -# } -# p.hat <- p.hat / matrix(rep(apply(p.hat, 1, sum), m), ncol = m) # normalise -# colnames(p.hat) <- y.levels -# -# tmp <- p.hat -# y.hat <- factor( -# apply(tmp, 1, function(x) which(x == max(x))), -# levels = seq_len(m) -# ) -# levels(y.hat) <- y.levels -# -# error.rate <- mean(as.numeric(y.hat) != as.numeric(y)) * 100 -# brier.score <- brier_score(as.numeric(y), as.numeric(y.hat), as.data.frame(p.hat)) -# -# type <- match.arg(type, c("train", "test")) -# -# structure(list(y = y.hat, prob = as.data.frame(p.hat), type = type, -# train.error = error.rate, brier.score = brier.score), -# class = "iprobit_predict") -# } - +sample_prob_mult <- function(object, n.samp, xstar = NULL, y = NULL) { + # Helper function to sample probabilities, error rates and brier scores for + # multinomial models. + alpha.samp <- theta.samp <- w.samp <- phat.samp <- list() + m <- get_m(object) + til <- FALSE + vb.closed <- grepl("Closed-form", object$est.method) + alpha <- c(get_alpha(object, by.class = TRUE)) + sd.alpha <- get_sd_alpha(object) + lambda <- get_lambda(object, by.class = TRUE) + sd.lambda <- matrix(get_sd_lambda(object), ncol = m, byrow = TRUE) + if (isTRUE(vb.closed)) til <- TRUE -#' @export -sample_prob_mult <- function(object, n.samp, Hl.new = NULL, y = NULL) { - # Helper function to sample probabilities, error rates and brier scores for - # multinomial models. - alpha.samp <- lambda.samp <- w.samp <- phat.samp <- list() - m <- object$ipriorKernel$m + # First, sample per class ---------------------------------------------------- for (j in seq_len(m)) { - # First sample per class - alpha.samp[[j]] <- rnorm(n.samp, object$alpha[j], object$se.alpha) - if (nrow(object$lambda) > 1) { - lambda.samp[[j]] <- mvtnorm::rmvnorm(n.samp, object$lambda[, j], - diag(object$se.lambda[, j] ^ 2)) - } else { - lambda.samp[[j]] <- matrix(rnorm(n.samp, object$lambda[, j], - object$se.lambda[, j])) + alpha.samp[[j]] <- sample_alpha(n.samp, alpha[j], sd.alpha[j]) + if (isTRUE(vb.closed)) { + theta.samp[[j]] <- sample_lambda(n.samp, lambda[, j], sd.lambda[, j]) } - w.samp[[j]] <- mvtnorm::rmvnorm(n.samp, object$w[, j], object$Varw[[j]]) + w.samp[[j]] <- sample_w(n.samp, object$w[, j], object$Varw[[j]]) } - # Then combine each sample into a list of length n.samp (makes it easier in - # the calculation of the probabilities etc.) + + # Then combine each sample into a list of length n.samp (makes it easier in -- + # the calculation of the probabilities etc.) --------------------------------- alpha.samp <- mapply(function(x) unlist(lapply(alpha.samp, `[`, x)), seq_len(n.samp), SIMPLIFY = FALSE) - lambda.samp <- mapply(function(x) matrix(unlist(lapply(lambda.samp, `[`, x, )), ncol = m), - seq_len(n.samp), SIMPLIFY = FALSE) + if (isTRUE(vb.closed)) { + theta.samp <- mapply(function(x) matrix(unlist(lapply(theta.samp, `[`, x, )), + ncol = m), + seq_len(n.samp), SIMPLIFY = FALSE) + } else { + theta.samp <- object$theta.samp + theta.samp <- theta.samp[sample(seq_along(theta.samp), size = n.samp)] + } w.samp <- mapply(function(x) matrix(unlist(lapply(w.samp, `[`, x, )), ncol = m), seq_len(n.samp), SIMPLIFY = FALSE) - phat.samp <- list() + error.samp <- brier.samp <- rep(NA, n.samp) - if (is.null(y)) y <- object$ipriorKernel$Y for (i in seq_len(n.samp)) { alpha <- alpha.samp[[i]] - lambda <- lambda.samp[[i]] + theta <- theta.samp[[i]] w <- w.samp[[i]] - ystar.samp <- calc_ystar(object, Hl.new = Hl.new, alpha.new = alpha, - lambda.new = lambda, w.new = w) - tmp <- predict_iprobit_mult(y, object$ipriorKernel$y.levels, ystar.samp) + ystar.samp <- calc_ystar(object$ipriorKernel, xstar, alpha, theta, w, til) + tmp <- probs_yhat_error(y, object$ipriorKernel$y.levels, ystar.samp) phat.samp[[i]] <- tmp$prob - error.samp[i] <- tmp$train.error - brier.samp[i] <- tmp$brier.score + error.samp[i] <- tmp$error + brier.samp[i] <- tmp$brier } list(phat.samp = phat.samp, error.samp = error.samp, brier.samp = brier.samp) } +sample_alpha <- function(n.samp, mean, sd) { + # Helper function for sample_prob_bin(). Samples the intercept from the + # (approximated) posterior N(mean, sd^2). + # + # Args: The number of samples n.samp, the mean and sd of the normal + # distribution. + # + # Returns: A vector of length n.samp containing the intercept samples. + rnorm(n.samp, mean, sd) +} + +sample_lambda <- function(n.samp, mean, sd) { + # Helper function for sample_prob_bin(). Samples the intercept from the + # (approximated) posterior N(mean, sd^2). + # + # Args: The number of samples n.samp, the mean and sd of the normal + # distribution. + # + # Returns: A matrix with n.samp rows containing the lambda samples. + if (length(mean) > 1) { + return(mvtnorm::rmvnorm(n.samp, mean, diag(sd ^ 2))) + } else { + return(matrix(rnorm(n.samp, mean, sd))) + } +} +sample_w <- function(n.samp, mean, variance) { + # Helper function for sample_prob_bin(). Samples the intercept from the + # (approximated) posterior N(mean, variance). + # + # Args: The number of samples n.samp, the mean and variance of the + # multivariate normal distribution. + # + # Returns: A matrix with n.samp rows containing the w samples. + mvtnorm::rmvnorm(n.samp, mean, variance) +} -#' @export convert_prob <- function(phat, transform = identity) { # Converts list of length n.samp containing random samples of the # probabilities. This function converts it into a list of length no.classes so @@ -373,7 +358,6 @@ convert_prob <- function(phat, transform = identity) { res } -#' @export quantile_prob <- function(phats) { # Get quantiles for lists returned from convert_prob() for (i in seq_along(phats)) { @@ -383,7 +367,6 @@ quantile_prob <- function(phats) { lapply(phats, as.data.frame) } -#' @export print.iprobitPredict <- function(x, rows = 10, digits = 3, ...) { if (x$type == "train") { cat("Training error:", paste0(decimal_place(x$error.rate, digits), "%\n")) @@ -413,7 +396,6 @@ print.iprobitPredict <- function(x, rows = 10, digits = 3, ...) { if (nrow(x$p) > rows) cat("# ... with", nrow(x$p) - rows, "more rows") } -#' @export print.iprobitPredict_quant <- function(x, rows = 5, digits = 3, ...) { rows.act <- nrow(x$prob[[1]]) rows <- min(rows.act, rows) diff --git a/R/iprobit.R b/R/iprobit.R index c1c11c2..9dec774 100644 --- a/R/iprobit.R +++ b/R/iprobit.R @@ -30,7 +30,7 @@ iprobit.default <- function(y, ..., kernel = "linear", interactions = NULL, train.samp, control = list()) { # Load the I-prior model ----------------------------------------------------- if (iprior::is.ipriorKernel(y)) { - mod <- y + mod <- remove_psi(y) # remove psi from estimation procedure if not already } else { mod <- iprior::kernL(y, ..., kernel = kernel, interactions = interactions, est.lambda = TRUE, est.psi = FALSE, psi = 1, @@ -42,16 +42,17 @@ iprobit.default <- function(y, ..., kernel = "linear", interactions = NULL, # Set up controls ------------------------------------------------------------ control_ <- list( - maxit = 10, + maxit = 100, stop.crit = 1e-5, silent = FALSE, alpha0 = NULL, # if NULL, parameters are - lambda0 = NULL, # initialised in VB + # lambda0 = NULL, # initialised in VB w0 = NULL, # routine theta0 = NULL, n.samp = 100, # settings for sd.samp = 0.15, # the metropolis thin.samp = 2, # sampler + seed = NULL, restarts = 0, restart.method = c("lb", "error", "brier") ) @@ -74,11 +75,11 @@ iprobit.default <- function(y, ..., kernel = "linear", interactions = NULL, if (m == 2) { # Binary models ---------------------------------------------------------- if (est.method["em.closed"]) { # VB CLOSED-FORM - res <- iprobit_bin(mod, maxit, stop.crit, silent, alpha0, lambda0, w0) + res <- iprobit_bin(mod, maxit, stop.crit, silent, alpha0, theta0, w0) res$est.method <- "Closed-form VB-EM algorithm." } else { res <- iprobit_bin_metr(mod, maxit, stop.crit, silent, alpha0, theta0, - w0, n.samp, sd.samp, thin.samp) + w0, n.samp, sd.samp, thin.samp, seed) res$est.method <- paste0("VB-EM with Metropolis sampler (", iprior::dec_plac(mean(res$acc.rate) * 100, 1), "% acc.).") @@ -87,12 +88,16 @@ iprobit.default <- function(y, ..., kernel = "linear", interactions = NULL, } else { # Multinomial models ----------------------------------------------------- if (est.method["em.closed"]) { # VB CLOSED-FORM - res <- iprobit_mult(mod, maxit, stop.crit, silent, alpha0, lambda0, w0, + res <- iprobit_mult(mod, maxit, stop.crit, silent, alpha0, theta0, w0, common.intercept, common.RKHS.scale) res$est.method <- "Closed-form VB-EM algorithm." - # res$coefficients <- rbind(get_alpha(res), get_lambda(res)) } else { - stop("Not implemented yet.") + res <- iprobit_mult_metr(mod, maxit, stop.crit, silent, alpha0, theta0, + w0, n.samp, sd.samp, thin.samp, seed, + common.intercept, common.RKHS.scale) + res$est.method <- paste0("VB-EM with Metropolis sampler (", + iprior::dec_plac(mean(res$acc.rate) * 100, 1), + "% acc.).") } class(res) <- c("iprobitMod", "iprobitMod_mult") } @@ -106,6 +111,7 @@ iprobit.default <- function(y, ..., kernel = "linear", interactions = NULL, res$ipriorKernel <- mod } res$coefficients <- param.full_to_coef(res$param.full, mod) + # rownames(res$coefficients) <- get_names(mod, expand = FALSE) # Change the call to "iprobit" ----------------------------------------------- res$call <- iprior::.fix_call_default(match.call(), "iprobit") @@ -146,7 +152,6 @@ iprobit.formula <- function(formula, data, kernel = "linear", one.lam = FALSE, iprobit.ipriorKernel <- function(object, control = list(), common.intercept = FALSE, common.RKHS.scale = FALSE, ...) { - object <- remove_psi(object) res <- iprobit.default(y = object, method = method, control = control, common.intercept = common.intercept, common.RKHS.scale = common.RKHS.scale) diff --git a/R/iprobit_bin.R b/R/iprobit_bin.R index 0e6e833..bbb25cb 100644 --- a/R/iprobit_bin.R +++ b/R/iprobit_bin.R @@ -18,9 +18,8 @@ # ################################################################################ -#' @export iprobit_bin <- function(mod, maxit = 100, stop.crit = 1e-5, silent = FALSE, - alpha0 = NULL, lambda0 = NULL, w0 = NULL) { + alpha0 = NULL, theta0 = NULL, w0 = NULL) { # Declare all variables and functions to be used into environment ------------ iprobit.env <- environment() list2env(mod, iprobit.env) @@ -33,8 +32,12 @@ iprobit_bin <- function(mod, maxit = 100, stop.crit = 1e-5, silent = FALSE, # Initialise ----------------------------------------------------------------- if (is.null(alpha0)) alpha0 <- rnorm(1) - if (is.null(lambda0)) lambda0 <- abs(rnorm(p)) + if (is.null(theta0)) theta0 <- rnorm(p) if (is.null(w0)) w0 <- rep(0, n) + if (p == 1) lambda0 <- exp(theta0) # ensures positive values + else lambda0 <- theta0 + + alpha <- alpha0 lambda <- ct <- lambda0 lambdasq <- lambda0 ^ 2 Hl <- iprior::.expand_Hl_and_lambda(Hl, rep(1, p), intr, intr.3plus)$Hl # expand Hl @@ -42,7 +45,6 @@ iprobit_bin <- function(mod, maxit = 100, stop.crit = 1e-5, silent = FALSE, lambdasq <- expand_lambda(lambdasq[1:p], intr, intr.3plus) Hlam <- get_Hlam(mod, lambda, theta.is.lambda = TRUE) Hlamsq <- get_Hlamsq() - alpha <- alpha0 w <- w0 Varw <- NA f.tmp <- as.numeric(alpha + Hlam %*% w) @@ -105,7 +107,7 @@ iprobit_bin <- function(mod, maxit = 100, stop.crit = 1e-5, silent = FALSE, (sum(diag(W)) + determinant(A)$modulus + sum(log(ct))) / 2 # theta -------------------------------------------------------------------- - if (length(lambda) == 1) theta <- log(lambda[1:p]) + if (p == 1) theta <- log(lambda[1]) else theta <- lambda[1:p] theta <- matrix(theta, ncol = 2, nrow = length(theta)) @@ -138,7 +140,7 @@ iprobit_bin <- function(mod, maxit = 100, stop.crit = 1e-5, silent = FALSE, `97.5%` = c(alpha, lambda[1:p]) + qnorm(0.975) * sqrt(c(1 / n, 1 / ct[1:p])) ) colnames(param.summ) <- c("Mean", "S.D.", "2.5%", "97.5%") - rownames(param.summ) <- get_names(mod, expand = FALSE) + rownames(param.summ) <- get_names(mod, c("intercept", "lambda"), FALSE) # Clean up and close --------------------------------------------------------- convergence <- niter == maxit diff --git a/R/iprobit_bin_metr.R b/R/iprobit_bin_metr.R index c25ea6a..17763a3 100644 --- a/R/iprobit_bin_metr.R +++ b/R/iprobit_bin_metr.R @@ -1,6 +1,40 @@ +################################################################################ +# +# iprobit: Binary and Multinomial Probit Regression with I-priors +# Copyright (C) 2017 Haziq Jamil +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +################################################################################ + +# The updating equation for theta depends on a density that cannot be +# recognised. Therefore, in order to obtain posterior samples from this density, +# we must emebed a Metropolis step within the VB-EM. After getting samples +# theta.samp, we can then calculate any statistic of interest, including +# E[H_theta], E[H_theta^2], the unconstrained version of the parameters +# g^{-1})(theta), and so on. Uncertainty of these estimates can be summarised +# through the standard deviation of the samples, and we also calculate the +# quantiles c(0.025, 0.975) for the relevant samples. +# +# In order to obtain E[H_theta] and E[H_theta^2], we create a list Hlaml (c.f. +# Hlamsql) of length n.samp. For each value of theta.samp, calculate H_theta and +# store it in the list. The estimate would then be the sample mean. + iprobit_bin_metr <- function(mod, maxit = 20, stop.crit = 1e-5, silent = FALSE, alpha0 = NULL, theta0 = NULL, w0 = NULL, - n.samp = 1000, sd.samp = 0.1, thin.samp = 10) { + n.samp = 1000, sd.samp = 0.15, thin.samp = 10, + seed = NULL) { # Declare all variables and functions to be used into environment ------------ iprobit.env <- environment() list2env(mod, iprobit.env) @@ -10,6 +44,7 @@ iprobit_bin_metr <- function(mod, maxit = 20, stop.crit = 1e-5, silent = FALSE, thin.seq <- seq_len(n.samp)[seq_len(n.samp) %% thin.samp == 0] # Initialise ----------------------------------------------------------------- + if (!is.null(seed)) set.seed(seed) if (is.null(alpha0)) alpha0 <- rnorm(1) if (is.null(theta0)) theta0 <- rnorm(thetal$n.theta) if (is.null(w0)) w0 <- rep(0, n) @@ -83,6 +118,7 @@ iprobit_bin_metr <- function(mod, maxit = 20, stop.crit = 1e-5, silent = FALSE, } acc.rate[niter] <- count / n.samp theta <- apply(theta.samp[thin.seq, ], 2, mean) + theta <- matrix(theta, ncol = 2, nrow = length(theta)) Hlam <- Reduce("+", Hlaml[thin.seq]) / length(Hlaml[thin.seq]) Hlamsq <- Reduce("+", Hlamsql[thin.seq]) / length(Hlamsql[thin.seq]) @@ -116,10 +152,12 @@ iprobit_bin_metr <- function(mod, maxit = 20, stop.crit = 1e-5, silent = FALSE, time.taken <- iprior::as.time(end.time - start.time) # Calculate posterior s.d. and prepare param table --------------------------- + param.full <- theta_to_param.full(theta, alpha, mod) param.summ <- rbind( - alpha = c(alpha, 1 / n, alpha - qnorm(0.975) / n, alpha + qnorm(0.975) / n), - theta.samp_to_param.summ(theta.samp[thin.seq, ], mod) + c(alpha, 1 / n, alpha - qnorm(0.975) / n, alpha + qnorm(0.975) / n), + theta.samp_to_summ(theta.samp[thin.seq, ], mod) ) + rownames(param.summ) <- get_names(mod, expand = FALSE) # Clean up and close --------------------------------------------------------- convergence <- niter == maxit @@ -129,8 +167,8 @@ iprobit_bin_metr <- function(mod, maxit = 20, stop.crit = 1e-5, silent = FALSE, else cat("Converged after", niter, "iterations.\n") } - list(theta = theta, param.summ = param.summ, w = w, Varw = Varw, - lower.bound = as.numeric(na.omit(lb)), niter = niter, + list(theta = theta, param.full = param.full, param.summ = param.summ, w = w, + Varw = Varw, lower.bound = as.numeric(na.omit(lb)), niter = niter, start.time = start.time, end.time = end.time, time = time.taken, fitted.values = fitted.values, test = fitted.test, train.error = as.numeric(na.omit(train.error)), @@ -141,7 +179,7 @@ iprobit_bin_metr <- function(mod, maxit = 20, stop.crit = 1e-5, silent = FALSE, theta.samp = theta.samp[thin.seq, ]) } -theta.samp_to_param.summ <- function(theta.samp, object) { +theta.samp_to_summ <- function(theta.samp, object) { # Args: theta.samp is the sample from the (approximate) posterior q(theta), # and object is an ipriorKernel object. theta <- apply(theta.samp, 2, mean) @@ -151,14 +189,14 @@ theta.samp_to_param.summ <- function(theta.samp, object) { ginv.theta.sd <- apply(ginv.theta.samp, 2, sd) ginv.theta.0025 <- apply(ginv.theta.samp, 2, stats::quantile, probs = 0.025) ginv.theta.0975 <- apply(ginv.theta.samp, 2, stats::quantile, probs = 0.975) - param.summ <- data.frame( + res <- data.frame( Mean = ginv.theta, S.D. = ginv.theta.sd, `2.5%` = ginv.theta.0025, `97.5%` = ginv.theta.0975 ) - colnames(param.summ) <- c("Mean", "S.D.", "2.5%", "97.5%") - param.summ + colnames(res) <- c("Mean", "S.D.", "2.5%", "97.5%") + res } diff --git a/R/iprobit_bin_metr (backup).R b/R/iprobit_bin_metr_backup.R similarity index 100% rename from R/iprobit_bin_metr (backup).R rename to R/iprobit_bin_metr_backup.R diff --git a/R/iprobit_helper.R b/R/iprobit_helper.R index 116b415..8f8a619 100644 --- a/R/iprobit_helper.R +++ b/R/iprobit_helper.R @@ -18,6 +18,192 @@ # ################################################################################ +expand_lambda <- function(x, intr, intr.3plus = NULL) { + # Helper function to expand lambda or lambda.sq (scale parameters) according + # to any interactions specification. + # + # Args: lambda + # + # Returns: Expanded lambda. + if (is.vector(x)) { + # Binary models ------------------------------------------------------------ + return(iprior::.expand_Hl_and_lambda(x, x, intr, intr.3plus)$lambda) + } else { + # Multinomial models ------------------------------------------------------- + res <- NULL + m <- ncol(x) + for (j in seq_len(m)) { + res[[j]] <- iprior::.expand_Hl_and_lambda(x[, j], x[, j], intr, + intr.3plus)$lambda + } + return(matrix(unlist(res), ncol = m)) + } +} + +get_Hlam <- function(object, theta, theta.is.lambda = FALSE) { + # Obtains the kernel matrix Hlam. + # + # Args: + # + # Returns: For binary models, this calculate Hlam. For multinomial models, + # this calculates Hlam for every class---so a list is returned. + if (is.iprobit_bin(object)) { + if (is.matrix(theta)) theta <- theta[, 1] + return(iprior::.get_Hlam(object, theta, theta.is.lambda)) + } else { + res <- NULL + m <- get_m(object) + for (j in seq_len(m)) { + res[[j]] <- iprior::.get_Hlam(object, as.numeric(theta[, j]), + theta.is.lambda) + } + return(res) + } +} + +get_Htildelam <- function(object, theta, xstar, theta.is.lambda = FALSE) { + if (is.iprobit_bin(object)) { + if (is.matrix(theta)) theta <- theta[, 1] + return(iprior::.get_Htildelam(object, theta, xstar, theta.is.lambda)) + } else { + res <- NULL + m <- get_m(object) + for (j in seq_len(m)) { + res[[j]] <- iprior::.get_Htildelam(object, as.numeric(theta[, j]), xstar, + theta.is.lambda) + } + return(res) + } +} + +get_Hlamsq <- function() { + # Calculate Hlamsq for closed-form VB. + # + # Args: mod is an ipriorKernel object defined in parent environment, along + # with lambda, lambdasq, Psql, Hsql, H2l, ind1, ind2, p, and no.int. + # + # Returns: Hlamsq mat for binary models, and list of Hlamsq matrices for + # multinomial models. + environment(Hlam_two_way_index) <- environment() + q <- p + no.int + m <- get_m(mod) + + if (is.iprobit_bin(mod)) { # BINARY MODEL + # Calculate square terms of Hlamsq ----------------------------------------- + if (is.null(Hsql)) + square.terms <- Reduce("+", mapply("*", Psql[1:q], lambdasq[1:q], + SIMPLIFY = FALSE)) + else + square.terms <- Reduce("+", mapply("*", Hsql[1:q], lambdasq[1:q], + SIMPLIFY = FALSE)) + + # Calculate two-way terms of Hlamsq ---------------------------------------- + if (is.null(ind1) && is.null(ind2)) { + two.way.terms <- 0 + } else { + lambda.two.way <- Hlam_two_way_index(lambda, lambdasq) + two.way.terms <- Reduce("+", mapply("*", H2l, lambda.two.way, + SIMPLIFY = FALSE)) + } + + return(square.terms + two.way.terms) + } else { + res <- NULL + for (j in seq_len(m)) { # MULTINOMIAL MODEL + # Calculate square terms of Hlamsq ----------------------------------------- + if (is.null(Hsql)) + square.terms <- Reduce("+", mapply("*", Psql[1:q], lambdasq[1:q, j], + SIMPLIFY = FALSE)) + else + square.terms <- Reduce("+", mapply("*", Hsql[1:q], lambdasq[1:q, j], + SIMPLIFY = FALSE)) + + # Calculate two-way terms of Hlamsq ---------------------------------------- + if (is.null(ind1) && is.null(ind2)) + two.way.terms <- 0 + else { + lambda.two.way <- Hlam_two_way_index(lambda[1:q, j], lambdasq[1:q, j]) + two.way.terms <- + Reduce("+", mapply("*", H2l, lambda.two.way, SIMPLIFY = FALSE)) + } + + res[[j]] <- square.terms + two.way.terms + } + return(res) + } +} + +Hlam_two_way_index <- function(lam, lamsq) { + # mod <- iprior::.kernL(Species ~ . ^ 2, iris) + # iprobit.env <- environment() + # list2env(mod, iprobit.env) + # list2env(BlockBstuff, iprobit.env) + # list2env(model, iprobit.env) + + comb.ind12 <- cbind(ind1, ind2) + comb.ind12 <- split(comb.ind12, row(comb.ind12)) + + replace_ind <- function(x) { + if (any(x > p)) { + here <- which(x > p) + what <- x[here] - p + res <- c(x[-here], intr[, what]) + sort(res) + } else { + x + } + } + + lam_ind <- function(x) { + here <- which(duplicated(x)) + if (length(here > 0)) { + what <- x[here] + what.not <- x[x != what] + prod(lamsq[what]) * prod(lam[what.not]) + } else { + prod(lam[x]) + } + } + + lapply(lapply(comb.ind12, replace_ind), lam_ind) +} + +# myfun <- function() { +# # Function to test lambdaExpand_mult() and the Hlam*_mult functions +# set.seed(123) +# dat <- gen_mixture(n = 4, m = 3) +# mod <- iprior::.kernL(y ~ . ^ 2, dat) +# iprobit.env <- environment() +# list2env(mod, iprobit.env) +# list2env(BlockBstuff, iprobit.env) +# list2env(model, iprobit.env) +# environment(lambdaExpand_mult) <- iprobit.env +# environment(HlamFn_mult) <- iprobit.env +# environment(HlamsqFn_mult) <- iprobit.env +# environment(Hlam_two_way_index) <- iprobit.env +# m <- length(y.levels) +# lambda <- matrix(2:3, ncol = m, nrow = l) +# lambda.sq <- lambda ^ 2 +# lambdaExpand_mult(x = lambda, y = lambda.sq, env = iprobit.env) +# HlamFn_mult(env = iprobit.env) +# HlamsqFn_mult(env = iprobit.env) +# list(H = Hl, Hsq = Hsql, lambda = lambda, lambda.sq = lambda.sq, +# Hlam.mat = Hlam.mat[[1]], Hlam.matsq = Hlam.matsq[[1]]) +# +# # CHECK +# # 2 3 6 (lambda) +# # 4 9 36 (lambda.sq) +# # +# # H1 <- H[[1]] +# # H2 <- H[[2]] +# # H12 <- H[[1]] * H[[2]] +# # H1sq <- H1 %*% H1 +# # H2sq <- H2 %*% H2 +# # H12sq <- H12 %*% H12 +# # +# # 4 * H1sq + 9 * H2sq + 36 * H12sq + 2 * 3 * (H1 %*% H2 + H2 %*% H1) + 4 * 3 * (H1 %*% H12 + H12 %*% H1) + 9 * 2 * (H2 %*% H12 + H12 %*% H2) +# } + # Helper function to determine when to stop the while loop for the VB-EM # algorithm. # If niter == 0 return TRUE because must complete 1 iteration. diff --git a/R/iprobit_mult.R b/R/iprobit_mult.R index 74d53dc..1494f47 100644 --- a/R/iprobit_mult.R +++ b/R/iprobit_mult.R @@ -18,9 +18,8 @@ # ################################################################################ -#' @export iprobit_mult <- function(mod, maxit = 10, stop.crit = 1e-5, silent = FALSE, - alpha0 = NULL, lambda0 = NULL, w0 = NULL, + alpha0 = NULL, theta0 = NULL, w0 = NULL, common.intercept = FALSE, common.RKHS.scale = FALSE) { # Declare all variables and functions to be used into environment ------------ iprobit.env <- environment() @@ -39,12 +38,15 @@ iprobit_mult <- function(mod, maxit = 10, stop.crit = 1e-5, silent = FALSE, if (isTRUE(common.intercept)) alpha0 <- rep(rnorm(1), m) else alpha0 <- rnorm(m) } - if (is.null(lambda0)) { - if (isTRUE(common.RKHS.scale)) lambda0 <- rep(abs(rnorm(p)), m) - else lambda0 <- abs(rnorm(p * m)) + if (is.null(theta0)) { + if (isTRUE(common.RKHS.scale)) theta0 <- rep(rnorm(p), m) + else theta0 <- rnorm(p * m) } + if (p == 1) lambda0 <- exp(theta0) + else lambda0 <- theta0 if (is.null(w0)) w0 <- matrix(0, ncol = m, nrow = n) - alpha <- rep(1, m); alpha[1:m] <- alpha0 + + alpha <- alpha0 theta <- lambda <- ct <- dt <- matrix(lambda0, ncol = m, nrow = p) lambdasq <- lambda ^ 2 Hl <- iprior::.expand_Hl_and_lambda(Hl, rep(1, p), intr, intr.3plus)$Hl # expand Hl @@ -141,8 +143,8 @@ iprobit_mult <- function(mod, maxit = 10, stop.crit = 1e-5, silent = FALSE, if (isTRUE(common.intercept)) alpha <- rep(mean(alpha), m) # theta -------------------------------------------------------------------- - if (nrow(lambda) == 1) theta <- log(lambda) - else theta <- lambda + if (p == 1) theta <- log(lambda[1, , drop = FALSE]) + else theta <- lambda[1:p, , drop = FALSE] # Calculate lower bound ---------------------------------------------------- lb.ystar <- sum(logClb) @@ -221,11 +223,6 @@ iprobit_mult <- function(mod, maxit = 10, stop.crit = 1e-5, silent = FALSE, test.brier = as.numeric(na.omit(test.brier)), convergence = convergence) } -# set.seed(123) -# dat <- gen_mixture(n = 400, m = 4) -# mod <- kernL(y ~ ., dat, est.psi = FALSE) -# set.seed(123); tmp <- iprobit_mult(mod) - # else { # # Nystrom approximation # K.mm <- Hlamsq[[j]][1:Nystrom$m, 1:Nystrom$m] diff --git a/R/iprobit_mult_metr.R b/R/iprobit_mult_metr.R new file mode 100644 index 0000000..6587966 --- /dev/null +++ b/R/iprobit_mult_metr.R @@ -0,0 +1,306 @@ +################################################################################ +# +# iprobit: Binary and Multinomial Probit Regression with I-priors +# Copyright (C) 2017 Haziq Jamil +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +################################################################################ + +# theta.samp would now be a list of length n.samp and each component of this +# list is the matrix theta.samp. +# +# Similarly Hlaml and Hlamsql are lists of length n.samp, and each component of +# the list is another list of length m (no. of classes) where the values +# Hlam_theta for each theta.samp are stored. + +iprobit_mult_metr <- function(mod, maxit = 5, stop.crit = 1e-5, silent = FALSE, + alpha0 = NULL, theta0 = NULL, w0 = NULL, + n.samp = 100, sd.samp = 0.15, thin.samp = 1, + seed = NULL, common.intercept = FALSE, + common.RKHS.scale = FALSE) { + # Declare all variables and functions to be used into environment ------------ + iprobit.env <- environment() + list2env(mod, iprobit.env) + environment(loop_logical) <- iprobit.env + y <- as.numeric(factor(y)) # as.factor then as.numeric to get y = 1, 2, ... + m <- length(y.levels) + nm <- n * m + maxit <- max(1, maxit) + thin.seq <- seq_len(n.samp)[seq_len(n.samp) %% thin.samp == 0] + + # Initialise ----------------------------------------------------------------- + if (!is.null(seed)) set.seed(seed) + if (is.null(alpha0)) { + if (isTRUE(common.intercept)) alpha0 <- rep(rnorm(1), m) + else alpha0 <- rnorm(m) + } + if (is.null(theta0)) { + if (isTRUE(common.RKHS.scale)) theta0 <- rep(abs(rnorm(p)), m) + else theta0 <- abs(rnorm(p * m)) + } + if (is.null(w0)) w0 <- matrix(0, ncol = m, nrow = n) + + alpha <- alpha0 + theta.samp <- Hlaml <- Hlamsql <- list(NULL) + theta <- matrix(theta0, ncol = m, nrow = thetal$n.theta) + Hlaml[[1]] <- Hlam <- get_Hlam(mod, theta) + Hlamsql[[1]] <- Hlamsq <- lapply(Hlaml[[1]], iprior::fastSquare) + theta.samp[[1]] <- theta + + w <- f.tmp <- ystar <- w0 + Varw <- W <- list(NULL) + f.tmp <- rep(alpha, each = n) + mapply("%*%", Hlam, split(w, col(w))) + logdetA <- rep(NA, m) + w.ind <- seq_len( + ifelse(isTRUE(common.intercept) && isTRUE(common.RKHS.scale), 1, m) + ) + niter <- 0 + lb <- train.error <- train.brier <- test.error <- test.brier <- rep(NA, maxit) + logClb <- rep(NA, n) + log.q.star <- rep(NA, m) + log.q <- matrix(NA, ncol = m, nrow = n.samp) + log.q[1, ] <- sapply(Hlamsq, function(x) -0.5 * sum(diag(x))) + acc.rate <- matrix(NA, nrow = maxit, ncol = m) + + # The variational EM loop ---------------------------------------------------- + if (!silent) pb <- txtProgressBar(min = 0, max = maxit, style = 1) + start.time <- Sys.time() + + while (loop_logical()) { # see loop_logical() function in iprobit_helper.R + # Update ystar ------------------------------------------------------------- + for (i in 1:n) { + j <- as.numeric(y[i]) + fi <- f.tmp[i, ] + fik <- fi[-j]; fij <- fi[j] + logClb[i] <- logC <- EprodPhiZ(fij - fik, log = TRUE) + for (k in seq_len(m)[-j]) { + logD <- log(integrate( + function(z) { + fij.minus.fil <- fij - fi[-c(k, j)] + logPhi.l <- 0 + for (kk in seq_len(length(fij.minus.fil))) + logPhi.l <- logPhi.l + pnorm(z + fij.minus.fil[kk], log.p = TRUE) + logphi.k <- dnorm(z + fij - fi[k], log = TRUE) + exp(logphi.k + logPhi.l) * dnorm(z) + }, lower = -Inf, upper = Inf + )$value) + ystar[i, k] <- fi[k] - exp(logD - logC) + } + ystar[i, j] <- fi[j] - sum(ystar[i, -j] - fi[-j]) + } + + # Update w ----------------------------------------------------------------- + for (j in w.ind) { + A <- Hlamsq[[j]] + diag(1, n) + a <- as.numeric(crossprod(Hlam[[j]], ystar[, j] - alpha[j])) + eigenA <- iprior::eigenCpp(A) + V <- eigenA$vec + u <- eigenA$val + 1e-8 # ensure positive eigenvalues + uinv.Vt <- t(V) / u + w[, j] <- as.numeric(crossprod(a, V) %*% uinv.Vt) + Varw[[j]] <- iprior::fastVDiag(V, 1 / u) # V %*% uinv.Vt + W[[j]] <- Varw[[j]] + tcrossprod(w[, j]) + logdetA[j] <- determinant(A)$mod + } + if (isTRUE(common.intercept) && isTRUE(common.RKHS.scale)) { + w <- matrix(w[, 1], nrow = n, ncol = m) + W <- rep(list(W[[1]]), m) + logdetA <- rep(logdetA[1], m) + } + + # Update theta ------------------------------------------------------------- + count <- rep(0, m) + if (isTRUE(common.RKHS.scale)) { + # SAME RKHS PARAMETERS, SO JUST NEED TO SAMPLE ONCE AND REPEAT IN EACH CLASS + for (t in seq_len(n.samp - 1)) { + theta.star <- theta.current <- theta.samp[[t]] + theta.star[] <- rnorm(thetal$n.theta, mean = theta.current[, 1], sd = sd.samp) + Hlam.star <- get_Hlam(mod, theta.star) + Hlamsq.star <- lapply(Hlam.star, iprior::fastSquare) + log.q.star[] <- as.numeric(-0.5 * ( + sum(Hlamsq.star[[1]] * W[[1]]) - + 2 * crossprod(ystar[, 1] - alpha[1], Hlam.star[[1]]) %*% w[, 1] + )) + log.prob.acc <- log.q.star - log.q[t, ] + prob.acc <- exp(log.prob.acc) + prob.acc[prob.acc > 1] <- 1 + if (runif(1) < prob.acc[j]) { + theta.samp[[t + 1]] <- theta.star + Hlaml[[t + 1]] <- Hlam.star + Hlamsql[[t + 1]] <- Hlamsq.star + log.q[t + 1, ] <- log.q.star + count <- count + 1 + } else { + theta.samp[[t + 1]] <- theta.current + Hlaml[[t + 1]] <- Hlaml[[t]] + Hlamsql[[t + 1]] <- Hlamsql[[t]] + log.q[t + 1, ] <- log.q[t, ] + } + } + } else { + # DIFFERENT RKHS SCALE, SO INDEPENDENT SAMPLER FOR EACH CLASS + for (t in seq_len(n.samp - 1)) { + theta.star <- theta.current <- theta.samp[[t]] + for (j in 1:m) { + theta.star[, j] <- rnorm(thetal$n.theta, mean = theta.current[, j], sd = sd.samp) + } + Hlam.star <- get_Hlam(mod, theta.star) + Hlamsq.star <- lapply(Hlam.star, iprior::fastSquare) + for (j in 1:m) { + log.q.star[j] <- as.numeric(-0.5 * ( + sum(Hlamsq.star[[j]] * W[[j]]) - + 2 * crossprod(ystar[, j] - alpha[j], Hlam.star[[j]]) %*% w[, j] + )) + } + log.prob.acc <- log.q.star - log.q[t, ] + prob.acc <- exp(log.prob.acc) + prob.acc[prob.acc > 1] <- 1 + Hlam.tmp <- Hlaml[[t]] + Hlamsq.tmp <- Hlamsql[[t]] + theta.tmp <- theta.current + for (j in 1:m) { + if (runif(1) < prob.acc[j]) { + theta.tmp[, j] <- theta.star[, j] + Hlam.tmp[[j]] <- Hlam.star[[j]] + Hlamsq.tmp[[j]] <- Hlamsq.star[[j]] + log.q[t + 1, j] <- log.q.star[j] + count[j] <- count[j] + 1 + } else { + log.q[t + 1, j] <- log.q[t, j] + } + } + theta.samp[[t + 1]] <- theta.tmp + Hlaml[[t + 1]] <- Hlam.tmp + Hlamsql[[t + 1]] <- Hlamsq.tmp + } + } + acc.rate[niter, ] <- count / n.samp + theta <- Reduce("+", theta.samp[thin.seq]) / length(theta.samp[thin.seq]) + + # Update Hlam and Hlamsq --------------------------------------------------- + for (j in 1:m) { + tmp <- lapply(Hlaml, function(x) x[[j]]) + Hlam[[j]] <- Reduce("+", tmp[thin.seq]) / length(tmp[thin.seq]) + tmp <- lapply(Hlamsql, function(x) x[[j]]) + Hlamsq[[j]] <- Reduce("+", tmp[thin.seq]) / length(tmp[thin.seq]) + } + + # Update alpha ------------------------------------------------------------- + alpha <- apply(ystar - mapply("%*%", Hlam, split(w, col(w))), 2, mean) + if (isTRUE(common.intercept)) alpha <- rep(mean(alpha), m) + + # Calculate lower bound ---------------------------------------------------- + lb.ystar <- sum(logClb) + lb.w <- 0.5 * (nm - sum(sapply(W, function(x) sum(diag(x)))) - sum(logdetA)) + lb.lambda <- -sum(apply(log.q, 2, mean)) + if (isTRUE(common.intercept)) + lb.alpha <- 0.5 * (1 + log(2 * pi) - log(nm)) + else + lb.alpha <- (m / 2) * (1 + log(2 * pi) - log(n)) + lb[niter + 1] <- lb.ystar + lb.w + lb.lambda + lb.alpha + + # Calculate fitted values and error rate ----------------------------------- + f.tmp <- rep(alpha, each = n) + mapply("%*%", Hlam, split(w, col(w))) + fitted.values <- probs_yhat_error(y, y.levels, f.tmp) + train.error[niter + 1] <- fitted.values$error + train.brier[niter + 1] <- fitted.values$brier + fitted.test <- NULL + if (iprior::.is.ipriorKernel_cv(mod)) { + ystar.test <- calc_ystar(mod, mod$Xl.test, alpha, theta, w) + fitted.test <- probs_yhat_error(y.test, y.levels, ystar.test) + test.error[niter + 1] <- fitted.test$error + test.brier[niter + 1] <- fitted.test$brier + } + + niter <- niter + 1 + if (!silent) setTxtProgressBar(pb, niter) + } + + end.time <- Sys.time() + time.taken <- iprior::as.time(end.time - start.time) + + # Calculate posterior s.d. and prepare param table --------------------------- + param.full <- theta_to_param.full(theta, alpha, mod) + if (isTRUE(common.intercept)) { + alpha <- unique(alpha) # since they're all the same + se.alpha <- sqrt(1 / nm) + } else { + se.alpha <- rep(sqrt(1 / n), m) + } + alpha.summ <- cbind(alpha, se.alpha, alpha - qnorm(0.975) * se.alpha, + alpha + qnorm(0.975) * se.alpha) + theta.summ <- theta.samp_to_summ_mult(theta.samp, mod) + colnames(alpha.summ) <- colnames(theta.summ) + param.summ <- rbind(alpha.summ, theta.summ) + rownames(param.summ) <- c(get_names(mod, "intercept", !common.intercept), + get_names(mod, c("lambda", "hurst", "lengthscale", + "offset"), !common.RKHS.scale)) + + # Clean up and close --------------------------------------------------------- + convergence <- niter == maxit + if (!silent) { + close(pb) + if (convergence) cat("Convergence criterion not met.\n") + else cat("Converged after", niter, "iterations.\n") + } + + list(theta = theta, param.full = param.full, param.summ = param.summ, w = w, + Varw = Varw, lower.bound = as.numeric(na.omit(lb)), niter = niter, + start.time = start.time, end.time = end.time, time = time.taken, + fitted.values = fitted.values, test = fitted.test, + train.error = as.numeric(na.omit(train.error)), + train.brier = as.numeric(na.omit(train.brier)), + test.error = as.numeric(na.omit(test.error)), + test.brier = as.numeric(na.omit(test.brier)), convergence = convergence, + acc.rate = as.numeric(na.omit(acc.rate)), + theta.samp = theta.samp[thin.seq]) +} + +theta.samp_to_summ_mult <- function(theta.samp, object) { + m <- ncol(theta.samp[[1]]) + theta.samp_col <- function(x, j) x[, j] + res <- NULL + for (j in seq_len(m)) { + tmp <- t(sapply(theta.samp, theta.samp_col, j = j)) + res[[j]] <- theta.samp_to_summ(tmp, object) + } + res.lambda <- do.call(rbind, lapply(res, function(x) x[grep("lambda", rownames(x)), ])) + res.hurst <- do.call(rbind, lapply(res, function(x) x[grep("hurst", rownames(x)), ])) + res.lengthscale <- do.call(rbind, lapply(res, function(x) x[grep("lengthscale", rownames(x)), ])) + res.offset <- do.call(rbind, lapply(res, function(x) x[grep("offset", rownames(x)), ])) + + res <- rbind( + res.lambda, res.hurst, res.lengthscale, res.offset + ) + unq.ind <- match(unique(res[, 1]), res[, 1]) + # rownames(res) <- get_names(mod, c("lambda", "hurst", "lengthscale", "offset")) + res[unq.ind, ] +} + +# else { +# # Nystrom approximation +# K.mm <- Hlamsq[[j]][1:Nystrom$m, 1:Nystrom$m] +# eigenK.mm <- iprior::eigenCpp(K.mm) +# V <- Hlamsq[[j]][, 1:Nystrom$m] %*% eigenK.mm$vec +# u <- eigenK.mm$val +# u.Vt <- t(V) * u +# D <- u.Vt %*% V + diag(1, Nystrom$m) +# E <- solve(D, u.Vt, tol = 1e-18) +# # see https://stackoverflow.com/questions/22134398/mahalonobis-distance-in-r-error-system-is-computationally-singular +# # see https://stackoverflow.com/questions/21451664/system-is-computationally-singular-error +# w[, j] <- as.numeric(a - V %*% (E %*% a)) +# W[[j]] <- (diag(1, n) - V %*% E) + tcrossprod(w[, j]) +# logdetA[j] <- determinant(A)$mod +# } diff --git a/man/Accessors.Rd b/man/Accessors.Rd new file mode 100644 index 0000000..a296596 --- /dev/null +++ b/man/Accessors.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Accessor_functions.R +\name{Accessors} +\alias{Accessors} +\alias{get_hyperparam} +\alias{get_intercept} +\title{Accessor functions for \code{ipriorMod} objects.} +\usage{ +get_hyperparam(object) + +get_intercept(object, by.class = FALSE) +} +\arguments{ +\item{object}{An \code{ipriorMod} object.} +} +\description{ +Accessor functions for \code{ipriorMod} objects. +} +\section{Functions}{ +\itemize{ +\item \code{get_hyperparam}: Obtain all of the hyperparameters. + +\item \code{get_intercept}: Obtain the intercept. +}} + diff --git a/man/logLik.iprobitMod.Rd b/man/logLik.iprobitMod.Rd index 77381f4..63e7eec 100644 --- a/man/logLik.iprobitMod.Rd +++ b/man/logLik.iprobitMod.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/Utilities.R +% Please edit documentation in R/Accessor_functions.R \name{logLik.iprobitMod} \alias{logLik.iprobitMod} \title{Extract the variational lower bound} diff --git a/tests/testthat/test-5kernels.R b/tests/testthat/OLDtest-5kernels.R similarity index 100% rename from tests/testthat/test-5kernels.R rename to tests/testthat/OLDtest-5kernels.R diff --git a/tests/testthat/test-1gen_data.R b/tests/testthat/test-01-gen_data.R similarity index 100% rename from tests/testthat/test-1gen_data.R rename to tests/testthat/test-01-gen_data.R diff --git a/tests/testthat/test-02-iprobit_bin.R b/tests/testthat/test-02-iprobit_bin.R new file mode 100644 index 0000000..05d9db8 --- /dev/null +++ b/tests/testthat/test-02-iprobit_bin.R @@ -0,0 +1,146 @@ +context("Binary estimation methods") + +test_that("VB-EM (Closed-form)", { + + mod <- iprior::kernL(y ~ ., gen_mixture(n = 4, seed = 123), est.psi = FALSE) + mod1 <- iprobit(mod, control = list(silent = TRUE, maxit = 20, theta0 = 1:2, + alpha0 = 1)) + mod2 <- iprobit_bin(mod, 20, silent = TRUE, alpha0 = 1, theta0 = 1:2) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(-0.060995395, 0.975918045, 0.001124226), tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + +}) + +test_that("VB-EM with Metropolis sampler", { + + mod <- iprior::kernL(y ~ ., gen_circle(n = 4, seed = 123), kernel = "se", + est.lengthscale = TRUE, est.psi = FALSE) + mod1 <- iprobit(mod, control = list(silent = TRUE, maxit = 2, theta0 = 1:4, + alpha0 = 1, n.samp = 10, thin.samp = 1, + seed = 123)) + mod2 <- iprobit_bin_metr(mod, 2, silent = TRUE, alpha0 = 1, theta0 = 1:4, + n.samp = 10, thin.samp = 1, seed = 123) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(0.1395819, 1.0103551, 2.0785975, 18.2429103, 83.1534269), + tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + +}) + +context("Binary models") + +# Generate data and model fit -------------------------------------------------- +dat <- gen_mixture(n = 10) +dat.test <- gen_mixture(n = 5) + +# Regular +mod <- iprobit(dat$y, dat$X, control = list(maxit = 5, silent = TRUE)) +# Formula (with training samples) +modf <- iprobit(y ~ ., dat, one.lam = TRUE, train.samp = c(1, 5), + control = list(maxit = 5, silent = TRUE)) +# Metropolis +modmetr <- iprobit(y ~ ., dat, kernel = "fbm", est.hurst = TRUE, + control = list(maxit = 2, silent = TRUE)) + +# Predict without quantiles ---------------------------------------------------- +mod.predict <- predict(mod, newdata = list(dat.test$X)) +modf.predict <- predict(modf, newdata = dat.test) +modmetr.predict <- predict(modmetr, newdata = dat.test) + +# Predict with quantiles ------------------------------------------------------- +mod.predict.q <- predict(mod, list(dat.test$X), quantiles = TRUE, n.samp = 3) +modf.predict.q <- predict(modf, dat.test, quantiles = TRUE, n.samp = 3) +modmetr.predict.q <- predict(modmetr, dat.test, quantiles = TRUE, n.samp = 3) + +test_that("Fitting binary models", { + + expect_s3_class(mod, "iprobitMod") + expect_s3_class(mod, "iprobitMod_bin") + expect_s3_class(modf, "iprobitMod") + expect_s3_class(modf, "iprobitMod_bin") + +}) + +test_that("Print", { + + expect_that(print(mod), prints_text("Training error rate")) + expect_that(print(modf), prints_text("Training error rate")) + +}) + +test_that("Summary", { + + expect_s3_class(summary(mod), "iprobitMod_summary") + expect_s3_class(summary(modf), "iprobitMod_summary") + +}) + +test_that("Fitted", { + + expect_that(fitted(mod), is_a("iprobitPredict")) + expect_that(fitted(modf), is_a("iprobitPredict")) + +}) + +test_that("Fitted quantiles", { + + expect_that(fitted(mod, TRUE, n.samp = 3), is_a("iprobitPredict_quant")) + expect_that(fitted(modf, TRUE, n.samp = 3), is_a("iprobitPredict_quant")) + +}) + +test_that("Predict (without test error rate)", { + + expect_s3_class(mod.predict, "iprobitPredict") + expect_s3_class(modf.predict, "iprobitPredict") + expect_s3_class(modmetr.predict, "iprobitPredict") + + expect_s3_class(mod.predict.q, "iprobitPredict_quant") + expect_s3_class(modf.predict.q, "iprobitPredict_quant") + expect_s3_class(modmetr.predict.q, "iprobitPredict_quant") + + expect_that(print(mod.predict), prints_text("Test data not provided.")) + +}) + +test_that("Predict (with test error rate)", { + + mod.predict <- predict(mod, newdata = list(dat.test$X), y = dat.test$y) + + expect_that(print(mod.predict), prints_text("Test error")) + expect_that(print(modf.predict), prints_text("Test error")) + expect_that(print(modmetr.predict), prints_text("Test error")) + +}) + +# test_that("Convergence", { +# +# set.seed(123) +# dat <- gen_mixture(n = 10) +# mod <- iprobit(dat$y, dat$X, control = list(maxit = 500, silent = TRUE)) +# modf <- iprobit(y ~ ., dat, one.lam = TRUE, +# control = list(maxit = 500, silent = TRUE)) +# expect_equal(as.numeric(get_lambda(mod)), 0.11646, tolerance = 1e-3) +# expect_equal(as.numeric(get_lambda(mod)), 0.11646, tolerance = 1e-3) +# +# # > summary(mod) +# # Call: +# # iprobit(y = dat$y, X1 = dat$X, control = list(maxit = 500, silent = TRUE)) +# # +# # Classes: +# # RKHS used: +# # Linear (X1) +# # +# # Hyperparameters: +# # Mean S.D. 2.5% 97.5% +# # alpha -0.0090 0.3162 -0.6288 0.6108 +# # lambda 0.1165 0.0201 0.0770 0.1559 +# # --- +# # +# # Closed-form VB-EM algorithm. Iterations: 71/500 +# # Converged to within 1e-05 tolerance. Time taken: 0.08387208 secs +# # Variational lower bound: -4.897003 +# # Training error: 0%. Brier score: 0.004980669 +# +# }) diff --git a/tests/testthat/test-03-iprobit_mult.R b/tests/testthat/test-03-iprobit_mult.R new file mode 100644 index 0000000..40ca6d9 --- /dev/null +++ b/tests/testthat/test-03-iprobit_mult.R @@ -0,0 +1,222 @@ +context("Multinomial estimation methods") + +test_that("VB-EM (Closed-form)", { + + mod <- iprior::kernL(y ~ ., gen_mixture(n = 6, m = 3, seed = 123), + est.psi = FALSE) + theta0 <- matrix(rep(1, 6), ncol = 3) + alpha0 <- rep(1, 3) + + # Different hyperparameters in each class ------------------------------------ + mod1 <- iprobit(mod, control = list(silent = TRUE, maxit = 20, theta0 = theta0, + alpha0 = alpha0)) + mod2 <- iprobit_mult(mod, 20, silent = TRUE, alpha0 = alpha0, theta0 = theta0) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(6.502061e-01, 5.067932e-01, -6.667536e-05), tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 2]), + c(0.998459716, 0.001371398, 0.760741027), tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 3]), + c(1.3513342, 0.1209030, 0.6443124), tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + + # Only different RKHS parameters in each class ------------------------------- + mod1 <- iprobit(mod, common.intercept = TRUE, + control = list(silent = TRUE, maxit = 20, theta0 = theta0, + alpha0 = alpha0)) + mod2 <- iprobit_mult(mod, 20, silent = TRUE, alpha0 = alpha0, theta0 = theta0, + common.intercept = TRUE) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(1, 0.4390309548, -0.0001031719), tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 2]), + c(1, 0.0009601449, 0.7745982109), tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 3]), + c(1, 0.1150476, 0.6924896), tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + + # Only different intercepts in each class ------------------------------------ + mod1 <- iprobit(mod, common.RKHS.scale = TRUE, + control = list(silent = TRUE, maxit = 20, theta0 = theta0, + alpha0 = alpha0)) + mod2 <- iprobit_mult(mod, 20, silent = TRUE, alpha0 = alpha0, theta0 = theta0, + common.RKHS.scale = TRUE) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(0.61014, -0.005820041, 0.048168828), tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 2]), + c(0.99696, -0.005820041, 0.048168828), tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 3]), + c(1.39291, -0.005820041, 0.048168828), tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + + # Same hyperparameters in each class ----------------------------------------- + mod1 <- iprobit(mod, common.intercept = TRUE, common.RKHS.scale = TRUE, + control = list(silent = TRUE, maxit = 20, theta0 = theta0, + alpha0 = alpha0)) + mod2 <- iprobit_mult(mod, 20, silent = TRUE, alpha0 = alpha0, theta0 = theta0, + common.intercept = TRUE, common.RKHS.scale = TRUE) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(1, 0 , 0), tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + +}) + +test_that("VB-EM with Metropolis sampler", { + + mod <- iprior::kernL(y ~ ., gen_circle(n = 6, m = 3, seed = 123), kernel = "fbm", + est.hurst = TRUE, est.psi = FALSE) + theta0 <- matrix(c(1, 1, 0, 0), ncol = 3, nrow = 4) + alpha0 <- rep(1, 3) + + # Different hyperparameters in each class ------------------------------------ + mod1 <- iprobit(mod, control = list(silent = TRUE, maxit = 20, theta0 = theta0, + alpha0 = alpha0, n.samp = 5, + thin.samp = 1, seed = 123)) + mod2 <- iprobit_mult_metr(mod, 20, silent = TRUE, alpha0 = alpha0, + theta0 = theta0, n.samp = 5, thin.samp = 1, + seed = 123) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(0.9985691, 1.0150100, 1.1014431, 0.5292299, 0.6026026), + tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 2]), + c(1.0055504, 0.6488293, 0.9076343, 0.5466233, 0.4670734), + tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 3]), + c(0.9958806, 0.9745103, 0.9592181, 0.4971903, 0.4359529), + tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + + # Only different RKHS parameters in each class ------------------------------- + mod1 <- iprobit(mod, common.intercept = TRUE, + control = list(silent = TRUE, maxit = 20, theta0 = theta0, + alpha0 = alpha0, n.samp = 5, thin.samp = 1, + seed = 123)) + mod2 <- iprobit_mult_metr(mod, 20, silent = TRUE, alpha0 = alpha0, + theta0 = theta0, n.samp = 5, thin.samp = 1, + seed = 123, common.intercept = TRUE) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(1, 1.0150100, 1.1014431, 0.5292299, 0.6026026), tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 2]), + c(1, 0.6488293, 0.9076343, 0.5466233, 0.4670734), tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 3]), + c(1, 0.9745103, 0.9592181, 0.4971903, 0.4359529), tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + + # Only different intercepts in each class ------------------------------------ + mod1 <- iprobit(mod, common.RKHS.scale = TRUE, + control = list(silent = TRUE, maxit = 20, theta0 = theta0, + alpha0 = alpha0, n.samp = 5, thin.samp = 1, + seed = 123)) + mod2 <- iprobit_mult_metr(mod, 20, silent = TRUE, alpha0 = alpha0, + theta0 = theta0, n.samp = 5, thin.samp = 1, + seed = 123, common.RKHS.scale = TRUE) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(0.9984867, 1.0390585, 1.0274495, 0.4885230, 0.5595608), + tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 2]), + c(1.0051488, 1.0390585, 1.0274495, 0.4885230, 0.5595608), + tol = 1e-5) + expect_equal(as.numeric(coef(mod1)[, 3]), + c(0.9963646, 1.0390585, 1.0274495, 0.4885230, 0.5595608), + tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + + # Same hyperparameters in each class ----------------------------------------- + mod1 <- iprobit(mod, common.intercept = TRUE, common.RKHS.scale = TRUE, + control = list(silent = TRUE, maxit = 20, theta0 = theta0, + alpha0 = alpha0, n.samp = 5, thin.samp = 1, + seed = 123)) + mod2 <- iprobit_mult_metr(mod, 20, silent = TRUE, alpha0 = alpha0, + theta0 = theta0, n.samp = 5, thin.samp = 1, + seed = 123, common.RKHS.scale = TRUE, + common.intercept = TRUE) + expect_equal(as.numeric(coef(mod1)[, 1]), + c(1, 1.0390585, 1.0274495, 0.4885230, 0.5595608), tol = 1e-5) + expect_equal(mod1$param.full, mod2$param.full, tol = 1e-5) + +}) + +context("Multinomial models") + +# Generate data and model fit -------------------------------------------------- +set.seed(123) +dat <- gen_circle(n = 15, m = 3) +dat.test <- gen_circle(n = 6, m = 3) + +# Regular +mod <- iprobit(dat$y, dat$X, control = list(maxit = 5, silent = TRUE)) +# Formula (with training samples) +modf <- iprobit(y ~ ., dat, one.lam = TRUE, train.samp = c(1, 5), + control = list(maxit = 5, silent = TRUE)) +# Metropolis +modmetr <- iprobit(y ~ ., dat, kernel = "fbm", est.hurst = TRUE, + control = list(maxit = 2, silent = TRUE)) + +# Predict without quantiles ---------------------------------------------------- +mod.predict <- predict(mod, newdata = list(dat.test$X)) +modf.predict <- predict(modf, newdata = dat.test) +modmetr.predict <- predict(modmetr, newdata = dat.test) + +# Predict with quantiles ------------------------------------------------------- +mod.predict.q <- predict(mod, list(dat.test$X), quantiles = TRUE, n.samp = 3) +modf.predict.q <- predict(modf, dat.test, quantiles = TRUE, n.samp = 3) +modmetr.predict.q <- predict(modmetr, dat.test, quantiles = TRUE, n.samp = 3) + +test_that("Fitting binary models", { + + expect_s3_class(mod, "iprobitMod") + expect_s3_class(mod, "iprobitMod_mult") + expect_s3_class(modf, "iprobitMod") + expect_s3_class(modf, "iprobitMod_mult") + +}) + +test_that("Print", { + + expect_that(print(mod), prints_text("Training error rate")) + expect_that(print(modf), prints_text("Training error rate")) + +}) + +test_that("Summary", { + + expect_s3_class(summary(mod), "iprobitMod_summary") + expect_s3_class(summary(modf), "iprobitMod_summary") + +}) + +test_that("Fitted", { + + expect_that(fitted(mod), is_a("iprobitPredict")) + expect_that(fitted(modf), is_a("iprobitPredict")) + +}) + +test_that("Fitted quantiles", { + + expect_that(fitted(mod, TRUE, n.samp = 3), is_a("iprobitPredict_quant")) + expect_that(fitted(modf, TRUE, n.samp = 3), is_a("iprobitPredict_quant")) + +}) + +test_that("Predict (without test error rate)", { + + expect_s3_class(mod.predict, "iprobitPredict") + expect_s3_class(modf.predict, "iprobitPredict") + expect_s3_class(modmetr.predict, "iprobitPredict") + + expect_s3_class(mod.predict.q, "iprobitPredict_quant") + expect_s3_class(modf.predict.q, "iprobitPredict_quant") + expect_s3_class(modmetr.predict.q, "iprobitPredict_quant") + + expect_that(print(mod.predict), prints_text("Test data not provided.")) + +}) + +test_that("Predict (with test error rate)", { + + mod.predict <- predict(mod, newdata = list(dat.test$X), y = dat.test$y) + + expect_that(print(mod.predict), prints_text("Test error")) + expect_that(print(modf.predict), prints_text("Test error")) + expect_that(print(modmetr.predict), prints_text("Test error")) + +}) diff --git a/tests/testthat/test-iprobit_helpers.R b/tests/testthat/test-04-iprobit_helpers.R similarity index 100% rename from tests/testthat/test-iprobit_helpers.R rename to tests/testthat/test-04-iprobit_helpers.R diff --git a/tests/testthat/test-05-plots.R b/tests/testthat/test-05-plots.R new file mode 100644 index 0000000..f1c58f7 --- /dev/null +++ b/tests/testthat/test-05-plots.R @@ -0,0 +1,164 @@ +context("Plots") + +# Binary models ---------------------------------------------------------------- + +set.seed(123) +n <- 50 +dat <- gen_circle(n) +mod <- iprobit(y ~ X1 + X2, dat, one.lam = TRUE, kernel = "fbm", + train.samp = sort(sample(seq_len(n), size = 40)), + control = list(silent = TRUE, maxit = 10)) + +test_that("iplot_fitted()", { + + expect_silent(p <- iplot_fitted(mod)) + +}) + +test_that("iplot_dec_bound()", { + + expect_silent(p <- iplot_dec_bound(mod, grid.len = 5)) + +}) + +test_that("iplot_predict()", { + + expect_silent(p <- iplot_predict(mod, grid.len = 5)) + expect_silent(p <- iplot_predict(mod, grid.len = 5, dec.bound = FALSE)) + expect_silent(p <- iplot_predict(mod, grid.len = 5, plot.test = FALSE)) + expect_silent(p <- iplot_predict(mod, grid.len = 5, dec.bound = FALSE, + plot.test = FALSE)) + +}) + +test_that("iplot_lb()", { + + expect_silent(p <- iplot_lb(mod)) + +}) + +test_that("iplot_error()", { + + expect_silent(p <- iplot_error(mod, 3)) + expect_silent(p <- iplot_error(mod, plot.test = FALSE)) + +}) + +test_that("iplot_lb_and_error()", { + + expect_silent(p <- iplot_lb_and_error(mod, niter.plot = 3, plot.test = FALSE)) + +}) + +# Binary models ---------------------------------------------------------------- + +set.seed(123) +n <- 100 +dat <- gen_mixture(n, m = 3) +mod <- iprobit(y ~ ., dat, kernel = "fbm", train.samp = sort(sample(seq_len(n), + size = 90)), + control = list(silent = TRUE, maxit = 4)) + +test_that("iplot_fitted()", { + + expect_silent(p <- iplot_fitted(mod)) + +}) + +test_that("iplot_dec_bound()", { + + expect_silent(p <- iplot_dec_bound(mod, grid.len = 5)) + +}) + +test_that("iplot_predict()", { + + expect_silent(p <- iplot_predict(mod, grid.len = 5)) + expect_silent(p <- iplot_predict(mod, grid.len = 5, dec.bound = FALSE)) + expect_silent(p <- iplot_predict(mod, grid.len = 5, plot.test = FALSE)) + expect_silent(p <- iplot_predict(mod, grid.len = 5, dec.bound = FALSE, + plot.test = FALSE)) + +}) + +test_that("iplot_lb()", { + + expect_silent(p <- iplot_lb(mod)) + +}) + +test_that("iplot_error()", { + + expect_silent(p <- iplot_error(mod, 3)) + expect_silent(p <- iplot_error(mod, plot.test = FALSE)) + +}) + +test_that("iplot_lb_and_error()", { + + expect_silent(p <- iplot_lb_and_error(mod, niter.plot = 3, plot.test = FALSE)) + +}) + +# context("Methods for iprobitMod objects") + +# Update/refit ----------------------------------------------------------------- +# +# test_that("Update from formula", { +# +# dat <- gen_mixture(10) +# mod.original <- mod <- iprobit(y ~ ., dat, control = list(maxit = 1), +# silent = TRUE) +# mod <- iprobit(mod, maxit = 5) +# +# expect_s3_class(mod, "iprobitMod") +# expect_equal(mod$niter, 6) +# expect_equal(length(mod$lower.bound), 6) +# expect_equal(length(mod$error), 6) +# expect_equal(length(mod$brier), 6) +# expect_equal(mod$formula, mod.original$formula) +# +# }) +# +# test_that("Update from non-formula", { +# +# dat <- gen_mixture(10) +# mod.original <- mod <- iprobit(dat$y, dat$X, control = list(maxit = 1), +# silent = TRUE) +# mod <- iprobit(mod, maxit = 5) +# +# expect_s3_class(mod, "iprobitMod") +# expect_equal(mod$niter, 6) +# expect_equal(length(mod$lower.bound), 6) +# expect_equal(length(mod$error), 6) +# expect_equal(length(mod$brier), 6) +# expect_equal(mod$formula, mod.original$formula) +# +# }) +# +# test_that("Update options", { +# +# dat <- gen_mixture(10) +# mod.original <- mod <- iprobit(dat$y, dat$X, control = list(maxit = 1), +# silent = TRUE) +# +# expect_message(mod <- iprobit(mod)) +# expect_output(mod <- iprobit(mod, stop.crit = 1e-1, maxit = 5, silent = FALSE)) +# +# }) +# +# test_that("The function update()", { +# +# dat <- gen_mixture(10) +# mod.original <- mod <- iprobit(dat$y, dat$X, control = list(maxit = 1), +# silent = TRUE) +# update(mod, maxit = 5) +# +# expect_s3_class(mod, "iprobitMod") +# expect_equal(mod$niter, 6) +# expect_equal(length(mod$lower.bound), 6) +# expect_equal(length(mod$error), 6) +# expect_equal(length(mod$brier), 6) +# expect_equal(mod$formula, mod.original$formula) +# +# }) diff --git a/tests/testthat/test-7methods.R b/tests/testthat/test-06-accessors.R similarity index 95% rename from tests/testthat/test-7methods.R rename to tests/testthat/test-06-accessors.R index 6dcf980..a5951db 100644 --- a/tests/testthat/test-7methods.R +++ b/tests/testthat/test-06-accessors.R @@ -1,6 +1,6 @@ # context("Methods for iprobitMod objects") -# -# # Update/refit ----------------------------------------------------------------- + +# Update/refit ----------------------------------------------------------------- # # test_that("Update from formula", { # diff --git a/tests/testthat/test-6utilities.R b/tests/testthat/test-07-utilities.R similarity index 100% rename from tests/testthat/test-6utilities.R rename to tests/testthat/test-07-utilities.R diff --git a/tests/testthat/test-4errors.R b/tests/testthat/test-08-errors.R similarity index 100% rename from tests/testthat/test-4errors.R rename to tests/testthat/test-08-errors.R diff --git a/tests/testthat/test-8parallel.R b/tests/testthat/test-09-parallel.R similarity index 100% rename from tests/testthat/test-8parallel.R rename to tests/testthat/test-09-parallel.R diff --git a/tests/testthat/test-2iprobit_bin.R b/tests/testthat/test-2iprobit_bin.R deleted file mode 100644 index e162211..0000000 --- a/tests/testthat/test-2iprobit_bin.R +++ /dev/null @@ -1,105 +0,0 @@ -context("Binary models") - -dat <- gen_mixture(n = 10) -dat.test <- gen_mixture(n = 5) - -mod <- iprobit(dat$y, dat$X, control = list(maxit = 5, silent = TRUE)) -modf <- iprobit(y ~ ., dat, one.lam = TRUE, control = list(maxit = 5, - silent = TRUE)) - -dat.test <- gen_mixture(n = 5) -mod.predict <- predict(mod, newdata = list(dat.test$X)) -modf.predict <- predict(modf, newdata = dat.test) - -mod.predict.q <- predict(mod, newdata = list(dat.test$X), quantiles = TRUE, - n.samp = 3) -modf.predict.q <- predict(modf, newdata = dat.test, quantiles = TRUE, - n.samp = 3) - - -test_that("Fitting binary models", { - - expect_s3_class(mod, "iprobitMod") - expect_s3_class(mod, "iprobitMod_bin") - expect_s3_class(modf, "iprobitMod") - expect_s3_class(modf, "iprobitMod_bin") - -}) - -test_that("Print", { - - expect_that(print(mod), prints_text("Training error rate")) - expect_that(print(modf), prints_text("Training error rate")) - -}) - -test_that("Summary", { - - expect_s3_class(summary(mod), "iprobitMod_summary") - expect_s3_class(summary(modf), "iprobitMod_summary") - -}) - -test_that("Fitted", { - - expect_that(fitted(mod), is_a("iprobitPredict")) - expect_that(fitted(modf), is_a("iprobitPredict")) - -}) - -test_that("Fitted quantiles", { - - expect_that(fitted(mod, TRUE, n.samp = 3), is_a("iprobitPredict_quant")) - expect_that(fitted(modf, TRUE, n.samp = 3), is_a("iprobitPredict_quant")) - -}) - -test_that("Predict (without test error rate)", { - - expect_s3_class(mod.predict, "iprobitPredict") - expect_s3_class(modf.predict, "iprobitPredict") - expect_s3_class(mod.predict.q, "iprobitPredict_quant") - expect_s3_class(modf.predict.q, "iprobitPredict_quant") - expect_that(print(mod.predict), prints_text("Test data not provided.")) - -}) - -test_that("Predict (with test error rate)", { - - mod.predict <- predict(mod, newdata = list(dat.test$X), y = dat.test$y) - - expect_that(print(mod.predict), prints_text("Test error")) - expect_that(print(modf.predict), prints_text("Test error")) - -}) - -test_that("Convergence", { - - set.seed(123) - dat <- gen_mixture(n = 10) - mod <- iprobit(dat$y, dat$X, control = list(maxit = 500, silent = TRUE)) - modf <- iprobit(y ~ ., dat, one.lam = TRUE, - control = list(maxit = 500, silent = TRUE)) - expect_equal(as.numeric(get_lambda(mod)), 0.11646, tolerance = 1e-3) - expect_equal(as.numeric(get_lambda(mod)), 0.11646, tolerance = 1e-3) - - # > summary(mod) - # Call: - # iprobit(y = dat$y, X1 = dat$X, control = list(maxit = 500, silent = TRUE)) - # - # Classes: - # RKHS used: - # Linear (X1) - # - # Hyperparameters: - # Mean S.D. 2.5% 97.5% - # alpha -0.0090 0.3162 -0.6288 0.6108 - # lambda 0.1165 0.0201 0.0770 0.1559 - # --- - # - # Closed-form VB-EM algorithm. Iterations: 71/500 - # Converged to within 1e-05 tolerance. Time taken: 0.08387208 secs - # Variational lower bound: -4.897003 - # Training error: 0%. Brier score: 0.004980669 - -}) diff --git a/tests/testthat/test-3iprobit_mult.R b/tests/testthat/test-3iprobit_mult.R deleted file mode 100644 index 27e4e7c..0000000 --- a/tests/testthat/test-3iprobit_mult.R +++ /dev/null @@ -1,142 +0,0 @@ -# context("Multinomial models") -# -# test_that("Fitting multinomial models (IIA)", { -# -# m <- 4 -# dat <- gen_mixture(n = 10, m = m) -# mod1 <- iprobit(dat$y, dat$X, silent = TRUE, control = list(maxit = 5)) -# modf <- iprobit(y ~ ., dat, silent = TRUE, one.lam = TRUE, -# control = list(maxit = 5)) -# expect_s3_class(mod1, "iprobitMod") -# expect_s3_class(mod1, "iprobitMod_mult") -# expect_s3_class(modf, "iprobitMod") -# expect_s3_class(modf, "iprobitMod_mult") -# -# # Common intercept -# mod2 <- iprobit(dat$y, dat$X, silent = TRUE, -# control = list(maxit = 5, common.intercept = TRUE)) -# expect_true(all.same(get_alpha(mod2))) -# -# # Common RKHS scale parameter -# mod3 <- iprobit(dat$y, dat$X, silent = TRUE, -# control = list(maxit = 5, common.RKHS.scale = TRUE)) -# expect_true(all.same(get_lambda(mod3))) -# -# # Common intercept and RKHS scale parameter -# mod4 <- iprobit(dat$y, dat$X, silent = TRUE, -# control = list(maxit = 5, -# common.intercept = TRUE, -# common.RKHS.scale = TRUE)) -# expect_true(all.same(get_alpha(mod4))) -# expect_true(all.same(get_lambda(mod4))) -# -# }) -# -# test_that("Print (IIA)", { -# -# m <- 4 -# dat <- gen_mixture(n = 10, m = m) -# mod <- iprobit(dat$y, dat$X, silent = TRUE, control = list(maxit = 5)) -# modf <- iprobit(y ~ ., dat, silent = TRUE, one.lam = TRUE, control = list(maxit = 5)) -# expect_that(print(mod), prints_text("Training error rate")) -# expect_that(print(modf), prints_text("Training error rate")) -# -# }) -# -# test_that("Summary", { -# -# m <- 4 -# dat <- gen_mixture(n = 10, m = m) -# mod <- iprobit(dat$y, dat$X, silent = TRUE, control = list(maxit = 5)) -# modf <- iprobit(y ~ ., dat, silent = TRUE, one.lam = TRUE, control = list(maxit = 5)) -# mod.summary <- summary(mod) -# modf.summary <- summary(modf) -# expect_s3_class(mod.summary, "iprobitSummary") -# expect_s3_class(modf.summary, "iprobitSummary") -# -# }) -# -# test_that("Fitted", { -# -# m <- 4 -# dat <- gen_mixture(n = 10, m = m) -# mod <- iprobit(dat$y, dat$X, silent = TRUE, control = list(maxit = 5)) -# modf <- iprobit(y ~ ., dat, silent = TRUE, one.lam = TRUE, control = list(maxit = 5)) -# expect_that(fitted(mod), is_a("iprobit_predict")) -# expect_that(fitted(modf), is_a("iprobit_predict")) -# -# }) -# -# test_that("Fitted quantiles", { -# -# m <- 4 -# dat <- gen_mixture(n = 10, m = m) -# mod <- iprobit(dat$y, dat$X, silent = TRUE, control = list(maxit = 5)) -# modf <- iprobit(y ~ ., dat, silent = TRUE, one.lam = TRUE, control = list(maxit = 5)) -# expect_that(fitted(mod, TRUE, n.samp = 3), is_a("iprobit_predict_quant")) -# expect_that(fitted(modf, TRUE, n.samp = 3), is_a("iprobit_predict_quant")) -# -# }) -# -# test_that("Predict (without test error rate)", { -# -# m <- 4 -# dat <- gen_mixture(n = 10, m = m) -# dat.test <- gen_mixture(n = 5, m = m) -# mod <- iprobit(dat$y, dat$X, silent = TRUE, control = list(maxit = 5)) -# modf <- iprobit(y ~ ., dat, silent = TRUE, one.lam = TRUE, -# control = list(maxit = 5)) -# -# mod.predict <- predict(mod, newdata = list(dat.test$X)) -# modf.predict <- predict(modf, newdata = dat.test) -# -# expect_s3_class(mod.predict, "iprobit_predict") -# expect_s3_class(modf.predict, "iprobit_predict") -# expect_that(print(mod.predict), prints_text("Test data not provided.")) -# -# }) -# -# test_that("Predict (with test error rate)", { -# -# m <- 4 -# dat <- gen_mixture(n = 10, m = m) -# dat.test <- gen_mixture(n = 5, m = m) -# mod <- iprobit(dat$y, dat$X, silent = TRUE, control = list(maxit = 5)) -# modf <- iprobit(y ~ ., dat, silent = TRUE, one.lam = TRUE, -# control = list(maxit = 5)) -# -# mod.predict <- predict(mod, newdata = list(dat.test$X), y = dat.test$y) -# modf.predict <- predict(modf, newdata = dat.test) -# -# expect_that(print(mod.predict), prints_text("Test error")) -# expect_that(print(modf.predict), prints_text("Test error")) -# -# }) -# -# test_that("Convergence", { -# -# set.seed(123) -# m <- 3 -# dat <- gen_mixture(n = 10, m = m) -# mod <- iprobit(dat$y, dat$X, -# control = list(maxit = 500, silent = TRUE, -# alpha0 = rep(1, m), -# lambda0 = rep(1, m))) -# modf <- iprobit(y ~ ., dat, one.lam = TRUE, -# control = list(maxit = 500, silent = TRUE, -# alpha0 = rep(1, m), -# lambda0 = rep(1, m))) -# expect_equal(get_lambda(mod)[2], 0.1743832, tolerance = 1e-3) -# expect_equal(get_lambda(modf)[2], 0.1743832, tolerance = 1e-3) -# -# # Single lambda -# # > modf -# # Training error rate: 20.00 % -# # Lower bound value: -12.87285 -# # -# # Class = 1 Class = 2 Class = 3 -# # alpha 0.60748 1.76979 0.62274 -# # lambda 0.00000 0.17438 0.00000 -# -# }) -#