Come calcolare il numero di occorrenze di un dato carattere in ogni riga di una colonna di stringhe?


103

Ho un data.frame in cui alcune variabili contengono una stringa di testo. Desidero contare il numero di occorrenze di un dato carattere in ogni singola stringa.

Esempio:

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

Desidero creare una nuova colonna per q.data con il numero di occorrenze di "a" nella stringa (es. C (2,1,0)).

L'unico approccio contorto che ho gestito è:

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

Risposte:


141

Il pacchetto stringr fornisce la str_countfunzione che sembra fare quello che ti interessa

# 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
Il tuo è stato molto più veloce sebbene abbia bisogno di un as.character () attorno all'argomento principale per avere successo con il problema posto.
IRTFM

1
@DWin - È vero, ma ho evitato questo problema aggiungendo stringsAsFactors = FALSEdurante la definizione del frame di dati.
Dason

Scusa non sono stato chiaro. In realtà stavo rispondendo a tim riffe e dicendogli che la sua funzione ha generato un errore con il problema posto. Potrebbe aver usato la tua ridefinizione del problema, ma non l'ha detto.
IRTFM

sì, l'ho fatto anche io, stringsAsFactors=TRUEsul mio comp, ma non ho menzionato questo
tim riffe

La ricerca di una stringa in un fattore funzionerà, ad esempio str_count (d $ factor_column, 'A') ma non viceversa
Nitro

65

Se non vuoi lasciare la base R, ecco una possibilità abbastanza succinta ed espressiva:

x <- q.data$string
lengths(regmatches(x, gregexpr("a", x)))
# [1] 2 1 0

2
OK, forse ti sembrerà espressivo solo dopo aver usato regmatchese gregexprinsieme alcune volte, ma quella combinazione è abbastanza potente da pensare che meritasse una spina.
Josh O'Brien

regmatchesè relativamente nuovo. È stato introdotto nella 2.14.
Dason

Non penso che tu abbia bisogno del bit di regmatch. La funzione gregexpr restituisce un elenco con gli indici delle occorrenze corrispondenti per ogni elemento di x.
Savagent

@savagent - Ti dispiacerebbe condividere il codice che useresti per calcolare il numero di corrispondenze in ogni stringa?
Josh O'Brien

1
Scusa, mi ero dimenticato del -1. Funziona solo se ogni riga ha almeno una corrispondenza, sapply (gregexpr ("g", q.data $ string), length).
Savagent

17
nchar(as.character(q.data$string)) -nchar( gsub("a", "", q.data$string))
[1] 2 1 0

Si noti che costringo la variabile factor a carattere, prima di passare a nchar. Le funzioni regex sembrano farlo internamente.

Ecco i risultati del benchmark (con una dimensione del test aumentata a 3000 righe)

 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
Questa è la soluzione più veloce nelle risposte, ma è resa più veloce del 30% circa nel benchmark passando l'opzionale fixed=TRUEa gsub. Ci sono anche casi in cui fixed=TRUEsarebbe richiesto (cioè, quando il carattere che si desidera contare potrebbe essere interpretato come un'asserzione regolare come .).
C8H10N4O2

7
sum(charToRaw("abc.d.aa") == charToRaw('.'))

è una buona opzione.


5

Il stringipacchetto fornisce le funzioni stri_counte stri_count_fixedche sono molto veloci.

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

prova delle prestazioni

Rispetto all'approccio più veloce dalla risposta di @ 42 e alla funzione equivalente del stringrpacchetto per un vettore con 30.000 elementi.

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)

dati

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

inserisci qui la descrizione dell'immagine



2

Sono sicuro che qualcuno può fare di meglio, ma funziona:

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

o in una funzione:

countLetter <- function(charvec, letter){
  sapply(charvec, function(x, letter){
    sum(unlist(strsplit(x, split = "")) == letter)
  }, letter = letter)
}
countLetter(as.character(q.data$string),"a")

Mi sembra di avere un errore con il primo ... e il secondo ... (stavo cercando di confrontare tutti questi.)
IRTFM

1

Potresti semplicemente usare la divisione delle stringhe

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

Il che ti darà 1, 3, 1, 0. Puoi anche usare la divisione delle stringhe con espressioni regolari e parole intere.


0

Il modo più semplice e pulito 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`

Come si fa? Per me, lengths(gregexpr('a', q.data$string))ritorna 2 1 1, no 2 1 0.
Finn Årup Nielsen


0

Un'altra base Ropzione potrebbe essere:

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

[1] 2 1 0

-1

L'espressione successiva fa il lavoro e funziona anche per i simboli, non solo per le lettere.

L'espressione funziona come segue:

1: utilizza lapply sulle colonne del dataframe q.data per scorrere le righe della colonna 2 ("lapply (q.data [, 2],"),

2: applica ad ogni riga della colonna 2 una funzione "function (x) {sum ('a' == strsplit (as.character (x), '') [[1]])}". La funzione prende il valore di ogni riga della colonna 2 (x), la converte in carattere (nel caso si tratti di un fattore, ad esempio), ed esegue la divisione della stringa su ogni carattere ("strsplit (as.character (x), ' ') "). Di conseguenza abbiamo un vettore con ogni carattere del valore stringa per ogni riga della colonna 2.

3: Ogni valore del vettore del vettore viene confrontato con il carattere desiderato da contare, in questo caso "a" ("'a' =="). Questa operazione restituirà un vettore di valori True e False "c (True, False, True, ....)", che sono True quando il valore nel vettore corrisponde al carattere desiderato da contare.

4: Il numero totale di volte in cui il carattere "a" appare nella riga è calcolato come la somma di tutti i valori "True" nel vettore "sum (....)".

5: Quindi viene applicata la funzione "unlist" per decomprimere il risultato della funzione "lapply" e assegnarlo a una nuova colonna nel 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
La tua risposta sarebbe molto migliore con una spiegazione di ciò che fa, specialmente per i nuovi utenti in quanto non è esattamente un'espressione semplice .
Khaine775

Grazie @ Khaine775 per il tuo commento e le mie scuse per la mancanza di descrizione del post. Ho modificato il post e aggiunto alcuni commenti per una migliore descrizione di come funziona.
bacnqn

-2
s <- "aababacababaaathhhhhslsls jsjsjjsaa ghhaalll"
p <- "a"
s2 <- gsub(p,"",s)
numOcc <- nchar(s) - nchar(s2)

Potrebbe non essere quello efficiente ma risolvere il mio scopo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.