Major Changes: - NEW: Scripts 09 & 10 for KPI calculation and enhanced reporting - NEW: Shell script wrappers (01-10) for easier execution - NEW: R packages flextable and officer for enhanced Word reports - NEW: DEPLOYMENT_README.md with complete deployment guide - RENAMED: Numbered R scripts (02, 03, 04) for clarity - REMOVED: Old package management scripts (using renv only) - UPDATED: Workflow now uses scripts 09->10 instead of 05 Files Changed: 90+ files New Packages: flextable, officer New Scripts: 09_run_calculate_kpis.sh, 10_run_kpi_report.sh Documentation: DEPLOYMENT_README.md, EMAIL_TO_ADMIN.txt See DEPLOYMENT_README.md for full deployment instructions.
615 lines
20 KiB
Plaintext
615 lines
20 KiB
Plaintext
---
|
|
params:
|
|
ref: "word-styles-reference-var1.docx"
|
|
output_file: CI_report.docx
|
|
report_date: "2025-09-24"
|
|
data_dir: "esa"
|
|
mail_day: "Wednesday"
|
|
borders: FALSE
|
|
ci_plot_type: "both" # options: "absolute", "cumulative", "both"
|
|
colorblind_friendly: TRUE # use colorblind-friendly palettes (viridis/plasma)
|
|
facet_by_season: FALSE # facet CI trend plots by season instead of overlaying
|
|
x_axis_unit: "days" # x-axis unit for trend plots: "days" or "weeks"
|
|
output:
|
|
# html_document:
|
|
# toc: yes
|
|
# df_print: paged
|
|
word_document:
|
|
reference_docx: !expr file.path("word-styles-reference-var1.docx")
|
|
toc: no
|
|
editor_options:
|
|
chunk_output_type: console
|
|
---
|
|
|
|
```{r setup_parameters, include=FALSE}
|
|
# Set up basic report parameters from input values
|
|
report_date <- params$report_date
|
|
mail_day <- params$mail_day
|
|
borders <- params$borders
|
|
ci_plot_type <- params$ci_plot_type
|
|
colorblind_friendly <- params$colorblind_friendly
|
|
facet_by_season <- params$facet_by_season
|
|
x_axis_unit <- params$x_axis_unit
|
|
|
|
# Environment setup notes (commented out)
|
|
# # Activeer de renv omgeving
|
|
# renv::activate()
|
|
# renv::deactivate()
|
|
# # Optioneel: Herstel de omgeving als dat nodig is
|
|
# # Je kunt dit commentaar geven als je het normaal niet wilt uitvoeren
|
|
# renv::restore()
|
|
```
|
|
|
|
```{r load_libraries, message=FALSE, warning=FALSE, include=FALSE}
|
|
# Configure knitr options
|
|
knitr::opts_chunk$set(warning = FALSE, message = FALSE)
|
|
|
|
# Load all packages at once with suppressPackageStartupMessages
|
|
suppressPackageStartupMessages({
|
|
library(here)
|
|
library(sf)
|
|
library(terra)
|
|
library(exactextractr)
|
|
library(tidyverse)
|
|
library(tmap)
|
|
library(lubridate)
|
|
library(zoo)
|
|
library(rsample)
|
|
library(caret)
|
|
library(randomForest)
|
|
library(CAST)
|
|
})
|
|
|
|
# Load custom utility functions
|
|
tryCatch({
|
|
source("report_utils.R")
|
|
}, error = function(e) {
|
|
message(paste("Error loading report_utils.R:", e$message))
|
|
# Try alternative path if the first one fails
|
|
tryCatch({
|
|
source(here::here("r_app", "report_utils.R"))
|
|
}, error = function(e) {
|
|
stop("Could not load report_utils.R from either location: ", e$message)
|
|
})
|
|
})
|
|
```
|
|
|
|
```{r initialize_project_config, message=FALSE, warning=FALSE, include=FALSE}
|
|
# Set the project directory from parameters
|
|
project_dir <- params$data_dir
|
|
|
|
# Source project parameters with error handling
|
|
tryCatch({
|
|
source(here::here("r_app", "parameters_project.R"))
|
|
}, error = function(e) {
|
|
stop("Error loading parameters_project.R: ", e$message)
|
|
})
|
|
|
|
# Log initial configuration
|
|
safe_log("Starting the R Markdown script")
|
|
safe_log(paste("mail_day params:", params$mail_day))
|
|
safe_log(paste("report_date params:", params$report_date))
|
|
safe_log(paste("mail_day variable:", mail_day))
|
|
```
|
|
|
|
```{r calculate_dates_and_weeks, message=FALSE, warning=FALSE, include=FALSE}
|
|
# Set locale for consistent date formatting
|
|
Sys.setlocale("LC_TIME", "C")
|
|
|
|
# Initialize date variables from parameters
|
|
today <- as.character(report_date)
|
|
mail_day_as_character <- as.character(mail_day)
|
|
|
|
# Calculate week days
|
|
report_date_as_week_day <- weekdays(lubridate::ymd(today))
|
|
days_of_week <- c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
|
|
|
|
# Calculate initial week number
|
|
week <- lubridate::week(today)
|
|
safe_log(paste("Initial week calculation:", week, "today:", today))
|
|
|
|
# Calculate previous dates for comparisons
|
|
today_minus_1 <- as.character(lubridate::ymd(today) - 7)
|
|
today_minus_2 <- as.character(lubridate::ymd(today) - 14)
|
|
today_minus_3 <- as.character(lubridate::ymd(today) - 21)
|
|
|
|
# Log the weekday calculations for debugging
|
|
safe_log(paste("Report date weekday:", report_date_as_week_day))
|
|
safe_log(paste("Weekday index:", which(days_of_week == report_date_as_week_day)))
|
|
safe_log(paste("Mail day:", mail_day_as_character))
|
|
safe_log(paste("Mail day index:", which(days_of_week == mail_day_as_character)))
|
|
|
|
# Adjust week calculation based on mail day
|
|
if (which(days_of_week == report_date_as_week_day) > which(days_of_week == mail_day_as_character)) {
|
|
safe_log("Adjusting weeks because of mail day")
|
|
week <- lubridate::week(today) + 1
|
|
today_minus_1 <- as.character(lubridate::ymd(today))
|
|
today_minus_2 <- as.character(lubridate::ymd(today) - 7)
|
|
today_minus_3 <- as.character(lubridate::ymd(today) - 14)
|
|
}
|
|
|
|
# Calculate week numbers for previous weeks
|
|
week_minus_1 <- week - 1
|
|
week_minus_2 <- week - 2
|
|
week_minus_3 <- week - 3
|
|
|
|
# Format current week with leading zeros
|
|
week <- sprintf("%02d", week)
|
|
|
|
# Get years for each date
|
|
year <- lubridate::year(today)
|
|
year_1 <- lubridate::year(today_minus_1)
|
|
year_2 <- lubridate::year(today_minus_2)
|
|
year_3 <- lubridate::year(today_minus_3)
|
|
```
|
|
|
|
```{r data, message=TRUE, warning=TRUE, include=FALSE}
|
|
# Load CI index data with error handling
|
|
tryCatch({
|
|
CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds"))
|
|
safe_log("Successfully loaded CI quadrant data")
|
|
}, error = function(e) {
|
|
stop("Error loading CI quadrant data: ", e$message)
|
|
})
|
|
|
|
# Get file paths for different weeks using the utility function
|
|
tryCatch({
|
|
path_to_week_current = get_week_path(weekly_CI_mosaic, today, 0)
|
|
path_to_week_minus_1 = get_week_path(weekly_CI_mosaic, today, -1)
|
|
path_to_week_minus_2 = get_week_path(weekly_CI_mosaic, today, -2)
|
|
path_to_week_minus_3 = get_week_path(weekly_CI_mosaic, today, -3)
|
|
|
|
# Log the calculated paths
|
|
safe_log("Required mosaic paths:")
|
|
safe_log(paste("Path to current week:", path_to_week_current))
|
|
safe_log(paste("Path to week minus 1:", path_to_week_minus_1))
|
|
safe_log(paste("Path to week minus 2:", path_to_week_minus_2))
|
|
safe_log(paste("Path to week minus 3:", path_to_week_minus_3))
|
|
|
|
# Validate that files exist
|
|
if (!file.exists(path_to_week_current)) warning("Current week mosaic file does not exist: ", path_to_week_current)
|
|
if (!file.exists(path_to_week_minus_1)) warning("Week minus 1 mosaic file does not exist: ", path_to_week_minus_1)
|
|
if (!file.exists(path_to_week_minus_2)) warning("Week minus 2 mosaic file does not exist: ", path_to_week_minus_2)
|
|
if (!file.exists(path_to_week_minus_3)) warning("Week minus 3 mosaic file does not exist: ", path_to_week_minus_3)
|
|
|
|
# Load raster data with terra functions
|
|
CI <- terra::rast(path_to_week_current)$CI
|
|
CI_m1 <- terra::rast(path_to_week_minus_1)$CI
|
|
CI_m2 <- terra::rast(path_to_week_minus_2)$CI
|
|
CI_m3 <- terra::rast(path_to_week_minus_3)$CI
|
|
|
|
}, error = function(e) {
|
|
stop("Error loading raster data: ", e$message)
|
|
})
|
|
```
|
|
|
|
```{r calculate_difference_rasters, message=TRUE, warning=TRUE, include=FALSE}
|
|
# Calculate difference rasters for comparisons
|
|
tryCatch({
|
|
# Calculate weekly difference
|
|
last_week_dif_raster_abs <- (CI - CI_m1)
|
|
safe_log("Calculated weekly difference raster")
|
|
|
|
# Calculate three-week difference
|
|
three_week_dif_raster_abs <- (CI - CI_m3)
|
|
safe_log("Calculated three-week difference raster")
|
|
}, error = function(e) {
|
|
safe_log(paste("Error calculating difference rasters:", e$message), "ERROR")
|
|
# Create placeholder rasters if calculations fail
|
|
if (!exists("last_week_dif_raster_abs")) {
|
|
last_week_dif_raster_abs <- CI * 0
|
|
}
|
|
if (!exists("three_week_dif_raster_abs")) {
|
|
three_week_dif_raster_abs <- CI * 0
|
|
}
|
|
})
|
|
```
|
|
|
|
```{r load_field_boundaries, message=TRUE, warning=TRUE, include=FALSE}
|
|
# Load field boundaries from parameters
|
|
tryCatch({
|
|
AllPivots0 <- field_boundaries_sf %>%
|
|
dplyr::filter(!is.na(field), !is.na(sub_field)) # Filter out NA field names
|
|
safe_log("Successfully loaded field boundaries")
|
|
}, error = function(e) {
|
|
stop("Error loading field boundaries: ", e$message)
|
|
})
|
|
```
|
|
|
|
```{r create_front_page_variables, include=FALSE}
|
|
# Create variables for the front page
|
|
farm_name <- stringr::str_to_title(gsub("_", " ", project_dir))
|
|
|
|
# Format dates for display
|
|
report_date_formatted <- format(as.Date(report_date), "%B %d, %Y")
|
|
current_year <- format(Sys.Date(), "%Y")
|
|
|
|
# Get total field count and area if available
|
|
tryCatch({
|
|
total_fields <- length(unique(AllPivots0$field))
|
|
total_area_ha <- round(sum(sf::st_area(AllPivots0)) / 10000, 1) # Convert to hectares
|
|
}, error = function(e) {
|
|
total_fields <- "N/A"
|
|
total_area_ha <- "N/A"
|
|
})
|
|
```
|
|
|
|
---
|
|
title: ""
|
|
---
|
|
|
|
```{=openxml}
|
|
<w:p>
|
|
<w:pPr>
|
|
<w:jc w:val="center"/>
|
|
<w:spacing w:after="720"/>
|
|
</w:pPr>
|
|
<w:r>
|
|
<w:rPr>
|
|
<w:sz w:val="48"/>
|
|
<w:b/>
|
|
</w:rPr>
|
|
<w:t>SUGARCANE CROP MONITORING REPORT</w:t>
|
|
</w:r>
|
|
</w:p>
|
|
```
|
|
|
|
<div style="text-align: center; margin-top: 2cm; margin-bottom: 2cm;">
|
|
|
|
**`r farm_name`**
|
|
|
|
**Chlorophyll Index Analysis**
|
|
|
|
Report Date: **`r report_date_formatted`**
|
|
|
|
---
|
|
|
|
</div>
|
|
|
|
<div style="margin-top: 3cm; margin-bottom: 2cm;">
|
|
|
|
## Report Summary
|
|
|
|
**Farm Location:** `r farm_name`
|
|
**Report Period:** Week `r week` of `r current_year`
|
|
**Data Source:** Planet Labs Satellite Imagery
|
|
**Analysis Type:** Chlorophyll Index (CI) Monitoring
|
|
|
|
**Field Coverage:**
|
|
- Total Fields Monitored: `r total_fields`
|
|
- Total Area: `r total_area_ha` hectares
|
|
|
|
**Report Generated:** `r format(Sys.Date(), "%B %d, %Y")`
|
|
|
|
---
|
|
|
|
## About This Report
|
|
|
|
This automated report provides weekly analysis of sugarcane crop health using satellite-derived Chlorophyll Index (CI) measurements. The analysis helps identify:
|
|
|
|
- Field-level crop health variations
|
|
- Weekly changes in crop vigor
|
|
- Areas requiring agricultural attention
|
|
- Growth patterns across different field sections
|
|
|
|
**Key Features:**
|
|
- High-resolution satellite imagery analysis
|
|
- Week-over-week change detection
|
|
- Individual field performance metrics
|
|
- Actionable insights for crop management
|
|
|
|
</div>
|
|
|
|
\newpage
|
|
|
|
<!-- Table of Contents -->
|
|
```{=openxml}
|
|
<w:p>
|
|
<w:pPr>
|
|
<w:jc w:val="center"/>
|
|
<w:spacing w:after="480"/>
|
|
</w:pPr>
|
|
<w:r>
|
|
<w:rPr>
|
|
<w:sz w:val="32"/>
|
|
<w:b/>
|
|
</w:rPr>
|
|
<w:t>TABLE OF CONTENTS</w:t>
|
|
</w:r>
|
|
</w:p>
|
|
```
|
|
|
|
```{=openxml}
|
|
<w:p>
|
|
<w:fldSimple w:instr=" TOC \o "1-3" \h \z \u ">
|
|
<w:r><w:t>Update this field to generate table of contents</w:t></w:r>
|
|
</w:fldSimple>
|
|
</w:p>
|
|
```
|
|
|
|
\newpage
|
|
|
|
<!-- Original content starts here -->
|
|
|
|
|
|
|
|
# Explanation of the Report
|
|
|
|
This report provides a detailed analysis of your sugarcane fields based on satellite imagery, helping you monitor crop health and development throughout the growing season. The data is processed weekly to give you timely insights for optimal farm management decisions.
|
|
|
|
## What is the Chlorophyll Index (CI)?
|
|
|
|
The **Chlorophyll Index (CI)** is a vegetation index that measures the relative amount of chlorophyll in plant leaves. Chlorophyll is the green pigment responsible for photosynthesis in plants. Higher CI values indicate:
|
|
|
|
* Greater photosynthetic activity
|
|
* Healthier plant tissue
|
|
* Better nitrogen uptake
|
|
* More vigorous crop growth
|
|
|
|
CI values typically range from 0 (bare soil or severely stressed vegetation) to 7+ (very healthy, dense vegetation). For sugarcane, values between 3-7 generally indicate good crop health, depending on the growth stage.
|
|
|
|
## What You'll Find in This Report:
|
|
|
|
1. **Chlorophyll Index Overview Map**: A comprehensive view of all your fields showing current CI values. This helps identify which fields are performing well and which might need attention.
|
|
|
|
2. **Weekly Difference Map**: Shows changes in CI values over the past week. Positive values (green) indicate improving crop health, while negative values (red) may signal stress or decline.
|
|
|
|
3. **Field-by-Field Analysis**: Detailed maps for each field showing:
|
|
* CI values for the current week and two previous weeks
|
|
* Week-to-week changes in CI values
|
|
* Three-week change in CI values to track longer-term trends
|
|
|
|
4. **Growth Trend Graphs**: Time-series visualizations showing how CI values have changed throughout the growing season for each section of your fields.
|
|
|
|
5. **Yield Prediction**: For mature crops (over 300 days), we provide estimated yield predictions based on historical data and current CI measurements.
|
|
|
|
Use these insights to identify areas that may need irrigation, fertilization, or other interventions, and to track the effectiveness of your management practices over time.
|
|
|
|
\newpage
|
|
|
|
|
|
```{r generate_field_visualizations, eval=TRUE, fig.height=3.8, fig.width=10, message=FALSE,echo=FALSE, warning=FALSE, include=TRUE, results='asis'}
|
|
# Generate detailed visualizations for each field
|
|
tryCatch({
|
|
# Merge field polygons for processing and filter out NA field names
|
|
AllPivots_merged <- AllPivots0 %>%
|
|
dplyr::filter(!is.na(field), !is.na(sub_field)) %>% # Filter out NA fields
|
|
dplyr::group_by(field) %>%
|
|
dplyr::summarise(.groups = 'drop')
|
|
|
|
# Generate plots for each field
|
|
for(i in seq_along(AllPivots_merged$field)) {
|
|
field_name <- AllPivots_merged$field[i]
|
|
|
|
# Skip if field_name is still NA (double check)
|
|
if(is.na(field_name)) {
|
|
next
|
|
}
|
|
|
|
tryCatch({
|
|
# Add page break before each field (except the first one)
|
|
if(i > 1) {
|
|
cat("\\newpage\n\n")
|
|
}
|
|
|
|
# Call ci_plot with explicit parameters (ci_plot will generate its own header)
|
|
ci_plot(
|
|
pivotName = field_name,
|
|
field_boundaries = AllPivots0,
|
|
current_ci = CI,
|
|
ci_minus_1 = CI_m1,
|
|
ci_minus_2 = CI_m2,
|
|
last_week_diff = last_week_dif_raster_abs,
|
|
three_week_diff = three_week_dif_raster_abs,
|
|
harvesting_data = harvesting_data,
|
|
week = week,
|
|
week_minus_1 = week_minus_1,
|
|
week_minus_2 = week_minus_2,
|
|
week_minus_3 = week_minus_3,
|
|
borders = borders,
|
|
colorblind_friendly = colorblind_friendly
|
|
)
|
|
|
|
cat("\n\n")
|
|
|
|
# Call cum_ci_plot with explicit parameters
|
|
cum_ci_plot(
|
|
pivotName = field_name,
|
|
ci_quadrant_data = CI_quadrant,
|
|
plot_type = ci_plot_type,
|
|
facet_on = facet_by_season,
|
|
x_unit = x_axis_unit,
|
|
colorblind_friendly = colorblind_friendly
|
|
)
|
|
|
|
cat("\n\n")
|
|
|
|
}, error = function(e) {
|
|
safe_log(paste("Error generating plots for field", field_name, ":", e$message), "ERROR")
|
|
cat("\\newpage\n\n")
|
|
cat("# Error generating plots for field ", field_name, "\n\n")
|
|
cat(e$message, "\n\n")
|
|
})
|
|
}
|
|
}, error = function(e) {
|
|
safe_log(paste("Error in field visualization section:", e$message), "ERROR")
|
|
cat("Error generating field plots. See log for details.\n\n")
|
|
})
|
|
```
|
|
|
|
```{r generate_subarea_visualizations, echo=FALSE, fig.height=3.8, fig.width=10, message=FALSE, warning=FALSE, results='asis', eval=FALSE}
|
|
# Alternative visualization grouped by sub-area (disabled by default)
|
|
tryCatch({
|
|
# Group pivots by sub-area
|
|
pivots_grouped <- AllPivots0
|
|
|
|
# Iterate over each subgroup
|
|
for (subgroup in unique(pivots_grouped$sub_area)) {
|
|
# Add subgroup heading
|
|
cat("\n")
|
|
cat("## Subgroup: ", subgroup, "\n")
|
|
|
|
# Filter data for current subgroup
|
|
subset_data <- dplyr::filter(pivots_grouped, sub_area == subgroup)
|
|
|
|
# Generate visualizations for each field in the subgroup
|
|
purrr::walk(subset_data$field, function(field_name) {
|
|
cat("\n")
|
|
ci_plot(field_name)
|
|
cat("\n")
|
|
cum_ci_plot(field_name)
|
|
cat("\n")
|
|
})
|
|
|
|
# Add page break after each subgroup
|
|
cat("\\newpage\n")
|
|
}
|
|
}, error = function(e) {
|
|
safe_log(paste("Error in subarea visualization section:", e$message), "ERROR")
|
|
cat("Error generating subarea plots. See log for details.\n")
|
|
})
|
|
```
|
|
|
|
# Yield prediction
|
|
The below table shows estimates of the biomass if you would harvest them now.
|
|
|
|
```{r yield_data_training, message=FALSE, warning=FALSE, include=FALSE}
|
|
# Load and prepare yield prediction data with error handling
|
|
tryCatch({
|
|
# Load CI quadrant data and fill missing values
|
|
CI_quadrant <- readRDS(here::here(cumulative_CI_vals_dir, "All_pivots_Cumulative_CI_quadrant_year_v2.rds")) %>%
|
|
dplyr::group_by(model) %>%
|
|
tidyr::fill(field, sub_field, .direction = "downup") %>%
|
|
dplyr::ungroup()
|
|
|
|
# Check if tonnage_ha is empty
|
|
if (all(is.na(harvesting_data$tonnage_ha))) {
|
|
safe_log("Lacking historic harvest data, please provide for yield prediction calculation", "WARNING")
|
|
knitr::knit_exit() # Exit the chunk if tonnage_ha is empty
|
|
}
|
|
|
|
# Rename year column to season for consistency
|
|
harvesting_data <- harvesting_data %>% dplyr::rename(season = year)
|
|
|
|
# Join CI and yield data
|
|
CI_and_yield <- dplyr::left_join(CI_quadrant, harvesting_data, by = c("field", "sub_field", "season")) %>%
|
|
dplyr::group_by(sub_field, season) %>%
|
|
dplyr::slice(which.max(DOY)) %>%
|
|
dplyr::select(field, sub_field, tonnage_ha, cumulative_CI, DOY, season, sub_area) %>%
|
|
dplyr::mutate(CI_per_day = cumulative_CI / DOY)
|
|
|
|
# Define predictors and response variables
|
|
predictors <- c("cumulative_CI", "DOY", "CI_per_day")
|
|
response <- "tonnage_ha"
|
|
|
|
# Prepare test and validation datasets
|
|
CI_and_yield_test <- CI_and_yield %>%
|
|
as.data.frame() %>%
|
|
dplyr::filter(!is.na(tonnage_ha))
|
|
|
|
CI_and_yield_validation <- CI_and_yield_test
|
|
|
|
# Prepare prediction dataset (fields without harvest data)
|
|
prediction_yields <- CI_and_yield %>%
|
|
as.data.frame() %>%
|
|
dplyr::filter(is.na(tonnage_ha))# #%>%
|
|
# dplyr::filter(Age_days > 300) # Only predict on fields older than 300 days
|
|
|
|
# Configure model training parameters
|
|
ctrl <- caret::trainControl(
|
|
method = "cv",
|
|
savePredictions = TRUE,
|
|
allowParallel = TRUE,
|
|
number = 5,
|
|
verboseIter = TRUE
|
|
)
|
|
|
|
# Train the model with feature selection
|
|
set.seed(202) # For reproducibility
|
|
model_ffs_rf <- CAST::ffs(
|
|
CI_and_yield_test[, predictors],
|
|
CI_and_yield_test[, response],
|
|
method = "rf",
|
|
trControl = ctrl,
|
|
importance = TRUE,
|
|
withinSE = TRUE,
|
|
tuneLength = 5,
|
|
na.rm = TRUE
|
|
)
|
|
|
|
# Function to prepare predictions with consistent naming and formatting
|
|
prepare_predictions <- function(predictions, newdata) {
|
|
return(predictions %>%
|
|
as.data.frame() %>%
|
|
dplyr::rename(predicted_Tcha = ".") %>%
|
|
dplyr::mutate(
|
|
sub_field = newdata$sub_field,
|
|
field = newdata$field,
|
|
Age_days = newdata$DOY,
|
|
total_CI = round(newdata$cumulative_CI, 0),
|
|
predicted_Tcha = round(predicted_Tcha, 0),
|
|
season = newdata$season
|
|
) %>%
|
|
dplyr::select(field, sub_field, Age_days, predicted_Tcha, season) %>%
|
|
dplyr::left_join(., newdata, by = c("field", "sub_field", "season"))
|
|
)
|
|
}
|
|
|
|
# Predict yields for the validation dataset
|
|
pred_ffs_rf <- prepare_predictions(stats::predict(model_ffs_rf, newdata = CI_and_yield_validation), CI_and_yield_validation)
|
|
|
|
# Predict yields for the current season (focus on mature fields over 300 days)
|
|
pred_rf_current_season <- prepare_predictions(stats::predict(model_ffs_rf, newdata = prediction_yields), prediction_yields) %>%
|
|
dplyr::filter(Age_days > 1) %>%
|
|
select(c("field", "Age_days", "predicted_Tcha", "season"))
|
|
|
|
safe_log("Successfully completed yield prediction calculations")
|
|
|
|
}, error = function(e) {
|
|
safe_log(paste("Error in yield prediction:", e$message), "ERROR")
|
|
# Create empty dataframes to prevent errors in subsequent chunks
|
|
pred_ffs_rf <- data.frame()
|
|
pred_rf_current_season <- data.frame()
|
|
})
|
|
```
|
|
|
|
```{r plotting_yield_data, echo=FALSE, fig.height=5, fig.width=8, message=FALSE, warning=FALSE}
|
|
# Display yield prediction visualizations with error handling
|
|
tryCatch({
|
|
if (nrow(pred_ffs_rf) > 0) {
|
|
# Plot model performance (predicted vs actual)
|
|
ggplot2::ggplot(pred_ffs_rf, ggplot2::aes(y = predicted_Tcha, x = tonnage_ha)) +
|
|
ggplot2::geom_point(size = 2, alpha = 0.6) +
|
|
ggplot2::geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") +
|
|
ggplot2::scale_x_continuous(limits = c(0, 200)) +
|
|
ggplot2::scale_y_continuous(limits = c(0, 200)) +
|
|
ggplot2::labs(title = "Model Performance: \nPredicted vs Actual Tonnage/ha",
|
|
x = "Actual tonnage/ha (Tcha)",
|
|
y = "Predicted tonnage/ha (Tcha)") +
|
|
ggplot2::theme_minimal()
|
|
}
|
|
|
|
if (nrow(pred_rf_current_season) > 0) {
|
|
# Plot predicted yields by age
|
|
ggplot2::ggplot(pred_rf_current_season, ggplot2::aes(x = Age_days, y = predicted_Tcha)) +
|
|
ggplot2::geom_point(size = 2, alpha = 0.6) +
|
|
ggplot2::labs(title = "Predicted Yields \n Yet to Be Harvested",
|
|
x = "Age (days)",
|
|
y = "Predicted tonnage/ha (Tcha)") +
|
|
ggplot2::scale_y_continuous(limits = c(0, 200)) +
|
|
ggplot2::theme_minimal()
|
|
|
|
# Display prediction table
|
|
knitr::kable(pred_rf_current_season,
|
|
digits = 0,
|
|
caption = "Predicted Tonnage/ha for Fields Over 300 Days Old")
|
|
} else {
|
|
cat("No fields over 300 days old without harvest data available for yield prediction.")
|
|
}
|
|
}, error = function(e) {
|
|
safe_log(paste("Error in yield prediction visualization:", e$message), "ERROR")
|
|
cat("Error generating yield prediction visualizations. See log for details.")
|
|
})
|
|
```
|
|
|