¿Cómo calcular el número de ocurrencias de un carácter dado en cada fila de una columna de cadenas?

103

Tengo un data.frame en el que ciertas variables contienen una cadena de texto. Deseo contar el número de ocurrencias de un carácter dado en cada cadena individual.

Ejemplo:

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

Deseo crear una nueva columna para q.data con el número de apariciones de "a" en la cadena (es decir, c (2,1,0)).

El único enfoque complicado que he logrado es:

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
fuente

Respuestas:

141

El paquete stringr proporciona la str_countfunción que parece hacer lo que le interesa

# 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
fuente
1
El suyo fue mucho más rápido, aunque necesita un as.character () alrededor del argumento principal para tener éxito con el problema planteado.
IRTFM
1
@DWin: eso es cierto, pero evité ese problema al agregar stringsAsFactors = FALSEal definir el marco de datos.
Dason
Lo siento, no estaba claro. De hecho, estaba respondiendo a tim riffe y diciéndole que su función arrojaba un error con el problema planteado. Puede que haya utilizado su redefinición del problema, pero no lo dijo.
IRTFM
Sí, yo también lo hice, stringsAsFactors=TRUEen mi comp, pero no mencionó este
Tim RIFFE
La búsqueda de una cadena en un factor funcionará, es decir, str_count (d $ factor_column, 'A') pero no viceversa
Nitro
65

Si no desea dejar la base R, aquí hay una posibilidad bastante sucinta y expresiva:

x <- q.data$string
lengths(regmatches(x, gregexpr("a", x)))
# [1] 2 1 0
Josh O'Brien
fuente
2
De acuerdo, tal vez solo se sienta expresivo una vez que haya usado regmatchesy gregexprjuntos unas cuantas veces, pero ese combo es lo suficientemente poderoso como para pensar que merecía un complemento.
Josh O'Brien
regmatcheses relativamente nuevo. Fue introducido en 2.14.
Dason
No creo que necesites el bit de regmatches. La función gregexpr devuelve una lista con los índices de apariciones coincidentes para cada elemento de x.
salvaje
@savagent: ¿le importaría compartir el código que usaría para calcular el número de coincidencias en cada cadena?
Josh O'Brien
1
Lo siento, me olvidé del -1. Solo funciona si cada línea tiene al menos una coincidencia, sapply (gregexpr ("g", q.data $ string), length).
salvaje
17
nchar(as.character(q.data$string)) -nchar( gsub("a", "", q.data$string))
[1] 2 1 0

Observe que forzo la variable factor a carácter, antes de pasar a nchar. Las funciones de expresiones regulares parecen hacer eso internamente.

Estos son los resultados de referencia (con un tamaño ampliado de la prueba a 3000 filas)

 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
fuente
2
Esta es la solución más rápida en las respuestas, pero se hace ~ 30% más rápido en su punto de referencia al pasar el opcional fixed=TRUEa gsub. También hay casos en los fixed=TRUEque sería necesario (es decir, cuando el carácter que desea contar podría interpretarse como una aserción de expresiones regulares como .).
C8H10N4O2
7
sum(charToRaw("abc.d.aa") == charToRaw('.'))

es una buena opción.

Zhang Tao
fuente
5

El stringipaquete proporciona las funciones stri_county stri_count_fixedque son muy rápidas.

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

punto de referencia

Comparado con el enfoque más rápido de la respuesta de @ 42- y con la función equivalente del stringrpaquete para un vector con 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)

datos

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

ingrese la descripción de la imagen aquí

Markus
fuente
2

Estoy seguro de que alguien puede hacerlo mejor, pero esto funciona:

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

o en una función:

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
fuente
Parece que obtengo un error con el primero ... y el segundo ... (estaba tratando de comparar todos estos)
IRTFM
1

Podrías usar la división de cuerdas

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

Lo que le dará 1, 3, 1, 0. También puede usar la división de cadenas con expresiones regulares y palabras completas.

Benbob
fuente
0

La forma más fácil y limpia en mi humilde opinión es:

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
fuente
¿Cómo se hace eso? Para mí, lengths(gregexpr('a', q.data$string))devuelve 2 1 1, no 2 1 0.
Finn Årup Nielsen
0

Otra base Ropción más podría ser:

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

[1] 2 1 0
tmfmnk
fuente
-1

La siguiente expresión hace el trabajo y también funciona para símbolos, no solo letras.

La expresión funciona de la siguiente manera:

1: utiliza lapply en las columnas del marco de datos q.data para iterar sobre las filas de la columna 2 ("lapply (q.data [, 2],"),

2: aplica a cada fila de la columna 2 una función "función (x) {suma ('a' == strsplit (as.character (x), '') [[1]])}". La función toma cada valor de fila de la columna 2 (x), lo convierte en carácter (en caso de que sea un factor, por ejemplo) y divide la cadena en cada carácter ("strsplit (as.character (x), ' ') "). Como resultado, tenemos un vector con cada carácter del valor de la cadena para cada fila de la columna 2.

3: Cada valor vectorial del vector se compara con el carácter que se desea contar, en este caso "a" ("'a' =="). Esta operación devolverá un vector de valores Verdadero y Falso "c (Verdadero, Falso, Verdadero, ....)", siendo Verdadero cuando el valor en el vector coincide con el carácter deseado para ser contado.

4: El total de veces que aparece el carácter 'a' en la fila se calcula como la suma de todos los valores 'Verdaderos' en el vector "suma (....)".

5: Luego se aplica la función "unlist" para descomprimir el resultado de la función "lapply" y asignarlo a una nueva columna en el 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
fuente
1
Su respuesta sería mucho mejor con una explicación de lo que hace, especialmente para los nuevos usuarios, ya que no es exactamente una expresión simple .
Khaine775
Gracias @ Khaine775 por tu comentario y mis disculpas por la falta de descripción de la publicación. Edité la publicación y agregué algunos comentarios para una mejor descripción de cómo funciona.
bacnqn
-2
s <- "aababacababaaathhhhhslsls jsjsjjsaa ghhaalll"
p <- "a"
s2 <- gsub(p,"",s)
numOcc <- nchar(s) - nchar(s2)

Puede que no sea el eficiente, pero resuelve mi propósito.

Amarjeet
fuente