Ejemplo de Procesamiento en R y en Python

Previos.

Hace algunos meses en una competencia en Kaggle se presentó un código para un sistema de recomendación. En general para todo sistema de recomendación la parte más pesada suele ser el procesamiento, es quizás la clave de obtener buenas variables para tener un modelo más robusto. En fin, se pueden combinar muchas técnicas de todo tipo para crear un sistema de recomendación, en otro momento escribiré sobre lo poco que se al respecto.

El sistema de recomendación en el fondo terminaba siendo un problema de clasificación. Hubo diversos acercamiento para abordar el problema, todos con ciertos aspectos en común pero otros radicalmente distintos, les recomiendo leer la descripción de las soluciones las cuales las pueden encontrar aquí.

Lo interesante del código que tomé para esta entrada, me parece que es un buen código, por que es  legible, hacía operaciones de procesamiento estándar, un modelo “sencillo” y daba buen rendimiento ante la métrica de la competencia. El reto que pensé y el origen de esta entrada, fue replicar el procesamiento que se realizaba en R pero ahora en el entorno de Python.

En la migración del código respeté todos los pasos seguidos en el original, hice uso de la memoria de manera similar y trate de hacer la eliminación de tablas de manera igual. Esto para tratar de comparar lo mejor posible el uso de los dos entornos.

La lucha, R vs Python.

El debate entré los enamorados de un entorno y otro, suelen tener discusiones de todo tipo, en general no aportan nada y los argumentos a favor o en contra no tienen ni sentido ni valor alguno al momento de abordar o resolver un problema real.

Nunca el lenguaje será lo más importante si tienes buenas ideas, buenos algoritmos, etc. Hay gente que programa sus redes neuronales directo desde cuda con C o C++, sin pasar por tensorflow o pytorch. Así que esta entrada no es para apoyar un lenguaje u otro, es para comparar algunos aspectos:

  • Legibilidad del código.
  • Eficiencia en líneas de código.
  • Uso de la memoria.

Quizás hay otros criterios o detalles que se pueden comparar, pero creo que con esos basta.

Más detalles pueden ser considerados, pero creo que para limitar y mantener la entrada a un nivel intermedio los tres aspectos cubren lo general del código.

Código

Pego todo el código para su lectura. El código original en R fue escrito por Fabien Vavrand y la versión en Python es mía.

###########################################################################################################
#
# Kaggle Instacart competition
# Fabien Vavrand, June 2017
# Simple xgboost starter, score 0.3791 on LB
# Products selection is based on product by product binary classification, with a global threshold (0.21)
#
###########################################################################################################

Codigo en R

library(data.table)
library(dplyr)
library(tidyr)


# Load Data ---------------------------------------------------------------
path <- "../input"

aisles <- fread(file.path(path, "aisles.csv"))
departments <- fread(file.path(path, "departments.csv"))
orderp <- fread(file.path(path, "order_products__prior.csv"))
ordert <- fread(file.path(path, "order_products__train.csv"))
orders <- fread(file.path(path, "orders.csv"))
products <- fread(file.path(path, "products.csv"))


# Reshape data ------------------------------------------------------------
aisles$aisle <- as.factor(aisles$aisle)
departments$department <- as.factor(departments$department)
orders$eval_set <- as.factor(orders$eval_set)
products$product_name <- as.factor(products$product_name)

products <- products %>% 
 inner_join(aisles) %>% inner_join(departments) %>% 
 select(-aisle_id, -department_id)
rm(aisles, departments)

ordert$user_id <- orders$user_id[match(ordert$order_id, orders$order_id)]

orders_products <- orders %>% inner_join(orderp, by = "order_id")

rm(orderp)
gc()


# Products ----------------------------------------------------------------
prd <- orders_products %>%
 arrange(user_id, order_number, product_id) %>%
 group_by(user_id, product_id) %>%
 mutate(product_time = row_number()) %>%
 ungroup() %>%
 group_by(product_id) %>%
 summarise(
 prod_orders = n(),
 prod_reorders = sum(reordered),
 prod_first_orders = sum(product_time == 1),
 prod_second_orders = sum(product_time == 2)
 )

prd$prod_reorder_probability <- prd$prod_second_orders / prd$prod_first_orders
prd$prod_reorder_times <- 1 + prd$prod_reorders / prd$prod_first_orders
prd$prod_reorder_ratio <- prd$prod_reorders / prd$prod_orders

prd <- prd %>% select(-prod_reorders, -prod_first_orders, -prod_second_orders)

rm(products)
gc()

# Users -------------------------------------------------------------------
users <- orders %>%
 filter(eval_set == "prior") %>%
 group_by(user_id) %>%
 summarise(
 user_orders = max(order_number),
 user_period = sum(days_since_prior_order, na.rm = T),
 user_mean_days_since_prior = mean(days_since_prior_order, na.rm = T)
 )

us <- orders_products %>%
 group_by(user_id) %>%
 summarise(
 user_total_products = n(),
 user_reorder_ratio = sum(reordered == 1) / sum(order_number > 1),
 user_distinct_products = n_distinct(product_id)
 )

users <- users %>% inner_join(us)
users$user_average_basket <- users$user_total_products / users$user_orders

us <- orders %>%
 filter(eval_set != "prior") %>%
 select(user_id, order_id, eval_set,
 time_since_last_order = days_since_prior_order)

users <- users %>% inner_join(us)

rm(us)
gc()


# Database ----------------------------------------------------------------
data <- orders_products %>%
 group_by(user_id, product_id) %>% 
 summarise(
 up_orders = n(),
 up_first_order = min(order_number),
 up_last_order = max(order_number),
 up_average_cart_position = mean(add_to_cart_order))

rm(orders_products, orders)

