|
| 1 | +#' Add Residual Lines to a Plot |
| 2 | +#' |
| 3 | +#' This function adds vertical lines representing residuals from a linear model to a ggformula plot. |
| 4 | +#' The residuals are drawn from the observed data points to the predicted values from the model. |
| 5 | +#' |
| 6 | +#' @param plot A ggformula plot object, typically created with `gf_point()`. |
| 7 | +#' @param model A fitted linear model object created using `lm()`. |
| 8 | +#' @param linewidth A numeric value specifying the width of the residual lines. Default is `0.2`. |
| 9 | +#' @param ... Additional aesthetics passed to `geom_segment()`, such as `color`, `alpha`, `linetype`. |
| 10 | +#' |
| 11 | +#' @return A ggplot object with residual lines added. |
| 12 | +#' |
| 13 | +#' @export |
| 14 | +#' @examples |
| 15 | +#' gf_point(Thumb ~ Height, data = Fingers) %>% |
| 16 | +#' gf_model(Height_model) %>% |
| 17 | +#' gf_resid(Height_model, color = "red", alpha = 0.5) |
| 18 | +gf_resid <- function(plot, model, linewidth = 0.2, ...) { |
| 19 | + # Handles random jitter |
| 20 | + rand_int <- sample(1:100, 1) |
| 21 | + set.seed(rand_int) |
| 22 | + |
| 23 | + # Get model predictions and residuals and assign them to the model data |
| 24 | + model_data <- model$model |
| 25 | + model_data$prediction <- predict(model) |
| 26 | + model_data$residual <- resid(model) |
| 27 | + |
| 28 | + # Access the x and y coordinates used in the plot |
| 29 | + plot_data <- ggplot_build(plot)$data[[1]] |
| 30 | + x_loc <- plot_data$x |
| 31 | + y_loc <- plot_data$y |
| 32 | + |
| 33 | + # Ensures same jitter as the x and y coord from plot |
| 34 | + set.seed(rand_int) |
| 35 | + plot + |
| 36 | + geom_segment(aes( |
| 37 | + x = x_loc, |
| 38 | + y = model_data$prediction, |
| 39 | + xend = x_loc, |
| 40 | + yend = y_loc |
| 41 | + ), |
| 42 | + inherit.aes = TRUE, |
| 43 | + linewidth = linewidth, |
| 44 | + ... |
| 45 | + ) |
| 46 | +} |
| 47 | + |
| 48 | +#' Add Squared Residual Visualization to a Plot |
| 49 | +#' |
| 50 | +#' This function adds squared residual representations to a ggformula plot, illustrating squared error as a polygon. |
| 51 | +#' The function dynamically adjusts the aspect ratio to ensure proper scaling of squares. |
| 52 | +#' |
| 53 | +#' @param plot A ggformula plot object, typically created with `gf_point()`. |
| 54 | +#' @param model A fitted linear model object created using `lm()`. |
| 55 | +#' @param aspect A numeric value controlling the square's aspect ratio. Default is `4/6`. |
| 56 | +#' @param alpha A numeric value specifying the transparency level of the polygon fill. Default is `0.1`. |
| 57 | +#' @param ... Additional aesthetics passed to `geom_polygon()`, such as `color` and `fill`. |
| 58 | +#' |
| 59 | +#' @return A ggplot object with squared residuals added. |
| 60 | +#' |
| 61 | +#' @export |
| 62 | +#' @examples |
| 63 | +#' gf_point(Thumb ~ Height, data = Fingers) %>% |
| 64 | +#' gf_model(Height_model) %>% |
| 65 | +#' gf_squaresid(Height_model, color = "blue", alpha = 0.5) |
| 66 | +gf_squaresid <- function(plot, model, aspect = 4/6, alpha = 0.1, ...) { |
| 67 | + # Handles random jitter |
| 68 | + rand_int <- sample(1:100, 1) |
| 69 | + set.seed(rand_int) |
| 70 | + |
| 71 | + # Get model predictions and residuals and assign them to the model data |
| 72 | + model_data <- model$model |
| 73 | + model_data$prediction <- predict(model) |
| 74 | + model_data$residual <- resid(model) |
| 75 | + |
| 76 | + # Access the x and y coordinates used in the plot |
| 77 | + plot_data <- ggplot_build(plot)$data[[1]] |
| 78 | + model_data$x_loc <- plot_data$x |
| 79 | + model_data$y_loc <- plot_data$y |
| 80 | + |
| 81 | + # Access the range of x and y used in the panel |
| 82 | + plot_layout <- ggplot_build(plot)$layout |
| 83 | + panel_params <- plot_layout$panel_params[[1]] |
| 84 | + x_range <- panel_params$x.range |
| 85 | + y_range <- panel_params$y.range |
| 86 | + |
| 87 | + # Compute ratio for proper aspect scaling |
| 88 | + range_ratio <- (x_range[2] - x_range[1]) / (y_range[2] - y_range[1]) |
| 89 | + model_data$dir <- ifelse(model_data$x_loc > mean(x_range), -1, 1) |
| 90 | + model_data$adj_side <- model_data$x_loc + model_data$dir * abs(model_data$residual * aspect * range_ratio) |
| 91 | + |
| 92 | + # Create a dataframe for plotting polygons |
| 93 | + squares_data <- do.call(rbind, lapply(1:nrow(model_data), function(i) { |
| 94 | + resid_side <- model_data$x_loc[i] |
| 95 | + top <- model_data$prediction[i] |
| 96 | + bottom <- model_data$y_loc[i] |
| 97 | + opp_side <- model_data$adj_side[i] |
| 98 | + |
| 99 | + data.frame( |
| 100 | + x = c(resid_side, opp_side, opp_side, resid_side), |
| 101 | + y = c(bottom, bottom, top, top), |
| 102 | + id = i # Unique identifier for each square |
| 103 | + ) |
| 104 | + })) |
| 105 | + |
| 106 | + # Ensures same jitter as the x and y coord from plot |
| 107 | + set.seed(rand_int) |
| 108 | + plot + |
| 109 | + geom_polygon( |
| 110 | + data = squares_data, |
| 111 | + aes(x = x, y = y, group = id), |
| 112 | + inherit.aes = FALSE, |
| 113 | + alpha = alpha, |
| 114 | + ... |
| 115 | + ) |
| 116 | +} |
0 commit comments