Vino y algo de python(y pandas)

11 minute read

Introducción

Mientras avanzo con mi magister, me di cuenta que los tutoriales y demás explicaciones de Data Science están, preferentemente, en inglés. Además, el intentar explicar y resumir en pocas palabras todo lo que se quiere decir es una habilidad que si o si deberé desarrollar. Por eso, quise hacer este minitutorial (ok, no tan mini). La idea es poder mostrar de forma simple, y en español, los pasos básicos que tiene cualquier proyecto de Data Science (y no, no me daré la lata explicando lo que es el Data Science, su importancia y blablabla. Para eso mejor irse a Wikipedia) Asique, A darle átomos!

Los pasos en un proyecto de Data Science

En términos generales, es posible descomponer cualquier proyecto en Data Science en 5 grandes pasos:

  1. Pregunta: Establecer una pregunta (científica) válida. ¿Qué es lo que quiero explorar? ¿Qué quiero investigar? ¿Qué hipótesis tengo?¿Qué quiero PREDECIR?.

  2. Obtener los datos: Preguntarse desde dónde vienen los datos que utilizaré para responder a mi pregunta. ¿los tengo en un archivo local?¿están en internet?¿son de acceso libre o debo pagar?

  3. Explorar los datos: Revisar los datos obtenidos de una forma exploratoria, me permite dirigir los esfuerzos para responder a mi pregunta de investigación. ¿Existen valores faltantes, y dónde puedo obtener los faltantes?¿hay variables que no sean numéricas?

  4. Modelar los datos: Usualmente, mi pregunta puede o debe poder responderse a través de un modelo (Estadístico, regresion lineal, estocástico). Para ello debo crear el modelo, probarlo y validarlo.

  5. Comunicar y visualizar los resultados: Sin una buena comunicación, clara, conscisa y precisa, todos los pasos anterior no tienen validez, pues no son capaces de entregar una respuesta adecuada a la pregunta planteada.

Análisis de proyecto: Vinos blancos y Tintos

Para ejemplificar el proceso descrito anteriormente, usé el dataset de vinos (porque…emmm…VINO!) (disponible aquí). Este set es de acceso público y la descripción de su utilidad se explica en el paper de cortez et al., 2009

Acá, los autores presentan un caso de estudio para modelar la preferencia a cierto tipo de vino, basado en datos analíticos que son fácilmente adquiribles en el proceso de certificación del vino. Los autores señalan que su modelo puede ayudar a segmentar el mercado y dirigir de mejor forma los esfuerzos de marketing y comercialización.

Todo el código lo hice en Jupyter notebook, asique se ve un poco diferente a cualquier código escrito en python

Paso 1: Pregunta

¿Es posible encontrar alguna relación lineal o algún modelo simple que explique la preferencia de un vino debido a alguna de sus características fisicoquímicas?

Paso 2: Obtener los datos

Para este paso, importaremos las librerías necesarias en python y abriremos el archivo desde internet. Nótese que la explicación del conjunto de datos se encuentra descrito

import numpy as np  # necesaria para cálculos numéricos
import pandas as pd # necesaria para la manipulación de los datos
import matplotlib.pyplot as plt # necesaria para graficar
import seaborn as sns #gráficos más bonitos
import warnings
warnings.filterwarnings('ignore')#no mostrar mensajes de advertencia
sns.set(style="ticks") #Para manejo de los ejes
# obtener y cargar los datos desde
# https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/
direccion = 'https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/'
archivo_tintos = direccion + 'winequality-red.csv'
red_wine = pd.read_csv(archivo_tintos, sep=';')

archivo_blancos = direccion + 'winequality-white.csv'
white_wine = pd.read_csv(archivo_blancos, sep=';')
#Explorar si se cargaron bien los datos de vinos blancos...
white_wine.head()
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.0 0.27 0.36 20.7 0.045 45.0 170.0 1.0010 3.00 0.45 8.8 6
1 6.3 0.30 0.34 1.6 0.049 14.0 132.0 0.9940 3.30 0.49 9.5 6
2 8.1 0.28 0.40 6.9 0.050 30.0 97.0 0.9951 3.26 0.44 10.1 6
3 7.2 0.23 0.32 8.5 0.058 47.0 186.0 0.9956 3.19 0.40 9.9 6
4 7.2 0.23 0.32 8.5 0.058 47.0 186.0 0.9956 3.19 0.40 9.9 6
#...y tintos
red_wine.head()
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
1 7.8 0.88 0.00 2.6 0.098 25.0 67.0 0.9968 3.20 0.68 9.8 5
2 7.8 0.76 0.04 2.3 0.092 15.0 54.0 0.9970 3.26 0.65 9.8 5
3 11.2 0.28 0.56 1.9 0.075 17.0 60.0 0.9980 3.16 0.58 9.8 6
4 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5