data <- data %>% 
 inner_join(prd, by = "product_id") %>%
 inner_join(users, by = "user_id")

data$up_order_rate <- data$up_orders / data$user_orders
data$up_orders_since_last_order <- data$user_orders - data$up_last_order
data$up_order_rate_since_first_order <- data$up_orders / (data$user_orders - data$up_first_order + 1)

data <- data %>% 
 left_join(ordert %>% select(user_id, product_id, reordered), 
 by = c("user_id", "product_id"))

rm(ordert, prd, users)
gc()


# Train / Test datasets ---------------------------------------------------
train <- as.data.frame(data[data$eval_set == "train",])
train$eval_set <- NULL
train$user_id <- NULL
train$product_id <- NULL
train$order_id <- NULL
train$reordered[is.na(train$reordered)] <- 0

test <- as.data.frame(data[data$eval_set == "test",])
test$eval_set <- NULL
test$user_id <- NULL
test$reordered <- NULL

rm(data)
gc()


# Model -------------------------------------------------------------------
library(xgboost)

params <- list(
 "objective" = "reg:logistic",
 "eval_metric" = "logloss",
 "eta" = 0.1,
 "max_depth" = 6,
 "min_child_weight" = 10,
 "gamma" = 0.70,
 "subsample" = 0.76,
 "colsample_bytree" = 0.95,
 "alpha" = 2e-05,
 "lambda" = 10
)

subtrain <- train %>% sample_frac(0.1)
X <- xgb.DMatrix(as.matrix(subtrain %>% select(-reordered)), label = subtrain$reordered)
model <- xgboost(data = X, params = params, nrounds = 80)

importance <- xgb.importance(colnames(X), model = model)
xgb.ggplot.importance(importance)

rm(X, importance, subtrain)
gc()


# Apply model -------------------------------------------------------------
X <- xgb.DMatrix(as.matrix(test %>% select(-order_id, -product_id)))
test$reordered <- predict(model, X)

test$reordered <- (test$reordered > 0.21) * 1

submission <- test %>%
 filter(reordered == 1) %>%
 group_by(order_id) %>%
 summarise(
 products = paste(product_id, collapse = " ")
 )

missing <- data.frame(
 order_id = unique(test$order_id[!test$order_id %in% submission$order_id]),
 products = "None"
)

submission <- submission %>% bind_rows(missing) %>% arrange(order_id)
write.csv(submission, file = "submit.csv", row.names = F)

¿Largo y confuso?…calma, explicaré lo que me resulta importante.

Pero ahora veamos el mismo código completo en Python.

###########################################################################################################
#
# Kaggle Instacart competition
# Similary to Fabien Vavrand's script , Ago 2017
# Simple xgboost starter, score 0.3791 on LB
# Products selection is based on product by product binary classification, with a global threshold (0.21)
# 
# Daniel Legorreta 
# 
###########################################################################################################

import numpy 
import pandas
from sklearn.preprocessing import LabelEncoder
import xgboost as xgb

# Load Data ---------------------------------------------------------------
path="../input/"

aisles=pd.read_csv(path+"aisles.csv")
departments=pd.read_csv(path+"departments.csv")
orderp=pd.read_csv(path+"order_products__prior.csv")
ordert= pd.read_csv(path+"order_products__train.csv")
orders= pd.read_csv(path+"orders.csv")
products= pd.read_csv(path+"products.csv")

# Reshape data ------------------------------------------------------------

Factor=LabelEncoder()
Factor.fit(aisles.aisle)
aisles['aisle']=Factor.transform(aisles.aisle)
Factor.fit(departments.department)
departments['department']=Factor.transform(departments.department)
Factor.fit(products.product_name)
products['product_name']=Factor.transform(products.product_name)

products=departments.join(aisles\
 .join(products.set_index("aisle_id"),how="inner",on='aisle_id')\
 .set_index("department_id"),how="inner",on='department_id')

del(products['aisle_id'])
del(products['department_id'])
del(aisles,departments)

ordert=pd.merge(ordert,orders[orders.eval_set=='train'][['order_id','user_id']],how='left',on='order_id')
orders_products=pd.merge(orders,orderp, how='inner',on = 'order_id')

del(orderp)

# Products ----------------------------------------------------------------

Aux_4=orders_products[['user_id','order_number','product_id','reordered']]\
 .assign(product_time=orders_products\
 .sort_values(['user_id','order_number','product_id'])\
 .groupby(['user_id','product_id'])\
 .cumcount() + 1)

prd1=Aux_4.groupby('product_id')\
 .apply(lambda x:pd.Series(dict(prod_orders=x.user_id.count(),prod_reorders=x.reordered\
 .sum(),prod_first_orders=(x.product_time == 1).sum(),prod_second_orders=(x.product_time == 2).sum())))

prd1.loc[:,'prod_reorder_probability']=prd1.prod_second_orders/prd1.prod_first_orders
prd1.loc[:,'prod_reorder_times']=1+prd1.prod_reorders/prd1.prod_first_orders
prd1.loc[:,'prod_reorder_ratio']=prd1.prod_reorders/prd1.prod_orders

prd=prd1.drop(['prod_reorders', 'prod_first_orders', 'prod_second_orders'],axis=1)

del(Aux_4,prd1)


# Users -------------------------------------------------------------------


users=orders[orders['eval_set'] == "prior"]\
 .groupby('user_id')\
 .agg({'order_number':'max','days_since_prior_order':['sum','mean']})

users.columns = ["_".join(x) for x in users.columns.ravel()]
users.columns=["user_orders","user_period","user_mean_days_since_prior"]

us=orders_products[['user_id','reordered','order_number','product_id']]\
 .groupby('user_id')\
 .apply(lambda x:pd.Series(dict(
 user_total_products=np.size(x.product_id),user_reorder_ratio = np.divide((x.reordered == 1).sum(),(x.order_number > 1).sum()),user_distinct_products =np.size(x.product_id.unique()))))

