У меня есть следующая проблема, о которой я уже поднимал в другом вопросе: у меня есть столбец фрейма данных со строками, выражающими отдельные числовые значения или диапазоны значений, например «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», поэтому процесс останавливается, но при большей выборке он продолжается (я пробовал).
Есть ли способ предотвратить это странное поведение?
Я согласен, что, вероятно, я мог бы выбрать более подходящий пример, но если вы нашли время прочитать вопрос, то в начале четко указано, как выглядят данные, и я также привел пример («1:1496,3545 :4785,7781").
Я не понимаю часть диапазонов. У вас есть строка «20:100»? Что там следует заменить? Стоит ли его расширить до 20,21,22,23,...,100 и там заменить цифры? Следует ли сохранить его как диапазон или оставить расширенным?
@AndreWildberg в диапазонах мне нужно только заменить начало и конец соответствующими значениями в словаре. Причина в том, что исходные значения являются буквенно-цифровыми; как только они все будут преобразованы в прогрессивный эквивалент, диапазоны можно будет расширить.
Возможно, приведите более сложный пример с ожидаемым результатом, охватывающим все возможности (включая диапазоны).
О том, почему возникает проблема, с которой вы столкнулись, см. 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()
, это означает, что информация о том, был ли разделитель запятой или двоеточием, не теряется.
Изначально я использовал аналогичный подход, но отказался от него, поскольку отдельные значения смешиваются с диапазонами, и я не могу позволить себе потерять эту информацию, сводя все запятыми.
Найдите другое решение ниже. Это может быть полезно там, где вам нужна большая скорость, но менее элегантно, чем решение @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
Из комментариев к ответу SamR ясно, что проблема не воспроизводится в текущем примере, поэтому я проголосовал за закрытие вопроса.