



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""")

UpperCAmelCase								=   """K-MEANS-CLUST/ """

def      A_ (		__a       :			Dict	,						__a       :			int	,						__a       :			Union[str, Any]=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       :			Optional[Any]	,						__a       :			Optional[int]						):

				"""simple docstring"""
				return pairwise_distances(__a	,						__a	,						metric="""euclidean"""						)

def      A_ (		__a       :			Dict	,						__a       :			Any						):

				"""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       :			List[Any]	,						__a       :			Tuple	,						__a       :			List[str]						):

				"""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       :			List[str]	,						__a       :			Union[str, Any]	,						__a       :			List[Any]	,						__a       :			int						):

				"""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       :			int	,						__a       :			Dict						):

				"""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""": 16}						)
				plt.show()

def      A_ (		__a       :			List[str]	,						__a       :			Any	,						__a       :			str	,						__a       :			Any=500	,						__a       :			List[str]=None	,						__a       :			List[Any]=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

	UpperCAmelCase								=   ds.load_iris()
	UpperCAmelCase								=   3
	UpperCAmelCase								=   []
	UpperCAmelCase								=   get_initial_centroids(dataset["""data"""], k, seed=0)
	UpperCAmelCase	,    UpperCAmelCase								=   kmeans(
	    dataset["""data"""],
	    k,
	    initial_centroids,
	    maxiter=400,
	    record_heterogeneity=heterogeneity,
	    verbose=True,
	)
	plot_heterogeneity(heterogeneity, k)

def      A_ (		__a       :			pd.DataFrame	,						__a       :			np.ndarray	,						__a       :			List[str]=None						):

				"""simple docstring"""
				# Fill missing values with given rules
				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						) * 25 / 100						) : round(len(__a						) * 75 / 100						)
				                        ]						)						),
				            ),
				            ("""mean_with_na""", np.mean),
				            ("""min""", lambda __a						: x.min()),
				            ("""5%""", lambda __a						: x.quantile(0.0_5						)),
				            ("""25%""", lambda __a						: x.quantile(0.2_5						)),
				            ("""50%""", lambda __a						: x.quantile(0.5_0						)),
				            ("""75%""", lambda __a						: x.quantile(0.7_5						)),
				            ("""95%""", lambda __a						: x.quantile(0.9_5						)),
				            ("""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()






def      A_ (		__a       :			float						):

				"""simple docstring"""
				return int(__a						) if x - int(__a						) >= 0 else int(__a						) - 1


if __name__ == "__main__":
	import doctest

	doctest.testmod()

