Clase 11: Graficación usando Plotly

Plotly es una biblioteca similar a Matplotlib. Su origen es una biblioteca en JavaScript para utilizar en el desarrollo de aplicaciones web, pero con el tiempo fue extendida con distintos bindings para R, Julia, y, por supuesto, Python.

Una de las características de Plotly es que tiene un mayor grado nativo de interactividad que Matplotlib.

Plotly viene en dos sabores: - plotly.express, es una biblioteca orientada a trabajar con datos con algún tipo de formato (típicamente csv), y está orientada a poder usarse con pandas que es una biblioteca para procesar datos. pandas puede leer fácilmente tablas de datos, que se pueden graficar eficientemente con plotly.express. - graph_objects, es el submódulo para graficar más similar a Matplotlib.

Finalmente, verán en la documentación referencias a Dash, que es otra biblioteca que permite directamente publicar gráficos en un servicio en la nube, que, por supuesto, provee la companía que produce Plotly.

Sin embargo, Plotly es completamente libre.

Plotly se instala usando pip:

pip install plotly==5.6.0

o conda:

conda install -c plotly plotly=5.6.0

import plotly.graph_objects as go
import numpy as np
print(np.__version__)
1.26.4

Definimos un conjunto de 51 datos equiespaciados entre 0 y 4, y la función

\[y = f(x) = 2.5 e^{-1.3 x} + 0.5. e^{-1.6 x}\]

Además, definimos un conjunto de datos con ruido, sumando a \(y\) un ruido gaussiano de media 0 y dispersión 1 (ver help(np.random.normal)):

x = np.linspace(0,4,51)
y = 2.5 * np.exp(-1.3 * x) + 0.5 * np.exp(-1.6 * x)
ruido = 0.2 * np.random.normal(size=x.size)
medicion = y + ruido

print(x.size)
51
fig = go.Figure()

fig.add_trace(go.Scatter(x=x,y=y))
fig.show() # Se muestra el gráfico en la pantalla
print(fig)
Figure({
    'data': [{'type': 'scatter',
              'x': array([0.  , 0.08, 0.16, 0.24, 0.32, 0.4 , 0.48, 0.56, 0.64, 0.72, 0.8 , 0.88,
                          0.96, 1.04, 1.12, 1.2 , 1.28, 1.36, 1.44, 1.52, 1.6 , 1.68, 1.76, 1.84,
                          1.92, 2.  , 2.08, 2.16, 2.24, 2.32, 2.4 , 2.48, 2.56, 2.64, 2.72, 2.8 ,
                          2.88, 2.96, 3.04, 3.12, 3.2 , 3.28, 3.36, 3.44, 3.52, 3.6 , 3.68, 3.76,
                          3.84, 3.92, 4.  ]),
              'y': array([3.        , 2.69298993, 2.41758858, 2.17051953, 1.94884857, 1.74994758,
                          1.5714624 , 1.41128403, 1.26752287, 1.13848575, 1.02265536, 0.91867178,
                          0.82531612, 0.74149572, 0.66623099, 0.59864366, 0.53794617, 0.48343229,
                          0.43446861, 0.39048702, 0.3509779 , 0.31548409, 0.28359542, 0.25494386,
                          0.22919919, 0.20606505, 0.18527545, 0.16659167, 0.14979943, 0.13470634,
                          0.12113972, 0.1089445 , 0.09798146, 0.08812556, 0.07926454, 0.07129757,
                          0.06413406, 0.05769268, 0.05190035, 0.04669142, 0.04200691, 0.03779383,
                          0.03400457, 0.03059634, 0.0275307 , 0.02477309, 0.02229246, 0.0200609 ,
                          0.01805333, 0.01624718, 0.01462219])}],
    'layout': {'template': '...'}
})

Para Plotly, cualquier objeto gráfico es representado en un árbol de atributos. Los atributos se componen de una lista de diccionarios de Python que representan las distintas características del gráfico. Hay tres atributos básicos - data que, justamente, representa los datos a graficar - layout, que describe la representación del gráfico - frames, que se utiliza para hacer animaciones.

En el caso del atributo data, tiene más de 40 tipos de diccionarios distintos, que se denominan trazos (traces). Cada uno de éstos representa un tipo de gráfico.

fig = go.Figure()

fig.add_trace(go.Scatter(
     x=x
    ,y=y))
fig.add_trace(go.Scatter(
     x=x
    ,y=medicion))
