docs: def series(self, name, interval, **kwargs):
      '''
      Return all the data in a named time series for a given interval. If steps
      not defined and there are none in the config, defaults to 1.
  
      Returns an ordered dict of interval timestamps to a single interval, which
      matches the return value in get().
  
      If transform is defined, will utilize one of `[mean, count, min, max, sum]`
      to process each row of data returned. If the transform is a callable, will
      pass an array of data to the function. Note that the transform will be run
      after the data is condensed.
  
      Raises UnknownInterval if `interval` not configured.
      '''
      config = self._intervals.get(interval)
      if not config:
        raise UnknownInterval(interval)
  
      start = kwargs.get('start')
      end = kwargs.get('end')
      steps = kwargs.get('steps') or config.get('steps',1)
  
      fetch = kwargs.get('fetch')
      process_row = kwargs.get('process_row') or self._process_row
      condense = kwargs.get('condense', False)
      join_rows = kwargs.get('join_rows') or self._join
      collapse = kwargs.get('collapse', False)
      transform = kwargs.get('transform')
      # DEPRECATED handle the deprecated version of condense
      condense = kwargs.get('condensed',condense)
  
      # If collapse, also condense
      if collapse: condense = condense or True
  
      # Fugly range determination, all to get ourselves a start and end
      # timestamp. Adjust steps argument to include the anchoring date.
      if end is None:
        if start is None:
          end = time.time()
          end_bucket = config['i_calc'].to_bucket( end )
          start_bucket = config['i_calc'].to_bucket( end, (-steps+1) )
        else:
          start_bucket = config['i_calc'].to_bucket( start )
          end_bucket = config['i_calc'].to_bucket( start, steps-1 )
      else:
        end_bucket = config['i_calc'].to_bucket( end )
        if start is None:
          start_bucket = config['i_calc'].to_bucket( end, (-steps+1) )
        else:
          start_bucket = config['i_calc'].to_bucket( start )
  
      # Now that we have start and end buckets, convert them back to normalized
      # time stamps and then back to buckets. :)
      start = config['i_calc'].from_bucket( start_bucket )
      end = config['i_calc'].from_bucket( end_bucket )
      if start > end: end = start
  
      interval_buckets = config['i_calc'].buckets(start, end)
  
      # If name is a list, then join all of results. It is more efficient to
      # use a single data structure and join "in-line" but that requires a major
      # refactor of the backends, so trying this solution to start with. At a
      # minimum we'd have to rebuild the results anyway because of the potential
      # for sparse data points would result in an out-of-order result.
      if isinstance(name, (list,tuple,set)):
        results = [ self._series(x, interval, config, interval_buckets, fetch=fetch, process_row=process_row) for x in name ]
        rval = self._join_results( results, config['coarse'], join_rows )
      else:
        rval = self._series(name, interval, config, interval_buckets, fetch=fetch, process_row=process_row)
  
      # If fine-grained, first do the condensed pass so that it's easier to do
      # the collapse afterwards. Be careful not to run the transform if there's
      # going to be another pass at condensing the data.
      if not config['coarse']:
        if condense:
          condense = condense if callable(condense) else self._condense
          for key in rval.iterkeys():
            data = condense( rval[key] )
            if transform and not collapse:
              data = self._process_transform(data, transform, config['i_calc'].step_size(key))
            rval[key] = data
        elif transform:
          for interval,resolutions in rval.items():
            for key in resolutions.iterkeys():
              resolutions[key] = self._process_transform(resolutions[key], transform, config['r_calc'].step_size(key))
  
      if config['coarse'] or collapse:
        if collapse:
          collapse = collapse if callable(collapse) else condense if callable(condense) else self._condense
          data = collapse(rval)
          if transform:
            rval = { rval.keys()[0] : self._process_transform(data, transform, config['i_calc'].step_size(rval.keys()[0], rval.keys()[-1])) }
          else:
            rval = { rval.keys()[0] : data }
  
        elif transform:
          for key,data in rval.items():
            rval[key] = self._process_transform(data, transform, config['i_calc'].step_size(key))
  
      return rval