Jak obliczyć liczbę wystąpień danego znaku w każdym wierszu kolumny ciągów?

105

Mam data.frame, w której niektóre zmienne zawierają ciąg tekstowy. Chciałbym policzyć liczbę wystąpień danego znaku w każdym pojedynczym ciągu.

Przykład:

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

Chciałbym utworzyć nową kolumnę dla q.data z liczbą wystąpień „a” w ciągu znaków (tj. C (2,1,0)).

Jedyne zawiłe podejście, jakie udało mi się zastosować, to:

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
źródło

Odpowiedzi:

143

Pakiet stringr udostępnia str_countfunkcję, która wydaje się robić to, co Cię interesuje

# 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
źródło
1
Twój był znacznie szybszy, chociaż potrzebuje znaku as.character () wokół głównego argumentu, aby odnieść sukces z postawionym problemem.
IRTFM,
1
@DWin - To prawda, ale uniknąłem tego problemu, dodając stringsAsFactors = FALSEpodczas definiowania ramki danych.
Dason
Przepraszam, że nie było jasne. Właściwie odpowiadałem na tim riffe'a i mówiłem mu, że jego funkcja spowodowała błąd w przedstawionym problemie. Mógł skorzystać z twojej redefinicji problemu, ale tego nie powiedział.
IRTFM
tak, też zrobiłem to stringsAsFactors=TRUEna mojej
kompilacji
Wyszukanie ciągu znaków we współczynniku zadziała, tj. Str_count (d $ factor_column, 'A'), ale nie odwrotnie
Nitro
65

Jeśli nie chcesz opuszczać bazy R, oto dość zwięzła i wyrazista możliwość:

x <- q.data$string
lengths(regmatches(x, gregexpr("a", x)))
# [1] 2 1 0
Josh O'Brien
źródło
2
OK - może wyda się to wyraziste tylko wtedy, gdy użyjesz regmatchesi gregexprrazem kilka razy, ale to combo jest na tyle potężne, że pomyślałem, że zasługuje na wtyczkę.
Josh O'Brien
regmatchesjest stosunkowo nowy. Został wprowadzony w 2.14.
Dason
Myślę, że nie potrzebujesz trochę regmatches. Funkcja gregexpr zwraca listę z indeksami dopasowanych wystąpień dla każdego elementu x.
savagent
@savagent - Czy możesz udostępnić kod, którego użyłbyś do obliczenia liczby dopasowań w każdym ciągu?
Josh O'Brien
1
Przepraszam, zapomniałem o -1. Działa tylko wtedy, gdy każda linia ma co najmniej jedno dopasowanie, sapply (gregexpr ("g", q.data $ string), length).
savagent
18
nchar(as.character(q.data$string)) -nchar( gsub("a", "", q.data$string))
[1] 2 1 0

Zauważ, że przed przejściem do nchar przekształcam zmienną czynnika w znak. Wydaje się, że funkcje regex robią to wewnętrznie.

Oto wyniki testów porównawczych (ze zwiększonym rozmiarem testu do 3000 wierszy)

 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
źródło
3
Jest to najszybsze rozwiązanie spośród odpowiedzi, ale jest o około 30% szybsze w twoim teście porównawczym, przekazując opcję fixed=TRUEdo gsub. Istnieją również przypadki, w których fixed=TRUEbyłoby to wymagane (tj. Gdy znak, który chcesz policzyć, mógłby zostać zinterpretowany jako potwierdzenie wyrażenia regularnego, na przykład .).
C8H10N4O2
7

Kolejna dobra opcja, używając charToRaw :

sum(charToRaw("abc.d.aa") == charToRaw('.'))
Zhang Tao
źródło
6

Plik stringiPakiet zawiera funkcje stri_counti stri_count_fixedktóre są bardzo szybko.

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

reper

W porównaniu z najszybszym podejściem z odpowiedzi @ 42- i równoważną funkcją zstringr pakietu dla wektora zawierającego 30 000 elementów.

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)

dane

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

wprowadź opis obrazu tutaj

markus
źródło
2

Jestem pewien, że ktoś może zrobić lepiej, ale to działa:

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

lub w funkcji:

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
źródło
Wydaje mi się, że pojawia się błąd przy pierwszym ... a drugim ... (próbowałem
porównać je
1

Możesz po prostu użyć dzielenia ciągów

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

Co da ci 1, 3, 1, 0. Możesz także użyć dzielenia na ciąg z wyrażeniami regularnymi i całymi słowami.

Benbob
źródło
0

Najłatwiejszym i najczystszym sposobem IMHO jest:

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
źródło
Jak to się robi? Dla mnie,lengths(gregexpr('a', q.data$string)) wraca 2 1 1, nie 2 1 0.
Finn Årup Nielsen
0

Jeszcze inną base Ropcją mogłoby być:

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

[1] 2 1 0
tmfmnk
źródło
-1

Następne wyrażenie spełnia swoje zadanie i działa również dla symboli, nie tylko liter.

Wyrażenie działa w następujący sposób:

1: używa lapply na kolumnach ramki danych q.data do iteracji po wierszach kolumny 2 ("lapply (q.data [, 2],"),

2: stosuje do każdego wiersza kolumny 2 funkcję "function (x) {sum ('a' == strsplit (as.character (x), '') [[1]])}". Funkcja pobiera każdą wartość wiersza z kolumny 2 (x), konwertuje ją na znak (na przykład w przypadku, gdy jest to czynnik) i dokonuje podziału ciągu na każdy znak ("strsplit (as.character (x), ' ') ”). W rezultacie otrzymujemy wektor z każdym znakiem wartości ciągu dla każdego wiersza kolumny 2.

3: Każda wartość wektora wektora jest porównywana z żądanym znakiem do zliczenia, w tym przypadku "a" ("'a' =="). Ta operacja zwróci wektor wartości True i False „c (True, False, True, ....)”, przy czym wartość True w wektorze odpowiada żądanemu znakowi do zliczenia.

4: Suma razy, gdy znak „a” pojawia się w wierszu jest obliczana jako suma wszystkich wartości „Prawda” w wektorze „suma (....)”.

5: Następnie jest stosowana funkcja „unlist”, aby rozpakować wynik funkcji „lapply” i przypisać go do nowej kolumny w ramce danych („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
źródło
1
Twoja odpowiedź byłaby o wiele lepsza z wyjaśnieniem tego, co robi, zwłaszcza dla nowych użytkowników, ponieważ nie jest to do końca proste wyrażenie.
Khaine775
Dzięki @ Khaine775 za komentarz i przepraszam za brak opisu postu. Zredagowałem post i dodałem kilka komentarzy, aby lepiej opisać, jak to działa.
bacnqn
-2
s <- "aababacababaaathhhhhslsls jsjsjjsaa ghhaalll"
p <- "a"
s2 <- gsub(p,"",s)
numOcc <- nchar(s) - nchar(s2)

Może nie będzie skuteczny, ale rozwiąże mój cel.

Amarjeet
źródło