Фильтрация строк в группе на основе свойств группы - PullRequest
3 голосов
/ 03 июля 2019

Предположим, у меня есть отметка с переменной группировки и логическая переменная, которая указывает, является ли строка основным ответом для этой группы.

Я хочу сделать следующее:

  1. Если какая-либо строка в group помечена как is_primary, оставьте эту строку, но ни одна из остальных в группе
  2. Если ни одна строка в group не помечена is_primary, сохраните их все
  3. Фильтрация строк на основе вышеупомянутого

Вот некоторые примеры данных:

library(tidyverse)
data <- tibble(group=c("A","A","A","B","B","C","C","C","C"),
               is_primary=c(FALSE, FALSE, FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE),
               value=c(1,2,3,4,5,6,7,8,9))

В приведенном выше примере я хотел бы сохранить все строки A, потому что нет строки с is_primary==TRUE, оставить только вторую строку B и оставить последние две строки C.

Я думал, что очевидное решение будет примерно таким:

data %>%
  group_by(group) %>%
  mutate(keep_row=ifelse(any(is_primary),is_primary,TRUE))

Но это приводит к следующему, что не соответствует вышеуказанным критериям.

# A tibble: 9 x 4
# Groups:   group [3]
  group is_primary value keep_row
  <chr> <lgl>      <dbl> <lgl>   
1 A     FALSE          1 TRUE    
2 A     FALSE          2 TRUE    
3 A     FALSE          3 TRUE    
4 B     FALSE          4 FALSE   
5 B     TRUE           5 FALSE   
6 C     FALSE          6 FALSE   
7 C     FALSE          7 FALSE   
8 C     TRUE           8 FALSE   
9 C     TRUE           9 FALSE 

Однако, если я сделаю промежуточную переменную, которая указывает, есть ли у группы первичный ключ, она работает.

data %>%
  group_by(group) %>%
  mutate(has_primary=ifelse(any(is_primary),TRUE,FALSE)) %>%
  mutate(keep_row=ifelse(has_primary,is_primary,TRUE))

В результате keep_row будет правильным:

# A tibble: 9 x 5
# Groups:   group [3]
  group is_primary value has_primary keep_row
  <chr> <lgl>      <dbl> <lgl>       <lgl>   
1 A     FALSE          1 FALSE       TRUE    
2 A     FALSE          2 FALSE       TRUE    
3 A     FALSE          3 FALSE       TRUE    
4 B     FALSE          4 TRUE        FALSE   
5 B     TRUE           5 TRUE        TRUE    
6 C     FALSE          6 TRUE        FALSE   
7 C     FALSE          7 TRUE        FALSE   
8 C     TRUE           8 TRUE        TRUE    
9 C     TRUE           9 TRUE        TRUE

Что происходит в ifelse, когда первое решение не работает?

Ответы [ 2 ]

3 голосов
/ 03 июля 2019

Ваша проблема в том, что ifelse() возвращает вектор, равный длине ввода.Когда вы передаете ifelse(any(),...), any() вернет только один вектор, который повторяется для группы.Вы можете видеть это с помощью

x <- c(F,T,F,T, F)
ifelse(any(x), x, TRUE)
# [1] FALSE

Обратите внимание, как возвращается только одно значение.ifelse() - это не просто ярлык для правильного оператора if \ else.Это векторизованная функция, поэтому будьте осторожны, чтобы не использовать ее, когда вы пытаетесь условно выполнить код не векторизованным способом.

Другим способом выражения вашего фильтра будет

data %>% 
  group_by(group) %>% 
  filter(any(is_primary) & is_primary | !any(is_primary))
3 голосов
/ 03 июля 2019

Мы можем использовать условие if/else, чтобы возвращать строки, когда в «is_primary» нет элемента TRUE, или else возвращать только те строки, где «is_primary» равно TRUE

library(dplyr)
data %>%
    group_by(group) %>%
    filter(if(!any(is_primary)) TRUE else is_primary)
# A tibble: 6 x 3
# Groups:   group [3]
#  group is_primary value
#  <chr> <lgl>      <dbl>
#1 A     FALSE          1
#2 A     FALSE          2
#3 A     FALSE          3
#4 B     TRUE           5
#5 C     TRUE           8
#6 C     TRUE           9

Это также можно сделать с условием |

data %>%
   group_by(group) %>%
   filter(!any(is_primary) | is_primary)
# A tibble: 6 x 3
# Groups:   group [3]
#  group is_primary value
#  <chr> <lgl>      <dbl>
#1 A     FALSE          1
#2 A     FALSE          2
#3 A     FALSE          3
#4 B     TRUE           5
#5 C     TRUE           8
#6 C     TRUE           9

Или другой вариант

data %>%
  group_by(group) %>%
  filter(sum(is_primary) == 0 | is_primary)
# A tibble: 6 x 3
# Groups:   group [3]
#  group is_primary value
#  <chr> <lgl>      <dbl>
#1 A     FALSE          1
#2 A     FALSE          2
#3 A     FALSE          3
#4 B     TRUE           5
#5 C     TRUE           8
#6 C     TRUE           9

Или используя slice

data %>% 
  group_by(group) %>% 
  slice(if(!any(is_primary)) row_number() else which(is_primary))

A data.table вариант выше будет

library(data.table)
setDT(data)[data[, .I[!any(is_primary)|is_primary], by = group]$V1]

или используя base R

data[with(data, !ave(is_primary, group, FUN = any) | is_primary),]

Проблема с ifelse заключается в том, что согласно ?ifelse

ifelse (тест, да, нет)

Если да или нет слишком короткие, их элементы перерабатываются. yes будет оцениваться, если и только если какой-либо элемент теста верен, и аналогично для no.

В коде ОП

 ifelse(any(is_primary),TRUE,FALSE)

any возвращает логический вектор length 1. Согласно ?any

Значение является логическим вектором длины один.

На основании приведенной выше документации ifelse эти значения перерабатываются

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...