Как рассчитать количество вхождений данного символа в каждую строку столбца строк?

105

У меня есть data.frame, в котором определенные переменные содержат текстовую строку. Я хочу подсчитать количество вхождений данного символа в каждую отдельную строку.

Пример:

q.data<-data.frame(number=1:3, string=c("greatgreat", "magic", "not"))

Я хочу создать новый столбец для q.data с числом вхождений "a" в строке (т.е. c (2,1,0)).

Единственный запутанный подход, который мне удалось сделать, это:

string.counter<-function(strings, pattern){  
  counts<-NULL
  for(i in 1:length(strings)){
    counts[i]<-length(attr(gregexpr(pattern,strings[i])[[1]], "match.length")[attr(gregexpr(pattern,strings[i])[[1]], "match.length")>0])
  }
return(counts)
}

string.counter(strings=q.data$string, pattern="a")

 number     string number.of.a
1      1 greatgreat           2
2      2      magic           1
3      3        not           0
Этьен Лоу-Декари
источник

Ответы:

143

Пакет stringr предоставляет str_countфункцию, которая, кажется, делает то, что вам интересно.

# Load your example data
q.data<-data.frame(number=1:3, string=c("greatgreat", "magic", "not"), stringsAsFactors = F)
library(stringr)

# Count the number of 'a's in each element of string
q.data$number.of.a <- str_count(q.data$string, "a")
q.data
#  number     string number.of.a
#1      1 greatgreat           2
#2      2      magic           1
#3      3        not           0
Дэйсон
источник
1
Ваш был намного быстрее, хотя ему нужен as.character () вокруг основного аргумента, чтобы успешно решить поставленную проблему.
IRTFM
1
@DWin - это правда, но я избежал этой проблемы, добавив stringsAsFactors = FALSEпри определении фрейма данных.
Dason
Извините, я не понял. На самом деле я отвечал Тимриффу и говорил ему, что его функция вызвала ошибку с поставленной проблемой. Возможно, он использовал ваше новое определение проблемы, но он этого не сказал.
IRTFM
да, я тоже сделал, stringsAsFactors=TRUEна моем компе, но не упомянул об этом
Тим Риф
Поиск строки в факторе будет работать, например str_count (d $ factor_column, 'A'), но не наоборот
Nitro
65

Если вы не хотите покидать базовый R, вот довольно лаконичная и выразительная возможность:

x <- q.data$string
lengths(regmatches(x, gregexpr("a", x)))
# [1] 2 1 0
Джош О'Брайен
источник
2
Хорошо - может быть, это станет выразительным только после того, как вы используете regmatchesи gregexprвместе несколько раз, но эта комбинация достаточно мощная, и я подумал, что она заслуживает включения.
Джош О'Брайен,
regmatchesотносительно новый. Он был представлен в версии 2.14.
Дейсон
Я не думаю, что вам нужен бит регматов. Функция gregexpr возвращает список с индексами совпадающих вхождений для каждого элемента x.
savagent
@savagent - Не могли бы вы поделиться кодом, который вы бы использовали для вычисления количества совпадений в каждой строке?
Джош О'Брайен
1
Извините, я забыл про -1. Он работает только в том случае, если в каждой строке есть хотя бы одно совпадение, sapply (gregexpr ("g", q.data $ string), length).
savagent
18
nchar(as.character(q.data$string)) -nchar( gsub("a", "", q.data$string))
[1] 2 1 0

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

Вот результаты теста (с увеличенным размером теста до 3000 строк)

 q.data<-q.data[rep(1:NROW(q.data), 1000),]
 str(q.data)
'data.frame':   3000 obs. of  3 variables:
 $ number     : int  1 2 3 1 2 3 1 2 3 1 ...
 $ string     : Factor w/ 3 levels "greatgreat","magic",..: 1 2 3 1 2 3 1 2 3 1 ...
 $ number.of.a: int  2 1 0 2 1 0 2 1 0 2 ...

 benchmark( Dason = { q.data$number.of.a <- str_count(as.character(q.data$string), "a") },
 Tim = {resT <- sapply(as.character(q.data$string), function(x, letter = "a"){
                            sum(unlist(strsplit(x, split = "")) == letter) }) }, 

 DWin = {resW <- nchar(as.character(q.data$string)) -nchar( gsub("a", "", q.data$string))},
 Josh = {x <- sapply(regmatches(q.data$string, gregexpr("g",q.data$string )), length)}, replications=100)
#-----------------------
   test replications elapsed  relative user.self sys.self user.child sys.child
1 Dason          100   4.173  9.959427     2.985    1.204          0         0
3  DWin          100   0.419  1.000000     0.417    0.003          0         0
4  Josh          100  18.635 44.474940    17.883    0.827          0         0
2   Tim          100   3.705  8.842482     3.646    0.072          0         0
IRTFM
источник
3
Это самое быстрое решение в ответах, но оно на 30% быстрее в вашем тесте за счет передачи необязательного fixed=TRUEв gsub. Есть также случаи, когда fixed=TRUEэто потребуется (например, когда символ, который вы хотите подсчитать, может быть интерпретирован как утверждение регулярного выражения, например .).
C8H10N4O2
7

