CMIP6_Rain‐based seasonal detection methods - Rwema25/AE-project GitHub Wiki

Evaluation of Top Three Rain-Based Seasonal Detection Methods

Introduction

In evaluating rain-based seasonal detection methods, it is essential to consider criteria that reflect both scientific rigor and practical applicability. Key factors include the types of input variables required, the generalizability of the method across diverse climatic and soil conditions, and the extent to which the method has been adopted by major institutions and operational projects globally. It is important to balance methodological sophistication with feasibility, especially in data-limited contexts, while also ensuring that the approach aligns with real-world agricultural decision-making needs. This evaluation focuses on three representative methods that span this spectrum, from simple rainfall thresholds to integrated soil moisture models and advanced satellite vegetation indices, providing a comprehensive overview of their strengths, limitations, and institutional relevance.

1. Rainfall Threshold Method (FAO Approach)

  • Input Variables: Daily rainfall only.
  • Generalizability:
    • Regional: Hard-coded parameters (e.g., "25mm in first dekad, 20mm in next two") limit global applicability.
    • Soil Blindness: Ignores soil properties (e.g., drainage in sandy vs. clay soils), reducing accuracy in heterogeneous regions.
  • Institutional Adoption:
    • High: FAO’s standard method for agrometeorological bulletins and seasonal maps.
    • Approach Type: Heuristic, rule-based method using fixed rainfall thresholds.
    • Strengths: Low technical barriers, ideal for resource-limited regions.
    • Weaknesses: Fails in climate-variable zones; no evaporation/temperature integration.
  • Documentation: FAO Handbook (2019).

2. Soil Moisture-Integrated Approach

  • Input Variables: Rainfall, soil type, temperature, evapotranspiration.
  • Generalizability:
    • Moderate-High: Accounts for soil water-holding capacity (e.g., sand vs. clay) and climate.
    • Adaptability: Adjustable parameters for regional calibration (e.g., using SoilGrids data).
  • Institutional Adoption:
    • Moderate: World Bank projects (e.g., East Africa climate-smart agriculture); not yet FAO-adopted.
    • Approach Type: Model-based, incorporating soil water balance and climate variables.
    • Strengths: Reflects actual planting conditions; superior to rainfall-only methods.
    • Weaknesses: Requires soil data and local calibration.
  • Documentation:

3. Vegetation Index-Driven Method (NDVI/MVDI)

  • Input Variables: Satellite vegetation indices (NDVI/PRI), rainfall, temperature.
  • Generalizability:
    • High: Tracks actual plant "green-up" via satellite; applicable globally.
    • Strength: Less noise from dry spells; captures phenology (e.g., growing season length).
  • Institutional Adoption:
    • Emerging: NASA/ESA projects (e.g., MODIS VI); limited operational use in agriculture.
    • Approach Type: Model-based using remote sensing to track vegetation phenology.
    • Strengths: Direct biomass monitoring; large-scale suitability.
    • Weaknesses: Cloud cover disrupts data; technical capacity required.
  • Documentation:

Comparative Summary

Method Input Variables Approach Type Generalizability Institutional Adoption Strengths Limitations
Rainfall Threshold Rainfall only Heuristic (rule-based) Regional; parameters often hard-coded for specific climates and soils High (FAO widely used) Simple, low data needs, easy to implement Limited transferability; ignores soil and temp
Soil Moisture-Integrated Rainfall + soil + climate Model-based Moderate-High; adaptable with soil and climate data Moderate (World Bank projects) More accurate; accounts for soil water retention Requires soil data and calibration; moderate complexity
Vegetation Index (MVDI) Satellite vegetation indices + climate Model-based High; applicable globally via remote sensing Emerging (NASA/ESA) Captures actual plant phenology; large-scale monitoring Cloud cover issues; technical and data intensive

