Skip to content

Commit

Permalink
first pass at smoothers. Added ls, still need to debug lowess.
Browse files Browse the repository at this point in the history
  • Loading branch information
hamilton committed Aug 26, 2014
1 parent b1dedd3 commit aac6efb
Show file tree
Hide file tree
Showing 3 changed files with 317 additions and 8 deletions.
7 changes: 6 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,12 @@ <h2 class='trunk-title'>Scatterplots</h2>
</div>
<div class='col-lg-8'>
<div class='row'>
<div class='col-lg-12 gr' id='scatter1'></div>
<div class='col-lg-6 gr' id='scatter1'></div>
<div class='col-lg-6 gr' id='scatter2'></div>
</div>
<div class='row'>
<div class='col-lg-6 gr' id='scatter3'></div>
<div class='col-lg-6'></div>
</div>
</div>
</div>
Expand Down
38 changes: 36 additions & 2 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -576,8 +576,8 @@ $(document).ready(function() {
description: "A first example of a scatterplot.",
data: data,
chart_type: 'point',
width: trunk.width*2,
height: trunk.height*2,
width: trunk.width,
height: trunk.height,
right: trunk.right,
target: 'div#scatter1',
xax_format: function(f) {
Expand All @@ -587,6 +587,40 @@ $(document).ready(function() {
x_accessor: 'x',
y_accessor: 'y'
})
moz_chart({
title: "Least Squares",
description: "least squares line: use argument least_squares: true",
data: data,
least_squares: true,
chart_type: 'point',
width: trunk.width,
height: trunk.height,
right: trunk.right,
target: 'div#scatter2',
xax_format: function(f) {
var pf = d3.formatPrefix(f);
return pf.scale(f) + pf.symbol;
},
x_accessor: 'x',
y_accessor: 'y'
})
moz_chart({
title: "Lowess",
description: "use lowess: true",
data: data,
lowess: true,
chart_type: 'point',
width: trunk.width,
height: trunk.height,
right: trunk.right,
target: 'div#scatter3',
xax_format: function(f) {
var pf = d3.formatPrefix(f);
return pf.scale(f) + pf.symbol;
},
x_accessor: 'x',
y_accessor: 'y'
})
})


Expand Down
280 changes: 275 additions & 5 deletions js/metrics-graphics.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ function moz_chart() {
custom_line_color_map: [], // allows arbitrary mapping of lines to colors, e.g. [2,3] will map line 1 to color 2 and line 2 to color 3
max_data_size: null // explicitly specify the the max number of line series, for use with custom_line_color_map
}
moz.defaults.point={}
moz.defaults.point = {
ls: false,
lowess: false
}
moz.defaults.histogram = {
rollover_callback: function(d, i) {
$('#histogram svg .active_datapoint')
Expand Down Expand Up @@ -147,8 +150,241 @@ function chart_title(args) {
'trigger':'hover', 'placement': 'top'});
}
}

}


function _pow_weight(u, w){
if (u >= 0 && u <= 1) {
return Math.pow(1 - Math.pow(u,w), w)
} else {
return 0
}
}

function _bisquare_weight(u){
return _pow_weight(u, 2);
}

function _tricube_weight(u){
return _pow_weight(u, 3);
}

function _neighborhood_width(x0, xis){
return Array.max(xis.map(function(xi){
return Math.abs(x0 - xi)
}))
}

function _manhattan(x1,x2){
return Math.abs(x1-x2)
}

function _weighted_means(wxy){
var wsum = d3.sum(wxy.map(function(wxyi){return wxyi.w}));

return {
xbar:d3.sum(wxy.map(function(wxyi){
return wxyi.w * wxyi.x
})) / wsum,
ybar:d3.sum(wxy.map(function(wxyi){
return wxyi.w * wxyi.y
})) / wsum
}
}

function _weighted_beta(wxy, xbar, ybar){
var num = d3.sum(wxy.map(function(wxyi){
return Math.pow(wxyi.w, 2) * (wxyi.x - xbar) * (wxyi.y - ybar)
}))
var denom = d3.sum(wxy.map(function(wxyi){
return Math.pow(wxyi.w, 2) * (Math.pow(wxyi.x - xbar), 2)
}))
return num / denom;
}

function _weighted_least_squares(wxy){

var ybar, xbar, beta_i, x0;

var _wm = _weighted_means(wxy);

xbar = _wm.xbar;
ybar = _wm.ybar;

var beta = _weighted_beta(wxy, xbar, ybar)

return {
beta : beta,
xbar : xbar,
ybar : ybar,
x0 : ybar - beta * xbar

}
return num / denom
}