fig.show()
print(fig)
Figure({
    'data': [{'type': 'scatter',
              'x': array([0.  , 0.08, 0.16, 0.24, 0.32, 0.4 , 0.48, 0.56, 0.64, 0.72, 0.8 , 0.88,
                          0.96, 1.04, 1.12, 1.2 , 1.28, 1.36, 1.44, 1.52, 1.6 , 1.68, 1.76, 1.84,
                          1.92, 2.  , 2.08, 2.16, 2.24, 2.32, 2.4 , 2.48, 2.56, 2.64, 2.72, 2.8 ,
                          2.88, 2.96, 3.04, 3.12, 3.2 , 3.28, 3.36, 3.44, 3.52, 3.6 , 3.68, 3.76,
                          3.84, 3.92, 4.  ]),
              'y': array([3.        , 2.69298993, 2.41758858, 2.17051953, 1.94884857, 1.74994758,
                          1.5714624 , 1.41128403, 1.26752287, 1.13848575, 1.02265536, 0.91867178,
                          0.82531612, 0.74149572, 0.66623099, 0.59864366, 0.53794617, 0.48343229,
                          0.43446861, 0.39048702, 0.3509779 , 0.31548409, 0.28359542, 0.25494386,
                          0.22919919, 0.20606505, 0.18527545, 0.16659167, 0.14979943, 0.13470634,
                          0.12113972, 0.1089445 , 0.09798146, 0.08812556, 0.07926454, 0.07129757,
                          0.06413406, 0.05769268, 0.05190035, 0.04669142, 0.04200691, 0.03779383,
                          0.03400457, 0.03059634, 0.0275307 , 0.02477309, 0.02229246, 0.0200609 ,
                          0.01805333, 0.01624718, 0.01462219])},
             {'type': 'scatter',
              'x': array([0.  , 0.08, 0.16, 0.24, 0.32, 0.4 , 0.48, 0.56, 0.64, 0.72, 0.8 , 0.88,
                          0.96, 1.04, 1.12, 1.2 , 1.28, 1.36, 1.44, 1.52, 1.6 , 1.68, 1.76, 1.84,
                          1.92, 2.  , 2.08, 2.16, 2.24, 2.32, 2.4 , 2.48, 2.56, 2.64, 2.72, 2.8 ,
                          2.88, 2.96, 3.04, 3.12, 3.2 , 3.28, 3.36, 3.44, 3.52, 3.6 , 3.68, 3.76,
                          3.84, 3.92, 4.  ]),
              'y': array([ 2.82486790e+00,  2.67934530e+00,  2.56685855e+00,  2.29831438e+00,
                           2.20408823e+00,  1.79559175e+00,  1.50754167e+00,  1.26790199e+00,
                           1.95631111e+00,  1.29807475e+00,  1.10249829e+00,  1.09329565e+00,
                           6.76296177e-01,  7.89794635e-01,  7.26652447e-01,  4.03545751e-01,
                           9.00738392e-01,  4.94237741e-01,  4.25072026e-01,  4.50839348e-01,
                           4.00022985e-01,  5.80405830e-01,  3.57971509e-01,  2.16595778e-01,
                           1.29290552e-01,  2.55009385e-01,  3.18217356e-01, -1.62547763e-01,
                          -6.59792798e-02,  3.39719876e-01,  1.51982019e-01, -6.53563761e-02,
                           1.13313788e-01,  1.90815876e-01, -3.80674722e-01,  4.50989891e-01,
                           4.40736499e-01,  1.36524572e-01,  3.09087487e-01, -8.28776650e-02,
                           2.07269395e-01,  1.61108518e-02, -8.59526325e-04,  3.03510457e-02,
                           6.94846756e-02,  1.68733365e-01, -2.27033583e-01, -1.18723178e-01,
                           5.37281677e-02, -1.29708566e-01, -3.28273681e-01])}],
    'layout': {'template': '...'}
})
fig = go.Figure()
datos = dict(type='scatter'
             ,x=x
             ,y=y)
datos_medidos = dict(type='scatter'
             ,x=x
             ,y=medicion)
