4

I have the following dataframe called df (dput below):

  group indicator value
1     A     FALSE     2
2     A     FALSE     1
3     A     FALSE     2
4     A      TRUE     4
5     B     FALSE     5
6     B     FALSE     1
7     B      TRUE     3

I would like to remove the non-last rows with indicator == FALSE per group. This means that in df the rows: 1,2 and 5 should be removed because they are not the last rows with FALSE per group. Here is the desired output:

  group indicator value
1     A     FALSE     2
2     A      TRUE     4
3     B     FALSE     1
4     B      TRUE     3

So I was wondering if anyone knows how to remove non-last rows with certain condition per group in R?


dput of df:

df <- structure(list(group = c("A", "A", "A", "A", "B", "B", "B"), 
    indicator = c(FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE
    ), value = c(2, 1, 2, 4, 5, 1, 3)), class = "data.frame", row.names = c(NA, 
-7L))
Quinten
  • 35,235
  • 5
  • 20
  • 53

4 Answers4

4

Filter using last(which()) to find the row number of the last FALSE row per group:

library(dplyr)

df %>%
  group_by(group) %>%
  filter(indicator | row_number() == last(which(!indicator))) %>%
  ungroup()
# A tibble: 4 × 3
  group indicator value
  <chr> <lgl>     <dbl>
1 A     FALSE         2
2 A     TRUE          4
3 B     FALSE         1
4 B     TRUE          3
zephryl
  • 14,633
  • 3
  • 11
  • 30
1

You can do this with lead and check if the following indicator is TRUE.

library(tidyverse)
df <- structure(list(group = c("A", "A", "A", "A", "B", "B", "B"), 
                     indicator = c(FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE
                     ), value = c(2, 1, 2, 4, 5, 1, 3)), class = "data.frame", row.names = c(NA, 
                                                                                             -7L))
df |> 
  group_by(group) |> 
  mutate(slicer = if_else(lead(indicator) ==F, 1, 0)) |> 
  mutate(slicer = if_else(is.na(slicer), 0 , slicer)) |> 
  filter(slicer == 0) |> 
  select(-slicer)
#> # A tibble: 4 × 3
#> # Groups:   group [2]
#>   group indicator value
#>   <chr> <lgl>     <dbl>
#> 1 A     FALSE         2
#> 2 A     TRUE          4
#> 3 B     FALSE         1
#> 4 B     TRUE          3
MarBlo
  • 4,195
  • 1
  • 13
  • 27
1

Another approach:

library(dplyr)

df %>%
  group_by(group) %>%
  slice_max(cumsum(!indicator))

Note: While this approach covers the example shown and OP's clarification that T always comes last, it will not work in sequences such as T, F, F, T in which you'd like to keep both Ts and not just the one following F.

Output:

# A tibble: 4 x 3
# Groups:   group [2]
  group indicator value
  <chr> <lgl>     <dbl>
1 A     FALSE         2
2 A     TRUE          4
3 B     FALSE         1
4 B     TRUE          3
arg0naut91
  • 14,574
  • 2
  • 17
  • 38
1

Some alternatives one could come up with:

"Dumb" solution

should_be_kept <- logical(nrow(df))
for(row in 1:nrow(df)) {
  if(df[row,"Indicator"]) {
    should_be_kept[row] <- TRUE
  } else if(row == max(which(!df[, "Indicator"] & df$Group == df[row, "Group"]))) {
    should_be_kept[row] <- TRUE
  } else {
    should_be_kept[row] = FALSE
  }
}
df[should_be_kept, ]

Solution using a custom function to find the last FALSE indicators from each group

rows_to_keep <- logical(nrow(df)) #We create a TRUE/FALSE vector with one entry for each row of df
rows_to_keep[df$Indicator] <- TRUE #If Indicator is TRUE, we mark that row as "selectable"

get_last_false_in_group <- function(df, group) {
  return(max(which(df$Group == group & !df$Indicator))) #Gets the last time the condition inside of which() is met
}

#The following chunk does a group-by-group search of the last false indicator. There's probably some apply magic that simplifies this but I'm too dumb to come up with it.
groups <- levels(factor(df$Group))
for(current_group in groups) {
  rows_to_keep[get_last_false_in_group(df, current_group)] <- TRUE
}

#Now that our rows_to_keep vector is ready, we can filter the corresponding rows and get the intended result:
df[rows_to_keep,]

With the data.table package, it's possible to replace the calls to max(which(...)) with calls to just the last function

David
  • 371
  • 2
  • 13