如何在 JavaScript(或 PHP)中获取数组的中位数和四分位数/百分位数?

Posted

技术标签:

【中文标题】如何在 JavaScript(或 PHP)中获取数组的中位数和四分位数/百分位数?【英文标题】:How to get median and quartiles/percentiles of an array in JavaScript (or PHP)? 【发布时间】:2018-07-21 01:16:00 【问题描述】:

这个问题变成了问答,因为我很难找到答案,并认为它对其他人有用

我有一个 javascript 值数组,需要在 JavaScript 中计算其 Q2(第 50 个百分位,即 MEDIAN)、Q1(第 25 个百分位 ) 和 Q3(第 75 个百分位)值。

【问题讨论】:

Related (not vanilla JS, unfortunately)。 Related (median only)。 Also probably related (php). 【参考方案1】:

找了半天,发现不同的版本给出不同的结果,我在Bastian Pöttner's web blog上找到了这个不错的sn-p,但是对于PHP。对于相同的价格,我们得到数据的平均值标准差(对于正态分布)...

PHP 版本

//from https://blog.poettner.de/2011/06/09/simple-statistics-with-php/

function Median($Array) 
  return Quartile_50($Array);


function Quartile_25($Array) 
  return Quartile($Array, 0.25);


function Quartile_50($Array) 
  return Quartile($Array, 0.5);


function Quartile_75($Array) 
  return Quartile($Array, 0.75);


function Quartile($Array, $Quartile) 
  sort($Array);
  $pos = (count($Array) - 1) * $Quartile;

  $base = floor($pos);
  $rest = $pos - $base;

  if( isset($Array[$base+1]) ) 
    return $Array[$base] + $rest * ($Array[$base+1] - $Array[$base]);
   else 
    return $Array[$base];
  


function Average($Array) 
  return array_sum($Array) / count($Array);


function StdDev($Array) 
  if( count($Array) < 2 ) 
    return;
  

  $avg = Average($Array);

  $sum = 0;
  foreach($Array as $value) 
    $sum += pow($value - $avg, 2);
  

  return sqrt((1 / (count($Array) - 1)) * $sum);

根据作者的 cmets,我简单地写了一个 JavaScript 翻译,肯定会有用,因为令人惊讶的是,在网络上几乎不可能找到等效的 JavaScript,否则需要额外的库,例如数学.js

JavaScript 版本

//adapted from https://blog.poettner.de/2011/06/09/simple-statistics-with-php/
function Median(data) 
  return Quartile_50(data);


function Quartile_25(data) 
  return Quartile(data, 0.25);


function Quartile_50(data) 
  return Quartile(data, 0.5);


function Quartile_75(data) 
  return Quartile(data, 0.75);


function Quartile(data, q) 
  data=Array_Sort_Numbers(data);
  var pos = ((data.length) - 1) * q;
  var base = Math.floor(pos);
  var rest = pos - base;
  if( (data[base+1]!==undefined) ) 
    return data[base] + rest * (data[base+1] - data[base]);
   else 
    return data[base];
  


function Array_Sort_Numbers(inputarray)
  return inputarray.sort(function(a, b) 
    return a - b;
  );


function Array_Sum(t)
   return t.reduce(function(a, b)  return a + b; , 0); 


function Array_Average(data) 
  return Array_Sum(data) / data.length;


function Array_Stdev(tab)
   var i,j,total = 0, mean = 0, diffSqredArr = [];
   for(i=0;i<tab.length;i+=1)
       total+=tab[i];
   
   mean = total/tab.length;
   for(j=0;j<tab.length;j+=1)
       diffSqredArr.push(Math.pow((tab[j]-mean),2));
   
   return (Math.sqrt(diffSqredArr.reduce(function(firstEl, nextEl)
            return firstEl + nextEl;
          )/tab.length));  

【讨论】:

