Bagaimana cara menghitung jumlah kemunculan karakter tertentu di setiap baris kolom string?

103

Saya memiliki data.frame di mana variabel tertentu berisi string teks. Saya ingin menghitung jumlah kemunculan karakter tertentu di setiap string individu.

Contoh:

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

Saya ingin membuat kolom baru untuk q.data dengan jumlah kemunculan "a" dalam string (mis. C (2,1,0)).

Satu-satunya pendekatan berbelit-belit yang berhasil saya lakukan adalah:

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
sumber

Jawaban:

141

Paket stringr menyediakan str_countfungsi yang sepertinya melakukan apa yang Anda minati

# 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
sumber
1
Milik Anda jauh lebih cepat meskipun memang membutuhkan as.character () di sekitar argumen utama agar berhasil dengan masalah yang diajukan.
IRTFM
1
@DWin - Itu benar tetapi saya menghindari masalah itu dengan menambahkan stringsAsFactors = FALSEsaat menentukan bingkai data.
Dason
Maaf saya tidak jelas. Saya sebenarnya menanggapi tim riffe dan memberitahunya bahwa fungsinya membuat kesalahan dengan masalah yang ditimbulkan. Dia mungkin menggunakan definisi ulang Anda tentang masalah tetapi dia tidak mengatakannya.
IRTFM
ya, saya juga melakukannya, stringsAsFactors=TRUEdi komputer saya, tetapi tidak menyebutkan ini
tim riffe
Mencari string dalam sebuah faktor akan bekerja yaitu str_count (d $ factor_column, 'A') tetapi tidak sebaliknya
Nitro
65

Jika Anda tidak ingin meninggalkan basis R, berikut kemungkinan yang cukup singkat dan ekspresif:

x <- q.data$string
lengths(regmatches(x, gregexpr("a", x)))
# [1] 2 1 0
Josh O'Brien
sumber
2
Oke - mungkin itu hanya akan terasa ekspresif setelah Anda menggunakan regmatchesdan gregexprbersama beberapa kali, tetapi kombo itu cukup kuat sehingga saya pikir itu pantas dicolok.
Josh O'Brien
regmatchesrelatif baru. Itu diperkenalkan pada 2.14.
Dason
Saya tidak berpikir Anda membutuhkan sedikit regmatches. Fungsi gregexpr mengembalikan daftar dengan indeks kemunculan yang cocok untuk setiap elemen x.
biadab
@savagent - Maukah Anda membagikan kode yang akan Anda gunakan untuk menghitung jumlah kecocokan di setiap string?
Josh O'Brien
1
Maaf, saya lupa tentang -1. Ini hanya berfungsi jika setiap baris memiliki setidaknya satu kecocokan, sapply (gregexpr ("g", q.data $ string), length).
biadab
18
nchar(as.character(q.data$string)) -nchar( gsub("a", "", q.data$string))
[1] 2 1 0

Perhatikan bahwa saya memaksa variabel faktor menjadi karakter, sebelum meneruskan ke nchar. Fungsi regex tampaknya melakukannya secara internal.

Berikut hasil benchmark (dengan ukuran tes yang ditingkatkan menjadi 3000 baris)

 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
sumber
3
Ini adalah solusi tercepat dalam jawaban tetapi dibuat ~ 30% lebih cepat pada tolok ukur Anda dengan meneruskan opsional fixed=TRUEke gsub. Ada juga kasus di mana fixed=TRUEakan diperlukan (yaitu, ketika karakter yang ingin Anda hitung dapat diartikan sebagai pernyataan regex seperti .).
C8H10N4O2
7
sum(charToRaw("abc.d.aa") == charToRaw('.'))

adalah pilihan yang bagus.

Zhang Tao
sumber
5

The stringipaket menyediakan fungsi stri_countdan stri_count_fixedyang sangat cepat.

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

patokan

Dibandingkan dengan pendekatan tercepat dari jawaban @ 42- dan dengan fungsi yang setara dari stringrpaket untuk vektor dengan 30.000 elemen.

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)

data

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

masukkan deskripsi gambar di sini

markus
sumber
2

Saya yakin seseorang dapat melakukan lebih baik, tetapi ini berhasil:

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

atau dalam sebuah fungsi:

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
sumber
Sepertinya saya mendapatkan kesalahan dengan yang pertama ... dan yang kedua ... (mencoba untuk membandingkan semua ini.)
IRTFM
1

Anda bisa menggunakan pembagian string

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

Yang akan memberi Anda 1, 3, 1, 0. Anda juga dapat menggunakan pembagian string dengan ekspresi reguler dan seluruh kata.

Benbob
sumber
0

Cara IMHO termudah dan terbersih adalah:

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
sumber
Bagaimana caranya? Bagi saya, lengths(gregexpr('a', q.data$string))pengembalian 2 1 1, bukan 2 1 0.
Finn Årup Nielsen
0

Namun base Ropsi lain bisa jadi:

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

[1] 2 1 0
tmfmnk
sumber
-1

Ekspresi berikutnya melakukan pekerjaan itu dan juga berfungsi untuk simbol, tidak hanya huruf.

Ekspresi bekerja sebagai berikut:

1: menggunakan lapply pada kolom dataframe q.data untuk melakukan iterasi pada baris kolom 2 ("lapply (q.data [, 2],"),

2: itu berlaku untuk setiap baris kolom 2 fungsi "fungsi (x) {sum ('a' == strsplit (as.character (x), '') [[1]])}". Fungsi tersebut mengambil setiap nilai baris kolom 2 (x), mengonversi ke karakter (dalam kasus ini adalah faktor misalnya), dan itu melakukan pemisahan string pada setiap karakter ("strsplit (as.character (x), ' ') "). Hasilnya kita memiliki vektor dengan setiap karakter dari nilai string untuk setiap baris kolom 2.

3: Setiap nilai vektor dari vektor dibandingkan dengan karakter yang diinginkan untuk dihitung, dalam hal ini "a" ("'a' =="). Operasi ini akan mengembalikan vektor nilai True dan False "c (True, False, True, ....)", menjadi True ketika nilai dalam vektor cocok dengan karakter yang diinginkan untuk dihitung.

4: Total kali karakter 'a' muncul di baris dihitung sebagai jumlah dari semua nilai 'True' dalam vektor "sum (....)".

5: Kemudian diterapkan fungsi "unlist" untuk membongkar hasil dari fungsi "lapply" dan menetapkannya ke kolom baru di 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.dll
sumber
1
Jawaban Anda akan jauh lebih baik dengan eksaplanasi dari apa yang dilakukannya, terutama untuk pengguna baru karena ini bukan ekspresi yang sederhana .
Khaine775
Terima kasih @ Khaine775 atas komentar Anda dan permintaan maaf saya atas kurangnya deskripsi posting. Saya telah mengedit posting dan menambahkan beberapa komentar untuk deskripsi yang lebih baik tentang cara kerjanya.
bacnqn
-2
s <- "aababacababaaathhhhhslsls jsjsjjsaa ghhaalll"
p <- "a"
s2 <- gsub(p,"",s)
numOcc <- nchar(s) - nchar(s2)

Mungkin bukan yang efisien tetapi menyelesaikan tujuan saya.

Amarjeet
sumber