Geometric Mean: có tích hợp sẵn không?


106

Tôi đã cố gắng tìm một tích hợp cho giá trị trung bình hình học nhưng không thể.

(Rõ ràng là tích hợp sẵn sẽ không giúp tôi tiết kiệm bất kỳ thời gian nào khi làm việc trong shell, tôi cũng không nghi ngờ có bất kỳ sự khác biệt nào về độ chính xác; đối với các tập lệnh, tôi cố gắng sử dụng tích hợp sẵn thường xuyên nhất có thể, trong đó (tích lũy) tăng hiệu suất thường đáng chú ý.

Trong trường hợp không có (mà tôi nghi ngờ là trường hợp) thì đây là của tôi.

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

11
Cẩn thận về số âm và số tràn. prod (a) sẽ dưới hoặc tràn rất nhanh. Tôi đã cố gắng tính thời gian này bằng cách sử dụng một danh sách lớn và nhanh chóng nhận được Inf bằng cách sử dụng phương pháp của bạn so với 1.4 với exp (mean (log (x))); vấn đề làm tròn có thể khá nghiêm trọng.
Tristan

Tôi chỉ viết hàm trên một cách nhanh chóng vì tôi chắc chắn rằng 5 phút sau khi đăng Q này, ai đó sẽ cho tôi biết R tích hợp sẵn cho gm. Vì vậy, không có tích hợp sẵn, vì vậy bạn nên dành thời gian để viết lại mã theo nhận xét của bạn. + 1 từ tôi.
doug

1
Tôi chỉ gắn thẻ trung bình hình học và tích hợp này , 9 năm sau.
smci

Câu trả lời:


77

Dưới đây là một hàm được vectơ hóa, không và NA để tính giá trị trung bình hình học trong R. Việc meantính toán chi tiết liên quan length(x)là cần thiết cho các trường hợp xchứa các giá trị không dương.

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

Cảm ơn @ ben-bolker đã ghi nhận quá trình na.rmchuyển qua và @Gregor để đảm bảo rằng nó hoạt động chính xác.

Tôi nghĩ rằng một số nhận xét có liên quan đến sự tương đương sai của các NAgiá trị trong dữ liệu và số không. Trong ứng dụng, tôi đã nghĩ rằng chúng giống nhau, nhưng tất nhiên điều này nói chung không đúng. Do đó, nếu bạn muốn bao gồm việc truyền các số không tùy chọn và xử lý theo length(x)cách khác trong trường hợp NAloại bỏ, thì sau đây là một thay thế dài hơn một chút cho hàm trên.

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))
  }
}

Lưu ý rằng nó cũng kiểm tra bất kỳ giá trị âm nào và trả về một giá trị thông tin và phù hợp hơn NaNtôn trọng rằng giá trị trung bình hình học không được xác định cho các giá trị âm (mà là cho số không). Cảm ơn những người bình luận đã ở lại trường hợp của tôi về điều này.


2
Sẽ tốt hơn nếu chuyển na.rmqua dưới dạng đối số (tức là để người dùng quyết định xem họ có muốn chấp nhận NA hay không, để nhất quán với các hàm tóm tắt R khác)? Tôi lo lắng về việc tự động loại trừ các số 0 - tôi cũng sẽ coi đó là một lựa chọn.
Ben Bolker

1
Có lẽ bạn đã đúng khi vượt qua na.rmnhư một lựa chọn. Tôi sẽ cập nhật câu trả lời của tôi. Đối với việc loại trừ các số 0, giá trị trung bình hình học không được xác định cho các giá trị không dương, bao gồm cả các số 0. Trên đây là một cách khắc phục phổ biến cho giá trị trung bình hình học, trong đó các số 0 (hoặc trong trường hợp này là tất cả các số khác 0) được cho giá trị giả là 1, không ảnh hưởng đến tích (hoặc tương đương, số 0 trong tổng lôgarit).
Paul McMurdie

* Ý tôi là một cách khắc phục phổ biến cho các giá trị không dương, giá trị không phổ biến nhất khi giá trị trung bình hình học đang được sử dụng.
Paul McMurdie

1
Truyền của bạn na.rmkhông hoạt động như được mã hóa ... hãy xem gm_mean(c(1:3, NA), na.rm = T). Bạn cần xóa tên & !is.na(x)khỏi tập con vectơ và vì đối số đầu tiên của sumnó là ..., bạn cần chuyển na.rm = na.rmtheo tên và bạn cũng cần loại trừ 0's và NA' khỏi vectơ trong lệnh lengthgọi.
Gregor Thomas

2
Hãy cẩn thận: xchỉ chứa (các) số không, chẳng hạn như x <- 0, exp(sum(log(x[x>0]), na.rm = TRUE)/length(x))cho 1ý nghĩa hình học, điều này không có ý nghĩa.
adatum

88

Không, nhưng có một vài người đã viết một, chẳng hạn như ở đây .

Một khả năng khác là sử dụng cái này:

exp(mean(log(x)))

