Donaciones gracias a Packtpub

[Go to english version]

Hola a todos.

Durante la semana del 5 al 11 de junio (desde el próximo lunes al próximo domingo), desde PacktPub, nos han propuesto usar un enlace de afiliación que se usará para que donen 1$ (hasta 1000$) a la organización sin ánimo de lucro que les indiquemos.

¿Cómo funcionará la campaña y cómo nos podéis ayudar?

Os animamos a participar, os animamos a elegir a qué organización queréis que donemos y os animamos a descargaros los libros de PacktPub de la semana que viene usando nuestro link de afiliación. Shhhh, os adelantamos que habrá tres de los siete que tratarán sobre Python.

Muchas gracias a todos por participar.

Saludos.

P.D..: Aviso para navegantes, no sacamos nada de este acuerdo más que poder ayudar a la comunidad que elijáis.


English version

Hi,

During the week starting Monday, June 5, until next Sunday, PacktPub, has proposed us to promote an affiliation link that will be used to donate 1$ (up to 1000$) to the non-profit organization we prefer.

How works the campaing and how could you help us?

We encourage you to participate, to choose the organization and to download the free ebook using the affiliation link. Shhhh, thee out of the seven books will be about Python.

Thank you very much for participating.

Saludos.

PS: Pybonacci do not obtain any economical profit or earnings from Packtpub.

Como hacer un mapa muy bonito de España en ggplot2

(Este post apareció originalmente en mi blog).

Hace unas semanas leí un artículo en el cual Timo Grossenbacher mostraba como consiguió hacer, en mi opinión, uno de los mapas más bonitos que he visto nunca. Timo empleó la que es, en mi opinión, una de las librerias más expresivas y bellas que hay para hacer gráficos, ggplot2. La versión original de ggplot2 es para R, pero existe una versión de python no exhaustiva gracias a la buena gente de Yhat.

Asi que por supuesto, tenía que replicarlo.

Antes que nada, aquí está el mapa.

Mapilla

El código empleado para hacer el mapa lo podeis descargar en github. He compartido varias versiones del mapa para que se pueda observar como los diferentes cambios en las escalas afectan a la visualización.

Código.

Para empezar, importamos las librerías necesarias:

 
setwd("/DIRECTORIO_DE_TRABAJO/")

if (!require(rgdal)) {
install.packages("rgdal", repos = "http://cran.us.r-project.org")
require(rgdal)
}

if (!require(rgeos)) {
install.packages("rgeos", repos = "http://cran.us.r-project.org")
require(rgeos)
}
if (!require(rgdal)) {
install.packages("rgdal", repos = "http://cran.us.r-project.org")
require(rgdal)
}
if (!require(raster)) {
install.packages("raster", repos = "http://cran.us.r-project.org")
require(raster)
}
if(!require(ggplot2)) {
install.packages("ggplot2", repos="http://cloud.r-project.org")
require(ggplot2)
}
if(!require(viridis)) {
install.packages("viridis", repos="http://cloud.r-project.org")
require(viridis)
}
if(!require(dplyr)) {
install.packages("dplyr", repos = "https://cloud.r-project.org/")
require(dplyr)
}
if(!require(gtable)) {
install.packages("gtable", repos = "https://cloud.r-project.org/")
require(gtable)
}
if(!require(grid)) {
install.packages("grid", repos = "https://cloud.r-project.org/")
require(grid)
}
if(!require(tidyr)) {
install.packages("tidyr", repos = "https://cloud.r-project.org/")
require(tidyr)
}
}
 

El siguiente paso es importar los datos. Tras mucho buscar, encontré un archivo shapefile con los municipios españoles en ArcGis, sin ninguna atribución que pudiera encontrar.

Para obtener los datos del censo español, hice uso de la *fantástica* herramienta de extracción de datos proporcionada por el Instituto Nacional de Estadística. La herramienta es una pesadilla en términos de usabilidad, así que si queréis simplemente hacer el mapa he compartido los datos en el repositorio.

 
data_spain data_spain$municipality_code data_spain$People data_spain$Average.age

#Cargamos el shapefile y lo convertimos en un dataframe
municipalities_spain map_data_fortified_spain % mutate(id = as.numeric(id))

#Ahora unimos los datos del censo con los datos geométricos usando municipality_code como clave
map_data_spain % left_join(data_spain, by = c("id" = "municipality_code")) %>% fill(Average.age)
rm(data_spain)
rm(map_data_fortified_spain)
rm(municipalities_spain)
 

Finalmente, el código para hacer el mapa en sí. Hay muchísima lógica en dicho código orientada a hacer el mapa más bonito, os recomiendo mirar el artículo original para ver la evolución de los parámetros del gráfico, en particular todo lo relativo a la escala de colores.

 
# Aquí hacemos que los saltos de la escala de edades sean más bonitos e informativos visualmente

# encontramos los extremos del rango de edad
minVal maxVal # calculamos los valores de las etiquetas de los rangos de edad
labels brks # Redondeamos los extremos del rango de edad
for(idx in 1:length(brks)){
labels }

labels # definimos una nueva variable con los datos de la escala de edad
map_data_spain$brks breaks = brks,
include.lowest = TRUE,
labels = labels)

brks_scale labels_scale

theme_map theme_minimal() +
theme(
text = element_text(family = "Ubuntu Regular", color = "#22211d"),
axis.line = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
# panel.grid.minor = element_line(color = "#ebebe5", size = 0.2),
panel.grid.major = element_line(color = "#ebebe5", size = 0.2),
panel.grid.minor = element_blank(),
plot.background = element_rect(fill = "#f5f5f2", color = NA),
panel.background = element_rect(fill = "#f5f5f2", color = NA),
legend.background = element_rect(fill = "#f5f5f2", color = NA),
panel.border = element_blank(),
...
)
}

#Esta función simplemente extiende los extremos de la escala de edad para llegar al mínimo y el máximo
extendLegendWithExtremes p_grob legend legend_grobs # grab the first key of legend
legend_first_key legend_first_key$widths # modify its width and x properties to make it longer
legend_first_key$grobs[[1]]$width legend_first_key$grobs[[1]]$x

# último valor de la leyenda
legend_last_key legend_last_key$widths

legend_last_key$grobs[[1]]$width legend_last_key$grobs[[1]]$x

# cambiamos también la posición de la última etiqueta para que no se superponga a la anterior
legend_last_label legend_last_label$grobs[[1]]$x

# Insertamos el nuevo color de la leyenda en la leyenda combinada
legend_grobs$grobs[legend_grobs$layout$name == "key-3-1-1"][[1]] <-
legend_first_key$grobs[[1]]
legend_grobs$grobs[legend_grobs$layout$name == "key-3-6-1"][[1]] <-
legend_last_key$grobs[[1]]
legend_grobs$grobs[legend_grobs$layout$name == "label-5-6"][[1]] <-
legend_last_label$grobs[[1]]

# Ahora lo mismo para el valor mínimo de la leyenda
new_first_label new_first_label$label new_first_label$x new_first_label$hjust

legend_grobs new_first_label,
t = 6,
l = 2,
name = "label-5-0",
clip = "off")
legend$grobs[[1]]$grobs[1][[1]] p_grob$grobs[p_grob$layout$name == "guide-box"][[1]]

