Clase 15: Módulos, Pandas y Animaciones
Importando módulos
Python tiene reglas para la importación de módulos a nuestro código.
Recordemos que un módulo es un archivo cuya extensión es .py
.
Volviendo a usar el ejemplo de la clase anterior, tenemos un módulo
rotacion_p.py
.
En primer lugar, el intérprete busca un módulo denominado
rotación_p
dentro de los módulos incorporados automáticamente. La lista de dichos módulos se encuentra usando el métodosys.builtin_module_names
:
import sys
sys.builtin_module_names
('_abc',
'_ast',
'_codecs',
'_collections',
'_functools',
'_imp',
'_io',
'_locale',
'_operator',
'_signal',
'_sre',
'_stat',
'_string',
'_symtable',
'_thread',
'_tokenize',
'_tracemalloc',
'_typing',
'_warnings',
'_weakref',
'atexit',
'builtins',
'errno',
'faulthandler',
'gc',
'itertools',
'marshal',
'posix',
'pwd',
'sys',
'time')
'rotacion_p' in sys.builtin_module_names
False
En segundo lugar, busca un archivo
rotacion_p.py
en una lista de directorios dada por el atributosys.path
.
sys.path
['/Users/flavioc/Library/Mobile Documents/com~apple~CloudDocs/Documents/cursos/Python/GitLab/clase-python/clases',
'/Users/flavioc/miniconda3/envs/clases/lib/python312.zip',
'/Users/flavioc/miniconda3/envs/clases/lib/python3.12',
'/Users/flavioc/miniconda3/envs/clases/lib/python3.12/lib-dynload',
'',
'/Users/flavioc/miniconda3/envs/clases/lib/python3.12/site-packages']
Este atributo contiene el directorio local (el primero que aparece en la
lista de arriba), y una serie de directorios que provienen de - La
variable de entorno PYTHONPATH
- Un directorio dependiente de la
instalación (en este caso,
’/Users/flavioc/miniconda3/envs/clases/lib/python3.12`).
La manera pythonística de chequear si la variable de entorno
PYTHONPATH
existe es utilizar el método get
de os.environ
:
import os
os.environ.get('PYTHONPATH')
En nuestro caso no imprime nada, pero de la misma forma se puede setear dicha variable de entorno:
os.environ['PYTHONPATH'] = '..' # seteo la variable PYTHONPATH al directorio padre del directorio actual
os.environ.get('PYTHONPATH')
'..'
Por supuesto, todo va a depender de cómo tenemos estructurado nuestro código. En principio, aún cuando uno no utilice completamente las facilidades de Python como lenguaje orientado a objeto, agrupamos funciones que están relacionadas entre sí en distintos módulos. A medida que el código crece, es posible organizar los distintos módulos distribuyéndolos en directorios.
Importando módulos de directorios hijos
Imaginemos que tenemos la siguente estructura de código:
/miproyecto
main.py
/lib
rotacion.py
/graficos
simple.py
complejo.py
/tresd
vector.py
Es sencillo importar los módulos en los directorios hijos (lib
,
graficos
):
from graficos.simple import plot_data
from graficos.complejo import plot_data_complejo as plot_complejo
from graficos.tresd.vector import *
from lib.rotacion import rotate
Básicamente exponemos el módulo usando el operador .
para ir
incorporando los hijos. Por ejemplo, graficos.tresd.vector
refiere
al módulo que se encuentra en el archivo vector.py
en el directorio
graficos.tresd
.
Importando módulos desde padres o hermanos
Imports relativos
Para importar módulos que están en directorios padres o hermanos (estos
últimos son directorios al mismo nivel del directorio desde el cual
quiero importar) podemos diferentes estrategias. La primera de ellas es
usar la importación de paquetes relativos. Para ello, cada directorio
desde el que quiera importar debe poseer un archivo (en principio vacío)
denominado __init__.py
. Esto permite a Python reconocer los
directorios que contienen módulos aún cuando sean padres o hermanos.
Veamos ahora la estructura de directorios de miproyecto_relativo
con
los archivos __init__.py
agregados:
/miproyecto
main.py
__init__.py
/lib
rotacion.py
__init__.py
/graficos
__init__.py
simple.py
complejo.py
/tresd
__init__.py
vector.py
/tests
test_rotacion.py
Notemos que tests/test_rotacion.py
tiene también un main
, que
corre un test:
def test_rotacion():
v = np.array([1, 0, 0])
angle = np.array([0, 0, np.pi/2])
assert np.allclose(rotate(angle, v), np.array([0, -1, 0]))
if __name__ == "__main__":
test_rotacion()
print("All tests passed")
Esta es una estructura típica de Python, donde tengo tests que prueban
funciones en un módulo dado. Notemos cómo se importa el módulo
rotacion
desde test_rotacion.py
:
from ..lib.rotacion import rotate
Al igual que con directorios, ..
se refiere al directorio padre
relativo al directorio actual.
Si probamos desde el directorio miproyecto/tests
lo siguiente:
python test_rotacion.py
Nos encontraremos con el error:
ImportError: attempted relative import with no known parent package
Para correr el test, tenemos que ir al directorio padre del proyecto y correr el main del módulo explícitamente:
python -m miproyecto_relativo.tests.test_rotacion
All tests passed
Modificando sys.path
La forma anterior puede ser engorrosa en el caso en que se tengan muchos
módulos en archivos distribuidos en una estructura de directorios
complicada. Por otra parte, es posible modificar el atributo
sys.path
para incluir el directorio que sea de interés. En este
caso, modificamos test_rotacion.py
:
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
from lib.rotacion import rotate
Entonces, podemos correr desde el directorio tests
:
python test_rotacion.py
All tests passed
o desde su padre como
python -m tests.test_rotacion
o
python tests/test_rotacion.py
Pandas
Pandas
es una biblioteca de Python ampliamente utilizada para
análisis de datos y manipulación de datos tabulares. Proporciona
estructuras de datos potentes y flexibles, como DataFrame y Series, que
son fundamentales para trabajar con conjuntos de datos.
La instalamos como siempre
pip install pandas
import pandas as pd
Pandas tiene dos tipos fundamentales de estructuras de datos:
DataFrame
: Una estructura de datos tabular bidimensional con etiquetas en filas y columnas.Series
: Un arreglo unidimensional etiquetado capaz de contener cualquier tipo de datos.
# Crear una Serie a partir de una lista
s = pd.Series([1, 2, 3, 4, 5])
print(s)
0 1
1 2
2 3
3 4
4 5
dtype: int64
Notar que el print
ya muestra los datos en forma adecuada, indicando
los índices (columna de la izquierda) y el tipo de dato de la serie.
s.index, s.values
(RangeIndex(start=0, stop=5, step=1), array([1, 2, 3, 4, 5]))
import numpy as np
s_np = s.array # cast de la serie a un arreglo de numpy.
print(s_np)
np.sum(s_np)
<PandasArray>
[1, 2, 3, 4, 5]
Length: 5, dtype: int64
15
# Crear un DataFrame a partir de un diccionario
data = {'Nombre': ['Juan', 'María', 'Pedro', 'Ana'],
'Edad': [25, 30, 35, 40]}
df = pd.DataFrame(data)
print(df)
Nombre Edad
0 Juan 25
1 María 30
2 Pedro 35
3 Ana 40
Podemos agregar columnas como
df['Ocupacion'] = ['Estudiante', 'Ingeniera', 'Doctor', 'Profesora']
print(df)
Nombre Edad Ocupacion
0 Juan 25 Estudiante
1 María 30 Ingeniera
2 Pedro 35 Doctor
3 Ana 40 Profesora
Y podemos agregar una nueva fila:
eva = pd.DataFrame({'Nombre': ['Eva'], 'Edad': [28], 'Ocupacion': ['Ingeniera']})
df = pd.concat([df,eva], ignore_index=True)
print(df)
Nombre Edad Ocupacion
0 Juan 25 Estudiante
1 María 30 Ingeniera
2 Pedro 35 Doctor
3 Ana 40 Profesora
4 Eva 28 Ingeniera
Nota
En versiones anteriores de pandas a 2.0, existe una método
.append
. Sin embargo, ese método ya no está visible en la clase.
Notar también que cada valor del diccionario es una lista. Si se eligiera usar los correspondientes valores escalares, es necesario indicar el índice en el cual se quiere concatenar la fila:
lio = pd.DataFrame({'Nombre': 'Lionel', 'Edad': 37, 'Ocupacion': 'Futbolista'})
df = pd.concat([df,eva], ignore_index=True)
print(df)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[8], line 1
----> 1 lio = pd.DataFrame({'Nombre': 'Lionel', 'Edad': 37, 'Ocupacion': 'Futbolista'})
2 df = pd.concat([df,eva], ignore_index=True)
3 print(df)
File /usr/lib64/python3.11/site-packages/pandas/core/frame.py:664, in DataFrame.__init__(self, data, index, columns, dtype, copy)
658 mgr = self._init_mgr(
659 data, axes={"index": index, "columns": columns}, dtype=dtype, copy=copy
660 )
662 elif isinstance(data, dict):
663 # GH#38939 de facto copy defaults to False only in non-dict cases
--> 664 mgr = dict_to_mgr(data, index, columns, dtype=dtype, copy=copy, typ=manager)
665 elif isinstance(data, ma.MaskedArray):
666 import numpy.ma.mrecords as mrecords
File /usr/lib64/python3.11/site-packages/pandas/core/internals/construction.py:493, in dict_to_mgr(data, index, columns, dtype, typ, copy)
489 else:
490 # dtype check to exclude e.g. range objects, scalars
491 arrays = [x.copy() if hasattr(x, "dtype") else x for x in arrays]
--> 493 return arrays_to_mgr(arrays, columns, index, dtype=dtype, typ=typ, consolidate=copy)
File /usr/lib64/python3.11/site-packages/pandas/core/internals/construction.py:118, in arrays_to_mgr(arrays, columns, index, dtype, verify_integrity, typ, consolidate)
115 if verify_integrity:
116 # figure out the index, if necessary
117 if index is None:
--> 118 index = _extract_index(arrays)
119 else:
120 index = ensure_index(index)
File /usr/lib64/python3.11/site-packages/pandas/core/internals/construction.py:656, in _extract_index(data)
653 raise ValueError("Per-column arrays must each be 1-dimensional")
655 if not indexes and not raw_lengths:
--> 656 raise ValueError("If using all scalar values, you must pass an index")
658 elif have_series:
659 index = union_indexes(indexes)
ValueError: If using all scalar values, you must pass an index
Si en lugar de la lista, utilizamos un valor escalar tenemos que decirle en qué lugar (índice) queremos agregar el elemento
lio = pd.DataFrame({'Nombre': 'Lionel', 'Edad': 37, 'Ocupacion': 'Futbolista'}, index=[14])
df = pd.concat([df,lio])
print(df)
Nombre Edad Ocupacion
0 Juan 25 Estudiante
1 María 30 Ingeniera
2 Pedro 35 Doctor
3 Ana 40 Profesora
4 Eva 28 Ingeniera
14 Lionel 37 Futbolista
Notemos que si usamos ignore_index=True
, la fila se agrega al final
del DataFrame
, mientras que si no lo usamos
(ignore_index=False
), se mantiene el indice provisto por el
dataframe particular (en este caso, 14
).
print(df.head(2))
Nombre Edad Ocupacion
0 Juan 25 Estudiante
1 María 30 Ingeniera
Es fácil guardar los datos:
df.to_csv('../data/lista_gente.csv', index=False)
!cat ../data/lista_gente.csv
Nombre,Edad,Ocupacion
Juan,25,Estudiante
María,30,Ingeniera
Pedro,35,Doctor
Ana,40,Profesora
Eva,28,Ingeniera
Lionel,37,Futbolista
Del mismo modo, se puede leer un csv:
tasa_natalidad = pd.read_csv('../data/tasa-natalidad.csv')
# print(tasa_natalidad)
tasa_natalidad
indice_tiempo | natalidad_argentina | natalidad_ciudad_autonoma_de_buenos_aires | natalidad_buenos_aires | natalidad_catamarca | natalidad_cordoba | natalidad_corrientes | natalidad_chaco | natalidad_chubut | natalidad_entre_rios | ... | natalidad_neuquen | natalidad_rio_negro | natalidad_salta | natalidad_san_juan | natalidad_san_luis | natalidad_santa_cruz | natalidad_santa_fe | natalidad_santiago_del_estero | natalidad_tucuman | natalidad_tierra_del_fuego | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2000 | 19.0 | 14.3 | 17.5 | 25.8 | 17.2 | 22.7 | 25.8 | 19.4 | 21.3 | ... | 17.9 | 18.9 | 24.0 | 22.7 | 22.3 | 19.6 | 16.9 | 21.6 | 22.6 | 19.9 |
1 | 2001 | 18.2 | 13.9 | 16.9 | 24.9 | 15.9 | 21.9 | 22.2 | 18.4 | 20.5 | ... | 16.8 | 17.8 | 24.2 | 23.7 | 22.3 | 19.8 | 16.2 | 21.1 | 21.1 | 17.9 |
2 | 2002 | 18.3 | 13.6 | 17.0 | 24.4 | 16.6 | 23.3 | 24.9 | 17.1 | 19.5 | ... | 16.1 | 17.3 | 24.7 | 22.3 | 22.0 | 19.2 | 16.7 | 22.1 | 22.6 | 17.2 |
3 | 2003 | 18.4 | 14.2 | 17.3 | 22.5 | 17.5 | 22.2 | 20.9 | 18.7 | 19.8 | ... | 19.4 | 18.6 | 22.0 | 22.2 | 21.7 | 21.9 | 17.3 | 17.8 | 21.8 | 20.8 |
4 | 2004 | 19.3 | 14.9 | 18.5 | 20.6 | 17.8 | 22.7 | 25.1 | 19.3 | 19.5 | ... | 19.8 | 18.3 | 23.9 | 22.5 | 21.9 | 22.4 | 17.6 | 19.7 | 21.0 | 19.5 |
5 | 2005 | 18.5 | 14.5 | 17.9 | 19.7 | 17.1 | 20.2 | 22.6 | 19.2 | 19.0 | ... | 20.0 | 18.7 | 22.5 | 21.4 | 19.9 | 22.9 | 16.3 | 21.0 | 19.9 | 20.7 |
6 | 2006 | 17.9 | 14.6 | 17.7 | 18.3 | 16.5 | 18.7 | 19.6 | 20.0 | 17.2 | ... | 20.5 | 18.6 | 21.0 | 20.7 | 20.2 | 23.5 | 15.8 | 20.0 | 18.5 | 20.1 |
7 | 2007 | 17.8 | 14.1 | 17.7 | 18.3 | 16.3 | 18.9 | 18.4 | 20.2 | 16.9 | ... | 20.5 | 18.9 | 20.6 | 20.8 | 19.3 | 23.8 | 15.7 | 19.4 | 19.7 | 20.8 |
8 | 2008 | 18.8 | 15.1 | 18.6 | 18.6 | 17.4 | 19.7 | 20.8 | 21.4 | 17.2 | ... | 21.0 | 19.9 | 21.4 | 20.7 | 19.3 | 25.0 | 16.9 | 20.5 | 19.9 | 20.8 |
9 | 2009 | 18.6 | 14.6 | 18.4 | 17.4 | 17.4 | 19.9 | 20.4 | 21.3 | 17.5 | ... | 20.9 | 19.6 | 21.0 | 20.6 | 18.2 | 25.2 | 16.5 | 21.9 | 19.1 | 20.2 |
10 | 2010 | 18.7 | 14.9 | 18.9 | 16.9 | 17.2 | 19.8 | 21.2 | 21.2 | 17.4 | ... | 21.6 | 20.0 | 21.9 | 19.8 | 17.4 | 26.0 | 16.2 | 19.9 | 20.1 | 18.8 |
11 | 2011 | 18.5 | 14.8 | 18.8 | 16.0 | 16.9 | 19.9 | 22.6 | 20.7 | 17.2 | ... | 19.6 | 19.8 | 21.7 | 19.9 | 16.7 | 25.0 | 16.4 | 20.2 | 19.5 | 18.6 |
12 | 2012 | 17.9 | 14.2 | 18.1 | 15.0 | 16.5 | 18.6 | 20.2 | 20.2 | 16.7 | ... | 19.1 | 19.4 | 20.2 | 19.6 | 16.0 | 24.0 | 16.3 | 17.9 | 18.7 | 19.2 |
13 | 2013 | 17.9 | 14.3 | 17.8 | 16.9 | 16.0 | 19.0 | 19.9 | 18.5 | 17.3 | ... | 18.7 | 17.9 | 21.1 | 20.6 | 16.7 | 19.7 | 16.9 | 18.8 | 18.9 | 19.8 |
14 | 2014 | 18.2 | 14.3 | 17.9 | 17.4 | 16.8 | 19.8 | 20.2 | 17.8 | 17.8 | ... | 19.5 | 17.9 | 21.6 | 21.3 | 16.9 | 19.8 | 17.2 | 19.8 | 19.3 | 20.5 |
15 | 2015 | 17.9 | 13.7 | 17.3 | 17.2 | 16.4 | 19.3 | 22.7 | 17.4 | 17.8 | ... | 19.1 | 18.2 | 21.3 | 20.5 | 17.0 | 19.8 | 16.9 | 20.5 | 19.0 | 19.9 |
16 | 2016 | 16.7 | 13.1 | 16.2 | 16.7 | 15.7 | 18.5 | 19.1 | 16.6 | 16.6 | ... | 17.9 | 16.8 | 19.4 | 18.8 | 15.5 | 18.6 | 16.3 | 18.7 | 17.5 | 18.1 |
17 | 2017 | 16.0 | 11.7 | 15.4 | 15.8 | 15.0 | 18.3 | 19.8 | 15.3 | 16.3 | ... | 16.3 | 16.0 | 19.7 | 18.1 | 14.8 | 17.0 | 15.4 | 18.8 | 16.7 | 16.9 |
18 | 2018 | 15.4 | 11.6 | 14.6 | 16.4 | 14.5 | 18.0 | 21.0 | 14.3 | 15.6 | ... | 15.6 | 14.6 | 18.2 | 17.7 | 14.2 | 14.7 | 14.8 | 18.9 | 16.9 | 16.3 |
19 rows × 26 columns
Operaciones
Pandas provee de numerosos métodos para realizar operaciones sobre los datos de un DataFrame.
tasa_natalidad.head(2)
indice_tiempo | natalidad_argentina | natalidad_ciudad_autonoma_de_buenos_aires | natalidad_buenos_aires | natalidad_catamarca | natalidad_cordoba | natalidad_corrientes | natalidad_chaco | natalidad_chubut | natalidad_entre_rios | ... | natalidad_neuquen | natalidad_rio_negro | natalidad_salta | natalidad_san_juan | natalidad_san_luis | natalidad_santa_cruz | natalidad_santa_fe | natalidad_santiago_del_estero | natalidad_tucuman | natalidad_tierra_del_fuego | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2000 | 19.0 | 14.3 | 17.5 | 25.8 | 17.2 | 22.7 | 25.8 | 19.4 | 21.3 | ... | 17.9 | 18.9 | 24.0 | 22.7 | 22.3 | 19.6 | 16.9 | 21.6 | 22.6 | 19.9 |
1 | 2001 | 18.2 | 13.9 | 16.9 | 24.9 | 15.9 | 21.9 | 22.2 | 18.4 | 20.5 | ... | 16.8 | 17.8 | 24.2 | 23.7 | 22.3 | 19.8 | 16.2 | 21.1 | 21.1 | 17.9 |
2 rows × 26 columns
tasa_natalidad["natalidad_rio_negro"].mean()
18.273684210526316
for v,d,i in zip(tasa_natalidad.columns, tasa_natalidad.max(),tasa_natalidad.idxmax()):
provincia = v.split('_')[1]
print(f"la tasa máxima {d} de {provincia} ocurrió en {i}")
la tasa máxima 2018.0 de tiempo ocurrió en 18
la tasa máxima 19.3 de argentina ocurrió en 4
la tasa máxima 15.1 de ciudad ocurrió en 8
la tasa máxima 18.9 de buenos ocurrió en 10
la tasa máxima 25.8 de catamarca ocurrió en 0
la tasa máxima 17.8 de cordoba ocurrió en 4
la tasa máxima 23.3 de corrientes ocurrió en 2
la tasa máxima 25.8 de chaco ocurrió en 0
la tasa máxima 21.4 de chubut ocurrió en 8
la tasa máxima 21.3 de entre ocurrió en 0
la tasa máxima 26.5 de formosa ocurrió en 4
la tasa máxima 23.1 de jujuy ocurrió en 1
la tasa máxima 18.3 de la ocurrió en 3
la tasa máxima 22.6 de la ocurrió en 0
la tasa máxima 20.2 de mendoza ocurrió en 8
la tasa máxima 26.4 de misiones ocurrió en 4
la tasa máxima 21.6 de neuquen ocurrió en 10
la tasa máxima 20.0 de rio ocurrió en 10
la tasa máxima 24.7 de salta ocurrió en 2
la tasa máxima 23.7 de san ocurrió en 1
la tasa máxima 22.3 de san ocurrió en 0
la tasa máxima 26.0 de santa ocurrió en 10
la tasa máxima 17.6 de santa ocurrió en 4
la tasa máxima 22.1 de santiago ocurrió en 2
la tasa máxima 22.6 de tucuman ocurrió en 0
la tasa máxima 20.8 de tierra ocurrió en 3
Nótese que la primer columna ‘indice_tiempo’ también la toma como una
columna de datos, cuando en realidad, en este caso convendría que fuera
efectivamente la columna que indexa la tabla. Para eso, tenemos
set_index
.
tasa_natalidad.set_index('indice_tiempo',inplace=True)
tasa_natalidad.head(3)
natalidad_argentina | natalidad_ciudad_autonoma_de_buenos_aires | natalidad_buenos_aires | natalidad_catamarca | natalidad_cordoba | natalidad_corrientes | natalidad_chaco | natalidad_chubut | natalidad_entre_rios | natalidad_formosa | ... | natalidad_neuquen | natalidad_rio_negro | natalidad_salta | natalidad_san_juan | natalidad_san_luis | natalidad_santa_cruz | natalidad_santa_fe | natalidad_santiago_del_estero | natalidad_tucuman | natalidad_tierra_del_fuego | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
indice_tiempo | |||||||||||||||||||||
2000 | 19.0 | 14.3 | 17.5 | 25.8 | 17.2 | 22.7 | 25.8 | 19.4 | 21.3 | 25.7 | ... | 17.9 | 18.9 | 24.0 | 22.7 | 22.3 | 19.6 | 16.9 | 21.6 | 22.6 | 19.9 |
2001 | 18.2 | 13.9 | 16.9 | 24.9 | 15.9 | 21.9 | 22.2 | 18.4 | 20.5 | 22.4 | ... | 16.8 | 17.8 | 24.2 | 23.7 | 22.3 | 19.8 | 16.2 | 21.1 | 21.1 | 17.9 |
2002 | 18.3 | 13.6 | 17.0 | 24.4 | 16.6 | 23.3 | 24.9 | 17.1 | 19.5 | 25.1 | ... | 16.1 | 17.3 | 24.7 | 22.3 | 22.0 | 19.2 | 16.7 | 22.1 | 22.6 | 17.2 |
3 rows × 25 columns
También se pueden hacer otras operaciones, como ordenar por columnas
tasa_natalidad.sort_values('natalidad_argentina', ascending=True)
natalidad_argentina | natalidad_ciudad_autonoma_de_buenos_aires | natalidad_buenos_aires | natalidad_catamarca | natalidad_cordoba | natalidad_corrientes | natalidad_chaco | natalidad_chubut | natalidad_entre_rios | natalidad_formosa | ... | natalidad_neuquen | natalidad_rio_negro | natalidad_salta | natalidad_san_juan | natalidad_san_luis | natalidad_santa_cruz | natalidad_santa_fe | natalidad_santiago_del_estero | natalidad_tucuman | natalidad_tierra_del_fuego | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
indice_tiempo | |||||||||||||||||||||
2018 | 15.4 | 11.6 | 14.6 | 16.4 | 14.5 | 18.0 | 21.0 | 14.3 | 15.6 | 19.8 | ... | 15.6 | 14.6 | 18.2 | 17.7 | 14.2 | 14.7 | 14.8 | 18.9 | 16.9 | 16.3 |
2017 | 16.0 | 11.7 | 15.4 | 15.8 | 15.0 | 18.3 | 19.8 | 15.3 | 16.3 | 19.6 | ... | 16.3 | 16.0 | 19.7 | 18.1 | 14.8 | 17.0 | 15.4 | 18.8 | 16.7 | 16.9 |
2016 | 16.7 | 13.1 | 16.2 | 16.7 | 15.7 | 18.5 | 19.1 | 16.6 | 16.6 | 19.4 | ... | 17.9 | 16.8 | 19.4 | 18.8 | 15.5 | 18.6 | 16.3 | 18.7 | 17.5 | 18.1 |
2007 | 17.8 | 14.1 | 17.7 | 18.3 | 16.3 | 18.9 | 18.4 | 20.2 | 16.9 | 21.1 | ... | 20.5 | 18.9 | 20.6 | 20.8 | 19.3 | 23.8 | 15.7 | 19.4 | 19.7 | 20.8 |
2006 | 17.9 | 14.6 | 17.7 | 18.3 | 16.5 | 18.7 | 19.6 | 20.0 | 17.2 | 21.4 | ... | 20.5 | 18.6 | 21.0 | 20.7 | 20.2 | 23.5 | 15.8 | 20.0 | 18.5 | 20.1 |
2015 | 17.9 | 13.7 | 17.3 | 17.2 | 16.4 | 19.3 | 22.7 | 17.4 | 17.8 | 21.3 | ... | 19.1 | 18.2 | 21.3 | 20.5 | 17.0 | 19.8 | 16.9 | 20.5 | 19.0 | 19.9 |
2012 | 17.9 | 14.2 | 18.1 | 15.0 | 16.5 | 18.6 | 20.2 | 20.2 | 16.7 | 21.0 | ... | 19.1 | 19.4 | 20.2 | 19.6 | 16.0 | 24.0 | 16.3 | 17.9 | 18.7 | 19.2 |
2013 | 17.9 | 14.3 | 17.8 | 16.9 | 16.0 | 19.0 | 19.9 | 18.5 | 17.3 | 21.0 | ... | 18.7 | 17.9 | 21.1 | 20.6 | 16.7 | 19.7 | 16.9 | 18.8 | 18.9 | 19.8 |
2001 | 18.2 | 13.9 | 16.9 | 24.9 | 15.9 | 21.9 | 22.2 | 18.4 | 20.5 | 22.4 | ... | 16.8 | 17.8 | 24.2 | 23.7 | 22.3 | 19.8 | 16.2 | 21.1 | 21.1 | 17.9 |
2014 | 18.2 | 14.3 | 17.9 | 17.4 | 16.8 | 19.8 | 20.2 | 17.8 | 17.8 | 21.8 | ... | 19.5 | 17.9 | 21.6 | 21.3 | 16.9 | 19.8 | 17.2 | 19.8 | 19.3 | 20.5 |
2002 | 18.3 | 13.6 | 17.0 | 24.4 | 16.6 | 23.3 | 24.9 | 17.1 | 19.5 | 25.1 | ... | 16.1 | 17.3 | 24.7 | 22.3 | 22.0 | 19.2 | 16.7 | 22.1 | 22.6 | 17.2 |
2003 | 18.4 | 14.2 | 17.3 | 22.5 | 17.5 | 22.2 | 20.9 | 18.7 | 19.8 | 25.0 | ... | 19.4 | 18.6 | 22.0 | 22.2 | 21.7 | 21.9 | 17.3 | 17.8 | 21.8 | 20.8 |
2005 | 18.5 | 14.5 | 17.9 | 19.7 | 17.1 | 20.2 | 22.6 | 19.2 | 19.0 | 23.5 | ... | 20.0 | 18.7 | 22.5 | 21.4 | 19.9 | 22.9 | 16.3 | 21.0 | 19.9 | 20.7 |
2011 | 18.5 | 14.8 | 18.8 | 16.0 | 16.9 | 19.9 | 22.6 | 20.7 | 17.2 | 21.6 | ... | 19.6 | 19.8 | 21.7 | 19.9 | 16.7 | 25.0 | 16.4 | 20.2 | 19.5 | 18.6 |
2009 | 18.6 | 14.6 | 18.4 | 17.4 | 17.4 | 19.9 | 20.4 | 21.3 | 17.5 | 21.9 | ... | 20.9 | 19.6 | 21.0 | 20.6 | 18.2 | 25.2 | 16.5 | 21.9 | 19.1 | 20.2 |
2010 | 18.7 | 14.9 | 18.9 | 16.9 | 17.2 | 19.8 | 21.2 | 21.2 | 17.4 | 21.1 | ... | 21.6 | 20.0 | 21.9 | 19.8 | 17.4 | 26.0 | 16.2 | 19.9 | 20.1 | 18.8 |
2008 | 18.8 | 15.1 | 18.6 | 18.6 | 17.4 | 19.7 | 20.8 | 21.4 | 17.2 | 22.6 | ... | 21.0 | 19.9 | 21.4 | 20.7 | 19.3 | 25.0 | 16.9 | 20.5 | 19.9 | 20.8 |
2000 | 19.0 | 14.3 | 17.5 | 25.8 | 17.2 | 22.7 | 25.8 | 19.4 | 21.3 | 25.7 | ... | 17.9 | 18.9 | 24.0 | 22.7 | 22.3 | 19.6 | 16.9 | 21.6 | 22.6 | 19.9 |
2004 | 19.3 | 14.9 | 18.5 | 20.6 | 17.8 | 22.7 | 25.1 | 19.3 | 19.5 | 26.5 | ... | 19.8 | 18.3 | 23.9 | 22.5 | 21.9 | 22.4 | 17.6 | 19.7 | 21.0 | 19.5 |
19 rows × 25 columns
También se pueden filtrar los datos
tasa_natalidad[tasa_natalidad['natalidad_argentina'] > 18.5]
natalidad_argentina | natalidad_ciudad_autonoma_de_buenos_aires | natalidad_buenos_aires | natalidad_catamarca | natalidad_cordoba | natalidad_corrientes | natalidad_chaco | natalidad_chubut | natalidad_entre_rios | natalidad_formosa | ... | natalidad_neuquen | natalidad_rio_negro | natalidad_salta | natalidad_san_juan | natalidad_san_luis | natalidad_santa_cruz | natalidad_santa_fe | natalidad_santiago_del_estero | natalidad_tucuman | natalidad_tierra_del_fuego | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
indice_tiempo | |||||||||||||||||||||
2000 | 19.0 | 14.3 | 17.5 | 25.8 | 17.2 | 22.7 | 25.8 | 19.4 | 21.3 | 25.7 | ... | 17.9 | 18.9 | 24.0 | 22.7 | 22.3 | 19.6 | 16.9 | 21.6 | 22.6 | 19.9 |
2004 | 19.3 | 14.9 | 18.5 | 20.6 | 17.8 | 22.7 | 25.1 | 19.3 | 19.5 | 26.5 | ... | 19.8 | 18.3 | 23.9 | 22.5 | 21.9 | 22.4 | 17.6 | 19.7 | 21.0 | 19.5 |
2008 | 18.8 | 15.1 | 18.6 | 18.6 | 17.4 | 19.7 | 20.8 | 21.4 | 17.2 | 22.6 | ... | 21.0 | 19.9 | 21.4 | 20.7 | 19.3 | 25.0 | 16.9 | 20.5 | 19.9 | 20.8 |
2009 | 18.6 | 14.6 | 18.4 | 17.4 | 17.4 | 19.9 | 20.4 | 21.3 | 17.5 | 21.9 | ... | 20.9 | 19.6 | 21.0 | 20.6 | 18.2 | 25.2 | 16.5 | 21.9 | 19.1 | 20.2 |
2010 | 18.7 | 14.9 | 18.9 | 16.9 | 17.2 | 19.8 | 21.2 | 21.2 | 17.4 | 21.1 | ... | 21.6 | 20.0 | 21.9 | 19.8 | 17.4 | 26.0 | 16.2 | 19.9 | 20.1 | 18.8 |
5 rows × 25 columns
y contabilizarlos con el método .count()
:
tasa_natalidad[tasa_natalidad['natalidad_argentina'] > 18.5].count()
natalidad_argentina 5
natalidad_ciudad_autonoma_de_buenos_aires 5
natalidad_buenos_aires 5
natalidad_catamarca 5
natalidad_cordoba 5
natalidad_corrientes 5
natalidad_chaco 5
natalidad_chubut 5
natalidad_entre_rios 5
natalidad_formosa 5
natalidad_jujuy 5
natalidad_la_pampa 5
natalidad_la_rioja 5
natalidad_mendoza 5
natalidad_misiones 5
natalidad_neuquen 5
natalidad_rio_negro 5
natalidad_salta 5
natalidad_san_juan 5
natalidad_san_luis 5
natalidad_santa_cruz 5
natalidad_santa_fe 5
natalidad_santiago_del_estero 5
natalidad_tucuman 5
natalidad_tierra_del_fuego 5
dtype: int64
Graficando
Los DataFrames de Pandas tienen integrados la funcionalidad para
graficar de matplotlib
, de modo tal que se pueden graficar los datos
rápidamente:
import matplotlib.pyplot as plt
tasa_natalidad.plot()
plt.show()
import plotly.express as px
fig = px.line(data_frame=tasa_natalidad)
fig.show()
Ejercicios 15 (a)
En el archivo data/imdb_top_1000.csv
está la lista de las 1000
‘mejores’ peliculas de acuerdo a lo que refiere dicha plataforma.
Lea el archivo con Pandas.
Inspeccione el
DataFrame
con funciones de pandas (head
,columns
pueden ser métodos útiles).¿Cuál es la película más antigua que figura en este ranking?
Encuentre la ‘mejor’ película de acuerdo al índice
Meta_score
. (Busque la documentación del métodoiloc
de la claseDataFrame
de pandas)Encuentre la película más larga en la tabla (recuerde que el método
max
opera sobre números. Busque información sobre el métodostr
de unDataFrame
)Construya una función
del_director
que recibe un string con el nombre (posiblemente parcial) de un director y devuelve una lista de sus películas (str
puede serle también aquí útil).
Animaciones con Matploblib
Matplotlib tiene funciones para hacer animaciones de una manera conveniente. Hay excelente información sobre el tema en:
Vamos a ver brevemente cómo hacer animaciones, en pocos Pasos
Una animación simple en pocos pasos
pwd
'/home/fiol/Clases/IntPython/clases-python/clases'
%cd "./scripts/animaciones"
/home/fiol/Clases/IntPython/clases-python/clases/scripts/animaciones
%matplotlib tk
%run ejemplo_animation_1.py
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
plt.ioff()
# Creamos los datos
xmax = 2*np.pi
Npts= 50
x = np.linspace(0, xmax, Npts)
data = np.vstack([x, np.sin(x)])
def update_line(num, data, line):
line.set_data(data[:, :num])
return (line,)
# Creamos la figura e inicializamos
# Graficamos una línea sin ningún punto
# Fijamos las condiciones de graficación
fig1, ax = plt.subplots(figsize=(12,8))
L, = plt.plot([], [], '-o') # equivalente a la siguiente
# L = plt.plot([],[] , '-o')[0]
ax.set_xlim(0, xmax)
ax.set_ylim(-1.1, 1.1)
ax.set_xlabel('x')
ax.set_title('Animación de una oscilación')
#
line_ani = animation.FuncAnimation(fig1, update_line, Npts, fargs=(data, L), interval=100, blit=True)
plt.show()
Este código da como resultado una función oscilante que se va creando. Este es un ejemplo simple que puede ser útil para graficar datos de una medición o de un cálculo más o menos largo.
Preparación general
Como vemos, después de importar el submódulo animation
(además de lo
usual):
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
plt.ioff()
nos aseguramos que estamos trabajando en modo no interactivo (con
plt.ioff()
).
Creamos los datos para graficar
Creación de datos para graficar
Creamos los datos para mostrar en la animación.
xmax = 2*np.pi
Npts = 50
x = np.linspace(0, xmax, Npts)
data = np.vstack([x, np.sin(x)])
Acá data
es un array 2D, con los datos \(x\), \(y\).
Preparación de la figura
A continuación preparamos la zona de graficación:
Creamos la figura y eje
Creamos las líneas de graficación (una en este caso)
Fijamos los límites de graficación
Agregamos el texto, que va a ser invariante durante la animación
fig1, ax = plt.subplots(figsize=(12,8))
L, = plt.plot([0], [0], '-o', lw=3)
ax.set_xlim(0, xmax)
ax.set_ylim(-1.1, 1.1)
ax.set_xlabel('x')
ax.set_title('Animación de una oscilación')
Como sabemos, el llamado a plot()
devuelve una lista de líneas (de
un solo elemento). A este elemento lo llamamos L
, y ya le damos las
características que queremos que tenga. En este caso, fijamos el símbolo
(círculos), con líneas de ancho 3. Vamos a modificar esta línea L
en
cada cuadro de la animación.
Función para actualizar la línea
Debemos crear una función que modifique las curvas en cada cuadro.
def update_line(num, data, line):
line.set_data(data[:, :num])
return line,
Esta función debe recibir como argumento el número de cuadro, que acá
llamamos num
. Además, en este caso recibe los datos a graficar, y la
línea a modificar.
Esta función devuelve una línea L
, que es la parte del gráfico que
queremos que se actualice en cada frame.
Notemos acá que no es necesario que tome como argumento los datos
guardados en data
y la línea line
, ya que son variables globales
a las que hay acceso dentro del script. De la misma manera no es
necesario que devuelva la línea, por la misma razón.
Animación de la figura
Finalmente llamamos a la función que hace la animación:
animation.FuncAnimation()
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
np.info(animation.FuncAnimation)
FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, , cache_frame_data=True, **kwargs) Makes an animation by repeatedly calling a function *func. .. note:: You must store the created Animation in a variable that lives as long as the animation should run. Otherwise, the Animation object will be garbage-collected and the animation stops. Parameters ---------- fig : ~matplotlib.figure.Figure The figure object used to get needed events, such as draw or resize. func : callable The function to call at each frame. The first argument will be the next value in frames. Any additional positional arguments can be supplied via the fargs parameter. The required signature is:: def func(frame, fargs) -> iterable_of_artists If ``blit == True``, *func must return an iterable of all artists that were modified or created. This information is used by the blitting algorithm to determine which parts of the figure have to be updated. The return value is unused ifblit == False
and may be omitted in that case. frames : iterable, int, generator function, or None, optional Source of data to pass func and each frame of the animation - If an iterable, then simply use the values provided. If the iterable has a length, it will override the save_count kwarg. - If an integer, then equivalent to passingrange(frames)
- If a generator function, then must have the signature:: def gen_function() -> obj - If None, then equivalent to passingitertools.count
. In all of these cases, the values in frames is simply passed through to the user-supplied func and thus can be of any type. init_func : callable, optional A function used to draw a clear frame. If not given, the results of drawing from the first item in the frames sequence will be used. This function will be called once before the first frame. The required signature is:: def init_func() -> iterable_of_artists Ifblit == True
, init_func must return an iterable of artists to be re-drawn. This information is used by the blitting algorithm to determine which parts of the figure have to be updated. The return value is unused ifblit == False
and may be omitted in that case. fargs : tuple or None, optional Additional arguments to pass to each call to func. save_count : int, default: 100 Fallback for the number of values from frames to cache. This is only used if the number of frames cannot be inferred from frames, i.e. when it's an iterator without length or a generator. interval : int, default: 200 Delay between frames in milliseconds. repeat_delay : int, default: 0 The delay in milliseconds between consecutive animation runs, if repeat is True. repeat : bool, default: True Whether the animation repeats when the sequence of frames is completed. blit : bool, default: False Whether blitting is used to optimize drawing. Note: when using blitting, any animated artists will be drawn according to their zorder; however, they will be drawn on top of any previous artists, regardless of their zorder. cache_frame_data : bool, default: True Whether frame data is cached. Disabling cache might be helpful when frames contain large objects. Methods: new_frame_seq -- Return a new sequence of frame information. new_saved_frame_seq -- Return a new sequence of saved/cached frame information. pause -- Pause the animation. resume -- Resume the animation. save -- Save the animation as a movie file by drawing every frame. to_html5_video -- Convert the animation to an HTML5<video>
tag. to_jshtml -- Generate HTML representation of the animation.
line_anim = animation.FuncAnimation(fig1, update_line, Npts,
fargs=(data, L), interval=100, blit=True)
La función FuncAnimation()
toma como argumentos:
la figura (
fig1
) donde se realiza el gráfico.Una función a la que llama antes de dibujar cada frame (
update_line
),El argumento
interval
da el tiempo entre cuadros, en milisegundos.El argumento
fargs
es una tuple con los argumentos que necesita la funciónupdate_line()
. En este caso(data, L)
.El argumento
blit=True
hace que sólo se actualicen las partes que cambian en la animación, mientras que las partes estáticas no se dibujan en cada cuadro.
Es importante que el objeto creado por FuncAnimation()
no se
destruya. Esto lo podemos asegurar asignando el objeto resultante a una
variable, en este caso line_anim.
Opcional: grabar la animación a un archivo
Podemos grabar la animación a un archivo usando el método save()
o
el método to_html5_video()
del objeto (anim
) que devuelve la
función FuncAnimation()
.
Para poder grabar a archivo las animaciones se necesita tener instalados programas externos (alguno de ffmpeg, avconv, imagemagick). Ver https://matplotlib.org/api/animation_api.html para más información.
Segundo ejemplo
Veamos un ejemplo similar al primero, pero donde vamos cambiando los límites de los ejes en forma manual, a medida que los datos lo requieren
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Calcula los datos en tiempo real.
def data_gen(t=0):
cnt = 0
while cnt < 1000:
cnt += 1
t += 0.1
yield t, np.sin(2 * np.pi * t) * np.exp(-t / 10.)
# Necesitamos que se puede acceder a estas variables
# desde varias funciones -> globales
fig, ax = plt.subplots()
line, = ax.plot([], [], lw=2)
xdata, ydata = [], []
def init():
ax.grid()
ax.set_ylim(-1.1, 1.1)
ax.set_xlim(0, 10)
del xdata[:]
del ydata[:]
line.set_data(xdata, ydata)
return line,
def run(data):
# update the data
t, y = data
xdata.append(t)
ydata.append(y)
xmin, xmax = ax.get_xlim()
# Si los datos salen del eje, agrandamos el eje
# Después tenemos que redibujar el canvas manualmente
if t >= xmax:
ax.set_xlim(xmin, 2 * xmax)
ax.figure.canvas.draw()
line.set_data(xdata, ydata)
return line,
ani = animation.FuncAnimation(fig, run, data_gen, blit=False,
interval=30,repeat=False, init_func=init)
plt.show()
%run animate_decay.py
/home/fiol/Clases/IntPython/clases-python/clases/scripts/animaciones/animate_decay.py:46: UserWarning: frames=<function data_gen at 0x7ff058744900> which we can infer the length of, did not pass an explicit save_count and passed cache_frame_data=True. To avoid a possibly unbounded cache, frame data caching has been disabled. To suppress this warning either pass cache_frame_data=False or save_count=MAX_FRAMES. ani = animation.FuncAnimation(fig, run, data_gen, blit=False, interval=30,
plt.style.reload_library()
plt.style.use('default')
Tercer ejemplo: Quiver
Para hacer una animación de un campo de fuerzas o velocidades
necesitamos datos en tres dimensiones. El siguiente ejemplo sigue los
pasos de la animación anterior, excepto en la creación de datos y la
graficación, que en lugar de usar plot()
usa quiver()
:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
plt.style.use('ggplot')
plt.ioff()
# ############################################################
# Creación de datos
x = np.linspace(-3, 3, 91)
t = np.linspace(0, 25, 30)
y = np.linspace(-3, 3, 91)
X3, Y3, T3 = np.meshgrid(x, y, t)
sinT3 = np.sin(2*np.pi*T3 /
T3.max(axis=2)[..., np.newaxis])
G = (X3**2 + Y3**2)*sinT3
# Graficar una flecha cada step puntos
step = 10
x_q, y_q = x[::step], y[::step]
# Create U and V vectors to plot
U = G[::step, ::step, :-1].copy()
V = np.roll(U, shift=3, axis=2)
# ############################################################
# Figura y ejes.
fig1, ax = plt.subplots(figsize=(12,8))
ax.set_aspect('equal')
ax.set(xlim=(-4, 4), ylim=(-4, 4))
qax = ax.quiver(x_q, y_q, U[..., 0], V[..., 0],
scale=100)
def animate(i):
qax.set_UVC(U[..., i], V[..., i])
anim = animation.FuncAnimation(fig1, animate, interval=100, frames=len(t)-1, repeat=True)
# anim.save('quiver.gif', writer='imagemagick')
anim.save('quiver.mp4')
plt.show()
%run ejemplo_quiver.py
Comentarios:
Se utilizó la función
quiver()
para generar un campo vectorial. La forma de esta función es:quiver([X, Y], U, V, [C], **kw)
X, Y define the arrow locations, U, V define the arrow directions, and C optionally sets the color.
Se utilizaron Ellipsis, por ejemplo en casos como:
U[..., 0]
Las elipsis (tres puntos o la palabra
Ellipsis
) indican todo el rango para todas las dimensiones que no se dan explícitamente. En este ejemplo el arrayU
tiene tres dimensiones, por lo que tendremos:U[..., 0] = U[:, :, 0]
En general, las elipses reemplazan a los dos puntos en todas las dimensiones no dadas explícitamente
a = np.arange(36)
a2 = a.reshape((6,-1))
a4 = a.reshape((2,3,2,3))
print(a2[:,0])
print(a2[..., 0])
[ 0 6 12 18 24 30]
[ 0 6 12 18 24 30]
print(a4[0,:,:,0])
print(a4[0,...,0])
[[ 0 3]
[ 6 9]
[12 15]]
[[ 0 3]
[ 6 9]
[12 15]]
(a4[...,0] == a4[:,:,:,0]).all()
True
Uso de
np.roll(a, shift, axis=None)
que mueve elementos una distanciashift
a lo largo del ejeaxis
, y cuando pasan la última posición los reintroduce al principio. Por ejemplo, en una dimensión:
x = np.arange(10)
print(x)
print(np.roll(x, 2))
[0 1 2 3 4 5 6 7 8 9]
[8 9 0 1 2 3 4 5 6 7]
Ejercicios 15 (b)
Utilizando Matplotlib:
Hacer un gráfico donde dibuje una parábola \(y = x^{2}\) en el rango \([-5,5]\).
En el mismo gráfico, agregar un círculo en \(x=-5\).
El círculo debe moverse siguiendo la curva, como se muestra en la figura
Caída libre 2: Realizar animaciones del ejercicio de caída libre, de forma tal que:
La animación tenga un cartel indicando el tiempo, y la velocidad y altura correspondiente a ese tiempo.
Agregar una “cola fantasma” a la partícula, que muestre posiciones anteriores.
.