Python para Ciencias de la Tierra

Capítulo 2: Conceptos Básicos de Programación en Python (Pt. 4)

Damian
16 min readMar 15, 2022

--

Índice del curso
<<
Capítulo 2: Conceptos Básicos de Programación (Pt. 3)

2.3 Estructuras de Control

Las estructuras de control son aquéllas dentro del código que nos permiten hacer básicamente dos cosas: ejecutar varias veces una misma tarea; y controlar que el código tome decisiones de acuerdo a los datos que reciba el programa.

Sintácticamente las estructuras pueden identificarse dentro del código como bloques, los cuales tienen un principio y un final. Todos los lenguajes de programación tienen la forma de distinguir estos bloques. En python se distinguen por la comúnmente llamada indentación, es decir, esos renglones poseen una sangría. Todos los renglones del bloque deben tener el mismo sangrado, es decir, el mismo número de espacios. Al término del bloque los renglones siguientes están en su posición original.

Los bloques en general serán definidos por los ciclos for, las sentencias if, y los ciclos while, además de funciones, las cuales define el programador.

El siguiente cuadro muestra cómo se ve la sangría para distinguir un bloque.

#Línea que tiene la sentencia de control:
#Primera línea sangrada indica el inicio del bloque.
#Línea dos del bloque.
#Línea tres del bloque.
#Todas las líneas deben tener el mismo número de espacios.
#Esta línea ya no pertenece al bloque, por no tener sangría.

Al final de la línea de la sentencia, observa que tiene dos puntos al final (:), los cuales siempre se deben escribir. Al dar enter luego de los dos puntos, en jupyter automáticamente se agrega la sangría. Esto evita que cuentes los espacios en blanco, y te permite sólo fijarte en dónde empieza y dónde termina el bloque.

Puedes abrir un nuevo cuaderno en jupyter.

Ciclo for

En la mayoría de los programas que hagas, si no es que en todos, será necesario hacer una misma tarea varias veces; y en lugar de escribir un mismo código ese número de veces, se utilizan los ciclos. En python hay dos tipos de ciclos: definido y condicional.

El ciclo definido es el ciclo for, porque desde su declaración básicamente se define el número de iteraciones que hará. Al ser una estructura que controla el flujo del programa, debe estar definida en un bloque sangrado:

for i in range(5):       #i es la variable iterante
n = i + 1 #n es el número de ciclo
print('Éste es el ciclo:', n)

print('Ciclo for ejecutado con éxito')

>> Éste es el ciclo: 1
>> Éste es el ciclo: 2
>> Éste es el ciclo: 3
>> Éste es el ciclo: 4
>> Éste es el ciclo: 5
>> Ciclo for ejecutado con éxito

Observa los dos puntos al final de la línea que declara al for. El bloque tiene dos líneas, las cuales tienen sangría. La primera línea suma una unidad a la variable iterante para obtener el número de ciclo en el que va, el cual se imprime en la segunda línea. En tu cuaderno notarás además que las palabras for e in se tornan en negrita. Son dos palabras reservadas.

La función range() es una función diseñada para optimizar procesos, pues a partir de un rango de números enteros dado sólo genera los que se utilizan. Tiene la sintaxis range(inicio, final - 1, salto). A no ser que se indique lo contrario, siempre inicia desde 0, por eso se sumó un 1 en cada iteración en el código superior.

Las listas, las tuplas, los conjuntos y las cadenas son objetos iterables.
Las estructuras de datos vistas en las sesiones anteriores se denominan iterables, porque con una sentencia como el for se puede recorrer cada uno de sus elementos. La variable iterante será la que tome cada uno de esos valores y cambie en cada ciclo.

Así, se puede recorrer en pocas líneas todos los elementos de una estructura, sin importar cuántos elementos contenga.

rocas = ['Plutónica', 'Volcánica', 'Sedimentaria', 'Metamórfica']

for roca in rocas:
print(roca)

>> Plutónica
>> Volcánica
>> Sedimentaria
>> Metamórfica

Los nombres de las variables deben ser lo más intuitivas posibles. Por ejemplo en este código se define roca como la variable que tomará cada uno de los elementos que hay en la lista de rocas.

Si has tenido experiencia en otros lenguajes de programación, ya habrás notado que las listas, por ejemplo, son básicamente arreglos de una dimensión; y que no siempre es necesario declarar una variable contadora para acceder a sus elementos. Python es mucho más sencillo.

