code: 
  
  
  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""")
  
  A_    :					Tuple             ="""K-MEANS-CLUST/ """
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__=None      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						if seed is not None:  # useful for obtaining consistent results
  												np.random.seed(UpperCAmelCase__      )
  						a_					=		data.shape[0]  # number of data points
  
  						# Pick K indices from range [0, N).
  						a_					=		np.random.randint(0       ,    UpperCAmelCase__       ,    UpperCAmelCase__      )
  
  						# 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   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						return pairwise_distances(UpperCAmelCase__       ,    UpperCAmelCase__       ,    metric="""euclidean"""      )
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						a_					=		centroid_pairwise_dist(UpperCAmelCase__       ,    UpperCAmelCase__      )
  
  						# Compute cluster assignments for each data point:
  						# Fill in the blank (RHS only)
  						a_					=		np.argmin(UpperCAmelCase__       ,    axis=1      )
  
  						return cluster_assignment
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						a_					=		[]
  						for i in range(UpperCAmelCase__      ):
  												# 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(UpperCAmelCase__      )
  						a_					=		np.array(UpperCAmelCase__      )
  
  						return new_centroids
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						a_					=		0.0
  						for i in range(UpperCAmelCase__      ):
  												# 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(
  																		    UpperCAmelCase__       ,    [centroids[i]]       ,    metric="""euclidean"""      )
  																		a_					=		distances**2
  																		heterogeneity += np.sum(UpperCAmelCase__      )
  
  						return heterogeneity
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						plt.figure(figsize=(7, 4)      )
  						plt.plot(UpperCAmelCase__       ,    linewidth=4      )
  						plt.xlabel("""# Iterations"""      )
  						plt.ylabel("""Heterogeneity"""      )
  						plt.title(F"Heterogeneity of clustering over time, K={k:d}"      )
  						plt.rcParams.update({"""font.size""": 16}      )
  						plt.show()
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__=500       ,    UpperCAmelCase__=None       ,    UpperCAmelCase__=False      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						a_					=		initial_centroids[:]
  						a_					=		None
  
  						for itr in range(UpperCAmelCase__      ):
  												if verbose:
  																		print(UpperCAmelCase__       ,    end=""""""      )
  
  												# 1. Make cluster assignments using nearest centroids
  												a_					=		assign_clusters(UpperCAmelCase__       ,    UpperCAmelCase__      )
  
  												# 2. Compute a new centroid for each of the k clusters, averaging all data
  												#    points assigned to that cluster.
  												a_					=		revise_centroids(UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__      )
  
  												# 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(UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__      )
  																		record_heterogeneity.append(UpperCAmelCase__      )
  
  												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
  
  	A_    :					Dict             =ds.load_iris()
  	A_    :					Optional[int]             =3
  	A_    :					Optional[Any]             =[]
  	A_    :					Any             =get_initial_centroids(dataset["""data"""], k, seed=0)
  	A_    :					Any             =kmeans(
  	    dataset["""data"""],
  	    k,
  	    initial_centroids,
  	    maxiter=400,
  	    record_heterogeneity=heterogeneity,
  	    verbose=True,
  	)
  	plot_heterogeneity(heterogeneity, k)
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__=None      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						if fill_missing_report:
  												a_					=		df.fillna(value=UpperCAmelCase__      )
  						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 UpperCAmelCase__      : np.mean(np.nan_to_num(UpperCAmelCase__      )      )),
  						            ("""mean_without_zeros""", lambda UpperCAmelCase__      : x.replace(0       ,    np.NaN      ).mean()),
  						            (
  						                """mean_25-75""",
  						                lambda UpperCAmelCase__      : np.mean(
  						                    np.nan_to_num(
  						                        sorted(UpperCAmelCase__      )[
  						                            round(len(UpperCAmelCase__      ) * 25 / 100      ) : round(len(UpperCAmelCase__      ) * 75 / 100      )
  						                        ]      )      ),
  						            ),
  						            ("""mean_with_na""", np.mean),
  						            ("""min""", lambda UpperCAmelCase__      : x.min()),
  						            ("""5%""", lambda UpperCAmelCase__      : x.quantile(0.05      )),
  						            ("""25%""", lambda UpperCAmelCase__      : x.quantile(0.25      )),
  						            ("""50%""", lambda UpperCAmelCase__      : x.quantile(0.50      )),
  						            ("""75%""", lambda UpperCAmelCase__      : x.quantile(0.75      )),
  						            ("""95%""", lambda UpperCAmelCase__      : x.quantile(0.95      )),
  						            ("""max""", lambda UpperCAmelCase__      : x.max()),
  						            ("""count""", lambda UpperCAmelCase__      : x.count()),
  						            ("""stdev""", lambda UpperCAmelCase__      : x.std()),
  						            ("""mode""", lambda UpperCAmelCase__      : x.mode()[0]),
  						            ("""median""", lambda UpperCAmelCase__      : x.median()),
  						            ("""# > 0""", lambda UpperCAmelCase__      : (x > 0).sum()),
  						        ]      )
  						    .T.reset_index()
  						    .rename(index=UpperCAmelCase__       ,    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(UpperCAmelCase__      )
  						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 UpperCAmelCase__      : x.Type.map(UpperCAmelCase__      )       ,    Sortera=lambda UpperCAmelCase__      : list(reversed(range(len(UpperCAmelCase__      )      )      )      )       ,    )
  						    .sort_values(["""Sorter1""", """Mark""", """Sorter2"""]       ,    ascending=UpperCAmelCase__      )
  						    .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: 
  
  
  from collections.abc import Callable
  from math import log
  from os import name, system
  from random import gauss, seed
  from typing import TypeVar
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						seed(1      )
  						return [gauss(UpperCAmelCase__       ,    UpperCAmelCase__      ) for _ in range(UpperCAmelCase__      )]
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						return [k for k in range(UpperCAmelCase__      ) for _ in range(instance_count[k]      )]
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						return sum(UpperCAmelCase__      ) / instance_count
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						return instance_count / total_count
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						a_					=		[]  # An empty list to store all squared differences
  						# iterate over number of elements in items
  						for i in range(len(UpperCAmelCase__      )      ):
  												# for loop iterates over number of elements in inner layer of items
  												for j in range(len(items[i]      )      ):
  																		# appending squared differences to 'squared_diff' list
  																		squared_diff.append((items[i][j] - means[i]) ** 2      )
  
      # one divided by (the number of all instances - number of classes) multiplied by
      # sum of all squared differences
  						a_					=		len(UpperCAmelCase__      )  # Number of classes in dataset
  						return 1 / (total_count - n_classes) * sum(UpperCAmelCase__      )
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						a_					=		[]
  						# for loop iterates over number of elements in list
  						for i in range(len(UpperCAmelCase__      )      ):
  												# for loop iterates over number of inner items of each element
  												for j in range(len(x_items[i]      )      ):
  																		a_					=		[]  # to store all discriminant values of each item as a list
  																		# for loop iterates over number of classes we have in our dataset
  																		for k in range(len(UpperCAmelCase__      )      ):
  																								# appending values of discriminants for each class to 'temp' list
  																								temp.append(
  																								    x_items[i][j] * (means[k] / variance)
  																								    - (means[k] ** 2 / (2 * variance))
  																								    + log(probabilities[k]      )      )
  																		# appending discriminant values of each item to 'results' list
  																		results.append(UpperCAmelCase__      )
  
  						return [result.index(max(UpperCAmelCase__      )      ) for result in results]
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__      ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						a_					=		sum(1 for i, j in zip(UpperCAmelCase__       ,    UpperCAmelCase__      ) if i == j      )
  						# percentage of accuracy equals to number of correct predictions divided by number
  						# of all data and multiplied by 100
  						return (correct / len(UpperCAmelCase__      )) * 100
  
  
  A_    :					Dict             =TypeVar("""num""")
  
  
  
  
  def   lowerCamelCase_       (					UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__ = lambda UpperCAmelCase__      : True       ,    UpperCAmelCase__ = None       ,    ):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						while True:
  												try:
  																		a_					=		input_type(input(UpperCAmelCase__      ).strip() or default      )
  																		if condition(UpperCAmelCase__      ):
  																								return user_input
  																		else:
  																								print(F"{user_input}: {err_msg}"      )
  																								continue
  												except ValueError:
  																		print(
  																		    F"{user_input}: Incorrect input type, expected {input_type.__name__!r}"      )
  
  
  
  
  def   lowerCamelCase_       (					):
  
  
  
  						"""simple docstring"""
  
  
  
  
  						while True:
  												print(""" Linear Discriminant Analysis """.center(50       ,    """*"""      )      )
  												print("""*""" * 50       ,    """\n"""      )
  												print("""First of all we should specify the number of classes that"""      )
  												print("""we want to generate as training dataset"""      )
  												# Trying to get number of classes
  												a_					=		valid_input(
  												    input_type=UpperCAmelCase__       ,    condition=lambda UpperCAmelCase__      : x > 0       ,    input_msg="""Enter the number of classes (Data Groupings): """       ,    err_msg="""Number of classes should be positive!"""       ,    )
  
  												print("""-""" * 100      )
  
  												# Trying to get the value of standard deviation
  												a_					=		valid_input(
  												    input_type=UpperCAmelCase__       ,    condition=lambda UpperCAmelCase__      : x >= 0       ,    input_msg=(
  												        """Enter the value of standard deviation"""
  												        """(Default value is 1.0 for all classes): """
  												    )       ,    err_msg="""Standard deviation should not be negative!"""       ,    default="""1.0"""       ,    )
  
  												print("""-""" * 100      )
  
  												# Trying to get number of instances in classes and theirs means to generate
  												# dataset
  												a_					=		[]  # An empty list to store instance counts of classes in dataset
  												for i in range(UpperCAmelCase__      ):
  																		a_					=		valid_input(
  																		    input_type=UpperCAmelCase__       ,    condition=lambda UpperCAmelCase__      : x > 0       ,    input_msg=(F"Enter The number of instances for class_{i+1}: ")       ,    err_msg="""Number of instances should be positive!"""       ,    )
  																		counts.append(UpperCAmelCase__      )
  												print("""-""" * 100      )
  
  												# An empty list to store values of user-entered means of classes
  												a_					=		[]
  												for a in range(UpperCAmelCase__      ):
  																		a_					=		valid_input(
  																		    input_type=UpperCAmelCase__       ,    input_msg=(F"Enter the value of mean for class_{a+1}: ")       ,    err_msg="""This is an invalid value."""       ,    )
  																		user_means.append(UpperCAmelCase__      )
  												print("""-""" * 100      )
  
  												print("""Standard deviation: """       ,    UpperCAmelCase__      )
  												# print out the number of instances in classes in separated line
  												for i, count in enumerate(UpperCAmelCase__       ,    1      ):
  																		print(F"Number of instances in class_{i} is: {count}"      )
  												print("""-""" * 100      )
  
  												# print out mean values of classes separated line
  												for i, user_mean in enumerate(UpperCAmelCase__       ,    1      ):
  																		print(F"Mean of class_{i} is: {user_mean}"      )
  												print("""-""" * 100      )
  
  												# Generating training dataset drawn from gaussian distribution
  												a_					=		[
  												    gaussian_distribution(user_means[j]       ,    UpperCAmelCase__       ,    counts[j]      )
  												    for j in range(UpperCAmelCase__      )
  												]
  												print("""Generated Normal Distribution: \n"""       ,    UpperCAmelCase__      )
  												print("""-""" * 100      )
  
  												# Generating Ys to detecting corresponding classes
  												a_					=		y_generator(UpperCAmelCase__       ,    UpperCAmelCase__      )
  												print("""Generated Corresponding Ys: \n"""       ,    UpperCAmelCase__      )
  												print("""-""" * 100      )
  
  												# Calculating the value of actual mean for each class
  												a_					=		[calculate_mean(counts[k]       ,    x[k]      ) for k in range(UpperCAmelCase__      )]
  												# for loop iterates over number of elements in 'actual_means' list and print
  												# out them in separated line
  												for i, actual_mean in enumerate(UpperCAmelCase__       ,    1      ):
  																		print(F"Actual(Real) mean of class_{i} is: {actual_mean}"      )
  												print("""-""" * 100      )
  
  												# Calculating the value of probabilities for each class
  												a_					=		[
  												    calculate_probabilities(counts[i]       ,    sum(UpperCAmelCase__      )      ) for i in range(UpperCAmelCase__      )
  												]
  
  												# for loop iterates over number of elements in 'probabilities' list and print
  												# out them in separated line
  												for i, probability in enumerate(UpperCAmelCase__       ,    1      ):
  																		print(F"Probability of class_{i} is: {probability}"      )
  												print("""-""" * 100      )
  
  												# Calculating the values of variance for each class
  												a_					=		calculate_variance(UpperCAmelCase__       ,    UpperCAmelCase__       ,    sum(UpperCAmelCase__      )      )
  												print("""Variance: """       ,    UpperCAmelCase__      )
  												print("""-""" * 100      )
  
  												# Predicting Y values
  												# storing predicted Y values in 'pre_indexes' variable
  												a_					=		predict_y_values(UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__       ,    UpperCAmelCase__      )
  												print("""-""" * 100      )
  
  												# Calculating Accuracy of the model
  												print(F"Accuracy: {accuracy(UpperCAmelCase__       ,    UpperCAmelCase__      )}"      )
  												print("""-""" * 100      )
  												print(""" DONE """.center(100       ,    """+"""      )      )
  
  												if input("""Press any key to restart or 'q' for quit: """      ).strip().lower() == "q":
  																		print("""\n""" + """GoodBye!""".center(100       ,    """-"""      ) + """\n"""      )
  																		break
  												system("""cls""" if name == """nt""" else """clear"""      )  # noqa: S605
  
  
  if __name__ == "__main__":
  	main()