Forked from HYCAR-Hydro / airGR
Source project has a limited visibility.
FigUncertaintySources.py 7.62 KiB
"""
QRame
Copyright (C) 2023  INRAE

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

from PyQt5 import QtCore
import numpy as np
import pandas as pd

class FigUncertaintySources(object):
    """Class to plot uncertainty sources distribution graph.

        Attributes
        ----------
        canvas: MplCanvas
            Object of MplCanvas a FigureCanvas
        fig: Object
            Figure object of the canvas
        _translate: QCoreApplication.translate object
            Save words which need to be translated
        """

    def __init__(self, canvas):
        """Initialize object using the specified canvas.

        Parameters
        ----------
        canvas: MplCanvas
            Object of MplCanvas
        """

        # Initialize attributes
        self.canvas = canvas
        self.fig = canvas.fig
        self._translate = QtCore.QCoreApplication.translate

    def create(self, mean_selected_meas, no_moving_bed=False, column_grouped=None,
               column_sorted=None, ascending=True):
        """Create the axes and lines for the figure.

        Parameters
        ----------
        mean_selected_meas: pandas DataFrame
            Measurement results dataframe
        no_moving_bed: bool
            Specify if there is no moving bed observed
        column_grouped: str
            Columns name that could be used for grouped analysis
        column_sorted: str
            Columns name used to sort dataframe
        ascending: bool
            Specify if current data are sorted ascending or descending
        """
        # Clear the plot
        self.fig.clear()

        # Configure axis
        self.fig.ax = self.fig.add_subplot(1, 1, 1)

        # Set margins and padding for figure
        self.fig.ax.xaxis.label.set_fontsize(12)
        self.fig.ax.yaxis.label.set_fontsize(12)

        list_basic_color = ['#696969', '#808080', '#A9A9A9', '#00BFFF', '#0000FF', '#FF6666', '#FF0000', '#CC0000',
                            '#FF00FF', '#EE82EE', '#20B2AA', '#008B8B', '#00FFFF']

        # Uncertainty decomposition
        labels = [self._translate("Main", 'Systematic'), self._translate("Main", 'Compass'),
                  self._translate("Main", 'Moving-bed'), self._translate("Main", 'Measured'),
                  self._translate("Main", 'Nb of ens.'), self._translate("Main", 'Invalid boat'),
                  self._translate("Main", 'Invalid depth'), self._translate("Main", 'Invalid water'),
                  self._translate("Main", 'Top'), self._translate("Main", 'Bottom'),
                  self._translate("Main", 'Left'), self._translate("Main", 'Right'),
                  self._translate("Main", 'Coeff. of var.')]

        col_name = ['tr_oursin_u_syst', 'tr_oursin_u_compass', 'tr_oursin_u_movbed', 'tr_oursin_u_meas',
                    'tr_oursin_u_ens', 'tr_oursin_u_boat', 'tr_oursin_u_depth', 'tr_oursin_u_water', 'tr_oursin_u_top',
                    'tr_oursin_u_bot', 'tr_oursin_u_left', 'tr_oursin_u_right', 'tr_oursin_u_cov', 'meas_ntransects',
                    'meas_oursin_95']

        col_name_2 = ['o_2_syst', 'o_2_compass', 'o_2_movbed', 'o_2_meas', 'o_2_ens', 'o_2_badb', 'o_2_badd',
                      'o_2_badw', 'o_2_top', 'o_2_bot', 'o_2_left', 'o_2_rght', 'o_2_cov']

        if no_moving_bed:
            mean_selected_meas['tr_oursin_u_movbed'] = 0.5

        # Variance
        for i in range(len(col_name_2)):
            mean_selected_meas[col_name_2[i]] = mean_selected_meas[col_name[i]] ** 2

        # Random uncertainty sources
        mean_selected_meas['o_2_meas'] = mean_selected_meas.o_2_meas / mean_selected_meas.meas_ntransects
        mean_selected_meas['o_2_cov'] = mean_selected_meas.o_2_cov / mean_selected_meas.meas_ntransects

        if column_grouped is not None:
            # Group data
            mean_selected_meas[column_grouped] = mean_selected_meas[column_grouped].fillna(
                self._translate("Main", 'Unknown'))
            grouped_meas = mean_selected_meas.groupby([column_grouped]).median()
            if column_sorted is not None:
                if mean_selected_meas.dtypes[column_sorted] in (float, int):
                    grouped_meas = mean_selected_meas.groupby([column_grouped]).median()
                else:
                    grouped_str = mean_selected_meas.groupby([column_grouped]).agg({
                        column_sorted: lambda x: x.value_counts().index[0]})
                    grouped_meas = pd.concat([grouped_meas, grouped_str], axis=1)

                grouped_meas.index.names = ['index']
                grouped_meas = grouped_meas.sort_values(column_sorted, ascending=ascending)

            mean_selected_meas = grouped_meas

            # Plot empirical uncertainty bar by group
            mean_oursin = mean_selected_meas['meas_oursin_95']
            mean_empirical = mean_selected_meas[['U_Q_n', 'U_Q_n_min', 'U_Q_n_max']]
            p1 = self.fig.ax.bar(np.arange(len(mean_empirical)), mean_empirical['U_Q_n'],
                            # yerr=[mean_empirical['U_Q_n']-mean_empirical['U_Q_n_min'],
                            #       mean_empirical['U_Q_n']+mean_empirical['U_Q_n_max']],
                            align='center', color='gray', edgecolor='black', ecolor='black',
                            linewidth=2.5, alpha=0.3, width=0.6, capsize=10)
        else:
            sorted_names = list(mean_selected_meas.index)
            mean_oursin = mean_selected_meas.groupby('meas_name')['meas_oursin_95'].mean()
            mean_oursin = mean_oursin.reindex(sorted_names)

        # Plot uncertainty sources bar
        mean_selected_meas['Sum_n'] = mean_selected_meas[col_name_2].sum(axis=1)
        mean_plot = mean_selected_meas[col_name_2].div(mean_selected_meas['Sum_n'].values, axis=0).mul(
            mean_oursin.values, axis=0)
        my_xticks = [l for l in mean_plot.index]
        if len(mean_plot) > 0:
            mean_plot.plot(kind='bar', stacked=True, ax=self.fig.ax, legend=False, color=list_basic_color, zorder=1,
                           width=0.4)

        # Legend
        handles, _ = self.fig.ax.get_legend_handles_labels()
        handles_top = list(reversed(handles))
        labels_top = list(reversed(labels))

        if column_grouped is not None:
            handles_top.append(p1)
            labels_top.append(self._translate("Main", 'Empirical uncertainty'))

        self.fig.ax.legend(handles_top,
                           labels_top,
                           loc='upper left',
                           bbox_to_anchor=(1, 1),
                           labelspacing=0.25,
                           fontsize=14,
                           fancybox=True,
                           shadow=True)

        self.fig.ax.set_ylabel(self._translate("Main", 'Uncertainty') + ' (%)', fontsize=11)
        self.fig.ax.set_xlabel(None)
        if sum(len(s) for s in my_xticks[1:]) < 80:
            self.fig.ax.set_xticklabels(my_xticks, rotation=0, fontsize=10)
        else:
            self.fig.ax.set_xticklabels(my_xticks, rotation=80, fontsize=10, ha='right')

        self.fig.ax.grid(linestyle='--')
        # self.fig.tight_layout()
        # self.canvas.draw()