Как я могу найти запись из набора данных, которая наиболее похожа на тестовую запись, которая хранится в другом фрейме данных?

#r

#r

Вопрос:

У меня есть два фрейма данных, похожие на приведенную ниже структуру. Я хочу найти строку из первого фрейма данных, которая была бы ближайшей по значениям к одной единственной записи в моем втором фрейме данных. Поэтому в этом случае я ожидал бы, что строка с командой A будет моим результатом.

 Team    Var1    Var2    Var3
A       4       5       6 
B       10      10      10
C       15      14      18


Team    Var1    Var2    Var3
D       5       5       4
  

Я подумал, что могу использовать kNN с k = 1 для решения проблемы? По сути, я пытаюсь найти запись с наименьшим различием. Я не уверен, что это правильный подход?

Столбцы с 9 по 46 в моих данных являются числовыми. Следовательно, я только что извлек их, чтобы получить train amp; test

 data_train <- train[,c(9:46)]
data_test <- test[,c(9:46)]
  

Столбец 2 — это название команды, как показано ранее

 data_train_target <- train[,c(2)]

similar <- knn(train = data_train, test = data_test, cl = data_train_target, k=1)
  

Однако я не получаю ожидаемый результат, т.Е.

Ответ №1:

вы могли бы использовать dist() функцию, которая вычисляет евклидово расстояние.

предположим, что следующие фреймы данных, как вы упомянули выше:

 > df1 <- data.frame(Team = c("A","B","C"),Var1=c(4,10,15),Var2=c(5,10,14),Var3=c(6,10,18))
> df1
  Team Var1 Var2 Var3
1    A    4    5    6
2    B   10   10   10
3    C   15   14   18

> df2 <- data.frame(Team = "D",Var1=5,Var2=5,Var3=4)
> df2
  Team Var1 Var2 Var3
1    D    5    5    4
  

Мы можем объединить 2 фрейма данных в единую матрицу с соответствующей строкой в качестве первой строки:

 > m <- rbind(df2,df1)
> m
  Team Var1 Var2 Var3
1    D    5    5    4
2    A    4    5    6
3    B   10   10   10
4    C   15   14   18
  

Затем мы используем dist() для вычисления евклидова расстояния для каждой комбинации строк, зная, что строка, до которой мы хотим найти наименьшее расстояние, равна строке 1.

 > dm <- dist(m)
Warning message:
In dist(m) : NAs introduced by coercion
> dm
          1         2         3
2  2.581989                    
3 10.708252 10.132456          
4 22.420229 21.478672 11.832160
  

Чтобы найти, какая строка ближе всего к строке 1, мы можем использовать which.min() первый столбец. Сначала мы должны преобразовать объект dm в матрицу.

 > dm <- as.matrix(dm)
> dm
          1         2        3        4
1  0.000000  2.581989 10.70825 22.42023
2  2.581989  0.000000 10.13246 21.47867
3 10.708252 10.132456  0.00000 11.83216
4 22.420229 21.478672 11.83216  0.00000
  

Мы видим, что в виде матрицы значения расстояния дублируются, чтобы заполнить верхний треугольник, и расстояние также вычисляется от каждой строки до самой себя (диагональ). Чтобы найти строку с наименьшим расстоянием до 1, мы смотрим на первый столбец этой матрицы и исключаем первую строку (которая представляет собой расстояние от строки 1 до самой себя).

 > dm[-1,1]
        2         3         4 
 2.581989 10.708252 22.420229 
  

Мы можем which.min() использовать этот результат, чтобы определить строку, ближайшую к строке 1.

 > which.min(dm[1,-1])
2 
1 
  

Возвращаемое здесь значение выглядит немного странно при печати. «2» относится к имени элемента списка, потому что это была строка 2 нашей объединенной матрицы (from cbind(df2,df1) ), но фактическое значение, возвращаемое функцией, равно «1», что является ближайшей строкой из df1.

Все эти шаги можно объединить в один вызов с:

 > which.min(as.matrix(dist(rbind(df2,df1)))[1,-1])
2 
1 
  

Вы упомянули KNN в своем OP. Этот код аналогичен тому, что будет делать модель KNN, находя ближайших соседей, измеренных некоторым расстоянием в N-мерном пространстве (в вашем случае 3-мерном).

Ответ №2:

Я думаю, что осторожный итеративный подход хорош, но использование dist само по себе приведет к большему количеству вычислений, чем необходимо. (Соединяя rbind один фрейм с другим, вы получаете расстояния между всеми строками в первом… когда вам просто нужны расстояния между строками в первой и строками во второй.)

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

 closest <- function(y, x) {
  inds <- outer(seq_len(nrow(x)), seq_len(nrow(y)), function(a, b) {
    rowSums(abs(x[a,] - y[b,])^2)
  })
  apply(inds, 2, which.min)
}
  