users1=pd.merge(users,us, left_index=True, right_index=True,how='inner')
users1.loc[:,'user_average_basket']=users1.user_total_products / users1.user_orders

del(us)

us=orders[orders.eval_set != "prior"][['user_id', 'order_id', 'eval_set','days_since_prior_order']]\
 .rename(index=str, columns={"user_id": "user_id", "order_id": "order_id","eval_set":"eval_set","time_since_last_order":"days_since_prior_order"}) 
users=pd.merge(users1,us,left_index=True,right_on="user_id")

del(us,users1)


# Database ----------------------------------------------------------------

data=orders_products[["user_id", "product_id","order_number","add_to_cart_order"]]\
 .groupby(["user_id", "product_id"])\
 .agg({'order_number':['min','max','size'],'add_to_cart_order':['mean']})

data.columns = ["_".join(x) for x in data.columns.ravel()]
data.reset_index(level=[0,1], inplace=True)
data.columns =["user_id","product_id","up_first_order","up_last_order","up_orders","up_average_cart_position"]

prd.reset_index(level=0, inplace=True)

data=users.join(prd\
 .join(data.set_index("product_id"),on='product_id',how="inner")\
 .set_index("user_id"),on="user_id",how="inner")

data.loc[:,"up_order_rate"]=data.up_orders / data.user_orders
data.loc[:,"up_orders_since_last_order"]=data.user_orders - data.up_last_order
data.loc[:,"up_order_rate_since_first_order"]=data.up_orders / (data.user_orders - data.up_first_order + 1)

data=pd.merge(data,ordert[["user_id", "product_id", "reordered"]],how='left',on=["user_id", "product_id"])

del(ordert,prd,users)


# Train / Test datasets ---------------------------------------------------

train=data[data.eval_set == "train"]
train=train.drop(labels=['eval_set','user_id','product_id','order_id'], axis=1)
train.reordered=train.reordered.fillna(0)

test=data[data.eval_set == "test"]
test=test.drop(labels=['eval_set','user_id','reordered'], axis=1)

del(data)

# Model -------------------------------------------------------------------
import xgboost as xgb


subtrain=train.sample(frac=0.5)

param={'objective':'reg:logistic','eval_metric':'logloss','eta':0.1,'max_depth':6,'min_child_weight':10,
'gamma':0.7,'subsample':0.76,'colsample_bytree':0.95,'alpha':2e-05,'lambda':10}

X_train =xgb.DMatrix(subtrain.drop( "reordered", axis=1), label = subtrain.loc[:,"reordered"])


num_round = 80
model = xgb.train(param, X_train, num_round)

X_test =xgb.DMatrix(test.drop(labels=['order_id','product_id'],axis=1))

test.loc[:,'reordered']=model.predict(X_test)
test.loc[:,'reordered']=np.where(test.reordered>.21,1,0)


test.loc[:,'product_id']=test.product_id.apply(lambda x: str(x))
Submission=test[test.reordered==1][['order_id','product_id']].groupby('order_id')['product_id']\
 .agg(lambda x: ' '.join(x)).reset_index()

missing=pd.DataFrame()
missing.loc[:,'order_id']=np.unique(test.order_id[~test.order_id.isin(Submission.order_id)])
missing.loc[:,'product_id']=None

test_salida = pd.concat([Submission, missing], axis=0)
test_salida.columns=['order_id','products']

#Out writing
test_salida.to_csv("submit/submit4.csv", index=False)

¿Abrumado?…calma, revisemos los detalles que creo que son importantes.

Lo primero a comentar es que los entornos son sencillos, solo requiere unas cuantas bibliotecas para procesar los datos:

  • Por parte de R: data.table, dplyr, tidyr y xgboost.
  • Por parte de Python: numpy, pandas, sklearn y xgboost.

El que sean unas cuantas bibliotecas creo que me parece un buen ejemplo para comparar los dos entornos. Solo se hace uso de lo mínimo en los dos lenguajes, lo estándar para procesar y para construir un modelo de árboles con xgboost. Posiblemente algunos se confundan con algunas partes, si desean entender mejor todas las secciones podría explicar a detalle  pero creo innecesario para esta entrada.

Legibilidad.

Para se un poco justos, considerando que se tienen un nivel básico de R deberían resultar familiares el uso y funcionalidad de los “símbolos”:$,<-,%>%.

En Python debería resultar familiar la asignación con “=”, el uso de “.” y “\”.

Considerando que se tiene ese conocimiento mínimo, uno puede leer el código, pese a que no se conozcan muy bien las funciones y operaciones que se realizan, a mi parecer desde la parte de “#Reshape data” se empieza hacer un poco más difícil de seguir el código de Python.

Pero la situación se agrava cuando pasamos a leer parte del código donde se hace operaciones del tipo Filter+GruopBy+Agragation. Ejemplo en el siguiente extracto de código de la sección “# Users” en R:

users <- orders %>%
 filter(eval_set == "prior") %>%
 group_by(user_id) %>%
 summarise(
 user_orders = max(order_number),
 user_period = sum(days_since_prior_order, na.rm = T),
 user_mean_days_since_prior = mean(days_since_prior_order, na.rm = T)
 )

Para Python:

users=orders[orders['eval_set'] == "prior"]\
 .groupby('user_id')\
 .agg({'order_number':'max','days_since_prior_order':['sum','mean']})

users.columns = ["_".join(x) for x in users.columns.ravel()]
users.columns=["user_orders","user_period","user_mean_days_since_prior"]

