Skip to content

Commit e52c594

Browse files
authored
Merge pull request #4000 from anshul23102/fix/covariate-functions-null-return
fix(db): return data unchanged instead of NULL when no covariates found
2 parents 6817fac + bcf30c6 commit e52c594

4 files changed

Lines changed: 118 additions & 24 deletions

File tree

base/db/NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Fixed
44

5+
* `arrhenius.scaling.traits()`: previously returned `NULL` when no temperature covariates were found, crashing `query.trait.data()` with `argument is of length zero`. The function now drops rows that lack a measurement temperature covariate and emits a `logger.warn()` with the row count. If no observations have any temperature covariate, an empty data frame (zero rows, same columns) is returned. The `missing.temp` argument is retained for backward compatibility but is no longer applied.
6+
* `filter_sunleaf_traits()`: returned `NULL` instead of `data` unchanged when no `canopy_layer` covariate was found. Now returns the input data frame unmodified in that case, consistent with the more standardised measurement protocol for sun-leaf traits.
57
* `query.trait.data()`: the `warning()` call for missing trait data was placed after `return(NA)` and therefore never fired. Moved before the return and changed to `logger.warn()` for consistency with the rest of the codebase.
68

79
* Refactored `convert.input()` internals into smaller, and hopefully more testable, chunks. No user-visible changes expected.

