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


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.