векторное нахождение соседей по строкам фрейма данных - PullRequest
0 голосов
/ 12 сентября 2018

У меня есть два фрейма данных, ниже приведена небольшая выборка каждого из них:

df1 <- data.frame(a1= c(3,4), a2 = c(8, 8), a3 = c(4, 18), a4 = c(9,9), a5 = c(17, 30))

df2 <- data.frame(a1 = c(2,2,2,3,3,3,4,4,4), a2 = c(7,7,7,7,7,7,7,7,7), 
                 a3 = c(4,4,4,4,4,4,4,4,4), a4 = c(10,10,10, 10, 10, 10, 10,10,10), 
                 a5 = c(15,16,17, 15, 16, 17, 15, 16, 17))

Я хотел бы проверить для каждой строки df1, есть ли у нее «соседи» в df2где под соседями я подразумеваю наблюдения, которые отличаются не более чем на 1 в каждом столбце (в абсолютном значении).Так, например, строка 2 из df2 является соседом строки 1 в df1.

В настоящее время я делаю это следующим образом:

sweep(as.matrix(df2), 2, as.matrix(df1[1,]), "-")

Для строки 1 изdf1, и я должен повторить это для каждой строки df1. Обратите внимание, что df2 и df1 не имеют одинаковое количество строк.

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

Ответы [ 4 ]

0 голосов
/ 12 сентября 2018

Решение с использованием library(sqldf):

library(sqldf)

sqldf( "select df2.*, df1.rowid as df1_idx
        from df2 left join df1
           on df2.a1 between df1.a1-1 and df1.a1+1
          and df2.a2 between df1.a2-1 and df1.a2+1
          and df2.a3 between df1.a3-1 and df1.a3+1
          and df2.a4 between df1.a4-1 and df1.a4+1
          and df2.a5 between df1.a5-1 and df1.a5+1")

  a1 a2 a3 a4 a5 df1_idx
1  2  7  4 10 15     NA
2  2  7  4 10 16      1
3  2  7  4 10 17      1
4  3  7  4 10 15     NA
5  3  7  4 10 16      1
6  3  7  4 10 17      1
7  4  7  4 10 15     NA
8  4  7  4 10 16      1
9  4  7  4 10 17      1

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

library(sqldf)

cnames <- colnames(df1)

# main body of your sql
sql_main <- "select df2.*, df1.rowid as df1_idx
            from df2 left join df1
            on 1=1"

# join conditions (which will be added to above)
join_conditions <- 
  paste0( ' and df2.', cnames, ' BETWEEN df1.', cnames, '-1',
                                   ' AND df1.', cnames, '+1',
          collapse = '')

sql <- paste(sql_main, join_conditions)

sqldf(sql)
0 голосов
/ 12 сентября 2018

Здесь возможен data.table подход с использованием неравных объединений

library(data.table)
cols <- names(df2)

#convert into data.table and add row index for clarity
setDT(df1)[, rn1 := .I]
setDT(df2)[, rn2 := .I]

#create a lower (-1) and upper (+1) bound on each column
bandsNames <- paste0(rep(cols, each=2L), "_", rep(c("lower", "upper"), length(cols)))
df2Bands <- df2[, 
    {
        ans <- do.call(cbind, lapply(.SD, function(x) outer(x, c(-1L, 1L), `+`)))
        setnames(data.table(ans), bandsNames)
    }, by=.(rn2)]

#create the non-equi join conditions
lowerLimits <- paste0(cols, "_lower<=", cols)
upperLimits <- paste0(cols, "_upper>=", cols)

#perform the non-equi join on lower and upper limits and return the count
#`:=` add a new column in df1 by reference
df1[, Count := 
        df2Bands[df1, .N, by=.EACHI, on=c(lowerLimits, upperLimits)]$N
    ]

желаемый результат:

   a1 a2 a3 a4 a5 rn1 Count
1:  3  8  4  9 17   1     6
2:  4  8 18  9 30   2     0

Если вы также хотите найти подходящие строки:

df2Bands[df1, .(rn1=i.rn1, rn2=x.rn2), by=.EACHI, on=c(lowerLimits, upperLimits)][, 
    -(1L:length(bandsNames))]

Соответствующие строки:

   rn1 rn2
1:   1   2
2:   1   3
3:   1   5
4:   1   6
5:   1   8
6:   1   9
7:   2  NA
0 голосов
/ 12 сентября 2018

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

Моя попытканиже, который использует цикл for (который может быть заменен на lapply).Он возвращает матрицу истинности, строки с 1 могут быть сопоставлены столбцам с 1, что дает спаривание соседей.

col_comp = function(x,y)
{
    lx = length(x)
    ly = length(y)
    return(abs(rep(x,ly) - rep(y,each = lx) )<=1)
}

full_comp=function(df1,df2)
{
    rows1 = seq_len(nrow(df1))
    rows2 = seq_len(nrow(df2))
    M = matrix(1L, nrow=nrow(df1),ncol=nrow(df2))
    for(i in seq_len(ncol(df1)) )
    {
        matches = col_comp(df1[rows1,i],df2[rows2,i])

        M = M*matches
    }
    return(M)
}
0 голосов
/ 12 сентября 2018

Вы можете использовать разделение строки df1 на список, а затем использовать lapply для достижения векторизации:

my_list=lapply(as.list(data.frame(t(df1))),function(x) sweep(as.matrix(df2), 2, as.matrix(x), "-"))

каждый элемент my_list является результатом вычисления каждой строки вdf1

my_list[[1]]
      a1 a2 a3 a4 a5
 [1,] -1 -1  0  1 -2
 [2,] -1 -1  0  1 -1
 [3,] -1 -1  0  1  0
 [4,]  0 -1  0  1 -2
 [5,]  0 -1  0  1 -1
 [6,]  0 -1  0  1  0
 [7,]  1 -1  0  1 -2
 [8,]  1 -1  0  1 -1
 [9,]  1 -1  0  1  0

Также вы можете использовать parallel::mclapply, что быстрее, чем традиционные lapply

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...