Código
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as pxFecha: 19 de noviembre, 2026
Una buena gráfica permite ver en segundos lo que en una tabla de números tardaríamos mucho en descubrir: tendencias, relaciones, valores atípicos o el comportamiento de una función.
En este capítulo usaremos dos bibliotecas:
| Matplotlib | Plotly | |
|---|---|---|
| Tipo de gráfica | Estática (imagen). | Interactiva (zoom, desplazamiento, información al pasar el ratón). |
| Ideal para | Artículos, tesis, reportes en PDF. | Páginas web, exploración de datos, presentaciones. |
| Formato de salida | PNG, PDF, SVG. | HTML. |
| Estilo de código | Se construye paso a paso (ejes, líneas, etiquetas). | Una función crea la gráfica completa. |
Las bibliotecas de este capítulo (numpy, pandas, matplotlib, plotly y sympy) ya las instalaste en tu entorno quarto-python en el capítulo Configuraciones para usar Quarto con Python. Si necesitas repasar algo, consulta ahí:
| Si necesitas… | Consulta |
|---|---|
| Instalar las bibliotecas | Paso 3. Instalar todos los paquetes del curso |
| Comprobar que RStudio usa tu entorno | Paso 6. Verificar que todo funciona |
Resolver ModuleNotFoundError: No module named 'matplotlib' (o 'plotly') |
Solución de problemas: ModuleNotFoundError al ejecutar o renderizar desde RStudio |
En Google Colab y en los editores de esta página las bibliotecas ya están disponibles. Por convención se importan así:
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as pxToda gráfica de Matplotlib se compone de una figura (Figure, el lienzo completo) que contiene uno o más ejes (Axes, cada gráfica con sus ejes \(x\) y \(y\)).
x = np.linspace(-3, 3, 100) # 100 puntos entre -3 y 3
plt.figure(figsize=(6, 4)) # Tamaño de la figura en pulgadas
plt.plot(x, x**2, label="$f(x) = x^2$")
plt.plot(x, -x**2 + 5, label="$g(x) = -x^2 + 5$", linestyle="--")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Dos parábolas")
plt.legend()
plt.grid(True)
plt.show()
| Función | ¿Qué hace? |
|---|---|
plt.figure(figsize=(ancho, alto)) |
Crea una figura nueva del tamaño indicado. |
plt.plot(x, y) |
Dibuja una línea que une los puntos. |
plt.xlabel(), plt.ylabel() |
Etiquetas de los ejes. |
plt.title() |
Título. |
plt.legend() |
Muestra la leyenda (usa el argumento label de cada línea). |
plt.grid(True) |
Agrega una cuadrícula. |
plt.xlim(), plt.ylim() |
Límites de los ejes. |
plt.savefig("archivo.png", dpi=300) |
Guarda la figura en un archivo. |
plt.show() |
Muestra la figura. |
Los textos entre signos de dólar, como "$f(x) = x^2$", se escriben en LaTeX, igual que en Markdown.
| Argumento | Valores comunes |
|---|---|
color |
"red", "tab:blue", "#007f99" |
linestyle |
"-" (continua), "--" (guiones), ":" (puntos), "-." |
linewidth |
Grosor de la línea, por ejemplo 2. |
marker |
"o" (círculo), "s" (cuadrado), "^" (triángulo), "x" |
alpha |
Transparencia entre 0 y 1. |
plt.annotate() agrega un texto con una flecha que señala un punto. Por ejemplo, el máximo de \(g(x) = -x^2 + 5\):
plt.figure(figsize=(6, 4))
plt.plot(x, -x**2 + 5, color="tab:orange")
plt.scatter([0], [5], color="black", zorder=3)
plt.annotate("Máximo (0, 5)", xy=(0, 5), xytext=(1, 5.5),
arrowprops=dict(arrowstyle="->"))
plt.ylim(-5, 7)
plt.title("$g(x) = -x^2 + 5$")
plt.show()
x = np.linspace(0, 2 * np.pi, 200)
plt.figure(figsize=(6, 4))
plt.plot(x, np.sin(x), label="sen(x)")
plt.plot(x, np.cos(x), label="cos(x)")
plt.fill_between(x, np.sin(x), np.cos(x), alpha=0.3)
plt.legend()
plt.title("Área entre sen(x) y cos(x)")
plt.show()
subplots()Con plt.subplots(filas, columnas) creamos una cuadrícula de gráficas. Esta forma de trabajar se llama orientada a objetos: cada gráfica es un objeto ax con sus propios métodos.
x = np.linspace(0.1, 2 * np.pi, 200)
fig, axs = plt.subplots(2, 2, figsize=(8, 6))
axs[0, 0].plot(x, np.sin(x))
axs[0, 0].set_title("sen(x)")
axs[0, 1].plot(x, np.cos(x), color="tab:orange")
axs[0, 1].set_title("cos(x)")
axs[1, 0].plot(x, np.exp(-x), color="tab:green")
axs[1, 0].set_title("$e^{-x}$")
axs[1, 1].plot(x, np.log(x), color="tab:red")
axs[1, 1].set_title("ln(x)")
fig.tight_layout() # Evita que se encimen los títulos
plt.show()
Estilo plt |
Estilo orientado a objetos (ax) |
|---|---|
plt.plot() |
ax.plot() |
plt.title() |
ax.set_title() |
plt.xlabel() |
ax.set_xlabel() |
plt.xlim() |
ax.set_xlim() |
plt.legend() |
ax.legend() |
Observa que axs es un arreglo de NumPy de 2 × 2, por eso usamos axs[fila, columna].
| Gráfica | ¿Para qué sirve? | Matplotlib | Plotly Express |
|---|---|---|---|
| Línea | Funciones, tendencias en el tiempo. | plt.plot() |
px.line() |
| Dispersión | Relación entre dos variables. | plt.scatter() |
px.scatter() |
| Barras | Comparar categorías. | plt.bar() |
px.bar() |
| Histograma | Distribución de una variable. | plt.hist() |
px.histogram() |
| Caja (boxplot) | Mediana, cuartiles y valores atípicos. | plt.boxplot() |
px.box() |
| Pastel | Proporciones de un total (pocas categorías). | plt.pie() |
px.pie() |
| Curvas de nivel | Funciones de dos variables \(f(x, y)\). | plt.contour() |
go.Contour() |
| Mapa de calor | Matrices de valores. | plt.imshow() |
px.imshow() |
| Superficie 3D | Funciones \(z = f(x, y)\). | ax.plot_surface() |
go.Surface() |
rng = np.random.default_rng(seed=2026)
puntos_x = rng.normal(size=300)
puntos_y = rng.normal(size=300)
angulo = np.arctan2(puntos_y, puntos_x)
plt.figure(figsize=(5, 4))
plt.scatter(puntos_x, puntos_y, c=angulo, cmap="viridis", alpha=0.8)
plt.colorbar(label="Ángulo (radianes)")
plt.title("Puntos aleatorios coloreados por su ángulo")
plt.axis("equal")
plt.show()
Para las siguientes gráficas usaremos calificaciones simuladas con números aleatorios de NumPy (con semilla, para obtener siempre los mismos datos):
rng = np.random.default_rng(seed=2026)
niveles = ["MB", "B", "S", "NA"]
estudiantes_por_nivel = [28, 45, 35, 12]
calificaciones = np.clip(rng.normal(7.8, 1.3, 120), 0, 10) # 120 calificaciones entre 0 y 10
grupo_a = rng.normal(7.5, 1.0, 40)
grupo_b = rng.normal(8.2, 0.8, 40)
grupo_c = rng.normal(6.9, 1.4, 40)rng.normal(media, desviacion, n) genera n números con distribución normal, y np.clip(datos, 0, 10) recorta los valores para que queden entre 0 y 10.
plt.bar()Una gráfica de barras compara cantidades entre categorías. Recibe las categorías (eje \(x\)) y la altura de cada barra (eje \(y\)):
plt.figure(figsize=(6, 4))
plt.bar(niveles, estudiantes_por_nivel, color="tab:blue")
plt.xlabel("Nivel")
plt.ylabel("Número de estudiantes")
plt.title("Estudiantes por nivel")
plt.show()
Para barras horizontales usa plt.barh(), útil cuando los nombres de las categorías son largos.
plt.hist()Un histograma muestra la distribución de una variable numérica: divide el rango de los datos en intervalos (bins) y cuenta cuántos datos caen en cada uno.
plt.figure(figsize=(6, 4))
plt.hist(calificaciones, bins=15, color="tab:green", edgecolor="white")
plt.axvline(calificaciones.mean(), color="black", linestyle="--", label="Media")
plt.xlabel("Calificación")
plt.ylabel("Número de estudiantes")
plt.title("Histograma de calificaciones")
plt.legend()
plt.show()
bins es el número de intervalos: pocos bins ocultan detalles y demasiados producen una gráfica “con ruido”.plt.axvline(valor) dibuja una línea vertical; plt.axhline(valor), una horizontal.No confundas un histograma con una gráfica de barras: las barras comparan categorías (MB, B, S, NA), mientras que el histograma agrupa valores numéricos en intervalos.
plt.boxplot()Un diagrama de caja (boxplot) resume una distribución con cinco valores y permite comparar varios grupos:
plt.figure(figsize=(6, 4))
plt.boxplot([grupo_a, grupo_b, grupo_c])
plt.xticks([1, 2, 3], ["Grupo A", "Grupo B", "Grupo C"])
plt.ylabel("Calificación")
plt.title("Calificaciones por grupo")
plt.show()
| Parte de la caja | ¿Qué representa? |
|---|---|
| Línea dentro de la caja | La mediana. |
| Borde inferior y superior de la caja | Primer y tercer cuartil (el 50 % central de los datos). |
| “Bigotes” | El rango de los datos sin contar valores atípicos. |
| Puntos aislados | Valores atípicos (muy alejados del resto). |
plt.pie()Una gráfica de pastel muestra la proporción de cada categoría respecto al total:
plt.figure(figsize=(5, 5))
plt.pie(estudiantes_por_nivel, labels=niveles, autopct="%1.0f%%")
plt.title("Proporción de estudiantes por nivel")
plt.show()
autopct="%1.0f%%" muestra el porcentaje de cada rebanada sin decimales. Úsala solo con pocas categorías; si hay muchas, una gráfica de barras se lee mejor.
Ahora que conocemos cada gráfica, podemos reunirlas en una figura de 2 × 2 con plt.subplots(). Observa que el código de cada gráfica es el mismo, pero ahora usamos los métodos de cada ax (axs[0, 0].bar(), axs[0, 0].set_title()…) en lugar de plt.bar() y plt.title():
fig, axs = plt.subplots(2, 2, figsize=(9, 7))
axs[0, 0].bar(niveles, estudiantes_por_nivel, color="tab:blue")
axs[0, 0].set_title("Barras: estudiantes por nivel")
axs[0, 0].set_ylabel("Número de estudiantes")
axs[0, 1].hist(calificaciones, bins=15, color="tab:green", edgecolor="white")
axs[0, 1].axvline(calificaciones.mean(), color="black", linestyle="--", label="Media")
axs[0, 1].set_title("Histograma de calificaciones")
axs[0, 1].legend()
axs[1, 0].boxplot([grupo_a, grupo_b, grupo_c])
axs[1, 0].set_xticks([1, 2, 3], ["Grupo A", "Grupo B", "Grupo C"])
axs[1, 0].set_title("Caja: calificaciones por grupo")
axs[1, 1].pie(estudiantes_por_nivel, labels=niveles, autopct="%1.0f%%")
axs[1, 1].set_title("Pastel: proporción por nivel")
fig.tight_layout()
plt.show()
Para graficar una función de dos variables, por ejemplo \(f(x, y) = \sin(x)\cos(y)\), seguimos tres pasos: crear una malla de puntos, evaluar la función en la malla y graficar el resultado.
np.meshgrid()Para graficar \(f(x)\) basta un vector de valores de \(x\). Para \(f(x, y)\) necesitamos todas las combinaciones de valores de \(x\) y de \(y\). np.meshgrid() construye esas combinaciones a partir de dos vectores. Veámoslo con pocos puntos:
x_chico = np.array([0, 1, 2])
y_chico = np.array([10, 20])
X_chico, Y_chico = np.meshgrid(x_chico, y_chico)
print("X =\n", X_chico)X =
[[0 1 2]
[0 1 2]]
print("Y =\n", Y_chico)Y =
[[10 10 10]
[20 20 20]]
Cada posición [i, j] de las matrices X y Y corresponde a un punto \((x, y)\) de la malla: por ejemplo, X[1, 2] y Y[1, 2] forman el punto \((2, 20)\).
Como X y Y son arreglos de NumPy, la función se evalúa en todos los puntos a la vez:
x = np.linspace(-2 * np.pi, 2 * np.pi, 200)
y = np.linspace(-2 * np.pi, 2 * np.pi, 200)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
print("Forma de la malla:", X.shape) # 200 × 200 = 40 000 puntosForma de la malla: (200, 200)
print("Forma de Z:", Z.shape)Forma de Z: (200, 200)
plt.imshow()Un mapa de calor pinta cada valor de Z con un color según su magnitud:
plt.figure(figsize=(5, 4))
plt.imshow(Z, extent=[x.min(), x.max(), y.min(), y.max()], origin="lower", cmap="RdBu")
plt.colorbar(label="f(x, y)")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Mapa de calor de sen(x)·cos(y)")
plt.show()
extent indica los valores de \(x\) y \(y\) en los bordes de la imagen (si no, los ejes mostrarían las posiciones de la matriz).origin="lower" coloca la fila 0 abajo, como en un plano cartesiano.cmap es la paleta de colores; "RdBu" va de rojo (negativos) a azul (positivos).plt.contour()Las curvas de nivel unen los puntos donde la función tiene el mismo valor, como en un mapa topográfico:
plt.figure(figsize=(5, 4))
curvas = plt.contour(X, Y, Z, levels=8, cmap="RdBu")
plt.clabel(curvas, fontsize=7) # Escribe el valor de cada curva
plt.xlabel("x")
plt.ylabel("y")
plt.title("Curvas de nivel de sen(x)·cos(y)")
plt.show()
levels indica cuántas curvas dibujar. Con plt.contourf() (con f de filled) se rellenan las regiones entre curvas.
Finalmente, comparamos ambas representaciones lado a lado con plt.subplots(1, 2):
fig, axs = plt.subplots(1, 2, figsize=(10, 4))
imagen = axs[0].imshow(Z, extent=[x.min(), x.max(), y.min(), y.max()], origin="lower", cmap="RdBu")
fig.colorbar(imagen, ax=axs[0])
axs[0].set_title("Mapa de calor (imshow)")
curvas = axs[1].contour(X, Y, Z, levels=8, cmap="RdBu")
axs[1].clabel(curvas, fontsize=7)
axs[1].set_title("Curvas de nivel (contour)")
fig.tight_layout()
plt.show()
En el capítulo de SymPy vimos que sp.lambdify() convierte una expresión simbólica en una función de NumPy. Así podemos graficar una función junto con su derivada:
import sympy as sp
s = sp.symbols("x")
f = s**3 - 3*s
derivada = sp.diff(f, s)
f_np = sp.lambdify(s, f, "numpy")
d_np = sp.lambdify(s, derivada, "numpy")
x = np.linspace(-2.5, 2.5, 200)
plt.figure(figsize=(6, 4))
plt.plot(x, f_np(x), label=f"$f(x) = {sp.latex(f)}$")
plt.plot(x, d_np(x), label=f"$f'(x) = {sp.latex(derivada)}$")
plt.axhline(0, color="gray", linewidth=0.8)
plt.legend()
plt.title("Una función y su derivada")
plt.show()
Observa que los máximos y mínimos de \(f\) ocurren donde la derivada vale cero.
Plotly Express crea una gráfica completa con una sola función. Las gráficas son interactivas: pasa el ratón sobre los puntos, haz zoom seleccionando un área y da doble clic para regresar a la vista original.
Toda gráfica de Plotly sigue los mismos tres pasos:
import plotly.express as px # 1. Importar
fig = px.scatter(datos, x="columna_x", y="columna_y") # 2. Crear la figura (se guarda en la variable fig)
fig.show() # 3. Mostrar la figurafig.show()?
Crear la figura (fig = px.scatter(...)) no la dibuja: solo la guarda en la variable fig. Para verla hay que llamar al método fig.show(), igual que con plt.show() en Matplotlib. Funciona igual en Jupyter Notebook, Google Colab, en un documento de Quarto y en este libro.
px.datapx.data es un módulo de Plotly que incluye conjuntos de datos reales para practicar. Vienen dentro de la biblioteca, así que no necesitas descargar archivos ni tener internet. Cada función de px.data regresa los datos como una tabla de pandas (un DataFrame):
| Función | Contenido | Filas |
|---|---|---|
px.data.iris() |
Medidas de flores de tres especies de iris. | 150 |
px.data.gapminder() |
Esperanza de vida, PIB y población de 142 países (1952-2007). | 1704 |
px.data.tips() |
Cuentas y propinas en un restaurante. | 244 |
px.data.stocks() |
Precio de acciones de seis empresas (2018-2019). | 105 |
px.data.wind() |
Dirección e intensidad del viento. | 128 |
px.data.medals_long() |
Medallas olímpicas de patinaje por país. | 9 |
Un DataFrame es como una hoja de cálculo: tiene filas (cada observación) y columnas con nombre (cada variable). Veamos el conjunto iris, con las medidas en centímetros de 150 flores:
iris = px.data.iris()
print(iris.head()) # Primeras 5 filas sepal_length sepal_width petal_length petal_width species species_id
0 5.1 3.5 1.4 0.2 setosa 1
1 4.9 3.0 1.4 0.2 setosa 1
2 4.7 3.2 1.3 0.2 setosa 1
3 4.6 3.1 1.5 0.2 setosa 1
4 5.0 3.6 1.4 0.2 setosa 1
print("Forma:", iris.shape) # (filas, columnas), igual que en NumPyForma: (150, 6)
print("Columnas:", list(iris.columns))Columnas: ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species', 'species_id']
| Columna | Contenido |
|---|---|
sepal_length, sepal_width |
Largo y ancho del sépalo (cm). |
petal_length, petal_width |
Largo y ancho del pétalo (cm). |
species |
Especie: setosa, versicolor o virginica. |
species_id |
Número de la especie (1, 2 o 3). |

