Skip to content

Instantly share code, notes, and snippets.

@michaelwooley
Last active July 25, 2018 01:55
Show Gist options
  • Save michaelwooley/981f2f63f763cf8d8d7d81bc7887992b to your computer and use it in GitHub Desktop.
Save michaelwooley/981f2f63f763cf8d8d7d81bc7887992b to your computer and use it in GitHub Desktop.
Expectations Questions Sample
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Expectations</title>
<meta name="description" content="An interactive getting started guide for Brackets.">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
<style>
/* set the CSS */
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
#container {
width: 500px;
height: 450px;
border: 1px solid gray;
}
.pane {
width: 500px;
height: 400px;
//background-color: beige;
margin: 10px;
}
form#settings-form {
font-size: 1em;
}
form#settings-form textarea {
font-size: 1em;
}
div.bottom-buttons {
vertical-align: bottom;
}
</style>
</head>
<body>
<div class="pane" id="survey-question">
<div id="survey-intro">
</div>
<hr/>
<div class="question form-group" id="min-question">
<label for="min-response">[Min Question Here]</label>
<input type="text" name="response" id="min-response">
<button onClick="answerMin()">Submit</button>
</div>
<div class="question form-group" id="max-question">
<label for="max-response">[Max Question Here]</label>
<input type="text" name="response" id="max-response">
<button onClick="answerMax()">Submit</button>
</div>
<div class="question form-group" id="pctl-question-1">
<label for="pctl-response">[Pctl Question Here]</label>
<input type="text" name="response" id="pctl-response">
<button onClick="answerPctl(1)">Submit</button>
</div>
<div class="question form-group" id="pctl-question-2">
<label for="pctl-response">[Pctl Question Here]</label>
<input type="text" name="response" id="pctl-response">
<button onClick="answerPctl(2)">Submit</button>
</div>
<div class="question form-group" id="pctl-question-3">
<label for="pctl-response">[Pctl Question Here]</label>
<input type="text" name="response" id="pctl-response">
<button onClick="answerPctl(3)">Submit</button>
</div>
<div class="question form-group" id="pctl-question-4">
<label for="pctl-response">[Pctl Question Here]</label>
<input type="text" name="response" id="pctl-response">
<button onClick="answerPctl(4)">Submit</button>
</div>
<div class="question form-group" id="pctl-question-5">
<label for="pctl-response">[Pctl Question Here]</label>
<input type="text" name="response" id="pctl-response">
<button onClick="answerPctl(5)">Submit</button>
</div>
<div class="question form-group" id="pctl-question-6">
<label for="pctl-response">[Pctl Question Here]</label>
<input type="text" name="response" id="pctl-response">
<button onClick="answerPctl(6)">Submit</button>
</div>
<hr/>
<div class="bottom-buttons">
<button onClick="resetQuestions()">Reset</button>
<button type="button" class="btn" data-toggle="modal" data-target="#settings-modal">
Settings
</button>
</div>
</div>
<div class="pane" id="plot-container" style="display:none;">
<div id='graph-toggle' class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-secondary active">
<input type="radio" name="options" id="cdf" autocomplete="off" checked> CDF
</label>
<label class="btn btn-secondary">
<input type="radio" name="options" id="pdf" autocomplete="off"> PDF
</label>
</div>
</div>
<div class="modal fade" id="settings-modal" tabindex="-1" role="dialog" aria-labelledby="settings-modal-label" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Settings</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div>
<form id="settings-form">
<div class="form-group">
<label for="set-question-intro">Question Intro</label>
<textarea class="form-control" id="set-question-intro" rows="3">Now think about Social Security benefits today. In particular, imagine a person who is now 70 years old. Suppose that this person retired from work at age 65 and began collecting benefits after working full time for 40 years. Suppose that, while working, this person had high enough income to be eligible for the maximum Social Security benefit that is currently paid.</textarea>
<small class="form-text text-muted">
This text should introduce the question.
</small>
</div>
<div class="form-group">
<label for="set-question-maxmin">Question Minimum/Maximum</label>
<textarea class="form-control" id="set-question-minmax" rows="1">What do you think is the absolute {MAXMIN} amount of Social Security benefits, per year, that you would be eligible to receive?</textarea>
<small class="form-text text-muted">
This text will be used for framing the questions concerning the maximum and minimum values permitted. Use "{MAXMIN}" to demarcate the place where "Max" or "Min" should be placed.
</small>
</div>
<div class="form-group">
<label for="set-question-pctl">Percentiles Question</label>
<textarea class="form-control" id="set-question-pctl" rows="2">What is the percent chance (or chances out of 100) that you would be eligible to receive <em>no more than</em> ${VALUE} of Social Security benefits per year, when you turn 70? </textarea>
<small class="form-text text-muted">
This text will be used for framing the questions concerning the percentiles. Use "{VALUE}" to demarcate the place where the relvant values should be placed.
</small>
</div>
</form>
<div>To do: Constraints on inputs. Allow differences between highest/lowest.</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js" integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm" crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>!function(r,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(r.fmin=r.fmin||{})}(this,function(r){"use strict";function e(r,e,i,f){f=f||{};var x=f.maxIterations||100,t=f.tolerance||1e-10,n=r(e),a=r(i),s=i-e;if(n*a>0)throw"Initial bisect points must have opposite signs";if(0===n)return e;if(0===a)return i;for(var o=0;x>o;++o){s/=2;var c=e+s,l=r(c);if(l*n>=0&&(e=c),Math.abs(s)<t||0===l)return c}return e+s}function i(r){for(var e=new Array(r),i=0;r>i;++i)e[i]=0;return e}function f(r,e){return i(r).map(function(){return i(e)})}function x(r,e){for(var i=0,f=0;f<r.length;++f)i+=r[f]*e[f];return i}function t(r){return Math.sqrt(x(r,r))}function n(r,e,i){for(var f=0;f<e.length;++f)r[f]=e[f]*i}function a(r,e,i,f,x){for(var t=0;t<r.length;++t)r[t]=e*i[t]+f*x[t]}function s(r,e,i){function f(r){for(var e=0;e<r.length;e++)v[h][e]=r[e];v[h].fx=r.fx}i=i||{};var x,t=i.maxIterations||200*e.length,n=i.nonZeroDelta||1.05,s=i.zeroDelta||.001,o=i.minErrorDelta||1e-6,c=i.minErrorDelta||1e-5,l=void 0!==i.rho?i.rho:1,p=void 0!==i.chi?i.chi:2,u=void 0!==i.psi?i.psi:-.5,m=void 0!==i.sigma?i.sigma:.5,h=e.length,v=new Array(h+1);v[0]=e,v[0].fx=r(e),v[0].id=0;for(var d=0;h>d;++d){var g=e.slice();g[d]=g[d]?g[d]*n:s,v[d+1]=g,v[d+1].fx=r(g),v[d+1].id=d+1}for(var y=function(r,e){return r.fx-e.fx},b=e.slice(),M=e.slice(),D=e.slice(),I=e.slice(),w=0;t>w;++w){if(v.sort(y),i.history){var k=v.map(function(r){var e=r.slice();return e.fx=r.fx,e.id=r.id,e});k.sort(function(r,e){return r.id-e.id}),i.history.push({x:v[0].slice(),fx:v[0].fx,simplex:k})}for(x=0,d=0;h>d;++d)x=Math.max(x,Math.abs(v[0][d]-v[1][d]));if(Math.abs(v[0].fx-v[h].fx)<o&&c>x)break;for(d=0;h>d;++d){b[d]=0;for(var z=0;h>z;++z)b[d]+=v[z][d];b[d]/=h}var R=v[h];if(a(M,1+l,b,-l,R),M.fx=r(M),M.fx<v[0].fx)a(I,1+p,b,-p,R),I.fx=r(I),f(I.fx<M.fx?I:M);else if(M.fx>=v[h-1].fx){var j=!1;if(M.fx>R.fx?(a(D,1+u,b,-u,R),D.fx=r(D),D.fx<R.fx?f(D):j=!0):(a(D,1-u*l,b,u*l,R),D.fx=r(D),D.fx<M.fx?f(D):j=!0),j){if(m>=1)break;for(d=1;d<v.length;++d)a(v[d],1-m,v[0],m,v[d]),v[d].fx=r(v[d])}}else f(M)}return v.sort(y),{fx:v[0].fx,x:v[0]}}function o(r,e,i,f,t,n,s){function o(o,u,h){for(var v=0;16>v;++v)if(t=(o+u)/2,a(f.x,1,i.x,t,e),p=f.fx=r(f.x,f.fxprime),m=x(f.fxprime,e),p>c+n*t*l||p>=h)u=t;else{if(Math.abs(m)<=-s*l)return t;m*(u-o)>=0&&(u=o),o=t,h=p}return 0}var c=i.fx,l=x(i.fxprime,e),p=c,u=c,m=l,h=0;t=t||1,n=n||1e-6,s=s||.1;for(var v=0;10>v;++v){if(a(f.x,1,i.x,t,e),p=f.fx=r(f.x,f.fxprime),m=x(f.fxprime,e),p>c+n*t*l||v&&p>=u)return o(h,t,u);if(Math.abs(m)<=-s*l)return t;if(m>=0)return o(t,h,p);u=p,h=t,t*=2}return t}function c(r,e,i){var f,s,c,l={x:e.slice(),fx:0,fxprime:e.slice()},p={x:e.slice(),fx:0,fxprime:e.slice()},u=e.slice(),m=1;i=i||{},c=i.maxIterations||20*e.length,l.fx=r(l.x,l.fxprime),f=l.fxprime.slice(),n(f,l.fxprime,-1);for(var h=0;c>h;++h){if(m=o(r,f,l,p,m),i.history&&i.history.push({x:l.x.slice(),fx:l.fx,fxprime:l.fxprime.slice(),alpha:m}),m){a(u,1,p.fxprime,-1,l.fxprime);var v=x(l.fxprime,l.fxprime),d=Math.max(0,x(u,p.fxprime)/v);a(f,d,f,-1,p.fxprime),s=l,l=p,p=s}else n(f,l.fxprime,-1);if(t(l.fxprime)<=1e-5)break}return i.history&&i.history.push({x:l.x.slice(),fx:l.fx,fxprime:l.fxprime.slice(),alpha:m}),l}function l(r,e,i){i=i||{};for(var f=i.maxIterations||100*e.length,x=i.learnRate||.001,n={x:e.slice(),fx:0,fxprime:e.slice()},s=0;f>s&&(n.fx=r(n.x,n.fxprime),i.history&&i.history.push({x:n.x.slice(),fx:n.fx,fxprime:n.fxprime.slice()}),a(n.x,1,n.x,-x,n.fxprime),!(t(n.fxprime)<=1e-5));++s);return n}function p(r,e,i){i=i||{};var f,x={x:e.slice(),fx:0,fxprime:e.slice()},a={x:e.slice(),fx:0,fxprime:e.slice()},s=i.maxIterations||100*e.length,c=i.learnRate||1,l=e.slice(),p=i.c1||.001,u=i.c2||.1,m=[];if(i.history){var h=r;r=function(r,e){return m.push(r.slice()),h(r,e)}}x.fx=r(x.x,x.fxprime);for(var v=0;s>v&&(n(l,x.fxprime,-1),c=o(r,l,x,a,c,p,u),i.history&&(i.history.push({x:x.x.slice(),fx:x.fx,fxprime:x.fxprime.slice(),functionCalls:m,learnRate:c,alpha:c}),m=[]),f=x,x=a,a=f,!(0===c||t(x.fxprime)<1e-5));++v);return x}r.bisect=e,r.nelderMead=s,r.conjugateGradient=c,r.gradientDescent=l,r.gradientDescentLineSearch=p,r.zeros=i,r.zerosM=f,r.norm2=t,r.weightedSum=a,r.scale=n});</script>
<script>
const PI = 3.1415926535897932384626433832795028841971;
function round(value, decimals) {
return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
}
function erf(x) {
var p = 0.47047,
a1 = 0.3480242,
a2 = (-0.0958798),
a3 = 0.7478556;
var t = 1 / (1 + p * x);
return 1 - (a1 * t + a2 * Math.pow(t, 2) + a3 * Math.pow(t, 3)) * Math.exp(-Math.pow(x, 2));
}
function logNormalCDF(x, mu, sigma) {
var z = (Math.log(x) - mu) / (sigma * Math.sqrt(2));
if (z < 0) {
return (0.5 * (1 - erf(-z)));
} else {
return 1 - (0.5 * (1 - erf(z)));
}
}
function logNormalPDF(x, mu, sigma) {
return (1 / (x * sigma * Math.sqrt(2 * PI))) * Math.exp(-Math.pow(Math.log(x) - mu, 2) / (2 * Math.pow(sigma, 2)));
}
var data = [];
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 350 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
gCDF, gPDF;
// set the ranges
var xCDF = d3.scaleLinear().range([0, width]);
var yCDF = d3.scaleLinear().range([height, 0]);
var xPDF = d3.scaleLinear().range([0, width]);
var yPDF = d3.scaleLinear().range([height, 0]);
// define the line
var valuelineCdf = d3.line()
.x(function(d) {
return xCDF(d.v);
})
.y(function(d) {
return yCDF(d.cdf);
});
var valuelinePdf = d3.line()
.x(function(d) {
return xPDF(d.v);
})
.y(function(d) {
return yPDF(d.pdf);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("div#plot-container").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
// Set the questions
var questionIntro = '';
var questionMinMax = '';
var questionPctl = '';
var questionPctlValues = [];
var questionAdd = -99;
var dataMinMax = [];
function setQuestions() {
questionIntro = d3.select('form#settings-form textarea#set-question-intro').property('value');
d3.select('#survey-question div#survey-intro').html(questionIntro);
questionMinMax = d3.select('form#settings-form textarea#set-question-minmax').property('value');
questionPctl = d3.select('form#settings-form textarea#set-question-pctl').property('value');
d3.select('#survey-question div#min-question label').html(questionMinMax.replace("{MAXMIN}", "<b><em>lowest</em></b>"));
d3.select('#survey-question div#max-question label').html(questionMinMax.replace("{MAXMIN}", "<b><em>highest</em></b>"));
d3.selectAll('#survey-question div.question').style('display', 'none');
d3.select('#survey-question div#min-question').style('display', 'block').select('input').node().focus();
}
// Questions
function answerMin() {
var mr = d3.select('input#min-response');
dataMinMax.push(parseFloat(mr.property("value")));
// Toggle Visibility
d3.select('#survey-question div#min-question').style('display', 'none');
d3.select('#survey-question div#max-question').style('display', 'block').select('input').node().focus();
}
function answerMax() {
var fd = d3.select('#survey-question div#max-question');
var mx = parseFloat(fd.select('input').property('value'));
fd.selectAll('small').remove();
if (mx < dataMinMax[0]) {
fd.append('small').attr('class', 'form-text text-danger').html('Maximum value must be between greater than minimum value of ' + dataMinMax[0] + '.')
} else {
// TODO: Check to ensure that it is less than the min.
dataMinMax.push(parseFloat(fd.select('input').property('value')));
// Compute and add on in-betweens
questionAdd = (dataMinMax[1] - dataMinMax[0]) / 7;
data.push({
lbl: 'p0',
ord: 0,
cdf: 0,
v: dataMinMax[0]
});
data.push({
lbl: 'p7',
ord: 7,
cdf: 1,
v: dataMinMax[1]
});
for (ii = 1; ii < 7; ii++) {
var qq = questionPctl.replace('{VALUE}', "<b><em>" + round(dataMinMax[0] + ii * questionAdd, 2) + "</em></b>");
questionPctlValues.push(dataMinMax[0] + ii * questionAdd);
d3.select('#survey-question div#pctl-question-' + ii + ' label').html(qq);
}
// Toggle Visibility
fd.style('display', 'none');
d3.select('#survey-question div#pctl-question-1').style('display', 'block').select('input').node().focus();
console.log(questionPctlValues);
}
}
function answerPctl(ii) {
var fd = d3.select('#survey-question div#pctl-question-' + ii);
var pctl = parseFloat(fd.select('input#pctl-response').property("value"));
fd.selectAll('small').remove();
if (ii == 0 || (pctl / 100) >= data[ii - 1].cdf) {
fd.style('display', 'none');
data.splice(-1, 0, {
lbl: 'p' + ii,
ord: ii,
cdf: parseFloat(fd.select('input#pctl-response').property("value")) / 100,
v: questionPctlValues[ii - 1]
});
if (ii == 6) {
handleAnswers();
} else {
d3.select('#survey-question div#pctl-question-' + (ii + 1)).style('display', 'block').select('input').node().focus();
}
} else {
fd.append('small').attr('class', 'form-text text-danger').html('Value must be between ' + (data[ii - 1].cdf * 100) +
' and 100.')
}
}
function resetQuestions() {
data = [];
setQuestions();
}
function approxDistribution() {
function loss(z) {
var mu = Math.exp(z[0]),
sigma = Math.exp(z[1]);
out = 0;
for (ii = 1; ii < 6; ii++) {
out += Math.pow(data[ii].cdf - logNormalCDF(data[ii].v, mu, sigma), 2);
}
return out
}
var soln = fmin.nelderMead(loss, [-99, 0]);
var mom = {
mu: Math.exp(soln.x[0]),
sigma: Math.exp(soln.x[1])
}
var samp = [...Array(25).keys()].map(function(ii) {
let v = dataMinMax[0] + (dataMinMax[1] - dataMinMax[0]) * ii / 25;
return {
v: v,
cdf: logNormalCDF(v, mom.mu, mom.sigma),
pdf: logNormalPDF(v, mom.mu, mom.sigma),
};
})
return {
mom: mom,
samp: samp
};
}
function setCdfGraph(ad) {
// Add the valueline path.
gCDF = svg.append('g').attr('id', 'cdf-plot');
gCDF.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Scale the range of the data
xCDF.domain(dataMinMax);
yCDF.domain([0.0, 1.0]);
// Add the X Axis
gCDF.append("g")
.attr("transform", "translate(0," + height + ")")
.attr('class', 'x-axis')
.call(d3.axisBottom(xCDF));
// Add the Y Axis
gCDF.append("g")
.attr('class', 'y-axis')
.call(d3.axisLeft(yCDF));
// g.select('path.line').attr('d', valueline(data.slice(1, -1)));
// Add the report
gCDF.append("path")
.data([data])
.attr("class", "line")
.attr("d", valuelineCdf(data.slice(1, -1)));
// Add the approximation
gCDF.append("path")
.data([ad.samp])
.attr("class", "line")
.attr("d", valuelineCdf(ad.samp))
.style('stroke', 'red')
.style('stroke-opacity', 0.5);
gCDF.style('display', 'block');
}
function setPdfGraph(ad) {
// // Scale the range of the data // x.domain(dataMinMax); // y.domain([0, 1]);
// Add the valueline path.
gPDF = svg.append('g').attr('id', 'pdf-plot');
gPDF.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Scale the range of the data
var mxPdf = 1.1 * Math.max(...ad.samp.map(function(x) {
return x.pdf
}));
xPDF.domain(dataMinMax);
yPDF.domain([0, mxPdf]);
// Add the X Axis
gPDF.append("g")
.attr("transform", "translate(0," + height + ")")
.attr('class', 'x-axis')
.call(d3.axisBottom(xPDF));
// Add the Y Axis
gPDF.append("g")
.attr('class', 'y-axis')
.call(d3.axisLeft(yPDF));
// Add the approximation
gPDF.append("path")
.data([ad.samp])
.attr("class", "line")
.attr("d", valuelinePdf(ad.samp))
.style('stroke', 'red')
.style('stroke-opacity', 0.5);
gPDF.style('display', 'none');
}
function handleAnswers() {
var ad = approxDistribution();
console.log(ad);
setCdfGraph(ad);
setPdfGraph(ad);
d3.select('div.pane#survey-question').transition().style('display', 'none');
d3.select('div.pane#plot-container').transition().style('display', 'block');
}
setQuestions()
var activePlot = 'cdf';
d3.select('div#graph-toggle').on('click', function() {
if (activePlot === 'cdf') {
gCDF.style('display', 'none');
gPDF.style('display', 'block');
activePlot = 'pdf';
} else {
gPDF.style('display', 'none');
gCDF.style('display', 'block');
activePlot = 'cdf';
}
console.log(activePlot);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment