Archivo de la categoría: Monográficos

Leer una tabla en PDF con Excel (a través de R)

Hay situaciones en las que tenemos datos en pdf y los necesitamos exportar a Excel para graficar o cruzar esos datos. En ocasiones es mejor meter esos datos a mano, otras veces disponemos de un software de pago que nos permite realizar esa tarea y también hay páginas web que nos permiten cambiar el formato del pdf. En nuestro caso simplemente necesitamos una tabla que está en formato pdf para disponer de esos datos en Excel, más sencillo, copiar del pdf y pegar en Excel esa tabla. Si está en texto el pdf se puede complicar y si está en modo imagen más. Si empleas windows en tu esta entrada puede ser de utilidad ya que usando de R podrás hacer está tarea de copiar pdf y pegar Excel de un modo más rápido, te cuento paso por paso en video.

El primer paso será la instalación de R y RStudio:

Una vez hemos disponemos de R y Rstudio en nuestro equipo descarga este programa en R que te permitirá exportar una tabla a R con las instrucciones que resumo a continuación:

Esta entrada del blog quiero que sea dinámica, me gustaría mejorar el proceso porque tiene posibilidades de dar a conocer R a personas sin conocimientos de programación. Podemos crear desde una aplicación hasta leer url. Saludos.

Tratamiento y procesado de imágenes con R y magick

Estoy preparando la batalla entre geometría e inteligencia artificial, batalla que está perdida porque tengo que dar de comer a mis chavales y si tengo que ir a vender un producto queda más comercial contar lo que se supone que hace la inteligencia artificial y no contar lo que hacen vectores, direcciones, puntos en el espacio,… eso lo cuentan en la educación secundaria y no es “disruptivo”. Sin embargo, aprovecho para contar historia del abuelo, el único proyecto serio basado en inteligencia artificial en el que he estado involucrado se resolvió gracias a la geometría y a las mejoras que se propusieron en el reconocimiento óptico, las redes convolucionales nos provocaron un problema. Inicialmente es mejor plantear una solución sencilla.

