Pruebe la igualdad entre todos los elementos de un solo vector

101

Estoy tratando de probar si todos los elementos de un vector son iguales entre sí. Las soluciones que he encontrado parecen algo indirectas, y ambas implican verificación 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 elementos sería ideal para evitar problemas de la pregunta frecuente 7.31 .

¿Existe una función incorporada para el tipo de prueba que he pasado por alto por completo? identical()y all.equal()compare dos objetos R, para que no funcionen aquí.

Editar 1

A continuación, se muestran algunos resultados de evaluación comparativa. 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

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

kmm
fuente

Respuestas:

37

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 en serio, probablemente querría eliminar los valores faltantes antes de calcular el rango y la media.

Hadley
fuente
Elegí este por ser más rápido que el de Dirk. No tengo millones de elementos, pero esto debería funcionar un poco más rápido para mí.
kmm
@Kevin: ¿qué pasa con la solución de John? Es ~ 10 veces más rápido que el de Hadley y le permite establecer la tolerancia. ¿Es deficiente de alguna otra manera?
Joshua Ulrich
Proporcione algunos puntos de referencia: acabo de comprobar que el mío es aproximadamente el mismo para un vector de un millón de uniformes.
hadley
@hadley: Estaba corriendo system.time(for(i in 1:1e4) zero_range(x)), de dónde xera el OP. La solución de John es ~ 10x para x, ~ 3x más rápida para yy ligeramente más lenta para runif(1e6).
Joshua Ulrich
La diferencia de 10x no importa mucho cuando se observa la diferencia entre 0,00023 y 0,000023 segundos, y DWin probablemente afirmaría que son iguales en el grado de tolerancia especificado;)
hadley
46

¿Por qué no simplemente usar la variación?

var(x) == 0

Si todos los elementos de xson iguales, obtendrá una variación de 0.

Yohan Obadia
fuente
17
length(unique(x))=1termina siendo aproximadamente el doble de rápido, pero vares conciso, lo cual es bueno.
AdamO
YohanBadia, tengo una matriz c (-5.532456e-09, 1.695298e-09), y entiendo John test: TRUE ; DWin test: TRUE ; zero-range test: TRUE ; variance test: FALSEque todas las demás pruebas reconocen que los valores son idénticos en R. ¿Cómo se puede usar la prueba de varianza en ese contexto?
mjs
Los 2 valores de su matriz no son idénticos. ¿Por qué querrías que volviera la prueba TRUE? En el caso de la respuesta de John, verifica si la diferencia está por encima de cierto umbral. En su caso, la diferencia entre los 2 valores es muy baja, lo que podría hacer que esté por debajo del umbral que definió.
Yohan Obadia
41

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

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

es la solución a tu problema.

EDITAR:

Después de ver esta y otras respuestas, y comparar algunas cosas, lo siguiente resulta dos veces más rápido que la respuesta de DWin.

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

Esto es un poco sorprendentemente más rápido que diff(range(x))ya diffque no debería ser muy diferente de -y abscon dos números. Solicitar el rango debe optimizar obteniendo el mínimo y el máximo. Ambos diffy rangeson funciones primitivas. Pero el momento no miente.

