如何计算一列字符串的每一行中给定字符的出现次数?


103

我有一个data.frame,其中某些变量包含文本字符串。我希望计算每个字符串中给定字符出现的次数。

例:

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

我希望为q.data创建一个新列,其中出现字符串的数量为“ a”(即c(2,1,0))。

我管理的唯一复杂方法是​​:

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

Answers:


141

stringr包提供的str_count功能似乎可以满足您的需求

# 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
您的速度要快得多,尽管它确实需要围绕主参数使用as.character()才能成功解决所提出的问题。
IRTFM 2012年

1
@DWin-是的,但是我通过stringsAsFactors = FALSE在定义数据框时添加来避免了这个问题。
戴森2012年

对不起,我不清楚。我实际上是在回应蒂姆·里菲(tim riffe),并告诉他,他的职务对所提出的问题提出了错误。他可能使用过您对问题的重新定义,但他没有这么说。
IRTFM 2012年

是的,我也照做了,stringsAsFactors=TRUE但是我没有提到这一点
tim riffe 2012年

搜索一个因数中的字符串将起作用,即str_count(d $ factor_column,'A'),但反之则不然
Nitro

65

如果您不想离开基数R,那么这是一种相当简洁和具有表现力的可能性:

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

2
好的-也许只有当您使用和几次后,它才会感觉很富有表现力,但是该组合足够强大,我以为它值得使用。regmatchesgregexpr
乔什·奥布莱恩

regmatches比较新。它是在2.14中引入的。
戴森2012年

我认为您不需要regmatches位。gregexpr函数返回一个列表,其中包含x的每个元素的匹配出现的索引。
2014年

@savagent-您介意共享用于计算每个字符串中的匹配数的代码吗?
2014年

1
抱歉,我忘记了-1。仅当每一行至少有一个匹配项sapply(gregexpr(“ g”,q.data $ string),length)时,它才有效。
2014年

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

注意,在传递给nchar之前,我将factor变量强制转换为character。regex函数似乎是在内部完成的。

这是基准测试结果(测试的规模扩大到3000行)

 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
这是答案中最快的解决方案,但通过将可选参数传递给,可以fixed=TRUE使基准测试速度提高约30%gsub。在某些情况下,fixed=TRUE这也是必需的(例如,当您想要计数的字符可以解释为regex断言时,例如.)。
C8H10N4O2 '18年

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

是个不错的选择。


5

stringi软件包提供的功能stri_countstri_count_fixed它的速度非常快。

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

基准

@ 42-答案中最快的方法以及包装中具有30.000个元素的向量的等效函数相比stringr

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)

数据

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

在此处输入图片说明



2

我敢肯定有人可以做得更好,但是这样做有效:

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

或在函数中:

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

我似乎得到一个错误与第一个...,第二个......(试图基准所有这些。)
IRTFM

1

您可以使用字符串除法

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

这将为您提供1、3、1、0。您还可以对正则表达式和整个单词使用字符串分割。




0

另一个base R选择可能是:

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

[1] 2 1 0

-1

下一个表达式不仅可以完成工作,而且还可以用于符号,而不仅限于字母。

该表达式的工作方式如下:

1:它在数据框q.data的列上使用lapply来遍历列2的行(“ lapply(q.data [,2],”),

2:对列2的每一行应用函数“ function(x){sum('a'== strsplit(as.character(x),'')[[1]])}}”)。该函数获取第2列(x)的每个行值,转换为字符(例如,如果是一个因素),并且对每个字符进行字符串分割(“ strsplit(as.character(x),' ')“)。结果,我们有了一个向量,其中列2的每一行都有字符串值的每个字符。

3:将向量的每个向量值与要计数的所需字符进行比较,在这种情况下为“ a”(“'a'==“)。此操作将返回一个True和False值“ c(True,False,True,....)”的向量,当向量中的值与要计数的所需字符匹配时为True。

4:字符“ a”在行中出现的总时间计算为向量“ sum(....)”中所有“ True”值的总和。

5:然后应用“ unlist”函数解压缩“ lapply”函数的结果并将其分配给数据帧中的新列(“ 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
扩展其功能,您的答案会好得多,尤其是对新用户而言,因为它并不是一个简单的表达。
Khaine775

感谢@ Khaine775的评论和对帖子缺乏描述的歉意。我已经编辑了帖子,并添加了一些评论,以更好地描述它的工作方式。
bacnqn

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

可能不是高效的,但可以解决我的目的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.