1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-23 00:33:09 +02:00

polynomical order can now be set for the regression weight line

This commit is contained in:
OliE
2017-09-22 12:35:47 +02:00
parent b6b183f42d
commit b4ce4f6b09
5 changed files with 364 additions and 117 deletions

View File

@@ -0,0 +1,215 @@
package com.health.openscale.core.utils;
/***************************************************************************
* Copyright (C) 2009 by Paul Lutus, Ian Clarke *
* lutusp@arachnoid.com, ian.clarke@gmail.com *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
import java.util.ArrayList;
import java.util.List;
/**
* A class to fit a polynomial to a (potentially very large) dataset.
*
* @author Paul Lutus <lutusp@arachnoid.com>
* @author Ian Clarke <ian.clarke@gmail.com>
*
*/
/*
* Changelog:
* 20100130: Add note about relicensing
* 20091114: Modify so that points can be added after the curve is
* created, also some other minor fixes
* 20091113: Extensively modified by Ian Clarke, main changes:
* - Should now be able to handle extremely large datasets
* - Use generic Java collections classes and interfaces
* where possible
* - Data can be fed to the fitter as it is available, rather
* than all at once
*
* The code that this is based on was obtained from: http://arachnoid.com/polysolve
*
* Note: I (Ian Clarke) am happy to release this code under a more liberal
* license such as the LGPL, however Paul Lutus (the primary author) refuses
* to do this on the grounds that the LGPL is not an open source license.
* If you want to try to explain to him that the LGPL is indeed an open
* source license, good luck - it's like talking to a brick wall.
*/
public class PolynomialFitter {
private final int p, rs;
private long n = 0;
private final double[][] m;
private final double[] mpc;
/**
* @param degree
* The degree of the polynomial to be fit to the data
*/
public PolynomialFitter(final int degree) {
assert degree > 0;
p = degree + 1;
rs = 2 * p - 1;
m = new double[p][p + 1];
mpc = new double[rs];
}
/**
* Add a point to the set of points that the polynomial must be fit to
*
* @param x
* The x coordinate of the point
* @param y
* The y coordinate of the point
*/
public void addPoint(final double x, final double y) {
assert !Double.isInfinite(x) && !Double.isNaN(x);
assert !Double.isInfinite(y) && !Double.isNaN(y);
n++;
// process precalculation array
for (int r = 1; r < rs; r++) {
mpc[r] += Math.pow(x, r);
}
// process RH column cells
m[0][p] += y;
for (int r = 1; r < p; r++) {
m[r][p] += Math.pow(x, r) * y;
}
}
/**
* Returns a polynomial that seeks to minimize the square of the total
* distance between the set of points and the polynomial.
*
* @return A polynomial
*/
public Polynomial getBestFit() {
final double[] mpcClone = mpc.clone();
final double[][] mClone = new double[m.length][];
for (int x = 0; x < mClone.length; x++) {
mClone[x] = m[x].clone();
}
mpcClone[0] += n;
// populate square matrix section
for (int r = 0; r < p; r++) {
for (int c = 0; c < p; c++) {
mClone[r][c] = mpcClone[r + c];
}
}
gj_echelonize(mClone);
final Polynomial result = new Polynomial(p);
for (int j = 0; j < p; j++) {
result.add(j, mClone[j][p]);
}
return result;
}
private double fx(final double x, final List<Double> terms) {
double a = 0;
int e = 0;
for (final double i : terms) {
a += i * Math.pow(x, e);
e++;
}
return a;
}
private void gj_divide(final double[][] A, final int i, final int j, final int m) {
for (int q = j + 1; q < m; q++) {
A[i][q] /= A[i][j];
}
A[i][j] = 1;
}
private void gj_echelonize(final double[][] A) {
final int n = A.length;
final int m = A[0].length;
int i = 0;
int j = 0;
while (i < n && j < m) {
// look for a non-zero entry in col j at or below row i
int k = i;
while (k < n && A[k][j] == 0) {
k++;
}
// if such an entry is found at row k
if (k < n) {
// if k is not i, then swap row i with row k
if (k != i) {
gj_swap(A, i, j);
}
// if A[i][j] is not 1, then divide row i by A[i][j]
if (A[i][j] != 1) {
gj_divide(A, i, j, m);
}
// eliminate all other non-zero entries from col j by
// subtracting from each
// row (other than i) an appropriate multiple of row i
gj_eliminate(A, i, j, n, m);
i++;
}
j++;
}
}
private void gj_eliminate(final double[][] A, final int i, final int j, final int n, final int m) {
for (int k = 0; k < n; k++) {
if (k != i && A[k][j] != 0) {
for (int q = j + 1; q < m; q++) {
A[k][q] -= A[k][j] * A[i][q];
}
A[k][j] = 0;
}
}
}
private void gj_swap(final double[][] A, final int i, final int j) {
double temp[];
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
public static class Polynomial extends ArrayList<Double> {
private static final long serialVersionUID = 1692843494322684190L;
public Polynomial(final int p) {
super(p);
}
public double getY(final double x) {
double ret = 0;
for (int p=0; p<size(); p++) {
ret += get(p)*(Math.pow(x, p));
}
return ret;
}
@Override
public String toString() {
final StringBuilder ret = new StringBuilder();
for (int x = size() - 1; x > -1; x--) {
ret.append(get(x) + (x > 0 ? "x^" + x + " + " : ""));
}
return ret.toString();
}
}
}

View File

@@ -36,6 +36,7 @@ import android.widget.TextView;
import com.health.openscale.R;
import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleData;
import com.health.openscale.core.utils.PolynomialFitter;
import com.health.openscale.gui.activities.DataEntryActivity;
import java.text.SimpleDateFormat;
@@ -284,7 +285,6 @@ public class GraphFragment extends Fragment implements FragmentUpdateListener {
setHasPoints(prefs.getBoolean("pointsEnable", true)).
setFormatter(new SimpleLineChartValueFormatter(1));
if(prefs.getBoolean("weightEnable", true) && prefs.getBoolean(String.valueOf(diagramWeight.getId()), true)) {
lines.add(lineWeight);
diagramWeight.setBackgroundTintList(ColorStateList.valueOf(ChartUtils.COLOR_VIOLET));
@@ -333,6 +333,22 @@ public class GraphFragment extends Fragment implements FragmentUpdateListener {
enableMonth.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#d3d3d3")));
}
LineChartData lineData = new LineChartData(lines);
lineData.setAxisXBottom(new Axis(axisValues).
setHasLines(true).
setTextColor(Color.BLACK)
);
lineData.setAxisYLeft(new Axis().
setHasLines(true).
setMaxLabelChars(5).
setTextColor(Color.BLACK)
);
chartBottom.setLineChartData(lineData);
defaultTopViewport = new Viewport(0, chartBottom.getCurrentViewport().top+4, axisValues.size()-1, chartBottom.getCurrentViewport().bottom-4);
if (prefs.getBoolean("goalLine", true)) {
Stack<PointValue> valuesGoalLine = new Stack<PointValue>();
@@ -349,112 +365,23 @@ public class GraphFragment extends Fragment implements FragmentUpdateListener {
lines.add(goalLine);
}
if (prefs.getBoolean("regressionLine", true)) {
if (prefs.getBoolean("regressionLine", false)) {
PolynomialFitter polyFitter = new PolynomialFitter(Integer.parseInt(prefs.getString("regressionLineOrder", "1")));
/*
// quadratic regression y = ax^2 + bx + c
double x_value = 0.0;
double y_value = 0.0;
double s40 = 0; //sum of x^4
double s30 = 0; //sum of x^3
double s20 = 0; //sum of x^2
double s10 = 0; //sum of x
double s00 = scaleDataList.size();
//sum of x^0 * y^0 ie 1 * number of entries
double s21 = 0; //sum of x^2*y
double s11 = 0; //sum of x*y
double s01 = 0; //sum of y
for(ScaleData scaleEntry: scaleDataList) {
calDB.setTime(scaleEntry.getDateTime());
x_value = calDB.get(field);
y_value = scaleEntry.getConvertedWeight(openScale.getSelectedScaleUser().scale_unit);
s40 += Math.pow(x_value, 4);
s30 += Math.pow(x_value, 3);
s20 += Math.pow(x_value, 2);
s10 += x_value;
s21 += Math.pow(x_value, 2) * y_value;
s11 += x_value * y_value;
s01 += y_value;
for(PointValue weightValue : valuesWeight) {
polyFitter.addPoint(weightValue.getX(), weightValue.getY());
}
// solve equations using Cramer's law
double a = (s21*(s20 * s00 - s10 * s10) -
s11*(s30 * s00 - s10 * s20) +
s01*(s30 * s10 - s20 * s20))
/
(s40*(s20 * s00 - s10 * s10) -
s30*(s30 * s00 - s10 * s20) +
s20*(s30 * s10 - s20 * s20));
double b = (s40*(s11 * s00 - s01 * s10) -
s30*(s21 * s00 - s01 * s20) +
s20*(s21 * s10 - s11 * s20))
/
(s40 * (s20 * s00 - s10 * s10) -
s30 * (s30 * s00 - s10 * s20) +
s20 * (s30 * s10 - s20 * s20));
double c = (s40*(s20 * s01 - s10 * s11) -
s30*(s30 * s01 - s10 * s21) +
s20*(s30 * s11 - s20 * s21))
/
(s40 * (s20 * s00 - s10 * s10) -
s30 * (s30 * s00 - s10 * s20) +
s20 * (s30 * s10 - s20 * s20));
*/
// linear regression y = a + x*b
double sumx = 0.0;
double sumy = 0.0;
double x_value = 0.0;
double y_value = 0.0;
for(ScaleData scaleEntry: scaleDataList) {
calDB.setTime(scaleEntry.getDateTime());
x_value = calDB.get(field);
y_value = scaleEntry.getConvertedWeight(openScale.getSelectedScaleUser().scale_unit);
sumx += x_value;
sumy += y_value;
}
double xbar = sumx / scaleDataList.size();
double ybar = sumy / scaleDataList.size();
double xxbar = 0.0;
double xybar = 0.0;
for(ScaleData scaleEntry: scaleDataList) {
calDB.setTime(scaleEntry.getDateTime());
x_value = calDB.get(field);
y_value = scaleEntry.getConvertedWeight(openScale.getSelectedScaleUser().scale_unit);
xxbar += (x_value - xbar) * (x_value - xbar);
xybar += (y_value - xbar) * (y_value - ybar);
}
double b = xybar / xxbar;
double a = ybar - b * xbar;
PolynomialFitter.Polynomial polynom = polyFitter.getBestFit();
Stack<PointValue> valuesLinearRegression = new Stack<PointValue>();
for (int i = 0; i < 31; i++) {
y_value = a + b * i; // linear regression
//y_value = a * i*i + b * i + c; // quadratic regression
valuesLinearRegression.push(new PointValue((float) i, (float) y_value));
if (!valuesWeight.isEmpty()) {
for (int i = (int)valuesWeight.peek().getX(); i <= 31; i++) {
double y_value = polynom.getY(i);
valuesLinearRegression.push(new PointValue((float) i, (float) y_value));
}
}
Line linearRegressionLine = new Line(valuesLinearRegression)
.setColor(ChartUtils.COLOR_VIOLET)
.setHasPoints(false);
@@ -464,22 +391,8 @@ public class GraphFragment extends Fragment implements FragmentUpdateListener {
lines.add(linearRegressionLine);
}
LineChartData lineData = new LineChartData(lines);
lineData.setAxisXBottom(new Axis(axisValues).
setHasLines(true).
setTextColor(Color.BLACK)
);
lineData.setAxisYLeft(new Axis().
setHasLines(true).
setMaxLabelChars(5).
setTextColor(Color.BLACK)
);
chartBottom.setLineChartData(lineData);
defaultTopViewport = new Viewport(0, chartBottom.getCurrentViewport().top+4, axisValues.size()-1, chartBottom.getCurrentViewport().bottom-4);
chartBottom.setMaximumViewport(defaultTopViewport);
chartBottom.setCurrentViewport(defaultTopViewport);
}

View File

@@ -15,16 +15,132 @@
*/
package com.health.openscale.gui.preferences;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.MultiSelectListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.text.method.DigitsKeyListener;
import com.health.openscale.R;
public class GraphPreferences extends PreferenceFragment {
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class GraphPreferences extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String PREFERENCE_KEY_REGRESSION_LINE = "regressionLine";
public static final String PREFERENCE_KEY_REGRESSION_LINE_ORDER = "regressionLineOrder";
private CheckBoxPreference regressionLine;
private EditTextPreference regressionLineOrder;
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.graph_preferences);
regressionLine = (CheckBoxPreference) findPreference(PREFERENCE_KEY_REGRESSION_LINE);
regressionLineOrder = (EditTextPreference) findPreference(PREFERENCE_KEY_REGRESSION_LINE_ORDER);
regressionLineOrder.getEditText().setKeyListener(new DigitsKeyListener());
updateGraphPreferences();
initSummary(getPreferenceScreen());
}
@Override
public void onResume()
{
super.onResume();
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause()
{
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
{
updatePrefSummary(findPreference(key));
updateGraphPreferences();
}
private void initSummary(Preference p)
{
if (p instanceof PreferenceGroup)
{
PreferenceGroup pGrp = (PreferenceGroup) p;
for (int i = 0; i < pGrp.getPreferenceCount(); i++)
{
initSummary(pGrp.getPreference(i));
}
}
else
{
updatePrefSummary(p);
}
}
public void updateGraphPreferences()
{
if (regressionLine.isChecked())
{
regressionLineOrder.setEnabled(true);
}
else
{
regressionLineOrder.setEnabled(false);
}
}
private void updatePrefSummary(Preference p)
{
if (p instanceof ListPreference)
{
ListPreference listPref = (ListPreference) p;
p.setSummary(listPref.getEntry());
}
if (p instanceof EditTextPreference)
{
EditTextPreference editTextPref = (EditTextPreference) p;
if (p.getTitle().toString().contains("assword"))
{
p.setSummary("******");
}
else
{
p.setSummary(editTextPref.getText());
}
}
if (p instanceof MultiSelectListPreference)
{
MultiSelectListPreference editMultiListPref = (MultiSelectListPreference) p;
CharSequence[] entries = editMultiListPref.getEntries();
CharSequence[] entryValues = editMultiListPref.getEntryValues();
List<String> currentEntries = new ArrayList<>();
Set<String> currentEntryValues = editMultiListPref.getValues();
for (int i = 0; i < entries.length; i++)
{
if (currentEntryValues.contains(entryValues[i].toString())) currentEntries.add(entries[i].toString());
}
p.setSummary(currentEntries.toString());
}
}
}

View File

@@ -151,6 +151,7 @@
<string name="label_initial_weight">Initial weight</string>
<string name="label_average_data">Calculate average per day/month</string>
<string name="label_regression_line">Regression weight line</string>
<string name="label_regression_line_order">Regression order</string>
<string name="label_goal_line">Goal line</string>

View File

@@ -5,4 +5,6 @@
<CheckBoxPreference android:title="@string/label_delete_confirmation" android:summaryOn="@string/info_is_enable" android:summaryOff="@string/info_is_not_enable" android:key="deleteConfirmationEnable" android:defaultValue="true" />
<CheckBoxPreference android:title="@string/label_average_data" android:summaryOn="@string/info_is_enable" android:summaryOff="@string/info_is_not_enable" android:key="averageData" android:defaultValue="true" />
<CheckBoxPreference android:title="@string/label_goal_line" android:summaryOn="@string/info_is_visible" android:summaryOff="@string/info_is_not_visible" android:key="goalLine" android:defaultValue="true" />
<CheckBoxPreference android:title="@string/label_regression_line" android:summaryOn="@string/info_is_visible" android:summaryOff="@string/info_is_not_visible" android:key="regressionLine" android:defaultValue="true" /></PreferenceScreen>
<CheckBoxPreference android:title="@string/label_regression_line" android:summaryOn="@string/info_is_visible" android:summaryOff="@string/info_is_not_visible" android:key="regressionLine" android:defaultValue="false" />
<EditTextPreference android:title="@string/label_regression_line_order" android:key="regressionLineOrder" android:defaultValue="1" />
</PreferenceScreen>