Recommendations

  1. Tiered Implementation:

    • Resource-Limited: FAO rainfall threshold (simple, low-cost).
    • Data-Rich Regions: Soil moisture method (balanced accuracy/feasibility).
    • Advanced Monitoring: NDVI/MVDI for research/policy (e.g., climate adaptation).
  2. Validation Protocol:

    • Ground-truth methods using farmer-reported planting dates.

References

Testing threshold-based methods over different regions

Threshold-based methods are among the simplest and most widely used approaches to detect the onset and cessation of rainy seasons. These methods define specific rainfall criteria, such as cumulative rainfall over a certain number of days or a minimum daily rainfall threshold, to objectively determine when the rainy season starts and ends.

Adopted criteria as applied in previous literature:

Term Definition References
Onset Date (OD) The first period of five consecutive days accumulating at least 25 mm of rainfall, with the first day and at least two others having rainfall ≥ 1 mm, and no dry spell (rainfall < 1 mm) lasting 7 days or more in the following 30 days. Adelekan & Adegebo (2014); Mugo et al. (2016); Camberlin et al. (2009); Haleakala et al. (2018); Recha et al. (2012); Rwema et al. (2025); FAO Handbook (2019)
Cessation Date (CD) The first day when the accumulated rainfall over ten consecutive days is less than half of the corresponding potential evapotranspiration (PET). Kijazi & Reason (2012) ; Sebaziga et al. (2024)
Season Length (SL) The number of days between the onset and cessation dates, calculated by subtracting OD from CD for each year. Gebremichael et al. (2014); Haleakala et al. (2018)

Onset Detection for a Single Year (2024): Code and Results

The following R code demonstrates how to fetch Climate Data from the Open-Meteo API and detect the onset dates of the long and short rainy seasons for the year 2024 using our defined rainfall and dry spell criteria.

# Load Required Libraries

# Install if not already installed
if (!require(httr)) install.packages("httr")
if (!require(jsonlite)) install.packages("jsonlite")
if (!require(dplyr)) install.packages("dplyr")
if (!require(zoo)) install.packages("zoo")

library(httr)
library(jsonlite)
library(dplyr)
library(zoo)

# Fetch Climate Data from Open-Meteo API

url <- "https://climate-api.open-meteo.com/v1/climate"

get_climate_data <- function(start_date, end_date) {
  params <- list(
    latitude = -1.2921,      # Nairobi latitude
    longitude = 36.8219,     # Nairobi longitude
    start_date = start_date,
    end_date = end_date,
    models = "MRI_AGCM3_2_S",     # This model can be changed to try others
    daily = "temperature_2m_max,precipitation_sum",
    temperature_unit = "celsius",
    precipitation_unit = "mm",
    timeformat = "iso8601"
  )
  response <- GET(url, query = params)
  if (status_code(response) == 200) {
    data <- content(response, as = "parsed", type = "application/json")
    dates <- unlist(data$daily$time)
    temp_max <- unlist(data$daily$temperature_2m_max)
    precip <- unlist(data$daily$precipitation_sum)
    df <- data.frame(
      Date = as.Date(dates),
      Temperature_Max_C = temp_max,
      Rainfall = precip
    )
    return(df)
  } else {
    warning(paste("Request failed with status:", status_code(response), "for", start_date, "to", end_date))
    return(NULL)
  }
}

# Fetch data month by month for 2024
start_dates <- seq(as.Date("2024-01-01"), as.Date("2024-12-01"), by = "month")
end_dates <- seq(as.Date("2024-01-31"), as.Date("2024-12-31"), by = "month")
if(length(end_dates) < length(start_dates)) {
  end_dates <- c(end_dates, as.Date("2024-12-31"))
}

full_year_data <- data.frame()
for (i in seq_along(start_dates)) {
  cat("Fetching data from", as.character(start_dates[i]), "to", as.character(end_dates[i]), "\n")
  time_taken <- system.time({
    monthly_data <- tryCatch(
      get_climate_data(as.character(start_dates[i]), as.character(end_dates[i])),
      error = function(e) {
        cat("Error for", as.character(start_dates[i]), "to", as.character(end_dates[i]), "\n")
        return(NULL)
      }
    )
  })
  cat("Time taken:", time_taken["elapsed"], "seconds\n\n")
  if(!is.null(monthly_data)) {
    full_year_data <- bind_rows(full_year_data, monthly_data)
  }
}
full_year_data <- distinct(full_year_data)

