如何动态计算Javascript中对象值的数组?



我的输入格式如下,

var boxplotInput = [{Day: "01-07-2021", "Thomas": 95, "Diana": 94, "Claura": 93, "Chandler": 93},
{Day: "02-07-2021", "Thomas": 95, "Diana": 94, "Claura": 94, "Chandler": 94},        
...
...
{Day: "31-07-2021", "Thomas": 92, "Diana": 94, "Claura": 93, "Chandler": 91}];

我是javascript对象处理的新手。我写了下面的代码来计算Q1, Q3和中位数,它在数学上工作得很好,我期望的方式。

//Getting the list of students (excluding date)
var keys;
for(var i = 0; i <boxplotInput.length; i++ ){
keys = Object.keys(boxplotInput[i]).slice(1);      
}
////Here, I am hard-coding keys[0]. and getting "Thomas" data only. I am not getting how to avoid for one students only and achieve it for all students.
var studentDataSample = [];
for(var i = 0; i <boxplotInput.length; i++ ){  
student1 = boxplotInput[i][keys[0]];
studentDataSample.push(student1);
}
studentDataSample.sort(function(a, b) {return a - b;});
var length = studentDataSample.length;//31
var midIndex = middleIndex(studentDataSample, 0, length);//16
var medianValue = studentDataSample[midIndex];
var Q1 = studentDataSample[middleIndex(studentDataSample, 0, midIndex)];
var Q3 = studentDataSample[middleIndex(studentDataSample, midIndex + 1, length)];
console.log(Q1+", "+medianValue+", "+Q3);// here, the values are fine.
function middleIndex(data, initial, length){
var n = length - initial + 1;
n = parseInt((n + 1) / 2);
return parseInt(n + initial);
}

一些东西,我明白它可以通过循环再次实现…但是,并不是所有的学生都知道如何达到这个目标。请对此提供建议或想法。

提前感谢。

如果我理解正确的话,都需要以下JS方法:

  • Array.reduce
  • Array.filter

这里需要的主要内容是创建有用的学生分数集合。在这之后,你可以计算所有你想要的东西。在这个例子中,我展示了如何计算平均值。

var boxplotInput = [
{Day: "01-07-2021", "Thomas": 95, "Diana": 94, "Claura": 93, "Chandler": 93},
{Day: "02-07-2021", "Thomas": 95, "Diana": 94, "Claura": 94, "Chandler": 94},        
{Day: "31-07-2021", "Thomas": 92, "Diana": 94, "Claura": 93, "Chandler": 91}
];
/*
Get collection of students like:
{
Thomas: [ 95, 95, 92 ],
Diana: [ 94, 94, 94 ],
Claura: [ 93, 94, 93 ],
Chandler: [ 93, 94, 91 ]
}
*/
const students = boxplotInput.reduce((accumulator, currentDay) => {
const students = Object
.keys(currentDay)
.filter(el => el !== 'Day');

students.forEach(student => {
if (!accumulator[student]) {
accumulator[student] = [];
}

accumulator[student].push(currentDay[student]);
});
return accumulator;
}, {});
console.log('Student grades:', students);
// Then we can do anything with it
const studentNames = Object.keys(students);
// Example: finding mean
const studentMeans = studentNames.reduce((acc, student) => {
const grades = students[student];
const sumOfGrades = grades.reduce((acc, cur) => cur + acc, 0);

acc[student] = sumOfGrades / grades.length;
return acc;
}, {});
console.log('Means:', studentMeans);
/*
{
Thomas: 94,
Diana: 94,
Claura: 93.33333333333333,
Chandler: 92.66666666666667
}
*/

我将向您展示使用下划线的一种非常简洁的方法。让我们检查一下Underscore和JavaScript为此目的提供的所有工具,并一步一步地构建我们的解决方案。

来自Underscore的一个很好的函数是chain,它允许我们以不同的形状一步一步地处理数据,同时保持代码非常易于阅读。例如,您大概可以猜到下面的链要做什么:

var sortedLast = _.chain([2, 3, 1])
.sort()
.last();
console.log(sortedLast);
<script src="https://underscorejs.org/underscore-umd-min.js"></script>

chain在输入数据周围创建了一个特殊的包装器,它将所有下划线函数作为方法。每个方法返回一个新的包装器,因此您可以继续应用更多的Underscore函数。最后,您可以通过调用.value()来展开结果。在某些情况下,如上面的例子,这是自动发生的。last返回数组的最后一个元素。

一个好的结束形状,我们可能想要努力,可以是:

{
Thomas: {min: 92, Q1: 93.5, median: 95, Q3: 95, max: 95},
Diana: {min: 94, Q1: 94, median: 94, Q3: 94, max: 94},
Claura: {min: 93, Q1: 93, median: 93, Q3: 93.5, max: 94},
Chandler: {min: 91, Q1: 92, median: 93, Q3: 93.5, max: 94},
}