Este tipo de operaciones entran en el marco de “split-apply-combine”, sobre este tipo de operaciones hace tiempo escribí unas entradas con ejemplos de como se utilizan y cual es la idea detrás, se pueden leer aquí. Para más detalles de este tipo de operaciones se puede leer los artículos [1,2].

Las operaciones suelen ser muy usadas en sql (SELECT+WHERE+GROUPBY), lo que observo es que pese a que los dos entornos pueden hacer lo mismo , en R se vuelve más legible este tipo de procesamientos y en Python resulta ser menos claras, necesitas tener en mente otro tipo de conceptos como estructuras de datos básicas, diccionarios,funciones lambda, funciones en Numpy, etc.

Si observamos con cuidado, se hacen las siguientes operaciones: Orden->Filtro->GroupBy->Agregación. Existen en pandas la función “filter”, pero no funcionaría similar al filtro condicional que necesitamos. La agrupación es similar en los dos entornos, pero cuando pasamos a crear las nuevas variables o las “agregaciones” resulta fácil entender el código de R pero la parte de Python termina de parecer un poco extraño.

Si bien las dos últimas líneas son para agregar un prefijo y renombrar las columnas, esto podría realizarse de otro modo, pero de igual forma resulta más “oscuro” que el código de R.

El siguiente fracción en R y Python creo muestra otro aspecto que muestra lo confuso que puede ser Python.

us <- orders_products %>%
 group_by(user_id) %>%
 summarise(
 user_total_products = n(),
 user_reorder_ratio = sum(reordered == 1) / sum(order_number > 1),
 user_distinct_products = n_distinct(product_id)
 )

En Python:

us=orders_products[['user_id','reordered','order_number','product_id']]\
 .groupby('user_id')\
 .apply(lambda x:pd.Series(dict(
 user_total_products=np.size(x.product_id),user_reorder_ratio = np.divide((x.reordered == 1).sum(),\
 (x.order_number > 1).sum()),\
 user_distinct_products =np.size(x.product_id.unique()))))

En mi opinión, es fácil pensar en elegir algunas columnas( filter), agrupar los datos con respecto alguna de las variables (groupby) y construir nuevas variables (summarize) que informen aspectos como la cantidad de objetos, la media, etc. Lo cual debería ser fácil traducir la idea a código.

En Python resulta complejo pensar el proceso, debido a que tienes que pensarlo con un nivel de “programación mayor” que el requerido en R. Tampoco es que sean necesarios aspectos sumamente avanzados de programación, pero pensando en la situación de trabajo estándar en R creo que te focalizas más en el proceso y en Python tienes que pensar un poco más en la programación, además de pensar en el proceso.

En varios pasos en Python tienes que pensar en aspectos delicados como los índices de las tablas y aplicar alguna operación sobre ellos. Esto es bueno y malo, puede frustrar en un inicio pero después con algo de práctica son útiles.

En resumen las operaciones son de un tipo estándar en SQL o el manejadores de bases de datos, el entorno de R resulta fácil y te libera para pensar en procesamiento y dejar un poco de lado la programación, en Python creo que te obliga a contar con una idea de programación más demandante que en R.

¿Cuanto de esto es culpa mía? 

Quizás toda, ya que yo escribí la versión en Python, pero confieso que me esforcé en tratar de escribirlo lo más simple y legible y respetar la estructura original del código en R.

Cantidad de Líneas de Código y Eficiencia.

Si copias y pegas el código, veras que aproximadamente es considerablemente más compacto el código en Python, por aproximadamente unas 30 líneas de código.

Podría parecer poco, pero si piensas en que tienes que hacer 10 códigos similares, estamos hablando de 300 líneas menos. Lo cual puede ser bastante ahorro de trabajo.

En buen parte el uso de “%>%” en R y de “\” en Python, simplifican mucho el trabajo, otro aspecto es que Pandas en si hace cierta programación que se asemeja a la programación funcional y el uso de “.” ahorra muchos pequeños pasos.

La eficiencia, pensándola en el uso de la memoria RAM y del tiempo en el procesamiento resultaba considerablemente mejor en Python. Cuando se cargan las tablas y se hace el procesamiento resultaba mucho mejor manejada la memoria, si observas en el código de Python trato de seguir el mismo uso de memoria para que fuera lo más similar posible.

En mi experiencia, Python me da mejor rendimiento que R cuando trabajo con tablas considerablemente grandes y cuando lanzo algún algoritmo suele ser aún más notorio el rendimiento. Esto es mi caso, quizás a otros les resulta mejor el entorno de R.

Un aspecto extra.

Los join, la secuencia de join o merge, pese a que se pueden realizar las mismas operaciones en los dos entornos me sorprendió que cuando hacer una sucesión de estas operaciones, el orden es el contrario en R que en Python. Ejemplo en la siguiente fracción de código:

products <- products %>% 
 inner_join(aisles) %>% inner_join(departments) %>% 
 select(-aisle_id, -department_id)

Para Python:

products=departments.join(aisles\
 .join(products.set_index("aisle_id"),how="inner",on='aisle_id')\
 .set_index("department_id"),how="inner",on='department_id')

El orden de operaciones es Productos -> aisles->departaments, al final es un inner join de las tres tablas. Cuando realizar esta cadena de operaciones haces uso de los indices de cada tabla, para poder combinarlas,

Se observa que el orden es al revés, podría parecer que fue mi culpa, pero al construir y comparar las tablas que se obtienen la secuencia de operaciones debían de seguir ese orden por la relación entre los índices.

Esto yo lo veo del siguiente modo, la cadena de operaciones en R van de afuera hacia dentro, por otro lado en Python para de adentro hacia afuera.

Puede que exagere, pero mi apreciación es que ciertas operaciones en Pandas son pensadas solo para un par de objetos, cuando pasar de esa cantidad se vuelve menos claro y poco intuitivo. Creo que en Pandas puede resultar quizás mentalmente complejo pensar en esas operaciones de manera “natural”.

