缺失值的多重插补

机器算法验证 r spss 缺失数据 多重插补
2022-03-25 00:49:57

我想在某些约束下使用插补来替换我的数据集中的缺失值。

例如,我希望插补变量x1大于或等于我的其他两个变量的总和,比如x2x3我也想x3被任一0or推定>= 14,我想x2被任一0or推定>= 16

我尝试在 SPSS 中为多重插补定义这些约束,但在 SPSS 中我只能定义最大值和最小值。有没有办法在 SPSS 中定义进一步的约束,或者你知道任何 R 包可以让我定义这样的约束来填补缺失值吗?

我的数据如下:

   x1 =c(21, 50, 31, 15, 36, 82, 14, 14, 19, 18, 16, 36, 583, NA,NA,NA, 50, 52, 26, 24)
   x2 = c(0, NA, 18,0, 19, 0, NA, 0, 0, 0, 0, 0, 0,NA,NA, NA, 22, NA, 0, 0)
   x3 = c(0, 0, 0, 0, 0, 54, 0 ,0, 0, 0, 0, 0, 0, NA, NA, NA, NA, 0, 0, 0)
   dat=data.frame(x1=x1, x2=x2, x3=x3)
   > dat
       x1 x2 x3
   1   21  0  0
   2   50 NA  0
   3   31 18  0
   4   15  0  0
   5   36 19  0
   6   82  0 54
   7   14 NA  0
   8   14  0  0
   9   19  0  0
   10  18  0  0
   11  16  0  0
   12  36  0  0
   13 583  0  0
   14  NA NA NA
   15  NA NA NA
   16  NA NA NA
   17  50 22 NA
   18  52 NA  0
   19  26  0  0
   20  24  0  0
4个回答

一种解决方案是为mice包编写​​自己的自定义插补函数。该软件包为此做好了准备,并且设置令人惊讶地无痛。

首先,我们按照建议设置数据:

dat=data.frame(x1=c(21, 50, 31, 15, 36, 82, 14, 14, 19, 18, 16, 36, 583, NA,NA,NA, 50, 52, 26, 24), 
               x2=c(0, NA, 18,0, 19, 0, NA, 0, 0, 0, 0, 0, 0,NA,NA, NA, 22, NA, 0, 0), 
               x3=c(0, 0, 0, 0, 0, 54, 0 ,0, 0, 0, 0, 0, 0, NA, NA, NA, NA, 0, 0, 0))

接下来我们加载mice包,看看它默认选择了哪些方法:

library(mice)
# Do a non-imputation
imp_base <- mice(dat, m=0, maxit = 0)

# Find the methods that mice chooses
imp_base$method
# Returns: "pmm" "pmm" "pmm"

# Look at the imputation matrix
imp_base$predictorMatrix
# Returns:
#   x1 x2 x3
#x1  0  1  1
#x2  1  0  1
#x3  1  1  0

pmm代表预测均值匹配- 可能是用于插补连续变量的最流行的插补算法。它使用回归模型计算预测值,并选择最接近预测值的 5 个元素(通过欧几里得距离)。这些选择的元素称为供体池,最终值是从该供体池中随机选择的。

从预测矩阵中,我们发现这些方法获得了对限制感兴趣的传递变量。请注意,行是目标变量,列是预测变量。如果 x1 在 x3 列中没有 1,我们将不得不将其添加到矩阵中:imp_base$predictorMatrix["x1","x3"] <- 1

现在到有趣的部分,生成插补方法。我在这里选择了一种相当粗略的方法,如果它们不符合标准,我将丢弃所有值。这可能会导致较长的循环时间,并且保留有效的插补并仅重做剩余的插补可能会更有效,尽管这需要更多的调整。

# Generate our custom methods
mice.impute.pmm_x1 <- 
  function (y, ry, x, donors = 5, type = 1, ridge = 1e-05, version = "", 
            ...) 
  {
    max_sum <- sum(max(x[,"x2"], na.rm=TRUE),
                   max(x[,"x3"], na.rm=TRUE))
    repeat{
      vals <- mice.impute.pmm(y, ry, x, donors = 5, type = 1, ridge = 1e-05,
                              version = "", ...)
      if (all(vals < max_sum)){
        break
      }
    }
    return(vals)
  }

mice.impute.pmm_x2 <- 
  function (y, ry, x, donors = 5, type = 1, ridge = 1e-05, version = "", 
            ...) 
  {
    repeat{
      vals <- mice.impute.pmm(y, ry, x, donors = 5, type = 1, ridge = 1e-05,
                              version = "", ...)
      if (all(vals == 0 | vals >= 14)){
        break
      }
    }
    return(vals)
  }

