R/06_methods.R

Defines functions plot.bcnsm print.summary.bcnsm summary.bcnsm print.bcnsm residuals.bcnsm AIC.bcnsm logLik.bcnsm vcov.bcnsm

Documented in AIC.bcnsm logLik.bcnsm plot.bcnsm print.bcnsm print.summary.bcnsm residuals.bcnsm summary.bcnsm vcov.bcnsm

#' @name bcnsm-methods
#' @title Methods for \code{"bcnsm"} objects
#' @param x,object an object of class \code{bcnsm}.
#' @param digits number of digits in print methods.
#' @param k numeric, the penalty per parameter to be used; the default
#'     \code{k = 2} is the classical AIC.
#' @param ... further arguments passed to or from other methods.
#'
#' @author Rodrigo M. R. de Medeiros <\email{rodrigo.matheus@live.com}>
NULL

#  Variance-covariance matrix
#' @rdname bcnsm-methods
#' @param par character; specifies which submatrix of the asymptotic covariance matrix of the
#'     maximum likelihood estimators should be returned. The options are \code{"all"} (default),
#'     \code{"mu"}, \code{"sigma"}, \code{"lambda"}, \code{"nu"}, and \code{"gamma"}.
#'
#' @author Rodrigo M. R. de Medeiros <\email{rodrigo.matheus@live.com}>
#'
#' @export
vcov.bcnsm <- function(object, par = c("all", "mu", "sigma", "lambda", "nu", "gamma"), ...) {

  par <- match.arg(par, c("all", "mu", "sigma", "lambda", "nu", "gamma"))
  covm <- object$vcov

  margins <- object$margins
  lambda_id <- apply(matrix(margins, ncol = 1), 1, function(x) !grepl("Log-", as.bcs(x)$name))
  nu_id <- apply(matrix(margins, ncol = 1), 1, function(x) as.bcs(x)$extrap)

  d <- object$d

  names_mu <- paste0("mu", 1:d)
  names_sigma <- paste0("sigma", 1:d)
  names_lambda <- if (any(lambda_id)) paste0("lambda", (1:d)[lambda_id]) else NULL
  names_nu <- if (any(nu_id)) paste0("nu", (1:d)[nu_id]) else NULL

  if (object$association == "uniform") {

    names_gamma <- "gamma"

  } else {

    names_gamma <- vector()
    id <- which(upper.tri(diag(d)), arr.ind = TRUE)[order(which(upper.tri(diag(d)), arr.ind = TRUE)[, 1]), ]
    for (i in 1:nrow(id)) {
      names_gamma[i] <- paste0("gamma", id[i, 1], id[i, 2])
    }

  }

  colnames(covm) <- rownames(covm) <- c(names_mu, names_sigma, names_lambda, names_nu, names_gamma)

  par_id <- object$optim_params$par_id

  switch(par,
         "all" = covm,
         "mu" = covm[par_id$mu, par_id$mu],
         "sigma" = covm[par_id$sigma, par_id$sigma],
         "lambda" = covm[par_id$lambda, par_id$lambda],
         "nu" = covm[par_id$nu, par_id$nu],
         "gamma" = covm[par_id$gamma, par_id$gamma]
  )
}

# Log-likelihood
#' @rdname bcnsm-methods
#' @export
logLik.bcnsm <- function(object, ...) {

  structure(object$logLik,
            df = length(object$optim_params$par) + as.numeric(!is.null(object$delta)),
            class = "logLik")

}

# AIC
#' @export
#' @rdname bcnsm-methods
AIC.bcnsm <- function(object, ..., k = 2) {

  npars <- length(object$optim_params$par) + as.numeric(!is.null(object$delta))
  AIC <- -2 * object$logLik + k * npars

  class(AIC) <- "AIC"
  AIC

}

# Residuals
#' @name residuals.bcnsm
#' @title Extract Model Residuals for a BerG Regression
#'
#' @param object an \code{'bcnsm'} object.
#' @param type character; specifies which residual should be extracted. The available arguments are
#'     \code{"mahalanobis"} (default), and \code{"epsilon"}.
#' @param ... further arguments passed to or from other methods.
#'
#' @author Rodrigo M. R. de Medeiros <\email{rodrigo.matheus@live.com}>
#'
#' @export
#'
residuals.bcnsm <- function(object, type = c("mahalanobis", "epsilon"), ...) {

  type <- match.arg(type, c("mahalanobis", "epsilon"))

  y <- object$y

  n <- object$nobs
  d <- object$d
  epsilon <- matrix(NA, n, d)
  margins <- object$margins
  copula <- object$copula
  delta <- object$delta

  copula_inf <- make_copula(copula, delta)
  qPSI <- copula_inf$qPSI

  mu <- object$mu
  sigma <- object$sigma
  lambda <- object$lambda
  nu <- object$nu

  for (j in 1:d) {

    epsilon[, j] <- qPSI(get(paste0("p", margins[j]))(q = y[, j],
                                                      mu = mu[j],
                                                      sigma = sigma[j],
                                                      lambda = lambda[j],
                                                      nu = nu[j]))

  }

  epsilon
  colnames(epsilon) <- colnames(y)

  Gamma <- get(object$association)(d)$Gamma(object$gamma)

  # Squared Mahalanobis distance
  mahalanobis <- Rfast::mahala(epsilon, rep(0L, d), Gamma)

  # Out
  res <- switch(type,
                "mahalanobis" = as.numeric(mahalanobis),
                "epsilon" = epsilon
  )

  res
}

