Todas las entradas de: rvaquerizo

Inteligencia Arficial frente a un juego de niños. La partícula tonta de Nicolás

Pablo Picasso decía que en aprender a pintar como los pintores del renacimiento tardó unos años pero pintar como los niños le llevó toda la vida y en ocasiones creo que hacemos las cosas difíciles porque nos creemos que hacemos cosas difíciles y entonces llega un niño de nueve años y dice “Papá un punto que primero vaya a la izquierda y luego a la derecha no es tan difícil”.
Os pongo en antecedentes, el pasado 7 de mayo fui al AWS Summit de Madrid porque Sergio Caballero iba a contar uno de los casos de uso. Los de AWS no se deben ni imaginar de las maravillas que ha hecho Sergio en el Ayuntamiento de Alcobendas porque sólo dejaron que hablara 10 minutos, muy torpes ellos, su trabajo es mejor escaparate que el planteado por Mai-Lan Tomsen, un error en el planteamiento de la jornada. El caso es que había una “competición” de vehículos que circulaban por un circuito guiados por complicados algoritmos de inteligencia artificial. Vimos algún “bucanero serio” de alguno de los participantes, ya sabemos reinforcement learning, pero reinforcement reinforcement. Otros participantes más o menos honrosos, en fin, distraído. Viendo la competición me entraron ganas de participar y al llegar a casa me siento a preparar un algoritmo que recorriera el circuito del Jarama de Madrid, no un circuito cualquiera un circuito donde yo he visto ganar carreras a Jorge Martínez Aspar.

Portátil y R, empiezo mi trabajo con imager, busco en la Wikipedia el circuito, lo cargo, genero un data frame, selecciono puntos y comienzo a diseñar mi propia estrategia de reinforcement learning combinadas con técnicas de machine learning, algo como “SVM direccionables” se acerca por detrás mi hijo y me suelta “Papá un punto que primero vaya a la izquierda y luego a la derecha no es tan difícil”. Bueno, pues en 20 minutos sale esto:

De momento no funciona pero no me digáis que no es genial la idea, lo que hace con pocas líneas de código y una consulta en sql. En el repositorio de analisisydecision tenéis el código en R que realiza esta maravilla, he llamado al código partícula tonta y tiene aspectos interesantes en cuanto al uso de la librería imager de R para el tratamiento de imágenes y como transformo una imagen en un data frame de coordenadas y por supuesto la genial idea de Nicolás.

Por cierto, al ver el resultado Nicolás dijo que no sólo derecha e izquierda, también era necesario un arriba y abajo. Tengo abandonado el proyecto, como muchos, pero la anécdota me ayudó en mi trabajo.

Gráfico de correlaciones entre factores. Gráfico de la V de Cramer

Un gráfico muy habitual a la hora de construir modelos de riesgo para el cálculo de tarifas es el gráfico de correlaciones de la V de Cramer que nos sirve para medir la correlación entre factores, entre variables cuantitativas hace muchos años ya escribí sobre el tema. Hoy os traigo la creación de un corrplot con R aplicado a la V de Cramer y además os descubro una función muy elegante para realizar este análisis de correlaciones entre factores, esta función está sacada de stackoverflow (como no) y añado un análisis gráfico que nos permite conocer algunas opciones de corrplot.

 library(vcd)
library(corrplot)
library(tidyverse)

data(mtcars)

#Partimos de una matriz vacía con las dimensiones apropiadas
empty_m <- matrix(ncol = length(correlaciones),
                  nrow = length(correlaciones),
                  dimnames = list(names(correlaciones),
                                  names(correlaciones)))

#Calculamos el estadístico y vamos rellenando la matriz
calculate_cramer <- function(m, df) {
  for (r in seq(nrow(m))){
    for (c in seq(ncol(m))){
      m[[r, c]] <- assocstats(table(df[[r]], df[[c]]))$cramer
    }
  }
  return(m)
}

