Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace conditional statements

Tags:

r

data.table

I have a data table (DT) with year, dates and temperature measurements (TMEAN):

YEAR    DATE    TMEAN
2010    2010-01-01  -5
2010    2010-01-02  -9
2010    2010-01-03  -6
2010    2010-01-04  0.1
2010    2010-01-05  -0.5
2010    2010-01-06  1
2010    2010-01-07  1.6
2010    2010-01-08  8
2010    2010-01-09  6
2010    2010-01-10  3
2010    2010-01-11  5
2010    2010-01-12  3
2011    2011-01-01  -3
2011    2011-01-02  -5.4
2011    2011-01-03  -3.6
2011    2011-01-04  0.06
2011    2011-01-05  -0.3
2011    2011-01-06  0.6
2011    2011-01-07  0.96
2011    2011-01-08  4.8
2011    2011-01-09  3.6
2011    2011-01-10  1.8
2011    2011-01-11  3
2011    2011-01-12  1.8

For each year, I need to get the first day where the measurement is positive for at least 5 consecutive days...

An idea would be:

for (y in min(DT$YEAR):max(DT$YEAR)) {
    for (i in 1:nrow(DT)) {
DT$test <- ifelse(DT[i, TMEAN] > 0 & DT[i+1, TMEAN] > 0 & DT[i+2, TMEAN] > 0 & DT[i+3, TMEAN] > 0 & DT[i+4, TMEAN] > 0, 1, 0)
    }
}

DT2 <- DT[test == 1, ][, list(START = min(DATE)), by = .(YEAR)]

but it is super slow (and not so elegant!).

How could I replace the multiple use of for and ifelse?

like image 356
user2165907 Avatar asked Jul 22 '26 08:07

user2165907


2 Answers

Using the devel version of 'data.table' ie. v1.9.5, we can create an 'ind' column using rleid on the logical condition TMEAN >0 by 'YEAR', subset the dataset for nrows greater than 4 & positive values of TMEAN (.SD[.N >4 & TMEAN >0]) by 'ind' and 'YEAR'. Get the first row by YEAR (.SD[1L]) and assign the 'ind' column to NULL.

 library(data.table)#v1.9.5+
 DT[, ind:= rleid(TMEAN>0) ,YEAR][, .SD[.N>4 & TMEAN>0] ,
             list(ind,YEAR)][,.SD[1L] , YEAR][, ind:=NULL][]
 #   YEAR       DATE TMEAN
 #1: 2010 2010-01-06   1.0
 #2: 2011 2011-01-06   0.6

If 'DATE' is not ordered, we can use which.min(DATE) instead of .SD[1L]

 DT[, ind:= rleid(TMEAN>0) ,YEAR][, .SD[.N>4 & TMEAN>0] ,
    list(ind, YEAR)][, .SD[which.min(DATE)], YEAR][, ind:=NULL][]

NOTE: Instructions to install the devel version is here

We could also use rle from base R

  DT[,  ind:=inverse.rle(within.list(rle(TMEAN >0), 
        values <- seq_along(values))), YEAR][,
       .SD[ .N >4 & TMEAN >0], list(ind, YEAR)][, 
       .SD[which.min(DATE)], YEAR][, ind:=NULL][]

 #  YEAR       DATE TMEAN
 #1: 2010 2010-01-06   1.0
 #2: 2011 2011-01-06   0.6

If it is the 5th day as showed in @VLC's post

 DT[,  ind:=inverse.rle(within.list(rle(TMEAN >0), 
       values <- seq_along(values))), YEAR][,
       .SD[ .N >4 & TMEAN >0], list(ind, YEAR)][
       order(DATE), .SD[5L], YEAR][,ind:=NULL][]
 #   YEAR       DATE TMEAN
 #1: 2010 2010-01-10   3.0
 #2: 2011 2011-01-10   1.8
like image 159
akrun Avatar answered Jul 23 '26 23:07

akrun


First a dataset:

set.seed(1)
dataset <- data.frame(
  date = seq(as.Date("2011/1/1"), as.Date("2014/1/31"), "day"),
  year = format(date, "%Y"),
  tmean = runif(length(date), -10, 35)
)

Then we can define a function that takes two arguments (a vector containing your temperatures and a number that defines the number of consecutive days) and is mainly based on the rle function:

getFirstDay <- function(x,d){
  a1 <- rle(sign(x))
  a2 <- which(a1$lengths >= d & a1$values == 1)
  a3 <- rep(0, length(x))
  if(length(a2) != 0) a3[(d + sum(a1$lengths[seq_len(a2[1] - 1)])] <- 1
  a3      
}

I will use the ddplyfunction from plyr to extract the day from each year, but you can probably use it also with data.table:

library(plyr)
dataset2 <- ddply(dataset, .(year), mutate, theDay = getFirstDay(tmean, 5))
subset(dataset2, dataset2$theDay == 1)

#           date year     tmean theDay
# 17   2011-01-17 2011 22.292833      1
# 372  2012-01-07 2012 15.297955      1
# 761  2013-01-30 2013 24.971524      1
# 1102 2014-01-06 2014  1.419521      1

With your dataset:

dataset2 <- ddply(DT, .(YEAR), mutate, theDay = getFirstDay(TMEAN, 5))
subset(dataset2, dataset2$theDay == 1)
#    YEAR       DATE TMEAN theDay
# 10 2010 2010-01-10   3.0      1
# 22 2011 2011-01-10   1.8      1
like image 20
VLC Avatar answered Jul 23 '26 22:07

VLC



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!