Nox myn
Nox myn

Reputation: 21

dc.js average value over the summed field

I have the following data array:

  ID   Name   Number  
 ---- ------ -------- 
   1   G           1  
   1   G           2  
   1   F           3  

I want to do the following conversion to calculate the average, but I don’t know how to do it.

  ID   Name   Number_sum  
 ---- ------ ------------ 
   1   G               3  
   1   F               3  

after summing calculate the average

  ID   Number_avg  
 ---- ------------ 
   1            3  

If you do not pre-sum, then the average value is calculated incorrectly:

  ID   Number_avg  
 ---- ------------ 
   1            2  

I want to calculate the average value for each ID, but with an even field "Name".

Next, I plan to build a graph for each ID. I have a road identifier - 1. This road consists of 2 sections: G and F. Moreover, section G is divided into 2 more small sections, 1 and 2 km each.

If we consider the usual average value, then we get the average value over the maximum section of the value - a sub-section of the road. But I want to make a calculation based on the average value of the road sections.

example of calculations in spreadsheet

<!DOCTYPE html>
<html lang="en">
<head>
    <title>dc.js</title>
    <meta charset="UTF-8">
    
    <script type="text/javascript" src="https://dc-js.github.io/dc.js/js/d3.js"></script>
    <script type="text/javascript" src="https://dc-js.github.io/dc.js/js/crossfilter.js"></script>
    <script type="text/javascript" src="https://dc-js.github.io/dc.js/js/dc.js"></script>


    
</head>
<body>
<div id ="test"></div>

<script type="text/javascript">
//dc.js
 var inlineND = new dc.NumberDisplay("#test");
 
//data
var array1 = [
            {"ID": 1, "Name": "G", "Number": 1},
            {"ID": 1, "Name": "G", "Number": 2},
            {"ID": 1, "Name": "F", "Number": 3}
            ];

var make_calc = function() {
            var ndx                 = crossfilter(array1), //
            Dimension               = ndx.dimension(function(d) {return d.ID;}),
            DimensionGroup          = Dimension.group().reduce(reduceAdd, reduceRemove, reduceInitial);           
            
            function reduceAdd(p, v) {
            ++p.count;
            p.total += v.Number;
            return p;
            }

            function reduceRemove(p, v) {
            --p.count;
            p.total -= v.Number;
            return p;
            }

            function reduceInitial() {
            return {count: 0, total: 0};
            }
            
            inlineND
                .group(DimensionGroup)
                .valueAccessor(function(p) { return p.value.count > 0 ? p.value.total / p.value.count : 0; });
                
            dc.renderAll();
            //console.log(DimensionGroup);
};

make_calc();

</script>
</body>
</html>

Upvotes: 1

Views: 184

Answers (3)

Nox myn
Nox myn

Reputation: 21

In order to calculate the average, taking into account the "Name" field, it is necessary to consider the unique occurrence of this field in the reduce function. As a result, when calculating the average value, divide the sum of values ​​by the number of unique values ​​in the "Name" field

<!DOCTYPE html>
<html lang="en">
<head>
    <title>dc.js</title>
    <meta charset="UTF-8">
    
    <script type="text/javascript" src="https://dc-js.github.io/dc.js/js/d3.js"></script>
    <script type="text/javascript" src="https://dc-js.github.io/dc.js/js/crossfilter.js"></script>
    <script type="text/javascript" src="https://dc-js.github.io/dc.js/js/dc.js"></script>

</head>
<body>
<div id ="test"></div>

<script type="text/javascript">
//dc.js
 var inlineND = new dc.NumberDisplay("#test");
 
//data
var array1 = [
            {"ID": 1, "Name": "G", "Number": 1},
            {"ID": 1, "Name": "G", "Number": 2},
            {"ID": 1, "Name": "F", "Number": 3}
            ];

var make_calc = function() {
            var ndx                 = crossfilter(array1), //
            Dimension               = ndx.dimension(function(d) {return d.ID;}),
            DimensionGroup          = Dimension.group().reduce(reduceAdd, reduceRemove, reduceInitial);           
            
                       
            function reduceAdd(p, v) {
            ++p.count;
            p.total += v.Number;
                if(v.Name in p.Names){
                    p.Names[v.Name] += 1
                }
                else{
                    p.Names[v.Name] = 1;
                    p.Name_count++;
                };
            return p;
            }

            function reduceRemove(p, v) {
            --p.count;
            p.total -= v.Number;
                p.Names[v.Name]--;
                if(p.Names[v.Name] === 0){
                    delete p.Names[v.Name];
                    p.Name_count--;
                };
            return p;
            }

            function reduceInitial() {
            return {count: 0, total: 0, Name_count: 0, Names: {}};
            }
            
            inlineND
                .group(DimensionGroup)
                .valueAccessor(function(p) { return p.value.Name_count > 0 ? p.value.total / p.value.Name_count : 0; });
                
            dc.renderAll();
            //console.log(DimensionGroup);
};

make_calc();

</script>
</body>
</html>

Upvotes: 1

Gordon
Gordon

Reputation: 20150

I'm not sure if I completely understand, but if you want to group by Name, sum, and and then take the average of all groups, you could put your dimension on Name and use regular reduceSum:

            var ndx                 = crossfilter(array1), //
            Dimension               = ndx.dimension(function(d) {return d.Name;}),
            DimensionGroup          = Dimension.group().reduceSum(d => d.Number);

Then pass a "fake groupAll" which returns all the rows from the group to the number display, and calculate the average in the value accessor:

                .group({value: () => DimensionGroup.all()})
                .valueAccessor(a => a.length === 0 ? 0 : d3.sum(a, ({value}) => value) / a.length);

<!DOCTYPE html>
<html lang="en">
<head>
    <title>dc.js</title>
    <meta charset="UTF-8">
    
    <script type="text/javascript" src="https://dc-js.github.io/dc.js/js/d3.js"></script>
    <script type="text/javascript" src="https://dc-js.github.io/dc.js/js/crossfilter.js"></script>
    <script type="text/javascript" src="https://dc-js.github.io/dc.js/js/dc.js"></script>


    
</head>
<body>
<div id ="test"></div>

<script type="text/javascript">
//dc.js
 var inlineND = new dc.NumberDisplay("#test");
 
//data
var array1 = [
            {"ID": 1, "Name": "G", "Number": 1},
            {"ID": 1, "Name": "G", "Number": 2},
            {"ID": 1, "Name": "F", "Number": 3}
            ];

var make_calc = function() {
            var ndx                 = crossfilter(array1), //
            Dimension               = ndx.dimension(function(d) {return d.Name;}),
            DimensionGroup          = Dimension.group().reduceSum(d => d.Number);

            
            inlineND
                .group({value: () => DimensionGroup.all()})
                .valueAccessor(a => a.length === 0 ? 0 : d3.sum(a, ({value}) => value) / a.length);
                
            dc.renderAll();
            //console.log(DimensionGroup);
};

make_calc();

</script>
</body>
</html>

Upvotes: 0

ulou
ulou

Reputation: 5863

I'm not sure, but are you looking for something like this?

const arr = [
  {id: 1, name: 'G', number: 1},
  {id: 2, name: 'G', number: 2},
  {id: 3, name: 'F', number: 3}
]

const res = arr.reduce((acc, e) => {
  const idx = acc.findIndex(x => x.name === e.name)
  if (idx !== -1) {
    acc[idx].number += e.number
  } else {
    acc.push(e)
  }
  return acc
}, [])

console.log(res)

Upvotes: 0

Related Questions