En esta batalla perdida que he entablado con algún modelo de Tensorflow apareció el paquete magick de R para el procesamiento de imágenes con R y es sencillo y tiene un pequeño manual en español (https://www.datanovia.com/en/blog/easy-image-processing-in-r-using-the-magick-package/) además recientemente fue el cumpleaños de Sean Connery y voy a aprovechar para hacerle un pequeño homenaje.

La imagen de trabajo la saqué de este tweet:

Es una imagen grande 2048×1147 pixel que guardé en mi equipo y que se lee así:

library(dplyr)
library(magick)
ub = "C:\\Users\\rvaquerizo\\Pictures\\sean_connery.jpg"
sean = image_read(ub)
image_info(sean)
plot(sean)

Tenemos 5 imágenes de Sean Connery en una así pues será necesario seleccionar y estandarizar cada una de las imágenes para que la unión sea más homogénea:

sean_1 <- sean %>% image_crop( "400x620+0+100") %>% image_scale("x300") %>% 
  image_border(color = "grey", geometry = "5x5")
sean_2 <- sean %>% image_crop( "380x450+430+20") %>% image_scale("x300") %>% 
  image_border(color = "grey", geometry = "5x5")
sean_3 <- sean %>% image_crop( "400x750+826+0") %>% image_scale("x300") %>% 
  image_border(color = "grey", geometry = "5x5")
sean_4 <- sean %>% image_crop( "400x580+1226+50") %>% image_scale("x300") %>% 
  image_border(color = "grey", geometry = "5x5")
sean_5 <- sean %>% image_crop( "400x450+1635+0") %>% image_scale("x300") %>% 
  image_border(color = "grey", geometry = "5x5")
plot(sean_1)

Con image_crop vamos a cortar las imágenes del siguiente modo width x height + donde empiezo por la izquierda + donde empiezo por arriba. En el momento en el que habéis cortado 3 imágenes le cogéis el aire enseguida, no cuesta. Con image_scale le damos a todas las imágenes la misma escala, para nuestro ejercicio puede ser redundante pero está bien que lo sepamos. Por último vamos a añadir un borde con image_border es una cuestión estética.

Ya tenemos 5 imágenes similares y podemos realizar una animación pasando una tras otra:

image_resize(c(sean_1, sean_2, sean_3, sean_4, sean_5), '300x300!') %>%
  image_background('grey') %>%
  image_morph() %>%
  image_animate(fps=5)

image_resize ya realiza la homogeneización de las 5 imágenes de Sir Connery, por eso comentaba la redundancia. Ponemos un fondo con image_background, la combinación entre image_morph e image_animate realiza la animación donde hemos puesto la opción fps (frames por segundo) para que no pasen tan rápido. Desde mi punto de vista la forma más sencilla de realizar animaciones. Por cierto, a la hora de guardar la imagen lo hago desde el navegador.

Latent semantic analysis y la importancia de las matemáticas

Vivimos “días extraños”, tan extraños que en España se están planteando prescindir de la asignatura de matemáticas en la enseñanza obligatoria. Es evidente que las personas que gobiernan hoy (25/05/2020) España habrían suspendido matemáticas. Sin embargo, es curioso que haya pocos matemáticos ejerciendo cargos políticos, ¿puede ser que los matemáticos no tengan esa vocación por mejorar la vida de los demás? En fin, esta crítica a la ignorancia numérica y al egoísmo matemático me sirve de “extraña introducción” al Latent semantic analysis (LSA) como siempre los aspectos teóricos los podéis encontrar en otros sitios. Y todo este conjunto de frases inconexas hilan con la entrada en el blog de mi amigo J.L. Cañadas en muestrear no es pecado porque, reducción de dimensionalidad, el lenguaje y la importancia de las matemáticas es en realidad el Latent Semantic Analysis.

Si preguntas a un estadístico ¿qué es la reducción de dimensionalidad? Te contará lo que dice Cañadas, “analizar la varianza total de los datos y obtener las combinaciones lineales mejores en el sentido de máxima varianza” esto es lo mismo que preguntar a una persona que se ha leído un libro, “hazme un resumen en un párrafo”. En ese caso estás buscando una combinación de ideas que te permitan resumir un texto en el menor espacio posible, evidentemente asumes que te dejas cosas, estás asumiendo que las cosas varían, metes la menor variabilidad posible en un párrafo asumiendo toda la variación del texto. Entonces, todo ese follón de la reducción de dimensionalidad es algo parecido a un resumen objetivo de un texto algo que realiza cualquier persona cuando te describe un libro, un artículo o una anécdota. Las matemáticas pueden estructurar el conocimiento cognitivo que permite sintetizar un texto.

Programar el funcionamiento de un cerebro humano así a lo mecagüen es complicado, pero tenemos proyectos en marcha que ya están trabajando con ello, uno de estos proyectos es el paquete LSAfun que es capaz de realizar este tipo de síntesis. La idea es usar espacios semánticos para modelar relaciones entre los conceptos de un texto, podríamos emplear para ello la Wikipedia (por ejemplo). Es caso es que voy a emplear el paquete LSAfun para que me resuma la intervención de Pedro Sánchez en la última sesión del Congreso de los Disputados de España () porque la homeopatía política que vivimos está sustentada en un lenguaje completamente insustancial y que es posible que pudiéramos resumir en una sola frase:

#install.packages('LSAfun')

library(LSAfun)

ubicacion="c:\\temp\\intervencion.txt"
texto = read.table (ubicacion, sep="\r", encoding = 'UTF-8')
texto = toupper(texto)
genericSummary(texto, k=1, language="spanish", breakdown=T)


[1] " en particular el ministro de sanidad ha aprobado tres ordenes para fortalecer nuestro sistema nacional de salud tanto desde el punto de vista de los medios humanos como de los recursos disponibles en unas circunstancias tan extraordinarias como las actuales y en concreto ha ordenado las siguientes materias se ha aprobado la prorroga de la contratacion de los medicos residentes en el ultimo ano de formacion de algunas especialidades medicas y de enfermeria tambien especialmente criticas en la lucha contra el covid tales como la geriatria la medicina intensiva la microbiologia y la parasitologia se han suspendido las rotaciones de los medicos residentes para que estos puedan prestar servicios en aquellas unidades en las que se precise un refuerzo del personal se podra trasladar a medicos residentes de una comunidad autonoma a otra que tenga mayores necesidades asistenciales para la redistribucion de la asistencia en todo el territorio y se podra contratar de modo extraordinario y en algunos casos a personas con un grado o licenciatura en medicina aunque carezcan del titulo de especialista podra reincorporarse a profesionales sanitarios jubilados medicos y medicas enfermeros y enfermeras menores de setenta anos personal emerito y personal con dispensa absoluta para funciones sindicales todo ello para contar con el mayor numero de profesionales sanitarios en esta crisis en caso de que las circunstancias asi lo requirieran tambien se podra contratar de modo extraordinario a estudiantes de los grados de medicina y de enfermeria en su ultimo ano de formacion con el fin de realizar labores de apoyo y auxilio sanitario bajo la supervision de otros profesionales"

Afortunadamente la función genericSummary reconoce el lenguaje español aunque imagino que los espacios semánticos estarán menos desarrollados. Ahí tenemos todo un día de trabajo resumido en una frase que recoge las ideas clave de la sesión del Congreso. Aprovecho desde aquí para alentar a alguna compañía a realizar un análisis de las intervenciones desde el inicio de la democracia en España hasta ahora, sería una competición de datos (hackathon en lenguaje soplapollístico) muy interesante. Saludos.

Tipos de uniones (join) de tablas con Python Pandas

Recopilación de las uniones más habituales con Python Pandas en una sola entrada. No se realiza equivalencias con sql join, la intención es tener de forma resumida los códigos para realizar left join inner join y concatenación de data frames de Pandas. Hay amplia documentación esto es una síntesis.

Los data frames empleados para ilustrar el ejemplo son:

import pandas as pd
import numpy as np
ejemplo = { "variable1": [10, 20, 30, 40],
            "variable2": [100, 200, 300, 400]
}
anio=["2011", "2012", "2013", "2014"]
df1 = pd.DataFrame(ejemplo,index=anio)
df1
ejemplo = { "variable1": [50, 60, 70, 80],
            "variable3": [5000, 6000, 7000, 8000]
}
anio=["2013", "2014", "2015", "2016"]
df2 = pd.DataFrame(ejemplo,index=anio)
df2

Uniones de data frames con índices

La estructura de una join con Pandas es:

pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None,
     left_index=False, right_index=False, sort=True,
     suffixes=('_x', '_y'), copy=True, indicator=False,
     validate=None)

