Saltar al contenido

# introduccion-a-la-ciencia-de-datos-con-python

Introducción a la Ciencia de Datos con Python

Volver a la portada del curso
Lectura (Markdown)

Realización del proyecto paso a paso

Sección 5 · Proyecto Final de Ciencia de Datos

Realización del proyecto paso a paso

El proyecto final repite el ciclo del curso con otro dataset real: 244 propinas que un mesero registró durante unos meses en un restaurante de EE. UU. La pregunta es ¿qué explica el monto de la propina? Esta guía divide el trabajo en seis pasos, cada uno con un punto de control: un número que debes obtener antes de seguir. El notebook plantilla-proyecto trae la estructura con celdas TODO para que las completes en orden.

Paso 1. Cargar y revisar

import plotly.express as px

tips = px.data.tips()
print(tips.shape)                  # (244, 7)
print(tips.isna().sum().sum())     # 0
print(tips.duplicated().sum())     # 1
print(tips[tips.duplicated(keep=False)])

Control: 244 filas, 7 columnas, 0 nulos y 1 duplicado. Las dos filas iguales son dos mesas de 2 personas, un jueves a mediodía, con cuenta de 13.00 y propina de 2.00. Es perfectamente posible que sean dos mesas distintas: no hay un identificador de ticket que pruebe lo contrario. No borres a ciegas; decide y escribe por qué.

Paso 2. Crear la propina en porcentaje

Una propina de 5 dólares es generosa en una cuenta de 20 y escasa en una de 50. El porcentaje permite comparar mesas de distinto tamaño:

tips["pct"] = tips["tip"] / tips["total_bill"] * 100
print(tips[["tip", "pct"]].agg(["mean", "median", "max"]).round(2))

Control: propina media 3.00 dólares; porcentaje medio 16.08 y mediana 15.48. El máximo es 71.03 %: una cuenta de 7.25 con propina de 5.15. Es un valor atípico real, no un error de captura; menciónalo y decide si lo dejas.

Paso 3. Explorar por grupo

por_dia = tips.groupby("day").agg(n=("tip", "size"), propina=("tip", "mean"),
                                  pct=("pct", "mean"))
print(por_dia.reindex(["Thur", "Fri", "Sat", "Sun"]).round(2))
print(tips.groupby("size")["pct"].mean().round(2))

Control: el domingo deja la propina media más alta (3.26) y el viernes tiene solo 19 mesas. En porcentaje las diferencias son pequeñas: de 15.32 (sábado) a 16.99 (viernes). Por tamaño de mesa, el porcentaje baja de 21.73 con una persona a 14.15 con cinco, y vuelve a 15.62 con seis. Fíjate en la columna n: hay 4 mesas de una persona y 4 de seis; un promedio de 4 mesas no sostiene una conclusión. Repite el análisis con time, sex y smoker.

Paso 4. Gráficas clave

Dos gráficas bastan para la historia principal: la dispersión propina contra cuenta y el porcentaje por tamaño de mesa.

import matplotlib.pyplot as plt
import seaborn as sns

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4.5))
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time", ax=ax1)
sns.boxplot(data=tips, x="size", y="pct", ax=ax2)
ax2.set_ylabel("Propina (% de la cuenta)")

Control: la correlación propina–cuenta es 0.676 y propina–tamaño, 0.489. Pon título y unidades a cada eje antes de seguir.

Paso 5. Modelos: base, simple y completo

Predice tip con train_test_split(test_size=0.25, random_state=42) y compara cuatro modelos en el mismo conjunto de prueba:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, r2_score

X = pd.get_dummies(tips[["total_bill", "size", "sex", "smoker", "day", "time"]],
                   drop_first=True, dtype=float)
X_tr, X_te, y_tr, y_te = train_test_split(X, tips["tip"], test_size=0.25, random_state=42)
for cols in (["total_bill"], ["total_bill", "size"], list(X.columns)):
    pred = LinearRegression().fit(X_tr[cols], y_tr).predict(X_te[cols])
    print(len(cols), round(r2_score(y_te, pred), 3), round(mean_absolute_error(y_te, pred), 3))

Agrega tú la línea base con DummyRegressor. Control: base MAE 1.038; solo cuenta, $R^2$ 0.44 y MAE 0.652; cuenta y tamaño, 0.418 y 0.673; todas las variables, 0.35 y 0.697. Agregar variables empeoró la prueba.

Paso 6. Validación cruzada

Con 61 mesas de prueba, una partición puede engañar. Corre cross_val_score(LinearRegression(), X[cols], tips["tip"], cv=5, scoring="r2") para cada conjunto de variables. Control: solo cuenta da 0.457 de media, cuenta y tamaño 0.466, todas 0.421. Los pliegues de cuenta y tamaño van de 0.254 a 0.611: la incertidumbre es mayor que la diferencia entre los dos modelos simples. La conclusión honesta es que los dos modelos simples empatan y que el modelo con todas las variables queda por debajo.

Conclusiones y limitaciones

Cierra con un párrafo que responda la pregunta con cifras y otro con las limitaciones: 244 mesas, un solo mesero, un restaurante de EE. UU. con datos publicados en 1995, pocas mesas en algunos grupos, y correlación que no prueba causa. Que el porcentaje baje con el tamaño de la mesa no significa que agregar comensales reduzca la propina de cada uno.

Estructura del notebook entregable

  1. Pregunta y datos (origen, unidad de observación, variables).
  2. Revisión de calidad y decisiones de limpieza.
  3. Exploración por grupo con tablas.
  4. Dos o tres gráficas con título y unidades.
  5. Modelos: base, simple, completo, con tabla comparativa.
  6. Validación cruzada.
  7. Conclusiones y limitaciones.

Trampas comunes

  • Elegir el modelo por el $R^2$ de entrenamiento: más variables siempre lo suben.
  • Concluir con grupos de 4 mesas.
  • Olvidar drop_first y dejar columnas redundantes.
  • Mezclar dólares y porcentaje en la misma conclusión sin decir cuál usas.

Cierre

Avanza solo cuando el punto de control coincida. Si un número no sale, el error casi siempre está en un paso anterior: vuelve, corrígelo y sigue.

Recursos