En cuanto a los diccionarios, en ellos sólo se puede iterar con ayuda de los métodos keys(), values() e items() para recorrer las claves, los valores o las parejas clave-valor en la misma sentencia for:

rocas = {'Granito':'Intrusiva', 'Arenisca':'Sedimentaria', 
'Mármol': 'Metamórfica'}

for roca, tipo_de in rocas.items():
print(roca, 'es una roca', tipo_de)

>> Granito es una roca Intrusiva
>> Arenisca es una roca Sedimentaria
>> Mármol es una roca Metamórfica

Observa que si se desea recorrer las parejas de clave-valor se deben usar dos variables acomodadas en ese orden: primero la clave, luego el valor. Si usas keys() o values() sólo necesitas una variable, como si se tratara de una lista.

En muchas ocasiones, un proceso requiere que una variable tenga que cambiar con base en su anterior valor.

a = 0
a = a + 1
a = a + 1
print(a)

>> 2

Para estos casos, los ciclos son de mucha utilidad. Revisa el siguiente ejemplo.

Ejemplo 2.2 Suma de los números contenidos en una tupla.

#Definición de la lista contenedora de sólo números.
numeros = (3, 5, 7, 9, 11, 13, 15, 17, 19, 21)

suma = 0 #Se define una variable a incrementar con valor inicial.
i = 0 #Se define una variable contadora

for n in numeros:
i = i + 1
suma = suma + n #Al anterior valor de suma, se agrega el nuevo n.

print('La suma de los', i,'números es:', suma)

>> La suma de los 10 números es: 120

Las variables i y suma cambian en cada ciclo, y pueden llamarse contadora y acumuladora respectivamente. Este tipo de variables son muy útiles y comunes en métodos numéricos, donde las variables discretas tienen un índice i, j o k.

Fin del ejemplo 2.2

También los ciclos nos permiten crear listas de forma más rápida. Por ejemplo, la primera lista que viste en la sesión 3 consistía en números múltiplos de 5. En lugar de anotar manualmente esa serie de números, puedes optar por un ciclo donde sólo tengas que indicar cuántos elementos de dicha serie quieres en la lista. Si se desea empezar la lista con el 10, y que contenga 10 elementos:

multiplos_5 = list()

for i in range(2, 12):
mult = i*5
multiplos_5.append(mult)

print(multiplos_5)

>> [10, 15, 20, 25, 30, 35, 40, 45, 50, 55]

Otra forma de generar esta lista es utilizando las capacidades óptimas de la función range():

multiplos_5 = list()

for i in range(10, 60, 5):
multiplos_5.append(i)

print(multiplos_5)

>> [10, 15, 20, 25, 30, 35, 40, 45, 50, 55]

La intención de mostrar estas formas de crear listas es además el que puedas visualizar que a partir de diversas fuentes de datos puedas crear estructuras de datos. Caso contrario, también puedes pensar que un ciclo for permite limpiar una estructura de datos.

Ya sabes cómo trabajan los ciclos for, los cuales a partir de ahora sólo le llamaremos for. Pero debes saber que siempre se ejecutarán todos los ciclos a no ser que se encuentren con una condición. Por ello ahora veremos la condición if.

Sentencia lógica if

Una de las características más importante que puede tener cualquier programa es la capacidad de elegir o cambiar de tarea de acuerdo con el dato que ingrese a él. Es importante que para esta sección tengas presente las variables booleanas y sus operadores vistas hace unas sesiones.

Esta sentencia fácilmente se puede interpretar como si el evento A sucede, realiza X tarea. Ya estás muy familiarizado con este tipo de sentencias, ya que en tu día a día tomas decisiones basadas en condiciones. Por ejemplo:

Voy a salir de casa; si llueve, me pongo una chamarra.

Donde el evento que puede suceder es que A = llueve, y si se cumple, es decir, que sea verdadero, se realiza la tarea X = Ponerse una chamarra. Visto como código en python, este enunciado se leería:

evento_A = 'llueve'

if evento_A == 'llueve':
print('Ponerse una chamarra.')

>> Ponerse una chamarra.

La sentencia if es la condición que debe o no cumplirse. Al igual que en el for, se termina con dos puntos, y se inicia un bloque que indica todo lo que se debe hacer en caso de cumplirse la condición.

