Last active
February 6, 2016 17:34
-
-
Save aficionado/7538081 to your computer and use it in GitHub Desktop.
Iso-cost lines
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
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
html, body{ | |
height: 100%; | |
} | |
*, *:before, *:after { | |
-webkit-box-sizing: border-box; | |
-moz-box-sizing: border-box; | |
box-sizing: border-box; | |
} | |
body { | |
font: 14px/18px "Helvetica Neue", Helvetica, Arial, sans-serif; | |
margin: 0; | |
position: relative; | |
background: #fff; | |
display: block; | |
color: #333; | |
} | |
.clearfix:after { | |
visibility: hidden; | |
display: block; | |
font-size: 0; | |
content: " "; | |
clear: both; | |
height: 0; | |
} | |
.clearfix { display: inline-table; } | |
* html .clearfix { height: 1%; } | |
.clearfix { display: block; } | |
#wrap{ | |
min-height: 100%; | |
height: auto; | |
margin: 0 auto -60px; | |
padding: 0 0 60px; | |
} | |
#header{ | |
height: 100px; | |
background: #F7F7F7; | |
border-bottom: 1px solid #D6D8D9; | |
border-top: 4px solid #293A44; | |
} | |
#header img{ | |
margin-top: 10px; | |
} | |
#footer { | |
height: 60px; | |
background-color: #293A43; | |
color: #5D6F79; | |
text-align: center; | |
font-size: 11px; | |
padding-top: 20px; | |
} | |
.container{ | |
width: 980px; | |
margin: 0 auto; | |
padding: 0 30px; | |
} | |
#chart { | |
float: left; | |
width: 720px; | |
margin-top: 30px; | |
} | |
.sliders{ | |
float: left; | |
width: 160px; | |
margin-top: 30px; | |
} | |
.slider_box{ | |
background: #eee; | |
padding: 10px 10px 5px; | |
margin-top:20px; | |
border-radius:5px; | |
} | |
.sliders label{ | |
display: block; | |
} | |
.sliders input{ | |
float: left; | |
margin-bottom: 10px; | |
} | |
.sliders .value_slider{ | |
display: block; | |
font-weight: bold; | |
text-align: center; | |
} | |
.legend{ | |
font-size: 12px; | |
} | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: black; | |
shape-rendering: crispEdges; | |
} | |
.axis text { | |
font-family: sans-serif; | |
font-size: 11px; | |
} | |
</style> | |
<body> | |
<div id="wrap"> | |
<div id="header"> | |
<div class="container clearfix"> | |
<a href="https://bigml.com/" target="_blank"><img alt="bigml" src="https://static.bigml.com/static/img/bigml.png"></a> | |
</div> | |
</div> | |
<div class="container clearfix"> | |
<div id="chart"></div> | |
<div class="sliders"> | |
<div class="clearfix slider_box"> | |
<label>Iso-cost line:</label> | |
<input id="isoline" type="range" min="0" max="100" value="20"> | |
</div> | |
<div class="clearfix slider_box"> | |
<label>P(+):</label> | |
<input id="prevalence" type="range" min="0" max="40" value="20"> | |
<div id="prevalencedisplay" class="value_slider">0.5</div> | |
</div> | |
<div class="clearfix slider_box"> | |
<label>FP cost:</label> | |
<input id="fpcost" type="range" min="1" max="250" value="25"> | |
<div id="fpcostdisplay" class="value_slider">0.25</div> | |
</div> | |
<div class="clearfix slider_box"> | |
<label>FN cost:</label> | |
<input id="fncost" type="range" min="1" max="250" value="25"> | |
<div id="fncostdisplay" class="value_slider">0.25</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div id="footer"> | |
Copyright © 2013 BigML, Inc. | |
</div> | |
</body> | |
<script src="https://code.jquery.com/jquery-1.7.1.js" type="text/javascript"></script> | |
<script src="https://d3js.org/d3.v3.min.js"></script> | |
<script> | |
// Computes Area Under the Curve using Heron's formula | |
var AUC = function(tpr, fpr) { | |
a = Math.sqrt(2); | |
b = Math.sqrt(tpr * tpr + fpr * fpr); | |
c = Math.sqrt((1 - tpr) * (1 - tpr) + (1 - fpr) * (1 - fpr)); | |
s = (a + b + c) /2; | |
if (tpr == fpr) { | |
return 0.5 | |
} else if (tpr > fpr) { | |
return 0.5 + Math.sqrt(s * (s - a) * (s - b) * (s - c)); | |
} else { | |
return 0.5 - Math.sqrt(s * (s - a) * (s - b) * (s - c)); | |
} | |
}; | |
</script> | |
<script> | |
// Loads multiple evaluations | |
var loadEvaluations = function(options) { | |
var settings = $.extend({ | |
callback: function() {}, | |
maxInstances: 1, | |
urls: [ | |
'https://bigml.io/andromeda/evaluation/52407497035d0772e700ed50?username=francisco;api_key=0100cfce0561be0b2cd4a3203c89d2c110c94e1d', | |
'https://bigml.io/andromeda/evaluation/52407637035d0772e700ed71?username=francisco;api_key=0100cfce0561be0b2cd4a3203c89d2c110c94e1d', | |
'https://bigml.io/andromeda/evaluation/5230b787035d0772e3003ba6?username=francisco;api_key=0100cfce0561be0b2cd4a3203c89d2c110c94e1d', | |
'https://bigml.io/andromeda/evaluation/52274838035d0729c1000681?username=francisco;api_key=0100cfce0561be0b2cd4a3203c89d2c110c94e1d', | |
'https://bigml.io/andromeda/evaluation/51fb3e5e035d072bfd00111d?username=francisco;api_key=0100cfce0561be0b2cd4a3203c89d2c110c94e1d', | |
], | |
evaluations: [] | |
}, options || {}); | |
$.ajax({ | |
url : settings.urls[settings.evaluations.length], | |
dataType: 'jsonp', | |
crossDomain:true, | |
success: function(evaluation) { | |
var confusionMatrix = evaluation.result.model.confusion_matrix; | |
var tpPlusFN = confusionMatrix[0].reduce(function(previousValue, currentValue, index, array) { | |
return previousValue + currentValue; | |
}); | |
var truePositiveRate = confusionMatrix[0][0]/tpPlusFN | |
var fp = 0 | |
var fpPlusTN = 0 | |
for (var i=1;i<confusionMatrix.length;i++) { | |
fp += confusionMatrix[i][0] | |
fpPlusTN += confusionMatrix[i].reduce(function(previousValue, currentValue, index, array) { | |
return previousValue + currentValue; | |
}); | |
} | |
var falsePositiveRate = fp / fpPlusTN; | |
if (evaluation.sampled_rows > settings.maxInstances) { | |
settings.maxInstances = evaluation.sampled_rows | |
} | |
result = { | |
"resource": evaluation.resource, | |
"tpr": truePositiveRate, | |
"fpr": falsePositiveRate, | |
"instances": evaluation.sampled_rows, | |
"auc": AUC(truePositiveRate, falsePositiveRate)} | |
settings.evaluations.push(result); | |
if (settings.evaluations.length < settings.urls.length) { | |
loadEvaluations(settings); | |
} else { | |
settings.callback(settings.evaluations, settings.maxInstances); | |
} | |
} | |
}); | |
}; | |
</script> | |
<script> | |
var margin = {top: 20, right: 60, bottom: 30, left: 60}; | |
var width = 720 - margin.left - margin.right; | |
var height = 480 - margin.top - margin.bottom; | |
var xPadding = 20; | |
var yPadding = 35; | |
var xScale = d3.scale.linear() | |
.domain([0, 1]) | |
.range([0, width]); | |
var yScale = d3.scale.linear() | |
.domain([0, 1]) | |
.range([height, 0]); | |
var xAxis = d3.svg.axis() | |
.scale(xScale) | |
.orient("bottom") | |
.ticks(15); | |
var yAxis = d3.svg.axis() | |
.scale(yScale) | |
.orient("left") | |
.ticks(25); | |
var lineFunction = d3.svg.line() | |
.x(function(d) { return xScale(d.x); }) | |
.y(function(d) { return yScale(d.y); }) | |
.interpolate("linear"); | |
var colorEvaluation = d3.scale.category20(); | |
var colorLookup = function(evaluation) { | |
return colorEvaluation(evaluation.resource); | |
}; | |
// SVG panel. | |
var svg = d3.select("#chart") | |
.append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
// Adds X axis | |
svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + height + ")") | |
.call(xAxis) | |
.append("text") | |
.attr("class", "label") | |
.attr("x", width) | |
.attr("y", -6) | |
.style("text-anchor", "end") | |
.text("False Positive Rate"); | |
// Adds Y axis | |
svg.append("g") | |
.attr("class", "y axis") | |
.call(yAxis) | |
.append("text") | |
.attr("class", "label") | |
.attr("transform", "rotate(-90)") | |
.attr("y", 6) | |
.attr("dy", ".71em") | |
.style("text-anchor", "end") | |
.text("True Positive Rate") | |
// Draw X-axis grid lines | |
svg.selectAll("line.x") | |
.data(xScale.ticks(10)) | |
.enter().append("line") | |
.attr("class", "x") | |
.attr("x1", xScale) | |
.attr("x2", xScale) | |
.attr("y1", 0) | |
.attr("y2", height) | |
.style("stroke", "#ccc"); | |
// Draw Y-axis grid lines | |
svg.selectAll("line.y") | |
.data(yScale.ticks(10)) | |
.enter().append("line") | |
.attr("class", "y") | |
.attr("x1", 0) | |
.attr("x2", width) | |
.attr("y1", yScale) | |
.attr("y2", yScale) | |
.style("stroke", "#ccc"); | |
var isocost = 0.2 | |
var prevalence = 0.5 | |
var fpcost = 0.25 | |
var fncost = 0.25 | |
var costfp = (1 - prevalence) * fpcost | |
var costfn = prevalence * fncost | |
var slope = costfp / costfn; | |
var updateCostLines= function () { | |
costfp = (1 - prevalence) * fpcost; | |
costfn = Math.max(prevalence * fncost, 0.0001); | |
slope = costfp / costfn; | |
isoCostLines.attr("x1", function (cost) { | |
if (1 - cost/(costfn) < 0) { | |
return xScale(Math.min(1, (-1 + cost/(costfn))/slope)) | |
} else { | |
return 0 | |
} | |
}) | |
.attr("x2", function (cost) { | |
if (slope + 1 - cost/(costfn) > 1) { | |
return xScale((1- (1 - cost/(costfn)))/slope) | |
} else { | |
return xScale(1) | |
} | |
}) | |
.attr("y1", function (cost) { | |
return yScale(Math.max(0, 1 - cost/(costfn)));}) | |
.attr("y2", function (cost) { | |
return yScale(Math.max(0, Math.min(1, slope + 1 - cost/(costfn))));}) | |
} | |
// Draw Y-axis grid lines | |
var isoCostLines = svg.selectAll("iso-cost") | |
.data([isocost]) | |
.enter().append("line") | |
.attr("class", "y") | |
.attr("x1", function (cost) { | |
if (1 - cost/(costfn) < 0) { | |
return xScale(Math.min(1, (-1 + cost/(costfn))/slope)) | |
} else { | |
return 0 | |
} | |
}) | |
.attr("x2", function (cost) { | |
if (slope + 1 - cost/(costfn) > 1) { | |
return xScale((1- (1 - cost/(costfn)))/slope) | |
} else { | |
return xScale(1) | |
} | |
}) | |
.attr("y1", function (cost) { | |
return yScale(Math.max(0, 1 - cost/(costfn)));}) | |
.attr("y2", function (cost) { | |
return yScale(Math.max(0, Math.min(1, slope + 1 - cost/(costfn))));}) | |
.style("stroke", "red"); | |
d3.select("#isoline").on("change", function() { | |
isocost = this.value/100; | |
isoCostLines.data([isocost]); | |
updateCostLines();}); | |
d3.select("#prevalence").on("change", function() { | |
prevalence = this.value/40; | |
$("#prevalencedisplay").html(prevalence); | |
updateCostLines();}); | |
d3.select("#fpcost").on("change", function() { | |
fpcost = this.value/100; | |
$("#fpcostdisplay").html(fpcost); | |
updateCostLines();}); | |
d3.select("#fncost").on("change", function() { | |
fncost = this.value/100; | |
$("#fncostdisplay").html(fncost); | |
updateCostLines();}); | |
// Evaluation info | |
svg.append('text') | |
.attr("text-anchor", "middle") | |
.attr({'id': 'evaluationLabel', 'x': width/2, 'y': height-20}) | |
.style({'font-size': '15px', 'font-weight': 'bold', 'fill': 'black'}); | |
var line = d3.svg.line() | |
.interpolate("basis") | |
.x(function(d) { | |
return xScale(d);}) | |
.y(function(d) { | |
return yScale(d) }); | |
var data = [0.2,0.2,0.4,0.4]; | |
svg.append("svg:path").attr("d", line(data)) | |
var path; | |
loadEvaluations({ | |
callback: function(evaluations, maxInstances) { | |
// Draw evaluations | |
svg.selectAll("circle") | |
.data(evaluations) | |
.enter() | |
.append("circle") | |
.attr("cx", function(evaluation) { | |
return xScale(evaluation.fpr); | |
}) | |
.attr("cy", function(evaluation) { | |
return yScale(evaluation.tpr); | |
}) | |
.attr("r", function(evaluation) { | |
return (Math.sqrt(height - yScale(evaluation.instances/maxInstances)) + 5) | |
}) | |
.attr("fill", colorLookup) | |
.attr("stroke", colorLookup) | |
.style('cursor', 'pointer') | |
.on('mouseover', function(evaluation) { | |
d3.select('svg #evaluationLabel') | |
.text(evaluation.resource + ", AUC: " + evaluation.auc.toFixed(2) + ", Instances: " + evaluation.instances) | |
.transition() | |
.style('opacity', 1); | |
auc = [{"x": evaluation.fpr, "y": evaluation.tpr}, | |
{"x": 0, "y": 0}, | |
{"x": 1, "y": 0}, | |
{"x": 1, "y": 1}, | |
{"x": evaluation.fpr, "y": evaluation.tpr}]; | |
path = svg.append("path") | |
.attr("d", lineFunction(auc)) | |
.attr("stroke", "blue") | |
.attr("stroke-width", 2) | |
.attr("fill", "yellow") | |
.attr("opacity", "0.25");}) | |
.on('mouseout', function(d) { | |
d3.select('svg #evaluationLabel') | |
.transition() | |
.duration(1500) | |
.style('opacity', 0); | |
path.remove();}); | |
// Draw legends | |
var legend = svg.selectAll(".legend") | |
.data(colorEvaluation.domain()) | |
.enter().append("g") | |
.attr("class", "legend") | |
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); | |
legend.append("rect") | |
.attr("x", width + 5) | |
.attr("width", 10) | |
.attr("height", 10) | |
.style("fill", colorEvaluation); | |
legend.append("text") | |
.attr("x", width - 40) | |
.attr("y", 4) | |
.attr("dy", ".35em") | |
.style("text-anchor", "end") | |
.text(function(d) { return d }); | |
} | |
}); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment