Davis
Davis

Reputation: 508

Store variable with multiple condition in for loop until condition is met in R

(I know for loops aren't the preferred choice in R but this was the best I could come up with)

I'm trying to loop through a vector and return the vector value once a condition is met.

Once the next condition is met I would like to drop the variable.

So far I've gotten to the following:

df = c(1:10)

sig = function (df) {

  pos = integer(10)

  for (i in 1:10) {

    if (df[i] > 3 ) { # Once df[i] is bigger than 3 store the value of df[i]
      pos[i] = df[i]
    } 
    else if(df[i] < 7 ){ # Keep value of df[i] until next condition is met
      pos[i] = pos[i - 1]
    } 
    else{pos[i] = 0} # set the value back to 0
  }

  reclass(pos,df)
}

sig(df)

I'm getting the following error Error in pos[i] <- pos[i - 1] : replacement has length zero

The answer should look like the following:

df  sig
1    0
2    0
3    0
4    4
5    4
6    4
7    0
8    0
9    0
10   0

Any ideas?

Upvotes: 3

Views: 680

Answers (4)

Carles Mitjans
Carles Mitjans

Reputation: 4866

Here is a possible solution without using for loops. Instead, you can use rle:

a <- c(1:10)
r <- rle(a > 3 & a < 7)
r$values <- ifelse(r$values, a[head(cumsum(c(1, r$lengths)), -1)], 0)
inverse.rle(r)
 [1] 0 0 0 4 4 4 0 0 0 0

Notice, though, that this will only work if the vector is ordered.

Another example:

> a <- c(4, 7, 9, 6, 5, 8, 10, 2, 3, 1)
> r <- rle(a %% 2 == 0)
> r$values <- ifelse(r$values, a[head(cumsum(c(1, r$lengths)), -1)], 0)
> inverse.rle(r)
 [1] 4 0 0 6 0 8 8 8 0 0

Upvotes: 1

markus
markus

Reputation: 26343

You could also use ifelse

df <- c(1:10)
ifelse(df > 3 & df < 7, df[which(df > 3)][1], 0)
# [1] 0 0 0 4 4 4 0 0 0 0

Upvotes: 1

Orhan Yazar
Orhan Yazar

Reputation: 909

You can do it with data.table, here is the method

#Create the data.table
dt <- data.table(c(1:10))
#Create a keep column which is set to 1 for those which respect condition and 0 for the others
dt[,keep:=ifelse(V1>3&V1<7,min(V1),0)][]
#Then create sig column which contains only the value you want to keep 
dt[,sig:=ifelse(keep==0,0,V1*keep)][]
#And finally, you want to store only the first value which respect the condition, so if your data frame is order by number, you can take the min value by V1 column.
dt[,sig:=min(sig),by=keep][]

Here is the output

dt[,c(1,3)]
    V1 sig
 1:  1   0
 2:  2   0
 3:  3   0
 4:  4   4
 5:  5   4
 6:  6   4
 7:  7   0
 8:  8   0
 9:  9   0
10: 10   0

Upvotes: 1

Terru_theTerror
Terru_theTerror

Reputation: 5017

Another way to achieve your output:

pos = integer(10)
pos[df>3 & df<7]<-df[which.max(df>3 & df<7)]
cbind(df,pos)
      df pos
 [1,]  1   0
 [2,]  2   0
 [3,]  3   0
 [4,]  4   4
 [5,]  5   4
 [6,]  6   4
 [7,]  7   0
 [8,]  8   0
 [9,]  9   0
[10,] 10   0

About your problem

i start from 1, in the for loop you have pos[i-1], so pos[0] but the list start from 1.

Try this:

sig = function (df) {

  pos = integer(10)

  for (i in 1:10) {

    if (df[i] > 3 ) { # Once df[i] is bigger than 3 store the value of df[i]
      pos[i] = df[i]
    } 
    else if(df[i] < 7 ){ # Keep value of df[i] until next condition is met
      if(i>1) {
        pos[i] = pos[i - 1] 
      } else

      {
        pos[i]=0
      }
    } 
    else{pos[i] = 0} # set the value back to 0
  }

  return(cbind(df,pos))
}

return instruction added

Your output:

sig(df)
      df pos
 [1,]  1   0
 [2,]  2   0
 [3,]  3   0
 [4,]  4   4
 [5,]  5   5
 [6,]  6   6
 [7,]  7   7
 [8,]  8   8
 [9,]  9   9
[10,] 10  10

The output is different from the one aspected, so you have to find other errors in your logic inside the for loop.

Upvotes: 1

Related Questions