Lo que hace la brillante función es, partiendo de una matriz cuadrada con los factores, ir rellenando con el correspondiente cálculo de la V de Cramer. El resultado final será igual que una matriz de correlaciones por lo que podremos realizar el gráfico.

predictoras <- c("cyl","vs","am","gear","carb")
correlaciones <- select(mtcars,predictoras)

cor_matrix <- calculate_cramer(empty_m ,correlaciones)
#Ya podemos graficarlo
corrplot(cor_matrix, method="number", is.corr=F,type="upper", diag=F, cl.lim=c(0,1))

remove(correlaciones)

El resultado:

Aspectos interesantes con la función corrplot, con method = "number" sale el valor, no me gustan las bolas, aunque podéis probar con pie, mejor poned is.corr = F con type="upper" sale la parte superior de la matriz, quitamos la diagonal que es 1 con diag=F y la V de Cramer es un valor que va entre 0 y 1 con cl.lim establecemos los límites de la leyenda en el gráfico de correlaciones. A partir de aquí cada uno que establezca un umbral para determinar que dos factores están correlados, yo por ejemplo lo establezco en 0.33, saludos.

Medias ponderadas en Excel. Crear tu propia función

Hace años conocí a una persona que no sabía hacer medias ponderadas con Excel, hoy esa persona es una referencia dentro de este ecosistema de Inteligencia Artificial, Big Data, Machine Learning, Unsupervised Learning,… total, una referencia en la venta de humo porque me imagino que seguirá sin saber hacer una media ponderada en Excel con el SUMAPRODUCTO y por eso realizo esta entrada en homenaje a esas grandes locomotoras que echan humo y más humo pero que ahí siguen. Además también es útil para varias cosas más como:

  • Crear nuestra propia función en Excel
  • Emplear rangos en funciones de Excel
  • Crear sumas acumuladas con un bucle en nuestra función
  • Emplear funciones propias de Excel en nuestra función de visual basic

La función es sencilla y replica la forma habitual de hacer medias ponderadas en Excel con el SUMAPRODUCTO del dato del que deseamos calcular la media por el campo de ponderación dividido por la suma del campo de ponderación:

Public Function MEDIAPONDERADA(Valor As Range, Ponderacion As Range)

 If Valor.Rows.Count <> Ponderacion.Rows.Count Then
 Sample = "Tamaños distintos de rango"
 Exit Function
 
 ElseIf Valor.Rows.Count = 1 Then
 Sample = "Solo un número no se puede"
 Exit Function
 End If

 acum = 0
 For i = 1 To Valor.Rows.Count
   dato = Valor(i) * Ponderacion(i)
   acum = acum + dato
 Next
 
 MEDIAPONDERADA = acum / Excel.WorksheetFunction.Sum(Ponderacion)

End Function

Esta función la ponéis en el libro de macros personal y cuando escribáis =MEDIAPONDERADA ya la tendréis a vuestra disposición en todas las sesiones de Excel. Saludos.

Lectura de archivos csv con Python y Pandas

A continuación os planteo un acercamiento básico a la lectura de archivos csv con Python y algunos trucos para facilitar la vida cuando realizamos importaciones basados en la experiencia como son leer los primeros registros del csv o realizar una lectura de observaciones aleatoria por si el archivo es muy voluminoso. Para realizar las importaciones vamos a emplear Pandas y la función read_csv con sus infititas opciones:

 pd.read_csv(filepath_or_buffer, sep=', ', delimiter=None, header='infer', names=None, index_col=None, usecols=None, squeeze=False, prefix=None, mangle_dupe_cols=True, dtype=None, engine=None, converters=None, true_values=None, false_values=None, skipinitialspace=False, skiprows=None, nrows=None, na_values=None, keep_default_na=True, na_filter=True, verbose=False, skip_blank_lines=True, parse_dates=False, infer_datetime_format=False, keep_date_col=False, date_parser=None, dayfirst=False, iterator=False, chunksize=None, compression='infer', thousands=None, decimal=b'.', lineterminator=None, quotechar='"', quoting=0, escapechar=None, comment=None, encoding=None, dialect=None, tupleize_cols=None, error_bad_lines=True, warn_bad_lines=True, skipfooter=0, doublequote=True, delim_whitespace=False, low_memory=True, memory_map=False, float_precision=None)

