计算某些键的值与均值有多少标准偏差?

时间:2018-09-19 05:26:13

标签: javascript d3.js data-manipulation

我正在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)计算每年每个统计数据的均值和标准差,(2nd)使用该年该统计数据的均值和标准差,并将其映射到它的缩放值。性能/速度对我的应用程序很重要,我担心普通的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的缩放功能是否可以帮助解决此问题?

3 个答案:

答案 0 :(得分:3)

作为一名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)
<script src="https://d3js.org/d3.v5.min.js"></script>

除此之外,有两个注意事项:

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

答案 1 :(得分:2)

创建d3的continuous scale可以达到相同的结果(与R的比例)。请参见下面的代码段。

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)
   <script src="https://d3js.org/d3.v5.min.js"></script>

答案 2 :(得分:1)

尽管我认为自己是d3的仰慕者,但我认为向此问题添加标签更像是一个红鲱鱼。另外两个答案是非常好的,因为它们可以产生正确的结果,但是在性能方面会落后。由于这是您的问题的主要方面,我想在此基础上再加上我自己的两分钱。我认为自己坚持使用Vanilla-JS进行计算可能会有所帮助。

查看implementationd3.deviation()会发现,它只是d3.variance()周围的薄包装,用于计算方差的平方根。检查后者的implementation会引起两件事:

  1. 该代码使用safeguard来防止undefinedNaN值的出现:

      

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

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

  2. calculating的方差中,均值被计算为副作用:

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

    您可以使用此方法在一次遍历数据后返回方差和均值。

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

从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);

同意,对返回的对象进行的销毁不是代码中性能最高的部分,但是由于出于可读性的考虑,它仅被调用一次。