Проверка на равенство всех элементов одного числового вектора

Я пытаюсь проверить, все ли элементы вектора равны друг другу. Решения, которые я придумал, кажутся несколько окольными, и оба включают проверку 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

Решение, которое позволило бы мне включить значение допуска для оценки «равенства» между элементами, было бы идеальным, чтобы избежать FAQ 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 самый быстрый.


person kmm    schedule 20.01.2011    source источник


Ответы (11)


Я использую этот метод, который сравнивает минимальное и максимальное значение после деления на среднее значение:

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

Если бы вы использовали это более серьезно, вы, вероятно, захотели бы удалить отсутствующие значения перед вычислением диапазона и среднего.

person hadley    schedule 20.01.2011
comment
Я выбрал этот, потому что он быстрее, чем у Дирка. У меня нет миллионов элементов, но это должно работать немного быстрее. - person kmm; 21.01.2011
comment
@ Кевин: как насчет решения Джона? Это примерно в 10 раз быстрее, чем у Хэдли, и позволяет устанавливать допуск. Есть ли в нем какой-то другой недостаток? - person Joshua Ulrich; 21.01.2011
comment
Пожалуйста, проведите сравнительный анализ - я только что проверил, что у меня примерно то же самое для вектора из миллиона униформ. - person hadley; 21.01.2011
comment
@hadley: Я запускал system.time(for(i in 1:1e4) zero_range(x)), где x был из OP. Решение Джона ~ 10x для x, ~ 3x быстрее для y и немного медленнее для runif(1e6). - person Joshua Ulrich; 21.01.2011
comment
10-кратная разница не имеет большого значения, когда вы смотрите на разницу между 0,00023 и 0,000023 секунд - и DWin, вероятно, заявит, что они одинаковы с указанной степенью допуска;) - person hadley; 22.01.2011
comment
Отлично. Что isTRUE делает то, чего all.equal еще не делает? Спасибо. - person PatrickT; 03.11.2017

Почему бы просто не использовать дисперсию:

var(x) == 0

Если все элементы x равны, вы получите отклонение 0.

person Yohan Obadia    schedule 09.03.2016
comment
length(unique(x))=1 оказывается примерно в два раза быстрее, но var лаконичен, что приятно. - person AdamO; 18.07.2017
comment
YohanBadia, у меня есть массив c (-5.532456e-09, 1.695298e-09), и я получаю John test: TRUE ; DWin test: TRUE ; zero-range test: TRUE ; variance test: FALSE, что означает, что все остальные тесты распознают, что значения идентичны в R. Как можно использовать тест дисперсии в этом контексте? - person mjs; 24.01.2020
comment
2 значения в вашем массиве не идентичны. Почему вы хотите, чтобы тест возвращал TRUE? В случае ответа Джона вы проверяете, превышает ли разница определенный порог. В вашем случае разница между двумя значениями очень мала, что может привести к тому, что она окажется ниже установленного вами порога. - person Yohan Obadia; 24.01.2020

Если они все числовые значения, тогда если tol - ваша терпимость, тогда ...

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

это решение вашей проблемы.

РЕДАКТИРОВАТЬ:

Посмотрев на этот и другие ответы, а также протестировав несколько вещей, следующее получается более чем в два раза быстрее, чем ответ DWin.

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

Это немного на удивление быстрее, чем diff(range(x)), поскольку diff не должно сильно отличаться от - и abs с двумя числами. Запрос диапазона должен оптимизировать получение минимума и максимума. И diff, и range - примитивные функции. Но время не врет.

person John    schedule 20.01.2011
comment
Можете ли вы прокомментировать относительные преимущества вычитания среднего по сравнению с делением на него? - person hadley; 22.01.2011
comment
Это проще в вычислительном отношении. В зависимости от системы и того, как R компилируется и векторизуется, это будет выполняться быстрее с меньшим энергопотреблением. Кроме того, когда вы делите на среднее значение, ваш тестируемый результат относительно 1, а при вычитании - 0, что мне кажется приятнее. Кроме того, допуск имеет более прямое толкование. - person John; 22.01.2011
comment
Но дело даже не в том, что деление является сложным, поскольку поиск и сортировка, необходимые для извлечения диапазона, намного более затратны в вычислительном отношении, чем простое вычитание. Я проверил его, и приведенный выше код примерно в 10 раз быстрее, чем функция Hadley zero_range (и ваш ответ - это самый быстрый правильный ответ здесь). Функция сравнения у Дирка очень медленная. Это самый быстрый ответ. - person John; 22.01.2011
comment
Только что видел комментарии Джоша по времени в вашем ответе Хэдли ... У меня нет ситуаций, когда zero_range быстрее. Несоответствие между немного более быстрым (возможно, 20%) и 10-кратным всегда в пользу этого ответа. Было испробовано несколько способов. - person John; 22.01.2011