La lógica de esta estructura se puede ver más claramente con un diagrama.

Figura 2.17 Diagrama de la lógica de la sentencia if. El rombo equivale a la condición lógica if.

En caso que no se cumpla la condición, python salta todo ese bloque y continua con las demás tareas programadas.

Tarea alternativa
Es posible que de no cumplirse una condición, se tenga que realizar otra tarea. Siguiendo con el ejemplo, ahora tenemos dos posibilidades: Voy a salir de casa, si llueve, me pongo una chamarra; si no, me pongo un chaleco. Este enunciado en código sería escrito como:

evento_A = 'no llueve'

if evento_A == 'llueve':
print('Ponerse una chamarra.')
else:
print('Ponerse un chaleco.')

>> Ponerse un chaleco.

Ahora se utiliza el comando else para indicar un camino alternativo. De hecho else puede leerse como ‘si no’. A este caso se le conoce como sentencia if-else.

Figura 2.18 Diagrama de la sentencia if-else. De no cumplirse una condición, se ejecuta la tarea alternativa.

Condiciones múltiples
También puede haber múltiples casos, los cuales permiten al programa tomar uno de varios caminos. Para indicar cada uno de estos casos, se utiliza la sentencia elif, abreviación de else if.

Continuemos con el ejemplo: Voy a salir de casa, si llueve, me pongo una chamarra; si está soleado, me pongo una camiseta; si está nublado, me pongo un chaleco. Y en código se ve como sigue:

evento_A = 'soleado'

if evento_A == 'llueve':
print('Ponerse una chamarra.')
elif evento_A == 'soleado':
print('Ponerse una camiseta.')
elif evento_A == 'nublado':
print('Ponerse un chaleco.')

>> Ponerse una camiseta.

Esta sentencia es conocida como if-elif. Cada elif es una opción que se toma de acuerdo con el dato que ingrese, y hay una cantidad límite a escribir.

En este caso no se utilizó el else, pero puede usarse cuando ninguna de las condiciones se cumpla, por lo que siempre iría al final de la cadena de condiciones, y nunca lleva una condición delante. Sólo se da ante un evento obvio, es decir que sabemos que se puede cumplir, o ante uno desconocido. En este caso hablaríamos de una sentencia if-elif-else.

Figura 2.19 Izquierda: Diagrama de la sentencia if-elif. Derecha: Diagrama de la sentencia if-elif-else. El comando else se omite si se contemplan todos los casos, y se utiliza al no haber otra alternativa.

Condiciones anidadas
Las condiciones también pueden estar dentro de otras condiciones. De nuevo, no hay un límite de condiciones anidadas. Por ejemplo, una variación del segundo diagrama de la figura anterior se puede codificar como:

X = -7

if X != 0:
if X > 0:
print('X es real positivo.')
else:
print('X es real negativo.')
else:
print('X es cero.')

>> X es real negativo.

Figura 2.20 Diagrama de un if anidado. En este caso el if interno se da si la condición inicial se cumple.

El ciclo for y la sentencia if junto con sus variaciones prácticamente están siempre acompañados en el código. Es muy inusual encontrar un if solo, lo más común es que esta sentencia esté dentro de un ciclo. Esto porque se suele verificar que una condición se cumpla para cada elemento de una serie de datos. Tener un if dentro de un ciclo nos permite localizar, segmentar, filtrar, depurar, entre muchas otras acciones a nuestros datos.

Ejemplo 2.3 Clasificación de una serie de números en dos listas.
Dada una serie de números aleatorios, separar los números en dos listas: Una llamada multiplos_tres donde estarán los números múltiplos de 3, y otra llamada no_multiplos_tres, donde estarán todos los demás. Los números son: 13, 1, 7, 75, 58, 7, 17, 15, 5, 8, 98, 53, 32, 9, 18, 5, 31, 33, 13, 2.

Desarrollo: Empezamos considerando la información que tenemos. Nos dan una serie de números, la cual debe guardarse en una lista. Necesitaremos otras dos listas vacías, una donde se guardarán los múltiplos, y otra donde se guardarán todos los demás:

numeros = [13, 7, 17, 15, 5, 8, 9, 18, 5, 31, 33, 13, 2]
multiplos_tres = list()
no_multiplos_tres = list()

