Skip to content

Instantly share code, notes, and snippets.

@CarstenSchelp
Last active June 22, 2024 14:24
Show Gist options
  • Save CarstenSchelp/b992645537660bda692f218b562d0712 to your computer and use it in GitHub Desktop.
Save CarstenSchelp/b992645537660bda692f218b562d0712 to your computer and use it in GitHub Desktop.
A function to plot the confidence ellipse of the covariance of a 2D dataset. Uses matplotlib.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import matplotlib.transforms as transforms
def confidence_ellipse(x, y, ax, n_std=3.0, facecolor='none', **kwargs):
"""
Create a plot of the covariance confidence ellipse of `x` and `y`
See how and why this works: https://carstenschelp.github.io/2018/09/14/Plot_Confidence_Ellipse_001.html
This function has made it into the matplotlib examples collection:
https://matplotlib.org/devdocs/gallery/statistics/confidence_ellipse.html#sphx-glr-gallery-statistics-confidence-ellipse-py
Or, once matplotlib 3.1 has been released:
https://matplotlib.org/gallery/index.html#statistics
I update this gist according to the version there, because thanks to the matplotlib community
the code has improved quite a bit.
Parameters
----------
x, y : array_like, shape (n, )
Input data.
ax : matplotlib.axes.Axes
The axes object to draw the ellipse into.
n_std : float
The number of standard deviations to determine the ellipse's radiuses.
Returns
-------
matplotlib.patches.Ellipse
Other parameters
----------------
kwargs : `~matplotlib.patches.Patch` properties
"""
if x.size != y.size:
raise ValueError("x and y must be the same size")
cov = np.cov(x, y)
pearson = cov[0, 1]/np.sqrt(cov[0, 0] * cov[1, 1])
# Using a special case to obtain the eigenvalues of this
# two-dimensionl dataset.
ell_radius_x = np.sqrt(1 + pearson)
ell_radius_y = np.sqrt(1 - pearson)
ellipse = Ellipse((0, 0),
width=ell_radius_x * 2,
height=ell_radius_y * 2,
facecolor=facecolor,
**kwargs)
# Calculating the stdandard deviation of x from
# the squareroot of the variance and multiplying
# with the given number of standard deviations.
scale_x = np.sqrt(cov[0, 0]) * n_std
mean_x = np.mean(x)
# calculating the stdandard deviation of y ...
scale_y = np.sqrt(cov[1, 1]) * n_std
mean_y = np.mean(y)
transf = transforms.Affine2D() \
.rotate_deg(45) \
.scale(scale_x, scale_y) \
.translate(mean_x, mean_y)
ellipse.set_transform(transf + ax.transData)
return ax.add_patch(ellipse)
# render plot with "plt.show()".
@jensweiser
Copy link

Hi @jensweiser,

That was quite easy. Obviously, I cannot get a DOI for a gist, but the repo that feeds the github.io page has a release with a zenodo DOI, now. The file you want to refer to would be _posts/2018-09-14-Plot_Confidence_Ellipse_001.md Here's the data of the DOI:

