




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

_lowerCamelCase					:     str					  =  "K-MEANS-CLUST/ "




def 							_UpperCAmelCase     (UpperCamelCase_       :       Optional[int]					,			UpperCamelCase_       :       Any					,			UpperCamelCase_       :       Dict=None				):
				'''simple docstring'''


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

				# Pick K indices from range [0, N).
				_lowerCAmelCase			:			Optional[Any]        =					np.random.randint(0					,			lowercase_					,			lowercase_				)

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

				return centroids




def 							_UpperCAmelCase     (UpperCamelCase_       :       Optional[Any]					,			UpperCamelCase_       :       List[Any]				):
				'''simple docstring'''
				return pairwise_distances(lowercase_					,			lowercase_					,			metric="""euclidean"""				)




def 							_UpperCAmelCase     (UpperCamelCase_       :       Dict					,			UpperCamelCase_       :       List[Any]				):
				'''simple docstring'''
				_lowerCAmelCase			:			List[str]        =					centroid_pairwise_dist(lowercase_					,			lowercase_				)

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

				return cluster_assignment




def 							_UpperCAmelCase     (UpperCamelCase_       :       List[str]					,			UpperCamelCase_       :       str					,			UpperCamelCase_       :       List[str]				):
				'''simple docstring'''
				_lowerCAmelCase			:			List[str]        =					[]
				for i in range(lowercase_				):
								# Select all data points that belong to cluster i. Fill in the blank (RHS only)
								_lowerCAmelCase			:			List[str]        =					data[cluster_assignment == i]
								# Compute the mean of the data points. Fill in the blank (RHS only)
								_lowerCAmelCase			:			int        =					member_data_points.mean(axis=0				)
								new_centroids.append(lowercase_				)
				_lowerCAmelCase			:			Optional[Any]        =					np.array(lowercase_				)

				return new_centroids




def 							_UpperCAmelCase     (UpperCamelCase_       :       Optional[int]					,			UpperCamelCase_       :       Any					,			UpperCamelCase_       :       Tuple					,			UpperCamelCase_       :       int				):
				'''simple docstring'''
				_lowerCAmelCase			:			str        =					0.0
				for i in range(lowercase_				):
								# Select all data points that belong to cluster i. Fill in the blank (RHS only)
								_lowerCAmelCase			:			Union[str, Any]        =					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			:			str        =					pairwise_distances(
												    lowercase_					,			[centroids[i]]					,			metric="""euclidean"""				)
												_lowerCAmelCase			:			str        =					distances**2
												heterogeneity += np.sum(lowercase_				)

				return heterogeneity