Left Join

left_join = pd.merge(df1, df2, how='left', on=None, left_on=None, right_on=None,
         left_index=True, right_index=True, sort=True)
left_join

Outer Join

outer_join = pd.merge(df1, df2, how='outer', on=None, left_on=None, right_on=None,
         left_index=True, right_index=True, sort=True)
outer_join

Right Join

right_join = pd.merge(df1, df2, how='right', on=None, left_on=None, right_on=None,
         left_index=True, right_index=True, sort=True)
right_join

Inner Join

inner_join = pd.merge(df1, df2, how='inner', on=None, left_on=None, right_on=None,
         left_index=True, right_index=True, sort=True)
inner_join

Concatenar

Concatenación simple

concatenar = pd.concat([df1,df2])
concatenar

Concatenación inner

concatenar_inner = pd.concat([df1,df2],join="inner")
concatenar_inner

Concatenación outer

concatenar_outer = pd.concat([df1,df2],join="outer")
concatenar_outer

Uniones sin índices

Data frames de ejemplo análogos a los anteriores.

import pandas as pd
import numpy as np
ejemplo = { "variable1": [10, 20, 30, 40],
            "variable2": [100, 200, 300, 400],
            "anio":      ["2011", "2012", "2013", "2014"]
}

df1 = pd.DataFrame(ejemplo)

ejemplo = { "variable1": [50, 60, 70, 80],
            "variable3": [5000, 6000, 7000, 8000],
            "anio":      ["2013", "2014", "2015", "2016"]
}

df2 = pd.DataFrame(ejemplo)

