Elementos auxiliares (Elipse)
Para desenhar uma elipse utilizando o matplotlib, podemos utilizar o patch Ellipse. Este patch precisa de pelo menos três parâmetros:
-
xy: umatuplecom dois números (intoufloat) com as coordenadas do centro da elipse; -
width: um número (intoufloat) com o comprimento da largura da elipse; -
height: um número (intoufloat) com o comprimento da altura da elipse;
Por exemplo:
plt.figure(figsize=(8,6))
plt.scatter(x,y)
plt.gca().add_patch(patches.Ellipse(xy=(2,2), width=1, height=2))
plt.show()
Figura 1 - Gráfico de dispersão com uma elipse.

Observe que, mesmo se width = height, não teremos um círculo perfeito como deveria acontecer:
plt.figure(figsize=(8,6))
plt.scatter(x,y)
plt.gca().add_patch(patches.Ellipse(xy=(2,2), width=1, height=1))
plt.show()
Figura 2 - Gráfico de dispersão com uma elipse.

Isto novamente ocorre devido a proporção do eixo x em relação ao eixo y. Para desenhar um circulo perfeito com a elipse, temos as opções de utilizar eixos com o mesmo tamanho, utilizar o plt.axis("equal"), como vimos anteriormente, ou encontrar um valor de proporção para o circulo.
Neste caso, temos que o eixo x tem 8 polegadas, e o eixo y tem 6 polegadas (figsize=(8,6)). Dessa forma, a relação x/y é 8/6 = 4/3. Sendo assim, o eixo x é 4/3 mais longo que o eixo y. Então baseado nesta informação, podemos relativizar os valores de widht e height.
Por exemplo, vamos desenhar um círculo com diâmetro de 1.0, que seja proporcional ao tamanho do gráfico.
Para isto, vou criar a variável diametro:
diametro = 1
Como o gráfico tem proporção de 4(width)/3(height), e foi definido diametro = 1, precisamos definir em relação a qual eixo o diâmetro será efetivamente igual a 1. Vamos supor que seja no eixo x, que é o width. Logo, o eixo y, que é o height, deve ser 4/3 o valor de diâmetro. Dessa forma, conseguimos desenhar um círculo com aparência perfeita:
plt.figure(figsize=(8,6))
plt.scatter(x,y)
plt.gca().add_patch(patches.Ellipse(xy=(2,2), width=diametro, height=diametro*4/3))
plt.show()
Figura 3 - Gráfico de dispersão com um circulo feito com uma elipse.

Observe que, efetivamente, o tamanho do círculo no eixo x é maior do que o tamanho do círculo no eixo y. Mas como o gráfico também tem esta característica (figsize=(8,6)), visualmente temos a impressão de um círculo perfeito.
Para ficar mais óbvio, observe as linhas do grid:
plt.figure(figsize=(8,6))
plt.scatter(x,y)
plt.gca().add_patch(patches.Ellipse(xy=(2,2), width=diametro, height=diametro*4/3))
plt.grid()
plt.show()
Figura 4 - Gráfico de dispersão com um circulo feito com uma elipse.

Caso queira desenhar um circulo com height = 1, basta apenas inverter a lógica:
plt.figure(figsize=(8,6))
plt.scatter(x,y)
plt.gca().add_patch(patches.Ellipse(xy=(2,2), width=diametro*3/4, height=diametro))
plt.show()
Figura 5 - Gráfico de dispersão com um circulo feito com uma elipse.

Observe agora que, efetivamente, o tamanho do círculo no eixo y é maior do que o tamanho do círculo no eixo x. Mas como o gráfico também tem esta característica, visualmente temos a impressão de um círculo perfeito.
Para ficar mais óbvio, conte as linhas do grid:
plt.figure(figsize=(8,6))
plt.scatter(x,y)
plt.gca().add_patch(patches.Ellipse(xy=(2,2), width=diametro*3/4, height=diametro))
plt.grid()
plt.show()
Figura 6 - Gráfico de dispersão com um circulo feito com uma elipse.

Rotação
De forma análoga ao que vimos ao estudar a inserção do Rectangle, podemos rotacionar a elipse, passando o ângulo desejado ( int ou float) para o parâmetro angle. Por exemplo:
plt.figure(figsize=(8,6))
plt.scatter(x,y)
plt.gca().add_patch(patches.Ellipse(xy=(2,2), width=1, height=2, angle=45))
plt.show()
Figura 7 - Gráfico de dispersão com um circulo feito com uma elipse.

Edições
O patches.Ellipse aceita uma série de parâmetros para a sua edição, sendo possível alterar a cor de preenchimento (color ou facecolor), remover o preenchimento (fill), inserir linhas (linestyle, linewidth, edgecolor), inserir estilos de preenchimento (hatch), adicionar transparência (alpha), determinar a ordem de plotagem (zorder), inserir nome para legenda (label), entre outros, de forma análoga ao que temos feito.
Você encontra maiores detalhes na documentação.