La visualización científica es una disciplina que se enfoca en la generación de representaciones efectivas de los datos científicos para su exploración, su análisis y su comunicación efectiva.
Python nos da acceso a toda una diversidad de alternativas para producir visualizaciones estáticas y dinámicas, tanto en 2D como en 3D. En particular, el paquete Matplotlib es la opción más empleada para la visualización y se encuentra integrada en varios pilares de la plataforma científica de Python, como son: Pandas, IPython y Jupyter.
La forma acostumbrada de importar Matplotlib es mediante la siguiente instrucción:
import matplotlib.pyplot as plt
import numpy as np
El siguiente comándo mágico
de IPython le indica a Jupyter que los gráficos generados deberían ser desplegados en la libreta de Jupyter, en lugar de en una ventana independiente:
%matplotlib inline
Matplotlib dispone de distintos estilos, que uno puede escoger para darle una apariencia uniforme a las gráficas. La lista de estilos disponibles se almacena en el objeto: plt.style.available
:
plt.style.available
['seaborn-paper', 'seaborn-white', 'seaborn-talk', 'seaborn', 'fast', 'seaborn-ticks', 'tableau-colorblind10', 'dark_background', 'seaborn-poster', 'Solarize_Light2', 'seaborn-colorblind', 'seaborn-notebook', 'seaborn-bright', 'seaborn-deep', 'grayscale', 'ggplot', '_classic_test', 'classic', 'seaborn-muted', 'seaborn-pastel', 'seaborn-whitegrid', 'seaborn-dark', 'bmh', 'seaborn-dark-palette', 'seaborn-darkgrid', 'fivethirtyeight']
Para escoger el estilo que queremos usar para nuestras gráficas usamos el método plt.style.use(estilo)
plt.style.use("ggplot")
fig = plt.figure()
ax = plt.subplot(1, 1, 1)
fig.suptitle("Mi primera figura en Matplotlib") #Este es el encabezado de la figura
Text(0.5,0.98,'Mi primera figura en Matplotlib')
Matplotlib requiere secuencias típicamente numéricas (listas, tuplas o arreglos de Numpy) para construir una gráfica.
x = np.linspace(-np.pi, np.pi, 1000) #Esta función crea una secuencia de 1000 elementos que divide el rango entre -3.141516 y 3.141516
y = np.sin(x) #y contiene ahora el resultado de aplicar la función seno a cada elemento de la primera secuencia
plt.plot(x, y, "r-") #plt.plot() traza en este caso una línea roja ("r-") que une todos los puntos
[<matplotlib.lines.Line2D at 0x7f69b3a08fd0>]
En el siguiente ejemplo, generamos dos secuencias al azar y le solicitamos a Matplotlib que creé un gráfico de dispersión o scatterplot.
x1 = np.random.random(1000)
y1 = np.random.random(1000)
plt.scatter(x1, y1, c="green", marker="+") # En este caso el color y el tipo de marcador lo seleccionamos mediante los argumentos c y marker, respectivamente
<matplotlib.collections.PathCollection at 0x7f6984b582e8>
Con Matplotlib también es posible graficar datos categóricos:
import urllib.request
signos = ".,;-_\"'!?()[]"
palabras = []
with urllib.request.urlopen(
"http://www.gutenberg.org/ebooks/1112.txt.utf-8") as romeo:
for linea in romeo.readlines():
linea_limpia = linea.decode("utf8").lower()
for signo in signos:
linea_limpia = linea_limpia.replace(signo, "")
palabras.extend(linea_limpia.split())
len(palabras)
28974
El código que reemplaza los signos de puntuación en el ejemplo anterior podría implementarse de forma más sintética empleando los métodos .maketrans()
y .translate()
asociados a las cadenas de texto o incluso usando expresiones regulares. Por ejemplo:
tabla_traduccion = str.maketrans({letra:None for letra in ".,;-_\"'!?()[]"}) #
"Mon. Thou villain Capulet!- Hold me not, let me go.".translate(tabla_traduccion)
'Mon Thou villain Capulet Hold me not let me go'
casas = ("capulet", "montague")
personajes = ("juliet", "romeo", "mercutio", "tybalt")
c_data = {c: palabras.count(c) for c in casas}
p_data = {p: palabras.count(p) for p in personajes}
c_data
{'capulet': 33, 'montague': 31}
p_data
{'juliet': 65, 'romeo': 143, 'mercutio': 21, 'tybalt': 55}
plt.style.use("seaborn")
fig = plt.figure() #Crea una figura vacia
fig, axes = plt.subplots(1, 2, sharey=True) #Queremos dos plots, uno al lado de otro
# axes es una tupla con una referencia a cada una de las gráficas
fig.suptitle("Romeo y Julieta") #Agrega el titulo principal
plt.sca(axes[0]) #Activa el primer plot
plt.xticks(rotation=-30) #Gira las leyendas del eje x
axes[0].bar(p_data.keys(), p_data.values()) #Crea grafica de barras
axes[0].set_title("Frecuencia por personaje") #Pone un titulo al plot
axes[1].bar(c_data.keys(), c_data.values()) #Crea grafica de barras
axes[1].set_title("Frecuencia por familia") #Pone un titulo al plot
# Los siguientes dos ciclos colocan el valor encima de cada columna
for columna, datos in enumerate(p_data.items()):
axes[0].annotate(s=datos[1], xy=(columna, datos[1]))
for columna, datos in enumerate(c_data.items()):
axes[1].annotate(s=datos[1], xy=(columna, datos[1]))
<Figure size 576x396 with 0 Axes>
Pandas integra la funcionalidad de Matplotlib en sus objetos DataFrame y Series, por lo que a veces obtener un gráfico con los datos de un objeto de Pandas es tan fácil como invocar el método .plot()
.
Primero importamos Pandas de la forma acostumbrada:
import pandas as pd
Luego creamos un nuevo data frame:
alumnos = {
"nombre":{1:"cristina", 2:"moises",3:"fernando",4:"alejandro",
5:"victoria", 6:"elba", 7:"paola"},
"color":{1:"azul", 2:"rojo", 3:"amarillo", 4:"verde",
5:"cafe", 6:"azul", 7:"morado"},
"so": {1:"macos",2:"macos",3:"macos",4:"macos",5:"linux",
6:"macos",7:"linux"}
}
alumnos_df = pd.DataFrame(alumnos, dtype='category')
alumnos_df.so.value_counts().plot(kind='bar')
<matplotlib.axes._subplots.AxesSubplot at 0x7f697d68d2b0>
alumnos_df.color.value_counts().plot(kind='bar',
color=('blue','g','r','purple','brown','y'))
<matplotlib.axes._subplots.AxesSubplot at 0x7f697d5d6c50>
También podemos hacer lo mismo con un DataFrame que hemos creado a partir de importar un archivo de datos:
capufe_df = pd.read_csv("CAPUFE-21201DGST13ZONA32PCTLALPAN.csv", #Esta línea especifica la ruta del archivo
encoding="latin1", #En este caso el archivo no contiene texto Unicode utf8, por lo que es necesario especificarle a Pandas que usaremos otra codificación
parse_dates=['Fecha de Inicio',
'Fecha de Termino'], #También le vamos a indicar a Pandas las columnas que contienen las fechas
dtype={11:'object', 12:'object', 16:'object', 17:'object', 25:'category'}) #Podemos especificar el tipo de cada columna mediante el argumento dtype=
capufe_df.head()
CARRETERA | TRAMO | KILOMETRO | DIAS DE LA SEMANA | SENTIDO | HORA | Tipo | Tipo vehículo | LOCALIDAD_ORIGEN | ESTADO-ORIGEN | ... | W 2 | W 3 | W 4 | W 5 | W 6 | W 7 | W 8 | W 9 | Fecha de Inicio | Fecha de Termino | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | México - Cuernavaca (Cuota) | México - T. Izq. Cuautla | 23.36 | 2 | 1 | 0 | A | AUTOMOVIL PARTICULAR | Benito Juárez | D F | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2013-07-23 | 2013-07-28 |
1 | México - Cuernavaca (Cuota) | México - T. Izq. Cuautla | 23.36 | 2 | 1 | 0 | 11 | TRACTOR DE 3 EJES SEMIRREMOLQUE DE 2 EJES | Benito Juárez | D F | ... | 11327.0 | 8414.0 | 8827.0 | 7925.0 | NaN | NaN | NaN | NaN | 2013-07-23 | 2013-07-28 |
2 | México - Cuernavaca (Cuota) | México - T. Izq. Cuautla | 23.36 | 2 | 1 | 0 | 8 | CAMION DE 3 EJES | Benito Juárez | D F | ... | 3650.0 | 3666.0 | NaN | NaN | NaN | NaN | NaN | NaN | 2013-07-23 | 2013-07-28 |
3 | México - Cuernavaca (Cuota) | México - T. Izq. Cuautla | 23.36 | 2 | 1 | 0 | 8 | CAMION DE 3 EJES | Toluca de Lerdo | MEX | ... | 11450.0 | 9299.0 | NaN | NaN | NaN | NaN | NaN | NaN | 2013-07-23 | 2013-07-28 |
4 | México - Cuernavaca (Cuota) | México - T. Izq. Cuautla | 23.36 | 2 | 1 | 0 | 7 | CAMION DE 2 EJES | Heroica Zitácuaro | MICH | ... | 6744.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2013-07-23 | 2013-07-28 |
5 rows × 42 columns
list(enumerate(capufe_df.columns))
[(0, 'CARRETERA'), (1, 'TRAMO'), (2, 'KILOMETRO'), (3, 'DIAS DE LA SEMANA'), (4, 'SENTIDO'), (5, 'HORA'), (6, 'Tipo'), (7, 'Tipo vehículo'), (8, 'LOCALIDAD_ORIGEN'), (9, 'ESTADO-ORIGEN'), (10, 'CLAVE POBLACION-ORIGEN'), (11, 'Colonia_Origen'), (12, 'Delegación_Origen'), (13, 'LOCALIDAD_DESTINO'), (14, 'ESTADO-DESTINO'), (15, 'CLAVE POBLACION-DESTINO'), (16, 'Colonia_Destino'), (17, 'Delegación_Destino'), (18, 'Clave marca'), (19, 'Marca'), (20, 'AÑO'), (21, 'Clase combustible'), (22, 'Pasajeros'), (23, 'Tripulantes'), (24, 'Motivo de viaje'), (25, 'Clave carga'), (26, 'Carga'), (27, 'Cantidad'), (28, 'Unidad'), (29, 'Tonelada'), (30, 'Mercado'), (31, 'W 1'), (32, 'W 2'), (33, 'W 3'), (34, 'W 4'), (35, 'W 5'), (36, 'W 6'), (37, 'W 7'), (38, 'W 8'), (39, 'W 9'), (40, 'Fecha de Inicio'), (41, 'Fecha de Termino')]
Podemos dar un vistazo al contenido del este conjunto de datos usando dos filtros lógicos:
hacia_morelos = capufe_df["SENTIDO"] == 1 # Elegimos el sentido hacia Morelos
fig = plt.figure() # Inicializamos la figura
fig.add_subplot(1, 2, 1) # Creamos la primera gráfica
ax = capufe_df[hacia_morelos]["ESTADO-DESTINO"].value_counts().plot(kind='bar')
hacia_cdmx = capufe_df["SENTIDO"] == 2 # Elegimos el sentido hacia la CdMx
ax.set_title("De la CdMx")
fig.add_subplot(1, 2, 2) # Creamos la segunda gráfica
ax = capufe_df[hacia_cdmx]["ESTADO-DESTINO"].value_counts()["D F":"COAH"].plot(kind='bar')
ax.set_title("Hacia la CdMx")
Text(0.5,1,'Hacia la CdMx')
periodo = capufe_df['Fecha de Termino'] - capufe_df['Fecha de Inicio']
En los datos que contiene este dataset podemos ver una gran disparidad entre los tipos de vehículos que cruzan por la caseta de cobro en las fechas seleccionadas, como muestra la siguiente gráfica:
fig = plt.figure()
fig.suptitle("Vehículos ")
capufe_df['Tipo vehículo'].value_counts().plot(kind='bar')
<matplotlib.axes._subplots.AxesSubplot at 0x7f697cf30240>
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x =[1,2,3,4,5,6,7,8,9,10]
y =[5,6,2,3,13,4,1,2,4,8]
z =[2,3,3,3,5,7,9,11,9,10]
ax.scatter(x, y, z, c='r', marker='o')
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
plt.show()