Next value in a column, less than or equal to difference from current value

Viewed 206

I want to retrieve the value from one column, based on the values in another column. I have included an example which should help explain.

For this example, if we say that we want to return the value in index where the next value in number is less than or equal to the current number-10, we would get the column ind_return:

   index  number  ind_return
1      A     100           C
2      B     105           C
3      C      84          NA
4      D      90           G
5      E     120           F
6      F     110           G
7      G      75          NA



df <- data.frame(index = c("A", "B", "C", "D", "E", "F", "G"),
             number = c(100, 105, 84,  90,  120, 110,  75))

The question is, how can we get ind_return?

4 Answers

You can use outer to get the differences. Exclude from that the upper.tri and the diag and then get the row using which in apply.

i <- outer(df$number, df$number, "-")
i[upper.tri(i)] <- NA
diag(i) <- NA
df$index[apply(i < -9, 2, function(x) which(x)[1])]
#[1] "C" "C" NA  "G" "F" "G" NA 

Or making the comparison in a sapply loop

sapply(1:nrow(df), function(i) {
  df$index[i+which(df$number[i+seq_len(nrow(df)-i)] - df$number[i] < -9)[1]]
})
#[1] "C" "C" NA  "G" "F" "G" NA 

You can use the following solution in tidyverse . As a description of how my solution works:

  • It's basically a row-wise operation and in every iteration we remove the previous values of both index and number variables through -c(seq(.y - 1))
  • Since we iterate over 2 variables here I chose map2 function from package purrr. Here .x represents the corresponding value of our first variable index in every row and .y equals to the corresponding value of the second variable number.
library(dplyr)
library(purrr)

df %>%
  mutate(id = row_number()) %>%
  rowwise() %>%
  mutate(ind_return = 
           map2_chr(number, id, ~ df$index[-c(seq(.y - 1))][which(df$number[-c(seq(.y - 1))] <= .x - 10)][1]))

# A tibble: 7 x 4
# Rowwise: 
  index number    id ind_return
  <chr>  <dbl> <int> <chr>     
1 A        100     1 C         
2 B        105     2 C         
3 C         84     3 NA        
4 D         90     4 G         
5 E        120     5 F         
6 F        110     6 G         
7 G         75     7 NA   

Here is another version using dplyr and purrr, which I think is slightly more legible than Anoushiravan R's:

library(dplyr)
library(purrr)

filter_it <- function(the_letter){
  df %>% 
    filter(index >= the_letter) %>% 
    filter(number <= .$number[1] - 10) -> filtered
  
  ifelse(nrow(filtered) == 0, NA, filtered$index[1])
}

df %>% 
  mutate(ind_return = map_chr(index, filter_it))


  index number ind_return
1     A    100          C
2     B    105          C
3     C     84       <NA>
4     D     90          G
5     E    120          F
6     F    110          G
7     G     75       <NA>

All the answers proposed are fantastic. However, I would have done it like this-

  • use map2 with number as first arg and row_number() as second
  • store intermediate result of difference of current row's number with whole number vector in x (say)
  • check which of x are >= 10 and retrieve all corresponding index
  • extract its tail with n() - .y elements
  • take out its first element and store in another intermediate z (say)
  • finally check if z is empty to replace it with NA
df <- data.frame(index = c("A", "B", "C", "D", "E", "F", "G"),
                 number = c(100, 105, 84,  90,  120, 110,  75))
library(tidyverse)
df %>% mutate(ind_return = map2(number, row_number(), ~ {y <- .x - number; 
                        z <- head(tail(index[y >= 10], n() - .y), 1); 
                        if(is_empty(z)) NA else z}))

#>   index number ind_return
#> 1     A    100          C
#> 2     B    105          C
#> 3     C     84         NA
#> 4     D     90          G
#> 5     E    120          F
#> 6     F    110          G
#> 7     G     75         NA

Created on 2021-06-15 by the reprex package (v2.0.0)

Related