En las funciones de Plotly Express, el primer argumento es el DataFrame y los demás indican qué columna usar en cada parte de la gráfica, escribiendo su nombre como texto: x="sepal_length".
El botánico que midió las flores. A mediados de la década de 1930, el botánico estadounidense Edgar Anderson quería entender cómo se diferencian y evolucionan las especies de plantas. Para ello midió con cuidado el sépalo y el pétalo de cientos de flores de iris; gran parte de ellas las recolectó en la península de Gaspé, en Quebec, Canadá. Anderson tomó 50 flores de cada una de tres especies: Iris setosa, Iris versicolor e Iris virginica.
El estadístico que lo hizo famoso. En 1936, el estadístico y matemático británico Ronald A. Fisher usó las mediciones de Anderson en el artículo “The use of multiple measurements in taxonomic problems” (El uso de mediciones múltiples en problemas taxonómicos). Ahí presentó el análisis discriminante lineal: un método para encontrar la combinación lineal de las cuatro mediciones que mejor separa a las especies. Por eso el conjunto también se conoce como el iris de Fisher o el iris de Anderson.
¿Por qué sigue siendo tan usado casi 90 años después? Porque es pequeño (150 filas), no tiene datos faltantes, sus variables son fáciles de entender y tiene una estructura interesante: setosa se separa claramente de las otras dos especies, mientras que versicolor y virginica se traslapan un poco. Eso lo hace perfecto para probar métodos y verificar si funcionan. Lo verás en las gráficas de este capítulo.
| Área | ¿Para qué se usa el conjunto iris? |
|---|---|
| Botánica y taxonomía | Su origen: estudiar cómo se distinguen las especies a partir de mediciones. |
| Estadística | Estadística descriptiva, pruebas de hipótesis y análisis multivariado, como el análisis discriminante lineal de Fisher. |
| Álgebra lineal | Ejemplo clásico de proyecciones: el análisis de componentes principales (PCA) y el análisis discriminante reducen las 4 dimensiones a 2 que se pueden graficar. |
| Aprendizaje automático (machine learning) | Primer ejemplo de clasificación (predecir la especie a partir de las medidas) y de agrupamiento (encontrar grupos sin conocer la especie, por ejemplo con k-means). Es uno de los conjuntos más descargados del repositorio de aprendizaje automático de UCI, donde está disponible desde 1988. |
| Visualización de datos | Ejemplo estándar para enseñar gráficas de dispersión, histogramas, diagramas de caja y matrices de gráficas. |
| Programación y software | Viene incluido en muchas herramientas: en R (iris), en Python con plotly.express, seaborn y scikit-learn. Se usa para probar que un programa funciona. |
Como ya lo conoces de R, puedes compararlo: en la Console de R, head(iris) muestra las mismas primeras filas, con las columnas Sepal.Length, Sepal.Width, Petal.Length, Petal.Width y Species.
Fisher hizo aportaciones fundamentales a la estadística, pero también promovió la eugenesia, una ideología hoy rechazada, y publicó este trabajo en la revista Annals of Eugenics. Por eso, algunos docentes prefieren usar conjuntos alternativos, como Palmer penguins (medidas de pingüinos de la Antártida, publicado en 2020), que tiene una estructura parecida. Conocer el origen de los datos también es parte de analizarlos con responsabilidad.
fig = px.scatter(iris, x="sepal_length", y="sepal_width", color="species",
title="Iris: largo vs. ancho del sépalo",
labels={"sepal_length": "Largo del sépalo (cm)",
"sepal_width": "Ancho del sépalo (cm)",
"species": "Especie"})
fig.show()Con el argumento color, Plotly asigna un color a cada especie y crea la leyenda automáticamente. Da clic en una especie de la leyenda para ocultarla. El argumento labels cambia los nombres de las columnas por textos más claros en los ejes y la leyenda.
Plotly Express también acepta arreglos de NumPy en lugar de un DataFrame:
x = np.linspace(0, 4 * np.pi, 200)
fig = px.line(x=x, y=np.sin(x) * np.exp(-x / 5),
labels={"x": "x", "y": "f(x)"},
title="Oscilación amortiguada: f(x) = sen(x)·e^(-x/5)")
fig.show()fig = px.histogram(iris, x="petal_length", color="species", nbins=30,
marginal="box", title="Largo del pétalo por especie",
labels={"petal_length": "Largo del pétalo (cm)", "species": "Especie"})
fig.show()El argumento marginal="box" agrega un diagrama de caja arriba del histograma.
Plotly Express no tiene una función para superficies, así que usamos el módulo plotly.graph_objects (abreviado go), que permite construir gráficas más elaboradas pieza por pieza. La malla se construye con np.meshgrid(), igual que en Matplotlib:
import plotly.graph_objects as go
# 1. Crear la malla y evaluar la función
x = np.linspace(-2 * np.pi, 2 * np.pi, 80)
y = np.linspace(-2 * np.pi, 2 * np.pi, 80)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
# 2. Crear la figura con una superficie
fig = go.Figure(go.Surface(x=X, y=Y, z=Z, colorscale="RdBu"))
# 3. Agregar título y tamaño
fig.update_layout(title="z = sen(x)·cos(y)", width=650, height=550)
# 4. Mostrar
fig.show()Gira la superficie arrastrando con el ratón y acércate con la rueda del ratón.
| Parte | ¿Qué hace? |
|---|---|
go.Surface(x=X, y=Y, z=Z) |
Crea la superficie a partir de la malla. |
colorscale="RdBu" |
Paleta de colores (como cmap en Matplotlib). |
go.Figure(...) |
Crea la figura que contiene la superficie. |
fig.update_layout(...) |
Modifica el diseño: título, tamaño, ejes. |
fig.show() |
Muestra la figura. |

