{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "<p align=\"center\">\n",
    "    <img src=\"https://github.com/GeostatsGuy/GeostatsPy/blob/master/TCG_color_logo.png?raw=true\" width=\"220\" height=\"240\" />\n",
    "\n",
    "</p>\n",
    "\n",
    "## Subsurface Data Analytics \n",
    "\n",
    "## Interactive Spectral Clustering\n",
    "\n",
    "\n",
    "### Michael Pyrcz, Associate Professor, University of Texas at Austin \n",
    "\n",
    "#### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)\n",
    "\n",
    "### Interactive Spectral Clustering in Python \n",
    "\n",
    "Here's a simple workflow, demonstration of spectral declustering (for automated category assignment) for subsurface modeling workflows. This should provide you experiential learning to help you understand the spectral approach.\n",
    "\n",
    "####  Spectral Clustering\n",
    "\n",
    "Spectral clustering is based on graph theory, where the sample data are mapped to a $n \\times n$ array that describes the degree of pairwise 'connectivity' between the sample data, and then Eigen vectors are calculated to reduce the dimensionality and k-means clustering is applied on the retained Eigen vectors to find the clusters.\n",
    "\n",
    "The advantages of the method includes:\n",
    "\n",
    "* the ability to encode connectivity between the samples in the dataset. While kernel and nearest neightbour approaches are common to determine connectivity, this step could include integration of various information sources.  \n",
    "\n",
    "* the Eigen values provide useful diagnostic information to inform the number of natural clusters based on the degree of cutting of connection required to separate the dataset\n",
    "\n",
    "* the Eigen vectors provide reduced dimensional representations of the high dimensional representation of pairwise connection\n",
    "\n",
    "**Other Resources** - I found the short article with demonstration on [Spectral Clustering](https://towardsdatascience.com/spectral-clustering-aba2640c0d5b) by William Fleshman quite useful to understand this approach. \n",
    "\n",
    "These are the steps for spectral clustering:\n",
    "\n",
    "* **Calculate the Simularity Graph and Matrix**\n",
    "\n",
    "* **Calculate the Degree Matrix**\n",
    "\n",
    "* **Calculate the Graph Laplacian Matrix**\n",
    "\n",
    "* **Perform Dimensionality Reduction on the Graph Laplacian Matrix**\n",
    "\n",
    "* **Assign Clusters in the Reduced Dimensionality**\n",
    "\n",
    "##### Calculate the Simularity Graph and Matrix\n",
    "\n",
    "Spectral clustering first calculates a graph of the data in predictor space\n",
    "\n",
    "* a graph is comprised of the samples (nodes) and connections (edges/vertices) between the samples\n",
    "\n",
    "There are mulitple methods to represent the sample data graph in predictor feature space:\n",
    "\n",
    "* **affinity matrix** the connections are represented as an $n \\times n$ matrix with 0 if no connection and a measure of similarity for connected samples. This is accomplished with radial basis function kernel with a specified gamma, or other kernels such as polynomial.\n",
    "\n",
    "* **adjacency matrix** the connections are represented as a $n \\times n$ matrix with 0 if no connection and 1 if connected.  This matrix may be calculated with k-nearest neighbor (up to a determined maximum number of neighbors), epsilon-neighborhood (all points withing a radius) or radial basis function kernel with a specified gamma, or other kernels such as polynomial. \n",
    "\n",
    "##### Calculate the Degree Matrix\n",
    "\n",
    "The degree matrix is a diagonal matrix where elements $[i,i]$ are the summation of the adjacency matrix row $i$.\n",
    "\n",
    "##### Calculate the Graph Laplacian Matrix \n",
    "\n",
    "The graph Laplacian is the adjacency matrix subtracted from the degree matrix.\n",
    "\n",
    "##### Perform Dimensionality Reduction on the Graph Laplacian Matrix\n",
    "\n",
    "Calculate the Eigen Values and Eigen Vectors of the Graph Laplacian matrix. The result is $n$ Eigen vectors (of length $n$) and $n$ Eigen values.  We can interprete the Eigen values / vectors as follows:\n",
    "\n",
    "* the number of zero Eigen values are the number of connected parts of the dataset.  For example, when the simularity graph indicates 0 similarity for all sample data to sample data combinations, all Eigen values are 0, all sample data are independent connected parts. If all data are connected, then all Eigen values except for the first are nonzero, indicating all sample data in one connected part.\n",
    "\n",
    "* the first nonzero Eigen value is the **spectral gap**. It provides an indication of the density of the sample to sample connections. For example, if the affinity is 1.0 for all sample data pairs, the spectral gap is equal to $n$.\n",
    "\n",
    "* the second Eigen value is the **Fiedler value** and the associated Eigen vector is the **Fiedler vector**. It indicates the level of graph cut to separate the sample dataset into 2 parts. For example, if the second Eigen value is 0 already, then the dataset is already separated into atleast 2 parts; therefore, no cuts are needed!\n",
    "\n",
    "##### Assign Clusters in the Reduced Dimensionality\n",
    "\n",
    "The final step is to assign the clusters based on the result from the dimensionality reduced graph Laplacian matrix.  There are a couple of methods possible demonstrated here:\n",
    "\n",
    "1. We can use the Eigen values to determine the number of clusters:\n",
    "\n",
    "* jumps in Eigen values are used to identify the natural number of clusters in the dataset. i.e. if we add another connected group we would have to do a lot more cuts!\n",
    "\n",
    "2. Assignment of clusters by Eigen vector values:\n",
    "\n",
    "* the sign of the Fiedler vector provides the 2 custers (positive and negative) if the dataset where cut into 2 groups.\n",
    "\n",
    "3. Assignment of clusters by k-means clustering applied to the Eigen vectors \n",
    "\n",
    "* we apply k-means clustering on the Eigen vectors up to the determined number of clusters. This finds the groups asessed while integrating pairwise connections of the sample data.\n",
    "\n",
    "#### Objective \n",
    "\n",
    "In the PGE 383: Subsurface Machine Learning class I want to provide hands-on experience with building subsurface modeling workflows. Python provides an excellent vehicle to accomplish this. I have coded a package called GeostatsPy with GSLIB: Geostatistical Library (Deutsch and Journel, 1998) functionality that provides basic building blocks for building subsurface modeling workflows. \n",
    "\n",
    "The objective is to remove the hurdles of subsurface modeling workflow construction by providing building blocks and sufficient examples. This is not a coding class per se, but we need the ability to 'script' workflows working with numerical methods.    \n",
    "\n",
    "#### Getting Started\n",
    "\n",
    "Here's the steps to get setup in Python with the GeostatsPy package:\n",
    "\n",
    "1. Install Anaconda 3 on your machine (https://www.anaconda.com/download/). \n",
    "2. From Anaconda Navigator (within Anaconda3 group), go to the environment tab, click on base (root) green arrow and open a terminal. \n",
    "3. In the terminal type: pip install geostatspy. \n",
    "4. Open Jupyter and in the top block get started by copy and pasting the code block below from this Jupyter Notebook to start using the geostatspy functionality. \n",
    "\n",
    "You will need to copy the data file to your working directory.  They are available here:\n",
    "\n",
    "* Tabular data - unconv_MV.csv at https://git.io/fjmBH.\n",
    "\n",
    "There are exampled below with these functions. You can go here to see a list of the available functions, https://git.io/fh4eX, other example workflows and source code. \n",
    "\n",
    "#### Install Packages\n",
    "\n",
    "We will include the standard packages for DataFrames and ndarrays and add scikit-learn (sklearn) for machine learning and ipywidgets for interactivity.\n",
    "\n",
    "* These should have been installed with Anaconda 3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np                        # ndarrys for gridded data\n",
    "import pandas as pd                       # DataFrames for tabular data\n",
    "import os                                 # set working directory, run executables\n",
    "import matplotlib.pyplot as plt           # for plotting\n",
    "from matplotlib.colors import ListedColormap # custom color bar\n",
    "import matplotlib as mpl\n",
    "import copy                               # for deep copies\n",
    "import math                               # for square root function\n",
    "from sklearn.neighbors import NearestNeighbors # nearest neighbours function to calculate eps hyperparameter\n",
    "from sklearn.preprocessing import MinMaxScaler # min/max normalization\n",
    "from sklearn.cluster import KMeans        # k-means clustering\n",
    "from sklearn.cluster import DBSCAN        # DBSCAN clustering\n",
    "from sklearn.cluster import SpectralClustering # spectral clustering\n",
    "from ipywidgets import interactive        # widgets and interactivity\n",
    "from ipywidgets import widgets                            \n",
    "from ipywidgets import Layout\n",
    "from ipywidgets import Label\n",
    "from ipywidgets import VBox, HBox\n",
    "cmap = plt.cm.inferno                     # color map with tone and intensity change\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')         # supress warnings\n",
    "plt.rc('axes', axisbelow=True)            # grids behind plot elements"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Declare Functions\n",
    "\n",
    "None needed for this workflow.\n",
    "\n",
    "#### Set the working directory\n",
    "\n",
    "I always like to do this so I don't lose files and to simplify subsequent read and writes (avoid including the full address each time). \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#os.chdir(\"d:/PGE383\")                     # set the working directory with the input data file"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Specifying Data\n",
    "\n",
    "Let's specify a small dataset with arbitrary $X_1$ and $X_2$ features. \n",
    "\n",
    "* we can easily edit the data to explore the behavoir of spectral clustering\n",
    "\n",
    "* the can run the approach fast enough to itneractive with the system in realtime\n",
    "\n",
    "We will also calculate the:\n",
    "\n",
    "* **adjacency matrix** - we will asumme a radius to efficiently find the linkages\n",
    "\n",
    "* **degree matrix** - the sum of connections for each sample data as a diagnonal matrix\n",
    "\n",
    "* **graph Laplacian matrix** - the adjacency matrix subtracted from the degree matrix  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 89412 characters]",
      "text/plain": [
       "<Figure size 640x480 with 7 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# specify the sample data\n",
    "X1 = [100,150,170,150,270,360,440,470]; X2 = [400,320,200,90,420,350,420,270] # made up small dataset, 2 paired features\n",
    "\n",
    "radius = 150                              # specify the feature distance for pairwise connections\n",
    "\n",
    "adjacency = np.zeros([len(X1),len(X1)])   # declare the 2D matrices\n",
    "degree = np.zeros([len(X1),len(X2)])\n",
    "\n",
    "# populate the adjacency matrix\n",
    "for j in range(0,len(X1)):\n",
    "    for i in range(0,len(X1)):\n",
    "        if i != j:\n",
    "            distance = math.sqrt((X1[i]-X1[j])*(X1[i]-X1[j]) + (X2[i]-X2[j])*(X2[i]-X2[j]))\n",
    "            if distance <= radius:\n",
    "                adjacency[i,j] = 1; adjacency[j,i] = 1 \n",
    "\n",
    "# plot the adjacency matrix\n",
    "plt.subplot(222)  \n",
    "plt.imshow(adjacency,cmap = plt.cm.binary); plt.grid(alpha = 0.2)\n",
    "plt.xlim([-0.5,len(X1)-1+0.5]); plt.ylim([-0.5,len(X2)-1+0.5]);\n",
    "plt.title('Adjacency Matrix'); plt.xlabel('$X_1$'); plt.ylabel('$X_2$');\n",
    "plt.colorbar()\n",
    "\n",
    "# populate the degree matrix\n",
    "for j in range(0,len(X1)):\n",
    "    summation = 0\n",
    "    for i in range(0,len(X1)):\n",
    "        summation = summation + adjacency[i,j]\n",
    "    degree[j,j] = summation\n",
    "    \n",
    "# plot the degree matrix\n",
    "plt.subplot(223)  \n",
    "plt.imshow(degree,cmap = cmap); plt.grid(alpha = 0.2)\n",
    "plt.xlim([-0.5,len(X1)-1+0.5]); plt.ylim([-0.5,len(X2)-1+0.5]);\n",
    "plt.title('Degree Matrix'); plt.xlabel('$X_1$'); plt.ylabel('$X_2$');\n",
    "plt.colorbar() \n",
    "    \n",
    "# calculate the Laplacian matrix\n",
    "Laplace = degree - adjacency\n",
    "\n",
    "# plot the Laplacian matrix\n",
    "plt.subplot(224)  \n",
    "plt.imshow(Laplace,cmap = cmap); plt.grid(alpha = 0.2)\n",
    "plt.xlim([-0.5,len(X1)-1+0.5]); plt.ylim([-0.5,len(X2)-1+0.5]);\n",
    "plt.title('Graph Laplacian Matrix'); plt.xlabel('$X_1$'); plt.ylabel('$X_2$');\n",
    "plt.colorbar()\n",
    "    \n",
    "# plot the sample data graph\n",
    "plt.subplot(221)\n",
    "plt.scatter(X1,X2,c = 'red',alpha = 0.2,edgecolors = 'black')\n",
    "for j in range(0,len(X1)):\n",
    "    for i in range(0,len(X1)):\n",
    "        if adjacency[i,j] == 1:\n",
    "            x1 = [X1[i],X1[j]]; x2 = [X2[i],X2[j]]\n",
    "            plt.plot(x1,x2,c = 'red',alpha = 0.2)\n",
    "plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "plt.xlabel('$X_1$'); plt.ylabel('$X_2$'); plt.title('Sample Data and Graph')\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=2.0, wspace=0.2, hspace=0.3)        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Eigen Vectors and Values\n",
    "\n",
    "Let's calculate the Eigen vectors and values with the NumPy package\n",
    "\n",
    "```python\n",
    "vals, vecs = np.linalg.eig(Laplace)             \n",
    "vecs = vecs[:,np.argsort(vals)]\n",
    "vals = vals[np.argsort(vals)]\n",
    "```\n",
    "\n",
    "Note, NumPy's [argsort](https://numpy.org/devdocs/reference/generated/numpy.argsort.html) returns an int 1D ndarray iwth the indices that sort on value. This sorts the Eigen values and Eigen vectors in order of ascending Eigen value.\n",
    "\n",
    "* i.e. first Eigen vector and value has the lowest Eigen value\n",
    "\n",
    "* code taken from this [article and demonstration](https://towardsdatascience.com/spectral-clustering-aba2640c0d5b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 133480 characters]",
      "text/plain": [
       "<Figure size 640x480 with 7 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# calculate the Eigen values and vectors and sort by Eigen value\n",
    "vals, vecs = np.linalg.eig(Laplace)\n",
    "vecs = vecs[:,np.argsort(vals)]\n",
    "vals = vals[np.argsort(vals)]\n",
    "\n",
    "# plot the sorted Eigen values\n",
    "plt.subplot(221)\n",
    "plt.plot(vals,c = 'red',alpha = 0.4); \n",
    "plt.title('Eigen Values of Graph Laplacian'); plt.ylabel('Eigen Values'); plt.xlabel('Ordered Eigen Vectors')\n",
    "\n",
    "# plot the sample data graph with 2nd Eigen vector\n",
    "plt.subplot(222)\n",
    "plt.scatter(X1,X2,c = vecs[:,1],alpha = 0.8,edgecolors = 'black')\n",
    "for j in range(0,len(X1)):                 # plot vertices \n",
    "    for i in range(0,len(X1)):\n",
    "        if adjacency[i,j] == 1:\n",
    "            x1 = [X1[i],X1[j]]; x2 = [X2[i],X2[j]]\n",
    "            plt.plot(x1,x2,c = 'red',alpha = 0.2)\n",
    "plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "plt.xlabel('$X_1$'); plt.ylabel('$X_2$'); plt.title('Sample Data Graph and $2^{nd}$ Eigen Vector')\n",
    "cbar = plt.colorbar()\n",
    "cbar.set_label('$2^{nd}$ Eigen Vector', rotation=270, labelpad=20)\n",
    "\n",
    "# plot the sample data graph with 3rd Eigen vector\n",
    "plt.subplot(223)\n",
    "plt.scatter(X1,X2,c = vecs[:,2],alpha = 0.8,edgecolors = 'black')\n",
    "for j in range(0,len(X1)):                 # plot vertices\n",
    "    for i in range(0,len(X1)):\n",
    "        if adjacency[i,j] == 1:\n",
    "            x1 = [X1[i],X1[j]]; x2 = [X2[i],X2[j]]\n",
    "            plt.plot(x1,x2,c = 'red',alpha = 0.2)\n",
    "plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "plt.xlabel('$X_1$'); plt.ylabel('$X_2$'); plt.title('Sample Data Graph and $3^{rd}$ Eigen Vector')\n",
    "cbar = plt.colorbar()\n",
    "cbar.set_label('$3^{rd}$ Eigen Vector', rotation=270, labelpad=20)\n",
    "\n",
    "# plot the sample data graph with 4th Eigen vector\n",
    "plt.subplot(224)\n",
    "plt.scatter(X1,X2,c = vecs[:,3],alpha = 0.8,edgecolors = 'black') \n",
    "for j in range(0,len(X1)):                 # plot vertices\n",
    "    for i in range(0,len(X1)):\n",
    "        if adjacency[i,j] == 1:\n",
    "            x1 = [X1[i],X1[j]]; x2 = [X2[i],X2[j]]\n",
    "            plt.plot(x1,x2,c = 'red',alpha = 0.2)\n",
    "plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "plt.xlabel('$X_1$'); plt.ylabel('$X_2$'); plt.title('Sample Data Graph and $4^{th}$ Eigen Vector')\n",
    "cbar = plt.colorbar()\n",
    "cbar.set_label('$4^{th}$ Eigen Vector', rotation=270, labelpad=20)\n",
    "\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=2.0, wspace=0.2, hspace=0.3)      "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Assign Clusters by k-Means Clusters on Eigen Vectors\n",
    "\n",
    "Now we will apply k-Means clusters on the first k vectors where k is the number of clusters.\n",
    "\n",
    "* code taken from this [article and demonstration](https://towardsdatascience.com/spectral-clustering-aba2640c0d5b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 47308 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "k = 2                                     # set the number of clusters\n",
    "kmeans = KMeans(n_clusters=k)             # instantiate k-means clusters\n",
    "kmeans.fit(vecs[:,1:k])                   # fit k-means clusters\n",
    "clusters = kmeans.labels_\n",
    "\n",
    "# plot the sample data graph with 2nd Eigen vector\n",
    "plt.subplot(222)\n",
    "for j in range(0,len(X1)):                 # plot vertices\n",
    "    for i in range(0,len(X1)):\n",
    "        if adjacency[i,j] == 1:\n",
    "            x1 = [X1[i],X1[j]]; x2 = [X2[i],X2[j]]\n",
    "            plt.plot(x1,x2,c = 'red',alpha = 0.2)\n",
    "plt.scatter(X1,X2,c = clusters,alpha = 1.0,edgecolors = 'black',vmin = 0, vmax = k-1, cmap = plt.cm.Dark2)\n",
    "plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "plt.xlabel('$X_1$'); plt.ylabel('$X_2$'); plt.title('Sample Data Graph and Assigned Clusters')\n",
    "cbar = plt.colorbar()\n",
    "cbar.set_label('Cluster Index', rotation=270, labelpad=20)\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=3.0, wspace=0.2, hspace=0.3)  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Interactive Spectral Clustering Method\n",
    "\n",
    "The following code includes:\n",
    "\n",
    "* dashboard with number of clusters, variogram model sample locations in the feature space and the radius for connections \n",
    "\n",
    "* plots the Eigen values, 2nd Eigen vector and clusters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings; warnings.simplefilter('ignore')\n",
    "\n",
    "color = ['blue','red','green','yellow','orange','white','magenta','cyan']\n",
    "\n",
    "style = {'description_width': 'initial'}\n",
    "l = widgets.Text(value='                                              Spectral Clustering, Michael Pyrcz, Associate Professor, The University of Texas at Austin',layout=Layout(width='950px', height='30px'))\n",
    "ncluster = widgets.IntSlider(min = 2, max = 8, value = 2, step = 1, description = 'nclusters',orientation='vertical',\n",
    "                          layout=Layout(width='100px', height='200px'))\n",
    "ncluster.style.handle_color = 'gray'\n",
    "\n",
    "radius = widgets.FloatSlider(min=0.0, max = 1000.0, value = 50.0, step = 10.0, description = 'radius',\n",
    "                        orientation='vertical',layout=Layout(width='100px', height='200px'))\n",
    "radius.style.handle_color = 'gray'\n",
    "   \n",
    "x1 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[0], step = 1.0, description = '$x_1$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "x1.style.handle_color = color[0]\n",
    "y1 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[0], step = 1.0, description = '$y_1$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "y1.style.handle_color = color[0]\n",
    "\n",
    "x2 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[1], step = 1.0, description = '$x_2$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "x2.style.handle_color = color[1]\n",
    "y2 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[1], step = 1.0, description = '$y_2$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "y2.style.handle_color = color[1]\n",
    "\n",
    "x3 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[2], step = 1.0, description = '$x_3$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "x3.style.handle_color = color[2]\n",
    "y3 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[2], step = 1.0, description = '$y_3$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "y3.style.handle_color = color[2]\n",
    "\n",
    "x4 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[3], step = 1.0, description = '$x_4$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "x4.style.handle_color = color[3]\n",
    "y4 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[3], step = 1.0, description = '$y_4$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "y4.style.handle_color = color[3]\n",
    "\n",
    "x5 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[4], step = 1.0, description = '$x_5$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "x5.style.handle_color = color[4]\n",
    "y5 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[4], step = 1.0, description = '$y_5$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "y5.style.handle_color = color[4]\n",
    "\n",
    "x6 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[5], step = 1.0, description = '$x_6$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "x6.style.handle_color = color[5]\n",
    "y6 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[5], step = 1.0, description = '$y_6$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "y6.style.handle_color = color[5]\n",
    "\n",
    "x7 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[6], step = 1.0, description = '$x_7$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "x7.style.handle_color = color[6]\n",
    "y7 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[6], step = 1.0, description = '$y_7$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "y7.style.handle_color = color[6]\n",
    "\n",
    "x8 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[7], step = 1.0, description = '$x_8$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "x8.style.handle_color = color[7]\n",
    "y8 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[7], step = 1.0, description = '$y_8$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style)\n",
    "y8.style.handle_color = color[7]\n",
    "\n",
    "uipars = widgets.HBox([ncluster,radius,x1,y1,x2,y2,x3,y3,x4,y4,x5,y5,x6,y6,x7,y7,x8,y8],) \n",
    "uik = widgets.VBox([l,uipars],)\n",
    "\n",
    "def f_make_spectral(ncluster,radius,x1,y1,x2,y2,x3,y3,x4,y4,x5,y5,x6,y6,x7,y7,x8,y8):                       # function to take parameters, make sample and plot#\n",
    "#    text_trap = io.StringIO()\n",
    "#    sys.stdout = text_trap\n",
    "    X1_local = [x1,x2,x3,x4,x5,x6,x7,x8]; X2_local = [y1,y2,y3,y4,y5,y6,y7,y8]\n",
    "    \n",
    "    n = len(X1)\n",
    "    adjacency = np.zeros([len(X1_local),len(X1_local)])   # declare the 2D matrices\n",
    "    degree = np.zeros([len(X1_local),len(X2_local)])\n",
    "\n",
    "    # populate the adjacency matrix\n",
    "    for j in range(0,len(X1_local)):\n",
    "        for i in range(0,len(X1_local)):\n",
    "            if i != j:\n",
    "                distance = math.sqrt((X1_local[i]-X1_local[j])*(X1_local[i]-X1_local[j]) + (X2_local[i]-X2_local[j])*(X2_local[i]-X2_local[j]))\n",
    "                if distance <= radius:\n",
    "                    adjacency[i,j] = 1; adjacency[j,i] = 1 \n",
    "    \n",
    "    # plot the adjacency matrix - optional\n",
    "#     plt.subplot(222)  \n",
    "#     plt.imshow(adjacency,cmap = plt.cm.binary); plt.grid(alpha = 0.2)\n",
    "#     plt.xlim([-0.5,len(X1_local)-1+0.5]); plt.ylim([-0.5,len(X2_local)-1+0.5]);\n",
    "#     plt.title('Adjacency Matrix'); plt.xlabel('$X_1$'); plt.ylabel('$X_2$');\n",
    "#     plt.colorbar()\n",
    "    \n",
    "    # populate the degree matrix\n",
    "    for j in range(0,len(X1)):\n",
    "        summation = 0\n",
    "        for i in range(0,len(X1)):\n",
    "            summation = summation + adjacency[i,j]\n",
    "        degree[j,j] = summation\n",
    "        \n",
    "    # calculate the Laplacian matrix\n",
    "    Laplace = degree - adjacency\n",
    "    \n",
    "    # plot the Laplacian matrix - optional\n",
    "#     plt.subplot(223)  \n",
    "#     plt.imshow(Laplace,cmap = cmap); plt.grid(alpha = 0.2)\n",
    "#     plt.xlim([-0.5,len(X1_local)-1+0.5]); plt.ylim([-0.5,len(X2_local)-1+0.5]);\n",
    "#     plt.title('Graph Laplacian Matrix'); plt.xlabel('$X_1$'); plt.ylabel('$X_2$');\n",
    "#     plt.colorbar()\n",
    "        \n",
    "    # plot the sample data graph\n",
    "    plt.subplot(121)\n",
    "    \n",
    "    for i in range(0,n):\n",
    "        plt.scatter(X1_local[i],X2_local[i],c = color[i],s=150,alpha = 0.9,edgecolors = 'black')\n",
    "    for idata in range(0,8):\n",
    "        plt.annotate(str(idata+1),(X1_local[idata]-4,X2_local[idata]-4))\n",
    "    for j in range(0,len(X1)):\n",
    "        for i in range(0,len(X1)):\n",
    "            if adjacency[i,j] == 1:\n",
    "                x1 = [X1_local[i],X1_local[j]]; x2 = [X2_local[i],X2_local[j]]\n",
    "                plt.plot(x1,x2,c = 'black',alpha = 0.2)\n",
    "    plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "    plt.xlabel('$X$'); plt.ylabel('$Y$'); plt.title('Sample Data and Graph')\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=2.0, wspace=0.2, hspace=0.3)        \n",
    "\n",
    "    # calculate the Eigen values and vectors and sort by Eigen value\n",
    "    vals, vecs = np.linalg.eig(Laplace)\n",
    "    vecs = vecs[:,np.argsort(vals)]\n",
    "    vals = vals[np.argsort(vals)]    \n",
    "    \n",
    "    kmeans = KMeans(n_clusters=ncluster)             # instantiate k-means clusters\n",
    "    kmeans.fit(vecs[:,1:ncluster])                   # fit k-means clusters\n",
    "    clusters = kmeans.labels_\n",
    "\n",
    "    # plot the sample data graph with 2nd Eigen vector\n",
    "    plt.subplot(122)\n",
    "#     for j in range(0,len(X1_local)):                 # plot vertices\n",
    "#         for i in range(0,len(X1_local)):\n",
    "#             if adjacency[i,j] == 1:\n",
    "#                 x1 = [X1_local[i],X1_local[j]]; x2 = [X2_local[i],X2_local[j]]\n",
    "#                 plt.plot(x1,x2,c = 'red',alpha = 0.2)\n",
    "    cmap = plt.cm.get_cmap('Set2',8)\n",
    "    plt.scatter(X1_local,X2_local,c = clusters+1,s = 150,alpha = 0.9,edgecolors = 'black',vmin = 1, vmax = 8, cmap = cmap)\n",
    "    for idata in range(0,8):\n",
    "        plt.annotate(str(clusters[idata]+1),(X1_local[idata]-7,X2_local[idata]-4))\n",
    "    plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "    plt.xlabel('$X$'); plt.ylabel('$Y$'); plt.title('Sample Data Graph and Assigned Clusters')\n",
    "    cbar = plt.colorbar()\n",
    "    cbar.set_label('Cluster Index', rotation=270, labelpad=20)\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.5, wspace=0.2, hspace=0.3)  \n",
    "\n",
    "# connect the function to make the samples and plot to the widgets    \n",
    "interactive_plot = widgets.interactive_output(f_make_spectral, {'ncluster':ncluster, 'radius':radius, \n",
    "                                                      'x1':x1, 'y1':y1, 'x2':x2, 'y2':y2, 'x3':x3, 'y3':y3,\n",
    "                                                      'x4':x4, 'y4':y4, 'x5':x5, 'y5':y5, 'x6':x6, 'y6':y6,\n",
    "                                                      'x7':x7, 'y7':y7, 'x8':x8, 'y8':y8})\n",
    "interactive_plot.clear_output(wait = True)               # reduce flickering by delaying plot updating"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interactive Spectral Clustering Demostration\n",
    "\n",
    "* select the number of clusters and the data graph (locations and connections by radius)\n",
    "\n",
    "#### Michael Pyrcz, Associate Professor, University of Texas at Austin \n",
    "\n",
    "##### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1) | [GeostatsPy](https://github.com/GeostatsGuy/GeostatsPy)\n",
    "\n",
    "### The Inputs\n",
    "\n",
    "Select the variogram model and the data locations:\n",
    "\n",
    "* **nclusters**: the number of clusters\n",
    "\n",
    "* **radius**: maximum distance in feature space to form a connection\n",
    "\n",
    "* **$x_i$,$y_i$**: sample data locations in feature space"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "349ea3d3165b4c32a8eb43c24666fd7b",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(Text(value='                                              Spectral Clustering, Michael Pyrcz, A…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "06de8b7542ea4c79ad82a852eb18d2b8",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(uik, interactive_plot)                            # display the interactive plot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Interactive Spectral Clustering Showing All Steps\n",
    "\n",
    "Let's do this again, but showing all our work."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings; warnings.simplefilter('ignore')\n",
    "\n",
    "color = ['blue','red','green','yellow','orange','white','magenta','cyan']\n",
    "\n",
    "style = {'description_width': 'initial'}\n",
    "l = widgets.Text(value='                                              Spectral Clustering, Michael Pyrcz, Associate Professor, The University of Texas at Austin',layout=Layout(width='950px', height='30px'))\n",
    "ncluster = widgets.IntSlider(min = 2, max = 8, value = 2, step = 1, description = 'nclusters',orientation='vertical',\n",
    "                          layout=Layout(width='100px', height='200px'))\n",
    "ncluster.style.handle_color = 'gray'\n",
    "\n",
    "radius = widgets.FloatSlider(min=0.0, max = 1000.0, value = 200.0, step = 10.0, description = 'radius',\n",
    "                        orientation='vertical',layout=Layout(width='100px', height='200px'),continuous_update=False)\n",
    "radius.style.handle_color = 'gray'\n",
    "   \n",
    "x1 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[0], step = 1.0, description = '$x_1$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x1.style.handle_color = color[0]\n",
    "y1 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[0], step = 1.0, description = '$y_1$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y1.style.handle_color = color[0]\n",
    "\n",
    "x2 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[1], step = 1.0, description = '$x_2$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x2.style.handle_color = color[1]\n",
    "y2 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[1], step = 1.0, description = '$y_2$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y2.style.handle_color = color[1]\n",
    "\n",
    "x3 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[2], step = 1.0, description = '$x_3$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x3.style.handle_color = color[2]\n",
    "y3 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[2], step = 1.0, description = '$y_3$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y3.style.handle_color = color[2]\n",
    "\n",
    "x4 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[3], step = 1.0, description = '$x_4$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x4.style.handle_color = color[3]\n",
    "y4 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[3], step = 1.0, description = '$y_4$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y4.style.handle_color = color[3]\n",
    "\n",
    "x5 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[4], step = 1.0, description = '$x_5$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x5.style.handle_color = color[4]\n",
    "y5 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[4], step = 1.0, description = '$y_5$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y5.style.handle_color = color[4]\n",
    "\n",
    "x6 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[5], step = 1.0, description = '$x_6$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x6.style.handle_color = color[5]\n",
    "y6 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[5], step = 1.0, description = '$y_6$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y6.style.handle_color = color[5]\n",
    "\n",
    "x7 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[6], step = 1.0, description = '$x_7$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x7.style.handle_color = color[6]\n",
    "y7 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[6], step = 1.0, description = '$y_7$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y7.style.handle_color = color[6]\n",
    "\n",
    "x8 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[7], step = 1.0, description = '$x_8$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x8.style.handle_color = color[7]\n",
    "y8 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[7], step = 1.0, description = '$y_8$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y8.style.handle_color = color[7]\n",
    "\n",
    "uipars = widgets.HBox([ncluster,radius,x1,y1,x2,y2,x3,y3,x4,y4,x5,y5,x6,y6,x7,y7,x8,y8],) \n",
    "uik = widgets.VBox([l,uipars],)\n",
    "\n",
    "def f_make_spectral(ncluster,radius,x1,y1,x2,y2,x3,y3,x4,y4,x5,y5,x6,y6,x7,y7,x8,y8):                       # function to take parameters, make sample and plot#\n",
    "#    text_trap = io.StringIO()\n",
    "#    sys.stdout = text_trap\n",
    "    cmap = plt.cm.get_cmap('Set2',8)\n",
    "    X1_local = [x1,x2,x3,x4,x5,x6,x7,x8]; X2_local = [y1,y2,y3,y4,y5,y6,y7,y8]\n",
    "    \n",
    "    n = len(X1)\n",
    "    adjacency = np.zeros([len(X1_local),len(X1_local)])   # declare the 2D matrices\n",
    "    degree = np.zeros([len(X1_local),len(X2_local)])\n",
    "\n",
    "    # populate the adjacency matrix\n",
    "    for j in range(0,len(X1_local)):\n",
    "        for i in range(0,len(X1_local)):\n",
    "            if i != j:\n",
    "                distance = math.sqrt((X1_local[i]-X1_local[j])*(X1_local[i]-X1_local[j]) + (X2_local[i]-X2_local[j])*(X2_local[i]-X2_local[j]))\n",
    "                if distance <= radius:\n",
    "                    adjacency[i,j] = 1; adjacency[j,i] = 1 \n",
    "    \n",
    "    # plot the adjacency matrix - optional\n",
    "    plt.subplot(433)  \n",
    "    plt.imshow(adjacency,cmap = plt.cm.binary); plt.grid(alpha = 0.2)\n",
    "    plt.xlim([-0.5,len(X1_local)-1+0.5]); plt.ylim([-0.5,len(X2_local)-1+0.5]);\n",
    "    plt.title('Adjacency Matrix'); plt.xlabel(r'Data Samples, $\\bf{u}_i$'); plt.ylabel(r'Data Samples, $\\bf{u}_j$');\n",
    "    plt.xticks(np.linspace(0,7,8),np.arange(1,9,1)); plt.yticks(np.linspace(0,7,8),np.arange(1,9,1))\n",
    "    plt.colorbar()\n",
    "    \n",
    "    # populate the degree matrix\n",
    "    for j in range(0,len(X1)):\n",
    "        summation = 0\n",
    "        for i in range(0,len(X1)):\n",
    "            summation = summation + adjacency[i,j]\n",
    "        degree[j,j] = summation\n",
    "        \n",
    "    # calculate the Laplacian matrix\n",
    "    Laplace = degree - adjacency\n",
    "    \n",
    "    # plot the Laplacian matrix - optional\n",
    "    plt.subplot(434)  \n",
    "    plt.imshow(Laplace,cmap = plt.cm.inferno); plt.grid(alpha = 0.2)\n",
    "    plt.xlim([-0.5,len(X1_local)-1+0.5]); plt.ylim([-0.5,len(X2_local)-1+0.5]);\n",
    "    plt.title('Graph Laplacian Matrix'); plt.xlabel(r'Data Samples, $\\bf{u}_i$'); plt.ylabel(r'Data Samples, $\\bf{u}_j$');\n",
    "    plt.xticks(np.linspace(0,7,8),np.arange(1,9,1)); plt.yticks(np.linspace(0,7,8),np.arange(1,9,1))\n",
    "    plt.colorbar()\n",
    "        \n",
    "    # plot the sample data graph\n",
    "    plt.subplot(431)\n",
    "    \n",
    "    for i in range(0,n):\n",
    "        plt.scatter(X1_local[i],X2_local[i],c = color[i],s=150,alpha = 0.5,edgecolors = 'black')\n",
    "    for idata in range(0,8):\n",
    "        plt.annotate(str(idata+1),(X1_local[idata]-5,X2_local[idata]-7))\n",
    "    for j in range(0,len(X1)):\n",
    "        for i in range(0,len(X1)):\n",
    "            if adjacency[i,j] == 1:\n",
    "                x1 = [X1_local[i],X1_local[j]]; x2 = [X2_local[i],X2_local[j]]\n",
    "                plt.plot(x1,x2,c = 'black',alpha = 0.2)\n",
    "    plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "    plt.xlabel('$X$'); plt.ylabel('$Y$'); plt.title('Sample Data and Graph')\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=2.0, wspace=0.2, hspace=0.3)        \n",
    "\n",
    "    # calculate the Eigen values and vectors and sort by Eigen value\n",
    "    vals, vecs = np.linalg.eig(Laplace)\n",
    "    vecs = vecs[:,np.argsort(vals)]\n",
    "    vals = vals[np.argsort(vals)]\n",
    "    \n",
    "    kmeans = KMeans(n_clusters=ncluster)             # instantiate k-means clusters\n",
    "    kmeans.fit(vecs[:,1:ncluster])                   # fit k-means clusters\n",
    "    clusters = kmeans.labels_    \n",
    "    \n",
    "#     print(vecs[:,2])\n",
    "#     print(vecs[:,3])\n",
    "#     print(vecs[:,4])\n",
    "    \n",
    "    # make a custom colormap\n",
    "    my_colormap = plt.cm.get_cmap('RdBu_r', 256)\n",
    "    newcolors = my_colormap(np.linspace(0, 1, 256))\n",
    "    white = np.array([256/256, 256/256, 256/256, 1])\n",
    "    newcolors[100:150, :] = white                          # mask all correlations less than abs(0.8)\n",
    "    custom_cmap = ListedColormap(newcolors)\n",
    "        \n",
    "    plt.subplot(435)  \n",
    "    plt.imshow(vecs,cmap = custom_cmap); plt.grid(alpha = 0.2)\n",
    "    #plt.xlim([-0.5,len(X1_local)-1+0.5]); plt.ylim([-0.5,len(X2_local)-1+0.5]);\n",
    "    plt.title('Eigen Vectors'); plt.xlabel('Eigen Vectors'); plt.ylabel(r'Data Samples, $\\bf{u}_i$');\n",
    "    plt.xticks(np.linspace(0,7,8),np.arange(1,9,1)); plt.yticks(np.linspace(0,7,8),np.arange(1,9,1))\n",
    "    for ix in range(0,7):\n",
    "        plt.plot([.50+float(ix),0.5+float(ix)],[-0.5,7.5],color = 'black')    \n",
    "    plt.colorbar()\n",
    "    \n",
    "    plt.subplot(436)  \n",
    "    plt.grid(alpha = 0.2); plt.plot(vals, c = 'red'); \n",
    "    #plt.xlim([-0.5,len(X1_local)-1+0.5]); plt.ylim([-0.5,len(X2_local)-1+0.5]);\n",
    "    plt.title('Eigen Values'); plt.xlabel('Eigen Vectors'); plt.ylabel('Eigen Values');\n",
    "    plt.xticks(np.linspace(0,7,8),np.arange(1,9,1));\n",
    "    plt.colorbar()    \n",
    "    \n",
    "    plt.subplot(437)    \n",
    "    plt.scatter(X1_local,X2_local,c = vecs[:,0],s = 60,alpha = 1.0,edgecolors = 'black',vmin = -1.0, vmax = 1.0, cmap = custom_cmap)\n",
    "    plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "    plt.xlabel('$X$'); plt.ylabel('$Y$'); plt.title('Sample Data and First Eigen Vector')\n",
    "    cbar = plt.colorbar()\n",
    "    cbar.set_label('Eigen Vector #1', rotation=270, labelpad=20)\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.5, wspace=0.2, hspace=0.3) \n",
    "    \n",
    "    plt.subplot(438)    \n",
    "    plt.scatter(X1_local,X2_local,c = vecs[:,1],s = 60,alpha = 1.0,edgecolors = 'black',vmin = -1.0, vmax = 1.0, cmap = custom_cmap)\n",
    "    plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "    plt.xlabel('$X$'); plt.ylabel('$Y$'); plt.title('Sample Data and Second Eigen Vector')\n",
    "    cbar = plt.colorbar()\n",
    "    cbar.set_label('Eigen Vector #2', rotation=270, labelpad=20)\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.5, wspace=0.2, hspace=0.3)  \n",
    "    \n",
    "    plt.subplot(439)    \n",
    "    plt.scatter(X1_local,X2_local,c = vecs[:,2],s = 60,alpha = 1.0,edgecolors = 'black',vmin = -1.0, vmax = 1.0, cmap = custom_cmap)\n",
    "    plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "    plt.xlabel('$X$'); plt.ylabel('$Y$'); plt.title('Sample Data and Third Eigen Vector')\n",
    "    cbar = plt.colorbar()\n",
    "    cbar.set_label('Eigen Vector #3', rotation=270, labelpad=20)\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.5, wspace=0.2, hspace=0.3)  \n",
    "    \n",
    "    plt.subplot(4,3,10)    \n",
    "    plt.scatter(X1_local,X2_local,c = vecs[:,3],s = 60,alpha = 1.0,edgecolors = 'black',vmin = -1.0, vmax = 1.0, cmap = custom_cmap)\n",
    "    plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "    plt.xlabel('$X$'); plt.ylabel('$Y$'); plt.title('Sample Data and Fourth Eigen Vector')\n",
    "    cbar = plt.colorbar()\n",
    "    cbar.set_label('Eigen Vector #4', rotation=270, labelpad=20)\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.5, wspace=0.2, hspace=0.3)  \n",
    "    \n",
    "#         plt.scatter(X1_local,X2_local,c = clusters+1,s = 150,alpha = 0.5,edgecolors = 'black',vmin = 1, vmax = 8, cmap = cmap)\n",
    "#     for idata in range(0,8):\n",
    "#         plt.annotate(str(clusters[idata]+1),(X1_local[idata]-9,X2_local[idata]-8))\n",
    "    \n",
    "    plt.subplot(4,3,11)    \n",
    "    plt.scatter(vecs[:,1],vecs[:,2],c = clusters+1,s = 150,alpha = 0.5,edgecolors = 'black',vmin = 1, vmax = 8, cmap = cmap)\n",
    "    for idata in range(0,8):\n",
    "        plt.annotate(str(idata+1),(vecs[idata,1]-.08,vecs[idata,2]-.08))\n",
    "    plt.xlim([-2.0,2.0]); plt.ylim([-2.0,2.0]); \n",
    "    plt.xlabel(r'$2^{nd}$ Eigen Vectors'); plt.ylabel(r'$3^{rd}$ Eigen Vectors'); plt.title(r'$3^{rd}$ Eigen Vector vs. $2^{nd}$ Eigen Vector')\n",
    "    cbar = plt.colorbar()\n",
    "    cbar.set_label('Cluster Index', rotation=270, labelpad=20)\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.5, wspace=0.2, hspace=0.3) \n",
    "    \n",
    "    plt.subplot(4,3,12)    \n",
    "    plt.scatter(vecs[:,3],vecs[:,4],c = clusters+1,s = 150,alpha = 0.5,edgecolors = 'black',vmin = 1, vmax = 8, cmap = cmap)\n",
    "    for idata in range(0,8):\n",
    "        plt.annotate(str(idata+1),(vecs[idata,3]-.08,vecs[idata,4]-.08))\n",
    "    plt.xlim([-2.0,2.0]); plt.ylim([-2.0,2.0]); \n",
    "    plt.xlabel(r'$3^{rd}$ Eigen Vectors'); plt.ylabel(r'$4^{th}$ Eigen Vectors'); plt.title(r'$4^{th}$ Eigen Vector vs. $3^{rd}$ Eigen Vector')\n",
    "    cbar = plt.colorbar()\n",
    "    cbar.set_label('Cluster Index', rotation=270, labelpad=20)\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.5, wspace=0.2, hspace=0.3) \n",
    "    \n",
    "    # plot the sample data graph with 2nd Eigen vector\n",
    "    plt.subplot(432)\n",
    "#     for j in range(0,len(X1_local)):                 # plot vertices\n",
    "#         for i in range(0,len(X1_local)):\n",
    "#             if adjacency[i,j] == 1:\n",
    "#                 x1 = [X1_local[i],X1_local[j]]; x2 = [X2_local[i],X2_local[j]]\n",
    "#                 plt.plot(x1,x2,c = 'red',alpha = 0.2)  \n",
    "\n",
    "    plt.scatter(X1_local,X2_local,c = clusters+1,s = 150,alpha = 0.5,edgecolors = 'black',vmin = 1, vmax = 8, cmap = cmap)\n",
    "    for idata in range(0,8):\n",
    "        plt.annotate(str(clusters[idata]+1),(X1_local[idata]-9,X2_local[idata]-8))\n",
    "\n",
    "#     plt.scatter(X1_local,X2_local,c = clusters,s = 60,alpha = 1.0,edgecolors = 'black',vmin = 0, vmax = 8, cmap = plt.cm.Dark2)\n",
    "    plt.xlim([0,500]); plt.ylim([0,500]); \n",
    "    plt.xlabel('$X$'); plt.ylabel('$Y$'); plt.title('Sample Data Graph and Assigned Clusters')\n",
    "    cbar = plt.colorbar()\n",
    "    cbar.set_label('Cluster Index', rotation=270, labelpad=20)\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=3.5, wspace=0.5, hspace=0.3)  \n",
    "\n",
    "# connect the function to make the samples and plot to the widgets    \n",
    "interactive_plot2 = widgets.interactive_output(f_make_spectral, {'ncluster':ncluster, 'radius':radius, \n",
    "                                                      'x1':x1, 'y1':y1, 'x2':x2, 'y2':y2, 'x3':x3, 'y3':y3,\n",
    "                                                      'x4':x4, 'y4':y4, 'x5':x5, 'y5':y5, 'x6':x6, 'y6':y6,\n",
    "                                                      'x7':x7, 'y7':y7, 'x8':x8, 'y8':y8})\n",
    "interactive_plot2.clear_output(wait = True)               # reduce flickering by delaying plot updating"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interactive Spectral Clustering Demostration, All Steps Shown\n",
    "\n",
    "* select the number of clusters and the data graph (locations and connections by radius)\n",
    "\n",
    "#### Michael Pyrcz, Associate Professor, University of Texas at Austin \n",
    "\n",
    "##### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1) | [GeostatsPy](https://github.com/GeostatsGuy/GeostatsPy)\n",
    "\n",
    "### The Inputs\n",
    "\n",
    "Select the variogram model and the data locations:\n",
    "\n",
    "* **nclusters**: the number of clusters\n",
    "\n",
    "* **radius**: maximum distance in feature space to form a connection\n",
    "\n",
    "* **$x_i$,$y_i$**: sample data locations in feature space"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b1b0f90da224497a91c5852849740322",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(Text(value='                                              Spectral Clustering, Michael Pyrcz, A…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e15b16e5c8c84970871de50473d89f80",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(uik, interactive_plot2)                            # display the interactive plot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### UNDER CONSTRUCTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings; warnings.simplefilter('ignore')\n",
    "\n",
    "color = ['blue','red','green','yellow','orange','white','magenta','cyan']\n",
    "\n",
    "style = {'description_width': 'initial'}\n",
    "l = widgets.Text(value='                                              Spectral Clustering, Michael Pyrcz, Associate Professor, The University of Texas at Austin',layout=Layout(width='950px', height='30px'))\n",
    "\n",
    "radius = widgets.FloatSlider(min=0.0, max = 1000.0, value = 200.0, step = 10.0, description = 'radius',\n",
    "                        orientation='vertical',layout=Layout(width='100px', height='200px'),continuous_update=False)\n",
    "radius.style.handle_color = 'gray'\n",
    "   \n",
    "x1 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[0], step = 1.0, description = '$x_1$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x1.style.handle_color = color[0]\n",
    "y1 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[0], step = 1.0, description = '$y_1$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y1.style.handle_color = color[0]\n",
    "\n",
    "x2 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[1], step = 1.0, description = '$x_2$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x2.style.handle_color = color[1]\n",
    "y2 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[1], step = 1.0, description = '$y_2$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y2.style.handle_color = color[1]\n",
    "\n",
    "x3 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[2], step = 1.0, description = '$x_3$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x3.style.handle_color = color[2]\n",
    "y3 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[2], step = 1.0, description = '$y_3$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y3.style.handle_color = color[2]\n",
    "\n",
    "x4 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[3], step = 1.0, description = '$x_4$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x4.style.handle_color = color[3]\n",
    "y4 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[3], step = 1.0, description = '$y_4$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y4.style.handle_color = color[3]\n",
    "\n",
    "x5 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[4], step = 1.0, description = '$x_5$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x5.style.handle_color = color[4]\n",
    "y5 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[4], step = 1.0, description = '$y_5$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y5.style.handle_color = color[4]\n",
    "\n",
    "x6 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[5], step = 1.0, description = '$x_6$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x6.style.handle_color = color[5]\n",
    "y6 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[5], step = 1.0, description = '$y_6$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y6.style.handle_color = color[5]\n",
    "\n",
    "x7 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[6], step = 1.0, description = '$x_7$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x7.style.handle_color = color[6]\n",
    "y7 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[6], step = 1.0, description = '$y_7$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y7.style.handle_color = color[6]\n",
    "\n",
    "x8 = widgets.FloatSlider(min=0.0, max = 500.0, value = X1[7], step = 1.0, description = '$x_8$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "x8.style.handle_color = color[7]\n",
    "y8 = widgets.FloatSlider(min=0.0, max = 500.0, value = X2[7], step = 1.0, description = '$y_8$',orientation='vertical',\n",
    "                         layout=Layout(width='30px', height='200px'),readout_format = '.0f',style=style,continuous_update=False)\n",
    "y8.style.handle_color = color[7]\n",
    "\n",
    "uipars = widgets.HBox([radius,x1,y1,x2,y2,x3,y3,x4,y4,x5,y5,x6,y6,x7,y7,x8,y8],) \n",
    "uik = widgets.VBox([l,uipars],)\n",
    "\n",
    "def f_make_spectral(radius,x1,y1,x2,y2,x3,y3,x4,y4,x5,y5,x6,y6,x7,y7,x8,y8):                       # function to take parameters, make sample and plot#\n",
    "#    text_trap = io.StringIO()\n",
    "#    sys.stdout = text_trap\n",
    "    ncluster = 3\n",
    "    cmap1 = plt.cm.get_cmap('Set2',8)\n",
    "    cmap2 = plt.cm.get_cmap('Set1',ncluster+5)\n",
    "    X1_local = [x1,x2,x3,x4,x5,x6,x7,x8]; X2_local = [y1,y2,y3,y4,y5,y6,y7,y8]\n",
    "    \n",
    "    n = len(X1)\n",
    "    adjacency = np.zeros([len(X1_local),len(X1_local)])   # declare the 2D matrices\n",
    "    degree = np.zeros([len(X1_local),len(X2_local)])\n",
    "\n",
    "    # populate the adjacency matrix\n",
    "    for j in range(0,len(X1_local)):\n",
    "        for i in range(0,len(X1_local)):\n",
    "            if i != j:\n",
    "                distance = math.sqrt((X1_local[i]-X1_local[j])*(X1_local[i]-X1_local[j]) + (X2_local[i]-X2_local[j])*(X2_local[i]-X2_local[j]))\n",
    "                if distance <= radius:\n",
    "                    adjacency[i,j] = 1; adjacency[j,i] = 1 \n",
    "    \n",
    "    # populate the degree matrix\n",
    "    for j in range(0,len(X1)):\n",
    "        summation = 0\n",
    "        for i in range(0,len(X1)):\n",
    "            summation = summation + adjacency[i,j]\n",
    "        degree[j,j] = summation\n",
    "        \n",
    "    # calculate the Laplacian matrix\n",
    "    Laplace = degree - adjacency\n",
    "    \n",
    "    # calculate the Eigen values and vectors and sort by Eigen value\n",
    "    vals, vecs = np.linalg.eig(Laplace)\n",
    "    vecs = vecs[:,np.argsort(vals)]\n",
    "    vals = vals[np.argsort(vals)]\n",
    "    \n",
    "    kmeans = KMeans(n_clusters=ncluster)             # instantiate k-means clusters\n",
    "    kmeans.fit(vecs[:,1:ncluster])                   # fit k-means clusters\n",
    "    clusters = kmeans.labels_    \n",
    "        \n",
    "    # plot the sample data graph\n",
    "    plt.subplot(121)\n",
    "    for i in range(0,n):\n",
    "        plt.scatter(X1_local[i],X2_local[i],c = i+1,s=300,alpha = 1.0,edgecolors = 'black',vmin = 0.5, vmax = 8.5,cmap = cmap1,zorder=10)\n",
    "    for idata in range(0,8):\n",
    "        plt.annotate(str(idata+1),(X1_local[idata]-4,X2_local[idata]-5),zorder=20)\n",
    "    for j in range(0,len(X1)):\n",
    "        for i in range(0,len(X1)):\n",
    "            if adjacency[i,j] == 1:\n",
    "                x1 = [X1_local[i],X1_local[j]]; x2 = [X2_local[i],X2_local[j]]\n",
    "                plt.plot(x1,x2,c = 'black',alpha = 0.8,zorder=1)\n",
    "    \n",
    "    plt.scatter(X1_local,X2_local,c = clusters+1,s = 800,alpha = 1.0,edgecolors = 'black',vmin = 0.5, vmax = ncluster+0.5, cmap = cmap2,zorder=8)\n",
    " \n",
    "    plt.xlim([0,500]); plt.ylim([0,500]);\n",
    "    plt.xlabel('$X_1$'); plt.ylabel('$X_2$'); plt.title('Sample Data, Graph, and Spectral Cluster Assignments')\n",
    "    plt.gca().grid(which='minor', color='#EEEEEE', linestyle=':', linewidth=0.5)\n",
    "    plt.gca().grid(which='major', color='#DDDDDD', linewidth=0.8); plt.gca().minorticks_on()\n",
    "    \n",
    "#     cbar = plt.colorbar()\n",
    "#     cbar.set_label('Sample Index', rotation=270, labelpad=20)      \n",
    "\n",
    "    plt.subplot(122) \n",
    "    for i in range(0,n):\n",
    "        plt.scatter(vecs[:,1],vecs[:,2],c = np.arange(1,9,1),s = 300,alpha = 1.0,edgecolors = 'black',vmin = 0.5, vmax = 8.5, cmap = cmap1,zorder=10)\n",
    "    plt.scatter(vecs[:,1],vecs[:,2],c = clusters+1,s = 800,alpha = 1.0,edgecolors = 'black',vmin = 0.5, vmax = ncluster+0.5, cmap = cmap2,zorder=8)\n",
    "   \n",
    "\n",
    "    for idata in range(0,8):\n",
    "        plt.annotate(str(idata+1),(vecs[idata,1]-.04,vecs[idata,2]-.04),zorder=30)\n",
    "    plt.xlim([-2.0,2.0]); plt.ylim([-2.0,2.0]); \n",
    "    plt.xlabel(r'$2^{nd}$ Eigen Vectors Loadings'); plt.ylabel(r'$3^{rd}$ Eigen Vector Loadings'); plt.title(r'$3^{rd}$ Eigen Vector Loadings vs. $2^{nd}$ Eigen Vector Loadings')\n",
    "    plt.gca().grid(which='minor', color='#EEEEEE', linestyle=':', linewidth=0.5)\n",
    "    plt.gca().grid(which='major', color='#DDDDDD', linewidth=0.8); plt.gca().minorticks_on()\n",
    "    \n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=2.0, top=1.2, wspace=0.2, hspace=0.2)  \n",
    "\n",
    "# connect the function to make the samples and plot to the widgets    \n",
    "interactive_plot2 = widgets.interactive_output(f_make_spectral, {'radius':radius, \n",
    "                                                      'x1':x1, 'y1':y1, 'x2':x2, 'y2':y2, 'x3':x3, 'y3':y3,\n",
    "                                                      'x4':x4, 'y4':y4, 'x5':x5, 'y5':y5, 'x6':x6, 'y6':y6,\n",
    "                                                      'x7':x7, 'y7':y7, 'x8':x8, 'y8':y8})\n",
    "interactive_plot2.clear_output(wait = True)               # reduce flickering by delaying plot updating"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ac9746c7ee2041cbbe1017fef4bf7513",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(Text(value='                                              Spectral Clustering, Michael Pyrcz, A…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f350e4d8d7ba4aa49e1c7f85ff44e346",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output(outputs=({'output_type': 'display_data', 'data': {'text/plain': '<Figure size 640x480 with 2 Axes>', 'i…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(uik, interactive_plot2)                            # display the interactive plot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some observations from these spectral cluster assignments:\n",
    "\n",
    "* as expected the method is able to devide the sample data into a specified number of clusters\n",
    "\n",
    "* the cluster group index is arbitrary and will switch as you move the data\n",
    "\n",
    "Things to try:\n",
    "\n",
    "* make a chain of all the data (i.e. sample 1 connected to sample 2 connected to sample 3 and so on)\n",
    "\n",
    "* make no connections\n",
    "\n",
    "* make a ring of data\n",
    "\n",
    "* make connected clusters\n",
    "\n",
    "#### Comments\n",
    "\n",
    "I hope you found this tutorial useful. I'm always happy to discuss data analytics, geostatistics, statistical modeling, uncertainty modeling and machine learning,\n",
    "\n",
    "I have other demonstrations on the basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations, trend modeling and many other workflows available at https://github.com/GeostatsGuy/PythonNumericalDemos and https://github.com/GeostatsGuy/GeostatsPy. \n",
    "  \n",
    "I hope this was helpful,\n",
    "\n",
    "*Michael*\n",
    "\n",
    "#### The Author:\n",
    "\n",
    "### Michael Pyrcz, Associate Professor, University of Texas at Austin \n",
    "*Novel Data Analytics, Geostatistics and Machine Learning Subsurface Solutions*\n",
    "\n",
    "With over 17 years of experience in subsurface consulting, research and development, Michael has returned to academia driven by his passion for teaching and enthusiasm for enhancing engineers' and geoscientists' impact in subsurface resource development. \n",
    "\n",
    "For more about Michael check out these links:\n",
    "\n",
    "#### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)\n",
    "\n",
    "#### Want to Work Together?\n",
    "\n",
    "I hope this content is helpful to those that want to learn more about subsurface modeling, data analytics and machine learning. Students and working professionals are welcome to participate.\n",
    "\n",
    "* Want to invite me to visit your company for training, mentoring, project review, workflow design and / or consulting? I'd be happy to drop by and work with you! \n",
    "\n",
    "* Interested in partnering, supporting my graduate student research or my Subsurface Data Analytics and Machine Learning consortium (co-PIs including Profs. Foster, Torres-Verdin and van Oort)? My research combines data analytics, stochastic modeling and machine learning theory with practice to develop novel methods and workflows to add value. We are solving challenging subsurface problems!\n",
    "\n",
    "* I can be reached at mpyrcz@austin.utexas.edu.\n",
    "\n",
    "I'm always happy to discuss,\n",
    "\n",
    "*Michael*\n",
    "\n",
    "Michael Pyrcz, Ph.D., P.Eng. Associate Professor The Hildebrand Department of Petroleum and Geosystems Engineering, Bureau of Economic Geology, The Jackson School of Geosciences, The University of Texas at Austin\n",
    "\n",
    "I have other demonstrations on the basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations, trend modeling and many other workflows available at https://github.com/GeostatsGuy/PythonNumericalDemos and https://github.com/GeostatsGuy/GeostatsPy. \n",
    "  \n",
    "I hope this was helpful,\n",
    "\n",
    "*Michael*\n",
    "\n",
    "Michael Pyrcz, Ph.D., P.Eng. Associate Professor The Hildebrand Department of Petroleum and Geosystems Engineering, Bureau of Economic Geology, The Jackson School of Geosciences, The University of Texas at Austin\n",
    "\n",
    "#### More Resources Available at: [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig)  | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
