Saltar al contenido

lección 2

Día 1 (tarde) — Primer contacto con los datos

Carga los siete ficheros con Pandas y míralos antes de calcular nada. Aprende el modelo (el socio en el centro, la renovación al final), descubre por qué las exportaciones vienen sucias, y localiza las tres trampas que Álex avisó: el mes en inglés, los check-ins que el lector duplica, y la fecha de baja vacía que no es un hueco sino un socio de alta.

50 min

### Lo primero es mirar, no calcular

En tus encargos anteriores ya viviste este momento: el de abrir un fichero por primera vez y hacerle un `.info()` para ver qué te ha llegado de verdad, no lo que te prometieron. Rubén te ha dejado en la carpeta compartida siete exportaciones de los últimos dieciocho meses —socios, suscripciones, check-ins, clases, reservas, pagos y las sedes— con un mensaje que huele a titular ya escrito: «el anual es oro puro, LTV un 60% por encima, confírmame el LTV por sede y lanzo la campaña». La tentación de ir directo a calcular ese LTV es fuerte. No lo hagas. Antes de calcular una sola cifra, hay que saber qué hay: cuántas filas tiene cada tabla, qué tipo tiene cada columna, dónde hay huecos y dónde hay duplicados. Un número calculado sobre datos que no has mirado es un número en el que no puedes confiar, y esta semana hay un presupuesto encima de la mesa.

La razón de fondo la aprendiste ya, pero conviene repetirla porque cambia el orden de trabajo: explorar antes de calcular no es perder el tiempo, es lo que evita que calcules mal. Si sumas los pagos sin darte cuenta de que vienen como texto con el símbolo del euro pegado, el programa o falla o —peor— hace algo raro en silencio. Si cuentas check-ins sin ver que el lector duplica entradas, inflas todas tus cifras de asistencia. Si tratas la fecha de baja vacía como un dato que falta y la rellenas, borras la frontera entre los socios activos y los que se fueron. Cada una de esas trampas es invisible si vas directo al cálculo, y cada una tuerce el resultado. Hoy no calculas casi nada: hoy levantas el capó y anotas lo que hay que arreglar mañana.

### Por qué siete ficheros y no uno solo

Una pregunta razonable al ver siete ficheros es: ¿por qué no está todo junto en una sola tabla enorme con una fila por socio y todo lo suyo al lado? La respuesta explica cómo se guardan los datos en casi cualquier sistema, y conviene entenderla. Si metieras todo en una tabla, tendrías que repetir los datos del socio —su nombre, su sede, su alta— en cada una de sus visitas, en cada uno de sus pagos: para un socio con 200 check-ins, sus datos aparecerían 200 veces. Eso es un derroche y, peor, una fuente de errores: si el socio cambia de sede y solo actualizas algunas de esas 200 copias, tendrás el mismo socio en dos sedes a la vez. Para evitarlo, los datos se reparten en tablas: una para lo que hay uno por socio (la tabla de socios), otra para lo que hay muchos por socio (los check-ins), y se enlazan por el identificador. A esa forma de organizar se le llama normalización, y su regla es «cada cosa se guarda una sola vez, en un solo sitio».

Para ti, como analista, esto tiene una consecuencia directa: los datos que necesitas para una pregunta casi nunca están en una sola tabla, y por eso cruzar es tu pan de cada día. La pregunta «¿cuánto vale un socio anual?» necesita el plan (que está en suscripciones) y los pagos (que están en pagos), enlazados por el socio. La pregunta «¿renueva más quien viene más?» necesita las suscripciones y los check-ins. El sistema guarda los datos separados para no repetirlos; tú los vuelves a juntar, con JOINs, para responder preguntas. Entender que las tablas están separadas a propósito —y que tu trabajo es recomponerlas según la pregunta— te quita el susto de los siete ficheros: no son un caos, son piezas de un mismo puzle diseñado para no repetir información, y las claves son los enganches por donde encajan.