Paso 3: Explorar los datos

Para este análisis, primero se utilizarán las herramientas descriptivas de pandas y posteriormente se explorarán las variables en su conjunto.

3.1: Vinos Tintos

# esto me entrega estadísticas generales de los datos, tales como valores faltantes
# cantidad de columnas, tipos de datos, entre otros
red_wine.info() 
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1599 entries, 0 to 1598
Data columns (total 12 columns):
fixed acidity           1599 non-null float64
volatile acidity        1599 non-null float64
citric acid             1599 non-null float64
residual sugar          1599 non-null float64
chlorides               1599 non-null float64
free sulfur dioxide     1599 non-null float64
total sulfur dioxide    1599 non-null float64
density                 1599 non-null float64
pH                      1599 non-null float64
sulphates               1599 non-null float64
alcohol                 1599 non-null float64
quality                 1599 non-null int64
dtypes: float64(11), int64(1)
memory usage: 150.0 KB
red_wine.describe() #esto me entrega estadísticas generales de las columnas
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
count 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000 1599.000000
mean 8.319637 0.527821 0.270976 2.538806 0.087467 15.874922 46.467792 0.996747 3.311113 0.658149 10.422983 5.636023
std 1.741096 0.179060 0.194801 1.409928 0.047065 10.460157 32.895324 0.001887 0.154386 0.169507 1.065668 0.807569
min 4.600000 0.120000 0.000000 0.900000 0.012000 1.000000 6.000000 0.990070 2.740000 0.330000 8.400000 3.000000
25% 7.100000 0.390000 0.090000 1.900000 0.070000 7.000000 22.000000 0.995600 3.210000 0.550000 9.500000 5.000000
50% 7.900000 0.520000 0.260000 2.200000 0.079000 14.000000 38.000000 0.996750 3.310000 0.620000 10.200000 6.000000
75% 9.200000 0.640000 0.420000 2.600000 0.090000 21.000000 62.000000 0.997835 3.400000 0.730000 11.100000 6.000000
max 15.900000 1.580000 1.000000 15.500000 0.611000 72.000000 289.000000 1.003690 4.010000 2.000000 14.900000 8.000000
red_wine.isnull().sum() #re-revisar si hay valores faltantes
fixed acidity           0
volatile acidity        0
citric acid             0
residual sugar          0
chlorides               0
free sulfur dioxide     0
total sulfur dioxide    0
density                 0
pH                      0
sulphates               0
alcohol                 0
quality                 0
dtype: int64
sns.pairplot(red_wine, hue=None)
plt.show() #mostar.  Doble click en el gráfico lo agranda

png

Se observa que entre las variables ‘density’ y ‘fixed acidity’ existe una relación lineal. Serán estas variables las que se utilizarán para realizar el modelo.

3.1: Vinos Blancos

white_wine.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4898 entries, 0 to 4897
Data columns (total 12 columns):
fixed acidity           4898 non-null float64
volatile acidity        4898 non-null float64
citric acid             4898 non-null float64
residual sugar          4898 non-null float64
chlorides               4898 non-null float64
free sulfur dioxide     4898 non-null float64
total sulfur dioxide    4898 non-null float64
density                 4898 non-null float64
pH                      4898 non-null float64
sulphates               4898 non-null float64
alcohol                 4898 non-null float64
quality                 4898 non-null int64
dtypes: float64(11), int64(1)
memory usage: 459.3 KB
white_wine.describe()
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
count 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000 4898.000000
mean 6.854788 0.278241 0.334192 6.391415 0.045772 35.308085 138.360657 0.994027 3.188267 0.489847 10.514267 5.877909
std 0.843868 0.100795 0.121020 5.072058 0.021848 17.007137 42.498065 0.002991 0.151001 0.114126 1.230621 0.885639
min 3.800000 0.080000 0.000000 0.600000 0.009000 2.000000 9.000000 0.987110 2.720000 0.220000 8.000000 3.000000
25% 6.300000 0.210000 0.270000 1.700000 0.036000 23.000000 108.000000 0.991723 3.090000 0.410000 9.500000 5.000000
50% 6.800000 0.260000 0.320000 5.200000 0.043000 34.000000 134.000000 0.993740 3.180000 0.470000 10.400000 6.000000
75% 7.300000 0.320000 0.390000 9.900000 0.050000 46.000000 167.000000 0.996100 3.280000 0.550000 11.400000 6.000000
max 14.200000 1.100000 1.660000 65.800000 0.346000 289.000000 440.000000 1.038980 3.820000 1.080000 14.200000 9.000000
white_wine.isnull().sum() #re-revisar si hay valores faltantes
fixed acidity           0
volatile acidity        0
citric acid             0
residual sugar          0
chlorides               0
free sulfur dioxide     0
total sulfur dioxide    0
density                 0
pH                      0
sulphates               0
alcohol                 0
quality                 0
dtype: int64
sns.pairplot(white_wine, hue=None)
plt.show()