Creo que en este código se repite lo mismo, el nivel de codificación requerida en Python resulta mayor que en R, que sea bueno o malo, no lo se, depende de nuestra formación y acercamiento ambos entornos.

Conclusión

El ejercicio de hacer el símil de un lenguaje a otro creo que siempre es bueno, ayuda a ver ciertos detalles nos parecen obvios. En lo personal el rendimiento del código en Python me sorprendió, resultaba muchísimo mejor que el código en R. Desconozco los detalles a bajo nivel como para saber si es por el tipo de operaciones entre tablas, el manejo de los índices o si terminar pagando con rendimiento el que sea más legible que se gana en R.

Espero te sirva el ejemplo para comparar el mismo tipo de operaciones y no está de más quizás hacer el ejercicio en un entorno de scala con spark.

Referencias:

  1. The Split-Apply-Combine Strategy for Data Analysis.
  2. Split-Apply-Combine en Pandas.
  3. Los datos se pueden descargar desde aquí.
  4. Los códigos se pueden descargar desde el repositorio dlegor.
Anuncios

Algo sobre Python, análisis de datos y machine learning.

Sobre Python

Python es un lenguaje de programación interpretado multiparadigma, es decir; soporta hacer programación orientada a objetos y programación imperativa. Su creador fue Guido Van Rossum y debido a que es de código libre la comunidad de desarrolladores han creado librerías o módulos para hacer casi cualquier cosa.

En general la gente que hace análisis de datos o estadística conoce bien R project, SAS o SPSS. Pero pocos se han acercado a Python y sus librerías para análisis de datos, las ventajas pueden ser cuestionables con respecto a software que específicamente fueron diseñados con una perspectiva estadística, pero sin duda la potencia y calidad de librerías es muy buena. Principalmente para hacer uso de algoritmos de Machine Learning las librerías en Python son mejores que las de R project, sobre todo las técnicas de Deep Learning.

Quizás el mejor candidato para comparar el uso de Python en el análisis de datos es R project, por ser software libre y ser actualmente de alta demanda y atracción en la ciencia de datos. Sin embargo, Python siendo realmente un lenguaje de programación, no como R project, no se limita a ser usado  solamente para analizar datos, sino bien puede ser parte de un sistema o para desarrollar un proyecto completo. 

En caso de que resulte complicado hacer implementación de alguna técnica estadística en Python, se puede enviar un Script a R o manipular los datos desde R y extraerlos a Python por medio de la librería rpy2.

Yo siempre recomiendo para aprender a programar Python, por cosas muy sencillas, es fácil de aprender, fácil de leer y cuenta con librerías para hacer de todo. La curva de aprendizaje es muy corta y en muy poco tiempo uno puede estar programando cosas no tan triviales.

En caso de que se use Windows como sistema operativo, que la mayoría lo usa; se debe de instalar y revisar como hacer instalación de módulos, es bastante fácil y en general se hace desde la consola del sistema con el módulo pip. Si se usa Linux, ya no se tienen ningún problema, está por default, solo basta abrir la terminal del sistema para probarlo.

La mejor referencias para revisar información sobre python, tanto para instalar y conocer el lenguaje, es su página oficial:

https://www.python.org/

Para aprender desde cero a programar en este lenguaje existen muchas páginas y libros, pero creo que solo con ejemplos y ejercicios se aprende, recomiendo seguir y revisar el siguiente libro online:

http://learnpythonthehardway.org/book/

Comentario: cada ocasión que pongo un [] con un número me refiero al número de la lista de referencias.

Sobre Machine Learning y data analysis en Python

Considerando que se tiene instalado Python, las librerías necesarias para hacer los ejemplos que comentaré en las entradas de esta categoría son:

  1. Numpy
  2. SciPy
  3. Matplotlib
  4. Pandas
  5. mlpy
  6. scikit-learn
  7. Pybrain
  8. IPython
  9. NLTK

Por supuesto que hay más librerías necesarias, pero las comento conforme se haga uso de ellas.

Algunas de las convenciones que se hace al programar con las anteriores librerías, sobre todo con Numpy, SciPy , Pandas y Matplotlib, son las siguientes:

#Convenciones en los programas para hacer uso de ciertas librerías
import numpy as np
import pandas as pd
import scipy as sp
import matplotlib.pyplot as plt

 Lo único que dice el código anterior es que se hace uso de nombres cortos y que ahora son estándar en los programas que requieren estas librerías.

Suponiendo que ya se tiene instalado python y las librerías, uno puede revisar en la documentación del lenguaje en la página para ver como se hace uso con: números, variables boolenas, cadenas, listas, diccionarios, loops, tuplas, conjuntos, funciones y clases.

Principalmente el manejo de listas y diccionarios es de uso frecuente al analizar datos, ya que en general se usan para implementar algoritmos sobre un conjunto de datos, y cada uno de estas estructuras de datos tienen sus operaciones o métodos.

Si haz leído hasta este punto y te desanima un poco el aprender a instalar cosas, revisar tanta documentación para solo hacer uso de un software. Bueno, una opción es instalar un paquete con todas la librerías más usadas. Pensando en que mucha gente usa Windows y que la gente que usa Mac o Linux tienen familiaridad con instalación de software, entonces explico cuales son las opciones que conozco.

Las opciones son variadas y depende de las preferencias:

1.-Anaconda.

2.-WinonPyth

3.-Conopy

Cada una de estas instalaciones contiene todas la librerías que uso en los ejemplos, con excepción de pybrain y mlpy. Basta con seguir las instrucciones de instalación que son cosa de dar “click”.

