Contando el número de elementos con los valores de x en un vector


400

Tengo un vector de números:

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

¿Cómo puedo hacer que R cuente la cantidad de veces que aparece un valor x en el vector?

Respuestas:


505

Solo puedes usar 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 

Entonces puedes subconjuntarlo:

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

O conviértalo en un data.frame si se siente más cómodo trabajando con eso:

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

21
No olvide los posibles problemas de coma flotante, especialmente con la tabla, que obliga a los números a cadenas.
hadley

44
Ese es un gran punto. Todos estos son números enteros, por lo que no es un problema real en este ejemplo, ¿verdad?
Shane

no exactamente. Los elementos de la tabla son de clase clase entera (tabla (números) [1]), pero 435 es un número de coma flotante. Para convertirlo en un entero, puede usar 435L.
Ian Fellows

@ Ian: estoy confundido acerca de por qué 435 es un flotador en este ejemplo. ¿Puedes aclarar un poco? Gracias.
Heather Stark

44
¿Por qué no en a["435"]lugar de a[names(a)==435]?
pomber

262

La forma más directa es sum(numbers == x).

numbers == xcrea un vector lógico que es VERDADERO en cada ubicación en la que x ocurre, y cuando se suming, el vector lógico se convierte en numérico, lo que convierte VERDADERO en 1 y FALSO en 0.

Sin embargo, tenga en cuenta que los números de coma flotante que es mejor usar algo como: sum(abs(numbers - x) < 1e-6).


1
Buen punto sobre el tema de coma flotante. Eso me muerde el trasero más de lo que generalmente me gusta admitir.
JD Long

3
@Jason mientras responde la pregunta directamente, supongo que a la gente le gustó la solución más general que proporciona la respuesta para todos xlos datos en lugar de un valor conocido específico de x. Para ser justos, de eso se trataba la pregunta original. Como dije en mi respuesta a continuación, "Encuentro que es raro que quiera saber la frecuencia de un valor y no todos los valores ..."
JBecker

62

Probablemente haría algo como esto

length(which(numbers==x))

Pero realmente, una mejor manera es

table(numbers)

10
table(numbers)va a hacer mucho más trabajo que la solución más fácil sum(numbers==x), porque también va a calcular los recuentos de todos los otros números en la lista.
Ken Williams

1
El problema con la tabla es que es más difícil incluirlo cálculo dentro más complejo, por ejemplo utilizando aplicar () en tramas de datos
Skan

38

También hay count(numbers)de plyrpaquete. Mucho más conveniente que tableen mi opinión.


¿Hay un equivalente dplyr de esto?
stevec

34

Mi solución preferida usa rle, que devolverá un valor (la etiqueta, xen su ejemplo) y una longitud, que representa cuántas veces ese valor apareció en secuencia.

Al combinar rlecon sort, tiene una forma extremadamente rápida de contar la cantidad de veces que apareció cualquier valor. Esto puede ser útil con problemas más complejos.

Ejemplo:

> 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 el valor que desea no aparece, o necesita almacenar ese valor para más adelante, haga 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

Creo que es raro que quiera saber la frecuencia de un valor y no todos los valores, y rle parece ser la forma más rápida de contarlos y almacenarlos todos.


1
¿Es la ventaja de esto, frente a la tabla, que da un resultado en un formato más fácil de usar? gracias
Heather Stark

@HeatherStark Yo diría que hay dos ventajas. La primera es definitivamente que es un formato más fácil de usar que la salida de la tabla. El segundo es que a veces quiero contar el número de elementos "en una fila" en lugar de dentro del conjunto de datos completo. Por ejemplo, c(rep('A', 3), rep('G', 4), 'A', rep('G', 2), rep('C', 10))volvería values = c('A','G','A','G','C')y lengths=c(3, 4, 1, 2, 10)que a veces es útil.
JBecker

1
usando microbenchmark, parece que tablees más rápido when the vector is long(intenté con 100000) pero un poco más largo cuando fue más corto (intenté con 1000)
ClementWalter

Esto va a ser muy lento si tienes muchos números.
skan

19

Hay una función estándar en R para eso

tabulate(numbers)


La desventaja tabulatees que no puedes lidiar con números cero y negativos.
omar

2
Pero puede manejar cero instancias de un número dado, que las otras soluciones no manejan
Dodgie

Fantásticamente rápido! Y como dice omar, da recuento cero para valores que no aparecen, extremadamente útil cuando queremos construir una distribución de frecuencia. Se pueden manejar enteros cero o negativos agregando una constante antes de usar tabulate. Nota: sortparece ser necesario para su correcto uso en general: 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

Aquí hay una manera rápida y sucia:

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

9

Si desea contar el número de apariciones posteriormente, puede utilizar la sapplyfunción:

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

Salida:

        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

¿Es esto de alguna manera más rápido que la mesa?
Garini


3

Una forma más que encuentro conveniente es:

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)))

Esto convierte el conjunto de datos en factor, y luego summary () nos da los totales de control (recuentos de los valores únicos).

Salida es:

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 

Esto se puede almacenar como marco de datos si se prefiere.

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

aquí row.names se ha utilizado para renombrar nombres de fila. sin usar row.names, los nombres de columna en s se usan como nombres de fila en el nuevo marco de datos

Salida es:

     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

Usando la tabla pero sin comparar con 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 

tablees útil cuando usa los recuentos de diferentes elementos varias veces. Si solo necesita un recuento, usesum(numbers == x)


2

Hay diferentes formas de contar elementos específicos.

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

Un método que es relativamente rápido en vectores largos y proporciona una salida conveniente es usar lengths(split(numbers, numbers))(tenga en cuenta la S al final 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 salida es simplemente un vector con nombre.
La velocidad parece comparable a la rlepropuesta por JBecker e incluso un poco más rápida en vectores muy largos. Aquí hay un microbenchmark en R 3.6.2 con algunas de las funciones propuestas:

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  

Es importante destacar que la única función que también cuenta el número de valores faltantes NAes plyr::count. Estos también se pueden obtener por separado usandosum(is.na(vec))


1

Esta es una solución muy rápida para vectores atómicos unidimensionales. Se basa match(), por lo que es compatible con 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

También puede ajustar el algoritmo para que no se ejecute 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

En los casos en que ese resultado es deseable, probablemente ni siquiera lo necesite para volver a devolver el vector original, y la segunda columna es probablemente todo lo que necesita. Puede obtener eso en una línea con la tubería:

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

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

1
Muy buena solución! Ese también es el más rápido que se me ocurrió. Se puede mejorar un poco el rendimiento para la entrada de factores usando u <- if (is.factor (x)) x [! Duplicated (x)] else unique (x).
Taz

0

Esto se puede hacer outerpara obtener una metrix de igualdades seguida de rowSumsun significado obvio.
Para tener los recuentos y numbersen el mismo conjunto de datos, primero se crea un data.frame. Este paso no es necesario si desea entradas y salidas separadas.

df <- data.frame(No = numbers)
df$count <- rowSums(outer(df$No, df$No, FUN = `==`))
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.