Создание функции для замены NA из одного файла data.frame со значениями из другого
У меня регулярно возникают ситуации, когда мне нужно заменить отсутствующие значения из data.frame значениями из некоторого другого data.frame, который находится на другом уровне агрегации. Так, например, если у меня есть data.frame, полный данных графства, я могу заменить значения NA значениями состояния, хранящимися в другом data.frame. После написания того же merge
... ifelse(is.na())
yada yada несколько десятков раз я решил сломать и написать функцию, чтобы сделать это.
Вот то, что я приготовил, и пример того, как я его использую:
fillNaDf <- function(naDf, fillDf, mergeCols, fillCols){
mergedDf <- merge(naDf, fillDf, by=mergeCols)
for (col in fillCols){
colWithNas <- mergedDf[[paste(col, "x", sep=".")]]
colWithOutNas <- mergedDf[[paste(col, "y", sep=".")]]
k <- which( is.na( colWithNas ) )
colWithNas[k] <- colWithOutNas[k]
mergedDf[col] <- colWithNas
mergedDf[[paste(col, "x", sep=".")]] <- NULL
mergedDf[[paste(col, "y", sep=".")]] <- NULL
}
return(mergedDf)
}
## test case
fillDf <- data.frame(a = c(1,2,1,2), b = c(3,3,4,4) ,f = c(100,200, 300, 400), g = c(11, 12, 13, 14))
naDf <- data.frame( a = sample(c(1,2), 100, rep=TRUE), b = sample(c(3,4), 100, rep=TRUE), f = sample(c(0,NA), 100, rep=TRUE), g = sample(c(0,NA), 200, rep=TRUE) )
fillNaDf(naDf, fillDf, mergeCols=c("a","b"), fillCols=c("f","g") )
Итак, после того, как я получил это исполнение, у меня было это странное чувство, что кто-то, вероятно, решил эту проблему передо мной и намного более элегантным способом. Есть ли лучшее/облегченное/быстрое решение этой проблемы? Кроме того, есть ли способ, который устраняет цикл в середине моей функции? Этот цикл существует, потому что я часто заменяю NA в более чем одном столбце. И, да, функция предполагает, что столбцы, которые мы заполняем из, называются одинаковыми, а столбцы, которые мы заполняем -, и то же самое относится к слиянию.
Было бы полезно любое руководство или рефакторинг.
EDIT 2 декабря. Я понял, что у меня были логические недостатки в моем примере, который я исправил.
Ответы
Ответ 1
Какой великий вопрос.
Здесь a data.table
решение:
# Convert data.frames to data.tables (i.e. data.frames with extra powers;)
library(data.table)
fillDT <- data.table(fillDf, key=c("a", "b"))
naDT <- data.table(naDf, key=c("a", "b"))
# Merge data.tables, based on their keys (columns a & b)
outDT <- naDT[fillDT]
# a b f g f.1 g.1
# [1,] 1 3 NA 0 100 11
# [2,] 1 3 NA NA 100 11
# [3,] 1 3 NA 0 100 11
# [4,] 1 3 0 0 100 11
# [5,] 1 3 0 NA 100 11
# First 5 rows of 200 printed.
# In outDT[i, j], on the following two lines
# -- i is a Boolean vector indicating which rows will be operated on
# -- j is an expression saying "(sub)assign from right column (e.g. f.1) to
# left column (e.g. f)
outDT[is.na(f), f:=f.1]
outDT[is.na(g), g:=g.1]
# Just keep the four columns ultimately needed
outDT <- outDT[,list(a,b,g,f)]
# a b g f
# [1,] 1 3 0 0
# [2,] 1 3 11 0
# [3,] 1 3 0 0
# [4,] 1 3 11 0
# [5,] 1 3 11 0
# First 5 rows of 200 printed.
Ответ 2
Вот немного более сжатая/надежная версия вашего подхода. Вы можете заменить for-loop вызовом на lapply
, но я считаю, что цикл легче читать.
Эта функция предполагает, что любые столбцы, не входящие в mergeCols
, являются честной игрой, чтобы заполнить свои НС. Я не совсем уверен, что это помогает, но я буду рисковать с избирателями.
fillNaDf.ju <- function(naDf, fillDf, mergeCols) {
mergedDf <- merge(fillDf, naDf, by=mergeCols, suffixes=c(".fill",""))
dataCols <- setdiff(names(naDf),mergeCols)
# loop over all columns we didn't merge by
for(col in dataCols) {
rows <- is.na(mergedDf[,col])
# skip this column if it doesn't contain any NAs
if(!any(rows)) next
rows <- which(rows)
# replace NAs with values from fillDf
mergedDf[rows,col] <- mergedDf[rows,paste(col,"fill",sep=".")]
}
# don't return ".fill" columns
mergedDf[,names(naDf)]
}
Ответ 3
Мое предпочтение заключалось бы в том, чтобы вытащить код из слияния, который выполняет сопоставление, и сделать это сам, чтобы я мог сохранить порядок исходного фрейма данных без изменений, как по строкам, так и по столбцам. Я также использую индексирование матрицы, чтобы избежать каких-либо циклов, хотя для этого я создаю новый фрейм данных с исправленными fillCols и заменяю его столбцами; Я думал, что могу заполнить его напрямую, но, видимо, вы не можете использовать матричный порядок для замены частей data.frame, поэтому я не удивлюсь, если цикл над именами будет быстрее в некоторых ситуациях.
С индексом матрицы:
fillNaDf <- function(naDf, fillDf, mergeCols, fillCols) {
fillB <- do.call(paste, c(fillDf[, mergeCols, drop = FALSE], sep="\r"))
naB <- do.call(paste, c(naDf[, mergeCols, drop = FALSE], sep="\r"))
na.ind <- is.na(naDf[,fillCols])
fill.ind <- cbind(match(naB, fillB)[row(na.ind)[na.ind]], col(na.ind)[na.ind])
naX <- naDf[,fillCols]
fillX <- fillDf[,fillCols]
naX[na.ind] <- fillX[fill.ind]
naDf[,colnames(naX)] <- naX
naDf
}
С помощью цикла:
fillNaDf2 <- function(naDf, fillDf, mergeCols, fillCols) {
fillB <- do.call(paste, c(fillDf[, mergeCols, drop = FALSE], sep="\r"))
naB <- do.call(paste, c(naDf[, mergeCols, drop = FALSE], sep="\r"))
m <- match(naB, fillB)
for(col in fillCols) {
fix <- which(is.na(naDf[,col]))
naDf[fix, col] <- fillDf[m[fix],col]
}
naDf
}