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

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

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 в моих данных являются числовыми.Поэтому я только что извлек их для обучения и тестирования

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)

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

Ответы [ 3 ]

2 голосов
/ 15 апреля 2019

вы можете использовать функцию 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 нашей объединенной матрицы (от cbind(df2,df1)), но реальное значение, возвращаемое функцией, равно «1», что является ближайшей строкой из df1.

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

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

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

2 голосов
/ 15 апреля 2019

Я думаю, что предостерегающий итеративный подход - это хорошо, но использование 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)
1 голос
/ 15 апреля 2019

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

Давайте предположим, что у вас есть два фрейма данных 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]))), ])

data

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))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...