
'''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_     =  "K-MEANS-CLUST/ "

def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE=None    ):
    """simple docstring"""






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

    # Pick K indices from range [0, N).
    snake_case_	:      List[Any]            =  np.random.randint(0,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE    )

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

    return centroids

def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE    ):
    """simple docstring"""






    return pairwise_distances(__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						metric="euclidean"    )

def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE    ):
    """simple docstring"""






    snake_case_	:      Optional[Any]            =  centroid_pairwise_dist(__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE    )

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

    return cluster_assignment

def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE    ):
    """simple docstring"""






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

    return new_centroids

def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE    ):
    """simple docstring"""






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

    return heterogeneity

def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE    ):
    """simple docstring"""






    plt.figure(figsize=(7, 4)    )
    plt.plot(__SCREAMING_SNAKE_CASE,						linewidth=4    )
    plt.xlabel("# Iterations"    )
    plt.ylabel("Heterogeneity"    )
    plt.title(f'Heterogeneity of clustering over time, K={k:d}'    )
    plt.rcParams.update({"font.size": 1_6}    )
    plt.show()

def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE=5_0_0,						__SCREAMING_SNAKE_CASE=None,						__SCREAMING_SNAKE_CASE=False    ):
    """simple docstring"""






    snake_case_	:      Tuple            =  initial_centroids[:]
    snake_case_	:      Tuple            =  None

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

        # 1. Make cluster assignments using nearest centroids
        snake_case_	:      Tuple            =  assign_clusters(__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE    )

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

        # 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:
            snake_case_	:      Optional[Any]            =  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
            snake_case_	:      Tuple            =  compute_heterogeneity(__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE    )
            record_heterogeneity.append(__SCREAMING_SNAKE_CASE    )

        snake_case_	:      Optional[int]            =  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_     =  ds.load_iris()
  a_     =  3
  a_     =  []
  a_     =  get_initial_centroids(dataset["data"], k, seed=0)
  a_   ,a_     =  kmeans(
      dataset["data"],
      k,
      initial_centroids,
      maxiter=400,
      record_heterogeneity=heterogeneity,
      verbose=True,
  )
  plot_heterogeneity(heterogeneity, k)

def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE=None    ):
    """simple docstring"""






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


if __name__ == "__main__":
  import doctest

  doctest.testmod()






'''simple docstring'''
from __future__ import annotations

from collections.abc import Callable

def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE,						__SCREAMING_SNAKE_CASE = 1_0_0,						):
    """simple docstring"""






    snake_case_	:      Optional[Any]            =  x_start
    snake_case_	:      str            =  fnc(__SCREAMING_SNAKE_CASE    )
    snake_case_	:      List[str]            =  0.0

    for _ in range(__SCREAMING_SNAKE_CASE    ):
        # Approximates small segments of curve as linear and solve
        # for trapezoidal area
        snake_case_	:      List[str]            =  (x_end - x_start) / steps + xa
        snake_case_	:      Optional[int]            =  fnc(__SCREAMING_SNAKE_CASE    )
        area += abs(fxa + fxa    ) * (xa - xa) / 2

        # Increment step
        snake_case_	:      List[Any]            =  xa
        snake_case_	:      Optional[Any]            =  fxa
    return area


if __name__ == "__main__":

  def   UpperCamelCase_       (		__SCREAMING_SNAKE_CASE    ):
      """simple docstring"""






      return x**3

  print("f(x) = x^3")
  print("The area between the curve, x = -10, x = 10 and the x axis is:")
  a_     =  10
  while i <= 10_0000:
    a_     =  trapezoidal_area(f, -5, 5, i)
    print(f"""with {i} steps: {area}""")
    i *= 10




