ค่าเฉลี่ยทางเรขาคณิต: มีในตัวหรือไม่?


106

ฉันพยายามหาค่าเฉลี่ยเรขาคณิตในตัว แต่ทำไม่ได้

(เห็นได้ชัดว่าบิวท์อินจะไม่ช่วยฉันตลอดเวลาในขณะที่ทำงานในเชลล์และฉันไม่สงสัยว่ามีความแม่นยำแตกต่างกันสำหรับสคริปต์ฉันพยายามใช้บิวท์อินให้บ่อยที่สุดโดยที่ (สะสม) การเพิ่มประสิทธิภาพมักจะเห็นได้ชัดเจน

ในกรณีที่ไม่มี (ซึ่งฉันสงสัยว่าเป็นเช่นนั้น) นี่เป็นของฉัน

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

11
ระมัดระวังเกี่ยวกับจำนวนลบและการล้น prod (a) จะต่ำหรือล้นอย่างรวดเร็ว ฉันพยายามกำหนดเวลานี้โดยใช้รายการใหญ่และได้รับ Inf อย่างรวดเร็วโดยใช้วิธีของคุณเทียบกับ 1.4 ด้วย exp (ค่าเฉลี่ย (log (x))); ปัญหาการปัดเศษอาจค่อนข้างรุนแรง
Tristan

ฉันเพิ่งเขียนฟังก์ชันข้างต้นอย่างรวดเร็วเพราะฉันแน่ใจว่า 5 นาทีหลังจากโพสต์ Q นี้จะมีคนบอกฉันว่ามี R ในตัวสำหรับ gm ดังนั้นจึงไม่มีในตัวดังนั้นจึงคุ้มค่าที่จะสละเวลาในการเขียนโค้ดใหม่ตามคำพูดของคุณ +1 จากฉัน
doug

1
ฉันเพิ่งติดแท็กค่าเฉลี่ยเรขาคณิตนี้และในตัว 9 ปีต่อมา
smci

คำตอบ:


78

นี่คือฟังก์ชัน vectorized, zero- และ NA-tolerant สำหรับการคำนวณค่าเฉลี่ยทางเรขาคณิตใน 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ผ่านเป็นอาร์กิวเมนต์ (เช่นให้ผู้ใช้ตัดสินใจว่าพวกเขาต้องการที่จะทนต่อ NA หรือไม่เพื่อความสอดคล้องกับฟังก์ชันสรุป R อื่น ๆ ) ฉันกังวลเกี่ยวกับการไม่รวมศูนย์โดยอัตโนมัติ - ฉันจะทำให้เป็นตัวเลือกนั้นเช่นกัน
Ben Bolker

1
บางทีคุณอาจคิดถูกที่จะผ่านna.rmเป็นตัวเลือก ฉันจะอัปเดตคำตอบของฉัน สำหรับการไม่รวมศูนย์นั้นค่าเฉลี่ยทางเรขาคณิตนั้นไม่ได้กำหนดไว้สำหรับค่าที่ไม่เป็นบวกรวมถึงศูนย์ด้วย ข้างต้นเป็นการแก้ไขทั่วไปสำหรับค่าเฉลี่ยทางเรขาคณิตซึ่งศูนย์ (หรือในกรณีนี้ทั้งหมดที่ไม่ใช่ศูนย์) จะได้รับค่าดัมมี่เป็น 1 ซึ่งไม่มีผลต่อผลิตภัณฑ์ (หรือเทียบเท่ากับศูนย์ในผลรวมลอการิทึม)
Paul McMurdie

* ฉันหมายถึงการแก้ไขทั่วไปสำหรับค่าที่ไม่เป็นบวกโดยศูนย์เป็นค่าที่พบบ่อยที่สุดเมื่อใช้ค่าเฉลี่ยทางเรขาคณิต
Paul McMurdie

1
คุณna.rmส่งผ่านไม่ได้ทำงานเป็นรหัส ... gm_mean(c(1:3, NA), na.rm = T)เห็น คุณต้องลบออก& !is.na(x)จากเซตย่อยของเวกเตอร์และเนื่องจากอาร์กิวเมนต์แรกของsumคือ...คุณต้องส่งna.rm = na.rmชื่อและคุณต้องแยก0's และNA' ออกจากเวกเตอร์ในการlengthโทร
Gregor Thomas

2
ระวัง: สำหรับxที่มีเพียงศูนย์ (s), เช่นx <- 0, exp(sum(log(x[x>0]), na.rm = TRUE)/length(x))ให้1สำหรับค่าเฉลี่ยเรขาคณิตซึ่งไม่ได้ทำให้รู้สึก
adatum

88

ไม่มี แต่มีเพียงไม่กี่คนที่ได้เขียนอย่างใดอย่างหนึ่งเช่นที่นี่

ความเป็นไปได้อีกประการหนึ่งคือการใช้สิ่งนี้:

exp(mean(log(x)))

ข้อดีอีกอย่างของการใช้ exp (ค่าเฉลี่ย (log (x))) คือคุณสามารถทำงานกับรายการจำนวนมากที่ยาวได้ซึ่งเป็นปัญหาเมื่อใช้สูตรที่ชัดเจนยิ่งขึ้นโดยใช้ prod () โปรดทราบว่า prod (a) ^ (1 / length (a)) และ exp (mean (log (a))) ให้คำตอบเดียวกัน
lukeholman

ลิงก์ได้รับการแก้ไขแล้ว
PatrickT

15

เราสามารถใช้แพ็คเกจกายสิทธิ์และเรียกใช้ฟังก์ชันgeometric.mean


1
psych::geometric.mean()
smci

ฟังก์ชั่นเหล่านี้ควรเป็นชุดไม่ใช่การเติบโตอย่างน้อยก็เป็นตัวเลือกที่ฉันจะพูด
Christoph Hanck

12

exp(mean(log(x)))

จะใช้งานได้เว้นแต่จะมี 0 ใน x ถ้าเป็นเช่นนั้นบันทึกจะสร้าง -Inf (- ไม่มีที่สิ้นสุด) ซึ่งให้ผลลัพธ์เป็นค่าเฉลี่ยทางเรขาคณิตเป็น 0 เสมอ

วิธีแก้ไขอย่างหนึ่งคือการลบค่า -Inf ก่อนคำนวณค่าเฉลี่ย:

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

คุณสามารถใช้ one-liner เพื่อทำสิ่งนี้ได้ แต่หมายถึงการคำนวณบันทึกสองครั้งซึ่งไม่มีประสิทธิภาพ

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

ทำไมต้องคำนวณบันทึกสองครั้งในเมื่อคุณทำได้: exp (mean (x [x! = 0]))
zzk

ทั้งสองวิธีได้รับไม่ถูกต้องหมายถึงเพราะตัวหารสำหรับค่าเฉลี่ยsum(x) / length(x)คือผิดถ้าคุณกรอง x meanแล้วผ่านไป
Paul McMurdie

ฉันคิดว่าการกรองเป็นความคิดที่ไม่ดีเว้นแต่คุณจะตั้งใจทำอย่างชัดเจน (เช่นถ้าฉันกำลังเขียนฟังก์ชันวัตถุประสงค์ทั่วไปฉันจะไม่ทำการกรองค่าเริ่มต้น) - ตกลงถ้านี่เป็นโค้ดเพียงครั้งเดียวและคุณ คิดอย่างรอบคอบเกี่ยวกับสิ่งที่การกรองศูนย์ออกมีความหมายในบริบทของปัญหาของคุณ (!)
Ben Bolker

ตามความหมายค่าเฉลี่ยทางเรขาคณิตของชุดตัวเลขที่มีศูนย์ควรเป็นศูนย์! math.stackexchange.com/a/91445/221143
คริส

6

ฉันใช้สิ่งที่มาร์คบอก ด้วยวิธีนี้แม้จะใช้ Tapply คุณก็สามารถใช้meanฟังก์ชันในตัวได้โดยไม่จำเป็นต้องกำหนดของคุณ! ตัวอย่างเช่นในการคำนวณค่าทางเรขาคณิตต่อกลุ่มของ data $ 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_whenพวกเขาโง่เล็กน้อยดังนั้นฉันจึงลบพวกเขาออกและการพึ่งพาเพื่อสนับสนุนifs ฉันยังให้รายละเอียดบางอย่าง
Chris Coffee

1
ฉันใช้ความคิดหลังของคุณและเปลี่ยนค่าเริ่มต้นnan.rmเป็นTRUEเพื่อจัดแนวพารามิเตอร์ ".rm" ทั้งสามตัว
Chris Coffee

1
nitpick โวหารอื่น ๆ ifelseถูกออกแบบมาสำหรับ vectorization ด้วยเงื่อนไขเดียวในการตรวจสอบมันจะใช้สำนวนมากกว่าvalue.count <- if(zero.rm) sum(x[!is.na(x)] > 0) else sum(!is.na(x))
Gregor Thomas

มันดูดีกว่าifelseด้วย เปลี่ยนแล้ว. ขอบคุณ!
Chris Coffee


3

ในกรณีที่ไม่มีค่าในข้อมูลของคุณนี่ไม่ใช่กรณีที่หายาก คุณต้องเพิ่มอีกหนึ่งอาร์กิวเมนต์

คุณอาจลองใช้รหัสต่อไปนี้:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.