Мои данные состоят из двух переменных: 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
, но не смог никуда добраться.
Обновлено: я пропустил важную часть - могут быть случаи, когда имя персонажа не существует. Обновил пример - извините!
Быстрый грязный способ:
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
.
РЕДАКТИРОВАТЬ
Поскольку вы упомянули, что существуют определенные 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 - Я только что добавил stringsAsFactors = FALSE
в ваш вызов data.frame, чтобы столбцы стали символами.
@NelsonGon может быть более 1 name
, который может быть не-NA в каждой группе, но мы можем заменить его только одним значением, поэтому мы подмножествуем его и выбираем первое значение, отличное от NA, из каждой группы.
Хороший. Как убедиться, что мы не дублируем то, что заменяем?
вот что такое предположение. Для каждого name
будет одно и то же значение id
.
Решение с 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 Shah)
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)
В качестве альтернативы, это можно решить с помощью обновить присоединиться, используя Справочная таблица:
Таблица поиска создается путем фильтрации 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
на самом деле в конце концов пошел с аналогичным решением, но не таким аккуратным, как это.
Что делает
[1]
?