1

Say I have the following:

var sales = [5, 4, 2];
var months = ["Jan 2011", "Apr 2011", "Feb 2012"];

If I had a given range say:

var range = ["Jan 2011", "Mar 2012"];

I want to "interpolate" between months such that I can get a result of:

var sales = [5, 4, 4, 4, ..., 2, 2];
var months = ["Jan 2011", "Feb 2011", "Mar 2011", ...., "Feb 2012", "Mar 2012"];

Is it necessary to make a "range of all possible dates in sequential order" array with all the "months + year" that is desired to be included? Or is it possible with javascript to not have to do that? How to accomplish this?

NOTE: the assumption is that if a date isnt present in the months array, it would be filled in with data from any previous available month.

7
  • Is the assumption that if a date isn't present in the months array that it had the same value for sales as the previous month? Commented Mar 7, 2013 at 22:44
  • Yes, the assumption is that if a date isnt present in the months array, it would be filled in with data from any previous available month. Commented Mar 8, 2013 at 6:25
  • Are you using actual Date objects in your months array? If so (or if it's possible to use Dates), then you should be able to iterate through the months array, keep track of the index, and increment the date by 1 month until you reach the value at the next index, and populate the "result" sales array with the "input" sales value at the previous index. See stackoverflow.com/questions/5645058/… for adding months to dates in javascript Commented Mar 8, 2013 at 16:59
  • They could be actual date objects to come up with the strings stored in those arrays. Commented Mar 8, 2013 at 18:20
  • If at all possible, I'd recommend using the actual Date objects instead of strings, since the Date object exposes ways to do date arithmetic (which I think is essentially what you need here); doing the same on strings is obviously harder. If the end result has to be string representations, then I'd recommend doing the conversion to string AFTER you've done your interpolation (i.e. at the presentation layer) Commented Mar 8, 2013 at 18:53

1 Answer 1

2

If you don't strictly need to use the previous known sales figure, you can use a nice kind of spline interpolation for the sales figures:

/* Fritsch-Carlson monotone cubic spline interpolation
   Usage example:
    var f = createInterpolant([0, 1, 2, 3], [0, 1, 4, 9]);
    var message = '';
    for (var x = 0; x <= 3; x += 0.5) {
        var xSquared = f(x);
        message += x + ' squared is about ' + xSquared + '\n';
    }
    alert(message);
*/
var createInterpolant = function(xs, ys) {
    var i, length = xs.length;

    // Deal with length issues
    if (length != ys.length) { throw 'Need an equal count of xs and ys.'; }
    if (length === 0) { return function(x) { return 0; }; }
    if (length === 1) {
        // Impl: Precomputing the result prevents problems if ys is mutated later and allows garbage collection of ys
        // Impl: Unary plus properly converts values to numbers
        var result = +ys[0];
        return function(x) { return result; };
    }

    // Rearrange xs and ys so that xs is sorted
    var indexes = [];
    for (i = 0; i < length; i++) { indexes.push(i); }
    indexes.sort(function(a, b) { return xs[a] < xs[b] ? -1 : 1; });
    var oldXs = xs, oldYs = ys;
    // Impl: Creating new arrays also prevents problems if the input arrays are mutated later
    xs = []; ys = [];
    // Impl: Unary plus properly converts values to numbers
    for (i = 0; i < length; i++) { xs.push(+oldXs[indexes[i]]); ys.push(+oldYs[indexes[i]]); }

    // Get consecutive differences and slopes
    var dys = [], dxs = [], ms = [];
    for (i = 0; i < length - 1; i++) {
        var dx = xs[i + 1] - xs[i], dy = ys[i + 1] - ys[i];
        dxs.push(dx); dys.push(dy); ms.push(dy/dx);
    }

    // Get degree-1 coefficients
    var c1s = [ms[0]];
    for (i = 0; i < dxs.length - 1; i++) {
        var m = ms[i], mNext = ms[i + 1];
        if (m*mNext <= 0) {
            c1s.push(0);
        } else {
            var dx = dxs[i], dxNext = dxs[i + 1], common = dx + dxNext;
            c1s.push(3*common/((common + dxNext)/m + (common + dx)/mNext));
        }
    }
    c1s.push(ms[ms.length - 1]);

    // Get degree-2 and degree-3 coefficients
    var c2s = [], c3s = [];
    for (i = 0; i < c1s.length - 1; i++) {
        var c1 = c1s[i], m = ms[i], invDx = 1/dxs[i], common = c1 + c1s[i + 1] - m - m;
        c2s.push((m - c1 - common)*invDx); c3s.push(common*invDx*invDx);
    }

    // Return interpolant function
    return function(x) {
        // The rightmost point in the dataset should give an exact result
        var i = xs.length - 1;
        if (x == xs[i]) { return ys[i]; }

        // Search for the interval x is in, returning the corresponding y if x is one of the original xs
        var low = 0, mid, high = c3s.length - 1;
        while (low <= high) {
            mid = Math.floor(0.5*(low + high));
            var xHere = xs[mid];
            if (xHere < x) { low = mid + 1; }
            else if (xHere > x) { high = mid - 1; }
            else { return ys[mid]; }
        }
        i = Math.max(0, high);

        // Interpolate
        var diff = x - xs[i], diffSq = diff*diff;
        return ys[i] + c1s[i]*diff + c2s[i]*diffSq + c3s[i]*diff*diffSq;
    };
};

That can be used with the following code to do what you wanted:

var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var fromMonthNumber = function(monthNumber) {
    return monthNames[monthNumber % 12] + ' ' + ((monthNumber / 12) | 0);
};

var toMonthNumber = function(monthName) {
    var date = new Date(Date.parse(monthName));
    return 12*date.getFullYear() + date.getMonth();
};

var interpolateSales = function(sales, months, range) {
    var f = createInterpolant(months.map(toMonthNumber), sales);

    var resultSales = [], resultMonths = [];
    var firstMonth = toMonthNumber(range[0]), lastMonth = toMonthNumber(range[1]);
    for (var x = firstMonth; x <= lastMonth; x++) {
        resultSales.push(Math.round(f(x)));
        resultMonths.push(fromMonthNumber(x));
    }

    return { sales: resultSales, months: resultMonths };
};

With that, interpolateSales([5, 4, 2], ["Jan 2011", "Apr 2011", "Feb 2012"], ["Jan 2011", "Mar 2012"]) is:

{
    sales: [5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2],
    months: ["Jan 2011", "Feb 2011", "Mar 2011", "Apr 2011", "May 2011", "Jun 2011", "Jul 2011", "Aug 2011", "Sep 2011", "Oct 2011", "Nov 2011", "Dec 2011", "Jan 2012", "Feb 2012", "Mar 2012"]
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.