Tester l'égalité entre tous les éléments d'un même vecteur


101

J'essaie de tester si tous les éléments d'un vecteur sont égaux les uns aux autres. Les solutions que j'ai proposées semblent quelque peu détournées, toutes deux impliquant des vérifications length().

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

Avec unique():

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

Avec rle():

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

Une solution qui me permettrait d'inclure une valeur de tolérance pour évaluer «l'égalité» entre les éléments serait idéale pour éviter les problèmes de la FAQ 7.31 .

Existe-t-il une fonction intégrée pour le type de test que j'ai complètement négligée? identical()et all.equal()comparez deux objets R, afin qu'ils ne fonctionnent pas ici.

Modifier 1

Voici quelques résultats d'analyse comparative. En utilisant le code:

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)

Avec les résultats:

          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

On dirait donc que diff(range(x)) < .Machine$double.eps ^ 0.5c'est le plus rapide.

Réponses:


37

J'utilise cette méthode, qui compare le min et le max, après avoir divisé par la moyenne:

# 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 vous l'utilisiez plus sérieusement, vous voudriez probablement supprimer les valeurs manquantes avant de calculer la plage et la moyenne.


J'ai choisi celui-ci pour être plus rapide que celui de Dirk. Je n'ai pas des millions d'éléments, mais cela devrait fonctionner un peu plus vite pour moi.
kmm

@Kevin: qu'en est-il de la solution de John? Il est environ 10 fois plus rapide que celui de Hadley et vous permet de définir la tolérance. Est-il déficient d'une autre manière?
Joshua Ulrich

Veuillez fournir une analyse comparative - je viens de vérifier que le mien est à peu près le même pour un vecteur d'un million d'uniformes.
hadley

@hadley: Je courais system.time(for(i in 1:1e4) zero_range(x)), d'où venait xl'OP. La solution de John est ~ 10x pour x, ~ 3x plus rapide pour yet légèrement plus lente pour runif(1e6).
Joshua Ulrich

La différence 10x n'a pas beaucoup d'importance lorsque vous regardez la différence entre 0,00023 et 0,000023 seconde - et DWin prétendrait probablement qu'elles sont identiques au degré de tolérance spécifié;)
hadley

46

Pourquoi ne pas simplement utiliser la variance:

var(x) == 0

Si tous les éléments de xsont égaux, vous obtiendrez une variance de 0.


17
length(unique(x))=1finit par être environ deux fois plus rapide, mais varest laconique, ce qui est bien.
AdamO

YohanBadia, j'ai un tableau c (-5,532456e-09, 1,695298e-09), et j'obtiens un John test: TRUE ; DWin test: TRUE ; zero-range test: TRUE ; variance test: FALSEsens, tous les autres tests reconnaissent que les valeurs sont identiques dans R. Comment le test de variance peut-il être utilisé dans ce contexte?
mjs le

Les 2 valeurs de votre tableau ne sont pas identiques. Pourquoi voudriez-vous que le test revienne TRUE? Dans le cas de la réponse de John, vous vérifiez si la différence est supérieure à un certain seuil. Dans votre cas, la différence entre les 2 valeurs est très faible, ce qui pourrait le conduire à être en dessous du seuil que vous avez défini.
Yohan Obadia le

41

Si ce sont toutes des valeurs numériques, si tol est votre tolérance, alors ...

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

est la solution à votre problème.

ÉDITER:

Après avoir examiné ceci, et d'autres réponses, et comparé quelques points, ce qui suit ressort deux fois plus vite que la réponse DWin.

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

C'est un peu étonnamment plus rapide que diff(range(x))puisque diffne devrait pas être très différent de -et absavec deux nombres. La demande de la gamme devrait optimiser l'obtention du minimum et du maximum. Les deux diffet rangesont des fonctions primitives. Mais le timing ne ment pas.


Pouvez-vous commenter les mérites relatifs de soustraire la moyenne par rapport à la division par elle?
hadley

C'est plus simple en termes de calcul. En fonction du système et de la façon dont R est compilé et vectorisé, cela sera accompli plus rapidement avec moins de consommation d'énergie. De plus, lorsque vous divisez par la moyenne, votre résultat testé est relatif à 1 tandis qu'avec la soustraction, il est de 0, ce qui me semble plus agréable. En outre, la tolérance a une interprétation plus simple.
John

1
Mais ce n'est même pas tant que la division est complexe, car la recherche et le tri requis pour extraire la plage sont beaucoup plus coûteux en calcul qu'une simple soustraction. Je l'ai testé et le code ci-dessus est environ 10 fois plus rapide que la fonction zero_range Hadley (et la vôtre est la réponse correcte la plus rapide ici). La fonction de comparaison de Dirk est brutalement lente. C'est la réponse la plus rapide ici.
John

