22# #'
33# #' @param metric_dat dataframe to plot, with at least columns `time`, `model`, `obvs`
44# #' @param var variable name, used as plot title
5+ # #' @param unit measurement unit for the variable, added to the y-axis label; NULL to omit
56# #' @param filename path to save plot, or NA to not save
67# #' @param draw.plot logical: Return the plot object?
78# #'
89# #' @author Betsy Cowdery
910# #' @export
10- metric_residual_plot <- function (metric_dat , var , filename = NA , draw.plot = is.na(filename )) {
11+ metric_residual_plot <- function (metric_dat , var , unit = NULL , filename = NA , draw.plot = is.na(filename )) {
1112 PEcAn.logger :: logger.info(" Metric: Residual Plot" )
1213
13- metric_dat $ time <- lubridate :: year(as.Date(as.character(metric_dat $ time ), format = " %Y" ))
14- metric_dat $ diff <- abs(metric_dat $ model - metric_dat $ obvs )
15- metric_dat $ zeros <- rep(0 , length(metric_dat $ time ))
14+ metric_dat <- as.data.frame(metric_dat )
1615
17- p <- ggplot2 :: ggplot(data = metric_dat , ggplot2 :: aes(x = .data $ time ))
18- p <- p + ggplot2 :: geom_path(ggplot2 :: aes(y = .data $ zeros ), colour = " #666666" , size = 2 , linetype = 2 , lineend = " round" )
19- p <- p + ggplot2 :: geom_point(ggplot2 :: aes(y = .data $ diff ), size = 4 , colour = " #619CFF" )
20- p <- p + ggplot2 :: labs(title = var , x = " years" , y = " abs(model - observation)" )
16+ if (! " time" %in% colnames(metric_dat )) {
17+ metric_dat $ time <- seq_len(nrow(metric_dat ))
18+ } else {
19+ date.time <- try(as.Date(as.character(metric_dat $ time )), silent = TRUE )
20+ if (! inherits(date.time , " try-error" ) && ! all(is.na(date.time ))) {
21+ metric_dat $ time <- date.time
22+ }
23+ }
24+
25+ # Calculate residuals (Model - Observation)
26+ metric_dat $ diff <- metric_dat $ model - metric_dat $ obvs
27+
28+ is_multi_site <- " site" %in% colnames(metric_dat ) && length(unique(metric_dat $ site )) > 1
29+ is_multi_var <- " variable" %in% colnames(metric_dat ) && length(unique(metric_dat $ variable )) > 1
30+
31+ if (! " site" %in% colnames(metric_dat )) metric_dat $ site <- " All"
32+ if (! " variable" %in% colnames(metric_dat )) metric_dat $ variable <- var
33+
34+ facet_groups <- split(metric_dat , list (metric_dat $ site , metric_dat $ variable ), drop = TRUE )
35+
36+ fit_results <- lapply(names(facet_groups ), function (g ) {
37+ sub_dat <- facet_groups [[g ]]
38+
39+ time_num <- as.numeric(sub_dat $ time )
40+ if (inherits(sub_dat $ time , " POSIXt" )) {
41+ time_num <- time_num / 86400
42+ }
43+ time_num <- time_num - min(time_num , na.rm = TRUE )
44+ sub_dat $ time_num <- time_num
45+
46+ fit <- try(stats :: lm(diff ~ time_num , data = sub_dat ), silent = TRUE )
47+ if (! inherits(fit , " try-error" ) && length(stats :: coef(fit )) == 2 && ! is.na(stats :: coef(fit )[2 ])) {
48+ intercept <- stats :: coef(fit )[1 ]
49+ slope <- stats :: coef(fit )[2 ]
50+ label_str <- sprintf(" Intercept: %.3f\n Slope: %.4f / day" , intercept , slope )
51+ sub_dat $ trend <- stats :: fitted(fit )
52+ } else {
53+ label_str <- " Trend: N/A"
54+ sub_dat $ trend <- NA_real_
55+ }
56+
57+ annot <- data.frame (
58+ site = sub_dat $ site [1 ],
59+ variable = sub_dat $ variable [1 ],
60+ label = label_str
61+ )
62+
63+ list (sub_dat = sub_dat , annot = annot )
64+ })
65+
66+ metric_dat <- do.call(rbind , lapply(fit_results , `[[` , " sub_dat" ))
67+ annotations <- do.call(rbind , lapply(fit_results , `[[` , " annot" ))
68+
69+ ylab <- if (is.null(unit )) " residual (model - obs)" else sprintf(" residual (%s)" , unit )
70+
71+ p <- ggplot2 :: ggplot(data = metric_dat , ggplot2 :: aes(x = .data $ time , y = .data $ diff )) +
72+ ggplot2 :: geom_hline(yintercept = 0 , colour = " #666666" , linewidth = 1 , linetype = 2 ) +
73+ ggplot2 :: geom_point(size = 2 , alpha = 0.7 , colour = " #619CFF" ) +
74+ ggplot2 :: geom_line(ggplot2 :: aes(y = .data $ trend ), colour = " #FF3333" , linetype = " dashed" , na.rm = TRUE ) +
75+ ggplot2 :: labs(title = var , x = " time" , y = ylab , colour = NULL , fill = NULL ) +
76+ ggplot2 :: theme_minimal(base_size = 12 )
77+
78+ # Add per-panel annotations
79+ p <- p + ggplot2 :: geom_label(
80+ data = annotations ,
81+ ggplot2 :: aes(x = - Inf , y = Inf , label = .data $ label ),
82+ hjust = - 0.05 , vjust = 1.1 ,
83+ inherit.aes = FALSE ,
84+ alpha = 0.8
85+ )
86+
87+ # Add facets if applicable
88+ if (is_multi_site && is_multi_var ) {
89+ p <- p + ggplot2 :: facet_wrap(~ variable + site , scales = " free_y" )
90+ } else if (is_multi_site ) {
91+ p <- p + ggplot2 :: facet_wrap(~ site , scales = " free_y" )
92+ } else if (is_multi_var ) {
93+ p <- p + ggplot2 :: facet_wrap(~ variable , scales = " free_y" )
94+ }
2195
2296 if (! is.na(filename )) {
2397 grDevices :: pdf(filename , width = 10 , height = 6 )
24- plot (p )
98+ print (p )
2599 grDevices :: dev.off()
26100 }
27101
28102 if (draw.plot ) {
29103 return (p )
30104 }
31- } # metric_residual_plot
105+ invisible (p )
106+ } # metric_residual_plot
0 commit comments