我有一个大约有 20 个数字列的数据框,每个列都包含大量的 NA 值。我想选择这些列的子集,这将为我提供最多包含零 NA 值的行。详尽的搜索会花费大量的计算时间——是否有更好的方法来获得近似值?
这是一个具有较小数据框的示例(完全任意):
set.seed(2)
foo = as.data.frame(matrix(rnorm(200), nr = 20))
foo[sapply(foo, function(x) x > abs(x[1]))] = NA
foo = foo[-1, ]
round(foo, 3)
V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
2 0.185 -1.200 -1.959 NA -1.696 0.261 0.139 0.410 -0.638 -1.262
3 NA 1.590 -0.842 -0.703 -0.533 -0.314 NA -0.807 -0.268 0.392
4 -1.130 1.955 NA 0.158 -1.372 -0.750 -0.431 0.086 0.360 -1.131
5 -0.080 0.005 NA 0.506 -2.208 -0.862 -1.044 NA -1.313 0.544
6 0.132 -2.452 NA -0.820 NA NA 0.538 -0.654 -0.884 NA
7 0.708 0.477 -0.305 -1.999 -0.653 0.940 -0.670 NA NA 0.025
8 -0.240 -0.597 -0.091 -0.479 -0.285 NA 0.639 0.550 -2.099 0.515
9 NA 0.792 -0.184 0.084 -0.387 -0.421 -1.724 -0.807 -1.239 -0.654
10 -0.139 0.290 -1.199 -0.895 0.387 -0.351 -1.742 -0.997 NA 0.504
11 0.418 0.739 -0.838 -0.921 NA -1.027 0.690 NA NA -1.272
12 NA 0.319 NA 0.330 NA -0.251 0.331 -0.169 NA -0.077
13 -0.393 1.076 -0.562 -0.142 -1.184 0.472 0.871 NA 0.057 -1.345
14 -1.040 -0.284 NA 0.435 -1.358 NA -2.016 -0.844 0.324 -0.266
15 NA -0.777 -1.048 -0.054 -1.513 0.564 1.213 NA -0.905 NA
16 -2.311 -0.596 -1.966 -0.907 -1.253 0.456 1.200 -1.343 -0.652 0.701
17 0.879 -1.726 -0.323 1.304 NA NA 1.032 NA -0.262 -0.443
18 0.036 -0.903 NA 0.772 0.008 NA 0.786 0.464 -0.935 -0.789
19 NA -0.559 NA 1.053 -0.843 0.107 NA 0.268 NA -0.857
20 0.432 -0.247 NA -1.410 -0.601 -0.783 -1.454 NA -1.624 -0.746
dim(na.omit(foo))
[1] 1 10
以下是我如何制定详尽的搜索:
best.list = list()
for (i in 5:ncol(foo)) {
# get best subset for each size
collist = combn(ncol(foo), i)
numobs = apply(collist, 2, function(x) nrow(na.omit(foo[, x])))
cat("for subset size", i, "most complete obs is", max(numobs), "\n")
best = which(numobs == max(numobs))[1]
best.list = c(best.list, list(collist[, best]))
}
例如,
best.list[[1]]
告诉我,如果我保留 5 列,我可以有 12 个完整的观察值(NA 为零的行),并且第 1、2、4、7 和 10 列是我应该选择的列。
虽然这适用于非常小的数据帧,但对于较大的数据帧很快就会变得令人望而却步。 R中有没有一种方法可以有效地估计给定大小的最佳子集?我唯一能找到的是
subselect
包,尽管我不知道如何实现它的方法来解决手头的问题。
不确定这是否是完整的解决方案,但如果您想要快速结果,data.table 和影子矩阵是最可能的成分。
library(data.table)
df = data.table(foo) # your foo dataframe, converted to data.table
y = sort(df[,lapply(.SD, function(x) sum(is.na(x)))]) # nr of NA in columns, increasing
setcolorder(df, names(y)) # now the columns are ordered - less NA first
df[, idx := rowSums(is.na(df))] # count nr of NA in rows
df = df[order(idx),] # sort by nr of NA in rows
df[, idx := NULL] # idx not needed anymore
# now your data.table is sorted: columns with least NA to the left,
# rows with with least NA on top
# shadow matrix
x= data.table(abs(!is.na(df))) # 0 = NA value
y = as.data.table(t(x))
y = y[,lapply(.SD, cumprod)]
y = as.data.table(t(y))
y[,lapply(.SD, sum)]
# nr of complete cases from column selections:
# V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
# 1: 19 18 16 14 11 10 7 5 2 1
旧帖子,但有一个内置函数可以执行此操作。我敢打赌它非常有效:
df_noNAs <- df[complete.cases(df[,1:20]),]
这是一个 tidyverse 解决方案。
与@Henk解决方案类似,
dfminimiser()
函数首先按缺失的降序对列和行进行排序,从而避免了详尽的搜索过程。
它返回在删除缺失值后具有最大行数和列数方面最优化的 data.frame。
我还添加了一个
vars2keep
参数,您可以在其中指定您肯定想要保留在最终数据集中的任何变量。这些变量在函数开头与其余变量分开处理。
感谢来自this SO post的@Onyambu,帮助该行有效地从列表中选择具有最大尺寸的data.frame。
这是函数:
# Load tidyverse packages:
library(tidyverse)
# Data frame minimiser function:
dfminimiser <- function(df, vars2keep){
# Remove all rows that have NAs in the case definition column:
df = df %>%
drop_na(all_of(vars2keep))
# Create vector of colnames in decreasing order of completeness:
colnonmiss = colSums(!is.na(df)) %>%
sort(decreasing = TRUE) %>%
names()
# Sort columns and rows so most complete are at top left:
df = df %>%
select(colnonmiss) %>%
arrange(rowSums(is.na(.)))
# Create ordered list of columns to iteratively drop:
cols2drop = names(df)[!names(df) %in% vars2keep]
# Remove columns with the most missing one by one and drop NAs:
dflist = cols2drop %>%
map(~ df %>% select(1:.x) %>% drop_na())
# Select the data.frame with the biggest dimensions (nrow & ncol):
dfinal = dflist[[which.max(map_dbl(dflist, ~ prod(dim(.x))))]]
# Return the final data.frame:
return(dfinal)
}
结果如下:
# Apply the function:
foo_final <- dfminimiser(foo, vars2keep = "V1"
# View the results:
> foo_final
V1 V2 V7 V4 V10
1 -2.31106908 -0.595660499 1.2004947 -0.9071104 0.70056780
2 -1.13037567 1.954651642 -0.4306410 0.1581648 -1.13143826
3 -0.23969802 -0.596558169 0.6388056 -0.4792926 0.51513317
4 -0.13878701 0.289636710 -1.7424301 -0.8954866 0.50364199
5 -0.39269536 1.076164354 0.8710677 -0.1416608 -1.34531938
6 -0.08025176 0.004937777 -1.0442296 0.5062348 0.54414448
7 0.70795473 0.477237303 -0.6695860 -1.9988470 0.02522857
8 -1.03966898 -0.284157720 -2.0162456 0.4348478 -0.26631756
9 0.03580672 -0.902584480 0.7864103 0.7717898 -0.78851997
10 0.43226515 -0.246512567 -1.4538098 -1.4100383 -0.74641901
11 0.41765075 0.738938604 0.6898042 -0.9212757 -1.27211922
12 0.87860458 -1.725979779 1.0320683 1.3035122 -0.44275951
我无法让@Henk 的所有代码都工作来对这两种方法进行基准测试,但这个 tidyverse 解决方案似乎也相当快。