Teste de igualdade entre todos os elementos de um único vetor

101

Estou tentando testar se todos os elementos de um vetor são iguais uns aos outros. As soluções que encontrei parecem um tanto indiretas, ambas envolvendo verificação length().

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

Com unique():

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

Com rle():

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

Uma solução que me permitisse incluir um valor de tolerância para avaliar a 'igualdade' entre os elementos seria ideal para evitar os problemas da FAQ 7.31 .

Existe uma função integrada para o tipo de teste que eu ignorei completamente? identical()e all.equal()compare dois objetos R, então eles não funcionarão aqui.

Editar 1

Aqui estão alguns resultados de benchmarking. Usando o código:

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)

Com os resultados:

          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

Portanto, parece que diff(range(x)) < .Machine$double.eps ^ 0.5é o mais rápido.

kmm
fonte

Respostas:

37

Eu uso este método, que compara o mínimo e o máximo, depois de dividir pela média:

# 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 você estivesse usando isso mais a sério, provavelmente gostaria de remover os valores ausentes antes de calcular o intervalo e a média.

Hadley
fonte
Escolhi este por ser mais rápido que o de Dirk. Não tenho milhões de elementos, mas isso deve funcionar um pouco mais rápido para mim.
kmm
@Kevin: e a solução de John? É ~ 10x mais rápido que o de Hadley e permite definir a tolerância. É deficiente de alguma outra maneira?
Joshua Ulrich
Forneça algum benchmarking - acabei de verificar se o meu é o mesmo para um vetor de um milhão de uniformes.
hadley
@hadley: Eu estava correndo system.time(for(i in 1:1e4) zero_range(x)), de onde xvinha o OP. A solução de John é ~ 10x para x, ~ 3x mais rápida para ye ligeiramente mais lenta para runif(1e6).
Joshua Ulrich
A diferença de 10x não importa muito quando você está olhando para a diferença entre 0,00023 e 0,000023 segundos - e DWin provavelmente alegaria que eles são iguais para o grau de tolerância especificado;)
hadley
46

Por que não simplesmente usar a variação:

var(x) == 0

Se todos os elementos de xforem iguais, você obterá uma variação de 0.

Yohan Obadia
fonte
17
length(unique(x))=1acaba sendo duas vezes mais rápido, mas varé conciso, o que é bom.
AdamO
YohanBadia, eu tenho uma matriz c (-5.532456e-09, 1.695298e-09) e entendo John test: TRUE ; DWin test: TRUE ; zero-range test: TRUE ; variance test: FALSEque todos os outros testes reconhecem que os valores são idênticos em R. Como o teste de variância pode ser usado nesse contexto?
mjs
Os 2 valores em sua matriz não são idênticos. Por que você gostaria que o teste retornasse TRUE? No caso da resposta de John, você verifica se a diferença está acima de um certo limite. No seu caso, a diferença entre os 2 valores é muito baixa, o que pode fazer com que fique abaixo do limite definido.
Yohan Obadia
41

Se forem todos valores numéricos, se tol for sua tolerância, então ...

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

é a solução para o seu problema.

EDITAR:

Depois de olhar para esta e outras respostas, e comparar algumas coisas, o seguinte sai duas vezes mais rápido que a resposta DWin.

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

Isso é um pouco surpreendentemente mais rápido, diff(range(x))diffque não deve ser muito diferente de -e abscom dois números. Solicitar o intervalo deve otimizar a obtenção do mínimo e máximo. Ambos diffe rangesão funções primitivas. Mas o momento não mente.

John
fonte
Você pode comentar sobre os méritos relativos de subtrair a média em comparação com dividir por ela?
hadley
É computacionalmente mais simples. Dependendo do sistema e de como R é compilado e vetorizado, isso será realizado mais rapidamente com menos consumo de energia. Além disso, quando você divide pela média, o resultado testado é relativo a 1, enquanto com a subtração é 0, o que me parece mais agradável. Além disso, a tolerância tem uma interpretação mais direta.
João
1
Mas não é tanto que a divisão seja complexa, pois a pesquisa e a classificação necessárias para extrair o intervalo são muito mais caras do que uma simples subtração. Eu testei e o código acima é cerca de 10x mais rápido do que a função zero_range Hadley (e o seu é a resposta correta mais rápida aqui). A função de comparação de Dirk é brutalmente lenta. Esta é a resposta mais rápida aqui.
João
Acabei de ver os comentários de tempo de Josh em sua resposta Hadley ... Não vejo nenhuma situação em que intervalo_zero seja mais rápido. A discrepância é entre um pouco mais rápido (talvez 20%) a 10x sempre a favor se esta resposta. Ele tentou vários métodos.
João
24
> isTRUE(all.equal( max(y) ,min(y)) )
[1] TRUE
> isTRUE(all.equal( max(x) ,min(x)) )
[1] FALSE

