Skip to content

Commit b96f0f0

Browse files
authored
Merge pull request #4032 from ayushman1210/gsoc/issue-4027
Add testthat coverage for remaining numeric metrics
2 parents 67dded7 + 70403c4 commit b96f0f0

6 files changed

Lines changed: 307 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
library(testthat)
2+
3+
test_that("load_csv properly reads CSV files", {
4+
5+
# Create a temporary CSV file
6+
csv_file <- tempfile(fileext = ".csv")
7+
df_input <- data.frame(
8+
time = c("2000-01-01", "2000-01-02"),
9+
GPP = c(10.5, 11.2),
10+
NEE = c(-1.1, -2.2)
11+
)
12+
write.csv(df_input, csv_file, row.names = FALSE)
13+
14+
# Define format list (header = 1 means first row is header)
15+
format_list <- list(header = 1, skip = 0, na.strings = "NA")
16+
17+
# 1. Test loading all variables
18+
res_all <- load_csv(csv_file, format = format_list, site = NULL)
19+
expect_s3_class(res_all, "data.frame")
20+
expect_equal(nrow(res_all), 2)
21+
expect_equal(ncol(res_all), 3)
22+
expect_true(all(c("time", "GPP", "NEE") %in% names(res_all)))
23+
24+
# 2. Test loading subset of variables
25+
res_sub <- load_csv(csv_file, format = format_list, site = NULL, vars = c("GPP"))
26+
expect_s3_class(res_sub, "data.frame")
27+
expect_equal(ncol(res_sub), 1)
28+
expect_true("GPP" %in% names(res_sub))
29+
expect_false("NEE" %in% names(res_sub))
30+
31+
# Cleanup
32+
unlink(csv_file)
33+
})
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
library(testthat)
2+
3+
test_that("load_data correctly coordinates data loading and unit conversion", {
4+
5+
# 1. Setup mock CSV data (AmeriFlux style format)
6+
csv_file <- tempfile(fileext = ".csv")
7+
df_input <- data.frame(
8+
TIMESTAMP_START = c("200001010000", "200001010030"),
9+
temp_c = c(20.0, 21.0) # Celsius
10+
)
11+
write.csv(df_input, csv_file, row.names = FALSE)
12+
13+
# 2. Setup format config
14+
vars_df <- data.frame(
15+
variable_id = c(1, 2),
16+
input_name = c("TIMESTAMP_START", "temp_c"),
17+
input_units = c("NA", "Celsius"),
18+
pecan_name = c("time", "AirT"),
19+
pecan_units = c("NA", "K"),
20+
bety_name = c("time", "AirT"),
21+
storage_type = c("%Y%m%d%H%M", "numeric"),
22+
stringsAsFactors = FALSE
23+
)
24+
25+
format_list <- list(
26+
mimetype = "text/csv",
27+
file_name = "csv",
28+
header = 1,
29+
skip = 0,
30+
na.strings = "NA",
31+
time.row = 1,
32+
vars = vars_df
33+
)
34+
35+
site_list <- list(id = 1, lat = 40, lon = -80, time_zone = "UTC")
36+
37+
# 3. Test execution
38+
# load_data calls load_csv, renames columns, converts units (Celsius -> K), and parses time
39+
res <- load_data(data.path = csv_file, format = format_list, site = site_list)
40+
41+
# Assertions
42+
expect_s3_class(res, "data.frame")
43+
expect_equal(nrow(res), 2)
44+
expect_true(all(c("time", "AirT", "posix") %in% names(res))) # legacy creates posix
45+
46+
# Check unit conversion (Celsius to Kelvin: + 273.15)
47+
expect_equal(res$AirT[1], 293.15)
48+
expect_equal(res$AirT[2], 294.15)
49+
50+
# Check time parsing (should match AmeriFlux 30-min intervals)
51+
expected_time <- as.POSIXct(c("2000-01-01 00:00:00", "2000-01-01 00:30:00"), tz = "UTC")
52+
expect_equal(res$posix, expected_time)
53+
54+
# Cleanup
55+
unlink(csv_file)
56+
})
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
library(testthat)
2+
3+
test_that("load_x_netcdf properly loads NetCDF and parses time", {
4+
skip_if_not_installed("ncdf4")
5+
6+
# Create a temporary NetCDF file
7+
nc_file <- tempfile(fileext = ".nc")
8+
9+
# Define dimensions
10+
# Time: seconds since 2000-01-01 00:00:00
11+
time_vals <- c(0, 3600, 7200) # 3 hours
12+
dim_time <- ncdf4::ncdim_def("time", "seconds since 2000-01-01 00:00:00", time_vals)
13+
14+
# Define variables
15+
var_gpp <- ncdf4::ncvar_def("GPP", "kg m-2 s-1", dim_time, missval = NA_real_, prec = "double")
16+
var_nee <- ncdf4::ncvar_def("NEE", "kg m-2 s-1", dim_time, missval = NA_real_, prec = "double")
17+
18+
# Create the file
19+
nc_new <- ncdf4::nc_create(nc_file, list(var_gpp, var_nee))
20+
21+
# Put data
22+
ncdf4::ncvar_put(nc_new, var_gpp, c(1.1, 2.2, NA)) # One missing value
23+
ncdf4::ncvar_put(nc_new, var_nee, c(10, 20, 30))
24+
25+
ncdf4::nc_close(nc_new)
26+
27+
# Test loading
28+
format_list <- list(na.strings = c("-9999", "-9999.0"))
29+
res <- load_x_netcdf(nc_file, format = format_list, site = NULL, vars = c("GPP", "NEE"))
30+
31+
expect_s3_class(res, "data.frame")
32+
expect_equal(nrow(res), 3)
33+
expect_equal(ncol(res), 3) # GPP, NEE, and posix/time (depending on branch version)
34+
35+
# Check variable extraction
36+
expect_equal(res$GPP[1], 1.1)
37+
expect_equal(res$GPP[2], 2.2)
38+
expect_true(is.na(res$GPP[3])) # Check NA replacement
39+
40+
expect_equal(res$NEE, c(10, 20, 30))
41+
42+
# Check time parsing
43+
time_col <- if ("posix" %in% names(res)) "posix" else "time"
44+
expect_true(time_col %in% names(res))
45+
expect_s3_class(res[[time_col]], "POSIXct")
46+
47+
# Check actual time values
48+
expected_time <- as.POSIXct(c("2000-01-01 00:00:00", "2000-01-01 01:00:00", "2000-01-01 02:00:00"), tz = "UTC")
49+
expect_equal(res[[time_col]], expected_time)
50+
51+
# Cleanup
52+
unlink(nc_file)
53+
})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
library(testthat)
2+
3+
test_that("load_rds properly reads RDS files", {
4+
5+
# Create a temporary RDS file
6+
rds_file <- tempfile(fileext = ".rds")
7+
df_input <- data.frame(
8+
time = c("2000-01-01", "2000-01-02"),
9+
GPP = c(10.5, 11.2),
10+
NEE = c(-1.1, -2.2)
11+
)
12+
saveRDS(df_input, rds_file)
13+
14+
# 1. Test loading all variables
15+
res_all <- load_rds(rds_file, format = NULL, site = NULL)
16+
expect_s3_class(res_all, "data.frame")
17+
expect_equal(nrow(res_all), 2)
18+
expect_equal(ncol(res_all), 3)
19+
expect_true(all(c("time", "GPP", "NEE") %in% names(res_all)))
20+
21+
# 2. Test loading subset of variables
22+
res_sub <- load_rds(rds_file, format = NULL, site = NULL, vars = c("GPP"))
23+
expect_s3_class(res_sub, "data.frame")
24+
expect_equal(ncol(res_sub), 1)
25+
expect_true("GPP" %in% names(res_sub))
26+
expect_false("NEE" %in% names(res_sub))
27+
28+
# Cleanup
29+
unlink(rds_file)
30+
})
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
library(testthat)
2+
3+
test_that("load_tab_separated_values properly reads TSV files", {
4+
5+
# Create a temporary TSV file
6+
tsv_file <- tempfile(fileext = ".tsv")
7+
df_input <- data.frame(
8+
time = c("2000-01-01", "2000-01-02"),
9+
GPP = c(10.5, 11.2),
10+
NEE = c(-1.1, -2.2)
11+
)
12+
write.table(df_input, tsv_file, row.names = FALSE, sep = "\t", quote = FALSE)
13+
14+
# Define format list (header = 1 means first row is header)
15+
format_list <- list(header = 1, skip = 0, na.strings = "NA")
16+
17+
# 1. Test loading all variables
18+
res_all <- load_tab_separated_values(tsv_file, format = format_list, site = NULL)
19+
expect_s3_class(res_all, "data.frame")
20+
expect_equal(nrow(res_all), 2)
21+
expect_equal(ncol(res_all), 3)
22+
expect_true(all(c("time", "GPP", "NEE") %in% names(res_all)))
23+
24+
# 2. Test loading subset of variables
25+
res_sub <- load_tab_separated_values(tsv_file, format = format_list, site = NULL, vars = c("NEE"))
26+
expect_s3_class(res_sub, "data.frame")
27+
expect_equal(ncol(res_sub), 1)
28+
expect_true("NEE" %in% names(res_sub))
29+
expect_false("GPP" %in% names(res_sub))
30+
31+
# Cleanup
32+
unlink(tsv_file)
33+
})
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Test file for remaining numeric metrics (Issue #4027)
2+
3+
library(testthat)
4+
5+
# 1. metric_MSE
6+
test_that("metric_MSE returns 0 for perfect predictions", {
7+
dat <- data.frame(model = c(1, 2, 3), obvs = c(1, 2, 3))
8+
expect_equal(metric_MSE(dat), 0)
9+
})
10+
11+
test_that("metric_MSE handles NA values correctly", {
12+
# na.rm = TRUE means the NA row is ignored
13+
dat <- data.frame(model = c(1, NA, 3), obvs = c(1, 2, 3))
14+
# remaining errors are 0 and 0. Mean squared error is 0.
15+
expect_equal(metric_MSE(dat), 0)
16+
})
17+
18+
test_that("metric_MSE returns correct known value", {
19+
dat <- data.frame(model = c(2, 4), obvs = c(1, 2))
20+
# Errors: -1, -2. Squared errors: 1, 4. Mean: 2.5
21+
expect_equal(metric_MSE(dat), 2.5)
22+
})
23+
24+
# 2. metric_AME
25+
test_that("metric_AME returns 0 for perfect predictions", {
26+
dat <- data.frame(model = c(1, 2, 3), obvs = c(1, 2, 3))
27+
expect_equal(metric_AME(dat), 0)
28+
})
29+
30+
test_that("metric_AME handles NA values correctly", {
31+
dat <- data.frame(model = c(1, NA, 10), obvs = c(2, 100, 4))
32+
# absolute errors: 1, NA, 6. Max is 6.
33+
expect_equal(metric_AME(dat), 6)
34+
})
35+
36+
test_that("metric_AME returns correct known value", {
37+
dat <- data.frame(model = c(2, 10), obvs = c(1, 2))
38+
# absolute errors: 1, 8. Max is 8.
39+
expect_equal(metric_AME(dat), 8)
40+
})
41+
42+
# 3. metric_PPMC
43+
test_that("metric_PPMC returns 1 for perfect linear relationship", {
44+
dat <- data.frame(model = c(1, 2, 3), obvs = c(1, 2, 3))
45+
expect_equal(metric_PPMC(dat), 1)
46+
})
47+
48+
test_that("metric_PPMC handles NA values correctly", {
49+
dat <- data.frame(model = c(1, 2, NA, 4), obvs = c(2, 4, 100, 8))
50+
# Uses pairwise.complete.obs, so the NA row is ignored. Remaining is perfectly linear.
51+
expect_equal(metric_PPMC(dat), 1)
52+
})
53+
54+
test_that("metric_PPMC returns correct known value", {
55+
# simple known correlation case
56+
dat <- data.frame(model = c(1, 2, 3), obvs = c(1, 3, 2))
57+
# Cor(c(1,2,3), c(1,3,2)) = 0.5
58+
expect_equal(metric_PPMC(dat), 0.5)
59+
})
60+
61+
# 4. metric_RAE
62+
test_that("metric_RAE returns 0 for perfect predictions", {
63+
dat <- data.frame(model = c(1, 2, 3), obvs = c(1, 2, 3))
64+
expect_equal(metric_RAE(dat), 0)
65+
})
66+
67+
test_that("metric_RAE handles NA values correctly", {
68+
dat <- data.frame(model = c(1, NA, 3, 4), obvs = c(1, 100, 3, 4))
69+
# Uses na.omit internally, remaining data is perfect prediction
70+
expect_equal(metric_RAE(dat), 0)
71+
})
72+
73+
test_that("metric_RAE returns correct known value", {
74+
dat <- data.frame(model = c(2, 4, 6), obvs = c(1, 2, 3))
75+
# obvs mean = 2
76+
# abs(obvs - mean(obvs)) = c(1, 0, 1), mean = 2/3
77+
# abs(obvs - model) = c(1, 2, 3), mean = 6/3 = 2
78+
# RAE = 2 / (2/3) = 3
79+
expect_equal(metric_RAE(dat), 3)
80+
})
81+
82+
# 5. metric_Frechet
83+
test_that("metric_Frechet returns 0 for perfect predictions", {
84+
skip_if_not_installed("SimilarityMeasures")
85+
dat <- data.frame(model = c(1, 2, 3), obvs = c(1, 2, 3))
86+
expect_equal(metric_Frechet(dat), 0)
87+
})
88+
89+
test_that("metric_Frechet handles NA values correctly", {
90+
skip_if_not_installed("SimilarityMeasures")
91+
dat <- data.frame(model = c(1, NA, 3), obvs = c(1, 100, 3))
92+
# Uses na.omit internally, remaining data is perfect
93+
expect_equal(metric_Frechet(dat), 0)
94+
})
95+
96+
test_that("metric_Frechet returns correct known value", {
97+
skip_if_not_installed("SimilarityMeasures")
98+
dat <- data.frame(model = c(1, 2), obvs = c(1, 3))
99+
# Frechet distance between matrix(c(1,3)) and matrix(c(1,2))
100+
# Distance is max(|1-1|, |3-2|) = max(0, 1) = 1
101+
expect_equal(metric_Frechet(dat), 1)
102+
})

0 commit comments

Comments
 (0)