Yo recomiendo Anaconda, es la que he estado usando recientemente y creo que está bien en general. Estos paquetes contienen más herramientas de las que menciono en la entrada, se puede revisar dependiendo del interés de cada uno para qué sirve el resto de librerías.

Un poco de NumPy, SciPy y Matplotlib

En el resto de la entrada trataré de explicar algunas operaciones y usos de las librerías Numpy, Scipy y Matplotlib.

Prácticamente son la base para muchas librerías, Numpy permite manipular arreglos o matrices de datos, si se tiene un poco de conocimiento de álgebra lineal resultará natural conocer las operaciones y funciones de dicha librería. Si no se cuenta con conocimiento de álgebra lineal, recomiendo revisar cualquier texto que prefieran ya que el perfil del texto va desde muy abstracto hasta muy aplicado y depende de la formación e interes de cada persona.

Pero es altamente recomendable revisar conceptos de estos temas lo mejor posible, ya que las matrices son el centro de muchos algoritmos de Machine Learning a todos los niveles.

SciPy, concentra varios procesos, algoritmos o técnicas de manipulación numérica; esto va desde el cálculo de la transformada rápida de Fourier, optimización, procesamiento de señales e imágenes.

Si nunca haz escuchado cosas de estos temas, no te desesperes ni mucho menos tienes que saber todo sobre eso, se aprende con la práctica y la formación, hay gente con carreras completas en ingeniería que no entendió la relevancia del análisis de Fourier  u optimización en sus cursos. En buena medida depende del problema que se aborde para hacer uso de alguna herramienta de Scipy.

Por último Matplotlib, es un módulo para gráficar, existe otro módulo para realizar gráficas que es actualmente muy usado, ggplot2. Este último es muy popular en la comunidad de usuarios de R project pero existe también la versión del módulo en Python y depende la preferencia en cuanto al tipo de gráficos al elegir entre Matplotlib o ggplot2.

El nivel de gráficos es bastante bueno y en ocasiones es tan importante hacer una buena gráfica ya que esto clarifica lo que se hace en el análisis de datos o como se presentan los resultados.

En los últimos años de han desarrollado muchos proyectos de visualización de datos y es prácticamente un campo de investigación, ya que al analizar muchos datos es fundamental tener un modo agradable de visualizar lo resultados de los análisis o investigaciones, de modo tal que sea interactivo e informativo [1].

¿Cómo correr el código en sus computadoras?

Los ejemplos los corrí en una sistema Windows con una versión del sistema 8.1 y con la versión de Python 2.7.9, lamento no comentar respecto a como correrlos en Linux, pero es mucho más simple ya que solo se debe de agregar la ruta del directorio en un script y debe de correr el código sin problema alguno.

Entonces pensando en que hay más usuario de Windows que de Linux, los detalles para correr el código que comentaré requiere probar que está bien instalado Python , configurar la variable PATH para correr Python desde la consola cmd o desde powershell y en caso de que se haga uso de Notepad++, se puede configurar para enviar el código a la consola y correrlos en el IDLE.

Creo que la mejor recomendación es empezar a usar Ipython y cualquier editor de texto, puede ser simplemente el notepad, pero se pueden revisar las recomendaciones en el libro online que mencioné anteriormente para ver como correr un scrip de python y como interactuar con el intérprete. Si presionan en la palabra “interprete”, podrán ver una versión online en la página oficial de python.

El párrafo anterior desalienta un poco cuando uno usa R project, por que solo basta instalar R, instalar las librerías y listo. Pero usar Python da oportunidad de conocer mejor como funciona aspecto del sistema, de la creación de una aplicación y de una que otra cosa que termina sirviendo posteriormente.

Lo que suele pasar con los usuarios de R es que pocas ocasiones desarrollan Script de R y los corren en la consola del sistema, el cmd en Windows. Esto cuando uno está iniciando a programar es raro, pero es fundamental para poder ponerse hacer cosas más interesantes.

Algunas aclaraciones, Notepad++ es un editor de texto más amigable y con más detalles y herramientas que el notepad normal, es de libre descarga y entre los lenguajes que soporta está Python. El IDLE que vienen en la descarga usual de Python es sencillo y lo único que se debe de revisar es como crear un archivo para ejecutar el código, que es cosa simple. Por supuesto que existen otros editores de texto y otros IDLE, depende del gusto de cada persona, pero la idea es la misma, de hecho directamente los IDLE no requieren otro editor como  Notepad++, en mi caso es por que me gusta más usar este último que escribir código directamente en el IDLE. Por último, lo recomendable es usar Ipython y una editor de texto eso basta para tener un entorno de trabajo con todos los recursos para trabajar con Python.

Un poco de Numpy

Esta librería permite la manipulación de arreglos o matrices de datos, en consecuencia también el manejo de vectores que son matrices de un solo renglón. Permite hacer operaciones aritméticas sobre todos los elementos de la matriz o elegir entre ellos bajo condiciones pedidas.

Haciendo un poco de memoria sobre las operaciones con matrices en álgebra lineal, en general uno estudia la dimensión de la matriz, la forma, el tipo de entradas que tiene, la multiplicación de matrices por un escalar, la suma y resta de matrices, el cálculo de la inversa de una matriz, el producto de matrices, el producto de una matriz y un vector, el producto interno de dos vectores o de las columnas de una matriz.

Este tipo de operaciones algebráicas se realizan con Numpy; algo extra es la manipulación de las entradas cuando se cuentan con datos NaN o eligiendo aquellas entradas que son mayores o menores algún valor, los que han usado Ovecta o Matlab.

En el código doy algunos ejemplos sencillos.