# se usa esta función para dibujar la escala
grid.newpage()
grid.draw(p_grob)
}

p geom_polygon(data = map_data_spain, aes(fill = brks,
x = long,
y = lat,
group = group)) +
# municipality outline
geom_path(data = map_data_spain, aes(x = long,
y = lat,
group = group),
color = "white", size = 0.1) +
coord_equal() +
theme_map() +
theme(
legend.position = c(0.7, 0.03),
legend.text.align = 0,
legend.background = element_rect(fill = alpha('white', 0.0)),
legend.text = element_text(size = 14, hjust = 0, color = "#4e4d47"),
legend.title = element_text(size = 20),
plot.title = element_text(size = 28, hjust = 0.8, color = "#4e4d47"),
plot.subtitle = element_text(size = 20, hjust = 0.8, face = "italic", color = "#4e4d47"),
plot.caption = element_text(size = 14, hjust = 0.95, color = "#4e4d47"),
plot.margin = unit(c(.5,.5,.2,.5), "cm"),
panel.border = element_blank()
) +
labs(x = NULL,
y = NULL,
title = "Spain's regional demographics",
subtitle = "Average age in Spanish municipalities, 2011",
caption = "Author: Manuel Garrido (@manugarri) Original Idea: Timo Grossenbacher (@grssnbchr), Geometries: ArcGis Data: INE, 2011;") +
scale_fill_manual(
values = rev(magma(8, alpha = 0.8)[2:7]),
breaks = rev(brks_scale),
name = "Average age",
drop = FALSE,
labels = labels_scale,
guide = guide_legend(
direction = "horizontal",
keyheight = unit(2, units = "mm"),
keywidth = unit(70/length(labels), units = "mm"),
title.position = 'top',
title.hjust = 0.5,
label.hjust = 1,
nrow = 1,
byrow = T,
reverse = T,
label.position = "bottom"
)
)
extendLegendWithExtremes(p)
 

Este código está diseñado muy cuidadosamente para exportar una imagen con un ancho de 2400 píxeles.

Dado que las islas Canarias estan muy alejadas de la península, una práctica común es desplazar las islas más cerca de España, esto lo he hecho en Gimp.

Notas.

- Yo sabía que España tenía un problema poblacional, pero ¡madre mía! El noroeste del pais parece un gran asilo. El mapa original de Suiza tenia una escala de edad en el rango 40-52 años, pero he tenido que expandirlo a 40-56 debido al envejecimiento poblacional español.

- Una vez más, me he dado cuenta de lo mal que está el movimiento Open Data en España:

  • La herramienta de extracción de datos del INE parece que se hizo en los años 90 (tanto en usabilidad como en velocidad).
  • La información del censo de España se actualiza, ¡cada 10 años!. Esto significa que este mapa está usando la información más actualizada que existe de la población española, y es de 2011. Estos datos deberían actualizarse más frecuentemente en un mundo en el que todo cambia más rápido.
  • Si vais al mapa del artículo original, vereis que su mapa tiene una capa de topografía muy bonita encima de los municipios. Timo usó una imagen ráster a escala 1:1.000.000 con información topográfica proporcionada por la oficina federal de topografía suiza.
    Yo me tiré un dia entero buscando algo similar para España, y según parece el Centro Nacional de Información Geográfica sólo proporciona mapas ráster en la escala de 25 y 50 metros lo que obliga a descargarte cientos de archivos de imagen y unirlos luego. De verdad que yo no tenía ganas de hacer eso para hacer un mapa a escala tan pequeña. Al final, me hice mi propia imágen topográfica raster tomando imágenes de internet que procesé en Gimp. No incluyo el relieve en el mapa por que, al contrario que con Suiza (donde no hay municipios en la región de los Alpes), en España mostrar el relieve ocultaría la información de los municipios por debajo.

- Aunque tengo que decir que la cantidad de esfuerzo y dedicación que ha llevado realizar este mapa es impresionante (una de las principales razones por las cuales yo quería replicarlo), creo que debería haber una manera mejor de hacer gráficas customizadas en ggplot2. El código actual solo funciona con una resolución específica en mente, y se necesitan un montón de pruebas hasta llegar a encontrar los tamaños de fuentes que hacen que todo encaje. Idealmente ggplot2 usaría un sistema de referencia variable (algo como los em en css) que cambiara en funcion del tamaño del mapa.

Eso es todo por hoy, muchas gracias por vuestra atención 🙂

Manejo de atributos mediante propiedades.

En Python, cuando creamos una clase, definimos una serie de atributos (que indicarán su estado) y de métodos (que marcarán su comportamiento). El acceso a ambos se realiza de manera sencilla mediante el operador punto. Un ejemplo de ello (como nota decir que todo el código mostrado en esta entrada está escrito en Python 3 sobre el IDE PyScripter)  es el siguiente:

class MiClase:
    def __init__(self, midato):
        self.dato = midato
    def cuadrado(self):
        return self.dato ** 2
a = MiClase(12.72)
print("El cuadrado es:", a.cuadrado())
a.dato = 7.72
print("El cuadrado es:", a.cuadrado())

La salida sería:

>>> 
El cuadrado es: 161.79840000000002
El cuadrado es: 59.5984
>>>

Para ejemplos sencillos podría ser suficiente actuar de esta manera, pero en otros momentos necesitaremos más versatilidad. Imaginemos que queremos validar los atributos con una determinada condición. En nuestro caso podría ser que los valores de dato estuviesen entre 0 y 10. A los valores menores que 0 se les asignaría el 0 y a los mayores de 10, el 10. Todo ello se lograría fácilmente creando un método que manejase esa condición y ejecutándolo también en __init__():

class MiClase:
   def __init__(self, midato):
       self.set_dato(midato)
   def set_dato(self, midato):
       if isinstance(midato, (int, float)):
           if midato < 0:
               self.dato = 0
           elif midato > 10:
               self.dato = 10
           else:
               self.dato = midato
       else:
           raise ValueError("Dato no válido")
   def cuadrado(self):
       return self.dato ** 2
a = MiClase(12.72)
print("El cuadrado es:", a.cuadrado())
a.set_dato(7.72)
print("El cuadrado es:", a.cuadrado())

Con salida:

>>> 
El cuadrado es: 100
El cuadrado es: 59.5984
>>>

En el método set_dato() hacemos la comprobación que el dato es entero o real. De lo contrario lanzamos una excepción. Con ello nos aseguraríamos que el dato cumple las condiciones que queremos.

De la misma forma que hemos creado un método para manejar la escritura del atributo dato podríamos hacerlo para su lectura. Son los denominados, de forma genérica, métodos getter y setter, que nos permiten, respectivamente, obtener o modificar los atributos. El concepto de encapsulación de datos incluye, además del uso de estos métodos, el que los atributos sean privados (no accesibles desde fuera de la propia clase), algo que en Python se logra añadiendo un doble guión bajo al nombre del atributo. Tendríamos lo siguiente:

class MiClase:
    def __init__(self, midato):
        self.set_dato(midato)
    def get_dato(self):
        return self.__dato
    def set_dato(self, midato):
        if isinstance(midato, (int, float)):
            if midato < 0:
                self.__dato = 0
            elif midato > 10:
                self.__dato = 10
            else:
                self.__dato = midato
        else:
            raise ValueError("Dato no válido")
        return None
    def cuadrado(self):
        return self.__dato ** 2
