Wie berechnet man die Häufigkeit des Auftretens eines bestimmten Zeichens in jeder Zeile einer Zeichenfolgenspalte?

102

Ich habe einen data.frame, in dem bestimmte Variablen eine Textzeichenfolge enthalten. Ich möchte die Anzahl der Vorkommen eines bestimmten Zeichens in jeder einzelnen Zeichenfolge zählen.

Beispiel:

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

Ich möchte eine neue Spalte für q.data mit der Häufigkeit des Auftretens von "a" in der Zeichenfolge (dh c (2,1,0)) erstellen.

Der einzige verschlungene Ansatz, den ich geschafft habe, ist:

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
quelle

Antworten:

140

Das stringr-Paket bietet die str_countFunktion, die genau das zu tun scheint, woran Sie interessiert sind

# 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
quelle
1
Ihre war viel schneller, obwohl sie ein as.character () um das Hauptargument benötigt, um mit dem gestellten Problem erfolgreich zu sein.
IRTFM
1
@DWin - Das stimmt, aber ich habe dieses Problem vermieden, indem ich es stringsAsFactors = FALSEbeim Definieren des Datenrahmens hinzugefügt habe .
Dason
Entschuldigung, ich war unklar. Ich antwortete tatsächlich auf Tim Riffe und sagte ihm, dass seine Funktion einen Fehler mit dem gestellten Problem verursachte. Er hat vielleicht Ihre Neudefinition des Problems verwendet, aber er hat es nicht gesagt.
IRTFM
Ja, ich habe es auch stringsAsFactors=TRUEauf meinem Comp getan, aber das nicht erwähnt
Tim Riffe
Die Suche nach einer Zeichenfolge in einem Faktor funktioniert, dh str_count (d $ factor_column, 'A'), aber nicht umgekehrt
Nitro
65

Wenn Sie die Basis R nicht verlassen möchten, ist hier eine ziemlich prägnante und ausdrucksstarke Möglichkeit:

x <- q.data$string
lengths(regmatches(x, gregexpr("a", x)))
# [1] 2 1 0
Josh O'Brien
quelle
2
OK - vielleicht fühlt sich das nur dann ausdrucksstark an, wenn Sie das regmatchesund gregexprzusammen ein paar Mal verwendet haben, aber diese Kombination ist stark genug, dass ich dachte, sie hätte einen Stecker verdient.
Josh O'Brien
regmatchesist relativ neu. Es wurde in 2.14 eingeführt.
Dason
Ich glaube nicht, dass du das Regmatches-Bit brauchst. Die Funktion gregexpr gibt eine Liste mit den Indizes der übereinstimmenden Vorkommen für jedes Element von x zurück.
Savagent
@savagent - Würde es Ihnen etwas ausmachen, den Code zu teilen, mit dem Sie die Anzahl der Übereinstimmungen in jeder Zeichenfolge berechnen würden?
Josh O'Brien
1
Entschuldigung, ich habe die -1 vergessen. Es funktioniert nur, wenn jede Zeile mindestens eine Übereinstimmung hat, sapply (gregexpr ("g", q.data $ string), Länge).
Savagent
17
nchar(as.character(q.data$string)) -nchar( gsub("a", "", q.data$string))
[1] 2 1 0

Beachten Sie, dass ich die Faktorvariable zum Zeichen zwinge, bevor ich zu nchar übergebe. Die Regex-Funktionen scheinen dies intern zu tun.

Hier sind die Benchmark-Ergebnisse (mit einer auf 3000 Zeilen vergrößerten Größe des Tests)

 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
quelle
2
Dies ist die schnellste Lösung in den Antworten, wird jedoch auf Ihrem Benchmark um ~ 30% schneller gemacht, indem die Option fixed=TRUEan übergeben wird gsub. Es gibt auch Fälle, in denen fixed=TRUEdies erforderlich wäre (dh wenn das zu zählende Zeichen als Regex-Behauptung interpretiert werden könnte, wie z. B. .).
C8H10N4O2
7
sum(charToRaw("abc.d.aa") == charToRaw('.'))