Para trabajar la entrada vamos a necesitar dos archivos de texto:

Como costumbre poner la ubicación del archivo y después la lectura:

path = 'C:/temp/'

import pandas as pd
df = pd.read_csv (path + 'index.csv')
df.head()

En este caso la vida es maravillosa y ha salido todo a la primera pero sabemos que eso no pasa siempre, ejecutáis:

df = pd.read_csv (path + 'bank-additional-full.csv')
df.head()

El separador es distinto:

df = pd.read_csv (path + 'bank-additional-full.csv', sep = ';')
df.head()

La vida sigue sin ser muy complicada porque el archivo de ejemplo tiene pocos registros, pero imaginad que leéis unas docenas de GB por ello previamente es mejor ejecutar:

df = pd.read_csv (path + 'bank-additional-full.csv', nrows= 200)
df.shape

con nrows = 200 leemos las primeras 200 líneas y podemos comprobar si lo estamos leyendo correctamente y podemos ahorrarnos disgustos, tiempo y trabajo. E incluso estaría bien no leer las docenas de GB porque no tenemos suficiente memoria o porque no necesitamos leer entero el archivo podemos leer por trozos:

meses = ['may', 'jul']
df = pd.DataFrame()
for trozo in pd.read_csv(path + 'bank-additional-full.csv', sep=';',
                             chunksize=1000):
    df = pd.concat([df,trozo[trozo['month'].isin(meses)]])

df.month.value_counts()

Con chunksize estamos leyendo el archivo csv en trozos (chunks) de 1000 en 1000 y nos quedamos sólo con aquellos que cumplan un determinado requisito, en este caso que el campo month sea may o jul. E incluso podéis leer el csv extrayendo una muestra aleatoria mientras leéis el fichero por partes y no sobre pasar la memoria:

df2 = pd.DataFrame()
for trozo in pd.read_csv(path + 'bank-additional-full.csv', sep=';',
                             chunksize=1000):
    df2 = pd.concat([df2,trozo.sample(frac=0.25)])
df2.shape

Este último truco puede servir para leer csv extremadamente grandes y realizar los primeros análisis aproximativos a nuestro problema porque como dice un buen amigo “si en 200.000 registros no encuentras una señal no hace falta que cargues millones”.

Data Management básico con Pandas

Entrada dedicada al manejo de datos más básico con Python y Pandas, es análoga a otra ya realizada con dplyr para R. Sirve para tener en un vistazo las tareas más habituales que realizamos en el día a día con Pandas. Para aquel que se esté introduciendo al uso de Python puede ser de utilidad tener todo junto y más claro, a mi personalmente me sirve para no olvidar cosas que ya no uso. En una sola entrada recogemos las dudas más básicas cuando nos estamos iniciando con Python. Las tareas más comunes son:

  • Seleccionar columnas
  • Eliminar columnas
  • Seleccionar registros
  • Crear nuevas variables
  • Sumarizar datos
  • Ordenar datos

Para variar vamos a emplear el conjunto de datos iris y que nos descargamos directamente de una url para ello las primeras sentencias que hemos de ejecutar son las siguientes:

import pandas as pd
import io
import requests
url='https://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv'
s=requests.get(url).content
df=pd.read_csv(io.StringIO(s.decode('utf-8')))

Este código es un buen ejemplo de como obtener un csv directamente de una url porque en ocasiones pueden surgir problemas.

Seleccionar columnas con Pandas Python:
Directamente

df2 = df[['sepal_length','sepal_width']]
df2.head()

Mediante una lista, parece más claro.

seleccionadas = ['sepal_length','sepal_width']
df2 = df[seleccionadas]
df2.head()

