Перевірка рівності між усіма елементами одного вектора


101

Я намагаюся перевірити, чи всі елементи вектора рівні між собою. Рішення, які я придумав, здаються дещо зворотними, обидва включають перевірку length().

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

З unique():

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

З rle():

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

Рішення, яке дозволило б мені включити значення толерантності для оцінки «рівності» між елементами, було б ідеальним, щоб уникнути питань, що виникають із питань поширення 7.31 .

Чи є вбудована функція для типу тесту, яку я повністю пропустив? identical()і all.equal()порівняйте два об'єкти R, щоб вони не працювали тут.

Редагуйте 1

Ось деякі результати бенчмаркінгу. Використання коду:

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)

З результатами:

          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

Так що, схоже, diff(range(x)) < .Machine$double.eps ^ 0.5це найшвидше.

Відповіді:


37

Я використовую цей метод, який порівнює min та max після ділення на середнє:

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

Якщо ви використовували це більш серйозно, ви, ймовірно, захочете видалити пропущені значення, перш ніж обчислити діапазон і середнє значення.


Я вибрав цей, щоб бути швидшим, ніж Дірк. У мене немає мільйонів елементів, але це має працювати трохи швидше для мене.
кмм

@Kevin: як щодо рішення Джона? Це на 10 разів швидше, ніж у Хедлі, і дозволяє встановити толерантність. Чи є дефіцит якимось іншим способом?
Джошуа Ульріх

Будь ласка, надайте порівняльний аналіз - я щойно перевірив, що міна приблизно однакова для вектора мільйона форменого одягу.
хадлі

@hadley: Я бігав system.time(for(i in 1:1e4) zero_range(x)), де xбув з ОП. Рішення Іоана ~ 10x для x, ~ 3x швидше yі трохи повільніше для runif(1e6).
Джошуа Ульріх

10-кратна різниця не має великого значення, коли ви дивитесь на різницю між 0,00023 та 0,000023 секундами - і DWin, ймовірно, стверджує, що вони однакові до заданої ступеня допуску;)
hadley

46

Чому б просто не використовувати дисперсію:

var(x) == 0

Якщо всі елементи xдорівнюють рівним, ви отримаєте дисперсію 0.


17
length(unique(x))=1в кінцевому підсумку приблизно вдвічі швидше, але varкоротко, що приємно.
AdamO

YohanBadia, у мене є масив c (-5.532456e-09, 1.695298e-09), і я маю на John test: TRUE ; DWin test: TRUE ; zero-range test: TRUE ; variance test: FALSEувазі всі інші тести визнають, що значення однакові в R. Як можна використовувати тест дисперсії в цьому контексті?
міс

2 значення у вашому масиві не тотожні. Чому ви хочете повернути тест TRUE? У випадку відповіді Джона ви перевіряєте, чи різниця перевищує певний поріг. У вашому випадку різниця між двома значеннями дуже мала, що може призвести до того, що воно буде нижчим від визначеного вами порогу.
Йохан Обадія

41

Якщо вони всі числові значення, то, якщо tol - це ваша толерантність, то ...

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

- це рішення вашої проблеми.

Редагувати:

Після перегляду цього та інших відповідей та порівняльного аналізу кількох речей, наступне виходить удвічі швидше, ніж відповідь DWin.

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

Це трохи дивно швидше, ніж diff(range(x))оскільки diffне повинно сильно відрізнятися, ніж -і absз двома числами. Запит на діапазон повинен оптимізувати отримання мінімального та максимального. І те diffй rangeінше є примітивними функціями. Але терміни не брешуть.


Чи можете ви прокоментувати відносні достоїнства відняття середнього порівняно з діленням на нього?
Хадлі

Це обчислювально простіше. Залежно від системи та способу компіляції та векторизації R це буде досягнуто швидше з меншим споживанням енергії. Крім того, коли ви ділите на середнє значення, випробуваний результат відносно 1, а при відніманні - 0, що мені здається приємнішим. Також толерантність має більш прямолінійну інтерпретацію.
Іван

1
Але навіть поділ не є складним, оскільки пошук і сортування, необхідні для вилучення діапазону, набагато дорожче обчислювально, ніж просте віднімання. Я перевірив це, і вищевказаний код приблизно в 10 разів швидше, ніж функція zero_range Hadley (а ваш - про швидку правильну відповідь тут). Функція порівняння Дірка є жорстоко повільною. Тут найшвидша відповідь.
Іван

