Compter le nombre d'éléments avec les valeurs de x dans un vecteur


400

J'ai un vecteur de nombres:

numbers <- c(4,23,4,23,5,43,54,56,657,67,67,435,
         453,435,324,34,456,56,567,65,34,435)

Comment puis-je faire compter le nombre de fois où une valeur x apparaît dans le vecteur?

Réponses:


505

Vous pouvez simplement utiliser table():

> a <- table(numbers)
> a
numbers
  4   5  23  34  43  54  56  65  67 324 435 453 456 567 657 
  2   1   2   2   1   1   2   1   2   1   3   1   1   1   1 

Ensuite, vous pouvez le sous-définir:

> a[names(a)==435]
435 
  3

Ou convertissez-le en un data.frame si vous êtes plus à l'aise avec cela:

> as.data.frame(table(numbers))
   numbers Freq
1        4    2
2        5    1
3       23    2
4       34    2
...

21
N'oubliez pas les problèmes potentiels de virgule flottante, en particulier avec la table, qui contraint les nombres aux chaînes.
hadley

4
Voilà un excellent point. Ce sont tous des entiers, donc ce n'est pas un vrai problème dans cet exemple, non?
Shane

pas exactement. Les éléments de la table sont de classe classe entière (table (nombres) [1]), mais 435 est un nombre à virgule flottante. Pour en faire un entier, vous pouvez utiliser 435L.
Ian Fellows

@Ian - Je ne comprends pas pourquoi 435 est un flotteur dans cet exemple. Pouvez-vous clarifier un peu? Merci.
Heather Stark

4
Pourquoi pas en a["435"]tête a[names(a)==435]?
pomber

262

Le moyen le plus direct est sum(numbers == x).

numbers == xcrée un vecteur logique qui est VRAI à chaque emplacement où x se produit, et lors de l' suming, le vecteur logique est contraint en numérique qui convertit VRAI en 1 et FAUX en 0.

Toutefois, notez que pour les nombres à virgule flottante , il est préférable d'utiliser quelque chose comme: sum(abs(numbers - x) < 1e-6).


1
bon point sur la question de la virgule flottante. Cela me mord plus les fesses que j'aime généralement l'admettre.
JD Long

3
@Jason, bien qu'il réponde directement à la question, je suppose que les gens ont aimé la solution plus générale qui fournit la réponse pour tous xdans les données plutôt qu'une valeur connue spécifique de x. Pour être juste, c'était sur cela que portait la question initiale. Comme je l'ai dit dans ma réponse ci-dessous, "Je trouve qu'il est rare que je veuille connaître la fréquence d'une valeur et pas toutes les valeurs ..."
JBecker

62

Je ferais probablement quelque chose comme ça

length(which(numbers==x))

Mais vraiment, une meilleure façon est

table(numbers)

10
table(numbers)va faire beaucoup plus de travail que la solution la plus simple sum(numbers==x), car elle va aussi comprendre le nombre de tous les autres nombres de la liste.
Ken Williams

1
le problème avec la table est qu'il est plus difficile de l'inclure dans un calcul plus complexe, par exemple en utilisant apply () sur les dataframes
skan

38

Il y a aussi count(numbers)du plyrpackage. Beaucoup plus pratique qu'à tablemon avis.


Y a-t-il un équivalent dplyr de cela?
stevec

34

