lección 2
Día 1 — Primer contacto con los datos
Seis ficheros de cinco fuentes distintas en una carpeta compartida. Cárgalos con Pandas, mira qué hay antes de calcular nada (forma, tipos, vacíos, grafías) y entiende el modelo: la reserva en el centro, con su hotel, su tipo de habitación, su canal y su cliente colgando, sus noches que dicen cuánto se vende y su comisión y su coste que dicen cuánto se queda. Hoy no limpias: hoy te enteras de a qué te enfrentas, y confirmas los tres vacíos con significado y los duplicados de los que te avisó Dani.
⏱ 55 min
### Lunes, 11:00. Seis ficheros y ninguna instrucción
Ayer entendiste el negocio: un hotel vive del beneficio por habitación disponible, no de lo lleno que está. Hoy tienes en la carpeta compartida seis exportaciones que Bruno acaba de dejar caer con un «te adelanto el titular: Costa es el número uno 🙌», y una regla que ya conoces de tus siete encargos anteriores y que Íñigo no va a repetirte: antes de calcular nada, mira qué hay. No hay ejercicio más rentable en todo el análisis que el primer vistazo, porque cada minuto que dedicas a entender la forma de los datos te ahorra una hora de arreglar un número que salió mal por un tipo equivocado o un vacío mal interpretado.
La exploración se hace en Pandas, la librería de Python para trabajar con tablas de datos. Pandas lee un CSV crudo (el fichero de texto separado por comas que exporta cualquier sistema) y lo convierte en un DataFrame, que es una tabla en memoria con la que puedes preguntar cuántas filas tiene, de qué tipo es cada columna, cuántos vacíos hay y qué valores distintos aparecen. Es exactamente lo que un buen analista hace primero, y lo que Bruno nunca hace: él abre la hoja de cálculo, ve que Costa está lleno y cierra. Tú vas a abrir los seis ficheros y a hacerles las preguntas que nadie les ha hecho.
El modelo es sencillo de leer una vez lo ves dibujado. La reserva es el centro: cada fila es una estancia con su número de noches (lo que se vende), su tarifa por noche (lo que se cobra) y su canal (por dónde entró, que decide la comisión). De ella cuelgan cinco tablas de contexto: hoteles (con el tipo, las habitaciones y la categoría; las noches disponibles al año no vienen en la exportación, son un dato de negocio que da el hotel y que añadirás tú), tipos_habitacion (la clase de habitación y su tarifa base), canales (con la comisión de cada uno), clientes (con su país y si es socio del programa de fidelización) y costes_operativos (el coste fijo mensual y el coste variable por noche de cada hotel). Toda la semana consiste en juntar esas piezas de la forma correcta.
Consejo de senior: los ficheros pequeños son tan importantes como los grandes. canales tiene seis filas, pero de esas seis filas sale la comisión que hunde a Costa. costes_operativos tiene sesenta, pero de ahí sale lo que cuesta operar cada habitación. No mires solo la tabla gorda de reservas: las tablas de contexto son las que convierten un ingreso en un beneficio.
### La forma de cada fichero: cuántas filas y columnas hay
El primer vistazo es el más tonto y el más útil: cuántas filas y columnas trae cada fichero. El atributo .shape de un DataFrame devuelve una tupla (filas, columnas). Verás que reservas y clientes son los pesos pesados, y que las tablas de contexto son diminutas. Y verás algo más, escondido: reservas trae 107.417 filas, pero muchas están repetidas por los reintentos del webhook de Stripe y por la doble alta del PMS y el channel manager, así que el número de reservas de verdad es menor. Eso lo confirmarás en un momento.
1import pandas as pd23for f in ['hoteles', 'tipos_habitacion', 'canales', 'costes_operativos', 'clientes', 'reservas']:4 df = pd.read_csv(f'datos/{f}.csv')5 print(f"{f}: {df.shape[0]} filas, {df.shape[1]} columnas")
El primer vistazo: la forma de cada fichero. .shape[0] son las filas y .shape[1] las columnas
Después de la forma viene el tipo de cada columna, y ahí empiezan los problemas. Cuando Pandas lee un CSV, adivina el tipo de cada columna: si ve números limpios, la marca como numérica (int64 para enteros, float64 para decimales); si ve cualquier cosa rara —un «€», una coma, un texto—, la marca como texto (object). Que una columna que debería ser un número salga como object es la señal de que trae suciedad. Y aquí la tarifa, las fechas y el descuento salen como texto, lo que te dice, sin abrir una sola celda, que la tarifa viene con símbolo de euro, que las fechas vienen en formato de texto y que el descuento viene con el signo de porcentaje.
No te fíes del nombre de una columna para saber su tipo. Una columna llamada tarifa_noche que suena a número puede salir como object porque las celdas traen «90,00 €». Si intentaras sumarla directamente, Pandas te concatenaría los textos o te daría un error. El tipo real lo dice .dtype, y es lo primero que hay que mirar en cualquier columna con la que vayas a calcular.
### Los duplicados y los tres vacíos con significado
Dani te avisó de lo más importante, y hoy lo vas a confirmar con tus propios ojos, que es la única forma de creértelo. Primero, los duplicados: el channel manager y el PMS a veces meten la misma reserva dos veces, y el webhook de Stripe repite cobros, así que hay filas con el mismo reserva_id repetido. Si sumaras noches o ingresos sin deduplicar, inflarías todo. El método .duplicated() sobre la columna reserva_id marca las filas repetidas, y .drop_duplicates('reserva_id') se queda con una sola por reserva. La regla es sagrada: deduplicar por reserva_id antes de contar o sumar nada.
Y luego los tres vacíos con significado, que son el corazón de la limpieza de mañana y que hoy solo cuentas para saber cuántos son. El canal vacío marca las reservas directas (web propia, teléfono, cliente que llega a la puerta): no pagan comisión y son las que más margen dejan. La fecha de salida vacía marca las estancias en curso (el cliente aún no ha hecho el check-out). Y la fecha de cancelación vacía marca las reservas que no se cancelaron, las estancias buenas. Ninguno de los tres es un dato ausente: los tres significan algo. El único vacío que de verdad falta es el país de origen de algunos clientes, que sí es un dato desconocido. Contar cada uno hoy te da la escala del trabajo de mañana.
Fíjate en el número de reservas no canceladas: 92.791 de 105.917. La diferencia, unas 13.000, son cancelaciones registradas, y todavía faltan las que están en estado cancelada o no-show sin fecha. Esas no generaron noche ni ingreso, y hay que sacarlas del análisis cuando calcules ocupación e ingresos. Pero ojo: sacar las canceladas no es lo mismo que tirar las que tienen la fecha de cancelación vacía. Es justo al revés: las vacías son las buenas. Este es el tipo de detalle que, mal entendido, te haría borrar 92.791 estancias válidas y quedarte analizando las 13.000 canceladas. Por eso hoy solo cuentas, y mañana limpias con la cabeza fría.
### Las grafías: el mismo canal escrito de mil maneras
El último problema del día es el de las grafías (las distintas formas de escribir lo mismo). El canal es la dimensión de la que va a depender toda la comisión, y viene escrito como le sonó a cada sistema: «booking», «Booking.com», «BKG» y «ota» son el mismo canal; «turoperador», «mayorista», «grupo» y «TTOO» son otro; «corporate», «Corporate», «empresa» y «corp» son otro. El método .value_counts() cuenta cuántas veces aparece cada valor de una columna, y .nunique() cuenta cuántos valores distintos hay. Al aplicarlos al canal verás más de veinte grafías para lo que en realidad son seis canales. Normalizarlas —reducir esas veinte formas a seis— será una de las tareas de mañana, y es imprescindible: si no lo haces, al calcular la comisión por canal cada grafía contaría como un canal distinto y el análisis no cuadraría.
Consejo de senior: value_counts() con el parámetro dropna=False cuenta también los vacíos, no solo los valores escritos. Es un truco pequeño y valiosísimo, porque te enseña de un vistazo cuánto pesa el vacío frente a las grafías escritas. En el canal, el vacío (las directas) es la categoría más numerosa de todas, y eso ya te está gritando que las reservas directas son demasiadas para tirarlas.
Con esto cierras el primer día: sabes cuántas filas trae cada fichero, qué columnas están sucias, cuántos duplicados hay, cuánto pesa cada uno de los tres vacíos con significado y cuántas grafías tiene el canal. No has calculado ni una ocupación, y está bien: hoy tocaba entender el terreno. Mañana limpias, y con los datos limpios podrás empezar a subir la escalera de métricas. Antes de eso, un último ejercicio de criterio para asegurarte de que el modelo y los vacíos te han quedado claros.
Regístrate para guardar tu progreso.