Mi plantilla para Data Science con Python

Artículo actualizado el 26 de Febrero 2020

Durante los últimos años he estado aprendiendo Inteligencia Artificial. Todo este tiempo, mi manera de trabajar ha sido buscar el código que necesito en el momento en que lo necesito, mientras iba haciendo mis proyectos de Data Science. Utilizando la famosa técnica de copy-paste y adaptando el código a las necesidades de mi proyecto.

Siempre he pensado que seria muy útil tener algún tipo de plantilla con todo el código necesario para llevar a cabo un proyecto básico de Data Science de principio a fin. Así que creé una.

En este artículo voy a mostrar mi plantilla de Data Science. Es un archivo de Python con todo el código necesario para un proyecto de Data Science, estructurado de una manera que hace que sea muy fácil de seguir.

Comencemos por el final. Puedes encontrar esta plantilla en mi Github:albertsl/toolkit

Ahora que tienes fácil acceso al código, explicaré cómo está estructurado. Ten en cuenta que seguiré actualizando esta plantilla en Github, pero no actualizaré este artículo muy a menudo, algunas partes de lo que escribo aquí pueden quedar obsoletas.

En primer lugar, seguí la estructura de un proyecto de ciencia de datos que puedes encontrar en el Apéndice B del libro Hands-on Machine Learning with Scikit-Learn and TensorFlow de Aurelien Geron

Tras crear un archivo vacío, siguiendo la estructura descrita en el libro y agregando la mayor parte del texto del Apéndice B como comentarios en el código para darle estructura, empecé a rellenar cada parte del documento con fragmentos de código relevantes. Los fragmentos provienen de muchas fuentes diferentes, del código que escribí para competiciones en las que participé en Kaggle, de otros proyectos, de amigos, de ejemplos en Internet, de libros, etc.

Mientras la creaba, estaba participando en la competición de Kaggle CareerCon 2019 — Help Navigate Robots de la que también escribí un artículo explicando el resultado. Al hacer mis primeras pruebas, decidí seguir la estrategia de fast.ai, lanzar un modelo lo más rápido posible y obtener una métrica de referencia. Haciendo eso, probé el modelo Random Forest y obtuve una accuracy del 39%. Entonces empecé a seguir esta plantilla y llegué al 65% !!!

Vamos a profundizar en el código.

Se pretende que sea un resumen de la estructura, comentando las partes más importantes, solo incluiré en el artículo fragmentos del código para que te sitúes. Si quieres ver el código completo, dirígete a mi Github.

Como siempre, comenzaremos con los imports necesarios:

import numpy as np
import pandas as pd
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)
import matplotlib.pyplot as plt
import seaborn as sns
from tqdm import tqdm
#from tqdm import tqdm_notebook as tqdm
sns.set()
import numba

#También me gusta iniciar el código mostrando las versiones de las librerías con las que trabajo
import platform
print("Operating system:", platform.system(), platform.release())
import sys
print("Python version:", sys.version)
print("Numpy version:", np.version.version)
print("Pandas version:", pd.__version__)
print("Seaborn version:", sns.__version__)
print("Numba version:", numba.__version__)

La mayoría son las típicas librerías para Data Science. Los que vale la pena mencionar son seaborn, una biblioteca de visualización de datos que funciona sobre matplotlib, agregando funcionalidad adicional, diferentes tipos de gráficos y visuales en general más bonitos. Puedes ver más sobre librerías de visualización en este artículo. También vale la pena destacar tqdm, una biblioteca que genera barras de progreso para que puedas ver cuánto tardan en ejecutarse tus funciones.

librería tqdm en funcionamiento
Un ejemplo de la librería tqdm

Seguimos cargando nuestros datos. Puede haber muchas variaciones en este procedimiento dependiendo de cómo estén estructurados los datos, en qué tipo de archivo,… No vamos a cubrir esto aquí, solo el ejemplo más básico:

df = pd.read_csv(‘file.csv’)

Ahora visualizamos nuestros datos para poder ver rápidamente lo que tenemos en nuestras manos:

#Visualize data
df.head()
df.describe()
df.info()
df.columns
df.nunique()
df.unique()
#For a categorical dataset we want to see how many instances of each category there are
df['categorical_var'].value_counts()
#Automated data visualization
from pandas_profiling import ProfileReport
prof = ProfileReport(df)
prof.to_file(output_file='output.html')

