Search code examples
rfor-loopdplyrdata.tablerecurrence

Simple water demand supply model in R


I am trying to work out a simple soil water balance in R. Here's the step I need to do:

For a given year, starting from doy 200,I need to determine the Soil Water (SWi) which is calculated by following formula

              `SW(i) = SW(i-1)  + Rain(i) - ETo(i)` 

where SW(i-1) is the water content in previous day, Rain(i) is the rainfall and ETo(i) is the evapotranspiration on day i

The conditions are that SW(i) cannot be negative or be more than SW(max) which is 20.

Here's a sample data:

        library(tidyverse)
        set.seed(123)

        dat <- tibble(
              year = rep(1980:2015, each = 100),
              day = rep(200:299, times = 36),
              rain = sample(0:17, size = 100*36,replace = T),
              eto = sample(2:9, size = 100*36,replace = T))

        SW.initial <- data.frame(year= 1980:2015, SW.199 = sample(1:10, 36, replace = T))

SW.initial is the water content for doy 199 for for each year

        SW.max <- 20 
        dat$SW.fin <- NA                    

Taking the example of year 1980

        dat.1980 <- dat[dat$year == 1980,]
        SW.initial.1980 <- SW.initial[SW.initial$year== 1980,"SW.199"]

        for(doy in dat.1980$day){

            SW <- SW.initial.1980 
            SW <- SW + dat.1980[dat.1980$day == doy, "rain"] - dat.1980[dat.1980$day == doy, "eto"]
            SW <- ifelse(SW < 0, 0, ifelse(SW >= SW.max, SW.max, SW))
            dat[dat$year == years & dat$day == doy,"SW.fin"] <- SW
            SW.initial.1980 <- SW
          }

This loop will give me the SW of each day starting doy 200 till 299 using:

      `SW(i) = SW(i-1) + Rain[i] + ETo[i]`` 

where for doy 200, SW(i-1) was given from the SW.initial data frame

I can loop through all years:

        for(years in unique(dat$year)){
                test <- dat[dat$year == years,]
                SW.in <- SW.initial[SW.initial$year == years,"SW.199"]
              for(doy in test$day){
                    SW <- SW.in 
                    SW <- SW + test[test$day == doy, "rain"] - test[test$day == doy, "eto"]
                    SW <- ifelse(SW < 0, 0, ifelse(SW >= SW.max, SW.max, SW))
                    dat[dat$year == years & dat$day == doy,"SW.fin"] <- SW
                    SW.in <- SW
          }}

I really want to avoid this long loop and was thinking if there is much clever (and faster way to do this).


Solution

  • Does this give what you want ?

    edit -> added grouped by year

    dat %>% group_by(year) %>% mutate(sw_oneless = c(NA, day[1:length(day)-1]),
                   sw_oneless + rain - eto)
    
        # A tibble: 3,600 x 6
    # Groups: year [36]
        year   day  rain   eto sw_oneless `sw_oneless + rain - eto`
       <int> <int> <int> <int>      <int>                     <int>
     1  1980   200     5     2         NA                        NA
     2  1980   201    14     6        200                       208
     3  1980   202     7     4        201                       204
     4  1980   203    15     5        202                       212
     5  1980   204    16     5        203                       214
     6  1980   205     0     8        204                       196
     7  1980   206     9     9        205                       205
     8  1980   207    16     6        206                       216
     9  1980   208     9     9        207                       207
    10  1980   209     8     4        208                       212
    # ... with 3,590 more rows
    

    To solve the "problem" with day 200, why don't you just filter out day 199-300 from your original data? You can then run my code and na.omit() or filter again and the rows with day 199 are gone. Or, if you can't do that, merge your SW.initial with your dat data frame