a = MiClase(12.72)
print("El cuadrado es:", a.cuadrado())
a.set_dato(7.72)
print("El cuadrado de", a.get_dato(), "es", a.cuadrado())

La salida es:

>>> 
El cuadrado es: 100
El cuadrado de 7.72 es 59.5984
>>>

En Python es habitual que los atributos que en un principio son simples datos estáticos con el tiempo se conviertan en expresiones a calcular dinámicamente o, como ya hemos visto, haya que validarlos de alguna manera. Si actuamos con métodos getter y setter desde el principio podremos hacer esa nueva implementación sin cambiar la forma en la que accedemos a los datos, pero si lo hemos hecho simplemente mediante el operador punto, estaríamos en una situación delicada. Es para superar este escollo, o conseguir una forma mas pythónica que los métodos getter y setter, para lo que se usan las propiedades.

¿Qué son exactamente las propiedades?

Las propiedades son atributos que manejamos mediante métodos getter, setter y deleter, por lo que podríamos llamarlos "atributos manejados". Podemos considerarlos unos atributos "especiales". En realidad en Python los datos, métodos y propiedades de una clase son todos atributos. Los métodos serían atributos "llamables" y las propiedades atributos "personalizables". La única diferencia entre una propiedad y un atributo estándar es que las primeras pueden invocar código personalizado al ser obtenidas, modificadas o eliminadas. Las propiedades se asocian a los atributos de la clase y, como cualquiera de ellos, son heredados en subclases e instancias. Una propiedad maneja un solo y específico atributo, y siempre harán referencia a los métodos de la clase en la que estén definidas.
Por lo tanto, las propiedades parecen atributos estándar, pero al acceder a ellos se lanzan los métodos getter, setter o deleter correspondientes. Como éstos métodos reciben el argumento self, podremos acceder a todos los elementos de la clase o la instancia (sus atributos y métodos) y hacer uso de ellos.

¿Cuándo usar entonces las propiedades?

Respecto al uso desde el principio de simples métodos getter , setter y deleter, las propiedades nos aportan un código mas pythónico, siendo mas fácil la lectura y escritura de los atributos. La interfaz es mas homogénea ya que a todo se accede mediante el operador punto.

Respecto al uso de atributos estándar, las propiedades nos dan la posibilidad de incorporar código que se ejecute de forma dinámica cuando intentamos acceder a ellos (para obtenerlos, modificarlos o borrarlos). Imaginemos el caso comentado con anterioridad: tenemos hecho un programa donde accedemos a atributos estándar públicos. Posteriormente necesitamos que esos atributos estándar sean validados con unas determinadas condiciones. Mediante las propiedades lograremos nuestro objetivo sin modificar el formato del código ya escrito, algo que no lograríamos con los métodos.

¿Cómo crear las propiedades?

La función property(), integrada en el intérprete, nos permite canalizar la lectura o escritura de los atributos (además de interceptar el momento en el que son borrados o proporcionar documentación sobre ellos) mediante funciones o métodos. Su formato es el siguiente:

atributo_de_clase = property(fget=None, fset=None,fdel=None, doc)

Ninguno de los parámetros es obligatorio. Si no los pasamos su valor por defecto es None. La función fget() se encargará de interceptar la lectura del atributo, la fset() de hacerlo cuando se escriba, la fdel() a la hora de borrarlo y el argumento doc recibirá una cadena para documentar el atributo (si no lo recibe, se copia el docstring de fget(),que por defecto tiene valor None). Si alguna operación no está permitida(por ejemplo si intentamos borrar un atributo, algo no demasiado habitual, y no tenemos indicada fdel()) se lanzará una excepción. La función fget() devolverá el valor procesado del atributo y tanto fset() como fdel() devolverán None. La función property() devuelve un objeto de tipo propiedad que se asigna al nombre del atributo. Un primer ejemplo del uso de las propiedades podría ser el siguiente:

class Persona:
    def __init__(self, nombre):
        self.set_nombre(nombre)
    def get_nombre(self):
        try:
            print("Pedimos atributo:")
            return self.__nombre
        except AttributeError:
            print("Error. No existe el atributo indicado")
        except:
            print("Error al acceder al atributo")
    def set_nombre(self, nuevo_nombre):
        print("Asignamos el valor", nuevo_nombre,"al atributo 'nombre'")
        self.__nombre = nuevo_nombre
        return None
    def del_nombre(self):
        try:
            print("Borramos atributo", self.__nombre)
            del self.__nombre
        except AttributeError:
            print("Error. No existe el atributo que desea borrar")
        except:
            print("Error al intentar borrar el atributo")
        return None
    nombre = property(get_nombre, set_nombre, del_nombre, "Mi información")
def main():
    a = Persona("Pepe")
    a.nombre = "Juan"
    print(a.nombre)
    del a.nombre
    del a.nombre
    print(a.nombre)
    a.nombre = "Elena"
    print(a.nombre)
    print(help(Persona.nombre))
main()

Obtenemos la siguiente salida:

>>> 
Asignamos el valor Pepe al atributo 'nombre'
Asignamos el valor Juan al atributo 'nombre'
Pedimos atributo:
Juan
Borramos atributo Juan
Error. No existe el atributo que desea borrar
Pedimos atributo:
Error. No existe el atributo indicado
None
Asignamos el valor Elena al atributo 'nombre'
Pedimos atributo:
Elena
Help on property:
    Mi información
None
>>>

En él se ha creado una clase Persona con un atributo privado nombre y tres métodos de tipo getter, setter y deleter. Posteriormente se crea, mediante la función property(), la propiedad nombre vinculada a ellos, que reemplaza al atributo del mismo nombre. Se añade además una información de ayuda. Más adelante creamos una instancia de Persona a la que posteriormente cambiamos el atributo nombre, tras lo cual lo borramos. Al intentar  borrarlo de nuevo, generamos un error que manejamos. También se lanza un error manejado al intentar leer el atributo borrado. Es interesante ejecutar el código paso a paso para ver su funcionamiento exacto. En este ejemplo, por simplicidad, no hemos manejado posibles excepciones en el método setter, pero sería conveniente hacerlo en un código completo.

Si no quisiésemos contravenir uno de los principios del Zen de Python ("Debería haber una, y preferiblemente solo una, forma obvia de hacer las cosas") los métodos get_nombre(), set_nombre() y del_nombre() deberían ser privados (algo que nuevamente logramos colocando un doble guión bajo antes de su nombre).

Pero tampoco es ésta la forma mas pythónica para tratar con las propiedades, algo que se consigue mediante el uso de decoradores. Recordemos que un decorador es básicamente una función que "envuelve" a otra dotándola (al añadir código) de alguna funcionalidad extra. El formato es el siguiente:

def mifunción(argumentos):
    ...
mifunción = decorador(mifunción)

También podríamos ponerlo así:

@decorador
def mifunción(argumentos)
    ...

Podemos ahora usar la función property() como decorador para que se ejecute cuando queramos acceder a uno de los atributos.

class Miclase:
    def mi_atributo(self):
        ...
    mi_atributo = property(mi_atributo)

O también colocarlo de la siguiente manera:

class Miclase:
     @property
     def mi_atributo(self):
         ...