Щойно я побачив тимчасові коментарі Джоша у вашій відповіді Хедлі ... У мене не виникає ситуацій, коли zero_range швидше. Розбіжність між дещо швидшими (можливо, 20%) до 10 разів завжди на користь цієї відповіді. Він випробував ряд методів.
Іван

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

Ще одна за тими ж лініями:

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

Я не думаю, що це працює настільки добре для дуже малої кількості:x <- seq(1, 10) / 1e10
hadley

2
@Hadley: ОП попросило рішення, яке б дозволило визначити толерантність, імовірно, тому, що він не піклувався про дуже малі відмінності. all.equal може використовуватися з іншими допущеннями, і ОП, схоже, розуміє це.
IRTFM

2
Я не висловився дуже чітко - у моєму прикладі є десятикратна відносна різниця між найбільшими та найменшими числами. Це, мабуть, щось, що ви хочете помітити! Я думаю, що чисельну толерантність потрібно обчислювати щодо діапазону даних - я цього не робив раніше, і це спричинило проблеми.
хадлі

2
Я не думаю, що я неправильно зрозумів тебе. Я просто думав, що запитуючий запитує рішення, яке ігноруватиме десятикратну відносну різницю для чисел, які фактично дорівнюють нулю. Я чув, як він просить рішення, яке ігнорує різницю між 1e-11 та 1e-13.
IRTFM

5
Я намагаюся і даю людям те, що їм потрібно, а не те, що вони хочуть;) Але беруться до уваги.
хадлі

16

Ви можете використовувати identical()і all.equal()порівнюючи перший елемент з усіма іншими, ефективно переглядаючи порівняння:

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> 

Таким чином ви можете додати будь-який епсилон до identical()необхідності.


2
Хоч і неефективно, хоча ... (на моєму комп’ютері це займає близько 10 секунд на мільйон чисел)
hadley

2
Без сумніву. О.П. був , однак сумнівається , що це може бути зроблено на всіх . Зробити це добре - другий крок. І ви знаєте, де я стою з петлями ... ;-)
Дірк Еддельбуеттель,

10
Що петлі дивовижні? ;)
хадлі

4
Що мені подобається в цьому підході, це те, що він може бути використаний з нечисловими об'єктами.
Luciano Selzer

порівняти <- функція (v) всі (sapply (as.list (v [-1]), FUN = функція (z) {isTRUE (all.equal (z, v [1]))}))
Н. МакА .

16

Можна просто перевірити all(v==v[1])


Цей чудовий bc, він також працює і з рядками! Спасибі
arvi1000

Це працює, якщо у вас немає NAу векторі: x <- c(1,1,NA); all(x == x[1])повертається NA, ні FALSE. У таких випадках length(unique(x)) == 1працює.
HBat

11

Оскільки я постійно повертаюсь до цього питання знов і знов, ось Rcppрішення, яке, як правило, буде набагато швидше, ніж будь-яке з Rрішень, якщо відповідь насправді FALSE(адже це зупинить мить, коли вона зустрінеться з невідповідністю) і матиме ту саму швидкість як найшвидший R-рішення, якщо відповідь є TRUE. Наприклад, для базового показника OP, функціонує system.timeгодинник рівно 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

1
Це приємно і +1 для швидкості, але я не переконаний, що порівнювати всі елементи з 1-м елементом цілком правильно. Вектор може пройти цей тест, але різниця між max (x) та min (x) буде більшою, ніж точність. Наприкладfast_equal(c(2,1,3), 1.5)
dww

@dww Що ви вказуючи, що порівняння не є транзитивним , якщо у вас є питання точності - тобто a == b, b == cне обов'язково означає , що a == cякщо ви робите порівняння з плаваючою точкою. Ви можете або розділити точність за кількістю елементів , щоб уникнути цієї проблеми, або змінити алгоритм для обчислення minі maxі використовувати це як умова зупинки.
Едді

10

Я спеціально для цього написав функцію, яка може перевіряти не тільки елементи у векторі, але й здатні перевірити, чи всі елементи в списку однакові . Звичайно, це також добре обробляє вектори символів та всі інші типи векторних свердловин. Він також має відповідне поводження з помилками.

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

Тепер спробуйте кілька прикладів.

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

Насправді вам не потрібно використовувати min, mean або max. На основі відповіді Джона:

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

3

Тут альтернатива, використовуючи min, max фокус, але для кадру даних. У прикладі я порівнюю стовпці, але параметр поля applyможна змінити на 1 для рядків.

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

Якщо valid == 0тоді всі елементи однакові

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.