<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Perros o gatos?
Por Miguel Escalona
                                                                                     Edición Febrero 2017
Inicio del notebook
Para iniciar cualquier notebook, comenzaremos por invocar los módulos necesarios para su desarrollo. Para esto utilizaremos el comando import seguido del nombre del módulo. Cuando queramos utilizar una función interna del módulo, debemos escribir su nombre antes de la función. Por ejemplo
Step1:  Un problema de clasificación
Step2:  Los datos se componen de observaciones numeradas del 1 al 14 y 3 features o características representadas en las columnas (también se les conocen como inputs). La columna especie es la respuesta a nuestro problema, por lo que no representa un feature. Esto quiere decir que solo la usaremos para saber si el algoritmo de machine learning está haciendo una buena clasificación o no. A esta columna (especie) se la suele llamar target, label, output o y.
Aprendizaje supervisado (supervised learning)
Se dice que un problema de machine learning es supervisado si dentro de los datos tenemos el target, por lo que podemos evaluar a nuestro algoritmo durante su entrenamiento.
Aprendizaje no supervisado (unsupervised learning)
Si no contamos con las etiquetas o labels estaremos ante un problema no supervisado. El algoritmo deberá encontrar por sí mismo los patrones que puedan diferenciar los datos. 
Un ejemplo de este tipo de problemas es cuando queremos reconocer objetos en una imagen. Nuestro algoritmo intentará segmentar los diferentes objetos, usando por ejemplo sus contornos, pero sin conocer la forma exacta o el objeto que debe identificar. 
2. Mini Exploratory Data Analisys (EDA)
Step3:  Suma, media, mediana y desviación estándard (sum, mean, median, std)
¿Cuántos animales juegan al busca?
Step4:  Filtros de pandas
y cuantos de estos son perros?
Step5:  3. Separemos la columna especies para no confundirla
Step6:  ¡La variable comida favorita es del tipo categórica!
Esta variable tiene tres valores posibles. Para conocer con qué frecuencia aparece cada valor podemos utilizar el método value_counts() del dataframe
Step7:  4. Codificación de variables categóricas
Las variables categóricas deben ser convertidas a numéricas para poder ser interpretadas por el algorítmo de machine learning. Una posible codificación sería
Step8:  De esta forma, nuestro dataframe ya está preparado para ser utilizado por cualquiera de los algoritmos de clasificación de scikit-learn. 
5. Algoritmo de clasificación
Step9:  Evaluemos la pregunta si le gusta jugar al busca
Evaluemos las entropías
Step10:  ¿y la comida de gato?
a 0 perros y 6 gatos les gusta la comida de gatos
mientras que a 6 perros y 2 gatos no les gusta.
Su entropía es
Step11:  y no te olvides de la ganancia de información (information gain)
Ahora sí, el árbol de decisión automático
Step12:  5.1 importancia de los features
como vamos a graficar, utilizaremos la función mágica de jupyter 
python
%matplotlib inline
la cual nos permite realizar gráficos en el notebook.
Step13:  6. Visualizando el arbol, requiere graphviz
conda install graphviz
Step14:  La celda anterior exportó el árbol de decisión creado con sklearn y entrenado con nuestros datos a un archivo .dot
Este archivo lo procesaremos con el comando dot de la terminal. Desde jupyter, podemos ejecutar comandos de terminal sin salir del notebook
Step15:  finalmente para cargar la imagen usamos
Step16:  7. Evaluación del modelo
Step17:  Ahora evaluemos sobre datos nunca vistos por el modelo!!!!!
<ASSISTANT_TASK:>
Python Code:
import pandas as pd  # Cargamos pandas con el alias pd
Explanation: Perros o gatos?
Por Miguel Escalona
                                                                                     Edición Febrero 2017
