class_docstr: A simple clustering method that forms k clusters by first assigning
  samples to the closest medoids, and then swapping medoids with non-medoid
  samples if the total distance (cost) between the cluster members and their medoid
  is smaller than prevoisly.
  
  
  Parameters:
  -----------
  k: int
      The number of clusters the algorithm will form.
class_docstr_tok: ["A","simple","clustering","method","that","forms","k","clusters","by","first","assigning","samples","to","the","closest","medoids",",","and","then","swapping","medoids","with","non-medoid","samples","if","the","total","distance","(","cost",")","between","the","cluster","members","and","their","medoid","is","smaller","than","prevoisly",".","Parameters",":","--","--","--","--","--","-","k",":","int","The","number","of","clusters","the","algorithm","will","form","."]
docstr: Do Partitioning Around Medoids and return the cluster labels 
docstr_tok: ["Do","Partitioning","Around","Medoids","and","return","the","cluster","labels"]
code: def predict(self, X):
      """Do Partitioning Around Medoids and return the cluster labels"""
      # Initialize medoids randomly
      medoids = self._init_random_medoids(X)
      # Assign samples to closest medoids
      clusters = self._create_clusters(X, medoids)
  
      # Calculate the initial cost (total distance between samples and
      # corresponding medoids)
      cost = self._calculate_cost(X, clusters, medoids)
  
      # Iterate until we no longer have a cheaper cost
      while True:
          best_medoids = medoids
          lowest_cost = cost
          for medoid in medoids:
              # Get all non-medoid samples
              non_medoids = self._get_non_medoids(X, medoids)
              # Calculate the cost when swapping medoid and samples
              for sample in non_medoids:
                  # Swap sample with the medoid
                  new_medoids = medoids.copy()
                  new_medoids[medoids == medoid] = sample
                  # Assign samples to new medoids
                  new_clusters = self._create_clusters(X, new_medoids)
                  # Calculate the cost with the new set of medoids
                  new_cost = self._calculate_cost(
                      X, new_clusters, new_medoids
                  )
                  # If the swap gives us a lower cost we save the medoids and cost
                  if new_cost < lowest_cost:
                      lowest_cost = new_cost
                      best_medoids = new_medoids
          # If there was a swap that resultet in a lower cost we save the
          # resulting medoids from the best swap and the new cost
          if lowest_cost < cost:
              cost = lowest_cost
              medoids = best_medoids
          # Else finished
          else:
              break
  
      final_clusters = self._create_clusters(X, medoids)
      # Return the samples cluster indices as labels
      return self._get_cluster_labels(final_clusters, X)
  
file_path: ML-From-Scratch/mlfromscratch/unsupervised_learning/partitioning_around_medoids.py
imports: from __future__ import print_function, division
  import numpy
  from mlfromscratch.utils import normalize, euclidean_distance, Plot
  from mlfromscratch.unsupervised_learning import PCA
label_desc: Use image node_id 8 for calling the PAM obj's underlying member method code with example usage: obj.predict(X) and returns: self