也许你保留了不是构造函数的函数的约定,用小前导字母写名称。 如果将“rest”重命名为“sawtooth”,代码可能会更容易理解,因为它代表了pos上的sawtooth函数。【参考方案2】:

我更新了第一个答案的 JavaScript 翻译,以使用箭头函数和更简洁的符号。除了std 之外,功能基本保持不变,它现在计算样本标准差(除以arr.length - 1 而不仅仅是arr.length

// sort array ascending
const asc = arr => arr.sort((a, b) => a - b);

const sum = arr => arr.reduce((a, b) => a + b, 0);

const mean = arr => sum(arr) / arr.length;

// sample standard deviation
const std = (arr) => 
    const mu = mean(arr);
    const diffArr = arr.map(a => (a - mu) ** 2);
    return Math.sqrt(sum(diffArr) / (arr.length - 1));
;

const quantile = (arr, q) => 
    const sorted = asc(arr);
    const pos = (sorted.length - 1) * q;
    const base = Math.floor(pos);
    const rest = pos - base;
    if (sorted[base + 1] !== undefined) 
        return sorted[base] + rest * (sorted[base + 1] - sorted[base]);
     else 
        return sorted[base];
    
;

const q25 = arr => quantile(arr, .25);

const q50 = arr => quantile(arr, .50);

const q75 = arr => quantile(arr, .75);

const median = arr => q50(arr);

【讨论】:

这里为什么需要标准差? 计算中位数和分位数不需要它——它只是作为奖励;)【参考方案3】:

TL;DR

其他答案似乎具有"R-7" version of computing quantiles 的可靠实现。下面是使用相同 R-7 方法从 D3 借用的一些上下文和另一个 JavaScript 实现,其好处是 此解决方案符合 es5 (不需要 JavaScript 转换)并且可能涵盖更多优势案例。


来自 D3 的现有解决方案(移植到 es5/“vanilla JS”)

下面的“一些背景”部分应该说服您使用现有的实现而不是自己编写。

D3 的 d3.array 包是一个不错的候选者。它有一个quantile function,本质上是BSD licensed:

https://github.com/d3/d3-array/blob/master/src/quantile.js

我很快创建了一个从 es6 到 d3 的 quantileSorted 函数 (the second function defined in that file) 的普通 JavaScript 的非常直接的端口,需要元素数组已经排序。这里是。我已经根据 d3 自己的结果对其进行了足够的测试,足以认为它是一个有效的端口,但您的体验可能会有所不同(如果您发现不同之处,请在 cmets 中告诉我!):

再次记住,排序必须在调用此函数之前进行,就像在 D3 的 quantileSorted 中一样。

  //Credit D3: https://github.com/d3/d3-array/blob/master/LICENSE
  function quantileSorted(values, p, fnValueFrom) 
    var n = values.length;
    if (!n) 
      return;
    

    fnValueFrom =
      Object.prototype.toString.call(fnValueFrom) == "[object Function]"
        ? fnValueFrom
        : function (x) 
            return x;
          ;

    p = +p;

    if (p <= 0 || n < 2) 
      return +fnValueFrom(values[0], 0, values);
    

    if (p >= 1) 
      return +fnValueFrom(values[n - 1], n - 1, values);
    

    var i = (n - 1) * p,
      i0 = Math.floor(i),
      value0 = +fnValueFrom(values[i0], i0, values),
      value1 = +fnValueFrom(values[i0 + 1], i0 + 1, values);

    return value0 + (value1 - value0) * (i - i0);
  

请注意,fnValueFrom 是一种将复杂对象处理为值的方法。您可以看到in a list of d3 usage examples here 的工作原理——向下搜索.quantile 的使用位置。

快速版本是如果values 是乌龟并且您在每种情况下都对tortoise.age 进行排序,那么您的fnValueFrom 可能是x =&gt; x.age。更复杂的版本,包括在值计算期间可能需要访问索引(参数 2)和整个集合(参数 3)的版本,由读者自行决定。

