Archivo de la categoría: Consultoría

Los parámetros del modelo GLM como relatividades, como recargos o descuentos

Los modelos GLM son muy empleados en el ámbito actuarial para la obtención de modelos de riesgo, estos modelos de riesgo son los elementos fundamentales en el cálculo de tarifas y qué es una tarifa, imaginad el precio del seguro de vuestra vivienda, bueno pues es un cálculo en el que partiendo de un precio base se van añadiendo recargos y descuentos en función del tipo de riesgo que se quiera asegurar (recargos y descuentos en función de los metros cuadrados, de la ubicación de la vivienda de las calidades de construcción….). Esta es una visión muy simplista porque al final se tienen múltiples garantías y es necesaria la combinación de garantías, pero se puede entender de ese modo, un precio base al que recargamos o descontamos precio. Estos recargos y descuentos se denominan frecuentemente relatividades y hoy quiero acercaros a la obtención de esas relatividades y como un modelo GLM se transforma en el precio de un seguro.

En la línea habitual del blog vamos a ilustrar con un ejemplo usando unos datos muy conocidos para el trabajo con GLM y modelos de cálculo de tarifas. El primer paso es cargar el conjunto de datos en nuestra sesión de R:

library(dplyr)

varib <- c(edad = 2L, sexo = 1L, zona = 1L, clase_moto = 1L, antveh = 2L,
           bonus = 1L, exposicion = 8L, nsin = 4L, impsin = 8L)

varib.classes <- c("integer", rep("factor", 3), "integer",
                   "factor", "numeric", rep("integer", 2))

con <- url("https://staff.math.su.se/esbj/GLMbook/mccase.txt")
moto <- read.fwf(con, widths = varib, header = FALSE,
                 col.names = names(varib),
                 colClasses = varib.classes,
                 na.strings = NULL, comment.char = "")

Los datos empleados pertenecen a una cartera de motocicletas, disponemos del número de siniestros (variable nsin), el importe de los siniestros (impsin), la exposición al riesgo de ese registro y una serie de factores que creemos pueden influir en la estimación del número de siniestros o del importe de los siniestros como son la edad, la zona, el nivel de bonificación,… Vamos a partir del modelo más sencillo, un modelo de frecuencia siniestral en base a un factor edad. Si realizamos con R un GLM clásico haríamos:

moto$edad_factor <- case_when(
  as.numeric(moto$edad) <=18 ~ 18, as.numeric(moto$edad) >=60 ~ 60,
  TRUE ~ as.numeric(moto$edad))

moto$edad_factor <- as.factor(moto$edad_factor)

glm.1 <- glm(nsin ~ edad_factor+offset(log(exposicion)), data=filter(moto,exposicion>0),
             family=poisson())
summary(glm.1)

