class_docstr: Boosting method that uses a number of weak classifiers in 
  ensemble to make a strong classifier. This implementation uses decision
  stumps, which is a one level Decision Tree. 
  
  Parameters:
  -----------
  n_clf: int
      The number of weak classifiers that will be used. 
code: def fit(self, X, y):
      n_samples, n_features = np.shape(X)
  
      # Initialize weights to 1/N
      w = np.full(n_samples, (1 / n_samples))
  
      self.clfs = []
      # Iterate through classifiers
      for _ in range(self.n_clf):
          clf = DecisionStump()
          # Minimum error given for using a certain feature value threshold
          # for predicting sample label
          min_error = float("inf")
          # Iterate throught every unique feature value and see what value
          # makes the best threshold for predicting y
          for feature_i in range(n_features):
              feature_values = np.expand_dims(X[:, feature_i], axis=1)
              unique_values = np.unique(feature_values)
              # Try every unique feature value as threshold
              for threshold in unique_values:
                  p = 1
                  # Set all predictions to '1' initially
                  prediction = np.ones(np.shape(y))
                  # Label the samples whose values are below threshold as '-1'
                  prediction[X[:, feature_i] < threshold] = -1
                  # Error = sum of weights of misclassified samples
                  error = sum(w[y != prediction])
  
                  # If the error is over 50% we flip the polarity so that samples that
                  # were classified as 0 are classified as 1, and vice versa
                  # E.g error = 0.8 => (1 - error) = 0.2
                  if error > 0.5:
                      error = 1 - error
                      p = -1
  
                  # If this threshold resulted in the smallest error we save the
                  # configuration
                  if error < min_error:
                      clf.polarity = p
                      clf.threshold = threshold
                      clf.feature_index = feature_i
                      min_error = error
          # Calculate the alpha which is used to update the sample weights,
          # Alpha is also an approximation of this classifier's proficiency
          clf.alpha = 0.5 * math.log(
              (1.0 - min_error) / (min_error + 1e-10)
          )
          # Set all predictions to '1' initially
          predictions = np.ones(np.shape(y))
          # The indexes where the sample values are below threshold
          negative_idx = (
              clf.polarity * X[:, clf.feature_index]
              < clf.polarity * clf.threshold
          )
          # Label those as '-1'
          predictions[negative_idx] = -1
          # Calculate new weights
          # Missclassified samples gets larger weights and correctly classified samples smaller
          w *= np.exp(-clf.alpha * y * predictions)
          # Normalize to one
          w /= np.sum(w)
  
          # Save classifier
          self.clfs.append(clf)
  
file_path: ML-From-Scratch/mlfromscratch/supervised_learning/adaboost.py
imports: from __future__ import division, print_function
  import numpy
  import math
  from sklearn import datasets
  import matplotlib.pyplot
  import pandas
  from mlfromscratch.utils import train_test_split, accuracy_score, Plot
label_desc: Use image node_id 2 for calling the Adaboost obj's underlying member method code with example usage: obj.fit(X, y) without return types