我在这里添加了一个快速检查,以便如果没有为 fnValueFrom 提供任何内容,或者如果提供的不是函数,那么逻辑会假定 values 中的元素本身就是实际的排序值。


与现有答案的逻辑比较

我有理由确定这在其他两个答案中会减少到相同的版本(请参阅下面的“R-7 方法”),但是如果您需要向产品经理或其他任何人证明您使用它的原因也许以上会有所帮助。

快速比较:

function Quartile(data, q) 
  data=Array_Sort_Numbers(data);        // we're assuming it's already sorted, above, vs. the function use here. same difference.
  var pos = ((data.length) - 1) * q;    // i = (n - 1) * p
  var base = Math.floor(pos);           // i0 = Math.floor(i)
  var rest = pos - base;                // (i - i0);
  if( (data[base+1]!==undefined) ) 
    //      value0    + (i - i0)   * (value1 which is values[i0+1] - value0 which is values[i0])
    return data[base] + rest       * (data[base+1]                 - data[base]);
   else 
    // I think this is covered by if (p <= 0 || n < 2)
    return data[base];
  

所以这在逻辑上很接近/似乎完全相同。我认为我移植的 d3 版本涵盖了更多边缘/无效条件,并包括 fnValueFrom 集成,这两者都可能有用。


R-7 方法与“常识

正如 TL;DR 中提到的,这里的答案,根据d3.array's readme,都使用“R-7 方法”。

此特定实现 [来自 d3] 使用 R-7 method,这是 R 编程语言和 Excel 的默认值。

由于 d3.array 代码与此处的其他答案匹配,我们可以肯定地说它们都在使用 R-7。


背景

在对一些数学和统计数据 StackExchange 网站(1、2)进行了一些调查之后,我发现计算每个分位数都有“常见的”方法,但这些方法通常与九种公认的计算方法的结果。

second link from stats.stackexchange 的答案是关于...

您的教科书很混乱。很少有人或软件以这种方式定义四分位数。 (它往往会使第一个四分位数太小,而第三个四分位数太大。)

R 中的 quantile 函数实现了九种不同的分位数计算方法!

我认为最后一点很有趣,这就是我从这九种方法中挖掘出来的...

***的description of those nine methods here,很好地分组在一个表格中 统计教育杂志上标题为"Quartiles in Elementary Statistics"的文章 SAS.com 上名为 "Sample quantiles: A comparison of 9 definitions" 的博客文章

在 SO 问题"d3.quantile seems to be calculating q1 incorrectly" 中很好地展示了 d3 使用“方法 7”(R-7) 来确定分位数与常见的合理方法之间的差异,并且在 this post 中详细描述了原因可以在 philippe 的 php 版本的原始源中找到。

以下是谷歌翻译的一些内容(原文为德语):

在我们的示例中,该值位于 (n + 1) / 4 位 = 5.25,即介于第 5 个值 (= 5) 和第 6 个值 (= 7) 之间。分数 (0.25) 表示除了 5 的值之外,还增加了 5 和 6 之间距离的 1/4。因此,Q1 为 5 + 0.25 * 2 = 5.5。

总而言之,这告诉我,我可能不应该尝试根据我对四分位数的理解来编写代码,而应该借用别人的解决方案。

【讨论】:

以上是关于如何在 JavaScript(或 PHP)中获取数组的中位数和四分位数/百分位数?的主要内容,如果未能解决你的问题,请参考以下文章

如何从我的网站获取访问者位置(Javascript 或 PHP)

如何使用 JavaScript / PHP 从图像中获取地理位置数据?

如何使用 php 通过谷歌地图获取步行或驾车距离

在 PHP 或 JavaScript 中获取用户的纬度和经度以便在 PHP 中使用

使用 javascript 或 php 从图像中获取所有 HEX 值

如何使用 javascript 或 JQuery 从不可见的 iframe 中获取隐藏字段值