function _calculate_lowess_fit(x, y, alpha, inc, residuals){
// alpha - smoothing factor. 0 < alpha < 1/
//
//
var k = Math.floor(x.length * alpha);

var sorted_x = x.slice();

sorted_x.sort(function(a,b){
if (a < b) {return -1}
else if (a > b) {return 1}
return 0
});
var x_max = d3.quantile(sorted_x, .98);
var x_min = d3.quantile(sorted_x, .02);

var xy = d3.zip(x, y, residuals).sort();

var size = Math.abs(x_max - x_min) / inc;

var smallest = x_min// - size;
var largest = x_max// + size;
var x_proto = d3.range(smallest, largest, size);

var xi_neighbors;
var x_i, beta_i, x0_i, delta_i, xbar, ybar;

// for each prototype, find its fit.
var y_proto = [];

for (var i = 0; i < x_proto.length; i += 1){

x_i = x_proto[i]

// get k closest neighbors.
xi_neighbors = xy.map(function(xyi){
return [
Math.abs(xyi[0] - x_i),
xyi[0],
xyi[1],
xyi[2]]
}).sort().slice(0, k)

// Get the largest distance in the neighbor set.
delta_i = d3.max(xi_neighbors)[0]

// Prepare the weights for mean calculation and WLS.

xi_neighbors = xi_neighbors.map(function(wxy){
return {
w : _tricube_weight(wxy[0] / delta_i) * wxy[3],
x : wxy[1],
y :wxy[2]
}})

// Find the weighted least squares, obviously.
var _output = _weighted_least_squares(xi_neighbors)

x0_i = _output.x0;
beta_i = _output.beta;

//
y_proto.push(x0_i + beta_i * x_i)
}
return {x:x_proto, y:y_proto};
}

/* Here are the functions you are looking for. */

function lowess_robust(x, y, alpha, inc){
// Used http://www.unc.edu/courses/2007spring/biol/145/001/docs/lectures/Oct27.html
// for the clear explanation of robust lowess.

// calculate the the first pass.
var _l;
var r = [];
var yhat = d3.mean(y);
for (var i = 0; i < x.length; i += 1) {r.push(1)};
_l = _calculate_lowess_fit(x,y,alpha, inc, r);
var x_proto = _l.x;
var y_proto = _l.y;

// Now, take the fit, recalculate the weights, and re-run LOWESS using r*w instead of w.

for (var i = 0; i < 100; i += 1){

r = d3.zip(y_proto, y).map(function(yi){
return Math.abs(yi[1] - yi[0])
})

var q = d3.quantile(r.sort(), .5)

r = r.map(function(ri){
return _bisquare_weight(ri / (6 * q))
})

_l = _calculate_lowess_fit(x,y,alpha,inc, r);
x_proto = _l.x;
y_proto = _l.y;
}

return d3.zip(x_proto, y_proto).map(function(d){
var p = {};
p.x = d[0];
p.y = d[1];
return p;
});

}

function lowess(x, y, alpha, inc){
var r = [];
for (var i = 0; i < x.length; i += 1) {r.push(1)}
var _l = _calculate_lowess_fit(x, y, alpha, inc, r);

}

function least_squares(x, y) {
var xi, yi,
_x = 0,
_y = 0,
_xy = 0,
_xx = 0;

var n = x.length;

var xhat = d3.mean(x);
var yhat = d3.mean(y);
var numerator = 0, denominator = 0;
var xi, yi;
for (var i=0; i < x.length; i++){
xi = x[i];
yi = y[i];
numerator += (xi - xhat) * (yi - yhat);
denominator += (xi - xhat) * (xi - xhat)
}

var beta = numerator / denominator;
var x0 = yhat - beta * xhat;


return {
x0:x0,
beta:beta,
fit:function(x){
return x0 + x * beta;
}}
}

function process_point(args){

var data = args.data[0];
var x = data.map(function(d){return d[args.x_accessor]});
var y = data.map(function(d){return d[args.y_accessor]});
args.ls_line = least_squares(x,y);
args.lowess_line = lowess_robust(x,y, .5, 100)
return this;

}


function xAxis(args) {
var svg = d3.select(args.target + ' svg');
var g;
Expand Down Expand Up @@ -568,9 +804,7 @@ function process_line(args){
return this;
}

function process_point(args){
return this;
}


function process_histogram(args){
// if args.binned=False, then we need to bin the data appropriately.
Expand Down Expand Up @@ -1430,6 +1664,37 @@ charts.histogram = function(args) {
return this;
}

function add_ls(args){
var svg = d3.select(args.target + ' svg');
var data = args.data[0];
//var min_x = d3.min(data, function(d){return d[args.x_accessor]});
//var max_x = d3.max(data, function(d){return d[args.x_accessor]});
var min_x = args.scales.X.ticks(args.xax_count)[0];
var max_x = args.scales.X.ticks(args.xax_count)[args.scales.X.ticks(args.xax_count).length-1];

svg.append('svg:line')
.attr('x1', args.scales.X(min_x))
.attr('x2', args.scales.X(max_x))
.attr('y1', args.scales.Y(args.ls_line.fit(min_x)) )
.attr('y2', args.scales.Y(args.ls_line.fit(max_x)) )
.attr('stroke-width', 1)
.attr('stroke', 'red');
}

function add_lowess(args){
var svg = d3.select(args.target + ' svg');
var lowess = args.lowess_line;

var line = d3.svg.line()
.x(function(d){return args.scales.X(d.x)})
.y(function(d){return args.scales.Y(d.y)})
.interpolate(args.interpolate);
svg.append('path')
.attr('d', line(lowess))
.attr('stroke', 'red')
.attr('fill', 'none');
}

charts.point = function(args) {
this.args = args;

Expand All @@ -1444,7 +1709,12 @@ charts.point = function(args) {

this.markers = function() {
markers(args);

if (args.least_squares){
add_ls(args);
}
// if (args.lowess){
// add_lowess(args);
// }
return this
}

Expand Down

0 comments on commit aac6efb

Please sign in to comment.