png

Se observa que, al igual que en los vinos tintos, entre las variables ‘density’ y ‘fixed acidity’ existe una relación lineal. Sin embargo esta presenta una pendiente muy cercana a cero. Se elegirá entonces las variables ‘density’ y ‘residual sugar’. Además, los vinos blancos presentan una relación lineal más marcada entre su densidad y los grados alcohólicos. Serán estas variables las que se utilizarán a posteriori para realizar el modelo.

3.2 Reducción de datos

#para simplificar la notación, se seleccionarán las columnas señaladas anteriormente 
# y se crearan nuevos conjuntos de datos más pequeños
red_analysis = pd.DataFrame(data=red_wine, 
                            columns = ['density', 'fixed acidity', 
                                       'quality', 'residual sugar', 'alcohol'])
red_analysis.head()
density fixed acidity quality residual sugar alcohol
0 0.9978 7.4 5 1.9 9.4
1 0.9968 7.8 5 2.6 9.8
2 0.9970 7.8 5 2.3 9.8
3 0.9980 11.2 6 1.9 9.8
4 0.9978 7.4 5 1.9 9.4
white_analysis = pd.DataFrame(data=white_wine, 
                              columns = ['density', 'fixed acidity', 
                                          'quality', 'residual sugar', 'alcohol'])
white_analysis.head()
density fixed acidity quality residual sugar alcohol
0 1.0010 7.0 6 20.7 8.8
1 0.9940 6.3 6 1.6 9.5
2 0.9951 8.1 6 6.9 10.1
3 0.9956 7.2 6 8.5 9.9
4 0.9956 7.2 6 8.5 9.9
#acá, usando hue = 'quality', separo los datos por colores
sns.pairplot(red_analysis, hue='quality',palette="husl") 
plt.show()

png

sns.pairplot(white_analysis, hue='quality',palette="husl")
plt.show()

png

Paso 4: Modelar los datos

Para este paso, y recordando la pregunta “¿Es posible encontrar alguna relación lineal o algún modelo simple que explique la preferencia de un vino debido a alguna de sus características fisicoquímicas?”, se emplearán las variables señaladas en el paso anterior y se intentarán separar por la “calidad”, de forma tal de establecer un modelo que me permita poder predecir la calidad del vino según su grado alcohólico, densidad, acidez residual y azucar. Como se observa en los gráficos anteriores, es difícil establecer una relación lineal exclusiva para cada tipo de vino y para cada calidad. A modo de ejemplo se calculará la relación entre densidad y acidez para las 3 calidades de vino tinto más representadas.

sns.countplot(x="quality", data=red_analysis,hue = 'quality', palette="husl")
plt.xticks([])
plt.show()

png

#las calidades más representadas son los vinos de calidad 5,6 y 7
red_5 = pd.DataFrame(data=red_analysis[red_analysis.quality==5], 
                     columns=['density', 'fixed acidity'])
red_6 = pd.DataFrame(data=red_analysis[red_analysis.quality==6], 
                     columns=['density', 'fixed acidity'])
red_7 = pd.DataFrame(data=red_analysis[red_analysis.quality==7], 
                     columns=['density', 'fixed acidity'])
#Se graficarán las 3 calidades para ver qué tan separadas están
colores = sns.color_palette(palette='husl', n_colors=6, desat=None)#obtener los colores usados anteriormente
plt.plot(red_5['density'],red_5['fixed acidity'], '.', color = colores[0], label='5')
plt.plot(red_6['density'],red_6['fixed acidity'], '.', color = colores[3], label='6')
plt.plot(red_7['density'],red_7['fixed acidity'], '.', color = colores[5], label='7')
plt.show()

png

