我的数据由两个变量组成,一个id
和一个相应的name
。name
可以是两件事。ID 或一串字母。
如果存在非数字名称,我需要用此值替换任何数字名称。
数据示例
df <- data.frame(id = c("100", "100", "101", "102", "103", "104", "104", "105", "100", "106"),
name = c("100", "A", "B", "C", "D", "104", "E", "F", "100", "106"),
correct_name = c("A", "A", "B", "C", "D", "E", "E", "F", "A", "106"), stringsAsFactors = F)
第三列给出所需的结果。
我一直在%in%
乱搞,duplicated
和group_by
,但一无所获。
编辑:我错过了一个关键部分 - 可能存在不存在角色名称的实例。更新了示例 - 对不起!
编辑
由于您已经提到在某些情况下某些id
没有name
可以替换,因此我们可以修改ave
选项,检查条件并在一次调用中替换所有值。
df$name <- with(df, ave(name, id, FUN = function(x) {
inds = grepl("[0-9]+", x)
if (any(!inds))
replace(x, inds, x[which.max(!inds)])
else
x
}))
df
# id name correct_name
#1 100 A A
#2 100 A A
#3 101 B B
#4 102 C C
#5 103 D D
#6 104 E E
#7 104 E E
#8 105 F F
#9 100 A A
#10 106 106 106
原始答案
假设每个id
只有一个唯一的name
,使用dplyr
我们可以先做双replace
,我们将包含数字的名称更改为NA
,然后将这些NA
替换为组中的第一个非NA值。
library(dplyr)
df %>%
group_by(id) %>%
mutate(name = replace(name, grepl("[0-9]+", name), NA),
name = replace(name, is.na(name), name[!is.na(name)][1]))
# id name correct_name
# <chr> <chr> <chr>
#1 100 A A
#2 100 A A
#3 101 B B
#4 102 C C
#5 103 D D
#6 104 E E
#7 104 E E
#8 105 F F
#9 100 A A
并对基本 R 使用相同的逻辑ave
#Replace the numbers with NA
df$name[grepl("[0-9]+", df$name)] <- NA
#Change the NA's to first non-NA value in the group
df$name <- with(df,ave(name, id, FUN = function(x) x[!is.na(x)][1]))
另一种选择是在两个方向上使用tidyr
fill
library(tidyverse)
df %>%
mutate(name = replace(name, grepl("[0-9]+", name), NA)) %>%
group_by(id) %>%
fill(name) %>% #default direction is "down"
fill(name, .direction = "up")
# id name correct_name
# <chr> <chr> <chr>
#1 100 A A
#2 100 A A
#3 100 A A
#4 101 B B
#5 102 C C
#6 103 D D
#7 104 E E
#8 104 E E
#9 105 F F
PS - 我刚刚在您的 data.frame 调用中添加了stringsAsFactors = FALSE
,以使列成为字符。
具有dplyr
并使用ifelse
加grepl
的解决方案,模式设置为"\d+"
(即:数字)。
编辑:可以只有一个mutate
:
df %>%
group_by(id) %>%
mutate(namenew = ifelse(
grepl("\d+", name), # match for digits in the string
name[!grepl("\d+", name)][1], # if TRUE, substitute with the first non-digit
name # if FALSE, keep it
))
# id name correct_name namenew
# 1 100 100 A A
# 2 100 A A A
# 3 101 B B B
# 4 102 C C C
# 5 103 D D D
# 6 104 104 E A
# 7 104 E E E
# 8 105 F F F
# 9 100 100 A A
与我上面的解决方案相比,也许更清楚发生了什么。(类似于@Ronak沙阿)
library(dplyr)
df %>%
group_by(id) %>%
mutate(namenew = ifelse(
grepl("\d+", name),
NA,
name
)) %>%
mutate(namenew = ifelse(
is.na(namenew),
namenew[!is.na(namenew)][1],
namenew
))
# id name correct_name namenew
# 1 100 100 A A
# 2 100 A A A
# 3 101 B B B
# 4 102 C C C
# 5 103 D D D
# 6 104 104 E A
# 7 104 E E E
# 8 105 F F F
# 9 100 100 A A
数据(stringsAsFactors
很重要):
df <- data.frame(id = c("100", "100", "101", "102", "103", "104", "104", "105", "100"),
name = c("100", "A", "B", "C", "D", "104", "E", "F", "100"),
correct_name = c("A", "A", "B", "C", "D", "E", "E", "F", "A"), stringsAsFactors = F)
快速肮脏的方式:
sapply(1:nrow(df),function(x){
if (is.na(as.numeric(df$id[x]))==FALSE){
ind=which(df$id==df$id[x])
ind2=which(is.na(as.numeric(as.character((df$name[ind]))))==TRUE)
df$name[x]<<-df$name[ind[ind2[1]]]
}
})
df
id name correct_name
1 100 A A
2 100 A A
3 101 B B
4 102 C C
5 103 D D
6 104 E E
7 104 E E
8 105 F F
9 100 A A
将名称转换为numeric
。如果出现NA
则名称为字母。如果不是,则为数字。遍历具有相同id
的其他名称,并分配在具有相同id
的其他示例中找到的字母。
或者,这可以通过使用查找表的更新连接来解决:
查找表是通过筛选非数字条目df
创建的:
library(data.table)
setDT(df)[!name %like% "^\d+$"]
id name correct_name 1: 100 A A 2: 101 B B 3: 102 C C 4: 103 D D 5: 104 E E 6: 105 F F
现在,df
与查找表联接,在找到匹配项的位置,name
替换为查找表中的相应条目。否则,name
保持不变:
setDT(df)[df[!name %like% "^\d+$"], on = "id", name := i.name]
df
id name correct_name 1: 100 A A 2: 100 A A 3: 101 B B 4: 102 C C 5: 103 D D 6: 104 E E 7: 104 E E 8: 105 F F 9: 100 A A 10: 106 106 106