Nancy
Nancy

Reputation: 4099

Filter elements of array by max of specific property

I would like to subset each element an array so that I am left with all values associated with the max of one property. I will be using this in a D3.js graphic that will have annotations at the max value of each grouping in my data.

For example, here is how I'd nest the Iris data set by species and then take only the max value of SepalLength.

maxdata = d3.nest()
    .key(function (d) { return d.species; })
    .rollup(function (leaves) {
        var maxSepalLength = d3.max(leaves, function (x) { return x.sepalLength })
            return { maxSepalLength:maxSepalLength}
    })
    .entries(irisdata)

The output looks like this:

0:
    key: "setosa"
    value:
        maxSepalLength: 5.4
1:
    key: "versicolor"
    value:
        maxSepalLength: 6.8
2:
    key: "virginica"
    value:
        maxSepalLength: 7.9

This mostly works as expected, but I'm not sure how to keep the other variables. My desired output would be like this:

0:
    key: "setosa"
    value:
        maxSepalLength: 5.4
        sepalWidth: <value> 
        petalLength: <value>
        petalWidth: <value>

...etc

A fiddle with the data is here. How can I hold on to those other values?

Upvotes: 2

Views: 306

Answers (2)

Tom O.
Tom O.

Reputation: 5941

If I understood your question correctly then I think the code below could help resolve your issue. It's all vanilla JavaScript and not using any d3 functions.

Basically, I parsed your data string into an array of records. I analyze the current record in the reduce callback to see if it has a greater property value than the current max element - if it does, it becomes the new max element.

I provided a function called getMax which takes the data array I mentioned as the first parameter. getMax takes a second parameter that is used to determine which property you want to find the max of.

You can see in the demo below that I run each of your property names through the function and you can see how the datasets vary. Hopefully this helps!

function parse(s) {
  return s.split('\n').filter(el => el.length);
}

function generatePropertyKey(s) {
  var suffix = s.charAt(0).toUpperCase();
  suffix += s.substring(1);
  return `max${suffix}`;
}

function getMax(data, properyName) {
  var o = data.reduce((accum, el, idx) => {
    if (idx > 0) {
      var mk = generatePropertyKey(properyName),
        [
          sepalLength,
          sepalWidth,
          petalLength,
          petalWidth,
          species
        ] = el.split(',')

      var mv;
      switch (properyName) {
        case 'sepalLength':
          mv = sepalLength;
          break;
        case 'sepalWidth':
          mv = sepalWidth;
          break;
        case 'petalLength':
          mv = petalLength;
          break;
        case 'petalWidth':
          mv = petalWidth;
          break;
      }

      if (!accum[species] || (accum[species] && mv > accum[species][mk])) {
        accum[species] = {
          [mk]: mv,
          sepalLength,
          sepalWidth,
          petalLength,
          petalWidth
        };

        delete accum[species][properyName];
      }
    }

    return accum;
  }, {});

  return o;
}


//Demo code:
var data = parse(document.querySelector('#data').innerHTML);

var options = [
  'sepalLength',
  'sepalWidth',
  'petalLength',
  'petalWidth'
];

console.log(options.map(el => getMax(data, el)))
pre {
  display: none;
}
<pre id="data">
sepalLength,sepalWidth,petalLength,petalWidth,species
5.1,3.5,1.4,0.2,setosa
4.9,3,1.4,0.2,setosa
4.7,3.2,1.3,0.2,setosa
4.6,3.1,1.5,0.2,setosa
5,3.6,1.4,0.2,setosa
5.4,3.9,1.7,0.4,setosa
4.6,3.4,1.4,0.3,setosa
5,3.4,1.5,0.2,setosa
5.3,3.7,1.5,0.2,setosa
5,3.3,1.4,0.2,setosa
6.8,2.8,4.8,1.4,versicolor
6.7,3,5,1.7,versicolor
6,2.9,4.5,1.5,versicolor
5.7,2.6,3.5,1,versicolor
5.5,2.4,3.8,1.1,versicolor
5.5,2.4,3.7,1,versicolor
5.8,2.7,3.9,1.2,versicolor
6,2.7,5.1,1.6,versicolor
5.4,3,4.5,1.5,versicolor
6,3.4,4.5,1.6,versicolor
6.7,3.1,4.7,1.5,versicolor
6.3,2.3,4.4,1.3,versicolor
5.6,3,4.1,1.3,versicolor
5.5,2.5,4,1.3,versicolor
5.5,2.6,4.4,1.2,versicolor
6.1,3,4.6,1.4,versicolor
5.8,2.6,4,1.2,versicolor
5,2.3,3.3,1,versicolor
5.6,2.7,4.2,1.3,versicolor
5.7,3,4.2,1.2,versicolor
5.7,2.9,4.2,1.3,versicolor
6.2,2.9,4.3,1.3,versicolor
5.1,2.5,3,1.1,versicolor
5.7,2.8,4.1,1.3,versicolor
6.3,3.3,6,2.5,virginica
5.8,2.7,5.1,1.9,virginica
7.1,3,5.9,2.1,virginica
5.8,2.8,5.1,2.4,virginica
6.4,3.2,5.3,2.3,virginica
6.5,3,5.5,1.8,virginica
7.7,3.8,6.7,2.2,virginica
7.7,2.6,6.9,2.3,virginica
6,2.2,5,1.5,virginica
6.9,3.2,5.7,2.3,virginica
5.6,2.8,4.9,2,virginica
7.7,2.8,6.7,2,virginica
6.3,2.7,4.9,1.8,virginica
6.7,3.3,5.7,2.1,virginica
7.2,3.2,6,1.8,virginica
6.2,2.8,4.8,1.8,virginica
6.1,3,4.9,1.8,virginica
6.4,2.8,5.6,2.1,virginica
7.2,3,5.8,1.6,virginica
7.4,2.8,6.1,1.9,virginica
7.9,3.8,6.4,2,virginica
6.4,2.8,5.6,2.2,virginica
6.3,2.8,5.1,1.5,virginica
6.1,2.6,5.6,1.4,virginica
7.7,3,6.1,2.3,virginica
6.3,3.4,5.6,2.4,virginica
6.4,3.1,5.5,1.8,virginica
</pre>