Eliminar columnas:

df3 = df.drop(columns=['sepal_length','sepal_width'])
df3.head()

Seleccionar registros con Pandas Python:

Con condiciones simples, los operadores se pueden consultar pero no son “extraños”. También se presenta la función value_counts() que es una sumarización muy habitual.

df['species'].value_counts()
df4 = df[df['species']=="setosa"]
df4['species'].value_counts()

Algo que tiene especial relevancia (desde mi punto de vista) son los paréntesis en condiciones complejas o múltiples cuando usamos Pandas.

df5 = df.loc[(df.sepal_length<5) & (df.species=="setosa")]
df6 = df[(df['sepal_length']<5) & (df['species'] != "setosa")]

Particularmente la función isin para hacer condiciones del tipo in en listas la encuentro de mucha utilidad.

lista = ['setosa', 'virginica']
df7 = df[df['species'].isin(lista)]
df7['species'].value_counts()

Crear nuevas variables con Pandas Python:

df['sepal_length_tipi'] = df['sepal_length']/df['sepal_length'].mean()
df['sepal_length_tipi'].describe()

En este sentido destacaría el uso de la función de numpy where, el famoso np.where que trabaja igual que el ifelse de R.

import numpy as np

df['sepal_length_altas'] = np.where(df['sepal_length'] > np.mean(df['sepal_length']),
                                    "Por encima de la media", "Por debajo de la media")
df['sepal_length_altas'].value_counts()

Sumarizar datos con Pandas Python:

df[['sepal_length','species']].groupby('species').mean()
df[['sepal_length','species']].groupby('species').count()

Sumarizar por múltiples columnas tienes que listar variables.

df.groupby(['species','sepal_length_altas']).min()

Ordenar data frames con Pandas Python:

df8 = df.sort_values('sepal_length',ascending=[True])

Si queremos ordenar por múltiples campos del data frame con distintos órdenes:

df9 = df.sort_values(['sepal_length','sepal_width'],ascending=[True,False])

Pero en pocas líneas quedan recogidas las principales tareas con registros y columnas que se pueden hacer en un data frame con Pandas. La siguiente entrada irá encaminada a la unión de data frames con Python y Pandas.

Truco Excel. Producto cartesiano de dos campos

Hacía tiempo que no ponía trucos en Excel y hoy os traigo un truco que puede ser de utilidad cuando tienes que hacer combinaciones. Se trata de realizar el producto cartesiano mediante una macro de Excel, además os pongo el enlace al propio Excel para que podáis rellenar los campos a cruzar. No creo que haga falta describir que es un producto cartesiano de dos campospero de forma resumida se puede decir que es crear el total de pares de esos dos campos, un todos con todos, es útil cuando quieres hacer combinaciones (como ya he dicho). La macro en Visual Basic se podrá hacer mejor, pero a mi se me ha ocurrido hacer un triple bucle, probablemente se pueda hacer con n campos pero si tenéis que realizar productos cartesianos más complejos es preferible que lo hagáis con otra herramienta. El código empleado es este:

Sub ProductoCartesiano()
    
L1 = Range(Range("A2"), Range("A2").End(xlDown)).Rows.Count
L2 = Range(Range("B2"), Range("B2").End(xlDown)).Rows.Count

Cells(1, 4) = Cells(1, 1)
Cells(1, 5) = Cells(1, 2)

i = 2
j = 1


While i <= L1 * L2
While j <= L1
k = 1
While k <= L2

Cells(i, 4) = Cells(j + 1, 1)
Cells(i, 5) = Cells(k + 1, 2)

k = k + 1
If k > L2 Then j = j + 1
i = i + 1

Wend
Wend
Wend
End Sub

Nada emocionante pero tiene su “talento”. Si no queréis complicaros la vida directamente podéis descargar del siguiente enlace la hoja del cálculo que realiza este proceso:

Producto_cartesiano_excelV0

Saludos.

