From 7fd96b4696f0e7f4171197931f008be1dcbd7f69 Mon Sep 17 00:00:00 2001 From: Eilert Tunheim <emtunhei@stud.ntnu.no> Date: Wed, 20 Apr 2022 10:20:12 +0200 Subject: [PATCH] Added confidence interval and reset the x-axis back to index instead of hours --- Bachelor_application.iml | 1 + pom.xml | 7 ++ .../GUI/LineChartFunctionality.java | 119 ++++++++---------- .../GUI/LineChartFunctionality.class | Bin 5589 -> 7894 bytes 4 files changed, 60 insertions(+), 67 deletions(-) diff --git a/Bachelor_application.iml b/Bachelor_application.iml index bcd84db..cde0e4e 100644 --- a/Bachelor_application.iml +++ b/Bachelor_application.iml @@ -52,5 +52,6 @@ <orderEntry type="library" name="Maven: com.google.cloud:google-cloud-storage:2.4.0" level="project" /> <orderEntry type="library" name="Maven: com.google.apis:google-api-services-storage:v1-rev20211201-1.32.1" level="project" /> <orderEntry type="library" name="Maven: com.google.auto.value:auto-value-annotations:1.9" level="project" /> + <orderEntry type="library" name="Maven: org.apache.commons:commons-math3:3.6.1" level="project" /> </component> </module> \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3126ade..014584e 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,13 @@ <groupId>com.google.cloud</groupId> <artifactId>google-cloud-storage</artifactId> </dependency> + <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-math3 --> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-math3</artifactId> + <version>3.6.1</version> + </dependency> + </dependencies> <properties> diff --git a/src/main/java/com/application/GUI/LineChartFunctionality.java b/src/main/java/com/application/GUI/LineChartFunctionality.java index f4c249b..29d61ae 100644 --- a/src/main/java/com/application/GUI/LineChartFunctionality.java +++ b/src/main/java/com/application/GUI/LineChartFunctionality.java @@ -5,9 +5,11 @@ import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Map; +import org.apache.commons.math3.distribution.TDistribution; +import org.apache.commons.math3.exception.MathIllegalArgumentException; +import org.apache.commons.math3.stat.descriptive.SummaryStatistics; + +import java.util.*; public class LineChartFunctionality { @@ -15,17 +17,18 @@ public class LineChartFunctionality { private static CategoryAxis xAxis; private static NumberAxis yAxis; + private static final double CONFIDENCE_INTERVAL = 0.90; + public LineChartFunctionality() { xAxis = new CategoryAxis(); yAxis = new NumberAxis(); lineChart = new LineChart<>(xAxis, yAxis); - xAxis.setLabel("Hours"); + xAxis.setLabel("Data Points"); xAxis.setAnimated(false); yAxis.setLabel("Kwh"); yAxis.setAnimated(false); lineChart.setTitle("Drying Processes"); - lineChart.setCreateSymbols(false); } public static void setLineChart(LineChart<String, Number> lineChart) { @@ -44,99 +47,81 @@ public class LineChartFunctionality { lineChart.getData().clear(); } - private static double findDifference(String start_date, String end_date) { - // Defining a simple date format - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static Map<Integer, ArrayList<Double>> statistics(Map<Integer, ArrayList<Double>> multiMap){ - try{ - // try to convert the string to Date datatype - Date dateStart = dateFormat.parse(start_date); - Date dateEnd = dateFormat.parse(end_date); + System.out.println("\n\nMultimap: \n"); + for (Map.Entry<Integer, ArrayList<Double>> entry : multiMap.entrySet()) { + System.out.printf("\nIndex: \t%s\t\t\tkWh: \t%s\n", entry.getKey(), entry.getValue()); - // Finds the difference in millis - double differenceMillis = dateEnd.getTime() - dateStart.getTime(); + if(entry.getValue().size()>1){ + SummaryStatistics stats = new SummaryStatistics(); + for (double val : entry.getValue()) { + stats.addValue(val); + } - // Finds the difference in minutes - return (differenceMillis / (1000 * 60 )) % 60; + // Calculate 95% confidence interval + double ci = calcMeanCI(stats, CONFIDENCE_INTERVAL); + System.out.println(String.format("Mean: %f", stats.getMean())); + double lower = stats.getMean() - ci; + double upper = stats.getMean() + ci; + System.out.println(String.format("Confidence Interval 95%%: %f, %f", lower, upper)); - } catch (Exception e) { - System.out.println(e.getMessage()); + // Deletes entries if they are out of bounds with the confidence interval + entry.getValue().removeIf(value -> Double.compare(value, lower) < 0 || Double.compare(value, upper) > 0); + } + } + return multiMap; + } + + private static double calcMeanCI(SummaryStatistics stats, double level) { + try { + // Create T Distribution with N-1 degrees of freedom + TDistribution tDist = new TDistribution(stats.getN() - 1); + // Calculate critical value + double critVal = tDist.inverseCumulativeProbability(1.0 - (1 - level) / 2); + // Calculate confidence interval + return critVal * stats.getStandardDeviation() / Math.sqrt(stats.getN()); + } catch (MathIllegalArgumentException e) { + return Double.NaN; } - return 0; } public static LineChart<String, Number> loadSingleSeries(Map<Integer, Map<String, Number>> userInput) throws Exception { clearLineChart(); - //Map<Integer, Map<String, Number>> kWh = userInput; - //Map<Integer, Map<String, Number>> kWh = DB.setInputParameters(); - //System.out.println(kWh.size()); + Map<Integer, ArrayList<Double>> multiMap = new HashMap<>(); for (Map.Entry<Integer, Map<String, Number>> entryKwh : userInput.entrySet()) { Map data = entryKwh.getValue(); //System.out.println(data.size()); XYChart.Series<String, Number> newSeries = new XYChart.Series<String, Number>(); - long minutes = 0; - long hours; - String previouseDate = ""; + int index = 0; for (Object entryData : data.entrySet()) { //System.out.println("data: \t"+entryData); String entryString = entryData.toString(); String[] arr = entryString.split("="); - String currentDate = arr[0]; - int kwhValue = Integer.parseInt(arr[1]); - - - //System.out.printf("previouse date: \t%s\n",previouseDate); - //System.out.printf("Current date: \t\t%s\n",currentDate); - //System.out.printf("is prev empty?: \t%s\n",previouseDate.isEmpty()); - - - minutes += findDifference(previouseDate, currentDate); - - hours = minutes/60; - System.out.println(hours); - previouseDate = currentDate; + String date = arr[0]; + Double kwhValue = Double.parseDouble(arr[1]); //System.out.printf("Date: \t%s\t\t\tkWh: \t%s\n",date,kwhValue); - //System.out.printf("Hours: \t\t%s\n",hours); + + // Checks if the index already got an arraylist, if not one is created + multiMap.computeIfAbsent(index, k -> new ArrayList<Double>()); + multiMap.get(index).add(kwhValue); // Connect the data to a series - newSeries.getData().add(new XYChart.Data<String, Number>(String.valueOf(hours), kwhValue)); + newSeries.getData().add(new XYChart.Data<String, Number>(String.valueOf(index), kwhValue)); + index += 1; } updateLineChart(newSeries); } - return getLineChart(); - } - - public static LineChart<String, Number> loadMultipleSeries(Map<Integer, Map<String, Number>> userInput) throws Exception { - - //Map<Integer, Map<String, Number>> kWh = DB.setInputParameters(); - //System.out.println(kWh.size()); - for (Map.Entry<Integer, Map<String, Number>> entryKwh : userInput.entrySet()) { - Map data = entryKwh.getValue(); - //System.out.println(data.size()); + Map<Integer, ArrayList<Double>> confidenceIntervalData = statistics(multiMap); - XYChart.Series<String, Number> newSeries = new XYChart.Series<String, Number>(); - for (Object entryData : data.entrySet()) { - //System.out.println("data: \t"+entryData); - String entryString = entryData.toString(); - String[] arr = entryString.split("="); - String date = arr[0]; - int kwhValue = Integer.parseInt(arr[1]); + System.out.println(confidenceIntervalData); - //System.out.printf("Date: \t%s\t\t\tkWh: \t%s\n",date,kwhValue); - - - // Connect the data to a series - newSeries.getData().add(new XYChart.Data<String, Number>(date, kwhValue)); - - } - updateLineChart(newSeries); - } return getLineChart(); } } diff --git a/target/classes/com/application/GUI/LineChartFunctionality.class b/target/classes/com/application/GUI/LineChartFunctionality.class index 8427e84169ee5bbc8f1c49de41a658e1d8cf6745..a7c6a67ecc6871b6999f49cf884d5f375c5ac204 100644 GIT binary patch literal 7894 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 S?-@QYyk%f!WMgD!<NyF|QgF}! literal 5589 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 -- GitLab