text: #stats.py
  import pandas as pd
  def total(list_obj):
      total = 0
      n = len(list_obj)
      for i in range(n):
          total += list_obj[i]
      return total
  
  def mean(list_obj):
      n = len(list_obj)
      mean_ = total(list_obj) / n
      return mean_
  
  def median(list_obj):
      n = len(list_obj)
      list_obj = sorted(list_obj)
      #lists of even length divided by 2 have reminder 0
      if n % 2 != 0:
          #list length is odd
          middle_index = int((n - 1) / 2)
          median_ = list_obj[middle_index]
      else:
          upper_middle_index = int(n / 2)
          lower_middle_index = upper_middle_index - 1
          # pass slice with two middle values to mean()
          median_ = mean(list_obj[lower_middle_index : upper_middle_index + 1])
          
      return median_
  
  def mode(list_obj):
      # use to record value(s) that appear most times
      max_count = 0
      # use to count occurrences of each value in list
      counter_dict = {}
      for value in list_obj:
          # count for each value should start at 0
          counter_dict[value] = 0
      for value in list_obj:
          # add on to the count of the value for each occurrence in list_obj
          counter_dict[value] += 1
      # make a list of the value (not keys) from the dictionary
      count_list = list(counter_dict.values())
      # and find the max value
      max_count = max(count_list)
      # use a generator to make a list of the values (keys) whose number of 
      # occurences in the list match max_count
      mode_ = [key for key in counter_dict if counter_dict[key] == max_count]
      
      return mode_
  
  def variance(list_obj, sample = False):
      # popvar(list) = sum((xi - list_mean)**2) / n for all xi in list
      # save mean value of list
      list_mean = mean(list_obj)
      # use n to calculate average of sum squared diffs
      n = len(list_obj)
      # create value we can add squared diffs to
      sum_sq_diff = 0
      for val in list_obj:
          # adds each squared diff to sum_sq_diff
          sum_sq_diff += (val - list_mean) ** 2
      if sample == False:
          # normalize result by dividing by n
          variance_ = sum_sq_diff / n
      else:
          # for samples, normalize by dividing by (n-1)
          variance_ = sum_sq_diff / (n - 1)
      
      return variance_
  
  def SD(list_obj, sample = False):
      # Standard deviation is the square root of variance
      SD_ = variance(list_obj, sample) ** (1/2)
      
      return SD_
  def covariance(list_obj1, list_obj2, sample = False):
      # determine the mean of each list
      mean1 = mean(list_obj1)
      mean2 = mean(list_obj2)
      # instantiate a variable holding the value of 0; this will be used to 
      # sum the values generated in the for loop below
      cov = 0
      n1 = len(list_obj1)
      n2 = len(list_obj2)
      # check list lengths are equal
      if n1 == n2:
          n = n1
          # sum the product of the differences
          for i in range(n1):
              cov += (list_obj1[i] - mean1) * (list_obj2[i] - mean2)
          if sample == False:
              cov = cov / n
          # account for sample by dividing by one less than number of elements in list
          else:
              cov = cov / (n - 1)
          # return covariance
          return cov
      else:
          print("List lengths are not equal")
          print("List1:", n1)
          print("List2:", n2)
      
  
  def correlation(list_obj1, list_obj2):
      # corr(x,y) = cov(x, y) / (SD(x) * SD(y))
      cov = covariance(list_obj1, list_obj2)
      SD1 = SD(list_obj1)
      SD2 = SD(list_obj2)
      corr = cov / (SD1 * SD2)
      return corr
  
  def skewness(list_obj, sample = False):
      mean_ = mean(list_obj)
      SD_ = SD(list_obj, sample)
      skew = 0
      n = len(list_obj)
      for val in list_obj:
          skew += (val - mean_) ** 3
          skew = skew / n if not sample else n * skew / ((n - 1)*(n - 1) * SD_ ** 3)
          
      return skew
  
  def kurtosis(list_obj, sample = False):
      mean_ = mean(list_obj)
      kurt = 0
      SD_ = SD(list_obj, sample)
      n = len(list_obj)
      for x in list_obj:
          kurt += (x - mean_) ** 4
      kurt = kurt / (n * SD_ ** 4) if not sample else  n * (n + 1) * kurt / \
      ((n - 1) * (n - 2) * (SD_ ** 4)) - (3 *(n - 1) ** 2) / ((n - 2) * (n - 3))
      
      return kurt
  def gather_statistics(df, sample = False):
      dct = {key:{} for key in df}
      for key, val in df.items():
          # drop any missing observations from dataframe
          val = val.dropna(axis=0)
          dct[key]["mean"] = round(mean(val),3)
          dct[key]["median"] = round(median(val),3)
          #skip mode. . .         dct[key]["mode"] = mode(val)
          dct[key]["variance"] = round(variance(val, sample),3)
          dct[key]["S.D."] = round(SD(val, sample) ,3)
          dct[key]["skewness"] = round(skewness(val, sample),3)
          dct[key]["kurtosis"] = round(kurtosis(val, sample),3)
      stats_df = pd.DataFrame(dct)  
      return stats_df