Repository navigation
Expand file tree
/
Copy pathptd_spc.R
More file actions
218 lines (198 loc) · 7.82 KB
/
Copy pathptd_spc.R
File metadata and controls
218 lines (198 loc) · 7.82 KB
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#' SPC Plotting Function
#'
#' `ptd_spc` returns a plot object or data table with SPC values using NHSI
#' 'plot the dots' logic.
#'
#' This function is designed to produce consistent SPC charts
#' across Information Department reporting, according to the 'plot the dots'
#' logic produced by NHSI. The function can return either a plot or data frame.
#'
#'
#' @param .data A data frame containing a value field, a date field,
#' and a category field (if for faceting). There should be no gaps in the time
#' series for each category.
#' @param value_field Specify the field name which contains the value data, to
#' be plotted on y axis. The field name can be specified using non-standard
#' evaluation (i.e. without quotation marks).
#' @param date_field Specify the field name which contains the date data, to be
#' plotted on x axis. The field name can be specified using non-standard
#' evaluation (i.e. without quotation marks).
#' @param facet_field Optional: Specify field name which contains a grouping/
#' faceting variable. SPC logic will be applied to each group separately, with
#' outputs combined. Currently accepts 1 variable only. The field name can be
#' specified using non-standard evaluation (i.e. without quotation marks).
#' @param rebase Specify a date vector of dates when to rebase, or, if
#' `facet_field` is set, a named list of date vectors of when to rebase. Each
#' item in the list should be named after the facet you wish to rebase. See
#' [ptd_rebase()].
#' @param fix_after_n_points Specify a number points after which to fix SPC
#' calculations.
#' @param improvement_direction Specify whether process improvement is
#' represented by an increase or decrease in measured variable, or is neutral.
#' Accepted values are 'increase' for increase as improvement, 'decrease' for
#' decrease as improvement, and 'neutral' where neither direction represents
#' an improvement. Defaults to 'increase'.
#' @param target Specify a single value, which will apply the same target to
#' every facet of an SPC chart, or named values of targets, where the names
#' correspond to the names of the facets, in order to have different targets
#' for each facet. See [ptd_target()].
#' @param trajectory Specify a field name which contains a trajectory value.
#' The field name can be specified using non-standard evaluation (i.e. without
#' quotation marks).
#' @param screen_outliers Whether to screen for outliers when calculating the
#' control limits. Defaults to `TRUE`.
#'
#' @export ptd_spc
#'
#' @returns An object of type `ptd_spc_df`. This is a data.frame which can be
#' further manipulated like any other data.frame. The default print() method
#' for `ptd_spc_df` is to call [ptd_create_ggplot()], displaying the plot. If
#' you would like to get the data.frame, call as_tibble() or as.data.frame()
#' on the object.
#'
#' @importFrom rlang .data
#' @examples
#' library(NHSRdatasets)
#' library(dplyr)
#' data("ae_attendances")
#'
#' # Pick a trust at random to look at their data for two years
#' trust1 <- subset(ae_attendances, org_code == "RJZ" & type == 1)
#'
#' # Basic chart with improvement direction decreasing
#' ptd_spc(trust1,
#' value_field = breaches, date_field = period,
#' improvement_direction = "decrease"
#' )
#'
#' # Pick a few trust, and plot individually using facet
#' # Also set the x-axis scale to vary for each and date groups to 3 months
#' orgs <- c("RAS", "RJZ", "RR1", "RJC", "RQ1")
#' trusts4 <- filter(ae_attendances, org_code %in% orgs, type == 1)
#'
#' s <- ptd_spc(trusts4,
#' value_field = breaches, date_field = period, facet_field = org_code,
#' improvement_direction = "decrease"
#' )
#' plot(s, fixed_y_axis_multiple = FALSE, x_axis_breaks = "3 months")
#'
#' # Save the first chart as an object this time then alter the ggplot theme
#' my_spc <- ptd_spc(trust1,
#' value_field = "breaches", date_field = "period",
#' improvement_direction = "decrease"
#' )
#'
#' plot(my_spc) + ggplot2::theme_classic()
ptd_spc <- function(
.data,
value_field,
date_field,
facet_field,
rebase = ptd_rebase(),
fix_after_n_points = NULL,
improvement_direction = "increase",
target = ptd_target(),
trajectory,
screen_outliers = TRUE) {
UseMethod("ptd_spc")
}
#' @export
ptd_spc.SharedData <- function(.data, ...) { # Exclude Linting
key <- .data$key()
set <- .data$groupName()
# call the main data frame method
.data <- ptd_spc.data.frame(.data$origData(), ...)
# now update the returned data for {crosstalk}
.data[[".crossTalkKey"]] <- key
structure(.data, set = set)
}
#' @export
ptd_spc.data.frame <- function(.data, # Exclude Linting
value_field,
date_field,
facet_field,
rebase = ptd_rebase(),
fix_after_n_points = NULL,
improvement_direction = "increase",
target = ptd_target(),
trajectory,
screen_outliers = TRUE) {
value_field <- rlang::quo_name(rlang::enquo(value_field))
date_field <- rlang::quo_name(rlang::enquo(date_field))
facet_field <- if (!missing(facet_field)) rlang::quo_name(rlang::enquo(facet_field))
trajectory <- if (!missing(trajectory)) rlang::quo_name(rlang::enquo(trajectory))
# validate all inputs. Validation problems will generate an error and stop code execution.
options <- ptd_spc_options(
value_field, date_field, facet_field, rebase, fix_after_n_points,
improvement_direction, target, trajectory, screen_outliers
)
ptd_validate_spc_options(options, .data)
# promote value field to a double (in case it's an integer)
.data[[value_field]] <- as.double(.data[[value_field]])
.data[[date_field]] <- to_datetime(.data[[date_field]])
# add rebase column
.data <- ptd_add_rebase_column(.data, date_field, facet_field, rebase)
# Declare improvement direction as integer
improvement_direction <- switch(options$improvement_direction,
"increase" = 1,
"neutral" = 0,
"decrease" = -1
)
df <- .data %>%
ptd_spc_standard(options) %>%
ptd_calculate_point_type(improvement_direction) %>%
ptd_add_short_group_warnings() %>%
# add target column: we need to have called ptd_spc_standard to add the facet field
ptd_add_target_column(target)
structure(
df,
class = c("ptd_spc_df", class(df)),
options = options
)
}
#' @export
print.ptd_spc_df <- function(x, ...) {
p <- plot(x, ...)
print(p)
}
#' @export
summary.ptd_spc_df <- function(object, ...) {
options <- attr(object, "options")
print(options)
point_type <- object %>%
dplyr::group_by(.data$f, .data$rebase_group) |>
dplyr::filter(.data$x == max(.data$x)) |>
dplyr::select("f", "rebase_group", variation_type = "point_type")
s <- object %>%
dplyr::group_by(.data$f, .data$rebase_group) %>%
dplyr::summarise(
across(c("mean_col", "lpl", "upl"), dplyr::first),
n = dplyr::n(),
common_cause = .data$n - sum(.data$special_cause_flag),
special_cause_improvement = sum(.data$point_type == "special_cause_improvement"),
special_cause_concern = sum(.data$point_type == "special_cause_concern"),
.groups = "drop"
) |>
dplyr::inner_join(point_type, by = c("f", "rebase_group"))
if (!is.null(options$target)) {
at <- ptd_calculate_assurance_type(object)
s <- s %>%
dplyr::inner_join(at, by = "f") %>%
dplyr::group_by(.data$f) %>%
dplyr::mutate(
assurance_type = ifelse(
.data$rebase_group == max(.data$rebase_group),
.data$assurance_type,
as.character(NA)
)
) %>%
dplyr::ungroup()
}
if (is.null(options$facet_field)) {
s <- dplyr::select(s, -"f")
}
if (is.null(options$rebase)) {
s <- dplyr::select(s, -"rebase_group")
}
s
}