Comment calculer le nombre d'occurrences d'un caractère donné dans chaque ligne d'une colonne de chaînes?

103

J'ai un data.frame dans lequel certaines variables contiennent une chaîne de texte. Je souhaite compter le nombre d'occurrences d'un caractère donné dans chaque chaîne individuelle.

Exemple:

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

Je souhaite créer une nouvelle colonne pour q.data avec le nombre d'occurrences de "a" dans la chaîne (c.-à-d. C (2,1,0)).

La seule approche alambiquée que j'ai réussie est:

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
la source

Réponses:

141

Le package stringr fournit la str_countfonction qui semble faire ce qui vous intéresse

# 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
la source
1
Le vôtre a été beaucoup plus rapide bien qu'il ait besoin d'un as.character () autour de l'argument principal pour réussir avec le problème posé.
IRTFM le
1
@DWin - C'est vrai mais j'ai évité ce problème en ajoutant stringsAsFactors = FALSElors de la définition du bloc de données.
Dason le
Désolé, je n'étais pas clair. Je répondais en fait à Tim riffe et lui disais que sa fonction avait jeté une erreur avec le problème posé. Il a peut-être utilisé votre redéfinition du problème, mais il ne l'a pas dit.
IRTFM
ouais, je l'ai aussi fait, stringsAsFactors=TRUEsur ma composition, mais je n'ai pas mentionné cela
tim riffe
La recherche d'une chaîne dans un facteur fonctionnera, c'est-à-dire str_count (d $ factor_column, 'A') mais pas l'inverse
Nitro
65

Si vous ne voulez pas quitter la base R, voici une possibilité assez succincte et expressive:

x <- q.data$string
lengths(regmatches(x, gregexpr("a", x)))
# [1] 2 1 0
Josh O'Brien
la source
2
OK - peut-être que cela ne semblera expressif qu'une fois que vous aurez utilisé le regmatcheset gregexprplusieurs fois ensemble, mais ce combo est suffisamment puissant pour que je pense qu'il méritait une prise.
Josh O'Brien
regmatchesest relativement nouveau. Il a été introduit en 2.14.
Dason
Je ne pense pas que vous ayez besoin du bit regmatches. La fonction gregexpr renvoie une liste avec les indices des occurrences correspondantes pour chaque élément de x.
savagent
@savagent - Pourriez-vous partager le code que vous utiliseriez pour calculer le nombre de correspondances dans chaque chaîne?
Josh O'Brien
1
Désolé, j'ai oublié le -1. Cela ne fonctionne que si chaque ligne a au moins une correspondance, sapply (gregexpr ("g", q.data $ string), longueur).
savagent
18
nchar(as.character(q.data$string)) -nchar( gsub("a", "", q.data$string))
[1] 2 1 0

Notez que je force la variable factor à caractère, avant de passer à nchar. Les fonctions regex semblent faire cela en interne.

Voici les résultats de référence (avec une taille du test augmentée à 3000 lignes)

 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
la source
3
C'est la solution la plus rapide dans les réponses, mais elle est rendue ~ 30% plus rapide sur votre benchmark en passant l'option optionnelle fixed=TRUEà gsub. Il y a aussi des cas où fixed=TRUEcela serait nécessaire (c'est-à-dire, lorsque le caractère que vous voulez compter pourrait être interprété comme une assertion regex telle que .).
C8H10N4O2
7
sum(charToRaw("abc.d.aa") == charToRaw('.'))

est une bonne option.

Zhang Tao
la source
5

Le stringipackage fournit les fonctions stri_countet stri_count_fixedqui sont très rapides.

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

référence

Comparé à l'approche la plus rapide de la réponse de @ 42- et à la fonction équivalente du stringrpackage pour un vecteur de 30 000 éléments.

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)

Les données

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

entrez la description de l'image ici

Markus
la source
2

Je suis sûr que quelqu'un peut faire mieux, mais cela fonctionne:

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

ou dans une fonction:

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
la source
Il semble que je reçoive une erreur avec le premier ... et le second ... (essayait de comparer tous ces éléments.)
IRTFM
1

Vous pouvez simplement utiliser la division de chaîne

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

Ce qui vous donnera 1, 3, 1, 0. Vous pouvez également utiliser la division de chaîne avec des expressions régulières et des mots entiers.

Benbob
la source
0

Le moyen le plus simple et le plus propre à mon humble avis est:

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
la source
Comment cela se fait-il? Pour moi, lengths(gregexpr('a', q.data$string))revient 2 1 1, non 2 1 0.
Finn Årup Nielsen
0

Une autre base Roption pourrait être:

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

[1] 2 1 0
tmfmnk
la source
-1

L'expression suivante fait le travail et fonctionne également pour les symboles, pas seulement les lettres.

L'expression fonctionne comme suit:

1: il utilise lapply sur les colonnes du dataframe q.data pour parcourir les lignes de la colonne 2 ("lapply (q.data [, 2],"),

2: il applique à chaque ligne de la colonne 2 une fonction "function (x) {sum ('a' == strsplit (as.character (x), '') [[1]])}". La fonction prend chaque valeur de ligne de la colonne 2 (x), la convertit en caractère (au cas où il s'agirait d'un facteur par exemple), et effectue le fractionnement de la chaîne sur chaque caractère ("strsplit (as.character (x), ' ') "). En conséquence, nous avons un vecteur avec chaque caractère de la valeur de chaîne pour chaque ligne de la colonne 2.

3: Chaque valeur vectorielle du vecteur est comparée au caractère souhaité à compter, dans ce cas "a" ("'a' =="). Cette opération renverra un vecteur de valeurs True et False "c (True, False, True, ....)", étant True lorsque la valeur dans le vecteur correspond au caractère souhaité à compter.

4: Le nombre total de fois où le caractère 'a' apparaît dans la ligne est calculé comme la somme de toutes les valeurs 'True' dans le vecteur "somme (....)".

5: Ensuite, il est appliqué la fonction "unlist" pour décompresser le résultat de la fonction "lapply" et l'assigner à une nouvelle colonne dans le 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
la source
1
Votre réponse serait bien meilleure avec une explication de ce qu'il fait, en particulier pour les nouveaux utilisateurs car ce n'est pas exactement une expression simple .
Khaine775 le
Merci @ Khaine775 pour votre commentaire et mes excuses pour le manque de description du message. J'ai édité l'article et ajouté quelques commentaires pour une meilleure description de son fonctionnement.
bacnqn le
-2
s <- "aababacababaaathhhhhslsls jsjsjjsaa ghhaalll"
p <- "a"
s2 <- gsub(p,"",s)
numOcc <- nchar(s) - nchar(s2)

Peut-être pas le plus efficace mais résoudre mon objectif.

Amarjeet
la source