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.

Mapa estático de España con Python

Faltaban mapas de España con Python en el blog y hoy ilustro como hacerlos con geopandas y matplotlib, creo que una de las formas más sencillas de hacer este tipo de mapas. No debía de ser necesaria esta entrada puesto que la realización del mapa debería hacerse con QGis pero es posible que alguien necesite hacer un mapa de España por Comunidades Autónomas de manera rápida y sencilla en su sesión de Python. Es necesario comentar que este trabajo está hecho con Ubuntu, en un entorno Windows la instalación del paquete geopandas es un dolor de cabeza.

Shapefile

El primer paso para la realización del mapa es la obtención del shapefile desde GADM. No he encontrado un paquete de Python que realice esta tarea como sucede con R. Una vez lo hayamos descargado empezamos creando nuestro data frame con la información del shape file:

import geopandas as gpd
import matplotlib.pyplot as plt

# Filepaths
ub_shp = '/home/rvaquerizo/Mapas/gadm36_ESP_shp/gadm36_ESP_1.shp'

# Read files
espania = gpd.read_file(ub_shp, encoding='utf-8')
espania.head()

Reseñar que el mapa de GADM a nivel de Comunidad Autónoma Seguir leyendo Mapa estático de España con Python

Identificar los municipios costeros y limítrofes de España con R.

Otro ejercicio con spatial data R Rstats y data sciense para el trabajo con objetos espaciales en el ecosistema big data. Empiezo con frase ilógica y ridícula para mejorar las búsquedas de Google pero el tema que traigo hoy creo que puede ser útil para aquellos que, dado un spatial data, tienen que identificar los polígonos que bordean ese objeto, en este caso vamos a identificar los municipios que bordean España, pueden ser limítrofes con Francia y Portugal o bien municipios costeros. No se plantean algoritmos complicados, como en entradas anteriores nos centramos en la extracción de mapas de GADM:

Obtención de los mapas necesarios

library(maptools)
library(raster)
library(maps)
library(tidyverse)
library(sqldf)

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

Por un lado obtenemos el mapa de España sin división territorial que en GADM es el nivel 0 y por otro lado el municipal que es nivel 4. Un tipo brillante sería capaz de encontrar un algoritmo que identificara que polígonos no tienen adyacencia, pero un tipo mediocre pensaría "si cruzo el borde con los municipios, los objetos que crucen son el exterior"

Municipios del contorno

contorno < - map_data(Espania) %>% mutate(lat2=round(lat,1), long2=round(long,1)) %>% select(long2,lat2) 

municipios < - map_data(Espania2) %>% mutate(lat2=round(lat,1), long2=round(long,1))  %>% select(long2,lat2,region) 

contorno < - inner_join(municipios, contorno) 

En este punto hay aspectos claramente mejorables, el cruce se realiza por latitud y longitud, Seguir leyendo Identificar los municipios costeros y limítrofes de España con R.

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.

Entender una blockchain con R

Una introducción de bajo nivel (sin entrar mucho en tecnología) a los blockchain con #rtats. Es una entrada destinada a comprender que es un blockchain desde otro punto de vista, no sólo criptografía o criptomoneda, podemos poner información que sólo conoce el origen. ¿Os imagináis si pusieran a disposición de los científicos de datos información sobre todos los españoles identificados por NIF y si tiene o no coronavirus? Los científicos de datos podrían trabajar de forma anónima con esos datos y ayudar a establecer las zonas libres de covid-19, persona a persona de forma perfectamente anónima. Aunque no se descarta que algún cabestro se dedicara a desencriptar…

Al lío, de forma sencilla vamos a construir nuestra cadena de bloques aunque particularmente me gusta mucho el término contabilidad distribuida. Por ese motivo vamos a crear un apunte contable y distribuirlo dentro de una blockchain. No soy un experto en contabilidad pero se me ocurre crear un apunte contable del siguiente modo:

#Definimos un bloque como una transacción
bloque < - list(index = 1,
               fecha = "2020-04-09:12:00:00",
               descripcion = "Bloque 0",
               referencia = "1.1.1",
               debe = "3000",
               haber = "0",
               hash_previos = 0,
               profundidad = 9,
               hash = NULL)

