Сравните строки фрейма данных с «главной» строкой - PullRequest
1 голос
/ 12 марта 2019

Сегодня, разрабатывая ответ на чей-то вопрос, я понял, что какое-то время держался за циклы, что является грехом, учитывая, что R известен тем, насколько хорошо он может использовать векторизованные функции.

Скажем, у нас есть фрейм данных, в котором мы храним наши «условия», это строки данных, которые мы хотим использовать для сравнения с нашими данными.Если одна из наших «главных» строк полностью совпадает со строкой фрейма данных, мы хотим напечатать «match».

# Data to be checked
DF<-data.frame(A=c(1,4,5,6),B=c(1,5,4,2),C=c(5,6,3,2),D=c(1,2,3,4),
               E=c(4,2,3,4))

# Our condition/master data
Compare<-data.frame(A=4,B=5,C=6,D=2,E=2)

Используя цикл for, это простая задача, но она облагается налогом, как только мысоздавать все большие и большие наборы данных:

#This Works, but ew
for(i in 1:length(DF)){
    ifelse(DF[i,]==Compare, print("match"),print("no match"))
}
[1] "no match"
[1] "match"
[1] "no match"
[1] "no match"

Мой вопрос: как бы кто-то сделал это без использования цикла for в базе R .Я понимаю, что такие пакеты, как compare и sqldf могут сделать это легко, но мне интересно, может ли кто-нибудь сделать это на базе R. Я думаю, что это очевидно, но я не могу понять это.

РЕДАКТИРОВАТЬ: Как @Frank ответил в комментариях rowSums(DF == Compare[rep(1, nrow(DF)), ]) == ncol(DF) отлично работает, если все данные являются числовыми.Давайте усложним ситуацию и скажем, что в нашем главном списке есть также строки данных для сопоставления

# Sample Data
DF<-data.frame(
A=c("N","J","K","L"),
B=c(1,3,4,2),
C=c(5,4,3,2),
D=c(1,5,3,4),
E=c(4,2,3,4),stringsAsFactors=F)

Compare<-data.frame(A="J",B=3,C=4,D=5,E=2)

#This Works
for(i in 1:length(DF)){
    ifelse(DF[i,]==Compare, print("match"),print("no match"))
}

Исследование:

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

Извлечь все строки всопоставление фрейма данных со строками другого фрейма данных

Что-то, что ниже, делает это, но невозможно масштабировать.

which(DF$A == Compare$A & DF$B==Compare$B & DF$C == Compare$C) #etc.
[1] 2

Возвращать строку фрейма данных на основе значенияв колонке - R

Ответы [ 4 ]

4 голосов
/ 12 марта 2019

Просто используйте merge:

Compare$in_compare = "match"
merge(DF, Compare, all.x = TRUE)
#   A B C D E in_compare
# 1 1 1 5 1 4       <NA>
# 2 4 5 6 2 2      match
# 3 5 4 3 3 3       <NA>
# 4 6 2 2 4 4       <NA>

Если вы предпочитаете не изменять Compare, тогда мы можем использовать transform для модификации на месте в однострочнике:

merge(DF, transform(Compare, in_compare = "match"), all.x = TRUE)

Сроки

set.seed(47)
data(diamonds, package = "ggplot2")
diam = unique(diamonds)
DF = diam[rep(1:nrow(diam), times = 10), ]
# 538k rows, 10 columns

compare_rows = sample(nrow(diam), size = 10000)
compare_df = diam[compare_rows, ]

# test:
merge_result = merge(DF, transform(compare_df, result = "match"), all.x = TRUE)
apply_paste_result = transform(DF, result = apply(DF, 1, paste, collapse = " ") %in% apply(compare_df, 1, paste, collapse=" "))
do_call_paste_result = transform(DF, result = do.call("paste", DF) %in% do.call("paste", compare_df))

sum(merge_result$result == "match", na.rm = TRUE) #10000
sum(apply_paste_result$result) # 0!!!
sum(do_call_paste_result$result) #10000

Немного покопался, похоже, что метод apply_paste плохо работает с десятичными числами. Я не копал достаточно глубоко, чтобы понять, почему или попытаться отладить, но это явно не работает здесь. Тот же код отлично работает на игрушечном примере. Если присмотреться, do.call("paste", head(DF)) удивительно дает результат, отличный от apply(head(DF), 1, paste, collapse = " "), в 4-й строке метод apply имеет "4.2", где метод do.call имеет "4.20" ... возможно, это связано. Я предполагаю, что что-то происходит, когда apply приводит кадр данных к матрице, но, как я уже сказал, это кажется удивительным.

Итак, опуская этот метод из теста, похоже, что do.call метод вставки самый быстрый! Лично я думаю, что я все еще, вероятно, пошел бы с merge, поскольку он кажется подходящим инструментом для работы ... странная ошибка в методе apply_paste иллюстрирует риск умных хаков.

Если производительность на самом деле , то проблема data.table будет значительно быстрее, чем любой из этих методов:

library(data.table)
DT = as.data.table(DF)
compare_dt = as.data.table(compare_df)
dt_result = merge(DT, compare_dt[, result := "match"], all.x = TRUE)
sum(dt_result$result == "match", na.rm = TRUE) #10000


library(microbenchmark)
microbenchmark(
  merge = merge(DF, transform(Compare, in_compare = "match"), all.x = TRUE),
  do_call_paste = transform(DF, result = do.call("paste", DF) %in% do.call("paste", compare_df)),
  dt_result = merge(DT, compare_dt[, result := "match"], all.x = TRUE)
  times = 20
)
# Unit: milliseconds
#           expr       min        lq      mean    median       uq       max neval
#          merge 5905.6842 6171.2211 6473.5351 6253.0117 6946.098 7246.2727    10
#  do_call_paste 4269.8849 4635.2662 4850.4076 4824.5035 5196.911 5289.6226    10
#      dt_result  460.4646  494.7083  555.8484  538.2203  614.905  683.1518    10
3 голосов
/ 12 марта 2019

Это будет работать в baseR

DF[ do.call("paste", DF) %in% do.call("paste", Compare), ]

#   A B C D E
# 2 4 5 6 2 2

или просто:

do.call("paste", DF) %in% do.call("paste", Compare)
#[1] FALSE  TRUE FALSE FALSE
2 голосов
/ 12 марта 2019

Снова рассмотрим merge, так как вы можете указать all.x (т.е. LEFT JOIN) для сохранения исходных данных. Ниже используются within и transform в качестве менеджеров контекста для добавления / обновления столбца result и возврата обновленного фрейма данных:

final_df <- within(merge(DF, transform(Compare, result = "match"), all.x=TRUE),
                   result <- ifelse(is.na(result), "no match", "match"))    

final_df
#   A B C D E   result
# 1 1 1 5 1 4 no match
# 2 4 5 6 2 2    match
# 3 5 4 3 3 3 no match
# 4 6 2 2 4 4 no match
1 голос
/ 12 марта 2019
apply(DF,1,paste,collapse=" ") %in% apply(Compare,1,paste,collapse=" ")
[1] FALSE  TRUE FALSE FALSE

Это обман, потому что apply сам по себе является циклом, но это все же намного быстрее, чем ваше решение:

Unit: microseconds
 expr      min       lq       mean    median         uq       max neval cld
  iod  375.289   488.41   627.6017   570.742   705.7295  2050.474   100  a 
   op 9070.273 10491.32 14143.7312 11770.471 15281.4865 96645.749   100   b
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...