Создание функции для замены NA из одного data.frame значениями из другого - PullRequest
17 голосов
/ 02 декабря 2011

У меня регулярно возникают ситуации, когда мне нужно заменить отсутствующие значения в 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 в более чем одном столбце.И да, функция предполагает, что столбцы, которые мы заполняем из , имеют одинаковые имена, и столбцы, которые мы заполняем до , и то же самое относится к объединению.

Любое руководство или рефакторинг будет полезным.

РЕДАКТИРОВАТЬ 2 декабря Я понял, что в моем примере у меня были недостатки логики, которые я исправил.

Ответы [ 3 ]

14 голосов
/ 02 декабря 2011

Какой замечательный вопрос.

Вот решение 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.
6 голосов
/ 02 декабря 2011

Вот немного более краткая / надежная версия вашего подхода. Вы можете заменить цикл for вызовом lapply, но я считаю, что цикл легче читать.

Эта функция предполагает, что любые столбцы , а не в mergeCols являются честной игрой с заполнением их NA. Я не совсем уверен, что это помогает, но я рискну с избирателями.

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 голосов
/ 02 декабря 2011

Я бы предпочел извлечь код из слияния, который выполняет сопоставление, и сделать это сам, чтобы я мог сохранить порядок исходного фрейма данных как по строкам, так и по столбцам. Я также использую матричное индексирование, чтобы избежать каких-либо циклов, хотя для этого я создаю новый фрейм данных с пересмотренным 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
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...