Markdown: [![DOI](https://zenodo.org/badge/138745361.svg)](https://zenodo.org/badge/latestdoi/138745361)

reStructedText:

.. image:: https://zenodo.org/badge/138745361.svg
   :target: https://zenodo.org/badge/latestdoi/138745361

or as HTML: <a href="https://zenodo.org/badge/latestdoi/138745361"><img src="https://zenodo.org/badge/138745361.svg" alt="DOI"></a>

....fantastic, thanks a lot!

@CarstenSchelp
Copy link
Author

CarstenSchelp commented Apr 8, 2022 via email

@csaquino2
Copy link

Do you think is it possible to apply a variant of this to 3D coordinates (xyz)?

@camillefrayssinhes
Copy link

Hi!
I tested the gist to get the values of the radii and it doesn't seem to give the good values. I spent a bit of time trying to do some debugging but I can't figure out the problem! Did anyone have the same problem?

@Antoine-DupuyUL
Copy link

Hello Carsten,

Firstly, thank you for sharing this script and for all the information concerning it.
I was wondering if it is possible to extract the angle of the ellipse (either in radians or degrees).
I have already the radii and the width (minor and major). It would be awesome if I was able to get the "tilt" / the angle of the ellipse!

Thanks a million.

Best regards,
Antoine Dupuy.

@Antoine-DupuyUL
Copy link

Hi! I tested the gist to get the values of the radii and it doesn't seem to give the good values. I spent a bit of time trying to do some debugging but I can't figure out the problem! Did anyone have the same problem?

If you are taking the "ell_radius_x" & "ell_radius_x" directly, you will find weird results. That is because they are not scaled to your data yet.
Try with this:
scaled_ell_x_radius = np.sqrt(cov[0, 0]) * n_std
scaled_ell_y_radius = np.sqrt(cov[1, 1]) * n_std

There should be more logical results!

@ktevans
Copy link

ktevans commented May 3, 2024

Hello Carsten,

Firstly, thank you for sharing this script and for all the information concerning it. I was wondering if it is possible to extract the angle of the ellipse (either in radians or degrees). I have already the radii and the width (minor and major). It would be awesome if I was able to get the "tilt" / the angle of the ellipse!

Thanks a million.

Best regards, Antoine Dupuy.

This is also exactly what I am trying to get! Still working on it, but if anyone has any insight, I'd appreciate it!

@CarstenSchelp
Copy link
Author

CarstenSchelp commented May 4, 2024

Do you think is it possible to apply a variant of this to 3D coordinates (xyz)?

Guess what I have been banging my head against, the last six years :D
It's tricky, to say the least. 2D is just such a nice special case.
Don't hold your breath, but stubborn as I am, I keep trying.
Which is bold, because I think that people with profoundly deeper math-chops than I have tried this, too.
Kind regards, Carsten

@CarstenSchelp
Copy link
Author

Hello Carsten,
Firstly, thank you for sharing this script and for all the information concerning it. I was wondering if it is possible to extract the angle of the ellipse (either in radians or degrees). I have already the radii and the width (minor and major). It would be awesome if I was able to get the "tilt" / the angle of the ellipse!
Thanks a million.
Best regards, Antoine Dupuy.

This is also exactly what I am trying to get! Still working on it, but if anyone has any insight, I'd appreciate it!

Hi @ktevans and hi @Antoine-DupuyUL,

(And sorry Antoine, your question from months ago must have gotten lost in the shuffle).
I am trying something from top of my head, here. I am not at home with not enough time.

In a normalized case, that is, when the standard deviations of both variables are the same, the angle of the ellipse will be

arctan(std1/std2) = arctan(1) = pi/4 = 45degrees.

In the article I also mention that any deviation from 45degrees only comes forth from different scaling of the variables.
So you might give it a shot to determine the angle by

arctan(std1/std2) = angle in rad

std1 and std2 would be the "original", non-normalized standard deviations.
This will alsways be an "upward" angle.
A seemingly "downward" angle that you see when the pearson coefficient is negative is only due to the "second" radius being longer than the "first" radius.
Does this work for you?

Kind regards, Carsten

@ktevans
Copy link

ktevans commented May 6, 2024

Hello Carsten,
Firstly, thank you for sharing this script and for all the information concerning it. I was wondering if it is possible to extract the angle of the ellipse (either in radians or degrees). I have already the radii and the width (minor and major). It would be awesome if I was able to get the "tilt" / the angle of the ellipse!
Thanks a million.
Best regards, Antoine Dupuy.

This is also exactly what I am trying to get! Still working on it, but if anyone has any insight, I'd appreciate it!

Hi @ktevans and hi @Antoine-DupuyUL,

(And sorry Antoine, your question from months ago must have gotten lost in the shuffle). I am trying something from top of my head, here. I am not at home with not enough time.

In a normalized case, that is, when the standard deviations of both variables are the same, the angle of the ellipse will be

arctan(std1/std2) = arctan(1) = pi/4 = 45degrees.

In the article I also mention that any deviation from 45degrees only comes forth from different scaling of the variables. So you might give it a shot to determine the angle by

arctan(std1/std2) = angle in rad

std1 and std2 would be the "original", non-normalized standard deviations. This will alsways be an "upward" angle. A seemingly "downward" angle that you see when the pearson coefficient is negative is only due to the "second" radius being longer than the "first" radius. Does this work for you?

Kind regards, Carsten

Hi Carsten,

Thank you for this code. It has been of great help to me! I was able to get the eccentricity of the ellipse using this method!

I defined a simple correlation variable via the following:

corr = 0
if pearson > 0:
  corr = 1
if pearson < 0:
  corr = -1

Then, the eccentricity is just:
ecc = corr * (np.std(y) / np.std(x))

I got the following results where the slope in the legend is the calculated eccentricity:

Screen Shot 2024-05-06 at 12 50 44 PM Screen Shot 2024-05-06 at 12 51 00 PM

Thank you again! This was exactly what I needed!

@CarstenSchelp
Copy link
Author

[...] Thank you again! This was exactly what I needed!

Hi @ktevans, This is great. You turned it into your own thing.
You didn't even need the arctan(), after all. The slope was all you needed.
With the sign fixed, then.
I am pleased to see that this was useful.
Kind regards, Carsten

@Antoine-DupuyUL
Copy link

Antoine-DupuyUL commented May 10, 2024

Hello Carsten,
Firstly, thank you for sharing this script and for all the information concerning it. I was wondering if it is possible to extract the angle of the ellipse (either in radians or degrees). I have already the radii and the width (minor and major). It would be awesome if I was able to get the "tilt" / the angle of the ellipse!
Thanks a million.
Best regards, Antoine Dupuy.

This is also exactly what I am trying to get! Still working on it, but if anyone has any insight, I'd appreciate it!

Hi @ktevans and hi @Antoine-DupuyUL,

(And sorry Antoine, your question from months ago must have gotten lost in the shuffle). I am trying something from top of my head, here. I am not at home with not enough time.

In a normalized case, that is, when the standard deviations of both variables are the same, the angle of the ellipse will be

arctan(std1/std2) = arctan(1) = pi/4 = 45degrees.

In the article I also mention that any deviation from 45degrees only comes forth from different scaling of the variables. So you might give it a shot to determine the angle by

arctan(std1/std2) = angle in rad

std1 and std2 would be the "original", non-normalized standard deviations. This will alsways be an "upward" angle. A seemingly "downward" angle that you see when the pearson coefficient is negative is only due to the "second" radius being longer than the "first" radius. Does this work for you?

Kind regards, Carsten

Hello Carsten,

Thanks a million for your help and all the explanation!
It works perfectly 🙏

Again, thank you for making this code available.

Best regards,
Antoine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment