Add Q-Q plot.
This commit is contained in:
Родитель
43e2ccca41
Коммит
82153e60b5
1
Makefile
1
Makefile
|
@ -96,6 +96,7 @@ d3.chart.js: \
|
|||
src/chart/chart.js \
|
||||
src/chart/box.js \
|
||||
src/chart/bullet.js \
|
||||
src/chart/qq.js \
|
||||
src/end.js
|
||||
|
||||
d3.layout.js: \
|
||||
|
|
176
d3.chart.js
176
d3.chart.js
|
@ -535,4 +535,180 @@ function d3_chart_bulletWidth(x) {
|
|||
return Math.abs(x(d) - x0);
|
||||
};
|
||||
}
|
||||
// Based on http://vis.stanford.edu/protovis/ex/qqplot.html
|
||||
d3.chart.qq = function() {
|
||||
var width = 1,
|
||||
height = 1,
|
||||
duration = 0,
|
||||
domain = null,
|
||||
intervals = 100,
|
||||
distribution = d3_chart_qqUniform;
|
||||
values = d3_chart_qqValues;
|
||||
qi = d3_chart_qqQi;
|
||||
|
||||
// For each small multiple…
|
||||
function qq(g) {
|
||||
g.each(function(d, i) {
|
||||
var g = d3.select(this),
|
||||
q1 = d3_chart_qqQuantile(intervals, distribution.call(this, d, i)),
|
||||
q2 = d3_chart_qqQuantile(intervals, values.call(this, d, i));
|
||||
|
||||
// Compute the new x-scale.
|
||||
var x1 = d3.scale.linear()
|
||||
.domain(domain && domain.call(this, d, i) || [d3.min(q1), d3.max(q1)])
|
||||
.range([0, width]);
|
||||
var y1 = d3.scale.linear()
|
||||
.domain(domain && domain.call(this, d, i) || [d3.min(q1), d3.max(q2)])
|
||||
.range([height, 0]);
|
||||
|
||||
// Retrieve the old scales, if this is an update.
|
||||
var x0, y0;
|
||||
if (this.__chart__) {
|
||||
x0 = this.__chart__.x;
|
||||
y0 = this.__chart__.y;
|
||||
} else {
|
||||
x0 = d3.scale.linear()
|
||||
.domain([0, Infinity])
|
||||
.range(x1.range());
|
||||
y0 = d3.scale.linear()
|
||||
.domain([0, Infinity])
|
||||
.range(y1.range());
|
||||
}
|
||||
|
||||
// Stash the new scales.
|
||||
this.__chart__ = {x: x1, y: y1};
|
||||
|
||||
// Update diagonal line.
|
||||
var xd = x1.domain(), yd = y1.domain();
|
||||
var diagonal = g.selectAll("line.diagonal")
|
||||
.data([{x1: x1(yd[0]), y1: y1(xd[0]), x2: x1(yd[1]), y2: y1(xd[1])}]);
|
||||
|
||||
diagonal.enter().append("svg:line")
|
||||
.attr("class", "diagonal")
|
||||
.attr("x1", function(d) { return d.x1; })
|
||||
.attr("y1", function(d) { return d.y1; })
|
||||
.attr("x2", function(d) { return d.x2; })
|
||||
.attr("y2", function(d) { return d.y2; })
|
||||
.style("opacity", 1e-6)
|
||||
.transition()
|
||||
.duration(duration)
|
||||
.style("opacity", 1);
|
||||
|
||||
diagonal.transition()
|
||||
.duration(duration)
|
||||
.attr("x1", function(d) { return d.x1; })
|
||||
.attr("y1", function(d) { return d.y1; })
|
||||
.attr("x2", function(d) { return d.x2; })
|
||||
.attr("y2", function(d) { return d.y2; });
|
||||
|
||||
// Update quantile plots.
|
||||
var datum = g.selectAll("circle")
|
||||
.data(d3.range(.01, 1, .01));
|
||||
|
||||
datum.enter().append("svg:circle")
|
||||
.attr("cx", function(d) { return x0(qi(d, q1)); })
|
||||
.attr("cy", function(d) { return y0(qi(d, q2)); })
|
||||
.attr("r", 5)
|
||||
.transition()
|
||||
.duration(duration)
|
||||
.attr("cx", function(d) { return x1(qi(d, q1)); })
|
||||
.attr("cy", function(d) { return y1(qi(d, q2)); });
|
||||
|
||||
datum.transition()
|
||||
.duration(duration)
|
||||
.attr("cx", function(d) { return x1(qi(d, q1)); })
|
||||
.attr("cy", function(d) { return y1(qi(d, q2)); });
|
||||
|
||||
datum.exit().transition()
|
||||
.duration(duration)
|
||||
.style("opacity", 1e-6)
|
||||
.attr("cx", function(d) { return x1(qi(d, q1)); })
|
||||
.attr("cy", function(d) { return y1(qi(d, q2)); })
|
||||
.remove();
|
||||
|
||||
// Update border box.
|
||||
var box = g.selectAll("rect")
|
||||
.data([[xd[1], yd[1]]]);
|
||||
|
||||
box.enter().append("svg:rect")
|
||||
.attr("class", "box")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", function(d) { return x0(d[0]); })
|
||||
.attr("height", function(d) { return x0(d[1]); })
|
||||
.transition()
|
||||
.duration(duration)
|
||||
.attr("width", function(d) { return x1(d[0]); })
|
||||
.attr("height", function(d) { return x1(d[1]); });
|
||||
|
||||
box.transition()
|
||||
.duration(duration)
|
||||
.attr("width", function(d) { return x1(d[0]); })
|
||||
.attr("height", function(d) { return x1(d[1]); });
|
||||
});
|
||||
}
|
||||
|
||||
qq.width = function(x) {
|
||||
if (!arguments.length) return width;
|
||||
width = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.height = function(x) {
|
||||
if (!arguments.length) return height;
|
||||
height = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.duration = function(x) {
|
||||
if (!arguments.length) return duration;
|
||||
duration = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.domain = function(x) {
|
||||
if (!arguments.length) return domain;
|
||||
domain = x == null ? x : d3.functor(x);
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.intervals = function(x) {
|
||||
if (!arguments.length) return intervals;
|
||||
intervals = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.distribution = function(x) {
|
||||
if (!arguments.length) return distribution;
|
||||
distribution = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.values = function(x) {
|
||||
if (!arguments.length) return values;
|
||||
values = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
return qq;
|
||||
};
|
||||
|
||||
// Compute quantiles of a distribution.
|
||||
function d3_chart_qqQuantile(n, values) {
|
||||
values = values.slice().sort(function(a, b) { return a - b; });
|
||||
return d3.range(n).map(function(i) { return values[Math.floor(i * (values.length - 1) / n)]; });
|
||||
}
|
||||
|
||||
// Lookup the value for an input quantile.
|
||||
function d3_chart_qqQi(f, quantiles) {
|
||||
return quantiles[Math.round(f * (quantiles.length - 1))];
|
||||
}
|
||||
|
||||
function d3_chart_qqUniform() {
|
||||
return d3.range(10000).map(Math.random);
|
||||
}
|
||||
|
||||
function d3_chart_qqValues(d) {
|
||||
return d.values;
|
||||
}
|
||||
})()
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,3 @@
|
|||
.qq circle, .qq .diagonal, .qq .box { stroke: #000; stroke-width: .5px; fill: none; }
|
||||
.qq .box { stroke: #666; }
|
||||
.qq { font: 12px sans-serif; }
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Q-Q Plots</title>
|
||||
<script type="text/javascript" src="../../d3.js"></script>
|
||||
<script type="text/javascript" src="../../d3.chart.js"></script>
|
||||
<script type="text/javascript" src="../../d3.csv.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="../button.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="qq.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="chart">
|
||||
<button class="first last" onclick="transition()">
|
||||
Update
|
||||
</button><p>
|
||||
</div>
|
||||
<script type="text/javascript" src="turkers.js"></script>
|
||||
<script type="text/javascript" src="qq.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,101 @@
|
|||
/** Sample from a normal distribution with mean 0, stddev 1. */
|
||||
function normal_sample() {
|
||||
var x = 0, y = 0, rds, c;
|
||||
do {
|
||||
x = Math.random() * 2 - 1;
|
||||
y = Math.random() * 2 - 1;
|
||||
rds = x * x + y * y;
|
||||
} while (rds == 0 || rds > 1);
|
||||
c = Math.sqrt(-2 * Math.log(rds) / rds); // Box-Muller transform
|
||||
return x * c; // throw away extra sample y * c
|
||||
}
|
||||
|
||||
// Uniform random distribution
|
||||
var uniform = function() { return Math.random(); };
|
||||
uniform.label = "Uniform Distribution";
|
||||
|
||||
// Simple 1D Gaussian (normal) distribution
|
||||
var avg = mean(turkers.percent.values);
|
||||
var dev = deviation(turkers.percent.values);
|
||||
var normal1 = function() { return avg + dev * normal_sample(); }
|
||||
normal1.label = "Gaussian (Normal) Distribution";
|
||||
|
||||
// Gaussian Mixture Model (k=3) fit using E-M algorithm
|
||||
var normal3 = function() {
|
||||
var dd = [
|
||||
[0.10306430789206111, 0.0036139086950272735, 0.30498647327844536],
|
||||
[0.5924252668569606, 0.0462763685758622, 0.4340870312025223],
|
||||
[0.9847627827855167, 2.352350767874714E-4, 0.2609264955190324]],
|
||||
r = Math.random(),
|
||||
i = r < dd[0][2] ? 0 : r < dd[0][2] + dd[1][2] ? 1 : 2,
|
||||
d = dd[i];
|
||||
return d[0] + Math.sqrt(d[1]) * normal_sample();
|
||||
}
|
||||
normal3.label = "Mixture of 3 Gaussians";
|
||||
|
||||
var w = 270,
|
||||
h = 270,
|
||||
m = [10, 50, 20, 30], // top right bottom left
|
||||
min = Infinity,
|
||||
max = -Infinity;
|
||||
|
||||
var chart = d3.chart.qq()
|
||||
.width(w).height(h)
|
||||
.domain([-.5, 1.5])
|
||||
.distribution(function(d) { return d.dist; });
|
||||
|
||||
/* Distributions for comparison. */
|
||||
var dists = [uniform, normal1, normal3];
|
||||
|
||||
var wrapper = d3.select("#chart").append("svg:svg")
|
||||
.attr("class", "qq");
|
||||
|
||||
var vis = wrapper.selectAll("g")
|
||||
.data(dists.map(function(d) { return {dist: d3.range(10000).map(d), values: turkers.percent.values, label: d.label}; }))
|
||||
.enter().append("svg:g")
|
||||
.attr("transform", function(d, i) { return "translate(" + ((w + m[3]) * i + m[3]) + "," + m[0] + ")"; })
|
||||
.call(chart);
|
||||
|
||||
wrapper.append("svg:text")
|
||||
.attr("transform", "rotate(-90)translate(" + -(h / 2 + m[0]) + ", 20)")
|
||||
.attr("text-anchor", "middle")
|
||||
.text("Turker Task Group Completion %");
|
||||
|
||||
vis.append("svg:text")
|
||||
.attr("dy", "1em")
|
||||
.attr("dx", ".3em")
|
||||
.text(function(d, i) { return d.label; });
|
||||
|
||||
function sum(array) {
|
||||
var s = 0, i = -1, n = array.length;
|
||||
while (++i < n) s += array[i];
|
||||
return s;
|
||||
}
|
||||
|
||||
function mean(array) {
|
||||
return sum(array) / array.length;
|
||||
}
|
||||
|
||||
function variance(array) {
|
||||
if (array.length < 1) return NaN;
|
||||
if (array.length === 1) return 0;
|
||||
var m = mean(array), sum = 0;
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
var d = array[i] - m;
|
||||
sum += d * d;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
function deviation(array) {
|
||||
return Math.sqrt(variance(array) / (array.length - 1));
|
||||
}
|
||||
|
||||
chart.duration(1000);
|
||||
window.transition = function() {
|
||||
vis.map(randomize).call(chart);
|
||||
};
|
||||
|
||||
function randomize(d) {
|
||||
return {dist: d.dist, values: d.values.map(Math.random)};
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
var turkers = {
|
||||
percent: {
|
||||
minValue: 0.009259259,
|
||||
maxValue: 1,
|
||||
values: [
|
||||
0.009259259, 0.014285714, 0.014285714, 0.016666667,
|
||||
0.016666667, 0.017857143, 0.018518519, 0.027777778,
|
||||
0.028571429, 0.028571429, 0.028571429, 0.033333333,
|
||||
0.033333333, 0.035714286, 0.0375, 0.041666667,
|
||||
0.041666667, 0.041666667, 0.041666667, 0.042857143,
|
||||
0.042857143, 0.042857143, 0.05, 0.055555556,
|
||||
0.069444444, 0.083333333, 0.083333333, 0.083333333,
|
||||
0.083333333, 0.083333333, 0.083333333, 0.085714286,
|
||||
0.1, 0.1, 0.101851852, 0.104166667,
|
||||
0.111111111, 0.111111111, 0.114285714, 0.114285714,
|
||||
0.116666667, 0.12037037, 0.125, 0.125,
|
||||
0.128571429, 0.133333333, 0.138888889, 0.141666667,
|
||||
0.142857143, 0.142857143, 0.15, 0.152777778, 0.158333333,
|
||||
0.166666667, 0.171428571, 0.183333333, 0.185714286,
|
||||
0.185714286, 0.1875, 0.190140845, 0.194444444,
|
||||
0.2, 0.204545455, 0.208333333, 0.214285714,
|
||||
0.214285714, 0.253521127, 0.271428571, 0.277777778,
|
||||
0.291666667, 0.3, 0.3, 0.307017544,
|
||||
0.324074074, 0.328571429, 0.333333333, 0.333333333,
|
||||
0.342857143, 0.357142857, 0.358333333, 0.378787879,
|
||||
0.381355932, 0.395833333, 0.4, 0.414285714,
|
||||
0.414285714, 0.414285714, 0.414285714, 0.43,
|
||||
0.433333333, 0.4375, 0.445833333, 0.450704225,
|
||||
0.453333333, 0.458333333, 0.466666667, 0.476666667,
|
||||
0.494736842, 0.5, 0.516666667, 0.533333333,
|
||||
0.55, 0.557142857, 0.56884058, 0.569444444,
|
||||
0.571428571, 0.585714286, 0.61, 0.622222222,
|
||||
0.657407407, 0.666666667, 0.678947368, 0.685714286,
|
||||
0.685714286, 0.69047619, 0.7, 0.7,
|
||||
0.7, 0.711538462, 0.763888889, 0.771428571,
|
||||
0.788888889, 0.8, 0.8, 0.808333333,
|
||||
0.824712644, 0.828571429, 0.836842105, 0.839285714,
|
||||
0.839285714, 0.84, 0.842857143, 0.842857143,
|
||||
0.842857143, 0.85, 0.859649123, 0.869791667,
|
||||
0.871428571, 0.871428571, 0.892344498, 0.914285714,
|
||||
0.928571429, 0.933908046, 0.953703704, 0.973684211,
|
||||
0.975, 0.981481481, 0.983333333, 0.985714286,
|
||||
0.985714286, 0.985714286, 0.985714286, 0.985714286,
|
||||
0.985714286, 0.985714286, 0.985714286, 0.985714286,
|
||||
0.985714286, 0.985714286, 0.985714286, 0.985714286,
|
||||
0.985714286, 0.987096774, 0.990740741, 0.991666667,
|
||||
0.992, 0.994047619, 0.996666667, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
]
|
||||
}
|
||||
};
|
|
@ -0,0 +1,176 @@
|
|||
// Based on http://vis.stanford.edu/protovis/ex/qqplot.html
|
||||
d3.chart.qq = function() {
|
||||
var width = 1,
|
||||
height = 1,
|
||||
duration = 0,
|
||||
domain = null,
|
||||
intervals = 100,
|
||||
distribution = d3_chart_qqUniform;
|
||||
values = d3_chart_qqValues;
|
||||
qi = d3_chart_qqQi;
|
||||
|
||||
// For each small multiple…
|
||||
function qq(g) {
|
||||
g.each(function(d, i) {
|
||||
var g = d3.select(this),
|
||||
q1 = d3_chart_qqQuantile(intervals, distribution.call(this, d, i)),
|
||||
q2 = d3_chart_qqQuantile(intervals, values.call(this, d, i));
|
||||
|
||||
// Compute the new x-scale.
|
||||
var x1 = d3.scale.linear()
|
||||
.domain(domain && domain.call(this, d, i) || [d3.min(q1), d3.max(q1)])
|
||||
.range([0, width]);
|
||||
var y1 = d3.scale.linear()
|
||||
.domain(domain && domain.call(this, d, i) || [d3.min(q1), d3.max(q2)])
|
||||
.range([height, 0]);
|
||||
|
||||
// Retrieve the old scales, if this is an update.
|
||||
var x0, y0;
|
||||
if (this.__chart__) {
|
||||
x0 = this.__chart__.x;
|
||||
y0 = this.__chart__.y;
|
||||
} else {
|
||||
x0 = d3.scale.linear()
|
||||
.domain([0, Infinity])
|
||||
.range(x1.range());
|
||||
y0 = d3.scale.linear()
|
||||
.domain([0, Infinity])
|
||||
.range(y1.range());
|
||||
}
|
||||
|
||||
// Stash the new scales.
|
||||
this.__chart__ = {x: x1, y: y1};
|
||||
|
||||
// Update diagonal line.
|
||||
var xd = x1.domain(), yd = y1.domain();
|
||||
var diagonal = g.selectAll("line.diagonal")
|
||||
.data([{x1: x1(yd[0]), y1: y1(xd[0]), x2: x1(yd[1]), y2: y1(xd[1])}]);
|
||||
|
||||
diagonal.enter().append("svg:line")
|
||||
.attr("class", "diagonal")
|
||||
.attr("x1", function(d) { return d.x1; })
|
||||
.attr("y1", function(d) { return d.y1; })
|
||||
.attr("x2", function(d) { return d.x2; })
|
||||
.attr("y2", function(d) { return d.y2; })
|
||||
.style("opacity", 1e-6)
|
||||
.transition()
|
||||
.duration(duration)
|
||||
.style("opacity", 1);
|
||||
|
||||
diagonal.transition()
|
||||
.duration(duration)
|
||||
.attr("x1", function(d) { return d.x1; })
|
||||
.attr("y1", function(d) { return d.y1; })
|
||||
.attr("x2", function(d) { return d.x2; })
|
||||
.attr("y2", function(d) { return d.y2; });
|
||||
|
||||
// Update quantile plots.
|
||||
var datum = g.selectAll("circle")
|
||||
.data(d3.range(.01, 1, .01));
|
||||
|
||||
datum.enter().append("svg:circle")
|
||||
.attr("cx", function(d) { return x0(qi(d, q1)); })
|
||||
.attr("cy", function(d) { return y0(qi(d, q2)); })
|
||||
.attr("r", 5)
|
||||
.transition()
|
||||
.duration(duration)
|
||||
.attr("cx", function(d) { return x1(qi(d, q1)); })
|
||||
.attr("cy", function(d) { return y1(qi(d, q2)); });
|
||||
|
||||
datum.transition()
|
||||
.duration(duration)
|
||||
.attr("cx", function(d) { return x1(qi(d, q1)); })
|
||||
.attr("cy", function(d) { return y1(qi(d, q2)); });
|
||||
|
||||
datum.exit().transition()
|
||||
.duration(duration)
|
||||
.style("opacity", 1e-6)
|
||||
.attr("cx", function(d) { return x1(qi(d, q1)); })
|
||||
.attr("cy", function(d) { return y1(qi(d, q2)); })
|
||||
.remove();
|
||||
|
||||
// Update border box.
|
||||
var box = g.selectAll("rect")
|
||||
.data([[xd[1], yd[1]]]);
|
||||
|
||||
box.enter().append("svg:rect")
|
||||
.attr("class", "box")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", function(d) { return x0(d[0]); })
|
||||
.attr("height", function(d) { return x0(d[1]); })
|
||||
.transition()
|
||||
.duration(duration)
|
||||
.attr("width", function(d) { return x1(d[0]); })
|
||||
.attr("height", function(d) { return x1(d[1]); });
|
||||
|
||||
box.transition()
|
||||
.duration(duration)
|
||||
.attr("width", function(d) { return x1(d[0]); })
|
||||
.attr("height", function(d) { return x1(d[1]); });
|
||||
});
|
||||
}
|
||||
|
||||
qq.width = function(x) {
|
||||
if (!arguments.length) return width;
|
||||
width = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.height = function(x) {
|
||||
if (!arguments.length) return height;
|
||||
height = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.duration = function(x) {
|
||||
if (!arguments.length) return duration;
|
||||
duration = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.domain = function(x) {
|
||||
if (!arguments.length) return domain;
|
||||
domain = x == null ? x : d3.functor(x);
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.intervals = function(x) {
|
||||
if (!arguments.length) return intervals;
|
||||
intervals = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.distribution = function(x) {
|
||||
if (!arguments.length) return distribution;
|
||||
distribution = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
qq.values = function(x) {
|
||||
if (!arguments.length) return values;
|
||||
values = x;
|
||||
return qq;
|
||||
};
|
||||
|
||||
return qq;
|
||||
};
|
||||
|
||||
// Compute quantiles of a distribution.
|
||||
function d3_chart_qqQuantile(n, values) {
|
||||
values = values.slice().sort(function(a, b) { return a - b; });
|
||||
return d3.range(n).map(function(i) { return values[Math.floor(i * (values.length - 1) / n)]; });
|
||||
}
|
||||
|
||||
// Lookup the value for an input quantile.
|
||||
function d3_chart_qqQi(f, quantiles) {
|
||||
return quantiles[Math.round(f * (quantiles.length - 1))];
|
||||
}
|
||||
|
||||
function d3_chart_qqUniform() {
|
||||
return d3.range(10000).map(Math.random);
|
||||
}
|
||||
|
||||
function d3_chart_qqValues(d) {
|
||||
return d.values;
|
||||
}
|
Загрузка…
Ссылка в новой задаче