fig.add_trace(datos)
fig.add_trace(datos_medidos)
fig.show()
print(datos)
{'x': array([0.  , 0.08, 0.16, 0.24, 0.32, 0.4 , 0.48, 0.56, 0.64, 0.72, 0.8 ,
       0.88, 0.96, 1.04, 1.12, 1.2 , 1.28, 1.36, 1.44, 1.52, 1.6 , 1.68,
       1.76, 1.84, 1.92, 2.  , 2.08, 2.16, 2.24, 2.32, 2.4 , 2.48, 2.56,
       2.64, 2.72, 2.8 , 2.88, 2.96, 3.04, 3.12, 3.2 , 3.28, 3.36, 3.44,
       3.52, 3.6 , 3.68, 3.76, 3.84, 3.92, 4.  ]), 'y': array([3.        , 2.69298993, 2.41758858, 2.17051953, 1.94884857,
       1.74994758, 1.5714624 , 1.41128403, 1.26752287, 1.13848575,
       1.02265536, 0.91867178, 0.82531612, 0.74149572, 0.66623099,
       0.59864366, 0.53794617, 0.48343229, 0.43446861, 0.39048702,
       0.3509779 , 0.31548409, 0.28359542, 0.25494386, 0.22919919,
       0.20606505, 0.18527545, 0.16659167, 0.14979943, 0.13470634,
       0.12113972, 0.1089445 , 0.09798146, 0.08812556, 0.07926454,
       0.07129757, 0.06413406, 0.05769268, 0.05190035, 0.04669142,
       0.04200691, 0.03779383, 0.03400457, 0.03059634, 0.0275307 ,
       0.02477309, 0.02229246, 0.0200609 , 0.01805333, 0.01624718,
       0.01462219]), 'type': 'scatter'}

Líneas, símbolos y colores

fig = go.Figure()

fig.add_trace(go.Scatter(
     x=x
    ,y=y
    ,line=dict(color='black',width=2)))
fig.add_trace(go.Scatter(x=x,y=medicion,
                         line=dict(
                             color='royalblue'
                             ,width=4
                             ,dash='dash')))
fig.show()
help(go.Scatter())
fig = go.Figure()

fig.add_trace(go.Scatter(x=x,y=y,line=dict(color='black',width=2)))
fig.add_trace(go.Scatter(x=x,y=medicion,mode='lines+markers',line=dict(color='red',width=2,dash='dot'),marker=dict(color='red',symbol='circle-open',size=10)))
fig.show()

Nombres de ejes y leyendas

Vamos ahora a agregar nombres a los ejes y a las curvas.

