#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. Теперь работает отлично.