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.

kmm
źródło

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.

hadley
źródło
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.

Yohan Obadia
źródło
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.

Jan
źródło
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
IRTFM
źródło
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.

Dirk Eddelbuettel
źródło
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])

Maya Levy
źródło
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
eddi
źródło
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
Lawrence Lee
źródło
4

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

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

źródło
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

pedrosaurio
źródło