2011-12-01 13 views
17

Mam regularnie sytuacje, w których muszę zastąpić brakujące wartości z data.frame wartościami z innych danych.frame, który jest na innym poziomie agregacji . Tak więc, na przykład, jeśli mam dane.frame pełne danych hrabstwa, mogę zastąpić wartości NA wartościami stanowymi przechowywanymi w innym data.frame. Po napisaniu tego samego merge ... ifelse(is.na()) Yada Yada kilkadziesiąt razy postanowiłem zepsuć i napisać funkcję, aby to zrobić.Tworzenie funkcji zastępującej NA z jednego data.frame wartościami z innej

Oto co ugotowane, wraz z przykładem, jak go używać:

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")) 

Więc po Dostałem ten bieg Miałam dziwne uczucie, że ktoś prawdopodobnie rozwiązać ten problem przede mną iw sposób o wiele bardziej elegancki sposób. Czy istnieje lepsze/łatwiejsze/szybsze rozwiązanie tego problemu? Czy istnieje sposób, który eliminuje pętlę w środku mojej funkcji? Ta pętla istnieje, ponieważ często wymieniam NA w więcej niż jednej kolumnie. I tak, funkcja przyjmuje, że kolumny, które wypełniamy, to: od są nazywane tymi samymi i kolumny, które wypełniają do i to samo dotyczy scalenia.

Pomocne będą wszelkie wskazówki lub refaktoryzacja.

EDIT gru 2 zrozumiał, że błędy logiczne w moim przykładzie której stała.

Odpowiedz

14

Co za świetne pytanie.

Oto data.table rozwiązanie:

# 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. 
+0

ochłodzić. Niektóre komentarze mogą mi pomóc w zrozumieniu tego. Wygląda zwięźle! :) –

+0

OK - trochę to skomentowałem. Jeśli chcesz dowiedzieć się więcej, sekcja "Przykłady" na "# data.table" jest wzorcowa i warta jest około 20 minut pracy. Zwłaszcza jeśli jesteś wielkim facetem danych - i wygląda na to, że możesz być - to naprawdę warto zainwestować z góry. –

+0

Dzięki Josh.To naprawdę pomocne. –

5

Oto nieco bardziej zwięzły/solidna wersja swoim podejściu. Możesz zamienić pętlę for z wywołaniem na lapply, ale uważam, że pętla jest łatwiejsza do odczytania.

Ta funkcja zakłada, że ​​każda kolumna , a nie w mergeCols jest grą uczciwą, w której wypełniane są ich NA. Nie jestem pewien, czy to pomaga, ale wykorzystam swoje szanse z wyborcami.

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

Moje preferencje byłoby wyciągnąć kod z seryjnej, dokłada dopasowanie i zrobić to sam, abym mógł zachować kolejność oryginalnej ramki danych w stanie nienaruszonym, zarówno rząd, mądry i kolumny mądry. Używam również indeksowania macierzy w celu uniknięcia wszelkich pętli, ale aby to zrobić, tworzę nową ramkę danych ze zmienionymi fillCols i zastępuję kolumny oryginału; Pomyślałem, że mógłbym wypełnić to bezpośrednio, ale najwyraźniej nie można użyć porządkowania macierzy w celu zastąpienia części danych.frame, więc nie zdziwiłbym się, gdyby pętla nad nazwami była szybsza w niektórych sytuacjach.

z indeksowaniem Matrix

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 
} 

z pętli:

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 
} 
Powiązane problemy