|
| 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 |
0 commit comments