#Exploratory Data Analysis (EDA)
sns.pairplot(df, hue='categorical_var')
sns.distplot(df['column'])
sns.countplot(df['column'])
Ejemplo de un pairplot hecho con seaborn
Ejemplo de un pairplot

Data pre-processing

El primer paso tras cargar y visualizar los datos es el pre-processing. Básicamente les estaremos dando a los datos el formato adecuado para entrarlos en el modelo de Machine Learning.

Primero, revisemos los errores en nuestro dataset y hagamos las correcciones oportunas, verifiquemos si hay NaN, números infinitos, valores duplicados, etc.

#Fix or remove outliers
plt.boxplot(df['feature1'])
plt.boxplot(df['feature2'])
plt.scatter('var1', 'y') #Do this for all variables against y

#Check for missing data
total_null = df.isna().sum().sort_values(ascending=False)
percent = 100*(df.isna().sum()/df.isna().count()).sort_values(ascending=False)
missing_data = pd.concat([total_null, percent], axis=1, keys=['Total', 'Percent'])

#Generate new features with missing data
nanf = ['feature1', 'feature2', 'feature3']
for feature in nanf:
	df[feature + '_nan'] = df[feature].isna()

#Also look for infinite data, recommended to check it also after feature engineering
df.replace(np.inf,0,inplace=True)
df.replace(-np.inf,0,inplace=True)

#Check for duplicated data
df.duplicated().value_counts()
df['duplicated'] = df.duplicated() #Create a new feature

Luego pasamos a una fase de feature engineering. No voy a copiar ningún código aquí porque esta sección será totalmente diferente para cada proyecto en el que trabajes, en la plantilla están todas las funciones para feature engineering que he usado en proyectos anteriores. Solo son algunos ejemplos de feature engineering, ya que la cantidad de transformaciones que se pueden realizar es casi infinita y variará dependiendo de su proyecto y los tipos de datos con que se trabaje.

Selección y evaluación del modelo

Tras el pre-procesado de los datos y teniendo los datos en el formato adecuado, podemos comenzar a trabajar con los modelos.

Primero debemos definir una estrategia de validación como K-Fold Cross Validation o dividir el dataset en training/validation. Dependiendo de tu dataset y de tus objetivos, puedes optar por una opción u otra. Aquí está el código para algunas estrategias de validación:

#Train and validation set split
from sklearn.model_selection import train_test_split
X = df.drop('target_var', axis=1)
y = df['column to predict']
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size = 0.4, stratify = y.values, random_state = 101)

#Cross validation
from sklearn.model_selection import cross_val_score
cross_val_score(model, X, y, cv=5)

#StratifiedKFold
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5, random_state=101)
for train_index, val_index in skf.split(X, y):
	X_train, X_val = X[train_index], X[val_index]
	y_train, y_val = y[train_index], y[val_index]

Finalmente, pasamos a entrenar modelos de Machine Learning, podemos probar varios modelos diferentes y evaluar su desempeño comparándolos entre sí, y así podemos elegir los más prometedores. En la plantilla se muestran implementaciones de muchos algoritmos diferentes. No los voy a mostrar todos aquí ya que serían más de 100 líneas de código. Sin embargo, mostraré como ejemplo la implementación de Random Forest, que es uno de los algoritmos más versátiles utilizados en Machine Learning.

#########
# Random Forest
#########
from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier(n_estimators=200, random_state=101, n_jobs=-1, verbose=3)
rfc.fit(X_train, y_train)

from sklearn.ensemble import RandomForestRegressor
rfr = RandomForestRegressor(n_estimators=200, random_state=101, n_jobs=-1, verbose=3)
rfr.fit(X_train, y_train)

#Use model to predict
y_pred = rfr.predict(X_val)

#Evaluate accuracy of the model
acc_rf = round(rfr.score(X_val, y_val) * 100, 2)

Debemos decidir qué métricas de rendimiento utilizaremos para evaluar el modelo. Hay muchas métricas diferentes y, como siempre, dependiendo del problema y los datos, deberemos elegir una u otra o quizás varias de ellas. No escribiré código para esta sección ya que hay una gran cantidad de métricas distintas.

