Verifica l'uguaglianza tra tutti gli elementi di un singolo vettore


101

Sto cercando di verificare se tutti gli elementi di un vettore sono uguali tra loro. Le soluzioni che ho escogitato sembrano alquanto indirette, entrambe implicanti il ​​controllo length().

x <- c(1, 2, 3, 4, 5, 6, 1)  # FALSE
y <- rep(2, times = 7)       # TRUE

Con unique():

length(unique(x)) == 1
length(unique(y)) == 1

Con rle():

length(rle(x)$values) == 1
length(rle(y)$values) == 1

Una soluzione che mi permettesse di includere un valore di tolleranza per valutare l '"uguaglianza" tra gli elementi sarebbe l'ideale per evitare problemi con la FAQ 7.31 .

Esiste una funzione integrata per il tipo di test che ho completamente trascurato? identical()e all.equal()confronta due oggetti R, così non funzioneranno qui.

Modifica 1

Ecco alcuni risultati di benchmarking. Utilizzando il codice:

library(rbenchmark)

John <- function() all( abs(x - mean(x)) < .Machine$double.eps ^ 0.5 )
DWin <- function() {diff(range(x)) < .Machine$double.eps ^ 0.5}
zero_range <- function() {
  if (length(x) == 1) return(TRUE)
  x <- range(x) / mean(x)
  isTRUE(all.equal(x[1], x[2], tolerance = .Machine$double.eps ^ 0.5))
}

x <- runif(500000);

benchmark(John(), DWin(), zero_range(),
  columns=c("test", "replications", "elapsed", "relative"),
  order="relative", replications = 10000)

Con i risultati:

          test replications elapsed relative
2       DWin()        10000 109.415 1.000000
3 zero_range()        10000 126.912 1.159914
1       John()        10000 208.463 1.905251

Quindi sembra che diff(range(x)) < .Machine$double.eps ^ 0.5sia il più veloce.

Risposte:


37

Uso questo metodo, che confronta il minimo e il massimo, dopo aver diviso per la media:

# Determine if range of vector is FP 0.
zero_range <- function(x, tol = .Machine$double.eps ^ 0.5) {
  if (length(x) == 1) return(TRUE)
  x <- range(x) / mean(x)
  isTRUE(all.equal(x[1], x[2], tolerance = tol))
}

Se lo stessi usando più seriamente, probabilmente vorresti rimuovere i valori mancanti prima di calcolare l'intervallo e la media.


Ho scelto questo per essere più veloce di quello di Dirk. Non ho milioni di elementi, ma dovrebbe funzionare un po 'più velocemente per me.
kmm

@ Kevin: che mi dici della soluzione di John? È ~ 10 volte più veloce di Hadley e ti consente di impostare la tolleranza. È carente in qualche altro modo?
Joshua Ulrich

Fornisci qualche benchmarking: ho appena verificato che il mio è più o meno lo stesso per un vettore di un milione di uniformi.
Hadley

@hadley: stavo correndo system.time(for(i in 1:1e4) zero_range(x)), dov'era xdall'OP. La soluzione di John è ~ 10 volte per x, ~ 3 volte più veloce per ye leggermente più lenta per runif(1e6).
Joshua Ulrich

La differenza di 10 volte non ha molta importanza quando si osserva la differenza tra 0,00023 e 0,000023 secondi - e DWin probabilmente affermerebbe che sono gli stessi per il grado di tolleranza specificato;)
Hadley

46

Perché non usare semplicemente la varianza:

var(x) == 0

Se tutti gli elementi di xsono uguali, otterrai una varianza di 0.


17
length(unique(x))=1finisce per essere circa il doppio più veloce, ma varè conciso, il che è bello.
AdamO

YohanBadia, ho un array c (-5.532456e-09, 1.695298e-09) e John test: TRUE ; DWin test: TRUE ; zero-range test: TRUE ; variance test: FALSEtutti gli altri test riconoscono che i valori sono identici in R. Come può essere utilizzato il test di varianza in quel contesto?
mjs

I 2 valori nell'array non sono identici. Perché vorresti che il test tornasse TRUE? Nel caso della risposta di John, controlli se la differenza è al di sopra di una certa soglia. Nel tuo caso la differenza tra i 2 valori è molto bassa, il che potrebbe portare ad essere al di sotto della soglia da te definita.
Yohan Obadia il