Como debemos revisar cada número si es o no múltiplo de tres, un ciclo for será el indicado. Las listas se llenarán de acuerdo con el siguiente criterio: si este número es múltiplo de tres, guardar en la lista multiplos_tres, si no, guardar en la lista no_multiplos de tres. Claramente estamos ante una sentencia if-else. Y para probar la multiplicidad, basta con que el residuo del número en cuestión y tres, sea cero. Se concluye utilizar el operador modular para ello:

for numero in numeros:
if numero%3 == 0:
multiplos_tres.append(numero)
else:
no_multiplos_tres.append(numero)

Se imprimen ambas listas para verificar resultados visualmente.

print(multiplos_tres)

>> [15, 9, 18, 33]

print(no_multiplos_tres)

>> [13, 7, 17, 5, 8, 5, 31, 13, 2]

Puedes además ordenarlas para que la verificación sea más sencilla.

Fin del ejemplo 2.3

Ciclo while

Mientras el ciclo for es definido, el ciclo while es el condicional. Se puede ver como un caso especial entre un ciclo for y una sentencia if más una sentencia de control que veremos en el siguiente capítulo. Como sucede con un if, tiene una condición que controla los ciclos que se deben realizar. Mientras la condición se cumpla, el ciclo while ejecutará una y otra vez el bloque.

El siguiente ejemplo ejecuta un conteo regresivo a partir del 10, y se detiene en el 1, sólo para seguir con la impresión de Despegue.

n = 10
while n > 0:
print(n)
n = n - 1
print('Despegue.')

Nota que la sentencia n > 0 controla el número de ciclos. No se detiene hasta que la condición deja de cumplirse. Esa sentencia literalmente puede leerse como ‘mientras n sea mayor que cero, ejecuta lo siguiente’.

Pero cuidado, porque este ciclo puede causar un bucle infinito. Si se declara una condición que nunca deja de cumplirse, tendrás que detener el proceso al presionar el botón de stop en la barra de botones superior de jupyter.

Es fácil crear un bucle infinito, por ejemplo:

i = 5
while i > 0:
print(i)

No es necesario que ejecutes este programa, sólo observa su lógica. La variable i al tener un valor constante positivo, nunca será igual o menor que cero, por lo que siempre estará imprimiendo i. Es un ejemplo muy sencillo, pero la intensión es que tengas presente que debes cuidar las las sentencias lógicas del ciclo while para evitar no sólo ciclos infinitos, sino resultados inesperados o no deseados.

2.4 Trabajando con archivos

Existen diversas formas de trabajar con archivos en python. En esta sección hablaremos sólo de una de ellas, la cual ya está por defecto, es decir, sin tener que utilizar paqueterías externas. En esta sección trabajaremos con texto extraído de Claerbout (1996); tanto en el archivo de texto llamado simplemente text.txt (descárgalo dando clic aquí), como en el que se genere en texto_nuevo.txt.

Un pequeño anuncio: Puedes descargar la carpeta con los archivos utilizados y generados aquí, junto con el código y los ejercicios resueltos, así como apoyarme a hacer más contenido como éste, suscribiéndote a mi patreon: https://www.patreon.com/ciencias_tierra.

Antes de seguir es importante presentarte el carácter especial \n, que aunque parezcan dos, en realidad es leído por python como uno solo. El caracter \n es un salto de línea. Dentro de una cadena puedes agregar ese carácter donde desees que haya un salto de línea:

hola = 'Hola \nTierra'
print(hola)

>> Hola
>> Tierra

Pero si lo imprimes sin usar el comando print:

hola

>> ‘Hola \nTierra’

Se muestra el carácter. Recuerda que sin el print se muestra la variable sin alteración.

Archivo existente
Al igual que hacemos con un archivo en un editor de texto, para escribir o leer en él, primero debemos abrirlo. La función requerida es open(), la cual permite abrir un archivo existente. Si el archivo no está contenido en el directorio indicado, python regresa un error.

La función open() permite abrir o crear un archivo. Si el archivo existe en el mismo directorio, basta con escribir el nombre del archivo y su extensión.

archivo = open('text.txt')
print(archivo)

>> <_io.TextIOWrapper name=’text.txt’ mode=’r’ encoding=’cp1252'>

