code: def __train(self, n_clusters=4):
          
  
          try:
              for obs in self.observations:
                  features, ids = self.__get_features_for_observation(observation=obs, last_column_is_id=True)
                  # the last column is the observation id
                  normalised_data = whiten(features)
  
                  # skip any rows that contain just zero values... they create nans
                  first_safe_row = pdkit.utils.non_zero_index(normalised_data)
                  observation_ids = features.tolist()
                  sd = features[first_safe_row] / normalised_data[first_safe_row]
  
                  # Calculate centroids and sort result
                  centroids_array, _ = kmeans(normalised_data, n_clusters)
                  sorted_centroids = pdkit.utils.centroid_sort(centroids_array)
  
                  if not self.clusters:
                      self.clusters = [[obs, sd.tolist(), sorted_centroids.tolist()]]
                  else:
                      self.clusters.append([obs, sd.tolist(),sorted_centroids.tolist()])
          except IOError as e:
              ierr = "({}): {}".format(e.errno, e.strerror)
              logging.error("Error training UPDRS, file not found, I/O error %s", ierr)
  
          except ValueError as verr:
              logging.error("Error training UPDRS ValueError ->%s", verr.message)
  
          except:
              logging.error("Unexpected error on training UPDRS init: %s", sys.exc_info()[0])
docs: Calculate cluster's centroids and standard deviations. If there are at least the number of threshold rows \
              then:
  
              * Observations will be normalised.
  
              * Standard deviations will be returned.
  
              * Clusters will be returned.
  
              * Centroids are ordered based on their distance from an arbitrary -100, -100 point.
  
              If there are not enough Observations, then centroids and standard deviations will be set to the empty list.
  
              General strategy: Use numpy.array for calculations. Keep everything in float. Convert arrays back to lists \
              at the end.
  
              :param n_clusters: the number of clusters
              :type n_clusters: int