Si no tenemos índices es importante especificar en el parámetro on= la variable con la que hacemos la unión de las tablas. En este caso ponemos todas las uniones:

Left, outer, right con campo de unión común

left_join = pd.merge(df1, df2, how='left', on='anio')
outer_join = pd.merge(df1, df2, how='outer', left_on=df1['anio'], right_on=df2['anio'])
right_join = pd.merge(df1, df2, how='right', on='anio')

Inner join con campo de unión de distinto nombre

#Renombramos la variable anio
df2 = df2.rename(columns={"anio": "fecha"})
inner_join = pd.merge(df1, df2, how='inner', left_on='anio', right_on='fecha')

Uniones más habituales en una sola entrada y en pocas líneas de código.

Mover parte de un shapefile con R. Mapa con tasa de casos de coronavirus por habitante en España

Si leéis habitualmente el blog ya conocéis la entrada sobre el mapa del COVID por Comunidades Autónomas y estaréis de acuerdo conmigo en que el mapa de España representado con Rstats es feo de solemnidad. Pero el código es “sencillo” por ahí se ve cada representación que requiere ser desarrollador de R cinturón negro. Bueno, los torpes empleamos ggplot con geom_polygon pero podemos empezar a complicar el mapa añadiendo nuevas posibilidades. La que os traigo hoy es muy interesante en el caso de España, se trata de mover las Islas Canarias en el mapa de Comunidades Autónomas pero directamente con R. Ya tenemos hecho un mapa con QGIS en otra entrada, pero ahora vamos a mover esa parte del shapefile directamente con R y la función elide como hemos hecho en otra ocasión. Estaréis pensando “Vaquerizo no tiene imaginación por eso tira de entradas anteriores y las junta”, no es el caso.

Población por Comunidad Autónoma de datosmacro.expansion.com

library(rvest)
library(xml2)
library(lubridate)
library(tidyverse)
library(tabulizer)
library(tm)
numerea <- function(x) {as.numeric(sub(",",".",x)) }

url = 'https://datosmacro.expansion.com/demografia/poblacion/espana-comunidades-autonomas'

poblacion <- url %>%
  html() %>%
  html_nodes(xpath='//*[@id="tb1"]') %>%
  html_table()
poblacion <- poblacion[[1]]

poblacion <- poblacion [,-4] %>% mutate(CCAA = removePunctuation(CCAA),
                                        CCAA = substr(CCAA,1,nchar(CCAA)-1),
                                        habitantes=numerea(removePunctuation(Población))) %>%
  rename(region=CCAA)  %>% as_tibble()

poblacion <- poblacion [,c(1,5)] %>% mutate(region=case_when(
  region == "Comunidad Valenciana" ~ "C. Valenciana",
  region == "Castilla La Mancha" ~ "Castilla-La Mancha",
  region == "Islas Baleares" ~ "Baleares",
  TRUE ~ region  ))

Nada innovador, si queréis entender mejor que hace id a la primera de las páginas antes mencionadas.

Tabla de casos de COVID por Comunidad Autónoma y mapa de comunidades de GADM

#Situación por Comunidad Autónoma
library(maptools)
library(raster)
library(maps)

datadista = "https://raw.githubusercontent.com/datadista/datasets/master/COVID%2019/ccaa_covid19_casos.csv"

tabla_ccaa <- read.csv2(datadista, sep=',',encoding = 'UTF-8', check.names=FALSE)

Espania <- getData('GADM', country='Spain', level=1)
Espania$name = Espania$NAME_1

Situación similar al anterior código, pero siempre es necesario mencionar y rendir homenaje a Datadista y su trabajo.

Mover Canarias con elide

Espania_sin_canarias <- Espania[Espania$NAME_1 != 'Islas Canarias',]
Canarias <- Espania[Espania$NAME_1 == 'Islas Canarias',]
Canarias = elide(Canarias,shift=c(3.7,7))

ccaa1 <- map_data(Espania_sin_canarias)
ccaa2 <- map_data(Canarias)
ccaa <- rbind(ccaa1,ccaa2)

En este caso si es necesario pararse brevemente Seguir leyendo Mover parte de un shapefile con R. Mapa con tasa de casos de coronavirus por habitante en España