Consejo de senior: antes de cruzar dos tablas, ten claro cuántas filas esperas que salga. Si cruzas socios (9.000 filas, una por socio) con sus suscripciones (11.000, varias por socio), el resultado tendrá unas 11.000 filas, no 9.000, porque cada socio se repite por cada suscripción suya. Si no tienes esa expectativa antes de cruzar, no notarás cuando un cruce mal hecho multiplique las filas —el temido cruce que duplica todo porque la clave no era única—. Predecir el tamaño del resultado antes de ejecutar es una red de seguridad barata contra los cruces que se desmadran.

### El modelo: el socio en el centro, la renovación al final

Siete ficheros no son siete islas: están conectados, y entender cómo se conectan es la mitad del trabajo. En el centro de todo está el socio. De él cuelgan sus suscripciones (el plan que contrató, cuándo empieza y cuándo acaba), sus check-ins (cada vez que cruzó el torniquete), sus reservas de clases (qué clase reservó y si asistió) y sus pagos (cada cuota o prepago que Stripe le cobró). Las clases y las sedes son tablas de contexto: la clase la reserva el socio, y la sede es el gimnasio donde ocurre todo. La pieza que hay que grabarse es esta: la renovación no es una columna que puedas leer. No existe un campo que diga «este socio renovó». Hay que deducirla mirando si, cuando una suscripción del socio venció, arrancó otra suscripción suya poco después. Ese es el corazón del análisis de la semana, y por eso el modelo gira en torno a encadenar las suscripciones de cada socio en el tiempo.

El socio es el centro del modelo. La renovación no se lee: se deriva encadenando sus suscripciones en el tiempo

Las claves que unen las tablas son sencillas de nombrar. Cada suscripción, cada check-in, cada reserva y cada pago llevan el identificador del socio (`socio_id`), que apunta a la tabla de socios. Cada reserva lleva además el identificador de la clase (`clase_id`). Cada pago lleva el identificador de la suscripción que paga (`suscripcion_id`). Y el socio lleva el identificador de su sede (`sede_id`), que apunta a la tabla de sedes. Hay un cruce que es más delicado, y conviene fijarlo desde hoy: la sede aparece de dos formas distintas en los datos. En la tabla de socios es un número (`sede_id`) que cruza limpiamente con las sedes. Pero en los check-ins y en las clases la sede viene escrita como texto —«Madrid», «MAD», «madrid»—, y solo cuadrará con la tabla de sedes cuando normalices ese texto a la ciudad canónica. Si no lo haces, el análisis por sede se partirá en silencio, sin dar error, que es la peor forma de fallar.

### Por qué las exportaciones vienen sucias (y por qué en inglés)

Cuando hagas el `.info()` de los ficheros te vas a encontrar un desastre ordenado: casi ninguna columna tiene el tipo que debería. Las fechas son texto y vienen en tres formatos distintos, los importes son texto con el símbolo del euro y una coma decimal, el plan está escrito de mil maneras («anual», «Anual», «ANUAL», «año»), y la sede igual. Esto no es mala suerte: es lo normal cuando los datos se juntan de varias fuentes que nunca se pusieron de acuerdo. El torniquete escribe la fecha de una forma, la app de otra, y la exportación de BigQuery de una tercera. Nadie normalizó nada en origen, así que te llega el crudo. Limpiar esto no es un castigo: es el trabajo, y es donde un analista demuestra su oficio, porque una cifra calculada sobre datos sucios miente con toda la seguridad de un número bien formateado.

Hay un detalle de las fechas que merece un aviso propio, porque sorprende a todo el mundo la primera vez. Una de las tres formas en que viene la fecha trae el mes en inglés abreviado: «12 Mar 2024». No «12 Mar» de marzo en español —que en español sería «mar» también, sí, pero abreviaturas como «Ene», «Abr», «Ago», «Dic» no las entiende la herramienta—, sino el mes en inglés («Jan», «Feb», «Mar», «Apr»...). ¿Por qué una empresa española exporta fechas con el mes en inglés? Porque las herramientas de datos —la base de datos, la librería que parsea fechas— hablan inglés por dentro. Cuando el sistema formatea una fecha a texto con el nombre del mes, usa el idioma de la máquina, que casi siempre es el inglés, no el del país donde está la empresa. Es una de esas cosas que chocan al principio y que luego das por sentadas: los datos hablan inglés aunque la empresa hable español. Lo importante para ti es que, al parsear, hay que decirle a la herramienta que acepte varios formatos, porque el mes en inglés parsea y el mes en español no lo haría.