# Print
#' @rdname bcnsm-methods
#' @export
print.bcnsm <- function(x, digits = 2L, ...) {

  y <- x$y

  n <- x$nobs
  d <- x$d
  margins <- x$margins
  association <- x$association
  gamma <- x$gamma
  copula <- x$copula
  delta <- x$delta

  # Parameters
  mu <- x$mu
  sigma <- x$sigma
  lambda <- x$lambda
  nu <- x$nu

  lambda_id <- apply(matrix(margins, ncol = 1), 1, function(x) !grepl("Log-", as.bcs(x)$name))
  nu_id <- apply(matrix(margins, ncol = 1), 1, function(x) as.bcs(x)$extrap)

  # Names
  y_names <- if (is.null(colnames(y))) paste0("y", 1:d) else colnames(y)
  Margins <- toupper(margins)
  copula_name <- paste(toupper(substr(copula, 1, 1)), substr(copula, 2, nchar(copula)), sep = "")

  if (requireNamespace("crayon", quietly = TRUE)) {

    cat(crayon::cyan(
      "\nMultivariate Box-Cox Distribution with",
      if (is.null(delta)) copula_name else paste0(copula_name, "(", round(delta, 2), ")"),
      "Copula\n\n"
    ))

    cat(crayon::cyan("Call:\n\n"))
    print(x$call)
    if (x$optim_params$convergence > 0) {

      cat("\nmodel did not converge\n")

    } else {

      cat(crayon::cyan("\nMarginal fit:\n\n"))

      comma_lambda <- comma_nu <- rep(",", d)
      lambda <- round(lambda, digits)
      nu <- round(nu, digits)

      lambda[!lambda_id] <- comma_lambda[!lambda_id] <- ""
      nu[!nu_id] <- comma_nu[!nu_id] <- ""

      for (j in 1:d) {

        cat(y_names[j], " ~ ", Margins[j], "(", round(mu[j], digits), ",",
            round(sigma[j], digits), comma_lambda[j], lambda[j], comma_nu[j], nu[j], ")\n",
            sep = ""
        )

      }

      if (length(gamma) > 0) {

        cat(crayon::cyan("\n", sub("(.)", "\\U\\1", x$association, perl = TRUE),
                         " association matrix:\n\n", sep = ""))

        Gamma <- get(x$association)(d)$Gamma(round(x$gamma, digits))
        Gamma[upper.tri(Gamma)] <- diag(Gamma) <- NA
        colnames(Gamma) <- rownames(Gamma) <- y_names
        print(Gamma, na.print = ".")

      }

      cat(
        "\n---",
        crayon::cyan("\nlogLik:"), x$logLik, "|",
        crayon::cyan("AIC:"), stats::AIC(x), "|",
        crayon::cyan("BIC:"), stats::AIC(x, k = log(n)), "\n"
      )
    }

  } else {

    cat(
      "\nMultivariate Box-Cox Distribution with",
      if (is.null(delta)) copula_name else paste0(copula_name, "(", round(delta, 2), ")"),
      "Copula\n\n"
    )

    cat("Call:\n\n")
    print(x$call)
    if (x$optim_params$convergence > 0) {

      cat("\nmodel did not converge\n")

    } else {

      cat("\nMarginal fit:\n\n")

      comma_lambda <- comma_nu <- rep(",", d)
      lambda <- round(lambda, digits)
      nu <- round(nu, digits)

      lambda[!lambda_id] <- comma_lambda[!lambda_id] <- ""
      nu[!nu_id] <- comma_nu[!nu_id] <- ""

      for (j in 1:d) {

        cat(y_names[j], ": ", Margins[j], "(", round(mu[j], digits), ",",
            round(sigma[j], digits), comma_lambda[j], lambda[j], comma_nu[j], nu[j], ")\n",
            sep = ""
        )

      }


      if (length(gamma) > 0) {

        cat("\n", sub("(.)", "\\U\\1", x$association, perl = TRUE), " association matrix:\n\n", sep = "")

        Gamma <- get(x$association)(d)$Gamma(round(x$gamma, digits))
        Gamma[upper.tri(Gamma)] <- diag(Gamma) <- NA
        colnames(Gamma) <- rownames(Gamma) <- y_names
        print(Gamma, na.print = ".")
      }


      cat(
        "\n--------------------------------------------------",
        "\nlogLik:", x$logLik, "|",
        "AIC:", stats::AIC(x), "|",
        "BIC:", stats::AIC(x, k = log(n)), "\n"
      )
    }
  }



  invisible(x)
}