Используя ваши два фрейма в качестве отправных точек (я дополню второй только ради векторизации и полноты):

 
x1 <- read.table(header=TRUE, stringsAsFactors=FALSE, text="
Team    Var1    Var2    Var3
A       4       5       6 
B       10      10      10
C       15      14      18")

x2 <- read.table(header=TRUE, stringsAsFactors=FALSE, text="
Team    Var1    Var2    Var3
D       5       5       4
E       15       5       4
F       15       55       4
G       15       55       24")
  

Найти x2 ближайшую строку из числа x1 :

 closest(x2[,-1], x1[,-1])
# [1] 1 2 3 3
  

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

 closest2 <- function(y, x) {
  inds <- outer(seq_len(nrow(x)), seq_len(nrow(y)), function(a, b) {
    rowSums(abs(x[a,] - y[b,])^2)
  })
  x[apply(inds, 2, which.min),,drop = FALSE]
}
closest2(x2[,-1], x1[,-1])
#     Var1 Var2 Var3
# 1      4    5    6
# 2     10   10   10
# 3     15   14   18
# 3.1   15   14   18
  

Я упорядочил переменные так, как я это сделал, чтобы они хорошо работали в %>% конвейере, как в:

 x %>%
  do_something(.) %>%
  closest2(., some_reference_frame)
  

Ответ №3:

Я не уверен, нужен ли вам для этого какой-либо метод машинного обучения. Разве простой математики недостаточно?

Предположим, что у вас есть два df1 фрейма данных и df2 . Как вы упомянули df2 , имеет только одну запись, поэтому мы можем вычесть это значение из каждой строки df1 , взять абсолютное значение и найти строку с минимальной разницей, которая даст вам строку 1 из df1 .

 df1[which.min(rowSums(abs(df1[-1] - df2[rep(1, nrow(df1)), -1]))), ]

#  Team Var1 Var2 Var3
#1    A    4    5    6
  

Давайте разберем это, чтобы понять шаг за шагом

Повторите строки, df2 чтобы они имели ту же длину, что и df1

 df2[rep(1, nrow(df1)), -1]
#    Var1 Var2 Var3
#1      5    5    4
#1.1    5    5    4
#1.2    5    5    4
  

Вычесть df2 из df1

 df1[-1] - df2[rep(1, nrow(df1)), -1]
#  Var1 Var2 Var3
#1   -1    0    2
#2    5    5    6
#3   10    9   14
  

Возьмите абсолютное значение фрейма данных и используйте rowSums для вычисления абсолютной разницы в каждой строке df1 из df2

 rowSums(abs(df1[-1] - df2[rep(1, nrow(df1)), -1]))
#[1]  3 16 33
  

Выберите строку с минимальной разницей, используя which.min

 which.min(rowSums(abs(df1[-1] - df2[rep(1, nrow(df1)), -1])))
#[1] 1
  

Наконец, подмножество этой строки из df1

 df1[which.min(rowSums(abs(df1[-1] - df2[rep(1, nrow(df1)), -1]))), ]
#  Team Var1 Var2 Var3
#1    A    4    5    6
  

Как упоминалось @r2evans, если в нем больше строк, df2 и вы хотите найти ближайшую строку df1 для каждой строки, df2 мы можем использовать lapply цикл по индексу каждой строки и получить список ближайших строк.

 lapply(seq_len(nrow(df2)), function(i) 
    df1[which.min(rowSums(abs(df1[-1] - df2[rep(i, nrow(df1)), -1]))), ])
  

данные

 df1 <- structure(list(Team = structure(1:3, .Label = c("A", "B", "C"
), class = "factor"), Var1 = c(4L, 10L, 15L), Var2 = c(5L, 10L, 
14L), Var3 = c(6L, 10L, 18L)), class = "data.frame", row.names = c(NA, 
-3L))

df2 <- structure(list(Team = structure(1L, .Label = "D", class = "factor"), 
Var1 = 5L, Var2 = 5L, Var3 = 4L), class = "data.frame", row.names = c(NA,-1L))
  

Комментарии:

1. Если второй фрейм не состоит из 1 строки, это может быть полезно sapply для индексов меньшего фрейма для сравнения со всем первым фреймом.

2. Спасибо Ronak и @r2evans. Я понял, что мне нужно использовать библиотеку FNN. Теперь работает отлично.