Archivo de la categoría: R

Los pilares de mi simulación de la extensión del COVID19

No debería publicar esta simulación de la extensión del CODVID10 o coronavirus porque puede disparar alarmas, provocar insultos, levantar ampollas,… el caso es que yo llevo 7 días de aislamiento más que el resto de España porque sólo había que ver los datos de Italia para saber lo que iba a pasar y no avisé a nadie para no disparar alarmas, provocar insultos, levantar ampollas… Y AL FINAL YO TENÍA RAZÓN. Así que os voy a exponer el motivo por el cual estoy muy asustado, bueno, hoy quiero mostraros el inicio de una simulación mala y sin fundamento que estoy realizando sobre la extensión en España del COVID19. Para hacerla vamos a emplear la siguiente información:

Y allá voy a comentaros que estoy montando. Se trata de poner a los 47 millones de españoles en una tabla, situarlos en unas coordenadas y dadas 5 personas iniciales ver como se propaga el virus municipio a municipio y, en 98 días, determinar cuantas personas pueden estar contagiadas, cuantas enfermas, cuantas sanas o cuantas desgraciadamente muertas. Esto no es que tenga lagunas, es que está inventado, pero no os creáis que las cifras oficiales son más fiables. Evidentemente, lo voy a hacer con R y dplyr. No lo subo a git porque el equipo que uso tiene un usuario de github que no es el adecuado, pero ya sabéis que el código está a vuestra disposición.

Creación de la tabla de personas edad

library(tidyverse)
library(pxR)
library(sqldf)

#detach("package:dplyr", unload=TRUE)

censo = "C:\\temp\\personales\\covid19\\0001.px"
datos <-  read.px(censo)
datos <- datos$DATA[[1]]
names(datos) = c("rango_edad", 'seccion', 'sexo', 'habitantes' )
datos <- data.frame(lapply(datos, as.character), stringsAsFactors=FALSE)

muestra <- datos %>% mutate(habitantes=round(as.numeric(habitantes)/10,0)) %>% 
  filter(seccion != "TOTAL" & sexo == "Ambos Sexos" & rango_edad != "Total") %>% select(-sexo) %>% 
  mutate(rango_edad = case_when( 
    rango_edad %in% c('0-4', '5-9', '10-14', '15-19', '20-24') ~ '<25',
    rango_edad %in% c('80-84', '85-89', '90-94', '95-99', '100 y más') ~ '80 >',
    TRUE ~rango_edad  )) 

muestra <- muestra %>% group_by(seccion,rango_edad) %>% summarise(habitantes=sum(habitantes))


espania <- muestra %>% group_by(seccion,rango_edad) %>% expand(count = seq(1:habitantes)) %>% as_tibble()

Nota: si no funciona la creación de la muestra hacéis detach de dplyr

Leemos los datos del censo que nos hemos descargado del INE, es un fichero px pero con el paquete pxR podemos manejarlo. Los datos que tenemos están a nivel de sección censal, rangos de edad, sexo y disponemos del número de habitantes. Con esta tabla de frecuencias generamos con expand una tabla que repite un registro tantas veces como digamos en una variable, es decir, repetirá la edad, el sexo, la sección censal tantas veces como habitantes tenga. Manejo una muestra del 10% porque el tema tiene un tiempo importante de procesamiento. Con esto también hago un llamamiento por si Amazon, Microsoft o Google pueden poner buenas máquinas en manos de los gestores de información (mal llamados científicos de datos ahora) de forma altruista. En fin, tenemos a todos los españoles, ahora vamos a ubicarlos con la cartografía por sección censal del INE.

Creación de la tabla de centroides municipal

library(maptools)
library(sf)
ub_shp = "C:\\temp\\mapas\\Seccion_censal\\SECC_CPV_E_20111101_01_R_INE.shp"
seccion_censal = readShapeSpatial(ub_shp)

mapa <- map_data(seccion_censal)

centroides <- mapa %>% group_by(OBJECTID = as.numeric(region)) %>% 
  summarise(centro_long=mean(long), centro_lat=mean(lat))

