Turn Data Points into a Function with Matrix Operation in JS

Riky Perdana
6 min readApr 15, 2024
Photo by Max Harlynking on Unsplash

What can the primitives do with a stick? Use it for walking through the snow, nod a fruit until it fell off the tree, make hole on the ground for sanitary, herd the sheep, and even fight each other to defend their domain. A stick itself doesn’t care whatever the heck it would be used for, but human ingenuity always find ways to make use of this long and strong thing called stick to make their life better. Modern human have made a long way from utilizing sticks for a better life, to harnessing the power of matrix for vast area of purposes. Computer graphics, telecommunication, cryptography, graph theory, probability, statistics, and to the latest artificial intelligence stems from our perspective of what a matrix could be manifested in.

I strongly suggest you to understand what a matrix is and explore all the potential the matrices could deliver. For disclaimer, I’m no mathematician nor a legit computer scientist, and my social study and lecturing job barely has anything to do with matrices. But my admiration for sophisticatingly advanced maths and sheer f* curiousity brought me to places I’d never thought of before. This article is no way close to a rigour matrix textbook, a wikipedia page, or any other math articles related to matrices. But I do hope that this article could make a small contribution in how operation of matrices can help us deal with complex math problems as we never thought before.

PolyFit from points to function

MatLab PolyFit

I assume my readers were quite acquainted with concept like PolyFit that usually came built-in on MatLab software. A mathematical technique of deriving a continuous equation from a set of data points on a graph. The main idea is to find the best equation that mostly or even perfectly match the given data points. In statistics we have something like quadratic regression where you can give a set of data points and yield a quadratic function that has the least standardized error. But this time we want to give the matrix a chance of dealing with this kind of problem.

Matrix Linear Equation Solver

My previous post titled Matrix Operations in Functional JS has shown how matrix inversion could be used for solving linear equation problems by translating them into an n-squared matrix and feed it into our home made matrixLinSolve function.

// just a kind reminder
matrixLinSolve(
[
[1, 0, 4,-6],
[2, 5, 0, 3],
[-1,2, 3, 5],
[2, 1,-2, 3]
], [-12, 34, 41, 14]
) // correctly get [2, 3, 4, 5]

Experiment with 3 terms

I must say my thanks to sciencing.com for its article titled How to Find Quadratic Equations From a Table that inspired me to write this post. Let’s imagine that we have a set of data points like this.

Their pattern is somewhat linear in my eyes, though not exactly linear. So let’s assume that a quadratic equation exists which shall perfectly fit all the three data points on the graph.

aX^2 + bX + c = Y

a(1)^2 + b(1) + c = 5
a(2)^2 + b(2) + c = 11
a(3)^2 + b(3) + c = 19

Now suddenly the data points are well translated into a matrix problem. According to the origin site, the value of a, b, and c are supposed to be (1, 3, 1) through elimination method. You’re free to solve the problem by hands — if you so choose — but can we find the secret combination of a, b, and c with the help of matrix operation? Sure can!

matrixLinSolve(
[
[1, 1, 1],
[4, 2, 1],
[9, 3, 1]
],
[5, 11, 19]
) // correctly gets [1, 3, 1]

matrixLinSolve function yields the exact same values as what was presented on the origin site. This array is supposed to be read as x²+3x+1, which if we draw on the graph overlaying the data points, it should show you this:

Data points overlaid with a fitting function

I must admit that I was once shocked by how precise the matrixLinSolve yield the exact combination of values, it almost felt like a revelation at that moment and urge me to write this post (I believe that an avid mathematicians would think of it as just another Thursday). But it only proved that the concept does work, and invites us to go beyond what’s done.

Experiment with more terms

Now let’s step up the game by spreading four data points instead of three:

matrixLinSolve(
[
[Math.pow(2, 3), Math.pow(2, 2), 2, 1],
[Math.pow(7, 3), Math.pow(7, 2), 7, 1],
[Math.pow(10, 3), Math.pow(10, 2), 10, 1],
[Math.pow(11, 3), Math.pow(11, 2), 11, 1],
],
[1.5, 9, 8, 3]
) // gets [-0.10416, 1.75, -7.27083, 9.87500]
It fits again?!

Let’s create a new function

Let’s seal the deal by creating a new function which when given an array of x points and y points, it shall yield an array that represent the coeffiecients of a quadratic equation which perfectly fits the given data.

matrixPolySolve = (xs, ys) =>
matrixLinSolve(xs.map(i => makeArray(
ys.length, j => Math.pow(i, j)
).reverse()), ys)

matrixPolySolve(
[2, 7, 10, 11],
[1.5, 9, 8, 3]
) // gets [-0.10416, 1.75, -7.27083, 9.87500]
// exactly equal to the result above

More experiments

Let’s go wild a bit by spreading numerous random data points on a graph and check if the function breaks:

matrixPolySolve(
[1, 2.5, 4.1, 8, 30],
[9, 17, 13.2, 25,1.5]
) /* gets [
-0.018515640519887, // x^4
0.7843431166416297, // x^3
-7.690091972592737, // x^2
25.071144228344153, // x^1
-9.146879731873165, // x^0
]*/
It hits all the spots perfectly, damn

Hold your horses

Even though the matrixPolySolve manages to touch all the given data points on the graph, doesn’t mean we can lavishly pour all the data points into the function, for it may break your PC due to overload of computations or being prone to a disease called overfitting. Let’s have a spread of normally distributed data on a graph:

Seems like a set of data that slowly descend then sharply ascend

I actually tried feeding the matrixPolySolve with all those 22 data points to witness the perfectly fitting function, but unfortunately the computational load of O(n!) time complexity inherent in Laplace Extension is no joke and beyond my Celeron laptop paygrade. So I have to be contend with presenting only the simpler version.

matrixPolySolve(
[3, 13, 17],
[10, 6, 9]
) // get [0.0821428571428571, -1.714285714285714, 14.403571428571428]
Three term quadratic function is already fit enough

It perfectly fits the three data points intentionally chosen to represent the whole data set. Well, I guess that’s all I can offer through this article, may it be useful to you. Thanks.

Codes Recap

// you can refer all the previous codes in the previous article

matrixPolySolve = (xs, ys) =>
matrixLinSolve(xs.map(i => makeArray(
ys.length, j => Math.pow(i, j)
).reverse()), ys)

All the codes are also available at my Github Gist

--

--