Mi breve seguimiento del coronavirus con R

Ya comentaré con más detenimiento el código, pero es la unión de muchos de los códigos R de días anteriores, es un buen ejemplo de uso de la librería gridExtra para poner múltiples gráficos en una sola salida:

library(dplyr)
library(ggplot2)
library(reshape)
library(gridExtra)

df <- read.csv("https://raw.githubusercontent.com/datadista/datasets/master/COVID%2019/ccaa_covid19_fallecidos.csv", 
               sep=',', check.names=FALSE, encoding = 'UTF-8')
df2 <- melt(df[,-1])
names(df2) = c('CCAA','fecha','fallecidos')

mm <- df2 %>% group_by(CCAA) %>% summarise(total_fallecidos = sum(fallecidos)) %>% arrange(desc(total_fallecidos)) %>%
  mutate(CCAA2 = ifelse(row_number()>=10,'Resto', as.character(CCAA))) %>% select(CCAA,CCAA2)

df2 <- left_join(df2,mm)

table(mm$CCAA2)

df2 <- df2 %>% group_by(CCAA2,fecha) %>% summarise(fallecidos=sum(fallecidos))  %>%
  mutate(fecha = as.Date(as.character(fecha),origin='1970-01-01')) %>% as_tibble()
df3 <- df2 %>% mutate(fecha=fecha+1, fallecidos_anterior=fallecidos) %>%  select(-fallecidos)

df2 <- left_join(df2, df3) %>% mutate(fallecidos_dia = fallecidos - fallecidos_anterior)

#Función para hacer los gráficos
grafica <- function(comunidad){
  p <- ggplot(filter(df2,CCAA2==comunidad), aes(x=fecha)) + 
    geom_line(aes(y=fallecidos_dia, group = 1), alpha = 0.5, color='red') + 
    geom_smooth(aes(y=fallecidos_dia), method = "loess") + 
    ggtitle(comunidad) + 
    xlab("") + ylab("Fallecidos por día")
  return(p)}

madrid = grafica('Madrid')
cat = grafica('Cataluña')
mancha = grafica('Castilla-La Mancha')
leon = grafica('Castilla y León')
pvasco = grafica('País Vasco')
valencia = grafica('C. Valenciana')
andalucia = grafica('Andalucía')
aragon=grafica('Aragón')
resto = grafica('Resto')
total = grafica('Total')

grid.arrange(madrid, cat, mancha, leon, pvasco, valencia, andalucia, aragon, resto, total, nrow=5,ncol=2)

Del mismo modo podemos hacer el número de casos:

# Casos
df <- read.csv("https://raw.githubusercontent.com/datadista/datasets/master/COVID%2019/ccaa_covid19_casos.csv", 
               sep=',', check.names=FALSE, encoding = 'UTF-8')
df2 <- melt(df[,-1])
names(df2) = c('CCAA','fecha','casos')

mm <- df2 %>% group_by(CCAA) %>% summarise(total_casos = sum(casos)) %>% arrange(desc(total_casos)) %>%
  mutate(CCAA2 = ifelse(row_number()>=10,'Resto', as.character(CCAA))) %>% select(CCAA,CCAA2)

table(mm$CCAA2)
df2 <- left_join(df2,mm)

df2 <- df2 %>% group_by(CCAA2,fecha) %>% summarise(casos=sum(casos))  %>%
  mutate(fecha = as.Date(as.character(fecha),origin='1970-01-01')) %>% as_tibble()
df3 <- df2 %>% mutate(fecha=fecha+1, casos_anterior=casos) %>%  select(-casos)

df2 <- left_join(df2, df3) %>% mutate(casos_dia = casos - casos_anterior)

#Función para hacer los gráficos
grafica <- function(comunidad){
  p <- ggplot(filter(df2,CCAA2==comunidad), aes(x=fecha)) + 
    geom_line(aes(y=casos_dia, group = 1), alpha = 0.5, color='red') + 
    geom_smooth(aes(y=casos_dia), method = "loess") + 
    ggtitle(comunidad) + 
    xlab("") + ylab("casos por día")
  return(p)}