mice.impute.pmm_x3 <- 
  function (y, ry, x, donors = 5, type = 1, ridge = 1e-05, version = "", 
            ...) 
  {
    repeat{
      vals <- mice.impute.pmm(y, ry, x, donors = 5, type = 1, ridge = 1e-05,
                              version = "", ...)
      if (all(vals == 0 | vals >= 16)){
        break
      }
    }
    return(vals)
  }

一旦我们完成了定义方法,我们就可以简单地更改以前的方法。如果您只想更改单个变量,那么您可以简单地使用imp_base$method["x2"] <- "pmm_x2",但对于此示例,我们将全部更改(命名不是必需的):

imp_base$method <- c(x1 = "pmm_x1", x2 = "pmm_x2", x3 = "pmm_x3")

# The predictor matrix is not really necessary for this example
# but I use it just to illustrate in case you would like to 
# modify it
imp_ds <- 
  mice(dat, 
       method = imp_base$method, 
       predictorMatrix = imp_base$predictorMatrix)

现在让我们看一下第三个估算数据集:

> complete(imp_ds, action = 3)
    x1 x2 x3
1   21  0  0
2   50 19  0
3   31 18  0
4   15  0  0
5   36 19  0
6   82  0 54
7   14  0  0
8   14  0  0
9   19  0  0
10  18  0  0
11  16  0  0
12  36  0  0
13 583  0  0
14  50 22  0
15  52 19  0
16  14  0  0
17  50 22  0
18  52  0  0
19  26  0  0
20  24  0  0

好的,这就是工作。我喜欢这个解决方案,因为您可以搭载主流功能,只需添加您认为有意义的限制。

更新

为了强制执行评论中提到的@t0x1n 的严格限制,我们可能希望在包装函数中添加以下功能:

  1. 在循环期间保存有效值,以便之前部分成功运行的数据不会被丢弃
  2. 一种逃避机制,以避免无限循环
  3. 在尝试x次但没有找到合适的匹配项后膨胀供体池(这主要适用于 pmm)

这导致了一个稍微复杂的包装函数:

mice.impute.pmm_x1_adv <-   function (y, ry, 
                                      x, donors = 5, 
                                      type = 1, ridge = 1e-05, 
                                      version = "", ...) {
  # The mice:::remove.lindep may remove the parts required for
  # the test - in those cases we should escape the test
  if (!all(c("x2", "x3") %in% colnames(x))){
    warning("Could not enforce pmm_x1 due to missing column(s):",
            c("x2", "x3")[!c("x2", "x3") %in% colnames(x)])
    return(mice.impute.pmm(y, ry, x, donors = 5, type = 1, ridge = 1e-05,
                           version = "", ...))
  }

  # Select those missing
  max_vals <- rowSums(x[!ry, c("x2", "x3")])

  # We will keep saving the valid values in the valid_vals
  valid_vals <- rep(NA, length.out = sum(!ry))
  # We need a counter in order to avoid an eternal loop
  # and for inflating the donor pool if no match is found
  cntr <- 0
  repeat{
    # We should be prepared to increase the donor pool, otherwise
    # the criteria may become imposs
    donor_inflation <- floor(cntr/10)
    vals <- mice.impute.pmm(y, ry, x, 
                            donors = min(5 + donor_inflation, sum(ry)), 
                            type = 1, ridge = 1e-05,
                            version = "", ...)

    # Our criteria check
    correct <- vals < max_vals
    if (all(!is.na(valid_vals) |
              correct)){
      valid_vals[correct] <-
        vals[correct]
      break
    }else if (any(is.na(valid_vals) &
                    correct)){
      # Save the new valid values
      valid_vals[correct] <-
        vals[correct]
    }

    # An emergency exit to avoid endless loop
    cntr <- cntr + 1
    if (cntr > 200){
      warning("Could not completely enforce constraints for ",
              sum(is.na(valid_vals)),
              " out of ",
              length(valid_vals),
              " missing elements")
      if (all(is.na(valid_vals))){
        valid_vals <- vals
      }else{
        valid_vals[is.na(valid_vals)] <- 
          vals[is.na(valid_vals)]
      }
      break
    }
  }
  return(valid_vals)
}

请注意,这并没有表现得那么好,很可能是因为建议的数据集在所有情况下都没有通过约束而没有丢失。我需要在它开始表现之前将循环长度增加到 400-500。我认为这是无意的,您的估算应该模仿实际数据的生成方式。

优化

该参数ry包含非缺失值,我们可以通过删除我们发现符合条件的插补的元素来加速循环,但由于我不熟悉内部函数,所以我避免这样做。

我认为当您有需要时间来完成的强大约束时,最重要的事情是并行化您的插补(请参阅我在 CrossValidated 上的回答)。大多数今天的计算机都有 4-8 个内核,而 R 默认只使用其中一个。通过将内核数量增加一倍,时间可以(几乎)减半。

插补时缺少参数