Hemos creado un factor edad que va desde los 18 años hasta los 60, realizamos una regresión de poisson para estimar el número de siniestros, como al final lo que deseamos es crear una proporción de siniestros de la forma nsin/exposición (frecuencia siniestral) lo que hacemos es poner el nsin como variable dependiente y la exposición como variable offset, la única variable regresora es la edad en formato factor, con este modelo obtendremos un estimador para cada nivel del factor. Es un modelo aditivo de la forma log(Y) = B0 + Edad18*B1 + Edad19*B2 + … + log(exp) + Error pero si realizamos el exponencial de los parámetros obtenidos con el modelo tendremos E[Y/exp] = B’0 * Edad18*B’1 * Edad19*B`2 * … Es decir, el valor esperado para la frecuencia siniestral es función de unos parámetros que recargan o descuentan esa frecuencia esperada. Esos B’ que son el resultado de exp(B) es lo que denominamos relatividades. Esto es muy utilizado para la realización de modelos de riesgo en el cálculo de tarifas.

Obtención de las relatividades

Reiterando, el exponencial del parámetro obtenido con la formulación del modelo es lo que denominamos relatividad y esa relatividad multiplicada por un término independiente nos daría como resultado la estimación de la proporción de siniestros, la estimación de la frecuencia siniestral para cada nivel del factor. También es relevante estudiar y comprender como R presenta esos parámetros, si hacemos el exponencial de los parámetros del modelo glm.1 que hemos hecho tenemos:

data.frame(exp(glm.1$coefficients))
              exp.glm.1.coefficients.
(Intercept)                0.02986346
edad_factor19              0.48892314
edad_factor20              0.95974062
edad_factor21              0.73651804
….

¿Qué está pasando con la edad 18? Del término independiente pasa directamente a la edad 19 y de ahí hasta la edad 60, una estimación para cada nivel del factor a excepción del nivel 18. Bien, R considera al primer nivel del factor el nivel base, si lo vemos en forma de estimador un factor toma valor 1 si la observación está en ese nivel del factor y toma 0 si no lo está, pues si todos los estimadores presentes en el modelo toman el valor 0 el modelo estima que la proporción de siniestros en la edad 18 es de 0.02986, R no muestra ese estimador porque directamente no es necesario calcularlo, la edad 18 tiene una frecuencia siniestral del 3% Seguir leyendo Los parámetros del modelo GLM como relatividades, como recargos o descuentos

Los principales problemas de los españoles. Animaciones con R y gganimate

La realización de gráficos animados con R, gganimate y ggplot2 es algo que quiero empezar a trabajar en mis visualizaciones de datos, una buena forma de llamar la atención sobre nuestros gráficos. Para ilustrar el ejemplo he recogido los datos que publica mensualmente el CIS con las 3 principales preocupaciones de los españoles que podéis encontrar en este enlace, por cierto, este enlace tiene toda la pinta de ser una salida en SAS, no me parece muy apropiado pero no diré nada porque imagino que serán lectores del blog (ya podíais hacer una salida más acorde con los tiempos). El caso es que la primera parte de nuestro trabajo será el “scrapeado” de la web. Scrapear verbo regular de la primera conjugación:

library(XML)
library(dplyr)
library(lubridate)

#Lectura de los datos del CIS
url < - "http://www.cis.es/cis/export/sites/default/-Archivos/Indicadores/documentos_html/TresProblemas.html"
doc = htmlParse(url,  encoding = "UTF-8")
tableNodes = getNodeSet(doc, "//table")

#Leemos la tabla que tiene un formato un tanto peculiar
problemas <- readHTMLTable(tableNodes[[2]], skip.rows=1)
problemas <- problemas %>% filter(!is.na(V1)) 

#Transformamos los puntos en 0, parece que estuviera hecho con SAS
haz.cero.na=function(x){ifelse(x==".",0,as.numeric(as.character(x)))}

problemas < - cbind.data.frame(as.character(problemas$V1),sapply(problemas,haz.cero.na),stringsAsFactors=FALSE)
problemas <- problemas %>% select(-V1)

#El primer elemento de la tabla contiene los nombres que vamos a emplear
nombres < - readHTMLTable(tableNodes[[2]])[1,]
nombres$V1="Problema" 
nombres <- as.vector(t(nombres))

names(problemas) <- nombres

#Hay un registro en la tabla que tiene el número de encuestas, no es necesario
problemas <- filter(problemas,Problema != "(N)")

Cosas interesantes en el código. Hacemos HTMLParse de la web y nos quedamos con las tablas, Seguir leyendo Los principales problemas de los españoles. Animaciones con R y gganimate

Crear archivo csv desde SAS con Python

Con la librería sas7bdat de Python podemos leer archivos SAS y crear directamente un data frame, es la mejor librería para hacerlo, si la tabla SAS que deseáis leer está comprimida (compress=yes) con pandas no podréis hacerlo. Pero tengo que agradecer a mi compañero Juan que me haya descubierto la función convert_file para pasar directamente el archivo SAS a csv, es más eficiente y parece que consume menos recursos del equipo. La sintaxis es muy sencilla:

import pandas as pd
from sas7bdat import SAS7BDAT

start_time = time.time()
path_file_sas = '/ubicacion/archivo/sas/tabla_SAS.sas7bdat'
path_file_csv = 'ubicacion/archivo/csv/archivo_CSV.csv'
f = SAS7BDAT(path_file_sas)

f.convert_file(path_file_csv, delimiter=',', step_size=10000)

end_time = time.time()
(end_time - start_time) / 60 

La función convert_file realiza el proceso paso a paso, trozo a trozo, chunk to chunk. Si la tarea la realizas con un equipo esto te permite poder seguir usándolo. Me ha parecido un truco útil para poder importar tablas SAS a Python creando primero un csv, podéis agradecer a Juan.

 

Diagramas de Voronoi con spatial de python

En breve “mis cachorros”, como llamo a un grupo de los mejores Data Scientist de Europa (de los que tengo que hablar algún día) se van a enfrentar a un problema que probablemente tengan que resolver con análisis geométricos muy complejos. Para despertarles la curiosidad (sé que me leen) hoy traigo al blog una entrada que nos aproxima al método de interpolación geométrica más sencillo, al diagrama de Voronoi. Con spatial de scipy podemos trabajar con estos diagramas:

seed(89)
df = pd.DataFrame(np.random.uniform(1,100,size=(20, 2)), columns=list('XY'))
plt.scatter(df.X, df.Y,marker=".")
show()

voronoi_python1

Estos puntos aleatorios en el espacio de 2 dimensiones pueden generar regiones por interpolación y representarlas con voronoi_2d_plot:

from scipy.spatial import Voronoi, voronoi_plot_2d
vor = Voronoi(df)
voronoi_plot_2d(vor)

voronoi_python2

Ahora si queremos determinar si un punto de espacio está dentro de una de las celdas que delimitan los diagramas de Voronoi que hemos creado podemos hacerlo por vecinos cercanos con la función cKDTree:

from scipy.spatial import cKDTree
vor_kdtree = cKDTree(df)
puntos = [[1,100],[100,1]]
test_point_dist, test_point_regions = vor_kdtree.query(puntos,k=1)
test_point_regions

Y ahora viene lo único interesante de esta entrada, ¿cómo identificamos las regiones? No empleéis .regions, emplead:

pd.concat([df,pd.DataFrame(vor.point_region)],axis=1)

Los puntos (1,100) y (100,1) aparecen en la región que nos identifica la posición 19 y 9… Me ha costado tiempo entenderlo, me hago viejo. Saludos.

¿Puede la información de Twitter servir para calcular el precio de tu seguro?

rvaquerizo

Debemos de ir introduciendo el concepto de Social Pricing en el sector asegurador, si recordamos el año pasado Admirall y Facebook tuvieron un tira y afloja por el uso de la información de Facebook para el ajuste de primas de riesgo. Facebook alegaba a la sección 3.15 de su privacidad para no permitir emplear esta información a Admirall. Probablemente es un tema más económico. El caso es que tanto Facebook, como Instagram, como Twitter, como LinkedIn, como xVideos,… tienen información muy interesante acerca de nosotros, información que se puede emplear para el cálculo de primas en el sector asegurador (por ejemplo). No voy a decir como hacer esto, este blog no es el lugar, el que quiera conocer mis ideas que se ponga en contacto conmigo. Yo soy alguien “público”, no tengo problema en dejar mis redes sociales abiertas y este caso me sirve de ejemplo para analizar que dice Twitter de mí y también sirve de ejemplo para refrescar el manejo de información con Twitter con #rstats. Esta entrada es una combinación de entradas anteriores de esta bitácora así que recordemos como empezábamos a hacer scrapping de Twitter:

[sourcecode lang=”R”]
library(twitteR)
library(base64enc)

consumer_key="XXXXXXXXXxxxxXXXXXXXxx"
consumer_secret="xxXXXXXXXXxxXXXXXXXXXxxXXxxxxx"
access_token="81414758-XXxXxxxx"
access_secret="XXXxXXxXXxxxxx"

setup_twitter_oauth(consumer_key, consumer_secret, access_token=access_token, access_secret=access_secret)
[/sourcecode]

Vía Oauth ya podemos trabajar con el paquete twitteR desde nuestra sesión de R y ahora lo que vamos a crear es un objeto R del tipo “user” con la información que tiene el usuario r_vaquerizo (yo mismo):

[sourcecode lang=”R”]
rvaquerizo <- getUser(‘r_vaquerizo’)
rvaquerizo_seguidos <- rvaquerizo$getFriends(retryOnRateLimit=120)
seguidos <- do.call("rbind", lapply(rvaquerizo_seguidos, as.data.frame))
[/sourcecode]

El objeto rvaquerizo tiene mucha información sobre mí Seguir leyendo ¿Puede la información de Twitter servir para calcular el precio de tu seguro?

Ejemplo de web scraping con R. La formación de los diputados del Congreso

No sabía si realizar esta entrada sobre web scraping con R o con python. He obtado por la primera opción porque en un principio era una entrada para ilustrar un ejemplo de web scraping y al final se me están ocurriendo muchas ideas sobre el análisis de la web de Congreso de los diputados y he preferido hacerla con R porque tengo una mayor soltura para hacer distintos análisis. Quería empezar por estudiar la formación que tienen nuestros 350 diputados, para ello se me ocurrió descargarme las líneas que tienen en su ficha de diputado y crear un data frame con los datos personales referentes a su formación. Si entráis en la ficha de cualquier diputado (http://www.congreso.es/portal/page/portal/Congreso/Congreso/Diputados/BusqForm?_piref73_1333155_73_1333154_1333154.next_page=/wc/fichaDiputado?idDiputado=171&idLegislatura=12) veréis que les han dejado un pequeño texto donde describen su hoja de vida. La verdad es que cada uno a escrito lo que le ha parecido pero algún patrón se puede encontrar. Para ilustrar el ejemplo he preferido usar la librería rvest porque me ha parecido una sintaxis más sencilla. Yo no soy un buen programador, incluso soy un poco desastre, hasta guarrete programando y con rvest creo que el código es bastante claro.

El procedimiento para el web scraping será el siguiente:

  1. Identificar en la web del Congreso como funciona el formulario para cambiar de diputado, es sencillo basta con ver el link y tenemos fichaDiputado?idDiputado=171&idLegislatura=12″ es evidente que vamos a crear un bucle con el idDiputado.
  2. Que parte corresponde con el curriculum de cada personaje, esta parte también es sencilla, véis el código fuente y hay un bloque de contenido identificado como
    div id=”curriculum” esta es la parte que nos interesa.
  3. Tenemos que limpiar con alguna función de R el HTML y el texto que estamos “escrapeando”.
  4. Lo ponemos todo en un data frame por si queremos analizarlo.

Esta es la idea y se traduce en R del siguiente modo:

library(rvest)

curriculos = ""
for (dip in seq(1,350,by=1)){
url = paste0("http://www.congreso.es/portal/page/portal/Congreso/Congreso/Diputados/BusqForm?_piref73_1333155_73_1333154_1333154.next_page=/wc/fichaDiputado?idDiputado=",dip,"&idLegislatura=12")

congreso <- read_html(url)
curric <- congreso %>%
html_node("#curriculum") %>%
html_text %>%
strsplit(split = "\n") %>%
unlist() %>%
.[. != ""]
#Pequeña limpieza de texto
curric <- trimws(curric)
#Elimina las líneas sin contenido
curric <- curric[which(curric!="")]
#Nos quedamos justo con la linea que hay debajo de la palabra legislaturas
linea <- curric[grep("legislatura", curric)+1]
curriculos <- rbind(curriculos,linea)}

curriculos <- data.frame(curriculos[-1])

Ya podéis ver que la elegancia programando brilla por su ausencia pero queda todo muy claro. Particularidades, para identificar la formación dentro del texto libre he seleccionado aquellas líneas que están debajo de la palabra legislaturas, no he encontrado mejor forma y soy consciente de que falla, es suceptible de mejora. La función read_html de rvest es la que lee la web, el contenido que nos interesa lo seleccionamos con html_node pero es necesario que sea un texto y por eso aparece html_text  y por último particionamos el texto en función de los /n. Con el texto más o menos formateado pasamos la función TRIMWS que se cepilla los  espacios en blanco, tabuladores y saltos de línea. Tenía que meter esta función con calzador porque me parece útil para limipar textos con R y este ejemplo ilustra el funcionamiento. Para finalizar eliminamos las líneas vacías del texto con Which. Acumulamos las líneas con la formación de cada diputado y creamos el data frame curriculos que contiene lo que ellos han escrito como su formación.

No he trabajado mucho con ello, pero podemos buscar la palabra que más se repite replicando algún código ya conocido:

palabras = strsplit(curriculos, split=" ")
palabras = as.character(unlist(palabras))
palabras = data.frame(palabras)
names(palabras) = c("V1")
palabras$V1 = sub("([[:space:]])","",palabras$V1)
palabras$V1 = sub("([[:digit:]])","",palabras$V1)
palabras$V1 = sub("([[:punct:]])","",palabras$V1)
palabras$largo = nchar(palabras$V1)
palabras = subset(palabras, largo>4)

library(plyr)
conteo = data.frame(ddply(palabras, "V1",summarise, cuenta=length(V1) ))
conteo = conteo[order(-conteo$cuenta),]

Aproximadamente el 28% de los diputados son licenciados en derecho, no veo ingenierías por ningún sitio y muchos casados y ayuntamientos… No voy a valorar lo poco que he explorado pero es evidente que nos representan personas con una experiencia profesional muy acotada en las instituciones públicas (que forma más bonita de decir personas poco productivas). Seguiré escrapeando esta web os lo prometo.

Como obtener los centroides de municipios con SAS. Mapas con SGPLOT

mapa_municipios_sas2

Un amigo y lector del blog me ha pedido un mapa de códigos postales donde poder identificar los centroides para andar calculando distancias a otros puntos. Yo no tengo un mapa de España por códigos postales para poder usar con fines comerciales, pero si cuento en el blog como poder obtenerlo bajo ciertas condiciones. Lo que si puedo contar a Juan es como hacer un mapa por municipios con SAS, aunque ya he hablado de ello hay ciertos aspectos que pueden ser interesantes. y todo empieza donde siempre http://www.gadm.org/country la web donde tenemos los mapas “libres” por países, seleccionáis Spain y el formato shapefile una vez descargados los mapas en vuestros equipos empezamos con el trabajo en SAS:

[source languaje=”SAS”]
proc mapimport datafile="\directorio\mapa\ESP_adm_shp.shp"
out = work.espania;
run;
proc contents;quit;
[/source]

mapa_municipios_sas1

El procedimiento MAPIMPORT ha creado un conjunto de datos SAS donde tenemos caracterizados todos los polígonos que componen el shapefile. Entonces si tenemos que calcular el centroide de un municipio con SAS sugiero realizar un PROC SQL de la siguiente forma Seguir leyendo Como obtener los centroides de municipios con SAS. Mapas con SGPLOT

El parámetro gamma, el coste, la complejidad de un SVM

letra_o_svm_r

Cuando clasificamos datos con SVM es necesario fijar un margen de separación entre observaciones, si no fijamos este margen nuestro modelo sería tan bueno tan bueno que sólo serviría para esos datos, estaría sobrestimando y eso es malo. El coste C y el gamma son los dos parámetros con los que contamos en los SVM. El parámetro C es el peso que le

damos a cada observación a la hora de clasificar un mayor coste implicaría un mayor peso de una observación y el SVM sería más estricto (este link aclara mejor las cosas). Si tuvieramos un modelo que clasificara observaciones en el plano como una letra O podemos ver como se modifica la estimación en esta secuencia en la que se ha modificado el parámetro C:

r_svm_2

Seguir leyendo El parámetro gamma, el coste, la complejidad de un SVM

Trucos Excel. Mapa de Colombia por departamentos

mapa-colombia-excel

El mapa Excel de Colombia por departamentos era tarea pendiente, el modo en el que se ha hecho es el habitual y por ello lo primero que hay que hacer es citar al usuario de Wikipedia  Shadowxfox – Trabajo propio, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=44930910 es importante comentar que seleccioné este por el tamaño, en Excel se ve bien poniendo el zoom al 35% (¡!) y es que cada vez veo menos. El mapa puede representar 5 grupos  y estos grupos se pondrán en la columna C donde podemos cruzar por nombre. Comentar que falta el departamento de las islas de San Andrés y Providencia porque no sabía muy bien como ubicarlas y como representarlas en el mapa. 

Así pues el funcionamiento no puede ser más sencillo. Podemos pintar hasta 5 grupos, los colores de estos 5 grupos los podemos seleccionar en las correspondientes celdas de la columna H. Los grupos que clasifican los departamentos los pondremos en la columna C y con el botón pintar se realiza el mapa. Un esquema de los elementos del Excel Seguir leyendo Trucos Excel. Mapa de Colombia por departamentos

Como salva la linealidad una red neuronal

En los últimos tiempos estoy empeñado en usar redes neuronales para la tarificación en seguros. Históricamente la tarificación de seguros, el pricing, se ha basado en modelos lineales generalizados GLM (sus siglas en inglés) porque su estructura es sencilla, se interpreta bien y no olvidemos que el sector asegurador está regulado y es necesario elaborar una nota detallada de cómo se articula una tarifa y el GLM nos ofrece una estructura multiplicativa que se comprende y con la que los reguladores se sienten muy cómodos. Sin embargo, una red neuronal es el paradigma de “caja negra”, ¿cómo podemos saber que hace esa caja negra? Estoy trabajando en ello, la descripción del funcionamiento de las ponderaciones de una red está muy arriba en la lista de mis tareas pendientes.

Pero esta entrada del blog va encaminada a describir de forma como las neuronas de una red neuronal salvan la linealidad y como un mayor número de neuronas son capaces de ajustar mejor a una estructura compleja y si llegamos a describir como funciona esa estructura compleja podremos usar estas técnicas para realizar tarifas de riesgo. 
Como siempre, para ilustrar el funcionamiento se emplea un ejemplo muy sencillo:
[sourcecode language=”r”]#Variable independiente
indep = runif(500,100,3000)
#Función para crear la variable dependiente
foo = function(x){ mean(x)*(1-sin(-0.006042*x)+sqrt(x/100))
}
dep = sapply(indep,foo)

dep=dep+(runif(length(dep),-500,500))

dep = as.matrix(dep)
indep = as.matrix(indep)
plot(indep,dep)[/sourcecode]

redes_neuronales_tarificacion_seguros1

Creamos unos datos aleatorios que serán en una matriz nuestros datos inependientes y como variable dependiente una variable que dibuja una nube de puntos que simula una curva de observaciones. Si realizamos un modelo lineal se ajustará una recta sobre los datos, una red neuronal mejorará los resultados. Y para demostrarlo vamos a emplear el paquete de R monmlp que realiza un perceptrón multicapa Seguir leyendo Como salva la linealidad una red neuronal