Algorithmic approaches

Introduction

Here we show how you can estimate crop calendars with a simple algorithm based on rainfall. This approach, and similar approaches, should work particularly well in regions with a short and well-defined growing season, but it can be used under different conditions as well. Note that we only use rainfall, and ignore temperature. This algorithm was developed for maize in Sub-Saharan Africa, and perhaps seasonal variation in temperature can be ignored there, but at high latitudes it must certainly be included. You can expand the algorithm used here to include temperature; or have a look at the ecolim approach, that uses both rainfal and temperature, and is described in the next chapter.

Chapter requirements

We use R packages agro and geodata. See these installation instructions.

Function

We use the function agro::plant_harvest. See ?agro::plant_harvest. Here we show the source code of the function, including the comments that may help you understand what the function does.

plant_harvest <- function(x, min_prec=30, max_prec=90, max_len=5) {
## check input
# must have 12 values (months)
    stopifnot(length(x) == 12)
# cannot have any NAs
    if (any(is.na(x))) return (matrix(NA, nrow=12, ncol=2))
# max_len is the number of months
    stopifnot(max_len > 0 & max_len <= 12)
# min precipitation threshold below max threshold
    stopifnot(min_prec < max_prec)
## compute threshold
# median of the rainfall
    med <- stats::median(x)
# median clamped between min_prec and max_prec
    prec_threshold <- min(max_prec, max(min_prec, med))
# make a time series of 24 months, from July to July, to make it easier
# to do 'circular' computation across the year boundary. That is, we
# cannot ignore that (January comes after December)
    x24 <- c(x[7:12], x, x[1:6])
# which months are above the threshold?
    above_th <- x24 >= prec_threshold
# cumulate successive months above the threshold
    y <- cumsum(above_th)
# remove months below the threshold and reset
# the beginning of a sequence to 1
# (perhaps the most obscure step)
    wet <- (y - cummax(y * !above_th))
# go back to a single 12 months (Jan to Dec) year
    wet <- wet[7:18]
# set up output
    planting <- harvest <- rep(0, 12)
# find the length of the growing season
    m <- min(max_len, max(wet))
# growing season must be at least 3 months
    if (m > 2) {
    # harvest months
        harvest[wet >= m] <- 1
    # planting months
        p <- which(wet >= m) - m + 1
    # 1 month before January -> December
        p[p < 1] <- 12 + p[p < 1]
        planting[p] <- 1
    }
    return( cbind(planting=planting, harvest=harvest) )
}

First try it

It is often useful to try an algorithm on simple contrived data, before using it on real-world data. Here we look at four imaginary locations. First a location with a single rainy season.

Single season

rain <- c(0,0,10,40,50,60,60,50,40,10,0,0)
agro::plant_harvest(rain)
##       planting harvest
##  [1,]        0       0
##  [2,]        0       0
##  [3,]        0       0
##  [4,]        1       0
##  [5,]        1       0
##  [6,]        0       0
##  [7,]        0       0
##  [8,]        0       1
##  [9,]        0       1
## [10,]        0       0
## [11,]        0       0
## [12,]        0       0

Here are two helper functions, both to display the results in a nicer way.

The below function makes printed output more readable.

printSeason <- function(rain) {
    x <- agro::plant_harvest(rain)
    rownames(x) <- month.abb
    t(x)
}

Let’s try it:

printSeason(rain)
##          Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
## planting   0   0   0   1   1   0   0   0   0   0   0   0
## harvest    0   0   0   0   0   0   0   1   1   0   0   0

We can also make a barplot

plotSeason <- function(rain) {
    x <- agro::plant_harvest(rain)
    barplot(rain, names.arg=month.abb, las=2)
    barplot(x[,1]*rain, add=T, col="red", axes=FALSE, density=15)
    barplot(x[,2]*rain, add=T, col="blue", axes=FALSE, density=15, angle=315)
    legend("topright", c("rain", "plant", "harvest"), fill=c("gray", "red", "blue"), density=c(-1,25,25))
}

Use the function with the rain defined above.

plotSeason(rain)

image0

Bi-modal season

Now a location with a bi-modal rainy season

rain <- c(0,rep(50,4), 0, 0, rep(50,4), 0)
rain
##  [1]  0 50 50 50 50  0  0 50 50 50 50  0
printSeason(rain)
##          Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
## planting   0   1   0   0   0   0   0   1   0   0   0   0
## harvest    0   0   0   0   1   0   0   0   0   0   1   0
plotSeason(rain)

image1

Year-round rain

In a place where rains year-round you may planting and harvesting every month of the year.

rain <- c(4,4,5,5,6,6,6,5,5,4,4,4) * 10
rain
##  [1] 40 40 50 50 60 60 60 50 50 40 40 40
printSeason(rain)
##          Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
## planting   0   0   1   1   1   0   0   0   0   0   0   0
## harvest    0   0   0   0   0   0   1   1   1   0   0   0
plotSeason(rain)

image2

Arid conditions

When it is too dry to grow much, you can never plant or harvest.

rain <- c(1:6, 6:1)
printSeason(rain)
##          Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
## planting   0   0   0   0   0   0   0   0   0   0   0   0
## harvest    0   0   0   0   0   0   0   0   0   0   0   0
plotSeason(rain)

image3

Apply it

Now, let’s apply this to Senegal. First we get WorldClim data.

library(geodata)
rain <- geodata::worldclim_country("Senegal", var="prec", path=".")
adm <- geodata::gadm("Senegal", level=1, path=".")
rain <- mask(rain, adm)
names(rain) <- month.abb

Annual rainfall

plot(sum(rain))
lines(adm)

image4

Monthly rainfall

plot(rain)

image5

Aggregate the data a bit to speed up computations

arain <- aggregate(rain, 10, mean, na.rm=TRUE)

Now “apply” the function “plant_harvest” to SpatRaster “rain”

x <- app(arain, agro::plant_harvest)
x
## class       : SpatRaster
## dimensions  : 60, 84, 24  (nrow, ncol, nlyr)
## resolution  : 0.08333333, 0.08333333  (x, y)
## extent      : -18, -11, 12, 17  (xmin, xmax, ymin, ymax)
## coord. ref. : lon/lat WGS 84 (EPSG:4326)
## source(s)   : memory
## names       : lyr.1, lyr.2, lyr.3, lyr.4, lyr.5, lyr.6, ...
## min values  :     0,     0,     0,     0,     0,     0, ...
## max values  :     0,     0,     0,     0,     1,     1, ...

Output x has 24 layers, matching the output of plant_harvest (12 for planting, and 12 for harvest). Let’s separate them.

plant <- x[[1:12]]
names(plant) <- month.abb
harv <- x[[13:24]]
names(harv) <- month.abb

And have a look

plot(plant)

image6

First month of planting

plantna <- classify(plant, cbind(0, NA))
p <- which.max(plantna)
plot(p)

image7

First month of harvest (note that which works here because the harvest is in the same calendar-year as planting.

harvna <- classify(harv, cbind(0, NA))
h <- which.max(harvna)
plot(h)

image8