Las dos trampas que Álex repite y que nadie escucha. Primera: el lector del torniquete a veces registra la misma entrada dos veces en segundos, así que hay que deduplicar los check-ins antes de contarlos —y ojo, el duplicado llega con un identificador NUEVO, así que no se detecta comparando el id, sino mirando el mismo socio a la misma hora—. Segunda: la fecha de baja vacía NO es un nulo que falte, es que el socio sigue de alta. Si la rellenas o borras esas filas, pierdes justo a los socios activos. Las dos trampas parecen detalles y son la diferencia entre un análisis que aguanta y uno que se cae.

Sobre los duplicados hay un matiz que conviene entender bien, porque cambia cómo los cazas. En los pagos y en las reservas, el duplicado llega con el mismo identificador: Stripe reintenta un cobro y registra el mismo `pago_id` dos veces, así que para limpiarlo basta con quedarte con un `pago_id` de cada. Pero en los check-ins es distinto: el lector del torniquete, cuando reintenta, genera un `checkin_id` nuevo. Si buscas duplicados comparando `checkin_id`, no encontrarás ninguno —todos son únicos—, y creerás que la tabla está limpia. La forma correcta de deduplicar los check-ins es por socio y momento: si el mismo socio tiene dos entradas en el mismo minuto, es la misma entrada leída dos veces. Esta diferencia entre «duplicado con el mismo id» y «duplicado con id nuevo» es sutil y decide si tus cifras de asistencia son de fiar. Hoy solo lo compruebas; mañana lo limpias.

Vas a explorar los siete ficheros con las herramientas de siempre: `.shape` para el tamaño, `.dtype` para el tipo de cada columna, `.nunique()` y `.value_counts()` para ver cuántas grafías distintas tiene una categoría, `.isnull().sum()` para contar huecos. No hace falta que memorices nada nuevo. Lo que sí cambia respecto a tus encargos anteriores es que aquí decides tú qué mirar y en qué orden: nadie te va a decir «ahora cuenta los duplicados de esta tabla». Tú sabes que hay que hacerlo, y lo haces. Vamos a ello.

### Números, texto y fechas: por qué Pandas distingue los tipos

Cuando Pandas carga un fichero, a cada columna le asigna un tipo, y ese tipo decide qué puedes hacer con ella. Un número (los tipos int para enteros y float para decimales) se puede sumar, promediar, comparar por tamaño. Un texto (el tipo object) se puede agrupar y contar, pero no sumar: no tiene sentido «sumar» las palabras «Madrid» y «Barcelona». Una fecha (el tipo datetime) se puede restar de otra para saber cuántos días pasan entre las dos, ordenar cronológicamente, o filtrar por rango. El tipo no es una etiqueta decorativa: es lo que habilita o impide cada operación. Por eso, cuando una columna que debería ser número llega como texto, no es solo feo, es que literalmente no puedes calcular con ella hasta arreglarlo. La firma de la suciedad —el precio como object en vez de float— es también un candado: mientras el precio sea texto, sumar el LTV es imposible.

Entender esto te da un método para leer un `.dtypes` como una lista de tareas. Recorres los tipos y, para cada columna, te preguntas: ¿es el tipo que debería tener para lo que quiero hacer con ella? El identificador del socio puede ser número o texto, da igual, porque solo lo vas a usar para cruzar, no para sumar. Pero el precio TIENE que ser número, porque lo vas a promediar; la fecha de alta TIENE que ser fecha, porque vas a restar días para calcular la asistencia de las primeras semanas. Cada columna cuyo tipo no case con su uso previsto es una conversión pendiente. Y hay un detalle revelador: una columna de enteros que trae nulos aparece como float (decimal), porque el hueco obliga a Pandas a cambiar de tipo; ver un float donde esperabas enteros es, muchas veces, la pista de que hay valores que faltan.

