#r #string #dataframe
#r #строка #фрейм данных
Вопрос:
У меня есть столбец в одном фрейме данных с названиями городов и штатов в нем:
ac lt;- c("san francisco ca", "pittsburgh pa", "philadelphia pa", "washington dc", "new york ny", "aliquippa pa", "gainesville fl", "manhattan ks")
ac lt;- as.data.frame(ac)
Я хотел бы найти значения ac$ac
в другом столбце фрейма данных d$description
и вернуть значение столбца, id
если есть совпадение.
dput(df) structure(list(month = c(202110L, 201910L, 202005L, 201703L, 201208L, 201502L), id = c(100559687L, 100558763L, 100558934L, 100558946L, 100543422L, 100547618L), description = c("residential local telephone service local with more san francisco ca flat rate with eas package plan includes voicemail call forwarding call waiting caller id call restriction three way calling id block speed dialing call return call screening modem rental voip transmission telephone access line 34 95 modem rental 7 00 total 41 95", "digital video programming service multilatino ultra bensalem pa service includes digital economy multilatino digital preferred tier and certain additonal digital channels coaxial cable transmission", "residential all distance telephone service unlimited voice only harrisburg pa flat rate with eas only features call waiting caller id caller id with call waiting call screening call forwarding call forwarding selective call return 69 3 way calling anonymous call rejection repeat dialing speed dial caller id blocking coaxial cable transmission", "residential all distance telephone service unlimited voice only pittsburgh pa flat rate with eas only features call waiting caller id caller id with call waiting call screening call forwarding call forwarding selective call return 69 3 way calling anonymous call rejection repeat dialing speed dial caller id blocking", "local spot advertising 30 second advertisement austin tx weekday 6 am 6 pm other audience demographic w18 49 number of rating points for daypart 0 29 average cpp 125", "residential public switched toll interstate manhattan ks ks plan area residence switched toll base period average revenue per minute 0 18 minute online" )), row.names = c(1L, 1245L, 3800L, 10538L, 20362L, 50000L), class = "data.frame")
Я попытался сделать это, получив доступ к индексам строк совпадений с помощью следующих методов:
which(ac$ac %in% df$description)
—это возвращаетсяinteger(0)
.grep(ac$ac, df$description, value = FALSE)
—это возвращает первый индекс, 1. Но он не векторизован.str_detect(string = ac$ac, pattern = df$description)
— но это возвращает всеFALSE
, что неверно.
Мой вопрос: как мне выполнить поиск ac$ac
в df$description
и вернуть соответствующее значение df$id
в случае совпадения? Обратите внимание, что векторы не имеют одинаковой длины. Я ищу ВСЕ совпадения, а не только первое. Я бы предпочел что-то простое и быстрое, потому что фактические наборы данных, которые я буду использовать, содержат более 100 тысяч строк в каждом, но любые предложения или идеи приветствуются. Спасибо.
Редактировать. В связи с приведенным ниже первоначальным ответом Андре название вопроса было изменено с учетом изменения объема вопроса.
Правка (12/7): щедрость добавлена для создания дополнительного интереса и быстрого, эффективного масштабируемого решения.
Редактировать (12/8): Уточнение-я хотел бы иметь возможность добавить id
переменную из df
ac
в фрейм данных, как в ac$id
.
Комментарии:
1. Вопрос изменяется после того, как дан ответ. Имя переменной было заменено. Если вы измените значительную часть своего вопроса, вам лучше добавить свой вопрос новым блоком, иначе люди, добровольно отвечающие на ваши вопросы, будут, по-видимому, терять свое время, поскольку их ответы станут бессмысленными.
2. @asd-tm справедливая точка зрения. Я должен был уточнить свой вопрос. Теперь я отредактировал. Надеюсь, этого будет достаточно.
3. моя заметка была посвящена моему ответу относительно названия переменных
4. Я спрашиваю, потому что в противном случае можно было бы захватить/собрать результат в вектор вместо списка.
5. @javlenti Я обновил свой ответ. Надеюсь, это то, чего вы ожидали сейчас.
Ответ №1:
Самые простые решения, как правило, самые быстрые! Вот мое предложение:
str = paste0(ac, collapse="|") df$id[grep(str, df$description)]
Но вы также можете сделать это
df$id[as.logical(rowSums(!is.na(sapply(ac, function(x) stringr::str_match(df$description, x)))))]
Или вот так
df$id[grepl(str, df$description, perl=T)]
Однако это необходимо сравнить. Кстати, я добавил предложения от @Andre Wildberg и @Martina C. Arnolda. Ниже приведен контрольный показатель.
str = paste0(ac, collapse="|") fFiolka1 = function() df$id[grep(str, df$description)] fFiolka2 = function() df$id[as.logical(rowSums(!is.na(sapply(ac, function(x) stringr::str_match(df$description, x)))))] fFiolka3 = function() df$id[grepl(str, df$description, perl=T)] fWildberg1 = function() df$id[unlist(sapply(ac, function(x) grep(x, df$description)))] fWildberg2 = function() df$id[as.logical(rowSums(sapply(ac, function(x) stri_detect_regex(df$description, x))))] fArnolda1 = function() df[grep(str, df$description), ]["id"] fArnolda2 = function() df[stringi::stri_detect_regex(df$description, str), ]["id"] fArnolda3 = function() df %gt;% filter(description %gt;% str_detect(str)) %gt;% select(id) library(microbenchmark) ggplot2::autoplot(microbenchmark( fFiolka1(), fFiolka2(), fFiolka3(), fWildberg1(), fWildberg2(), fArnolda1(), fArnolda2(), fArnolda3(), times=100))
Note, for the sake of simplicity I left ac as a vector !.
ac lt;- c("san francisco ca", "pittsburgh pa", "philadelphia pa", "washington dc", "new york ny", "aliquippa pa", "gainesville fl", "manhattan ks")
Special update for @jvalenti
OKAY. Now I understand better what you want to achieve. However, in order to fully show the best solution, I have slightly modified your data. Here they are
library(tidyverse) ac lt;- c("san francisco ca", "pittsburgh pa", "philadelphia pa", "washington dc", "new york ny", "aliquippa pa", "gainesville fl", "manhattan ks") ac = tibble(ac = ac) df = structure(list( month = c(202110L, 201910L, 202005L, 201703L, 201208L, 201502L), id = c(100559687L, 100558763L, 100558934L, 100558946L, 100543422L, 100547618L), description = c( "residential local telephone pittsburgh pa local with more san francisco ca flat rate with eas philadelphia pa plan includes voicemail call forwarding call waiting caller id call restriction three way calling id block speed dialing call return call screening modem rental voip transmission telephone access line 34 95 modem rental 7 00 total 41 95", "digital video san francisco ca pittsburgh pa multilatino ultra bensalem pa service includes digital economy multilatino digital preferred tier and certain additonal digital channels coaxial cable transmission", "residential all distance telephone pittsburgh pa unlimited voice only harrisburg pa flat rate with eas only features call waiting caller id caller id with call waiting call screening call forwarding call forwarding selective call return 69 3 way calling anonymous call rejection repeat dialing speed dial caller id blocking coaxial cable transmission", "residential all distance telephone pittsburgh pa unlimited voice philadelphia pa san francisco ca pa flat rate with eas only features call waiting caller id caller id with call waiting call screening call forwarding call forwarding selective call return 69 3 way calling anonymous call rejection repeat dialing speed dial caller id blocking", "local spot advertising 30 second advertisement austin tx weekday 6 am 6 pm other audience demographic w18 49 number of rating points for daypart 0 29 average cpp 125", "residential public switched toll pittsburgh pa manhattan ks ks plan area residence switched toll base san philadelphia pa ca average revenue per minute 0 18 minute online" )), row.names = c(1L, 1245L, 3800L, 10538L, 20362L, 50000L), class = "data.frame")
Below you will find four different solutions. One based on the for
loop, two solutions based on the functions from the dplyr
package, and yet a function from the collapse
package.
fSolition1 = function(){ id = vector("list", nrow(ac)) for(i in seq_along(ac$ac)){ id[[i]] = df$id[grep(ac$ac[i], df$description)] } ac %gt;% mutate(id = id) %gt;% unnest(id) } fSolition1() fSolition2 = function(){ ac %gt;% group_by(ac) %gt;% mutate(id = list(df$id[grep(ac, df$description)])) %gt;% unnest(id) } fSolition2() fSolition3 = function(){ ac %gt;% rowwise(ac) %gt;% mutate(id = list(df$id[grep(ac, df$description)])) %gt;% unnest(id) } fSolition3() fSolition4 = function(){ ac %gt;% collapse::ftransform(id = lapply(ac, function(x) df$id[grep(x, df$description)])) %gt;% unnest(id) } fSolition4()
Обратите внимание, что для приведенных данных все функции, возвращающие в результате следующую таблицу
# A tibble: 12 x 2 ac id lt;chrgt; lt;intgt; 1 san francisco ca 100559687 2 san francisco ca 100558763 3 san francisco ca 100558946 4 pittsburgh pa 100559687 5 pittsburgh pa 100558763 6 pittsburgh pa 100558934 7 pittsburgh pa 100558946 8 pittsburgh pa 100547618 9 philadelphia pa 100559687 10 philadelphia pa 100558946 11 philadelphia pa 100547618 12 manhattan ks 100547618
Пришло время для ориентира
library(microbenchmark) ggplot2::autoplot(microbenchmark( fSolition1(), fSolition2(), fSolition3(), fSolition4(), times=100))
Возможно, никого не удивит, что collapse
решение на основе является самым быстрым. Однако второе место может стать большим сюрпризом. Старое доброе решение, основанное на for
функции, находится на втором месте!! Кто-нибудь еще хочет сказать, что for
это медленно?
Специальное обновление для @Gwang-Jin Kim
Действия по векторам не сильно изменились. Посмотрите ниже.
df_ac = ac$ac df_decription = df$description df_id = df$id fSolition5 = function(){ id = vector("list", length = length(df_ac)) for(i in seq_along(df_ac)){ id[[i]] = df_id[grep(df_ac[i], df_decription)] } ac %gt;% mutate(id = id) %gt;% unnest(id) } fSolition5() library(microbenchmark) ggplot2::autoplot(microbenchmark( fSolition1(), fSolition2(), fSolition3(), fSolition4(), fSolition5(), times=100))
Но сочетание for
и ftransform
может быть удивительным !!!
fSolition6 = function(){ id = vector("list", nrow(ac)) for(i in seq_along(ac$ac)){ id[[i]] = df$id[grep(ac$ac[i], df$description)] } ac %gt;% collapse::ftransform(id = id) %gt;% unnest(id) } fSolition6() library(microbenchmark) ggplot2::autoplot(microbenchmark( fSolition1(), fSolition2(), fSolition3(), fSolition4(), fSolition5(), fSolition6(), times=100))
Last update for @jvalenti
Dear jvaleniti, in your question you wrote I have a column in one dataframe with city and state names and then I will be using have over 100k rows. My conclusion is that it is very likely that a given city will appear several times in your variable description
.
However, in the comment you wrote I don’t want to change the number of rows in ac So what kind of results do you expect? Let’s see what can be done with it.
Solution 1 — we return all id
as a list of vectors
ac %gt;% collapse::ftransform(id = map(ac, ~df$id[grep(.x, df$description)])) # # A tibble: 8 x 2 # ac id # * lt;chrgt; lt;listgt; # 1 san francisco ca lt;int [3]gt; # 2 pittsburgh pa lt;int [5]gt; # 3 philadelphia pa lt;int [3]gt; # 4 washington dc lt;int [0]gt; # 5 new york ny lt;int [0]gt; # 6 aliquippa pa lt;int [0]gt; # 7 gainesville fl lt;int [0]gt; # 8 manhattan ks lt;int [1]gt;
Solution 2 — we only return the first id
ac %gt;% collapse::ftransform(id = map_int(ac, ~df$id[grep(.x, df$description)][1])) # # A tibble: 8 x 2 # ac id # * lt;chrgt; lt;intgt; # 1 san francisco ca 100559687 # 2 pittsburgh pa 100559687 # 3 philadelphia pa 100559687 # 4 washington dc NA # 5 new york ny NA # 6 aliquippa pa NA # 7 gainesville fl NA # 8 manhattan ks 100547618
Solution 3 — we only return the last id
ac %gt;% collapse::ftransform(id = map_int(ac, function(x) { idx = grep(x, df$description) ifelse(length(idx)gt;0, df$id[idx[length(idx)]], NA)})) # # A tibble: 8 x 2 # ac id # * lt;chrgt; lt;intgt; # 1 san francisco ca 100558946 # 2 pittsburgh pa 100547618 # 3 philadelphia pa 100547618 # 4 washington dc NA # 5 new york ny NA # 6 aliquippa pa NA # 7 gainesville fl NA # 8 manhattan ks 100547618
Решение 4 — или, может быть, вы хотели бы выбрать любое id
из всех возможных
ac %gt;% collapse::ftransform(id = map_int(ac, function(x) { idx = grep(x, df$description) ifelse(length(idx)==0, NA, ifelse(length(idx)==1, df$id[idx], df$id[sample(idx, 1)]))})) # # A tibble: 8 x 2 # ac id # * lt;chrgt; lt;intgt; # 1 san francisco ca 100558763 # 2 pittsburgh pa 100559687 # 3 philadelphia pa 100547618 # 4 washington dc NA # 5 new york ny NA # 6 aliquippa pa NA # 7 gainesville fl NA # 8 manhattan ks 100547618
Решение 5 — если вы случайно захотели увидеть все идентификаторы и хотели сохранить количество ac
строк одновременно
ac %gt;% collapse::ftransform(id = map(ac, function(x) { idx = grep(x, df$description) if(length(idx)==0) tibble(id = NA, idn = "id1") else tibble( id = df$id[idx], idn = paste0("id",1:length(id)))})) %gt;% unnest(id) %gt;% pivot_wider(ac, names_from = idn, values_from = id) # # A tibble: 8 x 6 # ac id1 id2 id3 id4 id5 # lt;chrgt; lt;intgt; lt;intgt; lt;intgt; lt;intgt; lt;intgt; # 1 san francisco ca 100559687 100558763 100558946 NA NA # 2 pittsburgh pa 100559687 100558763 100558934 100558946 100547618 # 3 philadelphia pa 100559687 100558946 100547618 NA NA # 4 washington dc NA NA NA NA NA # 5 new york ny NA NA NA NA NA # 6 aliquippa pa NA NA NA NA NA # 7 gainesville fl NA NA NA NA NA # 8 manhattan ks 100547618 NA NA NA NA
К сожалению, в предоставленном вами описании не указано, какое из вышеперечисленных пяти решений является приемлемым для вас. Вам придется решать самому.
Комментарии:
1. Мне нужно добавить
id
столбец в мойac
исходный фрейм данных. Поскольку они имеют разную длину, как это будет работать?2. что, если
uniqe(ac$ac)
бы их использовали?3. если оставить его в виде вектора или работать с кадрами данных, это определенно изменит скорость.
4. это здорово, но он не возвращает исходный кадр данных, только совпадения. можно ли вернуть исходный кадр данных
ac
с исходным количеством строк иid
var, добавленным пробелами илиNA
в строках без совпадений? Я не хочу изменять количество строкac
. Извините за путаницу.5. Большое тебе спасибо за твою помощь в этом, Марек
Ответ №2:
Возможно, это вариант?
ac$id lt;- sapply(ac$ac, function(x) d$id[grep(x, d$description)]) # ac id # 1 san francisco ca 100559687 # 2 pittsburgh pa 100558946 # 3 philadelphia pa # 4 washington dc # 5 new york ny # 6 aliquippa pa # 7 gainesville fl # 8 manhattan ks 100547618
Комментарии:
1. это было бы немного быстрее при подаче заявления
fixed = TRUE
Ответ №3:
Попробуйте это sapply
с grep
помощью .
df$id[ unlist( sapply( ac$ac, function(x) grep(x, df$description ) ) ) ] [1] 100559687 100558946 100547618
ОТРЕДАКТИРУЙТЕ, попробуйте stri_detect_regex
с stringi
. Должно быть в 2-5 раз быстрее.
library(stringi) df$id[ as.logical( rowSums( sapply( ac$ac, function(x) stri_detect_regex( df$description, x ) ) ) ) ] [1] 100559687 100558946 100547618
Микробенчмарка на расширенном наборе данных с 1,728 Млн строк:
Память не должна быть проблемой, если вы не используете систему с общим объемом оперативной памяти менее 4 ГБ.
nrow(df) [1] 1728000 library(microbenchmark) microbenchmark( "grep1" = { res lt;- sapply(ac$ac, function(x) df$id[grep(x, df$description)]) }, "grep2" = { res lt;- df$id[ unlist( sapply( ac$ac, function(x) grep(x, df$description ) ) ) ] }, "stringi" = { res lt;- df$id[ as.logical( rowSums( sapply( ac$ac, function(x) stri_detect_regex( df$description, x ) ) ) ) ] }, times=10 ) Unit: seconds expr min lq mean median uq max neval cld grep1 96.90757 97.98706 100.13299 99.05837 101.99050 107.04312 10 b grep2 97.51382 97.66425 100.00610 99.20753 101.17921 106.86661 10 b stringi 46.15548 46.65894 48.68073 47.29635 50.15713 53.50351 10 a
Объем памяти во время микропозиции:
Путь: /Библиотека/Фреймворки/R. фреймворк/Версии/4.0/Ресурсы/bin/exec/R
Физический след: 638,3 М
Физический след (пик): 1,8 Г
Комментарии:
1. это, кажется, работает, но очень медленно
2. @asd-tm Спасибо за записку! Я был в процессе редактирования, а затем увидел изменения. Так что все обновлено в ответе.
3. @Andre извините, что я по ошибке разместил комментарий к вашему ответу вместо того, чтобы поместить его под вопросом!
4. @asd-tm Не беспокойтесь, это полезно для недавних ответов, чтобы узнать, работает ли их код по-прежнему. И по формулировке я понял, что вы имели в виду операцию 🙂
5. Мне нравится это решение, потому что оно простое и читаемое, но, похоже, оно не работает для масштаба. Когда я попытался, я получил ошибку от R:
cannot allocate vector of size 2 GB
Ответ №4:
Вы можете использовать regex_inner_join
из пакета fuzzyjoin
gt; library(fuzzyjoin) gt; regex_inner_join(df, ac, by = c(description = "ac")) month id 1 202110 100559687 2 201703 100558946 3 201502 100547618 description 1 residential local telephone service local with more san francisco ca flat rate with eas package plan includes voicemail call forwarding call waiting caller id call restriction three way calling id block speed dialing call return call screening modem rental voip transmission telephone access line 34 95 modem rental 7 00 total 41 95 2 residential all distance telephone service unlimited voice only pittsburgh pa flat rate with eas only features call waiting caller id caller id with call waiting call screening call forwarding call forwarding selective call return 69 3 way calling anonymous call rejection repeat dialing speed dial caller id blocking 3 residential public switched toll interstate manhattan ks ks plan area residence switched toll base period average revenue per minute 0 18 minute online ac 1 san francisco ca 2 pittsburgh pa 3 manhattan ks
Ответ №5:
Проверка с использованием регулярного выражения и недорогих функций должна быть быстрой:
Во-первых, мы генерируем шаблон для проверки: ac_regex lt;- paste(ac$ac, collapse = "|")
.
Существует несколько способов обнаружения совпадений в description
и подмножестве. Вот три:
# 1 grep() df[grep(ac_regex, df$description), ]["id"], # 2 stringi::stri_detect_*() df[stri_detect_regex(df$description, ac_regex), ]["id"], # 3 stringr::str_detect() tidy subsetting df %gt;% filter(description %gt;% str_detect(ac_regex)) %gt;% select(id),
Все три возвращают желаемое подмножество df
:
id 1 100559687 2 100558946 3 100547618
(Вам нужны пакеты tidyverse
и stringi
для вариантов 2 и 3.)
Давайте проведем тест (используя пакет bench
):
bench::mark( base_grep = df[grep(ac_regex, df$description), ]["id"], base_stringi = df[stringi::stri_detect_regex(df$description, ac_regex), ]["id"], tidy = df %gt;% filter(description %gt;% str_detect(ac_regex)) %gt;% select(id), check = F )
expression median lt;bch:exprgt; lt;bch:tmgt; 1 base_grep 146.61µs 2 base_stringi 119.6µs 3 tidy 1.99ms
Я бы пошел с stringi
тобой !
Комментарии:
1. по какой-то причине это приводит
invalid regular expression
к ошибке при использовании всего фрейма данных. Кроме того, есть предупреждение:In grep(ac_regex, df$description): TRE pattern compilation error 'Out of memory'
. Я не понимаю, как у меня не хватает памяти, когда у меня много оперативной памяти.2. Это потому
paste0()
, что долженac
быть вектор. Я забыл включить это в свой ответ. Исправленный
Ответ №6:
Во — первых c$c
, в предоставленном коде нет присвоения. Все данные присваиваются переменной с именем c
. У этой переменной нет никаких c
членов ( c$c
), с которыми вы пытаетесь работать.
Во-вторых, очень плохая практика-присваивать какие-либо данные переменным, называемым базовыми функциями R c lt;- c(...)
.