Notebook 1: Conceptos básicos de Python y series de tiempo con pandas¶
Mini-curso de Introducción a herramientas computacionales para Oceanografía Física¶
Oceanografía Física, CICESE¶
21 de septiembre de 2026
La parte de fundamentos de Python de este notebook está basada en la lección Programming with Python de Software Carpentry, publicada bajo la Creative Commons - Attribution License. Traducción, adaptación al proyecto del curso y modificaciones por Karina Ramos Musalem.
Para el trabajo con datos tabulados usaremos pandas, una biblioteca de Python para trabajar con datos organizados en tablas y series de tiempo.
Volviendo al caso del curso, María Luisa ya organizó los archivos del proyecto desde la terminal. Ahora puede comenzar a trabajar con los datos. Tenemos dos series de tiempo en archivos CSV: el índice oceánico de El Niño (ONI) y precipitación mensual acumulada en Ensenada. Estos datos fueron proporcionados por la Dra. Marisol García Reyes.
Pregunta de este notebook: ¿qué contienen estos archivos y cómo podemos empezar a explorarlos de una forma que después podamos reutilizar?
Antes de empezar¶
Al terminar este notebook:
- recordarán qué es una variable y reconocerán algunos tipos de objetos de Python;
- importarán bibliotecas y usarán funciones que viven dentro de ellas;
- leerán archivos CSV con
pandas; - seleccionarán columnas, filas y datos que cumplen una condición;
- usarán
if,elifyelsepara tomar decisiones; - escribirán y usarán una función;
- usarán un ciclo
forpara repetir una operación; - identificarán datos faltantes (
NaN); - producirán una primera visualización de las series de tiempo.
JupyterLab¶
Los Jupyter notebooks combinan celdas de código, figuras,
ecuaciones y texto. Una celda de código se ejecuta con Shift + Enter.
El orden de ejecución sí importa: una variable sólo existe después de ejecutar la
celda que la define.
Antes de abrir este notebook, desde la raíz del proyecto:
cd enso_baja
conda activate oceanografia
jupyter lab
Este notebook debe estar en notebooks/ y los datos en data/. Por eso las rutas que
usaremos comienzan con ../data/: .. significa subir un directorio, como vimos en
la sesión de terminal.
1. Bibliotecas, objetos y variables¶
Preguntas a responder¶
- ¿Qué es una biblioteca de Python y cómo la usamos?
- ¿Qué guarda una variable?
- ¿Cómo podemos averiguar qué tipo de objeto tenemos?
Objetivos¶
- Importar bibliotecas usando nombres cortos convencionales.
- Asignar objetos a variables.
- Usar
type()para inspeccionar objetos.
Python incluye herramientas básicas, pero gran parte de la programación científica se apoya en bibliotecas: colecciones de funciones, clases y otros objetos diseñados para realizar tareas relacionadas.
En este proyecto usaremos:
numpypara operaciones numéricas;pandaspara tablas y series de tiempo;matplotlibpara visualización.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
La instrucción import pandas as pd importa la biblioteca pandas y le asigna el nombre
corto pd. Es una convención muy común: otras personas reconocerán inmediatamente
qué biblioteca estamos usando.
pd también es un objeto de Python:
type(pd)
module
Una variable es un nombre que asociamos con un objeto. Por ejemplo, podemos guardar la ruta de nuestro archivo ONI en una variable:
filename = "../data/oni_mensual_1981_2026.csv"
type(filename)
str
filename es un str (string o cadena de caracteres). El signo = asigna el
objeto de la derecha al nombre de la izquierda.
Los nombres de variables pueden contener letras, números y _, pero no pueden comenzar
con un número y distinguen entre mayúsculas y minúsculas.
Ayuda: en Jupyter puedes consultar la documentación de muchas funciones escribiendo
?funcion, por ejemplo?pd.read_csv, o usandohelp(funcion).
2. Leer nuestro primer archivo CSV¶
Pregunta a responder¶
¿Cómo representamos en Python una tabla con fechas y valores?
pandas utiliza principalmente dos estructuras:
DataFrame: una tabla bidimensional con filas y columnas etiquetadas;Series: una sola columna de datos con un índice.
Usaremos pd.read_csv() para leer un archivo de texto separado por comas. El argumento
parse_dates=["time"] le indica a pandas que interprete la columna time como fechas.
oni = pd.read_csv(filename, parse_dates=["time"])
oni.head()
| time | oni_degC | |
|---|---|---|
| 0 | 1981-09-01 | -0.25 |
| 1 | 1981-10-01 | -0.16 |
| 2 | 1981-11-01 | -0.13 |
| 3 | 1981-12-01 | -0.15 |
| 4 | 1982-01-01 | -0.08 |
head() es un método del objeto DataFrame: una función asociada al propio objeto.
La notación objeto.metodo() aparecerá constantemente en Python científico.
Inspeccionemos nuestro nuevo objeto.
type(oni)
pandas.DataFrame
oni.columns
Index(['time', 'oni_degC'], dtype='str')
oni.shape
(540, 2)
oni.dtypes
time datetime64[us] oni_degC float64 dtype: object
oni.describe()
| time | oni_degC | |
|---|---|---|
| count | 540 | 540.000000 |
| mean | 2004-02-15 08:00:00 | 0.046185 |
| min | 1981-09-01 00:00:00 | -1.850000 |
| 25% | 1992-11-23 12:00:00 | -0.552500 |
| 50% | 2004-02-15 12:00:00 | -0.075000 |
| 75% | 2015-05-08 18:00:00 | 0.572500 |
| max | 2026-08-01 00:00:00 | 2.750000 |
| std | NaN | 0.871231 |
shape contiene el número de filas y columnas. dtypes muestra el tipo de dato de cada
columna y describe() calcula un resumen estadístico de las columnas numéricas.
Pon en práctica lo que aprendiste¶
- ¿Cuántos meses contiene el archivo?
- ¿Cuál es el valor máximo y mínimo de
oni_degC? - ¿Qué fecha corresponde al valor máximo?
Intenta resolver las dos primeras preguntas usando la información que ya produjo
describe(). Para la tercera puedes experimentar con idxmax().
Una posible solución
len(oni)
oni["oni_degC"].min()
oni["oni_degC"].max()
i = oni["oni_degC"].idxmax()
oni.loc[i]
oni["oni_degC"]
0 -0.25
1 -0.16
2 -0.13
3 -0.15
4 -0.08
...
535 0.11
536 0.46
537 0.95
538 1.39
539 1.80
Name: oni_degC, Length: 540, dtype: float64
El resultado ya no es un DataFrame con varias columnas sino una Series:
type(oni["oni_degC"])
pandas.Series
Los métodos de pandas nos permiten operar directamente sobre la serie:
oni["oni_degC"].mean()
np.float64(0.046185185185185176)
También podemos seleccionar filas. iloc selecciona por posición (la primera fila
tiene posición 0):
oni.iloc[0]
time 1981-09-01 00:00:00 oni_degC -0.25 Name: 0, dtype: object
loc puede seleccionar datos usando etiquetas o una condición booleana. Por ejemplo,
la expresión siguiente produce True donde ONI es mayor que 0.5 y False donde no:
oni["oni_degC"] > 0.5
0 False
1 False
2 False
3 False
4 False
...
535 False
536 False
537 True
538 True
539 True
Name: oni_degC, Length: 540, dtype: bool
type(oni["oni_degC"] > 0.5)
pandas.Series
Podemos usar esa serie de valores True/False como una máscara para quedarnos únicamente
con las filas que cumplen la condición:
oni.loc[oni["oni_degC"] > 0.5]
| time | oni_degC | |
|---|---|---|
| 9 | 1982-06-01 | 0.66 |
| 10 | 1982-07-01 | 0.72 |
| 11 | 1982-08-01 | 0.79 |
| 12 | 1982-09-01 | 1.07 |
| 13 | 1982-10-01 | 1.58 |
| ... | ... | ... |
| 511 | 2024-04-01 | 1.26 |
| 512 | 2024-05-01 | 0.82 |
| 537 | 2026-06-01 | 0.95 |
| 538 | 2026-07-01 | 1.39 |
| 539 | 2026-08-01 | 1.80 |
145 rows × 2 columns
Los operadores de comparación que encontraremos con frecuencia son:
| Operador | Significado |
|---|---|
> |
mayor que |
< |
menor que |
>= |
mayor o igual |
<= |
menor o igual |
== |
igual |
!= |
distinto |
Observa que = asigna un valor, mientras que == compara dos valores.
4. Condicionales: tomar decisiones¶
Pregunta a responder¶
¿Cómo hacemos que un programa realice acciones diferentes dependiendo de los datos?
Los condicionales if, elif y else permiten ejecutar código solamente cuando se
cumple una condición.
Para fines didácticos vamos a usar una clasificación simplificada de cada valor ONI:
ONI >= 0.5:"El Niño"ONI <= -0.5:"La Niña"- valores intermedios:
"Neutral"
Importante: esto clasifica valores individuales de la serie para practicar programación. No sustituye los criterios operacionales usados para declarar un episodio El Niño o La Niña.
x = 1.2
if x >= 0.5:
print("El Niño")
elif x <= -0.5:
print("La Niña")
else:
print("Neutral")
El Niño
Python utiliza la indentación para indicar qué instrucciones pertenecen a cada bloque. El orden también importa: Python prueba las condiciones de arriba hacia abajo y ejecuta el primer bloque que sea verdadero.
Podríamos cambiar x y volver a ejecutar la celda muchas veces, pero copiar y pegar
el mismo código no es una buena estrategia. Para reutilizar código necesitamos una
función.
5. Funciones: empaquetar código para reutilizarlo¶
Preguntas a responder¶
- ¿Cómo definimos una función?
- ¿Cuál es la diferencia entre definirla y llamarla?
- ¿Cómo regresa un resultado?
Objetivos¶
- Definir una función con un parámetro.
- Usar
returnpara devolver un resultado. - Entender por qué una función facilita el mantenimiento y reciclaje del código.
Las funciones nos permiten dar un nombre a una tarea. En lugar de copiar varias veces las mismas líneas, escribimos la lógica una sola vez y la llamamos cada vez que la necesitamos. Esta idea será central cuando transformemos nuestro análisis en un script reproducible.
def enso_phase(oni_value):
"""Clasifica de manera simplificada un valor individual de ONI."""
if oni_value >= 0.5:
return "El Niño"
elif oni_value <= -0.5:
return "La Niña"
else:
return "Neutral"
La definición comienza con def, seguida del nombre de la función y sus
parámetros entre paréntesis.
def nombre(parametro):
# cuerpo de la función
return resultado
En nuestra función:
enso_phasees el nombre;oni_valuees el parámetro que recibe la función;- el texto entre comillas triples es el docstring, una descripción breve de lo que hace;
returndevuelve el resultado.
Definir una función no la ejecuta. Para ejecutarla debemos llamarla y darle un argumento:
enso_phase(1.2)
'El Niño'
enso_phase(-0.8)
'La Niña'
Puedes consultar el docstring con:
?enso_phase
Signature: enso_phase(oni_value) Docstring: Clasifica de manera simplificada un valor individual de ONI. File: /var/folders/r4/hg2wldcj1hjfp_tt15dp3kc80000gn/T/ipykernel_44062/3735144605.py Type: function
Pon en práctica lo que aprendiste¶
¿Qué regresará enso_phase(0.2)? ¿Y enso_phase(0.5)?
Después modifica temporalmente uno de los límites de la función y comprueba cómo cambia el resultado. Devuélvelo a su valor original antes de continuar.
values = [1.2, -0.8, 0.1]
for value in values:
print(value, enso_phase(value))
1.2 El Niño -0.8 La Niña 0.1 Neutral
En cada iteración, value toma el siguiente elemento de la lista values. Igual que
en un if o en una función, el cuerpo del ciclo está indentado.
La idea general es:
for elemento in coleccion:
haz_algo(elemento)
Los ciclos son fundamentales, pero muchas bibliotecas científicas ofrecen operaciones que ya recorren los datos internamente. Cuando existe una operación clara de NumPy, pandas o xarray, suele ser preferible usarla a escribir manualmente un ciclo.
Aplicar nuestra función a una columna completa¶
Series.apply() llama una función para cada valor de una serie. Así podemos reutilizar
enso_phase() sobre toda la columna ONI:
oni["phase"] = oni["oni_degC"].apply(enso_phase)
oni.head()
| time | oni_degC | phase | |
|---|---|---|---|
| 0 | 1981-09-01 | -0.25 | Neutral |
| 1 | 1981-10-01 | -0.16 | Neutral |
| 2 | 1981-11-01 | -0.13 | Neutral |
| 3 | 1981-12-01 | -0.15 | Neutral |
| 4 | 1982-01-01 | -0.08 | Neutral |
oni["phase"].value_counts()
phase Neutral 247 La Niña 147 El Niño 146 Name: count, dtype: int64
La función contiene la lógica; pandas se encarga de repetirla sobre la serie.
7. Segundo CSV: precipitación¶
Ahora leeremos un segundo archivo con precipitación mensual acumulada en una presa de
Ensenada. La estructura es similar, por lo que podemos reutilizar exactamente la misma
función pd.read_csv().
rain = pd.read_csv(
"../data/precipitacion_ensenada_1981_2026.csv",
parse_dates=["time"]
)
rain.head()
| time | precip_mm | |
|---|---|---|
| 0 | 1981-09-01 | 2.0 |
| 1 | 1981-10-01 | 5.0 |
| 2 | 1981-11-01 | 68.5 |
| 3 | 1981-12-01 | 20.0 |
| 4 | 1982-01-01 | 54.8 |
info() resume la estructura del DataFrame, incluyendo cuántos valores no nulos hay
en cada columna:
rain.info()
<class 'pandas.DataFrame'> RangeIndex: 540 entries, 0 to 539 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 time 540 non-null datetime64[us] 1 precip_mm 511 non-null float64 dtypes: datetime64[us](1), float64(1) memory usage: 8.6 KB
En datos observacionales es normal encontrar datos faltantes. Pandas los representa
usualmente como NaN (Not a Number). No debemos confundir un dato faltante con
precipitación igual a cero.
rain.isna().sum()
time 0 precip_mm 29 dtype: int64
Pon en práctica lo que aprendiste¶
Encuentra los meses para los que no tenemos precipitación.
Una posible solución
rain.loc[rain["precip_mm"].isna()]
8. Una primera visualización¶
pandas puede graficar directamente un DataFrame o una Series. Sus métodos de
gráfica utilizan matplotlib internamente.
oni.plot(x="time", y="oni_degC", figsize=(10, 3))
plt.ylabel("ONI (°C)")
plt.xlabel("Tiempo")
plt.title("Oceanic Niño Index")
plt.show()
rain.plot(x="time", y="precip_mm", figsize=(10, 3))
plt.ylabel("Precipitación mensual (mm)")
plt.xlabel("Tiempo")
plt.title("Precipitación en Ensenada")
plt.show()
Mira las dos series antes de hacer cálculos.
- ONI cambia de manera relativamente suave porque incorpora un promedio de tres meses.
- La precipitación mensual es mucho más intermitente.
- La precipitación presenta una estacionalidad marcada.
- Hay meses sin observación.
Pregunta para discutir: ¿tiene sentido comparar directamente estas dos series punto por punto? ¿Qué otras decisiones científicas tendríamos que tomar?
Por ahora no intentaremos contestar completamente esa pregunta. Primero necesitamos incorporar el tercer conjunto de datos del proyecto: la temperatura superficial del mar.
9. En resumen¶
En este notebook:
- importamos bibliotecas con
import; - usamos variables para dar nombres a objetos;
- leímos archivos CSV como
DataFrameconpd.read_csv(); - seleccionamos columnas y filas con
[],ilocyloc; - usamos comparaciones para construir máscaras booleanas;
- usamos
if,elifyelsepara tomar decisiones; - escribimos la función
enso_phase()para reutilizar código; - usamos un ciclo
forpara repetir una operación; - aplicamos una función a una
Series; - detectamos valores faltantes con
isna(); - hicimos gráficas básicas con pandas/matplotlib.
Para seguir¶
En el siguiente notebook cambiaremos de tipo de datos. En lugar de una tabla con una serie de tiempo tendremos un campo de SST con dimensiones:
tiempo × latitud × longitud
Para trabajar con estos datos multidimensionales usaremos xarray.