Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
688 lines (604 sloc) 29.8 KB
<!DOCTYPE html>
<html data-require="math math-format graphie interactive">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Estimating the line of best fit</title>
<script data-main="../local-only/main.js" src="../local-only/require.js"></script>
<style type="text/css">
.graphie.float {
float: left;
margin-right: 10px;
}
</style>
</head>
<body>
<div class="exercise">
<div class="problems">
<div id="linear" data-weight="3">
<div class="vars" data-ensure="VALIDATOR(REALSLOPE, REALINTERCEPT)">
<var id="SLOPE">randFromArray([-1, 1]) * randRange(10, 80) / 20</var>
<var id="INTERCEPT">randRange(-5, 5)</var>
<var id="FUNC">function(x) { return SLOPE * x + INTERCEPT }</var>
<var id="TOP">((SLOPE &lt; 0 ? -1 : 1) * 11 - INTERCEPT) / SLOPE</var>
<var id="BOTTOM">((SLOPE &lt; 0 ? 1 : -1) * 11 - INTERCEPT) / SLOPE</var>
<var id="BOUND">function(x, low, high) {
return (x &lt; low) ? low : (x &gt; high) ? high : x;
}</var>
<var id="OFFSET">random() + 0.5</var>
<var id="LEFT">(BOTTOM &lt; -11) ? -11 : BOTTOM</var>
<var id="RIGHT">(TOP &gt; 11) ? 11 : TOP</var>
<var id="XS">sortNumbers((function() {
var range = (RIGHT - LEFT) / 20;
return _.map(shuffle(_.range(-8, 9), 9), function(x) {
return x * range;
});
})())</var>
<var id="UNIQARRAYS">function(a) {
// make sure the points are unique in a
for (var i = 0; i &lt; a.length; ++i) {
for (var j = i + 1; j &lt; a.length; ++j) {
if (_.isEqual(a[i], a[j])) {
a.splice(j, 1);
--j;
}
}
}
return a;
}</var>
<var id="ANG">atan2(SLOPE, 1) + PI / 2</var>
<var id="TOTALOFFSET">[]</var>
<var id="POINTS">UNIQARRAYS(_.map(XS, function(x, index) {
// Calculate the sum of the offsets so far
var total = _.reduce(TOTALOFFSET, function(sum, num) {
return sum + num;
}, 0);
var randomRange = function(low, high) {
return random() * (high - low) + low;
}
var offset;
if (index &lt; 2 || index &gt; 6) {
// make sure the first two and last
// two points are on the same side
offset = randomRange(OFFSET/2, OFFSET);
} else if (total &lt; 0) {
// if the offset is negative, choose less
// negative numbers
offset = randomRange(-OFFSET - total, OFFSET);
} else {
// if it is positive, choose less
//positive numbers
offset = randomRange(-OFFSET, OFFSET - total);
}
// store this offset
TOTALOFFSET.push(offset);
// calculate the rounded point created by this offset
return [BOUND(round(x + cos(ANG) * offset), -9, 9),
BOUND(round(FUNC(x) + sin(ANG) * offset), -9, 9)];
}))</var>
<var id="REALSLOPE, REALINTERCEPT, R2">(function() {
// calculate the linear square regression line for our points
var n = POINTS.length;
var meanX = _.reduce(POINTS, function(sum, pt) {
return sum + pt[0];
}, 0) / n;
var meanX2 = pow(meanX, 2);
var meanY = _.reduce(POINTS, function(sum, pt) {
return sum + pt[1];
}, 0) / n;
var meanY2 = pow(meanY, 2);
var xi2 = _.reduce(POINTS, function(sum, pt) {
return sum + pow(pt[0], 2);
}, 0);
var yi2 = _.reduce(POINTS, function(sum, pt) {
return sum + pow(pt[1], 2);
}, 0);
var xiyi = _.reduce(POINTS, function(sum, pt) {
return sum + pt[0] * pt[1];
}, 0);
var error = xiyi - n * meanX * meanY;
var realIntercept = (meanY * xi2 - meanX * xiyi) / (xi2 - n * meanX2);
var realSlope = error / (xi2 - n * meanX2);
// Sample standard deviations
var correction = n / (n - 1);
var stdevX = correction * pow(xi2 / n - meanX2, 0.5);
var stdevY = correction * pow(yi2 / n - meanY2, 0.5);
var p = _.map(POINTS, function(pt) {return pt[0]; });
var r2 = error / ((n - 1) * stdevX * stdevY);
return [realSlope, realIntercept, r2];
})()</var>
<var id="VALIDATOR">function(slope, intercept) {
var low = _.first(POINTS), high = _.last(POINTS);
var slopeadd = 1 / REALSLOPE + REALSLOPE;
lowx = (1 / REALSLOPE * low[0] + low[1] - REALINTERCEPT) /
slopeadd;
highx = (1 / REALSLOPE * high[0] + high[1] - REALINTERCEPT) /
slopeadd;
var lowfunc = function(x) {
return -1 / REALSLOPE * (x - low[0]) + low[1];
};
var highfunc = function(x) {
return -1 / REALSLOPE * (x - high[0]) + high[1];
};
lowIntersectx = (1 / REALSLOPE * low[0] +
low[1] - intercept) /
(slope + 1 / REALSLOPE);
highIntersectx = (1 / REALSLOPE * high[0] +
high[1] - intercept) /
(slope + 1 / REALSLOPE);
// the differences between the least squares line and the
// given line, at the highest and lowest points
var lowDiff = sqrt(pow(lowfunc(lowx) -
lowfunc(lowIntersectx), 2) +
pow(lowx - lowIntersectx, 2));
var highDiff = sqrt(pow(highfunc(highx) -
highfunc(highIntersectx), 2) +
pow(highx - highIntersectx, 2));
// whether or not each of the points are above or below
// the given line
var updown = _.map(POINTS, function(pt) {
var x = pt[0], y = pt[1],
est = slope * x + intercept;
return y &gt;= est ? 1 : -1;
});
// sort and reverse-sort updown
var updownSorted = sortNumbers(updown);
var updownReversed = updownSorted.slice(0).reverse();
// ensure:
// all the points are not up, ..., up, down, ..., down
return !_.isEqual(updown, updownSorted) &amp;&amp;
// all the points are not down, ..., down, up, ..., up
!_.isEqual(updown, updownReversed) &amp;&amp;
// one point is above/below
_.include(updown, 1) &amp;&amp;
_.include(updown, -1) &amp;&amp;
// the differences are between some proportion
// of the offset
lowDiff &lt; 1.3 * OFFSET &amp;&amp;
highDiff &lt; 1.3 * OFFSET;
}</var>
</div>
<div class="question">
<p>In the graph below, can a line fit the data well?</p>
<p class="render-answer-area-here"></p>
<p class="question">If yes, fit a line to the data.</p>
</div>
<div class="problem">
<div class="graphie" id="main">
graphInit({
range: 11,
scale: 20,
axisArrows: "&lt;-&gt;",
tickStep: 1,
labelStep: 1,
gridOpacity: 0.05,
axisOpacity: 0.2,
tickOpacity: 0.4,
labelOpacity: 0.5
});
addMouseLayer();
// add the points
_.each(POINTS, function(pt) {
circle(pt, 0.2, { fill: "black" });
});
// add our movable line
graph.pointA = addMovablePoint({
coord: [-5, 5],
snapX: 0.5,
snapY: 0.5,
normalStyle: {
stroke: KhanUtil.BLUE,
fill: KhanUtil.BLUE
}
});
graph.pointB = addMovablePoint({
coord: [5, 5],
snapX: 0.5,
snapY: 0.5,
normalStyle: {
stroke: KhanUtil.BLUE,
fill: KhanUtil.BLUE
}
});
graph.bestFitLine = addMovableLineSegment({
pointA: graph.pointA,
pointZ: graph.pointB,
fixed: true,
extendLine: true
});
// A and B can't be in the same place
graph.pointA.onMove = function(x, y) {
return (x != graph.pointB.coord[0] ||
y != graph.pointB.coord[1]);
};
graph.pointB.onMove = function(x, y) {
return (x != graph.pointA.coord[0] ||
y != graph.pointA.coord[1]);
};
graph.pointA.toFront();
graph.pointB.toFront();
var shown = false;
graph.showLine = function() {
graph.pointA.visibleShape.show();
graph.pointA.mouseTarget.show();
graph.pointB.visibleShape.show();
graph.pointB.mouseTarget.show();
graph.bestFitLine.show();
};
graph.hideLine = function() {
graph.pointA.visibleShape.hide();
graph.pointA.mouseTarget.hide();
graph.pointB.visibleShape.hide();
graph.pointB.mouseTarget.hide();
graph.bestFitLine.hide();
};
// show the true least square regression line
graph.showSolution = function() {
if (shown) {
return;
} else {
shown = true;
}
var roundToHalf = function(x) {
return round(x * 2) / 2;
};
var realFunc = function(x) {
return REALSLOPE * x + REALINTERCEPT;
};
$("html, body").animate({
scrollTop: $(".question").offset().top
}, {
duration: 500,
easing: "swing",
complete: function() {
line([-11, realFunc(-11)],
[11, realFunc(11)],
{ stroke: ORANGE, opacity: 0 })
.animate({ opacity: 1 }, 750);
}
});
};
</div>
</div>
<div class="solution" data-type="custom">
<div class="instruction">
<ul>
<li>
<label>
<input id="exists" checked name="linear" onclick="KhanUtil.graphs.main.graph.showLine()" type="radio">
<span>Yes</span>
</label>
</li>
<li>
<label>
<input id="notexists" name="linear" onclick="KhanUtil.graphs.main.graph.hideLine()" type="radio">
<span>No</span>
</label>
</li>
</ul>
</div>
<div class="guess">[
KhanUtil.graphs.main.graph.pointA.coord,
KhanUtil.graphs.main.graph.pointB.coord,
$("input[name='linear']:checked").attr("id")
]</div>
<div class="validator-function">
if (_.isEqual(guess, [[-5, 5], [5, 5], "exists"])) {
return "";
}
// Check that the right checkbox is checked
if (guess[2] !== "exists") {
return false;
}
var slope = (guess[1][1] - guess[0][1]) /
(guess[1][0] - guess[0][0]);
var intercept = slope * -guess[0][0] + guess[0][1];
// Validate the line
return VALIDATOR(slope, intercept);
</div>
<div class="show-guess">
KhanUtil.graphs.main.graph.pointA.setCoord(guess[0]);
KhanUtil.graphs.main.graph.pointB.setCoord(guess[1]);
KhanUtil.graphs.main.graph.bestFitLine.transform(true);
</div>
<div class="show-guess-solutionarea">
$("#"+guess[2]).attr('checked', 'checked');
</div>
</div>
<div class="hints">
<p>A line of fit is a line that approximates the data points.</p>
<div>
<p>There are three main criteria to use when finding a line of fit.</p>
<p>First, make sure that your line passes through the points,
and does not lie completely above or below the points.</p>
<div class="clearfix">
<div class="graphie float">
init({ range: [[-5, 5], [-5, 5]], scale: [20, 20] });
circle([-2, -3], 0.2, { fill: "black" });
circle([ 0, -1], 0.2, { fill: "black" });
circle([-1, 1], 0.2, { fill: "black" });
circle([ 1, 1], 0.2, { fill: "black" });
circle([ 0, 2], 0.2, { fill: "black" });
line([-5, -4], [5, -3], { stroke: BLUE });
label([0, 4], i18n._("Bad"), "center", false)
.css("color", "red")
.css("font-size", "20px");
</div>
<div class="graphie float">
init({ range: [[-5, 5], [-5, 5]], scale: [20, 20] });
circle([-2, -3], 0.2, { fill: "black" });
circle([ 0, -1], 0.2, { fill: "black" });
circle([-1, 1], 0.2, { fill: "black" });
circle([ 1, 1], 0.2, { fill: "black" });
circle([ 0, 2], 0.2, { fill: "black" });
line([-5, -2.5], [5, -1.5], { stroke: BLUE });
label([0, 4], i18n._("Okay"), "center", false)
.css("color", "orange")
.css("font-size", "20px");
</div>
</div>
</div>
<div>
<p>Next, make sure that your line alternates between
passing above and then below points, and doesn't simply
go above some points and then below the rest.</p>
<div class="clearfix">
<div class="graphie float">
init({ range: [[-5, 5], [-5, 5]], scale: [20, 20] });
circle([-2, -3], 0.2, { fill: "black" });
circle([ 0, -1], 0.2, { fill: "black" });
circle([-1, 1], 0.2, { fill: "black" });
circle([ 1, 1], 0.2, { fill: "black" });
circle([ 0, 2], 0.2, { fill: "black" });
line([-5, -2.5], [5, -1.5], { stroke: BLUE });
label([0, 4], i18n._("Okay"), "center", false)
.css("color", "orange")
.css("font-size", "20px");
</div>
<div class="graphie float">
init({ range: [[-5, 5], [-5, 5]], scale: [20, 20] });
circle([-2, -3], 0.2, { fill: "black" });
circle([ 0, -1], 0.2, { fill: "black" });
circle([-1, 1], 0.2, { fill: "black" });
circle([ 1, 1], 0.2, { fill: "black" });
circle([ 0, 2], 0.2, { fill: "black" });
line([-5, -3.5], [3, 5], { stroke: BLUE });
label([0, 4], i18n._("Better"), "center", false)
.css("color", "#8EEB00")
.css("font-size", "20px");
</div>
</div>
</div>
<div>
<p>Finally, make sure that the line goes through the middle
of all the points, so that it is close to all of the points.</p>
<div class="clearfix">
<div class="graphie float">
init({ range: [[-5, 5], [-5, 5]], scale: [20, 20] });
circle([-2, -3], 0.2, { fill: "black" });
circle([ 0, -1], 0.2, { fill: "black" });
circle([-1, 1], 0.2, { fill: "black" });
circle([ 1, 1], 0.2, { fill: "black" });
circle([ 0, 2], 0.2, { fill: "black" });
line([-5, -3.5], [3, 5], { stroke: BLUE });
label([0, 4], i18n._("Better"), "center", false)
.css("color", "#9FEE00")
.css("font-size", "20px");
</div>
<div class="graphie float">
init({ range: [[-5, 5], [-5, 5]], scale: [20, 20] });
circle([-2, -3], 0.2, { fill: "black" });
circle([ 0, -1], 0.2, { fill: "black" });
circle([-1, 1], 0.2, { fill: "black" });
circle([ 1, 1], 0.2, { fill: "black" });
circle([ 0, 2], 0.2, { fill: "black" });
line([-4, -5], [3, 5], { stroke: BLUE });
label([0, 4], i18n._("Good"), "center", false)
.css("color", "#00C322")
.css("font-size", "20px");
</div>
</div>
</div>
<p>
There are several lines that satisfy this.
<a class="simple-button" href="javascript:void(0)"
onclick="javascript:KhanUtil.graphs.main.graph.showSolution()">
Click here
</a>
to show one of them.
</p>
</div>
</div>
<div id="nonlinear" data-type="linear" data-weight="1">
<div class="vars" data-ensure="abs(R2) < 0.36">
<var id="SLOPE">0</var>
<var id="INTERCEPT">0</var>
<var id="OFFSET">11</var>
</div>
<div class="solution" data-type="custom">
<div class="instruction">
<ul>
<li>
<label>
<input id="exists" checked name="nonlinear" onclick="KhanUtil.graphs.main.graph.showLine()" type="radio">
<span>Yes</span>
</label>
</li>
<li>
<label>
<input id="notexists" name="nonlinear" onclick="KhanUtil.graphs.main.graph.hideLine()" type="radio">
<span>No</span>
</label>
</li>
</ul>
</div>
<div class="guess">[
KhanUtil.graphs.main.graph.pointA.coord,
KhanUtil.graphs.main.graph.pointB.coord,
$("input[name='nonlinear']:checked").attr("id")
]</div>
<div class="validator-function">
if (_.isEqual(guess, [[-5, 5], [5, 5], "exists"])) {
return "";
}
return guess[2] === "notexists";
</div>
<div class="show-guess">
KhanUtil.graphs.main.graph.pointA.setCoord(guess[0]);
KhanUtil.graphs.main.graph.pointB.setCoord(guess[1]);
KhanUtil.graphs.main.graph.bestFitLine.transform(true);
</div>
<div class="show-guess-solutionarea">
$("#"+guess[2]).attr('checked', 'checked');
</div>
</div>
<div class="hints">
<p>Does the data look like it follows a linear relationship?</p>
<p>The data is mostly random, so there is no line that fits the data well.</p>
</div>
</div>
<div id="two-slopes" data-type="linear" data-weight="1">
<div class="vars" data-ensure="abs(R2) < 0.36">
<var id="BREAK">randRange(-3, 3)</var>
<var id="SLOPE1">randRange(-25, 25) / 10</var>
<var id="SLOPE2" data-ensure="abs(SLOPE1 - SLOPE2) > 1.5">randRange(-30, 30) / 10</var>
<var id="GETPOINTS">function(minX, maxX, slope) {
var arr = [];
var midX = (minX + maxX) / 2;
var midY = randRange(2 * abs(slope) - 8, 8 - 2 * abs(slope))
var xs = [];
while (minX &lt; maxX) {
xs.push(minX++);
}
xs = shuffle(xs);
var n = randRange(4, 5);
for (var i = 0; i &lt; xs.length; i++) {
var y = (xs[i] - midX) * slope + midY + 2 * random() - 1;
if (abs(y) &lt; 10) {
arr.push([xs[i], round(y)]);
if (arr.length === n) {
break;
}
}
}
return arr;
}</var>
<var id="POINTS1">GETPOINTS(-10, BREAK - 1, SLOPE1)</var>
<var id="POINTS2">GETPOINTS(BREAK + 1, 10, SLOPE2)</var>
<var id="POINTS">[].concat(POINTS1).concat(POINTS2)</var>
</div>
<div class="solution" data-type="custom">
<div class="instruction">
<ul>
<li>
<label>
<input id="exists" checked name="nonlinear" onclick="KhanUtil.graphs.main.graph.showLine()" type="radio">
<span>Yes</span>
</label>
</li>
<li>
<label>
<input id="notexists" name="nonlinear" onclick="KhanUtil.graphs.main.graph.hideLine()" type="radio">
<span>No</span>
</label>
</li>
</ul>
</div>
<div class="guess">[
KhanUtil.graphs.main.graph.pointA.coord, KhanUtil.graphs.main.graph.pointB.coord,
$("input[name='nonlinear']:checked").attr("id")
]</div>
<div class="validator-function">
if (_.isEqual(guess, [[-5, 5], [5, 5], "exists"])) {
return "";
}
return guess[2] === "notexists";
</div>
<div class="show-guess">
KhanUtil.graphs.main.graph.pointA.setCoord(guess[0]);
KhanUtil.graphs.main.graph.pointB.setCoord(guess[1]);
KhanUtil.graphs.main.graph.bestFitLine.transform(true);
</div>
<div class="show-guess-solutionarea">
$("#"+guess[2]).attr('checked', 'checked');
</div>
</div>
<div class="hints">
<p>Does the data look like it follows a linear relationship?</p>
<p>The data forms two separate linear trends, so there is no single line that fits the data well.</p>
</div>
</div>
<div id="parabola" data-type="linear" data-weight="1">
<div class="vars" data-ensure="abs(R2) < 0.36">
<var id="GETPOINTS">function() {
var xs = shuffle([-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
var n = randRange(9, 12);
var a = randRange(4, 10);
var b = randRange(-10, 10) / 10;
var c = randRange(-8, 8);
if (c &gt; 2) {
a *= -1;
} else if (c &gt; -2) {
a *= randFromArray([-1, 1]);
}
var arr = [];
for (var i = 0; i &lt; xs.length; i++) {
var x = xs[i];
var y = round(x * x / a + b * x + c + 2 * random() - 1);
if (abs(y) &lt; 10) {
arr.push([x, y]);
if (arr.length === n) {
break;
}
}
}
return arr;
}</var>
<var id="POINTS1">GETPOINTS()</var>
<var id="POINTS">[].concat(POINTS1)</var>
</div>
<div class="solution" data-type="custom">
<div class="instruction">
<ul>
<li>
<label>
<input id="exists" checked name="nonlinear" onclick="KhanUtil.graphs.main.graph.showLine()" type="radio">
<span>Yes</span>
</label>
</li>
<li>
<label>
<input id="notexists" name="nonlinear" onclick="KhanUtil.graphs.main.graph.hideLine()" type="radio">
<span>No</span>
</label>
</li>
</ul>
</div>
<div class="guess">[
KhanUtil.graphs.main.graph.pointA.coord,
KhanUtil.graphs.main.graph.pointB.coord,
$("input[name='nonlinear']:checked").attr("id")
]</div>
<div class="validator-function">
if (_.isEqual(guess, [[-5, 5], [5, 5], "exists"])) {
return "";
}
return guess[2] === "notexists";
</div>
<div class="show-guess">
KhanUtil.graphs.main.graph.pointA.setCoord(guess[0]);
KhanUtil.graphs.main.graph.pointB.setCoord(guess[1]);
KhanUtil.graphs.main.graph.bestFitLine.transform(true);
</div>
<div class="show-guess-solutionarea">
$("#"+guess[2]).attr('checked', 'checked');
</div>
</div>
<div class="hints">
<p>Does the data look like it follows a linear relationship?</p>
<p>The data does not form a linear trend, so there is no single line that fits the data well.</p>
</div>
</div>
</div>
</div>
</body>
</html>
Jump to Line
Something went wrong with that request. Please try again.