Para llegar a tener un gran algoritmo, podemos ajustar los hiperparámetros sobre los mejores algoritmos que hayamos probado. Este es un ejemplo de cómo hacerlo utilizando un algoritmo de Grid Search.

from sklearn.model_selection import GridSearchCV
param_grid = {'C':[0.1,1,10,100,1000], 'gamma':[1,0.1,0.01,0.001,0.0001]}
grid = GridSearchCV(model, param_grid, verbose = 3)
grid.fit(X_train, y_train)
grid.best_params_
grid.best_estimator_

Ensembling de modelos

Cuando queremos obtener los mejores resultados posibles con unas predicciones, muchas veces nos pueden ayudar los métodos de ensembling. Una vez tengamos un primer modelo funcionando, entrenamos otro (o más) y unimos sus predicciones. Hay diversas técnicas de ensembling y tendrás que probar varias para ver cual se adapta mejor a tu problema. Aquí un ejemplo básico:

#Max Voting
model1 = tree.DecisionTreeClassifier()
model2 = KNeighborsClassifier()
model3 = LogisticRegression()

model1.fit(x_train, y_train)
model2.fit(x_train, y_train)
model3.fit(x_train, y_train)

pred1=model1.predict(X_test)
pred2=model2.predict(X_test)
pred3=model3.predict(X_test)

final_pred = np.array([])
for i in range(len(X_test)):
	final_pred = np.append(final_pred, mode([pred1[i], pred2[i], pred3[i]]))

Interpretabilidad de modelos

Obtener un buen resultado del modelo predictivo no es siempre el objetivo principal. A veces queremos construir un modelo que sea interpretable y podamos sacar conclusiones de él. Por ejemplo si queremos predecir qué pacientes van a sufrir una enfermedad, es muy interesante que aparte de predecir los pacientes, el modelo nos diga porqué estos pacientes van a tener la enfermedad. Es porque el paciente tiene el gen X? O porque le falta vitamina Y?

En la plantilla puedes encontrar código para generar Partial Dependence Plots (PDP), o utilizar los SHAP values entre otras muchas técnicas que nos pueden ayudar a interpretar los resultados de los modelos. Un ejemplo de SHAP Values:

Ejemplo de gráfico de SHAP Values
Ejemplo de gráfico de SHAP Values
import shap
data_for_prediction = X_val.iloc[row_num]
explainer = shap.TreeExplainer(model)  #Use DeepExplainer for Deep Learning models, KernelExplainer for all other models
shap_vals = explainer.shap_values(data_for_prediction)
shap.initjs()
shap.force_plot(explainer.expected_value[1], shap_vals[1], data_for_prediction)

Sección Deep Learning

En esta sección puedes encontrar código para generar redes neuronales de todo tipo. Es un tema muy extenso que da para su propio artículo dedicado únicamente a redes neuronales. No voy a comentarlo en profundidad, simplemente quiero que sepas que está ahí la sección por si la necesitas. Podrás encontrar código para crear redes neuronales con las principales librerías (Tensorflow, Keras y Pytorch)

Natural Language Processing (NLP)

Igual que en la sección anterior, es un tema muy extenso que no voy a comentar en este artículo. Aquí podrás encontrar las principales funciones para separa un texto por palabras y analizarlo aplicando las técnicas más utilizadas en la actualidad.

Mapas y datos geográficos

Llegamos a la sección final de la plantilla. Como en las anteriores secciones, no la voy a comentar en profundidad ya que da para su propio artículo. Podrás encontrar funciones para generar mapas y trabajar con datos geográficos. Puedes aprender más sobre dibujo de mapas en la parte final de este artículo.

Conclusión

Con esto he cubierto todos los pasos que vas a necesitar para la mayoría de proyectos de Data Science. Cada sección debería ampliarse con código específico para cada dataset y problema. Es necesaria tu experiencia y sabiduría para decidir qué pasos se deben seguir y cuáles no, qué pasos deben ampliarse con código específico para el proyecto y qué pasos pueden utilizar el código de la plantilla.

Albert Sanchez Lafuente

Estudié Ingenieria Industrial en la UPC. Continué mi formación en inteligencia artificial junto a la asociación Saturdays.AI de la que he formado parte del equipo de Barcelona desde sus inicios.