使用 Mutate 和 Case_When 仅填充 NA 行
Using Mutate and Case_When only to fill NA row
我已经看了好几个小时了,我不确定在哪里可以找到像这样简单的问题的答案,所以我希望这不是一个重复的问题。
我有一个大数据框 (936848 x 12),其中一列是一个编码名称,我可以从中导出其他列的值,在本例中是基于列代码的第一个字符的制造年份。
数据框的小样本:
df <- data.frame(Code = c("AX123", "CL199", "GH679"),
Year = c(NA, "2014", "2018"))
我只想仅在值缺失时才根据列代码更改列 Year。我不想覆盖 Year 列中的现有值。
因为这也涉及识别代码中字符串中的第一个字母,所以我使用 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
需要 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>
如果您只想操作数据框的一部分,您可以在任何“<-”赋值的左侧部分索引它的一部分。
你可以通过dataframe后面的括号[]
来定义这些部分:
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_))
这是一种不同的方法,使用 lookup-table 和更新连接。应该执行得相当快。
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
这是另一种方法:
- 创建命名向量
replacement
- 创建一个
pattern
来匹配
- 对
str_detect
和 match
使用 ifelse
语句
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
我已经看了好几个小时了,我不确定在哪里可以找到像这样简单的问题的答案,所以我希望这不是一个重复的问题。
我有一个大数据框 (936848 x 12),其中一列是一个编码名称,我可以从中导出其他列的值,在本例中是基于列代码的第一个字符的制造年份。
数据框的小样本:
df <- data.frame(Code = c("AX123", "CL199", "GH679"),
Year = c(NA, "2014", "2018"))
我只想仅在值缺失时才根据列代码更改列 Year。我不想覆盖 Year 列中的现有值。
因为这也涉及识别代码中字符串中的第一个字母,所以我使用 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
需要 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>
如果您只想操作数据框的一部分,您可以在任何“<-”赋值的左侧部分索引它的一部分。
你可以通过dataframe后面的括号[]
来定义这些部分:
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_))
这是一种不同的方法,使用 lookup-table 和更新连接。应该执行得相当快。
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
这是另一种方法:
- 创建命名向量
replacement
- 创建一个
pattern
来匹配 - 对
str_detect
和match
使用
ifelse
语句
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