Misión 2. Trae la música
▶ Abrir en Colab ▶ Ejecutar en Binder
Colab abre en segundos. Binder, la primera vez, construye el entorno en la nube (10-20 min) y luego queda en caché.
Misión 2. Encargo: «descarga el catálogo de música».
Victoria: el catálogo real cargado en tu cuaderno, con sus primeras filas y su tamaño a la vista.
Insignia en juego: mensajero del dato.
El encargo
En la Misión 1 tecleaste tú mismo unos pocos números inventados para aprender. Estaba bien —nadie empieza la casa por el tejado—, pero un analista de verdad no se inventa los datos: los trae. Y ahí está tu encargo de hoy, el segundo de la campaña: descargar un catálogo de música real y meterlo entero en tu cuaderno. No cinco números: miles de canciones reales, con su popularidad y sus rasgos de sonido medidos una a una. Cuando lo consigas —al final de estas páginas— verás las primeras filas de esa tabla y su tamaño exacto en la pantalla, y te habrás ganado la insignia mensajero del dato.
Suena fácil («pues abre el fichero y ya»), y por eso mismo es traicionero. Traer un dato es el gesto que más veces sale mal en la vida real de un analista, y casi nunca por su causa esperada. El fichero llega en un formato que no conocías. Las tildes y las eñes se convierten en garabatos. Las columnas no traen su nombre, o lo traen mal. El número que creías entero resulta ser un texto con el que no se puede sumar. Nada de esto es culpa tuya: es que traer datos tiene sus reglas, y esta misión te las enseña una a una, con un catálogo de música de por medio.
Un cambio importante respecto a la Misión 1.
Hasta ahora los números eran de mentira. A partir de hoy son de verdad, y eso cambia las cosas. Un dato real tiene procedencia —viene de algún sitio, lo midió o lo recopiló alguien, con una herramienta, un día concreto—, y esa procedencia hay que respetarla y contarla con honradez. No vale «me salió este número»; hay que poder decir «este número salió de este fichero, que vino de esta fuente». Esa cadena de honestidad empieza justo aquí, cuando traes el primer dato de verdad.
El mapa de la misión.
Como siempre, no saltamos a la victoria: la construimos pieza a pieza, y cada pieza es una micro-victoria que puedes enseñar. Este es el recorrido:
Entender qué es un fichero y qué es un formato (CSV, y de pasada JSON y Parquet).
Mirar un CSV por dentro: texto plano, filas y comas.
Descargar el fichero: la primera celda lo baja a tu cuaderno.
Leerlo con pandas y obtener una tabla de verdad.
Mirar sus primeras filas (
head) y su tamaño (shape).Preguntarle cosas a la tabla: columnas, tipos, valores únicos.
Entender las codificaciones: por qué a veces salen «símbolos raros» y cómo evitarlo.
Conocer de dónde vienen los datos abiertos y guardar su procedencia con honradez.
Vencer al jefe de parte: la ficha completa del dataset.
No hay prisa. Teclea el código tú mismo, y mantén la regla de oro de la Misión 1 —predice antes de ejecutar—, que hoy sigue valiendo: antes de mirar una tabla, apuesta cuántas filas tendrá. Vamos allá.
La herramienta
Qué es un fichero
Todo lo que hiciste en la Misión 1 vivía en la memoria del cuaderno: tus variables, tus listas, tus funciones. La memoria tiene una pega enorme —se borra—. En cuanto cierras el cuaderno o lo reinicias, se esfuma: por eso, si ejecutas de nuevo, tienes que volver a crear tus variables desde cero. Un fichero es lo contrario: un trozo de información guardado en un disco, con un nombre, que sobrevive a que apagues la máquina. Cuando alguien «recopila un catálogo de canciones y lo publica», lo que hace es escribir esos datos en un fichero. Y cuando tú «traes la música», lo que haces es leer ese fichero.
Un fichero tiene un nombre y, casi siempre, una extensión: la coletilla tras el último punto. musica.csv es un fichero llamado musica con extensión .csv. La extensión no es magia —es solo una pista sobre qué hay dentro y cómo leerlo—, pero es una pista que conviene mirar siempre, porque te dice el formato.
Qué es un formato
Un formato es el acuerdo sobre cómo se ordenan los bytes dentro del fichero para que quien lo lea entienda aquello que quien lo escribió quiso decir. Es como un idioma: si el que escribe y el que lee no usan el mismo, no se entienden. Hay muchos formatos para datos, pero para empezar te bastan tres nombres, y hoy nos centramos en el primero:
CSV (comma-separated values, «valores separados por comas»): una tabla en texto plano, con una fila por línea y las columnas separadas por comas. Lo abre cualquier programa, lo lee un humano, y es con diferencia el formato más común del planeta para intercambiar tablas. Es el formato en el que casi siempre te llegará la música.
JSON (JavaScript Object Notation): el formato de las webs y las apps. En vez de una tabla plana, guarda datos anidados —cajas dentro de cajas—, con la misma pinta que los diccionarios y listas de la Misión 1. Lo verás cuando pidas datos a una API.
Parquet: un formato binario (no lo lee un humano) hecho para el trabajo serio con datos. Ocupa menos, es más rápido y —clave— recuerda el tipo de cada columna. Es adonde llevarás la música una vez traída.
Piénsalo así: CSV es el idioma de intercambio (todos lo hablan, aunque sea tosco); Parquet es el idioma de trabajo (más eficiente, para cuando los datos ya están en casa). Empezamos por el CSV porque es lo que sale por la puerta cuando descargas datos abiertos.
Un CSV por dentro
Lo mejor del CSV es que no esconde nada: es texto, y lo puedes leer con los ojos. Imagina que abres el fichero de música con un editor cualquiera. Sus primeras líneas serían así:
track_name,artists,track_genre,popularity,danceability,energy,valence,tempo,duration_ms,explicit
Hold On,Chord Overstreet,acoustic,82,0.618,0.443,0.167,119.949,198853,False
I'm Yours,Jason Mraz,acoustic,80,0.703,0.444,0.712,150.96,242946,False
Pano,Zack Tabudlo,acoustic,75,0.375,0.457,0.415,174.839,254400,False
Míralo con calma, porque hay toda una tabla escondida en ese texto. La primera línea no es un dato: es la cabecera, los nombres de las diez columnas. Cada línea siguiente es una fila —una canción— y sus diez valores van separados por comas, en el mismo orden que la cabecera. Así, la segunda línea dice: la canción Hold On, de Chord Overstreet, del género acoustic, con una popularidad de 82 sobre 100. La tabla está toda ahí; solo hay que saber leerla.
Fíjate en un par de detalles honestos desde ya. La duración no viene como «3:18», sino como 198853: milisegundos, la unidad que le conviene a la máquina, no a ti. Y cuando una canción tiene varios artistas, no vienen en columnas aparte: van pegados en el mismo campo, separados por un punto y coma ("Jason Mraz;Colbie Caillat"). Los datos reales vienen así, en el formato que le sale cómodo a quien los generó. No es un fallo: es lo normal cuando los datos los publica una máquina. Lo apuntamos y seguimos.
Por qué no se parte por comas a mano
La tentación es clara: si un CSV son líneas con comas, hago un bucle, un .split(",") por línea, y listo. Esta idea es casi siempre correcta —y ocasionalmente desastrosa—, que es la peor combinación posible: el código funciona con tus datos de prueba y falla en silencio con un dato de verdad. Tres trampas, para que las conozcas:
Comas dentro de un dato. El título
"You’re Gonna Go Far, Kid", de The Offspring, lleva una coma que forma parte del texto, no separa columnas. Por eso, en un CSV bien hecho, ese campo va entre comillas. Un.split(",")ingenuo lo partiría en dos y descuadraría todas las columnas siguientes.El separador no siempre es la coma. En España, la coma es el separador decimal (escribimos «0,618», no «0.618»). Para no chocar, muchos programas de aquí —y Excel en español— guardan los CSV usando el punto y coma (
;) como separador. El fichero es válido, pero no tiene ni una coma que separe columnas.Saltos de línea dentro de un campo. Un título raro o un comentario largo puede contener un salto de línea de verdad, así que una fila no siempre es una línea del fichero. Ir línea a línea, entonces, se rompe.
La conclusión, grábatela: un CSV no se parsea a mano partiendo por comas. Todas esas reglas —comillas, separadores raros, saltos— ya están resueltas, probadas y afinadas por herramientas hechas para ello. Reescribirlas a mano es reintroducir fallos que otros ya sufrieron. ¿Cuál es la herramienta? La que usarás durante todo el resto de la campaña: pandas.
La primera celda: descargar el fichero
Aquí aparece la primera regla de trabajar en Colab: tu cuaderno empieza vacío. No hay ningún fichero a tu lado —el catálogo de música no está ahí todavía—, así que lo primero de todo, antes de leer nada, es traerlo. Por eso la primera celda de casi cualquier cuaderno de datos es siempre la misma: una línea que descarga el fichero de una dirección de internet y lo deja junto a ti, listo para leer.
Nosotros hemos preparado la música de esta misión —una versión con la misma forma que la real, pero fija, para que tus cifras salgan idénticas a las del libro; te contamos por qué al final de la misión— y la hemos dejado servida en una dirección estable. Traerla es esta línea, y es la primera que ejecutas:
import urllib.request
# el fichero preparado del libro, en su direccion fija de github
URL = ("https://raw.githubusercontent.com/mmunozpl/"
"PythonCienciaDeDatosSTEM/main/data/musica.csv")
urllib.request.urlretrieve(URL, "musica.csv")Tras ejecutarla, musica.csv ya está en tu cuaderno —míralo en el panel de ficheros de Colab, a la izquierda—, y todo lo que viene después puede leerlo. (Esa dirección es la del repositorio del libro; si algún día cambia, la web del libro tiene siempre la buena.) No hace falta entender aún cada palabra de esa línea (qué es la biblioteca urllib, qué es una URL, qué hace urlretrieve); basta con saber qué logra: bajar el fichero de la red a tu lado. El detalle de cómo se pide un dato por internet —y cómo traerías el catálogo crudo directamente de su fuente— lo iremos ampliando en las misiones que vienen. Por hoy, con una línea, ya tienes la música.
pandas: la herramienta que lee tablas
pandas es una biblioteca de Python especializada en tablas. No viene de serie con el lenguaje, pero en Colab está ya instalada, así que solo hay que importarla. Por costumbre universal se le pone el apodo pd, para teclear menos:
import pandas as pdCon esa línea, todo el poder de pandas queda disponible bajo el nombre pd. Y ahora, el gesto de la misión, el que trae la música: leer el CSV. La orden es pd.read_csv («lee un csv»), y le pasas el nombre del fichero:
musica = pd.read_csv("musica.csv")Esa sola línea ha hecho una barbaridad de trabajo por ti: ha abierto el fichero, ha leído sus miles de líneas, ha respetado las comillas y los separadores, ha reconocido la cabecera como nombres de columna, ha adivinado el tipo de cada columna… y te ha devuelto una tabla entera, guardada en la variable musica. Esa tabla tiene un nombre técnico en pandas: DataFrame («marco de datos»). Piénsalo como una hoja de cálculo viva dentro de tu cuaderno: filas, columnas con nombre, y un montón de órdenes para interrogarla. El resto de la misión —y de la campaña— es aprender a hacerle preguntas.
Mira las primeras filas: head
Un DataFrame puede tener miles de filas; no vas a imprimirlas todas. Lo primero que hace cualquier analista al traer un dato nuevo es asomarse a sus primeras filas, para ver qué pinta tiene. La orden es .head() («la cabeza», las de arriba):
musica = pd.read_csv("musica.csv")
print(musica.head()) track_name artists ... duration_ms explicit
0 Hold On Chord Overstreet ... 198853 False
1 I'm Yours Jason Mraz ... 242946 False
2 Pano Zack Tabudlo ... 254400 False
3 Give Me Your Forever Zack Tabudlo ... 244800 False
4 Lucky Jason Mraz;Colbie Caillat ... 189613 False
[5 rows x 10 columns]
Ahí está el catálogo de música, dentro de tu cuaderno. Ya no son números que tecleaste: son canciones reales, servidas en una tabla que puedes leer. .head() muestra las cinco primeras filas por defecto (si quieres tres, pídele .head(3)). Fíjate en la columna de la izquierda sin nombre, con los números 0, 1, 2, 3, 4: es el índice, la posición de cada fila, y —cómo no— empieza en cero, como las listas. Y fíjate en los tres puntos ... del centro: como no caben las diez columnas de ancho, pandas esconde las del medio y te avisa abajo —[5 rows x 10 columns], «5 filas por 10 columnas»—. No se ha perdido nada: solo no cabían. Enseguida vemos las diez, una a una.
Su hermana .tail() («la cola») hace lo mismo con las últimas filas, y sirve para comprobar que el fichero se leyó entero, hasta el final:
print(musica[["track_genre", "popularity"]].tail()) track_genre popularity
3346 world-music 61
3347 world-music 61
3348 world-music 61
3349 world-music 61
3350 world-music 61
Dos cosas se ven de golpe. La última fila tiene el índice 3350, así que el fichero es grande (ya contaremos cuánto). Y las últimas canciones son del género world-music: el fichero está ordenado por género, de la a a la w, y lo hemos leído hasta el final. De paso, para no imprimir las diez columnas, hemos pedido solo dos, poniéndolas en una lista entre dobles corchetes: musica[["track_genre", "popularity"]]. Volveremos a ese truco.
Cuánto has traído: shape
La primera pregunta de todo analista ante un dato nuevo es «¿cuánto hay?». La respuesta la da .shape («la forma»), que te dice de un vistazo cuántas filas y cuántas columnas tiene la tabla. Ojo: shape no lleva paréntesis, porque no es una orden que calcula, sino un dato que la tabla ya tiene.
print(musica.shape)
print("filas:", musica.shape[0], "columnas:", musica.shape[1])(3351, 10)
filas: 3351 columnas: 10
3 351 filas y 10 columnas. Ese es el tamaño de lo que acabas de traer, y es la otra mitad de tu victoria de hoy. shape devuelve una pareja de números (una tupla): el primero, shape[0], es el número de filas; el segundo, shape[1], el de columnas. Tres mil y pico canciones. Ninguna persona lee eso a ojo, y ahí está justo la gracia de todo lo que aprendiste en la Misión 1: el ordenador sí puede, y le vas a mandar que lo haga.
¿De dónde salen 3 351? De un recorte con criterio: hemos guardado las treinta canciones más populares de cada género, y hay 114 géneros. Si cada uno aportara exactamente treinta, saldrían \(114 \times 30 = 3\,420\); pero algunos géneros traen menos de treinta canciones distintas, y por eso el total se queda un poco por debajo, en 3 351. Que el número casi cuadre —y saber por qué no cuadra del todo— es la primera señal de que entiendes de dónde viene tu fichero.
Qué columnas hay: columns
Antes de analizar nada conviene saber qué campos trae la tabla. La lista de nombres de columna está en .columns:
print(musica.columns)
print("numero de columnas:", len(musica.columns))Index(['track_name', 'artists', 'track_genre', 'popularity', 'danceability',
'energy', 'valence', 'tempo', 'duration_ms', 'explicit'],
dtype='object')
numero de columnas: 10
Diez nombres, diez columnas. (Pandas llama Index a esa lista de nombres, y como no le caben en una línea la parte en dos; no te despistes, son los diez de siempre.) Con esto ya puedes leer la tabla como quien lee la etiqueta de un producto. Traducidas al castellano:
track_name: el título de la canción, como"Hold On".artists: quién la firma; si son varios, van pegados con punto y coma ("Jason Mraz;Colbie Caillat").track_genre: el género musical (acoustic,pop,reggaeton…); hay 114 en total.popularity: la popularidad en Spotify, un número de0a100.danceability: cuánto invita a bailar la canción, de0a1.energy: la energía o intensidad que transmite, de0a1.valence: cuán alegre o positiva suena, de0a1.tempo: la velocidad de la canción, en pulsaciones por minuto.duration_ms: cuánto dura, en milisegundos.explicit: si tiene letra explícita (True) o no (False).
Esa popularity es la protagonista de toda la campaña. Es la nota de éxito que Spotify pone a cada canción, de 0 a 100, según cuánto se escucha ahora mismo. Toda la aventura de «¿qué hace que una canción triunfe?» gira alrededor de esa columna.
De qué tipo es cada columna: dtypes
En la Misión 1 aprendiste que cada dato tiene un tipo: texto, entero, decimal, booleano. Una tabla también, columna por columna, y saberlo importa: no se hacen las mismas cosas con texto que con números. pandas te lo cuenta con .dtypes («tipos de dato»):
print(musica.dtypes)track_name object
artists object
track_genre object
popularity int64
danceability float64
energy float64
valence float64
tempo float64
duration_ms int64
explicit bool
dtype: object
Léelo con tu vocabulario de la Misión 1. int64 es un entero (el int de antes, con un 64 detrás que dice cuánta memoria usa); float64 es un decimal (el float); y object es como pandas llama, casi siempre, al texto. Tiene sentido: la popularidad y la duración son enteros, los rasgos de sonido (danceability, energy …) llevan decimales, y el título, el artista y el género son texto. Y asoma otra vez el bool, el booleano que ya conociste en la Misión 1. La columna explicit solo vale True o False, y pandas lo reconoce y la marca como bool, no como texto —un pequeño lujo, porque así puedes filtrar «las explícitas» sin líos—.
Fíjate en la duration_ms: es un entero enorme (198853), porque la duración viene en milisegundos, no en minutos. Para pandas, ese número está perfecto; para ti, no dice gran cosa hasta que lo conviertes en algo legible como «3:18». Esa clase de ajuste pequeño —pasar milisegundos a minutos— es faena de la Misión 4; hoy basta con que sepas verlo.
La radiografía en una orden: info
shape, columns y dtypes te dan cada dato por separado. Hay una orden que los junta todos —y algo más— en una sola radiografía del fichero: .info(). Es lo primero que teclean muchos analistas al recibir una tabla, porque de un vistazo lo cuenta todo:
musica.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3351 entries, 0 to 3350
Data columns (total 10 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 track_name 3351 non-null object
1 artists 3351 non-null object
2 track_genre 3351 non-null object
3 popularity 3351 non-null int64
4 danceability 3351 non-null float64
5 energy 3351 non-null float64
6 valence 3351 non-null float64
7 tempo 3351 non-null float64
8 duration_ms 3351 non-null int64
9 explicit 3351 non-null bool
dtypes: bool(1), float64(4), int64(2), object(3)
memory usage: 239.0+ KB
Léela de arriba abajo. Es un DataFrame (primera línea). Tiene 3 351 filas, numeradas de 0 a 3 350 (RangeIndex). Y luego, columna por columna: su nombre, cuántos valores no vacíos tiene (Non-Null Count) y su tipo. Fíjate en esa columna del centro: las diez ponen 3351 non-null, es decir, ninguna tiene huecos. Eso es una gran noticia —en datos reales, lo normal es que falten valores—, e info es justo donde se detecta de un golpe. Abajo del todo te dice cuánta memoria ocupa la tabla (239 KB) y un resumen de tipos. Una sola orden, una foto completa: guárdala como la primera que le haces a cualquier dataset nuevo.
Una columna sola, una fila sola
Una tabla entera abruma; casi siempre quieres mirar un pedazo. Para quedarte con una columna, la pides por su nombre entre corchetes, igual que un valor de un diccionario en la Misión 1:
print(musica["popularity"].head())0 82
1 80
2 75
3 74
4 74
Name: popularity, dtype: int64
musica["popularity"] es la columna de la popularidad, ella sola. En pandas, una columna suelta tiene su propio nombre: Series («serie»), que es como una lista con superpoderes —y con su índice a la izquierda—. Le hemos encadenado .head() para no ver las 3 351; abajo del todo, pandas te recuerda el nombre de la serie (popularity) y su tipo (int64).
Para quedarte con una fila por su posición, usa .iloc (de integer location, «posición entera»):
print(musica.iloc[0])track_name Hold On
artists Chord Overstreet
track_genre acoustic
popularity 82
danceability 0.618
energy 0.443
valence 0.167
tempo 119.949
duration_ms 198853
explicit False
Name: 0, dtype: object
musica.iloc[0] es la primera fila entera, con cada columna y su valor —la ficha de una sola canción—. Cámbialo por musica.iloc[1] y tendrás la segunda. Con columnas por nombre y filas por posición, ya puedes apuntar a cualquier rincón de la tabla.
Preguntas rápidas: valores únicos, conteos y resumen
Aquí empiezan las micro-victorias de verdad: preguntarle cosas al catálogo real y que te conteste con un hecho. Primera pregunta: ¿qué valores toma una columna?. La orden .unique() da los valores distintos, sin repetir, y en las columnas de pocas categorías es un lujo; en las de muchas, mejor solo contarlas con .nunique():
print("valores de explicit:", musica["explicit"].unique())
print("generos distintos:", musica["track_genre"].nunique())
print("artistas distintos:", musica["artists"].nunique())valores de explicit: [False True]
generos distintos: 114
artistas distintos: 1846
La columna explicit solo toma dos valores (False y True), y ahí .unique() los enseña de un vistazo. En cambio hay 114 géneros y 1846 artistas distintos: listarlos no aportaría nada, así que .nunique() («número de únicos») los cuenta y punto. Segunda pregunta: ¿cuántas canciones son explícitas?. Para contar por categorías está .value_counts(), una de las órdenes que más usarás en toda tu vida de analista:
print(musica["explicit"].value_counts())explicit
False 2929
True 422
Name: count, dtype: int64
De un tirón sabes que 2 929 canciones no son explícitas y 422 sí —una de cada ocho, más o menos—. Y tercera pregunta, la más golosa: ¿cómo es la popularity en general?. La orden .describe() resume una columna de números en un vistazo:
print(musica["popularity"].describe().round(2))count 3351.00
mean 67.85
std 14.33
min 12.00
25% 58.00
50% 70.00
75% 78.00
max 100.00
Name: popularity, dtype: float64
Ocho números que cuentan la historia entera de una columna. count son cuántas canciones hay (las 3 351, ninguna vacía). mean es la media: la popularidad media de este catálogo es 67,8 —muy alta, y hay una razón que ya te adelanto: guardamos solo las más populares de cada género—. min y max son el menor y el mayor: la menos popular tiene 12 y hay alguna con el máximo de 100. Y el 50 % (la mediana) es 70: la mitad de las canciones quedan por debajo de ese valor. En la Misión 6 desmenuzarás qué significan la std y los percentiles 25 % y 75 %; por ahora quédate con la foto —un catálogo de música, resumido en ocho cifras, calculadas sobre datos reales sin que tú contaras nada a mano—. (Ojo a un detalle de analista: aquí describe enseña 67.85 porque le pedimos dos decimales, y más adelante la ficha del jefe dirá 67.8. No se contradicen: la ficha redondea la media original a un decimal, no el 67,85 ya redondeado. Regla que te ahorrará disgustos: redondea al final, nunca en medio.)
Prueba la misma orden con otra columna y verás cómo cada una cuenta su propia historia. La danceability, por ejemplo:
print(musica["danceability"].describe().round(3))count 3351.000
mean 0.582
std 0.172
min 0.000
25% 0.474
50% 0.588
75% 0.708
max 0.980
Name: danceability, dtype: float64
Ahí está el «cuánto se baila» de todo el catálogo: una media de 0,58 en una escala de 0 a 1, con un mínimo de 0 (alguna pieza que no invita nada a moverse, seguramente clásica) y un máximo de 0,98 (un temazo de pista). La misma orden, otra columna, otra historia. describe es tu primer resumen de cualquier columna de números —úsalo sin miedo con todas—.
Codificaciones: por qué salen «símbolos raros»
Tarde o temprano te pasará: abres un fichero y donde debería poner «Beyoncé» pone «Beyoncé», o donde va «español» aparece «español». Parece que el dato está roto. No lo está. Lo que ha fallado es la codificación, y entenderlo te ahorrará horas de desconcierto.
Por dentro, un ordenador solo guarda números (bytes). Una codificación es la regla que dice qué número representa cada letra. El problema es que hay varias reglas distintas, y si un fichero se escribe con una regla y se lee con otra, las letras corrientes (la a, la e) salen bien —porque casi todas las reglas coinciden en ellas—, pero las especiales (la ñ, las tildes) salen convertidas en garabatos. A ese destrozo se le llama, con una palabra japonesa preciosa, mojibake.
Vamos a fabricarlo a propósito, que se entiende mejor viéndolo. La regla buena y casi universal hoy se llama UTF-8. Cogemos «español» —como en «pop español»— con su eñe, la convertimos a bytes con UTF-8, y luego la leemos: primero con la regla correcta (UTF-8) y después con una regla vieja y distinta (latin-1):
palabra = "espa" + chr(0xf1) + "ol" # espanol, con la enye
crudo = palabra.encode("utf-8")
print("bytes:", crudo)
print("bien (utf-8):", crudo.decode("utf-8"))
print("mal (latin-1):", crudo.decode("latin-1"))bytes: b'espa\xc3\xb1ol'
bien (utf-8): español
mal (latin-1): español
Ahí lo tienes, el mojibake en directo. La «ñ» se guardó, en UTF-8, como dos bytes (\xc3\xb1). Leídos con la regla correcta, vuelven a ser una «ñ». Leídos con latin-1 —que cree que cada byte es una letra— se convierten en dos garabatos, «Ã±». Los bytes del disco están intactos; lo que falló fue la regla con que se leyeron. Y pasa igual con las tildes, tan frecuentes en los nombres de artistas —«Beyoncé», con su é—:
artista = "Beyonc" + chr(0xe9) # Beyonce, con la e tildada
b = artista.encode("utf-8")
print("bien:", b.decode("utf-8"))
print("mal :", b.decode("latin-1"))bien: Beyoncé
mal : Beyoncé
Misma historia: leída bien, se ve limpia; leída mal, le brota una «Ã» de la nada. Si algún día ves esos «Ã», «Â» o símbolos imposibles en tus datos, ya sabes el diagnóstico: no está roto el dato, está mal la codificación.
Leer con la codificación correcta
La buena noticia: en pandas se corrige con un solo parámetro. read_csv acepta encoding, donde le dices con qué regla leer. Casi siempre la respuesta es "utf-8", que es el estándar de la web y de los datos abiertos modernos. Mira la diferencia con un fichero pequeño de géneros que sí llevan tildes y eñes —sus nombres en español—, guardado en UTF-8, leído primero mal y luego bien:
malo = pd.read_csv("generos.csv", encoding="latin-1")
print(malo)
bueno = pd.read_csv("generos.csv", encoding="utf-8")
print(bueno) track_genre nombre_es
0 classical clásica
1 kids niños
track_genre nombre_es
0 classical clásica
1 kids niños
El mismo fichero, dos lecturas, dos resultados. Con latin-1, mojibake; con utf-8, «clásica» y «niños» limpios. La regla práctica para toda la campaña: al leer un CSV, si ves símbolos raros, prueba encoding="utf-8". Y si vino de un Excel viejo y sigue fallando, prueba "latin-1" o "utf-8-sig". No es adivinar a lo loco: es probar dos o tres reglas conocidas hasta que las tildes salgan bien.
Cuando el separador no es la coma
¿Recuerdas la trampa de antes —que en España el separador de columnas suele ser el punto y coma, no la coma—? Aquí la ves morder. Imagina un pequeño CSV «a la española», europeo.csv, con punto y coma entre columnas y coma decimal en los números. Si lo lees con read_csv tal cual, pandas busca comas para separar y no encuentra ninguna, así que se cree que toda la línea es una sola columna:
mal = pd.read_csv("europeo.csv")
print("columnas:", list(mal.columns))
print("forma:", mal.shape)columnas: ['pista;genero;danceability']
forma: (2, 1)
Una sola columna con un nombre monstruoso —pista;genero;danceability pegado— y forma (2, 1). El fichero no está roto: le has hablado en el idioma equivocado. Se corrige diciéndole el separador de verdad con el parámetro sep:
bien = pd.read_csv("europeo.csv", sep=";")
print(bien) pista genero danceability
0 Hold On acoustic 0,618
1 Pano acoustic 0,375
Ahora sí, tres columnas limpias. Pero fíjate en la danceability: 0,618, con coma decimal. pandas lo ha dejado como texto, no como número, porque un número de verdad, para él, lleva punto. Con ese «0,618» no podrías calcular todavía; habría que decirle a pandas que la coma es el separador decimal (con el parámetro decimal=","). Es un lío pequeño y muy español que conviene reconocer: separador de columnas y separador decimal se pisan, y por eso aquí exportamos con punto y coma. Cuando un CSV te llegue «raro», mira primero qué separa las columnas.
Un vistazo rápido sin leerlo todo
Cuando un fichero es enorme —y los de datos abiertos lo son— no siempre quieres cargarlo entero solo para echarle un ojo. read_csv tiene un parámetro, nrows, que lee solo las primeras filas que le pidas. Perfecto para asomarte a un fichero desconocido sin gastar memoria ni tiempo:
peek = pd.read_csv("musica.csv", nrows=3)
print(peek[["track_name", "track_genre", "popularity"]]) track_name track_genre popularity
0 Hold On acoustic 82
1 I'm Yours acoustic 80
2 Pano acoustic 75
Tres filas, leídas al instante, para ver si el fichero es lo que crees antes de cargarlo del todo. Es el equivalente a abrir la puerta y asomar la cabeza antes de entrar en una habitación a oscuras. Un buen hábito con ficheros que no conoces: nrows primero, la carga completa después.
Otros formatos: JSON y Parquet, de pasada
El CSV no es el único formato, solo el más común para tablas. Merece la pena que conozcas de vista los otros dos, porque te los cruzarás.
El JSON es el idioma de las webs y las apps. En vez de una tabla plana, guarda datos anidados, con la misma forma que los diccionarios y listas de la Misión 1. Una canción, en JSON, se vería así:
cancion = {
"track_name": "Hold On",
"artists": "Chord Overstreet",
"track_genre": "acoustic",
"rasgos": {"danceability": 0.618, "energy": 0.443, "popularity": 82},
}
print(cancion["rasgos"]["popularity"])82
Fíjate en las cajas dentro de cajas: rasgos es un diccionario dentro del diccionario de la canción, y por eso a la popularity se llega con dos saltos: cancion["rasgos"]["popularity"]. Esa capacidad de anidar es lo que hace al JSON perfecto para respuestas de internet, donde los datos no siempre caben en una tabla cuadrada. Lo trabajarás cuando pidas datos a una API.
El Parquet, en cambio, es lo contrario del texto legible: un formato binario, ilegible para un humano, pero hecho para el trabajo serio. Guarda cada columna por separado, comprimida, y —esto es lo importante— recuerda el tipo de cada columna. Es adonde llevarás la música ahora mismo.
Guardar en Parquet: más pequeño y con tipos
Un hábito que acelera todo trabajo de datos: en cuanto traes un CSV, guárdalo también en Parquet. Lo escribes con .to_parquet y lo relees con pd.read_parquet:
musica = pd.read_csv("musica.csv")
musica.to_parquet("musica.parquet")
otra_vez = pd.read_parquet("musica.parquet")
print(otra_vez.shape)(3351, 10)
Misma tabla, mismas 3 351 filas. ¿Qué hemos ganado, entonces? Dos cosas grandes. Primera, el tamaño. Comparemos lo que ocupa cada fichero en el disco:
import os
csv_kb = os.path.getsize("musica.csv") / 1024
pq_kb = os.path.getsize("musica.parquet") / 1024
print("CSV: ", round(csv_kb, 1), "KB")
print("Parquet:", round(pq_kb, 1), "KB")
print("veces mas pequeno:", round(csv_kb / pq_kb, 1))CSV: 279.6 KB
Parquet: 151.2 KB
veces mas pequeno: 1.8
El mismo dato exacto ocupa casi la mitad en Parquet. No es una diferencia tan brutal como podrías esperar, y hay motivo: este fichero es pequeño y lleva mucho texto (títulos, artistas), que se comprime peor que las columnas de números. Con un dataset grande y numérico —millones de filas de rasgos—, Parquet llega a ocupar diez o veinte veces menos; aquí solo lo intuyes. Segunda ventaja, la de los tipos: Parquet guarda que popularity es entera y danceability decimal, así que al releer no hay que adivinar nada —y además puedes pedir solo las columnas que necesites, sin leer el resto del fichero—:
dos = pd.read_parquet("musica.parquet",
columns=["track_genre", "popularity"])
print(dos.shape)
print(dos.head(3))(3351, 2)
track_genre popularity
0 acoustic 82
1 acoustic 80
2 acoustic 75
Le hemos pedido solo dos columnas de las diez, y Parquet ha leído solo esas dos, sin tocar las otras ocho. Con una tabla pequeña da igual; con una de millones de filas y cincuenta columnas, esa lectura selectiva es la diferencia entre esperar un suspiro o un minuto. La regla de oro: usa CSV o JSON para intercambiar datos (que otros los lean); usa Parquet para trabajar.
De dónde viene la música: los datos abiertos
Hasta ahora has usado nuestra versión preparada del fichero, la que bajaste en la primera celda. Pero ¿de dónde sale, en la vida real, un catálogo de música con todos esos rasgos? De los datos abiertos. Alguien —en este caso un recopilador llamado maharshipandya— reunió más de cien mil canciones de Spotify con su popularidad y sus rasgos de audio, armó una tabla y —esto es lo bonito— la publicó para que cualquiera la use, gratis, en una plataforma de datasets abiertos llamada Hugging Face. El conjunto se llama Spotify Tracks Dataset, y es de verdad: el mismo que usan estudiantes, curiosos e investigadores de todo el mundo.
Que un dato sea «abierto» significa dos cosas concretas. Una, que puedes descargarlo sin pagar. Otra, que tienes permiso para reutilizarlo —analizarlo, publicar tus conclusiones, hasta hacer una app— siempre que digas de dónde salió. Ese permiso viene escrito en una licencia. Los datos abiertos son una de las mejores cosas que le han pasado a la ciencia de datos: ponen la materia prima al alcance de cualquiera con curiosidad y un cuaderno —tú, ahora mismo—.
La honestidad de la procedencia
Y aquí una confesión que es, en sí misma, una lección. El fichero de esta misión no es el catálogo crudo entero: es una versión preparada por nosotros —misma forma, mismas columnas, un recorte de canciones reales— para que todo lo que ves en estas páginas te salga a ti idéntico al ejecutarlo. ¿Por qué no el fichero crudo, tal cual? Por tres razones que son puro oficio:
Reproducibilidad. La popularidad de una canción cambia con el tiempo —Spotify la recalcula según lo que se escucha—. Si usáramos el crudo vivo, las cifras de este libro dejarían de cuadrar con las tuyas mañana. Con un fichero que controlamos, tu salida y la nuestra coinciden siempre.
Estabilidad. Nuestra versión vive en una dirección fija que no se rompe cuando la fuente reorganiza sus ficheros o los renombra. La primera celda baja siempre el mismo fichero, pequeño y de forma conocida; nada de sorpresas a mitad de misión.
Cortesía. Miles de lectores lanzando a la vez la misma descarga de cien mil filas contra los servidores públicos de Hugging Face los cargarían sin necesidad. Al servir nosotros una copia pequeña, esa carga no cae sobre un servicio gratuito. No está bien abusar de él.
Nada de esto es un truco: es exactamente lo que hace un buen analista cuando prepara un ejemplo para otros. Lo importante —y aquí está la honradez— es que te lo contamos. Un dato tiene procedencia, y ocultarla o adornarla es la primera forma de mentir con datos. Apunta siempre de dónde salió lo que analizas: la fuente, la fecha, si es el crudo o una versión preparada. Ese pie de página tan aburrido es lo que separa un análisis en el que se puede confiar de uno que solo hay que creer.
La victoria
Tienes todas las piezas: sabes qué es un fichero y un formato, sabes leer un CSV con pandas, mirar sus primeras filas, medir su tamaño, ver sus columnas y sus tipos, y hasta esquivar el mojibake. Es hora de juntarlo en el gesto que corona la misión —el encargo de hoy, cumplido—: traer el catálogo de música y enseñarlo.
import pandas as pd
musica = pd.read_csv("musica.csv")
print("El catalogo de musica ya esta en tu cuaderno.")
print("Filas (canciones):", musica.shape[0])
print("Columnas:", musica.shape[1])
print(musica[["track_name", "track_genre", "popularity"]].head())El catalogo de musica ya esta en tu cuaderno.
Filas (canciones): 3351
Columnas: 10
track_name track_genre popularity
0 Hold On acoustic 82
1 I'm Yours acoustic 80
2 Pano acoustic 75
3 Give Me Your Forever acoustic 74
4 Lucky acoustic 74
Ahí está tu victoria, a la vista. Le has dado a la máquina el nombre de un fichero y te ha devuelto un catálogo real de música: 3 351 canciones, 10 columnas, y sus primeras filas en pantalla para que las mires con tus propios ojos. No lo tecleaste tú: lo trajiste. Ese es, exactamente, el trabajo de un mensajero del dato —llevar el dato de donde vive a donde se analiza, entero y sin estropearlo por el camino—.
¿Lo lograste?
Como en la Misión 1, no te fías de que «parece que va»: lo compruebas con un assert, ese guardián amable que se calla si todo está bien y te da una pista si no. Aquí comprobamos que la tabla tiene el tamaño esperado y la columna que importa:
musica = pd.read_csv("musica.csv")
assert musica.shape == (3351, 10), "Revisa la carga del fichero."
assert "popularity" in musica.columns, "Falta la columna popularity."
print("Lo lograste. Insignia desbloqueada: mensajero del dato.")Lo lograste. Insignia desbloqueada: mensajero del dato.
Si ves ese mensaje, enhorabuena de verdad: has traído tu primer dataset real, lo has cargado sin estropearlo y te has ganado la insignia mensajero del dato, la segunda de la campaña. Si en cambio salta un aviso, no es un fracaso: es el guardián haciendo su trabajo. Comprueba que el fichero se llama de verdad musica.csv y que lo leíste entero. Nadie mira; solo tú y el problema.
La bitácora del analista
Abre la bitácora que empezaste en la Misión 1 —esa celda de texto al final de tu cuaderno— y añade la entrada de hoy. Un mensajero del dato apunta siempre de dónde vino lo que trae. Para la Misión 2, anota al menos:
La fuente: de dónde sale este catálogo (el Spotify Tracks Dataset de maharshipandya, en Hugging Face), y que trabajas con una versión preparada para el libro. Esa línea es tu «pie de procedencia».
El tamaño: cuántas filas y columnas trajiste (\(3\,351 \times 10\)). El tamaño de un dataset es lo primero que se apunta.
Las columnas: los diez nombres, con una palabra de qué es cada uno. Tu chuleta para no perderte luego.
Una sorpresa: algo que no esperabas al mirar el dato real (¿la media de
popularitytan alta? ¿los artistas pegados con punto y coma? ¿la duración en milisegundos?).Una duda: la que sea. Por ejemplo: «¿por qué hay canciones repetidas en varios géneros?». Las dudas anotadas son las que luego se resuelven.
Escribir la procedencia no es burocracia: es la costumbre que, dentro de seis meses, te dejará volver a este análisis y saber exactamente qué tenías entre manos.
Sube de nivel: interroga el fichero
Traer el dato era el encargo; pero ya que lo tienes en el cuaderno, sería una pena no hacerle un par de preguntas más. Esto es un adelanto de la Misión 3, donde aprenderás a interrogar tablas en serio. Por ahora, cuatro preguntas que se responden con lo que ya sabes.
¿Cuántas canciones hay por género?
El catálogo se armó cogiendo las más populares de cada género. value_counts cuenta cuántas cayeron de cada uno, y con .max() y .min() vemos el reparto sin listar los 114:
conteo = musica["track_genre"].value_counts()
print("generos distintos:", conteo.shape[0])
print("canciones por genero (max):", conteo.max())
print("canciones por genero (min):", conteo.min())generos distintos: 114
canciones por genero (max): 30
canciones por genero (min): 24
géneros, con un tope de 30 canciones cada uno y un mínimo de 24. Ahí ves, con números, por qué el total no era un redondo 114\(\times\)30: la mayoría de los géneros llenan sus treinta o se quedan en 29, y algunos traen unas pocas menos.
¿Qué género es el más popular?
La pregunta gorda de la próxima misión, y ya casi la puedes contestar. Se hace agrupando: partes la tabla por género y calculas la popularity media de cada grupo, todo con .groupby(...). Como son 114 géneros, ordenamos de mayor a menor y nos quedamos con los cinco de arriba:
medias = musica.groupby("track_genre")["popularity"].mean().round(1)
print(medias.sort_values(ascending=False).head())track_genre
pop 91.1
latino 89.2
reggaeton 89.2
reggae 89.0
dance 88.3
Name: popularity, dtype: float64
El pop manda, con una popularidad media de 91,1, seguido muy de cerca por el latino, el reggaeton, el reggae y el dance. Todo cuadra con la intuición: son los géneros que más suenan hoy. Eso es interrogar un dato —y la Misión 3 va, entera, de exprimir esta orden hasta darle nombre al género más popular del catálogo—.
¿Cuál es la canción más popular?
La fila con la popularity más alta la encuentra .idxmax(), que da la posición del máximo, y con ella sacamos la fila entera:
mejor = musica.loc[musica["popularity"].idxmax()]
print("popularity:", mejor["popularity"])
print("cancion:", mejor["track_name"])
print("artistas:", mejor["artists"], "| genero:", mejor["track_genre"])popularity: 100
cancion: Unholy (feat. Kim Petras)
artistas: Sam Smith;Kim Petras | genero: dance
La canción más popular del catálogo, con el máximo de 100, es «Unholy», de Sam Smith y Kim Petras. Fíjate en lo que acabas de hacer —encontrar una aguja concreta en un pajar de 3 351 filas con dos líneas de código—.
¿Qué género es el más bailable?
La misma jugada del groupby, pero sobre la danceability. Es la pregunta que abrirá la Misión 6:
baile = musica.groupby("track_genre")["danceability"].mean().round(3)
print(baile.sort_values(ascending=False).head())track_genre
chicago-house 0.792
kids 0.781
latin 0.776
dancehall 0.774
reggaeton 0.772
Name: danceability, dtype: float64
Sorpresa honesta: el más bailable no es el reggaetón, sino el chicago-house (0,792), un estilo de música electrónica pensado para la pista. El reggaetón está arriba (0,772), sí, pero no gana. Guárdate esa intriga —«¿de verdad el reggaetón es el más bailable?»—, porque es justo el pleito que resolverás, con cuidado, en la Misión 6.
Sube otra vez: un género entero (el reggaetón)
Ya que hemos dado con la canción más popular del catálogo —«Unholy»—, asomémonos a un género entero. Elegimos el reggaetón, para tenerlo controlado. Esto se hace con un filtro: una condición entre corchetes que se queda solo con las filas que la cumplen.
regg = musica[musica["track_genre"] == "reggaeton"]
print("canciones del genero:", regg.shape[0])
print(regg[["track_name", "danceability", "popularity"]].head())canciones del genero: 30
track_name danceability popularity
2615 La Bachata 0.835 98
2616 Me Porto Bonito 0.911 97
2617 Tití Me Preguntó 0.650 97
2618 Efecto 0.801 95
2619 Moscow Mule 0.804 94
Treinta canciones —el género completo en el recorte—. Fíjate en el índice de la izquierda: no empieza en 0, sino en 2615. Es que al filtrar, pandas conserva el número original de cada fila en la tabla grande; esas treinta son las que ocupaban las posiciones 2615 a 2644 del catálogo entero (está ordenado por género, así que caen juntas). Un detalle pequeño que te ahorrará confusiones: filtrar no renumera.
Ahora resumamos ese género con las mismas preguntas de siempre, pero sobre el trozo filtrado:
print("popularity media:", round(regg["popularity"].mean(), 1))
print("popularity maxima:", regg["popularity"].max())
mas = regg.loc[regg["danceability"].idxmax()]
print("mas bailable:", mas["track_name"], "->", mas["danceability"])popularity media: 89.2
popularity maxima: 98
mas bailable: Me Porto Bonito -> 0.911
El reggaetón de este recorte tiene una popularidad media altísima (89,2), con un tope de 98, y su canción más bailable es «Me Porto Bonito», con 0,911 de danceability. ¿Y si le vemos la forma? Todavía no sabemos pintar de verdad —eso llega en la Misión 5—, pero puedes recuperar el truco de la Misión 1 (dibujar una barra multiplicando el carácter "#"), ahora sacando los títulos y su danceability de la tabla real con zip:
for n, d in zip(regg["track_name"], regg["danceability"]):
barra = "#" * int(d * 20)
print(n[:22].ljust(22), barra, d)La Bachata ################ 0.835
Me Porto Bonito ################## 0.911
Tití Me Preguntó ############# 0.65
Efecto ################ 0.801
Moscow Mule ################ 0.804
Ojitos Lindos ############ 0.647
Neverita ################# 0.876
PROVENZA ################# 0.87
Tarot ############### 0.795
Caile ############## 0.704
GATÚBELA ############ 0.627
La Corriente ############# 0.663
Party ################ 0.829
LA CANCIÓN ############### 0.754
La Llevo Al Cielo (Ft. ############### 0.795
Feliz Cumpleaños Ferxx ################# 0.87
Lo Siento BB:/ (with B ############ 0.639
Un Coco ################ 0.839
Un Ratito ############### 0.787
Yonaguni ############ 0.644
Andrea ############### 0.797
Dos Mil 16 ################ 0.817
Pepas ############### 0.762
Prohibidox ############ 0.647
Los Cachos ############### 0.774
MAMIII ################ 0.843
Normal ############## 0.712
Otro Atardecer ############### 0.777
X ÚLTIMA VEZ ################ 0.812
Yo No Soy Celoso ################# 0.872
La forma del género salta a la vista: casi todas las barras son largas —el reggaetón es, de suyo, muy bailable—, con «Me Porto Bonito» destacando y alguna más tranquila («GATÚBELA», «Ojitos Lindos») quedándose corta. Los mismos números que en la tabla, pero ahora los ves. zip es el pegamento que recorre dos columnas a la par —título y danceability— entregándote una pareja en cada vuelta. Y ahora un filtro con dos condiciones a la vez: para pedir «este género y muy bailable» se juntan con & y cada una va entre paréntesis. ¿Cuántos reggaetones pasan de 0,8 de danceability?
bailables = musica[(musica["track_genre"] == "reggaeton") &
(musica["danceability"] > 0.8)]
print("reggaeton muy bailable:", bailables.shape[0], "de", regg.shape[0])reggaeton muy bailable: 13 de 30
Trece de treinta reggaetones pasan de 0,8: casi la mitad del género es «muy bailable» según ese umbral que nos hemos inventado. Y lo mejor: el mismo filtro, aplicado a la tabla entera en vez de a un género, cuenta las canciones muy bailables de todo el catálogo sin escribir ni un bucle:
bailables = musica[musica["danceability"] > 0.8]
print("muy bailables:", bailables.shape[0])
print("porcentaje del total:",
round(100 * bailables.shape[0] / musica.shape[0], 1))muy bailables: 339
porcentaje del total: 10.1
De las 3 351 canciones del catálogo, 339 pasan de 0,8 de danceability: un 10,1 % del total. Contar eso a mano, con el bucle de la Misión 1, habría funcionado igual… pero fíjate en que aquí no hay bucle. Le has pedido a pandas «dame las filas donde danceability pasa de 0,8» y lo ha hecho de golpe, sobre las 3 351 a la vez. Esa es la superpotencia de pandas, y la razón de que un analista maneje catálogos enteros sin despeinarse.
El jefe de parte: la ficha del dataset
Hora de enfrentarte al jefe de parte, el miniproyecto que cierra la misión juntando todo lo aprendido. Tu encargo: escribir una función que, dado cualquier DataFrame de música, devuelva su ficha —un diccionario con lo esencial del fichero, listo para enseñar o pegar en la bitácora—. Es lo primero que hace un profesional al recibir un dataset nuevo: fabricar su carné de identidad.
def ficha_dataset(tabla):
return {
"filas": tabla.shape[0],
"columnas": tabla.shape[1],
"generos": tabla["track_genre"].nunique(),
"artistas": tabla["artists"].nunique(),
"popularity_media": round(tabla["popularity"].mean(), 1),
"popularity_max": int(tabla["popularity"].max()),
}
parte = ficha_dataset(musica)
for clave, valor in parte.items():
print(clave, "->", valor)filas -> 3351
columnas -> 10
generos -> 114
artistas -> 1846
popularity_media -> 67.8
popularity_max -> 100
Ahí está tu ficha: una sola función que resume un fichero entero en seis hechos, y que servirá igual para cualquier tabla de música que le pases —la de este recorte, la del catálogo entero, la de otra plataforma—. Has usado todo lo de la misión: shape para el tamaño, nunique para géneros y artistas, mean y max para la popularidad, y el diccionario y el bucle .items() de la Misión 1 para empaquetarlo y mostrarlo. Esto ya no es un ejercicio: es una herramienta de analista.
¿Lo lograste? (nivel jefe)
El jefe también se comprueba. Esta celda verifica de una vez las tres cifras clave de la ficha; si las tres cuadran, has vencido:
parte = ficha_dataset(musica)
assert parte["filas"] == 3351, "Cuenta de filas incorrecta."
assert parte["generos"] == 114, "Deberia haber 114 generos."
assert parte["popularity_max"] == 100, "El maximo deberia ser 100."
print("Ficha validada. Eres mensajero del dato.")Ficha validada. Eres mensajero del dato.
Tres guardianes, tres comprobaciones, un solo mensaje de victoria. Si los tres callan, tu ficha es sólida: no la crees porque «parece bien», la sabes porque la has comprobado. Ese es, en miniatura, el oficio entero —y la razón de que un mensajero del dato pueda mirar a los ojos y decir «esto es lo que hay, y esto es de dónde viene»—.
Guarda la ficha en un fichero
Un mensajero del dato no solo lee ficheros: también los escribe. Igual que read_csv lee, su gemelo to_csv escribe. Guardemos la ficha del jefe en su propio fichero, para tenerla a mano. Primero la metemos en una tabla de una sola fila —un DataFrame a partir de una lista con el diccionario— y luego la volcamos, pidiendo que no guarde el índice (index=False), que aquí no aporta:
resumen = pd.DataFrame([parte])
resumen.to_csv("ficha_musica.csv", index=False)
print(pd.read_csv("ficha_musica.csv")) filas columnas generos artistas popularity_media popularity_max
0 3351 10 114 1846 67.8 100
Acabas de cerrar el círculo de la entrada/salida: has leído un fichero al principio de la misión y ahora has escrito otro con tu conclusión dentro. Ese ficha_musica.csv es un resultado que puedes compartir, adjuntar a un correo o abrir en una hoja de cálculo. La tabla entró, el análisis salió: eso es, de cabo a rabo, el oficio.
Retos: elige tu nivel
Ahora te toca a ti. Los retos suben de dificultad: \(\star\) es imprescindible, \(\star\star\) te reta y \(\star\star\star\) es de jefe. Haz al menos todos los de una estrella; sube hasta donde te apetezca. Y en todos, la regla de oro: predice antes de ejecutar —antes de mirar la respuesta, apuesta cuál será—.
★ Asómate al final. Muestra las tres últimas filas de la tabla con
musica.tail(3). Antes de ejecutar, apuesta: ¿de qué género serán?★ Cuenta a mano el tamaño. Sin mirar
shape, apuesta cuántas filas habría si fueran 30 canciones por cada uno de los 114 géneros. Multiplícalo (\(30 \times 114\)), compara conmusica.shape[0]y explica por qué el fichero tiene menos.★ Una columna a la vista. Muestra las diez primeras popularidades con
musica["popularity"].head(10). ¿Son altas o bajas? ¿Por qué, si es un recorte de las más populares?★ Explícitas o no. Usa
value_countssobre la columnaexplicity di, con tus palabras, cuántas canciones llevan letra explícita y qué proporción es del total.★★ Filtra un género. Quédate solo con las filas del reggaetón (
musica[musica["track_genre"] == "reggaeton"]) y cuenta cuántas son. ¿Coincide con las 30 que esperabas?★★ La
popularitymedia por género. Agrupa por género y saca la media depopularityconmusica.groupby("track_genre")["popularity"].mean(). Ordénala con.sort_values(ascending=False)y mira las cinco primeras: ¿qué género gana? Piensa por qué antes de mirar.★★ La menos popular. Copia el ejemplo de
idxmaxy cámbialo poridxminpara encontrar la canción con menospopularitydel catálogo. ¿De qué género crees que será?★★ Guarda tu Parquet. Escribe la tabla en
musica.parquet, vuelve a leerla y comprueba con unassertque tiene la mismashapeque el CSV original. Acabas de convertir un formato en otro.★★ Caza el mojibake. Coge un artista con tilde —por ejemplo «Beyoncé» o «Beyoncé»—, codifícalo en UTF-8 y decodifícalo con
latin-1. Escribe en la bitácora qué garabato sale y por qué.★★★ El jefe: amplía la ficha. Añade a
ficha_datasetdos claves nuevas:"danceability_media"(la media dedanceability, redondeada a 3) y"popularity_min"(el mínimo depopularity). Vuelve a comprobar conassertque"popularity_min"vale 12.★★★ El jefe: una fila que no cuadra. Fabrica a mano una línea de CSV a la que le falte un campo (nueve valores en vez de diez), pártela con
.split(",")y comprueba con unassertque tiene 10 campos. Verás saltar el guardián: acabas de detectar un dato averiado, que es justo el trabajo de la Misión 4.★★★ El jefe final en miniatura. Escribe una función
procedenciaque devuelva un diccionario con la fuente ("Spotify Tracks Dataset (maharshipandya, Hugging Face)"), la licencia ("BSD / CC0") y una nota de que es una versión preparada para el libro. Imprímela junto a la ficha: acabas de fabricar el pie de procedencia de tu análisis.
¿Te has atascado? En el apéndice C tienes, para cada reto, primero una pista y solo después la solución. Pero el pacto es este: entra ahí después de haberlo intentado de verdad.
Tu caja de herramientas
Mira todo lo que sabes hacer ahora con un fichero de datos y hace unas páginas no sabías. Pégala en la bitácora y vuelve a ella cuando dudes.
Importar pandas:
import pandas as pd.Leer:
pd.read_csv("f.csv"),pd.read_parquet("f.parquet"). Codificación:pd.read_csv("f.csv", encoding="utf-8")(o"utf-8-sig"si vino de Excel).Guardar:
tabla.to_parquet("f.parquet"),tabla.to_csv("f.csv", index=False).Asomarse:
tabla.head(),tabla.tail(),tabla.head(3).Medir:
tabla.shape(filas, columnas),tabla.shape[0],tabla.columns,tabla.dtypes.Elegir: una columna
tabla["popularity"]; variastabla[["track_genre", "popularity"]]; una fila por posicióntabla.iloc[0].Preguntar:
col.unique(),col.nunique(),col.value_counts(),col.describe().Resumir números:
col.mean(),col.min(),col.max(),col.idxmax(),round(x, 1).Filtrar:
tabla[tabla["track_genre"] == "reggaeton"].Comprobar:
assert tabla.shape == (3351, 10).
No memorices esta lista: úsala. La memoria viene sola de tanto teclear —y en la próxima misión la usarás en cada línea—.
Cuando quieras ir en serio
Todo esto lo has hecho con los datos servidos, sin instalar nada, y para aprender está perfecto. Pero hay un mundo «de mayores» esperándote cuando quieras dar el salto. Traer datos de verdad, en un proyecto serio, tiene su disciplina: se descargan una sola vez y se guardan como crudos intocables (nunca se editan, son la evidencia primaria); todo lo demás —limpieza, conversiones— se escribe aparte como resultado derivado, que siempre se puede regenerar. Se apunta la fecha exacta de la descarga, para que el dato sea citable. Se usan formatos columnares como Parquet para lo que pesa, y motores que consultan ficheros enormes sin cargarlos enteros en memoria. Y se validan los datos al entrar: comprobar que las columnas son las que deben, que los rangos tienen sentido, que no faltan géneros.
No necesitas nada de eso para las próximas misiones —el cuaderno con datos servidos te basta—, pero está ahí, y el puente hacia ese mundo es el volumen adulto del que nace este libro. Por cierto, un consejo de la casa que ya puedes aplicar hoy: la primera vez que traigas un CSV, conviértelo a Parquet y trabaja sobre él. Es uno de los hábitos que más aceleran cualquier análisis, y ya sabes hacerlo.
Misión 2 completada. Insignia mensajero del dato, desbloqueada. Has aprendido qué es un fichero y un formato, has traído un catálogo real de música a tu cuaderno, lo has mirado por dentro —sus filas, su tamaño, sus columnas, sus tipos—, has esquivado el mojibake y has aprendido a respetar la procedencia de un dato. La música ya está en casa. En la Misión 3 dejaremos de solo mirarla: vamos a interrogarla en serio y a arrancarle su primera respuesta con nombre y cifra —«¿cuál es el género más popular del catálogo?»—.