Использование Mutate и Case_When только для заполнения строки NA

Я часами гуглил, и я не уверен, где найти ответ на что-то простое, подобное этому, поэтому я надеюсь, что это не дублирующий вопрос.

У меня есть большой фрейм данных (936848 x 12) с одним столбцом — это закодированное имя, из которого я могу получить значение другого столбца, в данном случае год изготовления на основе первого символа столбца Код.

Небольшой образец фрейма данных:

df <- data.frame(Code = c("AX123", "CL199", "GH679"), 
                 Year = c(NA, "2014", "2018")) 

Я просто хочу изменить столбец Year на основе кода столбца, только если значение отсутствует. Я не хотел перезаписывать существующее значение в столбце «Год».

Поскольку это также включает определение первого алфавита в строке в Code, я использую case_when и startsWith:

df <- df %>%
  filter(is.na(Year)) %>%
  mutate(Year = case_when(startsWith(Code, "A") ~ 2013,
                          startsWith(Code, "C") ~ 2014,
                          startsWith(Code, "D") ~ 2015,
                          startsWith(Code, "E") ~ 2016,
                          startsWith(Code, "F") ~ 2017,
                          startsWith(Code, "G") ~ 2018,
                          startsWith(Code, "H") ~ 2019,
                          startsWith(Code, "J") ~ 2020,
                          TRUE ~ NA_real_
                          ))

Это даст такой результат:

   Code Year
1 AX123 2013

Моя проблема в том, как я пишу этот фильтр для всех строк, отличных от NA, в фрейме данных. Я хочу сохранить фрейм данных как есть, только чтобы заполнить строку NA.

Я думаю вложить это в функцию ifelse, чтобы мутировать, только если столбец NA, но я не понимаю, как это написать.

df <- df %>%
  mutate(ifelse(is.na(Year),
                case_when(startsWith(Code, "A") ~ 2013,
                          startsWith(Code, "C") ~ 2014,
                          startsWith(Code, "D") ~ 2015,
                          startsWith(Code, "E") ~ 2016,
                          startsWith(Code, "F") ~ 2017,
                          startsWith(Code, "G") ~ 2018,
                          startsWith(Code, "H") ~ 2019,
                          startsWith(Code, "J") ~ 2020,
                          TRUE ~ NA_real_
  )), "")

что, очевидно, даст эту ошибку

Error: Problem with `mutate()` input `..1`.
i `..1 = ifelse(...)`.
x argument "no" is missing, with no default

У меня есть много подобных задач, где мне нужно использовать ifelse, grepl, substring и т. д., чтобы обнаружить символ в столбце кода и заполнить отсутствующий NA в другом столбце. Но поскольку многие строки уже заполнены значениями из-за исключений из правил, которые не следуют соглашению о кодированных именах, я не хотел перезаписывать их.

ваш подход ifelse правильный, есть только несоответствие скобок. Последняя строка должна быть ), ""))

AdroMine 21.03.2022 09:30

Это все еще дает Error: unexpected ')' in: " TRUE ~ NA_real_ )), ""))"

Helmasan 21.03.2022 11:41
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
2
103
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

Ты почти понял. ifelse требует 3 аргумента:

  • тест (в вашем случае: is.na())
  • да (в вашем случае: замените на год в соответствии с начальным символом)
  • нет (в вашем случае: скопировать Year)

df %>%
  mutate(Year1 = ifelse(is.na(Year),
                case_when(startsWith(Code, "A") ~ 2013,
                          startsWith(Code, "C") ~ 2014,
                          startsWith(Code, "D") ~ 2015,
                          startsWith(Code, "E") ~ 2016,
                          startsWith(Code, "F") ~ 2017,
                          startsWith(Code, "G") ~ 2018,
                          startsWith(Code, "H") ~ 2019,
                          startsWith(Code, "J") ~ 2020,
                ), Year))

Выход:

   Code Year Year1
1 AX123 <NA>  2013
2 CL199 2014  2014
3 GH679 2018  2018

Пример несоответствующей буквы, как указано в комментариях:

df <- data.frame(Code = c("AX123", "CL199", "GH679", "XX485"), 
                 Year = c(NA, "2014", "2018", NA))

df %>%
  mutate(Year1 = ifelse(is.na(Year),
                case_when(startsWith(Code, "A") ~ 2013,
                          startsWith(Code, "C") ~ 2014,
                          startsWith(Code, "D") ~ 2015,
                          startsWith(Code, "E") ~ 2016,
                          startsWith(Code, "F") ~ 2017,
                          startsWith(Code, "G") ~ 2018,
                          startsWith(Code, "H") ~ 2019,
                          startsWith(Code, "J") ~ 2020,
                ), Year))

Выход

   Code Year Year1
