docs: def mi(x, y, bins_x=None, bins_y=None, bins_xy=None, method='nearest-neighbors', units='bits'):
      '''
      compute and return the mutual information between x and y
  
      inputs:
      -------
          x, y:       numpy arrays of shape samples x dimension
          method:     'nearest-neighbors', 'gaussian', or 'bin'
          units:      'bits' or 'nats'
  
      output:
      -------
          mi:     float
  
      Notes:
      ------
          if you are trying to mix several symbols together as in mi(x, (y0,y1,...)), try
  
          info[p] = _info.mi(x, info.combine_symbols(y0, y1, ...) )
      '''
      # dict.values() returns a view object that has to be converted to a list before being
      # converted to an array
      # the following lines will execute properly in python3, but not python2 because there
      # is no zip object
      try:
          if isinstance(x, zip):
              x = list(x)
          if isinstance(y, zip):
              y = list(y)
      except:
          pass
  
      # wrapped in try bracket because x, y might have no .shape attribute
      try:
          # handling for 1d np arrays
          if len(x.shape) == 1:
              x = np.expand_dims(x, 1)
          if len(y.shape) == 1:
              y = np.expand_dims(y, 1)
      except:
          pass
  
      HX  = entropy(data=x, bins=bins_x, method=method, units=units)
      HY  = entropy(data=y, bins=bins_y, method=method, units=units)
      HXY = entropy(data=np.concatenate([x, y], axis=1), bins=bins_xy, method=method, units=units)
  
      return HX + HY - HXY