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:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
||||
|
||||
|
@@ -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>
|
Reference in New Issue
Block a user