Skip to content

Instantly share code, notes, and snippets.

@kgryte
Created July 3, 2013 04:45
Show Gist options
  • Select an option

  • Save kgryte/5915500 to your computer and use it in GitHub Desktop.

Select an option

Save kgryte/5915500 to your computer and use it in GitHub Desktop.

Revisions

  1. kgryte created this gist Jul 3, 2013.
    6 changes: 6 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    Anscombe's Quartet
    ==================

    <a href="http://www.d3js.org" target="_blank">D3.js</a> visualization of <a href="http://en.wikipedia.org/wiki/Anscombe's_quartet" target="_blank">Anscombe's Quartet</a>. Each dataset in the quartet is graphed separately along with its linear best fit. While all datasets have the same summary statistics (mean, variance, correlation, linear fit), their structures differ markedly.

    Anscombe used the quartet in his seminal paper <a href="http://www.jstor.org/stable/2682899" target="_blank">"Graphs in Statistical Analysis"</a> to emphasize the importance of data visualization for exploratory data analysis. Such visualization exploration enables the analyst to make more informed analytical decisions and conclusions.
    5 changes: 5 additions & 0 deletions d3.min.js
    5 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    186 changes: 186 additions & 0 deletions data.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,186 @@
    [
    [
    {
    "x": 10,
    "y": 8.04
    },
    {
    "x": 8,
    "y": 6.95
    },
    {
    "x": 13,
    "y": 7.58
    },
    {
    "x": 9,
    "y": 8.81
    },
    {
    "x": 11,
    "y": 8.33
    },
    {
    "x": 14,
    "y": 9.96
    },
    {
    "x": 6,
    "y": 7.24
    },
    {
    "x": 4,
    "y": 4.26
    },
    {
    "x": 12,
    "y": 10.84
    },
    {
    "x": 7,
    "y": 4.82
    },
    {
    "x": 5,
    "y": 5.68
    }
    ],
    [
    {
    "x": 10,
    "y": 9.14
    },
    {
    "x": 8,
    "y": 8.14
    },
    {
    "x": 13,
    "y": 8.74
    },
    {
    "x": 9,
    "y": 8.77
    },
    {
    "x": 11,
    "y": 9.26
    },
    {
    "x": 14,
    "y": 8.1
    },
    {
    "x": 6,
    "y": 6.13
    },
    {
    "x": 4,
    "y": 3.1
    },
    {
    "x": 12,
    "y": 9.13
    },
    {
    "x": 7,
    "y": 7.26
    },
    {
    "x": 5,
    "y": 4.74
    }
    ],
    [
    {
    "x": 10,
    "y": 7.46
    },
    {
    "x": 8,
    "y": 6.77
    },
    {
    "x": 13,
    "y": 12.74
    },
    {
    "x": 9,
    "y": 7.11
    },
    {
    "x": 11,
    "y": 7.81
    },
    {
    "x": 14,
    "y": 8.84
    },
    {
    "x": 6,
    "y": 6.08
    },
    {
    "x": 4,
    "y": 5.39
    },
    {
    "x": 12,
    "y": 8.15
    },
    {
    "x": 7,
    "y": 6.42
    },
    {
    "x": 5,
    "y": 5.73
    }
    ],
    [
    {
    "x": 8,
    "y": 6.58
    },
    {
    "x": 8,
    "y": 5.76
    },
    {
    "x": 8,
    "y": 7.71
    },
    {
    "x": 8,
    "y": 8.84
    },
    {
    "x": 8,
    "y": 8.47
    },
    {
    "x": 8,
    "y": 7.04
    },
    {
    "x": 8,
    "y": 5.25
    },
    {
    "x": 19,
    "y": 12.5
    },
    {
    "x": 8,
    "y": 5.56
    },
    {
    "x": 8,
    "y": 7.91
    },
    {
    "x": 8,
    "y": 6.89
    }
    ]
    ]
    58 changes: 58 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,58 @@
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <!-- Stylesheets -->
    <link href='http://fonts.googleapis.com/css?family=Cabin:500' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" type="text/css" href="reset.css">
    <link rel="stylesheet" type="text/css" href="style.css">
    <!-- Libraries -->
    <script type="text/javascript" src="d3.min.js"></script>
    <!-- Scripts -->
    <script type="text/javascript" src="script.js"></script>
    </head>
    <body>
    <h1>Anscombe's Quartet</h1>

    <script type="text/javascript">
    var scatter = Chart.scatter();

    // Update settings:
    scatter.xDomain( [3.5, 19] )
    .yDomain( [2, 12.5] )
    .yTicks( 5 );

    // Load the JSON data:
    d3.json( 'data.json', function( data ) {

    var figure, width;

    // For each dataset in 'data', create a figure and graph the dataset along with its linear fit:
    for (var i = 0; i < data.length; i++) {

    // Append a new figure to the DOM:
    figure = d3.select( 'body' )
    .append( 'figure' );

    // Get the figure width:
    width = parseInt( figure.style( 'width' ), 10 );

    // Update the chart generator settings:
    scatter.width( width )
    .xLabel( 'x' + (i+1) )
    .yLabel( 'y' + (i+1) );

    // Append the data and generate a new chart:
    figure.datum( data[i] )
    .attr('class', 'chart')
    .call( scatter );

    // Compute the linear fit:
    scatter.linearFit();

    }; // end FOR i

    });
    </script>
    </body>
    </html>
    48 changes: 48 additions & 0 deletions reset.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,48 @@
    /* http://meyerweb.com/eric/tools/css/reset/
    v2.0 | 20110126
    License: none (public domain)
    */

    html, body, div, span, applet, object, iframe,
    h1, h2, h3, h4, h5, h6, p, blockquote, pre,
    a, abbr, acronym, address, big, cite, code,
    del, dfn, em, img, ins, kbd, q, s, samp,
    small, strike, strong, sub, sup, tt, var,
    b, u, i, center,
    dl, dt, dd, ol, ul, li,
    fieldset, form, label, legend,
    table, caption, tbody, tfoot, thead, tr, th, td,
    article, aside, canvas, details, embed,
    figure, figcaption, footer, header, hgroup,
    menu, nav, output, ruby, section, summary,
    time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
    }
    /* HTML5 display-role reset for older browsers */
    article, aside, details, figcaption, figure,
    footer, header, hgroup, menu, nav, section {
    display: block;
    }
    body {
    line-height: 1;
    }
    ol, ul {
    list-style: none;
    }
    blockquote, q {
    quotes: none;
    }
    blockquote:before, blockquote:after,
    q:before, q:after {
    content: '';
    content: none;
    }
    table {
    border-collapse: collapse;
    border-spacing: 0;
    }
    302 changes: 302 additions & 0 deletions script.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,302 @@
    /**
    *
    *
    *
    *
    *
    * @author Kristofer Gryte. http://www.kgryte.com
    *
    *
    */


    var Chart = {};

    Chart.scatter = function() {

    var margin = {'top': 20, 'right': 20, 'bottom': 40, 'left': 80},
    height = 300,
    width = 760,
    radius = 5,
    xLabel = 'x',
    yLabel = 'y',
    xTicks, yTicks,
    xValue = function(d) { return d.x; },
    yValue = function(d) { return d.y; },
    xScale = d3.scale.linear(),
    yScale = d3.scale.linear(),
    xAxis = d3.svg.axis().scale( xScale ).orient( 'bottom' ),
    yAxis = d3.svg.axis().scale( yScale ).orient( 'left' ),
    xDomain, yDomain,
    line = d3.svg.line().x( X ).y( Y ),
    linearFit,
    canvas, graph, dataset, circles;


    function chart( selection ) {

    selection.each( function( data ) {

    // Convert data to standard representation; needed for non-deterministic accessors:
    data = data.map( function(d, i) {
    return {
    'x': xValue.call(data, d, i),
    'y': yValue.call(data, d, i)
    };
    });

    if (!xDomain) {
    xDomain = d3.extent( data, function(d) { return d.x; });
    };
    if (!yDomain) {
    yDomain = d3.extent( data, function(d) { return d.y; });
    };
    if (xTicks) {
    xAxis.ticks( xTicks );
    };
    if (yTicks) {
    yAxis.ticks( yTicks );
    };

    // Update the x-scale:
    xScale
    .domain( xDomain )
    .range( [0, width - margin.left - margin.right] );

    // Update the y-scale:
    yScale
    .domain( yDomain )
    .range( [height - margin.top - margin.bottom, 0]);

    // Create the SVG element:
    canvas = d3.select( this ).append('svg:svg')
    .attr('width', width)
    .attr('height', height)
    .attr('class', 'canvas');

    // Create the graph element:
    graph = canvas.append('svg:g')
    .attr('class', 'graph')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

    // Create the dataset group:
    dataset = graph.append('svg:g')
    .attr('class', 'dataset');

    // Create the circle marks:
    circles = dataset.selectAll('.point')
    .data( data )
    .enter().append('svg:circle')
    .attr('class', 'point')
    .attr('cx', function(d) { return X(d);} )
    .attr('cy', function(d) { return Y(d);} )
    .attr('r', radius );

    // Create the axes:
    graph.append('svg:g')
    .attr('class', 'x axis')
    .attr('transform', 'translate(0,' + yScale.range()[0] + ')')
    .call( xAxis )
    .append('svg:text')
    .attr('y', 40)
    .attr('x', xScale.range()[1] / 2)
    .attr('text-anchor', 'middle')
    .attr('class', 'label')
    .text( xLabel );

    graph.append('svg:g')
    .attr('class', 'y axis')
    .call( yAxis )
    .append('svg:text')
    .attr('transform', 'rotate(-90)')
    .attr('y', -74)
    .attr('dy', '.71em')
    .attr('x', -yScale.range()[0] / 2)
    .attr('text-anchor', 'middle')
    .attr('class', 'label')
    .text( yLabel );

    });

    }; // end FUNCTION chart()

    // x-accessor:
    function X(d) {
    return xScale( d.x );
    };

    // y-accessor:
    function Y(d) {
    return yScale( d.y );
    };

    // http://en.wikipedia.org/wiki/Simple_linear_regression
    function linearRegression( data ){
    var lr = {},
    n = data.length,
    sum_x = 0,
    sum_y = 0,
    sum_xy = 0,
    sum_xx = 0,
    sum_yy = 0;

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

    sum_x += data[i].x;
    sum_y += data[i].y;
    sum_xy += (data[i].x*data[i].y);
    sum_xx += (data[i].x*data[i].x);
    sum_yy += (data[i].y*data[i].y);

    };

    var XX = sum_x*sum_x,
    XY = sum_x*sum_y,
    YY = sum_y*sum_y,
    nxy = n*sum_xy,
    nxx = n*sum_xx,
    nyy = n*sum_yy;

    lr['slope'] = (nxy - XY) / (nxx - XX);
    lr['intercept'] = (sum_y - lr.slope*sum_x) / n;

    lr['r2'] = Math.pow( (nxy - XY) / Math.sqrt((nxx-XX)*(nyy-YY)), 2);

    lr['fn'] = function(x) { return this.slope*x + this.intercept; };

    return lr;
    };

    // Linear Regression: (best fit line)
    chart.linearFit = function() {
    // Initialize variables:
    var data, _data, fit, domain, x, slope, intercept;

    // Get the bound data:
    data = circles.data();

    // Perform the linear fit:
    fit = linearRegression( data );

    // Extract the fit statistics:
    slope = Math.round( fit['slope'] * 10000) / 10000;
    intercept = Math.round( fit['intercept'] * 10000 ) / 10000;
    r2 = Math.round( fit['r2'] * 10000 ) / 10000;

    // Generate the linear prediction based on a set of input values: 'x'
    domain = xScale.domain();
    x = d3.range( domain[0], domain[1]+0.5, 0.5)
    _data = x.map( function(d, i) {
    return {
    'x': d,
    'y': fit['fn']( d ) // evaluate the fit at a given 'x' value
    };
    });

    // Generate the path:
    graph.append('svg:path')
    .data( [ _data ] )
    .attr('class', 'line bestfit')
    .attr('d', line)
    .append('svg:title')
    .text( 'Fit: y = ' + slope + 'x + ' + intercept + ' ; r2 = ' + r2 );

    };

    // Set/Get: margin
    chart.margin = function( _ ) {
    if (!arguments.length) return margin;
    margin = _;
    return chart;
    };

    // Set/Get: width
    chart.width = function( _ ) {
    if (!arguments.length) return width;
    width = _;
    return chart;
    };

    // Set/Get: height
    chart.height = function( _ ) {
    if (!arguments.length) return height;
    height = _;
    return chart;
    };

    // Set/Get: x
    chart.x = function( _ ) {
    if (!arguments.length) return xValue;
    xValue = _;
    return chart;
    };

    // Set/Get: y
    chart.y = function( _ ) {
    if (!arguments.length) return yValue;
    yValue = _;
    return chart;
    };

    // Set/Get: radius
    chart.radius = function( _ ) {
    if (!arguments.length) return radius;
    radius = _;
    return chart;
    };

    // Set/Get: xLabel
    chart.xLabel = function( _ ) {
    if (!arguments.length) return xLabel;
    xLabel = _;
    return chart;
    };

    // Set/Get: yLabel
    chart.yLabel = function( _ ) {
    if (!arguments.length) return yLabel;
    yLabel = _;
    return chart;
    };

    // Set/Get: xDomain
    chart.xDomain = function( _ ) {
    if (!arguments.length) return xDomain;
    xDomain = _;
    return chart;
    };

    // Set/Get: yDomain
    chart.yDomain = function( _ ) {
    if (!arguments.length) return yDomain;
    yDomain = _;
    return chart;
    };

    // Set/Get: xTicks
    chart.xTicks = function( _ ) {
    if (!arguments.length) return xTicks;
    xTicks = _;
    return chart;
    };

    // Set/Get: yTicks
    chart.yTicks = function( _ ) {
    if (!arguments.length) return yTicks;
    yTicks = _;
    return chart;
    };

    return chart;

    }; // end FUNCTION scatterChart()










    63 changes: 63 additions & 0 deletions style.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,63 @@


    /* Apply a natural box layout model to all elements */
    * {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    }


    body {
    font-family: 'Cabin', sans-serif;
    font-size: 14px;
    font-weight: normal;
    color: #474747;
    width: 100%;
    min-height: 100%;
    }

    h1 {
    width: 70%;
    margin: 0 auto;
    margin-top: 20px;
    margin-bottom: 20px;
    text-align: center;
    font-size: 24px;
    }

    figure {
    position: relative;
    float: left;
    width: 50%;
    }

    .canvas {
    font-family: 'Cabin', sans-serif;
    font-size: 14px;
    fill: #474747;
    font-weight: normal;
    }

    .axis path, .axis line {
    fill: none;
    stroke: #aaa;
    stroke-width: 1px;
    shape-rendering: crispEdges;
    }

    .y.axis path {
    stroke: none;
    }

    .point {
    fill: rgb(255,166, 0);
    stroke: rgb(227,121,0);
    stroke-width: 2px;
    }

    .line {
    fill: none;
    stroke: steelblue;
    stroke-width: 2px;
    }