Skip to content

Commit cad6238

Browse files
committed
Merge 20-create-transition-rate-table into main
2 parents adf58a2 + bbb5965 commit cad6238

17 files changed

Lines changed: 510 additions & 15 deletions

AGENTS.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# How to interact with the evoland-plus codebase
2+
3+
_Naturally, this is also handy for human devs._
4+
5+
This is the codebase for evoland-plus, or evoland for short.
6+
It is an R package that is used for land use / land cover change analyses and simulations.
7+
The main concept that differentiates this package from e.g. <https://github.com/simonmoulds/lulcc> is that the data are processed in tabular form.
8+
This enables the storage and processing of sparse domains.
9+
10+
Storage is done in parquet files written and read via duckdb, with the domain-agnostic DB implementation in `R/parquet_duckdb.R`.
11+
Using a normal duckdb file is very inefficient in terms of storage/compression; the parquet files have the advantage of being writable by other software, so they should be considered part of the interface and schema changes should be avoided as much as possible.
12+
The domain specific elements are in the `R/evoland_db*.R` files.
13+
When working on in-memory tables, S3 objects inheriting from data.tables are used for efficiency.
14+
Regular data.frame-like objects can be coerced to these classes using `as_` functions, e.g. `as_lulc_data_t()`.
15+
Classes of source data or "materialized" expensive calculation results are suffixed with `_t` to differentiate them from cheap `_v` views on the database.
16+
17+
## Environment setup
18+
19+
If there is no renv environment set up (check if "renv.lock" exists) ask the user if you can set up an renv development environment.
20+
You can efficiently initialize an renv like so from the project root directory:
21+
22+
```sh
23+
R -e "install.packages('renv', repos = 'https://cloud.r-project.org'); renv::init(bare = TRUE); install.packages('pak'); pak::local_install_dev_deps(); pak::pkg_install('devtools')"
24+
```
25+
26+
There may be issues where .Rprofile already contains a source("renv/activate.R") line, or where renv.lock or the renv folder exist.
27+
These need to be deleted before installation can be successful.
28+
29+
## Dependencies
30+
31+
You are allowed to suggest new dependencies, but make sure the user knows why they are needed.
32+
Avoid packages from the tidyverse, as their APIs tend to shift around over the years.
33+
Avoid niche packages that are seldom maintained; if the functionality is simple, rather implement it as a non-exported utility function.
34+
35+
## Code Style
36+
37+
We use the tidyverse style in general.
38+
This repo uses air as its principal formatter. Config in `air.toml`.
39+
The user may have the languageserver R package installed, meaning `.lintr` directives should also be picked up.
40+
Pay attention to linter warnings, but only once the main functionality is implemented.
41+
42+
Don't add comments where the code's purpose is self-explanatory.
43+
Don't check for missingness or nullness to avoid errors: only catch errors early if the emitted error would be hard to understand for the user.
44+
Generally, use the `stopifnot("error message" = condition)` pattern if something could fail in a hard to understand manner.
45+
46+
## Documentation
47+
48+
The package uses roxygen, so you can use `R -e "roxygen2::roxygenize()"` to make sure the Rd documentation is up to date.
49+
Tutorials are written as quarto files in `vignettes/*.qmd`.
50+
A pkgdown github action transforms all of this into a webpage, see `.github/workflows/pkgdown.yaml`.
51+
52+
## Testing
53+
54+
This repo uses tinytest (not testthat) for conducting tests not only during development (i.e. when the full package namespace is attached via `pkgload::load_all()`), but also after installation.
55+
This means that non-exported functions need to be tested as `evoland:::private_function`.
56+
You can test the full package using `R -e "tinytest::build_test_install()"`.
57+
You can test individual files using `R -e "pkgload::load_all(); tinytest::run_test_file('inst/tinytest/somefile.R')"`
58+
59+
While tests should be comprehensive, the full test suite should remain small enough to test often.
60+
Don't overwhelm the user, add 10 tests at a time, then ask for feedback before continuing.
61+
62+
## Rcpp components
63+
64+
C++ code is in the `src/` folder.
65+
This code interfaces with R using Rcpp; you can make sure the binaries are built using `pkbuild::build()` and clean dlls using `pkgbuild::clean_dll()`
66+
If there is no low-hanging use case for writing a standalone C++ program/header, prefer to use the Rcpp namespace and take advantage of the data types that affords.

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Collate:
6262
'trans_models_rf.R'
6363
'trans_models_t.R'
6464
'trans_preds_t.R'
65+
'trans_rates_t.R'
6566
'util.R'
6667
'util_dinamica.r'
6768
'util_download.R'

NAMESPACE

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ S3method(print,pred_meta_t)
1515
S3method(print,trans_meta_t)
1616
S3method(print,trans_models_t)
1717
S3method(print,trans_preds_t)
18+
S3method(print,trans_rates_t)
1819
S3method(validate,alloc_params_t)
1920
S3method(validate,coords_t)
2021
S3method(validate,default)
@@ -34,6 +35,7 @@ S3method(validate,reporting_t)
3435
S3method(validate,trans_meta_t)
3536
S3method(validate,trans_models_t)
3637
S3method(validate,trans_preds_t)
38+
S3method(validate,trans_rates_t)
3739
export(as_alloc_params_t)
3840
export(as_coords_t)
3941
export(as_intrv_masks_t)
@@ -48,12 +50,15 @@ export(as_reporting_t)
4850
export(as_trans_meta_t)
4951
export(as_trans_models_t)
5052
export(as_trans_preds_t)
53+
export(as_trans_rates_t)
5154
export(covariance_filter)
5255
export(create_coords_t_square)
56+
export(create_extr_trans_rates_t)
5357
export(create_intrv_meta_t)
5458
export(create_intrv_meta_t_row)
5559
export(create_lulc_meta_t)
5660
export(create_neighbors_t)
61+
export(create_obs_trans_rates_t)
5762
export(create_periods_t)
5863
export(create_pred_meta_t)
5964
export(create_trans_meta_t)

