2011-12-01 12 views
17

Tôi thường xuyên có các tình huống mà tôi cần phải thay thế các giá trị bị thiếu từ một data.frame bằng các giá trị từ một số tệp dữ liệu khác. . Vì vậy, ví dụ, nếu tôi có một data.frame đầy đủ dữ liệu của quận tôi có thể thay thế các giá trị NA với các giá trị trạng thái được lưu trữ trong một data.frame khác. Sau khi viết cùng một số merge ... ifelse(is.na()) yada yada vài chục lần, tôi quyết định chia nhỏ và viết một hàm để thực hiện việc này.Tạo hàm để thay thế NA từ một data.frame bằng các giá trị từ một số khác

Đây là những gì tôi nấu chín lên, cùng với một ví dụ về cách tôi sử dụng nó:

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

Vì vậy, sau khi tôi đã chạy này tôi đã có cảm giác kỳ lạ này mà ai đó đã có thể giải quyết vấn đề này trước khi tôi và trong một nhiều cách thanh lịch hơn. Có một giải pháp tốt hơn/dễ dàng hơn/nhanh hơn cho vấn đề này không? Ngoài ra, có cách nào giúp loại bỏ vòng lặp ở giữa chức năng của tôi không? Vòng lặp đó là có bởi vì tôi thường thay thế NA trong nhiều hơn một cột. Và, vâng, hàm này giả định các cột mà chúng tôi đang điền từ được đặt tên giống nhau và các cột chúng tôi đang điền đến và điều tương tự cũng áp dụng cho hợp nhất.

Bất kỳ hướng dẫn hoặc tái cấu trúc nào đều hữu ích.

CHỈNH SỬA vào ngày 2 tháng 12 Tôi nhận ra mình có lỗi trong ví dụ mà tôi đã khắc phục.

Trả lời

14

Thật là một câu hỏi hay.

Dưới đây là một giải pháp 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. 
+0

mát. Một số bình luận có thể giúp tôi hiểu về nó. Nó trông ngắn gọn! :) –

+0

OK - Tôi đã nhận xét một chút. Nếu bạn quan tâm đến việc tìm hiểu thêm, phần 'Ví dụ' của '? Data.table' là một ví dụ điển hình, và cũng đáng giá ~ 20 phút để làm việc. Đặc biệt nếu bạn là một gã dữ liệu lớn - và có vẻ như bạn có thể - nó thực sự đáng để đầu tư thời gian lên phía trước. –

+0

Cảm ơn Josh.Điều đó thực sự hữu ích. –

5

Dưới đây là một/phiên bản mạnh mẽ hơi ngắn gọn hơn của phương pháp của bạn. Bạn có thể thay thế vòng lặp for bằng một cuộc gọi đến lapply, nhưng tôi thấy vòng lặp dễ đọc hơn.

Chức năng này giả định bất kỳ cột nào không trong mergeCols là trò chơi công bằng để các Quốc gia của họ được lấp đầy. Tôi không thực sự chắc chắn điều này sẽ giúp, nhưng tôi sẽ tận dụng cơ hội của tôi với các cử tri.

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

Tùy chọn của tôi sẽ là rút mã khỏi hợp nhất khớp với chính nó và giữ nguyên thứ tự của khung dữ liệu gốc nguyên vẹn, cả hai hàng khôn ngoan và cột. Tôi cũng sử dụng chỉ mục ma trận để tránh bất kỳ vòng lặp nào, mặc dù vậy tôi tạo một khung dữ liệu mới với các fillCols đã sửa đổi và thay thế các cột của bản gốc bằng nó; Tôi nghĩ rằng tôi có thể điền nó trực tiếp nhưng dường như bạn không thể sử dụng ma trận đặt hàng để thay thế các phần của một data.frame, vì vậy tôi sẽ không ngạc nhiên nếu một vòng lặp trên tên sẽ nhanh hơn trong một số tình huống.

Với ma trận lập chỉ mục:

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 
} 

Với một vòng lặp:

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 
}