From 36f52309e0516efaf8f0d58147494f361c85b207 Mon Sep 17 00:00:00 2001 From: Michael Fross Date: Mon, 29 Jan 2024 15:35:50 -0600 Subject: [PATCH] Enhanced linear regression and maven updates Added the ability to predict a result from a selected X value Adding next value to stack is no longer the default - you need to use the `add` parameter Updated the following plugins: maven-compiler-plugin maven-surefire-plugin maven-shade-plugin exec-maven-plugin Updated the following dependencies: fross libray jline-reader jline-terminal-jansi Updated UserGuide for the Linear Regression command Added enhanced lr tests --- .classpath | 3 +- mdbook/src/Chapters/CalculatorCommands.md | 2 +- pom.xml | 16 ++--- snap/snapcraft.yaml | 4 +- .../java/org/fross/rpncalc/CommandParser.java | 2 +- src/main/java/org/fross/rpncalc/Help.java | 2 +- .../java/org/fross/rpncalc/StackCommands.java | 62 ++++++++++++++----- .../org/fross/rpncalc/StackCommandsTest.java | 49 ++++++++++++--- 8 files changed, 102 insertions(+), 38 deletions(-) diff --git a/.classpath b/.classpath index 168af0d..debb9c3 100644 --- a/.classpath +++ b/.classpath @@ -13,9 +13,8 @@ - + - diff --git a/mdbook/src/Chapters/CalculatorCommands.md b/mdbook/src/Chapters/CalculatorCommands.md index bfd6992..56cae67 100644 --- a/mdbook/src/Chapters/CalculatorCommands.md +++ b/mdbook/src/Chapters/CalculatorCommands.md @@ -21,7 +21,7 @@ These commands, like the others you'll read about later, are executed by typing |fact
factorial| **FACTORIAL**
Takes the factorial of `line1`. The factorial target number must be an integer, so if there is a decimal component, it will be dropped (not rounded) as with the `int` command| |f
flip | **FLIP SIGN**
Flip the sign on the top stack item (`line1`). This is effectively multiplying `line1` by `-1`| |int| **INTEGER**
Converts the top stack item (`line1`) to it's integer value. This will discard the decimal portion regardless of it's value. For example: `4.34` will result in `4` and `4.999` will also result in `4`. If rounding is desired, execute the `round` command prior to `int` (or create a user defined function)| -|lr| **SIMPLE LINEAR REGRESSION**
[Linear regression](https://www.graphpad.com/guides/the-ultimate-guide-to-linear-regression) is used to model the relationship between two variables and create a line that can be used to estimate other values using a line-of-best-fit method. This implementation is for simple linear regression and will show you the formula, slope, y-intercept as well as add the next expected value to the top of the stack.

The values will be plotted from the bottom of the stack to the top (`line1`)(which is probably want you want). If you need it the other way around, simply reverse the stack with the `reverse` or `rev` command prior to executing `lr`| +|lr [add]
lr [x]| **SIMPLE LINEAR REGRESSION**
[Linear regression](https://www.graphpad.com/guides/the-ultimate-guide-to-linear-regression) is used to model the relationship between two variables and create a line that can be used to estimate other values using a line-of-best-fit method. This implementation is for simple linear regression, and will display the formula, slope, y-intercept as well as add the next expected value to the top of the stack.

If `add` (or just `a`) is entered, the next predicted value will be added to the top of the stack (`line1`). If a number is provided (`x`), the predicted `y` value will be displayed. The `y` value is the result of the linear extrapolation at the `x` value. If both are added, the y value at the x location will be both calculated and added to the top of the stack (`line1`)

The values will be plotted from the bottom of the stack to the top (`line1`)(which is probably want you want). If you need it the other way around, simply reverse the stack with the `reverse` or `rev` command prior to executing `lr`| |log|**LOGARITHM BASE e**
Calculates the [natural logarithm (base e)](https://en.wikipedia.org/wiki/Natural_logarithm). Please note that these are calculated as a `double` and therefore do not have unlimited precision| |log10|**LOGARITHM BASE 10**
Calculates the [base10 logarithm](https://en.wikipedia.org/wiki/Common_logarithm). Please note that these are calculated as a `double` and therefore do not have unlimited precision| |max|**MAXIMUM VALUE**
Copies the largest value in the top of the stack (`line1`)| diff --git a/pom.xml b/pom.xml index 16d75f5..328c2d1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.fross rpncalc - 5.1.7 + 5.1.8 jar rpncalc @@ -64,7 +64,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.12.1 ${maven.compiler.release} @@ -77,7 +77,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.1.0 + 3.2.5 @@ -87,7 +87,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.4.1 + 3.5.1 package @@ -182,7 +182,7 @@ org.codehaus.mojo exec-maven-plugin - 3.1.0 + 3.1.1 chmod @@ -240,7 +240,7 @@ org.fross library - 2023.12.03 + 2024.01.22 @@ -255,7 +255,7 @@ org.jline jline-reader - 3.25.0 + 3.25.1 @@ -263,7 +263,7 @@ org.jline jline-terminal-jansi - 3.25.0 + 3.25.1 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 186664b..262ba18 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: rpncalc -version: '5.1.7' +version: '5.1.8' summary: The command line Reverse Polish Notation (RPN) calculator description: | RPNCalc is an easy to use command line based Reverse Polish @@ -50,7 +50,7 @@ parts: plugin: maven source: https://github.com/frossm/library.git source-type: git - source-tag: 'v2023.12.03' + source-tag: 'v2024.01.22' maven-parameters: - install diff --git a/src/main/java/org/fross/rpncalc/CommandParser.java b/src/main/java/org/fross/rpncalc/CommandParser.java index 251a4a7..599ab3f 100644 --- a/src/main/java/org/fross/rpncalc/CommandParser.java +++ b/src/main/java/org/fross/rpncalc/CommandParser.java @@ -163,7 +163,7 @@ public static void Parse(StackObj calcStack, StackObj calcStack2, String cmdInpu // Linear Regression case "lr": - StackCommands.cmdLinearRegression(calcStack); + StackCommands.cmdLinearRegression(calcStack, cmdInputParam); break; // Absolute Value diff --git a/src/main/java/org/fross/rpncalc/Help.java b/src/main/java/org/fross/rpncalc/Help.java index 37cae9d..0cf1056 100644 --- a/src/main/java/org/fross/rpncalc/Help.java +++ b/src/main/java/org/fross/rpncalc/Help.java @@ -81,7 +81,7 @@ public static void Display() { Output.printColorln(Ansi.Color.WHITE, " fact Take a factorial of line1. Decimals will be dropped"); Output.printColorln(Ansi.Color.WHITE, " f Flip the sign of the element at line1"); Output.printColorln(Ansi.Color.WHITE, " int Convert line1 to an integer. No rounding is performed"); - Output.printColorln(Ansi.Color.WHITE, " lr Simple Linear regression. Calculate the next predicted value"); + Output.printColorln(Ansi.Color.WHITE, " lr [add] [x] Linear Regress. 'add' will add next value. x adds the y value at that x"); Output.printColorln(Ansi.Color.WHITE, " log | log10 Calculate the natural (base e) or base10 logarithm"); Output.printColorln(Ansi.Color.WHITE, " min | max Add the minimum or maximum stack value to the stack"); Output.printColorln(Ansi.Color.WHITE, " median [keep] Replace stack with median value. 'keep' will retain stack"); diff --git a/src/main/java/org/fross/rpncalc/StackCommands.java b/src/main/java/org/fross/rpncalc/StackCommands.java index 85ed5af..674f266 100644 --- a/src/main/java/org/fross/rpncalc/StackCommands.java +++ b/src/main/java/org/fross/rpncalc/StackCommands.java @@ -406,13 +406,40 @@ public static void cmdInteger(StackObj calcStack) { * * @param calcStack */ - public static void cmdLinearRegression(StackObj calcStack) { + public static void cmdLinearRegression(StackObj calcStack, String args) { + boolean argAdd = false; + BigDecimal argX = new BigDecimal(calcStack.size()); + // Ensure we have at least 2 values on the stack if (calcStack.size() < 2) { Output.printColorln(Ansi.Color.RED, "Error: There must be at least two items on the stack to calculate a linear regression"); return; } + // Process the arguments + try { + // If any arguments were supplied + if (!args.isEmpty()) { + // Loop through each option and process it + for (String a : args.split("\\s")) { + // Look for a string that starts with 'a' + if (a.toLowerCase().startsWith("a")) { + argAdd = true; + Output.debugPrintln("Setting LR ADD flag"); + continue; + } else { + argX = new BigDecimal(a).subtract(BigDecimal.ONE); + Output.debugPrintln("Setting LR 'x' value to : " + argX.toString()); + continue; + } + } + } + + } catch (Exception ex) { + Output.printColorln(Ansi.Color.RED, "ERROR: Acceptable linear regression options are 'add' or a number"); + return; + } + // X is the number of stack items BigDecimal n = new BigDecimal(String.valueOf(calcStack.size())); BigDecimal sumX = BigDecimal.ZERO; // X values are the stack numbers @@ -464,17 +491,18 @@ public static void cmdLinearRegression(StackObj calcStack) { BigDecimal b = b_top.divide(b_bottom, MathContext.DECIMAL128); // Output details if debug is enabled - Output.debugPrintln("n: " + n.toPlainString()); - Output.debugPrintln("sumX: " + sumX.toPlainString()); - Output.debugPrintln("sumY: " + sumY.toPlainString()); - Output.debugPrintln("sumXY: " + sumXY.toPlainString()); - Output.debugPrintln("sumX2: " + sumX2.toPlainString()); - Output.debugPrintln("sumY2: " + sumY2.toPlainString()); - Output.debugPrintln("a: " + a.toPlainString()); - Output.debugPrintln("b: " + b.toPlainString()); + Output.debugPrintln("n: " + n.toPlainString()); + Output.debugPrintln("sumX: " + sumX.toPlainString()); + Output.debugPrintln("sumY: " + sumY.toPlainString()); + Output.debugPrintln("sumXY: " + sumXY.toPlainString()); + Output.debugPrintln("sumX2: " + sumX2.toPlainString()); + Output.debugPrintln("sumY2: " + sumY2.toPlainString()); + Output.debugPrintln("a: " + a.toPlainString()); + Output.debugPrintln("b: " + b.toPlainString()); + Output.debugPrintln("NextValue: " + argX.toPlainString() + " (one less than displayed)"); // Rounded values are just for the display - BigDecimal nextValue = a.add(b.multiply(n.add(BigDecimal.ONE))); + BigDecimal nextValue = a.add(b.multiply(argX.add(BigDecimal.ONE))); BigDecimal aRounded = a.setScale(4, RoundingMode.HALF_UP); BigDecimal bRounded = b.setScale(4, RoundingMode.HALF_UP); BigDecimal nextValueRounded = nextValue.setScale(4, RoundingMode.HALF_UP); @@ -482,13 +510,15 @@ public static void cmdLinearRegression(StackObj calcStack) { // Display the LR formula Output.printColorln(Ansi.Color.CYAN, "Slope Equation: y = " + bRounded + "x + " + aRounded); Output.printColorln(Ansi.Color.CYAN, "Slope: " + bRounded + " Y-Intercept: " + aRounded); - Output.printColorln(Ansi.Color.CYAN, "Predicted next value (" + nextValueRounded + ") added to the top of the stack"); - - // Save current calcStack to the undoStack - calcStack.saveUndo(); + Output.printColorln(Ansi.Color.CYAN, "Predicted value at x = " + argX.add(BigDecimal.ONE) + ": " + nextValueRounded); - // Add the next predicted value to the stack - calcStack.push(nextValue); + // Add the next predicted value to the stack if 'add' was selected + if (argAdd) { + // Save current calcStack to the undoStack + calcStack.saveUndo(); + + calcStack.push(nextValue); + } } diff --git a/src/test/java/org/fross/rpncalc/StackCommandsTest.java b/src/test/java/org/fross/rpncalc/StackCommandsTest.java index d459e1e..8c8f50b 100644 --- a/src/test/java/org/fross/rpncalc/StackCommandsTest.java +++ b/src/test/java/org/fross/rpncalc/StackCommandsTest.java @@ -570,7 +570,7 @@ void testCmdInteger() { /** * Testing the linear regression results - * + * Reference for testing: https://www.socscistatistics.com/tests/regression/default.aspx */ @Test void testCmdLinearRegression() { @@ -584,7 +584,7 @@ void testCmdLinearRegression() { stk.push(20.00); stk.push(10.00); stk.push(35.00); - StackCommands.cmdLinearRegression(stk); + StackCommands.cmdLinearRegression(stk, "a"); StackCommands.cmdRound(stk, "4"); assertEquals(8, stk.size()); assertEquals(27.7143, stk.pop().doubleValue()); @@ -601,7 +601,7 @@ void testCmdLinearRegression() { stk.push(11.11); stk.push(22.22); stk.push(3.0); - StackCommands.cmdLinearRegression(stk); + StackCommands.cmdLinearRegression(stk, "a"); StackCommands.cmdRound(stk, "6"); assertEquals(11, stk.size()); assertEquals(16.671333, stk.pop().doubleValue()); @@ -618,7 +618,7 @@ void testCmdLinearRegression() { stk.push(-34.88); stk.push(40.99); stk.push(3.00); - StackCommands.cmdLinearRegression(stk); + StackCommands.cmdLinearRegression(stk, "a"); StackCommands.cmdRound(stk, "6"); assertEquals(11, stk.size()); assertEquals(4.271333, stk.pop().doubleValue()); @@ -629,7 +629,7 @@ void testCmdLinearRegression() { for (int i = 0; i < testValues.length; i++) { stk.push(testValues[i]); } - StackCommands.cmdLinearRegression(stk); + StackCommands.cmdLinearRegression(stk, "a"); StackCommands.cmdRound(stk, "7"); assertEquals(48.9842105, stk.pop().doubleValue()); @@ -639,7 +639,7 @@ void testCmdLinearRegression() { for (int i = 0; i < testValues1.length; i++) { stk.push(testValues1[i]); } - StackCommands.cmdLinearRegression(stk); + StackCommands.cmdLinearRegression(stk, "a"); StackCommands.cmdRound(stk, "9"); assertEquals(17, stk.size()); assertEquals(-9.101980055, stk.pop().doubleValue()); @@ -652,10 +652,45 @@ void testCmdLinearRegression() { stk.push(testValues2[i]); } assertEquals(12, stk.size()); - StackCommands.cmdLinearRegression(stk); + StackCommands.cmdLinearRegression(stk, "a"); StackCommands.cmdRound(stk, "9"); assertEquals("47498562223075895424.242424242", stk.peek().toEngineeringString()); assertEquals(13, stk.size()); + + // Test #7 - Custom X values + stk.clear(); + Double[] testValues3 = { 29.0, 41.0, 8.0, 18.0, 22.0, 99.0, 32.0, 15.0, 31.0, 3.0, 72.0, 12.0, 60.0, 32.0, 54.0, 45.0, 34.0, 76.0, 5.0, 67.0 }; + for (int i = 0; i < testValues3.length; i++) { + stk.push(testValues3[i]); + } + StackCommands.cmdLinearRegression(stk, "30 a"); + StackCommands.cmdRound(stk, "4"); + assertEquals(58.6135, stk.pop().doubleValue()); + assertEquals(20, stk.size()); + + // Test #8 - Custom X values + stk.clear(); + Double[] testValues4 = { 19.0, 41.0, 8.0, 18.0, 12.0, -99.0, 32.0, 15.0, 31.0, 3.0, 72.0, 12.0, 60.0, 32.0, 54.0, 45.0, 34.0, 76.0, 5.0, 67.0 }; + for (int i = 0; i < testValues4.length; i++) { + stk.push(testValues4[i]); + } + StackCommands.cmdLinearRegression(stk, "22 a"); + StackCommands.cmdRound(stk, "4"); + assertEquals(57.1564, stk.pop().doubleValue()); + assertEquals(20, stk.size()); + + // Test #9 - Custom X values + stk.clear(); + Double[] testValues5 = { 29.0, 41.0, 8.0, 18.0, 22.0, -99.0, 32.0, 15.0, 31.0, 3.0, 72.0, 12.0, -60.0, 32.0, 54.0, 45.0, 14.0, 76.0, 5.0, 44.0 }; + for (int i = 0; i < testValues5.length; i++) { + stk.push(testValues5[i]); + } + StackCommands.cmdLinearRegression(stk, "45 a"); + StackCommands.cmdRound(stk, "4"); + assertEquals(69.1932, stk.pop().doubleValue()); + assertEquals(20, stk.size()); + + } /**