def plot_xtick_format(calendar, minDays, maxDays, maxXTicks, yearStride=None): ''' Formats tick labels and positions along the x-axis for time series / index plots Parameters ---------- calendar : str the calendar to use for formatting the time axis minDays : float start time for labels maxDays : float end time for labels maxXTicks : int the maximum number of tick marks to display, used to sub-sample ticks if there are too many yearStride : int, optional the number of years to skip over between ticks ''' # Authors # ------- # Xylar Asay-Davis ax = plt.gca() start = days_to_datetime(np.amin(minDays), calendar=calendar) end = days_to_datetime(np.amax(maxDays), calendar=calendar) if yearStride is not None or end.year - start.year > maxXTicks/2: if yearStride is None: yearStride = 1 else: maxXTicks = None major = [date_to_days(year=year, calendar=calendar) for year in np.arange(start.year, end.year+1, yearStride)] formatterFun = partial(_date_tick, calendar=calendar, includeMonth=False) else: # add ticks for months major = [] for year in range(start.year, end.year+1): for month in range(1, 13): major.append(date_to_days(year=year, month=month, calendar=calendar)) formatterFun = partial(_date_tick, calendar=calendar, includeMonth=True) ax.xaxis.set_major_locator(FixedLocator(major, maxXTicks)) ax.xaxis.set_major_formatter(FuncFormatter(formatterFun)) plt.setp(ax.get_xticklabels(), rotation=30) plt.autoscale(enable=True, axis='x', tight=True)
def test_date_to_days(self): referenceDate = '0001-01-01' for calendar in ['gregorian', 'gregorian_noleap']: days = date_to_days(year=1, month=1, day=1, calendar=calendar, referenceDate=referenceDate) self.assertEqual(days, 0.) days = date_to_days(year=1, month=1, day=2, calendar=calendar, referenceDate=referenceDate) self.assertEqual(days, 1.) days = date_to_days(year=1, month=2, day=1, calendar=calendar, referenceDate=referenceDate) self.assertEqual(days, 31.) days = date_to_days(year=2, month=1, day=1, calendar=calendar, referenceDate=referenceDate) self.assertEqual(days, 365.) referenceDate = '2016-01-01' for calendar, expected_days in [('gregorian', 366.), ('gregorian_noleap', 365.)]: days = date_to_days(year=2017, month=1, day=1, calendar=calendar, referenceDate=referenceDate) self.assertEqual(days, expected_days)
def run_task(self): # {{{ """ Compute vertical agregates of the data and plot the time series """ # Authors # ------- # Xylar Asay-Davis, Milena Veneziani, Greg Streletz self.logger.info("\nPlotting depth-integrated time series of " "{}...".format(self.fieldNameInTitle)) config = self.config calendar = self.calendar mainRunName = config.get('runs', 'mainRunName') plotTitles = config.getExpression('regions', 'plotTitles') allRegionNames = config.getExpression('regions', 'regions') regionIndex = allRegionNames.index(self.regionName) regionNameInTitle = plotTitles[regionIndex] startDate = config.get('timeSeries', 'startDate') endDate = config.get('timeSeries', 'endDate') # Load data self.logger.info(' Load ocean data...') ds = open_mpas_dataset(fileName=self.inFileName, calendar=calendar, variableList=[self.mpasFieldName, 'depth'], timeVariableNames=None, startDate=startDate, endDate=endDate) ds = ds.isel(nOceanRegionsTmp=regionIndex) depths = ds.depth.values divisionDepths = config.getExpression(self.sectionName, 'depths') # for each depth interval to plot, determine the top and bottom depth topDepths = [0, 0] + divisionDepths bottomDepths = [depths[-1]] + divisionDepths + [depths[-1]] legends = [] for top, bottom in zip(topDepths, bottomDepths): if bottom == depths[-1]: legends.append('{}m-bottom'.format(top)) else: legends.append('{}m-{}m'.format(top, bottom)) # more possible symbols than we typically use lines = ['-', '-', '--', None, None, None, None] markers = [None, None, None, '+', 'o', '^', 'v'] widths = [5, 3, 3, 3, 3, 3, 3] points = [None, None, None, 300, 300, 300, 300] color = 'k' xLabel = 'Time [years]' yLabel = self.yAxisLabel title = '{}, {} \n {} (black)'.format(self.fieldNameInTitle, regionNameInTitle, mainRunName) outFileName = '{}/{}.png'.format(self.plotsDirectory, self.filePrefix) timeSeries = [] lineColors = [] lineStyles = [] lineMarkers = [] lineWidths = [] maxPoints = [] legendText = [] for rangeIndex in range(len(topDepths)): top = topDepths[rangeIndex] bottom = bottomDepths[rangeIndex] field = ds[self.mpasFieldName].where(ds.depth > top) field = field.where(ds.depth <= bottom) timeSeries.append(field.sum('nVertLevels')) lineColors.append(color) lineStyles.append(lines[rangeIndex]) lineMarkers.append(markers[rangeIndex]) lineWidths.append(widths[rangeIndex]) maxPoints.append(points[rangeIndex]) legendText.append(legends[rangeIndex]) preprocessedReferenceRunName = config.get( 'runs', 'preprocessedReferenceRunName') if preprocessedReferenceRunName != 'None': preprocessedInputDirectory = config.get( 'oceanPreprocessedReference', 'baseDirectory') self.logger.info(' Load in preprocessed reference data...') preprocessedFilePrefix = config.get(self.sectionName, 'preprocessedFilePrefix') inFilesPreprocessed = '{}/{}.{}.year*.nc'.format( preprocessedInputDirectory, preprocessedFilePrefix, preprocessedReferenceRunName) combine_time_series_with_ncrcat( inFilesPreprocessed, self.preprocessedIntermediateFileName, logger=self.logger) dsPreprocessed = open_mpas_dataset( fileName=self.preprocessedIntermediateFileName, calendar=calendar, timeVariableNames='xtime') yearStart = days_to_datetime(ds.Time.min(), calendar=calendar).year yearEnd = days_to_datetime(ds.Time.max(), calendar=calendar).year timeStart = date_to_days(year=yearStart, month=1, day=1, calendar=calendar) timeEnd = date_to_days(year=yearEnd, month=12, day=31, calendar=calendar) yearEndPreprocessed = days_to_datetime(dsPreprocessed.Time.max(), calendar=calendar).year if yearStart <= yearEndPreprocessed: dsPreprocessed = dsPreprocessed.sel( Time=slice(timeStart, timeEnd)) else: self.logger.warning('Warning: Preprocessed time series ends ' 'before the timeSeries startYear and will ' 'not be plotted.') preprocessedReferenceRunName = 'None' # rolling mean seems to have trouble with dask data sets so we # write out the data set and read it back as a single-file data set # (without dask) dsPreprocessed = dsPreprocessed.drop('xtime') write_netcdf(dsPreprocessed, self.preprocessedFileName) dsPreprocessed = xarray.open_dataset(self.preprocessedFileName) if preprocessedReferenceRunName != 'None': color = 'purple' title = '{} \n {} (purple)'.format(title, preprocessedReferenceRunName) preprocessedFieldPrefix = config.get(self.sectionName, 'preprocessedFieldPrefix') movingAveragePoints = config.getint(self.sectionName, 'movingAveragePoints') suffixes = ['tot' ] + ['{}m'.format(depth) for depth in divisionDepths] + ['btm'] # these preprocessed data are already anomalies dsPreprocessed = compute_moving_avg(dsPreprocessed, movingAveragePoints) for rangeIndex in range(len(suffixes)): variableName = '{}_{}'.format(preprocessedFieldPrefix, suffixes[rangeIndex]) if variableName in list(dsPreprocessed.data_vars.keys()): timeSeries.append(dsPreprocessed[variableName]) else: self.logger.warning( 'Warning: Preprocessed variable {} ' 'not found. Skipping.'.format(variableName)) timeSeries.extend(None) lineColors.append(color) lineStyles.append(lines[rangeIndex]) lineMarkers.append(markers[rangeIndex]) lineWidths.append(widths[rangeIndex]) maxPoints.append(points[rangeIndex]) legendText.append(None) if self.controlConfig is not None: controlRunName = self.controlConfig.get('runs', 'mainRunName') title = '{} \n {} (red)'.format(title, controlRunName) self.logger.info(' Load ocean data from control run...') controlStartYear = self.controlConfig.getint( 'timeSeries', 'startYear') controlEndYear = self.controlConfig.getint('timeSeries', 'endYear') controlStartDate = '{:04d}-01-01_00:00:00'.format(controlStartYear) controlEndDate = '{:04d}-12-31_23:59:59'.format(controlEndYear) dsRef = open_mpas_dataset( fileName=self.refFileName, calendar=calendar, variableList=[self.mpasFieldName, 'depth'], timeVariableNames=None, startDate=controlStartDate, endDate=controlEndDate) dsRef = dsRef.isel(nOceanRegionsTmp=regionIndex) color = 'r' for rangeIndex in range(len(topDepths)): top = topDepths[rangeIndex] bottom = bottomDepths[rangeIndex] field = dsRef[self.mpasFieldName].where(dsRef.depth > top) field = field.where(dsRef.depth <= bottom) timeSeries.append(field.sum('nVertLevels')) lineColors.append(color) lineStyles.append(lines[rangeIndex]) lineMarkers.append(markers[rangeIndex]) lineWidths.append(widths[rangeIndex]) maxPoints.append(points[rangeIndex]) legendText.append(None) if config.has_option(self.taskName, 'firstYearXTicks'): firstYearXTicks = config.getint(self.taskName, 'firstYearXTicks') else: firstYearXTicks = None if config.has_option(self.taskName, 'yearStrideXTicks'): yearStrideXTicks = config.getint(self.taskName, 'yearStrideXTicks') else: yearStrideXTicks = None timeseries_analysis_plot(config=config, dsvalues=timeSeries, calendar=calendar, title=title, xlabel=xLabel, ylabel=yLabel, movingAveragePoints=None, lineColors=lineColors, lineStyles=lineStyles, markers=lineMarkers, lineWidths=lineWidths, legendText=legendText, maxPoints=maxPoints, firstYearXTicks=firstYearXTicks, yearStrideXTicks=yearStrideXTicks) savefig(outFileName) write_image_xml(config=config, filePrefix=self.filePrefix, componentName='Ocean', componentSubdirectory='ocean', galleryGroup=self.galleryGroup, groupLink=self.groupLink, gallery=self.galleryName, thumbnailDescription='{} {}'.format( self.regionName, self.thumbnailSuffix), imageDescription=self.imageCaption, imageCaption=self.imageCaption)
def timeseries_analysis_plot(config, dsvalues, calendar, title, xlabel, ylabel, movingAveragePoints=None, lineColors=None, lineStyles=None, markers=None, lineWidths=None, legendText=None, maxPoints=None, titleFontSize=None, figsize=(15, 6), dpi=None, firstYearXTicks=None, yearStrideXTicks=None, maxXTicks=20, obsMean=None, obsUncertainty=None, obsLegend=None, legendLocation='lower left', maxTitleLength=90): """ Plots the list of time series data sets. Parameters ---------- config : instance of ConfigParser the configuration, containing a [plot] section with options that control plotting dsvalues : list of xarray DataSets the data set(s) to be plotted title : str the title of the plot xlabel, ylabel : str axis labels calendar : str the calendar to use for formatting the time axis movingAveragePoints : int, optional the number of time points over which to perform a moving average lineColors, lineStyles, markers, legendText : list of str, optional control line color, style, marker, and corresponding legend text. Default is black, solid line with no marker, and no legend. lineWidths : list of float, optional control line width. Default is 1.0. maxPoints : list of {None, int}, optional the approximate maximum number of time points to use in a time series. This can be helpful for reducing the number of symbols plotted if plotting with markers. Otherwise the markers become indistinguishable from each other. titleFontSize : int, optional the size of the title font figsize : tuple of float, optional the size of the figure in inches dpi : int, optional the number of dots per inch of the figure, taken from section ``plot`` option ``dpi`` in the config file by default firstYearXTicks : int, optional The year of the first tick on the x axis. By default, the first time entry is the first tick. yearStrideXTicks : int, optional The number of years between x ticks. By default, the stride is chosen automatically to have ``maxXTicks`` tick marks or fewer. maxXTicks : int, optional the maximum number of tick marks that will be allowed along the x axis. This may need to be adjusted depending on the figure size and aspect ratio. obsMean, obsUncertainty : list of float, optional Mean values and uncertainties for observations to be plotted as error bars. The two lists must have the same number of elements. obsLegend : list of str, optional The label in the legend for each element in ``obsMean`` (and ``obsUncertainty``) legendLocation : str, optional The location of the legend (see ``pyplot.legend()`` for details) maxTitleLength : int, optional the maximum number of characters in the title and legend, beyond which they are truncated with a trailing ellipsis Returns ------- fig : ``matplotlib.figure.Figure`` The resulting figure """ # Authors # ------- # Xylar Asay-Davis, Milena Veneziani, Stephen Price if dpi is None: dpi = config.getint('plot', 'dpi') fig = plt.figure(figsize=figsize, dpi=dpi) minDays = [] maxDays = [] labelCount = 0 for dsIndex in range(len(dsvalues)): dsvalue = dsvalues[dsIndex] if dsvalue is None: continue if movingAveragePoints == 1 or movingAveragePoints is None: mean = dsvalue else: mean = pd.Series.rolling(dsvalue.to_pandas(), movingAveragePoints, center=True).mean() mean = xr.DataArray.from_series(mean) minDays.append(mean.Time.min()) maxDays.append(mean.Time.max()) if maxPoints is not None and maxPoints[dsIndex] is not None: nTime = mean.sizes['Time'] if maxPoints[dsIndex] < nTime: stride = int(round(nTime / float(maxPoints[dsIndex]))) mean = mean.isel(Time=slice(0, None, stride)) if legendText is None: label = None else: label = legendText[dsIndex] if label is not None: label = limit_title(label, maxTitleLength) labelCount += 1 if lineColors is None: color = 'k' else: color = lineColors[dsIndex] if lineStyles is None: linestyle = '-' else: linestyle = lineStyles[dsIndex] if markers is None: marker = None else: marker = markers[dsIndex] if lineWidths is None: linewidth = 1. else: linewidth = lineWidths[dsIndex] plt.plot(mean['Time'].values, mean.values, color=color, linestyle=linestyle, marker=marker, linewidth=linewidth, label=label) if obsMean is not None: obsCount = len(obsMean) assert (len(obsUncertainty) == obsCount) # space the observations along the time line, leaving gaps at either # end start = np.amin(minDays) end = np.amax(maxDays) obsTimes = np.linspace(start, end, obsCount + 2)[1:-1] obsSymbols = ['o', '^', 's', 'D', '*'] obsColors = ['b', 'g', 'c', 'm', 'r'] for iObs in range(obsCount): if obsMean[iObs] is not None: symbol = obsSymbols[np.mod(iObs, len(obsSymbols))] color = obsColors[np.mod(iObs, len(obsColors))] fmt = '{}{}'.format(color, symbol) plt.errorbar(obsTimes[iObs], obsMean[iObs], yerr=obsUncertainty[iObs], fmt=fmt, ecolor=color, capsize=0, label=obsLegend[iObs]) # plot a box around the error bar to make it more visible boxHalfWidth = 0.01 * (end - start) boxHalfHeight = obsUncertainty[iObs] boxX = obsTimes[iObs] + \ boxHalfWidth * np.array([-1, 1, 1, -1, -1]) boxY = obsMean[iObs] + \ boxHalfHeight * np.array([-1, -1, 1, 1, -1]) plt.plot(boxX, boxY, '{}-'.format(color), linewidth=3) labelCount += 1 if labelCount > 1: plt.legend(loc=legendLocation) ax = plt.gca() if titleFontSize is None: titleFontSize = config.get('plot', 'titleFontSize') axis_font = {'size': config.get('plot', 'axisFontSize')} title_font = { 'size': titleFontSize, 'color': config.get('plot', 'titleFontColor'), 'weight': config.get('plot', 'titleFontWeight') } if firstYearXTicks is not None: minDays = date_to_days(year=firstYearXTicks, calendar=calendar) plot_xtick_format(calendar, minDays, maxDays, maxXTicks, yearStride=yearStrideXTicks) # Add a y=0 line if y ranges between positive and negative values yaxLimits = ax.get_ylim() if yaxLimits[0] * yaxLimits[1] < 0: x = ax.get_xlim() plt.plot(x, np.zeros(np.size(x)), 'k-', linewidth=1.2, zorder=1) if title is not None: title = limit_title(title, maxTitleLength) plt.title(title, **title_font) if xlabel is not None: plt.xlabel(xlabel, **axis_font) if ylabel is not None: plt.ylabel(ylabel, **axis_font) return fig
def run_task(self): # {{{ """ Performs analysis of time series of sea-ice properties. """ # Authors # ------- # Xylar Asay-Davis, Milena Veneziani self.logger.info("\nPlotting sea-ice area and volume time series...") config = self.config calendar = self.calendar sectionName = self.taskName plotTitles = {'iceArea': 'Sea-ice area', 'iceVolume': 'Sea-ice volume', 'iceThickness': 'Sea-ice mean thickness'} units = {'iceArea': '[km$^2$]', 'iceVolume': '[10$^3$ km$^3$]', 'iceThickness': '[m]'} obsFileNames = { 'iceArea': {'NH': build_obs_path( config, 'seaIce', relativePathOption='areaNH', relativePathSection=sectionName), 'SH': build_obs_path( config, 'seaIce', relativePathOption='areaSH', relativePathSection=sectionName)}, 'iceVolume': {'NH': build_obs_path( config, 'seaIce', relativePathOption='volNH', relativePathSection=sectionName), 'SH': build_obs_path( config, 'seaIce', relativePathOption='volSH', relativePathSection=sectionName)}} # Some plotting rules titleFontSize = config.get('timeSeriesSeaIceAreaVol', 'titleFontSize') mainRunName = config.get('runs', 'mainRunName') preprocessedReferenceRunName = \ config.get('runs', 'preprocessedReferenceRunName') preprocessedReferenceDirectory = \ config.get('seaIcePreprocessedReference', 'baseDirectory') compareWithObservations = config.getboolean('timeSeriesSeaIceAreaVol', 'compareWithObservations') movingAveragePoints = config.getint('timeSeriesSeaIceAreaVol', 'movingAveragePoints') polarPlot = config.getboolean('timeSeriesSeaIceAreaVol', 'polarPlot') outputDirectory = build_config_full_path(config, 'output', 'timeseriesSubdirectory') make_directories(outputDirectory) self.logger.info(' Load sea-ice data...') # Load mesh dsTimeSeries = self._compute_area_vol() yearStart = days_to_datetime(dsTimeSeries['NH'].Time.min(), calendar=calendar).year yearEnd = days_to_datetime(dsTimeSeries['NH'].Time.max(), calendar=calendar).year timeStart = date_to_days(year=yearStart, month=1, day=1, calendar=calendar) timeEnd = date_to_days(year=yearEnd, month=12, day=31, calendar=calendar) if preprocessedReferenceRunName != 'None': # determine if we're beyond the end of the preprocessed data # (and go ahead and cache the data set while we're checking) outFolder = '{}/preprocessed'.format(outputDirectory) make_directories(outFolder) inFilesPreprocessed = '{}/icevol.{}.year*.nc'.format( preprocessedReferenceDirectory, preprocessedReferenceRunName) outFileName = '{}/iceVolume.nc'.format(outFolder) combine_time_series_with_ncrcat(inFilesPreprocessed, outFileName, logger=self.logger) dsPreprocessed = open_mpas_dataset(fileName=outFileName, calendar=calendar, timeVariableNames='xtime') preprocessedYearEnd = days_to_datetime(dsPreprocessed.Time.max(), calendar=calendar).year if yearStart <= preprocessedYearEnd: dsPreprocessedTimeSlice = \ dsPreprocessed.sel(Time=slice(timeStart, timeEnd)) else: self.logger.warning('Preprocessed time series ends before the ' 'timeSeries startYear and will not be ' 'plotted.') preprocessedReferenceRunName = 'None' if self.controlConfig is not None: dsTimeSeriesRef = {} baseDirectory = build_config_full_path( self.controlConfig, 'output', 'timeSeriesSubdirectory') controlRunName = self.controlConfig.get('runs', 'mainRunName') for hemisphere in ['NH', 'SH']: inFileName = '{}/seaIceAreaVol{}.nc'.format(baseDirectory, hemisphere) dsTimeSeriesRef[hemisphere] = xr.open_dataset(inFileName) norm = {'iceArea': 1e-6, # m^2 to km^2 'iceVolume': 1e-12, # m^3 to 10^3 km^3 'iceThickness': 1.} xLabel = 'Time [years]' galleryGroup = 'Time Series' groupLink = 'timeseries' obs = {} preprocessed = {} figureNameStd = {} figureNamePolar = {} title = {} plotVars = {} obsLegend = {} plotVarsRef = {} for hemisphere in ['NH', 'SH']: self.logger.info(' Make {} plots...'.format(hemisphere)) for variableName in ['iceArea', 'iceVolume']: key = (hemisphere, variableName) # apply the norm to each variable plotVars[key] = (norm[variableName] * dsTimeSeries[hemisphere][variableName]) if self.controlConfig is not None: plotVarsRef[key] = norm[variableName] * \ dsTimeSeriesRef[hemisphere][variableName] prefix = '{}/{}{}_{}'.format(self.plotsDirectory, variableName, hemisphere, mainRunName) figureNameStd[key] = '{}.png'.format(prefix) figureNamePolar[key] = '{}_polar.png'.format(prefix) title[key] = '{} ({})'.format(plotTitles[variableName], hemisphere) if compareWithObservations: key = (hemisphere, 'iceArea') obsLegend[key] = 'SSM/I observations, annual cycle ' if hemisphere == 'NH': key = (hemisphere, 'iceVolume') obsLegend[key] = 'PIOMAS, annual cycle (blue)' if preprocessedReferenceRunName != 'None': for variableName in ['iceArea', 'iceVolume']: key = (hemisphere, variableName) if compareWithObservations: outFolder = '{}/obs'.format(outputDirectory) make_directories(outFolder) outFileName = '{}/iceArea{}.nc'.format(outFolder, hemisphere) combine_time_series_with_ncrcat( obsFileNames['iceArea'][hemisphere], outFileName, logger=self.logger) dsObs = open_mpas_dataset(fileName=outFileName, calendar=calendar, timeVariableNames='xtime') key = (hemisphere, 'iceArea') obs[key] = self._replicate_cycle(plotVars[key], dsObs.IceArea, calendar) key = (hemisphere, 'iceVolume') if hemisphere == 'NH': outFileName = '{}/iceVolume{}.nc'.format(outFolder, hemisphere) combine_time_series_with_ncrcat( obsFileNames['iceVolume'][hemisphere], outFileName, logger=self.logger) dsObs = open_mpas_dataset(fileName=outFileName, calendar=calendar, timeVariableNames='xtime') obs[key] = self._replicate_cycle(plotVars[key], dsObs.IceVol, calendar) else: obs[key] = None if preprocessedReferenceRunName != 'None': outFolder = '{}/preprocessed'.format(outputDirectory) inFilesPreprocessed = '{}/icearea.{}.year*.nc'.format( preprocessedReferenceDirectory, preprocessedReferenceRunName) outFileName = '{}/iceArea.nc'.format(outFolder) combine_time_series_with_ncrcat(inFilesPreprocessed, outFileName, logger=self.logger) dsPreprocessed = open_mpas_dataset(fileName=outFileName, calendar=calendar, timeVariableNames='xtime') dsPreprocessedTimeSlice = dsPreprocessed.sel( Time=slice(timeStart, timeEnd)) key = (hemisphere, 'iceArea') preprocessed[key] = dsPreprocessedTimeSlice[ 'icearea_{}'.format(hemisphere.lower())] inFilesPreprocessed = '{}/icevol.{}.year*.nc'.format( preprocessedReferenceDirectory, preprocessedReferenceRunName) outFileName = '{}/iceVolume.nc'.format(outFolder) combine_time_series_with_ncrcat(inFilesPreprocessed, outFileName, logger=self.logger) dsPreprocessed = open_mpas_dataset(fileName=outFileName, calendar=calendar, timeVariableNames='xtime') dsPreprocessedTimeSlice = dsPreprocessed.sel( Time=slice(timeStart, timeEnd)) key = (hemisphere, 'iceVolume') preprocessed[key] = dsPreprocessedTimeSlice[ 'icevolume_{}'.format(hemisphere.lower())] for variableName in ['iceArea', 'iceVolume']: key = (hemisphere, variableName) dsvalues = [plotVars[key]] legendText = [mainRunName] lineColors = ['k'] lineWidths = [3] if compareWithObservations and key in obsLegend.keys(): dsvalues.append(obs[key]) legendText.append(obsLegend[key]) lineColors.append('b') lineWidths.append(1.2) if preprocessedReferenceRunName != 'None': dsvalues.append(preprocessed[key]) legendText.append(preprocessedReferenceRunName) lineColors.append('purple') lineWidths.append(1.2) if self.controlConfig is not None: dsvalues.append(plotVarsRef[key]) legendText.append(controlRunName) lineColors.append('r') lineWidths.append(1.2) if config.has_option(sectionName, 'firstYearXTicks'): firstYearXTicks = config.getint(sectionName, 'firstYearXTicks') else: firstYearXTicks = None if config.has_option(sectionName, 'yearStrideXTicks'): yearStrideXTicks = config.getint(sectionName, 'yearStrideXTicks') else: yearStrideXTicks = None # separate plots for nothern and southern hemispheres timeseries_analysis_plot(config, dsvalues, movingAveragePoints, title[key], xLabel, units[variableName], calendar=calendar, lineColors=lineColors, lineWidths=lineWidths, legendText=legendText, titleFontSize=titleFontSize, firstYearXTicks=firstYearXTicks, yearStrideXTicks=yearStrideXTicks) savefig(figureNameStd[key]) filePrefix = '{}{}_{}'.format(variableName, hemisphere, mainRunName) thumbnailDescription = '{} {}'.format( hemisphere, plotTitles[variableName]) caption = 'Running mean of {}'.format( thumbnailDescription) write_image_xml( config, filePrefix, componentName='Sea Ice', componentSubdirectory='sea_ice', galleryGroup=galleryGroup, groupLink=groupLink, thumbnailDescription=thumbnailDescription, imageDescription=caption, imageCaption=caption) if (polarPlot): timeseries_analysis_plot_polar( config, dsvalues, movingAveragePoints, title[key], lineColors=lineColors, lineWidths=lineWidths, legendText=legendText, titleFontSize=titleFontSize) savefig(figureNamePolar[key]) filePrefix = '{}{}_{}_polar'.format(variableName, hemisphere, mainRunName) write_image_xml( config, filePrefix, componentName='Sea Ice', componentSubdirectory='sea_ice', galleryGroup=galleryGroup, groupLink=groupLink, thumbnailDescription=thumbnailDescription, imageDescription=caption, imageCaption=caption)
def plot_vertical_section( config, xArray, depthArray, fieldArray, colorMapSectionName, suffix='', colorbarLabel=None, title=None, xlabel=None, ylabel=None, figsize=(10, 4), dpi=None, titleFontSize=None, titleY=None, axisFontSize=None, xLim=None, yLim=None, lineWidth=2, lineStyle='solid', lineColor='black', backgroundColor='grey', secondXAxisData=None, secondXAxisLabel=None, thirdXAxisData=None, thirdXAxisLabel=None, numUpperTicks=None, upperXAxisTickLabelPrecision=None, invertYAxis=True, xArrayIsTime=False, movingAveragePoints=None, firstYearXTicks=None, yearStrideXTicks=None, maxXTicks=20, calendar='gregorian', plotAsContours=False, contourComparisonFieldArray=None, comparisonFieldName=None, originalFieldName=None, comparisonContourLineStyle=None, comparisonContourLineColor=None, labelContours=False, contourLabelPrecision=1, maxTitleLength=70): # {{{ """ Plots a data set as a x distance (latitude, longitude, or spherical distance) vs depth map (vertical section). Or, if xArrayIsTime is True, plots data set on a vertical Hovmoller plot (depth vs. time). Typically, the fieldArray data are plotted using a heatmap, but if contourComparisonFieldArray is not None, then contours of both fieldArray and contourComparisonFieldArray are plotted instead. Parameters ---------- config : instance of ConfigParser the configuration, containing a [plot] section with options that control plotting xArray : float array x array (latitude, longitude, or spherical distance; or, time for a Hovmoller plot) depthArray : float array depth array [m] fieldArray : float array field array to plot colorMapSectionName : str section name in ``config`` where color map info can be found. suffix : str, optional the suffix used for colorbar config options colorbarLabel : str, optional the label for the colorbar. If plotAsContours and labelContours are both True, colorbarLabel is used as follows (typically in order to indicate the units that are associated with the contour labels): if contourComparisonFieldArray is None, the colorbarLabel string is parenthetically appended to the plot title; if contourComparisonFieldArray is not None, it is parenthetically appended to the legend entries of the contour comparison plot. title : str, optional title of plot xlabel, ylabel : str, optional label of x- and y-axis figsize : tuple of float, optional size of the figure in inches, or None if the current figure should be used (e.g. if this is a subplot) dpi : int, optional the number of dots per inch of the figure, taken from section ``plot`` option ``dpi`` in the config file by default titleFontSize : int, optional size of the title font titleY : float, optional the y value to use for placing the plot title axisFontSize : int, optional size of the axis font xLim : float array, optional x range of plot yLim : float array, optional y range of plot lineWidth : int, optional the line width of contour lines (if specified) lineStyle : str, optional the line style of contour lines (if specified); this applies to the style of contour lines of fieldArray (the style of the contour lines of contourComparisonFieldArray is set using contourComparisonLineStyle). lineColor : str, optional the color of contour lines (if specified); this applies to the contour lines of fieldArray (the color of the contour lines of contourComparisonFieldArray is set using contourComparisonLineColor backgroundColor : str, optional the background color for the plot (NaNs will be shown in this color) secondXAxisData : the data to use to display a second x axis (which will be placed above the plot). This array must have the same number of values as xArray, and it is assumed that the values in this array define locations along the x axis that are the same as those defined by the corresponding values in xArray, but in some different unit system. secondXAxisLabel : the label for the second x axis, if requested thirdXAxisData : the data to use to display a third x axis (which will be placed above the plot and above the second x axis, which must be specified if a third x axis is to be specified). This array must have the same number of values as xArray, and it is assumed that the values in this array define locations along the x axis that are the same as those defined by the corresponding values in xArray, but in some different unit system (which is presumably also different from the unit system used for the values in the secondXAxisData array). The typical use for this third axis is for transects, for which the primary x axis represents distance along a transect, and the second and third x axes are used to display the corresponding latitudes and longitudes. thirdXAxisLabel : the label for the third x axis, if requested numUpperTicks : the approximate number of ticks to use on the upper x axis or axes (these are the second and third x axes, which are placed above the plot if they have been requested by specifying the secondXAxisData or thirdXAxisData arrays above) upperXAxisTickLabelPrecision : the number of decimal places (to the right of the decimal point) to use for values at upper axis ticks. This value can be adjusted (in concert with numUpperTicks) to avoid problems with overlapping numbers along the upper axis. invertYAxis : logical, optional if True, invert Y axis xArrayIsTime : logical, optional if True, format the x axis for time (this applies only to the primary x axis, not to the optional second or third x axes) movingAveragePoints : int, optional the number of points over which to perform a moving average NOTE: this option is mostly intended for use when xArrayIsTime is True, although it will work with other data as well. Also, the moving average calculation is based on number of points, not actual x axis values, so for best results, the values in the xArray should be equally spaced. firstYearXTicks : int, optional The year of the first tick on the x axis. By default, the first time entry is the first tick. yearStrideXTicks : int, optional The number of years between x ticks. By default, the stride is chosen automatically to have ``maxXTicks`` tick marks or fewer. maxXTicks : int, optional the maximum number of tick marks that will be allowed along the primary x axis. This may need to be adjusted depending on the figure size and aspect ratio. NOTE: maxXTicks is only used if xArrayIsTime is True calendar : str, optional the calendar to use for formatting the time axis NOTE: calendar is only used if xArrayIsTime is True plotAsContours : bool, optional if plotAsContours is True, instead of plotting fieldArray as a heatmap, the function will plot only the contours of fieldArray. In addition, if contourComparisonFieldArray is not None, the contours of this field will be plotted on the same plot. The selection of contour levels is still determined as for the contours on the heatmap plots, via the 'contours' entry in colorMapSectionName. contourComparisonFieldArray : float array, optional a comparison field array (typically observational data or results from another simulation run), assumed to be of the same shape as fieldArray, and related to xArray and depthArray in the same way fieldArray is. If contourComparisonFieldArray is None, then fieldArray will be plotted as a heatmap. However, if countourComparisonFieldArray is not None, then contours of both fieldArray and contourComparisonFieldArray will be plotted in order to enable a comparison of the two fields on the same plot. If plotAsContours is False, this parameter is ignored. comparisonFieldName : str, optional the name for the comparison field. If contourComparisonFieldArray is None, this parameter is ignored. originalFieldName : str, optional the name for the fieldArray field (for the purposes of labeling the contours on a contour comparison plot). If contourComparisonFieldArray is None, this parameter is ignored. comparisonContourLineStyle : str, optional the line style of contour lines of the comparisonFieldName field on a contour comparison plot comparisonContourLineColor : str, optional the line color of contour lines of the comparisonFieldName field on a contour comparison plot labelContours : bool, optional whether or not to label contour lines (if specified) with their values contourLabelPrecision : int, optional the precision (in terms of number of figures to the right of the decimal point) of contour labels maxTitleLength : int, optional the maximum number of characters in the title, beyond which it is truncated with a trailing ellipsis Returns ------- fig : ``matplotlib.figure.Figure`` The figure that was plotted ax : ``matplotlib.axes.Axes`` The subplot """ # Authors # ------- # Milena Veneziani, Mark Petersen, Xylar Asay-Davis, Greg Streletz # compute moving averages with respect to the x dimension if movingAveragePoints is not None and movingAveragePoints != 1: N = movingAveragePoints movingAverageDepthSlices = [] for nVertLevel in range(len(depthArray)): depthSlice = fieldArray[[nVertLevel]][0] # in case it's not an xarray already depthSlice = xr.DataArray(depthSlice) mean = pd.Series.rolling(depthSlice.to_series(), N, center=True).mean() mean = xr.DataArray.from_series(mean) mean = mean[int(N / 2.0):-int(round(N / 2.0) - 1)] movingAverageDepthSlices.append(mean) xArray = xArray[int(N / 2.0):-int(round(N / 2.0) - 1)] fieldArray = xr.DataArray(movingAverageDepthSlices) dimX = xArray.shape dimZ = depthArray.shape dimF = fieldArray.shape if contourComparisonFieldArray is not None: dimC = contourComparisonFieldArray.shape if len(dimX) != 1 and len(dimX) != 2: raise ValueError('xArray must have either one or two dimensions ' '(has %d)' % dimX) if len(dimZ) != 1 and len(dimZ) != 2: raise ValueError('depthArray must have either one or two dimensions ' '(has %d)' % dimZ) if len(dimF) != 2: raise ValueError('fieldArray must have two dimensions (has %d)' % dimF) if contourComparisonFieldArray is not None: if len(dimC) != 2: raise ValueError('contourComparisonFieldArray must have two ' 'dimensions (has %d)' % dimC) elif (fieldArray.shape[0] != contourComparisonFieldArray.shape[0]) or \ (fieldArray.shape[1] != contourComparisonFieldArray.shape[1]): raise ValueError('size mismatch between fieldArray (%d x %d) and ' 'contourComparisonFieldArray (%d x %d)' % (fieldArray.shape[0], fieldArray.shape[1], contourComparisonFieldArray.shape[0], contourComparisonFieldArray.shape[1])) # verify that the dimensions of fieldArray are consistent with those of # xArray and depthArray if len(dimX) == 1 and len(dimZ) == 1: num_x = dimX[0] num_z = dimZ[0] if num_x != fieldArray.shape[1] or num_z != fieldArray.shape[0]: raise ValueError('size mismatch between xArray (%d), ' 'depthArray (%d), and fieldArray (%d x %d)' % (num_x, num_z, fieldArray.shape[0], fieldArray.shape[1])) elif len(dimX) == 1: num_x = dimX[0] num_x_Z = dimZ[1] num_z = dimZ[0] if num_x != fieldArray.shape[1] or num_z != fieldArray.shape[0] or \ num_x != num_x_Z: raise ValueError('size mismatch between xArray (%d), ' 'depthArray (%d x %d), and fieldArray (%d x %d)' % (num_x, num_z, num_x_Z, fieldArray.shape[0], fieldArray.shape[1])) elif len(dimZ) == 1: num_x = dimX[1] num_z_X = dimX[0] num_z = dimZ[0] if num_x != fieldArray.shape[1] or num_z != fieldArray.shape[0] or \ num_z != num_z_X: raise ValueError('size mismatch between xArray (%d x %d), ' 'depthArray (%d), and fieldArray (%d x %d)' % (num_z_X, num_x, num_z, fieldArray.shape[0], fieldArray.shape[1])) else: num_x = dimX[1] num_z_X = dimX[0] num_x_Z = dimZ[1] num_z = dimZ[0] if num_x != fieldArray.shape[1] or num_z != fieldArray.shape[0] \ or num_x != num_x_Z or num_z != num_z_X: raise ValueError('size mismatch between xArray (%d x %d), ' 'depthArray (%d x %d), and fieldArray (%d x %d)' % (num_z_X, num_x, num_z, num_x_Z, fieldArray.shape[0], fieldArray.shape[1])) # Verify that the upper x-axis parameters are consistent with each other # and with xArray if secondXAxisData is None and thirdXAxisData is not None: raise ValueError('secondXAxisData cannot be None if thirdXAxisData ' 'is not None') if secondXAxisData is not None: arrayShape = secondXAxisData.shape if len(arrayShape) == 1 and arrayShape[0] != num_x: raise ValueError('secondXAxisData has %d x values, ' 'but should have num_x = %d x values' % (arrayShape[0], num_x)) elif len(arrayShape) == 2 and arrayShape[1] != num_x: raise ValueError('secondXAxisData has %d x values, ' 'but should have num_x = %d x values' % (arrayShape[1], num_x)) elif len(arrayShape) > 2: raise ValueError('secondXAxisData must be a 1D or 2D array, ' 'but is of dimension %d' % (len(arrayShape))) if thirdXAxisData is not None: arrayShape = thirdXAxisData.shape if len(arrayShape) == 1 and arrayShape[0] != num_x: raise ValueError('thirdXAxisData has %d x values, ' 'but should have num_x = %d x values' % (arrayShape[0], num_x)) elif len(arrayShape) == 2 and arrayShape[1] != num_x: raise ValueError('thirdXAxisData has %d x values, ' 'but should have num_x = %d x values' % (arrayShape[1], num_x)) elif len(arrayShape) > 2: raise ValueError('thirdXAxisData must be a 1D or 2D array, ' 'but is of dimension %d' % (len(arrayShape))) # define x and y as the appropriate 2D arrays for plotting if len(dimX) == 1 and len(dimZ) == 1: x, y = np.meshgrid(xArray, depthArray) elif len(dimX) == 1: x, y = np.meshgrid(xArray, np.zeros(num_z)) y = depthArray elif len(dimZ) == 1: x, y = np.meshgrid(np.zeros(num_x), depthArray) x = xArray else: x = xArray y = depthArray # set up figure if dpi is None: dpi = config.getint('plot', 'dpi') if figsize is not None: fig = plt.figure(figsize=figsize, dpi=dpi) else: fig = plt.gcf() colormapDict = setup_colormap(config, colorMapSectionName, suffix=suffix) if not plotAsContours: # display a heatmap of fieldArray if colormapDict['levels'] is None: # interpFieldArray contains the values at centers of grid cells, # for pcolormesh plots (using bilinear interpolation) interpFieldArray = \ 0.5 * (0.5 * (fieldArray[1:, 1:] + fieldArray[0:-1, 1:]) + 0.5 * (fieldArray[1:, 0:-1] + fieldArray[0:-1, 0:-1])) plotHandle = plt.pcolormesh(x, y, interpFieldArray, cmap=colormapDict['colormap'], norm=colormapDict['norm']) else: plotHandle = plt.contourf(x, y, fieldArray, cmap=colormapDict['colormap'], norm=colormapDict['norm'], levels=colormapDict['levels'], extend='both') cbar = plt.colorbar(plotHandle, orientation='vertical', spacing='uniform', aspect=9, ticks=colormapDict['ticks'], boundaries=colormapDict['ticks']) if colorbarLabel is not None: cbar.set_label(colorbarLabel) else: # display a white heatmap to get a white background for non-land zeroArray = np.ma.where(fieldArray != np.nan, 0.0, fieldArray) plt.contourf(x, y, zeroArray, colors='white') # set the color for NaN or masked regions, and draw a black # outline around them; technically, the contour level used should # be 1.0, but the contours don't show up when using 1.0, so 0.999 # is used instead ax = plt.gca() ax.set_facecolor(backgroundColor) landArray = np.ma.where(fieldArray != np.nan, 1.0, fieldArray) landArray = np.ma.masked_where(landArray == np.nan, landArray, copy=True) landArray = landArray.filled(0.0) plt.contour(x, y, landArray, levels=[0.999], colors='black', linewidths=1) # plot contours, if they were requested contourLevels = colormapDict['contours'] if contourLevels is not None: if len(contourLevels) == 0: # automatic calculation of contour levels contourLevels = None cs1 = plt.contour(x, y, fieldArray, levels=contourLevels, colors=lineColor, linestyles=lineStyle, linewidths=lineWidth) if labelContours: fmt_string = "%%1.%df" % int(contourLabelPrecision) plt.clabel(cs1, fmt=fmt_string) if plotAsContours and contourComparisonFieldArray is not None: cs2 = plt.contour(x, y, contourComparisonFieldArray, levels=contourLevels, colors=comparisonContourLineColor, linestyles=comparisonContourLineStyle, linewidths=lineWidth) if labelContours: plt.clabel(cs2, fmt=fmt_string) if plotAsContours and contourComparisonFieldArray is not None: h1, _ = cs1.legend_elements() h2, _ = cs2.legend_elements() if labelContours: originalFieldName = originalFieldName + " (" + colorbarLabel + ")" comparisonFieldName = comparisonFieldName + " (" + \ colorbarLabel + ")" ax.legend([h1[0], h2[0]], [originalFieldName, comparisonFieldName], loc='upper center', bbox_to_anchor=(0.5, -0.25), ncol=1) if title is not None: if plotAsContours and labelContours \ and contourComparisonFieldArray is None: title = limit_title(title, maxTitleLength-(3+len(colorbarLabel))) title = title + " (" + colorbarLabel + ")" else: title = limit_title(title, maxTitleLength) if titleFontSize is None: titleFontSize = config.get('plot', 'titleFontSize') title_font = {'size': titleFontSize, 'color': config.get('plot', 'titleFontColor'), 'weight': config.get('plot', 'titleFontWeight')} if titleY is not None: plt.title(title, y=titleY, **title_font) else: plt.title(title, **title_font) if (xlabel is not None) or (ylabel is not None): if axisFontSize is None: axisFontSize = config.get('plot', 'axisFontSize') axis_font = {'size': axisFontSize} if xlabel is not None: plt.xlabel(xlabel, **axis_font) if ylabel is not None: plt.ylabel(ylabel, **axis_font) if invertYAxis: ax.invert_yaxis() if xLim: ax.set_xlim(xLim) if yLim: ax.set_ylim(yLim) if xArrayIsTime: if firstYearXTicks is None: minDays = [xArray[0]] else: minDays = date_to_days(year=firstYearXTicks, calendar=calendar) maxDays = [xArray[-1]] plot_xtick_format(calendar, minDays, maxDays, maxXTicks, yearStride=yearStrideXTicks) # add a second x-axis scale, if it was requested if secondXAxisData is not None: ax2 = ax.twiny() ax2.set_facecolor(backgroundColor) ax2.set_xlabel(secondXAxisLabel, **axis_font) xlimits = ax.get_xlim() ax2.set_xlim(xlimits) xticks = np.linspace(xlimits[0], xlimits[1], numUpperTicks) tickValues = np.interp(xticks, x.flatten()[:num_x], secondXAxisData) ax2.set_xticks(xticks) formatString = "{{0:.{:d}f}}{}".format( upperXAxisTickLabelPrecision, r'$\degree$') ax2.set_xticklabels([formatString.format(member) for member in tickValues]) # add a third x-axis scale, if it was requested if thirdXAxisData is not None: ax3 = ax.twiny() ax3.set_facecolor(backgroundColor) ax3.set_xlabel(thirdXAxisLabel, **axis_font) ax3.set_xlim(xlimits) ax3.set_xticks(xticks) tickValues = np.interp(xticks, x.flatten()[:num_x], thirdXAxisData) ax3.set_xticklabels([formatString.format(member) for member in tickValues]) ax3.spines['top'].set_position(('outward', 36)) return fig, ax # }}}
def run_task(self): # {{{ """ Performs analysis of the time-series output of sea-surface temperature (SST). """ # Authors # ------- # Xylar Asay-Davis, Milena Veneziani self.logger.info("\nPlotting SST time series...") self.logger.info(' Load SST data...') config = self.config calendar = self.calendar mainRunName = config.get('runs', 'mainRunName') preprocessedReferenceRunName = \ config.get('runs', 'preprocessedReferenceRunName') preprocessedInputDirectory = config.get('oceanPreprocessedReference', 'baseDirectory') movingAveragePoints = config.getint('timeSeriesSST', 'movingAveragePoints') regions = config.getExpression('regions', 'regions') plotTitles = config.getExpression('regions', 'plotTitles') regionsToPlot = config.getExpression('timeSeriesSST', 'regions') regionIndicesToPlot = [ regions.index(region) for region in regionsToPlot ] outputDirectory = build_config_full_path(config, 'output', 'timeseriesSubdirectory') make_directories(outputDirectory) dsSST = open_mpas_dataset(fileName=self.inputFile, calendar=calendar, variableList=self.variableList, startDate=self.startDate, endDate=self.endDate) yearStart = days_to_datetime(dsSST.Time.min(), calendar=calendar).year yearEnd = days_to_datetime(dsSST.Time.max(), calendar=calendar).year timeStart = date_to_days(year=yearStart, month=1, day=1, calendar=calendar) timeEnd = date_to_days(year=yearEnd, month=12, day=31, calendar=calendar) if self.refConfig is not None: baseDirectory = build_config_full_path(self.refConfig, 'output', 'timeSeriesSubdirectory') refFileName = '{}/{}.nc'.format( baseDirectory, self.mpasTimeSeriesTask.fullTaskName) refStartYear = self.refConfig.getint('timeSeries', 'startYear') refEndYear = self.refConfig.getint('timeSeries', 'endYear') refStartDate = '{:04d}-01-01_00:00:00'.format(refStartYear) refEndDate = '{:04d}-12-31_23:59:59'.format(refEndYear) dsRefSST = open_mpas_dataset(fileName=refFileName, calendar=calendar, variableList=self.variableList, startDate=refStartDate, endDate=refEndDate) else: dsRefSST = None if preprocessedReferenceRunName != 'None': self.logger.info(' Load in SST for a preprocesses reference ' 'run...') inFilesPreprocessed = '{}/SST.{}.year*.nc'.format( preprocessedInputDirectory, preprocessedReferenceRunName) outFolder = '{}/preprocessed'.format(outputDirectory) make_directories(outFolder) outFileName = '{}/sst.nc'.format(outFolder) combine_time_series_with_ncrcat(inFilesPreprocessed, outFileName, logger=self.logger) dsPreprocessed = open_mpas_dataset(fileName=outFileName, calendar=calendar, timeVariableNames='xtime') yearEndPreprocessed = days_to_datetime(dsPreprocessed.Time.max(), calendar=calendar).year if yearStart <= yearEndPreprocessed: dsPreprocessedTimeSlice = \ dsPreprocessed.sel(Time=slice(timeStart, timeEnd)) else: self.logger.warning('Preprocessed time series ends before the ' 'timeSeries startYear and will not be ' 'plotted.') preprocessedReferenceRunName = 'None' self.logger.info(' Make plots...') for regionIndex in regionIndicesToPlot: region = regions[regionIndex] title = '{} SST'.format(plotTitles[regionIndex]) xLabel = 'Time [years]' yLabel = '[$\degree$C]' varName = self.variableList[0] SST = dsSST[varName].isel(nOceanRegions=regionIndex) filePrefix = self.filePrefixes[region] figureName = '{}/{}.png'.format(self.plotsDirectory, filePrefix) lineColors = ['k'] lineWidths = [3] fields = [SST] legendText = [mainRunName] if dsRefSST is not None: refSST = dsRefSST[varName].isel(nOceanRegions=regionIndex) fields.append(refSST) lineColors.append('r') lineWidths.append(1.5) refRunName = self.refConfig.get('runs', 'mainRunName') legendText.append(refRunName) if preprocessedReferenceRunName != 'None': SST_v0 = dsPreprocessedTimeSlice.SST fields.append(SST_v0) lineColors.append('purple') lineWidths.append(1.5) legendText.append(preprocessedReferenceRunName) if config.has_option(self.taskName, 'firstYearXTicks'): firstYearXTicks = config.getint(self.taskName, 'firstYearXTicks') else: firstYearXTicks = None if config.has_option(self.taskName, 'yearStrideXTicks'): yearStrideXTicks = config.getint(self.taskName, 'yearStrideXTicks') else: yearStrideXTicks = None timeseries_analysis_plot(config, fields, movingAveragePoints, title, xLabel, yLabel, figureName, calendar=calendar, lineColors=lineColors, lineWidths=lineWidths, legendText=legendText, firstYearXTicks=firstYearXTicks, yearStrideXTicks=yearStrideXTicks) caption = 'Running Mean of {} Sea Surface Temperature'.format( region) write_image_xml(config=config, filePrefix=filePrefix, componentName='Ocean', componentSubdirectory='ocean', galleryGroup='Time Series', groupLink='timeseries', thumbnailDescription='{} SST'.format(region), imageDescription=caption, imageCaption=caption)
def plot_vertical_section( config, xArray, depthArray, fieldArray, colorMapSectionName, suffix='', colorbarLabel=None, title=None, xlabel=None, ylabel=None, fileout='moc.png', figsize=(10, 4), dpi=None, xLim=None, yLim=None, linewidths=2, invertYAxis=True, xArrayIsTime=False, N=None, firstYearXTicks=None, yearStrideXTicks=None, maxXTicks=20, calendar='gregorian'): # {{{ """ Plots a data set as a x distance (latitude, longitude, or spherical distance) vs depth map (vertical section). Or, if xArrayIsTime is True, plots data set on a vertical Hovmoller plot (depth vs. time). Parameters ---------- config : instance of ConfigParser the configuration, containing a [plot] section with options that control plotting xArray : float array x array (latitude, longitude, or spherical distance; or, time for a Hovmoller plot) depthArray : float array depth array [m] fieldArray : float array field array to plot colorMapSectionName : str section name in ``config`` where color map info can be found. suffix : str, optional the suffix used for colorbar config options title : str, optional title of plot xlabel, ylabel : str, optional label of x- and y-axis fileout : str, optional the file name to be written figsize : tuple of float, optional size of the figure in inches dpi : int, optional the number of dots per inch of the figure, taken from section ``plot`` option ``dpi`` in the config file by default xLim : float array, optional x range of plot yLim : float array, optional y range of plot linewidths : int, optional linewidths for contours invertYAxis : logical, optional if True, invert Y axis xArrayIsTime : logical, optional if True, format X axis for time N : int, optional the number of points over which to perform a moving average NOTE: this option is mostly intended for use when xArrayIsTime is True, although it will work with other data as well. Also, the moving average calculation is based on number of points, not actual x axis values, so for best results, the values in the xArray should be equally spaced. firstYearXTicks : int, optional The year of the first tick on the x axis. By default, the first time entry is the first tick. yearStrideXTicks : int, optional The number of years between x ticks. By default, the stride is chosen automatically to have ``maxXTicks`` tick marks or fewer. maxXTicks : int, optional the maximum number of tick marks that will be allowed along the x axis. This may need to be adjusted depending on the figure size and aspect ratio. NOTE: maxXTicks is only used if xArrayIsTime is True calendar : str, optional the calendar to use for formatting the time axis NOTE: calendar is only used if xArrayIsTime is True """ # Authors # ------- # Milena Veneziani, Mark Petersen, Xylar Asay-Davis, Greg Streletz # verify that the dimensions of fieldArray are consistent with those of # xArray and depthArray if len(xArray) != fieldArray.shape[1]: raise ValueError('size mismatch between xArray and fieldArray') elif len(depthArray) != fieldArray.shape[0]: raise ValueError('size mismatch between depthArray and fieldArray') # set up figure if dpi is None: dpi = config.getint('plot', 'dpi') plt.figure(figsize=figsize, dpi=dpi) # compute moving averages with respect to the x dimension if N is not None and N != 1: movingAverageDepthSlices = [] for nVertLevel in range(len(depthArray)): depthSlice = fieldArray[[nVertLevel]][0] # in case it's not an xarray already depthSlice = xr.DataArray(depthSlice) mean = pd.Series.rolling(depthSlice.to_series(), N, center=True).mean() mean = xr.DataArray.from_series(mean) mean = mean[int(N/2.0):-int(round(N/2.0)-1)] movingAverageDepthSlices.append(mean) xArray = xArray[int(N/2.0):-int(round(N/2.0)-1)] fieldArray = xr.DataArray(movingAverageDepthSlices) x, y = np.meshgrid(xArray, depthArray) # change to zMid colormapDict = setup_colormap(config, colorMapSectionName, suffix=suffix) cs = plt.contourf(x, y, fieldArray, cmap=colormapDict['colormap'], norm=colormapDict['norm'], levels=colormapDict['levels'], extend='both') contourLevels = colormapDict['contours'] if contourLevels is not None: if len(contourLevels) == 0: # automatic calculation of contour levels contourLevels = None plt.contour(x, y, fieldArray, levels=contourLevels, colors='k', linewidths=linewidths) cbar = plt.colorbar(cs, orientation='vertical', spacing='uniform', ticks=colormapDict['ticks'], boundaries=colormapDict['ticks']) if colorbarLabel is not None: cbar.set_label(colorbarLabel) axis_font = {'size': config.get('plot', 'axisFontSize')} title_font = {'size': config.get('plot', 'titleFontSize'), 'color': config.get('plot', 'titleFontColor'), 'weight': config.get('plot', 'titleFontWeight')} if title is not None: plt.title(title, **title_font) if xlabel is not None: plt.xlabel(xlabel, **axis_font) if ylabel is not None: plt.ylabel(ylabel, **axis_font) if invertYAxis: plt.gca().invert_yaxis() if xLim: plt.xlim(xLim) if yLim: plt.ylim(yLim) if xArrayIsTime: if firstYearXTicks is None: minDays = [xArray[0]] else: minDays = date_to_days(year=firstYearXTicks, calendar=calendar) maxDays = [xArray[-1]] plot_xtick_format(calendar, minDays, maxDays, maxXTicks, yearStride=yearStrideXTicks) if (fileout is not None): plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1) if not config.getboolean('plot', 'displayToScreen'): plt.close() return # }}}
def timeseries_analysis_plot(config, dsvalues, N, title, xlabel, ylabel, fileout, lineStyles, lineWidths, legendText, calendar, maxPoints=None, titleFontSize=None, figsize=(15, 6), dpi=None, firstYearXTicks=None, yearStrideXTicks=None, maxXTicks=20, obsMean=None, obsUncertainty=None, obsLegend=None, legendLocation='lower left'): """ Plots the list of time series data sets and stores the result in an image file. Parameters ---------- config : instance of ConfigParser the configuration, containing a [plot] section with options that control plotting dsvalues : list of xarray DataSets the data set(s) to be plotted N : int the numer of time points over which to perform a moving average title : str the title of the plot xlabel, ylabel : str axis labels fileout : str the file name to be written lineStyles, lineWidths, legendText : list of str control line style/width and corresponding legend text calendar : str the calendar to use for formatting the time axis maxPoints : list of {None, int} the approximate maximum number of time points to use in a time series. This can be helpful for reducing the number of symbols plotted if plotting with markers. Otherwise the markers become indistinguishable from each other. titleFontSize : int, optional the size of the title font figsize : tuple of float, optional the size of the figure in inches dpi : int, optional the number of dots per inch of the figure, taken from section ``plot`` option ``dpi`` in the config file by default firstYearXTicks : int, optional The year of the first tick on the x axis. By default, the first time entry is the first tick. yearStrideXTicks : int, optional The number of years between x ticks. By default, the stride is chosen automatically to have ``maxXTicks`` tick marks or fewer. maxXTicks : int, optional the maximum number of tick marks that will be allowed along the x axis. This may need to be adjusted depending on the figure size and aspect ratio. obsMean, obsUncertainty : list of float, optional Mean values and uncertainties for observations to be plotted as error bars. The two lists must have the same number of elements. obsLegend : list of str, optional The label in the legend for each element in ``obsMean`` (and ``obsUncertainty``) legendLocation : str, optional The location of the legend (see ``pyplot.legend()`` for details) """ # Authors # ------- # Xylar Asay-Davis, Milena Veneziani, Stephen Price if dpi is None: dpi = config.getint('plot', 'dpi') plt.figure(figsize=figsize, dpi=dpi) minDays = [] maxDays = [] for dsIndex in range(len(dsvalues)): dsvalue = dsvalues[dsIndex] if dsvalue is None: continue if N == 1 or N is None: mean = dsvalue else: mean = pd.Series.rolling(dsvalue.to_pandas(), N, center=True).mean() mean = xr.DataArray.from_series(mean) minDays.append(mean.Time.min()) maxDays.append(mean.Time.max()) if maxPoints is not None and maxPoints[dsIndex] is not None: nTime = mean.sizes['Time'] if maxPoints[dsIndex] < nTime: stride = int(round(nTime/float(maxPoints[dsIndex]))) mean = mean.isel(Time=slice(0, None, stride)) plt.plot(mean['Time'].values, mean.values, lineStyles[dsIndex], linewidth=lineWidths[dsIndex], label=legendText[dsIndex]) if obsMean is not None: obsCount = len(obsMean) assert(len(obsUncertainty) == obsCount) # space the observations along the time line, leaving gaps at either # end start = np.amin(minDays) end = np.amax(maxDays) obsTimes = np.linspace(start, end, obsCount+2)[1:-1] obsSymbols = ['o', '^', 's', 'D', '*'] for iObs in range(obsCount): if obsMean[iObs] is not None: plt.errorbar(obsTimes[iObs], obsMean[iObs], yerr=obsUncertainty[iObs], fmt=obsSymbols[np.mod(iObs, len(obsSymbols))], ecolor='k', capthick=2, label=obsLegend[iObs]) plt.legend(loc=legendLocation) ax = plt.gca() if titleFontSize is None: titleFontSize = config.get('plot', 'titleFontSize') axis_font = {'size': config.get('plot', 'axisFontSize')} title_font = {'size': titleFontSize, 'color': config.get('plot', 'titleFontColor'), 'weight': config.get('plot', 'titleFontWeight')} if firstYearXTicks is not None: minDays = date_to_days(year=firstYearXTicks, calendar=calendar) plot_xtick_format(calendar, minDays, maxDays, maxXTicks, yearStride=yearStrideXTicks) # Add a y=0 line if y ranges between positive and negative values yaxLimits = ax.get_ylim() if yaxLimits[0]*yaxLimits[1] < 0: x = ax.get_xlim() plt.plot(x, np.zeros(np.size(x)), 'k-', linewidth=1.2, zorder=1) if title is not None: plt.title(title, **title_font) if xlabel is not None: plt.xlabel(xlabel, **axis_font) if ylabel is not None: plt.ylabel(ylabel, **axis_font) if fileout is not None: plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1) if not config.getboolean('plot', 'displayToScreen'): plt.close()