Удалите NA в ряду и переместите ячейку справа, где NA был расположен в R, также уникальные значения

Итак, у меня есть фрейм данных в R, как это

ID <- c(1, 2, 3)
c1 <- c( 1, 1, NA)
c2 <- c(NA, NA, 5)
c3 <- c(NA, NA, NA)
c4 <- c(2, NA, 5)
c5 <- c(5, 7, 3)

df <- data.frame(ID, c1, c2, c3, c4, c5)

Итак, это то, что я ищу

1. Treat every row as a vector
2. Be able to remove all NAs in every row/vector
3. In a given row there can't be repeated values (expect for ID vs a number in other cell)
4. I'm looking to "cut" this row/vector.  I don't need 5 values just 2.

Я делаю это для метрики MAP@k, поэтому порядок чисел (один слева важнее следующего) важен для поддержания порядка.

Это выход, который я ищу

ID <- c(1, 2, 3)
c1 <- c(1, 1, 5)
c2 <- c(2, 7, 3)

df2 <- data.frame(ID, c1, c2)

Спасибо за помощь

3 ответа

Мы перебираем строки 'df' (используя apply с MARGIN как 1), удалите NA элементы (!is.na(x)) и получить unique ценности. Тогда, если длина элементов не одинакова, на выходе будет list ('ЛСТ'). Мы используем lengths чтобы получить length каждого list элемент, get theминof it, based on it we subset theсписокelements andcbind` с первым столбцом 'ID'.

 lst <- apply(df[-1], 1, function(x) unique(x[!is.na(x)]))
 dfN <- cbind(df[1], do.call(rbind,lapply(lst, function(x) x[seq(min(lengths(lst)))])))
 colnames(dfN)[-1] <- paste0("c", colnames(dfN)[-1])
 dfN
 #  ID c1 c2
 #1  1  1  2
 #2  2  1  7
 #3  3  5  3

ПРИМЕЧАНИЕ: если length из unique элементы одинаковы в каждой строке (после удаления NA), на выходе будет matrix, Просто перенесите вывод и cbind с первым столбцом.


Или другой вариант data.table который должен быть очень эффективным.

library(data.table)
dM <- melt(setDT(df), id.var="ID", na.rm=TRUE)[, 
          .(value = unique(value), n = seq(uniqueN(value))), ID]
dcast(dM[dM[, n1 := min(tabulate(ID))][, .I[1:.N <=n1] , ID]$V1],
           ID~paste0("c", n), value.var="value")
#  ID c1 c2
#1:  1  1  2
#2:  2  1  7
#3:  3  5  3

Ужасно, но должно быть эффективно (пережевано 3M записей за 20 секунд и 300K за < 2 секунды):

sel <- !is.na(df[-1])
tmp <- unique(data.frame(ID=df$ID[row(df[-1])[sel]], c=df[-1][sel]))
tmp$time <- ave(tmp$ID, tmp$ID, FUN=seq_along)

reshape(tmp[tmp$time <= 2,], idvar="ID", direction="wide", sep="")

#  ID c1 c2
#1  1  1  2
#2  2  1  7
#3  3  5  3

Основываясь на идее akrun data.table, я перевел код data.table в dplyr/tidyr (мне легче читать, вот и все). Вот код

library(dplyr)
library(tidyr)

df_tidy <- df %>%
gather(importance, val, c1:c5) %>% 
na.omit %>% 
arrange(ID, importance) %>%
group_by(ID) %>%
distinct(ID, val) %>%
mutate(place = seq_len(n())) %>%
filter(place <= 2) %>%
mutate(place = paste("c", place, sep="")) %>%
select(-importance) %>%
spread(place, val)

Спасибо akrun и thelatemail!

Другие вопросы по тегам