Parcourir la source

wxGUI/tplot: add option to show simple regression model line (#1207)

* add option to show simple regression model line (calculate simple regression model, print calculated formula y = a + b*x, and r-squared parameter)
* explain conversion x axis date time string format to number (referenced date) with export data to CSV file and its effect on the calculation (in different software)
* increase the number of decimal places from 4 to 5 for the calculated simple regression model formula
Tomas Zigo il y a 4 ans
Parent
commit
c3405bb1fd

+ 80 - 1
gui/wxpython/tplot/frame.py

@@ -61,10 +61,11 @@ except ImportError:
 import wx.lib.filebrowsebutton as filebrowse
 
 from gui_core.widgets import GNotebook
-from gui_core.wrap import TextCtrl, Button, StaticText
+from gui_core.wrap import CheckBox, TextCtrl, Button, StaticText
 
 ALPHA = 0.5
 COLORS = ['b', 'g', 'r', 'c', 'm', 'y', 'k']
+LINEAR_REG_LINE_COLOR = (0.56, 0.00, 1.00)
 
 
 def check_version(*version):
@@ -204,6 +205,11 @@ class TplotFrame(wx.Frame):
 
         self.coorval.SetToolTip(_("Coordinates can be obtained for example"
                                   " by right-clicking on Map Display."))
+        self.linRegRaster = CheckBox(
+            parent=self.controlPanelRaster, id=wx.ID_ANY,
+            label=_('Show simple linear regression line'),
+        )
+
         self.controlPanelSizerRaster = wx.BoxSizer(wx.VERTICAL)
         # self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
         # label=_("Select space time raster dataset(s):")),
@@ -214,6 +220,7 @@ class TplotFrame(wx.Frame):
 
         self.controlPanelSizerRaster.Add(self.coor, flag=wx.EXPAND)
         self.controlPanelSizerRaster.Add(self.coorval, flag=wx.EXPAND)
+        self.controlPanelSizerRaster.Add(self.linRegRaster, flag=wx.EXPAND)
 
         self.controlPanelRaster.SetSizer(self.controlPanelSizerRaster)
         self.controlPanelSizerRaster.Fit(self)
@@ -253,6 +260,10 @@ class TplotFrame(wx.Frame):
         self.catsLabel = StaticText(parent=self.controlPanelVector,
                                     id=wx.ID_ANY,
                                     label=_('Select category of vector(s)'))
+        self.linRegVector = CheckBox(
+            parent=self.controlPanelVector, id=wx.ID_ANY,
+            label=_('Show simple linear regression line'),
+        )
 
         self.controlPanelSizerVector = wx.BoxSizer(wx.VERTICAL)
         # self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
@@ -267,6 +278,7 @@ class TplotFrame(wx.Frame):
 
         self.controlPanelSizerVector.Add(self.catsLabel, flag=wx.EXPAND)
         self.controlPanelSizerVector.Add(self.cats, flag=wx.EXPAND)
+        self.controlPanelSizerVector.Add(self.linRegVector, flag=wx.EXPAND)
 
         self.controlPanelVector.SetSizer(self.controlPanelSizerVector)
         self.controlPanelSizerVector.Fit(self)
@@ -659,6 +671,61 @@ class TplotFrame(wx.Frame):
                 writer.writerow(head)
             writer.writerows(zipped)
 
+    def _calcSimpleLinReg(self, x, y, returnFormula=False):
+        """Calculate simple linear regression model
+        y = a + b*x (y is dependent variable, a is intercept, b is slope,
+        x is explanatory variable)
+
+        param numpy.array x: explanatory variable
+        param numpy.array y: dependent variable
+        param returnFormula bool: return calculated simple linear
+        regression formula too
+
+        return tuple or function:
+
+        tuple: (simple linear regression function model for dependent
+        variable, calculated simple linear regression formula model)
+
+        function: simple linear regression model function for dependent
+        variable
+        """
+        def predict(x1):
+            return a + b * x1
+        b = ((len(x) * np.sum(x*y) - np.sum(x) * np.sum(y)) /
+             (len(x) * np.sum(x*x) - np.sum(x) * np.sum(x)))
+        a = (np.sum(y) - b *np.sum(x)) / len(x)
+        if returnFormula:
+            return predict, "y = {a:.5f} + {b:.5f}*x".format(a=a, b=b)
+        return predict
+
+    def _drawSimpleLinRegLine(self, xdata, ydata):
+        """Draw simple regression line
+
+        :param list xdata: x axis data
+        :param list xdata: y axis data
+
+        return None
+        """
+        predict, regFormula = self._calcSimpleLinReg(
+            x=np.array(xdata), y=np.array(ydata),
+            returnFormula=True)
+
+        r2 = "r\u00B2 = {:.5f}".format(
+            np.corrcoef(np.array(xdata), np.array(ydata))[0, 1]**2)
+        self.plots.append(
+            self.axes2d.plot(
+                xdata,
+                predict(x1=np.array(xdata)),
+                color=LINEAR_REG_LINE_COLOR,
+                label="{reg}, {r2}".format(reg=regFormula, r2=r2))[0])
+
+        print(regFormula)
+        import platform
+        if platform.system() == 'Windows':
+            print(' ='.join(['r2'] + r2.split('=')[1:]))
+        else:
+            print(r2)
+
     def drawR(self):
         ycsv = []
         xcsv = []
@@ -688,6 +755,10 @@ class TplotFrame(wx.Frame):
             self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
                                                color=color,
                                                label=self.plotNameListR[i])[0])
