|
|
{% extends "layout.html" %}
|
|
|
|
|
|
{% block content %}
|
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
<div class="container mx-auto py-10 px-4 max-w-5xl">
|
|
|
<h1 class="text-3xl font-bold text-center text-gray-800 mb-6"> Polynomial Regression Visualizer</h1>
|
|
|
|
|
|
<div class="bg-blue-50 border border-blue-200 rounded-xl p-6 shadow-sm mb-10">
|
|
|
<h2 class="text-2xl font-semibold text-blue-800 mb-4"> What is Polynomial Regression?</h2>
|
|
|
<p class="text-gray-700 mb-4">
|
|
|
Polynomial Regression is a type of regression analysis that models the relationship between the independent variable x
|
|
|
and the dependent variable y as an n-th degree polynomial. It extends simple linear regression by considering higher-degree terms.
|
|
|
</p>
|
|
|
|
|
|
<h3 class="text-xl font-semibold text-blue-700 mt-6 mb-2"> Why Polynomial Regression?</h3>
|
|
|
<ul class="list-disc ml-6 text-gray-700 mb-4">
|
|
|
<li>Linear Regression only fits straight lines. But real-world data is often curved or non-linear.</li>
|
|
|
<li>Polynomial Regression allows us to capture these curves while still being simple and interpretable.</li>
|
|
|
</ul>
|
|
|
|
|
|
<h3 class="text-xl font-semibold text-blue-700 mt-6 mb-2"> General Equation:</h3>
|
|
|
<p class="text-gray-700 italic mb-2">
|
|
|
<strong>y = θ<sub>0</sub> + θ<sub>1</sub>x + θ<sub>2</sub>x<sup>2</sup> + θ<sub>3</sub>x<sup>3</sup> + ... + θ<sub>n</sub>x<sup>n</sup></strong>
|
|
|
</p>
|
|
|
|
|
|
<p class="text-sm text-gray-600 mb-4">
|
|
|
Here, theta_0, theta_1, ..., theta_n are the coefficients learned by the model.
|
|
|
</p>
|
|
|
|
|
|
<div class="bg-blue-100 border border-blue-300 rounded-lg p-4 mb-6">
|
|
|
<h3 class="text-xl font-semibold text-blue-700 mb-3">Theoretical Examples: y = x^2 + 2x</h3>
|
|
|
<p class="text-gray-700 mb-3">
|
|
|
To illustrate how the equation y = x^2 + 2x creates a curve, let's substitute a few values for x:
|
|
|
</p>
|
|
|
<ul class="list-disc ml-6 text-gray-700">
|
|
|
<li class="mb-2">
|
|
|
<strong>When x = 0:</strong>
|
|
|
y = (0)^2 + 2(0) = 0 + 0 = **0**
|
|
|
<p class="text-sm text-gray-600 mt-1">
|
|
|
An input of 0 results in an output of 0.
|
|
|
</p>
|
|
|
</li>
|
|
|
<li class="mb-2">
|
|
|
<strong>When x = 1:</strong>
|
|
|
y = (1)^2 + 2(1) = 1 + 2 = **3**
|
|
|
<p class="text-sm text-gray-600 mt-1">
|
|
|
An input of 1 yields an output of 3.
|
|
|
</p>
|
|
|
</li>
|
|
|
<li class="mb-2">
|
|
|
<strong>When x = 3:</strong>
|
|
|
y = (3)^2 + 2(3) = 9 + 6 = **15**
|
|
|
<p class="text-sm text-gray-600 mt-1">
|
|
|
With an input of 3, the output becomes 15.
|
|
|
</p>
|
|
|
</li>
|
|
|
<li>
|
|
|
<strong>When x = 5:</strong>
|
|
|
y = (5)^2 + 2(5) = 25 + 10 = **35**
|
|
|
<p class="text-sm text-gray-600 mt-1">
|
|
|
For an input of 5, the predicted output is 35.
|
|
|
</p>
|
|
|
</li>
|
|
|
</ul>
|
|
|
<p class="text-gray-700 mt-3">
|
|
|
These examples show how the relationship between x and y is curved, not linear.
|
|
|
</p>
|
|
|
</div>
|
|
|
|
|
|
<h3 class="text-xl font-semibold text-blue-700 mt-6 mb-2"> How this App Works:</h3>
|
|
|
<ul class="list-disc ml-6 text-gray-700">
|
|
|
<li>The model is trained using Python's <code>PolynomialFeatures</code> from <code>sklearn.preprocessing</code>.</li>
|
|
|
<li>You enter a value (e.g., hours studied), and the trained model predicts the output (e.g., expected score).</li>
|
|
|
<li>A dynamic graph shows the curve and predicted point in real-time.</li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
|
|
|
<div class="bg-white shadow-md rounded-xl p-6 mb-8">
|
|
|
<h2 class="text-xl font-semibold text-gray-800 mb-4"> Polynomial Curve Visualization</h2>
|
|
|
<div class="flex flex-col md:flex-row gap-4">
|
|
|
<div class="flex-grow">
|
|
|
<canvas id="polyCanvas" class="w-full h-80 border border-gray-300 rounded-md bg-white"></canvas>
|
|
|
</div>
|
|
|
<div class="w-full md:w-1/3 p-4 bg-gray-50 rounded-lg">
|
|
|
<h3 class="text-lg font-semibold text-gray-700 mb-2">Model Details:</h3>
|
|
|
<p class="text-gray-600 mb-2">Equation: <span class="font-mono"><strong>y = x^2 + 2x</strong></span></p>
|
|
|
<p class="text-gray-600 mb-2">Our model learned coefficients for:</p>
|
|
|
<ul class="list-disc list-inside text-gray-600 pl-4">
|
|
|
<li>x^0 (Intercept): <span id="coeff0">0.00</span></li>
|
|
|
<li>x^1: <span id="coeff1">2.00</span></li>
|
|
|
<li>x^2: <span id="coeff2">1.00</span></li>
|
|
|
</ul>
|
|
|
<p class="text-gray-600 mt-4 text-sm">
|
|
|
(These coefficients correspond to the formula <strong>y = θ₀ + θ₁x + θ₂x²</strong>)
|
|
|
</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="bg-white shadow-md rounded-xl p-6">
|
|
|
<h2 class="text-xl font-semibold text-gray-800 mb-4"> Make a Prediction</h2>
|
|
|
<form method="POST" class="flex flex-col sm:flex-row items-center gap-4">
|
|
|
<label for="hoursInput" class="text-gray-700 font-medium">Enter Input (x):</label>
|
|
|
<input type="number" step="0.1" min="0" name="hours" id="hoursInput"
|
|
|
value="{{ input_hours if input_hours is not none else '3.0' }}"
|
|
|
class="border border-gray-300 rounded-md p-2 w-40" required>
|
|
|
<button type="submit" class="bg-blue-600 text-white px-6 py-2 rounded-md shadow hover:bg-blue-700">
|
|
|
Predict
|
|
|
</button>
|
|
|
</form>
|
|
|
|
|
|
{% if prediction is not none %}
|
|
|
<div class="mt-6 bg-green-50 border border-green-200 rounded-lg p-4">
|
|
|
<h3 class="text-lg font-semibold text-green-800 mb-2"> Predicted Value (y):</h3>
|
|
|
<p class="text-3xl font-bold text-green-900" id="predictedScore">{{ prediction }}</p>
|
|
|
</div>
|
|
|
{% endif %}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
const canvas = document.getElementById("polyCanvas");
|
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
|
|
|
|
|
const X_data = [1, 2, 3, 4, 5];
|
|
|
const y_data = [3, 8, 15, 24, 35];
|
|
|
|
|
|
|
|
|
|
|
|
const polyCoeffs = [0, 2, 1];
|
|
|
|
|
|
|
|
|
const initialHours = parseFloat("{{ input_hours if input_hours is not none else 'null' }}");
|
|
|
const initialPrediction = parseFloat("{{ prediction if prediction is not none else 'null' }}");
|
|
|
|
|
|
let predictedHours = initialHours !== null && !isNaN(initialHours) ? initialHours : null;
|
|
|
let predictedScore = initialPrediction !== null && !isNaN(initialPrediction) ? initialPrediction : null;
|
|
|
|
|
|
|
|
|
document.getElementById('coeff0').textContent = polyCoeffs[0].toFixed(2);
|
|
|
document.getElementById('coeff1').textContent = polyCoeffs[1].toFixed(2);
|
|
|
document.getElementById('coeff2').textContent = polyCoeffs[2].toFixed(2);
|
|
|
|
|
|
|
|
|
let xScale, yScale;
|
|
|
let xMin, xMax, yMin, yMax;
|
|
|
const padding = 50;
|
|
|
|
|
|
function toCanvasX(x) {
|
|
|
return padding + (x - xMin) * xScale;
|
|
|
}
|
|
|
|
|
|
function toCanvasY(y) {
|
|
|
return canvas.height - padding - (y - yMin) * yScale;
|
|
|
}
|
|
|
|
|
|
function setupScaling() {
|
|
|
const dpi = window.devicePixelRatio;
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
|
canvas.width = rect.width * dpi;
|
|
|
canvas.height = rect.height * dpi;
|
|
|
ctx.scale(dpi, dpi);
|
|
|
|
|
|
|
|
|
xMin = Math.min(...X_data, 0);
|
|
|
xMax = Math.max(...X_data, predictedHours !== null ? predictedHours : 0, 6) + 1;
|
|
|
|
|
|
yMin = Math.min(...y_data, 0);
|
|
|
|
|
|
|
|
|
const maxCurveY = polyCoeffs[0] + polyCoeffs[1] * xMax + polyCoeffs[2] * xMax * xMax;
|
|
|
yMax = Math.max(...y_data, predictedScore !== null ? predictedScore : 0, maxCurveY) + 10;
|
|
|
|
|
|
|
|
|
xScale = (canvas.width - 2 * padding) / (xMax - xMin);
|
|
|
yScale = (canvas.height - 2 * padding) / (yMax - yMin);
|
|
|
}
|
|
|
|
|
|
function drawGraph() {
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
ctx.strokeStyle = '#64748b';
|
|
|
ctx.lineWidth = 2;
|
|
|
ctx.moveTo(toCanvasX(xMin), toCanvasY(yMin));
|
|
|
ctx.lineTo(toCanvasX(xMax), toCanvasY(yMin));
|
|
|
ctx.moveTo(toCanvasX(xMin), toCanvasY(yMin));
|
|
|
ctx.lineTo(toCanvasX(xMin), toCanvasY(yMax));
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
|
|
ctx.fillStyle = '#475569';
|
|
|
ctx.font = '14px Inter';
|
|
|
ctx.textAlign = 'center';
|
|
|
ctx.textBaseline = 'top';
|
|
|
|
|
|
|
|
|
const xTickStep = 1;
|
|
|
for (let i = Math.ceil(xMin / xTickStep) * xTickStep; i <= Math.floor(xMax); i += xTickStep) {
|
|
|
if (i >= 0) {
|
|
|
ctx.fillText(i.toFixed(0), toCanvasX(i), canvas.height - padding + 10);
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(toCanvasX(i), canvas.height - padding);
|
|
|
ctx.lineTo(toCanvasX(i), canvas.height - padding - 5);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
}
|
|
|
ctx.fillText('Input (x)', canvas.width / 2, canvas.height - 20);
|
|
|
|
|
|
ctx.textAlign = 'right';
|
|
|
ctx.textBaseline = 'middle';
|
|
|
|
|
|
const yTickStep = Math.max(1, Math.floor((yMax - yMin) / 50) * 10 || 5);
|
|
|
for (let i = Math.ceil(yMin / yTickStep) * yTickStep; i <= Math.floor(yMax); i += yTickStep) {
|
|
|
if (i >= 0) {
|
|
|
ctx.fillText(i.toFixed(0), padding - 10, toCanvasY(i));
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(padding, toCanvasY(i));
|
|
|
ctx.lineTo(padding + 5, toCanvasY(i));
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
ctx.save();
|
|
|
ctx.translate(20, canvas.height / 2);
|
|
|
ctx.rotate(-Math.PI / 2);
|
|
|
ctx.textAlign = 'center';
|
|
|
ctx.fillText('Output (y)', 0, 0);
|
|
|
ctx.restore();
|
|
|
|
|
|
|
|
|
ctx.fillStyle = '#3b82f6';
|
|
|
X_data.forEach((x, i) => {
|
|
|
ctx.beginPath();
|
|
|
ctx.arc(toCanvasX(x), toCanvasY(y_data[i]), 5, 0, Math.PI * 2);
|
|
|
ctx.fill();
|
|
|
});
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
ctx.strokeStyle = '#ef4444';
|
|
|
ctx.lineWidth = 3;
|
|
|
|
|
|
for (let x = xMin; x <= xMax; x += 0.1) {
|
|
|
const y_curve = polyCoeffs[0] + polyCoeffs[1] * x + polyCoeffs[2] * x * x;
|
|
|
if (x === xMin) {
|
|
|
ctx.moveTo(toCanvasX(x), toCanvasY(y_curve));
|
|
|
} else {
|
|
|
ctx.lineTo(toCanvasX(x), toCanvasY(y_curve));
|
|
|
}
|
|
|
}
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
|
|
if (predictedHours !== null && predictedScore !== null) {
|
|
|
const predX_canvas = toCanvasX(predictedHours);
|
|
|
const predY_canvas = toCanvasY(predictedScore);
|
|
|
|
|
|
|
|
|
ctx.fillStyle = '#22c55e';
|
|
|
ctx.beginPath();
|
|
|
ctx.arc(predX_canvas, predY_canvas, 6, 0, Math.PI * 2);
|
|
|
ctx.fill();
|
|
|
|
|
|
|
|
|
ctx.strokeStyle = '#22c55e';
|
|
|
ctx.lineWidth = 1.5;
|
|
|
ctx.setLineDash([5, 5]);
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(predX_canvas, predY_canvas);
|
|
|
ctx.lineTo(predX_canvas, toCanvasY(yMin));
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(predX_canvas, predY_canvas);
|
|
|
ctx.lineTo(toCanvasX(xMin), predY_canvas);
|
|
|
ctx.stroke();
|
|
|
|
|
|
ctx.setLineDash([]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
window.addEventListener('load', () => {
|
|
|
setupScaling();
|
|
|
drawGraph();
|
|
|
});
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
setupScaling();
|
|
|
drawGraph();
|
|
|
});
|
|
|
|
|
|
|
|
|
canvas.addEventListener('click', (event) => {
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
|
const mouseX = (event.clientX - rect.left) / (canvas.width / rect.width);
|
|
|
const mouseY = (event.clientY - rect.top) / (canvas.height / rect.height);
|
|
|
|
|
|
const clickedX = xMin + (mouseX - padding) / xScale;
|
|
|
|
|
|
|
|
|
document.getElementById('hoursInput').value = clickedX.toFixed(1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
{% endblock %} |