def detrend(var, ax=None, lcopy=True, ldetrend=True, ltrend=False, degree=1, rcond=None, w=None, lsmooth=False, lresidual=False, window_len=11, window='hanning'): ''' subtract a linear trend from a time-series array (operation is in-place) ''' # check input if not isinstance(var,np.ndarray): raise NotImplementedError # too many checks if lcopy: var = var.copy() # make copy - not in-place! # fit over entire array (usually not what we want...) if ax is None and ldetrend: ax = np.arange(var.size) # make dummy axis, if necessary if var.ndim != 1: shape = var.shape var = var.ravel() # flatten array, if necessary else: shape = None # apply optional detrending if ldetrend or ltrend: # fit linear trend trend = np.polyfit(ax, var, deg=degree, rcond=rcond, w=w, full=False, cov=False) # evaluate and subtract linear trend if ldetrend and ltrend: raise ArgumentError("Can either return trend/polyfit or residuals, not both.") elif ldetrend and not ltrend: var -= np.polyval(trend, ax) # residuals elif ltrend and not ldetrend: var = np.polyval(trend, ax) # residuals # apply optional smoothing if lsmooth and lresidual: raise ArgumentError("Can either return smoothed array or residuals, not both.") elif lsmooth: var = smooth(var, window_len=window_len, window=window) elif lresidual: var -= smooth(var, window_len=window_len, window=window) # return detrended and/or smoothed time-series if shape is not None: var = var.reshape(shape) return var
def detrend(var, ax=None, lcopy=True, ldetrend=True, ltrend=False, degree=1, rcond=None, w=None, lsmooth=False, lresidual=False, window_len=11, window='hanning'): ''' subtract a linear trend from a time-series array (operation is in-place) ''' # check input if not isinstance(var, np.ndarray): raise NotImplementedError # too many checks if lcopy: var = var.copy() # make copy - not in-place! # fit over entire array (usually not what we want...) if ax is None and ldetrend: ax = np.arange(var.size) # make dummy axis, if necessary if var.ndim != 1: shape = var.shape var = var.ravel() # flatten array, if necessary else: shape = None # apply optional detrending if ldetrend or ltrend: # fit linear trend trend = np.polyfit(ax, var, deg=degree, rcond=rcond, w=w, full=False, cov=False) # evaluate and subtract linear trend if ldetrend and ltrend: raise ArgumentError( "Can either return trend/polyfit or residuals, not both.") elif ldetrend and not ltrend: var -= np.polyval(trend, ax) # residuals elif ltrend and not ldetrend: var = np.polyval(trend, ax) # residuals # apply optional smoothing if lsmooth and lresidual: raise ArgumentError( "Can either return smoothed array or residuals, not both.") elif lsmooth: var = smooth(var, window_len=window_len, window=window) elif lresidual: var -= smooth(var, window_len=window_len, window=window) # return detrended and/or smoothed time-series if shape is not None: var = var.reshape(shape) return var
def getPlotValues(var, checkunits=None, checkname=None, lsmooth=False, lperi=False, laxis=False): ''' Helper function to check variable/axis, get (scaled) values for plot, and return appropriate units. ''' # figure out units if var.plot is not None: varname = var.plot.name if checkname is not None and varname != checkname: # only check plotname! raise VariableError, "Expected variable name '{}', found '{}'.".format(checkname,varname) else: varname = var.atts['name'] val = var.getArray(unmask=True, copy=True) # the data to plot if var.plot is not None: if var.units != var.plot.units: val *= var.plot.scalefactor val += var.plot.offset varunits = var.plot.units else: varunits = var.atts['units'] if checkunits is not None and varunits != checkunits: raise VariableError, "Units for variable '{}': expected {}, found {}.".format(var.name,checkunits,varunits) # some post-processing val = val.squeeze() if lsmooth: val = smooth(val) if lperi: if laxis: delta = np.diff(val) val = np.concatenate((val[:1]-delta[:1],val,val[-1:]+delta[-1:])) else: val = np.concatenate((val[-1:],val,val[:1])) # return values, units, name return val, varunits, varname
def getPlotValues(var, checkunits=None, checkname=None, lsmooth=False, lperi=False, laxis=False): ''' Helper function to check variable/axis, get (scaled) values for plot, and return appropriate units. ''' # figure out units if var.plot is not None: varname = var.plot.name if checkname is not None and varname != checkname: # only check plotname! raise VariableError, "Expected variable name '{}', found '{}'.".format(checkname,varname) else: varname = var.atts['name'] if np.issubdtype(var.dtype, np.datetime64): val = var.data_array.copy() # need to preserve dates else: val = var.getArray(unmask=True, fillValue=np.NaN, dtype=np.float, copy=True) # the data to plot # N.B.: matplotlib does not understand masked arrays, therefor we have to convert masked values to NaN's # (and convert the data to float in the process...) if var.plot is not None: if var.units != var.plot.units: val *= var.plot.scalefactor val += var.plot.offset varunits = var.plot.units else: varunits = var.atts['units'] if checkunits is not None and varunits != checkunits: raise VariableError, "Units for variable '{}': expected {}, found {}.".format(var.name,checkunits,varunits) # some post-processing if val.size > 1: val = val.squeeze() if lsmooth: val = smooth(val) if lperi: if laxis: delta = np.diff(val) val = np.concatenate((val[:1]-delta[:1],val,val[-1:]+delta[-1:])) else: val = np.concatenate((val[-1:],val,val[:1])) # return values, units, name return val, varunits, varname
def getPlotValues(var, checkunits=None, checkname=None, lsmooth=False, lperi=False, pseudo_axis=None, laxis=False): ''' Helper function to check variable/axis, get (scaled) values for plot, and return appropriate units. ''' # figure out units if var.plot is not None: varname = var.plot.name if checkname is not None and varname != checkname: # only check plotname! raise VariableError( "Expected variable name '{}', found '{}'.".format( checkname, varname)) else: varname = var.atts['name'] if np.issubdtype(var.dtype, np.datetime64): val = var.data_array.copy() # need to preserve dates else: val = var.getArray(unmask=True, fillValue=np.NaN, dtype=np.float, copy=True) # the data to plot # N.B.: matplotlib does not understand masked arrays, therefor we have to convert masked values to NaN's # (and convert the data to float in the process...) if var.plot is not None: if var.units != var.plot.units: val *= var.plot.scalefactor val += var.plot.offset varunits = var.plot.units else: varunits = var.atts['units'] if checkunits is not None and varunits != checkunits: raise VariableError( "Units for variable '{}': expected {}, found {}.".format( var.name, checkunits, varunits)) # some post-processing if val.size > 1: val = val.squeeze() if lsmooth: val = smooth(val) if lperi: if laxis: delta = np.diff(val) val = np.concatenate( (val[:1] - delta[:1], val, val[-1:] + delta[-1:])) else: val = np.concatenate((val[-1:], val, val[:1])) # return values, units, name return val, varunits, varname
def getPlotValues(var, checkunits=None, checkname=None, lsmooth=False, lperi=False, laxis=False): ''' Helper function to check variable/axis, get (scaled) values for plot, and return appropriate units. ''' # figure out units if var.plot is not None: varname = var.plot.name if checkname is not None and varname != checkname: # only check plotname! raise VariableError, "Expected variable name '{}', found '{}'.".format( checkname, varname) else: varname = var.atts['name'] val = var.getArray(unmask=True, copy=True) # the data to plot if var.plot is not None: if var.units != var.plot.units: val *= var.plot.scalefactor val += var.plot.offset varunits = var.plot.units else: varunits = var.atts['units'] if checkunits is not None and varunits != checkunits: raise VariableError, "Units for variable '{}': expected {}, found {}.".format( var.name, checkunits, varunits) # some post-processing val = val.squeeze() if lsmooth: val = smooth(val) if lperi: if laxis: delta = np.diff(val) val = np.concatenate( (val[:1] - delta[:1], val, val[-1:] + delta[-1:])) else: val = np.concatenate((val[-1:], val, val[:1])) # return values, units, name return val, varunits, varname
def linePlot(varlist, ax=None, fig=None, linestyles=None, varatts=None, legend=None, xline=None, yline=None, title=None, flipxy=None, xlabel=None, ylabel=None, xlim=None, ylim=None, lsmooth=False, lprint=False, **kwargs): ''' A function to draw a list of 1D variables into an axes, and annotate the plot based on variable properties. ''' warn('Deprecated function: use Figure or Axes class methods.') # create axes, if necessary if ax is None: if fig is None: fig, ax = getFigAx(1) # single panel else: ax = fig.axes[0] # varlist is the list of variable objects that are to be plotted #print varlist if isinstance(varlist, Variable): varlist = [varlist] elif not isinstance(varlist, (tuple, list)) or not all( [isinstance(var, Variable) for var in varlist]): raise TypeError for var in varlist: var.squeeze() # remove singleton dimensions # linestyles is just a list of line styles for each plot if isinstance(linestyles, (basestring, NoneType)): linestyles = [linestyles] * len(varlist) elif not isinstance(linestyles, (tuple, list)): if not all([isinstance(linestyles, basestring) for var in varlist]): raise TypeError if len(varlist) != len(linestyles): raise ListError, "Failed to match linestyles to varlist!" # varatts are variable-specific attributes that are parsed for special keywords and then passed on to the if varatts is None: varatts = [dict()] * len(varlist) elif isinstance(varatts, dict): tmp = [ varatts[var.name] if var.name in varatts else dict() for var in varlist ] if any(tmp): varatts = tmp # if any variable names were found else: varatts = [varatts] * len( varlist ) # assume it is one varatts dict, which will be used for all variables elif not isinstance(varatts, (tuple, list)): raise TypeError if not all([isinstance(atts, dict) for atts in varatts]): raise TypeError # check axis: they need to have only one axes, which has to be the same for all! if len(varatts) != len(varlist): raise ListError, "Failed to match varatts to varlist!" for var in varlist: if var.ndim > 1: raise AxisError, "Variable '{}' has more than one dimension; consider squeezing.".format( var.name) elif var.ndim == 0: raise AxisError, "Variable '{}' is a scalar; consider display as a line.".format( var.name) # loop over variables plts = [] varname = None varunits = None axname = None axunits = None # list of plot handles for var, linestyle, varatt in zip(varlist, linestyles, varatts): varax = var.axes[0] # scale axis and variable values axe, axunits, axname = getPlotValues(varax, checkunits=axunits, checkname=None) val, varunits, varname = getPlotValues(var, checkunits=varunits, checkname=None) # variable and axis scaling is not always independent... if var.plot is not None and varax.plot is not None: if 'preserve' in var.plot and 'scalefactor' in varax.plot: if varax.units != axunits and var.plot.preserve == 'area': val /= varax.plot.scalefactor # figure out keyword options kwatts = kwargs.copy() kwatts.update(varatt) # join individual and common attributes if 'label' not in kwatts: kwatts['label'] = var.name # default label: variable name # N.B.: other scaling behavior could be added here if lprint: print varname, varunits, val.mean() if lsmooth: val = smooth(val) # figure out orientation if flipxy: xx, yy = val, axe else: xx, yy = axe, val # call plot function if linestyle is None: plts.append(ax.plot(xx, yy, **kwatts)[0]) else: plts.append(ax.plot(xx, yy, linestyle, **kwatts)[0]) # set axes limits if isinstance(xlim, (list, tuple)) and len(xlim) == 2: ax.set_xlim(*xlim) elif xlim is not None: raise TypeError if isinstance(ylim, (list, tuple)) and len(ylim) == 2: ax.set_ylim(*ylim) elif ylim is not None: raise TypeError # set title if title is not None: ax.set_title(title, dict(fontsize='medium')) pos = ax.get_position() pos = pos.from_bounds(x0=pos.x0, y0=pos.y0, width=pos.width, height=pos.height - 0.03) ax.set_position(pos) # set axes labels if flipxy: xname, xunits, yname, yunits = varname, varunits, axname, axunits else: xname, xunits, yname, yunits = axname, axunits, varname, varunits if not xlabel: xlabel = '{0:s} [{1:s}]'.format( xname, xunits) if xunits else '{0:s}'.format(xname) else: xlabel = xlabel.format(xname, xunits) if not ylabel: ylabel = '{0:s} [{1:s}]'.format( yname, yunits) if yunits else '{0:s}'.format(yname) else: ylabel = ylabel.format(yname, yunits) # a typical custom label that makes use of the units would look like this: 'custom label [{1:s}]', # where {} will be replaced by the appropriate default units (which have to be the same anyway) xpad = 2 xticks = ax.get_xaxis().get_ticklabels() ypad = -2 yticks = ax.get_yaxis().get_ticklabels() # len(xticks) > 0 is necessary to avoid errors with AxesGrid, which removes invisible tick labels if len(xticks) > 0 and xticks[-1].get_visible(): ax.set_xlabel(xlabel, labelpad=xpad) elif len(yticks) > 0 and not title: yticks[0].set_visible(False) # avoid overlap if len(yticks) > 0 and yticks[-1].get_visible(): ax.set_ylabel(ylabel, labelpad=ypad) elif len(xticks) > 0: xticks[0].set_visible(False) # avoid overlap # make monthly ticks if axname == 'time' and axunits == 'month': ax.xaxis.set_minor_locator( mpl.ticker.AutoMinorLocator(2)) # ax.minorticks_on() # add legend if legend: legatts = dict() if ax.get_yaxis().get_label(): legatts['fontsize'] = ax.get_yaxis().get_label().get_fontsize() if isinstance(legend, dict): legatts.update(legend) elif isinstance(legend, (int, np.integer, float, np.inexact)): legatts['loc'] = legend ax.legend(**legatts) # add orientation lines if isinstance(xline, (int, np.integer, float, np.inexact)): ax.axhline(y=xline, color='black') elif isinstance(xline, dict): ax.axhline(**xline) if isinstance(yline, (int, np.integer, float, np.inexact)): ax.axvline(x=yline, color='black') elif isinstance(xline, dict): ax.axvline(**yline) # return handle return plts
def linePlot(varlist, ax=None, fig=None, linestyles=None, varatts=None, legend=None, xline=None, yline=None, title=None, flipxy=None, xlabel=None, ylabel=None, xlim=None, ylim=None, lsmooth=False, lprint=False, **kwargs): ''' A function to draw a list of 1D variables into an axes, and annotate the plot based on variable properties. ''' warn('Deprecated function: use Figure or Axes class methods.') # create axes, if necessary if ax is None: if fig is None: fig,ax = getFigAx(1) # single panel else: ax = fig.axes[0] # varlist is the list of variable objects that are to be plotted #print varlist if isinstance(varlist,Variable): varlist = [varlist] elif not isinstance(varlist,(tuple,list)) or not all([isinstance(var,Variable) for var in varlist]): raise TypeError for var in varlist: var.squeeze() # remove singleton dimensions # linestyles is just a list of line styles for each plot if isinstance(linestyles,(basestring,NoneType)): linestyles = [linestyles]*len(varlist) elif not isinstance(linestyles,(tuple,list)): if not all([isinstance(linestyles,basestring) for var in varlist]): raise TypeError if len(varlist) != len(linestyles): raise ListError, "Failed to match linestyles to varlist!" # varatts are variable-specific attributes that are parsed for special keywords and then passed on to the if varatts is None: varatts = [dict()]*len(varlist) elif isinstance(varatts,dict): tmp = [varatts[var.name] if var.name in varatts else dict() for var in varlist] if any(tmp): varatts = tmp # if any variable names were found else: varatts = [varatts]*len(varlist) # assume it is one varatts dict, which will be used for all variables elif not isinstance(varatts,(tuple,list)): raise TypeError if not all([isinstance(atts,dict) for atts in varatts]): raise TypeError # check axis: they need to have only one axes, which has to be the same for all! if len(varatts) != len(varlist): raise ListError, "Failed to match varatts to varlist!" for var in varlist: if var.ndim > 1: raise AxisError, "Variable '{}' has more than one dimension; consider squeezing.".format(var.name) elif var.ndim == 0: raise AxisError, "Variable '{}' is a scalar; consider display as a line.".format(var.name) # loop over variables plts = []; varname = None; varunits = None; axname = None; axunits = None # list of plot handles for var,linestyle,varatt in zip(varlist,linestyles,varatts): varax = var.axes[0] # scale axis and variable values axe, axunits, axname = getPlotValues(varax, checkunits=axunits, checkname=None) val, varunits, varname = getPlotValues(var, checkunits=varunits, checkname=None) # variable and axis scaling is not always independent... if var.plot is not None and varax.plot is not None: if 'preserve' in var.plot and 'scalefactor' in varax.plot: if varax.units != axunits and var.plot.preserve == 'area': val /= varax.plot.scalefactor # figure out keyword options kwatts = kwargs.copy(); kwatts.update(varatt) # join individual and common attributes if 'label' not in kwatts: kwatts['label'] = var.name # default label: variable name # N.B.: other scaling behavior could be added here if lprint: print varname, varunits, val.mean() if lsmooth: val = smooth(val) # figure out orientation if flipxy: xx,yy = val, axe else: xx,yy = axe, val # call plot function if linestyle is None: plts.append(ax.plot(xx, yy, **kwatts)[0]) else: plts.append(ax.plot(xx, yy, linestyle, **kwatts)[0]) # set axes limits if isinstance(xlim,(list,tuple)) and len(xlim)==2: ax.set_xlim(*xlim) elif xlim is not None: raise TypeError if isinstance(ylim,(list,tuple)) and len(ylim)==2: ax.set_ylim(*ylim) elif ylim is not None: raise TypeError # set title if title is not None: ax.set_title(title, dict(fontsize='medium')) pos = ax.get_position() pos = pos.from_bounds(x0=pos.x0, y0=pos.y0, width=pos.width, height=pos.height-0.03) ax.set_position(pos) # set axes labels if flipxy: xname,xunits,yname,yunits = varname,varunits,axname,axunits else: xname,xunits,yname,yunits = axname,axunits,varname,varunits if not xlabel: xlabel = '{0:s} [{1:s}]'.format(xname,xunits) if xunits else '{0:s}'.format(xname) else: xlabel = xlabel.format(xname,xunits) if not ylabel: ylabel = '{0:s} [{1:s}]'.format(yname,yunits) if yunits else '{0:s}'.format(yname) else: ylabel = ylabel.format(yname,yunits) # a typical custom label that makes use of the units would look like this: 'custom label [{1:s}]', # where {} will be replaced by the appropriate default units (which have to be the same anyway) xpad = 2; xticks = ax.get_xaxis().get_ticklabels() ypad = -2; yticks = ax.get_yaxis().get_ticklabels() # len(xticks) > 0 is necessary to avoid errors with AxesGrid, which removes invisible tick labels if len(xticks) > 0 and xticks[-1].get_visible(): ax.set_xlabel(xlabel, labelpad=xpad) elif len(yticks) > 0 and not title: yticks[0].set_visible(False) # avoid overlap if len(yticks) > 0 and yticks[-1].get_visible(): ax.set_ylabel(ylabel, labelpad=ypad) elif len(xticks) > 0: xticks[0].set_visible(False) # avoid overlap # make monthly ticks if axname == 'time' and axunits == 'month': ax.xaxis.set_minor_locator(mpl.ticker.AutoMinorLocator(2)) # ax.minorticks_on() # add legend if legend: legatts = dict() if ax.get_yaxis().get_label(): legatts['fontsize'] = ax.get_yaxis().get_label().get_fontsize() if isinstance(legend,dict): legatts.update(legend) elif isinstance(legend,(int,np.integer,float,np.inexact)): legatts['loc'] = legend ax.legend(**legatts) # add orientation lines if isinstance(xline,(int,np.integer,float,np.inexact)): ax.axhline(y=xline, color='black') elif isinstance(xline,dict): ax.axhline(**xline) if isinstance(yline,(int,np.integer,float,np.inexact)): ax.axvline(x=yline, color='black') elif isinstance(xline,dict): ax.axvline(**yline) # return handle return plts
if ax is None and ldetrend: ax = np.arange(var.size) # make dummy axis, if necessary if var.ndim != 1: shape = var.shape var = var.ravel() # flatten array, if necessary else: shape = None # apply optional detrending if ldetrend or ltrend: # fit linear trend trend = np.polyfit(ax, var, deg=degree, rcond=rcond, w=w, full=False, cov=False) # evaluate and subtract linear trend if ldetrend and ltrend: raise ArgumentError, "Can either return trend/polyfit or residuals, not both." elif ldetrend and not ltrend: var -= np.polyval(trend, ax) # residuals elif ltrend and not ldetrend: var = np.polyval(trend, ax) # residuals # apply optional smoothing if lsmooth and lresidual: raise ArgumentError, "Can either return smoothed array or residuals, not both." elif lsmooth: var = smooth(var, window_len=window_len, window=window) elif lresidual: var -= smooth(var, window_len=window_len, window=window) # return detrended and/or smoothed time-series if shape is not None: var = var.reshape(shape) return var # function to smooth a vector (numpy array): moving mean, nothing fancy def movingMean(x,i): ''' smooth a vector (x, numpy array) using a moving mean of window width 2*i+1 ''' if x.ndim > 1: raise ValueError xs = x.copy() # smoothed output vector i = 2*i d = i+1 # denominator for later while i>0: t = x.copy(); t[i:] = t[:-i]; xs += t t = x.copy(); t[:-i] = t[i:]; xs += t