Самый быстрый способ найти второе (третье ...) самое высокое / самое низкое значение в векторе или столбце


161

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

Например, есть ли более быстрый способ получить второе по величине значение?


В комплект пакета на CRAN имеет topnфункцию , которая быстрее sort, orderи nth. Посмотри документацию.
Suresh_Patel

Ответы:


25

В Rfast есть функция nth_element, которая делает именно то, что вы просите, и работает быстрее, чем все реализации, описанные выше.

Также рассмотренные выше методы, основанные на частичной сортировке, не поддерживают поиск k наименьших значений

Rfast::nth(x, 5, descending = T)

Вернет 5-й по величине элемент х, в то время как

Rfast::nth(x, 5, descending = F)

Вернет 5-й самый маленький элемент х

Приведенные ниже контрольные показатели в отношении наиболее популярных ответов.

Для 10 тысяч номеров:

N = 10000
x = rnorm(N)

maxN <- function(x, N=2){
    len <- length(x)
    if(N>len){
        warning('N greater than length(x).  Setting N=length(x)')
        N <- length(x)
    }
    sort(x,partial=len-N+1)[len-N+1]
}

microbenchmark::microbenchmark(
    Rfast = Rfast::nth(x,5,descending = T),
    maxn = maxN(x,5),
    order = x[order(x, decreasing = T)[5]]
)

Unit: microseconds
  expr      min       lq      mean   median        uq       max neval
 Rfast  160.364  179.607  202.8024  194.575  210.1830   351.517   100
  maxN  396.419  423.360  559.2707  446.452  487.0775  4949.452   100
 order 1288.466 1343.417 1746.7627 1433.221 1500.7865 13768.148   100

Для 1 миллиона номеров:

N = 1e6 #evaluates to 1 million
x = rnorm(N)

microbenchmark::microbenchmark(
    Rfast = Rfast::nth(x,5,descending = T),
    maxN = maxN(x,5),
    order = x[order(x, decreasing = T)[5]]
)

Unit: milliseconds
  expr      min        lq      mean   median        uq       max neval
 Rfast  89.7722  93.63674  114.9893 104.6325  120.5767  204.8839   100
  maxN 150.2822 207.03922  235.3037 241.7604  259.7476  336.7051   100
 order 930.8924 968.54785 1005.5487 991.7995 1031.0290 1164.9129   100

8
Ницца! Обычно, когда я вижу, что пользователь с относительно низкой репутацией добавляет ответ на старый популярный вопрос, он довольно низкого качества. Это, с другой стороны, является отличным дополнением. Я сделал несколько правок для чтения, но это выглядит великолепно!
Грегор Томас

3
Следует упомянуть, что Rfast::nthможет возвращать несколько элементов (например, 8-й и 9-й по величине элементы), а также индексы этих элементов.
Яша

3
Что мне нравится в решении Rfast, так это то, что в пакете также есть легко реализуемое решение для каждой строки или столбца.
Джей

195

Используйте partialаргумент sort(). Для второго по величине значения:

n <- length(x)
sort(x,partial=n-1)[n-1]

4
В чем преимущество этого метода по сравнению с тем, sort(x, TRUE)[2]что описано в ответе @ Abrar, помимо несоблюдения ограничения в вопросе?
Хью

5
Я использовал этот метод, но получаю следующую ошибку: Error in sort.int(x, na.last = na.last, decreasing = decreasing, ...) : index 4705 outside bounds Любая идея, в чем может быть проблема? Некоторые детали: My x - это числовой вектор длиной 4706 с некоторыми NAs в данных. Я попытался получить второе по величине значение в векторе, используя тот же код, который предложил @RobHyndman.
Шрирамн

Почему бы вам не отсортировать по убыванию и взять второе из двух значений? Разве это не будет быстрее?
JWG

3
Аргумент уменьшения не совместим с частичной сортировкой.
Роб Хиндман

7
Хотя decreasingаргумент не совместим с частичной сортировкой, вы всегда можете -sort(-x, partial=n-1)[n-1]; это логически то же самое и занимает значительно меньше времени, чем sort(x, decreasing=TRUE)[n-1].
r2evans

