Skip to content

Commit 5ff2ebb

Browse files
committed
Polynomial Regression Predictor
1 parent 44037e4 commit 5ff2ebb

File tree

5 files changed

+244
-7
lines changed

5 files changed

+244
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

77
## Unreleased
8+
### Added
9+
- Polynomial Regression predictor
10+
811
### Modified
912
- code refactoring
1013

@@ -41,4 +44,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
4144

4245
## [0.1.0] - 2018-11-15
4346
### Added
44-
- K-Nearest Neighbours algorithm
47+
- K-Nearest Neighbours algorithm
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
defmodule LearnKit.Regression.Polynomial do
2+
@moduledoc """
3+
Module for Polynomial Regression algorithm
4+
"""
5+
6+
defstruct factors: [], results: [], coefficients: [], degree: 2
7+
8+
alias LearnKit.Regression.Polynomial
9+
10+
@type factors :: [number]
11+
@type results :: [number]
12+
@type coefficients :: [number]
13+
@type degree :: number
14+
15+
@doc """
16+
Creates polynomial predictor with data_set
17+
18+
## Parameters
19+
20+
- factors: Array of predictor variables
21+
- results: Array of criterion variables
22+
23+
## Examples
24+
25+
iex> predictor = LearnKit.Regression.Polynomial.new([1, 2, 3, 4], [3, 6, 10, 15])
26+
%LearnKit.Regression.Polynomial{factors: [1, 2, 3, 4], results: [3, 6, 10, 15], coefficients: [], degree: 2}
27+
28+
"""
29+
def new(factors, results) when is_list(factors) and is_list(results) do
30+
%Polynomial{factors: factors, results: results}
31+
end
32+
33+
def new(_, _), do: Polynomial.new([], [])
34+
def new, do: Polynomial.new([], [])
35+
36+
@doc """
37+
Fit train data
38+
39+
## Parameters
40+
41+
- predictor: %LearnKit.Regression.Polynomial{}
42+
- options: keyword list with options
43+
44+
## Options
45+
46+
- degree: nth degree of polynomial model, default set to 2
47+
48+
## Examples
49+
50+
iex> predictor = predictor |> LearnKit.Regression.Polynomial.fit
51+
%LearnKit.Regression.Polynomial{
52+
coefficients: [0.9999999999998295, 1.5000000000000853, 0.4999999999999787],
53+
degree: 2,
54+
factors: [1, 2, 3, 4],
55+
results: [3, 6, 10, 15]
56+
}
57+
58+
iex> predictor = predictor |> LearnKit.Regression.Polynomial.fit([degree: 3])
59+
%LearnKit.Regression.Polynomial{
60+
coefficients: [1.0000000000081855, 1.5000000000013642, 0.5,
61+
8.526512829121202e-14],
62+
degree: 3,
63+
factors: [1, 2, 3, 4],
64+
results: [3, 6, 10, 15]
65+
}
66+
67+
"""
68+
def fit(%Polynomial{factors: factors, results: results}, options \\ []) do
69+
degree = options[:degree] || 2
70+
matrix = matrix(factors, degree)
71+
xys = x_y_matrix(factors, results, degree + 1, [])
72+
coefficients = matrix |> Matrix.inv() |> Matrix.mult(xys) |> List.flatten()
73+
%Polynomial{factors: factors, results: results, coefficients: coefficients, degree: degree}
74+
end
75+
76+
@doc """
77+
Predict using the polynomial model
78+
79+
## Parameters
80+
81+
- predictor: %LearnKit.Regression.Polynomial{}
82+
- samples: Array of variables
83+
84+
## Examples
85+
86+
iex> predictor |> LearnKit.Regression.Polynomial.predict([5,6])
87+
{:ok, [20.999999999999723, 27.999999999999574]}
88+
89+
"""
90+
def predict(polynomial = %Polynomial{coefficients: _, degree: _}, samples)
91+
when is_list(samples) do
92+
{:ok,
93+
Enum.map(samples, fn sample ->
94+
{:ok, prediction} = predict(polynomial, sample)
95+
prediction
96+
end)}
97+
end
98+
99+
@doc """
100+
Predict using the polynomial model
101+
102+
## Parameters
103+
104+
- predictor: %LearnKit.Regression.Polynomial{}
105+
- sample: Sample variable
106+
107+
## Examples
108+
109+
iex> predictor |> LearnKit.Regression.Polynomial.predict(5)
110+
{:ok, 20.999999999999723}
111+
112+
"""
113+
def predict(%Polynomial{coefficients: coefficients, degree: degree}, sample) do
114+
ordered_coefficients = coefficients |> Enum.reverse()
115+
{:ok, substitute_coefficients(ordered_coefficients, sample, degree, 0.0)}
116+
end
117+
118+
defp matrix_line(1, factors, degree) do
119+
power_ofs = Enum.to_list(1..degree)
120+
121+
[Enum.count(factors)] ++
122+
Enum.map(power_ofs, fn factor ->
123+
sum_x_with_k(factors, factor, 0.0)
124+
end)
125+
end
126+
127+
defp matrix_line(line, factors, degree) do
128+
line_factor = line - 1
129+
power_ofs = Enum.to_list(line_factor..(degree + line_factor))
130+
131+
Enum.map(power_ofs, fn factor ->
132+
sum_x_with_k(factors, factor, 0.0)
133+
end)
134+
end
135+
136+
defp matrix(factors, degree) do
137+
lines = Enum.to_list(1..(degree + 1))
138+
139+
Enum.map(lines, fn line ->
140+
matrix_line(line, factors, degree)
141+
end)
142+
end
143+
144+
defp substitute_coefficients([], _, _, sum), do: sum
145+
146+
defp substitute_coefficients([coefficient | tail], x, k, sum) do
147+
sum = sum + :math.pow(x, k) * coefficient
148+
substitute_coefficients(tail, x, k - 1, sum)
149+
end
150+
151+
defp sum_x_with_k([x | tail], k, sum) do
152+
sum = sum + :math.pow(x, k)
153+
sum_x_with_k(tail, k, sum)
154+
end
155+
156+
defp sum_x_with_k([], _, sum), do: sum
157+
158+
defp sum_x_y_with_k([], [], _degree, sum), do: [sum]
159+
160+
defp sum_x_y_with_k([x | xtail], [y | ytail], degree, sum) do
161+
exponent = degree - 1
162+
sum = sum + :math.pow(x, exponent) * y
163+
sum_x_y_with_k(xtail, ytail, degree, sum)
164+
end
165+
166+
def x_y_matrix(_, _, 0, matrix), do: matrix |> Enum.reverse()
167+
168+
def x_y_matrix(xs, ys, degree, matrix) do
169+
matrix = matrix ++ [sum_x_y_with_k(xs, ys, degree, 0.0)]
170+
x_y_matrix(xs, ys, degree - 1, matrix)
171+
end
172+
end

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ defmodule LearnKit.MixProject do
2727

