Я часами гуглил, и я не уверен, где найти ответ на что-то простое, подобное этому, поэтому я надеюсь, что это не дублирующий вопрос.
У меня есть большой фрейм данных (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 в другом столбце. Но поскольку многие строки уже заполнены значениями из-за исключений из правил, которые не следуют соглашению о кодированных именах, я не хотел перезаписывать их.
Это все еще дает Error: unexpected ')' in: " TRUE ~ NA_real_ )), ""))"
Ты почти понял. 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, если нет подходящего алфавита, верно? Поскольку он вернет значение года как есть.
@Helma Hassan Правильно, смотрите обновление
Если вы хотите манипулировать только частью фрейма данных, вы можете индексировать его части в левой части любого назначения "<-".
вы можете определить эти части скобками []
за кадром данных:
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 каждый раз, когда манипулирую данными.
Вот другой подход, использующий таблицу поиска и объединение обновлений. Должно работать довольно быстро.
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 да, это правда
Вот альтернативный подход:
replacement
pattern
, чтобы соответствовать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
ваш подход ifelse правильный, есть только несоответствие скобок. Последняя строка должна быть
), ""))