Skip to content

Instantly share code, notes, and snippets.

@nautilytics
Last active December 26, 2015 13:29
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nautilytics/7158692 to your computer and use it in GitHub Desktop.
Save nautilytics/7158692 to your computer and use it in GitHub Desktop.
Dynamic Production Forecasting

Animation of three forecasting routines - Linear, Logarithmic, and Exponential - fitting FAOSTAT Brazil Soybean Production from 1961 to 2011. The fitting routines check every year combination, i.e. data from 1961 to 2011, data from 1962 to 2011, ... , data from 2006 to 2011 to find the fit and year range with the lowest in-sample RMSE.

Source: FAOSTAT; Nautilytics, LLC

year value
1961 271488
1962 345175
1963 322915
1964 304897
1965 523176
1966 594975
1967 715606
1968 654476
1969 1056610
1970 1508540
1971 2077290
1972 3222630
1973 5011610
1974 7876530
1975 9893010
1976 11227100
1977 12513400
1978 9540580
1979 10240300
1980 15155800
1981 15007400
1982 12836000
1983 14582300
1984 15540800
1985 18278600
1986 13333400
1987 16977200
1988 18011700
1989 24051700
1990 19897800
1991 14937800
1992 19214700
1993 22591000
1994 24931800
1995 25682600
1996 23155300
1997 26391400
1998 31307400
1999 30987500
2000 32735000
2001 39058000
2002 42769000
2003 51919400
2004 49549900
2005 51182100
2006 52464600
2007 57857200
2008 59833100
2009 57345400
2010 68756300
2011 74815400
! function () {
function t(a, b) {
return a.RMSE > b.RMSE ? -1 : a.RMSE < b.RMSE ? 1 : 0
}
function u(a, b) {
p.append("path").datum(a).attr("class", b).attr("d", s)
}
var g, k, l, m, n, a = [],
b = [],
f = "tonnes",
h = {
top: 20,
right: 20,
bottom: 30,
left: 50
}, i = 960 - h.left - h.right,
j = 500 - h.top - h.bottom,
o = [],
p = d3.select("body").append("svg").attr("width", i + h.left + h.right).attr("height", j + h.top + h.bottom).append("g").attr("transform", "translate(" + h.left + "," + h.top + ")"),
q = p.append("g").attr("class", "x axis").attr("transform", "translate(0," + j + ")"),
r = p.append("g").attr("class", "y axis"),
s = d3.svg.line().defined(function (a) {
return null != a
}).x(function (a, b) {
return k(new Date(g + b, 1, 1))
}).y(function (a) {
return l(a)
});
d3.csv("brazil_soybean_production.csv", function (c) {
c.forEach(function (c) {
a.push(+c.year), b.push(+c.value)
}), g = d3.min(a), k = d3.time.scale().domain([new Date(g, 1, 1), new Date(d3.max(a), 1, 1)]).range([0, i]), l = d3.scale.linear().range([j, 0]), m = d3.svg.axis().scale(k).orient("bottom"), q.call(m), n = d3.svg.axis().scale(l).orient("left").tickFormat(abbrNum);
var d = function (a) {
return a
}, e = function (a) {
return a
};
["linear", "logarithmic", "exponential"].forEach(function (c) {
switch (c) {
case "linear":
e = function (a) {
return a
}, d = function (a) {
return a
};
break;
case "logarithmic":
e = function (a) {
return Math.log(a)
}, d = function (a) {
return a
};
break;
case "exponential":
e = function (a) {
return a
}, d = function (a) {
return Math.log(a)
}
}
var f = a.length;
d3.range(f - 5).forEach(function (g) {
var h = a[g],
i = a.slice(g, f).map(function (a) {
return e(a)
}),
j = b.slice(g, f).map(function (a) {
return d(a)
}),
k = linearRegression(j, i),
l = k.slope,
m = k.intercept,
n = 0,
p = i.length,
q = a.map(function (a, d) {
if (a >= h) {
var f;
return f = "exponential" == c ? Math.exp(m) * Math.exp(a * l) : m + e(a) * l, n += Math.pow(b[d] - f, 2), 0 > f ? null : f
}
return null
});
o.push({
type: c,
values: q,
RMSE: Math.pow(n / p, .5),
minYear: h
})
})
});
var h = d3.extent(d3.merge(o.map(function (a) {
return a.values.filter(function (a) {
return null != a
})
})));
l.domain(h), u(b, "line"), r.call(n).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", ".71em").style("text-anchor", "end").text(f);
var v = o.sort(t);
p.append("g").attr("class", "lineGroup").selectAll("path").data(v).enter().append("path").attr("class", function (a) {
return a.type
}).style("visibility", function () {
return "hidden"
}).style("opacity", 1).attr("d", function (a) {
return s(a.values)
}).transition().delay(function (a, b) {
return 10 * b
}).style("visibility", function () {
return "visible"
}).transition().delay(function (a, b) {
return 100 * b
}).style("opacity", function (a, b) {
return b < v.length - 1 ? 0 : 1
})
})
}();
function linearRegression(y, x) {
var lr = {};
var sum_x = 0;
var sum_y = 0;
var sum_xy = 0;
var sum_xx = 0;
var sum_yy = 0;
var n = 0;
for (var i = 0; i < y.length; i++) {
if (y[i]) {
sum_x += x[i];
sum_y += y[i];
sum_xy += (x[i] * y[i]);
sum_xx += (x[i] * x[i]);
sum_yy += (y[i] * y[i]);
n++;
}
}
lr['slope'] = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
lr['intercept'] = (sum_y - lr.slope * sum_x) / n;
lr['r2'] = Math.pow((n * sum_xy - sum_x * sum_y) / Math.sqrt((n * sum_xx - sum_x * sum_x) * (n * sum_yy - sum_y * sum_y)), 2);
return lr;
}
function abbrNum(number, decPlaces) {
// 2 decimal places => 100, 3 => 1000, etc
decPlaces = Math.pow(10, decPlaces);
// Enumerate number abbreviations
var abbrev = [ "k", "m", "b", "t" ];
// Go through the array backwards, so we do the largest first
for (var i = abbrev.length - 1; i >= 0; i--) {
// Convert array index to "1000", "1000000", etc
var size = Math.pow(10, (i + 1) * 3);
// If the number is bigger or equal do the abbreviation
if (size <= number) {
// Here, we multiply by decPlaces, round, and then divide by decPlaces.
// This gives us nice rounding to a particular decimal place.
number = Math.round(number * decPlaces / size) / decPlaces;
// Handle special case where we round up to the next abbreviation
if ((number == 1000) && (i < abbrev.length - 1)) {
number = 1;
i++;
}
// Add the letter for the abbreviation
number += abbrev[i];
// We are done... stop
break;
}
}
return number;
}
<!doctype html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>
<title>Forecasting Optimization</title>
<link type='text/css' rel='stylesheet' href='styles.css'/>
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700|Lusitana' rel='stylesheet' type='text/css'>
<style>
body {
width: 850px;
height: 600px;
background-image: url(logo4.png);
background-repeat: no-repeat;
background-position: 20% 20%;
}
body text {
fill: white;
}
.axis path,
.axis line {
fill: none;
stroke: #f9fbff;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line, .linear, .logarithmic, .exponential {
fill: none;
stroke: white;
stroke-width: 1.5px;
}
.linear, .exponential, .logarithmic {
stroke: blue;
}
#ie8 {
display: none;
}
body {
margin: 10px;
font-family: 'Open Sans', sans-serif;
color: #ddd;
}
html {
background-color: #222;
}
h2 {
padding: 0;
margin: 0;
}
.subunit-label {
fill: #000000;
font-size: 14px;
font-weight: 300;
text-anchor: middle;
}
</style>
</head>
<body>
</body>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/colorbrewer.v1.min.js"></script>
<script src="data_analysis.js"></script>
<script src="d3.charting.min.js"></script>
</html>
#ie8 {
display: none;
}
body {
margin: 10px;
font-family: 'Open Sans', sans-serif;
color: #ddd;
}
html {
background-color: #222;
}
h2 {
padding: 0;
margin: 0;
}
.subunit-label {
fill: #000000;
font-size: 14px;
font-weight: 300;
text-anchor: middle;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment