【问题标题】:Calculate how many std deviations the values of certain keys are from the mean计算某些键的值与平均值有多少标准偏差
【发布时间】:2019-02-23 04:53:35
【问题描述】:

我在Javascript / React 工作,使用一组包含体育数据的对象。

以下是我正在处理的数据示例:

const mydata = [
  { name: "Tom", year: 2018, statA: 23.2, statB: 12.3 },
  { name: "Bob", year: 2018, statA: 13.2, statB: 10.1 },
  { name: "Joe", year: 2018, statA: 18.2, statB: 19.3 },
  { name: "Tim", year: 2018, statA: 21.1, statB: 21.3 },
  { name: "Jim", year: 2018, statA: 12.5, statB: 32.4 },
  { name: "Nik", year: 2017, statA: 23.6, statB: 23.8 },
  { name: "Tre", year: 2017, statA: 37.8, statB: 18.3 },
  { name: "Ton", year: 2017, statA: 15.3, statB: 12.1 },
  { name: "Bil", year: 2017, statA: 32.2, statB: 41.3 },
  { name: "Geo", year: 2017, statA: 21.5, statB: 39.8 }
];

我在这里遇到的数据操作问题感觉非常具有挑战性,我正在苦苦挣扎。我需要按年对数据中的几个键(statA、statB)中的每一个进行缩放(表示 0,stdev 1)。

例如,查看 statA 列中 year === 2018 的值,我们有 [23.2, 13.2, 18.2, 21.1, 12.5]。作为测试,将此向量插入 R 的 scale() 函数会得到以下结果:

scale(c(23.2, 13.2, 18.2, 21.1, 12.5))

           [,1]
[1,]  1.1765253
[2,] -0.9395274
[3,]  0.1184989
[4,]  0.7321542
[5,] -1.0876511
attr(,"scaled:center")
[1] 17.64
attr(,"scaled:scale")
[1] 4.72578 

...所以在我的原始对象数组中,第一个对象中的值 statA: 23.2 应更新为 1.1765,因为值 23.2 比 Year == 2018 的所有其他 statA 值的平均值高出 1.1765 个标准差. 在我的完整数据集中,我有约 8K 个对象和每个对象中约 50 个键,其中约 40 个我需要逐年扩展。

在高层次上,我认为我必须 (1st) 计算每年每个统计数据的均值和 st dev,并且 (2) 使用该年度该统计数据的均值和 st dev,并将其映射到其标度值。性能/速度对我的应用很重要,我担心普通的 for 循环会非常慢,尽管这是我目前正在尝试的。

对此的任何帮助表示赞赏!

编辑 2:

在我通读/编码之前,想发布我昨天完成的内容:

    const scaleCols = ['statA', 'statB'];
    const allYears = [...new Set(rawData.map(ps => ps.Year))];

    // loop over each year of the data
    for(var i = 0; i < allYears.length; i++) {

        // compute sums and counts (for mean calc)
        thisYearsArray = rawData.filter(d => d.Year === allYears[i])
        sums = {}, counts = {};
        for(var j = 0; j < thisYearsArray.length; j++) {
            for(var k = 0; k < scaleCols.length; k++) {
                if(!(scaleCols[k] in sums)) {
                    sums[scaleCols[k]] = 0;
                    counts[scaleCols[k]] = 0;
                }

                sums[scaleCols[k]] += thisYearsArray[j][scaleCols[k]];
                counts[scaleCols[k]] += 1;
            }
        }

        console.log('sums', sums)
        console.log('counts', counts)
    }

...就像我说的不太好。

编辑:使用 d3 的缩放功能会对此有所帮助吗?

【问题讨论】:

  • 您要求的是算法而不是此问题的编程解决方案。最好在cs.stackexchange.com/questions/tagged/algorithms 或 math.stackexchamge.com 上提问。您可能还想查看 nodejs 的统计库,例如 stats-lite 以获得一些帮助。
  • although that's what I'm attempting currently.你能发布你目前的尝试是什么让我们看看吗?
  • 这不是一个真正的算法,而是一个数据操作问题......只是缩放数据

标签: javascript d3.js data-manipulation


【解决方案1】:

创建 d3 的continuous scale 可以获得相同的结果(与 R 的比例一样)。请参阅下面的 sn-p。

var arr = [23.2, 13.2, 18.2, 21.1, 12.5];
var deviation = d3.deviation(arr)
var mean = d3.mean(arr)

var scale = d3.scaleLinear()
   .domain([mean-deviation, mean+deviation])
   .range([-1, 1]);
   
var result = arr.map(el => scale(el));

console.log(result)
   &lt;script src="https://d3js.org/d3.v5.min.js"&gt;&lt;/script&gt;