41

Se sono tutti valori numerici, se tol è la tua tolleranza, allora ...

all( abs(y - mean(y)) < tol ) 

è la soluzione al tuo problema.

MODIFICARE:

Dopo aver esaminato questa e altre risposte e aver analizzato alcune cose, quanto segue risulta due volte più veloce della risposta DWin.

abs(max(x) - min(x)) < tol

Questo è un po 'sorprendentemente più veloce di diff(range(x))quanto diffnon dovrebbe essere molto diverso da -e abscon due numeri. La richiesta dell'intervallo dovrebbe ottimizzare ottenendo il minimo e il massimo. Entrambi diffe rangesono funzioni primitive. Ma il tempismo non mente.


Puoi commentare i relativi meriti di sottrarre la media rispetto alla divisione per essa?
Hadley

È computazionalmente più semplice. A seconda del sistema e di come R è compilato e vettorializzato, sarà realizzato più velocemente con un minor consumo di energia. Inoltre, quando dividi per la media il risultato testato è relativo a 1 mentre con la sottrazione è 0, il che mi sembra più carino. Inoltre, la tolleranza ha un'interpretazione più semplice.
John

1
Ma non è nemmeno così tanto che la divisione è complessa in quanto la ricerca e l'ordinamento necessari per estrarre l'intervallo sono molto più costosi dal punto di vista computazionale di una semplice sottrazione. L'ho testato e il codice sopra è circa 10 volte più veloce della funzione zero_range di Hadley (e la tua è la risposta corretta più veloce qui). La funzione di confronto di Dirk è brutalmente lenta. Questa è la risposta più veloce qui.
John

Ho appena visto i commenti sui tempi di Josh nella tua risposta Hadley ... Non ho nessuna situazione in cui zero_range sia più veloce. La discrepanza è tra leggermente più veloce (forse 20%) a 10 volte sempre a favore se questa risposta. Ha provato diversi metodi.
John

24
> isTRUE(all.equal( max(y) ,min(y)) )
[1] TRUE
> isTRUE(all.equal( max(x) ,min(x)) )
[1] FALSE

Un altro sulla stessa linea:

> diff(range(x)) < .Machine$double.eps ^ 0.5
[1] FALSE
> diff(range(y)) < .Machine$double.eps ^ 0.5
[1] TRUE

Non penso che x <- seq(1, 10) / 1e10
funzioni

2
@ Hadley: L'OP ha chiesto una soluzione che consentisse di specificare una tolleranza, presumibilmente perché non gli interessavano differenze molto piccole. all.equal può essere utilizzato con altre tolleranze e l'OP sembra capirlo.
IRTFM

2
Non mi sono espresso molto chiaramente: nel mio esempio c'è una differenza relativa di dieci volte tra i numeri più grandi e quelli più piccoli. Probabilmente è qualcosa che vuoi notare! Penso che la tolleranza numerica debba essere calcolata rispetto alla gamma dei dati - non l'ho fatto in passato e ha causato problemi.
Hadley

2
Non credo di averti frainteso minimamente. Pensavo solo che l'interrogante stesse chiedendo una soluzione che ignorasse una differenza relativa di dieci volte per i numeri che sono effettivamente zero. L'ho sentito chiedere una soluzione che ignorasse la differenza tra 1e-11 e 1e-13.
IRTFM

5
Cerco di dare alle persone ciò di cui hanno bisogno, non ciò che vogliono;) Ma punto preso.
Hadley

16

Puoi usare identical()e all.equal()confrontando il primo elemento con tutti gli altri, estendendo efficacemente il confronto tra:

R> compare <- function(v) all(sapply( as.list(v[-1]), 
+                         FUN=function(z) {identical(z, v[1])}))
R> compare(x)
[1] FALSE
R> compare(y)
[1] TRUE
R> 

In questo modo puoi aggiungere qualsiasi epsilon a identical()seconda delle necessità.


2
Orribilmente inefficiente però ... (sul mio computer ci vogliono circa 10 secondi per un milione di numeri)
hadley,

