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

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




def __magic_name__  (							_lowerCamelCase:		str,				_lowerCamelCase:		Optional[int],				_lowerCamelCase:		Optional[Any]=None )				->	List[str]:

	'''simple docstring'''




	if seed is not None:  # useful for obtaining consistent results
		np.random.seed(_lowerCamelCase )
	lowerCAmelCase				       =     data.shape[0]  # number of data points

	# Pick K indices from range [0, N).
	lowerCAmelCase				       =     np.random.randint(0,				_lowerCamelCase,				_lowerCamelCase )

	# 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.
	lowerCAmelCase				       =     data[rand_indices, :]

	return centroids




def __magic_name__  (							_lowerCamelCase:		List[str],				_lowerCamelCase:		Any )				->	int:

	'''simple docstring'''




	return pairwise_distances(_lowerCamelCase,				_lowerCamelCase,				metric='''euclidean''' )




def __magic_name__  (							_lowerCamelCase:		Any,				_lowerCamelCase:		Optional[int] )				->	Dict:

	'''simple docstring'''




	lowerCAmelCase				       =     centroid_pairwise_dist(_lowerCamelCase,				_lowerCamelCase )

	# Compute cluster assignments for each data point:
	# Fill in the blank (RHS only)
	lowerCAmelCase				       =     np.argmin(_lowerCamelCase,				axis=1 )

	return cluster_assignment




def __magic_name__  (							_lowerCamelCase:		Optional[int],				_lowerCamelCase:		List[str],				_lowerCamelCase:		int )				->	Union[str, Any]:

	'''simple docstring'''




	lowerCAmelCase				       =     []
	for i in range(_lowerCamelCase ):
		# Select all data points that belong to cluster i. Fill in the blank (RHS only)
		lowerCAmelCase				       =     data[cluster_assignment == i]
		# Compute the mean of the data points. Fill in the blank (RHS only)
		lowerCAmelCase				       =     member_data_points.mean(axis=0 )
		new_centroids.append(_lowerCamelCase )
	lowerCAmelCase				       =     np.array(_lowerCamelCase )

	return new_centroids




def __magic_name__  (							_lowerCamelCase:		List[str],				_lowerCamelCase:		Tuple,				_lowerCamelCase:		List[Any],				_lowerCamelCase:		List[Any] )				->	str:

	'''simple docstring'''




	lowerCAmelCase				       =     0.0
	for i in range(_lowerCamelCase ):
		# Select all data points that belong to cluster i. Fill in the blank (RHS only)
		lowerCAmelCase				       =     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)
			lowerCAmelCase				       =     pairwise_distances(
			    _lowerCamelCase,				[centroids[i]],				metric='''euclidean''' )
			lowerCAmelCase				       =     distances**2
			heterogeneity += np.sum(_lowerCamelCase )

	return heterogeneity




def __magic_name__  (							_lowerCamelCase:		List[str],				_lowerCamelCase:		List[Any] )				->	Dict:

	'''simple docstring'''




	plt.figure(figsize=(7, 4) )
	plt.plot(_lowerCamelCase,				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 __magic_name__  (							_lowerCamelCase:		Union[str, Any],				_lowerCamelCase:		List[str],				_lowerCamelCase:		List[str],				_lowerCamelCase:		Dict=500,				_lowerCamelCase:		List[Any]=None,				_lowerCamelCase:		Optional[Any]=False )				->	Dict:

	'''simple docstring'''




	lowerCAmelCase				       =     initial_centroids[:]
	lowerCAmelCase				       =     None

	for itr in range(_lowerCamelCase ):
		if verbose:
			print(_lowerCamelCase,				end='''''' )

		# 1. Make cluster assignments using nearest centroids
		lowerCAmelCase				       =     assign_clusters(_lowerCamelCase,				_lowerCamelCase )

		# 2. Compute a new centroid for each of the k clusters, averaging all data
		#    points assigned to that cluster.
		lowerCAmelCase				       =     revise_centroids(_lowerCamelCase,				_lowerCamelCase,				_lowerCamelCase )

		# 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:
			lowerCAmelCase				       =     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
			lowerCAmelCase				       =     compute_heterogeneity(_lowerCamelCase,				_lowerCamelCase,				_lowerCamelCase,				_lowerCamelCase )
			record_heterogeneity.append(_lowerCamelCase )

		lowerCAmelCase				       =     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=4_0_0,
					    record_heterogeneity=heterogeneity,
					    verbose=True,
					)
					plot_heterogeneity(heterogeneity, k)