De esta manera lograríamos pasar mi_atributo como el primer argumento de la función property(), que es el que se usa cuando intentamos leer. El objeto propiedad tiene métodos getter, setter y deleter que asignan los métodos de acceso de la propiedad y que devuelven una copia de la propia propiedad. Los podemos, a su vez, usar para decorar métodos con el mismo nombre mi_atributo que usaremos en el momento en que intentamos modificar o borrar el atributo. Es un poco lioso, por lo que el código nos puede aclarar un poco las cosas:

class Persona:
    def __init__(self, nombre):
        self.nombre = nombre
    @property
    def nombre(self):
        "Documentación del atributo 'nombre' "
        try:
            print("Pedimos atributo:")
            return self.__nombre
        except AttributeError:
            print("Error. No existe el atributo indicado")
        except:
            print("Error al leer el atributo")
    @nombre.setter
    def nombre(self, nuevo_nombre):
        print("Asignamos el valor",nuevo_nombre," al atributo 'nombre'")
        self.__nombre = nuevo_nombre
        return None
    @nombre.deleter
    def nombre(self):
        try:
            print("Borramos atributo", self.__nombre)
            del self.__nombre
        except AttributeError:
             print("Error. No existe el atributo que desea borrar")
        except:
             print("Error al borrar atributo")
        return None
def main():
    a = Persona("Pepe")
    a.nombre = "Juan"
    print(a.nombre)
    del a.nombre
    del a.nombre
    print(a.nombre)
    a.nombre = "Jaime"
    print(a.nombre)
    print(help(Persona.nombre))
main()

Salida:

>>> 
Asignamos el valor Pepe al atributo 'nombre'
Valor del atributo 'nombre':
Pepe
Asignamos el valor Juan al atributo 'nombre'
Valor del atributo 'nombre':
Juan
Borramos atributo Juan
Error. No existe el atributo que desea borrar
Valor del atributo 'nombre':
Error. No existe el atributo indicado
None
Asignamos el valor Jaime al atributo 'nombre'
Valor del atributo 'nombre':
Jaime
Help on property:
    Documentación del atributo 'nombre'
None
>>>

En el código definimos en  __init__() el atributo nombre como público. Eso nos permitirá que se ejecute, al crear la instancia de Persona, el método setter asociado a su propiedad y por tanto añadir el código que creamos conveniente. Nuevamente, por simplicidad, no se ha añadido manejo de excepciones ni condiciones de validación en él. Mediante la función property() decoramos el método getter que tiene el mismo nombre que nuestro atributo, es decir, nombre. Una vez hecho ésto, podemos usar los métodos setter y deleter del objeto propiedad para decorar los métodos correspondientes, de nombre nombre. Esta si es la forma mas pythónica de tratar las propiedades.

Los métodos getter de las propiedades pueden ser muy útiles si necesitamos calcular atributos sobre la marcha teniendo como base otros atributos. O también si queremos almacenar el resultado de un cálculo complejo para que sucesivas peticiones de ese cálculo no lo realicen de nuevo, sino que recuperen el resultado previo. Un ejemplo heterodoxo del uso de todo ello  es el siguiente: imaginemos que queremos crear una clase que almacene uno o una serie de valores. Podemos tener  o un número(entero o real) o una lista de números (reales y/o enteros). Como condición de validación tendremos que el número o números esté/n entre 0 y 10 (inclusive). De no ser así asignaremos al número el valor 0. Hay en la clase una propiedad llamada que simula (mediante el uso del módulo time) una operación pesada computacionalmente aplicada a los datos. Simulamos que nos cuesta tres segundos calcularla y el resultado es 123.79382.  Lo almacenamos en transformada  por si hay una posterior petición de su cálculo. Otra propiedad llamada media calcula el promedio de los datos ya filtrados mediante la validación. Se comprueba que los datos de entrada son correctos y si no se lanza una excepción de tipo ValueError.

El código y su correspondiente salida serían algo así, aconsejando una ejecución paso a paso para observar su funcionamiento detalladamente:

import time
class MiClase:
   def __init__(self, valor=None):
       self.valor = valor
       self.__transformada = None
   @property
   def valor(self):
       return self.__valor
   @valor.setter
   def valor(self, mivalor):
       if isinstance(mivalor, (int, float)):
           if 0 <= mivalor <=10:
               self.__valor = mivalor
           else:
               self.__valor = 0
       elif isinstance(mivalor, list):
           res = []
           for midato in mivalor:
               if isinstance(midato, (int, float)):
                   if 0 <= midato <=10:
                       res.append(midato)
                   else:
                       res.append(0)
               else:
                   raise ValueError ("El dato introducido no es válido")
           self.__valor = res
       else:
           raise ValueError("El dato introducido no es válido")
       return None
    @property
    def transformada(self):
        if not self.__transformada:
            print("Calculando transformada...")
            time.sleep(3)
            self.__transformada = 123.79382
        else:
            print("Transformada en caché. Valor:")
        return self.__transformada
    @property
    def media(self):
        if isinstance(self.valor, list):
            return sum(self.__valor) / len(self.__valor)
        else:
            return "\nNo se puede calcular la media al no ser una lista"
def main():
    datos = [12.21, 8.68, -2, 7.77]
    print("Los datos introducidos son: ", datos)
    a = MiClase(datos)
    print("Los datos filtrados son: ", a.valor)
    print("La media de los datos filtrados es:", a.media)
    print(a.transformada)
    print(a.transformada)
main()
>>> 
Los datos introducidos son: [12.21, 8.68, -2, 7.77]
Los datos filtrados son: [0, 8.68, 0, 7.77]
La media de los datos filtrados es: 4.1125
Calculando transformada...
123.79382
Transformada en caché. Valor:
123.79382
>>> 

Revisión del libro “Python 3, Curso Práctico” de Alberto Cuevas

Nos han pedido una revisión de un nuevo libro sobre Python en español. El libro se titula 'Python 3, Curso Práctico' y lo ha escrito Alberto Cuevas Álvarez. Si seguís leyendo podréis ver cómo ganar una copia en papel del mismo ;-D

Primero de todo, algunas características del libro:

  • Título: Python 3, Curso Práctico.
  • Autor: Alberto Cuevas Álvarez
  • Año de edición: 2016
  • Nº páginas: 560
  • ISBN: 978-84-9964-658-9
  • Editorial: RA-MA EDITORIAL
  • Encuadernación: Rústica
  • Idioma: Español (de España).
  • Versión de Python usada en el libro: 3.3
  • Link: http://www.ra-ma.es/libros/PYTHON-3-CURSO-PRACTICO/94627/978-84-9964-658-9
  • Precio: 31.90 € para la versión en papel.
  • Versión electrónica: No disponible de momento.

Le hemos pedido al autor que nos defina su intención al escribir el libro y esta es la frase resumen que nos ha enviado:

"Mi intención a la hora de realizar el libro ha sido explicar los fundamentos básicos de Python y las herramientas necesarias dentro de su ecosistema para conseguir crear aplicaciones gráficas completas en 2D ."

Como venimos haciendo, vamos a ir viendo el libro capítulo a capítulo para poder comentar de forma pormenorizada las pequeñas piezas que componen el mismo:

INTRODUCCIÓN

El libro empieza fuerte explicando, aunque sea muy por encima, cosas básicas de programación que pueden venir bien para fijar ciertos conceptos si no se dispone de cierto bagaje en ciencias de la computación.