madrid = grafica('Madrid')
cat = grafica('Cataluña')
mancha = grafica('Castilla-La Mancha')
leon = grafica('Castilla y León')
pvasco = grafica('País Vasco')
valencia = grafica('C. Valenciana')
andalucia = grafica('Andalucía')
galicia=grafica('Galicia')
resto = grafica('Resto')
total = grafica('Total')

grid.arrange(madrid, cat, mancha, leon, pvasco, valencia, andalucia, galicia, resto, total, nrow=5,ncol=2)

En este caso cambiamos Aragón por Galicia. También cabe destacar que es un buen ejemplo de uso de melt para transponer columnas a filas, al hacer eso el lag lo realizamos mediante left join sumando un día y así podemos calcular la diferencia diaria con el acumulado, esperemos que este tipo de análisis tan burdos se estén llevando a cabo en otros sitios donde toman decisiones. Saludos.

Leer archivos Excel con Python

Entrada sobre la importación de Excel con Python, un aporte que sirve para mi documentación y que es posible que sea de ayuda para muchos que se estén iniciando en el uso de Python y Pandas, aunque en este caso para la lectura del Excel usaremos tanto Pandas como la librería xlrd.

Lectura de Excel con Pandas

Lo más sencillo para importarnos en Python un Excel y crearnos un data frame de Pandas es:

import pandas as pd
archivo = 'C:/Users/Documents/ejemplo.xlsx'

df = pd.read_excel(archivo, sheetname='Hoja1')

df.describe()

La función read_excel será suficiente en el 80% de las ocasiones que realicemos esta tarea. Como es habitual en la ayuda tenéis perfectamente descritas sus posibilidades.

Lectura de Excel con xlrd

Es posible que necesitemos realizar tareas más complejas a la hora de leer archivos Excel y podemos usar xlrd. Vemos algunas de las posibilidades:

import xlrd 
 
archivo = 'C:/Users/rvaquerizo/Documents/ejemplo.xlsx'
  
wb = xlrd.open_workbook(archivo) 

hoja = wb.sheet_by_index(0) 
print(hoja.nrows) 
print(hoja.ncols) 
print(hoja.cell_value(0, 0))

open_workbook nos abre el Excel para trabajar con él. Seleccionamos hojas por índice (empezando por el 0) y con la hoja seleccionada podemos ver el número de filas (nrows) o columnas (ncols). Seleccionar una celda lo hacemos con cell_value mediante índices (empezando por el 0). Otras posibilidades:

archivo = 'C:/Users/rvaquerizo/Documents/ejemplo.xlsx'
  
wb = xlrd.open_workbook(archivo) 

hoja = wb.sheet_by_name('Hoja1') 
for i in range(0,hoja.nrows):
    print(hoja.cell_value(i,1))

Si por ejemplo deseamos saber las cabeceras, los nombres de las columnas:

archivo = 'C:/Users/rvaquerizo/Documents/ejemplo.xlsx'
  
wb = xlrd.open_workbook(archivo) 

hoja = wb.sheet_by_index(0) 
nombres = hoja.row(0)  
print(nombres)

Y mediante xlrd podemos crear data frames de pandas con lo que es posible realizar lecturas de rangos:

archivo = 'C:/Users/rvaquerizo/Documents/ejemplo.xlsx'
  
wb = xlrd.open_workbook(archivo) 

hoja = wb.sheet_by_index(0) 

# Creamos listas
filas = []
for fila in range(1,hoja.nrows):
    columnas = []
    for columna in range(0,2):
        columnas.append(hoja.cell_value(fila,columna))
    filas.append(columnas)

import pandas as pd
df = pd.DataFrame(filas)
df.head()

Hay alguna librería que lo hace de forma más elegante pero la importación de rangos de Excel con xlrd, una vez te familiarizas, me parece bastante sencilla. Espero que sea de utilidad

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.

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.

Me rindo, es necesario trabajar en Agile

Imagen de previsualización de YouTube

“Agile sounds good” y representa todo eso que critico. Tenía compuesta y preparada una canción que versiona el “Me cago en el amor” de Tonino Carotone, “Me cago en el Agile” se llamaba. ¿Por qué este cambio de opinión tan radical?  Porque no se trabaja de forma horizontal, se trabaja de forma vertical y cada uno hace la guerra por su cuenta. Me voy a mi terreno Agile Analytics

