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?
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With