# Summary
#' @rdname bcnsm-methods
#' @export
summary.bcnsm <- function(object, digits = 4L, ...) {

  y <- object$y

  n <- object$nobs
  d <- object$d

  margins <- object$margins
  association <- object$association
  gamma <- object$gamma
  copula <- object$copula
  delta <- object$delta

  # Parameters
  mu <- object$mu
  sigma <- object$sigma
  lambda <- object$lambda
  nu <- object$nu

  lambda_id <- apply(matrix(margins, ncol = 1), 1, function(x) !grepl("Log-", as.bcs(x)$name))
  nu_id <- apply(matrix(margins, ncol = 1), 1, function(x) as.bcs(x)$extrap)
  par_id <- object$optim_params$par_id

  # Names
  y_names <- if (is.null(colnames(y))) paste0("y", 1:d) else colnames(y)
  Margins <- toupper(margins)
  copula_name <- paste(toupper(substr(copula, 1, 1)), substr(copula, 2, nchar(copula)), sep = "")

  # Summary for mu
  se_mu <- sqrt(diag(object$vcov)[par_id$mu])
  TAB_mu <- round(cbind(Estimate = c(mu), `Std. Error` = se_mu), digits)
  rownames(TAB_mu) <- colnames(y)

  # Summary for sigma
  se_sigma <- sqrt(diag(object$vcov)[par_id$sigma])
  TAB_sigma <- round(cbind(Estimate = c(sigma), `Std. Error` = se_sigma), digits)
  rownames(TAB_sigma) <- colnames(y)

  # Summary for lambda
  TAB_lambda <- NULL
  if (any(lambda_id)) {
    se_lambda <- sqrt(diag(object$vcov)[par_id$lambda])
    TAB_lambda <- round(cbind(Estimate = c(lambda[lambda_id]), `Std. Error` = se_lambda), digits)
    rownames(TAB_lambda) <- colnames(y)[lambda_id]
  }

  # Summary for nu
  TAB_nu <- NULL
  if (any(nu_id)) {
    se_nu <- sqrt(diag(object$vcov)[object$optim_params$par_id$nu])
    TAB_nu <- round(cbind(Estimate = object$nu[nu_id], `Std. Error` = se_nu), digits)
    rownames(TAB_nu) <- colnames(y)[nu_id]
  }

  gamma_id <- length(gamma) > 0

  # Summary for gamma
  TAB_gamma <- NULL
  if (gamma_id) {

    se_gamma <- sqrt(diag(object$vcov)[object$optim_params$par_id$gamma])
    TAB_gamma <- round(cbind(
      Estimate = gamma,
      `Std. Error` = se_gamma
      #`z value` = gamma / se_gamma,
      #`Pr(>|z|)` = 2 * stats::pnorm(abs(gamma / se_gamma), lower.tail = FALSE)
    ), digits)


    if (object$association == "uniform") {

      rownames(TAB_gamma) <- "gamma"

    } else {

      names_gamma <- vector()
      id <- which(upper.tri(diag(d)), arr.ind = TRUE)[order(which(upper.tri(diag(d)), arr.ind = TRUE)[, 1]), ]
      for (i in 1:nrow(id)) {
        names_gamma[i] <- paste0("gamma", id[i, 1], id[i, 2])
      }

      rownames(TAB_gamma) <- names_gamma

    }

  }

  npar <- length(object$optim_params$par)
  out <- list(
    mu = TAB_mu,
    sigma = TAB_sigma,
    lambda = TAB_lambda,
    nu = TAB_nu,
    gamma = TAB_gamma,
    margins = margins,
    association = association,
    copula = copula,
    y = y,
    d = d,
    delta = delta,
    logLik = object$logLik,
    AIC = stats::AIC(object),
    BIC = stats::AIC(object, k = log(n)),
    call = object$call
  )

  class(out) <- "summary.bcnsm"
  out
}