Inicio del notebook
Para iniciar cualquier notebook, comenzaremos por invocar los módulos necesarios para su desarrollo. Para esto utilizaremos el comando import seguido del nombre del módulo. Cuando queramos utilizar una función interna del módulo, debemos escribir su nombre antes de la función. Por ejemplo:
python
import modulo
modulo.funcion()
en el caso en que no queramos escribir el nombre completo de un modulo, podemos colocar con alias con el comando as
python
import modulo as md
md.funcion()
finalmente, si solo queremos acceder de forma directa a todas las funciones internas del módulo sin necesidad de escribir su nombre (ni el alias) cada vez, podemos escribir
python
from modulo import *
funcion()
lo cual cargará todas las funciones del módulo sobrecargando aquellas funciones ya definidas en el código que tengan el mismo nombre. 
<p class="alert alert-danger">Este último método, aunque cómodo, es el menos aconsejable de todos</span>.
End of explanation
dfl = pd.read_csv('data/perros_o_gatos.csv', index_col='observacion')
print('Estos datos han sido tomados del libro Mastering machine learning with scikit-learn de Gavin Hackeling, \
PACKT publishing open source, pp. 99')
dfl  # En jupyter al escribir una variable sin mas, la celda nos devuelve su contenido.
Explanation: Un problema de clasificación: ¿perro o gato?
En este notebook resolveremos un problema simple de clasificación que deja en evidencia conceptos básicos de Machine Learning (ML).
El problema planteado consiste en identificar la especie del animal (perro o gato) basados en tres características: ¿el animal busca la pelota cuando se la lanzamos?, ¿el animal suele ser apático? y ¿el animal disfruta más de la comida de perro, de la de gato o del bacon?
1. Cargando los datos
Para la carga de datos usaremos la función read_csv de pandas. Pandas cuenta con un amplio listado de funciones para la carga de datos. Mas informacion en la documentación de la API.
End of explanation
dfl.describe()
Explanation: Los datos se componen de observaciones numeradas del 1 al 14 y 3 features o características representadas en las columnas (también se les conocen como inputs). La columna especie es la respuesta a nuestro problema, por lo que no representa un feature. Esto quiere decir que solo la usaremos para saber si el algoritmo de machine learning está haciendo una buena clasificación o no. A esta columna (especie) se la suele llamar target, label, output o y.
Aprendizaje supervisado (supervised learning)
Se dice que un problema de machine learning es supervisado si dentro de los datos tenemos el target, por lo que podemos evaluar a nuestro algoritmo durante su entrenamiento.
Aprendizaje no supervisado (unsupervised learning)
Si no contamos con las etiquetas o labels estaremos ante un problema no supervisado. El algoritmo deberá encontrar por sí mismo los patrones que puedan diferenciar los datos. 
Un ejemplo de este tipo de problemas es cuando queremos reconocer objetos en una imagen. Nuestro algoritmo intentará segmentar los diferentes objetos, usando por ejemplo sus contornos, pero sin conocer la forma exacta o el objeto que debe identificar. 
2. Mini Exploratory Data Analisys (EDA)
End of explanation
dfl['juega al busca'].sum()
Explanation: Suma, media, mediana y desviación estándard (sum, mean, median, std)
¿Cuántos animales juegan al busca?
End of explanation
dfl.loc[dfl['especie']=='perro','juega al busca'].sum()
Explanation: Filtros de pandas
y cuantos de estos son perros?
End of explanation
labels = dfl['especie']
df = dfl[['juega al busca', 'apatico', 'comida favorita']]
df
labels 
Explanation: 3. Separemos la columna especies para no confundirla
End of explanation
df['comida favorita'].value_counts()
Explanation: ¡La variable comida favorita es del tipo categórica!
Esta variable tiene tres valores posibles. Para conocer con qué frecuencia aparece cada valor podemos utilizar el método value_counts() del dataframe
End of explanation
from sklearn.feature_extraction import DictVectorizer
vectorizer = DictVectorizer(sparse=False)
ab = vectorizer.fit_transform(df.to_dict(orient='records'))
dft = pd.DataFrame(ab, columns=vectorizer.get_feature_names())
dft.head()
Explanation: 4. Codificación de variables categóricas
Las variables categóricas deben ser convertidas a numéricas para poder ser interpretadas por el algorítmo de machine learning. Una posible codificación sería:
```
| comida favorita | valor   |
|-----------------|---------|
|comida de gato   |    0    |
|comida de perro  |    1    |
|bacon            |    2    |
```
Sin embargo, esta codificación asigna un orden artificial a las variables. Nuestro ordenador sabe que 0 < 1 < 2, por lo que asociará que
comida de gato < comida de perro < bacon.
Codificacion one-Hot
Este tipo de codificación representa la columna comida favorita en tres columnas de 0 o 1 de la siguiente manera
```
| comida favorita | comida favorita=comida de gato | comida favorita=comida de perro | comida favorita=bacon | 
|-----------------|--------------------------------|---------------------------------|-----------------------|
|comida de gato   |                1               |                0                |            0          |
|comida de perro  |                0               |                1                |            0          |
|bacon            |                0               |                0                |            1          |
```
Atención, se debe tener cuidado cuando se desee utilizar este tipo de codificación en datasets (muy) grandes en el que  el némero de categorías son cientos o miles, pues cada categoría generará una nueva columna en nuestro dataset.
All you need is... scikit-learn (sklearn) ... well, not really.
El módulo scikit-learn contiene una gran mayoría de las herramientas que necesitamos para resolver un problema típico de machine learning. Aquí podemos encontrar algorítmos de clasificación, regresión, clustering; además de métodos para el preprocesado de datos. Mas información en el sitio oficial de scikit-learn.
Para cargar este módulo usaremos el comando
python
import sklearn
en lugar de usar su nombre completo.
Otra forma de cargar funciones de un módulo es hacer referencia a la función directamente:
python
from sklearn.preprocessing import OneHotEncoder
A pesar de que la función OneHotEncoder existe en sklearn, nosotros utilizaremos otra función llamada DictVectorizer. Esta función recibe como entrada variables numéricas y/o categóricas y devuelve las variables numéricas como flotantes y a las categóricas le aplica la codificación one-hot.
<p class="alert alert-danger">
Nota: la función `DictVectorizer` no respeta el orden de entrada de las columnas pudiendo devolver un dataframe con otro orden de columnas. Para que respete el orden de las filas, debemos especificar el parámetro **`orient='records'`**</span>.
End of explanation
from numpy import log2
def entropia_perro_gato(count_perro, count_gato):
    prob_perro = count_perro / float(count_perro + count_gato)
    prob_gato = count_gato / float(count_perro + count_gato)
    return 0.0 if not count_perro or not count_gato else -(prob_perro*log2(prob_perro) + prob_gato*log2(prob_gato))