EMPEZANDO A PROGRAMAR

En este punto se introducen conceptos importantes como lo que son las variables, cómo se asignan, ciertas funciones integradas (builtin) en el intérprete, operadores,etc. Cosas básicas introducidas en el momento oportuno. Está bien lo detallado de algún punto con múltiples ejemplos útiles. Se comenta la instalación de PyScripter, un IDE solo windows 🙁

ELEMENTOS FUNDAMENTALES DE PROGRAMACIÓN: INSTRUCCIÓN CONDICIONAL Y BUCLES

El capítulo habla de forma extensa del control de flujo, bucles y condiciones. Muy detallada la explicación de las condiciones. En este mismo capítulo se introducen algunas cosas que no tienen mucha relación como el mecanismo del import (explicado muy brevemente), el uso de números aleatorios (que no tiene mucha relación con el resto de contenidos del capítulo), depuración con PyScripter (menos útil para personas fuera de Windows),...

PROGRAMACIÓN FUNCIONAL

El título del capítulo es algo desafortunado ya que no estamos hablando sobre programación funcional sino sobre el uso de funciones en Python y esto podría confundir a alguien. El capítulo es muy completo para aprender a usar funciones en Python, el 'scope' de las variables, los parámetros y argumentos,... Nuevamente, algún ejemplo podría resultar confuso pero, en general, está bastante completo. Echo en falta que se nombre a las funciones lambda.

PROGRAMACIÓN ORIENTADA A OBJETOS

Llegamos a la parte de clases y la programación orientada a objetos (POO). El capítulo es bastante extenso y se explican muchas cosas, algunas de ellas avanzadas. Sin duda, este es el capítulo del libro que me gusta menos. Los ejemplos que se usan no son muy ortodoxos, nuevamente se recurre a casos muy particulares que provocan, siempre en mi modesta opinión, que algunas cosas resulten en más complejas de lo que deberían en este punto. Una buena parte del capítulo se habla sobre como se hacen algunas cosas con PyScripter lo que le resta valor a usuarios fuera de Windows.

TIPOS DE DATOS EN PYTHON

Este capítulo es muy detallado. Se explica con mucha profundidad los tipos básicos en Python, cadenas, listas, tuplas, conjuntos y diccionarios. Podría, incluso, servir como guía de referencia en español del uso de estos tipos  por lo detallado (son unas 90 páginas). Además, se usan muchísimos ejemplos para explicar los conceptos.

FICHEROS Y EXCEPCIONES

Se habla sobre cómo poder usar ficheros para leer y escribir información. Esta parte es muy detallada y extensa con múltiples ejemplos útiles. La parte de excepciones es más que correcta con un alcance adecuado para introducirlos. Por último, se muestra el uso de with pero se despacha en menos de una página por lo que me parece insuficiente. Este capítulo y el anterior son los que más me gustan del libro.

PROGRAMACIÓN GRÁFICA EN PYTHON MEDIANTE PYQT

No es normal encontrar una introducción a interfaces gráficas en un libro introductorio. Como mucho, se muestra un ejemplo básico para dejar al lector que se introduzca por su cuenta en el tópico si tiene interés. En este caso se hace una introducción mucho más extensa que el ejemplo típico por lo que si tienes interés en crear interfaces gráficas estás de suerte. Sin embargo, si no tienes mucho interés en ello, se van muchas páginas en ello. Se explican de forma detallada muchos de los widgets disponibles en PyQt y está muy enfocado a crear las interfaces usando Qt Designer.

GENERACIÓN DE GRÁFICOS EN PYTHON MEDIANTE MATPLOTLIB

Nuevamente, se introduce otra librería que quizá no sea de interés para todo el mundo. En alrededor de 50 páginas se habla sobre cómo instalar la librería, cómo usar pyplot con muchos ejemplos, como usar matplotlib usando POO y cómo integrar matplotlib en una aplicación PyQt. Reitero, si tienes interés en hacer gráficas tienes suerte pero si no es así se te van otras 50 páginas de libro en ello.

Apuntes varios sobre el libro:

Me gusta:

  • Lo detallado de la explicación del if en el capítulo 3.
  • Que no haya referencias a Python 2.
  • Lo detallado de la explicación de las funciones.
  • Lo detallado de la explicación de los tipos básicos de Python pudiendo servir, incluso, como guía de referencia en español.
  • La parte del tratamiendo de ficheros y excepciones se hace con un alto nivel de detalle.
  • La extensión del libro, que permite poder desarrollar algunos temas de forma muy completa.
  • Que sea en español.

No me gusta:

  • El uso de eval en muchos ejemplos del libro lo considero una mala práctica.
  • No se respeta el PEP8, enseñando malas prácticas, lo cual no me parece adecuado en un curso introductorio.
  • Algunos ejemplos usan casos extremos para explicar ciertos conceptos. Estos casos extremos pueden ser detalles de la implementación y no los considero adecuados ya que en lugar de ayudar pueden resultar más confusos.
  • Está muy enfocado a Windows (uso de PyScripter, instalaciones) haciendo que una buena parte de las páginas sea menos útil para gente que use otros sistemas operativos.

Conclusión

El libro tiene algunos altibajos con partes que brillan con luz propia y partes que mejoraría.

Si de mi dependiera me gustaría que algunas cosas se explicasen un poco mejor, como el uso de with, las funciones lambda, el mecanismo del import, la parte de POO. Metería partes con muchas librerías útiles de la librería estándar (math, itertools, datetime, os, sys, collections,...), como crear paquetes y módulos,... Reduciría o eliminaría los dos últimos capítulos.

Como comentario general, considero que el libro está bien pero, como todo en esta vida, se podría mejorar en algunos aspectos.

Sorteamos una copia entre nuestros lectores

El autor nos envió varias copias del libro. Una de ellas la vamos a sortear entre todos los lectores de Pybonacci residentes en España. Para participar:

  • Solo tienes que escribir un tweet indicando porqué te gustaría tener este libro incluyendo un enlace a http://www.ra-ma.es/libros/PYTHON-3-CURSO-PRACTICO/94627/978-84-9964-658-9
  • Una vez enviado el tweet nos lo enlazas en los comentarios de más abajo para que no se nos escape el tweet.
  • Si no tienes cuenta en twitter, déjanos un comentario más abajo indicando porqué te gustaría tener este libro.

Tenéis hasta el miércoles, 2 de noviembre a las 21:00:00 (CET) para participar en el sorteo. Después de pasada la fecha indicaremos cómo se hará el sorteo (actualizando algunas cosas de aquí), usando el número ganador del sorteo de la ONCE de ese día (2016/11/02) y anunciaremos el ganador.

Actualización: Resultado del sorteo

El número de la ONCE fue el 69907. Si introducís el código aquí:

sale que el ganador ha sido @FlixUjo. He usado los participantes por orden de fecha en su comentario, del más antiguo al más nuevo:

participantes = ['FlixUjo', 'Javier @runjaj', 'Antonio Molina',
'Raúl', 'José Carlos Juanos', 'Christian',
'Kike', 'Eduardo Campillos']

Enhorabuena al vencedor, por favor, mándanos un DM por twitter o usa el formulario de contacto para mandarnos una dirección de correo/teléfono o lo que prefieras.

Saludos a todos.