def 							_UpperCAmelCase     (UpperCamelCase_       :       Union[str, Any]					,			UpperCamelCase_       :       Optional[int]				):
				'''simple docstring'''
				plt.figure(figsize=(7, 4)				)
				plt.plot(lowercase_					,			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 							_UpperCAmelCase     (UpperCamelCase_       :       List[Any]					,			UpperCamelCase_       :       Tuple					,			UpperCamelCase_       :       Optional[int]					,			UpperCamelCase_       :       int=500					,			UpperCamelCase_       :       Union[str, Any]=None					,			UpperCamelCase_       :       str=False				):
				'''simple docstring'''
				_lowerCAmelCase			:			List[str]        =					initial_centroids[:]
				_lowerCAmelCase			:			List[str]        =					None

				for itr in range(lowercase_				):
								if verbose:
												print(lowercase_					,			end=""""""				)

								# 1. Make cluster assignments using nearest centroids
								_lowerCAmelCase			:			Any        =					assign_clusters(lowercase_					,			lowercase_				)

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

								# 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			:			List[str]        =					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			:			List[Any]        =					compute_heterogeneity(lowercase_					,			lowercase_					,			lowercase_					,			lowercase_				)
												record_heterogeneity.append(lowercase_				)

								_lowerCAmelCase			:			Any        =					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

	_lowerCamelCase					:     Optional[Any]					  =  ds.load_iris()
	_lowerCamelCase					:     Optional[Any]					  =  3
	_lowerCamelCase					:     Tuple					  =  []
	_lowerCamelCase					:     List[str]					  =  get_initial_centroids(dataset["data"], k, seed=0)
	_lowerCamelCase  ,       _lowerCamelCase					:     int					  =  kmeans(
	    dataset["data"],
	    k,
	    initial_centroids,
	    maxiter=4_0_0,
	    record_heterogeneity=heterogeneity,
	    verbose=True,
	)
	plot_heterogeneity(heterogeneity, k)




def 							_UpperCAmelCase     (UpperCamelCase_       :       List[Any]					,			UpperCamelCase_       :       str					,			UpperCamelCase_       :       Optional[Any]=None				):
				'''simple docstring'''


				if fill_missing_report:
								_lowerCAmelCase			:			Union[str, Any]        =					df.fillna(value=lowercase_				)
				_lowerCAmelCase			:			str        =					1
				_lowerCAmelCase			:			Any        =					df.select_dtypes(np.number				).columns
				_lowerCAmelCase			:			str        =					(
				    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(lowercase_				)				)),
				            ("""mean_without_zeros""", lambda UpperCamelCase_				: x.replace(0					,			np.NaN				).mean()),
				            (
				                """mean_25-75""",
				                lambda UpperCamelCase_				: np.mean(
				                    np.nan_to_num(
				                        sorted(lowercase_				)[
				                            round(len(lowercase_				) * 25 / 100				) : round(len(lowercase_				) * 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=lowercase_					,			columns={"""level_0""": """Features""", """level_1""": """Type"""}				)
				)  # rename columns
				# calculate the size of cluster(count of clientID's)
				_lowerCAmelCase			:			Dict        =					report[
				    (report["""Features"""] == """dummy""") & (report["""Type"""] == """count""")
				].copy()  # avoid SettingWithCopyWarning
				_lowerCAmelCase			:			Optional[Any]        =					(
				    """ClusterSize"""  # rename created cluster df to match report column names
				)
				_lowerCAmelCase			:			Tuple        =					"""# of Customers"""
				_lowerCAmelCase			:			Optional[int]        =					pd.DataFrame(
				    clustersize.iloc[:, 2:].values
				    / clustersize.iloc[:, 2:].values.sum()				)
				_lowerCAmelCase			:			Tuple        =					"""% of Customers"""  # rename created cluster df to match report column names
				_lowerCAmelCase			:			Any        =					"""ClusterProportion"""
				_lowerCAmelCase			:			Optional[int]        =					clusterproportion.columns.tolist()
				_lowerCAmelCase			:			Optional[Any]        =					cols[-2:] + cols[:-2]
				_lowerCAmelCase			:			List[str]        =					clusterproportion[cols]  # rearrange columns to match report
				_lowerCAmelCase			:			Union[str, Any]        =					report.columns
				_lowerCAmelCase			:			str        =					pd.DataFrame(
				    abs(
				        report[report["""Type"""] == """count"""].iloc[:, 2:].values
				        - clustersize.iloc[:, 2:].values				)				)  # generating df with count of nan values
				_lowerCAmelCase			:			Optional[Any]        =					0
				_lowerCAmelCase			:			Dict        =					"""# of nan"""
				_lowerCAmelCase			:			List[Any]        =					report[
				    report["""Type"""] == """count"""
				].Features.tolist()  # filling values in order to match report
				_lowerCAmelCase			:			int        =					a.columns.tolist()
				_lowerCAmelCase			:			Union[str, Any]        =					cols[-2:] + cols[:-2]
				_lowerCAmelCase			:			Optional[int]        =					a[cols]  # rearrange columns to match report
				_lowerCAmelCase			:			Dict        =					report.columns  # rename columns to match report
				_lowerCAmelCase			:			Optional[Any]        =					report.drop(
				    report[report.Type == """count"""].index				)  # drop count values except cluster size
				_lowerCAmelCase			:			Optional[Any]        =					pd.concat(
				    [report, a, clustersize, clusterproportion]					,			axis=0				)  # concat report with clustert size and nan values
				_lowerCAmelCase			:			int        =					report["""Features"""].isin(lowercase_				)
				_lowerCAmelCase			:			int        =					report.columns.tolist()
				_lowerCAmelCase			:			Dict        =					cols[0:2] + cols[-1:] + cols[2:-1]
				_lowerCAmelCase			:			Optional[Any]        =					report[cols]
				_lowerCAmelCase			:			int        =					{
				    """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			:			Optional[Any]        =					(
				    report.assign(
				        Sortera=lambda UpperCamelCase_				: x.Type.map(lowercase_				)					,			Sortera=lambda UpperCamelCase_				: list(reversed(range(len(lowercase_				)				)				)				)					,			)
				    .sort_values(["""Sorter1""", """Mark""", """Sorter2"""]					,			ascending=lowercase_				)
				    .drop(["""Sorter1""", """Sorter2"""]					,			axis=1				)
				)
				_lowerCAmelCase			:			Optional[Any]        =					""""""
				_lowerCAmelCase			:			Optional[Any]        =					report.reset_index()
				_lowerCAmelCase			:			List[str]        =					report.drop(columns=["""index"""]				)
				return report


if __name__ == "__main__":
	import doctest

	doctest.testmod()







import random

class        _lowerCamelCase  :

      """simple docstring"""

      def __init__(   self    :       Any       ,				snake_case    :       float       ,				snake_case    :       float ):
          __UpperCamelCase     						=  x
          __UpperCamelCase     						=  y







      def 					snake_case		(   self    :       List[Any] ):
          return (self.x**2 + self.y**2) <= 1







      @classmethod
      def 					snake_case		(   cls    :       List[str] ):
          return cls(x=random.random()       ,				y=random.random() )







def     __SCREAMING_SNAKE_CASE      (     lowercase_					)				->		float:

    """simple docstring"""


    if number_of_simulations < 1:
        raise ValueError('''At least one simulation is necessary to estimate PI.'''					)

    __UpperCamelCase     						=  0
    for _ in range(lowercase_					):
        __UpperCamelCase     						=  Point.random_unit_square()

        if random_point.is_in_unit_circle():
            number_in_unit_circle += 1

    return 4 * number_in_unit_circle / number_of_simulations


if __name__ == "__main__":
 # import doctest

 # doctest.testmod()
 from math import pi

 a_      						=   "Please enter the desired number of Monte Carlo simulations: "
 a_      						=   estimate_pi(int(input(prompt).strip()))
 print(f"""An estimate of PI is {my_pi} with an error of {abs(my_pi - pi)}""")