Explanation: De esta forma, nuestro dataframe ya está preparado para ser utilizado por cualquiera de los algoritmos de clasificación de scikit-learn. 
5. Algoritmo de clasificación: Arbol de decisión
Un árbol de clasificación divide los datos en subconjuntos cada vez mas pequeños para ir determinando la clase a la cual pertenecen.  Básicamente, lo que hace este algoritmo es buscar la pregunta necesaria para poder separar la mayor cantidad de datos en dos grupos. Luego, vuelve a buscar la siguiente pregunta que romperá mejor al subgrupo y así, sucesivamente, hasta reducir los grupos lo suficiente hasta que quedemos satisfechos. 
Las preguntas
Para este ejemplo tenemos tantas preguntas posibles como features. Por ejemplo, podemos preguntar:
* El animal juega al busca, ¿si o no?
* El animal es apático, ¿si o no?
* ¿le gusta la comida de gato?
* ¿le gusta la comida de perro?
* ¿le gusta el bacon?
Pero,  ¿qué deberíamos preguntar primero?
Para responder esta clase de preguntas los algoritmos de machine learning se sirven de una función de transferencia o una función de costo. Un árbol de decisión utiliza una función de tranferencia llamada Entropía para cuantificar el nivel de incertidumbre que tenemos en la clasificación de los datos. Es decir, la pregunta que sea capaz de reducir mas la incertidumbre (entropía) será la primera pregunta a hacer.
La entropía viene definida de la siguiente manera:
$H(x) = - \sum_{i=1}^n p_i \log_2 p_i$
donde $H$ es la entropía, $p_i$ es la probabilidad que sea perro o gato. Veamos cual es la entropía de nuestro problema:
Como tenemos 6 perros y 8 gatos, la probabilidad de escoger un perro al azar es $\frac{6}{14}$, y la de que sea gato es $\frac{8}{14}$. Entonces la entropía inicial de nuestro problema es:
$H(x) = -(\frac{6}{14}\log_2\frac{6}{14} + \frac{8}{14}\log_2\frac{8}{14}) = 0.9852... $
End of explanation
perro = dfl['especie']=='perro'
gato = dfl['especie']=='gato'
no_busca = dfl['juega al busca']==False
si_busca = dfl['juega al busca']==True
print('A %d perros y %d gatos sí les gusta jugar al busca. H=%0.4f' % (
      dfl[perro]['juega al busca'].sum(),#podemos contar sumando el numero de True
      len(dfl[gato & si_busca]),#o filtrando y contando cueantos valores quedan
      entropia_perro_gato(4,1),
     ))