Analizando datos sobre el Brexit con Pandas

Esta entrada apareció en inglés en mi blog.

Desde hace tiempo quería aprender Pandas. Por fin llegó la oportunidad: Brexit = datos.

Como siempre empecé con un ejercicio / objetivo práctico, en este caso procesar los datos del referéndum. Usé Pandas para analizar los datos (CSV) publicados por electoralcommission.org.uk.

Aunque quería responder a más preguntas este CSV era suficiente para estrenarme con Pandas (¡es inmenso!). Además aprendí a usar Jupyter notebook para documentar todo. El notebook lo puedes ver / bajar desde Github.

Consegui mi objetivo de representar los datos mostrados aquí. Aquí algunos pantallazos del notebook:

 

Añadiendo datos demográficos

Vinculé los datos del voto con los datos de censo públicamente disponibles (como sugirió Pybonacci), gracias). Encontré unas correlaciones interesantes (y aprendí algunas cosas de matplotlib usándolo), puedes ver el notebook aquí:

¿Cómo influye la edad en el voto por salir / quedar?

¿Cómo influye el porcentaje de paro?

¿Cómo influye un nivel más alto de estudios (educación)?

Y, ¿cómo influye el porcentaje de gente nacida fuera de Inglaterra?

Claramente, áreas con una población mayor y una tasa de paro más alta votan por salir. Por otro lado, áreas con un alto porcentaje de estudios superiores, y regiones donde más gente nacieron fuera de Inglaterra prefieren (por lo general) que Inglaterra se quede en la unión.

Lo dicho, para ver como llegué a estos resultados con Pandas el notebook está aquí.

Y por último: datos de ingresos por región

Los datos de ingresos (sueldo) eran más dificiles de obtener en los datos del censo entonces usé este enlace para comprobar la relación entre la mediana de ingresos y el voto. Encontré un patrón interesante:

(el parsing de los datos está documentado en el mismo notebook)

Se ve claramente que regiones con una mediana de ingresos más baja prefieren salir de la unión, aunque no es 100% consistente: Irlanda tiene una mediana relativamente baja pero vota por quedarse, y South East tiene un sueldo mediano más alto y, no obstante, vota por salir. Es interesante como se ve este tipo de tendencias combinando varias fuentes de datos.

Enlaces de referencia para aprender Pandas

* Pandas home y docs
* Python’s pandas make data analysis easy and powerful with a few lines of code - tutorial breve y fácil para empezar.
* Python for Data Analysis - libro del creador de Pandas Wes McKinney.
* Introduction to Pandas for Developers / Data Wrangling and Analysis with Python - ya he visto algunos videos, son buenos.

Como mejorar tu script fácilmente

Esta entrada apareció originalmente en inglés en mi blog.

Nos ha pasado a todos. Ese momento en el que descubres que sabes suficiente sobre un lenguage de programacion que quieres ponerlo en práctica y construir "algo", lo que sea.
Una de las mejores cosas de la comunidad de Python es no sólo su habilidad para construir cosas increíbles, sino también para compartirlas con todo el mundo, mejorando la comunidad en el proceso.

Sin embargo, llevo un tiempo fijándome en un patrón que se repite en algunos de estos proyectos. Seguro que has visto alguno así. Hablo de esos proyectos con 2 ó 3 componentes, donde el README tiene una pequeña descripción del proyecto, quizás un par de lineas explicando como ejecutar el proyecto, y frases del tipo, "Seguramente añadiré X o Y si tengo tiempo".

El caso es que muchos de estos proyectos son realmente interesantes, y tienen algún tipo de componentes que me gustaría usar sin tener que implementarlos yo mismo.

Te voy a mostrar 3 formas distintas de implementar uno de estos proyectos, cada una de ellas mejor (desde mi punto de vista) que la anterior:

Supongamos que queremos construir un script genial, donde la funcionalidad principal será que, dado un número entero por el usuario, realizará un calculo simple en base a ese entero, y devolverá el resultado.

Implementación 1

 
#!/usr/bin/env python

"""
Super awesome script
Asks the user for a number:
 - If the number is less or equal to 100, it returns the 1st tetration of the number (power of itself)
 - else, it returns the number squared
"""

__version__ = '0.1'

if __name__ == '__main__':

    while 1:
        user_number = input('Choose a number:\n') #raw_input() in python2
        if user_number.isdigit():
            user_number = int(user_number)
            break
        else:
            print('{} is not a valid number'.format(user_number))

    if user_number > 100:
        print(user_number**2)
    else:
        print(user_number**user_number)

Ésta suele ser la implementación de alquien que lleva poco tiempo en python. Funciona, pregunta al usuario por el input, realiza la operación, e imprime en pantalla el resultado.

Veo dos problemas en esta implementación:

1. No hay ningún tipo de separación entre la lógica de la interacción del usuario y la lógica del cálculo. Todo esta incluido en el mismo macro bloque. Pese a ser funcional, esta implementación hace que sea díficil el modificar o expandir este script (para hacerlo tendrías que leerte todo el código).

2. Estamos gestionando toda la validación por nuestra cuenta. Python tiene formas de hacer esto para que tú no te tengas que molestar en hacerlo :).

Para la siguiente implementación, usaremos el módulo mas simple de la libreria standard para trabajar con inputs del usuario, .

Implementación 2

 
#!/usr/bin/env python

"""
Super awesome script
Asks the user for a number:
 - If the number is less or equal to 100, it returns it to the power of itself
 - else, it returns the number squared
"""

import argparse

__version__ = '0.2'


if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument('--number', required=True, type=int,
                        help='number to perform calculation')
    values = parser.parse_args()
    user_number = values.number
    if user_number > 100:
        print(user_number**2)
    else:
        print(user_number**user_number)

En esta implementación hemos eliminado el problema #2 de la implementación anterior. En esta ocasión usamos argparse, de esta forma dejamos que la libreria estándar se encargue de la validación del input. Esta implementación no funciona a menos que el input sea válido.

Todavía tenemos el problema #1, la separación entre la lógica del input y la lógica primaria (la función de calculo).

En la siguiente implementación vemos como podemos arreglar esto.

Implementación 3

 
#!/usr/bin/env python

"""
Super awesome script
Asks the user for a number:
 - If the number is less or equal to 100, it returns it to the power of itself
 - else, it returns the number squared
"""

import argparse

__version__ = '0.3'



def calculation(number):
    """Performs awesome calculation"""
    if number > 100:
        return number**2
    else:
        return number**number

if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument('--number', required=True, type=int,
                        help='number to perform calculation')
    values = parser.parse_args()
    user_number = values.number
    calculation_result = calculation(user_number)
    print(calculation_result)

En esta implementación, hemos hecho dos cosas:

1. Hemos puesto la carga de la validación en un módulo bien mantenida como es argparse.
2. Hemos separado la lógica del input del usuario de la lógica del input de cálculo.

Éste último cambio tiene tres ventajas sobre #1 y #2.

- Ventaja 1: En primer lugar, si nos damos cuenta que por algún motivo queremos modificar el 100 por un 200, ahora podemos fácilmente modificar eso, sin tener que modificar ni leer todo el código. Siempre y cuando la función calculation siga teniendo los mismos inputs y outputs, el resto de código seguirá funcionando sin problemas.