En R el bloque es una lista con determinados elementos, siendo un apunte contable ponemos una fecha, una descripción, una referencia y un debe/haber; los elementos que necesitamos para crear los eslabones de la cadena serán la profundidad de la cadena y los hash, tanto previo como el de nuestro bloque. ¿Qué es un hash? Es un procedimiento criptográfico que transforma una información en una cadena de caracteres. Ojo con los hash porque una vez creados no se pueden descifrar, es decir, una vez creado el bloque este queda guardado a fuego. Esto en contabilidad es un problema porque no se puede deshacer, no nos podemos equivocar. Sin embargo, desde el punto de vista del auditor de una cuenta puede ser interesante. En R esa cadena de caracteres la vamos a crear a partir de la librería digest:

#Esta librería de R crea hash de objetos de R
library("digest")

#¿Cómo se ve el término analisisydecision encriptado?
digest("analisisydecision" ,"sha256")

[1] "bec1a55f485045e8a1f5f774fe2a66f09cc93e046eb9fa978c97a7c061009d9c"

La función digest transforma un elemento de R en una cadena de caracteres Seguir leyendo Entender una blockchain con R

Etiquetas en scatter plot. Muertes covid por millón de habitantes vs gasto en salud

He intentado permanecer ajeno a los datos del coronoavirus pero es imposible, sin embargo, me gustaría aprovechar para mostrar algunos trucos con R y Python. Esta vez en una sola entrada vamos a tratar las siguientes situaciones:

  • Importar la tabla de worldometer sobre datos de países.
  • Problemas con la librería OECD.
  • Importar Excel con R.
  • Not in con R.
  • Gráficos de ranking ordenados con ggplot.
  • Etiquetas en los scatter plot.

Importar la tabla de worldometer

Esta parte es probable que ya la haya puesto en otra ocasión, scraping sobre la web que tenemos todos en favoritos https://www.worldometers.info/coronavirus/

library(tidy verse)
library(rvest)
library(xml2)
library(tm)
url ='https://www.worldometers.info/coronavirus/'

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

worldometers <- worldometers %>% rename(pais = `Country,Other` , muertes_habitante = `Deaths/1M pop`) %>%
  mutate(muertes = as.numeric(removePunctuation(TotalDeaths))) %>%
  select(pais,muertes_habitante, muertes)

Creamos un data frame con las variables necesarias Seguir leyendo Etiquetas en scatter plot. Muertes covid por millón de habitantes vs gasto en salud

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

Datos agrupados en R con dplyr

Entrada rápida para ilustrar como crear un campo autonumérico por un factor, es una duda que me plantean, tienen datos de clientes y fechas y necesitan crear un autonumérico en R que les diga el número de orden de los eventos de una fecha. Algo parecido a lo que hacemos con el retain de R. Vamos a ilustrar la tarea con un ejemplo:

clientes = 100
id_cliente = rpois(clientes,10)
fecha = rpois(clientes, today()-rpois(clientes,5) )

eventos <- cbind.data.frame(id_cliente,fecha)

eventos$fecha <- as.Date(eventos$fecha, origin="1970-01-01")
eventos <- eventos %>% arrange(id_cliente,fecha) 

100 clientes que aparecen una o n veces con fechas asociadas, el primer paso que sugiero hacer es eliminar duplicados con dplyr:

eventos <- eventos %>% group_by(id_cliente, fecha) %>% 
  filter(row_number()==1) %>% as_tibble()

Agrupamos por cliente y fecha y nos quedamos con el primer registro, en este caso da igual quedarse con el primero que con el último. Ahora que no tenemos duplicados la agrupación ya no es por cliente y por fecha, como vamos a crear un valor agrupado por cliente haremos el group_by solo por cliente:

eventos <- eventos %>% group_by(id_cliente) %>% 
  mutate(evento=row_number()) %>% as_tibble()

Cada evento irá numerado de 1 a n gracias a row_number(), el mismo se reinicia a 1 cada vez que cambia el valor del group_by.