R/plt-visualize-walks.R

Defines functions visualize_walks

Documented in visualize_walks

#' Visualize Walks
#'
#' @family Visualization Functions
#'
#' @author Antti Lennart Rask
#'
#' @description visualize_walks() visualizes the output of the random walk
#' functions in the RandomWalker package, resulting in one or more ggplot2 plots
#' put together in a patchwork composed of 1 or more patches.
#'
#' @details visualize_walks() generates visualizations of the random walks
#' generated by the  random walk functions in the RandomWalker package. These
#' are the functions at the moment of writing:
#' - brownian_motion()
#' - discrete_walk()
#' - geometric_brownian_motion()
#' - random_normal_drift_walk()
#' - random_normal_walk()
#' - rw30()
#'
#' It is possible there are more when you read this, but you can check the rest
#' of the documentation for the current situation.
#'
#' The visualization function is meant to be easy to use. No parameters needed,
#' but you can set `.alpha` if the default value of 0.7 isn't to your
#' liking.
#'
#' You can also choose whether you want the visualization to be interactive or
#' not by setting `.interactive` to TRUE. The function uses the `ggiraph`
#' package for making the patches interactive.
#'
#' If you want to visualize only one of the attributes, you can choose use one of
#' these values (`y`, `cum_sum`, `cum_prod`, `cum_min`, `cum_max`, `cum_mean`) for
#' the `.pluck` parameter.
#'
#' @param .data The input data. Assumed to be created by one of the random walk
#' functions in the RandomWalker package, but can be any data frame or tibble
#' that contains columns `walk_number`, `x`, and one or more numeric columns
#' like `y`, `cum_sum`, `cum_prod`, `cum_min`, `cum_max` and `cum_mean`, for
#' instance.
#' @param .alpha The alpha value for all the line charts in the visualization.
#' Values range from 0 to 1. Default is 0.7.
#' @param .interactive A boolean value. TRUE if you want the patches to be
#' interactive. FALSE if you don't. Default is FALSE.
#' @param .pluck If you want to visualize only one of the You can choose one of
#' the values (`y`, `cum_sum`, `cum_prod`, `cum_min`, `cum_max`, `cum_mean`).
#' Default is FALSE.
#'
#' @return A patchwork composed of 1 or more patches
#'
#' @examples
#' # Generate random walks and visualize the result
#' set.seed(123)
#' rw30() |>
#'  visualize_walks()
#'
#' # Set the alpha value to be other than the default 0.7
#' set.seed(123)
#' rw30() |>
#'  visualize_walks(.alpha = 0.5)
#'
#' # Use the function with an input that has alternatives for y
#' set.seed(123)
#' random_normal_walk(.num_walks = 5, .initial_value = 100) |>
#'  visualize_walks()
#'
#' # Use the function to create interactive visualizations
#' set.seed(123)
#' random_normal_walk(.num_walks = 5, .initial_value = 100) |>
#'  visualize_walks(.interactive = TRUE)
#'
#' # Use .pluck to pick just one visualization
#' set.seed(123)
#' random_normal_walk(.num_walks = 5, .initial_value = 100) |>
#'  visualize_walks(.pluck = "cum_sum")
#'
#' @name visualize_walks
NULL
#' @rdname visualize_walks
#' @export
visualize_walks <- function(.data, .alpha = 0.7, .interactive = FALSE, .pluck = FALSE) {

  # Retrieve the attributes of the data (e.g., function name, number of walks, etc.)
  atb <- attributes(.data)

  # Function to generate a plot for a given y-variable in the data
  generate_plot <- function(y_var) {

    # Convert y-label to a more readable format if it's not 'y'
    y_label_pretty <- if (y_var == "y") "y" else convert_snake_to_title_case(y_var)

    # Create a static ggplot visualization
    if (.interactive == FALSE) {

      # Create a ggplot object
      p <- ggplot2::ggplot(.data, ggplot2::aes(x = x, y = get(y_var), color = walk_number)) +
        # Plot lines with some transparency
        ggplot2::geom_line(alpha = .alpha) +
        # Use a minimal theme
        ggplot2::theme_minimal() +
        # Remove the legend
        ggplot2::theme(legend.position = "none") +
        # Set plot labels
        ggplot2::labs(title = y_label_pretty, x = "Step", y = NULL)

      return(p)

      # Create an interactive visualization with ggiraph
    } else if (.interactive == TRUE) {

      # Add tooltip information to the data
      .data <- .data |>
        dplyr::mutate(
          .tooltip = paste0(
            "Walk Number: ", walk_number, " | ",
            "Step: ", x, " | ",
            y_label_pretty, ": ", round(get(y_var), digits = 3)
          )
        )

      # Create an interactive plot with ggiraph
      g <- ggplot2::ggplot(
        .data,
        ggplot2::aes(
          x       = x,
          y       = get(y_var),
          color   = walk_number,
          group   = walk_number,
          data_id = walk_number,
          tooltip = .tooltip
        )
      ) +
        # Add interactive lines
        ggiraph::geom_line_interactive(alpha = .alpha) +
        # Add interactive points
        ggiraph::geom_point_interactive(alpha = .alpha, size = 0.1) +
        # Use a minimal theme
        ggplot2::theme_minimal() +
        # Remove the legend
        ggplot2::theme(legend.position = "none") +
        # Set plot labels
        ggplot2::labs(title = y_label_pretty, x = "Step", y = NULL)

      return(g)

      # Check the .interactive parameter
    } else {
      rlang::abort(
        message = "The parameter `.interactive` must be either TRUE/FALSE",
        use_cli_format = TRUE
      )
    }
  }

  # Identify variables to plot, excluding 'walk_number' and 'x'
  plot_vars <- setdiff(atb$names, c("walk_number", "x"))

  # Generate a list of plots for each variable in plot_vars
  plots <- lapply(plot_vars, generate_plot)

  # Generate a subtitle based on the function name in attributes
  .subtitle <- paste0("Function: ", if (atb$fns == "rw30") "rw30" else convert_snake_to_title_case(atb$fns))

  # Generate the plot caption dynamically based on attributes
  .caption <- generate_caption(atb)

  # Define annotations (title, subtitle, caption) for the combined plot
  plot_annotations <- patchwork::plot_annotation(
    title    = paste0(atb$num_walks, " Random Walks"),
    subtitle = .subtitle,
    caption  = .caption
  )

  # Define theme adjustment for the combined plot (applicable in interactive mode)
  plot_theme <- ggplot2::theme(
    plot.caption = ggplot2::element_text(hjust = 1, margin = ggplot2::margin(t = 0, r = 0, b = 0, l = 0)),
    plot.margin  = ggplot2::margin(t = 10, r = 10, b = 0, l = 10)
  )

  # Handle the `.pluck` option for selecting a specific plot
  if (.pluck != FALSE) {
    .pluck_n <- switch(
      .pluck,
      "y"        = 1,
      "cum_sum"  = 2,
      "cum_prod" = 3,
      "cum_min"  = 4,
      "cum_max"  = 5,
      "cum_mean" = 6,
      rlang::abort(
        message        = "Invalid parameter value for `.pluck`",
        use_cli_format = TRUE
      )
    )

    # Return the plucked plot with annotations
    plucked_plot <- plots[[.pluck_n]] + plot_annotations

    # If interactive, return the interactive version of the plucked plot
    if (.interactive == TRUE) {
      return(
        ggiraph::girafe(
          ggobj   = plucked_plot + plot_theme,
          options = list(
            ggiraph::opts_hover(css = "stroke:black;stroke-width:2pt;"),
            ggiraph::opts_hover_inv(css = "opacity:0.4;"),
            ggiraph::opts_toolbar(position = "topright"),
            ggiraph::opts_tooltip(
              offx           = 200,
              offy           = 5,
              use_cursor_pos = FALSE,
              opacity        = 0.7
            ),
            ggiraph::opts_zoom(max = 5)
          )
        )
      )
    }

    return(plucked_plot)
  }

  # Patchwork for the default version of the visualization
  if (.interactive == FALSE) {

    # Combine the individual plots into a single plot, or return the single plot with annotations
    combined_plot <- if (length(plots) > 1) {
      patchwork::wrap_plots(plots) + plot_annotations
    } else {
      plots[[1]] + plot_annotations
    }

    return(combined_plot)

    # Patchwork for the interactive version of the visualization
  } else {

    # Define plot options for ggiraph
    plot_options <- list(
      # Customize hover effect
      ggiraph::opts_hover(css = "stroke:black;stroke-width:2pt;"),
      # Customize hover-out effect
      ggiraph::opts_hover_inv(css = "opacity:0.4;"),
      # Place toolbar on top right
      ggiraph::opts_toolbar(position = "topright"),
      # Customize tooltip
      ggiraph::opts_tooltip(offx = 200, offy = 5, use_cursor_pos = FALSE, opacity = 0.7),
      # Enable zoom
      ggiraph::opts_zoom(max = 5)
    )

    # Combine plots using patchwork for interactive visualization
    combined_plot <- if (length(plots) > 1) {
      ggiraph::girafe(
        ggobj   = patchwork::wrap_plots(plots) + plot_annotations + plot_theme,
        options = plot_options
      )
    } else {
      ggiraph::girafe(
        ggobj   = plots[[1]] + plot_annotations + plot_theme,
        options = plot_options
      )
    }

    return(combined_plot)
  }
}

Try the RandomWalker package in your browser

Any scripts or data that you put into this service are public.

RandomWalker documentation built on Oct. 23, 2024, 5:07 p.m.