Mapa de códigos postales con R. Aunque el mapa es lo de menos

Entrada para facilitar la realización de mapas de códigos postales de España con R. Todo parte del trabajo de Íñigo Flores al que ya mencionamos en otra entrada. Íñigo descargó de Cartociudad y recopiló los objetos shape file para realizar estos gráficos y los subió a su repositorio, están desactualizados pero puede ser suficiente para la realización de mapas de códigos postales. Íñigo subió en formato .zip todos los archivos necesarios provincia a provincia como lo tenía Cartociudad. Podemos clonarnos el repositorio o leer directamente de github, en cualquier caso necesitamos una función en R que nos permita leer archivos comprimidos en formato zip y cuando lea el zip seleccionar que expresamente lea el archivo shp que contiene el spatial data.

Función para la lectura de archivos comprimidos zip con R

library(maptools)

leer.zip <- function(archivozip) {
  zipdir <- tempfile()
  dir.create(zipdir)

  unzip(archivozip, exdir=zipdir)
  
  archivo <- list.files(zipdir)
   
  archivo <- archivo[grepl("shp",archivo)>0]
  archivo <- paste(zipdir, archivo, sep="/")
  readShapeSpatial(archivo)
} 

Esta función leer.zip permite leer archivos zip, guardarlos en un directorio temporal y posteriormente sólo lee aquel archivo extraído que en su nombre contenga el texto “shp”. Función interesante que modificada ligeramente os permitirá descomprimir cualquier archivo y leer el elemento que deseáis, además de un buen ejemplo de uso de unzip. En este punto, como comentamos antes, podemos leer directamente de github con R.

Leer archivo zip de github con R

url <- 'https://github.com/inigoflores/ds-codigos-postales/raw/master/archive/42605-NAVARRA.zip'

tf = tempfile(tmpdir=tempdir(), fileext=".zip")
download.file(url, tf)
navarra <- leer.zip(tf)

Creamos un temporal para descargarnos el zip pero es necesario especificar la extensión. Descargamos de la url correspondiente el archivo con los elementos comprimidos y el objeto navarra será el resultado de la lectura del shapefile con los códigos postales de Navarra. La otra forma es clonar el repositorio y acceder directamente al directorio:

navarra <- leer.zip('C:\\temp\\personales\\wordpress\\ds-codigos-postales-master\\archive\\42605-NAVARRA.zip')

Otro de los motivos de esta entrada es mostraros como podemos realizar mapas de modo rápido con la librería tmap.

Ejemplo de mapa quick & dirty con R

library(tmap)

navarra <- leer.zip('C:\\temp\\personales\\wordpress\\ds-codigos-postales-master\\archive\\42605-NAVARRA.zip')
navarra@data$dato <- rpois(nrow(navarra@data),2)
qtm(shp = navarra, fill = "dato", fill.palette = "Blues")

La función qtm se traduce como -Quick thematic plot- y quick es muy quick. El mejor balance entre rápido y sencillo que hay (bajo mi punto de vista). En el ejemplo se pinta un dato aleatorio pero podéis hacer una left join con vuestros datos (que me conozco a algunos). Y si queremos crear un objeto con cada uno de los elementos que preparó Íñigo podemos hacer.

Lectura de archivos y creación de data frame mediante un bucle que lee otro data frame

trabajo <- 'C:/temp/personales/wordpress/ds-codigos-postales-master/archive/'
provincias <- list.files(trabajo)
provincias <- data.frame(archivo=provincias)
provincias$nombre <- substr(provincias$archivo,regexpr("-",provincias$archivo)+1,length(provincias$archivo))
provincias$nombre <- gsub('.zip','',provincias$nombre)

for (i in 1:nrow(provincias) ){
  instruccion <- paste0(provincias$nombre[i],' <- leer.zip("',trabajo,provincias$archivo[i],'")')
  eval(parse(text=instruccion))
}