Ma solution préférée utilise rle, qui renverra une valeur (l'étiquette, xdans votre exemple) et une longueur, qui représente le nombre de fois où cette valeur est apparue en séquence.

En combinant rleavec sort, vous avez un moyen extrêmement rapide de compter le nombre de fois qu'une valeur est apparue. Cela peut être utile pour des problèmes plus complexes.

Exemple:

> numbers <- c(4,23,4,23,5,43,54,56,657,67,67,435,453,435,324,34,456,56,567,65,34,435)
> a <- rle(sort(numbers))
> a
  Run Length Encoding
    lengths: int [1:15] 2 1 2 2 1 1 2 1 2 1 ...
    values : num [1:15] 4 5 23 34 43 54 56 65 67 324 ...

Si la valeur souhaitée n'apparaît pas ou si vous devez la stocker pour plus tard, faites aun data.frame.

> b <- data.frame(number=a$values, n=a$lengths)
> b
    values n
 1       4 2
 2       5 1
 3      23 2
 4      34 2
 5      43 1
 6      54 1
 7      56 2
 8      65 1
 9      67 2
 10    324 1
 11    435 3
 12    453 1
 13    456 1
 14    567 1
 15    657 1

Je trouve qu'il est rare que je veuille connaître la fréquence d'une valeur et pas toutes les valeurs, et rle semble être le moyen le plus rapide pour obtenir le comptage et les stocker toutes.


1
L'avantage de cela, par rapport au tableau, est-il qu'il donne un résultat dans un format plus facilement utilisable? merci
Heather Stark

@HeatherStark Je dirais qu'il y a deux avantages. Le premier est certainement qu'il s'agit d'un format plus facilement utilisable que la sortie de table. La seconde est que parfois je veux compter le nombre d'éléments "dans une rangée" plutôt que dans l'ensemble de données. Par exemple, c(rep('A', 3), rep('G', 4), 'A', rep('G', 2), rep('C', 10))reviendrait values = c('A','G','A','G','C')et lengths=c(3, 4, 1, 2, 10)qui est parfois utile.
JBecker

1
à l'aide de microbenchmark, il semble que tablec'est plus rapide when the vector is long(j'ai essayé 100000) mais légèrement plus long quand il est plus court (j'ai essayé 1000)
ClementWalter

Cela va être très lent si vous avez beaucoup de chiffres.
skan

19

Il y a une fonction standard dans R pour cela

tabulate(numbers)


L'inconvénient tabulateest que vous ne pouvez pas gérer les nombres nuls et négatifs.
omar

2
Mais vous pouvez gérer zéro instance d'un nombre donné, que les autres solutions ne gèrent pas
Dodgie

Extrêmement rapide! Et comme le dit omar, cela donne un décompte nul pour les valeurs non apparentes, extrêmement utile lorsque nous voulons construire une distribution de fréquence. Zéro ou entier négatif peut être géré en ajoutant une constante avant utilisation tabulate. Note: sortsemble être nécessaire pour son utilisation correcte en général: tabulate(sort(numbers)).
pglpm

11
numbers <- c(4,23,4,23,5,43,54,56,657,67,67,435 453,435,324,34,456,56,567,65,34,435)

> length(grep(435, numbers))
[1] 3


> length(which(435 == numbers))
[1] 3


> require(plyr)
> df = count(numbers)
> df[df$x == 435, ] 
     x freq
11 435    3


> sum(435 == numbers)
[1] 3


> sum(grepl(435, numbers))
[1] 3


> sum(435 == numbers)
[1] 3


> tabulate(numbers)[435]
[1] 3


> table(numbers)['435']
435 
  3 


> length(subset(numbers, numbers=='435')) 
[1] 3

9

voici une façon rapide et sale:

x <- 23
length(subset(numbers, numbers==x))

9

Si vous souhaitez compter le nombre d'apparitions ultérieurement, vous pouvez utiliser la sapplyfonction:

index<-sapply(1:length(numbers),function(x)sum(numbers[1:x]==numbers[x]))
cbind(numbers, index)

Production:

        numbers index
 [1,]       4     1
 [2,]      23     1
 [3,]       4     2
 [4,]      23     2
 [5,]       5     1
 [6,]      43     1
 [7,]      54     1
 [8,]      56     1
 [9,]     657     1
[10,]      67     1
[11,]      67     2
[12,]     435     1
[13,]     453     1
[14,]     435     2
[15,]     324     1
[16,]      34     1
[17,]     456     1
[18,]      56     2
[19,]     567     1
[20,]      65     1
[21,]      34     2
[22,]     435     3

Est-ce que c'est plus rapide que le tableau ??
Garini


3

Une autre façon que je trouve pratique est:

numbers <- c(4,23,4,23,5,43,54,56,657,67,67,435,453,435,324,34,456,56,567,65,34,435)
(s<-summary (as.factor(numbers)))

Cela convertit l'ensemble de données en facteur, puis summary () nous donne les totaux de contrôle (décompte des valeurs uniques).

La sortie est:

4   5  23  34  43  54  56  65  67 324 435 453 456 567 657 
2   1   2   2   1   1   2   1   2   1   3   1   1   1   1 

Cela peut être stocké en tant que trame de données si vous préférez.

as.data.frame (cbind (Number = noms (s), Freq = s), stringsAsFactors = F, row.names = 1: longueur (s))

ici row.names a été utilisé pour renommer les noms de ligne. sans utiliser row.names, les noms de colonne en s sont utilisés comme noms de ligne dans la nouvelle trame de données

La sortie est:

     Number Freq
1       4    2
2       5    1
3      23    2
4      34    2
5      43    1
6      54    1
7      56    2
8      65    1
9      67    2
10    324    1
11    435    3
12    453    1
13    456    1
14    567    1
15    657    1

3

En utilisant le tableau mais sans comparer avec names:

numbers <- c(4,23,4,23,5,43,54,56,657,67,67,435)
x <- 67
numbertable <- table(numbers)
numbertable[as.character(x)]
#67 
# 2 

tableest utile lorsque vous utilisez plusieurs fois le nombre d'éléments différents. Si vous n'avez besoin que d'un seul comptage, utilisezsum(numbers == x)


2

Il existe différentes façons de compter un élément spécifique

library(plyr)
numbers =c(4,23,4,23,5,43,54,56,657,67,67,435,453,435,7,65,34,435)

print(length(which(numbers==435)))

#Sum counts number of TRUE's in a vector 
print(sum(numbers==435))
print(sum(c(TRUE, FALSE, TRUE)))

#count is present in plyr library 
#o/p of count is a DataFrame, freq is 1 of the columns of data frame
print(count(numbers[numbers==435]))
print(count(numbers[numbers==435])[['freq']])

1

Une méthode qui est relativement rapide sur les vecteurs longs et qui donne une sortie pratique est à utiliser lengths(split(numbers, numbers))(notez le S à la fin de lengths):

# Make some integer vectors of different sizes
set.seed(123)
x <- sample.int(1e3, 1e4, replace = TRUE)
xl <- sample.int(1e3, 1e6, replace = TRUE)
xxl <-sample.int(1e3, 1e7, replace = TRUE)

# Number of times each value appears in x:
a <- lengths(split(x,x))

# Number of times the value 64 appears:
a["64"]
#~ 64
#~ 15

# Occurences of the first 10 values
a[1:10]
#~ 1  2  3  4  5  6  7  8  9 10 
#~ 13 12  6 14 12  5 13 14 11 14 

La sortie est simplement un vecteur nommé.
La vitesse semble comparable à celle rleproposée par JBecker et même un peu plus rapide sur de très longs vecteurs. Voici une microbenchmark dans R 3.6.2 avec certaines des fonctions proposées:

library(microbenchmark)

f1 <- function(vec) lengths(split(vec,vec))
f2 <- function(vec) table(vec)
f3 <- function(vec) rle(sort(vec))
f4 <- function(vec) plyr::count(vec)

microbenchmark(split = f1(x),
               table = f2(x),
               rle = f3(x),
               plyr = f4(x))
#~ Unit: microseconds
#~   expr      min        lq      mean    median        uq      max neval  cld
#~  split  402.024  423.2445  492.3400  446.7695  484.3560 2970.107   100  b  
#~  table 1234.888 1290.0150 1378.8902 1333.2445 1382.2005 3203.332   100    d
#~    rle  227.685  238.3845  264.2269  245.7935  279.5435  378.514   100 a   
#~   plyr  758.866  793.0020  866.9325  843.2290  894.5620 2346.407   100   c 

microbenchmark(split = f1(xl),
               table = f2(xl),
               rle = f3(xl),
               plyr = f4(xl))
#~ Unit: milliseconds
#~   expr       min        lq      mean    median        uq       max neval cld
#~  split  21.96075  22.42355  26.39247  23.24847  24.60674  82.88853   100 ab 
#~  table 100.30543 104.05397 111.62963 105.54308 110.28732 168.27695   100   c
#~    rle  19.07365  20.64686  23.71367  21.30467  23.22815  78.67523   100 a  
#~   plyr  24.33968  25.21049  29.71205  26.50363  27.75960  92.02273   100  b 

microbenchmark(split = f1(xxl),
               table = f2(xxl),
               rle = f3(xxl),
               plyr = f4(xxl))
#~ Unit: milliseconds
#~   expr       min        lq      mean    median        uq       max neval  cld
#~  split  296.4496  310.9702  342.6766  332.5098  374.6485  421.1348   100 a   
#~  table 1151.4551 1239.9688 1283.8998 1288.0994 1323.1833 1385.3040   100    d
#~    rle  399.9442  430.8396  464.2605  471.4376  483.2439  555.9278   100   c 
#~   plyr  350.0607  373.1603  414.3596  425.1436  437.8395  506.0169   100  b  

Surtout, la seule fonction qui compte également le nombre de valeurs manquantes NAest plyr::count. Ceux-ci peuvent également être obtenus séparément en utilisantsum(is.na(vec))


1

Il s'agit d'une solution très rapide pour les vecteurs atomiques unidimensionnels. Il repose sur match(), il est donc compatible avec NA:

x <- c("a", NA, "a", "c", "a", "b", NA, "c")

fn <- function(x) {
  u <- unique.default(x)
  out <- list(x = u, freq = .Internal(tabulate(match(x, u), length(u))))
  class(out) <- "data.frame"
  attr(out, "row.names") <- seq_along(u)
  out
}

fn(x)

#>      x freq
#> 1    a    3
#> 2 <NA>    2
#> 3    c    2
#> 4    b    1

Vous pouvez également modifier l'algorithme pour qu'il ne s'exécute pas unique().

fn2 <- function(x) {
  y <- match(x, x)
  out <- list(x = x, freq = .Internal(tabulate(y, length(x)))[y])
  class(out) <- "data.frame"
  attr(out, "row.names") <- seq_along(x)
  out
}

fn2(x)

#>      x freq
#> 1    a    3
#> 2 <NA>    2
#> 3    a    3
#> 4    c    2
#> 5    a    3
#> 6    b    1
#> 7 <NA>    2
#> 8    c    2

Dans les cas où cette sortie est souhaitable, vous n'en avez probablement même pas besoin pour renvoyer le vecteur d'origine, et la deuxième colonne est probablement tout ce dont vous avez besoin. Vous pouvez obtenir cela en une seule ligne avec le tuyau:

match(x, x) %>% `[`(tabulate(.), .)

#> [1] 3 2 3 2 3 1 2 2

1
Vraiment une excellente solution! C'est aussi le plus rapide que j'ai pu trouver. Il peut être un peu amélioré pour les performances de l'entrée de facteur en utilisant u <- if (is.factor (x)) x [! Duplicated (x)] else unique (x).
Taz

0

Cela peut être fait avec outerpour obtenir une métrique des égalités suivie rowSumsd'une signification évidente.
Afin d'avoir les numbersnombres et dans le même ensemble de données, un data.frame est d'abord créé. Cette étape n'est pas nécessaire si vous souhaitez une entrée et une sortie distinctes.

df <- data.frame(No = numbers)
df$count <- rowSums(outer(df$No, df$No, FUN = `==`))
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.