Echamos y vendemos humo. Empecemos: arquitectos, ingenieros, científicos de datos,  analistas de negocio, diseñadores,…  y al final nos olvidamos que estamos ahí para ganar € o hacer ganar a nuestra organización €, €€€€€€ no estamos para conectar nodos edge, ingestar data lakes con millones de registros que nadie usa, diseñar algoritmos, crear contenedores, APIs, visualizaciones espectaculares, mapas interactivos, inteligencias artificiales, etecé, etecé. ¿Objetivo final del proyecto? ¿Cuántos € retorna? Bueno pues ha sido necesario de crear una parafernalia con pizarras y posit para que no nos desviemos de esos objetivos y que en último término sean capaces de medir cuantos € supone cada proyecto. Si todos los implicados en un proyecto trabajaran de forma conjunta, ¿serían necesarias estas figuras? No, pero como eso no pasa, no es que sea necesario, es que se torna imprescindible trabajar en Agile.

Me gustaría plantear una visión distinta de las personas y los roles dentro de un proyecto analítico:

  • Jefe de proyecto (project manager sounds better). Esto ni Agile, ni cascadas, hace falta alguien que se las lleve y que reparta, no queda más remedio. Es muy importante su rol en la documentación y entregables de resultados.
  • Científico, ingeniero o arquitecto de soluciones. Al final son los que trabajan tutelados por un jefe de proyecto.
  • Sponsor de negocio. Este es el que plantea el caso de negocio,  el que pone en marcha toda la maquinaria para plantear un proyecto.
  • Usuario de negocio. Persona que en último término va a usar las herramientas que estén desarrollando y el que tiene un problema que la analítica puede resolver. La línea entre sponsor y usuario es fina.
  • Product Owner (la prefiero en english). Esta figura es la clave porque es la verdadera figura horizontal a las anteriores, rol multidisciplinar tiene que entender el lenguaje de todas las personas implicadas y velar por la correcta administración del proyecto, el que se traga la correcta documentación e imputación de las tareas, que si el resto lo hiciera bien no sería necesario. Junto con el jefe de proyecto cuida del cumplimiento de los objetivos a corto plazo.
  • Scrum máster. Es la persona encargada de que todo lo anterior se lleve a cabo y para ello dispone de una serie de ceremonias y de ritos ridículos pero que sin ellos todas las figuras anteriormente citadas harían lo que les viene en gana. Tiene una tarea de interlocución a más alto nivel en la organización muy importante ya que en el caso de producirse retrasos, problemas o impedimentos en el desarrollo de los proyectos debe ser capaz de eliminar esos problemas. También mide junto con negocio y dirección cual es el impacto económico de los proyectos llevados a cabo.

Esta es mi visión de un proyecto en Agile Analytics y alguno llevo encima (con cierto éxito).  No entro en las ceremonias, que tienen su pena y su gloria también pero obligan a que todos los elementos participantes de un proyecto se reúnan, se escriban actas y se gestionen tareas. Si alguien quiere hablaré sobre ellas.

Ahora bien, hace 20 años yo trabajaba sentado con Ana (de Negocio), tenía 2 pcs porque con uno ejecutaba Business Object y si no me salía la cucaracha con el otro realizaba los análisis con Access/Excel. Vale que vendíamos politonos, paquetes de SMS y móviles de concha pero no era necesaria toda esta parafernalia y éramos 2. Podemos opinar sobre la complejidad de los problemas de negocio actuales, la información disponible o el entorno competitivo son argumentos para trabajar de este modo, pero en mi opinión el problema está en la lejanía y la especialización.  Al final no salían las cosas, así que algunos listos han vestido de metodología (que poco me gusta esta palabra) el sentido común y han dado una solución a este problema y con un halo marketiniano muy molón que llamamos Agile. A todo esto la mayor beneficiada ha sido 3M que en 2018 estaba en máximos de cotización, creo que ahora está cayendo un poco quizá haya vuelto el sentido común, no creo, hasta yo me he rendido.