Ближайшее значение к определенному столбцу в R

Я хотел бы найти ближайшее значение к столбцу x3 ниже.

data=data.frame(x1=c(24,12,76),x2=c(15,30,20),x3=c(45,27,15))
data
  x1 x2 x3
1 24 15 45
2 12 30 27
3 76 20 15

Таким образом, желаемый результат будет

Closest_Value_to_x3
   24
   30
   20

Пожалуйста помоги. Спасибо


person melik    schedule 23.01.2019    source источник


Ответы (4)


Используйте max.col(-abs(data[, 3] - data[, -3])), чтобы найти позиции столбцов ближайших значений и использовать этот результат как часть матрицы для извлечения желаемых значений из ваших данных. Матрица возвращается cbind

col <- 3
data[, -col][cbind(1:nrow(data),
                   max.col(-abs(data[, col] - data[, -col])))]
#[1] 24 30 20
person markus    schedule 23.01.2019
comment
Хороший ответ. Хотя я не думаю, что внешнее подмножество [, 1:2] необходимо, поскольку вы уже сделали это подмножество внутри вызова abs(). - person Rich Scriven; 23.01.2019
comment
@RichScriven Спасибо за ваш комментарий. Думаю, я включил его на тот случай, если OP передумает и захочет найти значения, наиболее близкие, скажем, к столбцу 1, и в этом случае нам понадобится подмножество [, 2:3]. - person markus; 23.01.2019

tidyverse решение:

data %>%
  rowid_to_column() %>%
  gather(var, val, -c(x3, rowid)) %>%
  mutate(temp = x3 - val) %>%
  group_by(rowid) %>%
  filter(abs(temp) == min(abs(temp))) %>%
  ungroup() %>%
  select(val)

    val
  <dbl>
1    24
2    30
3    20

Во-первых, он добавляет идентификатор строки. Во-вторых, он преобразует данные из широких в длинные. В-третьих, он вычисляет разницу между «x3» и другими переменными. Наконец, он группируется по идентификатору строки и сохраняет строки, абсолютная разница в которых наименьшая.

Or:

data %>%
  rowid_to_column() %>%
  gather(var, val, -c(x3, rowid)) %>%
  mutate(temp = x3 - val) %>%
  group_by(rowid) %>%
  filter(abs(temp) == min(abs(temp))) %>%
  ungroup() %>%
  pull(val)

[1] 24 30 20

Или используя подход, изначально предложенный @markus (предполагается, что ваши столбцы названы "x"):

data %>%
 mutate(temp = paste0("x", max.col(-abs(.[, -3] - .[, 3])))) %>%
 rowwise() %>%
 summarise(val = eval(as.symbol(temp)))

    val
  <dbl>
1   24.
2   30.
3   20.

Во-первых, он оценивает индекс столбца переменной, где абсолютная разница в отношении «x3» является наименьшей, и объединяет ее с «x». Затем он оценивает комбинацию x и индекса столбца как переменную и возвращает соответствующее значение.

Также заимствуя идею из @markus (не предполагая, что ваши столбцы названы "x"):

data %>%
 mutate(temp = max.col(-abs(.[, -3] - .[, 3]))) %>%
 rowwise %>%
 mutate(temp = names(.)[[temp]]) %>%
 summarise(val = eval(as.symbol(temp)))

Во-первых, он оценивает индекс столбца переменной, где абсолютная разница в отношении «x3» наименьшая. Во-вторых, он возвращает имя столбца на основе индекса столбца. Наконец, он оценивает его как переменную и возвращает соответствующее значение.

Или вариант, в котором вы можете ссылаться на переменную "x3" по ее имени, а не по индексу столбца (основная идея все еще от @markus):

data %>%
 mutate(temp = max.col(-abs(.[, !grepl("x3", colnames(.))] - .[, grepl("x3", colnames(.))]))) %>% 
 rowwise %>%
 mutate(temp = names(.)[[temp]]) %>%
 summarise(val = eval(as.symbol(temp)))
person tmfmnk    schedule 23.01.2019
comment
Мне нравится, что я всегда могу рассчитывать на ваш подход, но иногда они выглядят такими сложными и устрашающими. Все равно здорово! - person NelsonGon; 23.01.2019
comment
@NelsonGon иногда становится действительно многословным, это правда. Но верно также и то, что tidyverse aprroaches в целом не те, которые содержат самый короткий код. В любом случае, спасибо за комплимент :) - person tmfmnk; 23.01.2019
comment
Это отличное решение, не единственное, и я бы не стал его писать. Вы можете сделать код менее подробным, следуя естественной логике другого ответа, без необходимости изменять форму данных. - person Konrad Rudolph; 23.01.2019
comment
@Konrad Rudolph Я согласен с вами, однако я не представляю этот пост как решение tidyverse. Кроме того, я не говорю, что эту конкретную проблему нельзя решить, используя tidyverse с гораздо менее подробным кодом. Я просто говорю, что tidyverse решения, как правило, не с наименее подробным кодом. - person tmfmnk; 23.01.2019
comment
@tmfmnk Вы совершенно правы, но для пояснения, я думаю, что в данном случае вы мучаете dplyr, а он ни в чем не признается. Вы могли бы сделать более простой сингл mutate: data %>% mutate(d = .[, -3][cbind(row_number(), max.col(- abs(.[, 3] - .[, -3])))]) - у меня возникло бы искушение ввести временный столбец для хранения результата max.col, но в остальном все. - person Konrad Rudolph; 24.01.2019
comment
Думаю, в данном случае вы мучаете dplyr, и он ни в чем не признается, это могло быть на fortunes. - person RLave; 24.01.2019
comment
@Konrad Rudolph, код в вашем посте - это буквальная транскрипция кода от Маркуса. Это одна линия, и она выполняет свою работу, да, но, на мой взгляд, она не отражает менталитет tidyverse. В любом случае, спасибо за замечания :) - person tmfmnk; 24.01.2019
comment
@tmfmnk «Это не представитель менталитета tidyverse» - ну, точнее, он не отражает менталитет dplyr, потому что этот пакет специально разработан для обработки аккуратных данных ( т.е. длинные данные). И проблема OP - это, в частности, проблема вычисления матрицы, для решения которой dplyr намеренно не предназначен. И в этом моя точка зрения. - person Konrad Rudolph; 24.01.2019

Вот еще один подход с использованием matrixStats

x <- as.matrix(data[,-3L])
y <- abs(x - .subset2(data, 3L))
x[matrixStats::rowMins(y) == y]
# [1] 24 30 20

Или в base с помощью vapply

x <- as.matrix(data[,-3L])
y <- abs(x - .subset2(data, 3L))
vapply(1:nrow(data), 
       function(k) x[k,][which.min(y[k,])], 
       numeric(1))
# [1] 24 30 20
person niko    schedule 23.01.2019

Определите функцию closest_to_3, которая работает с вектором и возвращает значение в векторе, ближайшем к третьему члену:

closest_to_3 <- function(v) v[-3][which.min(abs( v[-3]-v[3] ))]

(Идиома v[-3] удаляет 3-й член из v.) Затем примените эту функцию к каждой строке вашего фрейма данных:

apply(data, 1, closest_to_3)
#[1] 24 30 20
person grand_chat    schedule 23.01.2019