def minimos_cuadrados(x,y):  
    m = len(x) #len() mide el largo del vector
    sumax = np.sum(x)
    sumay = np.sum(y)
    sumax2 = np.sum(x**2) # cada termino al cuadrado se suma
    sumaxy = np.dot(x,y) # producto punto entre x e y
    a = (m*sumaxy - sumax*sumay) / (m*sumax2 - sumax**2)
    b = (sumax2*sumay - sumaxy*sumax) / (m*sumax2 - sumax**2)
    return a,b
#obtener el ajuste de mínimos cuadrados para los 3 conjuntos de datos
a5,b5 = minimos_cuadrados(red_5['density'],red_5['fixed acidity'])
a6,b6 = minimos_cuadrados(red_6['density'],red_6['fixed acidity'])
a7,b7 = minimos_cuadrados(red_7['density'],red_7['fixed acidity'])

x = np.linspace(0.980,1.005,101)
y5 = a5*x + b5 
y6 = a6*x + b6
y7 = a7*x + b7

Paso 5: Comunicar Resultados

Para este paso, se elaborará un gráfico que muestre la nube de puntos para las 3 calidades de vino (5, 6 y 7), las rectas de ajuste por mínimos cuadrados y un gráfico que permita comparar las pendientes para los 3 ajustes realizados


plt.figure(figsize=(25,9))#figura de tamaño 15x5
plt.suptitle("Resultados de los análisis para vino tinto")#titulo general
plt.subplot(121) #figura con 2 gráficos. 1º gráfico
#graficar las nubes de puntos
plt.title('Datos y sus ajustes por MC')
plt.plot(red_5['density'],red_5['fixed acidity'], '.', color = colores[0], label='')
plt.plot(red_6['density'],red_6['fixed acidity'], '.', color = colores[3],label='')
plt.plot(red_7['density'],red_7['fixed acidity'], '.', color = colores[5],label='')
#graficar los ajustes
plt.plot(x,y5, '-', color = colores[0],label='$A_5 = %0.3f D_5+ %0.3f $'%(a5,b5))
plt.plot(x,y6, '-', color = colores[3],label='$A_6 = %0.3f D_6+ %0.3f $'%(a6,b6))
plt.plot(x,y7, '-', color = colores[5],label='$A_7 = %0.3f D_7+ %0.3f $'%(a7,b7))
plt.xlabel('Density')#etiquetar correctamente los gráficos
plt.ylabel('Fixed Acidity ')
plt.legend() #agregar leyenda al gráfico
plt.xlim(0.990,1.004)#ordenar el rango a mostrar
plt.ylim(0,17)

plt.subplot(122)#2º gráfico
plt.title('Pendientes de ajuste por MC para calidades de vino tinto')
plt.bar([5,6,7], [a5,a6,a7], color = [colores[0], colores[3], colores[5]])
plt.xlabel('Wine Quality')
plt.ylabel('Pendiente')
plt.xticks([5,6,7])
plt.show()

png

Paso 6(opcional pero necesario): Conclusiones

Usualmente este paso, si bien no siempre es escrito, siempre es necesario, pues permite ver si se respondió la pregunta planteada en el paso 1 o no. En este caso, no es posible diferenciar la calidad de los 3 vinos analizados, sólo conociendo su densidad y su acidez. Si bien se observa que existen diferencias en las pendientes, en la nube de puntos no es posible diferenciar a los datos, por lo tanto el ajuste por mínimos cuadrados no es suficiente y se requieren otras técnicas para poder estimar la calidad del vino.

Comentarios

Después de toda esa lata, logré por fin darme cuenta que mi modelo vale callampa requiere más información y quizá un análisis estadístico clásico no es suficiente. Es en este punto donde viene la necesidad de contar con más herramientas. A estas herramientas se les conoce como “Machine Learning” y espero poder hacer luego o no tan luego, un post sobre eso. Sea cortés, ande con cuidado, edúquese lo mas que pueda, respete para que lo respeten…Y que Dios noh ampare!!

Canción del Día: La Combo Tortuga - Amor Borracho

La Combo Tortuga - Amor Borracho

Bonus Track (para escucharla con un vinito)

El bloque Depresivo - Regresa

Referencias y lectura adicional

  1. Libro de documentación de pandas
  2. Cheat Sheet Pandas
  3. Ejercicios en Pandas(inglés)
  4. Pandas en 10 minutos
  5. Varios pandas(1)
  6. Varios pandas(2)
  7. Cursos de Data Science online

Leave a Comment