El conjunto gapminder contiene la esperanza de vida, el PIB per cápita y la población de 142 países entre 1952 y 2007, cada 5 años. Primero veamos sus columnas:
gapminder = px.data.gapminder()
print(gapminder.head()) country continent year ... gdpPercap iso_alpha iso_num
0 Afghanistan Asia 1952 ... 779.445314 AFG 4
1 Afghanistan Asia 1957 ... 820.853030 AFG 4
2 Afghanistan Asia 1962 ... 853.100710 AFG 4
3 Afghanistan Asia 1967 ... 836.197138 AFG 4
4 Afghanistan Asia 1972 ... 739.981106 AFG 4
[5 rows x 8 columns]
Con el argumento animation_frame, Plotly crea un cuadro por cada valor de una columna (aquí, uno por año) y agrega un botón ▶ y una barra deslizante para recorrerlos:
fig = px.scatter(
gapminder, # 1. Datos
x="gdpPercap", # 2. Eje x: PIB per cápita
y="lifeExp", # Eje y: esperanza de vida
size="pop", # 3. Tamaño del punto: población
color="continent", # Color del punto: continente
hover_name="country", # 4. Nombre que aparece al pasar el ratón
animation_frame="year", # 5. Un cuadro de la animación por cada año
log_x=True, # 6. Escala logarítmica en el eje x
size_max=50, # Tamaño máximo de los puntos
range_y=[25, 90], # Rango fijo del eje y en todos los cuadros
labels={"gdpPercap": "PIB per cápita (escala log)", "lifeExp": "Esperanza de vida",
"continent": "Continente", "year": "Año", "pop": "Población"}, # 7. Textos
title="Esperanza de vida vs. PIB per cápita"
)
fig.show() # 8. MostrarEl código se divide en estas partes:
| Parte | Argumentos | ¿Qué hace? |
|---|---|---|
| 1. Datos | gapminder |
El DataFrame con la información. Cada fila es un país en un año. |
| 2. Ejes | x="gdpPercap", y="lifeExp" |
Qué columnas se grafican en cada eje. |
| 3. Tamaño y color | size="pop", color="continent" |
Cada punto es más grande si el país tiene más población y tiene el color de su continente. Así una sola gráfica muestra cuatro variables. |
| 4. Información al pasar el ratón | hover_name="country" |
Muestra el nombre del país en la etiqueta emergente. |
| 5. Animación | animation_frame="year" |
Separa los datos por año: cada año es un cuadro de la animación. |
| 6. Ajustes de los ejes | log_x=True, size_max=50, range_y=[25, 90] |
La escala logarítmica evita que los países ricos aplasten a los demás; range_y mantiene el eje fijo para que los cuadros sean comparables. |
| 7. Textos | labels, title |
Nombres claros en español para ejes, leyenda y título. |
| 8. Mostrar | fig.show() |
Dibuja la gráfica con sus controles de animación. |
Presiona ▶ para ver cómo cambian los países con el tiempo, o mueve la barra deslizante para elegir un año. Pasa el ratón sobre un punto para ver de qué país se trata.
fig.write_html("mi_grafica.html") # Archivo HTML interactivo (se abre en el navegador)
fig.write_image("mi_grafica.png") # Imagen estática (requiere el paquete kaleido)| Práctica | ¿Por qué? |
|---|---|
| Etiqueta siempre los ejes, con unidades. | Una gráfica sin etiquetas no se puede interpretar. |
| Usa un título que diga qué muestra la gráfica. | El lector debe entenderla sin leer el código. |
| Elige el tipo de gráfica según la pregunta. | Comparar → barras; distribución → histograma; relación → dispersión. |
| Evita las gráficas de pastel con muchas categorías. | Es difícil comparar ángulos; mejor usa barras. |
| Inicia los ejes de las barras en cero. | Si no, las diferencias parecen mayores de lo que son. |
Usa paletas aptas para daltonismo, como viridis. |
Aproximadamente 1 de cada 12 hombres tiene alguna forma de daltonismo. |
| No sobrecargues la gráfica. | Demasiadas líneas o colores ocultan el mensaje principal. |
Resuelve los ejercicios en los editores interactivos. Recuerda que lo que escribas no se guarda al recargar la página. La primera vez que ejecutes un editor, las bibliotecas pueden tardar unos segundos en cargar.
Como en estos ejercicios el resultado es una gráfica, no hay revisión automática: compara tu gráfica con la de la solución.
plt.scatter().Las funciones son x**2 y -x**2 + 5. Los puntos de intersección tienen coordenadas xi y xi**2.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 100)
plt.plot(x, x**2, label="f(x) = x²")
plt.plot(x, -x**2 + 5, label="g(x) = -x² + 5")
xi = np.array([-np.sqrt(5 / 2), np.sqrt(5 / 2)])
plt.scatter(xi, xi**2, color="black", zorder=3, label="Intersecciones")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.grid(True)
plt.show()2026.plt.axvline().rng.normal(170, 8, size=1000). La media se calcula con estaturas.mean().
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(seed=2026)
estaturas = rng.normal(170, 8, size=1000)
plt.hist(estaturas, bins=30, color="tab:blue", edgecolor="white")
plt.axvline(estaturas.mean(), color="red", linestyle="--", label="Media")
plt.xlabel("Estatura (cm)")
plt.ylabel("Frecuencia")
plt.title("Distribución de estaturas simuladas")
plt.legend()
plt.show()Crea una figura de 2 × 2 con plt.subplots() y grafica en el intervalo \([-2, 2]\): \(x\), \(x^2\), \(x^3\) y \(|x|\). Cada gráfica debe tener su título.
plt.subplots(2, 2). Las otras posiciones son axs[0, 1], axs[1, 0] y axs[1, 1]. El valor absoluto es np.abs(x).
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-2, 2, 100)
fig, axs = plt.subplots(2, 2, figsize=(7, 6))
axs[0, 0].plot(x, x)
axs[0, 0].set_title("y = x")
axs[0, 1].plot(x, x**2)
axs[0, 1].set_title("y = x²")
axs[1, 0].plot(x, x**3)
axs[1, 0].set_title("y = x³")
axs[1, 1].plot(x, np.abs(x))
axs[1, 1].set_title("y = |x|")
fig.tight_layout()
plt.show()Grafica las curvas de nivel del paraboloide \(f(x, y) = x^2 + y^2\) en \([-3, 3] \times [-3, 3]\). ¿Qué forma tienen las curvas? ¿Por qué?
X, Y = np.meshgrid(x, y) y Z = X**2 + Y**2.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
Z = X**2 + Y**2
curvas = plt.contour(X, Y, Z, levels=10, cmap="viridis")
plt.clabel(curvas, fontsize=8)
plt.axis("equal")
plt.title("Curvas de nivel de f(x, y) = x² + y²")
plt.show()Las curvas son circunferencias centradas en el origen: \(x^2 + y^2 = c\) es la ecuación de una circunferencia de radio \(\sqrt{c}\).
Para usar Plotly en los editores de esta página, las primeras líneas del código lo instalan en el navegador y preparan fig.show() para que muestre la gráfica. No las modifiques. En Jupyter o Colab no las necesitas.
La primera vez que ejecutes un editor con Plotly, la instalación tarda entre 15 y 20 segundos. Ejecuta un editor de Plotly a la vez y espera a que aparezca la gráfica antes de ejecutar el siguiente.
Con los datos iris, crea una gráfica de dispersión del largo (petal_length) contra el ancho (petal_width) del pétalo, con un color por especie (species). Pasa el ratón sobre los puntos.
Los nombres de las columnas se escriben como texto: x="petal_length", y="petal_width" y color="species".
import plotly.express as px
iris = px.data.iris()
fig = px.scatter(iris, x="petal_length", y="petal_width", color="species",
title="Iris: largo vs. ancho del pétalo")
fig.show()Observa que con las medidas del pétalo las especies se separan muy bien, sobre todo setosa.
Grafica la superficie de la silla de montar \(z = x^2 - y^2\) en \([-2, 2] \times [-2, 2]\) con go.Surface(). Gírala con el ratón: ¿por qué crees que se llama así?
Z = X**2 - Y**2.
import numpy as np
import plotly.graph_objects as go
x = np.linspace(-2, 2, 60)
y = np.linspace(-2, 2, 60)
X, Y = np.meshgrid(x, y)
Z = X**2 - Y**2
fig = go.Figure(go.Surface(x=X, y=Y, z=Z, colorscale="Viridis"))
fig.update_layout(title="Silla de montar: z = x² - y²", width=600, height=500)
fig.show()El origen es un punto silla: es un mínimo en la dirección de \(x\) y un máximo en la dirección de \(y\).
En el capítulo de ciclos for aproximamos \(\pi\) con la serie de Leibniz. Ahora visualiza cómo se acerca a \(\pi\):
np.cumsum() obtén las sumas parciales (la suma acumulada hasta cada término).plt.axhline().terminos = 4 * (-1.0) ** k / (2 * k + 1), sumas_parciales = np.cumsum(terminos) y la línea horizontal en np.pi.
import numpy as np
import matplotlib.pyplot as plt
k = np.arange(200)
terminos = 4 * (-1.0) ** k / (2 * k + 1)
sumas_parciales = np.cumsum(terminos)
plt.figure(figsize=(7, 4))
plt.plot(k + 1, sumas_parciales, marker=".", linewidth=0.8, label="Sumas parciales")
plt.axhline(np.pi, color="red", linestyle="--", label="π")
plt.ylim(2.8, 3.5)
plt.xlabel("Número de términos")
plt.ylabel("Aproximación")
plt.title("Convergencia de la serie de Leibniz")
plt.legend()
plt.show()Las sumas parciales oscilan alrededor de \(\pi\): quedan por arriba cuando el último término es positivo y por debajo cuando es negativo, y cada vez se acercan más. Extra: haz la misma gráfica con px.line() para poder hacer zoom en las primeras sumas.
| Tema | Idea clave |
|---|---|
| Matplotlib | Gráficas estáticas: plt.plot, plt.scatter, plt.bar, plt.hist, plt.boxplot, plt.pie. |
| Elementos de una gráfica | xlabel, ylabel, title, legend, grid, annotate, savefig. |
| Varias gráficas | fig, axs = plt.subplots(filas, columnas) y los métodos ax.set_title(), ax.set_xlabel()… |
| Funciones \(f(x, y)\) | np.meshgrid() + plt.contour() o plt.imshow(). |
| SymPy + Matplotlib | sp.lambdify() convierte una expresión en una función de NumPy para graficarla. |
| Plotly Express | Gráficas interactivas con una función: px.scatter, px.line, px.histogram, px.box. |
| Plotly Graph Objects | Gráficas avanzadas, como superficies: go.Surface(). |
| Mostrar y guardar | fig.show(), fig.write_html(); en Matplotlib, plt.show() y plt.savefig(). |