+
+            if self.linRegRaster.IsChecked():
+                self._drawSimpleLinRegLine(xdata=xdata, ydata=ydata)
+
             if self.csvpath:
                 ycsv.append(ydata)
 
@@ -740,6 +811,10 @@ class TplotFrame(wx.Frame):
                     marker='o',
                     color=color,
                     label=labelname)[0])
+
+            if self.linRegVector.IsChecked():
+                self._drawSimpleLinRegLine(xdata=xdata, ydata=ydata)
+
             if self.csvpath:
                 ycsv.append(ydata)
 
@@ -780,6 +855,10 @@ class TplotFrame(wx.Frame):
 
             self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
                                                color=color, label=name)[0])
+
+            if self.linRegVector.IsChecked():
+                self._drawSimpleLinRegLine(xdata=xdata, ydata=ydata)
+
             if self.csvpath:
                 ycsv.append(ydata)
 

+ 13 - 3
gui/wxpython/tplot/g.gui.tplot.html

@@ -10,12 +10,22 @@ pair, in one or more temporal datasets (strds, stvds, str3ds).
 Supported features:
 <ul>
   <li>temporal datasets with interval/point and absolute/relative time,</li>
+  <li>show simple linear regression model line with calculated formula
+    <pre>y = a + b*x (y is dependent variable, a is intercept, b is slope, x is explanatory variable)</pre> and
+    <pre>r-squared (parameter of goodness-of-fit measure for linear regression model)</pre></li>
   <li>pop-up annotations with values information,</li>
   <li>query and plot multiple points via the command line,</li>
   <li>zoom and pan,</li>
   <li>change labels to x and y axes,</li>
   <li>add title to the plot, and</li>
-  <li>export the time series values to a CSV file.</li>
+  <li>export the time series values to a CSV file (x axis data has date time string format,
+    if you want to use for calculating simple regression model in the
+    <a href="https://www.r-project.org/">R environment<a>,
+    <a href="https://www.libreoffice.org/">LibreOffice</a>
+    etc., you will obtain a different calculated formula <pre>y = a + b*x</pre>
+    because these software packages use a reference date other than
+    the UNIX Epoch time (00:00:00 UTC on 1 January 1970)).
+  </li>
 </ul>
 
 <div align="center" style="margin: 10px">
@@ -37,7 +47,7 @@ showed below.
 </div>
 
 To export the time series data to a text file (date and data values), type a
-name for the file and click <em>Draw</em> again. Optionally, set the 
+name for the file and click <em>Draw</em> again. Optionally, set the
 check-box to print headers, as well.
 
 <div align="center" style="margin: 10px">
@@ -49,7 +59,7 @@ check-box to print headers, as well.
 
 <h2>NOTES</h2>
 
-<em>g.gui.tplot</em> requires the Python plotting library 
+<em>g.gui.tplot</em> requires the Python plotting library
 <a href="http://matplotlib.org/">Matplotlib</a>.
 
 <h2>SEE ALSO</h2>

+ 8 - 0
gui/wxpython/tplot/g.gui.tplot.py

@@ -33,6 +33,11 @@
 #% description: Set the header of CSV file, to be used with csv option
 #%end
 
+#%flag
+#% key: l
+#% description: Show simple linear regression model line
+#%end
+
 #%option G_OPT_STVDS_INPUTS
 #% key: stvds
 #% required: no
@@ -158,6 +163,9 @@ def main():
         csvfile = options['csv']
     app = wx.App()
     frame = TplotFrame(parent=None, giface=StandaloneGrassInterface())
+    if flags['l']:
+        frame.linRegRaster.SetValue(state=True)
+        frame.linRegVector.SetValue(state=True)
     frame.SetDatasets(rasters, vectors, coords, cats, attr, title, xlabel,
                       ylabel, csvfile, flags['h'], gscript .overwrite)
     if output: