import streamlit as st import pandas as pd import numpy as np from sklearn import datasets from sklearn.cluster import KMeans import matplotlib.pyplot as plt import plotly.express as px import base64 import plotly.figure_factory as ff import plotly.graph_objects as go from scipy.spatial import ConvexHull from scipy.spatial import distance from sklearn.decomposition import PCA st.set_page_config(layout="wide") # JS hack to add a toggle button for the sidebar st.markdown(""" """, unsafe_allow_html=True) # Load iris dataset iris = datasets.load_iris() X = iris.data st.title('Understanding K-Means Clustering') tab1, tab2, about = st.tabs(["Basic ☕", "Advanced 🔬"," ℹ️ About"]) if "toggle" not in st.session_state: st.session_state.toggle = True toggle_button = st.button("Toggle Sidebar") if toggle_button: st.session_state.toggle = not st.session_state.toggle dmojis = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"] # Initialize user_features and n_clusters_advanced outside of any condition user_features = [6.5, 3.5, 4.5, 1.5] n_clusters_advanced = 2 if st.session_state.toggle: # User Input on Sidebar st.sidebar.header('Input Your Flower Data') def user_input_features(): sepal_length = st.sidebar.slider('Sepal Length (cm)', 4.0, 8.0, 6.5) sepal_width = st.sidebar.slider('Sepal Width (cm)', 2.0, 4.5, 3.5) petal_length = st.sidebar.slider('Petal Length (cm)', 1.0, 7.0, 4.5) petal_width = st.sidebar.slider('Petal Width (cm)', 0.1, 2.5, 1.5) return [sepal_length, sepal_width, petal_length, petal_width] user_features = user_input_features() # Update the user_features variable when sliders change # Slider for Advanced in the sidebar st.sidebar.header('K-Means Parameters') n_clusters_advanced = st.sidebar.slider('Number of Clusters (K)', 1, 8, n_clusters_advanced) st.markdown(""" """, unsafe_allow_html=True) with tab1: st.write(""" ### What is Clustering? ##### Clustering with K-Means is a machine learning concept like tidying a messy room by grouping similar items, but for data instead of physical objects. """) # Button to toggle PCA if st.button('Toggle PCA for Visualization'): st.session_state.use_pca = not st.session_state.use_pca # Check if 'use_pca' is already in the session state if 'use_pca' not in st.session_state: st.session_state.use_pca = True if st.session_state.use_pca: st.write(""" ##### 🧠 PCA (Principal Component Analysis) is like looking at a messy room from the best angle to see the most mess. It helps us see our data more clearly! """) # Apply PCA for dimensionality reduction pca = PCA(n_components=2) X_transformed = pca.fit_transform(X) user_features_transformed = pca.transform([user_features])[0] else: X_transformed = X[:, :2] # Just use the first two features for visualization user_features_transformed = user_features[:2] st.write(""" ### Visualizing Groups ##### Here are the groups from our tidying method. Each color has a number at its center, representing its group. """) # Create a DataFrame for easier plotting with plotly df_transformed = pd.DataFrame(X_transformed, columns=['Feature1', 'Feature2']) # K-Means Algorithm kmeans = KMeans(n_clusters=n_clusters_advanced) y_kmeans = kmeans.fit_predict(X_transformed) df_transformed['cluster'] = y_kmeans # Predict the cluster for the user input in the transformed space predicted_cluster = kmeans.predict([user_features_transformed]) # For tab1 fig = go.Figure() # Add shaded regions using convex hull for cluster in np.unique(y_kmeans): cluster_data = df_transformed[df_transformed['cluster'] == cluster] x_data = cluster_data['Feature1'].values y_data = cluster_data['Feature2'].values if len(cluster_data) > 2: # ConvexHull requires at least 3 points hull = ConvexHull(cluster_data[['Feature1', 'Feature2']]) fig.add_trace(go.Scatter(x=x_data[hull.vertices], y=y_data[hull.vertices], fill='toself', fillcolor=px.colors.qualitative.Set1[cluster], opacity=0.5, line=dict(width=0), showlegend=False)) # Add scatter plot based on PCA toggle if st.session_state.use_pca: fig.add_trace(go.Scatter(x=df_transformed['Feature1'], y=df_transformed['Feature2'], mode='markers', marker=dict(color=y_kmeans, colorscale=px.colors.qualitative.Set1), showlegend=False)) else: fig.add_trace(go.Scatter(x=df_transformed['Feature1'], y=df_transformed['Feature2'], mode='markers', marker=dict(color=y_kmeans, colorscale=px.colors.qualitative.Set1, symbol='square'), showlegend=False)) # Add user input as a star marker fig.add_trace(go.Scatter(x=[user_features_transformed[0]], y=[user_features_transformed[1]], mode='markers', marker=dict(symbol='star', size=30, color='white'))) # Add centroids with group numbers for i, coord in enumerate(kmeans.cluster_centers_): fig.add_annotation( x=coord[0], y=coord[1], text=dmojis[i+1], showarrow=True, font=dict(color='white', size=30) ) # Update layout fig.update_layout(width=1200, height=500) st.plotly_chart(fig) # Button to toggle PCA if st.button('Toggle PCA for Visualization',key=125): st.session_state.use_pca = not st.session_state.use_pca if st.session_state.use_pca: st.write(""" ##### 🧠 PCA (Principal Component Analysis) is like looking at a messy room from the best angle to see the most mess. It helps us see our data more clearly! """) st.write(f"##### Overlapping clusters mean some flowers are very similar and hard to tell apart just by looking at these features.") st.write(f"# Based on your flower data (⭐), it likely belongs to **Group {dmojis[predicted_cluster[0]+1]}**") # Closing Note st.write(""" ### Wrap Up ##### Just as sorting toys in a room, we group flowers by features; adjust the data to pick a flower and set how many boxes (groups) you want to use. """) with tab2: st.write(""" ## Advanced Overview of Clustering Clustering, in the context of machine learning, refers to the task of partitioning the dataset into groups, known as clusters. The aim is to segregate groups with similar traits and assign them into clusters. ### K-Means Algorithm The K-Means clustering method is an iterative method that tries to partition the dataset into \(K\) pre-defined distinct non-overlapping subgroups (clusters) where each data point belongs to only one group. Here's a brief rundown: 1. **Initialization**: Choose \(K\) initial centroids. (Centroids is a fancy term for 'the center of the cluster'.) 2. **Assignment**: Assign each data point to the nearest centroid. All the points assigned to a centroid form a cluster. 3. **Update**: Recompute the centroid of each cluster. 4. **Repeat**: Keep repeating steps 2 and 3 until the centroids no longer move too much. """) st.write("The mathematical goal is to minimize the within-cluster sum of squares. The formula is:") st.latex(r''' \mathrm{WCSS} = \sum_{i=1}^{K} \sum_{x \in C_i} \| x - \mu_i \|^2 ''') st.latex(r''' \begin{align*} \text{Where:} \\ & \mathrm{WCSS} \text{ is the within-cluster sum of squares we want to minimize.} \\ & K \text{ is the number of clusters.} \\ & C_i \text{ is the i-th cluster.} \\ & \mu_i \text{ is the centroid of the i-th cluster.} \\ & x \text{ is a data point in cluster } C_i. \end{align*} ''') st.write(""" The K-Means algorithm tries to find the best centroids such that the \( \mathrm{WCSS} \) is minimized. """) # Button to toggle PCA if st.button('Toggle PCA for Visualization', key=12): st.session_state.use_pca = not st.session_state.use_pca # Check if 'use_pca' is already in the session state if 'use_pca' not in st.session_state: st.session_state.use_pca = True if st.session_state.use_pca: st.write(""" ##### 🧠 PCA (Principal Component Analysis) is a mathematical technique that helps us view our data from the best perspective. It identifies the directions (principal components) that maximize variance, allowing us to see patterns and structures more clearly. """) # Apply PCA for dimensionality reduction pca = PCA(n_components=2) X_transformed = pca.fit_transform(X) user_features_transformed = pca.transform([user_features])[0] else: X_transformed = X[:, :2] # Just use the first two features for visualization user_features_transformed = user_features[:2] # K-Means Algorithm for Advanced Tab kmeans_advanced = KMeans(n_clusters=n_clusters_advanced) y_kmeans_advanced = kmeans_advanced.fit_predict(X_transformed) # Create a DataFrame for easier plotting with plotly df_transformed = pd.DataFrame(X_transformed, columns=['Feature1', 'Feature2']) df_transformed['cluster'] = y_kmeans_advanced fig_advanced = go.Figure() # Add shaded regions using convex hull for cluster in np.unique(y_kmeans_advanced): cluster_data = df_transformed[df_transformed['cluster'] == cluster] x_data = cluster_data['Feature1'].values y_data = cluster_data['Feature2'].values if len(cluster_data) > 2: # ConvexHull requires at least 3 points hull = ConvexHull(cluster_data[['Feature1', 'Feature2']]) fig_advanced.add_trace(go.Scatter(x=x_data[hull.vertices], y=y_data[hull.vertices], fill='toself', fillcolor=px.colors.qualitative.Set1[cluster], opacity=0.5, line=dict(width=0), showlegend=False)) # Add scatter plot based on PCA toggle fig_advanced.add_trace(go.Scatter(x=df_transformed['Feature1'], y=df_transformed['Feature2'], mode='markers', marker=dict(color=y_kmeans_advanced, colorscale=px.colors.qualitative.Set1), showlegend=False)) # Add user input as a star marker fig_advanced.add_trace(go.Scatter(x=[user_features_transformed[0]], y=[user_features_transformed[1]], mode='markers', marker=dict(symbol='star', size=30, color='white'))) # Add centroids with group numbers for i, coord in enumerate(kmeans_advanced.cluster_centers_): fig_advanced.add_annotation( x=coord[0], y=coord[1], text=dmojis[i+1], showarrow=True, font=dict(color='white', size=30) ) # Update layout fig_advanced.update_layout(width=1200, height=500) st.plotly_chart(fig_advanced) st.write(""" ### Interpretation The plot displays how data points are grouped into clusters. The big gray X marks represent the center of each cluster, known as centroids. The positioning of these centroids is determined by the mean of all data points in the cluster. Keep in mind that the positioning of these centroids is crucial, as they determine the grouping of data. The algorithm tries to place them in such a way that the distance between the data points and their respective centroid is minimized. **Feel free to adjust the number of clusters to see how data points get re-grouped!** """) with about: st.title("About") st.markdown(""" ## Created by **Mustafa Alhamad**. """) st.markdown('[](https://www.linkedin.com/in/mustafa-al-hamad-975b67213/)', unsafe_allow_html=True) st.markdown('### Made with ', unsafe_allow_html=True) hide_streamlit_style = """ """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)