El desarrollo de la Inteligencia Artificial, un paralelismo con el desarrollo de la aviación

Estoy en el claustro de profesores del Máster de la UNED en Big Data y Business Analytics y revisando la documentación, las clases teóricas y las clases prácticas he decidido incluir una diapositiva que establece un paralelismo entre el desarrollo de la Inteligencia Artificial (IA) y el desarrollo de la aviación. Este paralelismo se me ocurrió tras una conversación con una de esas personas que trabajan en este negocio y son íntegras y honradas [empezamos a quedar pocos]. El caso es que he añadido la siguiente imagen en la formación:

A ver, mi trabajo no es hacer diapositivas bonitas. Planteo un paralelismo que, en mi opinión, describe lo que estamos viviendo ahora. Me ahorro a Da Vinci, la Edad Media y los inicios, estamos más avanzados, tenemos Python… El caso es que podemos hacer nuestros primeros trabajos, estamos en disposición de planear, algunos estamos diseñando parapentes para tirarnos por las colinas y otros ya tienen aviones, aviones como los que hicieron los Hermanos Wright, con mucho esfuerzo vuelan. Seguramente en los ámbitos más avanzados estarán trabajando los primeros aviones de guerra como sucedió en la Primera Guerra Mundial. Igual que en la IA, la mayoría estamos agitando los brazos y dando saltitos al tiempo que decimos “mira como hago IA”, hay proyectos donde ya vuelan pero el coste para levantar el vuelo es mucho. Y por supuesto, en el ámbito armamentístico quizá se estén dando los primeros pasos en verdadera Inteligencia Artificial. Es inherente al hombre destruirse por eso imagino que en la Defensa es donde estarán los proyectos más avanzados.

Estos primeros proyectos generarán otros de mayor tamaño que se podrán empezar a utilizar comercialmente por aquellos que dispongan de más m€dio$ como los primeros bombarderos de la segunda guerra mundial que fueron la semilla de la aviación comercial. Sin embargo, necesitamos ese gran invento, ese gran desarrollo para que esto empiece a funcionar y se pueda democratizar, como sucedió con la aparición del avión a reacción, ese, desde mi ignorante punto de vista, es el giro copernicano en la historia de la aviación. En el caso de la IA podría tratarse de la computación cuántica porque a día de hoy con bits, con 010100100010101111 los tensores sólo pueden hacer aviones como los hizo Oscar Wright o buenos aviones de combate si ponemos medios. Con los 01010111 a un tensor le cuesta una cuadrilla de peones mejorar a un cálculo matricial.

Al final todos estos desarrollos se tienen que transformar en dinero, como la aviación empezó a usar la aviación comercial, al principio serán unos pocos privilegiados y luego se democratizará como está pasando con el precio de los billetes de avión que han bajado su precio (ajustado a inflación) un 50% desde 1980. Después, cuando la AI empiece a funcionar, todos tendremos un teclado basado en IA y nos moveremos en coches autónomos y construiremos aberraciones como el Airbus A380 que no irán a ningún sitio.

Con esta opinión no estoy diciendo que no se invierta, al contrario, es necesario invertir para obtener un uso comercial, con esta opinión estoy alertando. No dejéis que os vendan transbordadores espaciales, que el negocio empieza a oler a humo.

¿Qué nos pasa con R? (de nuevo)

Hace años ya sorprendió R situándose muy arriba en la lista tiobe de lenguajes de programación subió en los años siguientes y ahora nos encontramos con una sorprendente bajada en el índice:

¿Volvemos a tener complejo por usar R? A veces tengo la sensación de que no eres un “pro” si no usas Python. Debe ser que determinados framework funcionan mejor en otros lenguajes, o no, pero nos da vergüenza usar R (de nuevo).

Longitud de las frases del Quijote con #rstats

