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

_a			:	str								=  '''K-MEANS-CLUST/ '''


def  lowerCamelCase__						(  SCREAMING_SNAKE_CASE :				Tuple    ,				SCREAMING_SNAKE_CASE :				Union[str, Any]    ,				SCREAMING_SNAKE_CASE :				Optional[int]=None  ):
  if seed is not None:  # useful for obtaining consistent results
    np.random.seed(A__  )
  UpperCAmelCase  							=		data.shape[0]  # number of data points

  # Pick K indices from range [0, N).
  UpperCAmelCase  							=		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.
  UpperCAmelCase  							=		data[rand_indices, :]

  return centroids


def  lowerCamelCase__						(  SCREAMING_SNAKE_CASE :				Dict    ,				SCREAMING_SNAKE_CASE :				Union[str, Any]  ):
  return pairwise_distances(A__    ,				A__    ,				metric='euclidean'  )


def  lowerCamelCase__						(  SCREAMING_SNAKE_CASE :				List[Any]    ,				SCREAMING_SNAKE_CASE :				Any  ):
  # Compute distances between each data point and the set of centroids:
  # Fill in the blank (RHS only)
  UpperCAmelCase  							=		centroid_pairwise_dist(A__    ,				A__  )

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

  return cluster_assignment


def  lowerCamelCase__						(  SCREAMING_SNAKE_CASE :				Optional[Any]    ,				SCREAMING_SNAKE_CASE :				List[str]    ,				SCREAMING_SNAKE_CASE :				Any  ):
  UpperCAmelCase  							=		[]
  for i in range(A__  ):
    # Select all data points that belong to cluster i. Fill in the blank (RHS only)
    UpperCAmelCase  							=		data[cluster_assignment == i]
    # Compute the mean of the data points. Fill in the blank (RHS only)
    UpperCAmelCase  							=		member_data_points.mean(axis=0  )
    new_centroids.append(A__  )
  UpperCAmelCase  							=		np.array(A__  )

  return new_centroids


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

  return heterogeneity


def  lowerCamelCase__						(  SCREAMING_SNAKE_CASE :				Optional[Any]    ,				SCREAMING_SNAKE_CASE :				Union[str, Any]  ):
  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  lowerCamelCase__						(  SCREAMING_SNAKE_CASE :				Any    ,				SCREAMING_SNAKE_CASE :				List[str]    ,				SCREAMING_SNAKE_CASE :				Union[str, Any]    ,				SCREAMING_SNAKE_CASE :				str=500    ,				SCREAMING_SNAKE_CASE :				Dict=None    ,				SCREAMING_SNAKE_CASE :				Tuple=False  ):
  UpperCAmelCase  							=		initial_centroids[:]
  UpperCAmelCase  							=		None

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

    # 1. Make cluster assignments using nearest centroids
    UpperCAmelCase  							=		assign_clusters(A__    ,				A__  )

    # 2. Compute a new centroid for each of the k clusters, averaging all data
    #    points assigned to that cluster.
    UpperCAmelCase  							=		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:
      UpperCAmelCase  							=		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
      UpperCAmelCase  							=		compute_heterogeneity(A__    ,				A__    ,				A__    ,				A__  )
      record_heterogeneity.append(A__  )

    UpperCAmelCase  							=		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			:	Union[str, Any]								=  ds.load_iris()
  _a			:	Tuple								=  3
  _a			:	Optional[int]								=  []
  _a			:	Optional[int]								=  get_initial_centroids(dataset['data'], k, seed=0)
  _a			:	Union[str, Any]								=  kmeans(
      dataset['data'],
      k,
      initial_centroids,
      maxiter=400,
      record_heterogeneity=heterogeneity,
      verbose=True,
  )
  plot_heterogeneity(heterogeneity, k)


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


if __name__ == "__main__":
  import doctest

  doctest.testmod()





'''simple docstring'''




def  lowerCamelCase__						(  SCREAMING_SNAKE_CASE :				str  ):
  UpperCAmelCase  							=		str(SCREAMING_SNAKE_CASE  ).strip()
  if not bin_string:
    raise ValueError('Empty string was passed to the function'  )
  UpperCAmelCase  							=		bin_string[0] == '-'
  if is_negative:
    UpperCAmelCase  							=		bin_string[1:]
  if not all(char in '01' for char in bin_string  ):
    raise ValueError('Non-binary value was passed to the function'  )
  UpperCAmelCase  							=		0
  for char in bin_string:
    UpperCAmelCase  							=		2 * decimal_number + int(SCREAMING_SNAKE_CASE  )
  return -decimal_number if is_negative else decimal_number


if __name__ == "__main__":
  from doctest import testmod

  testmod()