Một ưu điểm khác của việc sử dụng exp (mean (log (x))) là bạn có thể làm việc với danh sách dài gồm các số lớn, điều này có vấn đề khi sử dụng công thức rõ ràng hơn bằng cách sử dụng prod (). Lưu ý rằng prod (a) ^ (1 / length (a)) và exp (mean (log (a))) cho cùng một câu trả lời.
lukeholman

liên kết đã được sửa
PatrickT

15

Chúng ta có thể sử dụng gói psych và gọi hàm images.mean .


1
psych::geometric.mean()
smci

Tôi sẽ nói rằng những chức năng này nên lấy chuỗi chứ không phải sự phát triển của chúng, ít nhất là một tùy chọn.
Christoph Hanck

12

Các

exp(mean(log(x)))

sẽ hoạt động trừ khi có số 0 trong x. Nếu vậy, nhật ký sẽ tạo ra -Inf (-Infinite) luôn dẫn đến giá trị trung bình hình học là 0.

Một giải pháp là loại bỏ giá trị -Inf trước khi tính giá trị trung bình:

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

Bạn có thể sử dụng một lớp lót để làm điều này nhưng nó có nghĩa là tính toán nhật ký hai lần là không hiệu quả.

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

tại sao tính toán nhật ký hai lần khi bạn có thể làm: ([! x = 0] bình (x)) exp
zzk

cả hai cách tiếp cận đều sai giá trị trung bình, bởi vì mẫu số cho giá trị trung bình, sum(x) / length(x)là sai nếu bạn lọc x và sau đó chuyển nó cho mean.
Paul McMurdie

Tôi nghĩ rằng lọc là một ý tưởng tồi trừ khi bạn có ý định làm điều đó một cách rõ ràng (ví dụ: nếu tôi đang viết một hàm có mục đích chung, tôi sẽ không đặt lọc làm mặc định) - OK nếu đây là đoạn mã một lần và bạn đã suy nghĩ rất kỹ về việc lọc các số 0 thực sự có ý nghĩa gì trong bối cảnh vấn đề của bạn (!)
Ben Bolker

Theo định nghĩa, một trung bình hình học của một tập hợp các số có chứa số 0 phải là số không! math.stackexchange.com/a/91445/221143
Chris

6

Tôi sử dụng chính xác những gì Mark nói. Bằng cách này, ngay cả với tapply, bạn có thể sử dụng meanchức năng tích hợp sẵn, không cần xác định chức năng của bạn! Ví dụ: để tính giá trị dữ liệu trung bình theo hình học cho mỗi nhóm:

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

3

Phiên bản này cung cấp nhiều tùy chọn hơn các câu trả lời khác.

  • Nó cho phép người dùng phân biệt giữa các kết quả không phải là số (thực) và những kết quả không có sẵn. Nếu có số âm, thì câu trả lời sẽ không phải là số thực, vì vậy NaNsẽ được trả về. Nếu đó là tất cả các NAgiá trị thì NA_real_thay vào đó hàm sẽ trả về để phản ánh rằng một giá trị thực không có sẵn. Đây là một sự khác biệt nhỏ, nhưng một sự khác biệt có thể mang lại (một chút) kết quả mạnh mẽ hơn.

  • Tham số tùy chọn đầu tiên zero.rmnhằm cho phép người dùng có các số không ảnh hưởng đến đầu ra mà không làm cho nó bằng không. Nếu zero.rmđược đặt thành FALSEetađược đặt thành NA_real_(giá trị mặc định của nó), các số không có tác dụng thu nhỏ kết quả về một. Tôi không có bất kỳ lý do lý thuyết nào cho điều này - có vẻ hợp lý hơn khi không bỏ qua các số 0 mà "làm điều gì đó" không liên quan đến việc tự động làm cho kết quả bằng không.

  • etalà một cách xử lý số không được lấy cảm hứng từ cuộc thảo luận sau: 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
Bạn có thể thêm một số chi tiết giải thích điều này khác với / cải thiện như thế nào trên các giải pháp hiện có? (Cá nhân tôi sẽ không muốn thêm một sự phụ thuộc nặng nề như dplyrcho một tiện ích như vậy trừ khi cần thiết ...)
Ben Bolker

Tôi đồng ý, case_whens hơi ngớ ngẩn, vì vậy tôi đã loại bỏ chúng và sự phụ thuộc có lợi cho ifs. Tôi cũng cung cấp một số chi tiết.
Chris Coffee

1
Tôi đã đi theo ý tưởng thứ hai của bạn và thay đổi mặc định nan.rmthành TRUEđể căn chỉnh cả ba tham số `` .rm ''.
Chris Coffee

1
Một nitpick phong cách khác. ifelseđược thiết kế để vector hóa. Với một điều kiện duy nhất để kiểm tra, sẽ dễ sử dụng thành ngữ hơnvalue.count <- if(zero.rm) sum(x[!is.na(x)] > 0) else sum(!is.na(x))
Gregor Thomas

Nó trông cũng đẹp hơn ifelse. Đã thay đổi. Cảm ơn!
Chris Coffee


3

Trong trường hợp thiếu giá trị trong dữ liệu của bạn, đây không phải là trường hợp hiếm. bạn cần thêm một đối số nữa.

Bạn có thể thử mã sau:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.