Je viens de voir les commentaires de Josh sur le timing dans ta réponse Hadley ... Je n'ai aucune situation où zero_range est plus rapide. L'écart est entre légèrement plus rapide (peut-être 20%) à 10x toujours en faveur si cette réponse. Il a essayé un certain nombre de méthodes.
John

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

Un autre dans le même sens:

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

Je ne pense pas que cela fonctionne si bien pour de très petits nombres:x <- seq(1, 10) / 1e10
hadley

2
@Hadley: Le PO a demandé une solution qui permettrait de spécifier une tolérance, probablement parce qu'il ne se souciait pas des très petites différences. all.equal peut être utilisé avec d'autres tolérances et l'OP semble le comprendre.
IRTFM

2
Je ne me suis pas exprimé très clairement - dans mon exemple, il y a une différence relative décuplée entre le plus grand et le plus petit nombre. C'est probablement quelque chose que vous voulez remarquer! Je pense que la tolérance numérique doit être calculée par rapport à la gamme des données - je ne l'ai pas fait dans le passé et cela a causé des problèmes.
hadley

2
Je ne pense pas que je vous ai mal compris. Je pensais simplement que l'interlocuteur demandait une solution qui ignorerait une différence relative décuplée pour des nombres qui sont effectivement zéro. Je l'ai entendu demander une solution qui ignorerait la différence entre 1e-11 et 1e-13.
IRTFM

5
J'essaie de donner aux gens ce dont ils ont besoin, pas ce qu'ils veulent;) Mais point pris.
hadley

16

Vous pouvez utiliser identical()et all.equal()en comparant le premier élément à tous les autres, balayant efficacement la comparaison à travers:

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 cette façon, vous pouvez ajouter n'importe quel epsilon au identical()besoin.


2
Hideusement inefficace cependant ... (sur mon ordinateur, cela prend environ 10 secondes pour un million de nombres)
hadley

2
Sans aucun doute. Le PO se demandait cependant si cela pouvait être fait du tout . Bien le faire est une deuxième étape. Et vous savez où je me situe avec des boucles ... ;-)
Dirk Eddelbuettel

10
Que les boucles sont géniales? ;)
hadley

4
Ce que j'aime dans cette approche, c'est qu'elle peut être utilisée avec des objets non numériques.
Luciano Selzer

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

16

Vous pouvez simplement vérifier all(v==v[1])


Celui-ci est génial car il fonctionne aussi avec des cordes! Merci
arvi1000

Cela fonctionne à moins que vous n'ayez NAdans votre vecteur: x <- c(1,1,NA); all(x == x[1])renvoie NA, non FALSE. Dans de tels cas length(unique(x)) == 1fonctionne.
HB du

11

Puisque je reviens sans cesse sur cette question, voici une Rcppsolution qui sera généralement beaucoup plus rapide que n'importe laquelle des Rsolutions si la réponse est réellement FALSE(car elle s'arrêtera au moment où elle rencontrera une discordance) et aura la même vitesse comme la solution R la plus rapide si la réponse est TRUE. Par exemple, pour le benchmark OP, system.timehorloges à exactement 0 en utilisant cette fonction.

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
C'est bien & +1 pour la vitesse, mais je ne suis pas convaincu que comparer tous les éléments au 1er élément soit tout à fait correct. Un vecteur peut réussir ce test, mais la différence entre max (x) et min (x) est supérieure à la précision. Par exemplefast_equal(c(2,1,3), 1.5)
dww

@dww Qu'est - ce que vous remarquer est que la comparaison n'est pas transitive lorsque vous avez des problèmes de précision - à savoir a == b, b == cne signifie pas nécessairement a == csi vous faites des comparaisons à virgule flottante. Vous pouvez diviser votre précision par le nombre d'éléments pour éviter ce problème, ou de modifier l'algorithme pour calculer minet maxet utiliser cela comme une condition d' arrêt.
eddi

10

J'ai écrit une fonction spécialement pour cela, qui peut vérifier non seulement les éléments d'un vecteur, mais également capable de vérifier si tous les éléments d'une liste sont identiques . Bien sûr, il gère également les vecteurs de caractères et tous les autres types de vecteurs. Il dispose également d'une gestion des erreurs appropriée.

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

Maintenant, essayez quelques exemples.

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

Vous n'avez pas réellement besoin d'utiliser min, mean ou max. Basé sur la réponse de John:

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

3

Voici une alternative utilisant l'astuce min, max mais pour une trame de données. Dans l'exemple, je compare des colonnes, mais le paramètre de marge de applypeut être changé en 1 pour les lignes.

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

Si valid == 0alors tous les éléments sont les mêmes

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.