如何按组 / 子集用平均值替换NA?

25

我有一个数据框,其中包含来自蝾螈肠道的各种节肢动物的长度和宽度。因为有些肠道有数千个特定的猎物,我只测量了每种猎物的子集。现在,我想用该猎物的平均长度和宽度替换每个未测量的个体。我想保留数据框,并仅添加填充后的列(length2,width2)。主要原因是每一行也有关于捕捉蝾螈的日期和位置的数据列。我可以使用所测量个体的随机选择来填充NA,但出于论点的目的,让我们假设我只想用平均值替换每个NA。

例如,假设我有一个类似以下内容的数据框:

id    taxa        length  width
101   collembola  2.1     0.9
102   mite        0.9     0.7
103   mite        1.1     0.8
104   collembola  NA      NA
105   collembola  1.5     0.5
106   mite        NA      NA

实际上,我有更多的列,大约有25个不同的分类单元,总共约有30,000个猎物。看起来plyr包可能很适合这个问题,但我就是想不出该怎么做。虽然我不算非常熟练的R或编程专家,但我正在努力学习。

虽然我不知道自己在干什么,但如果有帮助,我会尝试创建一个小数据集来练手。

exampleDF <- data.frame(id = seq(1:100), taxa = c(rep("collembola", 50), rep("mite", 25), 
rep("ant", 25)), length = c(rnorm(40, 1, 0.5), rep("NA", 10), rnorm(20, 0.8, 0.1), rep("NA", 
5), rnorm(20, 2.5, 0.5), rep("NA", 5)), width = c(rnorm(40, 0.5, 0.25), rep("NA", 10), 
rnorm(20, 0.3, 0.01), rep("NA", 5), rnorm(20, 1, 0.1), rep("NA", 5)))

以下是我尝试过但未成功的几件事:

# mean imputation to recode NA in length and width with means 
  (could do random imputation but unnecessary here)
mean.imp <- function(x) { 
  missing <- is.na(x) 
  n.missing <-sum(missing) 
  x.obs <-a[!missing] 
  imputed <- x 
  imputed[missing] <- mean(x.obs) 
  return (imputed) 
  } 

mean.imp(exampleDF[exampleDF$taxa == "collembola", "length"])

n.taxa <- length(unique(exampleDF$taxa))
for(i in 1:n.taxa) {
  mean.imp(exampleDF[exampleDF$taxa == unique(exampleDF$taxa[i]), "length"])
} # no way to get back into dataframe in proper places, try plyr? 

另一种尝试:

imp.mean <- function(x) {
  a <- mean(x, na.rm = TRUE)
  return (ifelse (is.na(x) == TRUE , a, x)) 
 } # tried but not sure how to use this in ddply

Diet2 <- ddply(exampleDF, .(taxa), transform, length2 = function(x) {
  a <- mean(exampleDF$length, na.rm = TRUE)
  return (ifelse (is.na(exampleDF$length) == TRUE , a, exampleDF$length)) 
  })

有什么建议吗?


8
你应该考虑使用mice软件包来填补数值缺失。 - Wojciech Sobala
2
mi 包也非常不错。Ameliamicemi 都要快得多,但它确实依赖于您的变量是多元正态分布的。 - richiemorrisroe
6个回答

50

这不是我自己的技巧,我在一段时间前在论坛上看到了它:

dat <- read.table(text = "id    taxa        length  width
101   collembola  2.1     0.9
102   mite        0.9     0.7
103   mite        1.1     0.8
104   collembola  NA      NA
105   collembola  1.5     0.5
106   mite        NA      NA", header=TRUE)


library(plyr)
impute.mean <- function(x) replace(x, is.na(x), mean(x, na.rm = TRUE))
dat2 <- ddply(dat, ~ taxa, transform, length = impute.mean(length),
     width = impute.mean(width))

dat2[order(dat2$id), ] #plyr orders by group so we have to reorder

编辑 一个使用 for 循环的非 plyr 方法:

for (i in which(sapply(dat, is.numeric))) {
    for (j in which(is.na(dat[, i]))) {
        dat[j, i] <- mean(dat[dat[, "taxa"] == dat[j, "taxa"], i],  na.rm = TRUE)
    }
}

多年后编辑,这里有一个data.tabledplyr的方法:

data.table

library(data.table)
setDT(dat)

dat[, length := impute.mean(length), by = taxa][,
    width := impute.mean(width), by = taxa]

dplyr

library(dplyr)

dat %>%
    group_by(taxa) %>%
    mutate(
        length = impute.mean(length),
        width = impute.mean(width)  
    )

4
@djhocking,感谢Hadley,我找到了这个东西的来源:(链接) - Tyler Rinker
如果我有太多的列需要进行填充,如何使用mutate进行填充? - JodeCharger100
@JyothsnaHarithsa 我可能会使用 mutate_if。另外还可以看看 mutate_atmutate_all - Tyler Rinker

3

还有几个选项:

1)使用的新nafill函数