def __magic_name__  (							_lowerCamelCase:		pd.DataFrame,				_lowerCamelCase:		np.ndarray,				_lowerCamelCase:		Tuple=None )				->	pd.DataFrame:

	'''simple docstring'''




	if fill_missing_report:
		lowerCAmelCase				       =     df.fillna(value=_lowerCamelCase )
	lowerCAmelCase				       =     1
	lowerCAmelCase				       =     df.select_dtypes(np.number ).columns
	lowerCAmelCase				       =     (
	    df.groupby(['''Cluster'''] )[  # construct report dataframe
	        numeric_cols
	    ]  # group by cluster number
	    .agg(
	        [
	            ('''sum''', np.sum),
	            ('''mean_with_zeros''', lambda _lowerCamelCase : np.mean(np.nan_to_num(_lowerCamelCase ) )),
	            ('''mean_without_zeros''', lambda _lowerCamelCase : x.replace(0,				np.NaN ).mean()),
	            (
	                '''mean_25-75''',
	                lambda _lowerCamelCase : np.mean(
	                    np.nan_to_num(
	                        sorted(_lowerCamelCase )[
	                            round(len(_lowerCamelCase ) * 25 / 100 ) : round(len(_lowerCamelCase ) * 75 / 100 )
	                        ] ) ),
	            ),
	            ('''mean_with_na''', np.mean),
	            ('''min''', lambda _lowerCamelCase : x.min()),
	            ('''5%''', lambda _lowerCamelCase : x.quantile(0.05 )),
	            ('''25%''', lambda _lowerCamelCase : x.quantile(0.25 )),
	            ('''50%''', lambda _lowerCamelCase : x.quantile(0.50 )),
	            ('''75%''', lambda _lowerCamelCase : x.quantile(0.75 )),
	            ('''95%''', lambda _lowerCamelCase : x.quantile(0.95 )),
	            ('''max''', lambda _lowerCamelCase : x.max()),
	            ('''count''', lambda _lowerCamelCase : x.count()),
	            ('''stdev''', lambda _lowerCamelCase : x.std()),
	            ('''mode''', lambda _lowerCamelCase : x.mode()[0]),
	            ('''median''', lambda _lowerCamelCase : x.median()),
	            ('''# > 0''', lambda _lowerCamelCase : (x > 0).sum()),
	        ] )
	    .T.reset_index()
	    .rename(index=_lowerCamelCase,				columns={'''level_0''': '''Features''', '''level_1''': '''Type'''} )
	)  # rename columns
	# calculate the size of cluster(count of clientID's)
	lowerCAmelCase				       =     report[
	    (report['''Features'''] == '''dummy''') & (report['''Type'''] == '''count''')
	].copy()  # avoid SettingWithCopyWarning
	lowerCAmelCase				       =     (
	    '''ClusterSize'''  # rename created cluster df to match report column names
	)
	lowerCAmelCase				       =     '''# of Customers'''
	lowerCAmelCase				       =     pd.DataFrame(
	    clustersize.iloc[:, 2:].values
	    / clustersize.iloc[:, 2:].values.sum() )
	lowerCAmelCase				       =     '''% of Customers'''  # rename created cluster df to match report column names
	lowerCAmelCase				       =     '''ClusterProportion'''
	lowerCAmelCase				       =     clusterproportion.columns.tolist()
	lowerCAmelCase				       =     cols[-2:] + cols[:-2]
	lowerCAmelCase				       =     clusterproportion[cols]  # rearrange columns to match report
	lowerCAmelCase				       =     report.columns
	lowerCAmelCase				       =     pd.DataFrame(
	    abs(
	        report[report['''Type'''] == '''count'''].iloc[:, 2:].values
	        - clustersize.iloc[:, 2:].values ) )  # generating df with count of nan values
	lowerCAmelCase				       =     0
	lowerCAmelCase				       =     '''# of nan'''
	lowerCAmelCase				       =     report[
	    report['''Type'''] == '''count'''
	].Features.tolist()  # filling values in order to match report
	lowerCAmelCase				       =     a.columns.tolist()
	lowerCAmelCase				       =     cols[-2:] + cols[:-2]
	lowerCAmelCase				       =     a[cols]  # rearrange columns to match report
	lowerCAmelCase				       =     report.columns  # rename columns to match report
	lowerCAmelCase				       =     report.drop(
	    report[report.Type == '''count'''].index )  # drop count values except cluster size
	lowerCAmelCase				       =     pd.concat(
	    [report, a, clustersize, clusterproportion],				axis=0 )  # concat report with clustert size and nan values
	lowerCAmelCase				       =     report['''Features'''].isin(_lowerCamelCase )
	lowerCAmelCase				       =     report.columns.tolist()
	lowerCAmelCase				       =     cols[0:2] + cols[-1:] + cols[2:-1]
	lowerCAmelCase				       =     report[cols]
	lowerCAmelCase				       =     {
	    '''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,
	}
	lowerCAmelCase				       =     (
	    report.assign(
	        Sortera=lambda _lowerCamelCase : x.Type.map(_lowerCamelCase ),				Sortera=lambda _lowerCamelCase : list(reversed(range(len(_lowerCamelCase ) ) ) ),				)
	    .sort_values(['''Sorter1''', '''Mark''', '''Sorter2'''],				ascending=_lowerCamelCase )
	    .drop(['''Sorter1''', '''Sorter2'''],				axis=1 )
	)
	lowerCAmelCase				       =     ''''''
	lowerCAmelCase				       =     report.reset_index()
	lowerCAmelCase				       =     report.drop(columns=['''index'''] )
	return report


if __name__ == "__main__":
					import doctest

					doctest.testmod()




"""simple docstring"""






def __magic_name__  (							_lowerCamelCase:		int,				_lowerCamelCase:		int )				->	int:

	'''simple docstring'''




	lowerCAmelCase				       =     1.0
	for i in range(1,				r + 1 ):
		ret *= (n + 1 - i) / i
	return round(_lowerCamelCase )




def __magic_name__  (							_lowerCamelCase:		int )				->	int:

	'''simple docstring'''




	return choose(8 + n,				_lowerCamelCase ) + choose(9 + n,				_lowerCamelCase ) - 10




def __magic_name__  (							_lowerCamelCase:		int )				->	int:

	'''simple docstring'''




	return sum(non_bouncy_exact(_lowerCamelCase ) for i in range(1,				n + 1 ) )




def __magic_name__  (							_lowerCamelCase:		int = 100 )				->	int:

	'''simple docstring'''




	return non_bouncy_upto(_lowerCamelCase )


if __name__ == "__main__":
					print(f"""{solution() = }""")