# Print the first few rows of the data
print(head(full_year_data))

# Onset Detection Logic: Definition
# --- Onset detection function ---
detect_onset_precise <- function(rainfall, 
                                 onset_cum_threshold = 25, 
                                 onset_window = 5, 
                                 min_rainy_days = 3,  # including first day
                                 min_first_day_rain = 1,
                                 dry_threshold = 1,
                                 max_dry_spell_length = 6,  # less than 7 days allowed
                                 dry_spell_check_days = 30) {
  n <- length(rainfall)
  rolling_sum <- zoo::rollapply(rainfall, width = onset_window, FUN = sum, align = "left", fill = NA)
  for (i in 1:(n - onset_window - dry_spell_check_days + 1)) {
    if (!is.na(rolling_sum[i]) && rolling_sum[i] >= onset_cum_threshold) {
      window_rain <- rainfall[i:(i + onset_window - 1)]
      if (window_rain[1] < min_first_day_rain) next
      rainy_days_other <- sum(window_rain[-1] >= min_first_day_rain)
      if (rainy_days_other < (min_rainy_days - 1)) next
      dry_period <- rainfall[(i + onset_window):(i + onset_window + dry_spell_check_days - 1)]
      rle_dry <- rle(dry_period < dry_threshold)
      if (any(rle_dry$values == TRUE & rle_dry$lengths >= 7)) next
      return(i)
    }
  }
  return(NA)
}

detect_onset_period <- function(data, start_date, end_date) {
  period_data <- data %>% filter(Date >= start_date & Date <= end_date)
  onset_idx <- detect_onset_precise(period_data$Rainfall)
  if (!is.na(onset_idx)) {
    return(period_data$Date[onset_idx])
  } else {
    return(NA)
  }
}

# Define Rainy Seasons and Calculate Onset
year_of_interest <- 2024
long_rains_start <- as.Date(paste0(year_of_interest, "-03-01"))
long_rains_end <- as.Date(paste0(year_of_interest, "-06-30"))
short_rains_start <- as.Date(paste0(year_of_interest, "-09-01"))
short_rains_end <- as.Date(paste0(year_of_interest, "-12-31"))

long_rains_onset <- detect_onset_period(full_year_data, long_rains_start, long_rains_end)
short_rains_onset <- detect_onset_period(full_year_data, short_rains_start, short_rains_end)

cat("Year:", year_of_interest, "\n")
cat("Long rains onset:", ifelse(is.na(long_rains_onset), "No onset detected", as.character(long_rains_onset)), "\n")
cat("Short rains onset:", ifelse(is.na(short_rains_onset), "No onset detected", as.character(short_rains_onset)), "\n")

The output below shows the first rows of the climate data printed by the scripts

print(head(full_year_data))
        Date Temperature_Max_C Rainfall
1 2024-01-01              29.3     0.00
2 2024-01-02              28.5     1.46
3 2024-01-03              26.8     2.19
4 2024-01-04              26.7     0.00
5 2024-01-05              27.1     0.00
6 2024-01-06              29.8     0.88

The output below shows the detected onset dates for the long and short rainy seasons in 2024 based on the applied criteria.

cat("Year:", year_of_interest, "\n")
Year: 2024 
> cat("Long rains onset:", ifelse(is.na(long_rains_onset), "No onset detected", as.character(long_rains_onset)), "\n")
Long rains onset: 2024-03-18 
> cat("Short rains onset:", ifelse(is.na(short_rains_onset), "No onset detected", as.character(short_rains_onset)), "\n")
Short rains onset: 2024-11-14 

Onset Detection for the dataset (1981-2024): Code and Results

