Геометричне середнє: чи є вбудований?


106

Я намагався знайти вбудований для геометричного середнього, але не міг.

(Очевидно, що вбудований модуль не врятує мене ніколи під час роботи в оболонці, і не підозрюю, що є різниця в точності; для скриптів я намагаюся використовувати вбудовані файли якомога частіше, де (накопичувальний) Часто помітний підвищення продуктивності.

На випадок, якщо такого немає (що я сумніваюся, так) ось моє.

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

11
Обережно ставляться до негативних чисел та переливів. prod (a) буде дуже швидко перетікати або переповнювати. Я спробував виправити це за допомогою великого списку і швидко отримав Inf, використовуючи ваш метод проти 1.4 з exp (середнє (log (x)))); проблема округлення може бути досить гострою.
Трістан

Я просто написав функцію вище, тому що я був впевнений, що через 5 хв після публікації цього питання хтось скаже мені вбудований R для GM. Тому ніякого вбудованого, тому напевно варто витратити час на повторний код у світлі ваших зауважень. + 1 від мене.
дог

1
Я щойно помітив цю геометричну середню і вбудовану через 9 років.
smci

Відповіді:


78

Ось векторизована, нульова та NA-толерантна функція для обчислення геометричної середньої величини в Р. Дослідний 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аргумент (тобто дозволити користувачеві вирішити, хоче він бути стійким до NA чи ні, для узгодженості з іншими функціями зведення R)? Мене нервує автоматичне виключення нулів - я б також зробив цей варіант.
Бен Болкер

1
Можливо, ти маєш рацію щодо проходу na.rmяк варіанту. Я оновлю свою відповідь. Що стосується виключення нулів, то для непозитивних значень, включаючи нулі, середнє геометричне значення не визначене. Вищенаведене є загальним виправленням середнього геометричного значення, в якому нулям (або в даному випадку всі ненулі) присвоюється фіктивне значення 1, яке не впливає на добуток (або, що еквівалентно, нуль у логарифмічній сумі).
Пол Мак-Мерді

* Я мав на увазі загальне виправлення непозитивних значень, нуль є найбільш поширеним, коли використовується геометричне середнє.
Пол Мак-Мерді

1
Ваш na.rmпрохід не працює як закодований ... див gm_mean(c(1:3, NA), na.rm = T). Вам потрібно видалити & !is.na(x)векторну підмножину, і оскільки перший аргумент sumє ..., вам потрібно пройти na.rm = na.rmпо імені, а також потрібно виключити 0's і NA' з вектора у lengthвиклику.
Грегор Томас

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 / довжина (a)) та exp (середнє (log (a))) дають однакову відповідь.
lukeholman

посилання виправлено
PatrickT


12

The

exp(mean(log(x)))

буде працювати, якщо немає 0 у x. Якщо так, то журнал створить -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 (середнє значення (x [x! = 0]))
zzk

обидва підходи отримують середину неправильно, оскільки знаменник середнього sum(x) / length(x)значення неправильний, якщо ви фільтруєте x, а потім передаєте його mean.
Пол МакМерді

Я думаю, що фільтрування - це погана ідея, якщо ви прямо не маєте намір це робити (наприклад, якщо я писав функцію загального призначення, я б не робив фільтрацію за замовчуванням) - Гаразд, якщо це одноразовий фрагмент коду, і ви дуже ретельно подумав про те, що насправді означає фільтрацію нулів у контексті вашої проблеми (!)
Бен Болкер,

За визначенням середнє геометричне набору чисел, що містять нуль, має бути дорівнює нулю! math.stackexchange.com/a/91445/221143
Кріс

6

Я використовую саме те, що говорить Марк. Таким чином, навіть при натисканні, ви можете використовувати вбудовану meanфункцію, не потрібно визначати свою! Наприклад, для обчислення для кожної групи геометричних засобів значення $ $:

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такої утиліти, якщо не потрібно ...)
Бен Болкер

Я згоден, case_whens були трохи нерозумні, тому я усунув їх і залежність на користь ifs. Я також надав певну деталізацію.
Кріс Кава

1
Я пішов з останньої ідеєю і змінити значення за замовчуванням nan.rmдля TRUEвирівняти всі три `` `.rm`` параметрів.
Кріс Кава

1
Ще один стилістичний нітпік. ifelseпризначений для векторизації. З єдиною умовою для перевірки було б ідіоматичніше використовуватиvalue.count <- if(zero.rm) sum(x[!is.na(x)] > 0) else sum(!is.na(x))
Грегор Томас

Це виглядає і приємніше ifelse. Змінено. Дякую!
Кріс Кава


3

Якщо у ваших даних відсутні значення, це не рідкісний випадок. вам потрібно додати ще один аргумент.

Ви можете спробувати наступний код:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.