Sprawdź równość wszystkich elementów pojedynczego wektora


101

Próbuję sprawdzić, czy wszystkie elementy wektora są sobie równe. Rozwiązania, które wymyśliłem, wydają się nieco okrężne, oba wymagają sprawdzenia length().

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

Z unique():

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

Z rle():

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

Rozwiązanie, które pozwoliłoby mi uwzględnić wartość tolerancji dla oceny „równości” między elementami, byłoby idealne, aby uniknąć problemów z FAQ 7.31 .

Czy istnieje funkcja wbudowana dla typu testu, którą całkowicie przeoczyłem? identical()i all.equal()porównaj dwa obiekty R, więc nie będą tutaj działać.

Edytuj 1

Oto kilka wyników testów porównawczych. Korzystanie z kodu:

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)

Z wynikami:

          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

Więc wygląda na to, że diff(range(x)) < .Machine$double.eps ^ 0.5jest najszybszy.

Odpowiedzi:


37

Używam tej metody, która porównuje min i max po podzieleniu przez średnią:

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

Jeśli używałeś tego bardziej poważnie, prawdopodobnie chciałbyś usunąć brakujące wartości przed obliczeniem zakresu i średniej.


Wybrałem ten, ponieważ jestem szybszy niż Dirk. Nie mam milionów elementów, ale to powinno działać trochę szybciej.
— kmm

@Kevin: a co z rozwiązaniem Johna? Jest ~ 10x szybszy niż Hadley i pozwala ustawić tolerancję. Czy jest wadliwy w jakiś inny sposób?
— Joshua Ulrich

Proszę podać jakieś testy porównawcze - właśnie sprawdziłem, że mój jest mniej więcej taki sam dla wektora miliona mundurów.
— hadley,

@hadley: Biegałem system.time(for(i in 1:1e4) zero_range(x)), skąd xpochodziłem z OP. Rozwiązanie Johna jest ~ 10x dla x, ~ 3x szybsze dla yi nieco wolniejsze dla runif(1e6).
— Joshua Ulrich

10-krotna różnica nie ma większego znaczenia, gdy patrzy się na różnicę między 0,00023 a 0,000023 sekundy - a DWin prawdopodobnie twierdziłby, że są takie same w określonym stopniu tolerancji;)
— hadley

46

Dlaczego po prostu nie użyć wariancji:

var(x) == 0

Jeśli wszystkie elementy xsą równe, otrzymasz wariancję 0.


17
length(unique(x))=1kończy się dwukrotnie szybciej, ale varjest zwięzły, co jest miłe.
— AdamO

YohanBadia, mam tablicę c (-5.532456e-09, 1.695298e-09) i rozumiem, John test: TRUE ; DWin test: TRUE ; zero-range test: TRUE ; variance test: FALSEże wszystkie inne testy rozpoznają, że wartości są identyczne w R. Jak można użyć testu wariancji w tym kontekście?
— mjs

Dwie wartości w Twojej tablicy nie są identyczne. Dlaczego chcesz, aby test powrócił TRUE? W przypadku odpowiedzi Jana sprawdzasz, czy różnica jest powyżej pewnego progu. W twoim przypadku różnica między dwiema wartościami jest bardzo mała, co może prowadzić do tego, że znajdzie się poniżej zdefiniowanego progu.
— Yohan Obadia

41

Jeśli wszystkie są wartościami liczbowymi, to jeśli tol jest twoją tolerancją, to ...

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

jest rozwiązaniem Twojego problemu.

EDYTOWAĆ:

Po przyjrzeniu się temu i innym odpowiedziom oraz przeprowadzeniu testów porównawczych kilku rzeczy, następujące wyniki są dwa razy szybsze niż odpowiedź DWin.

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

Jest to nieco zaskakujące szybciej niż diff(range(x))ponieważ diffnie powinno być znacznie różni się od -i absdwa numery. Żądanie zakresu powinno zoptymalizować uzyskanie minimum i maksimum. Obie diffi rangesą funkcjami prymitywnymi. Ale czas nie kłamie.


Czy możesz skomentować względne zalety odejmowania średniej w porównaniu z dzieleniem przez nią?
— hadley,

Jest to prostsze obliczeniowo. W zależności od systemu oraz sposobu kompilacji i wektoryzacji języka R, zostanie to osiągnięte szybciej przy mniejszym zużyciu energii. Ponadto, jeśli podzielisz przez średnią, twój testowany wynik jest odniesiony do 1, podczas gdy z odejmowaniem wynosi 0, co wydaje mi się lepsze. Ponadto tolerancja ma prostszą interpretację.
— John

1
Ale nie chodzi nawet o to, że dzielenie jest złożone, ponieważ wyszukiwanie i sortowanie wymagane do wyodrębnienia zakresu jest znacznie bardziej kosztowne obliczeniowo niż zwykłe odejmowanie. Przetestowałem to i powyższy kod jest około 10x szybszy niż funkcja zero_range Hadley (a twoja jest tutaj najszybszą poprawną odpowiedzią). Funkcja porównawcza Dirka jest brutalnie powolna. To najszybsza odpowiedź.
— John