关于在x2插补时丢失的问题 - 老鼠实际上从不将缺失值输入x- data.frame老鼠方法包括在开始时填写一些随机值。插补的链部分限制了该初始值的影响。如果您查看-function,您可以在插补调用( -function)mice之前找到它:mice:::sampler

...
if (method[j] != "") {
  for (i in 1:m) {
    if (nmis[j] < nrow(data)) {
      if (is.null(data.init)) {
        imp[[j]][, i] <- mice.impute.sample(y, 
                                            ry, ...)
      }
      else {
        imp[[j]][, i] <- data.init[!ry, j]
      }
    }
    else imp[[j]][, i] <- rnorm(nrow(data))
  }
}
...

data.init可以提供给mice函数,mice.imput.sample一个基本的采样过程。

参观顺序

如果访问顺序很重要,您可以指定mice-function 运行插补的顺序。默认为 from1:ncol(data)但您可以将 设置为visitSequence您喜欢的任何内容。

我能找到的最接近的是Amelia 的先前信息包含。请参阅小插图中的第 4.7 章,特别是 4.7.2:

观察级先验

研究人员通常会根据先前的研究、学术共识或个人经验,获得有关缺失数据值的额外先验信息。Amelia 可以结合这些信息来产生大大改进的插补。Amelia 算法允许用户包含有关单个缺失数据单元的信息贝叶斯先验,而不是更一般的模型参数,其中许多参数没有直接意义。

先验的结合遵循基本的贝叶斯分析,其中插补结果是基于模型的插补和先验均值的加权平均值,其中权重是数据和先验相对强度的函数:当模型预测得很好时,插补会降低先验权重,反之亦然(Honaker and King,2010)。

关于个人观察的先验应该描述分析师对缺失数据单元分布的信念。这可以采取均值和标准差或置信区间的形式。例如,我们可能知道 1986 年泰国的关税税率在 40% 左右,但我们对确切值有一些不确定性。然后,我们对缺失数据单元分布的先前信念以 40 为中心,其标准差反映了我们对先前信念的不确定性。

要输入先验,您必须构建一个包含四列或五列的先验矩阵。矩阵的每一行代表一个观察或一个变量的先验。在任何行中,第一列的条目是观察的行,第二列的条目是观察的列。在四列先验矩阵中,第三列和第四列是缺失值先验分布的均值和标准差。

因此,虽然您通常无法说出类似的x1<x2+x3内容,但您可以遍历您的数据集并为每个相关案例添加一个观察级别的先验。也可以应用常量边界(例如将 x1、x2 和 x3 设置为非负数)。例如:

priors = matrix(NA, nrow=0, ncol=5);
for (i in seq(1, length(data))) 
{
    x1 = data$x1[i];
    x2 = data$x2[i];
    x3 = data$x3[i];

    if (is.na(x1) && !is.na(x2) && !is.na(x3))
    {
        priors = rbind(priors, c(i, 1, 0, x2+x3, 0.999999))
    }
}

amelia(data, m=1, bound = rbind(c(1, 0, Inf), c(2, 0, Inf), c(3, 0, Inf)), pr = priors);

在预测均值匹配多重插补中,约束可能更容易实现。这假设存在大量具有满足约束条件的非缺失约束变量的观测值。我正在考虑在 RHmiscaregImpute函数中实现这一点。您可能想在一个月左右后再回来查看。指定供体观察到的与目标的最大距离很重要,因为约束会使供体远离理想的无约束供体。

我相信Amelia(Amelia II)包目前对指定数据值范围约束具有最全面的支持。但是,问题在于Amelia假设数据是多元正态的。

如果在您的情况下,多元正态性假设不适用,您可能需要检查micepackage,它通过chained equations实现多重插补(MI) 这个包没有多元正态性的假设。它还有一个功能可能足以指定约束,但我不确定到什么程度。该函数被调用您可以在文档中阅读它:http: //cran.r-project.org/web/packages/mice/mice.pdf另一个好处是它在允许规范用户定义的插补函数和更广泛的算法选择方面的灵活性。这是一个关于执行 MI 的教程,使用squeeze()micemicehttp://www.ats.ucla.edu/stat/r/faq/R_pmm_mi.htm

据我了解,Harrell 博士的Hmisc软件包使用相同的链式方程预测均值匹配)方法,可能支持非正态数据(normpmm方法除外)。也许他已经按照上面的回答实现了约束规范功能。我没用过aregImpute(),所以不能多说(我用过Ameliaand mice,但我绝对不是统计学专家,只是想尽可能多地学习)。

最后,您可能会发现以下有趣的,有点过时但仍然很好的方法,方法和软件的概述,用于对具有缺失值的数据进行多重插补: http ://www.ncbi.nlm.nih.gov/pmc/articles /PMC1839993我确信有更新的关于 MI 的概述论文,但目前我只知道这些。我希望这有点帮助。