Código rudimentario que crea un data frame a partir de los archivos de un directorio de trabajo, los archivos son los .zip que nos clonamos de github y con ellos vamos a crear 52 data frame para cada una de las provincias. El nombre de los archivos es XXXX-provincia.zip por eso tenemos que usar algunas funciones de texto para obtener el nombre de la provincia como regexpr que nos permite encontrar la primera posición en la se encuentra un patrón dentro de un texto, por otro lado gsub nos sirve para sustituir un patrón de texto por otro. Así leemos desde el – y posteriormente tenemos que eliminar el .zip para tener el nombre de cada provincia. Y por último un clásico en mis programas de R herencia de los tiempos en los que trabajaba con macros en SAS, tengo que recorrer ese data frame con los elementos del directorio y el nombre del objeto será una columna del data frame y el archivo a leer otra columna, para evaluar un texto el mítico eval ( parse ( text = nunca me falla, habrá formas más elegantes pero esta son dos líneas. Siempre hay que poner talento en la construcción de la instrucción y acordarse de cerrar paréntesis y demás. Ejecutando eso tendríamos un objeto para cada provincia, si queremos toda España.

Creación de un mapa de España por códigos postales

trabajo <- 'C:/temp/personales/wordpress/ds-codigos-postales-master/archive/'
provincias <- list.files(trabajo)
provincias <- data.frame(archivo=provincias)
provincias$nombre <- substr(provincias$archivo,regexpr("-",provincias$archivo)+1,length(provincias$archivo))
provincias$nombre <- gsub('.zip','',provincias$nombre)

for (i in 1:nrow(provincias) ){
  instruccion <- paste0('borrar <- leer.zip("',trabajo,provincias$archivo[i],'")')
  eval(parse(text=instruccion))
  if (i == 1) {espania <- borrar}
  espania <- rbind(espania,borrar)
  remove(borrar)
}

plot(espania)

Otro bucle con la marca de la casa pero que funciona perfectamente, leemos uno a uno cada zip con las provincias y con rbind podemos unir los objetos spatial para poder pintar el mapa de España y cuidado que esto si genera un objeto de casi 120 MB. Podéis manejar los objetos spatial data y así reducir su tamaño, así como idea por si pongo el código en el repositorio.

El caso es que ya sabéis como hacer un mapa de España de códigos postales con R, incluso si sois avezados podéis guardar el objeto final resultante y utilizarlo con QGIS u otra herramienta que uséis para hacer mapas. Además esta entrada es todo un compendio de malas prácticas en programación con R que funcionan a las mil maravillas, desde leer archivos zip con R, seleccionar el que deseamos a funciones de texto para extraer con condiciones, ejemplo de gráfico de mapa rápido con tmap y un bucle que lee un data frame y genera objetos con él.

Ejecutar un código al iniciar la sesión de R

A raíz de una conversación en Twitter os traigo un pequeño truco de R para aquellos que tenéis funciones predefinidas y que tenéis que cargarlas al iniciar las sesiones de R, es como ejecutar el código nada más abrir R. En mi caso el código que quiero ejecutar son una serie de utilidades que tengo guardadas en C:\carpeta, con source(«C:/carpeta/Utils.R», encoding=»UTF-8″) R cargaría todo el código R alojado en ese script de R y necesito que se ejecute el script al inicial la sesión de R, no quiero poner esa línea al principio de cada programa. Lo primero que tenemos que hacer es buscar donde tenemos instalado R, una vez hallamos accedido a la correspondiente carpeta vamos a la subcarpeta /etc y tenemos un archivo llamado Rprofile.site lo abrimos con un editor de texto:

# Things you might want to change

# options(papersize="a4")
# options(editor="notepad")
# options(pager="internal")

# set the default help type
# options(help_type="text")
  options(help_type="html")

# set a site library
# .Library.site <- file.path(chartr("\\", "/", R.home()), "site-library")

# set a CRAN mirror
# local({r <- getOption("repos")
#       r["CRAN"] <- "http://my.local.cran"
#       options(repos=r)})

# Give a fortune cookie, but only to interactive sessions
# (This would need the fortunes package to be installed.)
#  if (interactive()) 
#    fortunes::fortune()

source("C:/carpeta/Utils.R", encoding="UTF-8")

Pues en ese archivo ponemos source("C:/carpeta/Utils.R", encoding="UTF-8") y cada vez que abramos nuestro R, desde RStudio por ejemplo, se ejecutará el script con nuestras utilidades.

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.

Calendario de días laborales con Pandas

Es habitual escuchar que un científico de datos es un estadístico que trabaja con Python. En parte, tiene razón. Sin embargo, quien ha trabajado dentro del mundo académico sabe que para un estadístico las vacas son esféricas y los meses tienen 365,25/12 días. En cambio, en el mundo real, ni hay dos vacas iguales ni un mes igual a otro.
Sirva esta entrada para poner en valor todo aquel trabajo adicional y tiempo dedicado por aquellos que trabajan con datos y huyen de simplificaciones estadísticas, ya se denominen científicos de datos o cómo quieran llamarse.

Series temporales con Pandas

Pandas, como se ha visto aquí, es la librería por excelencia para el manejo de datos ya que permite trabajar fácilmente con tablas numéricas y series temporales.
Una utilidad disponible en Pandas en relación a las series temporales es crear directamente rangos de fechas con la función pd.date_range(), la cual utiliza los siguientes parámetros (no todos obligatorios):

  • start: Inicio del rango. Límite izquierdo para generar fechas.
  • end: Fin del rango. Límite derecho para generar fechas.
  • period: Número de periodos a generar
  • freq: Frecuencia de las proyecciones. Entre otros:
    • D: Diaria (por defecto)
    • Y: Anual
    • M: Mensual
  • closed: Si queremos excluir el inicio (closed=’right’) o el final (closed=’left’)
  • name: Nombre del DatetimeIndex resultante (por defecto ninguno)

Ejemplo básico de una serie temporal

import pandas as pd
s = pd.date_range(start='2019-01-01', periods=12, freq='M')
df = pd.DataFrame(s, columns=['Fecha'])
print(df)
        Fecha
0  2019-01-31
1  2019-02-28
2  2019-03-31
3  2019-04-30
4  2019-05-31
5  2019-06-30
6  2019-07-31
7  2019-08-31
8  2019-09-30
9  2019-10-31
10 2019-11-30
11 2019-12-31

Crear una serie temporal con los últimos días laborales de cada mes

En determinados ámbitos, principalmente financiero y actuarial, resulta especialmente útil manejar rangos de fechas en donde los días de la serie correspondan al primer o último día laboral del mes (por ejemplo, cuando proyectamos pagos de cupones o rentas).
Para ello, la función pd.date_range() dispone de diferentes valores para el parámetro frecuencia. En el siguiente ejemplo, ‘BM’ corresponde con BusinessMonthEnd (último día laboral del mes):

import pandas as pd
s = pd.date_range('2019-01-01', periods=12, freq='BM')
df = pd.DataFrame(s, columns=['Fecha'])
print(df)
        Fecha
0  2019-01-31
1  2019-02-28
2  2019-03-29
3  2019-04-30
4  2019-05-31
5  2019-06-28
6  2019-07-31
7  2019-08-30
8  2019-09-30
9  2019-10-31
10 2019-11-29
11 2019-12-31

Aunque si queremos mayor exactitud, debemos tener en cuenta los días festivos (por ejemplo, si calculamos costes que dependan de los días exactamente transcurridos entre cupón y cupón, un 1 día entre 20 representa un 5% de error). Conocer los días festivos dentro de un periodo de tiempo es especialmente útil cuando estimamos usos y comportamientos humanos (transporte público, asistencias médicas, cargas en servidores informáticos…). En el sector asegurador, dichos patrones pueden afectar directamente en las reservas contables, por ejemplo, a la hora de calcular los costes incurridos pero no declarados (IBNR). De hecho, en algunas compañías aseguradoras es habitual que se incrementen ligeramente los ratios de siniestralidad los años bisiestos simplemente por disponer de un día natural más que el resto.

En el siguiente ejemplo introducimos una lista con un par de días festivos simplemente para ver el funcionamiento: el 31 de mayo (Día de Castilla-La Mancha) y 28 de febrero (Día de Andalucía). Posteriormente calculamos los días transcurridos entre pagos si estos se produjeran el último día de mes:

import pandas as pd
fiestas = ['2019-02-28','2019-05-31']
es_holidays = pd.tseries.offsets.CustomBusinessMonthEnd(holidays=fiestas)
s = pd.date_range('2019-01-01', periods=12, freq=es_holidays)
df = pd.DataFrame(s, columns=['Fecha'])
df['n_dias'] = df['Fecha'].diff().dt.days.fillna(0)
print(df)
        Fecha  n_dias
0  2019-01-31     0.0
1  2019-02-27    27.0
2  2019-03-29    30.0
3  2019-04-30    32.0
4  2019-05-30    30.0
5  2019-06-28    29.0
6  2019-07-31    33.0
7  2019-08-30    30.0
8  2019-09-30    31.0
9  2019-10-31    31.0
10 2019-11-29    29.0
11 2019-12-31    32.0

(Como puede observarse, el 28-2 y 31-5 no aparecen en el calendario de pagos)

Reglas de cálculo para los días festivos

Por último, existe la opción de escribir reglas para calcular los días festivos (ya que normalmente estos son trasladados al siguiente día laboral). En ciertos países, como Estados Unidos, existen leyes al respecto (como la Uniform Monday Holiday Act of 1968), cuyas reglas están incluidas en Pandas y pueden consultarse haciendo un print(USFederalHolidayCalendar.rules).

Sin embargo, en muchos otros países no existen reglas para determinar los festivos del año (como ocurre en España), sino que cada año las fiestas laborales son fijadas por normativa. No obstante, vamos a aventurarnos a crear las reglas del calendario laboral español con la premisa de que los festivos que caigan en domingo se pasan al lunes siguiente (instrucción observance=sunday_to_monday). Posteriormente, mostramos los días laborales de diciembre para comprobar su funcionamiento.

from pandas.tseries.holiday import *
from pandas.tseries.offsets import CustomBusinessDay

class EsBusinessCalendar(AbstractHolidayCalendar):
   rules = [
     Holiday('Año Nuevo', month=1, day=1, observance=sunday_to_monday),
     Holiday('Epifanía del Señor', month=1, day=6, observance=sunday_to_monday),
     Holiday('Viernes Santo', month=1, day=1, offset=[Easter(), Day(-2)]),
     Holiday('Día del Trabajador', month=5, day=1, observance=sunday_to_monday),
     Holiday('Asunción de la Virgen', month=8, day=15, observance=sunday_to_monday),
     Holiday('Día de la Hispanidad', month=10, day=12, observance=sunday_to_monday),
     Holiday('Todos los Santos', month=11, day=1, observance=sunday_to_monday),
     Holiday('Día Constitución', month=12, day=6, observance=sunday_to_monday),
     Holiday('Inmaculada Concepción', month=12, day=8, observance=sunday_to_monday),	    
     Holiday('Navidad', month=12, day=25, observance=sunday_to_monday)
   ]

es_BD = CustomBusinessDay(calendar=EsBusinessCalendar())
s = pd.date_range('2019-12-01', end='2019-12-31', freq=es_BD)
df = pd.DataFrame(s, columns=['Fecha'])
print(df)
        Fecha
0  2019-12-02
1  2019-12-03
2  2019-12-04
3  2019-12-05
4  2019-12-10
5  2019-12-11
6  2019-12-12
7  2019-12-13
8  2019-12-16
9  2019-12-17
10 2019-12-18
11 2019-12-19
12 2019-12-20
13 2019-12-23
14 2019-12-24
15 2019-12-26
16 2019-12-27
17 2019-12-30
18 2019-12-31

(Se aprecia como el domingo 8 de diciembre es reemplazado por el lunes 9)

Así, una vez creado nuestro calendario de fiestas nacionales, podemos utilizarlo para conocer las fiestas de próximos años, por ejemplo para el año 2020:

calendar = EsBusinessCalendar()
print(calendar.holidays(start='2020-01-01', end='2020-12-31'))
DatetimeIndex(['2020-01-01', '2020-01-06', '2020-04-10', '2020-05-01',
               '2020-08-15', '2020-10-12', '2020-11-02', '2020-12-07',
               '2020-12-08', '2020-12-25'],
              dtype='datetime64[ns]', freq=None)

Las fiestas regionales o locales pueden ser incluidas de la misma manera, aunque hay que tener en cuenta que en ciertas comunidades es costumbre trasladar ciertas fiestas al 19 de marzo o el jueves del Corpus en vez de al siguiente lunes (en ese caso habría que crear una nueva función basada en la función de Pandas sunday_to_monday).

Nuestro calendario puede ser utilizado también en otras librerías como Prophet. Prophet es una librería avanzada de machine learning creada por Facebook, enfocada a modelos de regresión no lineales de datos a lo largo del tiempo, la cual requiere un listado de días festivos que debe ser proporcionado por el usuario.

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:
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 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:

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:

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 python pandas:

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.