diff --git a/glidertest/plots.py b/glidertest/plots.py index beab328..ea83fa0 100644 --- a/glidertest/plots.py +++ b/glidertest/plots.py @@ -19,19 +19,21 @@ -def plot_updown_bias(df: pd.DataFrame, ax: plt.Axes = None, xlabel='Temperature [C]', **kw: dict, ) -> tuple({plt.Figure, plt.Axes}): +def plot_updown_bias(ds: xr.Dataset, var='TEMP', v_res=1, ax: plt.Axes = None, **kw: dict, ) -> tuple({plt.Figure, plt.Axes}): """ This function can be used to plot the up and downcast differences computed with the updown_bias function Parameters ---------- - df: pandas dataframe containing dc (Dive - Climb average), cd (Climb - Dive average) and depth + ds: xarray on OG1 format containing at least time, depth, latitude, longitude and the selected variable. + Data should not be gridded. + var: Selected variable + v_res: Vertical resolution for the gridding ax: axis to plot the data - xlabel: label for the x-axis Returns ------- - A line plot comparing the day and night average over depth for the selected day + A line plot comparing the climb and dive average over depth Original author ---------------- @@ -41,27 +43,31 @@ def plot_updown_bias(df: pd.DataFrame, ax: plt.Axes = None, xlabel='Temperature if ax is None: fig, ax = plt.subplots() third_width = fig.get_size_inches()[0] / 3.11 - fig.set_size_inches(third_width, third_width *1.1) + fig.set_size_inches(third_width, third_width * 1.1) force_plot = True else: fig = plt.gcf() force_plot = False + df = tools.quant_updown_bias(ds, var=var, v_res=v_res) if not all(hasattr(df, attr) for attr in ['dc', 'depth']): - ax.text(0.5, 0.55, xlabel, va='center', ha='center', transform=ax.transAxes, bbox=dict(facecolor='white', alpha=0.5, edgecolor='none')) - ax.text(0.5, 0.45, 'data unavailable', va='center', ha='center', transform=ax.transAxes, bbox=dict(facecolor='white', alpha=0.5, edgecolor='none')) + ax.text(0.5, 0.55, ds[var].standard_name, va='center', ha='center', transform=ax.transAxes, + bbox=dict(facecolor='white', alpha=0.5, edgecolor='none')) + ax.text(0.5, 0.45, 'data unavailable', va='center', ha='center', transform=ax.transAxes, + bbox=dict(facecolor='white', alpha=0.5, edgecolor='none')) else: - ax.plot(df.dc, df.depth, label='Dive-Climb') - ax.plot(df.cd, df.depth, label='Climb-Dive') + ax.plot(df.dc, df.depth, label='Dive-Climb', **kw) + ax.plot(df.cd, df.depth, label='Climb-Dive', **kw) ax.legend(loc=3) lims = np.abs(df.dc) ax.set_xlim(-np.nanpercentile(lims, 99.5), np.nanpercentile(lims, 99.5)) ax.set_ylim(df.depth.max() + 1, -df.depth.max() / 30) - ax.set_xlabel(xlabel) + ax.set_xlabel(f'{utilities.plotting_labels(var)} ({utilities.plotting_units(ds,var)})') + ax.set_ylabel(f'Depth (m)') ax.grid() if force_plot: plt.show() - return fig, ax + return fig, ax def plot_basic_vars(ds: xr.Dataset, v_res=1, start_prof=0, end_prof=-1): """ @@ -113,16 +119,16 @@ def plot_basic_vars(ds: xr.Dataset, v_res=1, start_prof=0, end_prof=-1): ax1.plot(np.nanmean(salG, axis=0), depthG[0, :], c='red') ax2.plot(np.nanmean(denG, axis=0), depthG[0, :], c='black') - ax[0].set(xlabel=f'Temperature [C]', ylabel='Depth (m)') + ax[0].set(ylabel='Depth (m)', xlabel=f'{utilities.plotting_labels("TEMP")} \n({utilities.plotting_units(ds,"TEMP")})') ax[0].tick_params(axis='x', colors='blue') ax[0].xaxis.label.set_color('blue') ax1.spines['bottom'].set_color('blue') - ax1.set(xlabel=f'Salinity [PSU]') + ax1.set(xlabel=f'{utilities.plotting_labels("PSAL")} ({utilities.plotting_units(ds,"PSAL")})') ax1.xaxis.label.set_color('red') ax1.spines['top'].set_color('red') ax1.tick_params(axis='x', colors='red') ax2.spines['bottom'].set_color('black') - ax2.set(xlabel=f'Density [kg m-3]') + ax2.set(xlabel=f'{utilities.plotting_labels("DENSITY")} ({utilities.plotting_units(ds,"DENSITY")})') ax2.xaxis.label.set_color('black') ax2.spines['top'].set_color('black') ax2.tick_params(axis='x', colors='black') @@ -136,7 +142,7 @@ def plot_basic_vars(ds: xr.Dataset, v_res=1, start_prof=0, end_prof=-1): chlaG = chlaG[start_prof:end_prof, :] ax2_1 = ax[1].twiny() ax2_1.plot(np.nanmean(chlaG, axis=0), depthG[0, :], c='green') - ax2_1.set(xlabel=f'Chlorophyll-a [mg m-3]') + ax2_1.set(xlabel=f'{utilities.plotting_labels("CHLA")} ({utilities.plotting_units(ds,"CHLA")})') ax2_1.xaxis.label.set_color('green') ax2_1.spines['top'].set_color('green') ax2_1.tick_params(axis='x', colors='green') @@ -147,7 +153,7 @@ def plot_basic_vars(ds: xr.Dataset, v_res=1, start_prof=0, end_prof=-1): oxyG, profG, depthG = utilities.construct_2dgrid(ds.PROFILE_NUMBER, ds.DEPTH, ds.DOXY, p, z) oxyG = oxyG[start_prof:end_prof, :] ax[1].plot(np.nanmean(oxyG, axis=0), depthG[0, :], c='orange') - ax[1].set(xlabel=f'Oxygen [mmol m-3]') + ax[1].set(xlabel=f'{utilities.plotting_labels("DOXY")} \n({utilities.plotting_units(ds,"DOXY")})') ax[1].xaxis.label.set_color('orange') ax[1].spines['top'].set_color('orange') ax[1].tick_params(axis='x', colors='orange') @@ -212,7 +218,7 @@ def process_optics_assess(ds, var='CHLA'): ax.grid() ax.set(ylim=(np.nanpercentile(bottom_opt_data, 0.5), np.nanpercentile(bottom_opt_data, 99.5)), xlabel='Measurements', - ylabel=var) + ylabel=f'{utilities.plotting_labels(var)} ({utilities.plotting_units(ds,var)})') plt.show() percentage_change = (((slope * len(bottom_opt_data) + intercept) - intercept) / abs(intercept)) * 100 @@ -227,18 +233,16 @@ def process_optics_assess(ds, var='CHLA'): return ax -def plot_daynight_avg(day: pd.DataFrame, night: pd.DataFrame, ax: plt.Axes = None, sel_day=None, - xlabel='Chlorophyll [mg m-3]', **kw: dict, ) -> tuple({plt.Figure, plt.Axes}): +def plot_daynight_avg(ds,var='PSAL', ax: plt.Axes = None, sel_day=None, **kw: dict, ) -> tuple({plt.Figure, plt.Axes}): """ This function can be used to plot the day and night averages computed with the day_night_avg function Parameters ---------- - day: pandas dataframe containing the day averages - night: pandas dataframe containing the night averages + ds: xarray dataset in OG1 format containing at least time, depth and the selected variable + var: name of the selected variable ax: axis to plot the data sel_day: selected day to plot. Defaults to the median day - xlabel: label for the x-axis Returns ------- @@ -249,6 +253,7 @@ def plot_daynight_avg(day: pd.DataFrame, night: pd.DataFrame, ax: plt.Axes = Non Chiara Monforte """ + day, night = tools.compute_daynight_avg(ds, sel_var=var) if not sel_day: dates = list(day.date.dropna().values) + list(night.date.dropna().values) dates.sort() @@ -260,6 +265,7 @@ def plot_daynight_avg(day: pd.DataFrame, night: pd.DataFrame, ax: plt.Axes = Non else: fig = plt.gcf() force_plot = False + ax.plot(night.where(night.date == sel_day).dropna().dat, night.where(night.date == sel_day).dropna().depth, label='Night time average') ax.plot(day.where(day.date == sel_day).dropna().dat, day.where(day.date == sel_day).dropna().depth, @@ -267,7 +273,7 @@ def plot_daynight_avg(day: pd.DataFrame, night: pd.DataFrame, ax: plt.Axes = Non ax.legend() ax.invert_yaxis() ax.grid() - ax.set(xlabel=xlabel, ylabel='Depth [m]') + ax.set(xlabel=f'{utilities.plotting_labels(var)} ({utilities.plotting_units(ds,var)})', ylabel='Depth (m)') ax.set_title(sel_day) if force_plot: plt.show() @@ -339,13 +345,13 @@ def plot_quench_assess(ds: xr.Dataset, sel_var: str, ax: plt.Axes = None, start_ ax.axvline(np.unique(n), c='blue') for m in np.unique(sunrise): ax.axvline(np.unique(m), c='orange') - ax.set_ylabel('Depth [m]') + ax.set_ylabel('Depth (m)') # Set x-tick labels based on duration of the selection # Could pop out as a utility plotting function? utilities._time_axis_formatter(ax, ds_sel, format_x_axis=True) - plt.colorbar(c, label=f'{sel_var} [{ds[sel_var].units}]') + plt.colorbar(c, label=f'{utilities.plotting_labels(sel_var)} ({utilities.plotting_units(ds,sel_var)})') plt.show() return fig, ax @@ -383,10 +389,10 @@ def check_temporal_drift(ds: xr.Dataset, var: str, ax: plt.Axes = None, **kw: di # Set x-tick labels based on duration of the selection utilities._time_axis_formatter(ax[0], ds, format_x_axis=True) - ax[0].set(ylim=(np.nanpercentile(ds[var], 0.01), np.nanpercentile(ds[var], 99.99)), ylabel=var) + ax[0].set(ylim=(np.nanpercentile(ds[var], 0.01), np.nanpercentile(ds[var], 99.99)), ylabel=f'{utilities.plotting_labels(var)} ({utilities.plotting_units(ds,var)})') c = ax[1].scatter(ds[var], ds.DEPTH, c=mdates.date2num(ds.TIME), s=10) - ax[1].set(xlim=(np.nanpercentile(ds[var], 0.01), np.nanpercentile(ds[var], 99.99)), ylabel='Depth (m)', xlabel=var) + ax[1].set(xlim=(np.nanpercentile(ds[var], 0.01), np.nanpercentile(ds[var], 99.99)), ylabel='Depth (m)', xlabel=f'{utilities.plotting_labels(var)} ({utilities.plotting_units(ds,var)})') ax[1].invert_yaxis() [a.grid() for a in ax] @@ -496,8 +502,8 @@ def plot_glider_track(ds: xr.Dataset, ax: plt.Axes = None, **kw: dict) -> tuple( ax.add_feature(cfeature.OCEAN) ax.add_feature(cfeature.COASTLINE) - ax.set_xlabel('Longitude') - ax.set_ylabel('Latitude') + ax.set_xlabel(f'Longitude') + ax.set_ylabel(f'Latitude') ax.set_title('Glider Track') gl = ax.gridlines(draw_labels=True, color='black', alpha=0.5, linestyle='--') gl.top_labels = False @@ -694,7 +700,7 @@ def plot_sampling_period(ds: xr.Dataset, ax: plt.Axes = None, variable='TEMP'): ax.set_xlabel('Time Spacing (s)') if variable=='TEMP': ax.set_ylabel('Frequency') ax.set_title('Histogram of Sampling Period' + '\n' + - 'for ' + variable + ', \n' + + 'for ' + utilities.plotting_labels(variable) + ', \n' + 'valid values: {:.1f}'.format(100*(np.sum(nonan)/ds.TIME.values.shape[0]))+'%') annotation_text = ( @@ -1083,7 +1089,7 @@ def plot_hysteresis(ds, var='DOXY', v_res=1, perct_err=2, ax=None): [a.grid() for a in ax] [a.invert_yaxis() for a in ax] ax[0].set_ylabel('Depth (m)') - ax[0].set_xlabel(f'{var} concentration $=mean$ \n({ds[var].units})') + ax[0].set_xlabel(f'{utilities.plotting_labels(var)} $=mean$ \n({utilities.plotting_units(ds, var)})') ax[1].set_xlabel(f'Absolute difference = |$\Delta$| \n({ds[var].units})') ax[2].set_xlabel('Percent error = |$\Delta$|/$mean$ \n(%)') for ax1 in ax[:-1]: @@ -1093,7 +1099,7 @@ def plot_hysteresis(ds, var='DOXY', v_res=1, perct_err=2, ax=None): vmax=np.nanpercentile(np.diff(varG, axis=0), 99.5), cmap='seismic') plt.colorbar(c, ax=ax[3], label=f'Difference dive-climb \n({ds[var].units})', fraction=0.05) ax[3].set(ylabel='Depth (m)', xlabel='Profile number') - fig.suptitle(var, y=.98) + fig.suptitle(utilities.plotting_labels(var), y=.98) if force_plot: plt.show() return fig, ax @@ -1108,18 +1114,14 @@ def plot_outlier_duration(ds: xr.Dataset, rolling_mean: pd.Series, overtime, std Parameters ---------- ds : An xarray object containing at least the variables 'TIME', 'DEPTH', and 'PROFILE_NUMBER'. - These are used to compute the profile durations and plot depth profiles. - + These are used to compute the profile durations and plot depth profiles. rolling_mean : A series representing the rolling mean of the profile durations, - which is used to highlight outliers based on standard deviation. - - overtime : A list of profile numbers identified as having unusual durations. - These profiles are marked on the plot to highlight the outliers. - + which is used to highlight outliers based on standard deviation. + overtime : A list of profile numbers identified as having unusual durations. + These profiles are marked on the plot to highlight the outliers. std : float, optional, default 2 The number of standard deviations above and below the rolling mean that will be used to define the range of "normal" durations. Profiles outside this range are considered outliers. - ax :The axes object on which to plot the results. If not provided, a new figure with two subplots is created. Returns @@ -1185,13 +1187,9 @@ def plot_global_range(ds, var='DOXY', min_val=-5, max_val=600, ax=None): Parameters ---------- ds : The xarray dataset containing the variable (`var`) to be plotted. - - var : The name of the variable to plot. Default is 'DOXY'. - - min_val : The minimum value of the global range to highlight on the plot. Default is -5. - - max_val : The maximum value of the global range to highlight on the plot. Default is 600. - + var : The name of the variable to plot. + min_val : The minimum value of the global range to highlight on the plot. + max_val : The maximum value of the global range to highlight on the plot. ax : matplotlib.axes.Axes, optional The axes on which to plot the histogram. If `None`, a new figure and axes are created. Default is `None`. @@ -1200,7 +1198,6 @@ def plot_global_range(ds, var='DOXY', min_val=-5, max_val=600, ax=None): ------- fig : matplotlib.figure.Figure The figure object containing the plot. - ax : matplotlib.axes.Axes The axes object containing the histogram plot. @@ -1219,7 +1216,7 @@ def plot_global_range(ds, var='DOXY', min_val=-5, max_val=600, ax=None): ax.hist(ds[var], bins=50) ax.axvline(min_val, c='r') ax.axvline(max_val, c='r') - ax.set(xlabel=f'{ds[var].long_name} ({ds[var].units})', ylabel='Frequency') + ax.set(xlabel=f'{utilities.plotting_labels(var)} ({utilities.plotting_units(ds,var)})', ylabel='Frequency') ax.set_title('Global range check') ax.grid() if force_plot: @@ -1237,17 +1234,13 @@ def plot_ioosqc(data, suspect_threshold=[25], fail_threshold=[50], title='', ax= ----------- data : The result from the IOOS_QC test. A sequence of numerical values representing the data points to be plotted. - suspect_threshold : A list containing one or two numerical values indicating the thresholds for suspect values. If one value is provided, it applies to both lower and upper bounds for suspect data points. If two values are provided, they define the lower and upper bounds for suspect values. - fail_threshold A list containing one or two numerical values indicating the thresholds for fail values. Similar to `suspect_threshold`, it can have one or two values to define the bounds for fail data points. - title : str, optional, default = '' The title to display at the top of the plot. - ax : matplotlib Axes object, optional, default = None If provided, the plot will be drawn on this existing Axes object. If None, a new figure and axis will be created. @@ -1299,7 +1292,7 @@ def plot_ioosqc(data, suspect_threshold=[25], fail_threshold=[50], title='', ax= ax2.set_yticklabels(a_2, fontsize=12) - ax.set_xlabel('Data Index') + ax.set_xlabel('Data index') ax.grid() ax.set_title(title) if force_plot: diff --git a/glidertest/utilities.py b/glidertest/utilities.py index 7289030..109f9e8 100644 --- a/glidertest/utilities.py +++ b/glidertest/utilities.py @@ -273,3 +273,94 @@ def calc_DEPTH_Z(ds): } return ds + +label_dict={ + "PSAL": { + "label": "Practical salinity", + "units": "PSU"}, + "TEMP": { + "label": "Temperature", + "units": "°C"}, + "DENSITY":{ + "label": "In situ density", + "units": "kg m⁻³" + }, + "DOXY": { + "label": "Dissolved oxygen", + "units": "mmol m⁻³" + }, + "SA":{ + "label": "Absolute salinity", + "units": "g kg⁻¹" + }, + "CHLA":{ + "label": "Chlorophyll", + "units": "mg m⁻³" + }, + "CNDC":{ + "label": "Conductivity", + "units": "mS cm⁻¹" + }, + "DPAR":{ + "label": "Irradiance PAR", + "units": "μE cm⁻² s⁻¹" + }, + "BBP700":{ + "label": "Red backscatter, b${bp}$(700)", + "units": "m⁻¹" + } +} + +def plotting_labels(var: str): + """ + Retrieves the label associated with a variable from a predefined dictionary. + + This function checks if the given variable `var` exists as a key in the `label_dict` dictionary. + If found, it returns the associated label from `label_dict`. If not, it returns the variable name itself as the label. + + Parameters + ---------- + var (str): The variable (key) whose label is to be retrieved. + + Returns: + ---------- + str: The label corresponding to the variable `var`. If the variable is not found in `label_dict`, + the function returns the variable name as the label. + + Original author: + ---------- + Chiara Monforte + """ + if var in label_dict: + label = f'{label_dict[var]["label"]}' + else: + label= f'{var}' + return label +def plotting_units(ds: xr.Dataset,var: str): + """ + Retrieves the units associated with a variable from a dataset or a predefined dictionary. + + This function checks if the given variable `var` exists as a key in the `label_dict` dictionary. + If found, it returns the associated units from `label_dict`. If not, it returns the units of the variable + from the dataset `ds` using the `var` key. + + Parameters + ---------- + ds (xarray.Dataset or similar): The dataset containing the variable `var`. + var (str): The variable (key) whose units are to be retrieved. + + Returns: + ---------- + str: The units corresponding to the variable `var`. If the variable is found in `label_dict`, + the associated units will be returned. If not, the function returns the units from `ds[var]`. + + Original author: + ---------- + Chiara Monforte + """ + if var in label_dict: + return f'{label_dict[var]["units"]}' + elif 'units' in ds[var].attrs: + return f'{ds[var].units}' + else: + return "" \ No newline at end of file diff --git a/notebooks/demo.ipynb b/notebooks/demo.ipynb index 98bdc8e..bb22af5 100644 --- a/notebooks/demo.ipynb +++ b/notebooks/demo.ipynb @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "429c3105", "metadata": {}, "outputs": [], @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "c336267e-b924-4bcf-937a-5f0c11857da2", "metadata": {}, "outputs": [], @@ -235,6 +235,17 @@ "See [OG1 description of phase](https://github.com/OceanGlidersCommunity/OG-format-user-manual/blob/main/vocabularyCollection/phase.md) for more information on `PHASE`." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a9a26c6-a71b-4dd2-aec3-9ac5a21dec14", + "metadata": {}, + "outputs": [], + "source": [ + "updown_psal = tools.quant_updown_bias(ds, var='PSAL', v_res=1)\n", + "updown_psal" + ] + }, { "cell_type": "code", "execution_count": null, @@ -243,11 +254,11 @@ "outputs": [], "source": [ "fig, ax = plt.subplots(1, 4, figsize=(15, 5))\n", - "plots.plot_updown_bias(tools.quant_updown_bias(ds, var='TEMP', v_res=1), ax[0], xlabel='Temperature [C]')\n", - "plots.plot_updown_bias(tools.quant_updown_bias(ds, var='PSAL', v_res=1), ax[1], xlabel='Salinity [PSU]')\n", - "plots.plot_updown_bias(tools.quant_updown_bias(ds, var='DOXY', v_res=1), ax[2], xlabel='Dissolved Oxygen [mmol m-3]')\n", - "plots.plot_updown_bias(tools.quant_updown_bias(ds, var='CHLA', v_res=1), ax[3], xlabel='Chlorophyll [mg m-3]')\n", - "ax[0].set_ylabel('Depth [m]')\n", + "plots.plot_updown_bias(ds, var='TEMP', v_res=1, ax=ax[0])\n", + "plots.plot_updown_bias(ds, var='PSAL', v_res=1, ax=ax[1])\n", + "plots.plot_updown_bias(ds, var='DOXY', v_res=1, ax=ax[2])\n", + "plots.plot_updown_bias(ds, var='CHLA', v_res=1, ax=ax[3])\n", + "\n", "plt.show()" ] }, @@ -285,7 +296,8 @@ "import gsw\n", "o2sol = gsw.O2sol(ds.PSAL, ds.TEMP, ds.PRES, ds.LATITUDE, ds.LONGITUDE)\n", "o2sat = 100 * ds.DOXY / o2sol\n", - "ds['DOXY_SAT']= o2sat" + "ds['DOXY_SAT']= o2sat\n", + "ds['DOXY_SAT']=ds['DOXY_SAT'].assign_attrs(units='%')\n" ] }, { @@ -340,7 +352,8 @@ " fail_threshold= 50,\n", " method = \"average\",)\n", "fig, ax = plt.subplots()\n", - "fig,ax = plots.plot_ioosqc(spike,suspect_threshold= [25], fail_threshold= [50], title='Spike test DOXY', ax=ax)" + "fig, ax = plots.plot_ioosqc(spike,suspect_threshold= [25], fail_threshold= [50], title='Spike test Dissolved oxygen', ax=ax)\n", + "plt.show()" ] }, { @@ -353,7 +366,7 @@ "#3) Stuck value test\n", "flat =qartod.flat_line_test(ds.DOXY,ds.TIME,1,2, 0.001)\n", "fig, ax = plt.subplots()\n", - "fig, ax = plots.plot_ioosqc(flat,suspect_threshold= [0.01], fail_threshold=[0.1], title='Flat test DOXY',ax=ax)\n", + "fig, ax = plots.plot_ioosqc(flat,suspect_threshold= [0.01], fail_threshold=[0.1], title='Flat test Dissolved oxygen',ax=ax)\n", "plt.close()\n", "\n", "stuck_data = np.where(flat>2) # When it is not good\n", @@ -391,8 +404,7 @@ "outputs": [], "source": [ "# Check any noise at surface during night time\n", - "dayOS, nightOS = tools.compute_daynight_avg(ds, sel_var='DOXY_SAT')\n", - "plots.plot_daynight_avg( dayOS, nightOS, xlabel='Oxygen saturation')" + "plots.plot_daynight_avg(ds, var='DOXY_SAT')" ] }, { @@ -419,11 +431,11 @@ "plt.close()\n", "\n", "#Stuck value test\n", - "plots.plot_ioosqc(flat,suspect_threshold= [0.01], fail_threshold=[0.1], title='Flat test DOXY',ax=ax[2])\n", + "plots.plot_ioosqc(flat,suspect_threshold= [0.01], fail_threshold=[0.1], title='Flat test Dissolved oxygen',ax=ax[2])\n", "plt.close()\n", "\n", "#Spike test\n", - "plots.plot_ioosqc(spike,suspect_threshold= [25], fail_threshold= [50], title='Spike test DOXY',ax=ax[4])\n", + "plots.plot_ioosqc(spike,suspect_threshold= [25], fail_threshold= [50], title='Spike test Dissolved oxygen',ax=ax[4])\n", "plt.close()\n", "\n", "# Check for issue due to biofouliing\n", @@ -434,7 +446,7 @@ "ax[3].set_xlim(75,120)\n", "\n", "#Check for noise during the night\n", - "plots.plot_daynight_avg( dayOS, nightOS, ax=ax[5], xlabel='Oxygen saturation')\n", + "plots.plot_daynight_avg(ds, var='DOXY_SAT', ax=ax[5])\n", "plt.close()\n", "ax[5].set_ylim(20,-3)\n", "ax[5].set_xlim(80,120)\n", @@ -530,8 +542,7 @@ "source": [ "# Compute day and night average for chlorophylla and temeparture\n", "dayT, nightT = tools.compute_daynight_avg(ds, sel_var='TEMP')\n", - "dayS, nightS = tools.compute_daynight_avg(ds, sel_var='PSAL')\n", - "dayC, nightC = tools.compute_daynight_avg(ds, sel_var='CHLA')" + "dayT" ] }, { @@ -543,9 +554,9 @@ "source": [ "fig, ax = plt.subplots(1, 3, figsize=(15, 5))\n", "\n", - "plots.plot_daynight_avg( dayT, nightT, ax[0], xlabel='Temperature [C]')\n", - "plots.plot_daynight_avg( dayS, nightS, ax[1], xlabel='Salinity [PSU]')\n", - "plots.plot_daynight_avg( dayC, nightC, ax[2], xlabel='Chlorophyll [mg m-3]')\n", + "plots.plot_daynight_avg(ds, var='TEMP', ax=ax[0])\n", + "plots.plot_daynight_avg(ds, var='PSAL', ax=ax[1])\n", + "plots.plot_daynight_avg(ds, var='CHLA', ax=ax[2])\n", "plt.show()" ] }, @@ -600,7 +611,7 @@ "metadata": {}, "outputs": [], "source": [ - "plots.process_optics_assess(ds, var='BBP700');" + "plots.process_optics_assess(ds, var='BBP700')" ] }, { @@ -619,7 +630,7 @@ "outputs": [], "source": [ "fig, ax = plt.subplots(1, 1, figsize=(5, 5))\n", - "plots.plot_updown_bias(tools.quant_updown_bias(ds, var='DPAR', v_res=1), ax, xlabel='Irradiance')\n", + "plots.plot_updown_bias(ds, var='DPAR', v_res=1, ax=ax)\n", "plt.show()" ] }, @@ -753,7 +764,7 @@ "metadata": {}, "outputs": [], "source": [ - "figure = plt.figure(figsize=(18,10))\n", + "figure = plt.figure(figsize=(22,10))\n", "\n", "ax1=plt.subplot(5, 5, 1)\n", "ax2=plt.subplot(5, 5, 2)\n", @@ -815,8 +826,6 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "\n", "figts, ax_ts = plots.plot_ts(ds, axs=[ax3, ax4, ax5, ax6,ax7,ax8])\n", "plt.close()\n", "grid, ax_grid = plots.plot_grid_spacing(ds, ax=[ax1, ax2])\n", @@ -901,11 +910,19 @@ "\n", "figure" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd6390bc-d1cb-42c3-bdb8-3c67bc889a32", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "glidertest", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -919,7 +936,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.8" + "version": "3.11.8" } }, "nbformat": 4, diff --git a/tests/test_plots.py b/tests/test_plots.py index e482fa4..990b068 100644 --- a/tests/test_plots.py +++ b/tests/test_plots.py @@ -1,68 +1,69 @@ import pytest -from glidertest import fetchers, tools, plots +from glidertest import fetchers, tools, plots, utilities import matplotlib.pyplot as plt import numpy as np import matplotlib from ioos_qc import qartod + matplotlib.use('agg') # use agg backend to prevent creating plot windows during tests + def test_plots(start_prof=0, end_prof=100): ds = fetchers.load_sample_dataset() ds = ds.drop_vars(['DENSITY']) - fig, ax = plots.plot_basic_vars(ds,start_prof=start_prof, end_prof=end_prof) + fig, ax = plots.plot_basic_vars(ds, start_prof=start_prof, end_prof=end_prof) assert ax[0].get_ylabel() == 'Depth (m)' - assert ax[0].get_xlabel() == f'Temperature [C]' + assert ax[0].get_xlabel() == 'Temperature \n(°C)' -def test_up_down_bias(v_res=1, xlabel='Salinity'): +def test_up_down_bias(v_res=1): ds = fetchers.load_sample_dataset() fig, ax = plt.subplots() + plots.plot_updown_bias(ds, var='PSAL', v_res=1, ax=ax) df = tools.quant_updown_bias(ds, var='PSAL', v_res=v_res) - plots.plot_updown_bias(df, ax, xlabel=xlabel) lims = np.abs(df.dc) assert ax.get_xlim() == (-np.nanpercentile(lims, 99.5), np.nanpercentile(lims, 99.5)) assert ax.get_ylim() == (df.depth.max() + 1, -df.depth.max() / 30) - assert ax.get_xlabel() == xlabel + assert ax.get_xlabel() == 'Practical salinity (PSU)' # check without passing axis - new_fig, new_ax = plots.plot_updown_bias(df, xlabel=xlabel) + new_fig, new_ax = plots.plot_updown_bias(ds, var='PSAL', v_res=1) assert new_ax.get_xlim() == (-np.nanpercentile(lims, 99.5), np.nanpercentile(lims, 99.5)) assert new_ax.get_ylim() == (df.depth.max() + 1, -df.depth.max() / 30) - assert new_ax.get_xlabel() == xlabel + assert new_ax.get_xlabel() == 'Practical salinity (PSU)' def test_chl(var1='CHLA', var2='BBP700'): ds = fetchers.load_sample_dataset() ax = plots.process_optics_assess(ds, var=var1) - assert ax.get_ylabel() == var1 + assert ax.get_ylabel() == 'Chlorophyll (mg m⁻³)' ax = plots.process_optics_assess(ds, var=var2) - assert ax.get_ylabel() == var2 + assert ax.get_ylabel() == 'Red backscatter, b${bp}$(700) (m⁻¹)' with pytest.raises(KeyError) as e: plots.process_optics_assess(ds, var='nonexistent_variable') -def test_quench_sequence(xlabel='Temperature [C]',ylim=45): +def test_quench_sequence(ylim=45): ds = fetchers.load_sample_dataset() if not "TIME" in ds.indexes.keys(): ds = ds.set_xindex('TIME') fig, ax = plt.subplots() - plots.plot_quench_assess(ds, 'CHLA', ax,ylim=ylim) - assert ax.get_ylabel() == 'Depth [m]' + plots.plot_quench_assess(ds, 'CHLA', ax, ylim=ylim) + assert ax.get_ylabel() == 'Depth (m)' assert ax.get_ylim() == (ylim, -ylim / 30) - - dayT, nightT = tools.compute_daynight_avg(ds, sel_var='TEMP') - fig, ax = plots.plot_daynight_avg(dayT, nightT,xlabel=xlabel) - assert ax.get_ylabel() == 'Depth [m]' - assert ax.get_xlabel() == xlabel + + fig, ax = plots.plot_daynight_avg(ds, var='TEMP') + assert ax.get_ylabel() == 'Depth (m)' + assert ax.get_xlabel() == 'Temperature (°C)' def test_temporal_drift(var='DOXY'): ds = fetchers.load_sample_dataset() fig, ax = plt.subplots(1, 2) - plots.check_temporal_drift(ds,var, ax) + plots.check_temporal_drift(ds, var, ax) assert ax[1].get_ylabel() == 'Depth (m)' - assert ax[0].get_ylabel() == var + assert ax[0].get_ylabel() == f'{utilities.plotting_labels(var)} ({utilities.plotting_units(ds, var)})' assert ax[1].get_xlim() == (np.nanpercentile(ds[var], 0.01), np.nanpercentile(ds[var], 99.99)) - plots.check_temporal_drift(ds,'CHLA') + plots.check_temporal_drift(ds, 'CHLA') def test_profile_check(): @@ -73,11 +74,12 @@ def test_profile_check(): assert ax[1].get_ylabel() == 'Depth (m)' duration = tools.compute_prof_duration(ds) rolling_mean, overtime = tools.find_outlier_duration(duration, rolling=20, std=2) - fig, ax = plots.plot_outlier_duration(ds, rolling_mean, overtime, std = 2) + fig, ax = plots.plot_outlier_duration(ds, rolling_mean, overtime, std=2) assert ax[0].get_ylabel() == 'Profile duration (min)' assert ax[0].get_xlabel() == 'Profile number' assert ax[1].get_ylabel() == 'Depth (m)' + def test_basic_statistics(): ds = fetchers.load_sample_dataset() plots.plot_glider_track(ds) @@ -92,8 +94,8 @@ def test_vert_vel(): plots.plot_vertical_speeds_with_histograms(ds_sg014) ds_dives = ds_sg014.sel(N_MEASUREMENTS=ds_sg014.PHASE == 2) ds_climbs = ds_sg014.sel(N_MEASUREMENTS=ds_sg014.PHASE == 1) - ds_out_dives = tools.quant_binavg(ds_dives, var = 'VERT_CURR_MODEL', dz=10) - ds_out_climbs = tools.quant_binavg(ds_climbs, var = 'VERT_CURR_MODEL', dz=10) + ds_out_dives = tools.quant_binavg(ds_dives, var='VERT_CURR_MODEL', dz=10) + ds_out_climbs = tools.quant_binavg(ds_climbs, var='VERT_CURR_MODEL', dz=10) plots.plot_combined_velocity_profiles(ds_out_dives, ds_out_climbs) # extra tests for ramsey calculations of DEPTH_Z ds_climbs = ds_climbs.drop_vars(['DEPTH_Z']) @@ -101,21 +103,25 @@ def test_vert_vel(): ds_climbs = ds_climbs.drop_vars(['LATITUDE']) with pytest.raises(KeyError) as e: tools.quant_binavg(ds_climbs, var='VERT_CURR_MODEL', dz=10) + + def test_hyst_plot(var='DOXY'): ds = fetchers.load_sample_dataset() fig, ax = plots.plot_hysteresis(ds, var=var, v_res=1, perct_err=2, ax=None) assert ax[3].get_ylabel() == 'Depth (m)' assert ax[0].get_ylabel() == 'Depth (m)' + def test_sop(): ds = fetchers.load_sample_dataset() plots.plot_global_range(ds, var='DOXY', min_val=-5, max_val=600, ax=None) - spike = qartod.spike_test(ds.DOXY,suspect_threshold=25,fail_threshold=50, method="average", ) + spike = qartod.spike_test(ds.DOXY, suspect_threshold=25, fail_threshold=50, method="average", ) plots.plot_ioosqc(spike, suspect_threshold=[25], fail_threshold=[50], title='Spike test DOXY') flat = qartod.flat_line_test(ds.DOXY, ds.TIME, 1, 2, 0.001) plots.plot_ioosqc(flat, suspect_threshold=[0.01], fail_threshold=[0.1], title='Flat test DOXY') + def test_plot_sampling_period_all(): ds = fetchers.load_sample_dataset() plots.plot_sampling_period_all(ds) - plots.plot_sampling_period(ds,variable='CHLA') + plots.plot_sampling_period(ds, variable='CHLA') \ No newline at end of file diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 4820b36..351a5a0 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -24,3 +24,13 @@ def test_depth_z(): ds = utilities.calc_DEPTH_Z(ds) assert 'DEPTH_Z' in ds.variables assert ds.DEPTH_Z.min() < -50 +def test_labels(): + ds = fetchers.load_sample_dataset() + var = 'PITCH' + label = utilities.plotting_labels(var) + assert label == 'PITCH' + var = 'TEMP' + label = utilities.plotting_labels(var) + assert label == 'Temperature' + unit=utilities.plotting_units(ds, var) + assert unit == "°C" \ No newline at end of file