text: 
  
  '''
  .. module:: skrf.networkSet
  ========================================
  networkSet (:mod:`skrf.networkSet`)
  ========================================
  
  
  Provides a class representing an un-ordered set of n-port
  microwave networks.
  
  
  Frequently one needs to make calculations, such as mean or standard
  deviation, on an entire set of n-port networks. To facilitate these
  calculations the :class:`NetworkSet` class provides convenient
  ways to make such calculations.
  
  The results are returned in :class:`~skrf.network.Network` objects, so they can be plotted and saved in the same way one would do with a
  :class:`~skrf.network.Network`.
  
  The functionality in this module is provided as methods and
  properties of the :class:`NetworkSet` Class.
  
  
  NetworkSet Class
  ================
  
  .. autosummary::
     :toctree: generated/
  
     NetworkSet
  
  
  
  '''
  
  import os
  from . network import average as network_average
  from . network import Network, PRIMARY_PROPERTIES, COMPONENT_FUNC_DICT, Y_LABEL_DICT
  
  from . import mathFunctions as mf
  import zipfile
  from copy import deepcopy
  import warnings
  import numpy as npy
  import pylab as plb
  # delayed imports due to circular dependencies
  # NetworkSet.from_dir : from io.general import read_all_networks
  
  class NetworkSet(object):
      '''
      A set of Networks.
  
      This class allows functions on sets of Networks, such as mean or
      standard deviation, to be calculated conveniently. The results are
      returned in :class:`~skrf.network.Network` objects, so that they may be
      plotted and saved in like :class:`~skrf.network.Network` objects.
  
      This class also provides methods which can be used to plot uncertainty
      bounds for a set of :class:`~skrf.network.Network`.
  
      The names of the :class:`NetworkSet` properties are generated
      dynamically upon initialization, and thus documentation for
      individual properties and methods is not available. However, the
      properties do follow the convention::
  
              >>> my_network_set.function_name_network_property_name
  
      For example, the complex average (mean)
      :class:`~skrf.network.Network` for a
      :class:`NetworkSet` is::
  
              >>> my_network_set.mean_s
  
      This accesses the property 's', for each element in the
      set, and **then** calculates the 'mean' of the resultant set. The
      order of operations is important.
  
      Results are returned as :class:`~skrf.network.Network` objects,
      so they may be plotted or saved in the same way as for
      :class:`~skrf.network.Network` objects::
  
              >>> my_network_set.mean_s.plot_s_mag()
              >>> my_network_set.mean_s.write_touchstone('mean_response')
  
      If you are calculating functions that return scalar variables, then
      the result is accessible through the Network property .s_re. For
      example::
  
              >>> std_s_deg = my_network_set.std_s_deg
  
      This result would be plotted by::
  
              >>> std_s_deg.plot_s_re()
  
  
      The operators, properties, and methods of NetworkSet object are
      dynamically generated by private methods
  
       * :func:`~NetworkSet.__add_a_operator`
       * :func:`~NetworkSet.__add_a_func_on_property`
       * :func:`~NetworkSet.__add_a_element_wise_method`
       * :func:`~NetworkSet.__add_a_plot_uncertainty`
  
      thus, documentation on the individual methods and properties are
      not available.
  
  
      '''
  
      def __init__(self, ntwk_set, name = None):
          '''
          Initializer for NetworkSet
  
          Parameters
          -----------
          ntwk_set : list of :class:`~skrf.network.Network` objects
                  the set of :class:`~skrf.network.Network` objects
          name : string
                  the name of the NetworkSet, given to the Networks returned
                  from properties of this class.
          '''
          ## type checking
          if hasattr(ntwk_set, 'values'):
              ntwk_set = list(ntwk_set.values())
  
          # did they pass a list of Networks?
          if not isinstance(ntwk_set[0], Network):
              raise(TypeError('input must be list of Network types'))
  
          # do all Networks have the same # ports?
          if len (set([ntwk.number_of_ports for ntwk in ntwk_set])) >1:
              raise(ValueError('All elements in list of Networks must have same number of ports'))
  
          # is all frequency information the same?
          if npy.all([(ntwk_set[0].frequency == ntwk.frequency) \
                  for ntwk in ntwk_set]) == False:
              raise(ValueError('All elements in list of Networks must have same frequency information'))
  
          ## initialization
          # we are good to go
          self.ntwk_set = ntwk_set
          self.name = name
  
          # create list of network properties, which we use to dynamically
          # create a statistical properties of this set
          network_property_list = [k+'_'+l \
              for k in PRIMARY_PROPERTIES \
              for l in COMPONENT_FUNC_DICT.keys()] + \
              ['passivity','s']
  
          # dynamically generate properties. this is slick.
          max, min = npy.max, npy.min
          max.__name__ = 'max'
          min.__name__ = 'min'
          for network_property_name in network_property_list:
              for func in [npy.mean, npy.std, max, min]:
                  self.__add_a_func_on_property(func, network_property_name)
  
              if 'db' not in network_property_name:# != 's_db' and network_property_name != 's':
                  # db uncertainty requires a special function call see
                  # plot_uncertainty_bounds_s_db
                  self.__add_a_plot_uncertainty(network_property_name)
                  self.__add_a_plot_minmax(network_property_name)
  
              self.__add_a_element_wise_method('plot_'+network_property_name)
              self.__add_a_element_wise_method('plot_s_db')
              self.__add_a_element_wise_method('plot_s_db_time')
          for network_method_name in \
                  ['write_touchstone','interpolate','plot_s_smith']:
              self.__add_a_element_wise_method(network_method_name)
  
          for operator_name in \
                  ['__pow__','__floordiv__','__mul__','__div__','__add__','__sub__']:
              self.__add_a_operator(operator_name)
  
      @classmethod
      def from_zip(cls, zip_file_name, sort_filenames=True, *args, **kwargs):
          '''
          creates a NetworkSet from a zipfile of touchstones.
  
          Parameters
          -----------
          zip_file_name : string
              name of zipfile
          sort_filenames: Boolean
              sort the filenames in the zip file before constructing the
              NetworkSet
          \\*args,\\*\\*kwargs : arguments
              passed to NetworkSet constructor
  
          Examples
          ----------
  
          >>> import skrf as rf
          >>> my_set = rf.NetworkSet.from_zip('myzip.zip')
  
          '''
          z = zipfile.ZipFile(zip_file_name)
          filename_list = z.namelist()
  
          ntwk_list = []
  
          if sort_filenames:
              filename_list.sort()
  
  
          for filename in filename_list:
              # try/except block in case not all files are touchstones
              n= Network()
              try:
                  n.read_touchstone(z.open(filename))
                  ntwk_list.append(n)
                  continue
              except:
                  pass
              try:
                  n.read(z.open(filename))
                  ntwk_list.append(n)
                  continue
              except:
                  pass
  
          return cls(ntwk_list)
  
      @classmethod
      def from_dir(cls, dir='.',*args, **kwargs):
          '''
          Create a NetworkSet from a directory containing Networks
  
          This just calls ::
  
              rf.NetworkSet(rf.read_all_networks(dir), *args, **kwargs)
  
          Parameters
          ---------------
          dir : str
              directory containing Network files.
          
          \*args, \*\*kwargs :
              passed to NetworkSet constructor
  
          Examples
          ----------
  
          >>> my_set = rf.NetworkSet.from_dir('./data/')
          '''
          from io.general import read_all_networks
          return cls(read_all_networks(dir), *args, **kwargs)
          
          
  
      @classmethod
      def from_s_dict(cls,d, frequency, *args, **kwargs):
          '''
          Create a NetworkSet from a dictionary of s-parameters
  
          The resultant elements of the NetworkSet are named by the keys of
          the dictionary.
  
          Parameters
          -------------
          d : dict
              dictionary of s-parameters data. values of this should be
              :class:`numpy.ndarray` assignable to :attr:`skrf.network.Network.s`
          frequency: :class:`~skrf.frequency.Frequency` object
              frequency assigned to each network
  
          \*args, \*\*kwargs :
              passed to Network.__init__ for each key/value pair of d
  
          Returns
          ----------
          ns : NetworkSet
  
          See Also
          ----------
          NetworkSet.to_s_dict
          '''
          return cls([Network(s=d[k], frequency=frequency, name=k,
                              *args, **kwargs)  for k in d])
  
      
          
  
      def __add_a_operator(self,operator_name):
          '''
          adds a operator method to the NetworkSet.
  
          this is made to
          take either a Network or a NetworkSet. if a Network is passed
          to the operator, each element of the set will operate on the
          Network. If a NetworkSet is passed to the operator, and is the
          same length as self. then it will operate element-to-element
          like a dot-product.
          '''
          def operator_func(self, other):
              if isinstance(other, NetworkSet):
                  if len(other) != len(self):
                      raise(ValueError('Network sets must be of same length to be cascaded'))
                  return NetworkSet([self.ntwk_set[k].__getattribute__(operator_name)(other.ntwk_set[k]) for k in range(len(self))])
  
              elif isinstance(other, Network):
                  return NetworkSet([ntwk.__getattribute__(operator_name)(other) for ntwk in self.ntwk_set])
  
              else:
                  raise(TypeError('NetworkSet operators operate on either Network, or NetworkSet types'))
          setattr(self.__class__,operator_name,operator_func)
  
  
      def __str__(self):
          '''
          '''
          output =  \
                  'A NetworkSet of length %i'%len(self.ntwk_set)
  
          return output
  
      def __repr__(self):
          return self.__str__()
  
      def __getitem__(self,key):
          '''
          returns an element of the network set
          '''
          if isinstance(key, str):
              # if they pass a string then slice each network in this set
              return NetworkSet([k[key] for k in self.ntwk_set],
                                name = self.name)
          else:
              return self.ntwk_set[key]
  
      def __len__(self):
          '''
          returns an element of the network set
          '''
          return len(self.ntwk_set)
  
  
      def __add_a_element_wise_method(self,network_method_name):
          def func(self,  *args, **kwargs):
              return self.element_wise_method(network_method_name, *args, **kwargs)
          setattr(self.__class__,network_method_name,func)
  
  
      def __add_a_func_on_property(self,func,network_property_name):
          '''
          dynamically adds a property to this class (NetworkSet).
          this is mostly used internally to genrate all of the classes
          properties.
  
          takes:
                  network_property_name: a property of the Network class,
                          a string. this must have a matrix output of shape fxnxn
                  func: a function to be applied to the network_property
                          accross the first axis of the property's output
  
  
  
          example:
                  my_ntwk_set.add_a_func_on_property('s',mean)
  
  
          '''
          fget = lambda self: fon(self.ntwk_set,func,network_property_name,\
                  name = self.name)
          setattr(self.__class__,func.__name__+'_'+network_property_name,\
                  property(fget))
  
      def __add_a_plot_uncertainty(self,network_property_name):
          '''
  
          takes:
                  network_property_name: a property of the Network class,
                          a string. this must have a matrix output of shape fxnxn
  
  
  
          example:
                  my_ntwk_set.add_a_func_on_property('s',mean)
  
  
          '''
          def plot_func(self,*args, **kwargs):
              kwargs.update({'attribute':network_property_name})
              self.plot_uncertainty_bounds_component(*args,**kwargs)
  
          setattr(self.__class__,'plot_uncertainty_bounds_'+\
                  network_property_name,plot_func)
  
          setattr(self.__class__,'plot_ub_'+\
                  network_property_name,plot_func)
  
      def __add_a_plot_minmax(self,network_property_name):
          '''
  
          takes:
                  network_property_name: a property of the Network class,
                          a string. this must have a matrix output of shape fxnxn
  
  
  
          example:
                  my_ntwk_set.add_a_func_on_property('s',mean)
  
  
          '''
          def plot_func(self,*args, **kwargs):
              kwargs.update({'attribute':network_property_name})
              self.plot_minmax_bounds_component(*args,**kwargs)
  
          setattr(self.__class__,'plot_minmax_bounds_'+\
                  network_property_name,plot_func)
  
          setattr(self.__class__,'plot_mm_'+\
                  network_property_name,plot_func)
  
      def to_dict(self):
          '''
          Returns a dictionary representation of the NetworkSet
  
          The returned dictionary has the Network names for keys, and the
          Networks as values.
          '''
          return dict([(k.name, k) for k in self.ntwk_set])
  
      def to_s_dict(ns, *args, **kwargs):
          '''
          Converts a NetworkSet to a dictionary of s-parameters
  
          The resultant  keys of the dictionary are the names of the Networks
          in NetworkSet
  
          Parameters
          -------------
          ns : NetworkSet
              dictionary of s-parameters data. values of this should be
              :class:`numpy.ndarray` assignable to :attr:`skrf.network.Network.s`
          frequency: :class:`~skrf.frequency.Frequency` object
              frequency assigned to each network
  
          \*args, \*\*kwargs :
              passed to Network.__init__ for each key/value pair of d
  
          Returns
          ----------
          s_dict : dictionary
              contains s-parameters in the form of complex numpy arrays
  
          See Also
          --------
          NetworkSet.from_s_dict
          '''
          d = ns.to_dict()
          for k in d:
              d[k] = d[k].s
          return d
  
  
  
      def element_wise_method(self,network_method_name, *args, **kwargs):
          '''
          calls a given method of each element and returns the result as
          a new NetworkSet if the output is a Network.
          '''
          output = [ntwk.__getattribute__(network_method_name)(*args, **kwargs) for ntwk in self.ntwk_set]
          if isinstance(output[0],Network):
              return NetworkSet(output)
          else:
              return output
  
      def copy(self):
          '''
          copies each network of the network set.
          '''
          return NetworkSet([k.copy() for k in self.ntwk_set])
      
      def sort(self, key=lambda x: x.name, **kwargs):
          '''
          sort this network set. 
          
          Parameters
          -------------
          **kwargs : dict
              keyword args passed to builtin sorted acting on self.ntwk_set
              
          Examples
          -----------
          >>> ns = rf.NetworkSet.from_dir('mydir')
          >>> ns.sort()
          
          Sort by other property 
          >>> ns.sort(key= lambda x: x.voltage)
          '''
          self.ntwk_set = sorted(self.ntwk_set, key = key, **kwargs)
          
      def rand(self,n=1):
          '''
          return `n` random samples from this NetworkSet
          
          Parameters
          ----------
          n : int
              number of samples to return 
          '''
          idx = npy.random.randint(0,len(self), n)
          out = [self.ntwk_set[k] for k in idx]
          
          if n ==1:
              return out[0]
          else:
              return out
          
          
          
      
      @property
      def mean_s_db(self):
          '''
          the mean magnitude in dB.
  
          note:
                  the mean is taken on the magnitude before converted to db, so
                          magnitude_2_db( mean(s_mag))
                  which is NOT the same as
                          mean(s_db)
          '''
          ntwk= self.mean_s_mag
          ntwk.s = ntwk.s_db
          return ntwk
  
      @property
      def std_s_db(self):
          '''
          the mean magnitude in dB.
  
          note:
                  the mean is taken on the magnitude before converted to db, so
                          magnitude_2_db( mean(s_mag))
                  which is NOT the same as
                          mean(s_db)
          '''
          ntwk= self.std_s_mag
          ntwk.s = ntwk.s_db
          return ntwk
  
      @property
      def inv(self):
          return NetworkSet( [ntwk.inv for ntwk in self.ntwk_set])
  
      def animate(self, attr='s_deg',ylims=(-5,5),xlims = None, show=True,
          savefigs =False, *args, **kwargs ):
          '''
          animate a property of the networkset
  
          This loops through all elements in the NetworkSet and calls
          a plotting attribute (ie Network.plot_`attr`), with given \*args
          and \*\*kwargs.
  
          Parameters
          --------------
          attr : str
              plotting property of a Network (ie 's_db', 's_deg', etc)
          ylims : tuple
              passed to ylim. needed to have consistent y-limits accross frames
          xlims : tuple
              passed to xlim
          show : bool
              show each frame as its animated
          savefigs : bool
              save each frame as a png
  
          \*args, \*\*kwargs :
              passed to the Network plotting function
  
          Notes
          --------
          using `label=None` will speed up animation significantly,
          because it prevents the legend from drawing
  
          Examples
          ------------
          >>>ns.animate('s_deg', ylims=(-5,5),label=None)
  
          '''
          was_interactive = plb.isinteractive()
          plb.ioff()
  
          for idx, k in enumerate(self):
              plb.clf()
              if 'time' in attr:
                  tmp_ntwk = k.windowed()
                  tmp_ntwk.__getattribute__('plot_'+attr)( *args, **kwargs)
              else:
                  k.__getattribute__('plot_'+attr)( *args, **kwargs)
              if ylims is not None:
                  plb.ylim(ylims)
              if xlims is not None:
                  plb.xlim(xlims)
              #rf.legend_off()
              plb.draw();
              if show:
                  plb.show()
              if savefigs:
                  plb.savefig('out_%.5i'%idx+'.png')
                  print(('out_%.5i'%idx+'.png'))
  
          if savefigs:
              print('\nto create video paste this:\n\n!ffmpeg -r 10 -i out_%5d.png  -vcodec huffyuv out.avi\n')
          if was_interactive:
              plb.ion()
  
  
  
      def add_polar_noise(self, ntwk):
          from scipy import stats
          from numpy import frompyfunc
  
          gimme_norm = lambda x: stats.norm(loc=0,scale=x).rvs(1)[0]
          ugimme_norm = frompyfunc(gimme_norm,1,1)
  
          s_deg_rv = npy.array(map(ugimme_norm, self.std_s_deg.s_re), dtype=float)
          s_mag_rv = npy.array(map(ugimme_norm, self.std_s_mag.s_re), dtype=float)
  
          mag = ntwk.s_mag+s_mag_rv
          deg = ntwk.s_deg+s_deg_rv
          ntwk.s = mag* npy.exp(1j*npy.pi/180.*deg)
          return ntwk
  
      def set_wise_function(self, func, a_property, *args, **kwargs):
          '''
          calls a function on a specific property of the networks in
          this NetworkSet.
  
          example:
                  my_ntwk_set.set_wise_func(mean,'s')
          '''
          return fon(self.ntwk_set, func, a_property, *args, **kwargs)
  
      # plotting functions
      #def plot_uncertainty_bounds(self,attribute='s_mag',m=0,n=0,\
          #n_deviations=3, alpha=.3,fill_color ='b',std_attribute=None,*args,**kwargs):
          #'''
          #plots mean value with +- uncertainty bounds in an Network attribute,
          #for a list of Networks.
  
          #takes:
              #attribute: attribute of Network type to analyze [string]
              #m: first index of attribute matrix [int]
              #n: second index of attribute matrix [int]
              #n_deviations: number of std deviations to plot as bounds [number]
              #alpha: passed to matplotlib.fill_between() command. [number, 0-1]
              #*args,**kwargs: passed to Network.plot_'attribute' command
  
          #returns:
              #None
  
  
          #Caution:
              #if your list_of_networks is for a calibrated short, then the
              #std dev of deg_unwrap might blow up, because even though each
              #network is unwrapped, they may fall on either side fo the pi
              #relative to one another.
          #'''
  
          ## calculate mean response, and std dev of given attribute
          #ntwk_mean = average(self.ntwk_set)
          #if std_attribute is None:
              ## they want to calculate teh std deviation on a different attribute
              #std_attribute = attribute
          #ntwk_std = func_on_networks(self.ntwk_set,npy.std, attribute=std_attribute)
  
          ## pull out port of interest
          #ntwk_mean.s = ntwk_mean.s[:,m,n]
          #ntwk_std.s = ntwk_std.s[:,m,n]
  
          ## create bounds (the s_mag here is confusing but is realy in units
          ## of whatever 'attribute' is. read the func_on_networks call to understand
          #upper_bound =  ntwk_mean.__getattribute__(attribute) +\
              #ntwk_std.s_mag*n_deviations
          #lower_bound =   ntwk_mean.__getattribute__(attribute) -\
              #ntwk_std.s_mag*n_deviations
  
          ## find the correct ploting method
          #plot_func = ntwk_mean.__getattribute__('plot_'+attribute)
  
          ##plot mean response
          #plot_func(*args,**kwargs)
  
          ##plot bounds
          #plb.fill_between(ntwk_mean.frequency.f_scaled, \
              #lower_bound.squeeze(),upper_bound.squeeze(), alpha=alpha, color=fill_color)
          #plb.axis('tight')
          #plb.draw()
  
      def uncertainty_ntwk_triplet(self, attribute,n_deviations=3):
          '''
          returns a 3-tuple of Network objects which contain the
          mean, upper_bound, and lower_bound for the given Network
          attribute.
  
          Used to save and plot uncertainty information data
          '''
          ntwk_mean = self.__getattribute__('mean_'+attribute)
          ntwk_std = self.__getattribute__('std_'+attribute)
          ntwk_std.s = n_deviations * ntwk_std.s
  
          upper_bound = (ntwk_mean +ntwk_std)
          lower_bound = (ntwk_mean -ntwk_std)
  
          return (ntwk_mean, lower_bound, upper_bound)
  
      def plot_uncertainty_bounds_component(self,attribute,m=None,n=None,\
              type='shade',n_deviations=3, alpha=.3, color_error =None,markevery_error=20,
              ax=None,ppf=None,kwargs_error={},*args,**kwargs):
          '''
          plots mean value of the NetworkSet with +- uncertainty bounds
          in an Network's attribute. This is designed to represent
          uncertainty in a scalar component of the s-parameter. for example
          plotting the uncertainty in the magnitude would be expressed by,
  
                  mean(abs(s)) +- std(abs(s))
  
          the order of mean and abs is important.
  
  
          takes:
                  attribute: attribute of Network type to analyze [string]
                  m: first index of attribute matrix [int]
                  n: second index of attribute matrix [int]
                  type: ['shade' | 'bar'], type of plot to draw
                  n_deviations: number of std deviations to plot as bounds [number]
                  alpha: passed to matplotlib.fill_between() command. [number, 0-1]
                  color_error: color of the +- std dev fill shading
                  markevery_error: if type=='bar', this controls frequency
                          of error bars
                  ax: Axes to plot on
                  ppf: post processing function. a function applied to the
                          upper and low
                  *args,**kwargs: passed to Network.plot_s_re command used
                          to plot mean response
                  kwargs_error: dictionary of kwargs to pass to the fill_between
                          or errorbar plot command depending on value of type.
  
          returns:
                  None
  
  
          Note:
                  for phase uncertainty you probably want s_deg_unwrap, or
                  similar. uncerainty for wrapped phase blows up at +-pi.
  
          '''
  
          if m is None:
              M = range(self[0].number_of_ports)
          else:
              M = [m]
          if n is None:
              N = range(self[0].number_of_ports)
          else:
              N = [n]
  
          for m in M:
              for n in N:
  
                  ax = plb.gca()
  
                  ntwk_mean = self.__getattribute__('mean_'+attribute)
                  ntwk_std = self.__getattribute__('std_'+attribute)
                  ntwk_std.s = n_deviations * ntwk_std.s
  
                  upper_bound = (ntwk_mean.s[:,m,n] +ntwk_std.s[:,m,n]).squeeze()
                  lower_bound = (ntwk_mean.s[:,m,n] -ntwk_std.s[:,m,n]).squeeze()
  
  
                  if ppf is not None:
                      if type =='bar':
                          warnings.warn('the \'ppf\' options doesnt work correctly with the bar-type error plots')
                      ntwk_mean.s = ppf(ntwk_mean.s)
                      upper_bound = ppf(upper_bound)
                      lower_bound = ppf(lower_bound)
                      lower_bound[npy.isnan(lower_bound)]=min(lower_bound)
                      if ppf in [mf.magnitude_2_db, mf.mag_2_db]: # quickfix of wrong ylabels due to usage of ppf for *_db plots
                          if attribute is 's_mag':
                              attribute = 's_db'
                          elif attribute is 's_time_mag':
                              attribute = 's_time_db'
  
                  if type == 'shade':
                      ntwk_mean.plot_s_re(ax=ax,m=m,n=n,*args, **kwargs)
                      if color_error is None:
                          color_error = ax.get_lines()[-1].get_color()
                      ax.fill_between(ntwk_mean.frequency.f_scaled, \
                              lower_bound,upper_bound, alpha=alpha, color=color_error,
                              **kwargs_error)
                      #ax.plot(ntwk_mean.frequency.f_scaled,ntwk_mean.s[:,m,n],*args,**kwargs)
                  elif type =='bar':
                      ntwk_mean.plot_s_re(ax=ax,m=m,n=n,*args, **kwargs)
                      if color_error is None:
                          color_error = ax.get_lines()[-1].get_color()
                      ax.errorbar(ntwk_mean.frequency.f_scaled[::markevery_error],\
                              ntwk_mean.s_re[:,m,n].squeeze()[::markevery_error], \
                              yerr=ntwk_std.s_mag[:,m,n].squeeze()[::markevery_error],\
                              color=color_error,**kwargs_error)
  
                  else:
                      raise(ValueError('incorrect plot type'))
  
                  ax.set_ylabel(Y_LABEL_DICT.get(attribute[2:],''))  # use only the function of the attribute
                  ax.axis('tight')
  
  
      def plot_minmax_bounds_component(self,attribute,m=0,n=0,\
              type='shade', alpha=.3, color_error =None,markevery_error=20,
              ax=None,ppf=None,kwargs_error={},*args,**kwargs):
          '''
          plots mean value of the NetworkSet with +- uncertainty bounds
          in an Network's attribute. This is designed to represent
          uncertainty in a scalar component of the s-parameter. for example
          plotting the uncertainty in the magnitude would be expressed by,
  
                  mean(abs(s)) +- std(abs(s))
  
          the order of mean and abs is important.
  
  
          takes:
                  attribute: attribute of Network type to analyze [string]
                  m: first index of attribute matrix [int]
                  n: second index of attribute matrix [int]
                  type: ['shade' | 'bar'], type of plot to draw
                  n_deviations: number of std deviations to plot as bounds [number]
                  alpha: passed to matplotlib.fill_between() command. [number, 0-1]
                  color_error: color of the +- std dev fill shading
                  markevery_error: if type=='bar', this controls frequency
                          of error bars
                  ax: Axes to plot on
                  ppf: post processing function. a function applied to the
                          upper and low
                  *args,**kwargs: passed to Network.plot_s_re command used
                          to plot mean response
                  kwargs_error: dictionary of kwargs to pass to the fill_between
                          or errorbar plot command depending on value of type.
  
          returns:
                  None
  
  
          Note:
                  for phase uncertainty you probably want s_deg_unwrap, or
                  similar.  uncertainty for wrapped phase blows up at +-pi.
  
          '''
  
          ax = plb.gca()
  
          ntwk_mean = self.__getattribute__('mean_'+attribute)
  
  
          lower_bound = self.__getattribute__('min_'+attribute).s_re[:,m,n].squeeze()
          upper_bound = self.__getattribute__('max_'+attribute).s_re[:,m,n].squeeze()
  
  
  
          if ppf is not None:
              if type =='bar':
                  warnings.warn('the \'ppf\' options don\'t work correctly with the bar-type error plots')
              ntwk_mean.s = ppf(ntwk_mean.s)
              upper_bound = ppf(upper_bound)
              lower_bound = ppf(lower_bound)
              lower_bound[npy.isnan(lower_bound)]=min(lower_bound)
              if ppf in [mf.magnitude_2_db, mf.mag_2_db]: # quickfix of wrong ylabels due to usage of ppf for *_db plots
                  if attribute is 's_mag':
                      attribute = 's_db'
                  elif attribute is 's_time_mag':
                      attribute = 's_time_db'
  
          if type == 'shade':
              ntwk_mean.plot_s_re(ax=ax,m=m,n=n,*args, **kwargs)
              if color_error is None:
                  color_error = ax.get_lines()[-1].get_color()
              ax.fill_between(ntwk_mean.frequency.f_scaled, \
                      lower_bound,upper_bound, alpha=alpha, color=color_error,
                      **kwargs_error)
              #ax.plot(ntwk_mean.frequency.f_scaled,ntwk_mean.s[:,m,n],*args,**kwargs)
          elif type =='bar':
              raise (NotImplementedError)
              ntwk_mean.plot_s_re(ax=ax,m=m,n=n,*args, **kwargs)
              if color_error is None:
                  color_error = ax.get_lines()[-1].get_color()
              ax.errorbar(ntwk_mean.frequency.f_scaled[::markevery_error],\
                      ntwk_mean.s_re[:,m,n].squeeze()[::markevery_error], \
                      yerr=ntwk_std.s_mag[:,m,n].squeeze()[::markevery_error],\
                      color=color_error,**kwargs_error)
  
          else:
              raise(ValueError('incorrect plot type'))
  
          ax.set_ylabel(Y_LABEL_DICT.get(attribute[2:],''))  # use only the function of the attribute
          ax.axis('tight')
  
  
      def plot_uncertainty_bounds_s_db(self,*args, **kwargs):
          '''
          this just calls
                  plot_uncertainty_bounds(attribute= 's_mag','ppf':mf.magnitude_2_db*args,**kwargs)
          see plot_uncertainty_bounds for help
  
          '''
          kwargs.update({'attribute':'s_mag','ppf':mf.magnitude_2_db})
          self.plot_uncertainty_bounds_component(*args,**kwargs)
  
      def plot_minmax_bounds_s_db(self,*args, **kwargs):
          '''
          this just calls
                  plot_uncertainty_bounds(attribute= 's_mag','ppf':mf.magnitude_2_db*args,**kwargs)
          see plot_uncertainty_bounds for help
  
          '''
          kwargs.update({'attribute':'s_mag','ppf':mf.magnitude_2_db})
          self.plot_minmax_bounds_component(*args,**kwargs)
  
      def plot_minmax_bounds_s_db10(self,*args, **kwargs):
          '''
          this just calls
                  plot_uncertainty_bounds(attribute= 's_mag','ppf':mf.magnitude_2_db*args,**kwargs)
          see plot_uncertainty_bounds for help
  
          '''
          kwargs.update({'attribute':'s_mag','ppf':mf.mag_2_db10})
          self.plot_minmax_bounds_component(*args,**kwargs)
  
      def plot_uncertainty_bounds_s_time_db(self,*args, **kwargs):
          '''
          this just calls
                  plot_uncertainty_bounds(attribute= 's_mag','ppf':mf.magnitude_2_db*args,**kwargs)
          see plot_uncertainty_bounds for help
  
          '''
          kwargs.update({'attribute':'s_time_mag','ppf':mf.magnitude_2_db})
          self.plot_uncertainty_bounds_component(*args,**kwargs)
  
      def plot_minmax_bounds_s_time_db(self,*args, **kwargs):
          '''
          this just calls
                  plot_uncertainty_bounds(attribute= 's_mag','ppf':mf.magnitude_2_db*args,**kwargs)
          see plot_uncertainty_bounds for help
  
          '''
          kwargs.update({'attribute':'s_time_mag','ppf':mf.magnitude_2_db})
          self.plot_minmax_bounds_component(*args,**kwargs)
  
      def plot_uncertainty_decomposition(self, m=0,n=0):
          '''
          plots the total and  component-wise uncertainty
  
          Parameters
          --------------
          m : int
              first s-parameters index
          n :
              second s-parameter index
  
          '''
          if self.name is not None:
              plb.title(r'Uncertainty Decomposition: %s $S_{%i%i}$'%(self.name,m,n))
          self.std_s.plot_s_mag(label='Distance', m=m,n=n)
          self.std_s_re.plot_s_mag(label='Real',  m=m,n=n)
          self.std_s_im.plot_s_mag(label='Imaginary',  m=m,n=n)
          self.std_s_mag.plot_s_mag(label='Magnitude',  m=m,n=n)
          self.std_s_arcl.plot_s_mag(label='Arc-length',  m=m,n=n)
  
  
      def plot_uncertainty_bounds_s(self, multiplier =200, *args, **kwargs):
          '''
          Plots complex uncertainty bounds plot on smith chart.
  
          This function plots the complex uncertainty of a NetworkSet
          as circles on the smith chart. At each frequency a circle
          with radii proportional to the complex standard deviation
          of the set at that frequency is drawn. Due to the fact that
          the `markersize` argument is in pixels, the radii can scaled by
          the input argument  `multiplier`.
  
          default kwargs are
              {
              'marker':'o',
              'color':'b',
              'mew':0,
              'ls':'',
              'alpha':.1,
              'label':None,
              }
  
          Parameters
          -------------
          multipliter : float
              controls the circle sizes, by multiples of the standard
              deviation.
  
  
  
          '''
          default_kwargs = {
              'marker':'o',
              'color':'b',
              'mew':0,
              'ls':'',
              'alpha':.1,
              'label':None,
              }
          default_kwargs.update(**kwargs)
  
  
  
          if plb.isinteractive():
              was_interactive = True
              plb.interactive(0)
          else:
              was_interactive = False
  
          [self.mean_s[k].plot_s_smith(*args, ms = self.std_s[k].s_mag*multiplier, **default_kwargs) for k in range(len(self[0]))]
  
          if was_interactive:
              plb.interactive(1)
          plb.draw()
          plb.show()
  
  
  
      def plot_logsigma(self, label_axis=True, *args,**kwargs):
          '''
          plots the uncertainty for the set in units of log-sigma.
          Log-sigma is the complex standard deviation, plotted in units
          of dB's.
  
          Parameters
          ------------
          \\*args, \\*\\*kwargs : arguments
              passed to self.std_s.plot_s_db()
          '''
          self.std_s.plot_s_db(*args,**kwargs)
          if label_axis:
              plb.ylabel('Standard Deviation(dB)')
  
      def signature(self,m=0,n=0,from_mean=False, operation='__sub__',
          component='s_mag',vmax = None,  *args, **kwargs):
          '''
          visualization of relative changes in a NetworkSet.
  
          Creates a colored image representing the devation of each
          Network from the from mean Network of the NetworkSet, vs
          frequency.
  
  
          Parameters
          ------------
          m : int
              first s-parameters index
          n : int
              second s-parameter index
          from_mean : Boolean
              calculate distance from mean if True. or distance from
              first network in networkset if False.
          operation : ['__sub__', '__div__'], ..
              operation to apply between each network and the reference
              network, which is either the mean, or the initial ntwk.
          component : ['s_mag','s_db','s_deg' ..]
              scalar component of Network to plot on the imshow. should
              be a property of the Network object.
  
          vmax : number
              sets upper limit of colorbar, if None, will be set to
              3*mean of the magnitude of the complex difference
          \*args,\*\*kwargs : arguments, keyword arguments
              passed to :func:`~pylab.imshow`
  
  
          '''
          if from_mean:
              diff_set = self.__getattribute__(operation)(self.mean_s)
          else:
              diff_set = self.__getattribute__(operation)(self.ntwk_set[0])
  
          sig = npy.array([diff_set[k].__getattribute__(component)[:,m,n] \
              for k in range(len(self))])
          if vmax is None:
              vmax == 3*sig.mean()
          plb.imshow(sig, vmax = vmax, *args, **kwargs)
          plb.axis('tight')
          plb.ylabel('Network \#')
          c_bar = plb.colorbar()
          c_bar.set_label('Distance From Mean')
  
  
      # io
      def write(self, file=None,  *args, **kwargs):
          '''
          Write the NetworkSet to disk using :func:`~skrf.io.general.write`
  
  
          Parameters
          -----------
          file : str or file-object
              filename or a file-object. If left as None then the
              filename will be set to Calibration.name, if its not None.
              If both are None, ValueError is raised.
          \*args, \*\*kwargs : arguments and keyword arguments
              passed through to :func:`~skrf.io.general.write`
  
          Notes
          ------
          If the self.name is not None and file is  can left as None
          and the resultant file will have the `.ns` extension appended
          to the filename.
  
          Examples
          ---------
          >>> ns.name = 'my_ns'
          >>> ns.write()
  
          See Also
          ---------
          skrf.io.general.write
          skrf.io.general.read
  
          '''
          # this import is delayed until here because of a circular dependency
          from io.general import write
  
          if file is None:
              if self.name is None:
                   raise (ValueError('No filename given. You must provide a filename, or set the name attribute'))
              file = self.name
  
          write(file,self, *args, **kwargs)
  
  
      def write_spreadsheet(self, *args, **kwargs):
          '''
          Write contents of network to a spreadsheet, for your boss to use.
  
          See Also
          ---------
          skrf.io.general.network_2_spreadsheet
          '''
          from io.general import networkset_2_spreadsheet
          networkset_2_spreadsheet(self, *args, **kwargs)
  
      def ntwk_attr_2_df(self, attr='s_db',m=0, n=0, *args, **kwargs):
          '''
          Converts an attributes of the Networks within a NetworkSet to a
          Pandas DataFrame
  
          Examples
          ---------
          df = ns.ntwk_attr_2_df('s_db',m=1,n=0)
          df.to_excel('output.xls') # see Pandas docs for more info
  
          '''
          from pandas import DataFrame, Series, Index
          index = Index(
              self[0].frequency.f_scaled,
              name='Freq(%s)'%self[0].frequency.unit
              )
          df = DataFrame(
              dict([('%s'%(k.name),
                  Series(k.__getattribute__(attr)[:,m,n],index=index))
                  for k in self]),
              index = index,
              )
          return df
  
  def plot_uncertainty_bounds_s_db(ntwk_list, *args, **kwargs):
      NetworkSet(ntwk_list).plot_uncertainty_bounds_s_db(*args, **kwargs)
  
  def func_on_networks(ntwk_list, func, attribute='s',name=None, *args,\
          **kwargs):
      '''
      Applies a function to some attribute of a list of networks.
  
  
      Returns the result in the form of a Network. This means information
      that may not be s-parameters is stored in the s-matrix of the
      returned Network.
  
      Parameters
      -------------
      ntwk_list : list of :class:`~skrf.network.Network` objects
              list of Networks on which to apply `func` to
      func : function
              function to operate on `ntwk_list` s-matrices
      attribute : string
              attribute of Network's  in ntwk_list for func to act on
      \*args,\*\*kwargs : arguments and keyword arguments
              passed to func
  
      Returns
      ---------
      ntwk : :class:`~skrf.network.Network`
              Network with s-matrix the result of func, operating on
              ntwk_list's s-matrices
  
  
      Examples
      ----------
      averaging can be implemented with func_on_networks by
  
      >>> func_on_networks(ntwk_list,mean)
  
      '''
      data_matrix = \
              npy.array([ntwk.__getattribute__(attribute) for ntwk in ntwk_list])
  
      new_ntwk = ntwk_list[0].copy()
      new_ntwk.s = func(data_matrix,axis=0,*args,**kwargs)
  
      if name is not None:
          new_ntwk.name = name
  
      return new_ntwk
  
  # short hand name for convenience
  fon = func_on_networks
  
  
  def getset(ntwk_dict, s, *args, **kwargs):
      '''
      Creates a :class:`NetworkSet`, of all :class:`~skrf.network.Network`s
      objects in a dictionary that contain `s` in its key. This is useful
      for dealing with the output of
      :func:`~skrf.io.general.load_all_touchstones`, which contains
      Networks grouped by some kind of naming convention.
  
      Parameters
      ------------
      ntwk_dict : dictionary of Network objects
          network dictionary that contains a set of keys `s`
      s : string
          string contained in the keys of ntwk_dict that are to be in the
          NetworkSet that is returned
      \*args,\*\*kwargs : passed to NetworkSet()
  
      Returns
      --------
      ntwk_set :  NetworkSet object
          A NetworkSet that made from values of ntwk_dict with `s` in
          their key
  
      Examples
      ---------
      >>>ntwk_dict = rf.load_all_touchstone('my_dir')
      >>>set5v = getset(ntwk_dict,'5v')
      >>>set10v = getset(ntwk_dict,'10v')
      '''
      ntwk_list = [ntwk_dict[k] for k in ntwk_dict if s in k]
      if len(ntwk_list) > 0:
          return NetworkSet( ntwk_list,*args, **kwargs)
      else:
          print('Warning: No keys in ntwk_dict contain \'%s\''%s)
          return None
  