52

Немного более медленная альтернатива, только для записей:

x <- c(12.45,34,4,0,-234,45.6,4)
max( x[x!=max(x)] )
min( x[x!=min(x)] )

Было бы удивительно, если бы это было быстрее, чем сортировка всего вектора и получение n-1-го значения!
JWG

@jwg Это O (n), поэтому оно должно быть быстрее, чем сортировка больших наборов данных.
Музейный

Работает лучше с NA, чем с другим принятым ответом - просто используйте «na.rm = TRUE» в качестве аргумента для функции «min».
Яир

2
Мне кажется, вы можете добиться значительного улучшения скорости с помощью небольшой модификации:max(x[-which.max(x)])
sindri_baldur

31

Я обернул ответ Роба в чуть более общую функцию, которую можно использовать, чтобы найти 2-й, 3-й, 4-й (и т. Д.) Максимум:

maxN <- function(x, N=2){
  len <- length(x)
  if(N>len){
    warning('N greater than length(x).  Setting N=length(x)')
    N <- length(x)
  }
  sort(x,partial=len-N+1)[len-N+1]
}

maxN(1:10)

1
Прохладно. Это использование особенно полезно maxN(1:10, 1:3)(я бы по умолчанию установил N на 1)
PatrickT

16

Вот простой способ найти индексы N самых маленьких / самых больших значений в векторе (пример для N = 3):

N <- 3

N Наименьший:

ndx <- order(x)[1:N]

N Largest:

ndx <- order(x, decreasing = T)[1:N]

Таким образом, вы можете извлечь значения как:

x[ndx]

Это выполняется в L log L time, где L - длина x. Я думаю, что пользователь надеялся на метод, который выполняется в журнале L времени.
Арсмат

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

1
Лучший теоретический и принятый метод (надеюсь) выполняется за время O (L), а не O (log L). Этот работает в O (L log L).
Валентина

6

Для n-го наивысшего значения,

sort(x, TRUE)[n]

9
ОП уже сказал в своем посте, что это решение, которое он не хотел использовать: «кроме сортировки всего вектора и выбора значения x из этого вектора».
Пол Химстра

3

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

system.time({a=runif(1000000);m=max(a);i=which.max(a);b=a[-i];max(b)})
   user  system elapsed 
  0.092   0.000   0.659 

system.time({a=runif(1000000);n=length(a);sort(a,partial=n-1)[n-1]})
   user  system elapsed 
  0.096   0.000   0.653 

2

Вот самый простой способ, который я нашел,

num <- c(5665,1615,5154,65564,69895646)

num <- sort(num, decreasing = F)

tail(num, 1)                           # Highest number
head(tail(num, 2),1)                   # Second Highest number
head(tail(num, 3),1)                   # Third Highest number
head(tail(num, n),1)                   # Generl equation for finding nth Highest number

1

Когда я недавно искал R возвращающую индексы верхних чисел N max / min в данном векторе, я был удивлен, что такой функции нет.

И это нечто очень похожее.

Решение грубой силы, использующее функцию base :: order, кажется самым простым.

topMaxUsingFullSort <- function(x, N) {
  sort(x, decreasing = TRUE)[1:min(N, length(x))]
}

Но он не самый быстрый, если ваше значение N относительно мало по сравнению с длиной вектора x .

С другой стороны, если N действительно мало, вы можете использовать функцию base :: whichMax итеративно, и в каждой итерации вы можете заменить найденное значение на -Inf

# the input vector 'x' must not contain -Inf value 
topMaxUsingWhichMax <- function(x, N) {
  vals <- c()
  for(i in 1:min(N, length(x))) {
    idx      <- which.max(x)
    vals     <- c(vals, x[idx]) # copy-on-modify (this is not an issue because idxs is relative small vector)
    x[idx]   <- -Inf            # copy-on-modify (this is the issue because data vector could be huge)
  }
  vals
}

Я полагаю, что вы видите проблему - природу копирования при модификации R. Таким образом, это будет работать лучше для очень очень очень малого N (1,2,3), но будет быстро замедляться при больших значениях N. И вы перебираете все элементы вектора x N раз.