Para agregar nombres a las curvas, tenemos que agregar un label, en este caso en el mismo comando plot(), y luego mostrarlo con `legend()

fig = go.Figure()

fig.add_trace(go.Scatter(x=x,y=y,name='Teoría',line=dict(color='blue',width=2)))
fig.add_trace(go.Scatter(x=x,y=medicion,name='Medición',mode='lines+markers',
                         line=dict(color='red',width=2,dash='dot'),
                         marker=dict(color='red',symbol='circle',size=10,line_width=2,line_color="midnightblue")))


fig.show()

Para agregar nombres a los ejes usamos xlabel y ylabel: Los títulos a la figura se pueden agregar con title:

fig = go.Figure()

fig.add_trace(go.Scatter(
    x=x
    ,y=y
    ,name='Teoría'
    ,line=dict(
         color='blue'
        ,width=2)
    ))
fig.add_trace(go.Scatter(
     x=x
    ,y=medicion
    ,name='Medición'
    ,mode='lines+markers'
    ,line=dict(
        color='red'
        ,width=2
        ,dash='dot')
    ,marker=dict(
        color='red'
        ,symbol='circle'
        ,size=10
        ,line_width=2
        ,line_color="midnightblue")))

fig.update_layout(
    title="Resultados",
    xaxis_title="Tiempo [seg]",
    yaxis_title="Valor [mV]",
    legend_title="Referencias",
    font=dict(
        family="Courier New, monospace",
        size=18,
        color="RebeccaPurple"
    ),
    legend=dict(
    yanchor="top",
    y=0.99,
    xanchor="left",
    x=0.8)
)

fig.show()

Podemos tambien graficar lineas verticales y horizontales usando axvline y axhline

fig.add_hline(y=1.5)
fig.add_vline(x=2.5, line_width=3, line_dash="dash", line_color="green")
fig.show()

Para pasar a escala logarítmica actualizamos los ejes con update_xaxes o update_yaxes:

fig.update_xaxes(type='log')

fig.show()

Dos gráficos en la misma figura

Tenemos que importar el módulo subplots:

from plotly.subplots import make_subplots
fig = go.Figure()


fig = make_subplots(rows=1,cols=2,
                   subplot_titles = ('Resultados escala logarítmica',
                                     'Resultados escala lineal'))


fig.add_trace(go.Scatter(
    x=x
    ,y=y
    ,name='Teoría'
    ,line=dict(
         color='blue'
        ,width=2)
    )
    ,row=1,col=1)
fig.add_trace(go.Scatter(
     x=x
    ,y=medicion
    ,name='Medición'
    ,mode='lines+markers'
    ,line=dict(
        color='red'
        ,width=2
        ,dash='dot')
    ,marker=dict(
        color='red'
        ,symbol='circle'
        ,size=10
        ,line_width=2
        ,line_color="midnightblue"))
    ,row=1,col=1)

fig.add_trace(go.Scatter(
    x=x
    ,y=y
    ,name='Teoría'
    ,line=dict(
         color='blue'
        ,width=2)
    )
    ,row=1,col=2)
fig.add_trace(go.Scatter(
     x=x
    ,y=medicion
    ,name='Medición'
    ,mode='lines+markers'
    ,line=dict(
        color='red'
        ,width=2
        ,dash='dot')
    ,marker=dict(
        color='red'
        ,symbol='circle'
        ,size=10
        ,line_width=2
        ,line_color="midnightblue"))
    ,row=1,col=2)




fig.update_xaxes(type='log',
                    row=1,col=1)

Exportar las figuras

El output sugerido de Plotly es un archivo .html. > Estos archivos conservan la interactividad del gráfico!

fig.write_html('fig1.html')

Para obtener un gráfico estático, es necesario contar con el módulo Kaleido instalado en la distribución de python.

conda install -c conda-forge python-kaleido

fig.write_image('fig1.jpg')
%ls

Acá también se puede utilizar formato tipo LaTeX para parte del texto. Si utilizamos una expresión encerrada entre los símbolos $, Plotly interpreta que está escrito en (un subconjunto) de LaTeX.

fig = go.Figure()

fig.add_trace(go.Scatter(
    x=x
    ,y=y
    ,name='Teoría'
    ,line=dict(
         color='blue'
        ,width=2)
    ))
fig.add_trace(go.Scatter(
     x=x
    ,y=medicion
    ,name=r'$f(x) = 2.5 e^{-1.3 x} + 0.5. e^{-1.6 x}$'
    ,mode='lines+markers'
    ,line=dict(
        color='red'
        ,width=2
        ,dash='dot')
    ,marker=dict(
        color='red'
        ,symbol='circle'
        ,size=10
        ,line_width=2
        ,line_color="midnightblue")))

fig.update_layout(
    title="Resultados",
    xaxis_title="Tiempo [seg]",
    yaxis_title="Valor [mV]",
    legend_title="Referencias",
    font=dict(
        family="Times New Roman",
        size=14,
        color="RebeccaPurple"
    ),
    legend=dict(
    yanchor="top",
    y=0.99,
    xanchor="left",
    x=0.6)
)

fig.show()
fig.write_html("fig_with_latex.html")

Al momento hay un bug que no muestra el texto en LaTeX en Visual Studio Code, pero sí funciona usando Jupyter notebooks en un navegador.

Gráficos en coordenadas polares

r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r

fig = go.Figure(
    go.Scatterpolar(
        theta = theta,
        r = r,
        thetaunit = 'radians',
        mode = 'markers',
    ))
fig.update_polars(radialaxis=dict(range=[0, 2]))
fig.update_polars(angularaxis_thetaunit='radians')

fig.show()

Otros gráficos

x = np.array([0,1,2,3,4,5])

fig = go.Figure()


fig = make_subplots(rows=1,cols=4,
                   subplot_titles = ('SCATTER','STEP','BAR','FILL BETWEEN'))

fig.add_trace(go.Scatter(
     x=x
    ,y=x + 0.25*np.random.randn(len(x))
    ,name='scatter'
    ,mode='markers'
    ,line=dict(
        color='red'
        ,width=2
        ,dash='dot')
    ,marker=dict(
        color='red'
        ,symbol='circle'
        ,size=10
        ,line_width=2
        ,line_color="midnightblue"))
    ,row=1,col=1)


fig.add_trace(go.Scatter(
     x=x
    ,y=x**2
    ,name='step'
    ,line = dict(shape='hv')
    ,mode='lines')
    ,row=1,col=2)


fig.add_trace(go.Bar(
     x=x
    ,y=x**2
    ,name='bar')
    ,row=1,col=3)


fig.add_trace(go.Scatter(
     x=x
    ,y=x**2
    ,mode='lines'
    ,fill='tozeroy'
    ,name='fill')
    ,row=1,col=4)

Histogramas

n = np.random.randn(10000) #randn devuelve una distribucion normal

fig = go.Figure()


fig = make_subplots(rows=1,cols=2,
                   subplot_titles = ('Histograma','Acumulado'))

fig.add_trace(go.Histogram(x=n),row=1,col=1)
fig.add_trace(go.Histogram(x=n, cumulative_enabled=True),row=1,col=2)
n = np.random.randn(10000) #randn devuelve una distribucion normal

fig = go.Figure()


fig = make_subplots(rows=1,cols=2,
                   subplot_titles = ('Histograma','Acumulado'))

fig.add_trace(go.Histogram(x=n,nbinsx=20,texttemplate="%{x}"),row=1,col=1)
fig.add_trace(go.Histogram(x=n,nbinsx=20, cumulative_enabled=True),row=1,col=2)
fig.show()

Ejercicio: Tasa de natalidad en Argentina

En el archivo (../data/tasa-natalidad.csv) se encuentra una tabla con la tasa de natalidad en la Argentina, entre los años 2000 y 2018. El archivo es de tipo .csv, comma separated values, es decir que los datos están separados por comas. El objetivo de este ejercicio es realizar un gráfico represente estos datos. Para ello

  • Abra el archivo con un editor de texto y familiarícese con su estructura, para entender qué datos hay que leer.

  • Abra el archivo con Python y organice la información con numpy.

  • Haga un gráfico claro y bello representando los datos del punto anterior.

  • Usando numpy, obtenga las dos provincias de mayor tasa de natalidad, y las dos provincias de menor tasa de natalidad en promedio entre los años 2000-2018.

  • Haga un gráfico claro y bello representando los datos del punto anterior, comparándolos con la tasa de natalidad promedio del país.

Leyendo una imagen

Además de realizar gráficos 2D, Plotly permite trabajar con imágenes (al igual que matplotlib). Se pueden leer imágenes que son interpretadas como un array de numpy (al igual que matplotlib).

Usamos un submódulo de matplotlib para leer la imagen de archivo. Esto mismo se puede hacer con otros módulos de procesamiento de imágenes:

import matplotlib.image as mpimg
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np

grace = mpimg.imread('../data/grace_hopper.jpg')
print(grace.shape)
print(type(grace))


jean = mpimg.imread('../data/jean_sammet.jpg')
print(type(jean))

cirs = mpimg.imread('../data/cirs_slice.png')

Ambas imágenes tienen 3 canales, Rojo, Verde y Azul, que puede verse en el shape de cada una de ellas:

print(grace.shape)
print(jean.shape)
fig = go.Figure()

fig = make_subplots(rows=1,cols=2,
                   subplot_titles = ('Grace Hopper','Jean Sammet'))


fig.add_trace(go.Image(z=grace),1,1)
fig.add_trace(go.Image(z=jean),1,2)
fig.show()

Uno de los primeros procesamientos que uno puede hacer de una imagen es obtener el histograma por cada canal:

fig = go.Figure()

fig = make_subplots(rows=1,cols=2,
                   subplot_titles = ('Jean Sammet','Histograma'))

fig.add_trace(go.Image(z=jean),1,1)
for channel, color in enumerate(['red', 'green', 'blue']):
    fig.add_trace(go.Histogram(x=jean[..., channel].ravel(), opacity=0.5,
                               marker_color=color, name='%s channel' %color), 1, 2)

fig.show()
ravel_grace = grace.ravel()
print(ravel_grace.shape)
print(ravel_grace)

Algo a tener en cuenta es que las imágenes pueden venir definidas como RGB con enteros entre 0 y 255, como las imágenes anteriores; o entre 0 y 1, como la siguiente:

cirs = mpimg.imread('../data/cirs_slice.png')
print(type(cirs))
print(cirs.shape)
print("Max image: ",np.max(cirs))
#
#  Puedo normalizar la imagen al rango 0-1
#
img_maximo = np.max(cirs)
cirs = cirs/img_maximo
#
#  Normalizo la imagen al rango 0-255
#
img_maximo = np.max(cirs)
cirs = cirs/img_maximo*255
cirs = cirs.astype(int) # Convierte el array de numpy de float a enteros

fig = go.Figure()

fig = make_subplots(rows=1,cols=2,
                   subplot_titles = ('CIRS','Histograma'))

fig.add_trace(go.Image(z=cirs),1,1)
fig.add_trace(go.Histogram(x=cirs[..., 0].ravel(), opacity=0.5,
                               marker_color=color, name='BW channel'), 1, 2)

fig.show()
print(cirs)

También se puede calcular el histograma en numpy:

#
#  Puedo normalizar la imagen al rango 0-1
#
img_maximo = np.max(cirs)
cirs = cirs/img_maximo


nbins = 20
c = cirs.ravel()
h = np.histogram(c,bins=nbins) # np.histogram devuelve una tupla con dos arrays, el primero
                            # es el histograma, el segundo corresponde a los límites de los bines
print(type(h[0]))
print(h[0])
print(h[0].shape)
print(h[1].shape)

Pero es más trabajoso hacer el histograma a mano: - Hay que notar la diferencia en el tamaño entre el histograma en h[0] y los límites de los bines en h[1]. Por eso es necesario seleccionar todos los elementos de h[1] excepto el último con h[1][:-1]. - Además usamos align='edge' para que las barras queden alineadas a la izquierda de cada intervalo.

Siguiendo con imágenes

El paquete `scikit-image <https://scikit-image.org/>`__ que se instala con