ggplot(data = centroides, aes(x = centro_long, y = centro_lat, group = 1)) +
  geom_polygon() 

secciones <- seccion_censal@data %>% mutate(seccion=as.character(CUSEC), municipio=as.character(CUMUN)) %>%
  select(OBJECTID,seccion,municipio)

municipios <- left_join(secciones,centroides) %>% group_by(municipio) %>% 
  summarise(centro_long=mean(long), centro_lat=mean(lat)) %>%
  select(municipio, centro_long, centro_lat)  

#Matriz de distancias
distancias <- sqldf(" select a.municipio, b.municipio as municipio2, 
                    a.centro_long, a.centro_lat, b.centro_long as centro_long2, b.centro_lat as centro_lat2
                    from municipios a , municipios b where a.municipio != b.municipio")

distancias <- distancias %>% mutate(distancia=sqrt((centro_long - centro_long2)**2 + (centro_lat-centro_lat2)**2))

Os habéis desgarcado el shapefile con las secciones censales de España y con ella calculamos el centroide de cada municipio, también he calculado una matriz de distancias porque, como veréis más adelante, la distancia de desplazamiento puede ser interesante para determinar como se mueve y como se expande el virus. En este punto está mi otra de mis reclamaciones, las compañías de telefonía podían ofrecer datos de movilidad para ayudarnos y controlar el movimiento de personas.

En fin, si cruzáis ambas tablas empieza la simulación (de mierda):

#Proceso
indices <- sample( 1:nrow( espania ), nrow(espania)/2)
espania2 <- espania[indices,]
espania2 <- espania2 %>% left_join(secciones) %>% 
  mutate(id_persona=row_number(),
         dia=0,contagiado=0, evolucion_dias=0)

sanos = espania2
contagiados = espania2[0,]
enfermos = espania2[0,]
curados = espania2[0,]
muertos = espania2[0,]

#Dia 1
#5 contagiados
dia <- sample_n(filter(espania2,seccion %in% c('2807908161','0810205003')) , 5)
contagiados <- inner_join(dia, select(sanos,id_persona)) %>% 
  mutate(contagiado=1)
lista_contagiados = unique(contagiados$id_persona)
sanos <- sanos %>% filter(id_persona %notin% lista_contagiados)

max_distancia =max(distancias$distancia,na.rm = T)

Tenemos una tabla con la población española por edad y ubicación, son 5 personas al azar de Igualada y Madrid las que empiezan todo… Veré si me atrevo a seguir contando porque lo que sigue me lo he inventado completamente.

Evolución del número de casos de coronavirus

Seguimos a vueltas con la (ya) pandemia y R y hoy quería traeros unos buenos ejemplos de uso de la librería dplyr para preparar datos. Se trata de ver una evolución del número de casos diarios para saber en qué punto tanto España como Italia pueden frenar el crecimiento de los casos de coronavirus, se trata de crear este gráfico:

Se observa como países como China o Korea vivieron un fuerte crecimiento que ahora se ha transformado en una caída del número de casos de coronavirus, pero parece que Irán ha estabilizado en 1000 casos diarios pero Italia y España siguen en fase de crecimiento por lo que no se espera que el comportamiento sea similar a China o Korea y es probable que el número de casos siga aumentando.

Para crear este gráfico estoy mejorando códigos que ya he venido utilizando y creo que son un buen ejemplo de uso de dplyr. El primer paso es crear el conjunto de datos inicial, código ya conocido:

library(lubridate)
library(ggplot2)
library(dplyr)
library(gridExtra)
datos <- read.csv2("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_19-covid-Confirmed.csv",
                   sep=',')


fechas <- seq(as.Date("2020/01/22"), as.Date(today()-1), "days")
fechas <- substr(as.character.Date(fechas),6,10)
names(datos) <- c("Provincia", "Pais","Latitud", "Longitud", fechas)

Para ver los datos de China es necesaria una agregación previa ya que China tiene los datos a nivel de región y no de país:

China <- filter(datos,Pais=="China")
China <- China %>% select(fechas) 
China <- data.frame(casos=as.numeric(mapply(sum,China)))
China <- China %>% mutate(fecha=row.names(China), dias=row_number())
China2 <- China %>% mutate(dias=dias+1) %>% rename(casos_menos1=casos) %>% select(-fecha)

China <- left_join(China,China2) %>% 
  mutate(incremento = casos-casos_menos1,
         incremento = case_when(is.na(incremento) ~0,
                                TRUE ~ incremento)) %>% 
  filter(dias<=50)

Aquí es interesante el uso de mapply para sumar todas las columnas del conjunto de datos. Por otro lado se trata de crear una variable incremento en función de los días que llevamos recogiendo datos, para ello lo que se hace es cruzar con los mismos datos pero sumamos un día al número de días de pandemia, de este modo, tenemos el dato del día y el dato del día anterior por lo que podemos crear una variable incremento. Para el resto de países, como la información no está a nivel de región, hacemos una función.

select_pais <- function(pais, numdias) {
  P <- filter(datos,Pais==pais) %>% select(fechas)
  P <- P %>% mutate(fecha=row.names(P), dias=row_number())
  P <- data.frame(casos=as.numeric(t(P)))
  P <- P %>% mutate(fecha=row.names(P), dias=row_number())
  P2 <- P %>% mutate(dias=dias+1) %>% rename(casos_menos1=casos) %>% select(-fecha)
  P <- left_join(P,P2) %>% 
    mutate(incremento = casos-casos_menos1,
           incremento = case_when(is.na(incremento) ~0,
                                  TRUE ~ incremento)) %>% 
    filter(dias<=numdias)
  return(P)}

Korea <- select_pais('Korea, South', 50) 
Iran <- select_pais('Iran', 50) 
Italia <- select_pais('Italy', 50) 
Espania <- select_pais('Spain', 50) 

Ya tenemos los datos con la forma deseada. Ahora nos toca realizar un gráfico para cada país:

evolucion <- function(pais) {
  df <- pais
  df$incremento <- ifelse(df$casos<=4,0,df$incremento)
  ggplot(df, aes(x=dias, y=incremento)) +
  geom_bar(stat="identity") + 
  scale_y_continuous(limits = c(0, 3000)) +
  ggtitle(paste0('Incremento de casos en ',deparse(substitute(pais)) )) +
  theme(plot.title = element_text(hjust = 0.5)) +
  xlab("Días desde 22/01/2020")}

p1 = evolucion(China)
p2 = evolucion(Korea)
p3 = evolucion(Espania)
p4 = evolucion(Italia)
p5 = evolucion(Iran)

Una función que realiza un gráfico de barras con ggplo2 para cada país y tiene un uso interesante de la función deparse que junto con sustitute nos permite poner en la función el nombre del data frame y no los datos que contiene. Es una forma sencilla de obtener el nombre de un data frame en una función. Si leéis que me reitero en algunas frases no os asustéis, sirve para facilitar búsquedas.

Ahora empleamos una librería que cambió mi vida gridExtra y podemos realizar el gráfico que abre esta entrada:

grid.arrange(p1, p2,p3,p4,p5,ncol=1)

En este punto sólo queda ejecutar este código todos los días y esperar a que Italia y España lleguen a ese máximo. Saludos.

Seguimiento del coronavirus en España por Comunidad Autónoma. Extraer información de un PDF con R

Una entrada anterior del blog ha dado lugar a una conversación interesante en twitter:

Es necesario obtener los datos del Ministerio y estos datos se hayan en un pdf (https://www.mscbs.gob.es/profesionales/saludPublica/ccayes/alertasActual/nCov-China/documentos/Actualizacion_41_COVID-19.pdf) Bien, tendremos que leer el pdf y crear un data frame para poder trabajar con estos datos. Para leer el pdf vamos a emplear el paquete de R tabulizer y la función extract_table pero necesitamos “algo de talento”.

La función extract_tables nos permite extraer información de un archivo pdf con R pero es necesario especificar la página que deseamos leer (fácil) y el área que deseamos leer. ¿El área de la hoja del pdf que deseamos leer? ¿Eso qué es? Pues ese es el “talento” que necesitamos, con la función locale_areas vamos a encontrar ese área. Veamos el código necesario:

library(tidyverse)
library(tabulizer)

ministerio = "https://www.mscbs.gob.es/profesionales/saludPublica/ccayes/alertasActual/nCov-China/documentos/Actualizacion_41_COVID-19.pdf"

area <- locate_areas(ministerio, pages = 2)

Al ejecutar ese código podemos seleccionar a mano alzada el área que deseamos seleccionar de la página específica del pdf:

Ya estamos en disposición de ver el área a leer:

area[[1]]

Un poco complicado, pero una vez sabemos el área crear un objeto con R que contenga la información actualizada por Comunidad Autónoma de los datos del coronavirus en España con R es así de sencillo:

pdf_lista <- extract_tables(
  ministerio,
  output = "data.frame",
  pages = c(2),
  area = list(
    c(337.89431,  90.69972, 684.23597, 510.25186)
  ),
  guess = FALSE,
  encoding = "UTF-8"
)


datos <- data.frame(pdf_lista[1])

Ahora ya tenéis los datos por Comunidad Autónoma actualizados, solo queda que alguien los tabule por día y haga representaciones gráficas. Ahora a esperar que el Ministerio no cambie el pdf. Mañana haremos algo con ellos.

Estimación de la evolución de casos del coronavirus en España

Ayer escribrí sobre la obtención de los datos del coronavirus con R y después me disponía ha escribir sobre modelos de regresión no lineal, hacer una estimación del coronavirus en España,… Pero estuve hablando con una amiga residente en Italia y allí el número de casos está dos semanas por delante de España, bueno, dos semanas exactamente no, 10 días:

library(lubridate)
library(ggplot2)
library(dplyr)
library(reshape2)
datos <- read.csv2("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_19-covid-Confirmed.csv",
                   sep=',')

fechas <- seq(as.Date("2020/01/22"), as.Date(today()-1), "days")
fechas <- substr(as.character.Date(fechas),6,10)
names(datos) <- c("Provincia", "Pais","Latitud", "Longitud", fechas)

esp_ita <- data.frame(fecha=fechas)
esp_ita <- cbind.data.frame(esp_ita, Espania = t(datos %>% filter(Pais=="Spain") %>% select(fechas)))
esp_ita <- cbind.data.frame(esp_ita, Italia = t(datos %>% filter(Pais=="Italy") %>% select(fechas)))

p <- ggplot(esp_ita, aes(x=fecha)) + 
  geom_line(aes(y=Espania, group = 1, color="España")) + 
  geom_line(aes(y=Italia, group = 1, color="Italia")) + 
  scale_color_manual(values = c("España" = "red", "Italia" = "blue")) +
  xlab("") + ylab("")
p

A la vista de las dos evoluciones hace pensar que el número de casos en España siga la misma evolución que sigue en Italia, por eso en este caso en vez de manejar fechas vamos a manejar días de evolución y al dato de España vamos a quitarle 10 días:

esp_ita <- data.frame(fecha=fechas) 
esp_ita <- esp_ita %>% mutate(dia=row_number())
Italia <- data.frame(Italia=t(datos %>% filter(Pais=="Italy") %>% select(fechas)))
Italia <- Italia %>% mutate(dia = row_number())

Espania <- data.frame(Espania=t(datos %>% filter(Pais=="Spain") %>% select(fechas)))
Espania <- Espania %>% filter(row_number() >= 10) %>% mutate(dia=row_number())

esp_ita <- esp_ita %>% left_join(Espania) %>% left_join(Italia)

p <- ggplot(esp_ita, aes(x=fecha)) + 
  geom_line(aes(y=Espania, group = 1, color="España")) + 
  geom_line(aes(y=Italia, group = 1, color="Italia")) + 
  scale_color_manual(values = c("España" = "red", "Italia" = "blue")) +
  xlab("") + ylab("")
p

Mi estimación para los próximos 10 días sobre la evolución del coronavirus en España está clara, ahora solo queda que no salga el ejército a la calle como en Milán.

Cloud words con R. Trabajar con la API del Europe PMC con R

Hace años ya tuvimos nubes de palabras en el blog y ya era hora de ir actualizando algunas cosas. Y además quería aprovechar y presentaros un paquete de R que nos permite consultar la API del Europe PMC. Para quien no sepa que es el Europe PMC podemos decir que es un un buscador de documentos y artículos científicos (que ahora todo el mundo molón llama papers) y que tiene una API desde la que podemos acceder desde R mediante el paquete europepmc.

Obtener datos de la API de Europe PMC con R

El primer paso para trabajar con la librería de R europepmc sería obtener el número de artículos publicados con el topic “Autism” en su descripción, para ello podemos hacer:

#install.packages("europepmc")
library(dplyr)
library(ggplot2)
library(europepmc)

autismo <- epmc_hits_trend("Autism", period = 2000:2019, synonym = TRUE)
autismo
ggplot(autismo, aes(year,  query_hits)) + 
  geom_line() +
  xlab("Año de Publicación") + 
  ylab("Articulos publicados sobre Autismo")

La función del paquete de R europepmc epmc_hits_trend obtiene de un periodo dado el número de artículos y el número de artículos de un determinado tópico. Como vemos se está incrementando casi exponencialmente el número de artículos dedicados al Autismo en los últimos años, no es una tendencia particular, se está incrementando el número de papers (asco de término pero que tiene que aparecer para las búsquedas), estamos ante la “burbuja de los papers” ya que da mas notoriedad hacer mucho que hacer bien, ya se lo leerá una inteligencia artificial, en fin que me disperso. Pero la función que puede resultarnos más interesante es epmc_search que crea un data frame con las búsquedas de los artículos con su ISBN, fechas de referencia, autores,… nos va a servir para obtener unas nubes de palabras sobre los escritos acerca del autismo Seguir leyendo Cloud words con R. Trabajar con la API del Europe PMC con R

Gráficos de calendarios con series temporales

Cuando se realizan gráficos de series temporales se emplean gráficos de líneas donde el eje X contiene la fecha y el eje Y contiene el valor a representar. Hoy quiero traer al blog otra forma de representar series temporales, los gráficos de calendario y su realización con R. Para ilustrar el ejemplo vamos a emplear las cotizaciones históricas del índice bursatil IBEX35:

require(quantmod)
require(ggplot2)
require(reshape2)
require(dplyr)
library(lubridate)

# Obtenemos las cotizaciones del IBEX 35 desde 2010
getSymbols('^IBEX', from = '2010-01-01')

# data frame de trabajo
df<-data.frame(date=index(IBEX),IBEX)

Mediante quantmod extraemos las cotizaciones del IBEX desde 2010 y creamos un data frame de trabajo que llamamos df. Vamos a realizar dos tipos de gráficos, un mapa de calor por años, meses, semanas y días y un calendario de un año puntual.

Calendario como mapa de calor por

Este es un gráfico basado en un trabajo anterior (¡de 2012!) y es una forma imaginativa de representar el cierre del IBEX 35 desde 2010 en una sola imagen. El primer paso será crear las variables a representar en el mapa de calor, el mes, el día de la semana y la semana dentro del mes:

df <- df %>% mutate(año=year(date),
                    mes=factor(month(date),levels=(1:12),
                               labels = c("ENE","FEB","MAR","ABR","MAY","JUN","JUL",
                                              "AGO","SEP","OCT","NOV","DIC"),ordered = T),
                    dia=factor(wday(date)-1,levels=rev(1:7),
                          labels=rev(c("L","M","X","J","V","S","D"))),
                    semanames=ceiling(day(date) / 7))

Ahora sólo queda representar el gráfico mediante ggplot2 donde los paneles de facet_grid serán los años en eje X y los meses en eje Y:

# Realizamos el calendario
calendario1<- ggplot(df, aes(semanames, dia, fill = IBEX.Adjusted)) + 
  geom_tile(colour = "white") + facet_grid(año~mes) + 
  scale_fill_gradient(low = "red", high = "darkgreen", na.value = "black") +  
  labs(title="Cierre histórico del IBEX", x ="Semana del mes", y = "")
calendario1

Un gráfico que me gusta bastante y una original forma de representar series temporales muy largas, no he usado paletas de colores pero imagino que los resultados mejorarán, podéis aportar esas mejoras en los comentarios.

Calendario con openair y calendarPlot

Si deseamos representar un calendario de un año concreto tenemos la función calendarPlot de openair (que me ha costado instalar en Ubuntu) que no puede ser más sencilla:

library(openair)
calendarPlot(df, pollutant = "IBEX.Adjusted", year = 2019, cols = "Greens")

Este último calendario no lo he usado pero la sintaxis es muy sencilla y el resultado queda bastante bien. Ahora vosotros mismos podéis juzgar si hay o no hay rally de fin de año.

El análisis de supervivencia en R para segmentar el churn

El análisis de supervivencia es uno de los olvidados por el Machine Learning y la nueva forma de ver el oficio. A la regresión logística si la damos algo de recorrido porque aparece en scikit-learn (con sus cositas), sin embargo, el análisis de supervivencia no tiene ese cartel porque en el momento que trabajas con un gran número de variables estos modelos “empiezan a echar chispas”.  Sin embargo ofrecen una serie de gráficos y resultados que más allá de la estimación nos describen problemas y pueden servirnos para segmentar poblaciones en base a la duración hasta la ocurrencia de un evento.

El modelo de supervivencia tiene como variable fundamental el tiempo hasta que ocurre un evento y como este tiempo se modifica en base a unas variables explicativas, mas allá de una tasa nos puede permitir identificar segmentos y poblaciones con comportamientos distintos. El ejemplo que quiero mostraros es el paradigma de todo lo que estoy contando, identificar segmentos de clientes que abandonan mi compañía de telecomunicaciones, mas allá de priorizar clientes en base a su probabilidad de anulación tratamos de identificar características que hacen que mi cliente dure más o menos en la compañía.

El ejemplo que vamos a usar está sacado de este:

https://github.com/zangell44/survival-analysis-lifeline-basics/blob/master/customer_churn.ipynb

Tenéis la descripción de las variables, la más importante es tenure, tiempo en meses hasta que se produce el evento y churn que es el evento, la cancelación de la línea, el resto de variables son propias de la línea. En nuestro caso vamos a trabajar con R porque me parecen interesantes los objetos que generan algunas funciones. Leemos los datos y realizamos una pequeña transformación sobre la variable respuesta:

datos <- read.csv('https://raw.githubusercontent.com/treselle-systems/customer_churn_analysis/master/WA_Fn-UseC_-Telco-Customer-Churn.csv')

datos$Churn <- as.integer(ifelse(datos$Churn=="Yes",1,0))

Las librerías de R que vamos a usar son survival y survminer Seguir leyendo El análisis de supervivencia en R para segmentar el churn

Obteniendo los parámetros de mi modelo GAM

Vimos como los modelos GAM iban más allá del GLM porque en el momento de obtener los parámetros asociados al modelo de un factor nos proponían, en vez de una función lineal una función de suavizado no paramétrica para aquellos factores susceptibles de transformar en variables numéricas ordinales con un sentido determinado. Se trabajó con un modelo de riesgo con una sola variable como era la edad y al sumarizar el modelo no era posible obtener los parámetros en la salida. En último término nuestra intención con este tipo de modelos es obtener esos parámetros para transformarlos en relatividades. Qué sentido tiene obtener un buen modelo para Negocio si su resultado no se puede expresar en términos de incrementos o descuentos, en términos de relatividades.

La entrada del blog que ahora os propongo nos permite extraer los parámetros de cualquier modelo GLM o GAM a partir de la función predict y una de las opciones más olvidadas por todos nosotros:

predict(modelo, newdata = datos,  type = "terms")

con type = “terms” lo que obtenemos en el momento de realizar la predicción son los parámetros del modelo que aplicamos, no es el resultado de la predicción.

Obteniendo las relatividades de nuestro modelo GAM

Partimos del ejemplo que estamos manejando en la serie de entradas:

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 = "")


library(mgcv)

moto$edad_numero <- as.numeric(moto$edad)

gam.1 <- gam(nsin ~ s(edad_numero,bs="cr",k=3) + zona, data=filter(moto,exposicion>0), 
             offset = log(exposicion), family = poisson(link='log'))
summary(gam.1)

Ejecutad este código y obtendréis un modelo GAM con la zona por la que circula el riesgo y una función de suavizado de la edad del asegurado. A la hora de sumarizar el modelo para la edad, la variable suavizada, no vemos parámetros solo una función, si queremos obtener parámetros solo aparece la zona, ¿cómo puedo obtener las relatividades que me arroja este modelo? Empleando predict como se indicó con anterioridad:

terminos <- data.frame(exp(predict(gam.1, newdata = moto, type = "terms")))
names(terminos) <- c("rela_zona","rela_edad") 
terminos <- cbind.data.frame(terminos,select(moto,zona,edad))

Se crea el data.frame terminos que tiene el exponencial del parámetro asociado a ese registro para los factores participantes en el modelo. Cabe señalar que predict no respeta el orden de las variables en el modelo, primero pone las variables que no están suavizadas y después las suavizadas. Después de obtener los parámetros registro a registro lo que hacemos es añadir al data frame los factores de los que deseamos obtener las relatividades y como os podéis imaginar la tabla de relatividades finalmente es el resultado de seleccionar los distintos elementos:

rela_zona <- distinct(select(terminos,zona,rela_zona))
rela_edad <- distinct(select(terminos,edad,rela_edad))

Ya sabéis, no subestiméis a predict...
 

Modelos GAM con R. Dejando satisfechos a los equipos de negocio

Los modelos GAM (Generalized Additive Model) son el conjuntos de modelos que tenemos los estadísticos, actuarios, data scientist o como nos denominen en el momento que leas esto para dejar a nuestros equipos de negocio contentos con los resultados de nuestro modelo GLM. No voy a entrar en los aspectos teóricos de este tipo de modelos, hay documentación como esta que os puede ayudar. Por qué se quedan contentos los equipos de negocio, porque nos ayudan a dar sentido a los modelos. Retomemos un ejemplo que vimos en otra entrada del blog: https://analisisydecision.es/los-parametros-del-modelo-glm-como-relatividades-como-recargos-o-descuentos/ en esta entrada presentamos como el resultado de un modelo GLM se transforma en una relatividad, en un mecanismo para ofrecer recargos y descuentos.

Si desarrollamos un modelo GLM en último término podríamos enseñar este gráfico al responsable comercial:

Parece evidente que a mayor edad mayor proporción de siniestros, además, a partir de los 40 – 45 puede considerarse que las relatividades no varían. Se aprecian tendencias, pero no tiene sentido de negocio aplicar directamente los resultados de las estimaciones, no podemos aplicar esas relatividades obtenidas, es necesario realizar un suavizado y seguramente nos veríamos tentados, una vez hecho el modelo, de aplicar unos suavizados posteriores a la obtención de los parámetros. Podríamos hacer:

#g2 es el gráfico anterior obtenido en https://analisisydecision.es/los-parametros-del-modelo-glm-como-relatividades-como-recargos-o-descuentos/

spline_edad_factor < - smooth.spline(relatividades$rela,w=relatividades$exp,spar=0.65)
g2 + geom_line(aes(y=spline_edad_factor$y *5000), group=1, color="green",size=1.5)

Con smoot.spline hacemos una función de suavizado para nuestras relatividades, el nivel del suavizado lo controlamos con el parámetro spar que va desde 0 (sin suavizado) a 1 (función lineal). El caso es que el resultado de ese suavizado ya podría tener un mayor sentido de negocio y tendríamos más contentos a nuestro equipo comercial, pero... lo estamos haciendo a posteriori, eso no es una estimación, es echar balones fuera. Bien, qué os parece si tenemos un mecanismo para hacer una función previa a la estimación, pues este mecanismo se denomina modelo GAM y la librería de R que vamos a emplear para aproximarnos a ellos es mgcv. Vamos a replicar el modelo más básico con la edad del conductor. Seguir leyendo Modelos GAM con R. Dejando satisfechos a los equipos de negocio

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