# Print summary
#' @rdname bcnsm-methods
#' @export
print.summary.bcnsm <- function(x, ...) {

  d <- x$d
  delta <- x$delta
  margins <- x$margins

  lambda_id <- apply(matrix(margins, ncol = 1), 1, function(x) !grepl("Log-", as.bcs(x)$name))
  nu_id <- apply(matrix(margins, ncol = 1), 1, function(x) as.bcs(x)$extrap)

  # Names
  y_names <- if (is.null(colnames(x$y))) paste0("y", 1:d) else colnames(x$y)
  Margins <- toupper(x$margins)
  copula_name <- paste(toupper(substr(x$copula, 1, 1)), substr(x$copula, 2, nchar(x$copula)), sep = "")

  # Print
  if (requireNamespace("crayon", quietly = TRUE)) {

    cat(crayon::cyan(
      "\nMultivariate Box-Cox Distribution with",
      if (is.null(delta)) copula_name else paste0(copula_name, "(", round(delta, 2), ")"),
      "Copula\n\n"
    ))

    cat(crayon::cyan("Call:\n\n"))
    print(x$call)

    cat(crayon::cyan("\nSummary for the marginal parameters:\n\n"))

    nu <- lambda <- matrix(NA, d, 2)
    colnames(nu) <- colnames(lambda) <- colnames(x$lambda)
    lambda[lambda_id, ] <- x$lambda
    nu[nu_id, ] <- x$nu

    for (j in 1:d) {

      cat(y_names[j], " ~ ", get(x$margins[j])()$name, " Distribution:\n", sep = "")

      TAB <- rbind(
        x$mu[j, ],
        x$sigma[j, ],
        if (lambda_id[j]) lambda[j, ],
        if (nu_id[j]) nu[j, ]
      )

      rownames(TAB) <- c("mu", "sigma", if (lambda_id[j]) "lambda", if (nu_id[j]) "nu")
      print(TAB)
      cat("\n\n")
    }

    if (!is.null(x$gamma)) {
      cat(crayon::cyan("Summary for the association parameters:\n\n", sep = ""))
      stats::printCoefmat(x$gamma)
    }

    article <- if (x$association == "non-associative") "a" else "an"
    cat(
      crayon::cyan("\nMarginal distributions:"), x$margins,
      crayon::cyan("\nDependence modeling:"), if (is.null(x$delta)) copula_name else paste0(copula_name, "(", x$delta, ")"),
      "copula with", article, tolower(x$association), "association matrix",
      crayon::cyan("\nLoglik:"), x$logLik, "|",
      crayon::cyan("AIC:"), x$AIC, "|",
      crayon::cyan("BIC:"), x$BIC
    )

  } else {

    cat(
      "\nMultivariate Box-Cox Distribution with",
      if (is.null(delta)) copula_name else paste0(copula_name, "(", round(delta, 2), ")"),
      "Copula\n\n"
    )

    cat("Call:\n\n")
    print(x$call)

    cat("\nSummary for the marginal parameters:\n\n")

    nu <- lambda <- matrix(NA, d, 4)
    colnames(nu) <- colnames(lambda) <- colnames(x$lambda)
    lambda[lambda_id, ] <- x$lambda
    nu[nu_id, 1:2] <- x$nu[, 1:2]

    for (j in 1:d) {

      cat(y_names[j], " ~ ", get(x$margins[j])()$name, " Distribution:\n", sep = "")

      TAB <- rbind(
        x$mu[j, ],
        x$sigma[j, ],
        if (lambda_id[j]) lambda[j, ],
        if (nu_id[j]) nu[j, ]
      )

      rownames(TAB) <- c("mu", "sigma", if (lambda_id[j]) "lambda", if (nu_id[j]) "nu")
      stats::printCoefmat(TAB, signif.legend = (j == d), na.print = "---")
      cat("\n\n")
    }

    if (!is.null(x$gamma)) {
      cat("Summary for the association parameters:\n\n", sep = "")
      stats::printCoefmat(x$gamma)
    }

    article <- if (x$association == "Non-associative") "a" else "an"
    cat(
      "\n---------------------------------------------------------------",
      "\nMarginal distributions:", x$margins,
      "\nDependence modeling:", if (is.null(x$delta)) copula_name else paste0(copula_name, "(", x$delta, ")"),
      "copula with", article, tolower(x$association), "association matrix",
      "\nLoglik:", x$logLik, "|",
      "AIC:", x$AIC, "|",
      "BIC:", x$BIC
    )
  }

  invisible(x)
}