El tipo de una columna decide qué puedes hacer con ella: sumar un número, agrupar un texto, restar fechas. Un tipo equivocado es un candado sobre el cálculo

### Contar no es mirar: para qué sirve head()

Hay dos formas de conocer un fichero, y las dos hacen falta. Una es contar: cuántas filas, cuántos valores distintos, cuántos nulos. Da la foto agregada, el tamaño del problema. La otra es mirar: leer unas cuantas filas de verdad, con sus valores concretos, para ver qué pinta tienen los datos. Eso lo da `.head()`, que muestra las primeras filas de la tabla. Contar te dice que la columna de fechas tiene tres formatos; mirar te enseña cuáles son —«2024-03-12», «12/03/2024», «12 Mar 2024»— y solo entonces entiendes qué tienes que arreglar. Un recuento sin haber mirado nunca las filas es como estudiar un país por sus estadísticas sin haberlo pisado: sabes los números pero no reconoces la realidad. El primer gesto ante una tabla nueva debería ser siempre un `.head()`, para poner cara a los datos antes de contarlos.

Mirar las filas de verdad es también como se cazan las sorpresas que ningún recuento revela. Un `.value_counts()` te dice que la sede tiene 25 grafías, pero solo al mirarlas ves que hay «MAD», «Madrid» y «madrid» y entiendes que son la misma con distinta forma. Un recuento de nulos te dice que hay muchos vacíos en la fecha de baja, pero solo al mirar unas filas —viendo que esos socios sin fecha de baja son precisamente los que tienen suscripción activa— entiendes que el vacío significa «de alta». El ojo humano reconoce patrones que ningún estadístico resume: por eso el análisis serio alterna siempre entre contar (para el tamaño) y mirar (para el sentido). Quedarse solo en los recuentos es fiarse de un resumen sin haber leído nunca el texto original.

Consejo de senior: cuando una columna te dé un recuento raro, mira las filas concretas que lo producen antes de sacar conclusiones. ¿Muchos nulos en una columna? Filtra esas filas y míralas: entenderás si es un error o un significado. ¿Un valor que aparece muchísimas veces? Mira algún caso. El salto de «el número es raro» a «ya sé por qué» casi siempre pasa por leer las filas de carne y hueso que hay detrás del agregado. Contar te da la alarma; mirar te da la causa. Un analista que solo cuenta vive de sospechas; uno que además mira, de certezas.

### El analista construye el diccionario que la empresa no tiene

Recuerda una de las costuras del lunes: VueltaFit no tiene diccionario de datos. Nadie ha dejado escrito qué significa cada campo, qué valores puede tomar, ni qué quiere decir un vacío. La consecuencia la vas a sufrir hoy mismo, y la oportunidad también. La sufres porque cada columna es un pequeño misterio: ¿la fecha de baja vacía es un error o un socio de alta? ¿El estado «vencida» de una suscripción es lo mismo que «baja»? Sin diccionario, cada duda es una interrupción para preguntar por Slack. Pero también es una oportunidad, porque lo que descubras hoy —lo que significa de verdad cada campo— puedes dejarlo escrito, y así conviertes tu exploración en el diccionario que la empresa nunca hizo. Un analista que documenta lo que aprende no solo resuelve su encargo: deja el terreno mejor que como lo encontró, y eso se nota y se recuerda.

Documentar no es escribir un tratado; es apuntar, al lado de cada tabla, lo que has averiguado. «checkins: 236.097 filas; el lector duplica entradas con id nuevo, hay que deduplicar por socio y minuto». «suscripciones.fecha_baja: vacía = sigue de alta, NO es un dato que falte». «tipo_plan: ocho grafías, se mapean a mensual/anual». Esas notas, que te cuestan un minuto, valen oro: para tu yo del jueves, que ya no se acordará de por qué los check-ins salían raros; para el próximo analista que herede estos ficheros; y para ti mismo cuando en la demo del viernes alguien pregunte «¿y cómo sabes que ese socio sigue de alta?». La memoria es traicionera y los proyectos duran; lo que no se escribe, se pierde y se vuelve a descubrir con esfuerzo. Escribir mientras exploras es de las costumbres más rentables del oficio.