Upvotes: 0

Taki
Taki

Reputation: 17654

you can look for the object that has the maxSepalLength in the leaves array and add it to the returned object :

return { maxSepalLength , ...leaves.find(e => e.sepalLength === maxSepalLength) }

EDIT :

you can reduce the number of iterations by using reduce instead of d3.max :

maxdata = d3.nest()
    .key(function (d) { return d.species; })
    .rollup(function (leaves) {
        return leaves.reduce((max, curr) => {      
        max['maxSepalLength'] = max['maxSepalLength'] || 0;

        if(max['maxSepalLength'] < curr.sepalLength)
            max = {...curr, maxSepalLength : curr.sepalLength};

        return max;
      }, {});      
    })
    .entries(irisdata)

var irisdata = d3.csvParse(d3.select("pre#data").text());
console.log({ irisdata })

irisdata.forEach(function (d) {
    d.sepalLength = +d.sepalLength;
});

maxdata = d3.nest()
    .key(function (d) { return d.species; })
    .rollup(function (leaves) {
    
        var maxSepalLength = d3.max(leaves, function (x) { return x.sepalLength })
     		return { maxSepalLength , ...leaves.find(e => e.sepalLength === maxSepalLength) }
    })
    .entries(irisdata)

console.log({maxdata})
pre {
  display:none;
  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<pre id="data">
sepalLength,sepalWidth,petalLength,petalWidth,species
5.1,3.5,1.4,0.2,setosa
4.9,3,1.4,0.2,setosa
4.7,3.2,1.3,0.2,setosa
4.6,3.1,1.5,0.2,setosa
5,3.6,1.4,0.2,setosa
5.4,3.9,1.7,0.4,setosa
4.6,3.4,1.4,0.3,setosa
5,3.4,1.5,0.2,setosa
5.3,3.7,1.5,0.2,setosa
5,3.3,1.4,0.2,setosa
6.8,2.8,4.8,1.4,versicolor
6.7,3,5,1.7,versicolor
6,2.9,4.5,1.5,versicolor
5.7,2.6,3.5,1,versicolor
5.5,2.4,3.8,1.1,versicolor
5.5,2.4,3.7,1,versicolor
5.8,2.7,3.9,1.2,versicolor
6,2.7,5.1,1.6,versicolor
5.4,3,4.5,1.5,versicolor
6,3.4,4.5,1.6,versicolor
6.7,3.1,4.7,1.5,versicolor
6.3,2.3,4.4,1.3,versicolor
5.6,3,4.1,1.3,versicolor
5.5,2.5,4,1.3,versicolor
5.5,2.6,4.4,1.2,versicolor
6.1,3,4.6,1.4,versicolor
5.8,2.6,4,1.2,versicolor
5,2.3,3.3,1,versicolor
5.6,2.7,4.2,1.3,versicolor
5.7,3,4.2,1.2,versicolor
5.7,2.9,4.2,1.3,versicolor
6.2,2.9,4.3,1.3,versicolor
5.1,2.5,3,1.1,versicolor
5.7,2.8,4.1,1.3,versicolor
6.3,3.3,6,2.5,virginica
5.8,2.7,5.1,1.9,virginica
7.1,3,5.9,2.1,virginica
5.8,2.8,5.1,2.4,virginica
6.4,3.2,5.3,2.3,virginica
6.5,3,5.5,1.8,virginica
7.7,3.8,6.7,2.2,virginica
7.7,2.6,6.9,2.3,virginica
6,2.2,5,1.5,virginica
6.9,3.2,5.7,2.3,virginica
5.6,2.8,4.9,2,virginica
7.7,2.8,6.7,2,virginica
6.3,2.7,4.9,1.8,virginica
6.7,3.3,5.7,2.1,virginica
7.2,3.2,6,1.8,virginica
6.2,2.8,4.8,1.8,virginica
6.1,3,4.9,1.8,virginica
6.4,2.8,5.6,2.1,virginica
7.2,3,5.8,1.6,virginica
7.4,2.8,6.1,1.9,virginica
7.9,3.8,6.4,2,virginica
6.4,2.8,5.6,2.2,virginica
6.3,2.8,5.1,1.5,virginica
6.1,2.6,5.6,1.4,virginica
7.7,3,6.1,2.3,virginica
6.3,3.4,5.6,2.4,virginica
6.4,3.1,5.5,1.8,virginica
</pre>

Upvotes: 1

Related Questions