print('A %d perros y %d gatos no les gusta jugar al busca. H=%0.4f' % (
      len(df[perro&no_busca]),
      len(df[gato&no_busca]),
      entropia_perro_gato(len(dfl[perro & no_busca]),
                          len(dfl[gato & no_busca])),
     ))
Explanation: Evaluemos la pregunta si le gusta jugar al busca
Evaluemos las entropías:
End of explanation
print(entropia_perro_gato(0,6))
print(entropia_perro_gato(6,2))
Explanation: ¿y la comida de gato?
a 0 perros y 6 gatos les gusta la comida de gatos
mientras que a 6 perros y 2 gatos no les gusta.
Su entropía es:
End of explanation
from sklearn.tree import DecisionTreeClassifier
classifier = DecisionTreeClassifier(criterion='entropy')
classifier.fit(dft, labels)
Explanation: y no te olvides de la ganancia de información (information gain)
Ahora sí, el árbol de decisión automático
End of explanation
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12
feat = pd.DataFrame(index=dft.keys(), data=classifier.feature_importances_, columns=['score'])
feat = feat.sort_values(by='score', ascending=False)
feat.plot(kind='bar',rot=85)
Explanation: 5.1 importancia de los features
como vamos a graficar, utilizaremos la función mágica de jupyter 
python
%matplotlib inline
la cual nos permite realizar gráficos en el notebook.
End of explanation
from sklearn.tree import export_graphviz
dotfile = open('perro_gato_tree.dot', 'w')
export_graphviz(
    classifier, 
    out_file = dotfile, 
    filled=True, 
    feature_names = dft.columns, 
    class_names=list(labels), 
    rotate=True, 
    max_depth=None, 
    rounded=True,
)
dotfile.close()
Explanation: 6. Visualizando el arbol, requiere graphviz
conda install graphviz
End of explanation
!dot -Tpng perro_gato_tree.dot -o perro_gato_tree.png 
Explanation: La celda anterior exportó el árbol de decisión creado con sklearn y entrenado con nuestros datos a un archivo .dot
Este archivo lo procesaremos con el comando dot de la terminal. Desde jupyter, podemos ejecutar comandos de terminal sin salir del notebook:
End of explanation
from IPython.display import Image
Image('perro_gato_tree.png', width=1000)
Explanation: finalmente para cargar la imagen usamos:
End of explanation
import numpy as np
np.array(classifier.predict(dft))
np.array(labels)
print('Error rate %0.4f'%((np.array(classifier.predict(dft))==np.array(labels)).sum() / float(len(labels))))
Explanation: 7. Evaluación del modelo
End of explanation
test = pd.read_csv('data/perros_o_gatos_TEST.csv', index_col='observacion')
test
label_test = test['especie']
del test['especie']
ab = vectorizer.transform(test.to_dict(orient='records'))
dftest = pd.DataFrame(ab, columns=vectorizer.get_feature_names())
dftest.head()
list(classifier.predict(dftest))
list(label_test)
print('Error rate %0.4f'%((np.array(classifier.predict(dftest))==np.array(label_test)).sum() / float(len(label_test))))
Explanation: Ahora evaluemos sobre datos nunca vistos por el modelo!!!!!
End of explanation
<END_TASK>