我想删除此数据框中的行:
a) 在所有列中包含NA
。以下是我的示例数据框。
gene hsap mmul mmus rnor cfam
1 ENSG00000208234 0 NA NA NA NA
2 ENSG00000199674 0 2 2 2 2
3 ENSG00000221622 0 NA NA NA NA
4 ENSG00000207604 0 NA NA 1 2
5 ENSG00000207431 0 NA NA NA NA
6 ENSG00000221312 0 1 2 3 2
基本上,我想获得如下数据框。
gene hsap mmul mmus rnor cfam
2 ENSG00000199674 0 2 2 2 2
6 ENSG00000221312 0 1 2 3 2
b) 仅在某些列中包含 NA
,所以我也可以得到这个结果:
gene hsap mmul mmus rnor cfam
2 ENSG00000199674 0 2 2 2 2
4 ENSG00000207604 0 NA NA 1 2
6 ENSG00000221312 0 1 2 3 2
还要检查 complete.cases
:
> final[complete.cases(final), ]
gene hsap mmul mmus rnor cfam
2 ENSG00000199674 0 2 2 2 2
6 ENSG00000221312 0 1 2 3 2
na.omit
更适合仅删除所有 NA
。 complete.cases
允许通过仅包含数据框的某些列来进行部分选择:
> final[complete.cases(final[ , 5:6]),]
gene hsap mmul mmus rnor cfam
2 ENSG00000199674 0 2 2 2 2
4 ENSG00000207604 0 NA NA 1 2
6 ENSG00000221312 0 1 2 3 2
你的解决方案行不通。如果您坚持使用 is.na
,那么您必须执行以下操作:
> final[rowSums(is.na(final[ , 5:6])) == 0, ]
gene hsap mmul mmus rnor cfam
2 ENSG00000199674 0 2 2 2 2
4 ENSG00000207604 0 NA NA 1 2
6 ENSG00000221312 0 1 2 3 2
但是使用 complete.cases
更清晰,更快。
试试 na.omit(your.data.frame)
。至于第二个问题,请尝试将其作为另一个问题发布(为清楚起见)。
tidyr
有一个新功能 drop_na
:
library(tidyr)
df %>% drop_na()
# gene hsap mmul mmus rnor cfam
# 2 ENSG00000199674 0 2 2 2 2
# 6 ENSG00000221312 0 1 2 3 2
df %>% drop_na(rnor, cfam)
# gene hsap mmul mmus rnor cfam
# 2 ENSG00000199674 0 2 2 2 2
# 4 ENSG00000207604 0 NA NA 1 2
# 6 ENSG00000221312 0 1 2 3 2
rnor
不存在。为什么 ?
rnor
应该是表中的列名
我更喜欢以下方法来检查行是否包含任何 NA:
row.has.na <- apply(final, 1, function(x){any(is.na(x))})
这将返回逻辑向量,其值表示一行中是否有任何 NA。您可以使用它来查看必须删除的行数:
sum(row.has.na)
并最终放弃它们
final.filtered <- final[!row.has.na,]
对于过滤具有特定部分 NA 的行,它变得有点棘手(例如,您可以将 'final[,5:6]' 提供给 'apply')。一般来说,Joris Meys 的解决方案似乎更优雅。
rowSum(!is.na(final))
似乎比 apply()
更适合
final[rowSum(is.na(final)),]
吗?
如果您想控制每行有多少 NA 有效,请尝试此功能。对于许多调查数据集,太多的空白问题回答可能会破坏结果。所以它们在一定的阈值后被删除。此功能将允许您选择该行在删除之前可以有多少个 NA:
delete.na <- function(DF, n=0) {
DF[rowSums(is.na(DF)) <= n,]
}
默认情况下,它将消除所有 NA:
delete.na(final)
gene hsap mmul mmus rnor cfam
2 ENSG00000199674 0 2 2 2 2
6 ENSG00000221312 0 1 2 3 2
或者指定允许的最大 NA 数:
delete.na(final, 2)
gene hsap mmul mmus rnor cfam
2 ENSG00000199674 0 2 2 2 2
4 ENSG00000207604 0 NA NA 1 2
6 ENSG00000221312 0 1 2 3 2
如果您想更好地控制行被视为无效的方式,另一种选择是
final <- final[!(is.na(final$rnor)) | !(is.na(rawdata$cfam)),]
使用上面的,这个:
gene hsap mmul mmus rnor cfam
1 ENSG00000208234 0 NA NA NA 2
2 ENSG00000199674 0 2 2 2 2
3 ENSG00000221622 0 NA NA 2 NA
4 ENSG00000207604 0 NA NA 1 2
5 ENSG00000207431 0 NA NA NA NA
6 ENSG00000221312 0 1 2 3 2
变成:
gene hsap mmul mmus rnor cfam
1 ENSG00000208234 0 NA NA NA 2
2 ENSG00000199674 0 2 2 2 2
3 ENSG00000221622 0 NA NA 2 NA
4 ENSG00000207604 0 NA NA 1 2
6 ENSG00000221312 0 1 2 3 2
...仅删除第 5 行,因为它是唯一包含 rnor
和 cfam
的 NA 的行。然后可以更改布尔逻辑以适应特定要求。
如果性能是优先考虑的,请使用 data.table 和 na.omit() 以及可选参数 cols=。
na.omit.data.table
在我的基准测试中是最快的(见下文),无论是对于所有列还是对于选择列(OP 问题第 2 部分)。
如果您不想使用 data.table,请使用 complete.cases()。
在普通 data.frame
上,complete.cases
比 na.omit()
或 dplyr::drop_na()
快。请注意,na.omit.data.frame
不支持 cols=
。
基准测试结果
这是基本(蓝色)、dplyr
(粉红色)和 data.table
(黄色)方法的比较,用于丢弃所有或选择缺失的观察值,在 20 个数值变量的 100 万个观察值的概念数据集上,独立的 5% 可能性丢失,以及第 2 部分的 4 个变量的子集。
您的结果可能会因特定数据集的长度、宽度和稀疏性而异。
注意 y 轴上的对数刻度。
https://i.stack.imgur.com/EtBWe.png
基准脚本
#------- Adjust these assumptions for your own use case ------------
row_size <- 1e6L
col_size <- 20 # not including ID column
p_missing <- 0.05 # likelihood of missing observation (except ID col)
col_subset <- 18:21 # second part of question: filter on select columns
#------- System info for benchmark ----------------------------------
R.version # R version 3.4.3 (2017-11-30), platform = x86_64-w64-mingw32
library(data.table); packageVersion('data.table') # 1.10.4.3
library(dplyr); packageVersion('dplyr') # 0.7.4
library(tidyr); packageVersion('tidyr') # 0.8.0
library(microbenchmark)
#------- Example dataset using above assumptions --------------------
fakeData <- function(m, n, p){
set.seed(123)
m <- matrix(runif(m*n), nrow=m, ncol=n)
m[m<p] <- NA
return(m)
}
df <- cbind( data.frame(id = paste0('ID',seq(row_size)),
stringsAsFactors = FALSE),
data.frame(fakeData(row_size, col_size, p_missing) )
)
dt <- data.table(df)
par(las=3, mfcol=c(1,2), mar=c(22,4,1,1)+0.1)
boxplot(
microbenchmark(
df[complete.cases(df), ],
na.omit(df),
df %>% drop_na,
dt[complete.cases(dt), ],
na.omit(dt)
), xlab='',
main = 'Performance: Drop any NA observation',
col=c(rep('lightblue',2),'salmon',rep('beige',2))
)
boxplot(
microbenchmark(
df[complete.cases(df[,col_subset]), ],
#na.omit(df), # col subset not supported in na.omit.data.frame
df %>% drop_na(col_subset),
dt[complete.cases(dt[,col_subset,with=FALSE]), ],
na.omit(dt, cols=col_subset) # see ?na.omit.data.table
), xlab='',
main = 'Performance: Drop NA obs. in select cols',
col=c('lightblue','salmon',rep('beige',2))
)
使用 dplyr 包我们可以过滤 NA 如下:
dplyr::filter(df, !is.na(columnname))
drop_na()
慢了大约 10.000 倍
drop_na
使用“任何”逻辑,filter
使用“所有”逻辑。所以如果你需要更灵活的表达方式,filter有更多的可能性。
这将返回至少有一个非 NA 值的行。
final[rowSums(is.na(final))<length(final),]
这将返回至少有两个非 NA 值的行。
final[rowSums(is.na(final))<(length(final)-1),]
对于您的第一个问题,我有一个可以摆脱所有 NA 的代码。感谢@Gregor 使它更简单。
final[!(rowSums(is.na(final))),]
对于第二个问题,代码只是之前解决方案的一个替代方案。
final[as.logical((rowSums(is.na(final))-5)),]
请注意 -5 是数据中的列数。这将消除所有 NA 的行,因为 rowSums 加起来为 5,并且在减法后它们变为零。这一次, as.logical 是必要的。
一种既通用又产生相当可读代码的方法是使用 {dplyr} 包中的 filter()
函数和 across()
辅助函数。
library(dplyr)
vars_to_check <- c("rnor", "cfam")
# Filter a specific list of columns to keep only non-missing entries
df %>%
filter(across(one_of(vars_to_check),
~ !is.na(.x)))
# Filter all the columns to exclude NA
df %>%
filter(across(everything(),
~ !is.na(.)))
# Filter only numeric columns
df %>%
filter(across(where(is.numeric),
~ !is.na(.)))
同样,dplyr 包中也有变体函数(filter_all
、filter_at
、filter_if
),它们完成了同样的事情:
library(dplyr)
vars_to_check <- c("rnor", "cfam")
# Filter a specific list of columns to keep only non-missing entries
df %>%
filter_at(.vars = vars(one_of(vars_to_check)),
~ !is.na(.))
# Filter all the columns to exclude NA
df %>%
filter_all(~ !is.na(.))
# Filter only numeric columns
df %>%
filter_if(is.numeric,
~ !is.na(.))
across
的另一个示例,请参见 here
我们也可以为此使用子集函数。
finalData<-subset(data,!(is.na(data["mmul"]) | is.na(data["rnor"])))
这将只给出那些在 mmul 和 rnor 中都没有 NA 的行
假设 dat
作为您的数据框,可以使用
1.rowSums
> dat[!rowSums((is.na(dat))),]
gene hsap mmul mmus rnor cfam
2 ENSG00000199674 0 2 2 2 2
6 ENSG00000221312 0 1 2 3 2
2.lapply
> dat[!Reduce('|',lapply(dat,is.na)),]
gene hsap mmul mmus rnor cfam
2 ENSG00000199674 0 2 2 2 2
6 ENSG00000221312 0 1 2 3 2
我是合成器:)。在这里,我将答案组合成一个函数:
#' keep rows that have a certain number (range) of NAs anywhere/somewhere and delete others
#' @param df a data frame
#' @param col restrict to the columns where you would like to search for NA; eg, 3, c(3), 2:5, "place", c("place","age")
#' \cr default is NULL, search for all columns
#' @param n integer or vector, 0, c(3,5), number/range of NAs allowed.
#' \cr If a number, the exact number of NAs kept
#' \cr Range includes both ends 3<=n<=5
#' \cr Range could be -Inf, Inf
#' @return returns a new df with rows that have NA(s) removed
#' @export
ez.na.keep = function(df, col=NULL, n=0){
if (!is.null(col)) {
# R converts a single row/col to a vector if the parameter col has only one col
# see https://radfordneal.wordpress.com/2008/08/20/design-flaws-in-r-2-%E2%80%94-dropped-dimensions/#comments
df.temp = df[,col,drop=FALSE]
} else {
df.temp = df
}
if (length(n)==1){
if (n==0) {
# simply call complete.cases which might be faster
result = df[complete.cases(df.temp),]
} else {
# credit: http://stackoverflow.com/a/30461945/2292993
log <- apply(df.temp, 2, is.na)
logindex <- apply(log, 1, function(x) sum(x) == n)
result = df[logindex, ]
}
}
if (length(n)==2){
min = n[1]; max = n[2]
log <- apply(df.temp, 2, is.na)
logindex <- apply(log, 1, function(x) {sum(x) >= min && sum(x) <= max})
result = df[logindex, ]
}
return(result)
}
dplyr 1.0.4 为 filter
引入了两个伴随函数:它们是 if_any()
和 if_all()
。 if_all()
伴随函数在这种情况下特别有用:
a) 删除所有列中包含 NA 的行
df %>%
filter(if_all(everything(), ~ !is.na(.x)))
此行将仅保留所有列都没有 NA 的行。
b) 删除仅在某些列中包含 NA 的行
cols_to_check = c("rnor", "cfam")
df %>%
filter(if_all(cols_to_check, ~ !is.na(.x)))
此行将检查任何指定的列 (cols_to_check) 是否具有 NA,并且仅保留那些不是这种情况的行。
delete.dirt <- function(DF, dart=c('NA')) {
dirty_rows <- apply(DF, 1, function(r) !any(r %in% dart))
DF <- DF[dirty_rows, ]
}
mydata <- delete.dirt(mydata)
上面的函数从数据框中删除任何列中具有“NA”的所有行并返回结果数据。如果要检查多个值,例如 NA
和 ?
,请将函数参数中的 dart=c('NA')
更改为 dart=c('NA', '?')
我的猜测是,这可以通过这种方式更优雅地解决:
m <- matrix(1:25, ncol = 5)
m[c(1, 6, 13, 25)] <- NA
df <- data.frame(m)
library(dplyr)
df %>%
filter_all(any_vars(is.na(.)))
#> X1 X2 X3 X4 X5
#> 1 NA NA 11 16 21
#> 2 3 8 NA 18 23
#> 3 5 10 15 20 NA
NA
的行。我认为 OP 想要的是:df %>% filter_all(all_vars(!is.na(.)))
不定期副业成功案例分享
final[complete.cases(final),]
中尾随逗号的含义是什么?complete.cases(final)
返回一个布尔值,其中没有像(TRUE, FALSE, TRUE)
这样的NA
的行。尾随逗号表示所有列。因此,在逗号之前您过滤行但在逗号之后您不进行过滤并要求所有内容complete.cases
语句指定列。