Xóa các cột khỏi khung dữ liệu trong đó TẤT CẢ các giá trị là NA


149

Tôi đang gặp rắc rối với một khung dữ liệu và có thể không thực sự giải quyết vấn đề mà bản thân mình:
Các dataframe có tùy ý thuộc tính như cộtmỗi hàng đại diện cho một tập hợp dữ liệu .

Câu hỏi là:
Làm thế nào để thoát khỏi các cột trong đó TẤT CẢ các hàng có giá trị là NA ?

Câu trả lời:


155

Thử cái này:

df <- df[,colSums(is.na(df))<nrow(df)]

3
Điều này tạo ra một đối tượng kích thước của đối tượng cũ là một vấn đề với bộ nhớ trên các đối tượng lớn. Tốt hơn để sử dụng một chức năng để giảm kích thước. Câu trả lời dưới đây bằng Bộ lọc hoặc sử dụng data.table sẽ giúp sử dụng bộ nhớ của bạn.
mtelesha

3
Điều này dường như không hoạt động với các cột không số.
verbamour

Nó thay đổi tên cột nếu chúng được sao chép
Peter.k

97

Hai cách tiếp cận được cung cấp cho đến nay thất bại với các tập dữ liệu lớn là (trong số các vấn đề bộ nhớ khác) mà chúng tạo ra is.na(df), sẽ là một đối tượng có cùng kích thước df.

Đây là hai cách tiếp cận hiệu quả hơn về bộ nhớ và thời gian

Một cách tiếp cận bằng cách sử dụng Filter

Filter(function(x)!all(is.na(x)), df)

và một cách tiếp cận bằng cách sử dụng data.table (cho hiệu quả thời gian và bộ nhớ chung)

library(data.table)
DT <- as.data.table(df)
DT[,which(unlist(lapply(DT, function(x)!all(is.na(x))))),with=F]

ví dụ sử dụng dữ liệu lớn (30 cột, 1e6 hàng)

big_data <- replicate(10, data.frame(rep(NA, 1e6), sample(c(1:8,NA),1e6,T), sample(250,1e6,T)),simplify=F)
bd <- do.call(data.frame,big_data)
names(bd) <- paste0('X',seq_len(30))
DT <- as.data.table(bd)

system.time({df1 <- bd[,colSums(is.na(bd) < nrow(bd))]})
# error -- can't allocate vector of size ...
system.time({df2 <- bd[, !apply(is.na(bd), 2, all)]})
# error -- can't allocate vector of size ...
system.time({df3 <- Filter(function(x)!all(is.na(x)), bd)})
## user  system elapsed 
## 0.26    0.03    0.29 
system.time({DT1 <- DT[,which(unlist(lapply(DT, function(x)!all(is.na(x))))),with=F]})
## user  system elapsed 
## 0.14    0.03    0.18 

6
Rất đẹp. Bạn có thể làm tương tự với data.frame, mặc dù. Không có gì ở đây thực sự cần data.table. Điều quan trọng là lapply, trong đó tránh bản sao của toàn bộ đối tượng được thực hiện bởi is.na(df). +10 để chỉ ra điều đó.
Matt Dowle

1
Làm thế nào bạn sẽ làm điều đó với một data.frame? @ matt-dowle
s_a

8
@s_a, bd1 <- bd[, unlist(lapply(bd, function(x), !all(is.na(x))))]
mnel

6
@mnel Tôi nghĩ bạn cần xóa phần ,sau function(x)- cảm ơn vì ví dụ btw
Thieme Hennis

1
Bạn có thể làm điều đó nhanh hơn với: = hoặc với một tập hợp () không?
skan

49

dplyrbây giờ có một select_ifđộng từ có thể hữu ích ở đây:

library(dplyr)
temp <- data.frame(x = 1:5, y = c(1,2,NA,4, 5), z = rep(NA, 5))
not_all_na <- function(x) any(!is.na(x))
not_any_na <- function(x) all(!is.na(x))

> temp
  x  y  z
1 1  1 NA
2 2  2 NA
3 3 NA NA
4 4  4 NA
5 5  5 NA

> temp %>% select_if(not_all_na)
  x  y