- Ventaja 2: Otro efecto, y para mi el más significativo, es que si ahora yo leo este script que otra persona ha escrito, y me gusta tanto que quiero añadirlo a un proyecto mio, ¡ahora puedo importarlo sin problemas!.

En las implementacines #1 y #2, la única manera de usar el script era haciendo:

python calculation_script.py --number INTEGER

Ahora, en la implementación #3, tenemos una manera mucho mas útil de usar la lógica mas importante (la del cálculo). Si yo tengo otro script en el que quiero usar la funcion de cálculo, puedo usarla de la forma:

 
from calculation_script import calculation

number = 10
calculation_result = calculation(number)

¿Increíble, no? Simplemente haciendo una pequeña modificación a la estructura del proyecto, ahora cualquier persona se puede beneficiar del mismo.

- Ventaja 3: Supongamos que este simple proyecto empieza a crecer, más desarrolladores se interesan y empiezan a colaborar. El código empieza a crecer y alguien comenta que tendría sentido empezar a trabajar en el suite de testing. (si no sabes lo que es el testing, te recomiendo este artículo.)

Con la implementación #3, testear la funcionalidad de calculation es super fácil (gracias a /u/choffee en reddit por el apunte):

 
import pytest
from calculation_script import calculation

class TestCalculation:
    """Calculation function does funky things to number
    More above 100 than below
    """
    def test_zero():
        x = 0
        assert calculation(x) == 0

    def test_border():
        x = 100
        assert calculation(x) == 10000

    def test_one():
        x = 1
        assert calculation(x) == 1

Piensa en ello la próxima vez, no cuesta nada y hace que tu script sea mejor 🙂

El producto de matrices y el nuevo operador @

Introducción.

El 13 de septiembre de 2015 fue lanzada la versión 3.5 de Python. Entre las novedades podemos encontrar la inclusión del PEP 465 que trata sobre el nuevo operador @ para la multipliación matricial y del que hablaremos en este post. Como bien sabrán los lectores de este blog, los arrays son la piedra angular de numerosísimas áreas de la programación científica y sirven para realizar operaciones de forma masiva y mucho más eficiente. Esto, sumado a la posibilidad de utilizarlos como matrices, proporciona una herramienta muy potente para llevar a cabo operaciones algebraicas. NumPy es la librería que nos permite utilizar esta maravillosa estructura de datos y según figura en el ya citado PEP, podría ser la librería fuera de la librería estándar más importada del mundo Python.

Continue reading

Por qué dar una charla *ahora* y no luego

Nota: Esto iba a ser un email para la lista de PyLadies España, pero me ha parecido más divertido compartirlo en público 🙂

Contexto: Como sabéis, dentro de una semana se cierra el plazo para presentar charlas a la PyData Madrid 2016, que se celebrará en abril. Es un momento importante porque, si bien no es la primera vez que tenemos presencia de «trazas de PyData» en nuestro país (la primera fue en la EuroPython 2015) es la primera vez que se organiza como evento independiente. Yo tuve el privilegio de asistir a la primera que se hizo en Europa en un tiempo en el que me podía permitir el lujo de hacer estas cosas. Mucha gente se piensa que soy una especie de experto nacional de Python así que, inspirado por un intercambio que hemos tenido con una chica en privado por Twitter, me gustaría aclarar que mis inicios fueron bastante tortuosos, para así haceros reír un poco y de paso animaros a que participéis en este evento 😉 A continuación, mi email tal y como lo empecé:

Yo ya estoy animando a amigas y gente de mi entorno y empiezo a ver un patrón en las respuestas: «todavía no». Os animo a que hagáis piña, os volváis locas y presentéis charla - aunque penséis que «no sois expertas (¿todavía?)» o mejor: con más motivo si lo pensáis. Por tres razones:

La primera: no todos los que vamos a presentar charlas somos «expertos» ¡ni de coña! Es más, yo muchas veces he presentado charlas sobre temas que no dominaba, pero ha sido una excusa perfecta para estudiarlos.

La segunda: si no queréis presentar una charla «experta», las charlas introductorias son súper informativas, muy útiles y suelen gustar a un rango de público bastante amplio. En Internet hay demasiada información, pero vosotras en media hora podéis separar el grano de la paja e iluminar a quienes quieran seguir un determinado camino.

Y la tercera: esa gente que pensáis que es «experta» también la caga. Y a veces la caga bastante, y pasa vergüenzita y quieren que se los trague la tierra. En primicia, cómo me llevé unas diapos a medio preparar a la primera PyConES y luego para arreglarlo enseñé mi contraseña de PyPI en directo

He tardado dos años y medio en volver a ver este vídeo porque me daba pánico, y dos años y medio después me he dado cuenta de que desde fuera no fue tan horrible. Me encuentro sinceramente sorprendido 🙂 (y también de todo el pelo que he perdido en tan poco tiempo, ¿será la radiación de mi portátil?)

De mi segundo fallo no hay vídeo: sucedió en la PyData Londres 2014. Era mi primer evento en inglés, solo conocía a mi colega Fran (con quien volaba a Alicante al acabar) y en un momento dado me volví loco y apunté mi nombre en el tablón de lightning talks.

¡¡Me cago en todo, iba a dar una charla en inglés delante de un huevo de expertos sobre Python científico y análisis de datos!! La experiencia fue trepidante porque fui incapaz de hacer funcionar el proyector con mi portátil en dos ocasiones, así que estaba taquicárdico perdido. Ian Ozsvald me dio una última oportunidad y di la última lightning talk de esa conferencia.

Hubo un momento divertido cuando me puse a explorar la galería de ejemplos con widgets de IPython notebook, que entonces estaban aún en beta y a punto de salir, y no se me ocurre otra cosa que saltarme la sección de machine learning haciendo scroll a toda leche mientras decía al micrófono este comentario:

Machine learning, data science, blah blah blah...

Ni qué decir tiene que la gente soltó una buena carcajada y que yo me puse bastante rojo.

Lo mejor de todo esto es que... no pasa ná 🙂 Me recuperé sin terapia ni nada, mi lightning de los notebooks gustó mucho, he seguido dando charlas en un montón de países, y encima me invitaron a la PyData London 2015 y lo peté. Pero si nunca me hubiese atrevido a empezar a dar malas charlas, a medio preparar o directamente sudoroso y tembloroso como un flan nunca habría llegado a donde estoy.

Así que por favor: mandad charlas aunque no seáis expertas, mandadlas aunque penséis que todos os van a juzgar y mandadlas aunque tengáis miedo de hacerlo mal. Porque la realidad es que al principio tal vez lo haréis mal, os juzgarán menos de lo que pensáis y sois más expertas de lo que imagináis. Pero nada de esto importa en realidad 🙂

Si necesitáis ayuda, consejo de cualquier tipo o un empujoncito, no dudéis en escribirme a mi nombre acortado arroba este blog.

Desde aquí un aplauso a las personas de esta comunidad que trabajan duro por hacer de Python algo más que un simple lenguaje de programación y convertirlo en una herramienta de cambio social.

¡Un saludo y os veo en la PyData 2015!

Joyas Python del 2015

