code: def gaussian(df, width=0.3, downshift=-1.8, prefix=None):
      
  
      df = df.copy()
  
      imputed = df.isnull()  # Keep track of what's real
  
      if prefix:
          mask = np.array([l.startswith(prefix) for l in df.columns.values])
          mycols = np.arange(0, df.shape[1])[mask]
      else:
          mycols = np.arange(0, df.shape[1])
  
  
      if type(width) is not list:
          width = [width] * len(mycols)
  
      elif len(mycols) != len(width):
          raise ValueError("Length of iterable 'width' does not match # of columns")
  
      if type(downshift) is not list:
          downshift = [downshift] * len(mycols)
  
      elif len(mycols) != len(downshift):
          raise ValueError("Length of iterable 'downshift' does not match # of columns")
  
      for i in mycols:
          data = df.iloc[:, i]
          mask = data.isnull().values
          mean = data.mean(axis=0)
          stddev = data.std(axis=0)
  
          m = mean + downshift[i]*stddev
          s = stddev*width[i]
  
          # Generate a list of random numbers for filling in
          values = np.random.normal(loc=m, scale=s, size=df.shape[0])
  
          # Now fill them in
          df.iloc[mask, i] = values[mask]
  
      return df, imputed
docs: Impute missing values by drawing from a normal distribution
  
      :param df:
      :param width: Scale factor for the imputed distribution relative to the standard deviation of measured values. Can be a single number or list of one per column.
      :param downshift: Shift the imputed values down, in units of std. dev. Can be a single number or list of one per column
      :param prefix: The column prefix for imputed columns
      :return: