Str_replace_all ведет себя странно при передаче именованного вектора с общими ключами и значениями; что делать?

У меня есть следующая проблема, о которой я уже поднимал в другом вопросе: у меня есть столбец фрейма данных со строками, выражающими отдельные числовые значения или диапазоны значений, например «1:1496,3545:4785,7781» и так далее. У меня также есть словарь, в котором каждое числовое значение связано с прогрессивным идентификатором, например «91» = «91», но «91bis» = «92» (первый «двойной» элемент). Мне нужно заменить каждое числовое значение в ячейках фрейма данных прогрессивным идентификатором.

Вот образец словаря:

dict <- structure(list(q.ID = c("1", "2", "3", "4", "5", "6", "7", "8", 
"9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", 
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", 
"31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", 
"42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", 
"53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", 
"64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", 
"75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", 
"86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", 
"97", "98", "99", "100"), q.Voce = c("1", "2", "3", "4", "5", 
"6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", 
"17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", 
"28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", 
"39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", 
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", 
"61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", 
"72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", 
"83", "84", "85", "86", "87", "88", "89", "90", "91", "91bis", 
"91ter", "92", "93", "93bis", "94", "94bis", "95", "96")), row.names = c(NA, 
100L), class = "data.frame")

РЕДАКТИРОВАТЬ

И вот что я пробовал:

#library(stringr)

v <- "1,91bis:94" #example string

str_replace_all(v,setnames(dict$q.ID,dict$q.Voce))

Теперь это должно вернуться [1] "1,92:97", но на самом деле возвращается [1] "1,97:97". Как заметил пользователь @stefan, когда я впервые задал вопрос , это происходит потому, что ключи и значения словаря имеют общие элементы: когда функция запускается, она вводит «91bis» и преобразует его в «92», но поскольку 92 также значение в словаре, оно снова заменяется на «94» и, наконец, на «97». В словаре нет значения «97», поэтому процесс останавливается, но при большей выборке он продолжается (я пробовал). Есть ли способ предотвратить это странное поведение?

Из комментариев к ответу SamR ясно, что проблема не воспроизводится в текущем примере, поэтому я проголосовал за закрытие вопроса.

GuedesBF 23.08.2024 17:55

Я согласен, что, вероятно, я мог бы выбрать более подходящий пример, но если вы нашли время прочитать вопрос, то в начале четко указано, как выглядят данные, и я также привел пример («1:1496,3545 :4785,7781").

eazyezy 23.08.2024 17:58

Я не понимаю часть диапазонов. У вас есть строка «20:100»? Что там следует заменить? Стоит ли его расширить до 20,21,22,23,...,100 и там заменить цифры? Следует ли сохранить его как диапазон или оставить расширенным?

Andre Wildberg 23.08.2024 18:00

@AndreWildberg в диапазонах мне нужно только заменить начало и конец соответствующими значениями в словаре. Причина в том, что исходные значения являются буквенно-цифровыми; как только они все будут преобразованы в прогрессивный эквивалент, диапазоны можно будет расширить.

eazyezy 23.08.2024 18:05

Возможно, приведите более сложный пример с ожидаемым результатом, охватывающим все возможности (включая диапазоны).

Andre Wildberg 23.08.2024 18:10
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

О том, почему возникает проблема, с которой вы столкнулись, см. R: Эффективный способ str_replace_all без рекурсивной замены конфликтующих замен? Один ответ можно адаптировать к этой ситуации, но он требует существенных изменений, учитывая формат ваших данных:

v <- c("1,91bis:94", "1,91bis,94", "1,91bis,96")
# Output should be c("1,92,97", "1,92,97", "1,92,100")
str_replace_all(
    v,
    "(?<=,|^|:).*?(?=,|:|$)",
    \(x) setNames(dict$q.ID, dict$q.Voce)[x]
)
# [1] "1,92:97"  "1,92,97"  "1,92,100"

Я думаю, что регулярное выражение проще всего визуализировать:

Это заменит все, что находится между запятой и двоеточием (или началом и концом строки) значениями из словаря:

  • (?<=,|^|:): эта часть ищет запятую, начало строки или двоеточие. | внутри этой ретроспективы не заменяется группой без захвата, поскольку ретроспективы не поддерживают чередование внутри групп без захвата.
  • .*?: Не жадный подбор любого персонажа как можно меньше раз.
  • (?=(?:,|:|$)): просмотр запятой, двоеточия или конца строки без их включения в совпадение.

В отличие от использования strsplit(), это означает, что информация о том, был ли разделитель запятой или двоеточием, не теряется.

Изначально я использовал аналогичный подход, но отказался от него, поскольку отдельные значения смешиваются с диапазонами, и я не могу позволить себе потерять эту информацию, сводя все запятыми.

eazyezy 23.08.2024 17:45

Найдите другое решение ниже. Это может быть полезно там, где вам нужна большая скорость, но менее элегантно, чем решение @SamR.

v = c("1,91bis,94", "1,91bis:94", "1,91bis,96", "1,94bis,96", "1,91bis,96")

# subset the rows in dict that are needed for replacements in v 
tb = with(dict, dict[q.ID != q.Voce & q.Voce %in% unlist(strsplit(v, "[,:]")),])
tb = tb[order(tb$q.Voce, decreasing=TRUE),] # useful to properly handle patterns like 91 and 91bis
# replacement
with(tb, Reduce(\(x,i) gsub(q.Voce[i], q.ID[i], x, fixed=T), c(list(v), seq_along(q.Voce))))

[1] "1,92,97"  "1,92:97"  "1,92,100" "1,98,100" "1,92,100"






#### speed test:

set.seed(4934567)
v = sample(c("1,91bis,94", "1,91bis:94", "1,91bis,96", "1,94bis,96", "1,91bis,96"), 1e5, T)

microbenchmark::microbenchmark(
  Kamgang = {
    tb = with(dict, dict[q.ID != q.Voce & q.Voce %in% unlist(strsplit(v, "[,:]")),])
    tb = tb[order(tb$q.Voce, decreasing=TRUE),]
    with(tb, Reduce(\(x,i) gsub(q.Voce[i], q.ID[i], x, fixed=T), c(list(v), seq_along(q.Voce))))
  },
  SamR = stringr::str_replace_all(
    v,
    "(?<=,|^|:).*?(?=,|:|$)",
    \(x) setNames(dict$q.ID, dict$q.Voce)[x]
  ),
  times=3L,
  unit = "relative"
)

Unit: relative
    expr      min       lq     mean   median       uq      max neval
 Kamgang  1.00000  1.00000  1.00000  1.00000  1.00000  1.00000     3
    SamR 51.55286 51.12241 51.42632 50.69923 51.36459 52.01984     3

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