Search code examples
javascriptmathlogarithm

Mapping groups of events to segments using a logarithmic scale


I have some events (an array of groups of events) and I want to map those events to segments using a logarithmic scale.

There are some rules I want the solution to obey to:

  1. Math.log(x+1) must be used as it scales well 1 and numbers smaller than 1

  2. I want to calculate the single segments and not only the total

  3. The sum of the segments of a group i must be equal to the sum of the segments of another group j if the linear sums of the events are equal (i.e. if I have the events [1, 1] and [2] the total of the mapped segments of the two groups must be equal)

I have developed the solution you see below but it works partially, as the sum of groups[0].segments is different from the sum of groups[1].segments, because log(1+1)+log(1+1) != log(2+1)

How can I use Math.log(x+1) to scale the events while having rule 3 working?

var logSegmentLen = function(x) {
  return Math.log(x+1);
}

var groupOfSegmentsTotal = function(segmentLenFn) {
  var f = function(arrayOfSegments) {
    let segments = arrayOfSegments.map(segmentLenFn);
    let total = segments.reduce((a,b) => a+b,0);
    return {
      total: total,
      segments: segments      
    }
  }
  return f;
}

var events = [ [1, 1], [2]]
var groups = events.map(groupOfSegmentsTotal(logSegmentLen));
console.log(groups[0].segments, groups[1].segments);
console.log(Math.abs(groups[0].total - groups[1].total) < 0.001); // true must be printed


Solution

  • How can I use Math.log(x+1) to scale the events while having rule 3 working?

    Apparently this is just impossible. Your requirement #3 which can be restated as for given scale function F(x)

    sum(Xi) = sum(Yj)  =>  sum(F(Xi)) = sum(F(Yj))
    

    actually implies that the only F(x) that fits it is of form F(x) = k*x i.e. just a linear scale. Any non-linear scale (including logarithmic) will break that requirement. You probably should describe your higher-level problem and somebody might be able to come up with non-self-contradicting translation of that problem.

    Sidenote Sketch of the proof. Fix {Xi} to be just a collection of n identical values x and {Yj} to be a collection of m identical y. So

    sum(Xi) = n*x
    sum(Yj) = m*y
    sum(F(Xi)) = n*F(x)
    sum(F(Yj)) = m*F(y)
    

    and the requirement becomes:

    sum(Xi) = sum(Yj)  =>  sum(F(Xi)) = sum(F(Yj))
    n*x = m*y => n*F(x) = m*F(y)
    

    And this holds for arbitrary integers n and m. Now lets fix F(1) = k. It means that for any m/n F(m/n) = m/n*k. As real numbers can be approximated by fractions with arbitrary precision, it means that for any x F(x) = x*k.