Archivo de la categoría: Formación

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.

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 con Pandas Python:
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 Pandas Python:

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 con Pandas Python:

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 con Pandas Python:

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

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.

Longitud de las frases del Quijote con #rstats

Siempre he querido hacer cosas con Rstats y el Quijote y ayer se me ocurrió medir la longitud de las frases del Quijote y crear un histograma que describa esta longitud. Aunque confieso que no me lo he leído, me he quedado en el capítulo 7 u 8 (no recuerdo) el caso es que me pareció hipnótico con sus ritmos, es musical. Además tengo muchas ganas de meter mano al proyecto Gutemberg porque esos ritmos, esa musicalidad, el uso de palabras esdrújulas,… me llama la atención.
Bueno, al lío, todo el código está subido al repositorio por si lo queréis, pero hay algunas funciones y algunas ideas que me parecen interesantes.

library(dplyr)
library(ggplot2)
library(plotly)

#Leemos el fichero desde proyecto Gutemberg
ubicacion <- "https://www.gutenberg.org/cache/epub/2000/pg2000.txt"
quijote <- read.table (ubicacion,sep="\r", encoding="UTF-8")
quijote <- data.frame(quijote)
names(quijote) <- 'linea'

#Transformaciones e identificar el inicio del libro.
quijote <- quijote %>%
  mutate(linea = toupper(linea),
         inicio = grepl("EN UN LUGAR DE LA MANCHA",linea)>0)

Leemos directamente un txt desde Gutemberg y prefiero transformarlo en data frame para usar dplyr. Todas las palabras las pongo en mayúsculas e identifico donde empieza el Quijote, para evitar prólogos y demás. Ya tengo unos datos con los que poder trabajar:

#Marcamos lo que vamos a leer
desde <- which(quijote$inicio)
hasta <- nrow(quijote)

#Texto de trabajo
texto <- quijote[desde:hasta,1]

#El texto lo transformamos en una lista separada por espacios
texto_split = strsplit(texto, split=" ")

#Deshacemos esa lista y tenemos el data.frame
texto_col = as.character(unlist(texto_split))
texto_col = data.frame(texto_col)
names(texto_col) = 'palabra'

En este caso los datos los quiero de tal forma que disponga de un data frame con una sola variable que sea cada palabra del Quijote. Ahora voy a medir las frases identificando donde hay puntos en esas palabras:

#Identificamos donde tenemos puntos y un autonumérico del registro
texto_col <- texto_col %>% filter(!is.na(palabra)) %>%
  mutate(punto = ifelse(grepl('.',palabra,fixed=T),"FIN","NO"),
         posicion = row_number())

¿Qué se me ha ocurrido? Trabajar con autonuméricos, tengo identificados los puntos, ahora tengo que fijar una posición inicial y una posición final:

#Si unimos las posiciones con puntos con lag podemos calcular la longitud
pos_puntos1 <- filter(texto_col,punto=="FIN") %>% 
  select(posicion) %>% mutate(id = row_number())

pos_puntos2 <- pos_puntos1 %>% mutate(id = id + 1) %>%
  rename(posicion_final = posicion)

pos_puntos <- left_join(pos_puntos1,pos_puntos2) %>%
  mutate(longitud = ifelse(is.na(posicion_final), posicion, posicion - posicion_final))

Como no soy un tipo muy brillante opto por una opción sencilla de cruzar una tabla consigo misma, como me ponen los productos cartesianos “con talento”. La idea es seleccionar solo los registros que marcan el final de la frase, un autonumérico me marca cual es cada frase, ahora si hago una left join por el id de la frase y el id + 1 de la frase creo una especie de lag. La longitud de la frase será donde está el punto menos donde estaba el final de la anterior frase. Creo que me he explicado de pena, pero si veis el data frame final lo entenderéis mejor. Ahora ya pinto un histograma:

#GRaficamos la longitud
plot_ly(data = pos_puntos, x = ~longitud, type = "histogram") %>%
  layout(title = "Longitud de las frases del Quijote",
         xaxis = list(title = "Longitud"), yaxis = list(title = ""))

Y queda una gamma perfecta, yo diría que hasta bonita. Ahora quedaría identificar los parámetros de esta gamma y compararlos con otros libros, e incluso comparar lenguas. Pero esas tareas se las dejo a los “buenos”.

Gráfico con eje secundario en ggplot2

Los gráficos con eje secundario o con dos ejes son un tema que ya he puesto en el blog en varias ocasiones, hay un ejemplo con R que tenía sus problemas y hay un ejemplo con Python y matplotlib que particularmente me gusta por elegancia y sencillez. En esta entrada vamos a repetir el ejercicio y vamos a realizar un gráfico de columnas y líneas con 2 ejes, primario y secundario pero con ggplot2. Este tipo de gráficos son muy utilizados por los actuarios para representar frecuencias o siniestralidades y exposición. Para ilustrar el ejercicio vamos a emplear los mismos datos que usamos en el ejemplo con matplotlib pero vemos paso a paso como realizaríamos el gráfico Seguir leyendo Gráfico con eje secundario en ggplot2

Trucos simples para #rstats

En mi cuenta de twitter suelo poner algunos trucos sencillos de R, cosas que me surgen cuando estoy trabajando y que no me cuesta compartir en 2 minutos, por si puedo ayudar a alguien. Me acabo de dar cuenta que de verdad son útiles y que tenerlos en twitter desperdigados es un problema, así que he pensado en recopilarlos en una entrada del blog para que sea más sencillo buscarlos (incluso para mi). Aquí van algunos de esos trucos Seguir leyendo Trucos simples para #rstats

Muestrear no es pecado

Hace unos días que nació la web de Jose Luis Cañadas y ya está añadida a los enlaces de AyD, desde aquí nos hacemos eco de esta nueva bitácora:

https://muestrear-no-es-pecado.netlify.com/

Seguiremos muy de cerca a JL porque es un tipo de esos que merece ser escuchado por como mezcla pragmatismo y rigor dos de las principales características que tienen que tener los científicos de datos y que últimamente están vilipendiadas.

Mapa de México rápido (y sucio) y estático con #rstats

No sabía como mostraros el funcionamiento de getData del paquete raster para tener que evitaros ir a GADM y descargar los correspondientes mapas. Bueno, pues se me ha ocurrido hacer una entrada que tenga el menor número de líneas posibles y que genere un mapa. No me lo tengáis mucho en cuenta:

library(ggplot2)
library(raster)
library(dplyr)

#Obtenemos el mapa de GADM
mex <- getData("GADM", country = "MX", level = 2)

#El dato que vamos a pintar
prov <- data.frame(region=unique(mex@data$NAME_2))
prov$aleatorio <- runif(nrow(prov),0,100)

#Creamos el objeto mapa al que le añadimos el dato que necesitamos pintar
mex@data$name = mex@data$NAME_2
mex <- map_data(mex)
mex <- left_join(mex,prov)

#Pintamos el mapa
ggplot(data = mex, aes(x = long, y = lat, group = group)) +
  geom_polygon(aes(fill = aleatorio)) +
  scale_fill_continuous(low="white",high="blue") +
  labs(title = "Quick and dirty") + 
  theme_void()

Ahí lo tenéis getData se conecta a GADM donde vía ISO 3 le decimos que mapa queremos y el nivel que queremos y pintamos un mapa de México con ggplot2 en un pis pas. Comentad si no entendéis algo, hay miles de entradas que hacen lo mismo de forma más detallada pero en menos líneas ninguna. Saludos.