-
Notifications
You must be signed in to change notification settings - Fork 61
/
plot_anomaly_decomposition.R
99 lines (83 loc) · 4.42 KB
/
plot_anomaly_decomposition.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#' Visualize the time series decomposition with anomalies shown
#'
#' @param data A `tibble` or `tbl_time` object.
#' @param ncol Number of columns to display. Set to 1 for single column by default.
#' @param color_no Color for non-anomalous data.
#' @param color_yes Color for anomalous data.
#' @param alpha_dots Controls the transparency of the dots. Reduce when too many dots on the screen.
#' @param alpha_circles Controls the transparency of the circles that identify anomalies.
#' @param size_dots Controls the size of the dots.
#' @param size_circles Controls the size of the circles that identify anomalies.
#' @param strip.position Controls the placement of the strip that identifies the time series decomposition components.
#'
#' @return Returns a `ggplot` object.
#'
#' @details
#' The first step in reviewing the anomaly detection process is to evaluate
#' a single times series to observe how the algorithm is selecting anomalies.
#' The `plot_anomaly_decomposition()` function is used to gain
#' an understanding as to whether or not the method is detecting anomalies correctly and
#' whether or not parameters such as decomposition method, anomalize method,
#' alpha, frequency, and so on should be adjusted.
#'
#' @seealso [plot_anomalies()]
#'
#' @examples
#'
#' library(dplyr)
#' library(ggplot2)
#'
#' tidyverse_cran_downloads %>%
#' filter(package == "tidyquant") %>%
#' ungroup() %>%
#' time_decompose(count, method = "stl") %>%
#' anomalize(remainder, method = "iqr") %>%
#' plot_anomaly_decomposition()
#'
#' @export
plot_anomaly_decomposition <- function(data, ncol = 1, color_no = "#2c3e50", color_yes = "#e31a1c",
alpha_dots = 1, alpha_circles = 1, size_dots = 1.5, size_circles = 4,
strip.position = "right") {
UseMethod("plot_anomaly_decomposition", data)
}
#' @export
plot_anomaly_decomposition.default <- function(data, ncol = 1, color_no = "#2c3e50", color_yes = "#e31a1c",
alpha_dots = 1, alpha_circles = 1, size_dots = 1.5, size_circles = 4,
strip.position = "right") {
stop("Object is not of class `tbl_time`.", call. = FALSE)
}
#' @export
plot_anomaly_decomposition.grouped_tbl_time <- function(data, ncol = 1, color_no = "#2c3e50", color_yes = "#e31a1c",
alpha_dots = 1, alpha_circles = 1, size_dots = 1.5, size_circles = 4,
strip.position = "right") {
stop("Object cannot be grouped. Select a single time series for evaluation, and use `dplyr::ungroup()`.", call. = FALSE)
}
#' @export
plot_anomaly_decomposition.tbl_time <- function(data, ncol = 1, color_no = "#2c3e50", color_yes = "#e31a1c",
alpha_dots = 1, alpha_circles = 1, size_dots = 1.5, size_circles = 4,
strip.position = "right") {
# Checks
column_names <- names(data)
check_names <- c("observed", "remainder", "anomaly", "remainder_l1", "remainder_l2") %in% column_names
if (!all(check_names)) stop('Error in plot_anomaly_decomposition(): key names are missing. Make sure observed:remainder, remainder_l1, and remainder_l2 are present', call. = FALSE)
# Setup
date_expr <- tibbletime::get_index_quo(data)
date_col <- tibbletime::get_index_char(data)
data_anomaly_tbl <- data %>%
dplyr::select(!!date_expr, observed:remainder, anomaly) %>%
tidyr::gather(key = key, value = value, -dplyr::one_of(c(!! date_col, 'anomaly')), factor_key = T)
g <- data_anomaly_tbl %>%
ggplot2::ggplot(ggplot2::aes(x = .data[[date_col]], y = .data$value, color = .data$anomaly)) +
# Points
ggplot2::geom_point(size = size_dots, alpha = alpha_dots) +
# Circles
ggplot2::geom_point(size = size_circles, shape = 1, alpha = alpha_circles,
data = data_anomaly_tbl %>% dplyr::filter(anomaly == "Yes")) +
# Horizontal Line at Y = 0
ggplot2::geom_hline(yintercept = 0, color = palette_light()[[1]]) +
theme_tq() +
ggplot2::facet_wrap(~ key, ncol = ncol, scales = "free_y", strip.position = strip.position) +
ggplot2::scale_color_manual(values = c("No" = color_no, "Yes" = color_yes)) +
ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 30, hjust = 1))
return(g)
}