2828
defp deps do
2929
[
30-
{:ex_doc, "~> 0.19", only: :dev}
30+
{:ex_doc, "~> 0.19", only: :dev},
31+
{:matrix, "~> 0.3.2"}
3132
]
3233
end
3334

mix.lock

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
%{
2-
"earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"},
3-
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
4-
"makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
5-
"makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
6-
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
2+
"earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], []},
3+
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, optional: false]}]},
4+
"exprintf": {:hex, :exprintf, "0.2.1", "b7e895dfb00520cfb7fc1671303b63b37dc3897c59be7cbf1ae62f766a8a0314", [:mix], []},
5+
"makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, optional: false]}]},
6+
"makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, optional: false]}]},
7+
"matrix": {:hex, :matrix, "0.3.2", "9c826bc3a1117bf5e1c5cdcf3a3d95456c93bc2e127a04e363e9fc90b724f784", [:mix], [{:exprintf, "~> 0.1", [hex: :exprintf, optional: false]}]},
8+
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], []},
79
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
defmodule LearnKit.Regression.PolynomialTest do
2+
use ExUnit.Case
3+
4+
alias LearnKit.Regression.Polynomial
5+
6+
test "create new polynomial predictor with empty data set" do
7+
assert Polynomial.new() == %Polynomial{}
8+
end
9+
10+
test "create new polynomial predictor with data" do
11+
assert Polynomial.new([1, 2], [3, 4]) == %Polynomial{
12+
coefficients: [],
13+
degree: 2,
14+
factors: [1, 2],
15+
results: [3, 4]
16+
}
17+
end
18+
19+
def factors, do: [-3, -2, -1, -0.2, 1, 3]
20+
def results, do: [0.9, 0.8, 0.4, 0.2, 0.1, 0]
21+
22+
test "fit data set" do
23+
assert Polynomial.new(factors(), results())
24+
|> Polynomial.fit(degree: 2) == %Polynomial{
25+
coefficients: [0.2290655593570844, -0.16280041315555793, 0.027763965678671648],
26+
degree: 2,
27+
factors: factors(),
28+
results: results()
29+
}
30+
end
31+
32+
test "fit data set with degree of 4" do
33+
assert Polynomial.new(factors(), results())
34+
|> Polynomial.fit(degree: 4) == %Polynomial{
35+
coefficients: [
36+
0.14805723970909512,
37+
-0.15811217698985996,
38+
0.12329778502873823,
39+
8.627221168971827e-4,
40+
-0.009963024223179073
41+
],
42+
degree: 4,
43+
factors: factors(),
44+
results: results()
45+
}
46+
end
47+
48+
test "predict using the polynomial model of simple sample" do
49+
assert Polynomial.new(factors(), results())
50+
|> Polynomial.fit(degree: 2)
51+
|> Polynomial.predict(3) == {:ok, -0.009459989001544572}
52+
end
53+
54+
test "predict using the polynomial model of multiple samples" do
55+
assert Polynomial.new(factors(), results())
56+
|> Polynomial.fit(degree: 2)
57+
|> Polynomial.predict([3, 5]) == {:ok, [-0.009459989001544572, 0.10916263554608596]}
58+
end
59+
end

0 commit comments

Comments
 (0)