Я думаю, что лучшим решением в чистом R является использование частичного base :: sort .

topMaxUsingPartialSort <- function(x, N) {
  N <- min(N, length(x))
  x[x >= -sort(-x, partial=N)[N]][1:N]
}

Затем вы можете выбрать последний ( N- й) элемент из результата функций, описанных выше.

Примечание: функции, определенные выше, являются просто примерами - если вы хотите использовать их, вы должны проверить / рассудить входные данные (например, N> length (x) ).

Я написал небольшую статью о чем-то очень похожем (получить индексы верхних значений N max / min вектора) по адресу http://palusga.cz/?p=18 - здесь вы можете найти некоторые тесты аналогичных функций, которые я определил выше.



0
topn = function(vector, n){
  maxs=c()
  ind=c()
  for (i in 1:n){
    biggest=match(max(vector), vector)
    ind[i]=biggest
    maxs[i]=max(vector)
    vector=vector[-biggest]
  }
  mat=cbind(maxs, ind)
  return(mat)
}

эта функция вернет матрицу с верхними значениями n и их индексами. надеюсь, это поможет VDevi-Chou


0

Это найдет индекс N-го наименьшего или наибольшего значения во входном числовом векторе x. Установите bottom = TRUE в аргументах, если вы хотите, чтобы N-е снизу, или bottom = FALSE, если вы хотите, чтобы N-й сверху. N = 1 и bottom = TRUE эквивалентны which.min, N = 1 и bottom = FALSE эквивалентны which.max.

FindIndicesBottomTopN <- function(x=c(4,-2,5,-77,99),N=1,bottom=FALSE)
{

  k1 <- rank(x)
  if(bottom==TRUE){
    Nindex <- which(k1==N)
    Nindex <- Nindex[1]
  }

  if(bottom==FALSE){
    Nindex <- which(k1==(length(x)+1-N))
    Nindex <- Nindex[1]
  }

  return(Nindex)
}

0

У dplyr есть функция nth, где первый аргумент - вектор, а второй - место, которое вы хотите. Это касается и повторяющихся элементов. Например:

x = c(1,2, 8, 16, 17, 20, 1, 20)

Нахождение второго по величине значения:

 nth(unique(x),length(unique(x))-1)

[1] 17

2
это быстро ...?
Бен Болкер,

2
внутренне это использует x[[order(order_by)[[n]]]]- так что требуется сортировка всего вектора. Так что это будет не так быстро, как принятый ответ.
Бен Болкер,

5
но он использует sort с аргументом частичного = (который меняет все)
Бен Болкер

@BenBolker, который подразумевает, что ответ Паоло или Роба может быть использован для улучшения dplyr::nth()? bench::mark(max(x[-which.max(x)]), x[[order(-x)[[2]]]] ), nth()Кажется , почти в 10 раз медленнее, где length(x)есть 3 миллиона человек .
sindri_baldur

-1

Вы можете идентифицировать следующее более высокое значение с cummax(). Например, если вы хотите указать местоположение каждого нового более высокого значения, вы можете передать свой вектор cummax()значений в diff()функцию, чтобы определить местоположения, в которых это cummax()значение изменилось. скажем, у нас есть вектор

v <- c(4,6,3,2,-5,6,8,12,16)
cummax(v) will give us the vector
4  6  6  6  6  6  8 12 16

Теперь, если вы хотите найти место изменения, у cummax()вас есть много вариантов, которые я склонен использовать sign(diff(cummax(v))). Вы должны скорректировать потерянный первый элемент из-за diff(). Полный код для вектора vбудет:

which(sign(diff(cummax(v)))==1)+1

Я думаю, что вы неправильно поняли вопрос. Цель состоит в том, чтобы найти, скажем, второе по величине значение. Как это поможет вам получить от v до 12 ... и за третье место до 8?
Франк

-1

Вы можете использовать sortключевое слово следующим образом:

sort(unique(c))[1:N]

Пример:

c <- c(4,2,44,2,1,45,34,2,4,22,244)
sort(unique(c), decreasing = TRUE)[1:5]

даст первые 5 максимальных чисел.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.