#Numpy
import numpy as np
#Creamos una matriz
a=np.array([0,1,2,3,4,5])
print 'Presenta la matriz'
print a
print 'Dimension de la matriz'
print a.ndim
#Vemos sus dimesión
print 'Forma de la matriz'
print a.shape
#Vemos su forma, el número de renglones y columnas
print 'Multiplica cada entrada por 2'
print a*2 
#Multiplicamos cada entrada por 2
print 'Eleva a la potencia 2 cada entrada'
print a**2
#Elevamos a la segunda potencia cada entrada
print 'Cambiando la forma del la matriz'
print a.reshape((3,2))
print 'Se imprime cada uno de sus elementos'
print a[0],a[1],a[2]
a[0]=5
print 'Se cambia la primera entrada a[0]=5'
print a
#Creamos una nueva matriz para de más dimensiones
b=np.array([[0,1,2],[3,4,5]])
print 'Se presenta b'
print b
print 'La forma de b'
print b.shape
print 'Se multiplica cada entrada por 3'
print b*3
print 'Se eleva cada entrada al cuadrado'
print b**2
#Se eliminan el vector y la matriz
del(a,b)


#Creacion de matrices especiales

a=np.zeros((3,4))
print 'Se presenta la matriz nula de 3X4'
print a
b=np.ones((2,2))

print 'Se presenta la matriz de unos'
print b
#Se creo la matriz con un valor igual para todas las entradas
c=np.full((3,3),3.5)
print 'Se presenta la matriz con valores iguales en todas las entradas'
print c

#Matriz identidad

d=np.eye(5)

print 'Se presenta la matriz identida de dimension 5'
print d

#Matriz con valores aleatorios

e=np.random.random((7,7))
print 'Se preseta la matri con valores aleatorios en cada entrada'
print e
#Eliminamos las matrices
del(a,b,c,d,e)

#Algunos detalles con los valores de las matrices y asignaciones

a=np.array([[1,2,3],[4,5,6],[7,8,9]])
print 'Se presenta la nueva matriz a'
print a
print 'Se presenta el valor de sus colnas'
print a[:,2]
#Se presentan las dos primeras filas
print 'Se presentan las dos primeras filas'
print a[:2,:]
#Creamos una nueva matriz con las primeras 4 entradas de a

b=a[:2,:2]
print 'Sub matriz de a'
print b
print 'modificamos el valor de b y modifica el de a'
b[0][0]=65
print b
print 'Modifica a'
print a
#esto tiene que ver con la asignación de memoria y de valores por python
del(a,b)
#Operaciones matematicas
a=np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.float64)
b=np.array([[1,0,1],[2,6,1],[1,1,1]], dtype=np.float64)
#Suma de matrices
print a+b
print np.add(a,b)
#Resta de matrices
print a-b
print np.subtract(a,b)

#Multiplicacion
print a*b
print np.multiply(a,b) 

#division
print b/a 
print np.divide(b,a)

#Raiz cuadrada

print np.sqrt(a) 

#Producto interno de vectores y de vectores con matrices

c=np.array([1,0,1])

print 'Produto interno por (1,0,1)'
print a.dot(c)
print 'Producto punto de matrices'
print a.dot(b)

print 'Valores de b'
print b
print 'elecci󮠤e valores mayores a 1'
print b[b>1]
print b>1
print 'Generamos otra matriz con un elemento NAN'
c=np.array([1,2,np.NAN,3,4])
print 'Presentamos su valor'
print c
print 'Identificamos los valores que son NAN'
print np.isnan(c)
print 'Elegimos los valores que no son NAN'
print c[~np.isnan(c)]

Puede haber evitado poner tanto comentario en pantalla o estar enviando “print” en cada orden y comentario, pero los dejo así sobre todo para gente que recién inicia a manejar Python y le pueda causar confusión solo ver números y no saber que cosa es lo que está haciendo.

Si se pone atención el objeto sobre el cual se trabaja siempre es np y de él se toman lo necesitado para crear el tipo de matrices que se desea.

Algunas cosas que no comenté son las operaciones como suma de valores en la columna o de valores en la fila, la transpuesta y el asignar a una matriz para que tengas valores enteros o flotantes. Esto es sencillo y se puede consultar en la documentación de NumPy.

Algo de SciPy y Matplotlib

Como antes mencioné SciPy es una caja de herramientas y Matplotlib es una librería para graficar. Dos ejemplo son los siguientes:

#Dos ejemplos de Scipy
#Cargamos los módulos necesarios
import numpy as np
from scipy import signal, misc
import matplotlib.pyplot as plt

#Se revisará una imagen como dato

image = misc.lena().astype(np.float32)
derfilt = np.array([1.0, -2, 1.0], dtype=np.float32)
ck = signal.cspline2d(image, 8.0)
deriv = (signal.sepfir2d(ck, derfilt, [1]) +
 signal.sepfir2d(ck, [1], derfilt))

#Se calcula el Lapaciano

laplacian = np.array([[0,1,0], [1,-4,1], [0,1,0]], dtype=np.float32)
deriv2 = signal.convolve2d(ck,laplacian,mode='same',boundary='symm')

#Se presenta la imagen
plt.figure()
plt.imshow(image)
plt.gray()
plt.title('Original image')
plt.show()

#Se presenta la imagen modificada por el filtro y la señal gaussiana

image = misc.lena()
w = signal.gaussian(50, 5.0)
image_new = signal.sepfir2d(image, w, w)
plt.figure()
plt.imshow(image_new)
plt.gray()
plt.title('Filtered image')
plt.show()

Las imágenes  que obtenemos es:

figura_1

 

Este es un ejemplo clásico de SciPy, la siguiente foto, generada por las últimas líneas del programa anterior no regresan la siguiente modificación a la imagen. Qué lo que se hace es extraer como señal gaussiana o filtrar la imagen original, esto lo pueden consultar en la página de SciPy o en textos de procesamiento de imágenes.

figure_2

 

