Contando il numero di elementi con i valori di x in un vettore


400

Ho un vettore di numeri:

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

Come posso avere R contare il numero di volte che un valore x appare nel vettore?

Risposte:


505

Puoi semplicemente usare 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 

Quindi puoi sottoimpostarlo:

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

O convertilo in un data.frame se ti senti più a tuo agio nel farlo:

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

21
Non dimenticare i potenziali problemi in virgola mobile, in particolare con la tabella, che costringe i numeri a stringhe.
Hadley,

4
Questo è un ottimo punto. Questi sono tutti numeri interi, quindi non è un vero problema in questo esempio, giusto?
Shane,

non esattamente. Gli elementi della tabella sono di classe intera (tabella (numeri) [1]), ma 435 è un numero in virgola mobile. Per renderlo un numero intero puoi usare 435L.
Ian Fellows,

@Ian - Sono confuso sul perché 435 sia un float in questo esempio. Puoi chiarire un po '? Grazie.
Heather Stark,

4
Perché non a["435"]insetead di a[names(a)==435]?
pomber,

262

Il modo più diretto è sum(numbers == x).

numbers == xcrea un vettore logico che è VERO in ogni posizione in cui si verifica x, e quando suming, il vettore logico viene forzato in numerico che converte VERO in 1 e FALSO in 0.

Si noti tuttavia che per i numeri in virgola mobile è meglio usare qualcosa come: sum(abs(numbers - x) < 1e-6).


1
buon punto sul problema in virgola mobile. Questo mi morde il sedere più di quanto generalmente mi piacerebbe ammettere.
JD Long

3
@Jason mentre risponde direttamente alla domanda, la mia ipotesi è che alla gente sia piaciuta la soluzione più generale che fornisce la risposta per tutti xnei dati piuttosto che uno specifico valore noto di x. Ad essere sinceri, questo era l'argomento iniziale. Come ho detto nella mia risposta di seguito, "Trovo raro che voglia conoscere la frequenza di un valore e non tutti i valori ..."
JBecker,

62

Probabilmente farei qualcosa del genere

length(which(numbers==x))

Ma davvero, un modo migliore è

table(numbers)

10
table(numbers)farà molto più lavoro della soluzione più semplice sum(numbers==x), perché determinerà anche i conteggi di tutti gli altri numeri nell'elenco.
Ken Williams,

1
il problema con la tabella è che è più difficile includerlo all'interno di un calcolo più complesso, ad esempio usando apply () su frame di dati
skan

38

C'è anche count(numbers)dal plyrpacchetto. Molto più conveniente che tablesecondo me.


C'è un equivalente dplyr di questo?
Stevec,

34

La mia soluzione preferita utilizza rle, che restituirà un valore (l'etichetta, xnel tuo esempio) e una lunghezza, che rappresenta quante volte quel valore è apparso in sequenza.

Combinando rlecon sort, hai un modo estremamente veloce per contare il numero di volte in cui è apparso un valore. Questo può essere utile con problemi più complessi.

Esempio:

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

Se il valore desiderato non viene visualizzato o devi memorizzarlo per un momento successivo, crea 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

Trovo raro che voglia conoscere la frequenza di un valore e non tutti i valori, e rle sembra essere il modo più veloce per ottenere il conteggio e memorizzarli tutti.


1
È il vantaggio di questo, rispetto alla tabella, che dà un risultato in un formato più facilmente utilizzabile? grazie
Heather Stark,

@HeatherStark Direi che ci sono due vantaggi. Il primo è sicuramente che si tratta di un formato più facilmente utilizzato rispetto all'output della tabella. Il secondo è che a volte voglio contare il numero di elementi "in una riga" anziché all'interno dell'intero set di dati. Ad esempio, c(rep('A', 3), rep('G', 4), 'A', rep('G', 2), rep('C', 10))ritornerebbe values = c('A','G','A','G','C')e lengths=c(3, 4, 1, 2, 10)che a volte è utile.
JBecker,

1
usando il microbenchmark, sembra che tablesia più veloce when the vector is long(ho provato 100000) ma leggermente più lungo quando è più corto (ho provato 1000)
ClementWalter

Questo sarà molto lento se hai molti numeri.
skan

19

C'è una funzione standard in R per questo

tabulate(numbers)


Lo svantaggio di tabulateè che non è possibile gestire i numeri zero e negativi.
omar,

2
Ma puoi gestire zero istanze di un determinato numero, che le altre soluzioni non gestiscono
Dodgie

Incredibilmente veloce! E come dice Omar, dà zero conteggi per i valori non visualizzati, estremamente utile quando vogliamo costruire una distribuzione di frequenza. I numeri interi zero o negativi possono essere gestiti aggiungendo una costante prima dell'uso tabulate. Nota: sortsembra essere necessarie per il suo corretto utilizzo in generale: 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

ecco un modo veloce e sporco:

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

9

Se si desidera contare il numero di presenze successivamente, è possibile utilizzare la sapplyfunzione:

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

Produzione:

        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

Questo è sicuramente più veloce della tabella ??
Garini,


3

Un altro modo che trovo conveniente è:

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

Questo converte il set di dati in fattore e quindi il sommario () ci fornisce i totali del controllo (conteggi dei valori univoci).

L'output è:

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 

Questo può essere memorizzato come frame di dati, se preferito.

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

qui row.names è stato usato per rinominare i nomi delle righe. senza usare row.names, i nomi delle colonne in s vengono usati come nomi di riga nel nuovo frame di dati

L'output è:

     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

Utilizzando table ma senza confrontarlo 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 

tableè utile quando si utilizzano più volte i conteggi di diversi elementi. Se hai bisogno di un solo conteggio, usasum(numbers == x)


2

Esistono diversi modi per contare elementi specifici

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 metodo che è relativamente veloce su vettori lunghi e fornisce un output conveniente è quello di usare lengths(split(numbers, numbers))(notare la S alla fine di 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 

L'output è semplicemente un vettore denominato.
La velocità appare paragonabile a quella rleproposta da JBecker e persino un po 'più veloce su vettori molto lunghi. Ecco un microbenchmark in R 3.6.2 con alcune delle funzioni proposte:

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  

È importante sottolineare che l'unica funzione che conta anche il numero di valori mancanti NAè plyr::count. Questi possono anche essere ottenuti separatamente usandosum(is.na(vec))


1

Questa è una soluzione molto veloce per i vettori atomici monodimensionali. Si affida match(), quindi è compatibile 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

Potresti anche modificare l'algoritmo in modo che non venga eseguito 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

Nei casi in cui l'output è desiderabile, probabilmente non è nemmeno necessario che restituisca nuovamente il vettore originale e la seconda colonna è probabilmente tutto ciò di cui hai bisogno. Puoi ottenerlo in una riga con la pipe:

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

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

1
Davvero un'ottima soluzione! Questo è anche il più veloce che ho potuto inventare. Può essere leggermente migliorato per le prestazioni dell'input del fattore usando u <- if (is.factor (x)) x [! Duplicated (x)] else unique (x).
Taz,

0

Questo può essere fatto outerper ottenere un metrox di uguaglianze seguito da rowSums, con un significato ovvio.
Per avere i conteggi e numbersnello stesso set di dati, viene prima creato un data.frame. Questo passaggio non è necessario se si desidera input e output separati.

df <- data.frame(No = numbers)
df$count <- rowSums(outer(df$No, df$No, FUN = `==`))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.