这个对象与boxplotInput的每个元素都有相同的键,除了Day。下划线有一个omit函数,它可以让我们干净地完成这个任务,而不必依赖于以特定顺序出现的键:

_.chain(boxplotInput[0])
.omit('Day');
// {Thomas: 95, Diana: 94, Claura: 93, Chandler: 93}

现在我们有了一个键正确但值错误的对象。

mapObject允许我们创建一个具有相同键但不同值的新对象。除了输入对象之外,它还接受一个函数,该函数将依次应用于输入对象的每个键值对。该函数将值作为第一个参数,将键作为第二个参数。它的返回值成为新对象中对应键处的值。

作为中间步骤,让我们创建一个对象,其中包含每个学生的所有分数:

{
Thomas: [95, 95, 92],
Diana: [94, 94, 94],
Claura: [93, 94, 93],
Chandler: [93, 94, 91],
}

为了在mapObject中实现这一点,我们需要编写一个函数,给定一个学生的名字,返回一个包含该学生分数的数组。它的开头看起来像这样:

function studentScores(firstScore, studentName) {
// code here
}

让我们来看一种获得这些分数的优雅方法。在原始代码中,您编写了这样的内容(但使用key[0]而不是studentName):

var studentDataSample = [];
for (var i = 0; i < boxplotInput.length; i++) {
var student1 = boxplotInput[i][studentName];
studentDataSample.push(student1);
}

下划线可以让您在很短的行中使用map:

获得相同的结果
var studentDataSample = _.map(boxplotInput, studentName);

JavaScript的数组现在有一个内置的map方法,可以让你做类似的事情。它不像Underscore的map那样灵活和简洁,但为了完整起见,我将展示如何使用它:

var studentDataSample = boxplotInput.map(dayScores => dayScores[studentName]);

我们现在知道如何编写studentScores:

function studentScores(firstScore, studentName) {
return _.map(boxplotInput, studentName);
}

我们不需要firstScore,但无论如何我们必须接受它作为第一个参数,因为我们要将这个函数传递给mapObject,它总是首先传递值。幸运的是,我们可以忽略它。我们可以使用新的箭头符号更简洁地编写这个函数:

(fs, studentName) => _.map(boxplotInput, studentName)
现在我们可以将这个函数包含在链中,以得到前面讨论过的中间结果:
_.chain(boxplotInput[0])
.omit('Day')
.mapObject((fs, studentName) => _.map(boxplotInput, studentName));
// {
//     Thomas: [95, 95, 92],
//     Diana: [94, 94, 94],
//     Claura: [93, 94, 93],
//     Chandler: [93, 94, 91]
// }

让我们对分数也进行排序,作为计算分位数的准备:

_.chain(boxplotInput[0])
.omit('Day')
.mapObject((fs, studentName) => _.map(boxplotInput, studentName).sort());
// {
//     Thomas: [92, 95, 95],
//     Diana: [94, 94, 94],
//     Claura: [93, 93, 94],
//     Chandler: [91, 93, 94]
// }

我们可以在链中添加另一个mapObject,以便将这些排序分数数组转换为我们想要的最终{min, Q1, median, Q3, max}对象。由于这不是你问题的真正内容,我将只提出一种可能的方法,以函数式的方式完成它:

// A function that returns a function (this is not a typo) that
// computes a particular quantile from a sorted array of numbers.
function quantile(fraction) {
return function(numbers) {
var middle = (numbers.length - 1) * fraction;
return (numbers[Math.floor(middle)] + numbers[Math.ceil(middle)]) / 2;
};
}
// A "blueprint" object with the keys we want to have, each having a
// function to compute the corresponding value from a sorted array of 
// scores.
var quantileComputations = {
min: _.first,
Q1: quantile(.25),
median: quantile(.5),
Q3: quantile(.75),
max: _.last,
};
// A function that applies the above blueprint to a given array of 
// numbers.
function getQuantiles(numbers) {
return _.mapObject(quantileComputations, f => f(numbers));
}
// Redefining the input data to make this snippet runnable.
var boxplotInput = [
{Day: "01-07-2021", "Thomas": 95, "Diana": 94, "Claura": 93, "Chandler": 93},
{Day: "02-07-2021", "Thomas": 95, "Diana": 94, "Claura": 94, "Chandler": 94},        
{Day: "31-07-2021", "Thomas": 92, "Diana": 94, "Claura": 93, "Chandler": 91},
];
// Completing our chain using the above.
var statistics = _.chain(boxplotInput[0])
.omit('Day')
.mapObject((fs, studentName) => _.map(boxplotInput, studentName).sort())
.mapObject(getQuantiles)
.value();
console.log(statistics);
<script src="https://underscorejs.org/underscore-umd-min.js"></script>

最新更新