Como calcular o número de ocorrências de um determinado caractere em cada linha de uma coluna de strings?

103

Eu tenho um data.frame no qual certas variáveis ​​contêm uma string de texto. Desejo contar o número de ocorrências de um determinado caractere em cada string individual.

Exemplo:

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

Desejo criar uma nova coluna para q.data com o número de ocorrências de "a" na string (ou seja, c (2,1,0)).

A única abordagem complicada que consegui é:

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
Etienne Low-Décarie
fonte

Respostas:

141

O pacote stringr fornece a str_countfunção que parece fazer o que você está interessado

# 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
Dason
fonte
1
O seu foi muito mais rápido, embora precise de um as.character () em torno do argumento principal para ter sucesso com o problema apresentado.
IRTFM
1
@DWin - Isso é verdade, mas evitei esse problema adicionando stringsAsFactors = FALSEao definir o quadro de dados.
Dason
Desculpe, eu não estava claro. Na verdade, eu estava respondendo a tim riffe e dizendo a ele que sua função gerou um erro com o problema apresentado. Ele pode ter usado sua redefinição do problema, mas não disse isso.
IRTFM
sim, eu também fiz, stringsAsFactors=TRUEno meu
computador
Pesquisar uma string em um fator funcionará, por exemplo, str_count (d $ factor_column, 'A'), mas não vice-versa
Nitro
65

Se você não quiser deixar a base R, aqui está uma possibilidade bastante sucinta e expressiva:

x <- q.data$string
lengths(regmatches(x, gregexpr("a", x)))
# [1] 2 1 0
Josh O'Brien
fonte
2
OK - talvez isso só pareça expressivo depois que você usar o regmatchese gregexprjuntos algumas vezes, mas esse combo é poderoso o suficiente para que eu achei que merecia um plug.
Josh O'Brien
regmatchesé relativamente novo. Foi introduzido em 2.14.
Dason de
Eu não acho que você precisa do bit de regmatches. A função gregexpr retorna uma lista com os índices das ocorrências correspondentes para cada elemento de x.
selvagem
@savagent - Você se importaria de compartilhar o código que usaria para calcular o número de correspondências em cada string?
Josh O'Brien
1
Desculpe, esqueci do -1. Só funciona se cada linha tiver pelo menos uma correspondência, sapply (gregexpr ("g", q.data $ string), comprimento).
selvagem
17
nchar(as.character(q.data$string)) -nchar( gsub("a", "", q.data$string))
[1] 2 1 0

Observe que eu forço a variável de fator a caractere, antes de passar para nchar. As funções regex parecem fazer isso internamente.

Aqui estão os resultados do benchmark (com um tamanho ampliado do teste para 3.000 linhas)

 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
fonte
2
Esta é a solução mais rápida nas respostas, mas se tornou cerca de 30% mais rápida em seu benchmark, passando o opcional fixed=TRUEpara gsub. Também há casos em que fixed=TRUEisso seria necessário (ou seja, quando o caractere que você deseja contar pode ser interpretado como uma afirmação de regex, como .).
C8H10N4O2
7
sum(charToRaw("abc.d.aa") == charToRaw('.'))

é uma boa opção.

Zhang Tao
fonte
5

O stringipacote oferece as funções stri_counte stri_count_fixedque são muito rápidas.

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

benchmark

Comparado com a abordagem mais rápida da resposta de @ 42-'s e com a função equivalente do stringrpacote para um vetor com 30.000 elementos.

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)

dados

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

insira a descrição da imagem aqui

Markus
fonte
2

Tenho certeza que alguém pode fazer melhor, mas isso funciona:

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

ou em uma função:

countLetter <- function(charvec, letter){
  sapply(charvec, function(x, letter){
    sum(unlist(strsplit(x, split = "")) == letter)
  }, letter = letter)
}
countLetter(as.character(q.data$string),"a")
Tim riffe
fonte
Parece que recebo um erro com o primeiro ... e o segundo ... (estava tentando comparar todos esses.)
IRTFM
1

Você poderia apenas usar a divisão de string

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

O que lhe dará 1, 3, 1, 0. Você também pode usar a divisão de string com expressões regulares e palavras inteiras.

Benbob
fonte
0

A maneira mais fácil e limpa de IMHO é:

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`
Giovanni Campagnoli
fonte
Como isso é feito? Para mim, lengths(gregexpr('a', q.data$string))retorna 2 1 1, não 2 1 0.
Finn Årup Nielsen
0

Ainda outra base Ropção poderia ser:

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

[1] 2 1 0
tmfmnk
fonte
-1

A próxima expressão faz o trabalho e também funciona para símbolos, não apenas letras.

A expressão funciona da seguinte maneira:

1: ele usa lapply nas colunas do dataframe q.data para iterar sobre as linhas da coluna 2 ("lapply (q.data [, 2],"),

2: aplica a cada linha da coluna 2 uma função "function (x) {sum ('a' == strsplit (as.character (x), '') [[1]])}". A função pega cada valor de linha da coluna 2 (x), converte em caractere (no caso de ser um fator, por exemplo) e faz a divisão da string em cada caractere ("strsplit (as.character (x), ' ') "). Como resultado, temos um vetor com cada caractere do valor da string para cada linha da coluna 2.

3: Cada valor do vetor do vetor é comparado com o caractere desejado a ser contado, neste caso "a" ("'a' =="). Esta operação retornará um vetor de valores Verdadeiro e Falso "c (Verdadeiro, Falso, Verdadeiro, ....)", sendo Verdadeiro quando o valor no vetor corresponder ao caractere desejado a ser contado.

4: O total de vezes que o caractere 'a' aparece na linha é calculado como a soma de todos os valores 'Verdadeiros' no vetor "soma (....)".

5: Em seguida, é aplicada a função "unlist" para desempacotar o resultado da função "lapply" e atribuí-lo a uma nova coluna no dataframe ("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
bacnqn
fonte
1
Sua resposta seria muito melhor com uma explicação do que ele faz, principalmente para novos usuários, pois não é exatamente uma expressão simples .
Khaine775
Obrigado @ Khaine775 pelo seu comentário e minhas desculpas pela falta de descrição do post. Eu editei a postagem e adicionei alguns comentários para uma melhor descrição de como funciona.
bacnqn
-2
s <- "aababacababaaathhhhhslsls jsjsjjsaa ghhaalll"
p <- "a"
s2 <- gsub(p,"",s)
numOcc <- nchar(s) - nchar(s2)

Pode não ser o eficiente, mas resolve meu propósito.

Amarjeet
fonte