Below is the complete R script to fetch Climate Data from the Open-Meteo API and detect the onset dates of the long and short rainy seasons for each year from 1981 to 2024, using a precise rainfall and dry spell definition. The code fetches climate data from the Open-Meteo API and processes the daily rainfall data, applies the onset detection criteria, and outputs a clean summary of onset dates for both seasons.

# Load Required Libraries
if (!require(httr)) install.packages("httr")
if (!require(jsonlite)) install.packages("jsonlite")
if (!require(dplyr)) install.packages("dplyr")
if (!require(zoo)) install.packages("zoo")
if (!require(tibble)) install.packages("tibble")

library(httr)
library(jsonlite)
library(dplyr)
library(zoo)
library(tibble)  # For better data frame handling

# Function to fetch climate data from API for a given date range
get_climate_data <- function(start_date, end_date) {
  params <- list(
    latitude = -1.2921,       # Dakar latitude
    longitude = 36.8219,     # Dakar longitude
    start_date = start_date,
    end_date = end_date,
    models = "MRI_AGCM3_2_S",     # Change model if needed
    daily = "temperature_2m_max,precipitation_sum",
    temperature_unit = "celsius",
    precipitation_unit = "mm",
    timeformat = "iso8601"
  )
  response <- GET("https://climate-api.open-meteo.com/v1/climate", query = params)
  if (status_code(response) == 200) {
    data <- content(response, as = "parsed", type = "application/json")
    dates <- unlist(data$daily$time)
    temp_max <- unlist(data$daily$temperature_2m_max)
    precip <- unlist(data$daily$precipitation_sum)
    df <- data.frame(
      Date = as.Date(dates),
      Temperature_Max_C = temp_max,
      Rainfall = precip
    )
    return(df)
  } else {
    warning(paste("Request failed with status:", status_code(response), "for", start_date, "to", end_date))
    return(NULL)
  }
}

# Onset detection function (unchanged)
detect_onset_precise <- function(rainfall, 
                                 onset_cum_threshold = 25, 
                                 onset_window = 5, 
                                 min_rainy_days = 3,  
                                 min_first_day_rain = 1,
                                 dry_threshold = 1,
                                 max_dry_spell_length = 6,  
                                 dry_spell_check_days = 30) {
  n <- length(rainfall)
  rolling_sum <- zoo::rollapply(rainfall, width = onset_window, FUN = sum, align = "left", fill = NA)
  for (i in 1:(n - onset_window - dry_spell_check_days + 1)) {
    if (!is.na(rolling_sum[i]) && rolling_sum[i] >= onset_cum_threshold) {
      window_rain <- rainfall[i:(i + onset_window - 1)]
      if (window_rain[1] < min_first_day_rain) next
      rainy_days_other <- sum(window_rain[-1] >= min_first_day_rain)
      if (rainy_days_other < (min_rainy_days - 1)) next
      dry_period <- rainfall[(i + onset_window):(i + onset_window + dry_spell_check_days - 1)]
      rle_dry <- rle(dry_period < dry_threshold)
      if (any(rle_dry$values == TRUE & rle_dry$lengths >= 7)) next
      return(i)
    }
  }
  return(NA)
}

detect_onset_period <- function(data, start_date, end_date) {
  period_data <- data %>% 
    filter(Date >= start_date & Date <= end_date) %>% 
    arrange(Date)
  
  # Ensure Date column is Date class
  period_data$Date <- as.Date(period_data$Date)
  
  onset_idx <- detect_onset_precise(period_data$Rainfall)
  
  if (!is.na(onset_idx)) {
    onset_date <- period_data$Date[onset_idx]
    return(onset_date)
  } else {
    return(NA)
  }
}

# Prepare to fetch data for each year from 1981 to 2024
years <- 1981:2024
all_data <- data.frame()