Este es un resumen de algunas joyas que he descubierto este 2015 dentro del mundo Python. Que las haya descubierto en el 2015 no significa que necesariamente sean cosas novedosas pero la mayoría siguen de actualidad. Tampoco es un resumen ordenado. de hecho, es un pequeño cajón de sastre. Tampoco es temático sobre ciencia, aunque la mayoría están relacionadas con ciencia ya que es a lo que me dedico. En las siguientes líneas nombro muchas cosas pero solo incluyo enlaces sobre las joyas de las que quiero hablar.

WEB:

  • En el pasado he trasteado algo con Django para hacer cosas que se puedan compartir con mucha otra gente. Django es un framework web muy completo o, como se suele decir, con baterías incluidas y el de más amplio uso dentro del mundo Python. El hecho de incluir tantas cosas de uso habitual en un desarrollo web es su fuerte para la mayoría de desarrolladores pero también su talón de Aquiles para gente que solo lo usa de vez en cuando para hacer cosas simples. Demasiado sobrecargado para acordarte de todo ello cuando lo usas muy eventualmente y demasiado condicionante para hacer cosas simples sin un guión claro. Es por ello que este año he empezado a trastear con Flask. Lo recomiendo para gente que quiere convertir una idea simple en algo usable en poco tiempo. He prestado algo de atención a wagtail y me gustaría encontrar un tutorial para gente especialmente lerda en desarrollo web (que no se lo vendan a un desarrollador Django, vamos) y que no tiene tiempo.
  • Relacionado con el trasteo anterior, he empezado a trastear también con SQLAlchemy. Como Flask no te aporta de serie su propia idea de ORM, como sí hace Django, puedes acoplar el ORM que elijas, usar SQL a capón, Mongo,... Facilita mucho el trabajo con BBDD. Y aquí puedes encontrar una serie de recursos relacionados con SQLAlchemy.
  • También relacionado con el uso de Flask, he estado trasteando con Babel para internacionalizar 'cosas' (poder hacer uso de distintos idiomas). Es increible la facilidad de uso pero he encontrado ciertos problemas que no he sabido resolver (aun no sé muy bien porqué, seguramente mi poca experiencia con la biblioteca).

GRÁFICAS:

  • ¿Quieres hacer un mapa interactivo con Python? Hasta ahora había usado mis propias soluciones. Mis soluciones son fáciles de usar y fácilmente portables a la web de forma independiente pero requieren aprender, por ejemplo, OpenLayers o Leaflet y para hacer algo simple puede resultar excesivo. Pero para otros casos de uso hay otras soluciones que pueden resultar más convenientes. Es por ello que en los últimos tiempos he estado usando Folium. Es muy simple de usar y para según que necesidad es muy apropiado. Por otra parte, quizá su diseño limite un poco las posibilidades reales. Es por ello que, después de investigar un poco, descubrí mplleaflet. Esta última librería sigue la misma filosofía que mpld3, usa matplotlib y exporta el código en algo que es capaz de interpretar la librería javascript de turno (d3js para el caso de mpld3 o leaflet para el caso de mplleaflet). Las posibilidades de uso que se me ocurren para mplleaflet son muchas.
  • Otra joyita para hacer análisis estadístico y visualización es Seaborn. Es una delicia ver como con tan poco se puede hacer tanto. Una capa sobre otra capa sobre otra capa,..., dan un gran poder con un mínimo esfuerzo. Se pierde poder de personalización pero se gana inmediatez y, en el mundo del 'loquieroparahoy', es una gran ventaja eso de la inmediatez.
  • Una pequeña tontería pero que te puede resultar útil en algún sistema donde es difícil usar un interfaz gráfico o quieres tener algo ligero para hacer gráficas de ¿baja calidad? sería bashplotlib (hasta el nombre mola).
  • He empezado a trastear algo con Plotly pero los resultados no han sido especialmente buenos (le tendré que dar una nueva oportunidad en 2016):

UTILIDADES:

  • Una pequeña tontada para el día a día sería tqdm, que te añade una barra de progreso a los bucles de tu código.

RENDIMIENTO:

  • La depuración y optimización de código siempre es algo árido y gris. La optimización prematura es la raiz de todo mal. Juntamos las churras con las merinas y nos sale que tienes que probar line_profiler sin ningún género de dudas. Date un paseo por tú código paso a paso y descubre qué es lo que está haciendo que tooooodo sea tan lento.
  • Para correr código más rápido en Python mis opciones de hoy en día serían, por orden de qué es lo que intentaría antes, numba (si es código científico) o pypy (si solo uso numpy mezclado con cosas más estándar que no dependan de bibliotecas que usan la C-API de CPython). Si Numba no funciona y pypy no resuleve la papeleta valoro si el código lo voy a necesitar ejecutar muchas veces y el tiempo que tarda es inasumible en la mayoría de ocasiones y, si es así, tiro de Cython que, con un poco de esfuerzo y afeando un poco el código Python original, permite obtener velocidades cercanas a o del mismo orden que C/C++/Fortran.

LIBRERÍA ESTÁNDAR Y ALGUNAS ALTERNATIVAS:

  • De la librería estándar he estado usando bastante argparse, collections e itertools. Los tres no tienen nada que ver, los tres son muy potentes y, sabiendo usarlos, los tres se hacen imprescindibles. Quizá para el año que viene me ponga como deberes mirar más a fondo click como mejora a argparse y functools, toolz y/o CyToolz en combinación con collections e itertools.

AÑO 2016 (DEBERES QUE ME PONGO):

  • dask
  • Más PyTables.
  • Creo que le voy a dar bastante más a d3js (por dictadura del navegador).
  • scikit-extremes, mi propia solución al análisis de extremos univariantes en Python (se aceptan ayudas).
  • PyMC y/o PyMC3.

¿Y vuestras joyitas? Animáos a compartirlas en los comentarios, independientemente que estén relacionadas con ciencia o no.

Saludos y feliz año!!

The Software Sustainability Institute

Y eso... ¿qué es?

El instituto de software sostenible (SSI por sus siglas del inglés) es una organización con sede en el Reino Unido cuyo objetivo es mejorar el software en el ámbito de la investigación, cubriendo áreas de ciencia, humanidades y arte. Su lema es claro:

"Un mejor software, una mejor investigación".

Según los resultados de una encuesta que realizaron el año pasado, siete de cada diez investigadores en Reino Unido no pueden llevar a cabo su investigación sin software. Lo que muestra que el software es una pieza imprescindible en la investigación de hoy en día.

Por otro lado, el término sostenible, en software, significa que lo que uses hoy puedas usarlo mañana, y que no se haya abandonado. Esto, que puede parecer una trivialidad, no es una cosa que se haya tenido en cuenta en muchas disciplinas por mucho tiempo. La mejor manera de ponerse en situación es pensar en esas películas que grabaste de la tele o de tu videocámara allá por los noventa, ¿quieres verlas ahora? ¿tienes un aparato de esos que solían estar debajo del televisor? ¿aún funciona? Bueno, pues en ciencia pasa tres cuartos de lo mismo... pero el problema es bastante más grave. Muchos de los datos, medidas y resultados de experimentos que nos han hecho avanzar hasta donde estamos hoy están perdidos, y con ellos los programas que se usaron. Razones varias, pero una de ellas es que cuando algo se publicaba el software o los datos no eran requeridos. El SSI intenta concienciar a la comunidad investigadora de estos problemas, y dispone de manuales y cursos para que los científicos no sigan tropezando en esa roca.

Continue reading