conda install scikit-image

tiene un excelente tutorial para seguir aprendiendo el procesamiento básico de imágenes, en particular, el tutorial sobre filtrado de imágenes y se puede bajar como un notebook. Un poco de graficación 3D =========================

import numpy as np
import plotly.graph_objects as go

Gráficos y procesamiento sencillo en 2D

Histogramas en 2D

Así como trabajamos con histogramas de arrays unidimensionales en forma sencilla usando plt.hist() o np.histogram(), podemos hacerlo de una manera similar trabajando en el plano. Empecemos creando algunos datos

np.random.seed(0)
n = 10000
x = np.r_[np.random.normal(size=n), np.random.normal(loc=3, size=n)]
y = 2.0 + 4.0 * x - x**2 / 5 + 2.0 * np.r_[np.random.normal(size=n), np.random.normal(loc=-3, size=n)]

Acá la notación r_[] hace concatenación por filas. Veamos que forma tienen x e y

x.shape

Para crear el histograma usamos simplemente la función Histogram2d.

H = go.Figure(go.Histogram2d(
     x = x
    ,y = y))
H.show()
H = go.Figure(go.Histogram2d(
    x = x
    ,nbinsx = 60
    ,y = y
    ,nbinsy = 60))
H.show()

Aquí pusimos igual número de “cajas” en cada dimensión. También podemos pasarle un array con distinto número de cajas

