R: создать столбцы индикатора из списка условий

У меня есть датафрейм и ряд условий. Каждое условие должно проверять, находится ли значение в определенном столбце фрейма данных в наборе допустимых значений.

Вот что я пробовал:

# create the sample dataframe
age <- c(120, 45)
sex <- c("x", "f")

df <-data.frame(age, sex)

# create the sample conditions
conditions <- list(
  list("age", c(18:100)),
  list("sex", c("f", "m"))
)

addIndicator <- function (df, columnName, validValues) {
  indicator <- vector()

  for (row in df[, toString(columnName)]) {
    # for some strange reason, %in% doesn't work correctly here, but always returns FALSe
    indicator <- append(indicator, row %in% validValues)
  }
  df <- cbind(df, indicator)

  # rename the column
  names(df)[length(names(df))] <- paste0("I_", columnName)

  return(df)
}

for (condition in conditions){
  columnName <- condition[1]
  validValues <- condition[2]
  df <- addIndicator(df, columnName, validValues)
}

print(df)

Однако это приводит к тому, что все условия считаются невыполненными - чего я не ожидал:

  age sex I_age I_sex
1 120   x FALSE FALSE
2  45   f FALSE FALSE

Я полагал, что %in% не возвращает ожидаемого результата. Я проверил typeof(row) и попытался свести это к минимуму. В простом ME с тем же типом и значениями переменных %in% работает правильно. Итак, что-то должно быть не так в контексте, который я пытаюсь применить. Поскольку это моя первая попытка написать что-либо на R, я застрял здесь.

Что я делаю не так и как добиться желаемого?


person jonathan.scholbach    schedule 02.06.2020    source источник
comment
Когда вы устанавливаете validValues в condition[2], ваш результат будет списком, а не вектором; но вы, вероятно, намеревались снабдить свою функцию вектором. Чтобы извлечь необходимые значения столбцов, попробуйте вместо этого validValues <- condition[[2]] в вашем for цикле. Кроме того, вероятно, существует более простой или оптимизированный подход к установлению этих показателей, если это интересно ...   -  person Ben    schedule 02.06.2020


Ответы (3)


Если вы предпочитаете подход, в котором используется семейство пакетов tidyverse:

library(tidyverse)

allowed_values <- list(age = 18:100, sex = c("f", "m"))

df %>%
  imap_dfr(~ .x %in% allowed_values[[.y]]) %>%
  rename_with(~ paste0('I_', .x)) %>%
  bind_cols(df)

imap_dfr позволяет вам управлять каждым столбцом в df с помощью лямбда-функции. .x ссылается на содержимое столбца, а .y ссылается на имя.

rename_with переименовывает столбцы с помощью другой лямбда-функции, а bind_cols объединяет результаты с исходным фреймом данных.

Я позаимствовал упрощенный список условий из ответа Бена. Я считаю, что мой подход немного более читабелен, но это вопрос вкуса и того, используете ли вы уже тидиверс в другом месте.

person severin    schedule 02.06.2020

conditions выглядит вложенным списком. Когда вы используете:

validValues <- condition[2]

в вашем цикле for ваш результат также является списком.

Чтобы получить вектор значений для использования с %in%, вы можете извлечь [[ следующим образом:

validValues <- condition[[2]]

Упрощенный подход к получению индикаторов может заключаться в простом списке:

conditions_lst <- list(age = 18:100, sex = c("f", "m"))

И используя sapply вместо цикла for:

cbind(df, sapply(setNames(names(df), paste("I", names(df), sep = "_")), function(x) {
  df[[x]] %in% conditions_lst[[x]]
}))

Вывод

  age sex I_age I_sex
1 120   x FALSE FALSE
2  45   f  TRUE  TRUE
person Ben    schedule 02.06.2020

Альтернативный подход с использованием across и cur_column() (и сильно опираясь на решение Северина):

library(tidyverse)

df <- tibble(age = c(12, 45), sex = c('f', 'f'))
allowed_values <- list(age = 18:100, sex = c("f", "m"))

df %>%
  mutate(across(c(age, sex),
                c(valid = ~ .x %in% allowed_values[[cur_column()]])
                )
         )

Ссылка: https://dplyr.tidyverse.org/articles/colwise.html#current-column

Связанный вопрос: Обращение к именам столбцов внутри dplyr через ()

person s_pike    schedule 10.12.2020