Search code examples
rtidyverse

Iterate between different functions depending on the input value


I have data on the outside temperature

df1 <- read.table(text = "DT   temp.out
'2023-01-18 00:00:00'   6.8
'2023-02-18 23:00:00'   1.5
'2023-03-04 00:00:00'   2.6
'2023-04-20 03:00:00'   -5.0
'2023-06-21 05:00:00'   11.9
'2023-08-10 19:00:00'   6.2
'2023-08-21 23:00:00'   -2.8
'2023-09-19 01:00:00'   5.7
'2023-11-3 07:00:00'    9.1
'2023-12-21 13:00:00'   -19.8", header = TRUE) %>% 
  mutate (DT = as.POSIXct(DT))

For each observation I determine the value of the COP indicator. It depends on the value of tzas. Since (currently) I have two functions describing the COP value depending on tzas (60 and 30), I calculate the value for intermediate values using a proportion (linear relationship).

COP.licz <- function(tout) {
  tzas = -0.78 * tout + 35.789
  COP.60 = 0.026 * tout + 2.937
  COP.30 = 0.0368 * tout + 3.46
  (COP.30 - COP.60) / (60 - 30) * (tzas - 30) + COP.30
}


df1 <- df1 %>% 
  mutate (
    tzas = -0.78 * temp.out + 35.789,
    COP.chw = COP.licz (temp.out)
  )

I get results left

> df1
                    DT temp.out  COP.chw
1  2023-01-18 00:00:00      6.8 3.719882
2  2023-02-18 23:00:00      1.5 3.598219
3  2023-03-04 00:00:00      2.6 3.624767
4  2023-04-20 03:00:00     -5.0 3.427471
5  2023-06-21 05:00:00     11.9 3.822061
6  2023-08-10 19:00:00      6.2 3.706901
7  2023-08-21 23:00:00     -2.8 3.487919
8  2023-09-19 01:00:00      5.7 3.695929
9  2023-11-03 07:00:00      9.1 3.767771
10 2023-12-21 13:00:00    -19.8 2.950173
> 

In the next version I have several (maybe more) COP functions (tzas): COP.60, COP.55, COP.50, COP.30. I want to use these additional functions in COP.chw calculations For example: temp.out is -19.8, therefore tzas is 51.233 COP.chw will be calculated from the proportion between the values of COP.50 and COP.60

Using if or case_when seems to be a very forceful solution.

Example dependencies/functions

COP.60 = 0.026 * tout + 2.937
COP.55 = 0.029 * tout + 2.907
COP.50 = 0.0315 * tout + 3.001
COP.30 = 0.0368 * tout + 3.46

Solution

  • You could use the following structure; Note that for tzas values less than 30 you would want to define another segment, as it currently returns null, as there is no matching blend.

    df1 <- read.table(text = "DT   temp.out
    '2023-01-18 00:00:00'   6.8
    '2023-02-18 23:00:00'   1.5
    '2023-03-04 00:00:00'   2.6
    '2023-04-20 03:00:00'   -5.0
    '2023-06-21 05:00:00'   11.9
    '2023-08-10 19:00:00'   6.2
    '2023-08-21 23:00:00'   -2.8
    '2023-09-19 01:00:00'   5.7
    '2023-11-3 07:00:00'    9.1
    '2023-12-21 13:00:00'   -19.8", header = TRUE) %>% 
      mutate (DT = as.POSIXct(DT))
    
    blend <- function(tzas,low_cut,low_val,high_cut,high_val){
      (low_val - high_val) / (high_cut-low_cut) * (tzas - low_cut) + low_val
    }
    
    COP.licz_0 <- function(tout) {
      tzas = -0.78 * tout + 35.789
      
      COP.30 = 0.0368 * tout + 3.46
      COP.50 = 0.0315 * tout + 3.001
      COP.55 = 0.029 * tout + 2.907
      COP.60 = 0.026 * tout + 2.937
    
      which_cut <- as.integer(cut(tzas,breaks = c(30,50,55,60)))
      r <- switch(which_cut,
             blend(tzas,30,COP.30,50,COP.50),
             blend(tzas,50,COP.50,55,COP.55),
             blend(tzas,55,COP.55,60,COP.60)
              )
    r
    }
    COP.licz <- Vectorize(COP.licz_0,vectorize.args = "tout")
    
    
    df1 <- df1 %>% 
      mutate (
        tzas = -0.78 * temp.out + 35.789,
        COP.chw1 = COP.licz (temp.out)
      )
    
    

    further comments : the issue of a list result, is by tzas falling outside of defined breaks and returning NULL, so it has the solution I mentioned in my post, define proper bounds with proper behaviour.. and then it wont go back to you as a list . yet, if you dont want to and want to have NA values in those spaces though you can add .

    if(is.null(r)){
        r <- NA_real_
      }
      r
    

    to the end of COP.licz_0 function to turn nulls to numeric NA values