H = go.Figure(go.Histogram2d(
    x = x
    ,nbinsx = 60
    ,y = y
    ,nbinsy = 150))
H.show()

Se puede definir el número de bins de esta otra forma:

H = go.Figure(go.Histogram2d(
    x = x
    ,autobinx=False
    ,xbins=dict(start=-5, end=7.5, size=0.5)
    ,y = y
    ,nbinsy = 60))
H.show()

Por supuesto podemos cambiar el esquema de colores utilizado. Para ello le damos explícitamente el argumento cmap especificando el “colormap” deseado:

H = go.Figure(go.Histogram2d(
    x = x
    ,autobinx=False
    ,xbins=dict(start=-5, end=7.5, size=0.5)
    ,y = y
    ,nbinsy = 60
    ,    colorscale='YlGnBu'
    ))
H.show()

Se puede elegir el valor máximo de Z:

H = go.Figure(go.Histogram2d(
    x = x
    ,autobinx=False
    ,xbins=dict(start=-5, end=7.5, size=0.5)
    ,y = y
    ,nbinsy = 60
    ,zmax = 200
    ,zauto = False
    ,    colorscale='YlGnBu'
    ))
H.show()

O por ejemplo, se puede agregar el valor de cada bin:

H = go.Figure(go.Histogram2d(
    x = x
    ,nbinsx = 30
    ,y = y
    ,nbinsy = 30
    ,colorscale='YlGnBu'
    ,texttemplate= "%{z}"
    ))
H.show()

Gráficos de contornos

