ecological-agent-skills 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_CONTEXT.md +191 -0
- package/CATALOG.md +329 -0
- package/LICENSE +692 -0
- package/README.md +347 -0
- package/bin/install.mjs +168 -0
- package/docs/comparison-with-alternatives.md +38 -0
- package/docs/global-examples-index.md +103 -0
- package/docs/repository-statistics.md +101 -0
- package/docs/theoretical-foundations.md +188 -0
- package/environment.yaml +106 -0
- package/examples/community/arctic_tundra_vegetation_example.md +247 -0
- package/examples/community/bird_landuse_example.md +63 -0
- package/examples/community/phytoplankton_reservoir_example.md +60 -0
- package/examples/community/reef_fish_indopacific_example.md +221 -0
- package/examples/impact/baci_road_example.md +57 -0
- package/examples/impact/ecosystem_services_atlantic_forest.md +83 -0
- package/examples/impact/forest_loss_borneo_timeseries_example.md +225 -0
- package/examples/occupancy/puma_camera_example.md +61 -0
- package/examples/occupancy/snow_leopard_himalayas_example.md +204 -0
- package/examples/reproducible/whittaker_biome_sdm_example.md +406 -0
- package/examples/sdm/anteater_cerrado_example.md +69 -0
- package/examples/sdm/jaguar_amazon_example.md +80 -0
- package/examples/sdm/koala_climate_change_example.md +170 -0
- package/examples/sdm/wolf_recolonization_europe_example.md +193 -0
- package/package.json +43 -0
- package/renv.lock +194 -0
- package/skills/SKILL_INDEX.json +1020 -0
- package/skills/acoustic-monitoring/SKILL.md +163 -0
- package/skills/acoustic-monitoring/examples/example-prompts.md +100 -0
- package/skills/acoustic-monitoring/examples/temperate_forest_birds_example.md +285 -0
- package/skills/acoustic-monitoring/resources/acoustic-indices-reference.md +93 -0
- package/skills/acoustic-monitoring/resources/soundscape-ecology-guide.md +90 -0
- package/skills/acoustic-monitoring/resources/species-id-tools-comparison.md +89 -0
- package/skills/acoustic-monitoring/scripts/batch_species_detection.py +360 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.R +235 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.py +374 -0
- package/skills/biostatistics-workbench/SKILL.md +140 -0
- package/skills/biostatistics-workbench/examples/example-prompts.md +39 -0
- package/skills/biostatistics-workbench/resources/effect-size-reference.md +81 -0
- package/skills/biostatistics-workbench/resources/glm-family-link-reference.md +47 -0
- package/skills/biostatistics-workbench/resources/test-selection-guide.md +93 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.R +78 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.py +210 -0
- package/skills/camera-trap-processing/SKILL.md +159 -0
- package/skills/camera-trap-processing/examples/example-prompts.md +103 -0
- package/skills/camera-trap-processing/examples/leopard_serengeti_example.md +231 -0
- package/skills/camera-trap-processing/resources/activity-patterns-reference.md +113 -0
- package/skills/camera-trap-processing/resources/camtrapR-workflow-guide.md +130 -0
- package/skills/camera-trap-processing/resources/detection-event-definition-guide.md +89 -0
- package/skills/camera-trap-processing/scripts/estimate_activity.R +169 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.R +179 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.py +192 -0
- package/skills/community-ecology-ordination/SKILL.md +133 -0
- package/skills/community-ecology-ordination/examples/example-prompts.md +35 -0
- package/skills/community-ecology-ordination/resources/dissimilarity-metric-guide.md +53 -0
- package/skills/community-ecology-ordination/resources/nmds-interpretation-guide.md +104 -0
- package/skills/community-ecology-ordination/scripts/__pycache__/community_analysis.cpython-311.pyc +0 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.R +143 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.py +231 -0
- package/skills/ecological-data-foundation/SKILL.md +129 -0
- package/skills/ecological-data-foundation/examples/example-prompts.md +40 -0
- package/skills/ecological-data-foundation/resources/coordinate-cleaning-flags.md +66 -0
- package/skills/ecological-data-foundation/resources/darwin-core-glossary.md +91 -0
- package/skills/ecological-data-foundation/resources/data-citation-guide.md +265 -0
- package/skills/ecological-data-foundation/resources/gbif-data-citation-guide.md +193 -0
- package/skills/ecological-data-foundation/resources/qa-checklist.md +83 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/clean_occurrences.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_ebird.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_inat.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_iucn.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_obis.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.R +230 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.py +268 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.R +251 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.py +364 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.R +315 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.py +407 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.R +238 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.py +304 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.R +273 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.py +344 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.R +248 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.py +318 -0
- package/skills/ecological-impact-assessment/SKILL.md +123 -0
- package/skills/ecological-impact-assessment/examples/example-prompts.md +32 -0
- package/skills/ecological-impact-assessment/resources/baci-design-guide.md +55 -0
- package/skills/ecological-impact-assessment/resources/fragmentation-metrics-reference.md +86 -0
- package/skills/ecological-impact-assessment/resources/pressure-index-template.md +78 -0
- package/skills/ecological-impact-assessment/resources/study-design-guide.md +168 -0
- package/skills/ecological-impact-assessment/scripts/baci_analysis.R +161 -0
- package/skills/ecological-impact-assessment/scripts/fragmentation_analysis.py +141 -0
- package/skills/ecological-impact-assessment/scripts/power_analysis_baci.R +274 -0
- package/skills/ecosystem-services-assessment/SKILL.md +125 -0
- package/skills/ecosystem-services-assessment/examples/example-prompts.md +24 -0
- package/skills/ecosystem-services-assessment/resources/es-indicator-reference.md +45 -0
- package/skills/ecosystem-services-assessment/resources/invest-parameter-guide.md +86 -0
- package/skills/ecosystem-services-assessment/resources/rusle-coefficients.md +88 -0
- package/skills/ecosystem-services-assessment/scripts/__pycache__/compute_es.cpython-311.pyc +0 -0
- package/skills/ecosystem-services-assessment/scripts/compute_es.py +189 -0
- package/skills/ecosystem-services-assessment/scripts/tradeoff_analysis.R +161 -0
- package/skills/environmental-time-series/SKILL.md +125 -0
- package/skills/environmental-time-series/examples/example-prompts.md +33 -0
- package/skills/environmental-time-series/resources/anomaly-indices-reference.md +88 -0
- package/skills/environmental-time-series/resources/bfast-parameter-guide.md +69 -0
- package/skills/environmental-time-series/scripts/__pycache__/recovery_trajectory.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/__pycache__/trend_analysis.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.R +305 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.py +178 -0
- package/skills/environmental-time-series/scripts/trend_analysis.R +192 -0
- package/skills/environmental-time-series/scripts/trend_analysis.py +184 -0
- package/skills/geoprocessing-for-ecology/SKILL.md +123 -0
- package/skills/geoprocessing-for-ecology/examples/example-prompts.md +32 -0
- package/skills/geoprocessing-for-ecology/resources/crs-reference.md +62 -0
- package/skills/geoprocessing-for-ecology/resources/global-predictor-sources.md +331 -0
- package/skills/geoprocessing-for-ecology/resources/resampling-methods.md +57 -0
- package/skills/geoprocessing-for-ecology/scripts/__pycache__/download_predictors.cpython-311.pyc +0 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.R +239 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.py +379 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.R +224 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.py +172 -0
- package/skills/landscape-connectivity/SKILL.md +170 -0
- package/skills/landscape-connectivity/examples/example-prompts.md +96 -0
- package/skills/landscape-connectivity/examples/jaguar_mesoamerica_corridor_example.md +271 -0
- package/skills/landscape-connectivity/resources/circuitscape-parameter-guide.md +155 -0
- package/skills/landscape-connectivity/resources/graph-theory-for-ecology.md +134 -0
- package/skills/landscape-connectivity/resources/resistance-surface-guide.md +141 -0
- package/skills/landscape-connectivity/scripts/connectivity_analysis.py +387 -0
- package/skills/landscape-connectivity/scripts/connectivity_metrics.R +274 -0
- package/skills/landscape-connectivity/scripts/resistance_surface.R +239 -0
- package/skills/model-validation-and-uncertainty/SKILL.md +131 -0
- package/skills/model-validation-and-uncertainty/examples/example-prompts.md +30 -0
- package/skills/model-validation-and-uncertainty/resources/extrapolation-risk-guide.md +236 -0
- package/skills/model-validation-and-uncertainty/resources/metric-selection-guide.md +52 -0
- package/skills/model-validation-and-uncertainty/resources/threshold-selection-guide.md +64 -0
- package/skills/model-validation-and-uncertainty/scripts/__pycache__/validate_model.cpython-311.pyc +0 -0
- package/skills/model-validation-and-uncertainty/scripts/extrapolation_risk.R +315 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_model.py +226 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_sdm.R +162 -0
- package/skills/occupancy-and-detection/SKILL.md +126 -0
- package/skills/occupancy-and-detection/examples/example-prompts.md +33 -0
- package/skills/occupancy-and-detection/resources/detection-history-format.md +100 -0
- package/skills/occupancy-and-detection/resources/occupancy-study-design.md +47 -0
- package/skills/occupancy-and-detection/scripts/__pycache__/occupancy_analysis.cpython-311.pyc +0 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.R +160 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.py +159 -0
- package/skills/population-viability-analysis/SKILL.md +161 -0
- package/skills/population-viability-analysis/examples/african_elephant_pva_example.md +266 -0
- package/skills/population-viability-analysis/examples/example-prompts.md +95 -0
- package/skills/population-viability-analysis/resources/extinction-risk-thresholds.md +128 -0
- package/skills/population-viability-analysis/resources/matrix-model-guide.md +139 -0
- package/skills/population-viability-analysis/resources/sensitivity-elasticity-reference.md +182 -0
- package/skills/population-viability-analysis/scripts/matrix_pva.R +258 -0
- package/skills/population-viability-analysis/scripts/pva_analysis.py +442 -0
- package/skills/population-viability-analysis/scripts/stochastic_pva.R +353 -0
- package/skills/predictive-modeling-best-practices/SKILL.md +136 -0
- package/skills/predictive-modeling-best-practices/examples/example-prompts.md +58 -0
- package/skills/predictive-modeling-best-practices/resources/collinearity-decision-tree.md +65 -0
- package/skills/predictive-modeling-best-practices/resources/sampling-bias-correction.md +267 -0
- package/skills/predictive-modeling-best-practices/resources/spatial-cv-guide.md +73 -0
- package/skills/predictive-modeling-best-practices/scripts/__pycache__/spatial_cv.cpython-311.pyc +0 -0
- package/skills/predictive-modeling-best-practices/scripts/collinearity_check.R +112 -0
- package/skills/predictive-modeling-best-practices/scripts/spatial_cv.py +182 -0
- package/skills/reproducible-ecology-pipeline/SKILL.md +139 -0
- package/skills/reproducible-ecology-pipeline/examples/example-prompts.md +35 -0
- package/skills/reproducible-ecology-pipeline/resources/directory-structure-template.md +94 -0
- package/skills/reproducible-ecology-pipeline/resources/params-yaml-template.yaml +84 -0
- package/skills/reproducible-ecology-pipeline/resources/reproducibility-checklist-template.md +66 -0
- package/skills/reproducible-ecology-pipeline/scripts/generate_file_manifest.py +110 -0
- package/skills/reproducible-ecology-pipeline/scripts/init_project.sh +53 -0
- package/skills/spatial-prioritization/SKILL.md +162 -0
- package/skills/spatial-prioritization/examples/biodiversity_hotspot_prioritization_example.md +289 -0
- package/skills/spatial-prioritization/examples/example-prompts.md +93 -0
- package/skills/spatial-prioritization/resources/cost-surface-reference.md +130 -0
- package/skills/spatial-prioritization/resources/marxan-vs-prioritizr-comparison.md +125 -0
- package/skills/spatial-prioritization/resources/prioritizr-formulation-guide.md +188 -0
- package/skills/spatial-prioritization/resources/representation-targets-guide.md +186 -0
- package/skills/spatial-prioritization/scripts/prioritization_sensitivity.R +320 -0
- package/skills/spatial-prioritization/scripts/run_prioritization.R +336 -0
- package/skills/species-distribution-modeling/SKILL.md +139 -0
- package/skills/species-distribution-modeling/examples/example-prompts.md +36 -0
- package/skills/species-distribution-modeling/resources/algorithm-comparison.md +25 -0
- package/skills/species-distribution-modeling/resources/calibration-area-guide.md +71 -0
- package/skills/species-distribution-modeling/resources/climate-scenario-preparation.md +170 -0
- package/skills/species-distribution-modeling/resources/maxent-calibration-guide.md +211 -0
- package/skills/species-distribution-modeling/resources/sdm-checklist.md +37 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.R +236 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.py +286 -0
- package/skills/species-distribution-modeling/scripts/prepare_future_layers.R +351 -0
- package/skills/species-distribution-modeling/scripts/project_scenarios.R +220 -0
- package/skills/species-distribution-modeling/scripts/run_ensemble_sdm.R +99 -0
- package/skills/species-distribution-modeling/scripts/sdm_pipeline.py +318 -0
- package/skills/species-distribution-modeling/scripts/tune_maxnet.R +344 -0
- package/templates/SKILL_TEMPLATE.md +225 -0
- package/templates/checklists/data-submission-checklist.md +38 -0
- package/templates/checklists/post-analysis-checklist.md +55 -0
- package/templates/checklists/pre-analysis-checklist.md +31 -0
- package/templates/prompts/debug-skill.md +47 -0
- package/templates/prompts/invoke-skill.md +34 -0
- package/templates/prompts/invoke-workflow.md +45 -0
- package/templates/reports/technical-report-template.md +80 -0
- package/templates/scripts/logger_setup.R +79 -0
- package/templates/scripts/logger_setup.py +119 -0
- package/templates/scripts/params_loader.R +28 -0
- package/templates/scripts/params_loader.py +38 -0
- package/workflows/analyze-community-structure/WORKFLOW.md +72 -0
- package/workflows/analyze-environmental-change/WORKFLOW.md +73 -0
- package/workflows/assess-ecological-impact/WORKFLOW.md +75 -0
- package/workflows/assess-ecosystem-services/WORKFLOW.md +68 -0
- package/workflows/assess-landscape-connectivity/WORKFLOW.md +84 -0
- package/workflows/build-fire-risk-map/WORKFLOW.md +79 -0
- package/workflows/produce-technical-report/WORKFLOW.md +113 -0
- package/workflows/run-camera-trap-occupancy/WORKFLOW.md +87 -0
- package/workflows/run-conservation-prioritization/WORKFLOW.md +89 -0
- package/workflows/run-multispecies-screening/WORKFLOW.md +197 -0
- package/workflows/run-occupancy-analysis/WORKFLOW.md +74 -0
- package/workflows/run-population-viability/WORKFLOW.md +90 -0
- package/workflows/run-sdm-study/WORKFLOW.md +99 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
compute_es.py
|
|
7
|
+
Compute basic ecosystem service indicators from land cover + biophysical data.
|
|
8
|
+
Usage: python compute_es.py <landcover_tif> <carbon_pools_csv> <output_dir>
|
|
9
|
+
Services: carbon storage, erosion control (RUSLE C-factor), pollination habitat index
|
|
10
|
+
Requires: rasterio, numpy, pandas, geopandas
|
|
11
|
+
"""
|
|
12
|
+
import logging
|
|
13
|
+
import sys
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
SKILL_NAME = "ecosystem-services-assessment"
|
|
18
|
+
_LOG_DIR = Path("logs")
|
|
19
|
+
_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
_log_file = _LOG_DIR / f"skill_{SKILL_NAME}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
|
21
|
+
logging.basicConfig(
|
|
22
|
+
level=logging.INFO,
|
|
23
|
+
format="[%(asctime)s] [%(levelname)s] [" + SKILL_NAME + "] %(message)s",
|
|
24
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
25
|
+
handlers=[
|
|
26
|
+
logging.StreamHandler(sys.stdout),
|
|
27
|
+
logging.FileHandler(_log_file, encoding="utf-8"),
|
|
28
|
+
],
|
|
29
|
+
)
|
|
30
|
+
logger = logging.getLogger(SKILL_NAME)
|
|
31
|
+
|
|
32
|
+
def log_step(n: int, desc: str) -> None:
|
|
33
|
+
logger.info("-- STEP %d: %s", n, desc)
|
|
34
|
+
|
|
35
|
+
def log_decision(var: str, val, why: str) -> None:
|
|
36
|
+
logger.info("DECISION | %s = %s | %s", var, val, why)
|
|
37
|
+
|
|
38
|
+
import numpy as np
|
|
39
|
+
import pandas as pd
|
|
40
|
+
import rasterio
|
|
41
|
+
from rasterio.transform import rowcol
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def load_raster(path: str) -> tuple:
|
|
45
|
+
with rasterio.open(path) as src:
|
|
46
|
+
data = src.read(1).astype(float)
|
|
47
|
+
meta = src.meta.copy()
|
|
48
|
+
return data, meta
|
|
49
|
+
|
|
50
|
+
def zonal_summary(lc: np.ndarray, es_layer: np.ndarray,
|
|
51
|
+
class_codes: list, class_names: dict) -> pd.DataFrame:
|
|
52
|
+
rows = []
|
|
53
|
+
for code in class_codes:
|
|
54
|
+
mask = lc == code
|
|
55
|
+
if mask.sum() > 0:
|
|
56
|
+
rows.append({"lulc_code": code,
|
|
57
|
+
"lulc_name": class_names.get(code, str(code)),
|
|
58
|
+
"n_pixels": int(mask.sum()),
|
|
59
|
+
"mean_es": round(float(np.nanmean(es_layer[mask])), 4),
|
|
60
|
+
"total_es": round(float(np.nansum(es_layer[mask])), 2)})
|
|
61
|
+
return pd.DataFrame(rows).sort_values("lulc_code")
|
|
62
|
+
|
|
63
|
+
def main():
|
|
64
|
+
lc_file = sys.argv[1] if len(sys.argv) > 1 else "data/landcover.tif"
|
|
65
|
+
carbon_file = sys.argv[2] if len(sys.argv) > 2 else "data/carbon_pools.csv"
|
|
66
|
+
output_dir = Path(sys.argv[3]) if len(sys.argv) > 3 else Path("outputs/ecosystem_services")
|
|
67
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
log_decision("lc_file", lc_file, "Input land cover raster")
|
|
70
|
+
log_decision("carbon_file", carbon_file, "Carbon pools lookup CSV")
|
|
71
|
+
log_decision("output_dir", str(output_dir), "Directory for ecosystem service outputs")
|
|
72
|
+
|
|
73
|
+
if not Path(lc_file).exists():
|
|
74
|
+
logger.error(
|
|
75
|
+
"Input nao encontrado: %s\n"
|
|
76
|
+
" Causa provavel: passo anterior nao concluiu.\n"
|
|
77
|
+
" Skill anterior que deveria ter produzido este input: geoprocessing-for-ecology",
|
|
78
|
+
lc_file
|
|
79
|
+
)
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
|
|
82
|
+
if not Path(carbon_file).exists():
|
|
83
|
+
logger.error(
|
|
84
|
+
"Input nao encontrado: %s\n"
|
|
85
|
+
" Causa provavel: passo anterior nao concluiu.\n"
|
|
86
|
+
" Skill anterior que deveria ter produzido este input: reproducible-ecology-pipeline",
|
|
87
|
+
carbon_file
|
|
88
|
+
)
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
log_step(1, "Loading land cover raster")
|
|
93
|
+
lc, meta = load_raster(lc_file)
|
|
94
|
+
n_classes = len(np.unique(lc[~np.isnan(lc)]))
|
|
95
|
+
logger.info("Land cover raster: %s | Classes: %d", lc.shape, n_classes)
|
|
96
|
+
|
|
97
|
+
log_step(2, "Loading and validating carbon pools CSV")
|
|
98
|
+
carbon_df = pd.read_csv(carbon_file)
|
|
99
|
+
required_cols = ["lucode", "C_above", "C_below", "C_soil", "C_dead"]
|
|
100
|
+
missing = [c for c in required_cols if c not in carbon_df.columns]
|
|
101
|
+
if missing:
|
|
102
|
+
logger.warning(
|
|
103
|
+
"Carbon pools CSV missing columns: %s. Using zeros for missing fields.", missing
|
|
104
|
+
)
|
|
105
|
+
for c in missing:
|
|
106
|
+
carbon_df[c] = 0
|
|
107
|
+
|
|
108
|
+
# ── 1. Carbon storage ──────────────────────────────────────────────────
|
|
109
|
+
log_step(3, "Computing carbon storage layer")
|
|
110
|
+
carbon_map = np.full(lc.shape, np.nan)
|
|
111
|
+
class_names = {}
|
|
112
|
+
for _, row in carbon_df.iterrows():
|
|
113
|
+
mask = lc == row["lucode"]
|
|
114
|
+
total_c = row["C_above"] + row["C_below"] + row["C_soil"] + row["C_dead"]
|
|
115
|
+
carbon_map[mask] = total_c
|
|
116
|
+
if "LULC_name" in carbon_df.columns:
|
|
117
|
+
class_names[int(row["lucode"])] = row["LULC_name"]
|
|
118
|
+
|
|
119
|
+
carbon_out = output_dir / "carbon_storage_MgCha.tif"
|
|
120
|
+
with rasterio.open(carbon_out, "w", **meta) as dst:
|
|
121
|
+
dst.write(carbon_map.astype(np.float32), 1)
|
|
122
|
+
logger.info("Carbon: mean = %.1f MgC/ha | Output: %s", np.nanmean(carbon_map), carbon_out)
|
|
123
|
+
|
|
124
|
+
# ── 2. RUSLE C-factor (erosion control proxy) ─────────────────────────
|
|
125
|
+
log_step(4, "Computing RUSLE C-factor erosion control layer")
|
|
126
|
+
# Default C-factors (add your own mapping)
|
|
127
|
+
c_factor_defaults = {1: 0.001, 2: 0.005, 3: 0.01, 4: 0.15, 5: 0.30, 6: 1.0}
|
|
128
|
+
log_decision("c_factor_defaults", c_factor_defaults,
|
|
129
|
+
"Default RUSLE C-factors per LULC class; replace with site-specific values")
|
|
130
|
+
c_map = np.full(lc.shape, np.nan)
|
|
131
|
+
for code, c_val in c_factor_defaults.items():
|
|
132
|
+
c_map[lc == code] = c_val
|
|
133
|
+
# Erosion control ES = avoided erosion = (1 - C_factor); higher = better service
|
|
134
|
+
erosion_control = 1.0 - c_map
|
|
135
|
+
unmapped = np.sum(np.isnan(c_map) & ~np.isnan(lc))
|
|
136
|
+
if unmapped > 0:
|
|
137
|
+
logger.warning(
|
|
138
|
+
"%d pixels have no C-factor mapping (not in c_factor_defaults). "
|
|
139
|
+
"Erosion control will be NaN for those pixels.",
|
|
140
|
+
unmapped
|
|
141
|
+
)
|
|
142
|
+
erosion_out = output_dir / "erosion_control_index.tif"
|
|
143
|
+
with rasterio.open(erosion_out, "w", **meta) as dst:
|
|
144
|
+
dst.write(erosion_control.astype(np.float32), 1)
|
|
145
|
+
logger.info("Erosion control layer written: %s", erosion_out)
|
|
146
|
+
|
|
147
|
+
# ── 3. Pollination habitat index ─────────────────────────────────────
|
|
148
|
+
log_step(5, "Computing pollination habitat index layer")
|
|
149
|
+
# Natural / semi-natural = high value (1); crops = partial (0.5); urban/bare = 0
|
|
150
|
+
pollination_suitability = {1: 1.0, 2: 0.8, 3: 0.7, 4: 0.3, 5: 0.1, 6: 0.0}
|
|
151
|
+
log_decision("pollination_suitability", pollination_suitability,
|
|
152
|
+
"Expert-based pollination suitability scores per LULC class")
|
|
153
|
+
poll_map = np.full(lc.shape, np.nan)
|
|
154
|
+
for code, val in pollination_suitability.items():
|
|
155
|
+
poll_map[lc == code] = val
|
|
156
|
+
poll_out = output_dir / "pollination_habitat.tif"
|
|
157
|
+
with rasterio.open(poll_out, "w", **meta) as dst:
|
|
158
|
+
dst.write(poll_map.astype(np.float32), 1)
|
|
159
|
+
logger.info("Pollination habitat layer written: %s", poll_out)
|
|
160
|
+
|
|
161
|
+
# ── Summary table ─────────────────────────────────────────────────────
|
|
162
|
+
log_step(6, "Building zonal summary table")
|
|
163
|
+
class_codes = [int(c) for c in np.unique(lc[~np.isnan(lc)])]
|
|
164
|
+
summary = zonal_summary(lc, carbon_map, class_codes, class_names)
|
|
165
|
+
summary["erosion_control_mean"] = [
|
|
166
|
+
round(float(np.nanmean(erosion_control[lc == c])), 4)
|
|
167
|
+
if np.any(lc == c) else np.nan for c in class_codes]
|
|
168
|
+
summary["pollination_mean"] = [
|
|
169
|
+
round(float(np.nanmean(poll_map[lc == c])), 4)
|
|
170
|
+
if np.any(lc == c) else np.nan for c in class_codes]
|
|
171
|
+
summary_out = output_dir / "es_summary_table.csv"
|
|
172
|
+
summary.to_csv(summary_out, index=False)
|
|
173
|
+
logger.info("ES summary:\n%s", summary.to_string(index=False))
|
|
174
|
+
logger.info("Outputs written to: %s", output_dir)
|
|
175
|
+
|
|
176
|
+
except FileNotFoundError as e:
|
|
177
|
+
logger.error(
|
|
178
|
+
"Input file not found: %s\n"
|
|
179
|
+
" Expected output from: geoprocessing-for-ecology\n"
|
|
180
|
+
" Check that previous step completed.",
|
|
181
|
+
e
|
|
182
|
+
)
|
|
183
|
+
raise
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.error("Unexpected error in ecosystem services assessment: %s", e)
|
|
186
|
+
raise
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
main()
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript tradeoff_analysis.R <es_summary_table.csv> <output_dir>
|
|
5
|
+
# ES trade-off and synergy analysis across pixels or land cover units
|
|
6
|
+
# Usage: Rscript tradeoff_analysis.R <es_summary_csv> <output_dir>
|
|
7
|
+
# Requires: dplyr, ggplot2, corrplot, tidyr
|
|
8
|
+
|
|
9
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
10
|
+
SKILL_NAME <- "ecosystem-services-assessment"
|
|
11
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
12
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
13
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
14
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
15
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
16
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
17
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
18
|
+
|
|
19
|
+
suppressPackageStartupMessages({
|
|
20
|
+
library(dplyr)
|
|
21
|
+
library(ggplot2)
|
|
22
|
+
library(corrplot)
|
|
23
|
+
library(tidyr)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
27
|
+
es_file <- ifelse(length(args) >= 1, args[1], "outputs/ecosystem_services/es_summary_table.csv")
|
|
28
|
+
output_dir <- ifelse(length(args) >= 2, args[2], "outputs/ecosystem_services")
|
|
29
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
30
|
+
|
|
31
|
+
log_info("Skill: %s | es_file=%s | output_dir=%s", SKILL_NAME, es_file, output_dir)
|
|
32
|
+
|
|
33
|
+
# ── Input precondition check ──────────────────────────────────────────────────
|
|
34
|
+
if (!file.exists(es_file)) {
|
|
35
|
+
log_error(
|
|
36
|
+
"Input nao encontrado: %s\nCausa provavel: o script de quantificacao de servicos ecossistemicos nao foi executado ou o caminho esta errado.\nVerifique: execute primeiro o script de mapeamento/quantificacao de ES.\nSkill anterior: ecosystem-services-assessment (quantification step).",
|
|
37
|
+
es_file
|
|
38
|
+
)
|
|
39
|
+
stop("Missing: ", es_file)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
log_step(1, "Carregar tabela de servicos ecossistemicos")
|
|
43
|
+
es <- tryCatch({
|
|
44
|
+
read.csv(es_file)
|
|
45
|
+
}, error = function(e) {
|
|
46
|
+
log_error(
|
|
47
|
+
"Falha ao ler CSV de servicos ecossistemicos: %s\nCausa provavel: arquivo corrompido ou com separador incorreto.\nVerifique: abra o arquivo em editor de texto e confira o formato.\nSkill anterior: ecosystem-services-assessment (quantification step).",
|
|
48
|
+
conditionMessage(e)
|
|
49
|
+
)
|
|
50
|
+
stop(e)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
log_info("Tabela de ES carregada: %d classes de uso do solo", nrow(es))
|
|
54
|
+
|
|
55
|
+
n_na_total <- sum(is.na(es))
|
|
56
|
+
if (n_na_total > 0) {
|
|
57
|
+
log_warn("Tabela de ES contem %d valores NA no total — correlacoes serao calculadas com 'complete.obs'.", n_na_total)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# ── Identify numeric ES columns ───────────────────────────────────────────────
|
|
61
|
+
log_step(2, "Identificar colunas de indicadores de ES e normalizar 0-1")
|
|
62
|
+
es_cols <- names(es)[sapply(es, is.numeric) & !names(es) %in% c("lulc_code", "n_pixels")]
|
|
63
|
+
log_info("Indicadores de ES: %s", paste(es_cols, collapse = ", "))
|
|
64
|
+
log_decision("es_cols", paste(es_cols, collapse = ", "),
|
|
65
|
+
"colunas numericas excluindo lulc_code e n_pixels sao tratadas como indicadores de ES")
|
|
66
|
+
|
|
67
|
+
if (length(es_cols) < 2) {
|
|
68
|
+
log_warn("Menos de 2 indicadores de ES encontrados — analise de trade-off nao e possivel com apenas %d coluna(s).", length(es_cols))
|
|
69
|
+
log_info("Encerrando sem erro. Adicione mais indicadores ao CSV de entrada.")
|
|
70
|
+
quit(status = 0)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# ── Normalise to 0-1 ──────────────────────────────────────────────────────────
|
|
74
|
+
es_norm <- tryCatch({
|
|
75
|
+
es |>
|
|
76
|
+
mutate(across(all_of(es_cols),
|
|
77
|
+
~ (. - min(., na.rm=TRUE)) / (max(., na.rm=TRUE) - min(., na.rm=TRUE) + 1e-10)))
|
|
78
|
+
}, error = function(e) {
|
|
79
|
+
log_error(
|
|
80
|
+
"Falha na normalizacao 0-1 dos indicadores: %s\nCausa provavel: colunas nao numericas identificadas incorretamente.\nVerifique: os tipos de coluna no CSV de entrada.\nSkill anterior: ecosystem-services-assessment (quantification step).",
|
|
81
|
+
conditionMessage(e)
|
|
82
|
+
)
|
|
83
|
+
stop(e)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
log_decision("normalization", "min-max [0,1] com epsilon 1e-10",
|
|
87
|
+
"evita divisao por zero quando todos os valores de um indicador sao iguais")
|
|
88
|
+
|
|
89
|
+
# ── Correlation matrix (Spearman) ─────────────────────────────────────────────
|
|
90
|
+
log_step(3, "Calcular matriz de correlacao de Spearman entre indicadores de ES")
|
|
91
|
+
cor_mat <- tryCatch({
|
|
92
|
+
cor(es_norm[es_cols], method = "spearman", use = "complete.obs")
|
|
93
|
+
}, error = function(e) {
|
|
94
|
+
log_error(
|
|
95
|
+
"Falha ao calcular matriz de correlacao: %s\nCausa provavel: todos os valores de alguma coluna sao NA apos normalizacao.\nVerifique: presenca de variacao nos indicadores de ES.\nSkill anterior: ecosystem-services-assessment (quantification step).",
|
|
96
|
+
conditionMessage(e)
|
|
97
|
+
)
|
|
98
|
+
stop(e)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
log_decision("correlation_method", "Spearman",
|
|
102
|
+
"metodo nao-parametrico robusto a distribuicoes assimetricas comuns em dados de ES")
|
|
103
|
+
|
|
104
|
+
tryCatch({
|
|
105
|
+
write.csv(as.data.frame(cor_mat), file.path(output_dir, "tradeoff_matrix.csv"))
|
|
106
|
+
log_info("tradeoff_matrix.csv salvo em: %s", output_dir)
|
|
107
|
+
}, error = function(e) {
|
|
108
|
+
log_error(
|
|
109
|
+
"Falha ao salvar tradeoff_matrix.csv: %s\nCausa provavel: permissao negada ou disco cheio.\nVerifique: permissoes do diretorio de saida.\nSkill anterior: nenhuma.",
|
|
110
|
+
conditionMessage(e)
|
|
111
|
+
)
|
|
112
|
+
stop(e)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
# ── Correlation heatmap ────────────────────────────────────────────────────────
|
|
116
|
+
log_step(4, "Gerar heatmap de trade-offs (corrplot)")
|
|
117
|
+
tryCatch({
|
|
118
|
+
png(file.path(output_dir, "tradeoff_heatmap.png"), width = 800, height = 700, res = 150)
|
|
119
|
+
corrplot(cor_mat, method = "color", type = "upper", tl.cex = 0.8,
|
|
120
|
+
addCoef.col = "black", number.cex = 0.7, cl.cex = 0.7,
|
|
121
|
+
title = "ES Trade-offs (Spearman r)", mar = c(0, 0, 2, 0))
|
|
122
|
+
dev.off()
|
|
123
|
+
log_info("tradeoff_heatmap.png salvo em: %s", output_dir)
|
|
124
|
+
}, error = function(e) {
|
|
125
|
+
log_error(
|
|
126
|
+
"Falha ao gerar heatmap de trade-offs: %s\nCausa provavel: corrplot nao instalado ou matriz de correlacao invalida.\nVerifique: se o pacote corrplot esta disponivel e a matriz tem pelo menos 2 variaveis.\nSkill anterior: nenhuma.",
|
|
127
|
+
conditionMessage(e)
|
|
128
|
+
)
|
|
129
|
+
stop(e)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
# ── Scatter plots for top pairs ───────────────────────────────────────────────
|
|
133
|
+
log_step(5, "Gerar graficos de dispersao para pares de indicadores de ES")
|
|
134
|
+
pair_combos <- combn(es_cols, 2, simplify = FALSE)
|
|
135
|
+
n_pairs <- min(6, length(pair_combos))
|
|
136
|
+
log_info("Gerando %d graficos de dispersao (de %d pares possiveis)", n_pairs, length(pair_combos))
|
|
137
|
+
log_decision("max_scatter_plots", as.character(n_pairs),
|
|
138
|
+
"limitado a 6 pares para evitar geracao excessiva de arquivos")
|
|
139
|
+
|
|
140
|
+
for (pr in pair_combos[seq_len(n_pairs)]) {
|
|
141
|
+
tryCatch({
|
|
142
|
+
p <- ggplot(es |> mutate(label = lulc_code),
|
|
143
|
+
aes(x = .data[[pr[1]]], y = .data[[pr[2]]], label = label)) +
|
|
144
|
+
geom_point(size = 3, colour = "#2166ac") +
|
|
145
|
+
ggrepel::geom_text_repel(size = 2.5, max.overlaps = 10) +
|
|
146
|
+
labs(x = pr[1], y = pr[2],
|
|
147
|
+
title = paste("Trade-off:", pr[1], "vs", pr[2])) +
|
|
148
|
+
theme_bw()
|
|
149
|
+
fname <- paste0("scatter_", pr[1], "_vs_", pr[2], ".png")
|
|
150
|
+
ggsave(file.path(output_dir, fname), p, width = 5, height = 4, dpi = 150)
|
|
151
|
+
log_info("Salvo: %s", fname)
|
|
152
|
+
}, error = function(e) {
|
|
153
|
+
log_error(
|
|
154
|
+
"Falha ao gerar grafico de dispersao para par %s vs %s: %s\nCausa provavel: coluna ausente apos filtragem ou problema com ggrepel.\nVerifique: se as colunas '%s' e '%s' existem e possuem dados validos.\nSkill anterior: nenhuma.",
|
|
155
|
+
pr[1], pr[2], conditionMessage(e), pr[1], pr[2]
|
|
156
|
+
)
|
|
157
|
+
stop(e)
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
log_info("Analise de trade-off concluida. Saidas em: %s", output_dir)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: environmental-time-series
|
|
3
|
+
description: "Detects trends, breakpoints, and recovery trajectories in environmental time series data from remote sensing or field measurements. Use this skill when the user mentions time series analysis, NDVI/EVI/LST trends, Mann-Kendall tests, Sen slope, BFAST breakpoints, structural change detection, seasonal decomposition (STL), anomaly detection, recovery trajectories, regime shifts, or pixel-wise trend analysis."
|
|
4
|
+
skill_version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill: environmental-time-series
|
|
8
|
+
|
|
9
|
+
**Domain:** Trend · Seasonality · Breakpoints · Anomalies · Recovery
|
|
10
|
+
**Phase:** 2 — Modeling
|
|
11
|
+
**Used by:** build-fire-risk-map, analyze-environmental-change
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Purpose
|
|
16
|
+
|
|
17
|
+
Guides the agent through the analysis of environmental and ecological time series: trend detection, seasonal decomposition, structural breakpoint identification, anomaly detection, and post-disturbance recovery trajectory estimation.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## When to Invoke
|
|
22
|
+
|
|
23
|
+
- Analysing NDVI, EVI, LST, rainfall, or any environmental time series
|
|
24
|
+
- Detecting trend direction and magnitude in ecological indicators
|
|
25
|
+
- Identifying regime shifts or abrupt changes in a time series
|
|
26
|
+
- Characterising anomalies relative to historical baseline
|
|
27
|
+
- Estimating recovery time after a disturbance event
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Inputs
|
|
32
|
+
|
|
33
|
+
| Input | Format | Required |
|
|
34
|
+
|-------|--------|----------|
|
|
35
|
+
| Time-indexed environmental variable | CSV or GeoTIFF time stack | Yes |
|
|
36
|
+
| Time series frequency (daily, monthly, annual) | Text | Yes |
|
|
37
|
+
| Disturbance event date (for recovery) | Date | Conditional |
|
|
38
|
+
| Historical baseline period | Date range | Recommended |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Outputs
|
|
43
|
+
|
|
44
|
+
| Output | Description |
|
|
45
|
+
|--------|-------------|
|
|
46
|
+
| `trend_results.csv` | Trend slope, magnitude, p-value per pixel or site |
|
|
47
|
+
| `seasonal_decomposition.png` | STL decomposition plot |
|
|
48
|
+
| `breakpoints.csv` | Detected breakpoint dates with confidence intervals |
|
|
49
|
+
| `anomaly_series.csv` | Standardised anomaly per time step |
|
|
50
|
+
| `recovery_metrics.csv` | Recovery rate and 80/100% recovery date |
|
|
51
|
+
| `timeseries_report.md` | Analysis narrative |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Steps
|
|
56
|
+
|
|
57
|
+
### 1. Inspect and Clean the Series
|
|
58
|
+
- Plot raw series; identify obvious outliers, data gaps, sensor artifacts
|
|
59
|
+
- Interpolate short gaps (≤ 3 steps) if justified; document method
|
|
60
|
+
- Report temporal extent, frequency, and missing value rate
|
|
61
|
+
|
|
62
|
+
### 2. Trend Detection
|
|
63
|
+
- Mann-Kendall test for monotonic trend (non-parametric, handles non-normality)
|
|
64
|
+
- Sen's slope estimator for trend magnitude
|
|
65
|
+
- For raster: pixel-wise MK + Sen's slope
|
|
66
|
+
- Report τ (Kendall's tau), p-value, and slope in original units/year
|
|
67
|
+
|
|
68
|
+
### 3. Seasonal Decomposition
|
|
69
|
+
- STL decomposition (Seasonal-Trend decomposition using LOESS): robust to outliers
|
|
70
|
+
- Report seasonal amplitude, trend component, and remainder
|
|
71
|
+
- For annual data with no seasonality, skip this step
|
|
72
|
+
|
|
73
|
+
### 4. Breakpoint Detection
|
|
74
|
+
- BFAST (Breaks For Additive Season and Trend): simultaneous trend and breakpoint
|
|
75
|
+
- `strucchange` (R) for structural change tests
|
|
76
|
+
- Report: number of breakpoints, dates, 95% CIs
|
|
77
|
+
- Verify detected breakpoints against known events (fire, drought, deforestation)
|
|
78
|
+
|
|
79
|
+
### 5. Anomaly Detection
|
|
80
|
+
- Compute historical baseline (mean ± SD or percentiles) from reference period
|
|
81
|
+
- Standardised anomaly: z = (x − μ) / σ
|
|
82
|
+
- Flag values beyond ±2σ as anomalous
|
|
83
|
+
- For rainfall: SPI (Standardised Precipitation Index)
|
|
84
|
+
- For vegetation: VCI (Vegetation Condition Index) or zNDVI
|
|
85
|
+
|
|
86
|
+
### 6. Recovery Trajectory (post-disturbance)
|
|
87
|
+
- Define pre-disturbance baseline from N years before event
|
|
88
|
+
- Fit recovery curve: linear, exponential, or logistic
|
|
89
|
+
- Estimate: time to 80% recovery, time to full recovery
|
|
90
|
+
- Compute Recovery Indicator (RI) = (post − min) / (pre − min)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Key Decisions to Document
|
|
95
|
+
|
|
96
|
+
- Baseline period definition
|
|
97
|
+
- Gap interpolation method
|
|
98
|
+
- Seasonal decomposition method and parameters
|
|
99
|
+
- Breakpoint detection method and significance level
|
|
100
|
+
- Anomaly threshold (z-score cutoff)
|
|
101
|
+
- Recovery model type
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Tools and Libraries
|
|
106
|
+
|
|
107
|
+
**R:** `bfast`, `strucchange`, `trend`, `zoo`, `terra`, `ggplot2`
|
|
108
|
+
**Python:** `pymannkendall`, `ruptures`, `statsmodels.tsa`, `xarray`
|
|
109
|
+
**Remote sensing:** Google Earth Engine (pixel-wise BFAST, trend)
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Resources
|
|
114
|
+
|
|
115
|
+
- `resources/bfast-parameter-guide.md` — how to set h, season, and type in BFAST
|
|
116
|
+
- `resources/anomaly-indices-reference.md` — SPI, VCI, zNDVI definitions
|
|
117
|
+
- `examples/` — worked BFAST and recovery analysis example
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Notes
|
|
122
|
+
|
|
123
|
+
- Autocorrelation in time series violates independence assumptions of standard tests; use MK which handles it
|
|
124
|
+
- BFAST requires at least 3 full seasonal cycles to be reliable
|
|
125
|
+
- For raster time series > 10 years monthly, consider GEE for computational efficiency
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Example Invocation Prompts — environmental-time-series
|
|
2
|
+
|
|
3
|
+
## NDVI Trend and Breakpoint Analysis
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Load skill: environmental-time-series
|
|
7
|
+
Task: Analyse long-term NDVI trends in the Cerrado (2001–2023) from MODIS MOD13A3.
|
|
8
|
+
|
|
9
|
+
Input: data/ndvi_monthly_cerrado.csv
|
|
10
|
+
Columns: date (YYYY-MM-DD), pixel_id, ndvi (0–1 scaled)
|
|
11
|
+
|
|
12
|
+
Steps:
|
|
13
|
+
1. STL decomposition (period = 12 months).
|
|
14
|
+
2. Mann-Kendall trend test + Sen's slope per pixel (or aggregated if single site).
|
|
15
|
+
3. BFAST breakpoint detection (h = 0.15, season = "harmonic").
|
|
16
|
+
4. Standardised anomaly relative to 2001–2010 baseline.
|
|
17
|
+
5. Report: trend slope (NDVI/year), breakpoint dates, anomaly time series.
|
|
18
|
+
Output: trend_results.csv, breakpoints.csv, decomposition_plot.png, anomaly_series.csv
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Post-Fire Recovery
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Load skill: environmental-time-series
|
|
25
|
+
Task: Estimate vegetation recovery trajectory after the 2020 fires in the Pantanal.
|
|
26
|
+
Fire event date: 2020-07-01.
|
|
27
|
+
Pre-fire baseline: 2015-01-01 to 2020-06-30.
|
|
28
|
+
Data: data/ndvi_pantanal_recovery.csv (monthly NDVI per burned polygon, 2015–2023)
|
|
29
|
+
|
|
30
|
+
Fit recovery curve (logistic or exponential).
|
|
31
|
+
Compute: Recovery Indicator (RI) per year, estimated time to 80% and 100% recovery.
|
|
32
|
+
Output: recovery_metrics.csv, recovery_curves.png
|
|
33
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Environmental Anomaly Indices Reference
|
|
2
|
+
|
|
3
|
+
## Standardised Precipitation Index (SPI)
|
|
4
|
+
|
|
5
|
+
Measures rainfall deficit/surplus relative to a long-term distribution, at multiple time scales.
|
|
6
|
+
|
|
7
|
+
| SPI value | Category |
|
|
8
|
+
|-----------|---------|
|
|
9
|
+
| > 2.0 | Extremely wet |
|
|
10
|
+
| 1.5 to 2.0 | Very wet |
|
|
11
|
+
| 1.0 to 1.5 | Moderately wet |
|
|
12
|
+
| -1.0 to 1.0 | Near normal |
|
|
13
|
+
| -1.5 to -1.0 | Moderately dry |
|
|
14
|
+
| -2.0 to -1.5 | Severely dry |
|
|
15
|
+
| < -2.0 | Extremely dry |
|
|
16
|
+
|
|
17
|
+
Time scales: SPI-1 (monthly), SPI-3 (seasonal), SPI-6, SPI-12 (annual drought).
|
|
18
|
+
|
|
19
|
+
```r
|
|
20
|
+
library(SPEI)
|
|
21
|
+
data(wichita) # example dataset
|
|
22
|
+
spi_3 <- spi(wichita$PRCP, scale = 3)
|
|
23
|
+
plot(spi_3)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Standardised Precipitation Evapotranspiration Index (SPEI)
|
|
27
|
+
|
|
28
|
+
Like SPI but accounts for evapotranspiration (temperature effect). Better for drought assessment under warming climate.
|
|
29
|
+
|
|
30
|
+
```r
|
|
31
|
+
spei_12 <- spei(wichita$PRCP - wichita$PET, scale = 12)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Vegetation Condition Index (VCI)
|
|
35
|
+
|
|
36
|
+
Normalises NDVI relative to historical minimum and maximum for the same period of year.
|
|
37
|
+
|
|
38
|
+
VCI = 100 × (NDVI − NDVI_min) / (NDVI_max − NDVI_min)
|
|
39
|
+
|
|
40
|
+
| VCI | Vegetation condition |
|
|
41
|
+
|-----|---------------------|
|
|
42
|
+
| 0–10 | Extreme stress |
|
|
43
|
+
| 10–20 | Severe stress |
|
|
44
|
+
| 20–40 | Moderate stress |
|
|
45
|
+
| 40–60 | Good condition |
|
|
46
|
+
| 60–80 | Very good |
|
|
47
|
+
| 80–100 | Excellent |
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import numpy as np
|
|
51
|
+
# NDVI time stack (time × lat × lon)
|
|
52
|
+
vci = 100 * (ndvi - ndvi.min(axis=0)) / (ndvi.max(axis=0) - ndvi.min(axis=0) + 1e-6)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Standardised NDVI Anomaly (zNDVI)
|
|
56
|
+
|
|
57
|
+
zNDVI = (NDVI_i − μ_NDVI) / σ_NDVI
|
|
58
|
+
|
|
59
|
+
where μ and σ are computed per pixel over the baseline period (same calendar period).
|
|
60
|
+
|
|
61
|
+
Interpretation: zNDVI < -1.5 indicates vegetation stress; zNDVI < -2.0 indicates severe anomaly.
|
|
62
|
+
|
|
63
|
+
## Temperature Anomaly
|
|
64
|
+
|
|
65
|
+
T_anomaly = T_i − T_climatological_mean (for the same month/season)
|
|
66
|
+
|
|
67
|
+
## Recovery Indicator (RI)
|
|
68
|
+
|
|
69
|
+
Post-disturbance vegetation recovery relative to pre-disturbance baseline:
|
|
70
|
+
|
|
71
|
+
RI_t = (NDVI_t − NDVI_min) / (NDVI_pre − NDVI_min)
|
|
72
|
+
|
|
73
|
+
- RI = 0: at the lowest post-disturbance value
|
|
74
|
+
- RI = 1: full recovery to pre-disturbance level
|
|
75
|
+
- RI < 0: further decline after disturbance
|
|
76
|
+
- RI > 1: exceeds pre-disturbance level (e.g., fire-stimulated flush)
|
|
77
|
+
|
|
78
|
+
## Reference Datasets
|
|
79
|
+
|
|
80
|
+
| Variable | Product | Resolution | Source |
|
|
81
|
+
|----------|---------|-----------|--------|
|
|
82
|
+
| NDVI (8-day) | MODIS MOD13Q1 | 250 m | NASA LP DAAC |
|
|
83
|
+
| NDVI (monthly) | MODIS MOD13A3 | 1 km | NASA LP DAAC |
|
|
84
|
+
| NDVI (daily, 10 m) | Sentinel-2 (Harmonized) | 10 m | Copernicus / GEE |
|
|
85
|
+
| Rainfall | CHIRPS v2.0 | 5 km | UCSB Climate Hazards Group |
|
|
86
|
+
| Rainfall | ERA5-Land | 9 km | ECMWF |
|
|
87
|
+
| LST | MODIS MOD11A2 | 1 km | NASA LP DAAC |
|
|
88
|
+
| SPI | Global SPEI Database | 0.5° | CSIC Spain |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# BFAST Parameter Guide
|
|
2
|
+
|
|
3
|
+
BFAST (Breaks For Additive Season and Trend) detects breakpoints in both the seasonal and trend components of a time series simultaneously.
|
|
4
|
+
|
|
5
|
+
## Key Parameters
|
|
6
|
+
|
|
7
|
+
### `h` — Minimum Segment Size
|
|
8
|
+
Minimum proportion of observations between two consecutive breakpoints.
|
|
9
|
+
- Default: `h = 0.15` (15% of n observations)
|
|
10
|
+
- Example: for n = 120 monthly observations, h = 0.15 means ≥ 18 observations per segment (≥ 1.5 years)
|
|
11
|
+
- **Set smaller** if breakpoints may occur close together
|
|
12
|
+
- **Set larger** to avoid detecting noise as breakpoints
|
|
13
|
+
|
|
14
|
+
### `season` — Seasonal Model
|
|
15
|
+
- `"harmonic"`: Fourier terms (sine + cosine); recommended for MODIS NDVI, temperature
|
|
16
|
+
- `"dummy"`: Dummy variables for each period; less flexible
|
|
17
|
+
- `"none"`: No seasonal component; for annual time series
|
|
18
|
+
|
|
19
|
+
### `max.iter` — Maximum Iterations
|
|
20
|
+
- Default: `max.iter = 10`
|
|
21
|
+
- Increase to 20–50 for complex time series that do not converge quickly
|
|
22
|
+
|
|
23
|
+
### `breaks` — Maximum Number of Breakpoints
|
|
24
|
+
- Default: `NULL` (auto-select by BIC)
|
|
25
|
+
- Set `breaks = 1` to force detection of at most 1 breakpoint
|
|
26
|
+
- Set `breaks = 5` for long series with multiple expected events
|
|
27
|
+
|
|
28
|
+
### `type` — Type of Breakpoint Test
|
|
29
|
+
- `"OLS-MOSUM"` (default): Fast; appropriate for most cases
|
|
30
|
+
- `"OLS-CUSUM"`: More sensitive to gradual changes
|
|
31
|
+
- `"DYNP"`: Dynamic programming; exact but slow for large n
|
|
32
|
+
|
|
33
|
+
## Typical MODIS NDVI Configuration
|
|
34
|
+
|
|
35
|
+
```r
|
|
36
|
+
library(bfast)
|
|
37
|
+
|
|
38
|
+
# Monthly NDVI time series (16-day composites aggregated to monthly)
|
|
39
|
+
ts_ndvi <- ts(ndvi_vector, start = c(2000, 1), frequency = 12)
|
|
40
|
+
|
|
41
|
+
fit <- bfast(ts_ndvi,
|
|
42
|
+
h = 0.15,
|
|
43
|
+
season = "harmonic",
|
|
44
|
+
max.iter = 20,
|
|
45
|
+
breaks = NULL)
|
|
46
|
+
|
|
47
|
+
# Extract results
|
|
48
|
+
if (fit$output[[1]]$Tt.bp > 0) { # breakpoints detected
|
|
49
|
+
bp_dates <- fit$output[[1]]$bp.Vt$breakpoints
|
|
50
|
+
cat("Breakpoints at observations:", bp_dates, "\n")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
plot(fit)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Interpreting Outputs
|
|
57
|
+
|
|
58
|
+
| Component | Meaning |
|
|
59
|
+
|-----------|---------|
|
|
60
|
+
| `Tt` | Trend component with breakpoints |
|
|
61
|
+
| `St` | Seasonal component with breakpoints |
|
|
62
|
+
| `Nt` | Remainder (noise) |
|
|
63
|
+
| `bp.Vt` | Trend breakpoints (indices) |
|
|
64
|
+
| `bp.Wt` | Seasonal breakpoints (indices) |
|
|
65
|
+
|
|
66
|
+
## Minimum Series Length
|
|
67
|
+
- At least **3 full seasonal cycles** are required
|
|
68
|
+
- For monthly data: ≥ 36 observations (3 years)
|
|
69
|
+
- For 16-day MODIS: ≥ 69 observations (≥ 3 years)
|