Índice
Cómo pasé de analizar resultados con Excel a analizarlos con R+Python
Cómo empezó todo…
Todo empezó cuando me mandaron hacer una práctica para la universidad en la que tenía que:
- Desarrollar cuatro algoritmos diferentes.
- Ejecutarlos en 20 casos diferentes, desde tamaño n = 20 hasta tamaño n = 256.
- Hacer una documentación explicando el problema a resolver, cada algoritmo desarrollado e incluyendo un pseudocódigo de los mismos.
- Incluir en dicha documentación un estudio estadístico de los resultados, valorando cada algoritmo en función de cómo había trabajado con casos grandes (n grande) o casos pequeños (n pequeño) y sobre la eficacia del algoritmo en comparación con el mejor resultado conocido y con los resultados obtenidos por el resto de algoritmos. Además, incluir gráficas que apoyaran tus conclusiones estaba “muy bien visto” (codo, codo, guiño guiño).
y todo esto en unas tres semanas aproximadamente.
Puedes ver una práctica del estilo en Optimizando código y evaluando el rendimiento
Dormida en los laureles
Empecé con bastante tranquilidad pensando que en dos semanas tendría todo el trabajo hecho y me quedaría otra semana extra para documentar. A la hora de realizar los algoritmos me encontré con algunos fallos y varios quebraderos de cabeza, pero pude tenerlos todos terminados en esas dos semanas. Empecé a hacer mi documentación explicando con todo detalle todo lo que había hecho, centrándome en obtener tanto una buena presentación como una buena redacción y, finalmente, me quedaron dos días para hacer lo más importante: el análisis de los resultados.
Para hacer el análisis, el profesor nos había dejado preparada una hoja de cálculo plantilla con todos los datos que teníamos que rellenar y calcular. En rellenar la maldita plantilla se me fue media mañana, por no decir la mañana entera, ya que en Calc tienes que introducir los decimales con una coma y Python los devolvía con un punto. A pesar de esto aún no cundía demasiado el pánico pues tenía otro día más.
Cuando ya calculé con Calc todo lo que había que calcular vino el problema: poner todos los datos que había calculado en mi documento de LaTeX. Un amigo me pasó un script que te permite exportar la tabla con sintaxis tabular
de LaTeX pero, en la hoja de cálculo tenía las tablas que había hecho para todos y cada uno de los algoritmos y en mi documento tenían que ir separadas en secciones. Al final después de pelearme con Calc conseguí separar las tablas e incluirlas en mi documento, pero había perdido un precioso día para documentar y analizar los resultados que había estado todo el día preparando.
Último día. Kernel panic.
Llegó el último día que, como tenía clase por la tarde, era en realidad la última mañana. Decidí que sería una buena idea poner gráficas en mi documentación, pues con las tablas no me había dado como para escribir todo lo que me hubiese gustado y además, le darían a mi documento un extra de “profesionalidad”.
Así que me dispuse a intentar generar un gráfico en la hoja de cálculo que tenía. Intenté todas las combinaciones posibles de datos, tipos de gráficos, colores, leyendas y demás herramientas que proporciona Calc para hacer gráficos y con ninguna obtenía un buen gráfico que realmente reflejase lo que quería. De hecho, lo más decente que pude obtener fueron dos gráficos que no decían absolutamente nada sobre los datos y que no supe ni siquiera etiquetar… supongo que por los nervios de que la entrega se aproximaba y mi análisis daba pena.
Estos son los gráficos que finalmente incluí en la que iba a ser la chachi documentación extra profesional a 5 minutos de que la entrega se cerrase.
¿Podéis sacar alguna conclusión de ellos? Yo no.
Cuello de botella
Tras el gran desastre que hice en la primera práctica, me prometí que no me pasaría lo mismo en la segunda. Tras un rato de reflexión, me di cuenta de que mi principal problema fue el tiempo que gasté metiendo datos en la hoja de cálculo a mano e intentando hacer una gráfica medio decente. Tareas tediosas y, sobre todo, que podría haber automatizado con un script.
Empecé a investigar cómo podría hacer un equivalente a mi hoja de cálculo, pero exportable a LaTeX y sin tener que usar Calc. Fue entonces cuando encontré el maravillo paquete de Python llamado tabulate
. Este paquete, te permite generar a partir de una lista de listas una preciosa tabla en varios formatos (podéis consultarlos en su documentación) entre los que se encontraba tabular
de LaTeX.
Tuve la suerte de haber hecho mi práctica en python, por lo que añadirle el script que voy a describir a continuación fue pan comido.
Mi script con tabulate
Si habéis leído la documentación de tabulate
habréis visto que es bastante sencillo de usar: sólo hay que hacer una lista de listas, donde cada lista de la lista representa una fila de la tabla. En mi caso, la información que tenía que almacenar para cada algoritmo era:
Caso | Coste obtenido | Tiempo de ejecución | Desviación |
---|---|---|---|
La última columna era una forma de reflejar la diferencia del coste obtenido por el algoritmo con el mejor coste obtenido para ese caso del problema.
Mi script consistió en ejecutar todos los casos del algoritmo y guardar dos medidas, en dos listas separadas: tiempo de ejecución y coste de la solución obtenida. De antemano tenía guardados los distintos casos que tenía que ejecutar y las mejores soluciones conocidas de cada algoritmo, que usé para calcular la desviación de cada solución con la siguiente fórmula:
Una vez tenía estos datos, generé la tabla en formato LaTeX consultando las diferentes listas que había creado.
def imprime(self):
# Calculamos la desviación de cada solución con respecto a la mejor
self.calcula_desv()
# Generamos cada fila de la tabla accediendo a los datos guardados en cada
# una de las listas
table = [[self.ficheros[i], self.valores[i], self.tiempos[i],
self.desviaciones[i]] for i in range(len(self.ficheros))]
# Añadimos a la tabla una cabecera para cada columna
print(tabulate(table, headers=["Fichero", "Coste", "Tiempo", "Desviación"],
tablefmt="latex"))
# Además, calculamos la desviación media y el tiempo medio de ejecución
print("Desviación = ",self.desv())
print("Tiempo = ",self.tiempo())
Y los gráficos, ¿qué?
Una vez tuve mi script “hoja de cálculo” hecho, pasé a pensar cómo podría automatizar la creación de los gráficos que yo quería. Justo por esa época, @JJ dio una charla sobre Visualización de datos con R en la que describía el paquete ggplot2
. El funcionamiento de ggplot2
es muy sencillo, y permite obtener gráficos muy bonitos e ilustrativos a partir de un Data frame.
Antes de describir el script en R que hice, quiero enseñaros los gráficos que obtuve para que los comparéis con los que obtuve con Calc.
¿Qué algoritmo funciona mejor en casos pequeños? ¿Y en casos grandes?
Este último gráfico lo hice con los datos de la práctica 2, en vez de con los de la práctica 1. Representa la convergencia a una solución, al ser algoritmos multiarranque, pueden llegar a una buena solución y después dar con otra peor. ¿Qué algoritmo da con la mejor solución? ¿Qué algoritmo converge más rápido?
Mi script con ggplot2
Pasando datos de Python a R
ggplot2
era ideal, pero tenía un problema: ¿cómo paso mis datos de python a R? La respuesta no fue muy difícil de encontrar. Lo que hice fue añadir una función más a mi script en python que guardaba los datos de la “hoja de cálculo” en un fichero csv
. Con el fichero csv
de cada algoritmo, creaba un Data frame en R con los datos que me interesaban para mi gráfica.
def csv(self):
with open("Resultados/"+self.nombre_csv, 'w') as csvfile:
fieldnames = ["Coste","Tiempo","Desviacion"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for i in range(len(self.ficheros)):
writer.writerow({'Coste':str(self.valores[i]),
'Tiempo':str(self.tiempos[i]), 'Desviacion':str(self.desviaciones[i])})
csvfile.close()
Como también representaba en la gráfica los mejores resultados conocidos para cada caso, hice un csv
para almacenarlos y poder leerlos con R. Esta función sólo tuve que ejecutarla una vez pues la mejor solución de cada caso es la misma la compares con el algoritmo que la compares.
def mejor_csv(self):
with open("mejor_csv.csv", 'w') as csvfile:
fieldnames = ["Caso","Coste"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for i in range(len(self.ficheros)):
writer.writerow({'Caso':self.ficheros[i].split("/",1)[1],
'Coste':str(self.mejores_sol[i])})
csvfile.close()
Representando en R los datos
Una vez tenía los datos en ficheros csv
sólo tenía que usar la función read.csv
para convertirlos en un Data frame. En mi caso, como habréis visto en las gráficas, sólo estaba interesada en el coste obtenido por cada algoritmo en casos pequeños y grandes, por lo que obivé los datos relacionados con el tiempo de ejecución.
# Parámetros de la función:
# resultados: nombre de los ficheros csv de cada algoritmo
# nombres: nombres de las columnas en el data frame
construye_datos <- function(resultados, nombres) {
# leemos los datos sobre los mejores costes obtenidos para cada caso
mejor <- read.csv('Resultados/mejor_csv.csv')
# y los costes obtenidos por el algoritmo greedy, este algoritmo se compara
# con todos los algoritmos desarrollados en todas las prácticas.
greedy <- read.csv('Resultados/resultados_greedy.csv')
# leemos el resto de datos de los algoritmos de la práctica concreta
algoritmos <- lapply(X=resultados, FUN=function(r) read.csv(paste("Resultados/", r, sep="")))
# hacemos un data frame guardando únicamente el coste obtenido por cada
# algoritmo.
d <- data.frame(cbind(mejor, greedy, algoritmos))
names(d) <- c("Caso", "MejorCoste", "CosteGreedy", "TiempoGreedy", "DesvGreedy", nombres)
d
}
Una vez tenía un Data frame con los datos que quería representar en la gráfica, pasaba a realizar la gráfica con otra función. Debido a que quería representar tres medidas diferentes en la gráfica (coste obtenido por cada algoritmo en algunos de los casos), tuve que hacer un tratamiento previo al Data frame para conseguirlo.
La función construye_datos
aplicada sobre dos algoritmos “Ejemplo 1” y “Ejemplo 2” nos devuelve un Data frame con la siguiente forma:
Caso | MejorCoste | CosteGreedy | TiempoGreedy | DesvGreedy | CosteEj1 | TiempoEj1 | DesvEj1 | CosteEj2 | TiempoEj2 | DesvEj2 |
---|---|---|---|---|---|---|---|---|---|---|
De esos datos, como he dicho antes, sólo nos interesan los relativos al coste obtenido para cada caso. Por tanto, el primer “procesamiento” que debemos hacer al Data frame es quedarnos sólo con las columnas que nos interesan:
Caso | MejorCoste | CosteGreedy | CosteEj1 | CosteEj2 |
---|---|---|---|---|
Una vez hecho esto, tenía que “reorganizar” el Data frame de forma que pasase a tener únicamente 3 columnas, que son las que iba a representar en la gráfica:
Caso | variable | value |
---|---|---|
chr20a.dat | MejorCoste | 2192 |
chr20c.dat | MejorCoste | 14142 |
chr22b.dat | MejorCoste | 6194 |
chr25a.dat | MejorCoste | 3796 |
chr20a.dat | CosteGreedy | 8100 |
chr20c.dat | CosteGreedy | 78718 |
chr22b.dat | CosteGreedy | 11942 |
chr25a.dat | CosteGreedy | 17556 |
chr20a.dat | CosteEj1 | 2418 |
chr20c.dat | CosteEj1 | 15672 |
chr22b.dat | CosteEj1 | 6530 |
chr25a.dat | CosteEj1 | 4762 |
chr20a.dat | CosteEj2 | 2546 |
chr20c.dat | CosteEj2 | 16986 |
chr22b.dat | CosteEj2 | 6660 |
chr25a.dat | CosteEj2 | 4440 |
Como veis, usando la columna Caso como identificador, hemos reordenado la tabla de manera que, para cada algoritmo, tendremos un gráfico de barras representando el coste obtenido en cada caso.
Una vez explicado paso a paso el tratamiento que hice a los datos, paso a enseñar la función que hace la gráfica:
# Parámetros de la función:
# datos: Data frame que devuelve la función constuye_datos
# cols: columnas del data frame que vamos a representar en la gráfica
# (en nuestro caso, el coste obtenido por cada algoritmo para cada caso)
# nombres: nombres de cada columna del data frame que aparecerán en la gráfica
# file: nombre del archivo en el que guardaremos las gráficas finalmente
representa_datos <- function(datos, cols, nombres, file) {
# importación de librerías
library(ggplot2) # para hacer gŕaficos
library(reshape2) # para poder redistribuir el data frame
# nos quedamos con las columnas del data frame que nos interesan
d_c <- data.frame(cbind(datos[,cols]))
names(d_c) <- c("Caso", "Mejor Coste", "Coste Greedy", nombres)
d_c$Caso = datos$Caso
# filtramos los casos que empiezan por chr2, ya que estos son casos
# bastante pequeños y redistribuimos el data frame
d_chr <- melt(d_c[grepl("chr2",datos$Caso),])
names(d_chr) <- c("Caso", "Algoritmo", "Coste")
# representamos la gráfica de barras
ggplot(d_chr, aes(Caso, Coste)) + geom_bar(aes(fill=Algoritmo), position="dodge", stat="identity")
# guardamos el archivo
ggsave(paste("chr",file,".png",sep=""))
# repetimos para casos tai, que son casos grandes.
d_tai <- melt(d_c[grepl("tai",datos$Caso),])
names(d_tai) <- c("Caso", "Algoritmo", "Coste")
ggplot(d_tai, aes(Caso, Coste)) + geom_bar(aes(fill=Algoritmo), position="dodge", stat="identity")
ggsave(paste("tai",file,".png",sep=""))
}
Por ejemplo, para obtener la gráfica de los algoritmos “Ejemplo 1” y “Ejemplo 2”, realizaríamos la siguiente llamada:
datos_prueba <- construye_datos(resultados = c("resultados_Ej1.csv","resultados_Ej2.csv"),
nombres = c("CosteEj1", "TiempoEj1", "DesvEj1","CosteEj2", "TiempoEj2", "DesvEj2"))
representa_datos(datos=datos_prueba, cols=c(1,2,3,6,9), nombres=c("Coste Ej1", "Coste Ej2), file="prueba")
Conseguí optimizar tanto el tiempo con estos scripts, que me sobró tiempo para implementar un gráfico de convergencia en R. Para ello, modifiqué mi práctica para que guardase en un csv
cada coste que iba encontrando, para después representarlo en una gráfica. Una vez comprendida la función anterior, esta no tiene apenas ninguna diferencia:
representa_convergencia <- function(resultados, nombres, file) {
library(ggplot2)
library(reshape2)
# leemos la convergencia de cada algoritmo
convergencias <- lapply(X=resultados, FUN=function(r) read.csv(paste("Resultados/",r,sep="")))
# El csv guarda tanto las iteraciones dadas por el algoritmo como el coste
# obtenido en cada iteración. Como sólo nos interesa el coste, nos quedamos
# con la segunda columna.
c <- lapply(X=convergencias, FUN=function(conv) conv[[2]])
# hacemos un nuevo data frame con la siguiente estructura:
# Iteraciones ConvergenciaAlgoritmo 1 ..... ConvergenciaAlgoritmo n
d_c <- data.frame(convergencias[[1]][,1],c)
names(d_c) <- nombres
d_c
# reorganizamos el data frame
d_cm <- melt(d_c, id.vars=nombres[1])
names(d_cm) <- c(nombres[1], "Algoritmo", "Coste")
# lo representamos con ggplot. Hago la distinción de si los datos se han
# obtenido por número de llamadas a la función de evaluación o por número de
# iteraciones.
if (nombres[1] == "Evaluaciones")
ggplot(d_cm, aes(Evaluaciones,Coste)) + geom_line(aes(color=Algoritmo))
else
ggplot(d_cm, aes(Iteraciones,Coste)) + geom_line(aes(color=Algoritmo))
# y lo guardamos.
ggsave(paste("conver",file,".png", sep=""))
}
Referencias
El código de la práctica está disponible en el GitHub de Marta: github.com/mgmacias95/Analisis-Resultados-R-Python
Más sobre R y Python
Puedes consultar los siguientes atrículos sobre R y python:
- Vectorizar El Acceso a Columnas en R
- Cómo modificar Una Variable Desde Dentro De Una Función en R
- Introducción a las expresiones regulares en python
- Generar listas de reproducción de una determinada duración con Python
- Crear Una Lista De Listas De Forma Eficiente en Python
- Cómo Añadir Automáticamente El Tamaño De Una Imagen en HTML Con Python
- Los artículos etiquetados con estos temas se encuentran en /tags/r y /tags/python
¿Has visto algún error?: Por favor, ayúdame a corregirlo contactando conmigo o comentando abajo.