Z,xedges,yedges = np.histogram2d(x,y,bins=20)
fig1= go.Figure(data =
     go.Contour(
         x = xedges
        ,y = yedges
        ,z = Z))
fig1.show()
fig1= go.Figure(data =
     go.Contour(
         x = xedges
        ,y = yedges
        ,z = Z
        ,colorscale='rainbow'))
fig1.show()
fig1= go.Figure(data =
     go.Contour(
         x = xedges
        ,y = yedges
        ,z = Z
        ,contours = dict(
            coloring='lines'
            ,showlabels=True
            ,labelfont = dict(
                size = 11,
#                 color = 'white',
            ))
#          ,line_smoothing=0.5
        ,contours_coloring='heatmap' # can also be 'lines', or 'none'

         ))
fig1.show()

También podemos mostrar la imagen con los contornos superpuestos:

Superficies y contornos

Realizar gráficos “realmente” en 3D es tan simple como cambiar Contour por Surface

fig1= go.Figure(data =
     go.Surface(
         x = xedges
        ,y = yedges
        ,z = Z))
fig1.show()
fig1= go.Figure(data =
     go.Surface(
         x = xedges
        ,y = yedges
        ,z = Z
        ,opacity=0.7))
fig1.show()
fig1.write_html('3dplot.html')
fig1= go.Figure(data =
     go.Surface(
         x = xedges
        ,y = yedges
        ,z = Z
        ,contours = {
            "z": {"show": True}
           ,"x": {"show": True, "color": "white"}
         }))
fig1.show()
fig1= go.Figure(data =
     go.Surface(
         x = xedges
        ,y = yedges
        ,z = Z
        ,contours = {
            "z": {"show": True, "start": 0, "end": 600, "size":50}
           ,"x": {"show": True, "color": "white"}
         }))
fig1.update_traces(contours_z=dict(show=True, usecolormap=True,
                                  highlightcolor="limegreen", project_z=True))
fig1.show()

Para realizar gráficos de campos (de velocidades, fuerzas, etc) podemos utilizar la función quiver(), que grafica flechas en cada punto, con una dirección y longitud dada

Veamos un ejemplo de la documentación de Plotly

x,y,z,u,v,w = np.loadtxt("https://raw.githubusercontent.com/plotly/datasets/master/vortex.csv",skiprows=1,unpack=True,delimiter=',')

fig = go.Figure(data = go.Cone(
    x=x,
    y=y,
    z=z,
    u=u,
    v=v,
    w=w,
    colorscale='Blues',
    sizemode="absolute",
    sizeref=40))


fig.update_layout(scene=dict(aspectratio=dict(x=1, y=1, z=0.8),
                             camera_eye=dict(x=1.2, y=1.2, z=0.6)))

fig.show()
# Make the grid
x = np.arange(0, 1, 0.1)
y = np.arange(0, 1, 0.1)
z = np.arange(0, 1, 0.1)

# Make the direction data for the arrows
u = np.sin(np.pi * x)* np.cos(np.pi * y) * np.cos(np.pi * z)
v = -np.cos(np.pi * y)* np.sin(np.pi * y) * np.cos(np.pi * z)
w = (np.sqrt(2.0 / 3.0)* np.sin(np.pi * z)) * np.cos(np.pi * x) * np.cos(np.pi * y)


fig = go.Figure(
        go.Cone(
        x = x
        ,y = y
        ,z = z
        ,u = u
        ,v = v
        ,w = w
        ,sizemode="absolute"
        ,sizeref=2
        ,anchor="tip"
        ))
fig.show()

Streamtubes of the ABC Flow: Flowing from Y-Plane

def vector_field(x, y, z, A=1, B=np.sqrt(2./3), C=np.sqrt(1./3)):
    return A*np.sin(z) + C*np.cos(y), B*np.sin(x) + A*np.cos(z), C*np.sin(y) + B*np.cos(x)
x, y, z=np.mgrid[0: 2*np.pi:30j, 0:2*np.pi:30j, 0:2*np.pi:30j]
u, v, w=vector_field(x, y, z)
x.shape
u.shape
fig = go.Figure(
        go.Streamtube(
        x=x.flatten(),
        y=y.flatten(),
        z=z.flatten(),
        u=u.flatten(),
        v=v.flatten(),
        w=w.flatten(),
                 maxdisplayed=3500,
                 sizeref=0.3,
                 reversescale=True,
                 showscale=True,

        ))
fig.show()