ist eine gute Option.

Zhang Tao
quelle
5

Das stringiPaket bietet die Funktionen stri_countund stri_count_fixeddie sind sehr schnell.

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

Benchmark

Verglichen mit dem schnellsten Ansatz aus der Antwort von @ 42- und der entsprechenden Funktion aus dem stringrPaket für einen Vektor mit 30.000 Elementen.

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)

Daten

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

Geben Sie hier die Bildbeschreibung ein

Markus
quelle
2

Ich bin sicher, jemand kann es besser machen, aber das funktioniert:

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

oder in einer Funktion:

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
quelle
Ich scheine einen Fehler mit dem ersten zu bekommen ... und dem zweiten ... (versuchte all dies zu vergleichen.)
IRTFM
1

Sie können einfach die Zeichenfolgenteilung verwenden

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

Das gibt Ihnen 1, 3, 1, 0. Sie können auch die Zeichenfolgenteilung mit regulären Ausdrücken und ganzen Wörtern verwenden.

Benbob
quelle
0

Der einfachste und sauberste Weg ist meiner Meinung nach:

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
quelle
Wie geht das? Für mich lengths(gregexpr('a', q.data$string))kehrt 2 1 1nicht zurück 2 1 0.
Finn Årup Nielsen
0

Eine weitere base ROption könnte sein:

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

[1] 2 1 0
tmfmnk
quelle
-1

Der nächste Ausdruck erledigt die Aufgabe und funktioniert auch für Symbole, nicht nur für Buchstaben.

Der Ausdruck funktioniert wie folgt:

1: Es verwendet lapply für die Spalten des Datenrahmens q.data, um über die Zeilen der Spalte 2 zu iterieren ("lapply (q.data [, 2],"),

2: Für jede Zeile der Spalte 2 wird eine Funktion "function (x) {sum ('a' == strsplit (as.character (x), '') [[1]])}" angewendet. Die Funktion nimmt jeden Zeilenwert von Spalte 2 (x), konvertiert ihn in ein Zeichen (falls es sich beispielsweise um einen Faktor handelt) und teilt die Zeichenfolge für jedes Zeichen auf ("strsplit (as.character (x),") ') "). Als Ergebnis haben wir einen Vektor mit jedem Zeichen des Zeichenfolgenwerts für jede Zeile der Spalte 2.

3: Jeder Vektorwert des Vektors wird mit dem gewünschten zu zählenden Zeichen verglichen, in diesem Fall "a" ("'a' =="). Diese Operation gibt einen Vektor mit True- und False-Werten "c (True, False, True, ....)" zurück, der True ist, wenn der Wert im Vektor mit dem gewünschten zu zählenden Zeichen übereinstimmt.

4: Die Summe, mit der das Zeichen 'a' in der Zeile erscheint, wird als Summe aller 'True'-Werte im Vektor "sum (....)" berechnet.

5: Dann wird die Funktion "unlist" angewendet, um das Ergebnis der Funktion "lapply" zu entpacken und es einer neuen Spalte im Datenrahmen zuzuweisen ("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
quelle
1
Ihre Antwort wäre viel besser mit einer Erklärung dessen, was es tut, insbesondere für neue Benutzer, da es nicht gerade ein einfacher Ausdruck ist.
Khaine775
Vielen Dank an @ Khaine775 für Ihren Kommentar und ich entschuldige mich für die fehlende Beschreibung des Beitrags. Ich habe den Beitrag bearbeitet und einige Kommentare hinzugefügt, um besser zu beschreiben, wie er funktioniert.
Bacnqn
-2
s <- "aababacababaaathhhhhslsls jsjsjjsaa ghhaalll"
p <- "a"
s2 <- gsub(p,"",s)
numOcc <- nchar(s) - nchar(s2)

Vielleicht nicht die effizienteste, aber ich löse meinen Zweck.

Amarjeet
quelle