library(data.table)
setDT(dat)

cols <- c("length", "width")

dat[, (cols) := lapply(.SD, function(x) nafill(x, type = "const", fill = mean(x, na.rm = TRUE)))
    , by = taxa
    , .SDcols = cols][]

2) 使用 na.aggregate 函数

library(zoo)
library(data.table)
setDT(dat)

cols <- c("length", "width")

dat[, (cols) := lapply(.SD, na.aggregate)
    , by = taxa
    , .SDcols = cols][]
na.aggregate 的默认函数是 mean;如果您想使用其他函数,应该使用 FUN 参数进行指定(例如:FUN = median)。另请参阅帮助文件 ?na.aggregate。当然,您也可以在 tidyverse 中使用此函数。
library(dplyr)
library(zoo)

dat %>% 
  group_by(taxa) %>% 
  mutate_at(cols, na.aggregate)

2
在回答这个问题之前,我想说我是R语言的初学者。如果我的回答有误,请告诉我。
代码:
DF[is.na(DF$length), "length"] <- mean(na.omit(telecom_original_1$length))

同样的方法也适用于宽度。

DF代表数据框的名称。

谢谢,Parthi


1

R-base

另一种基于 vapply() + ave() 的 R-base 方法。

类强制转换

> vapply(X = exampleDF, FUN = class, FUN.VALUE = "integer")
         id        taxa      length       width 
  "integer" "character" "character" "character" 

在执行“均值插补”之前,我们需要将其应用于“字符”类的列,因此我们首先将其强制转换为“数值”类型:
exampleDF[, c("length", "width")] <- 
  lapply(exampleDF[, c("length", "width")], as.numeric)

方法

# exampleDF[, c("length", "width")] <- 
vapply(X = exampleDF[, c("length", "width")], 
       FUN = \(x) {
         ave(x = x, 
             exampleDF[, "taxa"], # grouping 
             FUN = \(y) {
               y[is.na(y)] <- mean(y, na.rm = TRUE) 
               y 
               }
             )
       },
       FUN.VALUE = numeric(length = nrow(exampleDF))
       )

一个不那么死板的方法是将“填充机制”(均值、中位数、最大/最小值等)也作为参数进行指定,这样的方法可能值得封装成一个自定义函数~ impute.sth <- \(x, by, data, fun, ...){}原帖中的数据示例
exampleDF <- 
  data.frame(id = seq(1:100), 
             taxa = c(rep("collembola", 50), rep("mite", 25), rep("ant", 25)), 
             length = c(rnorm(40, 1, 0.5), rep("NA", 10), 
                        rnorm(20, 0.8, 0.1), rep("NA", 5), 
                        rnorm(20, 2.5, 0.5), rep("NA", 5)), 
             width = c(rnorm(40, 0.5, 0.25), rep("NA", 10), 
                       rnorm(20, 0.3, 0.01), rep("NA", 5), 
                       rnorm(20, 1, 0.1), rep("NA", 5)))

0

在@Tyler Rinker的解决方案基础上进行扩展,假设features是要填充的列。在这种情况下,features <- c('length', 'width')。然后使用data.table,解决方案变为:

library(data.table)
setDT(dat)

dat[, (features) := lapply(.SD, impute.mean), by = taxa, .SDcols = features]

-1

我遇到了类似的情况,我可以给出一个非常简单的步骤来对你的列进行分组平均值变异。

library(tidyr)

dataset <- dataset %>% group_by(taxa) %>% mutate(length1= ifelse(is.na(length),mean(length,na.rm = T),length))

View(dataset)

如果需要进一步的帮助,请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接