base/db/R/covariate.functions.R

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,37 +53,57 @@ query.covariates <- function(trait.ids, con = NULL, ...){
5353
##--------------------------------------------------------------------------------------------------#
5454
##' Apply Arrhenius scaling to 25 degC for temperature-dependent traits
5555
##'
56+
##' Rows whose measurement temperature covariate is missing are dropped with a
57+
##' warning rather than silently assigned a default temperature. If no
58+
##' temperature covariate is recorded for any observation the function returns
59+
##' an empty data frame (zero rows, same columns as \code{data}).
60+
##'
5661
##' @param data data frame of data to scale, as returned by query.data()
5762
##' @param covariates data frame of covariates, as returned by query.covariates().
58-
##' Note that data with no matching covariates will be unchanged.
5963
##' @param temp.covariates names of covariates used to adjust for temperature;
6064
##' if length > 1, order matters (first will be used preferentially)
61-
##' @param new.temp the reference temperature for the scaled traits. Curerntly 25 degC
62-
##' @param missing.temp the temperature assumed for traits with no covariate found. Curerntly 25 degC
65+
##' @param new.temp the reference temperature for the scaled traits. Currently 25 degC
66+
##' @param missing.temp no longer used; kept for backward compatibility only
6367
##' @author Carl Davidson, David LeBauer, Ryan Kelly
6468
arrhenius.scaling.traits <- function(data, covariates, temp.covariates, new.temp = 25, missing.temp = 25){
6569
# Select covariates that match temp.covariates
66-
covariates <- covariates[covariates$name %in% temp.covariates,]
67-
68-
if(nrow(covariates)>0) {
70+
covariates <- covariates[covariates$name %in% temp.covariates, ]
71+
72+
if (nrow(covariates) > 0) {
6973
# Sort covariates in order of priority
7074
covariates <- do.call(rbind,
71-
lapply(temp.covariates, function(temp.covariate) covariates[covariates$name == temp.covariate, ])
75+
lapply(temp.covariates, function(tc) covariates[covariates$name == tc, ])
7276
)
73-
77+
7478
data <- append.covariate(data, 'temp', covariates)
75-
76-
# Assign default value for traits with no covariates
77-
data$temp[is.na(data$temp)] <- missing.temp
78-
79+
80+
# Drop rows that have no temperature covariate recorded
81+
n_missing <- sum(is.na(data$temp))
82+
if (n_missing > 0) {
83+
PEcAn.logger::logger.warn(
84+
n_missing, "row(s) of trait data dropped due to missing temperature covariate."
85+
)
86+
data <- data[!is.na(data$temp), ]
87+
}
88+
89+
# Remove temporary covariate column before returning if nothing survived
90+
if (nrow(data) == 0) {
91+
return(data[, colnames(data) != 'temp', drop = FALSE])
92+
}
93+
7994
# Scale traits
80-
data$mean <- PEcAn.utils::arrhenius.scaling(observed.value = data$mean, old.temp = data$temp, new.temp=new.temp)
81-
data$stat <- PEcAn.utils::arrhenius.scaling(observed.value = data$stat, old.temp = data$temp, new.temp=new.temp)
82-
83-
#remove temporary covariate column.
84-
data<-data[,colnames(data)!='temp']
95+
data$mean <- PEcAn.utils::arrhenius.scaling(observed.value = data$mean, old.temp = data$temp, new.temp = new.temp)
96+
data$stat <- PEcAn.utils::arrhenius.scaling(observed.value = data$stat, old.temp = data$temp, new.temp = new.temp)
97+
98+
# Remove temporary covariate column
99+
data <- data[, colnames(data) != 'temp', drop = FALSE]
85100
} else {
86-
data <- NULL
101+
# No temperature covariates found for any observation; drop all rows.
102+
n_rows <- nrow(data)
103+
PEcAn.logger::logger.warn(
104+
n_rows, "row(s) of trait data dropped: no temperature covariate found for any observation."
105+
)
106+
data <- data[0, , drop = FALSE]
87107
}
88108
return(data)
89109
}
@@ -108,7 +128,7 @@ filter_sunleaf_traits <- function(data, covariates){
108128
# remove temporary covariate column
109129
data <- data[,colnames(data)!='canopy_layer']
110130
} else {
111-
data <- NULL
131+
# No canopy_layer covariate found; return data unchanged rather than NULL.
112132
}
113133
return(data)
114134
}

base/db/man/arrhenius.scaling.traits.Rd

Lines changed: 7 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

base/db/tests/testthat/test.covariate.functions.R

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,73 @@
1+
## Helper: minimal trait data frame used across several tests
2+
make_trait_data <- function(ids = 1:4) {
3+
data.frame(
4+
id = ids,
5+
mean = c(10, 20, 30, 40)[seq_along(ids)],
6+
stat = c(1, 2, 3, 4)[seq_along(ids)],
7+
n = rep(5L, length(ids)),
8+
stringsAsFactors = FALSE
9+
)
10+
}
11+
12+
## Helper: minimal covariates data frame
13+
make_temp_covariates <- function(trait_ids, temps) {
14+
data.frame(
15+
trait_id = trait_ids,
16+
level = temps,
17+
name = rep("Tleaf", length(trait_ids)),
18+
stringsAsFactors = FALSE
19+
)
20+
}
21+
22+
test_that("arrhenius.scaling.traits scales all rows when every row has a covariate", {
23+
data <- make_trait_data(1:3)
24+
covariates <- make_temp_covariates(1:3, c(20, 25, 30))
25+
26+
result <- arrhenius.scaling.traits(data, covariates, temp.covariates = "Tleaf")
27+
28+
expect_equal(nrow(result), 3)
29+
expect_false("temp" %in% colnames(result))
30+
# Row measured at 25 degC should be unchanged after scaling to 25 degC
31+
expect_equal(result$mean[2], data$mean[2])
32+
})
33+
34+
test_that("arrhenius.scaling.traits drops rows with missing covariate and warns", {
35+
data <- make_trait_data(1:4)
36+
# Only rows 1 and 3 have covariates
37+
covariates <- make_temp_covariates(c(1, 3), c(20, 30))
38+
39+
expect_warning(
40+
result <- arrhenius.scaling.traits(data, covariates, temp.covariates = "Tleaf"),
41+
regexp = NA # warning comes via logger.warn, not warning(); just confirm no error
42+
)
43+
# Rows 2 and 4 (no covariate) must be dropped
44+
expect_equal(nrow(result), 2)
45+
expect_true(all(result$id %in% c(1, 3)))
46+
expect_false("temp" %in% colnames(result))
47+
})
48+
49+
test_that("arrhenius.scaling.traits returns empty data frame when no covariates at all", {
50+
data <- make_trait_data(1:3)
51+
covariates <- data.frame(trait_id = integer(0), level = numeric(0),
52+
name = character(0), stringsAsFactors = FALSE)
53+
54+
result <- arrhenius.scaling.traits(data, covariates, temp.covariates = "Tleaf")
55+
56+
expect_equal(nrow(result), 0)
57+
# Columns should be preserved
58+
expect_true(all(c("id", "mean", "stat", "n") %in% colnames(result)))
59+
})
60+
61+
test_that("filter_sunleaf_traits returns data unchanged when covariates is empty", {
62+
data <- make_trait_data(1:3)
63+
covariates <- data.frame(trait_id = integer(0), level = numeric(0),
64+
name = character(0), stringsAsFactors = FALSE)
65+
66+
result <- filter_sunleaf_traits(data, covariates)
67+
68+
expect_equal(result, data)
69+
})
70+
171
test_that("`append.covariate` able to append new column for covariates in given data based on id", {
272
data <- data.frame(
373
id = c(1, 2, 3, 4),

0 commit comments

Comments
 (0)