Hay una idea de fondo que conviene interiorizar: los datos sin contexto no significan nada, y el contexto casi nunca viene en el fichero. Una columna que pone «estado: activa» no te dice si «activa» quiere decir «paga» o «viene», y esa diferencia —lo viste el lunes— es el corazón del caso. El número 468 en la columna de precio no significa nada hasta que sabes que es el prepago anual en euros. El significado vive fuera del dato, en la cabeza de quien montó el sistema, y tu trabajo es recuperarlo, escribirlo y no volver a perderlo. Por eso explorar no es solo mirar cifras: es reconstruir el sentido de cada columna, preguntando a Álex lo que no puedas deducir y anotando lo que averiguas. Un dato es una respuesta; sin la pregunta que responde, es solo un número suelto.

Explorar es reconstruir el significado que no viene en el fichero: cada duda resuelta se anota, y al final tienes el diccionario que la empresa nunca escribió

### Las cuatro preguntas del primer vistazo

Explorar un fichero por primera vez no es mirar a ver qué sale: es hacerle cuatro preguntas concretas, siempre las mismas, en orden. La primera: ¿cuántas filas y columnas tiene? Eso lo da `.shape`, y te dice el tamaño del problema y la proporción entre tablas. La segunda: ¿qué tipo tiene cada columna? Eso lo da `.dtypes`, y te delata la suciedad, porque una columna que debería ser número o fecha y sale como texto esconde algo pegado. La tercera: ¿cuántos valores distintos tiene cada categoría? Eso lo dan `.nunique()` y `.value_counts()`, y te dicen si una columna que debería tener dos o seis valores tiene veinticinco grafías. Y la cuarta: ¿dónde hay huecos? Eso lo da `.isnull().sum()`, y te obliga a decidir, hueco por hueco, si es un dato que falta o un dato que significa algo. Estas cuatro preguntas son el examen médico de rutina de cualquier tabla, y hacerlas antes de calcular es lo que separa un análisis serio de uno que da por buena la primera cifra que sale.

Lo que hace valioso este examen no es cada respuesta por separado, sino leerlas juntas con ojo de detective. Una columna que sale como texto (object) y además tiene muchos valores distintos casi siempre es una categoría sucia que hay que normalizar. Una columna numérica (int o float) con nulos aparece como decimal aunque sean enteros, porque el hueco obliga a Pandas a cambiar el tipo. Una tabla con muchas más filas que identificadores únicos tiene duplicados. Cada combinación de respuestas cuenta una historia de suciedad concreta, y con la práctica las reconoces de un vistazo, como un mecánico que oye el motor y ya sabe qué pieza falla. Hoy estás haciendo justo eso: escuchar el motor de las siete tablas antes de meterte a arreglarlas mañana.

Las cuatro preguntas del examen de rutina: tamaño, tipos, grafías y huecos. Leídas juntas, cada combinación delata un tipo de suciedad concreto

### El volumen manda: por qué 236.000 filas cambian cómo miras

Fíjate en la asimetría brutal de las siete tablas: 9.000 socios, unas 11.000 suscripciones, 5.000 clases, 8 sedes... y 236.000 check-ins y 100.000 reservas. Los datos de comportamiento —venir, reservar— son uno o dos órdenes de magnitud más grandes que los datos de estado —quién es socio, qué plan tiene—. Esto no es casualidad, es la naturaleza del negocio: te das de alta una vez, pero cruzas el torniquete cientos de veces. Y tiene una consecuencia práctica que conviene interiorizar: donde hay muchas filas hay comportamiento que medir, y donde hay pocas hay estado que describir. La renovación (pocas suscripciones) es un estado; la asistencia (muchísimos check-ins) es comportamiento. El hallazgo de la semana saldrá de cruzar el estado con el comportamiento, y por eso necesitas las dos escalas de tabla.