R/evoland_db_tables.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#' - `pred_meta_t` - Predictor metadata. See [as_pred_meta_t()]
1717
#' - `trans_meta_t` - Transition metadata. See [as_trans_meta_t()]
1818
#' - `trans_preds_t` - Transition-predictor relationships. See [as_trans_preds_t()]
19+
#' - `trans_rates_t` - Transition rates by period. See [as_trans_rates_t()]
1920
#' - `intrv_meta_t` - Intervention metadata. See [as_intrv_meta_t()]
2021
#' - `intrv_masks_t` - Intervention masks. See [as_intrv_masks_t()]
2122
#' - `trans_models_t` - Transition models. See [as_trans_models_t()]
@@ -155,6 +156,15 @@ evoland_db$set("active", "trans_preds_t", function(x) {
155156
)(x)
156157
})
157158

159+
evoland_db$set("active", "trans_rates_t", function(x) {
160+
create_table_binding(
161+
self,
162+
"trans_rates_t",
163+
as_trans_rates_t,
164+
key_cols = c("id_period", "id_trans")
165+
)(x)
166+
})
167+
158168
evoland_db$set("active", "intrv_meta_t", function(x) {
159169
create_table_binding(
160170
self,

R/evoland_db_views.R

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
#'
99
#' - `lulc_meta_long_v` - Unrolled LULC metadata with one row per source class
1010
#' - `pred_sources_v` - Distinct predictor URLs and their MD5 checksums
11-
#' - `transitions_v` - Land use transitions derived from lulc_data_t
11+
#' - `trans_v` - Land use transitions derived from lulc_data_t
1212
#' - `extent` - Spatial extent of coords_t as terra::SpatExtent
1313
#' - `coords_minimal` - Minimal coordinate representation (id_coord, lon, lat)
1414
#'
1515
#' @section Methods Added:
1616
#'
1717
#' - `trans_pred_data_v(id_trans)` - Returns wide table of transition results and predictor data for a specific transition. Used as input to covariance filtering.
18+
#' - `trans_rates_dinamica_v(id_period)` - Returns transition rates formatted for Dinamica export for a specific period.
1819
#'
1920
#' @name evoland_db_views
2021
#' @include evoland_db.R
@@ -49,7 +50,7 @@ evoland_db$set("active", "pred_sources_v", function() {
4950
})
5051
})
5152

52-
evoland_db$set("active", "transitions_v", function() {
53+
evoland_db$set("active", "trans_v", function() {
5354
self$with_tables("lulc_data_t", function() {
5455
self$get_query(glue::glue(
5556
r"{
@@ -137,8 +138,8 @@ evoland_db$set(
137138
stop(glue::glue("Transition id_trans = {id_trans} not found in trans_meta_t"))
138139
}
139140

140-
id_lulc_ant <- trans_info$id_lulc_anterior
141-
id_lulc_post <- trans_info$id_lulc_posterior
141+
id_lulc_ant <- trans_info[["id_lulc_anterior"]]
142+
id_lulc_post <- trans_info[["id_lulc_posterior"]]
142143

143144
ctes <- list()
144145

@@ -304,3 +305,37 @@ evoland_db$set(
304305
)
305306
}
306307
)
308+
309+
# get transition rates formatted for Dinamica export
310+
# id_period - integer period ID for which to export rates
311+
evoland_db$set(
312+
"public",
313+
"trans_rates_dinamica_v",
314+
function(id_period) {
315+
stopifnot(
316+
"id_period must be a single integer" = {
317+
length(id_period) == 1L && id_period == as.integer(id_period)
318+
}
319+
)
320+
321+
self$with_tables(
322+
c("trans_rates_t", "trans_meta_t"),
323+
function() {
324+
result <- self$get_query(glue::glue(
325+
"SELECT
326+
m.id_lulc_anterior as \"From*\",
327+
m.id_lulc_posterior as \"To*\",
328+
r.rate as \"Rate\"
329+
FROM
330+
trans_rates_t r,
331+
trans_meta_t m
332+
WHERE
333+
r.id_trans = m.id_trans
334+
AND r.id_period = {id_period}"
335+
))
336+
337+
result
338+
}
339+
)
340+
}
341+
)

R/trans_meta_t.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#'
77
#' @name trans_meta_t
88
#'
9-
#' @param transitions A transitions_v table, with columns id_coord, id_lulc_anterior,
9+
#' @param transitions A trans_v table, with columns id_coord, id_lulc_anterior,
1010
#' id_lulc_posterior, id_period
1111
#' @param min_cardinality_abs Minimum absolute number of transitions for viability (optional)
1212
#' @param min_frequency_rel Minimum relative frequency of transitions for viability (optional)
@@ -62,6 +62,7 @@ create_trans_meta_t <- function(
6262
# Aggregate transitions by type and calculate frequencies
6363
trans_summary <-
6464
transitions[
65+
# excludes non-transitions
6566
id_lulc_anterior != id_lulc_posterior
6667
][,
6768
.(cardinality = .N),

0 commit comments

Comments
 (0)