Właśnie zobaczyłem komentarze Josha dotyczące czasu w twojej odpowiedzi Hadley ... Nie mam żadnych sytuacji, w których zero_range jest szybsze. Rozbieżność wynosi od nieco szybszego (może 20%) do 10x zawsze na korzyść, jeśli ta odpowiedź. Próbował wielu metod.
— John

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

Inny w tym samym stylu:

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

Nie sądzę, żeby to x <- seq(1, 10) / 1e10
— działało

2
@Hadley: OP poprosił o rozwiązanie, które pozwoliłoby na określenie tolerancji, prawdopodobnie dlatego, że nie dbał o bardzo małe różnice. all.equal może być używany z innymi tolerancjami i wydaje się, że program operacyjny to rozumie.
— IRTFM,

2
Nie wyraziłem się zbyt jasno - w moim przykładzie jest dziesięciokrotna względna różnica między największą a najmniejszą liczbą. To prawdopodobnie coś, co chcesz zauważyć! Myślę, że tolerancję liczbową należy obliczyć w odniesieniu do zakresu danych - nie robiłem tego w przeszłości i powoduje to problemy.
— hadley

2
Nie sądzę, żebym cię źle zrozumiał. Po prostu pomyślałem, że pytający prosi o rozwiązanie, które zignorowałoby dziesięciokrotną względną różnicę dla liczb, które są w rzeczywistości zerowe. Słyszałem, jak prosił o rozwiązanie, które zignorowałoby różnicę między 1e-11 a 1e-13.
— IRTFM

5
Staram się dawać ludziom to, czego potrzebują, a nie to, czego chcą;) Ale uwaga.
— hadley,

16

Możesz użyć identical()i all.equal()porównując pierwszy element ze wszystkimi innymi, skutecznie przeglądając porównanie:

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> 

W ten sposób możesz dodać dowolny epsilon do identical()według potrzeb.


2
Jednak okropnie nieefektywne ... (na moim komputerze zajmuje to około 10 sekund dla miliona liczb)
— Hadley,

2
Bez wątpienia. OP został jednak wątpliwość, czy można to zrobić w ogóle . Robienie tego dobrze to drugi krok. I wiesz, gdzie stoję z pętli ... ;-)
— Dirk Eddelbuettel

10
Czy pętle są niesamowite? ;)
— hadley

4
W tym rozwiązaniu podoba mi się to, że można go używać z obiektami nienumerycznymi.
— Luciano Selzer

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

16

Możesz po prostu sprawdzić all(v==v[1])


Ten jest świetny, bo działa też ze sznurkami! Dzięki
— arvi1000

To działa, chyba że masz NAw swoim wektorze: x <- c(1,1,NA); all(x == x[1])zwraca NA, nie FALSE. W takich przypadkach length(unique(x)) == 1działa.
— HBat

11

Ponieważ w kółko wracam do tego pytania, oto Rcpprozwiązanie, które będzie generalnie znacznie szybsze niż którekolwiek z Rrozwiązań, jeśli odpowiedź brzmi rzeczywiście FALSE(ponieważ zatrzyma się w momencie napotkania niedopasowania) i będzie miało tę samą prędkość jako najszybsze rozwiązanie R, jeśli odpowiedź brzmi TRUE. Na przykład dla benchmarku OP system.timezegar przy użyciu tej funkcji ustawia dokładnie 0.

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
To fajne i +1 za szybkość, ale nie jestem przekonany, że porównywanie wszystkich elementów do pierwszego elementu jest całkiem poprawne. Wektor może przejść ten test, ale różnica między max (x) i min (x) jest większa niż precyzja. Na przykładfast_equal(c(2,1,3), 1.5)
— dww

@dww Co masz wskazując, że porównanie nie jest przechodnia, gdy masz problemy - czyli precyzyjne a == b, b == cnie musi koniecznie oznaczać a == cjeśli robisz pływających porównań punktowych. Można też podzielić precyzję przez liczbę elementów, aby uniknąć tego problemu, lub zmodyfikować algorytm do obliczenia mini maxi używając które jako warunek zatrzymania.
— eddi

10

Specjalnie do tego napisałem funkcję, która może sprawdzać nie tylko elementy w wektorze, ale także może sprawdzić, czy wszystkie elementy na liście są identyczne . Oczywiście dobrze radzi sobie z wektorami znakowymi i wszystkimi innymi typami wektorów. Posiada również odpowiednią obsługę błędów.

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

Teraz wypróbuj kilka przykładów.

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

W rzeczywistości nie musisz używać min, mean ani max. Na podstawie odpowiedzi Johna:

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

3

Tutaj alternatywa wykorzystująca sztuczkę min, max, ale dla ramki danych. W przykładzie porównuję kolumny, ale parametr marginesu z applymożna zmienić na 1 dla wierszy.

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

Jeśli valid == 0to wszystkie elementy są takie same

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.