Ejemplo de Mineria de Datos.

 

Preparación de datos

A lo largo de esta práctica veremos como aplicar diferentes tecnicas para la carga y preparación de datos:

  1. Carga de un conjunto de datos
  2. Análisis de los datos
    2.1 Análisis estadístico básico
    2.2 Análisis expxloratorio de los datos
  3. Reducción de la dimensionalidad
  4. Entrenamiento y test

Para eso necesitaremos las siguientes librerías:

In [1]:
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn import datasets
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.model_selection import train_test_split, cross_val_score

import matplotlib.pyplot as plt
import ssl

ssl._create_default_https_context = ssl._create_unverified_context


pd.set_option('display.max_columns', None)

%matplotlib inline

1. Carga del conjunto de datos

En primer lugar, debéis cargar el conjunto de datos Wine recognition (más información en el enlace https://archive.ics.uci.edu/ml/datasets/Wine). Se puede descargar de internet o se puede cargar directamente desde la librería "scikit-learn", que incorpora un conjunto de datasets muy conocidos y usados para la minería de datos y machine learning http://scikit-learn.org/stable/datasets/index.html.

Ejercicio: Cargad el conjunto de datos "Wine Recognition" y mostrad:
- el número y nombre de los atributos (variables que podrían ser usadas para predecir la respuesta "wine_class")
- el número de filas del conjunto de datos
- verificad si hay o no "missing values" y en que columnas
Sugerencia: Si usáis sklearn (sklearn.datasets.load_wine), explorad las diferents 'keys' del objecto obtenido. Sugerencia: Igual os resulta útil pasar los datos (atributos + target) a un dataframe de pandas.
In [2]:
from sklearn.datasets import load_wine
data = load_wine()
df = pd.DataFrame(data.data, columns=data.feature_names)
df['classes'] = pd.Categorical.from_codes(data.target, data.target_names)
print("Primer análisis de los datos")
df.head()
Primer análisis de los datos
Out[2]:
alcoholmalic_acidashalcalinity_of_ashmagnesiumtotal_phenolsflavanoidsnonflavanoid_phenolsproanthocyaninscolor_intensityhueod280/od315_of_diluted_winesprolineclasses
014.231.712.4315.6127.02.803.060.282.295.641.043.921065.0class_0
113.201.782.1411.2100.02.652.760.261.284.381.053.401050.0class_0
213.162.362.6718.6101.02.803.240.302.815.681.033.171185.0class_0
314.371.952.5016.8113.03.853.490.242.187.800.863.451480.0class_0
413.242.592.8721.0118.02.802.690.391.824.321.042.93735.0class_0
In [3]:
print("Descripción de los datos")
df.describe()
Descripción de los datos
Out[3]:
alcoholmalic_acidashalcalinity_of_ashmagnesiumtotal_phenolsflavanoidsnonflavanoid_phenolsproanthocyaninscolor_intensityhueod280/od315_of_diluted_winesproline
count178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000
mean13.0006182.3363482.36651719.49494499.7415732.2951122.0292700.3618541.5908995.0580900.9574492.611685746.893258
std0.8118271.1171460.2743443.33956414.2824840.6258510.9988590.1244530.5723592.3182860.2285720.709990314.907474
min11.0300000.7400001.36000010.60000070.0000000.9800000.3400000.1300000.4100001.2800000.4800001.270000278.000000
25%12.3625001.6025002.21000017.20000088.0000001.7425001.2050000.2700001.2500003.2200000.7825001.937500500.500000
50%13.0500001.8650002.36000019.50000098.0000002.3550002.1350000.3400001.5550004.6900000.9650002.780000673.500000
75%13.6775003.0825002.55750021.500000107.0000002.8000002.8750000.4375001.9500006.2000001.1200003.170000985.000000
max14.8300005.8000003.23000030.000000162.0000003.8800005.0800000.6600003.58000013.0000001.7100004.0000001680.000000
In [4]:
print("El número de líneas es: " + str(df.shape[0]) + " y el número de columnas: "+ str(df.shape[1]))
El número de líneas es: 178 y el número de columnas: 14
In [5]:
print("No existe ningún null")

df.isnull().sum()
No existe ningún null
Out[5]:
alcohol                         0
malic_acid                      0
ash                             0
alcalinity_of_ash               0
magnesium                       0
total_phenols                   0
flavanoids                      0
nonflavanoid_phenols            0
proanthocyanins                 0
color_intensity                 0
hue                             0
od280/od315_of_diluted_wines    0
proline                         0
classes                         0
dtype: int64

2. Análisis de los datos

2.1 Análisis estadístico básico

Ejercicio: Realizad un análisis estadístico básico:
- Variables categóricas: - Calculad la frecuencia. - Haced un gráfico de barras.
- Variables numéricas: - Calculad estadísticos descriptivos básicos: media, mediana, desviación estandard, ... - Haced un histograma de las variables: alcohol, magnesium y color_intensity
Sugerencia: podéis usar la librería 'pandas' y sus funciones 'describe' y 'value_counts'
In [6]:
print('Cálculo de la frecuéncia')
cols = df.columns
num_cols_no_categoric = df._get_numeric_data().columns

col_categoric=list(set(cols) - set(num_cols_no_categoric))
print("Frecuéncia")
print( pd.value_counts(df[col_categoric].values.flatten()))
frecuencia_data =pd.value_counts(df[col_categoric].values.flatten())

sns.catplot(x="classes", kind="count", palette="ch:.25", data=df);
Cálculo de la frecuéncia
Frecuéncia
class_1    71
class_0    59
class_2    48
dtype: int64
In [7]:
print("Estadísticos descriptivos básicos:")
df[num_cols_no_categoric].describe()
Estadísticos descriptivos básicos:
Out[7]:
alcoholmalic_acidashalcalinity_of_ashmagnesiumtotal_phenolsflavanoidsnonflavanoid_phenolsproanthocyaninscolor_intensityhueod280/od315_of_diluted_winesproline
count178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000178.000000
mean13.0006182.3363482.36651719.49494499.7415732.2951122.0292700.3618541.5908995.0580900.9574492.611685746.893258
std0.8118271.1171460.2743443.33956414.2824840.6258510.9988590.1244530.5723592.3182860.2285720.709990314.907474
min11.0300000.7400001.36000010.60000070.0000000.9800000.3400000.1300000.4100001.2800000.4800001.270000278.000000
25%12.3625001.6025002.21000017.20000088.0000001.7425001.2050000.2700001.2500003.2200000.7825001.937500500.500000
50%13.0500001.8650002.36000019.50000098.0000002.3550002.1350000.3400001.5550004.6900000.9650002.780000673.500000
75%13.6775003.0825002.55750021.500000107.0000002.8000002.8750000.4375001.9500006.2000001.1200003.170000985.000000
max14.8300005.8000003.23000030.000000162.0000003.8800005.0800000.6600003.58000013.0000001.7100004.0000001680.000000
In [8]:
print("Histograma de las variables: alcohol")

graf1  = sns.distplot(df['alcohol'],kde=0)
Histograma de las variables: alcohol
In [9]:
print("Histograma de las variables: color_intensity")
graf3  = sns.distplot(df['color_intensity'],kde=0,color="red")
Histograma de las variables: color_intensity
In [10]:
print("Histograma de las variables: magnesium")
graf2  = sns.distplot(df['magnesium'],kde=0,color="green")
Histograma de las variables: magnesium
Análisis: Comentad los resultados.

Respecto al target, la clase de vino, cabe destacar que el número de elementos está distribuido equitativamente entre los 3 tipos de vino, es decir, no tenemos una gran cantidad del tipo1 y muy poca del resto que complicaria las tareas de clasificación.

También es posible analizar como el alcohol es el elemento más distribuido de las tres gráficas, es decir, tenemos una gran cantidad de muestras diferentes lo que me hace deducir que será una variable dificil de utilizar para la clasificación. En otro sentido, el color de la intensidad se agrupa más y el magnesio esta muy agrupado alrededor de 90.

2.2 Análisis exploratorio de los datos

En este ejercicio exploraremos la relación de algunos de los atributos numéricos con la variable respuesta ("wine_class"), tanto gráficamente como cualitativamente, y analizaremos las diferentes correlaciones. Para empezar, seleccionaremos solo 3 atributos para explorar: alcohol, magnesium y color_intensity.

In [11]:
feats_to_explore = ['alcohol', 'magnesium', 'color_intensity']
Ejercicio: Usando una librería gráfica, como por ejemplo "matplotlib", realizad un gráfico del histograma de valores para cada uno de los atributos seleccionados, separados por los valores de la clase respuesta. Los tres gráficos tienen que estar sobrepuestos, es decir, por ejemplo, en el histograma de la feature "alcohol" tienen que haber en un solo gráfico tres histogramas, uno por cada clase de vino. Añadid una leyenda para saber a que clase corresponde cada histograma. La finalidad es observar como se distribuye cada uno de los atributos en función de la clase que tengan, para poder identificar de manera visual y rápida si algunos atributos permiten diferenciar de forma clara las diferentes clases de vinos.
Sugerencia: podéis usar el parámetro "alpha" en los gráficos para que se aprecien los tres histogramas.
In [12]:
for i in [0,1,2]:
    sns.distplot(df['alcohol'][data.target==i],
                 kde=False,label='{
                }'.format(i))

plt.legend()
Out[12]:
<matplotlib.legend.Legend at 0x1246dcf98>
In [13]:
for i in [0,1,2]:
    sns.distplot(df['color_intensity'][data.target==i],kde=False,label='{}'.format(i))

plt.legend()
Out[13]:
<matplotlib.legend.Legend at 0x105b58898>
In [14]:
for i in [0,1,2]:
    sns.distplot(df['magnesium'][data.target==i],kde=False,
                 label='{
                }'.format(i))

plt.legend()
Out[14]:
<matplotlib.legend.Legend at 0x105b6bfd0>
Análisis:
Mirando los histogramas, ¿que atributo parece tener más peso a la hora de clasificar un vino? ¿Cual parece tener menos peso?

La intensidad del color, mientras qeu el de menor peso el magnesium. Realmente el color esta más agrupado y diferenciado que el resto.

Ejercicio: Usando los histogramas anteriores, añadid una linia vertical indicando la media de cada uno de los histogramas (tres por gráfico). Pintad las linias del mismo color que el histograma para que quede claro a cual hacen referencia. Añadid a la leyenda, la clase de vino y la desviación estandard en cuestión. La finalidad es verificar numéricamente las diferencias identificadas anteriormente de forma visual.
Sugerencia: podeis usar "axvline", de matplotlib axis, para las linias verticales.
In [15]:
for i in [0,1,2]:
    std = round(df["alcohol"][data.target==i].std(),3)
    sns.distplot(df['alcohol'][data.target==i],
                 kde=1,label='{} con std {}'.format(i,std))

plt.legend()
Out[15]:
<matplotlib.legend.Legend at 0x125295dd8>
In [16]:
for i in [0,1,2]:
    std = round(df["color_intensity"][data.target==i].std(),3)
    sns.distplot(df['color_intensity'][data.target==i],
                 kde=1,label='{} con std {}'.format(i,std))

plt.legend()
Out[16]:
<matplotlib.legend.Legend at 0x1274e4048>
In [17]:
for i in [0,1,2]:
    std = round(df["magnesium"][data.target==i].std(),3)
    sns.distplot(df['magnesium'][data.target==i],
                 kde=1,label='{} con std {}'.format(i,std))

plt.legend()
Out[17]:
<matplotlib.legend.Legend at 0x127615748>
Ejercicio: Calculad y mostrad la correlación entre las tres variables que estamos analizando.
In [18]:
fig, (ax) = plt.subplots(1, 1, figsize=(10,6))
corr = df[feats_to_explore].corr()

hm = sns.heatmap(corr,
                 ax=ax,           # Axes in which to draw the plot, otherwise use the currently-active Axes.
                 cmap="coolwarm", # Color Map.
                 #square=True,    # If True, set the Axes aspect to “equal” so each cell will be square-shaped.
                 annot=True,
                 fmt='.2f',       # String formatting code to use when adding annotations.
                 #annot_kws={"size": 14},
                 linewidths=.05)

fig.subplots_adjust(top=0.93)
fig.suptitle('Wine Attributes Correlation Heatmap',
              fontsize=14,
              fontweight='bold')
Out[18]:
Text(0.5, 0.98, 'Wine Attributes Correlation Heatmap')
Ejercicio: Representad gráficamente las relaciones entre estas variables (scatterplots). Diferenciad con colores diferentes las diferentes clases. La finalidad es poder observar y analizar las correlaciones de manera gráfica entre algunas de las variables.
Sugerencia: podéis usar la función "pairplot" de la librería 'seaborn' con el parámetro "hue".
In [19]:
feats_to_explore.append("classes")
sns.pairplot( hue="classes", data= df[feats_to_explore])
Out[19]:
<seaborn.axisgrid.PairGrid at 0x1277a1198>
Análisis:
Observando las correlaciones, que variables son las que tienen una correlación más fuerte? Cuadra el resultado numérico con el gráfico obtenido?

Sip, cuadra que el color y el alcohol tiene una dsitribución de los datos mucho más clara que el resto (tercer nivel izquierda). También en el gráfico de correlación aparecia el valor más elevado lo que confirma la información visualizada.

3. Reducción de la dimensionalidad

En este ejercicio se aplicarán métodos de reducción de la dimensionalidad al conjunto original de datos. El objetivo es reducir el conjunto de atributos a un nuevo conjunto con menos dimensiones. Así en vez de trabajar con 3 variables elegidas al azahar, usaremos la información de todos los atributos.

Ejercicio: Aplicad el método de reducción de la dimensionalidad Principal Component Analysis (PCA) para reducir a 2 dimensiones (el dataset entero con todas las features). Generad un gráfico (en 2D) con el resultado del PCA usando colores diferentes para cada una de las clases de la respuesta (wine_class), con el objetivo de visualizar si es posible separar eficientemente las clases con este método. NOTA: Vigilad de no incluir la variable objetivo "wine class" a la reducción de dimensionalidad. Queremos poder explicar la variable objetivo en funcion del resto de variables reducidas a dos dimensiones.
Sugerencia: No es necesario que programéis el algoritmo, podéis usar la implementación disponible en la librería de "scikit-learn".
In [20]:
from sklearn.preprocessing import StandardScaler

features = data.feature_names
# Separating out the features
x = df.loc[:, features].values
# Separating out the target
y = df.loc[:,['classes']].values
# Standardizing the features
x = StandardScaler().fit_transform(x)

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
principalComponents = pca.fit_transform(x)
principalDf = pd.DataFrame(data = principalComponents
             , columns = ['principal component 1', 'principal component 2'])
principalDf
Out[20]:
principal component 1principal component 2
03.316751-1.443463
12.2094650.333393
22.516740-1.031151
33.757066-2.756372
41.008908-0.869831
.........
173-3.370524-2.216289
174-2.601956-1.757229
175-2.677839-2.760899
176-2.387017-2.297347
177-3.208758-2.768920

178 rows × 2 columns

In [21]:
finalDf = pd.concat([principalDf, df[['classes']]], axis = 1)
finalDf
Out[21]:
principal component 1principal component 2classes
03.316751-1.443463class_0
12.2094650.333393class_0
22.516740-1.031151class_0
33.757066-2.756372class_0
41.008908-0.869831class_0
............
173-3.370524-2.216289class_2
174-2.601956-1.757229class_2
175-2.677839-2.760899class_2
176-2.387017-2.297347class_2
177-3.208758-2.768920class_2

178 rows × 3 columns

In [22]:
fig = plt.figure(figsize = (8,8))
ax = fig.add_subplot(1,1,1)
ax.set_xlabel('Principal Component 1', fontsize = 15)
ax.set_ylabel('Principal Component 2', fontsize = 15)
ax.set_title('2 component PCA', fontsize = 20)
targets = ['class_0', 'class_1', 'class_2']
colors = ['r', 'g', 'b']
for target, color in zip(targets,colors):
    indicesToKeep = finalDf['classes'] == target
    ax.scatter(finalDf.loc[indicesToKeep, 'principal component 1']
               , finalDf.loc[indicesToKeep, 'principal component 2']
               , c = color
               , s = 50)
ax.legend(targets)
ax.grid()
Ejercicio: Repetid la reducción de dimensionalidad, pero en este caso usando TSNE. Podeis encontrar más información sobre este algoritmo al link: [https://distill.pub/2016/misread-tsne/](https://distill.pub/2016/misread-tsne/) Igual que antes, generad un gráfico (en 2D) con el resultado del PCA usando colores diferentes para cada una de las clases de la respuesta (wine_class), con el objetivo de visualizar si es posible separar eficientemente las clases con este método.
Sugerencia: No es necesario que programéis el algoritmo, podéis usar la implementación disponible en la librería de "scikit-learn". Sugerencia: A parte de especificar el número de componentes, probad de usar el parámetro "perplexity".
In [23]:
features = data.feature_names
# Separating out the features
x = df.loc[:, features].values
y=data.target
from sklearn.manifold import TSNE

tsne_results = TSNE(n_components=2).fit_transform(x)

target_ids = range(len([0,1,2]))


from matplotlib import pyplot as plt
colors = 'r', 'g', 'b', 'c', 'm', 'y', 'k', 'w', 'orange', 'purple'
for i, c, label in zip(target_ids, colors, [0,1,2]):
    plt.scatter(tsne_results[y == i, 0], tsne_results[y == i, 1], c=c, label=label)
plt.legend()
plt.show()


sns.scatterplot(tsne_results[:,0], tsne_results[:,1], hue=y, legend='full')
Out[23]:
<matplotlib.axes._subplots.AxesSubplot at 0x127690780>
Análisis:
Observando los dos gráficos, ¿crees que ha funcionado bien la reducción de dimensionalidad? ¿Ha conseguido separar las clases correctamente? ¿Cual de los dos métodos ha funcionado mejor? ¿Por que obtenemos resultados tan diferentes?

Si, creo que ha funcionado correctamente en el primer caso. Utilizando el PCA he conseguido observar de forma nítida la distribución del dataset en 3 clases de vinos mientras que en el segundo se entremezclan más y cuesta más diferenciarlos.

Los resultados son diferentes ya que los algoritmos están optimizados para diferentes dataset. Mientras que el PCA es muy útil para dataset con caracteristicas similares entre clases el TSNE es para todo lo contrario.

4. Entrenamiento y test

En este último ejercicio se trata de aplicar un método de aprendizaje supervisado, concretamente el clasificador Random Forest, para predecir la clase a la que pertenece cada vino y evaluar la precisión obtenida con el modelo. Para eso usaremos:

- El conjunto de datos original con todos los atributos
- El conjunto de datos reducido a solo 2 atributos con PCA
- El conjunto de datos reducido a solo 2 atributos con TSNE
Ejercicio: Usando el conjunto de datos original: - Dividid el dataset en train y test. - Definid un modelo Random Forest (fijando n_estimators=10 para mantener el modelo simple). - Aplicad validación cruzada con el modelo definido y el dataset de train (con cv=5 ya es suficiente). - Calculad la media y la desviación standard de la validación cruzada.
Sugerencia: Para separar entre train y test podéis usar train_test_split de sklearn. Sugerencia: Para entrenar un modelo random forest podéis usar 'RandomForestClassifier' de sklearn. Sugerencia: Para aplicar validaci