几何均值:是否有内置的?


106

我试图找到一个内置的几何均值,但找不到。

(显然,内置程序在外壳程序中工作不会节省我的时间,我也不怀疑准确性是否存在任何差异;对于脚本,我尝试尽可能多地使用内置程序,其中(累计)性能提升通常很明显。

万一没有(我怀疑是这样)这是我的。

gm_mean = function(a){prod(a)^(1/length(a))}

11
小心负数和溢出。prod(a)将很快消失或溢出。我试图使用一个大列表来计时,并使用您的方法快速获取了Inf以及1.4与exp(mean(log(x)));舍入问题可能非常严重。
Tristan 2010年

我刚刚快速编写了上面的函数,因为我确定在发布此Q后5分钟,有人会告诉我R的gm内置函数。因此没有内置功能,因此一定要花时间根据您的评论重新编码。我加1。
doug 2010年

1
我只是标记这个几何均值内置 9年后。
smci

Answers:


77

这是一个矢量化的,零容忍和NA容忍的函数,用于计算R中的几何平均值。mean涉及包含非正值length(x)的情况下,需要进行冗长的计算x

gm_mean = function(x, na.rm=TRUE){
  exp(sum(log(x[x > 0]), na.rm=na.rm) / length(x))
}

感谢@ ben-bolker注意na.rm传递,并感谢@Gregor确保传递正确。

我认为某些评论与NA数据和零值的错误等价性有关。在我想到的应用程序中,它们是相同的,但是当然,通常情况并非如此。因此,如果要包括可选的零传播,并且length(x)NA删除时进行不同的处理,则以下内容是上述函数的稍长替代方法。

gm_mean = function(x, na.rm=TRUE, zero.propagate = FALSE){
  if(any(x < 0, na.rm = TRUE)){
    return(NaN)
  }
  if(zero.propagate){
    if(any(x == 0, na.rm = TRUE)){
      return(0)
    }
    exp(mean(log(x), na.rm = na.rm))
  } else {
    exp(sum(log(x[x > 0]), na.rm=na.rm) / length(x))
  }
}

请注意,它还会检查任何负值,并返回更多信息和适当的信息,NaN即没有为负值定义几何平均值(而是为零)。感谢发表评论的评论者,他们一直在我的案子上发表评论。


2
将其na.rm作为参数传递会更好吗(即,为了与其他R摘要函数保持一致,让用户决定是否要接受NA容忍)?我很担心自动排除零-我也会选择这个。
本·博克

1
也许您是正确na.rm的选择。我将更新我的答案。至于排除零,对于非零值(包括零),几何平均值是不确定的。上面是几何平均值的通用修正方法,其中零(或在这种情况下为所有非零)的虚拟值为1,这对乘积没有影响(或对数和为零)。
Paul McMurdie 2014年

*我的意思是对非正值的通用修正,零是使用几何均数时最常见的修正。
Paul McMurdie 2014年

1
您的na.rm传递功能无法按编码方式工作...请参阅gm_mean(c(1:3, NA), na.rm = T)。您需要& !is.na(x)从向量子集中删除,并且由于sumis 的第一个arg ...,您需要na.rm = na.rm按名称传递,并且还需要在调用中从向量中排除0“和NAlength
2014年

2
当心:x仅包含零(例如)x <- 0,就exp(sum(log(x[x>0]), na.rm = TRUE)/length(x))给出1了几何平均值,这是没有意义的。
adatum

88

不,但是有几个人写过一个,例如这里

另一种可能性是使用此:

exp(mean(log(x)))

使用exp(mean(log(x())))的另一个优点是您可以处理大量的长列表,这在使用prod()使用更明显的公式时会出现问题。注意prod(a)^(1 / length(a))和exp(mean(log(a)))给出相同的答案。
lukeholman 2015年

链接已修复
-PatrickT


12

exp(mean(log(x)))

除非x中为0,否则它将起作用。如果是这样,对数将产生-Inf(-Infinite),该结果的几何平均值始终为0。

一种解决方案是在计算平均值之前删除-Inf值:

geo_mean <- function(data) {
    log_data <- log(data)
    gm <- exp(mean(log_data[is.finite(log_data)]))
    return(gm)
}

您可以使用单线来执行此操作,但这意味着两次计算日志效率不高。

exp(mean(log(i[is.finite(log(i))])))

为什么在可能的情况下计算两次日志:exp(mean(x [x!= 0]))
zzk 2014年

两种方法都得出均值错误,因为sum(x) / length(x)如果过滤x并将其传递给,则均值的分母是错误的mean
Paul McMurdie 2014年

我认为过滤是一个坏主意,除非您明确表示要这样做(例如,如果我正在编写通用函数,则我不会将过滤设置为默认值)-如果这是一次性代码,并且您已经非常仔细地考虑了过滤归零在您的问题中实际上意味着什么(!)
Ben Bolker 2014年

根据定义,一组包含零的数字的几何平均值应为零!math.stackexchange.com/a/91445/221143
克里斯(Chris)

6

我完全使用了马克说的话。这样,即使使用轻敲,您也可以使用内置mean功能,而无需定义您的功能!例如,要计算每组数据$ value的几何均值:

exp(tapply(log(data$value), data$group, mean))

3

这个版本提供了比其他答案更多的选择。

  • 它允许用户区分不是(真实)数字的结果和不可用的结果。如果存在负数,则答案将不是实数,因此NaN将返回。如果是所有NA值,则函数将返回NA_real_以反映实际值实际上不可用。这是一个细微的差异,但可能会产生(稍微)更可靠的结果。

  • 第一个可选参数zero.rm旨在使用户零影响输出而不将其设为零。如果zero.rm将设置为FALSE并将eta其设置为NA_real_(其默认值),则零将结果缩小为一。我对此没有任何理论上的理由-似乎更有意义的是不忽略零,而是“做一些不涉及自动使结果为零的事情”。

  • eta下列讨论启发了这种处理零的方法:https : //support.bioconductor.org/p/64014/

geomean <- function(x,
                    zero.rm = TRUE,
                    na.rm = TRUE,
                    nan.rm = TRUE,
                    eta = NA_real_) {
    nan.count <- sum(is.nan(x))
     na.count <- sum(is.na(x))
  value.count <- if(zero.rm) sum(x[!is.na(x)] > 0) else sum(!is.na(x))

  #Handle cases when there are negative values, all values are missing, or
  #missing values are not tolerated.
  if ((nan.count > 0 & !nan.rm) | any(x < 0, na.rm = TRUE)) {
    return(NaN)
  }
  if ((na.count > 0 & !na.rm) | value.count == 0) {
    return(NA_real_)
  }

  #Handle cases when non-missing values are either all positive or all zero.
  #In these cases the eta parameter is irrelevant and therefore ignored.
  if (all(x > 0, na.rm = TRUE)) {
    return(exp(mean(log(x), na.rm = TRUE)))
  }
  if (all(x == 0, na.rm = TRUE)) {
    return(0)
  }

  #All remaining cases are cases when there are a mix of positive and zero
  #values.
  #By default, we do not use an artificial constant or propagate zeros.
  if (is.na(eta)) {
    return(exp(sum(log(x[x > 0]), na.rm = TRUE) / value.count))
  }
  if (eta > 0) {
    return(exp(mean(log(x + eta), na.rm = TRUE)) - eta)
  }
  return(0) #only propagate zeroes when eta is set to 0 (or less than 0)
}

1
您是否可以添加一些细节来说明这与现有解决方案有何不同/改进?(dplyr除非有必要,否则我个人不想像这样的实用程序那样增加严重的依赖关系……)
Ben Bolker

我同意,case_whens有点愚蠢,因此我删除了它们,并删除了对ifs 的依赖。我还提供了一些详细说明。
克里斯·咖啡

1
我去你的想法后,改变的默认nan.rm,以TRUE对齐所有三个```.rm``参数。
克里斯·咖啡

1
另一种风格的nitpick。ifelse设计用于矢量化。仅需检查一个条件,使用起来就更惯用了value.count <- if(zero.rm) sum(x[!is.na(x)] > 0) else sum(!is.na(x))
Gregor Thomas

它看起来也比更好ifelse。变了 谢谢!
克里斯·咖啡


3

如果您的数据中缺少值,这种情况很少见。您需要再添加一个参数。

您可以尝试以下代码:

exp(mean(log(i[ is.finite(log(i)) ]), na.rm = TRUE))

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.