code: 
  
  
  """simple docstring"""
  
  
  
  
  import warnings
  
  import numpy as np
  import pandas as pd
  from matplotlib import pyplot as plt
  from sklearn.metrics import pairwise_distances
  
  warnings.filterwarnings("""ignore""")
  
  __SCREAMING_SNAKE_CASE					=						"""K-MEANS-CLUST/ """
  
  
  
  def 						__a      (  a,		a,		a=None					):
  
  
  
  
  
         """simple docstring"""
  
  
  
  
  
  
  
         if seed is not None:  # useful for obtaining consistent results
                np.random.seed(a					)
         _a          =				data.shape[0]  # number of data points
  
         # Pick K indices from range [0, N).
         _a          =				np.random.randint(0,		a,		a					)
  
         # Keep centroids as dense format, as many entries will be nonzero due to averaging.
         # As long as at least one document in a cluster contains a word,
         # it will carry a nonzero weight in the TF-IDF vector of the centroid.
         _a          =				data[rand_indices, :]
  
         return centroids
  
  
  
  def 						__a      (  a,		a					):
  
  
  
  
  
         """simple docstring"""
  
  
  
  
  
  
  
         return pairwise_distances(a,		a,		metric="euclidean"					)
  
  
  
  def 						__a      (  a,		a					):
  
  
  
  
  
         """simple docstring"""
  
  
  
  
  
  
  
         _a          =				centroid_pairwise_dist(a,		a					)
  
         # Compute cluster assignments for each data point:
         # Fill in the blank (RHS only)
         _a          =				np.argmin(a,		axis=1					)
  
         return cluster_assignment
  
  
  
  def 						__a      (  a,		a,		a					):
  
  
  
  
  
         """simple docstring"""
  
  
  
  
  
  
  
         _a          =				[]
         for i in range(a					):
                # Select all data points that belong to cluster i. Fill in the blank (RHS only)
                _a          =				data[cluster_assignment == i]
                # Compute the mean of the data points. Fill in the blank (RHS only)
                _a          =				member_data_points.mean(axis=0					)
                new_centroids.append(a					)
         _a          =				np.array(a					)
  
         return new_centroids
  
  
  
  def 						__a      (  a,		a,		a,		a					):
  
  
  
  
  
         """simple docstring"""
  
  
  
  
  
  
  
         _a          =				0.0
         for i in range(a					):
                # Select all data points that belong to cluster i. Fill in the blank (RHS only)
                _a          =				data[cluster_assignment == i, :]
  
                if member_data_points.shape[0] > 0:  # check if i-th cluster is non-empty
                       # Compute distances from centroid to data points (RHS only)
                       _a          =				pairwise_distances(
                           a,		[centroids[i]],		metric="euclidean"					)
                       _a          =				distances**2
                       heterogeneity += np.sum(a					)
  
         return heterogeneity
  
  
  
  def 						__a      (  a,		a					):
  
  
  
  
  
         """simple docstring"""
  
  
  
  
  
  
  
         plt.figure(figsize=(7, 4)					)
         plt.plot(a,		linewidth=4					)
         plt.xlabel("# Iterations"					)
         plt.ylabel("Heterogeneity"					)
         plt.title(F'Heterogeneity of clustering over time, K={k:d}'					)
         plt.rcParams.update({"font.size": 1_6}					)
         plt.show()
  
  
  
  def 						__a      (  a,		a,		a,		a=5_0_0,		a=None,		a=False					):
  
  
  
  
  
         """simple docstring"""
  
  
  
  
  
  
  
         _a          =				initial_centroids[:]
         _a          =				None
  
         for itr in range(a					):
                if verbose:
                       print(a,		end=""					)
  
                # 1. Make cluster assignments using nearest centroids
                _a          =				assign_clusters(a,		a					)
  
                # 2. Compute a new centroid for each of the k clusters, averaging all data
                #    points assigned to that cluster.
                _a          =				revise_centroids(a,		a,		a					)
  
                # Check for convergence: if none of the assignments changed, stop
                if (
                    prev_cluster_assignment is not None
                    and (prev_cluster_assignment == cluster_assignment).all()
                ):
                       break
  
                # Print number of new assignments
                if prev_cluster_assignment is not None:
                       _a          =				np.sum(prev_cluster_assignment != cluster_assignment					)
                       if verbose:
                              print(
                                  F'    {num_changed:5d} elements changed their cluster assignment.'					)
  
          # Record heterogeneity convergence metric
                if record_heterogeneity is not None:
                       # YOUR CODE HERE
                       _a          =				compute_heterogeneity(a,		a,		a,		a					)
                       record_heterogeneity.append(a					)
  
                _a          =				cluster_assignment[:]
  
         return centroids, cluster_assignment
  
  
  # Mock test below
  if False:  # change to true to run this test case.
     from sklearn import datasets as ds
  
     __SCREAMING_SNAKE_CASE					=						ds.load_iris()
     __SCREAMING_SNAKE_CASE					=						3
     __SCREAMING_SNAKE_CASE					=						[]
     __SCREAMING_SNAKE_CASE					=						get_initial_centroids(dataset["""data"""], k, seed=0)
     __SCREAMING_SNAKE_CASE		,  __SCREAMING_SNAKE_CASE					=						kmeans(
         dataset["""data"""],
         k,
         initial_centroids,
         maxiter=4_00,
         record_heterogeneity=heterogeneity,
         verbose=True,
     )
     plot_heterogeneity(heterogeneity, k)
  
  
  
  def 						__a      (  a,		a,		a=None					):
  
  
  
  
  
         """simple docstring"""
  
  
  
  
  
  
  
         if fill_missing_report:
                _a          =				df.fillna(value=a					)
         _a          =				1
         _a          =				df.select_dtypes(np.number					).columns
         _a          =				(
             df.groupby(["Cluster"]					)[  # construct report dataframe
                 numeric_cols
             ]  # group by cluster number
             .agg(
                 [
                     ("sum", np.sum),
                     ("mean_with_zeros", lambda a					: np.mean(np.nan_to_num(a					)					)),
                     ("mean_without_zeros", lambda a					: x.replace(0,		np.NaN					).mean()),
                     (
                         "mean_25-75",
                         lambda a					: np.mean(
                             np.nan_to_num(
                                 sorted(a					)[
                                     round(len(a					) * 2_5 / 1_0_0					) : round(len(a					) * 7_5 / 1_0_0					)
                                 ]					)					),
                     ),
                     ("mean_with_na", np.mean),
                     ("min", lambda a					: x.min()),
                     ("5%", lambda a					: x.quantile(0.05					)),
                     ("25%", lambda a					: x.quantile(0.25					)),
                     ("50%", lambda a					: x.quantile(0.50					)),
                     ("75%", lambda a					: x.quantile(0.75					)),
                     ("95%", lambda a					: x.quantile(0.95					)),
                     ("max", lambda a					: x.max()),
                     ("count", lambda a					: x.count()),
                     ("stdev", lambda a					: x.std()),
                     ("mode", lambda a					: x.mode()[0]),
                     ("median", lambda a					: x.median()),
                     ("# > 0", lambda a					: (x > 0).sum()),
                 ]					)
             .T.reset_index()
             .rename(index=a,		columns={"level_0": "Features", "level_1": "Type"}					)
         )  # rename columns
         # calculate the size of cluster(count of clientID's)
         _a          =				report[
             (report["Features"] == "dummy") & (report["Type"] == "count")
         ].copy()  # avoid SettingWithCopyWarning
         _a          =				(
             "ClusterSize"  # rename created cluster df to match report column names
         )
         _a          =				"# of Customers"
         _a          =				pd.DataFrame(
             clustersize.iloc[:, 2:].values
             / clustersize.iloc[:, 2:].values.sum()					)
         _a          =				"% of Customers"  # rename created cluster df to match report column names
         _a          =				"ClusterProportion"
         _a          =				clusterproportion.columns.tolist()
         _a          =				cols[-2:] + cols[:-2]
         _a          =				clusterproportion[cols]  # rearrange columns to match report
         _a          =				report.columns
         _a          =				pd.DataFrame(
             abs(
                 report[report["Type"] == "count"].iloc[:, 2:].values
                 - clustersize.iloc[:, 2:].values					)					)  # generating df with count of nan values
         _a          =				0
         _a          =				"# of nan"
         _a          =				report[
             report["Type"] == "count"
         ].Features.tolist()  # filling values in order to match report
         _a          =				a.columns.tolist()
         _a          =				cols[-2:] + cols[:-2]
         _a          =				a[cols]  # rearrange columns to match report
         _a          =				report.columns  # rename columns to match report
         _a          =				report.drop(
             report[report.Type == "count"].index					)  # drop count values except cluster size
         _a          =				pd.concat(
             [report, a, clustersize, clusterproportion],		axis=0					)  # concat report with clustert size and nan values
         _a          =				report["Features"].isin(a					)
         _a          =				report.columns.tolist()
         _a          =				cols[0:2] + cols[-1:] + cols[2:-1]
         _a          =				report[cols]
         _a          =				{
             "ClusterSize": 9,
             "ClusterProportion": 8,
             "mean_with_zeros": 7,
             "mean_with_na": 6,
             "max": 5,
             "50%": 4,
             "min": 3,
             "25%": 2,
             "75%": 1,
             "# of nan": 0,
             "# > 0": -1,
             "sum_with_na": -2,
         }
         _a          =				(
             report.assign(
                 Sortera=lambda a					: x.Type.map(a					),		Sortera=lambda a					: list(reversed(range(len(a					)					)					)					),		)
             .sort_values(["Sorter1", "Mark", "Sorter2"],		ascending=a					)
             .drop(["Sorter1", "Sorter2"],		axis=1					)
         )
         _a          =				""
         _a          =				report.reset_index()
         _a          =				report.drop(columns=["index"]					)
         return report
  
  
  if __name__ == "__main__":
     import doctest
  
     doctest.testmod()
  
