diff --git a/NAMESPACE b/NAMESPACE index 0ce2517..73d973a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,6 +19,7 @@ export(process_tdl_cycle_erml) export(read_gm_table) export(read_licor_file) export(read_tdl_file) +export(remove_points) export(smooth_tdl_data) export(specify_variables) diff --git a/R/remove_points.R b/R/remove_points.R new file mode 100644 index 0000000..ad32c3f --- /dev/null +++ b/R/remove_points.R @@ -0,0 +1,45 @@ +remove_points <- function(exdf_obj, ...) { + if (!is.exdf(exdf_obj)) { + stop("remove_points requires an exdf object") + } + + # Create a list of the optional input arguments + arg_list <- list(...) + + # Make sure the optional input arguments are all lists + list_check <- sapply(arg_list, function(x) {!is.list(x)}) + if (any(list_check)) { + stop("optional arguments to remove_points must be lists") + } + + # Make sure the optional input arguments have names + name_check <- sapply(arg_list, function(x) {is.null(names(x))}) + if (any(name_check)) { + stop("optional arguments to remove_points must have names") + } + + # Go through each set of conditions to remove the desired points + for (point_description in arg_list) { + # Make sure the exdf object contains the specified columns + required_columns <- + lapply(point_description, function(x) {return(NA)}) + + check_required_columns(exdf_obj, required_columns) + + # Initialize the logical vector of points to keep + points_to_keep <- rep.int(FALSE, nrow(exdf_obj)) + + # Apply all the conditions, adding back any points that don't meet the + # criteria + for (i in seq_along(point_description)) { + name <- names(point_description)[i] + condition <- point_description[[i]] + points_to_keep <- points_to_keep | !(exdf_obj[ , name] %in% condition) + } + + # Truncate the exdf to just the points that don't meet the condition + exdf_obj <- exdf_obj[points_to_keep, , TRUE] + } + + return(exdf_obj) +} diff --git a/man/remove_points.Rd b/man/remove_points.Rd new file mode 100644 index 0000000..66d291c --- /dev/null +++ b/man/remove_points.Rd @@ -0,0 +1,58 @@ +\name{remove_points} + +\alias{remove_points} + +\title{Remove specific points from an exdf object} + +\description{ + Removes all points from an \code{exdf} object that satisfy a set of + conditions. +} + +\usage{ + remove_points(exdf_obj, \dots) +} + +\arguments{ + \item{exdf_obj}{An \code{exdf} object.} + + \item{\dots}{ + Each optional argument should be a list of named elements that specify + points to be removed from \code{exdf_obj}. For example, + \code{list(species = 'soybean', plot = c('1a', '1b'))} specifies the set of + points where (1) \code{species} is \code{'soybean'} and (2) \code{plot} is + \code{'1a'} or \code{'1b'}. + } +} + +\value{ + An \code{exdf} object formed from \code{exdf_obj} by removing all rows that + meet the conditions specified by the optional input arguments. +} + +\seealso{\code{\link{exdf}}} + +\examples{ +# Create an exdf object by reading a Licor Excel file +licor_file <- read_licor_file( + system.file("extdata", "ball_berry_1.xlsx", package = "PhotoGEA"), + c(3, 5, 7, 9, 11, 13), 14, 15, 16, 17, 'time' +) + +# Print the number of points in the data set +nrow(licor_file) + +# Remove the following: +# - All points where `obs` is 28 (1 point) +# - All points where `species` is `soybean` and `plot` is `1a` or `1b` (14 points) +licor_file <- remove_points( + licor_file, + list(obs = 28), + list(species = 'soybean', plot = c('1a', '1b')) +) + +# There should now be 15 fewer points remaining in the data set +nrow(licor_file) +} + +\concept{exdf} diff --git a/vignettes/analyzing_ball_berry_data.Rmd b/vignettes/analyzing_ball_berry_data.Rmd index cd357f6..91e11db 100644 --- a/vignettes/analyzing_ball_berry_data.Rmd +++ b/vignettes/analyzing_ball_berry_data.Rmd @@ -310,6 +310,46 @@ so it is reasonable to expect that the measurements represent true steady-state values. Considering this, all of these curves are acceptable based on the CO~2~ plots. +### Stability + +```{r plot_stability} +xyplot( + `A:OK` + `gsw:OK` + Stable ~ Qin | curve_identifier, + data = licor_data$main_data, + type = 'b', + pch = 16, + auto = TRUE, + grid = TRUE, + xlim = c(0, 2200), + xlab = paste0('Incident PPFD (', licor_data$units$Qin, ')') +) +``` + +When measuring response curves with a Licor, it is possible to specify stability +criteria for each point in addition to minimum and maximum wait times. In other +words, once the set point for the driving variable is changed, the machine waits +until the stability criteria are met; there is a minimum waiting period, and +also a maximum to prevent the machine from waiting for too long. These stability +criteria are especially important for Ball-Berry curves, since the stomata may +take a long time to reach steady state. + +When these curves were measured, stability criteria were supplied for the net +assimilation rate `A` and the stomatal conductance `gsw`. The stability status +for each was stored in the log file because the appropriate logging option for +stability was set. Now, for each point, it is possible to check whether +stability was achieved or whether the point was logged because the maximum +waiting period had been met. If the maximum waiting period is reached and the +plant has still not stabilized, the data point may be unreliable, so it is very +important to check this information. + +In the plot, `A:OK` indicates whether `A` was stable (0 for no, 1 for yes), +`gsw:OK` indicates whether `gsw` was stable (0 for no, 1 for yes), and +`Stable` indicates the total number of stability conditions that were met. So, +we are looking for points where `Stable` is 2. Otherwise, we can check the other +traces to see whether `A` or `gsw` was unstable. Here it looks like many of the +high light points were not stable, and it may be a good idea to remove them +before proceeding with the Ball-Berry fitting. + ### Light-Response Curves ```{r plot_assimilation} @@ -340,10 +380,31 @@ likely a byproduct of the noise that was intentionally added to the true measured data (see [The Data]). Nevertheless, it may be a good idea to remove them before proceeding with the Ball-Berry fitting. -## Removing Bad Curves +## Cleaning the Licor Data + +While checking over the plots in [Qualitative Checks], two issues were noticed: +(1) some points were logged before stability was achieved and (2) some of the +curves look abnormal. In this section, we will demonstrate how to remove the +unstable points and the weird curves. + +The following command will keep only the points where `Stable` is exactly 2; +this condition means that all of the stability criteria were satisfied. +Sometimes, following this procedure, a curve will have very few stable points +remaining; it is usually a good idea to automatically exclude any curve with +fewer than three stable points. + +```{r remove_unstable_points} +# Only keep points where stability was achieved +licor_data <- licor_data[licor_data[, 'Stable'] == 2, , TRUE] + +# Remove any curves that have fewer than three remaining points +npts <- by(licor_data, licor_data[, 'curve_identifier'], nrow) +ids_to_keep <- names(npts[npts > 2]) +licor_data <- licor_data[licor_data[, 'curve_identifier'] %in% ids_to_keep, , TRUE] +``` Since we have identified a few curves that may not be acceptable for Ball-Berry -fitting, we can remove them as follows: +fitting, we can remove them via the `remove_points` function from `PhotoGEA`: ```{r remove_curves} # Define a list of curves to remove from the data set @@ -355,9 +416,13 @@ curves_to_remove <- c( # Remove them licor_data <- - licor_data[!licor_data[, 'curve_identifier'] %in% curves_to_remove, , TRUE] + remove_points(licor_data, list(curve_identifier = curves_to_remove)) ``` +Note that `remove_points` can also be used to exclude individual points instead +of entire curves; see its help entry by typing `?remove_points` for more +information. + # Fitting Licor Data Now that we have checked the data quality, we are ready to perform the fitting. @@ -554,6 +619,8 @@ file to initialize your own script. <> +<> + <> <> diff --git a/vignettes/analyzing_tdl_data.Rmd b/vignettes/analyzing_tdl_data.Rmd index 6704d00..7f3ecf4 100644 --- a/vignettes/analyzing_tdl_data.Rmd +++ b/vignettes/analyzing_tdl_data.Rmd @@ -349,9 +349,10 @@ system, and there is evidence that the calibration may be compromised during the spike, it is probably a good idea to remove the TDL cycles where the ^12^C signal from valve 26 suddenly becomes large. -Fortunately, this is simple to do in R. The following illustrates one way to -"clean" the data by removing the unreliable TDL cycles; here, we actually remove -two regions of the data that appear to be suspicious: +Fortunately, this is simple to do using the `remove_points` function from +`PhotoGEA`. The following illustrates how to "clean" the data by removing the +unreliable TDL cycles; here, we actually remove two regions of the data that +appear to be suspicious: ```{r removing_cycles} # Define a vector of cycle numbers that should be removed @@ -362,7 +363,7 @@ tdl_cycles_to_remove <- c( # Remove them tdl_files_clean <- - tdl_files[!tdl_files[, 'cycle_num'] %in% tdl_cycles_to_remove, , TRUE] + remove_points(tdl_files, list(cycle_num = tdl_cycles_to_remove)) ``` ## Smoothing the TDL Data