E imprimirlo no muestra el texto del archivo, sino una descripción de éste. Esto indica que ha sido abierto satisfactoriamente. El modo por defecto es read (‘r’), justo para leerlo.

Un archivo es una estructura ordenada de datos, por lo que es muy útil el uso de ciclos para inspeccionarlo. Podemos contar por ejemplo la cantidad de renglones que tiene, e imprimir cada renglón:

conteo = 0 

for linea in archivo:
print(linea)
conteo = conteo + 1

print('Número de líneas:', conteo)
Figura 2.21 Impresión de un archivo de texto línea por línea.

Hasta aquí todo bien, pero el texto no tardará mucho en dejar de estar en tu memoria temporal. Y si dejas pasar un minuto, y ejecutas de nuevo la impresión del texto, notarás que ya no está el archivo cargado. De hecho es probable que hayas tardado un poco en ejecutar la celda anterior, y nada se haya impreso ni contado; y te sientas frustrado. Descuida, que para evitar que esto pase lo conveniente es guardar el texto del archivo en una variable mientras persiste en nuestra memoria. Para ello está el método read(). Una vez guardado, debes cerrar el archivo.

archivo = open('text.txt')   #Abrir archivo.
texto = archivo.read() #Guardar archivo en variable.
archivo.close() #Cerrar archivo. Siempre cerrarlo.

Si imprimes la variable texto notarás que la impresión cambia un poco, pero es la misma información. Ahora tienes el archivo en una cadena enorme, a la cual tendrás acceso todo el tiempo que tengas abierto tu cuaderno.

Archivo nuevo
Para crear un nuevo archivo, indicamos como segundo parámetro el modo de escribir write (‘w’) dentro de la función open().

archivo = open('texto_nuevo.txt', 'w')

linea1 = 'It is remarkable that more than 99% of industrial\n'
archivo.write(linea1)
linea2 = 'petroleum prospecting ignores the existence of\n'
archivo.write(linea2)
linea3 = 'shear waves. Mathematically the earth is treated as\n'
archivo.write(linea3)
linea4 = 'if it were a liquid or a gas.\n'
archivo.write(linea4)

archivo.close()

Nota que el salto de línea \n siempre se agrega para que no se apile todo en un mismo renglón. En la carpeta donde está el cuaderno se habrá creado el archivo nuevo. Ábrelo para ver lo que escribiste en él.

Si el archivo que quieres cargar está en otro directorio, o lo quieres guardar en otra ubicación, debes indicar la dirección dentro de la cadena del primer parámetro: ‘directorio_destino/archivo.txt’.

Es posible que necesitemos consultar información contenida en un archivo de texto, o comprobar si hay palabras clave en él. Conociendo la estructura podemos identificarla. Por ejemplo, aprovechando las líneas escritas arriba deseamos encontrar la palabra ‘Mathematically’. Un ciclo for con una sentencia if sería suficiente:

#linea3 es una cadena. Se convierte en lista de palabras.
lista_linea3 = linea3.split(' ')

#Se recorre palabra por palabra en esta lista.
for palabra in lista_linea3:
if palabra == 'Mathematically':
keyword = palabra

print(keyword)

>> Mathematically

Sólo se utilizó un for porque es una línea, pero imagina que estamos en varias líneas, ya podrías pensar que se requieren dos ciclos, uno que recorra línea por línea, y otro que recorra palabra por palabra. Esta es una de varias formas de resolver un problema así (Ejercicio 2.17).

Puedes cerrar tu cuaderno en jupyter.

Por último, la tabla 2.6 muestra las opciones que pueden indicarse en la función open() como segundo parámetro.

Tabla 2.6 Opciones disponibles para abrir o crear un archivo.

En la siguiente sesión veremos cómo trabajar con archivos delimitados de por coma (.csv), para que ¡empieces a automatizar cálculos!

Puedes descargar el cuaderno de esta sesión, los archivos usados en los ejemplos y los ejercicios resueltos suscribiéndote a mi patreon: https://www.patreon.com/ciencias_tierra.

Ejercicios

2.13 - Sin ejecutar los siguientes códigos, determina qué resultado imprime cada uno. Si hay un ciclo, define las impresiones en el orden en que se ejecutarían. Usa diagramas de ser necesario. Ejecuta para comprobar.**