El volumen también cambia cómo trabajas. Con 8 sedes puedes mirarlas una a una a ojo; con 236.000 check-ins, ni de broma: tienes que agregar, contar, agrupar, porque ningún ser humano lee un cuarto de millón de filas. Esto, que parece obvio, es justo donde la hoja de cálculo se queda corta y entra una herramienta como Pandas o SQL. Una hoja de cálculo es estupenda para las 8 sedes o para revisar a mano un puñado de filas raras; pero abrir 236.000 check-ins en una hoja es pedirle que se cuelgue, y aunque abriera, no podrías cruzarla con las suscripciones por socio sin sufrir. No es que la hoja sea mala: es que cada herramienta tiene su escala, y a partir de decenas de miles de filas que hay que cruzar con otras tablas, el trabajo pide Pandas o SQL. Saber cuándo cambiar de herramienta es parte del oficio.

### Los identificadores son el pegamento (y a veces fallan)

Las siete tablas se mantienen unidas por sus identificadores: el `socio_id` que aparece en suscripciones, check-ins, reservas y pagos y que apunta a la tabla de socios; el `clase_id` que une reservas con clases; el `suscripcion_id` que une pagos con suscripciones. Cuando cruzas dos tablas por su identificador, confías en que cada identificador de una exista en la otra: que no haya un pago de un socio que no está en la tabla de socios, ni una reserva de una clase inexistente. A esa coherencia se le llama integridad referencial, y cuando falla —cuando hay filas «huérfanas» que apuntan a algo que no existe— tus cruces pierden datos en silencio. Por eso, antes de cruzar en serio, conviene comprobar que los identificadores cuadran: que todos los socios de las suscripciones existen entre los socios, y así con el resto.

En VueltaFit los identificadores cuadran bien —comprobarás que no hay socios huérfanos—, pero el cruce delicado es otro, y ya lo avisamos: la sede. En la tabla de socios la sede es un número que cruza limpio; en los check-ins y las clases la sede viene como texto sucio, y solo cruza con la tabla de sedes por el nombre de la ciudad una vez normalizado. Este es un fallo de cruce que no da error: si intentas juntar los check-ins con las sedes por «Madrid» pero los check-ins dicen «MAD», simplemente no encuentra pareja y esas filas desaparecen del resultado sin una sola advertencia. Los cruces que fallan callando son los más peligrosos, porque el número que sale parece correcto y está incompleto. Comprobar que un cruce encuentra lo que debería —contando las filas antes y después— es una costumbre que ahorra disgustos.

Un cruce (JOIN) que no encuentra pareja no avisa: descarta la fila y sigue. Si cruzas 236.000 check-ins con las sedes y la sede viene como «MAD» mientras la tabla de sedes dice «Madrid», el resultado saldrá con menos filas de las que esperabas y ni un mensaje de error. Por eso, tras un cruce importante, compara el número de filas de antes y de después: si has perdido filas que no esperabas perder, tienes un problema de claves que no casan. Un JOIN silencioso que se come la mitad de tus datos es una de las formas más caras de equivocarse, porque el número resultante parece perfectamente válido.

Consejo de senior: cuando cargues varias tablas, mira sus tamaños juntos antes que por separado. La forma de las tablas cuenta la historia del negocio de un vistazo: 9.000 socios, unas 11.000 suscripciones (más que socios, porque algunos han encadenado varias) y 236.000 check-ins (muchísimos, porque venir al gimnasio es un acto que se repite a diario). Esa proporción —pocas suscripciones, muchísimos check-ins— ya te dice dónde está el comportamiento que puede predecir la renovación: en la asistencia, que se repite, no en la suscripción, que es un puñado de filas por socio.

Consejo de senior: cuando una columna que debería ser un número te sale como texto (object), no es un capricho de la herramienta: es la firma de la suciedad. El símbolo del euro, la coma decimal o un espacio de más pegados al número le impiden a Pandas convertirlo, y lo deja como texto para no perder información. Cada columna «object» donde esperabas un número es una tarea de limpieza que apuntas hoy y resuelves mañana. Leer los tipos con ojo de detective te da la lista de la compra de la limpieza antes de tocar nada.

Regístrate para guardar tu progreso.