for (year in years) {
  cat("Fetching data for year:", year, "\n")
  
  # Generate monthly intervals for the year
  start_dates <- seq(as.Date(paste0(year, "-01-01")), as.Date(paste0(year, "-12-01")), by = "month")
  end_dates <- seq(as.Date(paste0(year, "-01-31")), as.Date(paste0(year, "-12-31")), by = "month")
  if (length(end_dates) < length(start_dates)) {
    end_dates <- c(end_dates, as.Date(paste0(year, "-12-31")))
  }
  
  yearly_data <- data.frame()
  
  for (i in seq_along(start_dates)) {
    cat("  Fetching from", as.character(start_dates[i]), "to", as.character(end_dates[i]), "\n")
    time_taken <- system.time({
      monthly_data <- tryCatch(
        get_climate_data(as.character(start_dates[i]), as.character(end_dates[i])),
        error = function(e) {
          cat("  Error fetching data for", as.character(start_dates[i]), "to", as.character(end_dates[i]), "\n")
          return(NULL)
        }
      )
    })
    cat("  Time taken:", time_taken["elapsed"], "seconds\n")
    
    if (!is.null(monthly_data)) {
      yearly_data <- bind_rows(yearly_data, monthly_data)
    }
  }
  
  yearly_data <- distinct(yearly_data)
  all_data <- bind_rows(all_data, yearly_data)
}

all_data <- distinct(all_data)

cat("Data fetching complete.\n")
cat("Total records fetched:", nrow(all_data), "\n")

# Print the first and last few rows of the combined climate data
cat("First few rows of the data:\n")
print(head(all_data))

cat("\nLast few rows of the data:\n")
print(tail(all_data))

# Initialize results tibble with proper Date columns
results <- tibble(
  Year = integer(),
  Long_Rains_Onset = as.Date(character()),
  Short_Rains_Onset = as.Date(character())
)

for (year in years) {
  long_rains_start <- as.Date(paste0(year, "-03-01"))
  long_rains_end <- as.Date(paste0(year, "-06-30"))
  short_rains_start <- as.Date(paste0(year, "-09-01"))
  short_rains_end <- as.Date(paste0(year, "-12-31"))
  
  # Filter data for this year
  year_data <- all_data %>% filter(format(Date, "%Y") == as.character(year))
  
  if (nrow(year_data) == 0) {
    cat("No data for year", year, "\n")
    results <- bind_rows(results, tibble(Year = year, Long_Rains_Onset = NA, Short_Rains_Onset = NA))
    next
  }
  
  long_onset <- detect_onset_period(year_data, long_rains_start, long_rains_end)
  short_onset <- detect_onset_period(year_data, short_rains_start, short_rains_end)
  
  cat("Year:", year, "\n")
  cat("  Long rains onset:", ifelse(is.na(long_onset), "No onset detected", as.character(long_onset)), "\n")
  cat("  Short rains onset:", ifelse(is.na(short_onset), "No onset detected", as.character(short_onset)), "\n\n")
  
  results <- bind_rows(results, tibble(Year = year, Long_Rains_Onset = long_onset, Short_Rains_Onset = short_onset))
}

# View or save results
options(tibble.print_max = Inf)

print(results)

# write.csv(results, "rainfall_onset_1981_2024_dakar.csv", row.names = FALSE)

#############################################################
# To get climatograph for a selected year (2024)

library(lubridate)

monthly_data <- all_data %>%
  mutate(Year = year(Date), Month = month(Date, label = TRUE, abbr = TRUE)) %>%
  group_by(Year, Month) %>%
  summarise(
    Monthly_Rainfall = sum(Rainfall, na.rm = TRUE),
    Avg_Temperature = mean(Temperature_Max_C, na.rm = TRUE)
  ) %>%
  ungroup()

# Plot climatograph for a selected year
selected_year <- 2024
monthly_year <- monthly_data %>% filter(Year == selected_year)

ggplot(monthly_year, aes(x = Month)) +
  geom_col(aes(y = Monthly_Rainfall), fill = "skyblue") +
  geom_line(aes(y = Avg_Temperature * 10, group = 1), color = "red", size = 1) +
  scale_y_continuous(
    name = "Monthly Rainfall (mm)",
    sec.axis = sec_axis(~./10, name = "Avg Max Temperature (°C)")
  ) +
  labs(title = paste("Monthly Climatograph for", selected_year)) +
  theme_minimal()