style_context: 
  
  
  """simple docstring"""
  
  
  
  
  from __future__ import annotations
  
  from collections.abc import Iterator
  from typing import Any
  
  
  
  
  
  class __snake_case		:
  
  
  
  
  
  
  
     """simple docstring"""
  
  
  
  
     def __init__(		self							:List[Any]				,  UpperCamelCase__							:Any       ):
            _a          =				data
            _a          =				None
  
  
  
  
     def __str__(		self							:Any       ):
            return f'{self.data}'
  
  
  
  
  
  
  class __snake_case		:
  
  
  
  
  
  
  
     """simple docstring"""
  
  
  
  
     def __init__(		self							:Any       ):
            _a          =				None
            _a          =				None
  
  
  
  
     def __iter__(		self							:List[str]       ):
            _a          =				self.front
            while node:
                   yield node.data
                   _a          =				node.next
  
  
  
  
     def __len__(		self							:List[str]       ):
            return len(tuple(iter(self       )       )       )
  
  
  
  
     def __str__(		self							:Union[str, Any]       ):
            return " <- ".join(str(UpperCamelCase__       ) for item in self       )
  
  
  
  
     def SCREAMING_SNAKE_CASE_      (		self							:Any       ):
            return len(self       ) == 0
  
  
  
  
     def SCREAMING_SNAKE_CASE_      (		self							:List[str]				,  UpperCamelCase__							:Any       ):
            _a          =				Node(UpperCamelCase__       )
            if self.is_empty():
                   _a          =				_a          =				node
            else:
                   assert isinstance(self.rear				,  UpperCamelCase__       )
                   _a          =				node
                   _a          =				node
  
  
  
  
     def SCREAMING_SNAKE_CASE_      (		self							:Tuple       ):
            if self.is_empty():
                   raise IndexError("dequeue from empty queue"       )
            assert isinstance(self.front				,  UpperCamelCase__       )
            _a          =				self.front
            _a          =				self.front.next
            if self.front is None:
                   _a          =				None
            return node.data
  
  
  
  
     def SCREAMING_SNAKE_CASE_      (		self							:int       ):
            _a          =				_a          =				None
  
  
  if __name__ == "__main__":
     from doctest import testmod
  
     testmod()
  