1 AX123 <NA>  2013
2 CL199 2014  2014
3 GH679 2018  2018
4 XX485 <NA>  <NA>

Большой! Я знаю, что это должно быть простое решение. Это также вернет NA, если нет подходящего алфавита, верно? Поскольку он вернет значение года как есть.

Helmasan 21.03.2022 10:31

@Helma Hassan Правильно, смотрите обновление

Gnueghoidune 21.03.2022 10:37

Если вы хотите манипулировать только частью фрейма данных, вы можете индексировать его части в левой части любого назначения "<-".

вы можете определить эти части скобками [] за кадром данных:

df[rows,columns]

подробнее об индексации: https://stats.oarc.ucla.edu/r/modules/subsetting-data/

В вашем случае это может быть:

df[is.na(df$Year),] <- df %>%
  filter(is.na(Year)) %>%
  mutate(Year = case_when(startsWith(Code, "A") ~ 2013,
                          startsWith(Code, "C") ~ 2014,
                          startsWith(Code, "D") ~ 2015,
                          startsWith(Code, "E") ~ 2016,
                          startsWith(Code, "F") ~ 2017,
                          startsWith(Code, "G") ~ 2018,
                          startsWith(Code, "H") ~ 2019,
                          startsWith(Code, "J") ~ 2020,
                          TRUE ~ NA_real_))

Ах, я даже не думал об использовании подмножества! Спасибо, очень полезный подход. Я трачу время на создание нового df каждый раз, когда манипулирую данными.

Helmasan 21.03.2022 10:44

Вот другой подход, использующий таблицу поиска и объединение обновлений. Должно работать довольно быстро.

df <- data.frame(Code = c("AX123", "CL199", "GH679"), 
                 Year = c(NA, 2014, 2018)) 

library(data.table)
# Create lookup table with regexes and years
lookup <- data.table(id = LETTERS[c(1,3:8,10)], newYear = 2013:2020)
# Make df a data.table  
setDT(df)
# Get the first letter of Code-column, to join on
df[, temp := substr(Code, 1, 1)]
# perform by-reference update join
df[is.na(Year), Year := lookup[df[is.na(Year), ], newYear, on = .(id = temp)]][]
# remove temp
df[, temp := NULL]
# Code Year
# 1: AX123 2013
# 2: CL199 2014
# 3: GH679 2018

Базовая альтернатива R:

# option 1: readable version
ix <- match(substr(df$Code[is.na(df$Year)],1,1), LETTERS[c(1,3:8,10)])
df$Year[is.na(df$Year)] <- ix + 2012

# option 2: direct version
df$Year[is.na(df$Year)] <- match(substr(df$Code[is.na(df$Year)],1,1), LETTERS[c(1,3:8,10)]) + 2012

что дает следующий результат:

> df
   Code Year
1 AX123 2013
2 CL199 2014
3 GH679 2018

Иногда мне приходилось переваривать это, но вау, это опрятно. Если я правильно понимаю, я также могу использовать аналогичный подход для декодирования второго, третьего алфавита и т. д., просто заменив начало и конец в substr ()? Как и в других df, год расшифровывается из 2-го или 3-го алфавита.

Helmasan 21.03.2022 11:38

@Helmasan да, это правда

Jaap 21.03.2022 11:44

Вот альтернативный подход:

  1. Создайте именованный вектор replacement
  2. создайте pattern, чтобы соответствовать
  3. Используйте оператор ifelse с str_detect и match

replacement <- 2013:2020
names(replacement) <- LETTERS[c(1, 3:9)]
pattern <- paste(names(replacement), collapse = '|')

library(dplyr)
library(stringr)

df %>% 
  mutate(helper = substring(Code, 1, 1),
         Year = ifelse(is.na(Year) & str_detect(helper, pattern), 
                       replacement[match(helper, names(replacement))], Year)) %>% 
  select(-helper)
  Code Year
1 AX123 2013
2 CL199 2014
3 GH679 2018

Другие вопросы по теме

Есть ли способ записать несколько фреймов данных pandas на один и тот же лист, используя движок openpyxl?
Как суммировать множество столбцов в уникальные комбинации
Панды переименовывают несколько столбцов, используя шаблон регулярного выражения
Как сохранить каждый столбец фрейма данных на отдельные листы в одном файле Excel
Как получить среднее значение строки для определенных столбцов в фрейме данных r?
Необходимо заменить все значения на ABC, где когда-либо ABC является подстрокой в ​​кадре данных
Объедините два DataFrame в индексе, но если в одном DF отсутствует индекс, я хочу, чтобы он создавал значения Null (Nan), если в одном из DF отсутствует этот индекс
Расплавить фрейм данных pandas
Найдите строки с помощью df.iterrows() и удалите некоторые из них в зависимости от условия
Как объединить два фрейма данных с отсутствующими значениями?