Условная замена при наличии "правильного" значения

Мои данные состоят из двух переменных: 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, но не смог никуда добраться.

Обновлено: я пропустил важную часть - могут быть случаи, когда имя персонажа не существует. Обновил пример - извините!

Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
250
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Быстрый грязный способ:

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]))

Другой вариант - использовать tidyrfill в обоих направлениях.

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, чтобы столбцы стали символами.

Что делает [1]?

NelsonGon 18.12.2018 10:26

@NelsonGon может быть более 1 name, который может быть не-NA в каждой группе, но мы можем заменить его только одним значением, поэтому мы подмножествуем его и выбираем первое значение, отличное от NA, из каждой группы.

Ronak Shah 18.12.2018 10:29

Хороший. Как убедиться, что мы не дублируем то, что заменяем?

NelsonGon 18.12.2018 10:31

вот что такое предположение. Для каждого name будет одно и то же значение id.

Ronak Shah 18.12.2018 10:54

Решение с 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

на самом деле в конце концов пошел с аналогичным решением, но не таким аккуратным, как это.

Thorst 02.01.2019 14:57

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