El material de este "notebook" está basado en la lección Programming with Python de los Software Carpentry Workshops, publicada bajo la licencia Creative Commons - Attribution License. Traducción y modificaciones por Karina Ramos Musalem.
Al terminar este notebook (intro02_python):
for
(for loop).for
para repetir cálculos simples.for
.En la clase pasada escribimos código de Python que grafica valores de interés de nuestro archivo de datos (precip_mensual_estacion01.txt
). Tenemos otros 6 archivos de datos de otras variables meteorológicas y podríamos tener muchos más. Queremos crear gráficas para todos nuestros archivos y para eso tenemos que enseñarle a la computadora a repetir acciones.
Por ejemplo, podemos repetir la impresión de cada caracter en un palabra:
palabra = 'plomo'
En Python, un string o cadena es básicamente una colección ordenada de caracteres y cada caracter tiene un número asociado - su índice (similar al índice que estudiamos en el notebook 1 para arreglos). Así que podemos acceder a los caracteres de una cadena usando sus índices. Por ejemplo, podemos obtener el primer caracter de la palabra 'plomo'
usando palabra[0]
. Una forma de imprimir cada caracter es usando 5 comandos print
:
print(palabra[0])
print(palabra[1])
print(palabra[2])
print(palabra[3])
print(palabra[4])
¡Guácala! Esta forma de hacerlo no es buena por tres razones:
Por ejemplo:
palabra = 'uno'
print(palabra[0])
print(palabra[1])
print(palabra[2])
print(palabra[3])
print(palabra[4])
Pero hay una mejor solución:
palabra = 'plomo'
for char in palabra:
print(char)
Esto es más corto - al menos más corto que 100 print
s - y es más robusto (podemos usar el mismo código con cualquier palabra):
palabra = 'oxygen'
for char in palabra:
print(char)
Esta versión usa un loop o ciclo for para repetir una operación - en este caso imprimir un caracter - una vez por cada 'elemento' en un secuencia. La forma general de un ciclo for es:
for variable in coleccion:
# haz algo usando variable, por ejemplo imprimir
Usando el ejemplo anterior (solo cambia word por palabra):
En donde cada caracter (char
) en la variable word
o en nuestro caso palabra
es pasada por el ciclo e impresa, un caracter tras otro. Los números en el diagrama denotan en qué ciclo se imprimió el caracter (1 es el primer ciclo y 6 es es último).
Podemos llamar a la variable del ciclo como queramos, pero debe haber un símbolo :
al final de la línea inicial de ciclo y siempre debemos respetar la alineación: Lo que queremos correr dentro del loop debe estar "indentado" hacia la derecha. En Python no hay un símbolo para indicar el final del ciclo; en cambio, lo que está indentado despues del for
será parte del ciclo.
Aquí hay otro ciclo que actualiza el valor de una variable cada vuelta:
largo = 0
for vocal in 'aeiou':
largo = largo + 1
print('Hay', largo, 'vocales')
Vale la pena desmenuzar la ejecución de este programa paso por paso. Como hay cinco caracteres en 'aeiou'
, el código en la tercera línea se ejecutará cinco veces. La primera vez, largo
es cero (el valor que le asinamos en la primera línea del código) y vocal
es 'a'
. La línea 3 agrega 1 al valor de largo
, produciendo 1, y actualiza largo
para referir al nuevo valor. En la segunda vuelta, vocal
es 'e'
y largo
es 1, así que largo
se actualiza al valor 2. Después de las tres vueltas siguentes, largo
es 5; como no hay nungún otro elemento en 'aeiou'
para que Python procese, el ciclo se termina y el comando print
de la cuarta línea se ejecuta y nos dice la respuesta final.
Nota que la variable del ciclo se está usando para llevar un registro del progreso del ciclo. Sigue existiendo cuando termina el ciclo y también podemos reutilizar varables que se usaron como variables de ciclo anterirormente:
letra = 'z'
for letra in 'abc':
print(letra)
print('Después del ciclo, letra es', letra)
Nota también que encontrar la longitud de un cadena de caracteres es una operación tan común que Python tiene una función integrada llamada len
para hacerlo:
print(len('aeiou'))
len
es mucho más rápida que cualquier función que podríamos escribir y mucho más fácil de leer que el ciclo que escribimos; nos puede dar la longitud de muchas otras cosas además de cadenas, así que la seguiremos usando cuando podamos.
Python tiene una función llamada range
que genera una secuencia de números. range
acepta 1, 2, o 3 parámetros.
range
genera una secuencia de esa longitud que comienza en 0 e incrementa en 1. Por ejemplo range(3)
produce números 0,1,2
.range
comienza en el primero y va hasta el segundo en incrementos de 1. Por ejemplo range(2,5)
produce 2, 3, 4
.range(3, 10, 2)
produce 3, 5, 7, 9
.Usando range
, escribe un ciclo que imprima los primeros 10 números naturles (1,2,3,...,10)
# Respuesta
for ii in range(1,11):
print(ii)
**
. Por ejemplo $2^3$ es 2**3
. Escribe un ciclo que calcule el mismo resultado que 2 ** 3
utilizando únicamente la multiplicación *
(sin usar exponenciación).# Respuesta
a = 1
for ii in range(3):
a = a*2
print(a)
#Comprobación
print(2**3)
for variable in secuencia:
para procesar uno por uno los elementos de una secuencia.for
debe estar indentado.len(cosa)
para determinar la longitud de alguna cosa que contiene valores.De manera similar a un cadena que contiene muchos caracteres, una lista en un contenedor que puede guardar muchos valores. A diferencia de los arreglos de NumPy, las listas son intrínsecas de Python, por lo cual no necesitamos importar ninguna bibliotca para usarlas. Podemos crear listas poniendo valores entre []
y separandolos con ,
:
impares = [1, 3, 5, 7]
print('los impares son:', impares)
Podemos acceder a los elementos de un alista usando índices:
print('El primer elemento es ', impares[0])
print('El último elemento es ', impares[3])
print('El elemento -1 es ', impares[-1])
Sí, podemos usar índices negativos en Pyhton. El elemento -1
nos da el último de la lista, el -2
el penúltimo, etc.
Si hacemos un ciclo sobre un a lista, la variable del ciclo se asigna los elementos de la lista uno por uno:
for numero in impares:
print(numero)
Hay una diferencia importante entre las cadenas de caracteres y las listas: podemos cambiar los valores de una lista, pero no podemos cambiar caracteres individuales en una cadena. Por ejemplo:
nombres = ['Curie', 'Franklyn', 'Turing'] # Franklin mal escrito
print('nombres era originalmente:', nombres)
nombres[1] = 'Franklin' # Nombre correcto
print('nombres es ahora:', nombres)
pero
nombre = 'Franklin'
nombre[0] = 'f'
no funciona.
Los números y las cadenas son inmutables, es decir, no pueden modificarse. Las listas y los arreglos son mutables y pueden modificarse después de ser creadas.
Las listas en Python pueden contener distintos tipos de elementos. Por ejemplo:
edades = [10, 20.4, 'desconocido'] # enteros, decimales y cadenas
Hay otras maneras de cambiar el contenido de una lista además de reasignando valores. Podemos agregar (append):
impares.append(11)
print('impares después de agregar un valor:', impares)
elemento_eliminado = impares.pop(0)
print('impares después de quitar el primer elemento:', impares)
print('elemento eliminado:', elemento_eliminado)
impares.reverse()
print('impares al revés:', impares)
Hay que tener cuidado cuando modificamos una lista. Si queremos hacer una copia de la lista y modificar la copia, es posible que queramos hacer lo siguiente:
impares = [1, 3, 5, 7]
primos = impares # Este es el paso mortal
primos.append(2)
print('lista primos', primos)
print('lista impares', impares)
¿Qué paso aquí? Solo queríamos modificar la lista primos
pero también cambió la lista impares
. Esto es porque Python guarda una lista en memoria y luego puede usar distintos nombres para referirse a la misma lista. Si solo queremos copiar una lista simple, podemos usar la función list
de manera que no modifiquemos una lista que no queríamos modificar:
impares = [1, 3, 5, 7]
primos = list(impares) # Este es el paso mortal
primos.append(2)
print('lista primos', primos)
print('lista impares', impares)
Se puede acceder a rebanadas o subconjuntos de listas y cadenas si especificamos los intervalos de valores entre []
como hicimos con los arreglos:
binomial_nombre = 'Drosophila melanogaster'
grupo = binomial_nombre[0:10]
print('grupo:', grupo)
especie = binomial_nombre[11:23]
print('especie:', especie)
chromosomas = ['X', 'Y', '2', '3', '4']
autosomas = chromosomas[2:5]
print('autosoma:', autosomas)
ultimo = chromosomas[-1]
print('último:', ultimo)
cadena_para_rebanar = 'Observaciones del 27-abr-2013'
lista_para_rebanar = ['fluor', 'cloro', 'bromo',
'neodimio', 'disprosio', 'einstenio']
¿Tu solución funciona sin importar si conoces de antemano o no la longitud de la cadena y la lista? Si la respuesta es no, intenta cambiar tu estrategia para que sea más robusta.
HINT: Recuerda que los índices pueden ser negativos también.
# Respuesta
print(cadena_para_rebanar[-4:])
print(lista_para_rebanar[-4:])
[valor1, valor2, valor3, ...]
crea una lista.[]
como los arreglos y las cadenas.for
para procesar múltiples archivos.Ya tenemos casi todo para procesar todos los archivos de datos que tenemos. Solo falta importar una biblioteca especial:
import glob
La biblioteca glob
contiene una función, también llamada glob
, que encuentra archivos y directorios (carpetas) cuyos nombres concuerdan con un patrón dado. Le proporcionamos esos partrones como cadenas: el caracter *
representa cero o más caracteres, mientras que ?
representa solo un caracter. Podemos usarlos para obtener los nombres de todos los archivos CSV en la carpeta, o directorio, en la que se encuentra este notebook:
print(glob.glob('*_mensual_estacion01.csv'))
Como muestran estos ejemplos, el resultado de glob.glob
es una lista de archivos y directorios en orden arbitrario. Así que podemos hacer un ciclo sobre esta lista para hacer algo con cada nombre de archivo. En este caso, ese algo es generar gráficas para todos los datos. Si queremos empezar por analizar los primeros cinco archivos por orden alfabético, podemos usar la función sorted
(ordenado) para generar una lista ordenada alfabéticamente del output de glob.glob
:
import glob
import numpy
import matplotlib.pyplot
filenames = sorted(glob.glob('*_mensual_estacion01.csv'))
filenames = filenames[0:5]
for filename in filenames:
print(filename)
data = numpy.loadtxt(fname=filename, delimiter=',')
fig = matplotlib.pyplot.figure(figsize=(10.0, 3.0))
axes1 = fig.add_subplot(1, 3, 1)
axes2 = fig.add_subplot(1, 3, 2)
axes3 = fig.add_subplot(1, 3, 3)
axes1.set_ylabel('promedio')
axes1.plot(numpy.mean(data, axis=0))
axes2.set_ylabel('max')
axes2.plot(numpy.max(data, axis=0))
axes3.set_ylabel('min')
axes3.plot(numpy.min(data, axis=0))
fig.tight_layout()
matplotlib.pyplot.show()
Si nos fjamos en los mínimos, vemos que hay muchos ceros en todas las variables. Es un poco sospechoso que la radiación solar mínima en el promeido mensual sea cero, o peor aún, ¡que la humedad mínima sea cero! Hay algo raro con estos datos que debemos averiguar...
glob.glob(patrón)
para crear una lista de nombres de archivos cuyos nombres concuerdan con el patrón.*
en un patrón como comodín para representar cero o más caracteres, y ?
como comodín para representar un solo caracter.if
, elif
y else
.and
y or
.Anteriormente descubrimos que había algo raro con los datos. ¿Cómo podemos usar Python para descubrir automáticamente los datos sospechosos y operar de distinta manera con ellos? En esta parte aprenderemos cómo escribir código que corra sólo cuando ciertas condiciones son ciertas.
Podemos pedirle a Python que ejecute ciertas acciones, dependiendo de alguna condición, utilizando el comando if
(si). Si (if) se cumple la condición, entonces haz ... :
num = 37
if num > 100:
print('mayor que')
else:
print('menor que')
print('fin')
La segunda línea usa la palabra if
para decirle a Python que vamos a hacer una elección. Si la condición que sigue despuésd de if
es verdadera, los comandos después del if
(indentados dentro del if
) se ejecutan y se imprime "mayor que". Si la prueba es falsa, se ejeculan los comandos después de else
y entonces se imprime "menor que". Sólo se ejecuta una u otra opción antes de continuar con el programa y que se imprima "fin":
No es absolutamente necesario tener un else
. Si no hay else
, Python simplemente no hace nada si la prueba es falsa:
num = 53
print('Antes del condicional...')
if num > 100:
print(num, ' es más grande que 100')
print('... después del condicional')
También podemos encadenar varias pruebas usando elif
, que es una abreviación de else if (si no). El siguiente programa usa elif
para imprimir el signo de un número:
num = -3
if num >0:
print(num, 'es positivo')
elif num == 0:
print(num, 'es cero')
else:
print(num, 'es negativo')
Nota que para probar igualdad usamos ==
porque =
es solamente para asignar valores.
También podemos combinar pruebas usando and
(y) y or
(o). La prueba and
es verdadera solo si ambas partes son verdaderas:
if (1 > 0) and (-1 > 0):
print('ambas partes son verdaderas')
else:
print('al menos una parte es falsa')
Mientras que or
es verdadero si al menos una parte es verdadera:
if (1 < 0) or (-1 < 0):
print('al menos una parte es verdadera')
Las palabras True
(verdadero) y False
(falso) son especiales en Python. Se llaman booleanos
y representan la "verdad" en los valores. Una expresión como 1<0
regresa el valor False
, mientras que -1<0
regresa el valor True
.
Anteriormente vimos que el mínimo mensual de los datos para todos los años era cero. Esto es sospechoso porque ninguna variable de las que estamos analizando debería ser cero, salvo la precipitación (temperatura, presión, radiación, índice UV y humedad). ¿Puedes pensar por qué no son cero? ¿Qué crees que esté pasando aquí?
Probablemente hay meses en donde no hubo mediciones y se reportaron como ceros. Cuando no hay mediciones o no son confiables, regularmente se elige una bandera o etiqueta especial para indicar estos valores faltantes o missing values. Se pueden poner valores fuera del rango esperado de mediciones como 999999, o NaN (Not a Number). NaN es una manera de decirle a la computadora que no hay número asignado.
Revisemos en qué años hay valores cero en la radiación:
# Abrimos el archivo de mediciones de radiación solar
radiacion = numpy.loadtxt(fname='radiacion_mensual_estacion01.csv', delimiter=',')
# Ciclo sobre los 8 años de datos (filas)
for fila in range(8):
# Prueba si el valor mínimo en la fila #fila es cero
if numpy.min(radiacion[fila,:]) == 0:
# Si la pueba es verdadera imprime esto:
print('Hay ceros en el año ', fila)
else:
# Si la prueba es falsa imprime esto:
print('No hay ceros en el año ', fila)
Por lo tanto, podemos descartar los datos para los años 0 (2011), 2 (2013), 4 (2015).
cadena = "Ciencias De La Tierra"
vocales = ["a", "e", "i", "o", "u", "á", "é", "í", "ó", "ú"]
contador = 0
for char in cadena:
if char in vocales:
contador = contador + 1
print(contador)
if condición:
para empezar un condicional, elif condición:
para agregar pruebas adicionales y else:
para agregar una acción default.==
para probar que dos cosas son iguales.X and Y
es verdadero solo si X
y Y
son ambos verdaderos.X or Y
es verdadero si alguno o ambos X
o Y
son verdaderos.True
y False
representan valores de verdad verdadero y falso.Hasta ahora, hemos escrito código para graficar algunas estadísticas de nuestros datos, hemos aprendido a abrir y analizar todos nuenstros archivos en un solo ciclo, a hacer que Python haga cosas con los datos dependiendo de alguna condición, pero el código se está volviendo largo y complicado. Imagina que tuviéramos cientos de archivos y que no quisieramos generar una gráfica de cada uno. Comentar las líneas de código que grafican los datos es tedioso y poco robusto. Cortar y pegar tampoco es una buena solución... Lo que necesitamos es una forma de empaquetar nuestro código para que sea fácil de reutilizar y esa solución es hacer "funciones". Esta es una manera rápida de ejecutar una y otra vez pedazos más largos de código.
Empecemos definiendo una función fahr_a_celsius
que convierte temperaturas en escala Fahrenheit a Celsius:
def fahr_a_celsius(temp):
return((temp - 32) * (5/9))
La definición comienza con def
seguido del nombre que queremos darle a la función (fahr_a_celsius
) y entre paréntesis una lista de nombres de parámetros (temp
). El cuerpo de la función - los comandos que se ejecutan cuando corre la función - deben estar indentados. El cuerpo concluye con return
seguido del valor que regresará la función.
Cuando llamamos a la función, los valores que le pasamos de le asignan a las variables que se usarán dentro de la función. Dentro de la función, usamos la expresión return
regresar el resultado a quien lo pidió.
Corramos nuestra función:
fahr_a_celsius(32)
Este comando llama a la función, usando "32" como input y regresa el valor de la función.
Llamar a nuestra función es lo mismo que llamar a cualquier función que hemos usado antes:
print('El papel se quema a:', fahr_a_celsius(451), 'C')
print('El punto de ebullición del agua es:', fahr_a_celsius(212), 'C')
Ahora escibamos una función que transforme de Celsius a Kelvin:
def celsius_a_kelvin(temp_c):
return temp_c + 273.15
print('El punto de fusión del agua en Kelvin:', celsius_a_kelvin(0.))
¿Qué hay de convertir de Fahrenheit a Kelvin? Podríamos escribir otra función pero no es necesario. Podemos componer dos funciones (como en su clase de matemáticas) que ya creamos:
def fahr_a_kelvin(temp_f):
temp_c = fahr_a_celsius(temp_f)
temp_k = celsius_a_kelvin(temp_c)
return temp_k
print('El punto de ebullición del agua en Kelvin:', fahr_a_kelvin(212.0))
Esta es una probadita de cómo se contruyen programas más grandes: definimos funciones básicas y las combinamos en pedazos más grandes. En la vida real, las funciones son un poco mas largas que las que definimos aquí - unas pocas decenas de líneas - pero no deben ser mucho más largas que eso.
Ahora que sabemos como empacar pedazos de código, podemos hacer que nuestro análisis de los datos PEMBU sea más fácil de leer y de reusar. Hagámos una función visualiza
que genere nuestras gráficas:
def visualiza(filename):
data = numpy.loadtxt(fname=filename, delimiter=',')
fig = matplotlib.pyplot.figure(figsize=(10.0, 3.0))
axes1 = fig.add_subplot(1, 3, 1)
axes2 = fig.add_subplot(1, 3, 2)
axes3 = fig.add_subplot(1, 3, 3)
axes1.set_ylabel('average')
axes1.plot(numpy.mean(data, axis=0))
axes2.set_ylabel('max')
axes2.plot(numpy.max(data, axis=0))
axes3.set_ylabel('min')
axes3.plot(numpy.min(data, axis=0))
fig.tight_layout()
matplotlib.pyplot.show()
y otra función detecta_problemas
que revisa si tenemos esos ceros extraños cuando faltan mediciones en los archivos:
def detecta_problemas(filename):
data = numpy.loadtxt(fname=filename, delimiter=',')
dimensiones = data.shape
for fila in range(dimensiones[0]):
if numpy.min(data[fila,:]) == 0:
print('Hay ceros en el año ', fila, 'archivo', filename)
Pero, ¿no olvidamos el return
? Ah, en Python no es neceario incluir un return
. La funciones pueden usarse con el simple propósito de agrupar código con una función específica.
Ahora, en vez de juntar el código en un ciclo gigante, podemos leer y reusar ambas ideas separadas. Podemos reproducir lo que hemos hecho en este notebook en un código mucho más simple:
archivos = sorted(glob.glob('*_mensual_estacion01.csv'))
for archivo in archivos[:3]:
print(archivo)
visualiza(archivo)
detecta_problemas(archivo)
Hasta ahora hemos pasado parámetros de dos formas: directamente como en type(data)
, y por nombre, como en numpy.loadtxt(fname=algo.csv', delimiter=',')
. De hecho, podemos pasar el nombre del archivo a loadtxt
sin usar fname=
:
numpy.loadtxt('precip_mensual_estacion01.csv', delimiter=',')
pero debemos decir delimiter=
:
numpy.loadtxt('precip_mensual_estacion01.csv', ',')
Para enteder qué está pasando veamos la ayuda de la función loadtxt
:
help(numpy.loadtxt)
Hay mucha información pero en las dos primeras lineas vemos:
loadtxt(fname, dtype=<class 'float'>, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0, encoding='bytes', max_rows=None)
Esto nos dice que loadtxt
tiene un parámetro llamado fname
que no tiene un valor default y ocho parámetros que sí tienen valor default. Esto lo sabemos porque el valor default está después del =
. fname
no tiene esto por lo tanto la función siempre espera que la llames pasándole este parámetro. Si no especificas los otros ocho, entonces éstos tomarán el valor default.
DOCUMENTACIÓN: Podemos agregar información de ayuda a la función de la siguente forma:
def fahr_a_kelvin(temp_f):
'''Esta función convierte el valor temp_f de Fahrenheit a Kelvin'''
temp_c = fahr_a_celsius(temp_f)
temp_k = celsius_a_kelvin(temp_c)
return temp_k
print('El punto de ebullición del agua en Kelvin:', fahr_a_kelvin(212.0))
help(fahr_a_kelvin)
def func(a, b=3, c=6):
print('a: ', a, 'b: ', b, 'c:', c)
¿Cuál será el resultado de correr?
func(-1, 2)
s
es una cadena, entonces s[0]
es el primer caracter de la cadena y s[-1]
es el último. Escribe una función que se llame extremos
que regrese una cadena constituida por el primer y úlimo elementos del input de la función. La llamada a tu función deberá verse como:print(extremos('helio'))
Output:
hm
def nombre_de_función(parámetros)
nombre_de_función(valor)
help(algo)
para ver la ayuda de algo.''' texto '''
.parámetro=valor default
en la lista de parámetros dentro de la definición de la función.Finalmente aprenderemos qué son los archivos en formato netCDF y aprovecharemos para explorar más funciones de visualización de la biblioteca matplotlib. Esta breve introducción a netCDF está basada en los tutoriales READING NETCDF4 DATA IN PYTHON y WRITING NETCDF DATA IN PYTHON de Uptal Kumar en el portal Earth Inversion y Python - NetCDF reading and writing example with plotting de Chris Slocum. Les recomiendo que los revisen completos porque tienen temas adicionales como el manejo de fechas, datos nulos y cómo usar la biblioteca xarray, que es súper útil y rápida.
En Ciencias de la Tierra es muy común lidiar con estructuras de datos de muchas dimensiones (generalmente 3 espaciales +1 temporal) como el estado del océano, la atmósfera, el interior de un planeta, etc. Es impráctico guardar estos datos en archivos de texto (como los .csv que hemos estado usando) porque necesitarimos mucha capacidad de memoria para guardarlos, leerlos y procesarlos. Una de las mejores herramientas para manipular datos multidimensionales son los netCDF. Estos archivos almacenan datos en formato HDF (Hierarchical Data Format).
En esta sección aprenderemos a leer y escribir datos netCDF pero antes de eso necesitamos instalar el paquete netcdf4
de python.
Ve a la terminal y escribe el siguente comando:
conda install -c conda-forge netcdf4
Cuando haya terminado la instalación continúa con la sección.
# Importa la biblioteca netCDF4
import netCDF4
Ahora leamos un archivo netCDF con datos de temperatura del aire cerca de la superficie en todo el planeta a lo largo de 1 año que provienen de la salida de un modelo numérico de NOAA. Los archivos NetCDF tienen extensión .nc
.
f = netCDF4.Dataset('air.sig995.2012.nc')
print(f)
En este caso leímos el archivo “air.sig995.2012.nc”. Cuando imprimimos el objeto f
podemos ver que el archivo es la clase NETCDF3. Hay más información como título, intitución, etc. Esto se conoce como metadatos.
Al final vemos las dimensiones y variables del conjunto de datos. Este conjunto de datos tiene 3 dimensiones: lat (size:73), lon(size:144), time(size:366). Luego tenemos las variables, que están definidas con base en las dimensiones anteriores. En este caso tenemos lon
, lat
, time
y aitr
basadas en las dimensiones lat
, lon
y time
. Podemos tener variables basadas en solo una dimensión, como time
o en tres como air
.
Podemos acceder a la información de este objeto como leeríamos un diccionario en Python:
print(f.variables.keys()) # Imprime los nombres de todas las variables en f
Esto imprime los nombres de todas las variables que se leyeron del archivo netCDF referidas por el objeto "f".
Podemos acceder a cada variable individualemente:
dep = f.variables['air']
print(dep)
La variable "air" tiene 3 dimensiones - lon,lat, time.
También podemos obtener más información (metadatos) como las coordenadas, el tiempo o las unidades de la variable. Las coordenadas son las variables 1D que tienen el mismo nombre que las dimensiones. La unidad de la variable air
es Kelvin (degK). La forma o dimensiones nos dan imformación acerca del tamaño de la variable. Aqui la forma (shape) es (366, 73, 144).
También podemos revisar el tamaño de las dimensiones individualmente:
for d in f.dimensions.items():
print(d)
Cuando tenemos "tiempo" como dimensión es común que ésta sea tipo unlimited (ilimitadas). Esto significa que el tamaño de esa dimensión puede incrementar indefinidamente. En este caso todas las dimsnsiones están fijas.
Si solo queremos encontrar las dimensiones de una variable particular podemos hacer:
print(dep.dimensions)
print(dep.shape)
Podemos inspeccionar las variables asociadas a las dimensiones de la siguiente forma:
time = f.variables['time']
air = f.variables['air']
lon,lat = f.variables['lon'], f.variables['lat']
print(time)
print(lon)
print(lat)
Ahora,¿cómo podemos acceder a los datos guaradados en cada variable? Las variables NetCDF se comportan de forma similar a arreglos de Numpy: podemos rebanarlas, enmascararlas y hacer cálculos con ellas.
Por ejemplo, leámos los datos de la variable lon
:
lon_data = lon[:]
print(lon_data)
lat_data = lat[:]
print(lat_data)
Ahora, grafiquemos los datos de temperatura del aire guardados en la variable air
. Esta variable tiene 3 dimensiones (time, lat, lon), por lo que podemos hacer representaciones de los datos en 2D rebanando el arreglo. Hay varias formas de representar estas rebanadas gráficamente. Probemos las funciones contourf y pcolormesh de matplotlib. Para ello rebanemos los datos de air sleccionando todas las latitudes y longitudes para un solo día, es decir, pasaremos de un arreglo tamaño (366, 73, 144) a uno tamaño (73, 144), que sí podemos visualizar:
time_index = 100 # elegimos un índice temporal al azar para visualizar
lat_data = lat[:]
air_data = air[time_index,:,:] # rebanamos los datos de air, eligiendo solo un día
# y todas las lats y lons
# contourf y pcolormesh
fig, (ax1,ax2) = matplotlib.pyplot.subplots(1,2, figsize=(13,4))
ax1.pcolormesh(lon_data, lat_data, air_data)
ax1.set_xlabel('lon')
ax1.set_ylabel('lat')
ax1.set_title('Pcolormesh')
ax2.contourf(lon_data, lat_data, air_data, 10) # dibuja 10 contornos
ax2.set_xlabel('lon')
ax2.set_ylabel('lat')
ax2.set_title('Contourf')
Ambos graficos tienen similitudes y diferencias. Pcolormesh genera una función que asigna un color, dentro de un mapa de colores, al rango de datos dentro de los cuales se encuentran los valores del arreglo. Dependiendo del valor del elemento se le asigna un color. En cambio, contourf genera contornos, como curvas de nivel, para los valores del arreglo. Tú puedes elegir el número de contornos a graficar y, para ambas funcones, el mapa de color que quieras utilizar.
Ahora, necesitamos una barra de color para poder traducir de colores a temperaturas. Por ejemplo ¿qué significa que una región sea amarilla o azul?
# contourf y pcolormesh
fig, (ax1,ax2) = matplotlib.pyplot.subplots(1,2, figsize=(13,4))
pc = ax1.pcolormesh(lon_data, lat_data, air_data)
matplotlib.pyplot.colorbar(pc, ax=ax1, label='Temperatura del aire (K)')
ax1.set_xlabel('lon')
ax1.set_ylabel('lat')
ax1.set_title('Pcolormesh')
pc2 = ax2.contourf(lon_data, lat_data, air_data)
matplotlib.pyplot.colorbar(pc2, ax=ax2, label='Temperatura del aire (K)')
ax2.set_xlabel('lon')
ax2.set_ylabel('lat')
ax2.set_title('Contourf')
Nota como las barras de color son distintas. La barra de pcolormesh es continua mientras que la de contourf es discreta, mostrando todos los intervalos representados en el gráfico.
cmap=
así como los límites del mapa de color usando los argumentos vmax=
y vmin=
. Recuerda que puedes consultar la documentación para más información.# contourf y pcolormesh
fig, (ax1,ax2) = matplotlib.pyplot.subplots(1,2, figsize=(13,4))
pc = ax1.pcolormesh(lon_data, lat_data, air_data,cmap='gist_heat', vmin=200, vmax=315,)
matplotlib.pyplot.colorbar(pc, ax=ax1, label='Temperatura del aire (K)')
ax1.set_xlabel('lon')
ax1.set_ylabel('lat')
ax1.set_title('Pcolormesh')
pc2 = ax2.contourf(lon_data, lat_data, air_data, cmap='gist_heat', vmin=200, vmax=315,)
matplotlib.pyplot.colorbar(pc2, ax=ax2, label='Temperatura del aire (K)')
ax2.set_xlabel('lon')
ax2.set_ylabel('lat')
ax2.set_title('Contourf')
Siguiendo el tutorial WRITING NETCDF DATA IN PYTHON crea tu propio archivo netcdf. Puede tener los datos aleatorios que generan en el tutorial o tu propio set de datos (puedes usar los del ejemplo anterior).