Outro na mesma linha:

> diff(range(x)) < .Machine$double.eps ^ 0.5
[1] FALSE
> diff(range(y)) < .Machine$double.eps ^ 0.5
[1] TRUE
IRTFM
fonte
Não acho que isso funcione tão bem para números muito pequenos:x <- seq(1, 10) / 1e10
hadley de
2
@Hadley: O OP pediu uma solução que permitisse a especificação de uma tolerância, provavelmente porque ele não se importava com diferenças muito pequenas. all.equal pode ser usado com outras tolerâncias e o OP parece entender isso.
IRTFM de
2
Eu não me expressei muito claramente - no meu exemplo, há uma diferença relativa de dez vezes entre o maior e o menor número. Provavelmente é algo que você deseja observar! Acho que a tolerância numérica precisa ser calculada em relação ao intervalo dos dados - eu não fiz isso no passado e isso causou problemas.
hadley de
2
Não acho que o entendi mal. Eu apenas pensei que o questionador estava pedindo uma solução que ignoraria uma diferença relativa de dez vezes para números que são efetivamente zero. Eu o ouvi pedindo uma solução que ignorasse a diferença entre 1e-11 e 1e-13.
IRTFM de
5
Eu tento dar às pessoas o que elas precisam, não o que elas querem;) Mas claro.
hadley
16

Você pode usar identical()e all.equal()comparando o primeiro elemento com todos os outros, varrendo efetivamente a comparação:

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> 

Dessa forma, você pode adicionar qualquer épsilon identical()conforme necessário.

Dirk Eddelbuettel
fonte
2
No entanto, terrivelmente ineficiente ... (no meu computador, leva cerca de 10 segundos para um milhão de números)
hadley
2
Sem dúvida. O OP foi, porém, questionando se isso poderia ser feito em tudo . Fazer bem é uma segunda etapa. E você sabe onde estou com loops ... ;-)
Dirk Eddelbuettel
10
Esses loops são fantásticos? ;)
hadley
4
O que eu gosto nessa abordagem é que ela pode ser usada com objetos não numéricos.
Luciano Selzer
compare <- function (v) all (sapply (as.list (v [-1]), FUN = function (z) {isTRUE (all.equal (z, v [1]))}))
N. McA .
16

Você pode apenas verificar all(v==v[1])

Maya Levy
fonte
Este é ótimo mas funciona com strings também! Obrigado
arvi1000
Isso funciona a menos que você tenha NAem seu vetor: x <- c(1,1,NA); all(x == x[1])retorna NA, não FALSE. Em tais casos, length(unique(x)) == 1funciona.
HBat
11

Como sempre volto a essa pergunta, aqui está uma Rcppsolução que geralmente será muito mais rápida do que qualquer uma das Rsoluções se a resposta for realmente FALSE(porque ela irá parar no momento em que encontra uma incompatibilidade) e terá a mesma velocidade como a solução R mais rápida se a resposta for TRUE. Por exemplo, para o benchmark OP, system.timeclica em exatamente 0 usando esta função.

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
fonte
1
Isso é bom e +1 para velocidade, mas não estou convencido de que comparar todos os elementos com o primeiro elemento esteja certo. Um vetor pode passar neste teste, mas a diferença entre max (x) e min (x) é maior do que a precisão. Por exemplofast_equal(c(2,1,3), 1.5)
dww
@dww O que você está apontando é que a comparação não é transitiva quando você tem problemas de precisão - ou seja a == b, b == cnão significa necessariamente a == cque você esteja fazendo comparações de ponto flutuante. Você pode dividir a sua precisão pelo número de elementos a evitar esse problema, ou modificar o algoritmo para calcular mine maxe usando isso como uma condição de parada.
eddi
10

Escrevi uma função especificamente para isso, que pode verificar não apenas os elementos em um vetor, mas também pode verificar se todos os elementos de uma lista são idênticos . É claro que também lida com vetores de caracteres e todos os outros tipos de vetores. Ele também possui tratamento de erros apropriado.

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

Agora tente alguns exemplos.

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
fonte
4

Na verdade, você não precisa usar min, mean ou max. Com base na resposta de John:

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

fonte
3

Aqui está uma alternativa usando o truque mínimo e máximo, mas para um quadro de dados. No exemplo, estou comparando colunas, mas o parâmetro de margem de applypode ser alterado para 1 para linhas.

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

Se valid == 0todos os elementos forem iguais

pedrosaurio
fonte