#####################################################################
########To print monthly climatology by considering all period 1981-2024

library(ggplot2)
library(dplyr)
library(lubridate)
library(tidyr)

# Aggregate daily data into monthly total rainfall for 1981-2024
monthly_rainfall <- all_data %>%
  filter(year(Date) >= 1981 & year(Date) <= 2024) %>%
  mutate(Year = year(Date),
         Month = month(Date, label = TRUE, abbr = TRUE)) %>%
  group_by(Year, Month) %>%
  summarise(Monthly_Rainfall = sum(Rainfall, na.rm = TRUE), .groups = "drop")

# Calculate long-term average monthly rainfall across all years
monthly_climatology <- monthly_rainfall %>%
  group_by(Month) %>%
  summarise(Avg_Monthly_Rainfall = mean(Monthly_Rainfall, na.rm = TRUE), .groups = "drop")

# Ensure all months (Jan-Dec) are present and in correct order
monthly_climatology <- monthly_climatology %>%
  complete(Month = factor(month.abb, levels = month.abb), fill = list(Avg_Monthly_Rainfall = 0))

# Plot the climatograph for rainfall only
ggplot(monthly_climatology, aes(x = Month, y = Avg_Monthly_Rainfall)) +
  geom_col(fill = "skyblue", width = 0.9) +
  labs(
    title = "Long-Term Average Monthly Rainfall (1981-2024) - Dakar",
    x = "Month",
    y = "Average Monthly Rainfall (mm)"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

The output below shows the first and last rows of the climate data printed by the scripts

cat("First few rows of the data:\n")
First few rows of the data:
> print(head(all_data))
        Date Temperature_Max_C Rainfall
1 1981-01-01              25.3        0
2 1981-01-02              25.7        0
3 1981-01-03              26.6        0
4 1981-01-04              26.9        0
5 1981-01-05              26.8        0
6 1981-01-06              27.6        0
> cat("\nLast few rows of the data:\n")

Last few rows of the data:
> print(tail(all_data))
            Date Temperature_Max_C Rainfall
16066 2024-12-26              25.7     2.45
16067 2024-12-27              24.3     1.88
16068 2024-12-28              25.6     2.17
16069 2024-12-29              25.8     1.16
16070 2024-12-30              25.8     1.74
16071 2024-12-31              26.6     2.77

The figure below illustrates the monthly climatology of precipitation for the selected location, providing an overview of the typical rainfall distribution throughout the year. Accompanying this, the table summarizes the detected onset dates for both the long and short rainy seasons over the period from 1981 to 2024. These onset dates have been identified based on the specific criteria applied in the analysis, enabling a consistent comparison across different years and locations.

Location: Nairobi, Kenya (latitude = -1.2921; longitude = 36.8219)

print(results)
   Year Long_Rains_Onset Short_Rains_Onset
1  1981       1981-03-30        1981-10-14
2  1982       1982-03-30        1982-10-28
3  1983       1983-03-28        1983-10-08
4  1984       1984-04-09              <NA>
5  1985       1985-04-12              <NA>
6  1986       1986-04-25              <NA>
7  1987       1987-03-31        1987-10-24
8  1988       1988-03-01              <NA>
9  1989             <NA>              <NA>
10 1990       1990-03-06              <NA>
11 1991             <NA>        1991-10-13
12 1992       1992-03-28              <NA>
13 1993       1993-03-01              <NA>
14 1994       1994-03-20              <NA>
15 1995       1995-04-01              <NA>
16 1996       1996-04-01              <NA>
17 1997       1997-04-08        1997-10-27
18 1998       1998-03-20              <NA>
19 1999       1999-03-18              <NA>
20 2000             <NA>              <NA>
21 2001       2001-03-17        2001-11-07
22 2002       2002-05-21        2002-11-02
23 2003       2003-04-08              <NA>
24 2004       2004-04-23              <NA>
25 2005       2005-03-20              <NA>
26 2006       2006-03-02        2006-10-27
27 2007       2007-03-16              <NA>
28 2008       2008-03-30              <NA>
29 2009       2009-04-16        2009-11-02
30 2010       2010-03-20        2010-10-14
31 2011       2011-04-17        2011-10-25
32 2012       2012-04-01        2012-11-19
33 2013             <NA>              <NA>
34 2014       2014-03-27              <NA>
35 2015             <NA>        2015-10-14
36 2016       2016-04-05              <NA>
37 2017             <NA>        2017-11-26
38 2018       2018-03-15        2018-10-22
39 2019             <NA>              <NA>
40 2020             <NA>        2020-10-31
41 2021       2021-04-12        2021-11-04
42 2022             <NA>        2022-10-25
43 2023       2023-04-11        2023-11-03
44 2024       2024-03-18        2024-11-14

Location: Dakar, Senegal (latitude = 14.69; longitude = -17.44)

print(results)
   Year Long_Rains_Onset Short_Rains_Onset
1  1981               NA                NA
2  1982               NA                NA
3  1983               NA                NA
4  1984               NA                NA
5  1985               NA              1985-09-01
6  1986               NA                NA
7  1987               NA                NA
8  1988               NA                NA
9  1989               NA                NA
10 1990               NA                NA
11 1991               NA                NA
12 1992               NA                NA
13 1993               NA                NA
14 1994               NA                NA
15 1995               NA                NA
16 1996               NA                NA
17 1997               NA                NA
18 1998               NA                NA
19 1999               NA                NA
20 2000               NA                NA
21 2001               NA                NA
22 2002               NA                NA
23 2003               NA                NA
24 2004               NA                NA
25 2005               NA                NA
26 2006               NA                NA
27 2007               NA                NA
28 2008               NA             2008-09-03
29 2009               NA                NA
30 2010               NA                NA
31 2011               NA             2011-09-08
32 2012               NA             2012-09-12
33 2013               NA                NA
34 2014               NA                NA
35 2015               NA             2015-09-01
36 2016               NA                NA
37 2017               NA                NA
38 2018               NA                NA
39 2019               NA                NA
40 2020               NA                NA
41 2021               NA             2021-09-02
42 2022               NA             2022-09-16
43 2023               NA                NA
44 2024               NA                NA

Location: Kisangani, DRC (latitude = 0.52; longitude = 25.20)

print(results)
   Year Long_Rains_Onset Short_Rains_Onset      
 1  1981 1981-03-01       1981-09-01       
 2  1982 1982-03-04       1982-09-07       
 3  1983 1983-03-08       1983-09-07       
 4  1984 1984-03-04       1984-09-06       
 5  1985 1985-03-01       1985-09-04       
 6  1986 1986-03-01       1986-09-01       
 7  1987 1987-03-01       1987-09-06       
 8  1988 1988-03-16       1988-09-05       
 9  1989 1989-03-01       1989-09-01       
10  1990 1990-03-01       1990-09-01       
11  1991 1991-03-01       1991-09-01       
12  1992 1992-03-08       1992-09-01       
13  1993 1993-03-05       1993-09-02       
14  1994 1994-03-01       1994-09-07       
15  1995 1995-03-13       1995-09-01       
16  1996 1996-03-09       1996-09-01       
17  1997 1997-03-19       1997-09-06       
18  1998 1998-03-01       1998-09-11       
19  1999 1999-03-01       1999-09-04       
20  2000 2000-03-03       2000-09-01       
21  2001 2001-03-02       2001-09-04       
22  2002 2002-03-01       2002-09-09       
23  2003 2003-03-01       2003-09-01       
24  2004 2004-03-01       2004-09-01       
25  2005 2005-03-01       2005-09-07       
26  2006 2006-03-01       2006-09-01       
27  2007 2007-03-01       2007-09-07       
28  2008 2008-03-01       2008-09-01       
29  2009 2009-03-02       2009-09-01       
30  2010 2010-03-01       2010-09-01       
31  2011 2011-03-01       2011-09-05       
32  2012 2012-03-01       2012-09-01       
33  2013 2013-03-09       2013-09-07       
34  2014 2014-03-06       2014-09-03       
35  2015 2015-03-01       2015-09-01       
36  2016 2016-03-02       2016-09-03       
37  2017 2017-03-01       2017-09-01       
38  2018 2018-03-01       2018-09-05       
39  2019 2019-03-01       2019-09-02       
40  2020 2020-03-01       2020-09-01       
41  2021 2021-03-01       2021-09-01       
42  2022 2022-03-01       2022-09-01       
43  2023 2023-03-04       2023-09-01       
44  2024 2024-03-04       2024-09-01

Location: Timbuktu, Mali (latitude = 16.77; longitude = -3.00)

print(results)
   Year Long_Rains_Onset Short_Rains_Onset
1  1981               NA                NA
2  1982               NA                NA
3  1983               NA                NA
4  1984               NA                NA
5  1985               NA                NA
6  1986               NA                NA
7  1987               NA                NA
8  1988               NA                NA
9  1989               NA                NA
10 1990               NA                NA
11 1991               NA                NA
12 1992               NA                NA
13 1993               NA                NA
14 1994               NA                NA
15 1995               NA                NA
16 1996               NA                NA
17 1997               NA                NA
18 1998               NA                NA
19 1999               NA                NA
20 2000               NA                NA
21 2001               NA                NA
22 2002               NA                NA
23 2003               NA                NA
24 2004               NA                NA
25 2005               NA                NA
26 2006               NA                NA
27 2007               NA                NA
28 2008               NA                NA
29 2009               NA                NA
30 2010               NA                NA
31 2011               NA                NA
32 2012               NA                NA
33 2013               NA                NA
34 2014               NA                NA
35 2015               NA                NA
36 2016               NA                NA
37 2017               NA                NA
38 2018               NA                NA
39 2019               NA                NA
40 2020               NA                NA
41 2021               NA                NA
42 2022               NA                NA
43 2023               NA                NA
44 2024               NA                NA           
Region Type Location Latitude Longitude Notes
Bimodal Region Nairobi, Kenya -1.2921 36.8219 Bimodal rainfall with long rains (Mar–May) and short rains (Oct–Dec) driven by ITCZ.
Kisumu, Kenya -0.0900 34.7600 Characteristic bimodal rainfall with long and short rainy seasons.
Dar es Salaam, Tanzania -6.7900 39.2100 Coastal bimodal rainfall pattern.
Unimodal Region Addis Ababa, Ethiopia 9.0300 38.7400 Exhibits a single rainy season, typically June to September.
Dakar, Senegal 14.6900 -17.4400 West African unimodal rainfall pattern.
Very Wet Region Libreville, Gabon 0.3900 9.4500 One of Africa’s wettest capitals, heavy equatorial rainfall.
Kisangani, DRC 0.5200 25.2000 Located in the Congo Basin rainforest, very high rainfall.
Very Dry Region Timbuktu, Mali 16.7700 -3.0000 Located in the Sahara Desert region, extremely low rainfall, ideal for dry climate test.
Aswan, Egypt 24.0900 32.9000 Hot desert climate with minimal precipitation.

Rationale and Context

  • Bimodal Regions: Equatorial East African locations such as Nairobi, Kisumu, and Dar es Salaam have two distinct rainy seasons due to the seasonal migration of the Inter-Tropical Convergence Zone (ITCZ).
  • Unimodal Regions: Areas in the Sahel and parts of East and West Africa experience a single rainy season annually.
  • Very Wet Regions: Equatorial rainforest zones like Gabon and Congo Basin receive heavy rainfall year-round.
  • Very Dry Regions: Sahara Desert and parts of northern Africa (e.g., Timbuktu, Aswan) have negligible rainfall, ideal to test dry onset criteria.
⚠️ **GitHub.com Fallback** ⚠️