> 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
person IRTFM    schedule 20.01.2011
comment
Я не думаю, что это так хорошо работает для очень маленьких чисел: x <- seq(1, 10) / 1e10 - person hadley; 21.01.2011
comment
@ Хэдли: ОП попросил решение, которое позволило бы указать допуск, по-видимому, потому, что его не волновали очень маленькие различия. all.equal можно использовать с другими допусками, и ОП, похоже, это понимает. - person IRTFM; 21.01.2011
comment
Я не очень четко выразился - в моем примере есть десятикратная относительная разница между наибольшим и наименьшим числами. Вероятно, вы захотите это заметить! Я думаю, что числовую терпимость необходимо рассчитывать относительно диапазона данных - я не делал этого в прошлом, и это вызывало проблемы. - person hadley; 21.01.2011
comment
Не думаю, что я вас неправильно понял. Я просто подумал, что спрашивающий просит решение, которое игнорировало бы десятикратную относительную разницу для чисел, которые фактически равны нулю. Я слышал, что он просил решения, которое игнорировало бы разницу между 1e-11 и 1e-13. - person IRTFM; 21.01.2011
comment
Я стараюсь давать людям то, что им нужно, а не то, что они хотят;) Но точка зрения принята. - person hadley; 21.01.2011

Вы можете просто проверить all(v==v[1])

person Maya Levy    schedule 27.11.2019
comment
Это отличный вариант, потому что он работает и со строками! Спасибо - person arvi1000; 16.01.2020
comment
Это работает, если в вашем векторе нет NA: x <- c(1,1,NA); all(x == x[1]) возвращает NA, а не FALSE. В таких случаях length(unique(x)) == 1 работает. - person HBat; 10.08.2020

Вы можете использовать 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() по мере необходимости.

person Dirk Eddelbuettel    schedule 20.01.2011
comment
Ужасно неэффективно ... (на моем компьютере миллион чисел занимает около 10 секунд) - person hadley; 21.01.2011
comment
Без сомнений. OP, однако, сомневался, можно ли это сделать вообще. Сделать это хорошо - второй шаг. А вы знаете, где я стою с петлями ... ;-) - person Dirk Eddelbuettel; 21.01.2011
comment
Какие петли классные? ;) - person hadley; 21.01.2011
comment
Что мне нравится в этом подходе, так это то, что его можно использовать с нечисловыми объектами. - person Luciano Selzer; 16.01.2013
comment
compare ‹- function (v) all (sapply (as.list (v [-1]), FUN = function (z) {isTRUE (all.equal (z, v [1]))})) - person N. McA.; 28.03.2013
comment
isTRUE помогает, потому что all.equal иногда возвращает сравнения, а не FALSE - person N. McA.; 28.03.2013
comment
Если вы хотите работать с non_numerical, почему бы просто не разложить объект на множители и не подсчитать количество уровней? кажется очень быстрым на моей машине - person Shape; 18.02.2016

Поскольку я возвращаюсь к этому вопросу снова и снова, вот решение 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
person eddi    schedule 19.07.2013
comment
Это хорошо и +1 для скорости, но я не уверен, что сравнение всех элементов с 1-м элементом является правильным. Вектор может пройти этот тест, но разница между max (x) и min (x) больше, чем точность. Например fast_equal(c(2,1,3), 1.5) - person dww; 06.04.2017
comment
@dww Вы указываете на то, что сравнение не является транзитивным, когда у вас есть проблемы с точностью, т.е. a == b, b == c не обязательно подразумевает a == c, если вы выполняете сравнения с плавающей запятой. Вы можете разделить точность на количество элементов, чтобы избежать этой проблемы, или изменить алгоритм для вычисления min и max и использования этого в качестве условия остановки. - person eddi; 06.04.2017

Я написал специально для этого функцию, которая может проверять не только элементы в векторе, но также может проверять, все ли элементы в списке идентичны. Конечно, он также хорошо обрабатывает символьные векторы и все другие типы векторов. Он также имеет соответствующую обработку ошибок.

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
person Lawrence Lee    schedule 10.07.2015

На самом деле вам не нужно использовать минимальное, среднее или максимальное значение. На основании ответа Джона:

all(abs(x - x[[1]]) < tolerance)
person Community    schedule 03.11.2014

Здесь альтернатива с использованием трюка min, max, но для фрейма данных. В этом примере я сравниваю столбцы, но параметр поля с apply можно изменить на 1 для строк.

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

Если valid == 0, то все элементы одинаковые

person pedrosaurio    schedule 13.08.2015

Другое решение, использующее пакет data.table, совместимое со строками и NA, - это uniqueN(x) == 1

person Daniel V    schedule 21.06.2021