Siempre he querido hacer cosas con Rstats y el Quijote y ayer se me ocurrió medir la longitud de las frases del Quijote y crear un histograma que describa esta longitud. Aunque confieso que no me lo he leído, me he quedado en el capítulo 7 u 8 (no recuerdo) el caso es que me pareció hipnótico con sus ritmos, es musical. Además tengo muchas ganas de meter mano al proyecto Gutemberg porque esos ritmos, esa musicalidad, el uso de palabras esdrújulas,… me llama la atención.
Bueno, al lío, todo el código está subido al repositorio por si lo queréis, pero hay algunas funciones y algunas ideas que me parecen interesantes.

library(dplyr)
library(ggplot2)
library(plotly)

#Leemos el fichero desde proyecto Gutemberg
ubicacion <- "https://www.gutenberg.org/cache/epub/2000/pg2000.txt"
quijote <- read.table (ubicacion,sep="\r", encoding="UTF-8")
quijote <- data.frame(quijote)
names(quijote) <- 'linea'

#Transformaciones e identificar el inicio del libro.
quijote <- quijote %>%
  mutate(linea = toupper(linea),
         inicio = grepl("EN UN LUGAR DE LA MANCHA",linea)>0)

Leemos directamente un txt desde Gutemberg y prefiero transformarlo en data frame para usar dplyr. Todas las palabras las pongo en mayúsculas e identifico donde empieza el Quijote, para evitar prólogos y demás. Ya tengo unos datos con los que poder trabajar:

#Marcamos lo que vamos a leer
desde <- which(quijote$inicio)
hasta <- nrow(quijote)

#Texto de trabajo
texto <- quijote[desde:hasta,1]

#El texto lo transformamos en una lista separada por espacios
texto_split = strsplit(texto, split=" ")

#Deshacemos esa lista y tenemos el data.frame
texto_col = as.character(unlist(texto_split))
texto_col = data.frame(texto_col)
names(texto_col) = 'palabra'

En este caso los datos los quiero de tal forma que disponga de un data frame con una sola variable que sea cada palabra del Quijote. Ahora voy a medir las frases identificando donde hay puntos en esas palabras:

#Identificamos donde tenemos puntos y un autonumérico del registro
texto_col <- texto_col %>% filter(!is.na(palabra)) %>%
  mutate(punto = ifelse(grepl('.',palabra,fixed=T),"FIN","NO"),
         posicion = row_number())

¿Qué se me ha ocurrido? Trabajar con autonuméricos, tengo identificados los puntos, ahora tengo que fijar una posición inicial y una posición final:

#Si unimos las posiciones con puntos con lag podemos calcular la longitud
pos_puntos1 <- filter(texto_col,punto=="FIN") %>% 
  select(posicion) %>% mutate(id = row_number())

pos_puntos2 <- pos_puntos1 %>% mutate(id = id + 1) %>%
  rename(posicion_final = posicion)

pos_puntos <- left_join(pos_puntos1,pos_puntos2) %>%
  mutate(longitud = ifelse(is.na(posicion_final), posicion, posicion - posicion_final))

Como no soy un tipo muy brillante opto por una opción sencilla de cruzar una tabla consigo misma, como me ponen los productos cartesianos “con talento”. La idea es seleccionar solo los registros que marcan el final de la frase, un autonumérico me marca cual es cada frase, ahora si hago una left join por el id de la frase y el id + 1 de la frase creo una especie de lag. La longitud de la frase será donde está el punto menos donde estaba el final de la anterior frase. Creo que me he explicado de pena, pero si veis el data frame final lo entenderéis mejor. Ahora ya pinto un histograma:

#GRaficamos la longitud
plot_ly(data = pos_puntos, x = ~longitud, type = "histogram") %>%
  layout(title = "Longitud de las frases del Quijote",
         xaxis = list(title = "Longitud"), yaxis = list(title = ""))

Y queda una gamma perfecta, yo diría que hasta bonita. Ahora quedaría identificar los parámetros de esta gamma y compararlos con otros libros, e incluso comparar lenguas. Pero esas tareas se las dejo a los “buenos”.