【讨论】:

  • 赞成,但您可能需要编辑您的答案:“不幸的是,它需要很多依赖项”。不,它只需要这个:&lt;script src="https://d3js.org/d3.v5.min.js"&gt;&lt;/script&gt;
  • @GerardoFurtado 谢谢,已更改。但是仍然拥有基于 for-loop 的解决方案会比此依赖项中的代码短得多
  • @GerardoFurtado 这可以用 d3 v4(不是 v5)来完成吗?这是我的应用程序使用的...
  • @Canovic 是的,语法是一样的。
【解决方案2】:

作为一名 D3 程序员,我很高兴看到 other answer 使用 D3 比例(特别是因为该问题最初并未使用 标记)。但是,作为answerer already hinted,您不需要在这里使用 D3 比例,这有点矫枉过正。

您只需要(value - mean) / deviation:

var result = arr.map(d => (d - mean) / deviation);

这里是演示:

var arr = [23.2, 13.2, 18.2, 21.1, 12.5];
var deviation = d3.deviation(arr)
var mean = d3.mean(arr)

var result = arr.map(d => (d - mean) / deviation);

console.log(result)
&lt;script src="https://d3js.org/d3.v5.min.js"&gt;&lt;/script&gt;

除此之外,还有两个考虑:

  1. “在高层次上,我认为我必须 (1st) 计算每年每个统计数据的均值和标准差,并且 (2) 使用该年该统计数据的均值和标准差”:没错,在知道标准差和均值之前,您无法计算出一个值与均值的标准差,您只能知道先循环整个数组。因此,您不可能对数据数组进行少于 2 次的迭代。
  2. “性能/速度对我的应用很重要,我担心普通的 for 循环会很慢”:现在情况有些不同了,但直到最近没有什么能比 for 循环更好关于性能。所以,你所说的普通循环通常是最快的解决方案。

【讨论】:

  • “现在情况有点不同了”——哇,我错过了什么,有什么参考吗?
  • @altocumulus 也许我错了,但我已经看到指控(以及性能测试)说whileforEachfiltermap、@987654333 之类的方法@ 等等 - 在现代浏览器中 - 与 for 循环一样快。这让我感到惊讶,因为我曾经认为没有什么比for 循环更快或一样快了。
  • @GerardoFurtado 很好,我使用了 d3,因为 OP 询问是否可以使用 d3 的比例来解决他的问题。 IMO 这是他的建议而不是要求。
【解决方案3】:

虽然我认为自己是 d3 的崇拜者,但我认为在这个问题中添加 标签更像是在转移注意力。其他两个答案非常好,因为它们产生了正确的结果,但在性能方面会落后。由于这是您问题的一个主要方面,我想为此添加我自己的两美分。我认为坚持使用 Vanilla-JS 自己实现计算可能会有所帮助。

查看d3.deviation()implementation 会发现它只是d3.variance() 的一个薄包装器,用于计算方差的平方根。检查后者的implementation 会想到两件事:

  1. 代码使用safeguard 来防止undefinedNaN 值:

    此方法忽略未定义和 NaN 值;这对于忽略丢失的数据很有用。

    如果您可以确定数据中没有缺失值,您就可以放心地摆脱这些昂贵的检查。

  2. calculating 的方差是作为副作用计算的:

    delta = value - mean;
    mean += delta / ++m;
    sum += delta * (value - mean);
    

    您可以使用它在对数据进行单次循环后返回方差和均值。

此外,d3.mean() 也使用与 d3.variance() 相同的 safeguard 来对抗 NaNundefined 值。当然,按顺序调用这两种方法确实意味着这些检查也将对每个值执行两次。

借用 d3 自己的实现,可以按照以下思路实现解决方案:

function meanAndDeviation(values) {
  const len = values.length;
  let i = 0;
  let value;
  let mean = 0;
  let sum = 0;
  while (i<len) {
    delta = (value = values[i]) - mean;
    mean += delta / ++i;
    sum += delta * (value - mean);
  }

  return { mean, deviation: Math.sqrt(sum / (i - 1))};
}

看看下面的演示:

function meanAndDeviation(values) {
  const len = values.length;
  let i = 0;
  let value;
  let mean = 0;
  let sum = 0;
  while (i<len) {
    delta = (value = values[i]) - mean;
    mean += delta / ++i;
    sum += delta * (value - mean);
  }
  
  return { mean, deviation: Math.sqrt(sum / (i - 1))};
}

const arr = [23.2, 13.2, 18.2, 21.1, 12.5];
const {mean, deviation} = meanAndDeviation(arr);

const result = arr.map(d => (d - mean) / deviation);

console.log(result);

同意,返回对象的解构并不是代码中性能最高的部分,但因为它的可读性我喜欢它只被调用一次。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-21
    • 2021-11-04
    • 1970-01-01
    • 2018-03-08
    • 1970-01-01
    • 2012-04-20
    相关资源
    最近更新 更多