Juan
fuente
¿Puede comentar sobre los méritos relativos de restar la media en comparación con dividir por ella?
hadley
Es computacionalmente más simple. Dependiendo del sistema y de cómo se compile y vectorice R, se logrará más rápido con menos consumo de energía. Además, cuando divide por la media, su resultado probado es relativo a 1, mientras que con la resta es 0, lo que me parece más agradable. Además, la tolerancia tiene una interpretación más sencilla.
John
1
Pero ni siquiera es tanto que la división sea compleja, ya que la búsqueda y la clasificación necesarias para extraer el rango son mucho más costosas desde el punto de vista computacional que una simple resta. Lo probé y el código anterior es aproximadamente 10 veces más rápido que la función zero_range Hadley (y la suya es la respuesta correcta más rápida aquí). La función de comparación de Dirk es brutalmente lenta. Esta es la respuesta más rápida aquí.
John
Acabo de ver los comentarios de tiempo de Josh en su respuesta Hadley ... No obtengo ninguna situación en la que zero_range sea más rápido. La discrepancia está entre un poco más rápido (quizás un 20%) y 10 veces siempre a favor si esta respuesta. Probó varios métodos.
John
24
> 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
IRTFM
fuente
No creo que esto funcione tan bien para números muy pequeños:x <- seq(1, 10) / 1e10
hadley
2
@Hadley: El OP pidió una solución que permitiera especificar una tolerancia, presumiblemente porque no le importaban diferencias muy pequeñas. all.equal se puede usar con otras tolerancias y el OP parece entender esto.
IRTFM
2
No me expresé con mucha claridad; en mi ejemplo, hay una diferencia relativa diez veces mayor entre los números más grandes y más pequeños. ¡Eso es probablemente algo que quieras notar! Creo que la tolerancia numérica debe calcularse en relación con el rango de los datos; no he hecho esto en el pasado y ha causado problemas.
hadley
2
No creo que te haya entendido mal en lo más mínimo. Simplemente pensé que el interrogador estaba pidiendo una solución que ignoraría una diferencia relativa diez veces mayor para números que son efectivamente cero. Lo escuché pidiendo una solución que ignorara la diferencia entre 1e-11 y 1e-13.
IRTFM
5
Trato de darle a la gente lo que necesitan, no lo que quieren;) Pero punto.
hadley
16

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 épsilon identical()según sea necesario.

Dirk Eddelbuettel
fuente
2
Sin embargo, horriblemente ineficiente ... (en mi computadora se necesitan aproximadamente 10 segundos para un millón de números)
hadley
2
Sin duda. Sin embargo, el PO cuestionaba si esto se podía hacer en absoluto . Hacerlo bien es un segundo paso. Y sabes dónde estoy con los bucles ... ;-)
Dirk Eddelbuettel
10
¿Que los bucles son increíbles? ;)
hadley
4
Lo que me gusta de este enfoque es que se puede usar con objetos no 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

Solo puedes comprobar all(v==v[1])

Maya Levy
fuente
¡Este es genial porque también funciona con cuerdas! Gracias
arvi1000
Esto funciona a menos que tenga NAen su vector: x <- c(1,1,NA); all(x == x[1])devuelve NA, no FALSE. En tales casos length(unique(x)) == 1funciona.
HBat
11

Como sigo volviendo a esta pregunta una y otra vez, aquí hay una Rcppsolución que generalmente será mucho más rápida que cualquiera de las Rsoluciones si la respuesta es realmente FALSE(porque se detendrá en el momento en que encuentre una falta de coincidencia) y tendrá la misma velocidad como la solución R más rápida si la respuesta es TRUE. Por ejemplo, para el punto de referencia OP, marca system.timeexactamente 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
eddi
fuente
1
Esto es bueno y +1 para la velocidad, pero no estoy convencido de que comparar todos los elementos con el primer elemento sea correcto. Un vector puede pasar esta prueba, pero la diferencia entre max (x) y min (x) es mayor que la precisión. Por ejemplofast_equal(c(2,1,3), 1.5)
dww
@dww Lo que está señalando es que la comparación no es transitiva cuando tiene problemas de precisión, es decir a == b, b == cno implica necesariamente a == csi está haciendo comparaciones de punto flotante. Se puede dividir o bien su precisión por el número de elementos para evitar este problema, o modificar el algoritmo para calcular miny maxy usando eso como una condición de parada.
eddi
10

Escribí una función específicamente para esto, que puede verificar no solo los elementos en un vector, sino que también es capaz de verificar si todos los elementos de 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 pruebe 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
Lawrence Lee
fuente
4

En realidad, no es necesario utilizar min, mean o max. Basado en la respuesta de John:

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

fuente
3

Aquí una alternativa que usa el truco mínimo, máximo pero para un marco de datos. En el ejemplo, estoy comparando columnas, pero el parámetro de margen de applyse puede cambiar a 1 para las filas.

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

Si valid == 0entonces todos los elementos son iguales

pedrosaurio
fuente