text: """Functions for plotting sipper data."""
  
  from collections import defaultdict
  import datetime
  
  import matplotlib as mpl
  import matplotlib.dates as mdates
  import matplotlib.pyplot as plt
  import numpy as np
  import pandas as pd
  from scipy import stats
  import seaborn as sns
  
  from sipper import SipperError
  
  #---dates and shading
  
  def convert_dt64_to_dt(dt64):
      """Converts numpy datetime to standard datetime (needed for shade_darkness
      function in most cases)."""
      new_date = ((dt64 - np.datetime64('1970-01-01T00:00:00'))/
                  np.timedelta64(1, 's'))
      new_date = datetime.datetime.utcfromtimestamp(new_date)
      return new_date
  
  def hours_between(start, end, convert=True):
      """
      Create a range of hours between two dates.
  
      Parameters
      ----------
      start, end : datetime-like object
          When to begin and end the data range
      convert : bool, optional
          Whether to convert the start/end arguments from numpy datetime to
          standard datetime. The default is True.
  
      Returns
      -------
      pandas DateTimeIndex
          Index array of all hours between start and end.
      """
      if convert:
          start = convert_dt64_to_dt(start)
          end = convert_dt64_to_dt(end)
      rounded_start = datetime.datetime(year=start.year,
                                        month=start.month,
                                        day=start.day,
                                        hour=start.hour)
      rounded_end = datetime.datetime(year=end.year,
                                      month=end.month,
                                      day=end.day,
                                      hour=end.hour)
      return pd.date_range(rounded_start,rounded_end,freq='1H')
  
  def is_day_or_night(time, period, lights_on=7, lights_off=19):
      """
      Check if a datetime occured at day or night
  
      Parameters
      ----------
      time : datetime or pandas.Timestamp
          time to check
      period : str
          'day' or 'night', which period to check if the date is part of,
          based on the lights_on and lights_off arguments
      lights_on : int, optional
          Hour of the day (0-23) when lights turn on. The default is 7.
      lights_off : int, optional
           Hour of the day (0-23) when lights turn off. The default is 19.
  
      Returns
      -------
      Bool
      """
      lights_on = datetime.time(hour=lights_on)
      lights_off = datetime.time(hour=lights_off)
      val = False
      #defaults to checking if at night
      if lights_off > lights_on:
          val = time.time() >= lights_off or time.time() < lights_on
      elif lights_off < lights_on:
          val = time.time() >= lights_off and time.time() < lights_on
      #reverses if period='day'
      return val if period=='night' else not val
  
  def get_daynight_count(start_time, end_time, lights_on=7, lights_off=9):
      """
      Compute the (fractional) number of completed light and dark periods
      between two dates.  Used for normalizing values grouped by day & nightime.
  
      Parameters
      ----------
      start_time : datetime
          starting time
      end_time : datetime
          ending time
      lights_on : int, optional
          Hour of the day (0-23) when lights turn on. The default is 7.
      lights_off : int, optional
          Hour of the day (0-23) when lights turn off. The default is 19.
  
      Returns
      -------
      dict
          dictionary with keys "day" and "night", values are the
          number of completed periods for each key.
      """
      cuts = []
      cuts.append(start_time)
      loop_time = start_time.replace(minute=0,second=0)
      while loop_time < end_time:
          loop_time += pd.Timedelta(hours=1)
          if loop_time.hour == lights_on:
              cuts.append(loop_time)
          elif loop_time.hour == lights_off:
              cuts.append(loop_time)
      cuts.append(end_time)
      days = []
      nights = []
      if lights_off > lights_on:
          day_hours = lights_off - lights_on
          night_hours = 24 - day_hours
      else:
          night_hours = lights_on - lights_off
          day_hours = 24 - night_hours
      day_hours = pd.Timedelta(hours = day_hours)
      night_hours = pd.Timedelta(hours = night_hours)
      for i, t in enumerate(cuts[:-1]):
          if is_day_or_night(t, 'day', lights_on, lights_off):
              days.append((cuts[i+1] - t)/day_hours)
          else:
              nights.append((cuts[i+1] - t)/night_hours)
      return {'day':sum(days),'night':sum(nights)}
  
  def night_intervals(array, lights_on, lights_off, instead_days=False):
      """
      Find intervals of a date-array corresponding to night time.
  
      Parameters
      ----------
      array : array-like
          Array of datetimes (e.g. generated by hours_between).
      lights_on : int
          Integer between 0 and 23 representing when the light cycle begins.
      lights_off : int
          Integer between 0 and 23 representing when the light cycle ends.
      instead_days : bool, optional
          Return intervals during daytime instead of nighttime.
          The default is False.
  
      Returns
      -------
      night_intervals : list
          List of tuples with structure (start of nighttime, end of nighttime).
      """
      night_intervals = []
      on_time = datetime.time(hour=lights_on)
      off_time = datetime.time(hour=lights_off)
      if on_time == off_time:
          return night_intervals
      else:
          at_night = [is_day_or_night(i, 'night',
                                      lights_on=lights_on,
                                      lights_off=lights_off) for i in array]
      if instead_days:
          at_night = [not i for i in at_night]
      if len(at_night) == 0:
          return night_intervals
      night_starts = []
      night_ends = []
      if at_night[0] == True:
          night_starts.append(array[0])
      for i, _ in enumerate(at_night[1:],start=1):
          if at_night[i] == True and at_night[i-1] == False:
              night_starts.append(array[i])
          elif at_night[i] == False and at_night[i-1] == True:
              night_ends.append(array[i])
      if at_night[-1] == True:
          night_ends.append(array[-1])
      night_intervals = list(zip(night_starts, night_ends))
      return night_intervals
  
  def shade_darkness(ax, min_date, max_date, lights_on, lights_off,
                     convert=True):
      """
      Shade the night periods of a matplotlib Axes with a datetime x-axis.
  
      Parameters
      ----------
      ax : matplotlib.axes.Axes
          Plot Axes.
      min_date : datetime
          Earliest date to shade.
      max_date : datetime
          Latest date to shade.
      lights_on : int
          Integer between 0 and 23 representing when the light cycle begins.
      lights_off : int
          Integer between 0 and 23 representing when the light cycle ends.
      convert : bool, optional
          Whether to convert the start/end arguments from numpy datetime to
          standard datetime. The default is True.
  
      Returns
      -------
      None.
      """
      hours_list = hours_between(min_date, max_date,convert=convert)
      nights = night_intervals(hours_list, lights_on=lights_on,
                               lights_off=lights_off)
      if nights:
          for i, interval in enumerate(nights):
              start = interval[0]
              end = interval[1]
              if start != end:
                  ax.axvspan(start,
                             end,
                             color='gray',
                             alpha=.2,
                             label='_'*i + 'lights off',
                             zorder=0)
  
  def date_format_x(ax, start, end):
      """
      Format the x-ticks of datetime plots created by FED3 Viz.  Handles various
      incoming dates by lowering the (time) frequency of ticks with longer
      date ranges.
  
      Parameters
      ----------
      ax : matplotlib.axes.Axes
          Graph Axes
      start : datetime
          Earliest x-position of the graph
      end : datetime
          Latest x-position of the graph
  
      Returns
      -------
      None.
      """
      quarter_hours = mdates.MinuteLocator(byminute=[0,15,30,45])
      all_hours = mdates.HourLocator()
      quarter_days = mdates.HourLocator(byhour=[0,6,12,18])
      days = mdates.DayLocator()
      two_days = mdates.DayLocator(interval=2)
      three_days = mdates.DayLocator(interval=3)
      months = mdates.MonthLocator()
      d8_span = end - start
      if d8_span < datetime.timedelta(hours=12):
          xfmt = mdates.DateFormatter('%H:%M')
          major = all_hours
          minor = quarter_hours
      elif ((d8_span >= datetime.timedelta(hours=12))
            and (d8_span < datetime.timedelta(hours=24))):
          xfmt = mdates.DateFormatter('%b %d %H:%M')
          major = quarter_days
          minor = all_hours
      elif ((d8_span >= datetime.timedelta(hours=24))
            and (d8_span < datetime.timedelta(days=3))):
          xfmt = mdates.DateFormatter('%b %d %H:%M')
          major = days
          minor = quarter_days
      elif (d8_span >= datetime.timedelta(days=3)
            and (d8_span < datetime.timedelta(days=6))):
          xfmt = mdates.DateFormatter('%b %d %H:%M')
          major = two_days
          minor = days
      elif ((d8_span >= datetime.timedelta(days=6))
            and (d8_span < datetime.timedelta(days=20))):
          xfmt = mdates.DateFormatter('%b %d')
          major = three_days
          minor = days
      elif d8_span >= datetime.timedelta(days=20):
          xfmt = mdates.DateFormatter("%b '%y")
          major = months
          minor = three_days
          plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
      ax.xaxis.set_major_locator(major)
      ax.xaxis.set_major_formatter(xfmt)
      ax.xaxis.set_minor_locator(minor)
  
  #---interdrink interval helpers
  def get_any_idi(sipper):
      """
      Returns the interdrink intervals for a Sipper,
      disregarding side or bottle contents
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
  
      Returns
      -------
      idi_minutes : pandas.Series
          array of the interdrink intervals in minutes
  
      """
      data = sipper.data
      combined = data['LeftCount'].diff() + data['RightCount'].diff()
      combined.dropna(inplace=True)
      combined = combined[combined > 0]
      idi_delta = combined.index.to_series().diff().dropna()
      idi_minutes = idi_delta.dt.total_seconds()/60
      return idi_minutes
  
  def get_side_idi(sipper, side):
      """
      Returns the interdrink intervals for the left or right bottle of a Sipper
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
  
      side : str ('left' or 'right')
          side to return the interdrink intervals for
  
      Returns
      -------
      idi_minutes : pandas.Series
          array of the interdrink intervals in minutes
  
      """
      data = sipper.data
      col = 'LeftCount' if side.lower() == 'left' else 'RightCount'
      diff = data[col].diff().dropna()
      diff = diff[diff > 0]
      idi_delta = diff.index.to_series().diff().dropna()
      idi_minutes = idi_delta.dt.total_seconds()/60
      return idi_minutes
  
  def get_content_idi(sipper, content, df=pd.DataFrame()):
      """
      Returns the interdrink intervals for specific bottle contents of a Sipper
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
  
      content : str
          str name of content to get values for
  
      df : pandas.DataFrame, optional
          a DataFrame of sipper data to get the values for, can be passed
          when you want values for a modified version of data (e.g.
          after doing a global date filter)
  
      Returns
      -------
      idi_minutes : pandas.Series
          array of the interdrink intervals in minutes
  
      """
      vals = sipper.get_content_values(content, out='Count', df=df)
      if vals.empty:
          return vals
      diff = vals.diff().dropna()
      diff = diff[diff > 0]
      idi_delta = diff.index.to_series().diff().dropna()
      idi_minutes = idi_delta.dt.total_seconds()/60
      return idi_minutes
  
  def setup_idi_axes(ax, logx):
      """
      Helper to prepare plots for interdrink interval histograms
  
      Parameters
      ----------
      ax : matplotlib.axes.Axes
          plot axes
      logx : bool
          whether or not to create a logarithmic x-axis
  
      Returns
      -------
      None.
  
      """
      ax.set_xlabel('Minutes Between Drinks')
      ax.set_title('Interdrink Interval Plot')
      if logx:
          lowest = -2
          highest = 5
          ax.set_xticks(range(lowest,highest))
          ax.set_xticklabels([10**num for num in range(-2,5)], rotation=45)
          ax.set_xlim(-2.5, 5.1)
      else:
          ax.set_xticks([0,300,600,900])
          ax.set_xlim(-100,1000)
  
  #---circadian helpers
  def get_chronogram_vals(series, lights_on, lights_off):
      """
      Convert a time series to chronongram values (i.e. averaged
      by hour for the light cycle)
  
      Parameters
      ----------
      series : pandas.Series
          time series data
      lights_on : int
          Integer from 0-23 denoting start of light cycle
      lights_off : int
          Integer from 0-23 denoting end of light cycle
  
      Returns
      -------
      reindexed : pandas.Series
          Series of chronogram values, with 0 being start of the light cycle
  
      """
      byhour = series.groupby([series.index.hour]).sum()
      byhourday = series.groupby([series.index.hour, series.index.date])
      num_days_by_hour = byhourday.sum().index.get_level_values(0).value_counts()
      byhour = byhour.divide(num_days_by_hour, axis=0)
      new_index = list(range(lights_on, 24)) + list(range(0,lights_on))
      reindexed = byhour.reindex(new_index)
      reindexed.index.name = 'hour'
      reindexed = reindexed.fillna(0)
      return reindexed
  
  #---averageing helpers
  def preproc_averaging(data, averaging='datetime', avg_bins='1H',
                        agg='sum'):
      """
      Average data for SipperViz
  
      Parameters
      ----------
      data : collection
          collection of pandas.Series to average
      averaging : str, optional
          Style of averaging. The default is 'datetime'.
          - 'datetime' = average in absolute time (no alignment, fails for
            time series which did not cooccur)
          - 'time' = align by time of day and then average
          - 'elapsed' = align by start of recording and then average
      avg_bins : str, optional
          Bin size to use for downsampling. The default is '1H'.
      agg : str, optional
          Function to aggregate data after downsampling; this is a
          string name of a function used by pandas for resampling.
          The default is 'sum'.
  
      Raises
      ------
      SipperError
          When "averaging" parameter is not recognized
  
      Returns
      -------
      output : dict
          Dictionary of results, with keys:
              - 'x' : x posititions of data
              - 'ys' : averaged data
      """
      if averaging not in ['datetime','time','elapsed']:
          raise SipperError('averaging must be "datetime", "time", or "elapsed"')
      output = {}
      output['ys'] = []
      if averaging == 'datetime':
          earliest_end = pd.Timestamp(2200,1,1,0,0,0)
          latest_start = pd.Timestamp(1970,1,1,0,0,0)
          for d in data:
              if min(d.index) > latest_start:
                  latest_start = min(d.index)
              if max(d.index) < earliest_end:
                  earliest_end = max(d.index)
          for d in data:
              if latest_start not in d.index:
                  d.loc[latest_start] = np.nan
              r = d.resample(avg_bins).apply(agg)
              r = r[(r.index >= latest_start) &
                    (r.index <= earliest_end)].copy()
              output['ys'].append(r)
          output['x'] = r.index
      elif averaging == 'time':
          earliest_start = pd.Timestamp(2200,1,1,0,0,0)
          latest_end = pd.Timestamp(1970,1,1,0,0,0)
          shifted = []
          for d in data:
              r = d.resample(avg_bins).apply(agg)
              first = r.index[0]
              aligned = pd.Timestamp(year=1970, month=1, day=1, hour=first.hour)
              shift = first - aligned
              r.index = [i-shift for i in r.index]
              if r.index.min() < earliest_start:
                  earliest_start = r.index.min()
              if r.index.max() > latest_end:
                  latest_end = r.index.max()
              shifted.append(r)
          full_dr = pd.date_range(earliest_start, latest_end, freq=avg_bins)
          output['x'] = full_dr
          for s in shifted:
              reindexed = s.reindex(full_dr)
              output['ys'].append(reindexed)
      elif averaging == 'elapsed':
          maxx = pd.Timedelta(0)
          elapsed_data = []
          for d in data:
              origin = d.index[0]
              elapsed = [i - origin for i in d.index]
              d.index = elapsed
              r = d.resample(avg_bins).apply(agg)
              if r.index.max() > maxx:
                  longest_index = r.index
              elapsed_data.append(r)
          output['x'] = longest_index.total_seconds()/3600
          for s in elapsed_data:
              reindexed = s.reindex(longest_index)
              reindexed.index = reindexed.index.total_seconds()/3600
              output['ys'].append(reindexed)
      return output
  
  def format_averaging_axes(ax, averaging, xdata, shade_dark=True,
                            lights_on=7, lights_off=19):
      """
      Helper function to setup axes for average plots in SipperViz
  
      Parameters
      ----------
      ax : matplotlib.axes.Axes
          plot axes
      averaging : str, optional
          Style of averaging. The default is 'datetime', other options
          are 'time' and 'elapsed'
      xdata : array
          x-positions of plotted data.  If multiple lines were plotted,
          this array should encompass all of them
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
          Has no effect when averaging is 'elapsed'. The default is True.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
  
      Returns
      -------
      None.
  
      """
      if averaging  == 'datetime':
          mindate = pd.Timestamp(2200,1,1,0,0,0)
          maxdate = pd.Timestamp(1970,1,1,0,0,0)
          for x in xdata:
              if x.min() < mindate:
                  mindate = x.min()
              if x.max() > maxdate:
                  maxdate = x.max()
          ax.set_xlabel('Date')
          date_format_x(ax, mindate, maxdate)
          if shade_dark:
              shade_darkness(ax, mindate, maxdate, lights_on, lights_off)
      elif averaging == 'time':
          mindate = pd.Timestamp(2200,1,1,0,0,0)
          maxdate = pd.Timestamp(1970,1,1,0,0,0)
          for x in xdata:
              if x.min() < mindate:
                  mindate = x.min()
              if x.max() > maxdate:
                  maxdate = x.max()
          start_hour = mindate.strftime('%I%p')
          if start_hour[0] == '0':
              start_hour = start_hour[1:]
          ax.set_xlabel('Hours Since {} on First Day'.format(start_hour))
          if shade_dark:
              shade_darkness(ax, mindate, maxdate,
                             lights_on=lights_on,
                             lights_off=lights_off,
                             convert=False)
          c = 12
          ticks = pd.date_range(mindate, maxdate, freq='{}H'.format(str(c)))
          tick_labels = [i*c for i in range(len(ticks))]
          while len(ticks) > 10:
              c += 12
              ticks = pd.date_range(mindate, maxdate, freq='{}H'.format(str(c)))
              tick_labels = [i*c for i in range(len(ticks))]
          ax.set_xticks(ticks)
          ax.set_xticklabels(tick_labels)
          ax.set_xlim(mindate, maxdate + datetime.timedelta(hours=5))
      elif averaging == 'elapsed':
          maxx = 0
          for x in xdata:
              if x.max() > maxx:
                  maxx = x.max()
          ax.set_xlabel('Elapsed Hours')
          c = 12
          ticks = range(0, int(maxx + 1), c)
          while len(ticks) > 10:
              c += 12
              ticks = range(0, len(maxx) + 1, c)
          ax.set_xticks(ticks)
  
  #---drink plots
  
  def drinkcount_cumulative(sipper, show_left=True, show_right=True,
                            show_content=[], shade_dark=True,
                            lights_on=7, lights_off=19, **kwargs):
      """
      Plot the cumulative drink count of a Sipper.
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
      show_left : bool, optional
          Show cumulative drinks for the left bottle
      show_right : bool, optional
          Show cumulative drinks for the right bottle
      show_content : collection, optional
          Array of contents to show drinks for. The default is [].
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' in kwargs:
          ax = kwargs['ax']
      else:
          fig, ax = plt.subplots()
      df = sipper.data
      if 'date_filter' in kwargs:
          s, e = kwargs['date_filter']
          df = df[(df.index >= s) &
                  (df.index <= e)].copy()
      if show_left:
          ax.plot(df.index, df['LeftCount'], drawstyle='steps', color='red',
                  label=sipper.left_name)
      if show_right:
          ax.plot(df.index, df['RightCount'], drawstyle='steps', color='blue',
                  label=sipper.right_name)
      content_max = df.index.min()
      content_min = df.index.max()
      if show_content:
          for c in show_content:
              count = sipper.get_content_values(c, out='Count', df=df)
              if not count.empty:
                  ax.plot(count.index, count, drawstyle='steps', label=c)
                  if count.index.max() > content_max:
                      content_max = count.index.max()
                  if count.index.min() < content_min:
                      content_min = count.index.min()
      if show_content and all([not show_left, not show_right]):
          dformat_min = content_min
          dformat_max = content_max
      else:
          dformat_min = df.index[0]
          dformat_max = df.index[-1]
      date_format_x(ax, dformat_min, dformat_max)
      ax.set_title('Drink Count for ' + sipper.filename)
      ax.set_ylabel('Total Drinks')
      ax.set_xlabel('Date')
      if shade_dark:
          shade_darkness(ax, dformat_min, dformat_max, lights_on, lights_off)
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def drinkcount_binned(sipper, binsize='1H', show_left=True, show_right=True,
                        show_content=[], shade_dark=True,
                        lights_on=7, lights_off=19, **kwargs):
      """
      Plot the binned drink count of a Sipper.
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
      binsize : str, optional
          Pandas time offset string to resample data. The default is '1H'.
      show_left : bool, optional
          Show cumulative drinks for the left bottle
      show_right : bool, optional
          Show cumulative drinks for the right bottle
      show_content : collection, optional
          Array of contents to show drinks for. The default is [].
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' in kwargs:
          ax = kwargs['ax']
      else:
          fig, ax = plt.subplots()
      df = sipper.data.copy()
      if 'date_filter' in kwargs:
          s, e = kwargs['date_filter']
          df = df[(df.index >= s) &
                  (df.index <= e)].copy()
      base = df.index[0].hour
      if show_left:
          l = df['LeftCount'].diff().resample(binsize, base=base).sum()
          ax.plot(l.index, l, color='red',
                  label=sipper.left_name)
      if show_right:
          r = df['RightCount'].diff().resample(binsize, base=base).sum()
          ax.plot(r.index, r, color='blue',
                  label=sipper.right_name)
      content_max = df.index.min()
      content_min = df.index.max()
      if show_content:
          for c in show_content:
              count = sipper.get_content_values(c, out='Count', df=df)
              binned = count.diff().resample(binsize, base=base).sum()
              if not count.empty:
                  ax.plot(binned.index, binned, label=c)
                  if count.index.max() > content_max:
                          content_max = count.index.max()
                  if count.index.min() < content_min:
                      content_min = count.index.min()
      if show_content and all([not show_left, not show_right]):
          dformat_min = content_min
          dformat_max = content_max
      else:
          dformat_min = df.index[0]
          dformat_max = df.index[-1]
      date_format_x(ax, dformat_min, dformat_max)
      ax.set_title('Drink Count for ' + sipper.filename)
      ax.set_ylabel('Drinks')
      ax.set_xlabel('Date')
      if shade_dark:
          shade_darkness(ax, dformat_min, dformat_max, lights_on, lights_off)
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def drinkduration_cumulative(sipper, show_left=True, show_right=True,
                               show_content=[], shade_dark=True,
                               lights_on=7, lights_off=19, **kwargs):
      """
      Plot the cumulative drink duration of a Sipper.
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
      show_left : bool, optional
          Show cumulative drinks for the left bottle
      show_right : bool, optional
          Show cumulative drinks for the right bottle
      show_content : collection, optional
          Array of contents to show drinks for. The default is [].
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' in kwargs:
          ax = kwargs['ax']
      else:
          fig, ax = plt.subplots()
      df = sipper.data
      if 'date_filter' in kwargs:
          s, e = kwargs['date_filter']
          df = df[(df.index >= s) &
                  (df.index <= e)].copy()
      if show_left:
          ax.plot(df.index, df['LeftDuration'], drawstyle='steps', color='red',
                  label=sipper.left_name)
      if show_right:
          ax.plot(df.index, df['RightDuration'], drawstyle='steps', color='blue',
                  label=sipper.right_name)
      content_max = df.index.min()
      content_min = df.index.max()
      if show_content:
          for c in show_content:
              count = sipper.get_content_values(c, out='Count', df=df)
              if not count.empty:
                  ax.plot(count.index, count, drawstyle='steps', label=c)
                  if count.index.max() > content_max:
                          content_max = count.index.max()
                  if count.index.min() < content_min:
                      content_min = count.index.min()
      if show_content and all([not show_left, not show_right]):
          dformat_min = content_min
          dformat_max = content_max
      else:
          dformat_min = df.index[0]
          dformat_max = df.index[-1]
      date_format_x(ax, dformat_min, dformat_max)
      ax.set_title('Drink Duration for ' + sipper.filename)
      ax.set_ylabel('Total Drink Duration (s)')
      ax.set_xlabel('Date')
      if shade_dark:
          shade_darkness(ax, dformat_min, dformat_max, lights_on, lights_off)
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def drinkduration_binned(sipper, binsize='1H', show_left=True, show_right=True,
                           show_content=[], shade_dark=True,
                           lights_on=7, lights_off=19, **kwargs):
      """
      Plot the binned drink count of a Sipper.
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
      binsize : str, optional
          Pandas time offset string to resample data. The default is '1H'.
      show_left : bool, optional
          Show cumulative drinks for the left bottle
      show_right : bool, optional
          Show cumulative drinks for the right bottle
      show_content : collection, optional
          Array of contents to show drinks for. The default is [].
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' in kwargs:
          ax = kwargs['ax']
      else:
          fig, ax = plt.subplots()
      df = sipper.data.copy()
      if 'date_filter' in kwargs:
          s, e = kwargs['date_filter']
          df = df[(df.index >= s) &
                  (df.index <= e)].copy()
      base = df.index[0].hour
      if show_left:
          l = df['LeftDuration'].diff().resample(binsize, base=base).sum()
          ax.plot(l.index, l, color='red',
                  label=sipper.left_name)
      if show_right:
          r = df['RightDuration'].diff().resample(binsize, base=base).sum()
          ax.plot(r.index, r, color='blue',
                  label=sipper.right_name)
      content_max = df.index.min()
      content_min = df.index.max()
      if show_content:
          for c in show_content:
              count = sipper.get_content_values(c, out='Duration', df=df)
              binned = count.diff().resample(binsize, base=base).sum()
              if not count.empty:
                  ax.plot(binned.index, binned, label=c)
                  if count.index.max() > content_max:
                          content_max = count.index.max()
                  if count.index.min() < content_min:
                      content_min = count.index.min()
      if show_content and all([not show_left, not show_right]):
          dformat_min = content_min
          dformat_max = content_max
      else:
          dformat_min = df.index[0]
          dformat_max = df.index[-1]
      date_format_x(ax, dformat_min, dformat_max)
      ax.set_title('Drink Duration for ' + sipper.filename)
      ax.set_ylabel('Drink Duration (s)')
      ax.set_xlabel('Date')
      if shade_dark:
          shade_darkness(ax, dformat_min, dformat_max, lights_on, lights_off)
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  #---interdrink intervals
  
  def interdrink_intervals(sippers, kde=True, logx=True,
                           combine=False, **kwargs):
      """
      Plot a histogram of the interdrink intervals of multiple Sippers
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      kde : bool, optional
          Include a kernel density estimation. The default is True.
      logx : bool, optional
          Use a logarithmic x-axis. The default is True.
      combine : bool, optional
          Concate the data from all sippers. The default is False,
          which plots data for each Sipper separately.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      setup_idi_axes(ax, logx)
      combined = []
      for sipper in sippers:
          df = sipper.data.copy()
          if 'date_filter' in kwargs:
              s, e = kwargs['date_filter']
              df = df[(df.index >= s) &
                      (df.index <= e)].copy()
          y = get_any_idi(sipper)
          if logx:
              y = [np.log10(val) for val in y if not pd.isna(val) if val != 0]
              bins = np.round(np.arange(-2, 5, .1), 2)
          else:
              bins = np.linspace(0, 900, 50)
          if combine:
              combined += list(y)
          else:
              sns.distplot(y, bins=bins, label=sipper.filename, ax=ax, norm_hist=False,
                           kde=kde)
      if combined:
          sns.distplot(combined, bins=bins, ax=ax, norm_hist=False, kde=kde)
      else:
          ax.legend(fontsize=8)
      ylabel = 'Density Estimation' if kde else 'Count'
      ax.set_ylabel(ylabel)
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def interdrink_intervals_byside(sippers, kde=True, logx=True, **kwargs):
      """
      Plot a histogram of the interdrink intervals of multiple Sippers,
      with one curve for each bottle
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      kde : bool, optional
          Include a kernel density estimation. The default is True.
      logx : bool, optional
          Use a logarithmic x-axis. The default is True.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      setup_idi_axes(ax, logx)
      colors = {'Left' : 'red',
                'Right' : 'blue'}
      for side in ['Left', 'Right']:
          combined = []
          for sipper in sippers:
              df = sipper.data.copy()
              if 'date_filter' in kwargs:
                  s, e = kwargs['date_filter']
                  df = df[(df.index >= s) &
                          (df.index <= e)].copy()
              y = get_side_idi(sipper, side)
              if logx:
                  y = [np.log10(val) for val in y if not pd.isna(val) if val != 0]
                  bins = np.round(np.arange(-2, 5, .1), 2)
              else:
                  bins = np.linspace(0, 900, 50)
              combined += list(y)
          sns.distplot(combined, bins=bins, label=side, ax=ax, norm_hist=False,
                       kde=kde, color=colors[side])
      ax.legend(fontsize=8)
      ylabel = 'Density Estimation' if kde else 'Count'
      ax.set_ylabel(ylabel)
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def interdrink_intervals_bycontent(sippers, idi_content, kde=True, logx=True, **kwargs):
      """
      Plot a histogram of the interdrink intervals of multiple Sippers,
      with one curve for each included content
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      show_content: collection:
          Array of str names of bottle contents
      kde : bool, optional
          Include a kernel density estimation. The default is True.
      logx : bool, optional
          Use a logarithmic x-axis. The default is True.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      setup_idi_axes(ax, logx)
      for c in idi_content:
          combined = []
          for sipper in sippers:
              df = sipper.data.copy()
              if 'date_filter' in kwargs:
                  s, e = kwargs['date_filter']
                  df = df[(df.index >= s) &
                          (df.index <= e)].copy()
              y = get_content_idi(sipper, c, df=df)
              if logx:
                  y = [np.log10(val) for val in y if not pd.isna(val) if val != 0]
                  bins = np.round(np.arange(-2, 5, .1), 2)
              else:
                  bins = np.linspace(0, 900, 50)
              combined += list(y)
          sns.distplot(combined, bins=bins, label=c, ax=ax, norm_hist=False,
                       kde=kde)
      ax.legend(fontsize=8)
      ylabel = 'Density Estimation' if kde else 'Count'
      ax.set_ylabel(ylabel)
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  #---chronograms
  
  def drinkcount_chronogram(sipper, circ_left=True, circ_right=True,
                            circ_content=None, lights_on=7,
                            lights_off=19, shade_dark=True, **kwargs):
      """
      Plot the drink count for a Sipper, taking an average for each hour
      of the day
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
      circ_left : bool, optional
          Show drinks for the left side. The default is True.
      circ_right : bool, optional
          Show drinks for the right side. The default is True.
      circ_content : collection, optional
          Show drinks for each content name in the collection. The default is None.
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      df = sipper.data
      if 'date_filter' in kwargs:
          s, e = kwargs['date_filter']
          df = df[(df.index >= s) &
                  (df.index <= e)].copy()
      to_plot = []
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      labels = []
      if circ_left:
          to_plot.append(df['LeftCount'].diff())
          colors.insert(0, 'red')
          labels.append('Left')
      if circ_right:
          to_plot.append(df['RightCount'].diff())
          colors.insert(0, 'blue')
          labels.append('Right')
      if circ_content:
          for c in circ_content:
              vals = sipper.get_content_values(c, 'Count', df=df).diff()
              if not vals.empty:
                  to_plot.append(vals)
                  labels.append(c)
      for i, series in enumerate(to_plot):
          reindexed = get_chronogram_vals(series, lights_on, lights_off)
          label = labels[i]
          ax.plot(range(0,24), reindexed, color=colors[i], label=label)
      ax.set_xlabel('Hours Into Light Cycle')
      ax.set_xticks([0,6,12,18,24])
      ax.set_ylabel('Drinks')
      ax.set_title('Chronogram')
      if shade_dark:
          new_index = list(range(lights_on, 24)) + list(range(0,lights_on))
          off = new_index.index(lights_off)
          ax.axvspan(off,24,color='gray',alpha=.2,zorder=0,label='lights off')
      ax.legend()
      plt.tight_layout()
  
      return fig if 'ax' not in kwargs else None
  
  def drinkcount_chronogram_grouped(sippers, groups, circ_left=True, circ_right=True,
                                    circ_content=None, circ_var='SEM',
                                    circ_show_indvl=False, lights_on=7,
                                    lights_off=19, shade_dark=True, **kwargs):
      '''
      Plot the drink count for multiple Sippers, taking an average for each hour
      of the day, and averaging those values for Groups.
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      groups : collection
          Array of group names, to aggregate Sipper objects based on their
          "groups" attribute.
      circ_left : bool, optional
          Show drinks for the left side. The default is True.
      circ_right : bool, optional
          Show drinks for the right side. The default is True.
      circ_content : collection, optional
          Show drinks for each content name in the collection. The default is None.
      circ_var : str, optional
          How to shade error for each group. The default is 'SEM', can also be
          'STD' or ther will be no effect.
      circ_show_indvl : bool, optional
          Include curve for each individual device. The default is False.
       shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      '''
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      to_plot = defaultdict(list)
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      for group in groups:
          for sipper in sippers:
              if group in sipper.groups:
                  df = sipper.data
                  if 'date_filter' in kwargs:
                      s, e = kwargs['date_filter']
                      df = df[(df.index >= s) &
                              (df.index <= e)].copy()
                  if circ_left:
                      key = group + ' - Left'
                      vals = get_chronogram_vals(df['LeftCount'].diff(),
                                                 lights_on,
                                                 lights_off)
                      to_plot[key].append(vals)
                  if circ_right:
                      key = group + ' - Right'
                      vals = get_chronogram_vals(df['RightCount'].diff(),
                                                 lights_on,
                                                 lights_off)
                      to_plot[key].append(vals)
                  if circ_content:
                      for c in circ_content:
                          key = group + ' - ' + c
                          content_vals = sipper.get_content_values(c, 'Count', df).diff()
                          if not content_vals.empty:
                              vals = get_chronogram_vals(content_vals,
                                                         lights_on,
                                                         lights_off)
                              to_plot[key].append(vals)
      for i, (label, data) in enumerate(to_plot.items()):
          x = range(0,24)
          y = np.nanmean(data, axis=0)
          error_shade = np.nan
          if circ_show_indvl:
              for d in data:
                  ax.plot(x, d, color=colors[i], alpha=.5, linewidth=.8)
          if circ_var == 'SEM':
              error_shade = stats.sem(data, axis=0, nan_policy='omit')
          elif circ_var == 'STD':
              error_shade = np.nanstd(data, axis=0)
          ax.plot(x, y, color=colors[i], label=label)
          ax.fill_between(x, y-error_shade, y+error_shade, color=colors[i],
                          alpha=.3)
      ax.set_xlabel('Hours Into Light Cycle')
      ax.set_xticks([0,6,12,18,24])
      ax.set_ylabel('Drinks')
      ax.set_title('Chronogram')
      if shade_dark:
          new_index = list(range(lights_on, 24)) + list(range(0,lights_on))
          off = new_index.index(lights_off)
          ax.axvspan(off,24,color='gray',alpha=.2,zorder=0,label='lights off')
      ax.legend()
      plt.tight_layout()
  
      return fig if 'ax' not in kwargs else None
  
  def drinkduration_chronogram(sipper, circ_left=True, circ_right=True,
                               circ_content=None, lights_on=7,
                               lights_off=19, shade_dark=True, **kwargs):
      """
      Plot the drink duration for a Sipper, taking an average for each hour
      of the day
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
      circ_left : bool, optional
          Show drinks for the left side. The default is True.
      circ_right : bool, optional
          Show drinks for the right side. The default is True.
      circ_content : collection, optional
          Show drinks for each content name in the collection. The default is None.
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      df = sipper.data
      if 'date_filter' in kwargs:
          s, e = kwargs['date_filter']
          df = df[(df.index >= s) &
                  (df.index <= e)].copy()
      to_plot = []
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      labels = []
      if circ_left:
          to_plot.append(df['LeftDuration'].diff())
          colors.insert(0, 'red')
          labels.append('Left')
      if circ_right:
          to_plot.append(df['RightDuration'].diff())
          colors.insert(0, 'blue')
          labels.append('Right')
      if circ_content:
          for c in circ_content:
              vals = sipper.get_content_values(c, 'Duration', df=df).diff()
              if not vals.empty:
                  to_plot.append(vals)
                  labels.append(c)
      for i, series in enumerate(to_plot):
          reindexed = get_chronogram_vals(series, lights_on, lights_off)
          label = labels[i]
          ax.plot(range(0,24), reindexed, color=colors[i], label=label)
      ax.set_xlabel('Hours Into Light Cycle')
      ax.set_xticks([0,6,12,18,24])
      ax.set_ylabel('Drink Duration (s)')
      ax.set_title('Chronogram')
      if shade_dark:
          new_index = list(range(lights_on, 24)) + list(range(0,lights_on))
          off = new_index.index(lights_off)
          ax.axvspan(off,24,color='gray',alpha=.2,zorder=0,label='lights off')
      ax.legend()
      plt.tight_layout()
  
      return fig if 'ax' not in kwargs else None
  
  def drinkduration_chronogram_grouped(sippers, groups, circ_left=True, circ_right=True,
                                       circ_content=None, circ_var='SEM',
                                       circ_show_indvl=False, lights_on=7,
                                       lights_off=19, shade_dark=True, **kwargs):
      '''
      Plot the drink duration for multiple Sippers, taking an average for each hour
      of the day, and averaging those values for Groups.
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      groups : collection
          Array of group names, to aggregate Sipper objects based on their
          "groups" attribute.
      circ_left : bool, optional
          Show drinks for the left side. The default is True.
      circ_right : bool, optional
          Show drinks for the right side. The default is True.
      circ_content : collection, optional
          Show drinks for each content name in the collection. The default is None.
      circ_var : str, optional
          How to shade error for each group. The default is 'SEM', can also be
          'STD' or ther will be no effect.
      circ_show_indvl : bool, optional
          Include curve for each individual device. The default is False.
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      '''
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      to_plot = defaultdict(list)
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      for group in groups:
          for sipper in sippers:
              if group in sipper.groups:
                  df = sipper.data
                  if 'date_filter' in kwargs:
                      s, e = kwargs['date_filter']
                      df = df[(df.index >= s) &
                              (df.index <= e)].copy()
                  if circ_left:
                      key = group + ' - Left'
                      vals = get_chronogram_vals(df['LeftDuration'].diff(),
                                                 lights_on,
                                                 lights_off)
                      to_plot[key].append(vals)
                  if circ_right:
                      key = group + ' - Right'
                      vals = get_chronogram_vals(df['RightDuration'].diff(),
                                                 lights_on,
                                                 lights_off)
                      to_plot[key].append(vals)
                  if circ_content:
                      for c in circ_content:
                          key = group + ' - ' + c
                          content_vals = sipper.get_content_values(c, 'Duration', df)
                          if not content_vals.empty:
                              vals = get_chronogram_vals(content_vals,
                                                         lights_on,
                                                         lights_off).diff()
                              to_plot[key].append(vals)
      for i, (label, data) in enumerate(to_plot.items()):
          x = range(0,24)
          y = np.nanmean(data, axis=0)
          error_shade = np.nan
          if circ_show_indvl:
              for d in data:
                  ax.plot(x, d, color=colors[i], alpha=.5, linewidth=.8)
          if circ_var == 'SEM':
              error_shade = stats.sem(data, axis=0, nan_policy='omit')
          elif circ_var == 'STD':
              error_shade = np.nanstd(data, axis=0)
          ax.plot(x, y, color=colors[i], label=label)
          ax.fill_between(x, y-error_shade, y+error_shade, color=colors[i],
                          alpha=.3)
      ax.set_xlabel('Hours Into Light Cycle')
      ax.set_xticks([0,6,12,18,24])
      ax.set_ylabel('Drink Duration (s)')
      ax.set_title('Chronogram')
      if shade_dark:
          new_index = list(range(lights_on, 24)) + list(range(0,lights_on))
          off = new_index.index(lights_off)
          ax.axvspan(off,24,color='gray',alpha=.2,zorder=0,label='lights off')
      ax.legend()
      plt.tight_layout()
  
      return fig if 'ax' not in kwargs else None
  
  #---preference
  
  def side_preference(sipper, pref_side='Left', pref_metric='Count', pref_bins='1H',
                      lights_on=7, lights_off=19, shade_dark=True, **kwargs):
      '''
      Plot the preference for one bottle over the other
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
      pref_side : str ('Left' or 'Right'), optional
          Side to calculate preference for. The default is 'Left'.
      pref_metric : str ('Count' or 'Duration'), optional
          Drink variable to use for calculating prefernce. The default is 'Count'.
      pref_bins : str, optional
          pandas time offset str to resample data. The default is '1H'.
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      '''
      if 'ax' in kwargs:
              ax = kwargs['ax']
      else:
          fig, ax = plt.subplots()
      df = sipper.data
      if 'date_filter' in kwargs:
          s, e = kwargs['date_filter']
          df = df[(df.index >= s) &
                  (df.index <= e)].copy()
      base = df.index[0].hour
      lcol = 'Left' + pref_metric
      rcol = 'Right' + pref_metric
      l_data = df[lcol].diff().resample(pref_bins, base=base).sum()
      r_data = df[rcol].diff().resample(pref_bins, base=base).sum()
      total = l_data + r_data
      if pref_side == 'Left':
          preference = l_data/total
      else:
          preference = r_data/total
      preference *= 100
      color = 'red' if pref_side == "Left" else 'blue'
      ax.plot(preference.index, preference, color=color,
              label=pref_side)
      ax.scatter(preference.index, preference, color=color)
      date_format_x(ax, df.index[0], df.index[-1])
      ax.set_title('Side Preference for ' + sipper.filename)
      label = pref_side + ' Preference (% Drink {})'.format(pref_metric)
      ax.set_ylabel(label)
      ax.set_xlabel('Date')
      if shade_dark:
          shade_darkness(ax, df.index[0], df.index[-1], lights_on, lights_off)
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def content_preference(sipper, pref_content, pref_metric='Count', pref_bins='1H',
                         lights_on=7, lights_off=19, shade_dark=True, **kwargs):
      '''
      Plot the preference for one type of content (over anything else in
      opposite bottle)
  
      Parameters
      ----------
      sipper : Sipper
          sipper data loaded into the Sipper class
      pref_content : collection
          Array of contents to calculate preference for.
      pref_metric : str ('Count' or 'Duration'), optional
          Drink variable to use for calculating prefernce. The default is 'Count'.
      pref_bins : str, optional
          pandas time offset str to resample data. The default is '1H'.
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      '''
      if 'ax' in kwargs:
              ax = kwargs['ax']
      else:
          fig, ax = plt.subplots()
      df = sipper.data
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      if 'date_filter' in kwargs:
          s, e = kwargs['date_filter']
          df = df[(df.index >= s) &
                  (df.index <= e)].copy()
      base = df.index[0].hour
      content_max = df.index.min()
      content_min = df.index.max()
      for i, c in enumerate(pref_content):
          target = sipper.get_content_values(c, out=pref_metric, df=df)
          if target.empty:
              continue
          target = target.diff().resample(pref_bins, base=base).sum()
          other  = sipper.get_content_values(c, out=pref_metric, df=df,
                                             opposite=True)
          other = other.diff().resample(pref_bins, base=base).sum()
          if not target.empty and not other.empty:
              preference = target / (target + other) * 100
              if preference.dropna().index.max() > content_max:
                      content_max = preference.dropna().index.max()
              if preference.dropna().index.min() < content_min:
                  content_min = preference.dropna().index.min()
              ax.plot(preference.index, preference, color=colors[i],
                      label=c)
              ax.scatter(preference.index, preference, color=colors[i])
      if content_max > content_min:
          dformat_min = content_min
          dformat_max = content_max
      else:
          dformat_min = df.index[0]
          dformat_max = df.index[-1]
      date_format_x(ax, dformat_min, dformat_max)
      ax.set_title('Content Prefernce for ' + sipper.filename)
      label = 'Content Preference (% Drink {})'.format(pref_metric)
      ax.set_ylabel(label)
      ax.set_xlabel('Date')
      if shade_dark:
          shade_darkness(ax, dformat_min, dformat_max, lights_on, lights_off)
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  #---averaging
  def averaged_drinkcount(sippers, groups, averaging='datetime', avg_bins='1H',
                          avg_var='SEM', show_left=True, show_right=True,
                          show_content=[], shade_dark=True, lights_on=7,
                          lights_off=19, **kwargs):
      """
      Plot the binned drink count for multiple Sippers, averaged by Group
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      groups : collection
          Array of group names, to aggregate Sipper objects based on their
          "groups" attribute.
      averaging : str, optional
          Style of averaging. The default is 'datetime'.
          - 'datetime' = average in absolute time (no alignment, fails for
            time series which did not cooccur)
          - 'time' = align by time of day and then average
          - 'elapsed' = align by start of recording and then average
      avg_bins : str, optional
          Bin size to use for downsampling. The default is '1H'.
      avg_var : str ('SEM','STD', or 'Individual Data'), optional
          How to show variance around the average. The default is 'SEM'.
      show_left : bool, optional
          Show cumulative drinks for the left bottle
      show_right : bool, optional
          Show cumulative drinks for the right bottle
      show_content : collection, optional
          Array of contents to show drinks for. The default is [].
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      to_plot = defaultdict(list)
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      for group in groups:
          for sipper in sippers:
              if group in sipper.groups:
                  df = sipper.data
                  if 'date_filter' in kwargs:
                      s, e = kwargs['date_filter']
                      df = df[(df.index >= s) &
                              (df.index <= e)].copy()
                  if show_left:
                      key = '{} - Left'.format(group)
                      to_plot[key].append(df['LeftCount'].diff())
                  if show_right:
                      key = '{} - Right'.format(group)
                      to_plot[key].append(df['RightCount'].diff())
                  if show_content:
                      for c in show_content:
                          key = '{} - {}'.format(group, c)
                          vals = sipper.get_content_values(c, out='Count',
                                                           df=df).diff()
                          if not vals.empty:
                              to_plot[key].append(vals)
      xdata = []
      for i, (label, data) in enumerate(to_plot.items()):
          error_shade = np.nan
          processed = preproc_averaging(data, averaging=averaging,
                                        avg_bins=avg_bins, agg='sum')
          x = processed['x']
          xdata.append(x)
          ys = processed['ys']
          mean = np.nanmean(ys, axis=0)
          if avg_var == 'Individual Data':
              for y in ys:
                  ax.plot(x, y, color=colors[i], alpha=.5, linewidth=.8)
          if avg_var == 'SEM':
              error_shade = stats.sem(ys, axis=0, nan_policy='omit')
          elif avg_var == 'STD':
              error_shade = np.nanstd(ys, axis=0)
          ax.plot(x, mean, label=label, color=colors[i])
          ax.fill_between(x, mean-error_shade, mean+error_shade, color=colors[i],
                          alpha=.3)
      format_averaging_axes(ax, averaging, xdata)
      ax.set_title('Average Drink Count')
      ax.set_ylabel('Drinks')
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def cumulative_averaged_drinkcount(sippers, groups, avg_bins='1H',
                                     avg_var='SEM', show_left=True, show_right=True,
                                     show_content=[], shade_dark=True, lights_on=7,
                                     lights_off=19, **kwargs):
      """
      Plot the cumulative drink count for multiple Sippers, averaged by Group
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      groups : collection
          Array of group names, to aggregate Sipper objects based on their
          "groups" attribute.
      avg_bins : str, optional
          Bin size to use for downsampling. The default is '1H'.
      avg_var : str ('SEM','STD', or 'Individual Data'), optional
          How to show variance around the average. The default is 'SEM'.
      show_left : bool, optional
          Show cumulative drinks for the left bottle
      show_right : bool, optional
          Show cumulative drinks for the right bottle
      show_content : collection, optional
          Array of contents to show drinks for. The default is [].
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      averaging = 'elapsed'
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      to_plot = defaultdict(list)
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      for group in groups:
          for sipper in sippers:
              if group in sipper.groups:
                  df = sipper.data
                  if 'date_filter' in kwargs:
                      s, e = kwargs['date_filter']
                      df = df[(df.index >= s) &
                              (df.index <= e)].copy()
                  if show_left:
                      key = '{} - Left'.format(group)
                      to_plot[key].append(df['LeftCount'])
                  if show_right:
                      key = '{} - Right'.format(group)
                      to_plot[key].append(df['RightCount'])
                  if show_content:
                      for c in show_content:
                          key = '{} - {}'.format(group, c)
                          vals = sipper.get_content_values(c, out='Count',
                                                           df=df)
                          if not vals.empty:
                              to_plot[key].append(vals)
      xdata = []
      for i, (label, data) in enumerate(to_plot.items()):
          error_shade = np.nan
          processed = preproc_averaging(data, averaging=averaging,
                                        avg_bins=avg_bins, agg='max')
          x = processed['x']
          xdata.append(x)
          ys = processed['ys']
          mean = np.nanmean(ys, axis=0)
          if avg_var == 'Individual Data':
              for y in ys:
                  ax.plot(x, y, color=colors[i], alpha=.5, linewidth=.8)
          if avg_var == 'SEM':
              error_shade = stats.sem(ys, axis=0, nan_policy='omit')
          elif avg_var == 'STD':
              error_shade = np.nanstd(ys, axis=0)
          ax.plot(x, mean, label=label, color=colors[i])
          ax.fill_between(x, mean-error_shade, mean+error_shade, color=colors[i],
                          alpha=.3)
      format_averaging_axes(ax, averaging, xdata)
      ax.set_title('Cumulative Average Drink Count')
      ax.set_ylabel('Total Drinks')
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def averaged_drinkduration(sippers, groups, averaging='datetime', avg_bins='1H',
                             avg_var='SEM', show_left=True, show_right=True,
                             show_content=[], shade_dark=True, lights_on=7,
                             lights_off=19, **kwargs):
      """
      Plot the binned drink duration for multiple Sippers, averaged by Group
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      groups : collection
          Array of group names, to aggregate Sipper objects based on their
          "groups" attribute.
      averaging : str, optional
          Style of averaging. The default is 'datetime'.
          - 'datetime' = average in absolute time (no alignment, fails for
            time series which did not cooccur)
          - 'time' = align by time of day and then average
          - 'elapsed' = align by start of recording and then average
      avg_bins : str, optional
          Bin size to use for downsampling. The default is '1H'.
      avg_var : str ('SEM','STD', or 'Individual Data'), optional
          How to show variance around the average. The default is 'SEM'.
      show_left : bool, optional
          Show cumulative drinks for the left bottle
      show_right : bool, optional
          Show cumulative drinks for the right bottle
      show_content : collection, optional
          Array of contents to show drinks for. The default is [].
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      to_plot = defaultdict(list)
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      for group in groups:
          for sipper in sippers:
              if group in sipper.groups:
                  df = sipper.data
                  if 'date_filter' in kwargs:
                      s, e = kwargs['date_filter']
                      df = df[(df.index >= s) &
                              (df.index <= e)].copy()
                  if show_left:
                      key = '{} - Left'.format(group)
                      to_plot[key].append(df['LeftDuration'].diff())
                  if show_right:
                      key = '{} - Right'.format(group)
                      to_plot[key].append(df['RightDuration'].diff())
                  if show_content:
                      for c in show_content:
                          key = '{} - {}'.format(group, c)
                          vals = sipper.get_content_values(c, out='Duration',
                                                           df=df).diff()
                          if not vals.empty:
                              to_plot[key].append(vals)
      xdata = []
      for i, (label, data) in enumerate(to_plot.items()):
          error_shade = np.nan
          processed = preproc_averaging(data, averaging=averaging,
                                        avg_bins=avg_bins, agg='sum')
          x = processed['x']
          xdata.append(x)
          ys = processed['ys']
          mean = np.nanmean(ys, axis=0)
          if avg_var == 'Individual Data':
              for y in ys:
                  ax.plot(x, y, color=colors[i], alpha=.5, linewidth=.8)
          if avg_var == 'SEM':
              error_shade = stats.sem(ys, axis=0, nan_policy='omit')
          elif avg_var == 'STD':
              error_shade = np.nanstd(ys, axis=0)
          ax.plot(x, mean, label=label, color=colors[i])
          ax.fill_between(x, mean-error_shade, mean+error_shade, color=colors[i],
                          alpha=.3)
      format_averaging_axes(ax, averaging, xdata)
      ax.set_title('Average Drink Duration')
      ax.set_ylabel('Drink Duration (s)')
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def cumulative_averaged_drinkduration(sippers, groups, avg_bins='1H',
                                        avg_var='SEM', show_left=True, show_right=True,
                                        show_content=[], shade_dark=True, lights_on=7,
                                        lights_off=19, **kwargs):
      """
      Plot the cumulative drink duration for multiple Sippers, averaged by Group
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      groups : collection
          Array of group names, to aggregate Sipper objects based on their
          "groups" attribute.
      avg_bins : str, optional
          Bin size to use for downsampling. The default is '1H'.
      avg_var : str ('SEM','STD', or 'Individual Data'), optional
          How to show variance around the average. The default is 'SEM'.
      show_left : bool, optional
          Show cumulative drinks for the left bottle
      show_right : bool, optional
          Show cumulative drinks for the right bottle
      show_content : collection, optional
          Array of contents to show drinks for. The default is [].
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      averaging = 'elapsed'
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      to_plot = defaultdict(list)
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      for group in groups:
          for sipper in sippers:
              if group in sipper.groups:
                  df = sipper.data
                  if 'date_filter' in kwargs:
                      s, e = kwargs['date_filter']
                      df = df[(df.index >= s) &
                              (df.index <= e)].copy()
                  if show_left:
                      key = '{} - Left'.format(group)
                      to_plot[key].append(df['LeftDuration'])
                  if show_right:
                      key = '{} - Right'.format(group)
                      to_plot[key].append(df['RightDuration'])
                  if show_content:
                      for c in show_content:
                          key = '{} - {}'.format(group, c)
                          vals = sipper.get_content_values(c, out='Duration',
                                                           df=df)
                          if not vals.empty:
                              to_plot[key].append(vals)
      xdata = []
      for i, (label, data) in enumerate(to_plot.items()):
          error_shade = np.nan
          processed = preproc_averaging(data, averaging=averaging,
                                        avg_bins=avg_bins, agg='max')
          x = processed['x']
          xdata.append(x)
          ys = processed['ys']
          mean = np.nanmean(ys, axis=0)
          if avg_var == 'Individual Data':
              for y in ys:
                  ax.plot(x, y, color=colors[i], alpha=.5, linewidth=.8)
          if avg_var == 'SEM':
              error_shade = stats.sem(ys, axis=0, nan_policy='omit')
          elif avg_var == 'STD':
              error_shade = np.nanstd(ys, axis=0)
          ax.plot(x, mean, label=label, color=colors[i])
          ax.fill_between(x, mean-error_shade, mean+error_shade, color=colors[i],
                          alpha=.3)
      format_averaging_axes(ax, averaging, xdata)
      ax.set_title('Cumulative Average Drink Count')
      ax.set_ylabel('Total Drink Duration (s)')
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def averaged_side_preference(sippers, groups, averaging='datetime', avg_bins='1H',
                               avg_var='SEM', pref_side='Left', pref_metric='Count',
                               shade_dark=True, lights_on=7, lights_off=19, **kwargs):
      """
      Plot the binned bottle side preference for multiple Sippers, averaged by Group
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      groups : collection
          Array of group names, to aggregate Sipper objects based on their
          "groups" attribute.
      averaging : str, optional
          Style of averaging. The default is 'datetime'.
          - 'datetime' = average in absolute time (no alignment, fails for
            time series which did not cooccur)
          - 'time' = align by time of day and then average
          - 'elapsed' = align by start of recording and then average
      avg_bins : str, optional
          Bin size to use for downsampling. The default is '1H'.
      avg_var : str ('SEM','STD', or 'Individual Data'), optional
          How to show variance around the average. The default is 'SEM'.
      pref_side : str ('Left' or 'Right'), optional
          Side to calculate preference for. The default is 'Left'.
      pref_metric : str ('Count' or 'Duration'), optional
          Drink variable to use for calculating prefernce. The default is 'Count'.
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      to_plot = defaultdict(lambda: defaultdict((list)))
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      for group in groups:
          for sipper in sippers:
              if group in sipper.groups:
                  df = sipper.data
                  if 'date_filter' in kwargs:
                      s, e = kwargs['date_filter']
                      df = df[(df.index >= s) &
                              (df.index <= e)].copy()
                  to_plot[group]['Left'].append(df['Left' + pref_metric].diff())
                  to_plot[group]['Right'].append(df['Right' + pref_metric].diff())
      xdata = []
      for i, (label, dic) in enumerate(to_plot.items()):
          l = dic['Left']
          r = dic['Right']
          error_shade = np.nan
          l_processed = preproc_averaging(l, averaging=averaging,
                                          avg_bins=avg_bins, agg='sum')
          r_processed = preproc_averaging(r, averaging=averaging,
                                          avg_bins=avg_bins, agg='sum')
          l_x = l_processed['x']
          xdata.append(l_x)
          l_ys = l_processed['ys']
          r_ys = r_processed['ys']
          preferences = []
          for lside, rside in zip(l_ys, r_ys):
              total = lside + rside
              if pref_side == "Left":
                  indvl_pref = lside / total * 100
              else:
                  indvl_pref = rside / total * 100
              preferences.append(indvl_pref)
              if avg_var == 'Individual Data':
                  ax.plot(indvl_pref.index, indvl_pref,
                          color=colors[i], alpha=.5, linewidth=.8)
          pref_mean = np.nanmean(preferences, axis=0)
          if avg_var == 'SEM':
              error_shade = stats.sem(preferences, axis=0, nan_policy='omit')
          elif avg_var == 'STD':
              error_shade = np.nanstd(preferences, axis=0)
          ax.plot(l_x, pref_mean, label=label, color=colors[i])
          ax.fill_between(l_x, pref_mean-error_shade, pref_mean+error_shade,
                          color=colors[i], alpha=.3)
      format_averaging_axes(ax, averaging, xdata)
      ax.set_ylim(-5, 105)
      ax.set_title('Averaged Side Preference')
      ax.set_ylabel('{} Preference (% Drink {})'.format(pref_side, pref_metric))
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None
  
  def averaged_content_preference(sippers, groups, pref_content=[],
                                  pref_metric='Count', averaging='datetime',
                                  avg_bins='1H', avg_var='SEM', shade_dark=True,
                                  lights_on=7, lights_off=19, **kwargs):
      """
      Plot the binned content preference for multiple Sippers, averaged by Group
  
      Parameters
      ----------
      sippers : collection
          Array of Sipper objects
      groups : collection
          Array of group names, to aggregate Sipper objects based on their
          "groups" attribute.
      pref_content : collection
          Array of contents to calculate preference for.
      pref_metric : str ('Count' or 'Duration'), optional
          Drink variable to use for calculating prefernce. The default is 'Count'.
      averaging : str, optional
          Style of averaging. The default is 'datetime'.
          - 'datetime' = average in absolute time (no alignment, fails for
            time series which did not cooccur)
          - 'time' = align by time of day and then average
          - 'elapsed' = align by start of recording and then average
      avg_bins : str, optional
          Bin size to use for downsampling. The default is '1H'.
      avg_var : str ('SEM','STD', or 'Individual Data'), optional
          How to show variance around the average. The default is 'SEM'.
      shade_dark : bool, optional
          Whether or not to shade nighttime periods of the ax.
      lights_on : int
          Integer from 0-23 denoting start of light cycle. The default is 7.
      lights_off : int
          Integer from 0-23 denoting end of light cycle. The default is 19.
      **kwargs :
          date_filter : two-tuple of start and end date to filter data
          ax : matplotlib axes to plot on
          **kwargs also allow SipperViz to by lazy about passing settings
          to functions.
  
      Returns
      -------
      matplotlib.figure.Figure (unless ax is passed, in which case none)
  
      """
      if 'ax' not in kwargs:
          fig, ax = plt.subplots()
      else:
          ax = kwargs['ax']
      to_plot = defaultdict(lambda: defaultdict((list)))
      colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
      for group in groups:
          for sipper in sippers:
              if group in sipper.groups:
                  df = sipper.data
                  if 'date_filter' in kwargs:
                      s, e = kwargs['date_filter']
                      df = df[(df.index >= s) &
                              (df.index <= e)].copy()
                  for i, c in enumerate(pref_content):
                      target = sipper.get_content_values(c, out=pref_metric, df=df)
                      other  = sipper.get_content_values(c, out=pref_metric, df=df,
                                                         opposite=True)
                      if not target.empty and not other.empty:
                          key = group + ' - ' + c
                          to_plot[key]['target'].append(target.diff())
                          to_plot[key]['other'].append(other.diff())
      xdata = []
      for i, (label, dic) in enumerate(to_plot.items()):
          target = dic['target']
          other = dic['other']
          t_processed = preproc_averaging(target, averaging=averaging,
                                          avg_bins=avg_bins, agg='sum')
          o_processed = preproc_averaging(other, averaging=averaging,
                                          avg_bins=avg_bins, agg='sum')
          t_x = t_processed['x']
          xdata.append(t_x)
          t_ys = t_processed['ys']
          o_ys = o_processed['ys']
          preferences = []
          for tside, oside in zip(t_ys, o_ys):
              total = tside + oside
              indvl_pref = tside / total * 100
              preferences.append(indvl_pref)
              if avg_var == 'Individual Data':
                  ax.plot(indvl_pref.index, indvl_pref,
                          color=colors[i], alpha=.5, linewidth=.8)
          pref_mean = np.nanmean(preferences, axis=0)
          if avg_var == 'SEM':
              error_shade = stats.sem(preferences, axis=0, nan_policy='omit')
          elif avg_var == 'STD':
              error_shade = np.nanstd(preferences, axis=0)
          ax.plot(t_x, pref_mean, label=label, color=colors[i])
          ax.fill_between(t_x, pref_mean-error_shade, pref_mean+error_shade,
                          color=colors[i], alpha=.3)
      format_averaging_axes(ax, averaging, xdata)
      ax.set_ylim(-5, 105)
      ax.set_title('Averaged Content Preference')
      ax.set_ylabel('Content Preference (% Drink {})'.format(pref_metric))
      ax.legend()
      plt.tight_layout()
      return fig if 'ax' not in kwargs else None