Prueba de igualdad entre todos los elementos de un único vector


Estoy tratando de probar si todos los elementos de un vector son iguales entre sí. Las soluciones que se me han ocurrido parecen algo indirectas, ambas implican verificar 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 solución que me permitiera incluir un valor de tolerancia para evaluar la 'igualdad' entre los elementos sería ideal para evitar Preguntas frecuentes 7.31.

¿Hay una función incorporada para el tipo de prueba que tengo completamente pasado por alto? identical() y all.equal() comparan dos objetos R, por lo que no funcionarán aquí.

Editar 1

Aquí hay algunos resultados de benchmarking. Usando el 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)

Con los 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

Así que parece que diff(range(x)) < .Machine$double.eps ^ 0.5 es el más rápido.

Author: kmm, 2011-01-20

9 answers

Utilizo este método, que compara el mínimo y el máximo, después de dividir por 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))
}

Si estuviera usando esto más seriamente, probablemente querrá eliminar los valores faltantes antes de calcular el rango y la media.

 26
Author: hadley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-21 17:25:10

Si son todos valores numéricos entonces si tol es su tolerancia entonces...

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

Es la solución a su problema.

EDITAR:

Después de mirar esto, y otras respuestas, y la evaluación comparativa de algunas cosas, lo siguiente sale más del doble de rápido que la respuesta de disminución.

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

Esto es un poco sorprendentemente más rápido que diff(range(x)) ya que diff no debería ser muy diferente de - y abs con dos números. Solicitar el rango debe optimizar la obtención de mínimo y máximo. Tanto diff como range son funciones primitivas. Pero el momento no miente.

 31
Author: John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-30 03:31:50

Por qué no simplemente usar la varianza:

var(x) == 0

Si todos los elementos de x son iguales, obtendrá una varianza de 0.

 21
Author: Yohan Obadia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-09 18:32:19
> isTRUE(all.equal( max(y) ,min(y)) )
[1] TRUE
> isTRUE(all.equal( max(x) ,min(x)) )
[1] FALSE

Otro en la misma línea:

> diff(range(x)) < .Machine$double.eps ^ 0.5
[1] FALSE
> diff(range(y)) < .Machine$double.eps ^ 0.5
[1] TRUE
 20
Author: 42-,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-20 21:11:30

Puede usar identical() y all.equal() comparando el primer elemento con todos los demás, barriendo efectivamente la comparación a través de:

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> 

De esa manera puede agregar cualquier epsilon a identical() según sea necesario.

 11
Author: Dirk Eddelbuettel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-20 20:44:03

Dado que sigo volviendo a esta pregunta una y otra vez, aquí hay una solución Rcpp que generalmente será mucho más rápida que cualquiera de las soluciones R si la respuesta es realmente FALSE (porque se detendrá en el momento en que encuentre un desajuste) y tendrá la misma velocidad que la solución R más rápida si la respuesta es TRUE. Por ejemplo, para el punto de referencia OP, system.time se registra exactamente en 0 usando esta función.

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
 10
Author: eddi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-19 20:29:04

Escribí una función específicamente para esto, que puede comprobar no solo los elementos en un vector, sino también capaz de comprobar si todos los elementos en una lista son idénticos. Por supuesto, también maneja bien los vectores de caracteres y todos los demás tipos de vectores. También tiene un manejo de errores apropiado.

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

Ahora prueba algunos ejemplos.

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
 6
Author: Lawrence Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-10 09:51:38

En realidad no necesita usar min, mean o max. Basado en la respuesta de Juan:

all(abs(x - x[[1]]) < tolerance)
 3
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-03 17:35:38

Aquí una alternativa usando el truco min, max pero para un marco de datos. En el ejemplo estoy comparando columnas, pero el parámetro margin de apply se puede cambiar a 1 para filas.

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

Si valid == 0 entonces todos los elementos son los mismos

 2
Author: pedrosaurio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-13 13:24:23