Еще один хороший вариант с использованием charToRaw :

sum(charToRaw("abc.d.aa") == charToRaw('.'))
Чжан Тао
источник
6

stringiПакет обеспечивает функцию stri_countи stri_count_fixedкоторые очень быстро.

stringi::stri_count(q.data$string, fixed = "a")
# [1] 2 1 0

ориентир

По сравнению с самым быстрым подходом из ответа @ 42- и эквивалентной функцией из stringrпакета для вектора с 30,000 элементами.

library(microbenchmark)

benchmark <- microbenchmark(
  stringi = stringi::stri_count(test.data$string, fixed = "a"),
  baseR = nchar(test.data$string) - nchar(gsub("a", "", test.data$string, fixed = TRUE)),
  stringr = str_count(test.data$string, "a")
)

autoplot(benchmark)

данные

q.data <- data.frame(number=1:3, string=c("greatgreat", "magic", "not"), stringsAsFactors = FALSE)
test.data <- q.data[rep(1:NROW(q.data), 10000),]

введите описание изображения здесь

Маркус
источник
2

Я уверен, что у кого-то получится лучше, но это работает:

sapply(as.character(q.data$string), function(x, letter = "a"){
  sum(unlist(strsplit(x, split = "")) == letter)
})
greatgreat      magic        not 
     2          1          0 

или в функции:

countLetter <- function(charvec, letter){
  sapply(charvec, function(x, letter){
    sum(unlist(strsplit(x, split = "")) == letter)
  }, letter = letter)
}
countLetter(as.character(q.data$string),"a")
Тим Риф
источник
Кажется, я получаю ошибку с первым ... и вторым ... (пытался все это
протестировать
1

Вы можете просто использовать строковое деление

require(roperators)
my_strings <- c('apple', banana', 'pear', 'melon')
my_strings %s/% 'a'

Что даст вам 1, 3, 1, 0. Вы также можете использовать строковое деление с регулярными выражениями и целыми словами.

Бенбоб
источник
0

Самый простой и чистый способ ИМХО:

q.data$number.of.a <- lengths(gregexpr('a', q.data$string))

#  number     string number.of.a`
#1      1 greatgreat           2`
#2      2      magic           1`
#3      3        not           0`
Джованни Кампаньоли
источник
Как это сделать? Для меня lengths(gregexpr('a', q.data$string))возвращается 2 1 1, а не 2 1 0.
Финн Оруп Нильсен
0

Еще один base Rвариант:

lengths(lapply(q.data$string, grepRaw, pattern = "a", all = TRUE, fixed = TRUE))

[1] 2 1 0
tmfmnk
источник
-1

Следующее выражение выполняет свою работу и работает не только с буквами, но и с символами.

Выражение работает следующим образом:

1: он использует lapply для столбцов кадра данных q.data для перебора строк столбца 2 ("lapply (q.data [, 2],"),

2: к каждой строке столбца 2 применяется функция "function (x) {sum ('a' == strsplit (as.character (x), '') [[1]])}". Функция принимает значение каждой строки столбца 2 (x), преобразует его в символ (например, в случае, если это фактор), и выполняет разбиение строки на каждый символ ("strsplit (as.character (x), ' ') "). В результате у нас есть вектор с каждым символом строкового значения для каждой строки столбца 2.

3: Каждое векторное значение вектора сравнивается с желаемым символом для подсчета, в данном случае "a" ("'a' =="). Эта операция вернет вектор значений True и False «c (True, False, True, ....)», которые будут True, когда значение в векторе совпадает с желаемым символом для подсчета.

4: Общее количество появлений символа «а» в строке рассчитывается как сумма всех «истинных» значений в векторе «сумма (....)».

5: Затем применяется функция «unlist», чтобы распаковать результат функции «lapply» и присвоить его новому столбцу в фрейме данных («q.data $ number.of.a <-unlist (.... ")

q.data$number.of.a<-unlist(lapply(q.data[,2],function(x){sum('a' == strsplit(as.character(x), '')[[1]])}))

>q.data

#  number     string     number.of.a
#1   greatgreat         2
#2      magic           1
#3      not             0
бакнкн
источник
1
Ваш ответ будет намного лучше с объяснением того, что он делает, особенно для новых пользователей, поскольку это не совсем простое выражение.
Khaine775,
Спасибо @ Khaine775 за ваш комментарий и мои извинения за отсутствие описания сообщения. Я отредактировал сообщение и добавил несколько комментариев для лучшего описания того, как это работает.
bacnqn
-2
s <- "aababacababaaathhhhhslsls jsjsjjsaa ghhaalll"
p <- "a"
s2 <- gsub(p,"",s)
numOcc <- nchar(s) - nchar(s2)

Может быть, не самый эффективный, но решит мою задачу.

Амарджит
источник