1 1  1
2 2  2
3 3 NA
4 4  4
5 5  5

> temp %>% select_if(not_any_na)
  x
1 1
2 2
3 3
4 4
5 5

Đến đây để tìm dplyrgiải pháp. Đã không thất vọng. Cảm ơn!
Andrew Brēza

Tôi thấy điều này có vấn đề là nó cũng sẽ xóa hầu hết các biến nhưng không phải tất cả các giá trị bị thiếu
MBorg

15

Một cách khác là sử dụng apply()chức năng.

Nếu bạn có data.frame

df <- data.frame (var1 = c(1:7,NA),
                  var2 = c(1,2,1,3,4,NA,NA,9),
                  var3 = c(NA)
                  )

sau đó bạn có thể sử dụng apply()để xem cột nào đáp ứng điều kiện của bạn và do đó bạn chỉ cần thực hiện cùng một tập hợp con như trong câu trả lời của Musa, chỉ với một applycách tiếp cận.

> !apply (is.na(df), 2, all)
 var1  var2  var3 
 TRUE  TRUE FALSE 

> df[, !apply(is.na(df), 2, all)]
  var1 var2
1    1    1
2    2    2
3    3    1
4    4    3
5    5    4
6    6   NA
7    7   NA
8   NA    9

3
Tôi hy vọng điều này sẽ nhanh hơn, vì giải pháp colSum () dường như đang làm nhiều việc hơn. Nhưng trong bộ thử nghiệm của tôi (213 quan sát của 1614 biến trước đó, so với 1377 biến sau đó) thì phải mất chính xác hơn 3 lần. (Nhưng +1 cho một cách tiếp cận thú vị.)
Darren Cook

10

Muộn trò chơi nhưng bạn cũng có thể sử dụng janitorgói. Hàm này sẽ xóa các cột là tất cả NA và cũng có thể được thay đổi để xóa các hàng cũng là NA.

df <- janitor::remove_empty(df, which = "cols")



4

Câu trả lời được chấp nhận không hoạt động với các cột không phải là số. Từ câu trả lời này , các công việc sau đây với các cột chứa các loại dữ liệu khác nhau

Filter(function(x) !all(is.na(x)), df)

Một số người khác đã đăng câu trả lời tương tự trong chủ đề này 4 năm trước khi bạn ... Xem câu trả lời của mnel bên dưới.
André.B

2

Một tùy chọn khác với purrrgói:

library(dplyr)

df <- data.frame(a = NA,
                 b = seq(1:5), 
                 c = c(rep(1, 4), NA))

df %>% purrr::discard(~all(is.na(.)))
df %>% purrr::keep(~!all(is.na(.)))

1

Tôi hy vọng điều này cũng có thể giúp đỡ. Nó có thể được tạo thành một lệnh duy nhất, nhưng tôi thấy nó dễ đọc hơn bằng cách chia nó thành hai lệnh. Tôi đã thực hiện một chức năng với hướng dẫn sau đây và làm việc nhanh như chớp.

naColsRemoval = function (DataTable) { na.cols = DataTable [ , .( which ( apply ( is.na ( .SD ) , 2 , all ) ) )] DataTable [ , unlist (na.cols) := NULL , with = F] }

.SD sẽ cho phép giới hạn xác minh ở một phần của bảng, nếu bạn muốn, nhưng nó sẽ lấy toàn bộ bảng dưới dạng


1

Một base Rtùy chọn tiện dụng có thể là colMeans():

df[, colMeans(is.na(df)) != 1]

0

Bạn có thể sử dụng gói Janitor remove_empty

library(janitor)

df %>%
  remove_empty(c("rows", "cols")) #select either row or cols or both

Ngoài ra, một cách tiếp cận dplyr khác

 library(dplyr) 
 df %>% select_if(~all(!is.na(.)))

HOẶC LÀ

df %>% select_if(colSums(!is.na(.)) == nrow(df))

điều này cũng hữu ích nếu bạn muốn chỉ loại trừ / giữ cột với số lượng giá trị bị thiếu nhất định, vd

 df %>% select_if(colSums(!is.na(.))>500)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.