{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# TPSJUL22 Gaussian Mixture: Cluster Analysis\n\nThis notebook shows\n\n- how to get an intuition for the adjusted Rand score (the competition metric),\n- how to find the \"elbow\" in the BIC and AIC diagrams,\n- various methods for visualizing the clusters found by a Gaussian mixture model,\n- how to slice the covariance matrices so that they can be understood,\n- that only f_07..f_13 and f_22..f_28 help to distinguish the clusters,\n- some ideas for further investigation.","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import MaxNLocator\nimport seaborn as sns\n\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.decomposition import PCA\nfrom sklearn.mixture import GaussianMixture\nfrom sklearn.metrics import adjusted_rand_score","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-07-04T16:37:22.412014Z","iopub.execute_input":"2022-07-04T16:37:22.412457Z","iopub.status.idle":"2022-07-04T16:37:24.215568Z","shell.execute_reply.started":"2022-07-04T16:37:22.412367Z","shell.execute_reply":"2022-07-04T16:37:24.214320Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Read the data\ndata = pd.read_csv('../input/tabular-playground-series-jul-2022/data.csv')\ndata.drop(columns='id', inplace=True)\ndata","metadata":{"execution":{"iopub.status.busy":"2022-07-04T16:37:24.218032Z","iopub.execute_input":"2022-07-04T16:37:24.218711Z","iopub.status.idle":"2022-07-04T16:37:25.655636Z","shell.execute_reply.started":"2022-07-04T16:37:24.218678Z","shell.execute_reply":"2022-07-04T16:37:25.654418Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Scale the data\nscaled = pd.DataFrame(StandardScaler().fit_transform(data), columns=data.columns)\n#scaled = scaled[[f\"f_{i:02d}\" for i in [7, 8, 9, 10, 11, 12, 13, 22, 23, 24, 25, 26, 27, 28]]] # only useful features\n#scaled = scaled[[f\"f_{i:02d}\" for i in [0, 1, 2, 3, 4, 5, 6, 14, 15, 16, 17, 18, 19, 20, 21]]] # only useless features\n\nfloat_cols = data.columns[data.dtypes == 'float']\nint_cols = data.columns[data.dtypes == 'int']\nfloat_cols, int_cols","metadata":{"execution":{"iopub.status.busy":"2022-07-04T16:37:25.657291Z","iopub.execute_input":"2022-07-04T16:37:25.657744Z","iopub.status.idle":"2022-07-04T16:37:25.734246Z","shell.execute_reply.started":"2022-07-04T16:37:25.657703Z","shell.execute_reply":"2022-07-04T16:37:25.733193Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n# Fit several models with different numbers of components\ncomponents_min, components_max = 4, 25\n\nresult_list = []\nfor n_components in range(components_min, components_max):\n    for seed in range(10):\n        gm = GaussianMixture(n_components=n_components, random_state=seed, verbose=0, n_init=1)\n        y = gm.fit_predict(scaled)\n        bic = gm.bic(scaled)\n        aic = gm.aic(scaled)\n        #print(f\"{n_components:2} {bic:16.5f} {aic:16.5f}\")\n        result_list.append((n_components, seed, bic, aic, y, gm))\n\nresults = pd.DataFrame(result_list, columns=['n_components', 'seed', 'bic', 'aic', 'y', 'gm'])\nresults = results.set_index(['n_components', 'seed'])","metadata":{"execution":{"iopub.status.busy":"2022-07-04T18:34:59.611066Z","iopub.execute_input":"2022-07-04T18:34:59.611684Z","iopub.status.idle":"2022-07-04T18:39:11.581246Z","shell.execute_reply.started":"2022-07-04T18:34:59.611636Z","shell.execute_reply":"2022-07-04T18:39:11.579801Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Getting an intuition for the adjusted Rand score\n\nThe Rand score (or Rand index) is a similarity measure for clusterings. If we compare two equivalent clusterings, the [adjusted Rand index](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.adjusted_rand_score.html) is 1.0:","metadata":{}},{"cell_type":"code","source":"def compare_clusterings(y1, y2, title=''):\n    \"\"\"Show the adjusted rand score and plot the two clusterings in color\"\"\"\n    ars = adjusted_rand_score(y1, y2)\n    n1 = y1.max() + 1\n    n2 = y2.max() + 1\n    argsort = np.argsort(y1*100 + y2) if n1 >= n2 else np.argsort(y2*100 + y1)\n    plt.figure(figsize=(16, 0.5))\n    for i in range(6, 11):\n        plt.scatter(np.arange(len(y1)), np.full_like(y1, i), c=y1[argsort], s=1, cmap='tab10')\n    for i in range(5):\n        plt.scatter(np.arange(len(y2)), np.full_like(y2, i), c=y2[argsort], s=1, cmap='tab10')\n    plt.gca().axis('off')\n    plt.title(f'{title}\\nAdjusted Rand score: {ars:.5f}')\n    plt.savefig(title + '.png', bbox_inches='tight')\n    plt.show()\n    \ncompare_clusterings(results.loc[8, 0].y, 7 - results.loc[8, 0].y, '8 clusters vs. relabeling')\n","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:08:12.052530Z","iopub.execute_input":"2022-07-04T19:08:12.052970Z","iopub.status.idle":"2022-07-04T19:08:41.721019Z","shell.execute_reply.started":"2022-07-04T19:08:12.052933Z","shell.execute_reply":"2022-07-04T19:08:41.719778Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If we compare a clustering with a random (independent) clustering, the adjusted Rand index is near 0:","metadata":{}},{"cell_type":"code","source":"rng = np.random.default_rng()\ncompare_clusterings(results.loc[8, 0].y, rng.integers(0, 3, len(data)), '8 clusters vs. 3 random clusters')\ncompare_clusterings(results.loc[8, 0].y, rng.integers(0, 7, len(data)), '8 clusters vs. 7 random clusters')","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:09:18.728034Z","iopub.execute_input":"2022-07-04T19:09:18.729216Z","iopub.status.idle":"2022-07-04T19:10:17.539853Z","shell.execute_reply.started":"2022-07-04T19:09:18.729166Z","shell.execute_reply":"2022-07-04T19:10:17.539029Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If we take an 8-component clustering and compare it with a 7-component clustering where two of the original 8 clusters are merged, we get an adjusted Rand index between 0.8 and 0.9:","metadata":{}},{"cell_type":"code","source":"compare_clusterings(results.loc[8, 0].y, results.loc[8, 0].y.clip(0, 6), '8 clusters vs. two of them merged')","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:10:17.541515Z","iopub.execute_input":"2022-07-04T19:10:17.542411Z","iopub.status.idle":"2022-07-04T19:10:47.253621Z","shell.execute_reply.started":"2022-07-04T19:10:17.542373Z","shell.execute_reply":"2022-07-04T19:10:47.252416Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If we compare the output of our Gaussian mixture model fitted with different seeds, interesting things happen: With seven clusters, seeds 0 and 2 give almost the same output, with a similarity of 0.998. The outputs for seeds 0 and 1 differ much more, with a similarity of 0.669. With eight clusters, seeds 1 and 2 give very similar clusterings:","metadata":{}},{"cell_type":"code","source":"compare_clusterings(results.loc[7, 0].y, results.loc[7, 2].y, '7 clusters, seeds 0 and 2')\ncompare_clusterings(results.loc[7, 0].y, results.loc[7, 1].y, '7 clusters, seeds 0 and 1')\ncompare_clusterings(results.loc[8, 1].y, results.loc[8, 2].y, '8 clusters, seeds 1 and 2')\ncompare_clusterings(results.loc[8, 0].y, results.loc[8, 1].y, '8 clusters, seeds 0 and 1')\ncompare_clusterings(results.loc[8, 0].y, results.loc[8, 2].y, '8 clusters, seeds 0 and 2')\n","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:10:47.254980Z","iopub.execute_input":"2022-07-04T19:10:47.255310Z","iopub.status.idle":"2022-07-04T19:13:15.780252Z","shell.execute_reply.started":"2022-07-04T19:10:47.255280Z","shell.execute_reply":"2022-07-04T19:13:15.778956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If we compare 7-component clusterings to 8-component clusterings, we get adjusted Rand scores between 0.69 and 0.85.","metadata":{}},{"cell_type":"code","source":"compare_clusterings(results.loc[7, 1].y, results.loc[8, 1].y, '7 clusters vs. 8 clusters')\ncompare_clusterings(results.loc[7, 0].y, results.loc[8, 0].y, '7 clusters vs. 8 clusters')\ncompare_clusterings(results.loc[7, 1].y, results.loc[8, 2].y, '7 clusters vs. 8 clusters')\ncompare_clusterings(results.loc[7, 0].y, results.loc[8, 2].y, '7 clusters vs. 8 clusters')","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:39:55.645283Z","iopub.execute_input":"2022-07-04T19:39:55.646038Z","iopub.status.idle":"2022-07-04T19:40:54.674702Z","shell.execute_reply.started":"2022-07-04T19:39:55.645994Z","shell.execute_reply":"2022-07-04T19:40:54.673434Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Even if the true clustering has 7 components and we submit a 10-component clustering (or vice versa), we can hope for a score of 0.65:","metadata":{}},{"cell_type":"code","source":"compare_clusterings(results.loc[7, 0].y, results.loc[10, 1].y, '7 clusters vs. 10 clusters')\ncompare_clusterings(results.loc[7, 1].y, results.loc[10, 2].y, '7 clusters vs. 10 clusters')\ncompare_clusterings(results.loc[7, 2].y, results.loc[10, 0].y, '7 clusters vs. 10 clusters')\n","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:33:00.794248Z","iopub.execute_input":"2022-07-04T19:33:00.794572Z","iopub.status.idle":"2022-07-04T19:33:30.458608Z","shell.execute_reply.started":"2022-07-04T19:33:00.794544Z","shell.execute_reply":"2022-07-04T19:33:30.457395Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Elbow diagrams for BIC and AIC\n\nGaussianMixture provides two metrics, the Bayesian information criterion (BIC) and the Akaike information criterion (AIC), to evaluate models. Lower scores are better.\n\nIt is said that one should plot these scores as a function of the number of clusters (`n_components`) and then select the \"elbow point\" where the line starts being flat. In the following, we plot the diagrams for BIC and AIC (because I don't know whether BIC or AIC is better). \n\nDecide for yourself: Can you see an elbow point? Where is it? How many components does the optimal clustering have?","metadata":{}},{"cell_type":"code","source":"# BIC diagram\nplt.figure(figsize=(16, 5))\nplt.scatter(results.reset_index().n_components, results.bic)\nm = results.reset_index().groupby('n_components').bic.min()\nplt.plot(m.index, m)\nplt.gca().xaxis.set_major_locator(MaxNLocator(integer=True))\nplt.ylabel('BIC score')\nplt.xlabel('Number of clusters')\nplt.title('BIC: Can you see an elbow? Is it really at 7 components?', fontsize=20)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:16:43.388809Z","iopub.execute_input":"2022-07-04T19:16:43.389199Z","iopub.status.idle":"2022-07-04T19:16:43.607688Z","shell.execute_reply.started":"2022-07-04T19:16:43.389167Z","shell.execute_reply":"2022-07-04T19:16:43.606449Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# AIC diagram\nplt.figure(figsize=(16, 5))\nplt.scatter(results.reset_index().n_components, results.aic)\nm = results.reset_index().groupby('n_components').aic.min()\nplt.plot(m.index, m)\nplt.gca().xaxis.set_major_locator(MaxNLocator(integer=True))\nplt.ylabel('AIC score')\nplt.xlabel('Number of clusters')\nplt.title('AIC: Can you see an elbow? Is it really at 7 components?', fontsize=20)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:16:43.609123Z","iopub.execute_input":"2022-07-04T19:16:43.609445Z","iopub.status.idle":"2022-07-04T19:16:43.819763Z","shell.execute_reply.started":"2022-07-04T19:16:43.609417Z","shell.execute_reply":"2022-07-04T19:16:43.818901Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Select the best model with 7 components for visualizing\ngm = results.loc[7, 1].gm\ny = results.loc[7, 1].y\nn_clusters = len(gm.means_)\n","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:16:43.820985Z","iopub.execute_input":"2022-07-04T19:16:43.822045Z","iopub.status.idle":"2022-07-04T19:16:43.828942Z","shell.execute_reply.started":"2022-07-04T19:16:43.822002Z","shell.execute_reply":"2022-07-04T19:16:43.828178Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Histograms\n\n## Float feature histograms\n\nWe can plot cluster-wise histograms of each of the 22 float features. These histograms show that the float features f_00 through f_21 do not help to distinguish the clusters: All clusters have mean 0 and standard deviation 1. We can (or even should?) drop these features from the data. Only the last seven features, f_22 through f_28, are useful.","metadata":{}},{"cell_type":"code","source":"fig, axs = plt.subplots(6, 4, figsize=(16, 20))\naxs = axs.ravel()\nfloat_columns = [col for col in data.columns if data[col].dtype == 'float']\nfor ax, f in zip(axs, float_columns):\n    for i in range(n_clusters):\n        h, edges = np.histogram(data[f][y == i], bins=np.linspace(-5, 5, 26))\n        ax.plot((edges[:-1] + edges[1:]) / 2, h, label=f\"Cluster {i}\", lw=3)\n    ax.set_title(f)\naxs[-2].axis('off')\naxs[-1].axis('off')\nplt.suptitle('Histograms of the float features of the 7 clusters', y=0.95, fontsize=20)\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2022-07-04T19:16:53.886601Z","iopub.execute_input":"2022-07-04T19:16:53.887341Z","iopub.status.idle":"2022-07-04T19:16:56.809153Z","shell.execute_reply.started":"2022-07-04T19:16:53.887298Z","shell.execute_reply":"2022-07-04T19:16:56.807783Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Integer feature histograms\nWe can plot cluster-wise histograms of each of the seven integer features. These histograms show that\n1. All seven int features help to distinguish the clusters\n2. The distributions are not Gaussian. Certainly a Poisson mixture model would give better clusters, but unfortunately scikit-learn knows only Gaussian mixture models. Who is capable of programming a Poisson mixture model? Some public notebooks use a `PowerTransformer` to make the distributions more Gaussian-like, and the lb scores of this workaround look quite promising.","metadata":{}},{"cell_type":"code","source":"prop_cycle = plt.rcParams['axes.prop_cycle']\n\nfig, axs = plt.subplots(4, 2, figsize=(16, 14))\naxs = axs.ravel()\nint_columns = [col for col in data.columns if data[col].dtype == 'int']\nfor ax, f in zip(axs, int_columns):\n    for i in range(n_clusters):\n        uv, uc = np.unique(data[f][y == i], return_counts=True)\n        ax.plot(uv, uc, alpha=1, color=prop_cycle.by_key()['color'][i % 10], lw=3)\n    ax.set_title(f)\n    #ax.legend()\naxs[-1].axis('off')\nplt.suptitle('Histograms of the int features of the 7 clusters', y=0.95, fontsize=20)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-03T19:39:12.903255Z","iopub.execute_input":"2022-07-03T19:39:12.90365Z","iopub.status.idle":"2022-07-03T19:39:13.700696Z","shell.execute_reply.started":"2022-07-03T19:39:12.90362Z","shell.execute_reply":"2022-07-03T19:39:13.699063Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 2D projections\n\n## PCA\n\nWe can project the data to its first two PCA dimensions and color the clusters. The plot shows that indeed there are seven distinguishable clusters, but it doesn't show much more.","metadata":{}},{"cell_type":"code","source":"# Compute the PCA\npca = PCA(n_components=3)\np = pca.fit_transform(scaled)","metadata":{"execution":{"iopub.status.busy":"2022-07-03T11:15:30.996911Z","iopub.execute_input":"2022-07-03T11:15:30.997356Z","iopub.status.idle":"2022-07-03T11:15:31.576462Z","shell.execute_reply.started":"2022-07-03T11:15:30.997316Z","shell.execute_reply":"2022-07-03T11:15:31.57504Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# PCA projection, random drawing order of points\nc = [prop_cycle.by_key()['color'][i % 10] for i in y]\n\nplt.figure(figsize=(8, 8))\nplt.scatter(p[:,0], p[:,1], s=1, label=f\"Cluster {i}\", c=c)\nplt.xlabel('PCA[0]')\nplt.ylabel('PCA[1]')\nplt.legend()\nplt.title('PCA projection')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-03T11:15:31.580184Z","iopub.execute_input":"2022-07-03T11:15:31.581694Z","iopub.status.idle":"2022-07-03T11:15:34.746251Z","shell.execute_reply.started":"2022-07-03T11:15:31.581639Z","shell.execute_reply":"2022-07-03T11:15:34.74507Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# PCA projection, pink cluster in front\nplt.figure(figsize=(8, 8))\nfor i in range(n_clusters):\n    plt.scatter(p[:,0][y == i], p[:,1][y == i], s=1, label=f\"Cluster {i}\")\nplt.xlabel('PCA[0]')\nplt.ylabel('PCA[1]')\nplt.legend()\nplt.title('PCA projection')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-03T11:15:34.747882Z","iopub.execute_input":"2022-07-03T11:15:34.748361Z","iopub.status.idle":"2022-07-03T11:15:36.440285Z","shell.execute_reply.started":"2022-07-03T11:15:34.748327Z","shell.execute_reply":"2022-07-03T11:15:36.439409Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Projection to two features\n\nWe don't need a PCA to project to two dimensions: The data can be projected to any two features. The diagram shows that some clusters have strange shapes. Do the strange shapes mean that the clustering is bad? Do we need more than seven clusters? These diagrams have the potential for more discoveries!","metadata":{}},{"cell_type":"code","source":"# Projections of clusters to feature pairs\n# We see several strange shapes. The strange shapes suggest that we haven't yet found the best clustering.\nfor f, g in [(f\"f_{i:02d}\", f\"f_{j:02d}\") for i in range(22, 29) for j in range(i+1, 29)]:\n    fig, axs = plt.subplots(1, n_clusters, figsize=(16, 5), sharex=True, sharey=True)\n    for i in range(n_clusters):\n        axs[i].scatter(data[f][y == i], data[g][y == i], s=1, label=f\"Cluster {i}\", color=prop_cycle.by_key()['color'][i % 10])\n        axs[i].set_xlabel(f)\n        axs[i].set_aspect('equal')\n    axs[0].set_ylabel(g)\n    if f == 'f_24' and g == 'f_25': plt.savefig(f'{f}_{g}.png')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-03T11:15:36.4414Z","iopub.execute_input":"2022-07-03T11:15:36.442065Z","iopub.status.idle":"2022-07-03T11:15:53.780119Z","shell.execute_reply.started":"2022-07-03T11:15:36.442031Z","shell.execute_reply":"2022-07-03T11:15:53.778988Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Cluster centers\n\nWe can look at the data from a completely other angle. The following diagram shows the seven cluster centers for every feature. We see again that for f_00 through f_06 and f_14 through f_21 all clusters have mean 0 and cannot be distinguished. The diagram shows as well that for f_09, the blue cluster has the smallest mean and the red cluster the largest mean - this matches what can be seen in the f_09 histogram above.","metadata":{}},{"cell_type":"code","source":"# Cluster means for every feature\n# Features where all cluster means coincide tend to be useless\nplt.figure(figsize=(16, 4))\nfor i in range(gm.means_.shape[0]):\n    plt.scatter(np.arange(scaled.shape[1]), gm.means_[i])\nplt.xticks(ticks=np.arange(scaled.shape[1]), labels=scaled.columns)\nplt.title('Cluster means')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-03T11:15:53.781489Z","iopub.execute_input":"2022-07-03T11:15:53.782417Z","iopub.status.idle":"2022-07-03T11:15:54.268541Z","shell.execute_reply.started":"2022-07-03T11:15:53.782384Z","shell.execute_reply":"2022-07-03T11:15:54.267386Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Covariance matrices\n\nThe covariance data is a three-dimensional array. To understand it, we have to slice the tensor and plot several 2d heatmaps. We can either show\n- a covariance heatmap per cluster, or\n- a covariance heatmap per feature.\n\nThe covariance matrix for every cluster is symmetrical. This symmetry appears in the slices as well.\n\nThe useless features have variance 1 and covariance 0 for every cluster.\n\nThe useful features can be grouped into two almost independent groups:\n- The integer features f_07 through f_13 have nonzero covariances.\n- The float feature f_22 through f_28 have nonzero covariances.","metadata":{}},{"cell_type":"code","source":"# A covariance heatmap per cluster\nfor i in range(len(gm.covariances_)):\n    print(f'Cluster {i}')\n    plt.figure(figsize=(16, 16))\n    sns.heatmap(gm.covariances_[i], annot=True, fmt='.1f', center=0)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-03T11:15:54.270178Z","iopub.execute_input":"2022-07-03T11:15:54.27092Z","iopub.status.idle":"2022-07-03T11:16:17.033495Z","shell.execute_reply.started":"2022-07-03T11:15:54.270886Z","shell.execute_reply":"2022-07-03T11:16:17.032259Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# A covariance heatmap per feature\nfor i in range(scaled.shape[1]):\n    print(f\"Covariances of {scaled.columns[i]}\")\n    plt.figure(figsize=(16, 4))\n    sns.heatmap(gm.covariances_[:, i], annot=True, fmt='.1f', center=0,\n                xticklabels=scaled.columns, yticklabels=[f\"Cluster {j}\" for j in np.arange(gm.covariances_.shape[0])])\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-07-03T11:16:17.034921Z","iopub.execute_input":"2022-07-03T11:16:17.035342Z","iopub.status.idle":"2022-07-03T11:16:48.367165Z","shell.execute_reply.started":"2022-07-03T11:16:17.035306Z","shell.execute_reply":"2022-07-03T11:16:48.366009Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}