# a)
x = 5
x = x + x
if x > 6:
print('Número grande.')
else:
print('Número pequeño.')
# b)
a = 'Plutónica'
b = 'Volcánica'
c = 'Sedimentaria'
x = c
x = b
if x != 'Sedimentaria':
print('Es una roca ígnea.')
else:
print('No es una roca ígnea.')
# c)
rocas = ('gabro','pumita','arenisca','migmatita','diorita')
for roca in rocas:
if roca == 'migmatita':
print('Aquí hay una roca metamórfica.')
elif roca == 'arenisca':
print('Aquí hay una roca sedimentaria.')
# d)
numeros = [2, 4, 6, 8, 10, 12, 15]
for n in numeros:
if n%5 == 0:
print('Número múltiplo de 5.')
elif n%3 == 0:
print('Número múltiplo de 3.')
else:
print('Número no múltiplo de 3 ni de 5.')
# e)
numeros = [2, 4, 6, 8, 10, 12, 15]
for n in numeros:
if n%3 == 0:
if n%5 == 0:
print('El número es divisible entre 3 y 5.')
if n%2 == 0:
if n%5 == 0:
print('El número es divisible entre 2 y 5.')
  • Extra: Modifica el código del inciso e) para que no haya if anidados y obtenga los mismos resultados. Utiliza operadores booleanos. Tip: para dos o más condiciones que deben cumplirse se utiliza la siguiente sintaxis: if condicion1 and/or condicion2:

2.14 - Escribe el código que haga lo mismo que las funciones len() y sum(). Preferible hacerlos en dos celdas diferentes. Prueba tu resultado con la lista de números [2, 5, 7, 3, 8, 1, 12, 4, 6, 13, 15]. Compara tu resultado con las funciones propias de python.*

2.15 - Escribe el código que haga lo mismo que las funciones min() y max(). Preferible hacerlos en dos celdas diferentes. Prueba tu resultado con la lista de numeros [2, 5, 7, 3, 8, 1, 12, 4, 6, 13, 15]. Compara tu resultado con las funciones propias de python.**

2.16 - Clasifica y separa la siguiente lista de rocas en tres listas, las cuales no deben tener elementos repetidos, según sea ígnea (i), sedimentaria (s) o metamórfica (m), lo cual está indicado al final de cada roca delante de un guion bajo. Las listas resultado no deben tener ese indicador, solo el nombre de cada roca. ***

Arenisca_s, kimberlita_m, dolomía_s, granito_i, riolita_i, limolita_s, gabro_i, cuarcita_m, granodiorita_i, pizarra_m, conglomerado_s, dacita_i, dolomía_s, pumita_i, andesita_i, arcilla_s, gneis_m, grauvaca_s, basalto_i, diorita_i, granito_i, caliza_s, migmatita_m, brecha_s, andesita_i, diorita_i, anfibolita_m, basalto_i, limolita_s, gabro_i, esquisto_m, grauvaca_s, granito_i, caliza_s, basalto_i, conglomerado_s, mármol_m, arcilla_s.

Las tres listas a obtener, no necesariamente en este orden, serán:

  • ígneas: granito, riolita, granodiorita, dacita, pumita, andesita, gabro, diorita, basalto.
  • sedimentarias: arenisca, dolomía, conglomerado, grauvaca, caliza, brecha, limolita, arcilla.
  • metamórficas: kimberlita, cuarcita, pizarra, gneis, migmatita, mármol, esquisto, anfibolita.

2.17 - En el archivo text.txt busca la expresión ‘reflection seismic imaging’ e imprímela tal como está escrito. No olvides todo lo que se has visto hasta ahora: ciclo for, sentencia if, salto de línea, métodos como el split y el join, entre otros. ***

2.18 - Se sabe que la Tierra es casi esférica, excepto por pequeñas diferencias entre los radios ecuatorial y polar. Por ello se considera un elipsoide con radio ecuatorial a = 6,378.16 km, y radio polar c = 6,356.18 km. Sabiendo que su masa está estimada en M = 5.976 x10²⁷ g. Obtener su densidad en g/cm³. *

La fórmula para obtener el volumen de un elipsoide es:

donde a, b y c son los semiejes del elipsoide respecto a los ejes X, Y y Z.

Respuesta: 5.517 g/cm³

--

--

Damian

Anything I want and is related to data. Learning to become a Data Professional.