Otro ejemplo estándar para usar SciPy es en el análisis de señales.

#Análisis de señales
from numpy import arange, sin, pi, random, array
x = arange(0, 6e-2, 6e-2 / 30)
A, k, theta = 10, 1.0 / 3e-2, pi / 6
y_true = A * sin(2 * pi * k * x + theta)
y_meas = y_true + 2*random.randn(len(x))

def residuals(p, y, x):
 A, k, theta = p
 err = y - A * sin(2 * pi * k * x + theta)
 return err

def peval(x, p):
 return p[0] * sin(2 * pi * p[1] * x + p[2])

p0 = [8, 1 / 2.3e-2, pi / 3]
print(array(p0))


from scipy.optimize import leastsq
plsq = leastsq(residuals, p0, args=(y_meas, x))
print(plsq[0])


print(array([A, k, theta]))


import matplotlib.pyplot as plt
plt.plot(x, peval(x, plsq[0]),x,y_meas,'o',x,y_true)
plt.title('Datos con Ruido')
plt.legend(['Estimacion', 'Ruido', 'True'])
plt.show()

figure_3

Observamos que todo el proceso anterior es el que se realiza para analizar una señal, se considera el ruido, se estima una curva y se analiza con respecto a los datos originales.

Los dos códigos anteriores son ejemplos de como se usa SciPy en dos contextos distintos, uno con números y otro con imágenes. Se puede consultar muchos ejemplos en la página oficial de SciPy y Matplotlib.

El último ejemplo es tomado del texto de “Building Machine Learning Systems with Python” y los datos se pueden descargar desde GitHub. La idea de lo siguiente es graficar el cruce entre datos de tráfico web y semanas, pero además agregar curvas ajustadas a los datos.

#Se simplifica el código presentado en el texto
import os
from utils import DATA_DIR
import scipy as sp
import matplotlib.pyplot as plt

 
sp.random.seed(3) 
data = sp.genfromtxt(os.path.join(DATA_DIR, "web_traffic.tsv"), delimiter="\t")
print(data[:10])
print(data.shape)

#Se separan los datos en x e y
x = data[:,0]
y = data[:,1]
print("Entradas no validas:", sp.sum(sp.isnan(y)))

#Se limpian los datos
x = x[~sp.isnan(y)]
y = y[~sp.isnan(y)]

#Modelo 1
fp1= sp.polyfit(x, y, 1)
f1 = sp.poly1d(fp1)
fx = sp.linspace(0,x[-1], 1000)
#Modelo 2
f2p = sp.polyfit(x, y, 2)
f2 = sp.poly1d(f2p)

#Gráfica de los modelos y los datos 
plt.scatter(x,y)
plt.title("Trafico Web en un Mes")
plt.xlabel("Tiempo")
plt.ylabel("Hits/hora")
plt.xticks([w*7*24 for w in range(10)],['Semana %i'%w for w in range(10)])
#Se agregan los ajustes
plt.plot(fx, f1(fx), linewidth=4)
plt.plot(fx, f2(fx), linewidth=4)
#Se agregan las leyendas de las curvas
plt.legend(["d=1","d=2"], loc="upper left")
plt.autoscale(tight=True)
plt.grid()
#Se muestra la gráfica en la pantalla
plt.show()

La gráfica que se obtienen es la siguiente:

figure_4

Si uno revisa el último código, se observa que se importan las librerías usuales, SciPy y Matplotlib, pero además la librería os (sistema operativo) y un módulo de nombre utils. Existe un módulo en Python con el nombre de utils, pero lo que realmente se está haciendo es usar un script escrito por Willi Richert y Luis Pedro Coelho, que son los autores de dicho libro, para extraer los datos de un archivo con el nombre de “web_traffic.tsv” desde algún directorio. Si solo se descargan los datos y se quiere correr el código en algún directorio  arbitrario donde no se encuentre ni los datos ni el archivo utils, Python no enviará un error.

Entonces para este último ejemplo es recomendable descargar el código en zip de GitHub del libro y revisar el código de los autores del libro.

Con estos ejemplos espero se de una idea de como se hace uso de Numpy, SciPy y Matplotlib. Sobre las otras librerías, en cada entrada comento lo necesario, pero cada uno tienen sus detalles al momento de usarlas. En algunos casos, como IPython, uno puede hacer uso de Python sin recurrir a su uso, pero no esta de más conocer el por qué es altamente usada y ya con la práctica terminaran convencidos de su utilidad.

Referencias:

1.-http://www.creativebloq.com/design-tools/data-visualization-712402

Libros:

1.-http://www.amazon.es/Building-Machine-Learning-Systems-Python/dp/1782161406

2.-http://www.amazon.es/Learning-Python-Mark-Lutz/dp/1449355730/ref=sr_1_1?s=foreign-books&ie=UTF8&qid=1431492984&sr=1-1&keywords=Python

3.-http://www.amazon.es/Python-Data-Analysis-Wrangling-IPython/dp/1449319793/ref=sr_1_6?s=foreign-books&ie=UTF8&qid=1431493009&sr=1-6&keywords=Python

4.-http://www.amazon.es/High-Performance-Python-Performant-Programming/dp/1449361595/ref=sr_1_11?s=foreign-books&ie=UTF8&qid=1431493009&sr=1-11&keywords=Python

5.-http://www.amazon.es/Natural-Language-Processing-Python-Steven/dp/0596516495/ref=sr_1_94?s=foreign-books&ie=UTF8&qid=1431493443&sr=1-94&keywords=Python

6.-http://www.amazon.es/Building-Machine-Learning-Systems-Python-ebook/dp/B00VAG2WU4/ref=sr_1_126?s=foreign-books&ie=UTF8&qid=1431493491&sr=1-126&keywords=Python