¿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

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

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

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

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.


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í



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")

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.


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`

¿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

-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

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.