globalVariables(c("theo", "emp", "marg", "grid_x", "grid_y", "prob", "density"))
# Plot
#' Visualization of the fit of the BCNSM distributions
#'
#'
#'
#' @param x an object of class \code{bcnsm}.
#' @param type character; specifies which graphical should be produced. The available options
#'     are \code{"response"} (default), \code{"margins"}, and \code{"epsilon"}.
#' @param outliers logical; used only when \code{type = "response"}. If \code{TRUE},
#'     possible outliers are highlighted in red.
#' @param alpha criterion according to the squared Mahalanobis distances that identifies a point as
#'     a possible outlier.
#' @param levels levels for contours plots.
#' @param panel A vector of the form \code{c(nr, nc)} with the number of rows and columns, where
#'     the figures will be drawn in an \code{nr-}by\code{-nc} array on the device.
#' @param ... further arguments passed to or from other methods.
#'
#' @author Rodrigo M. R. de Medeiros <\email{rodrigo.matheus@live.com}>
#'
#' @export
#'
plot.bcnsm <- function(x, type = c("response", "margins", "epsilon"),
                       outliers = FALSE, alpha = 0.01,
                       panel = NULL,
                       levels = c(1, 1e-1, 1e-2, 1e-3, 1e-4), ...) {

  type <- match.arg(type, c("response", "margins", "epsilon"))

  y <- x$y
  epsilon <- stats::residuals(x, "epsilon")

  n <- x$nobs
  d <- x$d
  margins <- x$margins
  copula <- x$copula
  delta <- x$delta

  mu <- x$mu
  sigma <- x$sigma
  lambda <- x$lambda
  nu <- x$nu

  gamma <- x$gamma
  digits <- 4L
  if (is.null(panel)) panel <- c(ceiling(d / 4), 4)

  Gamma <- get(x$association)(d)$Gamma(round(x$gamma, digits))

  md <- Rfast::mahala(epsilon, rep(0L, d), Gamma)
  id_md <- get(paste0("maha_", copula))(md, delta, d) > 1 - alpha

  y_names <- if (is.null(colnames(y))) paste0("y", 1:d) else colnames(y)

  copula_inf <- make_copula(copula, delta)
  dmv <- get(paste0("dmv_", copula))
  dPSI <- copula_inf$dPSI
  pPSI <- copula_inf$pPSI
  qPSI <- copula_inf$qPSI

  op <- graphics::par()

  y_aux <- y
  y_names <- colnames(y)

  # With ggplot ------------------------------------------------------------------------------------
  if (requireNamespace("ggplot2", quietly = TRUE) &
      requireNamespace("GGally", quietly = TRUE)) {


    ## Response contour plot -----------------------------------------------------------------------
    if (type == "response") {

      ### Diagonal plots
      diag_func <- function(data, mapping, ...){

        x <- GGally::eval_data_col(data, mapping$x)
        y <- GGally::eval_data_col(data, mapping$y)

        xid <- which(colSums(y_aux - x) == 0)

        dBCS <- function(x) {
          get(paste0("d", margins[xid]))(x,
                                         mu = mu[xid],
                                         sigma = sigma[xid],
                                         lambda = lambda[xid],
                                         nu = nu[xid])
        }

        ggplot2::ggplot(data, mapping) +
          ggplot2::geom_histogram(ggplot2::aes(y = ggplot2::after_stat(density)),
                                  colour = 1, fill = "white",
                                  bins = ceiling(1 + 3.33 * log(n))) +
          ggplot2::geom_function(fun = dBCS, col = "#00AFBB")


      }

      ### Upper plots
      upper_func <- function(data, mapping, ...){

        x <- GGally::eval_data_col(data, mapping$x)
        y <- GGally::eval_data_col(data, mapping$y)

        i <- which(colSums(y_aux - x) == 0)
        j <- which(colSums(y_aux - y) == 0)

        corr <- Gamma[i, j]

        colFn <- grDevices::colorRampPalette(c("brown1", "white", "dodgerblue"), interpolate ='spline')
        fill <- colFn(1000)[findInterval(corr, seq(-1, 1, length = 1000))]

        GGally::ggally_text(
          label = as.character(round(corr, 4)),
          mapping = ggplot2::aes(),
          xP = 0.5,
          yP = 0.5,
          cex = 2.5,
          color = 'black', ...) +
          ggplot2::theme_void() +
          ggplot2::theme(panel.background = ggplot2::element_rect(fill = fill))
      }

      ### Lower plots
      lower_func <- function(data, mapping, ...){

        x <- GGally::eval_data_col(data, mapping$x)
        y <- GGally::eval_data_col(data, mapping$y)

        i <- which(colSums(y_aux - x) == 0)
        j <- which(colSums(y_aux - y) == 0)

        Gamma_aux <- matrix(c(1, Gamma[i, j], Gamma[i, j], 1), 2, 2)

        mu_aux <- mu[c(i, j)]
        sigma_aux <- sigma[c(i, j)]
        lambda_aux <- lambda[c(i, j)]
        nu_aux <- nu[c(i, j)]

        grid <- expand.grid(seq(min(x) - 10, max(x) + 10, length.out = 200),
                            seq(min(y) - 10, max(y) + 10, length.out = 200))

        data_aux <- data.frame(grid_x = grid[, 1],
                               grid_y = grid[, 2],
                               prob = dbcnsm(cbind(grid[, 1], grid[, 2]),
                                             mu_aux, sigma_aux,
                                             lambda_aux, nu_aux, Gamma_aux,
                                             delta = delta, copula = copula,
                                             margins = margins[c(i, j)]))

        data_aux2 <- data.frame(x = x[id_md], y = y[id_md])


        out <- ggplot2::ggplot() +
          ggplot2::geom_point(data = data, mapping = ggplot2::aes(x = x, y = y)) +
          ggplot2::geom_contour(data = data_aux, mapping =  ggplot2::aes(x = grid_x, y = grid_y, z = prob),
                                col = "#00AFBB", breaks = levels) +
          ggplot2::labs(x = "", y = "")

        if (outliers) {
          out + ggplot2::geom_point(data = data_aux2, mapping = ggplot2::aes(x = x, y = y), col = "red")
        } else {
          out
        }



      }

      colFn <- grDevices::colorRampPalette(c("brown1", "white", "dodgerblue"), interpolate ='spline')
      lab <- ggplot2::ggplot(data.frame(x = stats::runif(200, -1, 1), y = stats::runif(200, -1, 1),
                                        z = stats::runif(200, -1, 1)), ggplot2::aes(x, y, colour = z)) +
        ggplot2::geom_point() +
        ggplot2::scale_colour_gradient2("Fitted association \nparameter",
                                        low = colFn(200)[1], high = colFn(200)[200]) +
        ggplot2::theme(legend.title.align = 0.5,
                       legend.position = "top",
                       legend.key.height = ggplot2::unit(0.3, 'cm'),
                       legend.key.width = ggplot2::unit(1.5, 'cm'))

      GGally::ggpairs(as.data.frame(y), #ggplot2::aes(colour = gender),
                      upper = list(continuous = GGally::wrap(upper_func)),
                      lower = list(continuous = GGally::wrap(lower_func)),
                      diag = list(continuous = GGally::wrap(diag_func)),
                      legend = GGally::grab_legend(lab),
                      progress = FALSE) +
        ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) +
        ggplot2::theme(legend.position = "top")


    } else if (type == "margins") {

      ## Marginal distributions ------------------------------------------------------------------------
      theo_CDF <- emp_CDF <- matrix(NA, n, ncol(y))
      ks <- vector()
      for(j in 1:d){

        theo_CDF[ , j] <- get(paste0("p", margins[j]))(q = y[, j],
                                                       mu = mu[j],
                                                       sigma = sigma[j],
                                                       lambda = lambda[j],
                                                       nu = nu[j])

        ks[j] <- round(stats::ks.test(theo_CDF[, j], "punif")$p.value, 2)

        emp_CDF[, j] <- stats::ecdf(y[, j])(y[, j])

        id <- order(theo_CDF[, j])

        theo_CDF[, j] <- theo_CDF[id, j]
        emp_CDF[, j] <- emp_CDF[id, j]

      }

      dKS <- sfsmisc::KSd(n)
      aux_data <- data.frame(theo = c(theo_CDF),
                             emp = c(emp_CDF),
                             lower = c(emp_CDF - dKS),
                             upper = c(emp_CDF + dKS),
                             marg = factor(rep(colnames(y), each = n),
                                           levels = colnames(y)),
                             ks = rep(paste0("KS p-value: ", ks), each = n))

      positions <- data.frame(x = c(rbind(-1, theo_CDF, 2, apply(theo_CDF, 2, rev))),
                              y = c(rbind(-1 - dKS, theo_CDF - dKS, 2 + dKS, apply(theo_CDF, 2, rev) + dKS)),
                              marg = factor(rep(colnames(y), each = 2 * n + 2),
                                            levels = colnames(y)))

      ggplot2::ggplot() +
        ggplot2::geom_polygon(ggplot2::aes(x = x, y = y, group = marg), positions, fill = "#cceff1") +
        ggplot2::coord_cartesian(xlim = c(0, 1), ylim = c(0, 1)) +
        ggplot2::geom_point(ggplot2::aes(x = theo, y = emp, group = marg), aux_data) +
        ggplot2::geom_abline(intercept = 0, slope = 1, col = "#00AFBB", lty = 1) +
        ggplot2::geom_text(ggplot2::aes(x = 0.8, y = 0, label = ks, group = marg), aux_data, size = 3) +
        ggplot2::facet_wrap(~ marg, nrow = panel[1], ncol = panel[2]) +
        ggplot2::labs(x = "Fitted distribution function",
                      y = "Empirical distribution function", size = 1)


    } else if (type == "epsilon") {
      ## Epsilon contour plot ----------------------------------------------------------------------


      ### Diagonal plots
      diag_func <- function(data, mapping, ...){

        x <- GGally::eval_data_col(data, mapping$x)

        ggplot2::ggplot(data, mapping) +
          ggplot2::geom_histogram(ggplot2::aes(y = ggplot2::after_stat(density)),
                                  colour = 1, fill = "white",
                                  bins = ceiling(1 + 3.33 * log(n))) +
          ggplot2::geom_function(fun = dPSI, col = "#00AFBB")


      }

      ### Upper plots
      upper_func <- function(data, mapping, ...){

        x <- GGally::eval_data_col(data, mapping$x)
        y <- GGally::eval_data_col(data, mapping$y)

        i <- which(colSums(epsilon - x) == 0)
        j <- which(colSums(epsilon - y) == 0)

        corr <- Gamma[i, j]

        colFn <- grDevices::colorRampPalette(c("brown1", "white", "dodgerblue"), interpolate ='spline')
        fill <- colFn(1000)[findInterval(corr, seq(-1, 1, length = 1000))]

        GGally::ggally_text(
          label = as.character(round(corr, 4)),
          mapping = ggplot2::aes(),
          xP = 0.5,
          yP = 0.5,
          cex = 2.5,
          color = 'black', ...) +
          ggplot2::theme_void() +
          ggplot2::theme(panel.background = ggplot2::element_rect(fill = fill))
      }

      ### Lower plots
      lower_func <- function(data, mapping, ...){

        x <- GGally::eval_data_col(data, mapping$x)
        y <- GGally::eval_data_col(data, mapping$y)

        i <- which(colSums(epsilon - x) == 0)
        j <- which(colSums(epsilon - y) == 0)

        Gamma_aux <- matrix(c(1, Gamma[i, j], Gamma[i, j], 1), 2, 2)

        mu_aux <- mu[c(i, j)]
        sigma_aux <- sigma[c(i, j)]
        lambda_aux <- lambda[c(i, j)]
        nu_aux <- nu[c(i, j)]

        grid <- expand.grid(seq(min(x) - 10, max(x) + 10, length.out = 200),
                            seq(min(y) - 10, max(y) + 10, length.out = 200))

        data_aux <- data.frame(grid_x = grid[, 1],
                               grid_y = grid[, 2],
                               prob = dmv(cbind(grid[, 1], grid[, 2]),
                                          mu = rep(0, 2L),
                                          Sigma = Gamma_aux,
                                          delta = delta))


        ggplot2::ggplot() +
          ggplot2::geom_point(data = data, mapping = ggplot2::aes(x = x, y = y)) +
          ggplot2::geom_contour(data = data_aux, mapping =  ggplot2::aes(x = grid_x, y = grid_y, z = prob),
                                col = "#00AFBB", breaks = levels) +
          ggplot2::labs(x = "", y = "")


      }

      colFn <- grDevices::colorRampPalette(c("brown1", "white", "dodgerblue"), interpolate ='spline')
      lab <- ggplot2::ggplot(data.frame(x = stats::runif(200, -1, 1), y = stats::runif(200, -1, 1),
                                        z = stats::runif(200, -1, 1)), ggplot2::aes(x, y, colour = z)) +
        ggplot2::geom_point() +
        ggplot2::scale_colour_gradient2("Fitted association \nparameter",
                                        low = colFn(200)[1], high = colFn(200)[200]) +
        ggplot2::theme(legend.title.align = 0.5,
                       legend.position = "top",
                       legend.key.height = ggplot2::unit(0.3, 'cm'),
                       legend.key.width = ggplot2::unit(1.5, 'cm'))

      GGally::ggpairs(as.data.frame(epsilon), #ggplot2::aes(colour = gender),
                      upper = list(continuous = GGally::wrap(upper_func)),
                      lower = list(continuous = GGally::wrap(lower_func)),
                      diag = list(continuous = GGally::wrap(diag_func)),
                      legend = GGally::grab_legend(lab),
                      progress = FALSE) +
        ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust = 1)) +
        ggplot2::theme(legend.position = "top")


    }

    # With R base plots ------------------------------------------------------------------------------
  } else {

    if (type == "response") {

      ## Response contour plot -----------------------------------------------------------------------

      ## put histograms on the diagonal
      panel.hist <- function(x, ...) {

        xid <- which(colSums(y_aux - x) == 0)


        usr <- graphics::par("usr")
        on.exit(graphics::par(usr = usr))
        h <- graphics::hist(x, plot = FALSE)
        breaks <- h$breaks
        nB <- length(breaks)
        y <- h$density
        graphics::par(usr = c(usr[1:2], 0, max(y) + 0.5 * diff(range(y))))
        graphics::rect(breaks[-nB], 0, breaks[-1], y, col = "white", ...)

        dBCS <- function(x) {
          get(paste0("d", margins[xid]))(x, mu = mu[xid], sigma = sigma[xid],
                                         lambda = lambda[xid], nu = nu[xid])
        }

        graphics::curve(dBCS(x), add = TRUE, col = "#00AFBB", lwd = 2, ...)

      }


      ## put an association measure on the upper panels
      panel.cor <- function(x, y, ...) {

        xid <- which(colSums(y_aux - x) == 0)
        yid <- which(colSums(y_aux - y) == 0)

        usr <- graphics::par("usr")
        on.exit(graphics::par(usr = usr))

        r <- Gamma[xid, yid]

        colFn <- grDevices::colorRampPalette(c("brown1", "white", "dodgerblue"), interpolate = "spline")
        fill <- colFn(1000)[findInterval(r, seq(-1, 1, length = 1000))]

        graphics::par(usr = c(0, 1, 0, 1))
        txt <- format(c(r, 0.123456789), digits = 2)[1]
        txt <- paste0(txt)
        graphics::rect(0, 0, 1, 1, col = fill, border = "black")
        graphics::text(0.5, 0.5, txt, cex = 1.2)

      }

      ## put scatterplots with countour lines on the lower panels
      panel.scatter <- function(x, y, ...) {

        xid <- which(colSums(y_aux - x) == 0)
        yid <- which(colSums(y_aux - y) == 0)

        usr <- graphics::par("usr")
        on.exit(graphics::par(usr = usr))
        graphics::points(x, y, pch = 16)

        mu_aux <- mu[c(xid, yid)]
        sigma_aux <- sigma[c(xid, yid)]
        lambda_aux <- lambda[c(xid, yid)]
        nu_aux <- nu[c(xid, yid)]
        Gamma_aux <- matrix(c(1, Gamma[xid, yid], Gamma[xid, yid], 1), 2, 2)

        faux <- function(x, y) {

          dbcnsm(
            cbind(x, y), mu_aux, sigma_aux, lambda_aux, nu_aux, Gamma_aux,
            copula, delta, margins[c(xid, yid)]
          )

        }

        z <- outer(seq(min(x), max(x), length.out = 200),
                   seq(min(y), max(y), length.out = 200), faux)

        graphics::contour(seq(min(x), max(x), length.out = 200),
                          seq(min(y), max(y), length.out = 200), z,
                          levels = levels, col = "#00AFBB", lwd = 2, add = TRUE
        )



      }


      graphics::pairs(y, lower.panel = panel.scatter,
                      upper.panel = panel.cor,
                      diag.panel = panel.hist,
                      labels = y_names, cex.labels = 1.1, font.labels = 2)




    } else if (type == "margins") {

      ## Marginal distributions --------------------------------------------------------------------

      graphics::par(mfrow = panel)

      # P-P plot
      for (j in 1:d) {

        u <- stats::ecdf(y[, j])(y[, j])
        v <- get(paste0("p", margins[j]))(q = y[, j], mu = mu[j], sigma = sigma[j],
                                          lambda = lambda[j], nu = nu[j])

        dKS <- sfsmisc::KSd(n)
        id <- order(v)

        plot(v[id], u[id], main = y_names[j], pch = 16, xlim = c(0, 1), ylim = c(0, 1),
             xlab = "Fitted distribution function", ylab = "Empirical distribution function", type = "n")
        graphics::polygon(x = c(-1, u[id], 2, rev(u[id])),
                          y = c(-1 - dKS, u[id] - dKS, 2 + dKS, rev(u[id]) + dKS),
                          col = "#cceff1", border = NA)
        graphics::points(v, u, pch = 16)
        graphics::abline(0, 1, col = "#00AFBB", lwd = 2)
        graphics::box()
        graphics::legend("bottomright",
                         paste("KS p-value:", suppressWarnings(round(stats::ks.test(v, "punif", 0, 1)$p.value, 2))),
                         bty = "n", cex = 0.75)

      }

      graphics::par(mfrow = op$mfrow)

    } else if (type == "epsilon") {

      ## Epsilon contour plot ----------------------------------------------------------------------------

      ## put histograms on the diagonal
      panel.hist <- function(x, ...) {

        usr <- graphics::par("usr")
        on.exit(graphics::par(usr = usr))
        graphics::par(usr = c(usr[1:2], 0, 0.6))
        h <- graphics::hist(x, plot = FALSE)
        breaks <- h$breaks
        nB <- length(breaks)
        y <- h$density
        graphics::par(usr = c(usr[1:2], 0, max(max(y), dPSI(0L)) + 0.5 * diff(range(y))))
        graphics::rect(breaks[-nB], 0, breaks[-1], y, col = "white", ...)
        graphics::curve(dPSI(x), add = TRUE, col = "#00AFBB", lwd = 2)

      }

      ## put kendall's tau on the upper panels
      panel.cor <- function(x, y, ...) {

        xid <- which(colSums(epsilon - x) == 0)
        yid <- which(colSums(epsilon - y) == 0)

        usr <- graphics::par("usr")
        on.exit(graphics::par(usr = usr))

        r <- Gamma[xid, yid]

        colFn <- grDevices::colorRampPalette(c("brown1", "white", "dodgerblue"), interpolate = "spline")
        fill <- colFn(1000)[findInterval(r, seq(-1, 1, length = 1000))]

        graphics::par(usr = c(0, 1, 0, 1))
        txt <- format(c(r, 0.123456789), digits = 2)[1]
        txt <- paste0("Fitted\n Kendall's tau:\n", txt)
        graphics::rect(0, 0, 1, 1, col = fill, border = "black")
        graphics::text(0.5, 0.5, txt, cex = 1.2)
      }

      ## put scatterplots with countour lines on the lower panels
      panel.scatter <- function(x, y, ...) {

        xid <- which(colSums(epsilon - x) == 0)
        yid <- which(colSums(epsilon - y) == 0)

        faux <- function(x, y, Gamma) {
          if (copula == "gaussian") {
            dmv_gaussian(cbind(x, y), rep(0, 2), Gamma)
          } else if (copula == "t") {
            dmv_t(cbind(x, y), rep(0, 2), Gamma, delta)
          } else if (copula == "slash") {
            dmv_slash(cbind(x, y), rep(0, 2), Gamma, delta)
          } else {
            dmv_hyp(cbind(x, y), rep(0, 2), Rfast::cholesky(Gamma), delta)
          }

        }

        Gamma_aux <- matrix(c(1, Gamma[xid, yid], Gamma[xid, yid], 1), 2, 2)
        z <- outer(seq(min(x), max(x), length.out = 200),
                   seq(min(y), max(y), length.out = 200), faux,
                   Gamma = Gamma_aux)

        usr <- graphics::par("usr")
        on.exit(graphics::par(usr = usr))
        graphics::points(x, y, pch = 16)
        graphics::contour(seq(min(x), max(x), length.out = 200),
                          seq(min(y), max(y), length.out = 200), z,
                          levels = levels, col = "#00AFBB", lwd = 2, add = TRUE
        )
      }

      graphics::pairs(epsilon,
                      lower.panel = panel.cor,
                      upper.panel = panel.scatter,
                      diag.panel = panel.hist,
                      labels = y_names, cex.labels = 1.1, font.labels = 2
      )

    }



  }

}
rdmatheus/BCNSM documentation built on Feb. 8, 2024, 1:28 a.m.