{
 "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",
    "### Interactive Workflow of Principal Component Analysis as a Rotation\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",
    "#### Principal Component Analysis (PCA)\n",
    "\n",
    "This was a basic demonstration of PCA as a orthogonal transformation (a rotation as we preserve the inner product space, the pairwise distances and angles between all points).\n",
    "\n",
    "* by rotating the orthogonal components in 2D we can maximize the variance explained by a single component while reducing the absolute correlation between the components to 0.0. \n",
    "\n",
    "Appreciation to Professor Martin H. Trauth for the suggestion to also include the correlation and to comment on the decorrelation of components at the same time as maximizing the variance explained by the first component.\n",
    "\n",
    "First, some more on Principal Component Analysis one of a variety of methods for dimensional reduction:\n",
    "\n",
    "Dimensional reduction transforms the data to a lower dimension\n",
    "\n",
    "* Given features, $𝑋_1,\\dots,𝑋_𝑚$  we would require ${m \\choose 2}=\\frac{𝑚 \\cdot (𝑚−1)}{2}$ scatter plots to visualize just the two-dimensional scatter plots.\n",
    "\n",
    "* Once we have 4 or more variables understanding our data gets very hard.\n",
    "\n",
    "* Recall the curse of dimensionality, impact inference, modeling and visualization. \n",
    "\n",
    "One solution, is to find a good lower dimensional, $𝑝$,  representation of the original dimensions $𝑚$\n",
    "\n",
    "Benefits of Working in a Reduced Dimensional Representation:\n",
    "\n",
    "1. Data storage / Computational Time\n",
    "\n",
    "2. Easier visualization\n",
    "\n",
    "3. Also takes care of multicollinearity \n",
    "\n",
    "#### Orthogonal Transformation \n",
    "\n",
    "Convert a set of observations into a set of linearly uncorrelated variables known as principal components\n",
    "\n",
    "* The number of principal components ($k$) available are min⁡($𝑛−1,𝑚$) \n",
    "\n",
    "* Limited by the variables/features, $𝑚$, and the number of data\n",
    "\n",
    "Components are ordered\n",
    "\n",
    "* First component describes the larges possible variance / accounts for as much variability as possible\n",
    "* Next component describes the largest possible remaining variance \n",
    "* Up to the maximum number of principal components\n",
    "\n",
    "Eigen Values / Eigen Vectors\n",
    "\n",
    "* The Eigen values are the variance explained for each component. \n",
    "* The Eigen vectors of the data covariance matrix are the principal components and the Eigen  \n",
    "* Out of scope – just making the linkage\n",
    "\n",
    "#### Getting Started\n",
    "Here are the steps to get setup in Python with the GeostatsPy package:\n",
    "1.\tInstall Anaconda 3 on your machine (https://www.anaconda.com/download/).\n",
    "2.\tFrom Anaconda Navigator (within Anaconda3 group), go to the environment tab, click on base (root) green arrow and open a terminal.\n",
    "3.\tIn the terminal type: pip install geostatspy.\n",
    "4.\tOpen 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",
    "   - Tabular data - unconv_MV_v2.csv at https://git.io/fjmBH.\n",
    "\n",
    "#### Install Packages\n",
    "\n",
    "For this interactive workflow to work, we need to install several packages relating to display features, widgets and data analysis interpretation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os                                                 # to set current working directory \n",
    "from sklearn.decomposition import PCA                     # PCA program from scikit learn (package for machine learning)\n",
    "from sklearn.preprocessing import StandardScaler          # standardize variables to mean of 0.0 and variance of 1.0\n",
    "import pandas as pd                                       # DataFrames and plotting\n",
    "import pandas.plotting as pd_plot                         # matrix scatter plots\n",
    "import numpy as np                                        # arrays and matrix math\n",
    "import matplotlib.pyplot as plt                           # plotting\n",
    "from matplotlib.ticker import AutoMinorLocator            # gridlines\n",
    "from matplotlib.gridspec import GridSpec\n",
    "import seaborn as sns\n",
    "import ipywidgets as widgets\n",
    "from ipywidgets import interactive, interact              # widgets and interactivity\n",
    "from ipywidgets import widgets                            \n",
    "from ipywidgets import Layout\n",
    "from ipywidgets import Label\n",
    "import matplotlib.transforms as transforms\n",
    "import math\n",
    "from ipywidgets import VBox, HBox\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "cmap = plt.cm.inferno\n",
    "plt.rc('axes', axisbelow=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you get a package import error, you may have to first install some of these packages. This can usually be accomplished by opening up a command window on Windows and then typing 'python -m pip install [package-name]'. More assistance is available with the respective package docs.  \n",
    "\n",
    "#### Declare Functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_grid():\n",
    "    plt.gca().grid(True, which='major',linewidth = 1.0); plt.gca().grid(True, which='minor',linewidth = 0.2) # add y grids\n",
    "    plt.gca().tick_params(which='major',length=7); plt.gca().tick_params(which='minor', length=4)\n",
    "    plt.gca().xaxis.set_minor_locator(AutoMinorLocator()); plt.gca().yaxis.set_minor_locator(AutoMinorLocator()) # turn on minor ticks\n",
    "    \n",
    "def add_grid2(sub_plot):\n",
    "    sub_plot.grid(True, which='major',linewidth = 1.0); sub_plot.grid(True, which='minor',linewidth = 0.2) # add y grids\n",
    "    sub_plot.tick_params(which='major',length=7); sub_plot.tick_params(which='minor', length=4)\n",
    "    sub_plot.xaxis.set_minor_locator(AutoMinorLocator()); sub_plot.yaxis.set_minor_locator(AutoMinorLocator()) # turn on minor ticks   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Mutlivariate Dataset\n",
    "\n",
    "Let’s load multivariate dataset, from subsurface energy with 1,000 unconventional wells including:\n",
    "\n",
    "* porosity\n",
    "* log transform of permeability (to linearize the relationships with other variables)\n",
    "* accoustic impedance (kg/m^3 x m/s x 10^6)\n",
    "* brittness ratio (%)\n",
    "* total organic carbon (%)\n",
    "* vitrinite reflectance (%)\n",
    "* initial production 90 day average (MCFPD).\n",
    "* scaled production\n",
    " \n",
    "all samples have the support volume of a well (one measure per well).\n",
    " \n",
    "Note, this dataset is available on my GitHub in my [GeoDataSets](https://github.com/GeostatsGuy/GeoDataSets) repository."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 37000 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "df=pd.read_csv(r\"https://raw.githubusercontent.com/GeostatsGuy/GeoDataSets/master/unconv_MV_v2.csv\")[[\"Por\",\"TOC\"]].iloc[0:100]\n",
    "\n",
    "x = StandardScaler().fit(df).transform(df)\n",
    "plt.scatter(x[:,0],x[:,1],color='darkorange',edgecolor='black',s=10); plt.xlim([-3,3]); plt.ylim([-3,3])\n",
    "add_grid(); plt.xlabel('Standardized Porosity S[Fraction]'); plt.ylabel('Standardized TOC S[fraction]'); plt.title('Two Correlated Spatial Features')\n",
    "\n",
    "plt.subplots_adjust(left=0.0, bottom=0.0, right=0.5, top=0.7, wspace=0.1, hspace=0.1); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interactive Feature Projection with Arbitrary Rotation\n",
    "\n",
    "#### Michael Pyrcz, Professor, The University of Texas at Austin \n",
    "\n",
    "Observed the partitioning of variance and corretion over 2 new features through orthogonal projection / rotation.\n",
    "\n",
    "##### The Inputs\n",
    "\n",
    "* **Angle**: data rotation angle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 323,
   "metadata": {},
   "outputs": [],
   "source": [
    "def dashboard(Angle):\n",
    "    \n",
    "    fig = plt.figure(constrained_layout=False)\n",
    "    gs = GridSpec(2, 2, figure=fig)\n",
    "    \n",
    "    ax1 = fig.add_subplot(gs[:, 0])\n",
    "      \n",
    "    base = plt.gca().transData\n",
    "    #print(base)\n",
    "    rot = transforms.Affine2D().rotate_deg(int(Angle))\n",
    "    #line=ax16.plot(x[:,0],x[:,1], 'o', transform= rot + base, c = 'black', alpha = 0.3)\n",
    "    line=ax1.plot(norm[:,0],norm[:,1], 'o', c = 'black', alpha = 0.3)\n",
    "    \n",
    "    xdata=x[:,0]*math.cos(math.radians(int(Angle)))-x[:,1]*math.sin(math.radians(int(Angle)))\n",
    "    ydata=x[:,1]*math.cos(math.radians(int(Angle)))+x[:,0]*math.sin(math.radians(int(Angle)))\n",
    "    \n",
    "    eigen = np.zeros([2,2])\n",
    "    eigen[0,0] = math.cos(Angle*math.pi/180.0)\n",
    "    eigen[1,0] = math.sin(Angle*math.pi/180.0)\n",
    "    eigen[0,1] = -1*math.sin(Angle*math.pi/180.0)\n",
    "    eigen[1,1] = math.cos(Angle*math.pi/180.0)\n",
    "    \n",
    "    df2 = pd.DataFrame({'x':xdata, 'y':ydata})\n",
    "    data = df2.values\n",
    "    lists=[]\n",
    "    \n",
    "    ydataZeroed = np.zeros(len(ydata))\n",
    "\n",
    "    rotinv = transforms.Affine2D().rotate_deg(int(-Angle)) \n",
    "    ax1.plot(xdata, ydataZeroed,\"or\", c = 'red', alpha = 0.3,transform= rotinv + base,label=r'$C_1$')\n",
    "    ax1.plot(ydataZeroed, ydata,\"or\", c= 'blue', alpha = 0.3,transform= rotinv + base,label=r'$C_2$')\n",
    "    ax1.set_xlim(left=-3.5, right=3.5); ax1.set_ylim(bottom=-3.5, top=3.5)\n",
    "    ax1.set_title(\"Data and Arbitrary Feature Projection Components\"); ax1.set_xlabel(r'Standardized Porosity, $X_1$'); ax1.set_ylabel(r'Standardized TOC, $X_2$')\n",
    "    ax1.annotate(r'$C_1=X_1 \\cdot COS \\left(\\alpha \\cdot \\frac{180}{\\pi} \\right)-X_2 \\cdot SIN \\left(\\alpha \\cdot \\frac{180}{\\pi} \\right)$',(-3.0,-2.5)) \n",
    "    ax1.annotate(r'$C_2=X_1 \\cdot SIN \\left(\\alpha \\cdot \\frac{180}{\\pi} \\right)+X_2 \\cdot COS \\left(\\alpha \\cdot \\frac{180}{\\pi} \\right)$',(-3.0,-2.8)) \n",
    "    \n",
    "    add_grid2(ax1); ax1.legend(loc='lower right')\n",
    "    sizes = []\n",
    "    \n",
    "#     print('Your Estimated Principal Component/Eigen Vector #1 = ' + str(eigen[:,0]))\n",
    "#     print('Your Estimated Principal Component/Eigen Vector #2 = ' + str(eigen[:,1]))\n",
    "    \n",
    "    sumOfVariance=df2.var()['x']+df2.var()['y']\n",
    "    sizes.append(df2.var()['x']/sumOfVariance)\n",
    "    sizes.append(df2.var()['y']/sumOfVariance)\n",
    "    \n",
    "    ax2 = fig.add_subplot(gs[0, 1])\n",
    "    \n",
    "    n = ax2.pie(sizes, autopct='%1.1f%%',colors = ['lightcoral','royalblue'],shadow=True,startangle=90)\n",
    "    n[0][0].set_alpha(1.0); n[0][1].set_alpha(1.0)\n",
    "    ax2.axis('equal')\n",
    "    labels = [r'$\\frac{\\sigma_{C_1}^2}{\\sigma_{X_1+X_2}^2}$', r'$\\frac{\\sigma_{C_2}^2}{\\sigma_{X_1+X_2}^2}$']\n",
    "    ax2.legend(sizes, labels=labels,loc='upper left')\n",
    "    ax2.set_title('Components\\' Proportion of Variance')\n",
    "#    plt.tight_layout()\n",
    "\n",
    "\n",
    "    ax3 = fig.add_subplot(gs[1, 1])\n",
    "    nAngle = 30\n",
    "    var_pc1 = np.zeros(nAngle); var_pc2 = np.zeros(nAngle); corr = np.zeros(nAngle)\n",
    "    \n",
    "    for iAngle, lAngle in enumerate(np.linspace(0,180,nAngle)): \n",
    "        xdata=x[:,0]*math.cos(math.radians(int(lAngle)))-x[:,1]*math.sin(math.radians(int(lAngle)))\n",
    "        ydata=x[:,1]*math.cos(math.radians(int(lAngle)))+x[:,0]*math.sin(math.radians(int(lAngle)))\n",
    "        var_pc1[iAngle] = np.var(xdata); var_pc2[iAngle] = np.var(ydata) \n",
    "        corr[iAngle] = np.corrcoef(xdata,ydata)[0,1]\n",
    "\n",
    "    ax3.plot(np.linspace(0,180,nAngle),var_pc1/np.full((nAngle),2.0)-0.006,color='red',lw=2)\n",
    "    ax3.plot(np.linspace(0,180,nAngle),var_pc1/np.full((nAngle),2.0)+0.006,color='teal',lw=2)\n",
    "    #ax3.plot(np.linspace(0,180,nAngle),var_pc2/np.full((nAngle),2.0),color='blue',lw=2,label = r'Prop $\\sigma_{x_2}^2$')\n",
    "    ax3.fill_between(np.linspace(0,180,nAngle),var_pc1/np.full((nAngle),2.0),np.full((nAngle),0.0),color='red',alpha=0.4,label = r'$\\frac{\\sigma_{C_1}^2}{\\sigma_{X_1+X_2}^2}$',zorder=2)\n",
    "    ax3.fill_between(np.linspace(0,180,nAngle),np.full((nAngle),1.0),var_pc1/np.full((nAngle),2.0),color='blue',alpha=0.4,label = r'$\\frac{\\sigma_{C_2}^2}{\\sigma_{X_1+X_2}^2}$',zorder=2)\n",
    "    ax3.plot([Angle,Angle],[0,1.0],color='black',lw=3,ls='--',zorder=500)\n",
    "    \n",
    "    ax4 = ax3.twinx()\n",
    "    ax4.plot(np.linspace(0,180,nAngle),corr,color='black',lw=4,label = r'$\\rho_{C_1,C_2}$',zorder=101)\n",
    "    ax4.plot(np.linspace(0,180,nAngle),corr,color='white',lw=6,zorder=100)\n",
    "    ax4.plot([0,180],[0,0],color='black',lw=2,zorder=101)\n",
    "    ax4.plot([0,180],[0,0],color='white',lw=4,zorder=100)\n",
    "    add_grid2(ax3); plt.xlim([0,180]); ax3.set_ylabel('Proportion of Variance ($C_1$|$C_2$)')\n",
    "    ax3.set_ylim([0,1]); ax4.set_ylim([-1,1]); ax3.legend(loc='lower left'); ax4.legend(loc='lower right')\n",
    "    ax4.set_ylabel('Correlation'); ax3.set_title('Components\\' Variance Proportions and Correlation for all Angles')\n",
    "    ax3.set_xlabel(r'Rotation Angle ($\\alpha$)')\n",
    "    newYlabel = ['0.0|1.0','0.2|0.8','0.4|0.6','0.6|0.4','0.8|0.2','1.0|0.0']\n",
    "    ax3.set_yticklabels(newYlabel)\n",
    "    plt.subplots_adjust(left=0.0, bottom=0.0, right=1.8, top=1.1, wspace=0.2, hspace=0.1); plt.show()\n",
    "    \n",
    "title = widgets.Text(value='                           Understanding PCA with an Interactive Orthogonal Feature Projection, Professor Michael J. Pyrcz, The University of Texas at Austin',layout=Layout(width='950px', height='30px'))\n",
    "style = {'description_width': 'initial'}\n",
    "widget_angle = widgets.IntSlider(min=0, max = 180, value = 0, step = 5, description = r'Rotation Angle ($\\alpha$)',orientation='horizontal',continuous_update=False,layout=Layout(width='950px', height='30px'),style=style)\n",
    "uik2 = widgets.VBox([title,widget_angle],)\n",
    "interactive_plot = widgets.interactive_output(dashboard, {'Angle': widget_angle})\n",
    "interactive_plot.clear_output(wait = True)               # reduce flickering by delaying plot updating"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interactive Feature Projection with Arbitrary Rotation Angle to Explore Principal Component Analysis\n",
    "\n",
    "#### Michael Pyrcz, Professor, The University of Texas at Austin \n",
    "\n",
    "Observed the partitioning of variance over 2 new features through orthogonal projection / rotation.\n",
    "\n",
    "The Inputs: **Angle**: data rotation angle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 324,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "efcf7b33c6e74a1995cc48cd85e6ec06",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(Text(value='                           Understanding PCA with an Interactive Orthogonal Feature…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "15f7f96a8cfd4b9a90e91d43fbb45b35",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output(outputs=({'output_type': 'display_data', 'data': {'text/plain': '<Figure size 640x480 with 4 Axes>', 'i…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(uik2,interactive_plot)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Comments\n",
    "\n",
    "This was a basic demonstration of PCA as a orthogonal transformation (a rotation as we preserve the inner product space, the pairwise distances and angles between all points).\n",
    "\n",
    "* by rotating the orthogonal components in 2D we can maximize the variance explained by a single component while reducing the absolute correlation between the components to 0.0.\n",
    "\n",
    "I have other demonstrations on the basics of working with DataFrames, ndarrays, univariate statistics, plotting data, declustering, data transformations, trend modeling, multivariate analysis, inferntial and predictive machine learning, deep learning 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, Professor, The 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. Professor, Cockrell School of Engineering and 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)"
   ]
  },
  {
   "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": 4
}