2
Nessun dubbio. L'OP è stato però chiedersi se questo potrebbe essere fatto a tutti . Farlo bene è un secondo passo. E sai dove mi trovo con i loop ... ;-)
Dirk Eddelbuettel

10
Che i loop sono fantastici? ;)
Hadley

4
Quello che mi piace di questo approccio è che può essere utilizzato con oggetti non numerici.
Luciano Selzer

confronta <- function (v) all (sapply (as.list (v [-1]), FUN = function (z) {isTRUE (all.equal (z, v [1]))}))
N. McA .

16

Puoi solo controllare all(v==v[1])


Questo è fantastico bc funziona anche con le corde! Grazie
arvi1000

Funziona a meno che tu non abbia NAnel tuo vettore: x <- c(1,1,NA); all(x == x[1])ritorni NA, no FALSE. In questi casi length(unique(x)) == 1funziona.
HB il

11

Dato che continuo a tornare su questa domanda più e più volte, ecco una Rcppsoluzione che sarà generalmente molto più veloce di qualsiasi Rsoluzione se la risposta è effettivamente FALSE(perché si fermerà nel momento in cui incontra una mancata corrispondenza) e avrà la stessa velocità come la soluzione R più veloce se la risposta è TRUE. Ad esempio per il benchmark OP, system.timeclock esattamente a 0 utilizzando questa funzione.

library(inline)
library(Rcpp)

fast_equal = cxxfunction(signature(x = 'numeric', y = 'numeric'), '
  NumericVector var(x);
  double precision = as<double>(y);

  for (int i = 0, size = var.size(); i < size; ++i) {
    if (var[i] - var[0] > precision || var[0] - var[i] > precision)
      return Rcpp::wrap(false);
  }

  return Rcpp::wrap(true);
', plugin = 'Rcpp')

fast_equal(c(1,2,3), 0.1)
#[1] FALSE
fast_equal(c(1,2,3), 2)
#[2] TRUE

1
Questo è bello e +1 per la velocità, ma non sono convinto che il confronto di tutti gli elementi con il primo elemento sia corretto. Un vettore può superare questo test, ma la differenza tra max (x) e min (x) è maggiore della precisione. Ad esempiofast_equal(c(2,1,3), 1.5)
dww

@dww Quello che stai sottolineare è che il confronto non è transitiva quando si hanno problemi di precisione - vale a dire a == b, b == cnon implica necessariamente a == cse si sta facendo il confronto in virgola mobile. Si sia possibile dividere la tua precisione per il numero di elementi per evitare questo problema, o modificare l'algoritmo per calcolare mine maxed usando che come condizione di arresto.
eddi

10

Ho scritto una funzione appositamente per questo, che può controllare non solo gli elementi in un vettore, ma anche in grado di verificare se tutti gli elementi in un elenco sono identici . Ovviamente gestisce bene anche i vettori di caratteri e tutti gli altri tipi di vettore. Ha anche una gestione degli errori appropriata.

all_identical <- function(x) {
  if (length(x) == 1L) {
    warning("'x' has a length of only 1")
    return(TRUE)
  } else if (length(x) == 0L) {
    warning("'x' has a length of 0")
    return(logical(0))
  } else {
    TF <- vapply(1:(length(x)-1),
                 function(n) identical(x[[n]], x[[n+1]]),
                 logical(1))
    if (all(TF)) TRUE else FALSE
  }
}

Ora prova alcuni esempi.

x <- c(1, 1, 1, NA, 1, 1, 1)
all_identical(x)       ## Return FALSE
all_identical(x[-4])   ## Return TRUE
y <- list(fac1 = factor(c("A", "B")),
          fac2 = factor(c("A", "B"), levels = c("B", "A"))
          )
all_identical(y)     ## Return FALSE as fac1 and fac2 have different level order

4

In realtà non è necessario utilizzare min, mean o max. Basato sulla risposta di John:

all(abs(x - x[[1]]) < tolerance)

3

Ecco un'alternativa che usa il trucco min, max ma per un data frame. Nell'esempio sto confrontando le colonne ma il parametro del margine da applypuò essere modificato in 1 per le righe.

valid = sum(!apply(your_dataframe, 2, function(x) diff(c(min(x), max(x)))) == 0)

Se valid == 0poi tutti gli elementi sono gli stessi

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.