Instantly share code, notes, and snippets.

Last active March 1, 2016 14:08
Show Gist options
• Save jeroenjanssens/6395842 to your computer and use it in GitHub Desktop.
Stem-and-Leaf Plot

Back in the old days, when many data sets were still small, stem-and-leaf plots were a popular method of representing quantitative data. The example data shown in the text area comes from the cover of John Tukey's Exploratory Data Analysis. The stem-and-leaf plot updates as you change the data. Try adding fractions and negative values. Hover over the leaves to see the original values.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 stem_and_leaf = (value, base) -> sign = if value < 0 then "-" else "" # String because "-0" != "0" value = Math.abs(Math.round(value)) stem = sign + Math.floor(value / base) leaf = value % base [stem, leaf] stems_and_leaves = (data, base=10) -> # Sort the data so that the leaves appear in the correct order data.sort((a,b) -> a-b) # Determine lowest and highest stem min = data[0] max = data[data.length - 1] start = +stem_and_leaf(min, base)[0] end = +stem_and_leaf(max, base)[0] # Add all the necessary stems, including "-0" if necessary stems = {} for stem in [start..end] if stem is 0 and min < 0 stems["-0"] = [] stems[""+stem] = [] # Add the leaves to the stems for value in data [stem, leaf] = stem_and_leaf(value, base) stems[stem].push {'leaf': leaf, 'value': value} stemdata = [] for stem, leaves of stems stemdata.push({'stem': stem, 'leaves': leaves}) stemdata update = -> # Get data from textarea data = text.node().value.split(",").map (x) -> parseFloat(x) data = (x for x in data when not isNaN(x)) console.log data stemdata = stems_and_leaves(data) # Bind data to stems stems = plot.selectAll("li.stem") .data(stemdata, (d) -> d.stem+(x.value for x in d.leaves).join(',') ) # Stem enter stem_enter = stems.enter() .append("li") .attr("class", "stem") stem_enter.append("div") # Leaves enter stem_enter.append("ul") .attr("class", "leaves") .selectAll("li.leaf") .data((d) -> d.leaves) .enter() .append("li") .attr("class", "leaf") .attr('data-value', (d) -> d.value) .text((d) -> d.leaf) # Re-order the stems stems.sort((a, b) -> x = a.stem y = b.stem if (x is "-0") and (y is "0") then return -1 if (y is "-0") and (x is "0") then return 1 if (+x < +y) then -1 else 1 ) # Stem update stems.select("div").text((d) -> d.stem) # Stem exit stems.exit().remove() initial_data = [17, 32, 47, 53, 60, 61, 64, 67, 70, 70, 71, 72, 73, 73, 74, 76, 77, 79, 81, 82, 83, 83, 83, 83, 84, 85, 86, 87, 87, 88, 89, 90, 91, 91, 92, 94, 94, 95, 96, 97, 98, 98, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 102, 103, 103, 103, 103, 104, 106, 106, 106, 106, 107, 107, 107, 107, 108, 109, 109, 110, 111, 111, 111, 112, 112, 113, 114, 114, 114, 115, 116, 117, 117, 119, 120, 120, 120, 120, 121, 121, 122, 122, 122, 123, 124, 124, 125, 125, 126, 126, 127, 128, 129, 130, 131, 131, 131, 131, 132, 132, 132, 132, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 138, 139, 140, 140, 142, 143, 144, 145, 145, 145, 145, 145, 147, 149, 152, 155, 157, 159] div = d3.select("div") text = div.append("textarea") plot = div.append("ul").attr("class", "stems") text.text(initial_data.join(",")) text.on("keyup", -> update()) update()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 // Generated by CoffeeScript 1.6.1 (function() { var div, initial_data, plot, stem_and_leaf, stems_and_leaves, text, update; stem_and_leaf = function(value, base) { var leaf, sign, stem; sign = value < 0 ? "-" : ""; value = Math.abs(Math.round(value)); stem = sign + Math.floor(value / base); leaf = value % base; return [stem, leaf]; }; stems_and_leaves = function(data, base) { var end, leaf, leaves, max, min, start, stem, stemdata, stems, value, _i, _j, _len, _ref; if (base == null) { base = 10; } data.sort(function(a, b) { return a - b; }); min = data[0]; max = data[data.length - 1]; start = +stem_and_leaf(min, base)[0]; end = +stem_and_leaf(max, base)[0]; stems = {}; for (stem = _i = start; start <= end ? _i <= end : _i >= end; stem = start <= end ? ++_i : --_i) { if (stem === 0 && min < 0) { stems["-0"] = []; } stems["" + stem] = []; } for (_j = 0, _len = data.length; _j < _len; _j++) { value = data[_j]; _ref = stem_and_leaf(value, base), stem = _ref[0], leaf = _ref[1]; stems[stem].push({ 'leaf': leaf, 'value': value }); } stemdata = []; for (stem in stems) { leaves = stems[stem]; stemdata.push({ 'stem': stem, 'leaves': leaves }); } return stemdata; }; update = function() { var data, stem_enter, stemdata, stems, x; data = text.node().value.split(",").map(function(x) { return parseFloat(x); }); data = (function() { var _i, _len, _results; _results = []; for (_i = 0, _len = data.length; _i < _len; _i++) { x = data[_i]; if (!isNaN(x)) { _results.push(x); } } return _results; })(); console.log(data); stemdata = stems_and_leaves(data); stems = plot.selectAll("li.stem").data(stemdata, function(d) { return d.stem + ((function() { var _i, _len, _ref, _results; _ref = d.leaves; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { x = _ref[_i]; _results.push(x.value); } return _results; })()).join(','); }); stem_enter = stems.enter().append("li").attr("class", "stem"); stem_enter.append("div"); stem_enter.append("ul").attr("class", "leaves").selectAll("li.leaf").data(function(d) { return d.leaves; }).enter().append("li").attr("class", "leaf").attr('data-value', function(d) { return d.value; }).text(function(d) { return d.leaf; }); stems.sort(function(a, b) { var y; x = a.stem; y = b.stem; if ((x === "-0") && (y === "0")) { return -1; } if ((y === "-0") && (x === "0")) { return 1; } if (+x < +y) { return -1; } else { return 1; } }); stems.select("div").text(function(d) { return d.stem; }); return stems.exit().remove(); d3.select(self.frameElement).style("height", div.style("height")); }; initial_data = [17, 32, 47, 53, 60, 61, 64, 67, 70, 70, 71, 72, 73, 73, 74, 76, 77, 79, 81, 82, 83, 83, 83, 83, 84, 85, 86, 87, 87, 88, 89, 90, 91, 91, 92, 94, 94, 95, 96, 97, 98, 98, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 102, 103, 103, 103, 103, 104, 106, 106, 106, 106, 107, 107, 107, 107, 108, 109, 109, 110, 111, 111, 111, 112, 112, 113, 114, 114, 114, 115, 116, 117, 117, 119, 120, 120, 120, 120, 121, 121, 122, 122, 122, 123, 124, 124, 125, 125, 126, 126, 127, 128, 129, 130, 131, 131, 131, 131, 132, 132, 132, 132, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 138, 139, 140, 140, 142, 143, 144, 145, 145, 145, 145, 145, 147, 149, 152, 155, 157, 159]; div = d3.select("div"); text = div.append("textarea"); plot = div.append("ul").attr("class", "stems"); text.text(initial_data.join(",")); text.on("keyup", function() { return update(); }); update(); }).call(this);