Joyitas en la stdlib: collections

Dentro de la biblioteca estándar de Python dispones de auténticas joyas, muchas veces ignoradas u olvidadas. Es por ello que voy a empezar un breve pero intenso recorrido por algunas piezas de arte disponibles de serie.

Módulo collections

Con la ayuda de este módulo puedes aumentar las estructuras de datos típicas disponibles en Python (listas, tuplas, diccionarios,...). Veamos algunas utilidades disponibles:

ChainMap

Solo Python 3. Actualízate!!

Dicho en bruto, es un conglomerado de diccionarios (también conocidos como mappings o hash tables).

Para que puede ser útil:

Ejemplo, imaginemos que tenemos un diccionario de configuración dict_a, que posee las claves a y b, y queremos actualizar sus valores con otros pares clave:valor que están en el diccionario dict_b, que posee las claves b y c. Podemos hacer:

from collections import ChainMap

dict_a = {'a': 1, 'b': 10}
dict_b = {'b': 100, 'c': 1000}

cm = ChainMap(dict_a, dict_b)
for key, value in cm.items():
    print(key, value)
a 1
c 1000
b 10

Hemos añadido el valor de la clave c de dict_b sin necesidad de modificar nuestro diccionario original de configuración dict_a, es decir, hemos hecho un 'cambio' reversible. También podemos 'sobreescribir' las claves que están en nuestro diccionario original de configuración, dict_b variando los parámetros del constructor:

cm = ChainMap(dict_b, dict_a)
for key, value in cm.items():
    print(key, value)
b 100
a 1
c 1000

Vemos que, además de añadir la clave c, hemos sobreescrito la clave b.

Los diccionarios originales están disponibles haciendo uso del atributo maps:

cm.maps
[{'b': 100, 'c': 1000}, {'a': 1, 'b': 10}]

Ejercicio: haced un dir de cm y un dir de dict_a y veréis que los atributos y métodos disponibles son parecidos.

Más información en este hilo de stackoverflow en el que me he basado para el ejemplo anterior (¿basar y copiar no son sinónimos?).

Counter

Permite contar ocurrencias de forma simple. En realidad, su funcionalidad se podría conseguir sin problemas con algunas líneas extra de código pero ya que lo tenemos, está testeado e implementado por gente experta vamos a aprovecharnos de ello.

En la documentación oficial hay algunos ejemplos interesantes y en github podéis encontrar unos cuantos más. Veamos un ejemplo simple pero potente, yo trabajo mucho con datos meteorológicos y uno de los problemas recurrentes es tener fechas repetidas que no deberían existir (pero pasa demasiado a menudo). Una forma rápida de buscar problemas de estos en ficheros y lanzar una alarma cuando ocurra lo que buscamos, sería:

from io import StringIO
from collections import Counter

virtual_file = StringIO("""2010/01/01 2.7
2010/01/02 2.2
2010/01/03 2.1
2010/01/04 2.3
2010/01/05 2.4
2010/01/06 2.2
2010/01/02 2.2
2010/01/03 2.1
2010/01/04 2.3
""")

if Counter(virtual_file.readlines()).most_common(1)[0][1] > 1:
    print('fichero con fecha repetida')
fichero con fecha repetida

namedtuple

A veces me toca crear algún tipo de estructura que guarda datos y algunos metadatos. Una forma simple sin crear una clase ad-hoc sería usar un diccionario. Un ejemplo simple sería:

import numpy as np
import datetime as dt
from pprint import pprint

datos = {
    'valores': np.random.randn(100),
    'frecuencia': dt.timedelta(minutes = 10),
    'fecha_inicial': dt.datetime(2016, 1, 1, 0, 0),
    'parametro': 'wind_speed',
    'unidades': 'm/s'
}

pprint(datos)
{'fecha_inicial': datetime.datetime(2016, 1, 1, 0, 0),
 'frecuencia': datetime.timedelta(0, 600),
 'parametro': 'wind_speed',
 'unidades': 'm/s',
 'valores': array([-3.02664796, -0.59492715, -1.36233816, -0.27333458,  0.34971592,
        1.43105631,  1.12980511,  0.49542105,  0.37546829,  1.37230197,
       -1.00757915,  1.39334713,  0.73904326,  0.01129817,  0.12431242,
        0.4388826 , -0.49561972, -0.9777947 ,  0.6009799 ,  0.89101799,
        0.48529884,  1.80287157,  1.56321415, -0.62089358, -2.22113341,
       -0.04751354,  0.89715794, -0.23252567,  0.2259216 ,  0.35214745,
       -1.50915239, -1.46547279, -0.4260315 ,  0.20851012,  1.60555432,
        0.4221521 , -1.03399518,  1.68276277,  0.5010984 ,  0.01294853,
       -0.80004557,  1.72141514, -1.38314354,  0.41374512,  0.32861028,
       -2.22385654,  0.80125671, -0.84757451,  0.66896035, -0.26901047,
       -0.06195842, -0.60743183, -0.15538184,  1.16314508, -0.42198419,
        0.61174838,  0.97211057, -1.19791368, -0.68773007,  2.96956504,
       -1.13000346, -0.24523032,  1.6312053 ,  0.77060561, -1.69925633,
       -0.31417013,  0.44196826, -0.59763569,  0.91595894,  1.47587324,
        0.5520219 , -0.62321715,  0.32543574, -1.26181508,  0.94623275,
       -0.25690824,  1.36108942,  0.15445091, -1.25607974,  0.50635589,
        0.65698443, -0.82418166, -0.34054522,  0.23511397, -1.5096761 ,
       -1.12291338, -1.82440698, -0.47433931, -1.86537903,  1.29256869,
        1.78898905,  0.72081117, -0.15169929, -1.24106944,  0.68920997,
        0.36932816, -1.15901835, -0.93990956,  0.37258685, -0.41316085])}

Lo anterior es simple y rápido pero usando una namedtuple dispongo de algo parecido con algunas cosas extra. Veamos un ejemplo similar usando namedtuple:

from collections import namedtuple

Datos = namedtuple('Datos', 'valores frecuencia fecha_inicial parametro unidades')

datos = Datos(np.random.randn(100), 
              dt.timedelta(minutes = 10),
              dt.datetime(2016, 1, 1, 0, 0),
              'wind_speed',
              'm/s')
print(datos)
Datos(valores=array([ 1.50377059, -1.48083897, -0.76143985,  0.15346996, -0.01094251,
        0.42117233,  1.07136364, -0.24586714,  1.2001748 ,  0.56880926,
        0.56959121,  0.63811853,  0.4621489 ,  1.06636058,  0.32129287,
        2.42264145, -1.25830559, -0.27102862,  2.04853711,  2.07166845,
       -0.27138347, -0.07075163, -0.43547714,  1.69140984,  2.57150371,
        0.80336641, -0.78767876, -2.22281324,  0.23112338, -0.0605485 ,
        0.58304378,  3.33116997, -1.1285789 , -0.2047658 , -0.39240644,
       -1.69724959, -0.0313781 , -0.22892613, -0.06029154, -0.32368036,
       -0.12969429,  1.06231438,  0.05429922, -1.12206555,  1.33383161,
        0.92582424,  0.51615352,  0.93188459,  0.65273332,  0.39108396,
        1.56345696, -0.33158622, -0.27455745,  0.69101563,  1.61244861,
        0.7961402 ,  0.38661924, -0.99864208, -0.10720116,  0.40919342,
       -0.43784138, -3.06455306,  1.69280852,  1.82180641,  0.03604298,
        0.17515747,  1.4370723 , -0.47437528,  1.14510249,  1.36360776,
        0.34575948, -0.14623582,  1.1048332 , -0.2266261 ,  1.34319382,
        0.75608216, -0.62416011, -0.27821722,  0.45365802, -0.98537653,
        0.20172051,  1.70476797,  0.55529542, -0.07833625, -0.62619796,
       -0.02892921, -0.07349236,  0.94659497,  0.20823509,  0.91628769,
       -1.14603843, -0.20748714,  1.13008222, -0.93365802, -0.48125316,
        0.45564591, -0.03136778, -0.86333962,  1.04590165, -0.51757806]), frecuencia=datetime.timedelta(0, 600), fecha_inicial=datetime.datetime(2016, 1, 1, 0, 0), parametro='wind_speed', unidades='m/s')

Ventajas que le veo con respecto a lo anterior:

  • Puedo acceder a los 'campos' o claves del diccionario usando dot notation
print(datos.valores)
[ 1.50377059 -1.48083897 -0.76143985  0.15346996 -0.01094251  0.42117233
  1.07136364 -0.24586714  1.2001748   0.56880926  0.56959121  0.63811853
  0.4621489   1.06636058  0.32129287  2.42264145 -1.25830559 -0.27102862
  2.04853711  2.07166845 -0.27138347 -0.07075163 -0.43547714  1.69140984
  2.57150371  0.80336641 -0.78767876 -2.22281324  0.23112338 -0.0605485
  0.58304378  3.33116997 -1.1285789  -0.2047658  -0.39240644 -1.69724959
 -0.0313781  -0.22892613 -0.06029154 -0.32368036 -0.12969429  1.06231438
  0.05429922 -1.12206555  1.33383161  0.92582424  0.51615352  0.93188459
  0.65273332  0.39108396  1.56345696 -0.33158622 -0.27455745  0.69101563
  1.61244861  0.7961402   0.38661924 -0.99864208 -0.10720116  0.40919342
 -0.43784138 -3.06455306  1.69280852  1.82180641  0.03604298  0.17515747
  1.4370723  -0.47437528  1.14510249  1.36360776  0.34575948 -0.14623582
  1.1048332  -0.2266261   1.34319382  0.75608216 -0.62416011 -0.27821722
  0.45365802 -0.98537653  0.20172051  1.70476797  0.55529542 -0.07833625
 -0.62619796 -0.02892921 -0.07349236  0.94659497  0.20823509  0.91628769
 -1.14603843 -0.20748714  1.13008222 -0.93365802 -0.48125316  0.45564591
 -0.03136778 -0.86333962  1.04590165 -0.51757806]
  • Puedo ver el código usado para crear la estructura de datos usando verbose = True. Usa exec entre bambalinas (o_O). Puedo ver que todas las claves se transforman en property's. Puedo ver que se crea documentación... MAGIA en estado puro!!!

(Si no quieres usar la keyword verbose = True puedes seguir teniendo acceso en un objeto usando obj._source)

Datos = namedtuple('Datos', 'valores frecuencia fecha_inicial parametro unidades', verbose = True)
from builtins import property as _property, tuple as _tuple
from operator import itemgetter as _itemgetter
from collections import OrderedDict

class Datos(tuple):
    'Datos(valores, frecuencia, fecha_inicial, parametro, unidades)'

    __slots__ = ()

    _fields = ('valores', 'frecuencia', 'fecha_inicial', 'parametro', 'unidades')

    def __new__(_cls, valores, frecuencia, fecha_inicial, parametro, unidades):
        'Create new instance of Datos(valores, frecuencia, fecha_inicial, parametro, unidades)'
        return _tuple.__new__(_cls, (valores, frecuencia, fecha_inicial, parametro, unidades))

    @classmethod
    def _make(cls, iterable, new=tuple.__new__, len=len):
        'Make a new Datos object from a sequence or iterable'
        result = new(cls, iterable)
        if len(result) != 5:
            raise TypeError('Expected 5 arguments, got %d' % len(result))
        return result

    def _replace(_self, **kwds):
        'Return a new Datos object replacing specified fields with new values'
        result = _self._make(map(kwds.pop, ('valores', 'frecuencia', 'fecha_inicial', 'parametro', 'unidades'), _self))
        if kwds:
            raise ValueError('Got unexpected field names: %r' % list(kwds))
        return result

    def __repr__(self):
        'Return a nicely formatted representation string'
        return self.__class__.__name__ + '(valores=%r, frecuencia=%r, fecha_inicial=%r, parametro=%r, unidades=%r)' % self

    def _asdict(self):
        'Return a new OrderedDict which maps field names to their values.'
        return OrderedDict(zip(self._fields, self))

    def __getnewargs__(self):
        'Return self as a plain tuple.  Used by copy and pickle.'
        return tuple(self)

    valores = _property(_itemgetter(0), doc='Alias for field number 0')

    frecuencia = _property(_itemgetter(1), doc='Alias for field number 1')

    fecha_inicial = _property(_itemgetter(2), doc='Alias for field number 2')

    parametro = _property(_itemgetter(3), doc='Alias for field number 3')

    unidades = _property(_itemgetter(4), doc='Alias for field number 4')


# Lo mismo de antes
print(datos._source)
from builtins import property as _property, tuple as _tuple
from operator import itemgetter as _itemgetter
from collections import OrderedDict

class Datos(tuple):
    'Datos(valores, frecuencia, fecha_inicial, parametro, unidades)'

    __slots__ = ()

    _fields = ('valores', 'frecuencia', 'fecha_inicial', 'parametro', 'unidades')

    def __new__(_cls, valores, frecuencia, fecha_inicial, parametro, unidades):
        'Create new instance of Datos(valores, frecuencia, fecha_inicial, parametro, unidades)'
        return _tuple.__new__(_cls, (valores, frecuencia, fecha_inicial, parametro, unidades))

    @classmethod
    def _make(cls, iterable, new=tuple.__new__, len=len):
        'Make a new Datos object from a sequence or iterable'
        result = new(cls, iterable)
        if len(result) != 5:
            raise TypeError('Expected 5 arguments, got %d' % len(result))
        return result

    def _replace(_self, **kwds):
        'Return a new Datos object replacing specified fields with new values'
        result = _self._make(map(kwds.pop, ('valores', 'frecuencia', 'fecha_inicial', 'parametro', 'unidades'), _self))
        if kwds:
            raise ValueError('Got unexpected field names: %r' % list(kwds))
        return result

    def __repr__(self):
        'Return a nicely formatted representation string'
        return self.__class__.__name__ + '(valores=%r, frecuencia=%r, fecha_inicial=%r, parametro=%r, unidades=%r)' % self

    def _asdict(self):
        'Return a new OrderedDict which maps field names to their values.'
        return OrderedDict(zip(self._fields, self))

    def __getnewargs__(self):
        'Return self as a plain tuple.  Used by copy and pickle.'
        return tuple(self)

    valores = _property(_itemgetter(0), doc='Alias for field number 0')

    frecuencia = _property(_itemgetter(1), doc='Alias for field number 1')

    fecha_inicial = _property(_itemgetter(2), doc='Alias for field number 2')

    parametro = _property(_itemgetter(3), doc='Alias for field number 3')

    unidades = _property(_itemgetter(4), doc='Alias for field number 4')


  • Puedo seguir obteniendo un diccionario (un OrderedDict, también incluido en el módulo collections) si así lo deseo:
datos._asdict()['valores']
array([ 1.50377059, -1.48083897, -0.76143985,  0.15346996, -0.01094251,
        0.42117233,  1.07136364, -0.24586714,  1.2001748 ,  0.56880926,
        0.56959121,  0.63811853,  0.4621489 ,  1.06636058,  0.32129287,
        2.42264145, -1.25830559, -0.27102862,  2.04853711,  2.07166845,
       -0.27138347, -0.07075163, -0.43547714,  1.69140984,  2.57150371,
        0.80336641, -0.78767876, -2.22281324,  0.23112338, -0.0605485 ,
        0.58304378,  3.33116997, -1.1285789 , -0.2047658 , -0.39240644,
       -1.69724959, -0.0313781 , -0.22892613, -0.06029154, -0.32368036,
       -0.12969429,  1.06231438,  0.05429922, -1.12206555,  1.33383161,
        0.92582424,  0.51615352,  0.93188459,  0.65273332,  0.39108396,
        1.56345696, -0.33158622, -0.27455745,  0.69101563,  1.61244861,
        0.7961402 ,  0.38661924, -0.99864208, -0.10720116,  0.40919342,
       -0.43784138, -3.06455306,  1.69280852,  1.82180641,  0.03604298,
        0.17515747,  1.4370723 , -0.47437528,  1.14510249,  1.36360776,
        0.34575948, -0.14623582,  1.1048332 , -0.2266261 ,  1.34319382,
        0.75608216, -0.62416011, -0.27821722,  0.45365802, -0.98537653,
        0.20172051,  1.70476797,  0.55529542, -0.07833625, -0.62619796,
       -0.02892921, -0.07349236,  0.94659497,  0.20823509,  0.91628769,
       -1.14603843, -0.20748714,  1.13008222, -0.93365802, -0.48125316,
        0.45564591, -0.03136778, -0.86333962,  1.04590165, -0.51757806])
  • Puedo crear subclases de forma simple para añadir funcionalidad. Por ejemplo, creamos una nueva clase con un nuevo método que calcula la media de los valores:
class DatosExtendidos(Datos):
    def media(self):
        "Calcula la media de los valores."
        return self.valores.mean()

datos_ext = DatosExtendidos(**datos._asdict())

print(datos_ext.media())
0.27764229179

deque

Otra joyita que quizá debería usar más a menudo sería deque. Es una secuencia mutable (parecido a una lista), pero con una serie de ventajas. Es una cola/lista cuyo principio y fin es 'indistinguible', es thread-safe y está diseñada para poder insertar y eliminar de forma rápida en ambos extremos de la cola (ahora veremos qué significa todo esto). Un uso evidente es el de usar, por ejemplo, una secuencia como stream de datos con un número de elementos fijo y/o rápidamente actualizable:

  • Podemos limitar su tamaño y si añadimos elementos por un lado se eliminan los del otro extremo.
  • Podemos rotar los datos de forma eficiente.
  • ...

Veamos un ejemplo:

from collections import deque

dq = deque(range(10), maxlen = 10)
lst = list(range(10))
print(dq)
print(lst)
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# los tres últimos elementos los anexa nuevamente al principio de la secuencia.
dq.rotate(3)
print(dq)

lst = lst[-3:] + lst[:-3]
print(lst)
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
[7, 8, 9, 0, 1, 2, 3, 4, 5, 6]

Veamos la eficiencia de esta operación:

tmp = deque(range(100000), maxlen = 100000)
%timeit dq.rotate(30000)
tmp = list(range(100000))
%timeit tmp[-30000:] + tmp[:-30000]
The slowest run took 9.62 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 519 ns per loop
100 loops, best of 3: 3.07 ms per loop

Con una queue podemos anexar de forma eficiente a ambos lados:

dq.append(100)
print(dq)
dq.appendleft(10000)
print(dq)
deque([8, 9, 0, 1, 2, 3, 4, 5, 6, 100], maxlen=10)
deque([10000, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
dq.extend(range(10))
print(dq)
dq.extendleft([10, 100])
print(dq)
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([100, 10, 0, 1, 2, 3, 4, 5, 6, 7], maxlen=10)

Etc.

Puedes hacer cosas similares a las hechas con listas pero de forma más eficiente y práctica en determinados casos!!

Recordad que, además, disponemos del módulo queue en la librería estándar.

Conclusión

Este módulo esconde cosas muy interesantes, algunas que no hemos visto. Por tanto, si no lo conocéis, deberíais explorar el módulo collections, si lo conocéis nos podéis indicar como lo usáis en los comentarios que puedes encontrar más abajo.

Resaca PyData Madrid 2016

Este es un mini resumen de parte del trabajo y experiencias vividas durante muchos meses trabajando en la organización de la primera PyData por estos lares.

¿Cómo?

Muchos de los que nos leéis, seguramente, ya estéis envueltos en meetups locales, talleres,... Estos eventos son importantísimos para que la comunidad se conozca a pequeña escala y es una labor regular de mucha gente que os agradezco ya que es el germen de cosas más grandes. Una vez que hay comunidad local, esta puede contactar con otras comunidades locales o pueden intentar crear un evento que salga de ese ámbito más pequeño o cerrado. Estos eventos mayores hacen que las comunidades locales puedan interactuar en persona con el resto de comunidades locales.

De la experiencia vivida detrás de la asociación Python España desde sus inicios hasta hoy (en realidad, hasta anteayer) he venido observando que, sin duda, el interactuar en persona es mucho más eficiente, amigable y productivo que andar hablando por las listas de correo, twitter, etc. Por ejemplo, de la primera PyConES, celebrada en Madrid en 2013, surgieron o se consolidaron grupos, PyBirras y/o meetups en Valencia, Vigo, Tenerife, Barcelona, Madrid,... Y más tarde han venido otros en Granada, Sevilla, Murcia, Castellón,... (recordad que la asociación provee de ciertas herramientas para las comunidades locales).

En torno a todo esto, además, han surgido interesantes iniciativas como un calendario de eventos, un podcast, openbadges para reconocer el esfuerzo de muchos dando charlas de forma desinteresada o asociándose,..., otras iniciativas que se están incubando y, además, hemos seguido celebrando eventos como la PyConES (este año se celebrará en Almería la 4ª edición) y han surgido nuevos como la PyData Madrid 2016 (ya nos acercamos...).

¿Por qué todo este rollo?

Todo lo anterior es un poco de contexto para saber como se llega a las cosas. El núcleo de la organización local de la PyData hemos sido Salvador, Guillem y yo. Pero a Salvador y a Guillem, seguramente, no los habría conocido si no existiera el meetup local de Python Madrid (con Juanlu, Pablo, Jesús Cea, Yamila, Jesús Espino, Andrey,... empujando por detrás durante muuuuuchos años ) o la PyConES (organizada por gente de Madrid, Valencia, Zaragoza, Almería,..., LO SIENTO, no me caben todos los nombres). Y de aquellos polvos, estos lodos. Empezamos a hablar de una PyData hace varios años en la PyConES de Zaragoza. Esos días me junté con gente como Christine, Samuel Guerra, Guillem, Nuria, Antònia, Fran (Cacheme), Álex (AeroPython),..., y de ahí surgió la lista SciPyData-ES  donde se fue apuntando gente, donde empezamos los primeros contactos para la PyData que celebramos la semana pasada y donde hemos intentado informar de todo lo que acontecía además de intentar convocar a nuevos locos en el esfuerzo de la organización.

¿Y nos vamos acercando a la PyData Madrid 2016?

Además del núcleo duro hemos tenido asistencia remota de Christine (desde Texas, Madrid, Barcelona,...GRACIAS!!!). Christine nos ayudó a poner en marcha todas las comunicaciones con NumFocus, responsable final de las PyData. Dentro de NumFocus hemos hablado (mucho) con Leah, Gina, Lynn, Savannah, Martey,..., que se han encargado de cosas importantísimas como la web, la facturación, promoción,...

¿Qué es una PyData?

Son un tipo de conferencias donde se habla de computación científica realizada con ayuda de software libre. Las organiza NumFocus con ayuda de comunidades locales alrededor del mundo.

¿Qué es NumFocus?

Es una organización sin ánimo de lucro que se encarga de velar por la continuidad de muchos de esos proyectos que usamos a diario (Numpy, Scipy, Julia, Matplotlib, pandas, PyTables,...).

¿Qué envuelve la organización de un evento de este tipo?

MUCHOS CORREOS.

MÁS CORREOS.

Y UNOS POCOS CORREOS MÁS.

Hablando en serio. Hemos conversado con gente que está en la otra parte del mundo, con organizadores de otras PyData como Ian (Ozsvald, Londres),... Con todo ello, hemos intentado hacerlo lo mejor posible.

El lugar.

  • Lo más importante es encontrar el lugar adecuado. En este caso consideramos usar dos sitios diferentes y partimos el evento en dos mini-eventos (talleres y charlas) para poder controlar el aforo de uno y de otro (diferente aforo en los dos sitios). Gracias CIBBVA y Campus Madrid por las facilidades ofrecidas desde el primer momento. Y a Reinaldo en CIBBVA y Patricia en Campus Madrid por contestar a las millones de dudas y resolver los problemas en todo momento.
  • El lugar elegido te dictará una serie de cosas como el aforo, número de tracks, posibilidad de vídeo, wifi, facilidad de acceso,... Al final nos quedó un día completo de talleres y dos días completos de charlas en un único track.

Los patrocinadores.

  • Desde el primer momento contamos con la ayuda de la Python Software Foundation, que fue nuestro primer patrocinador oficial y que nos hizo facilísimo contar con algo de dinero desde un primer momento (Thanks Betsy and others, You rule!!!).
  • Luego hemos contactado con muchas posibilidades y hemos acabado teniendo un conjunto de patrocinadores muy involucrados con el evento. Los cito por orden tal como están en la web: Continuum Analytics, Synergic partners, NFQ solutions, KSchool, OpenSistemas, Mozilla, ScrapingHub, Paradigma digital y GMV. Empresas punteras trabajando en ciencia aplicada, en nuevas tecnologías, en formación,... Muchas españolas y algunas extranjeras. GRACIAS A TODAS!!!

Los ponentes.

  • Un gran aplauso para todos!!! Gente que, de forma desinteresada, comparte su conocimiento con todos nosotros después de perder tiempo en casa preparando materiales con su experiencia adquirida durante años, que tiene que buscar hoteles, coches, aviones,...
  • Gracias Jaime, Peque, Claudia, Antònia, Álex, Jesús, Pablo, Tomás, Ricardo, Nathan, Siro, Guillem, Marc, Juanlu, Manu, José Manuel,...
  • Quiero agradecer personalmente a nuestros dos keynoters, Christine y Francesc, que desde un primer momento nos facilitaron muchísimo la vida ayudándonos en todo lo posible. Christine es la persona de referencia en toda la explosión de la analitica de datos y nuevas herramientas que están impulsando desde Continuum y Francesc es una de las personas detrás de proyectos tan importantes como PyTables, numexpr, blosc, bcolz,..., que nos muestra desde hace años (lo difícil) cosas que solo se están empezando a aceptar/adoptar ahora (ahora es fácil subirse al carro).

El público.

  • Sin el público no hay PyData que valga por lo que todo está hecho para vosotros.

Los voluntarios.

  • Carlos, Manú, Luís, Yami, Guillermo, Álex,... Los cuales velaron para que todos estuviéramos cómodos durante el evento, montaron, desmontaron, subieron, bajaron,....

Otras muchas cosas en las que pensar.

  • Catering, el alérgico a las nueces de brasil, la alérgica a los frutos secos, los vegetarianos, las veganas, los celíacos, las intolerantes a la lactosa,...
  • Los lanyards, identificaciones, camisetas de tallas correctas, bolsas, productos de patrocinadores, cartelería, papelería, impresión....
  • Montar stands, desmontar stands,...
  • Gestionar el tema del vídeo.
  • Pagar facturas.
  • Preocuparse porque todo esté correcto durante las charlas.
  • Gestionar PyBirras.
  • Gestionar cena para ponentes y patrocinadores.
  • Hacer de maestros de ceremonias (Guillem haciendo de cabeza visible de forma excelente, Salva actuando en la sombra para que todo estuviera en forma y tiempo, yo molestando por ahí,...).
  • Certificados de asistencia.
  • Certificados de pago.
  • Mover cajas de un sitio a otro.
  • Gestionar viajes.
  • Actualizar webs.
  • Buscar información de ayuda para los asistentes.
  • Comité de selección de charlas.
  • Gestionar voluntarios.
  • Gestionar el tema de venta de ropa con freewear.
  • ...

Agradecimientos.

He agradecido a lo largo del texto a muchisima gente. Este rinconcito lo dejo para Salva y Guillem. Los cuales me han aguantado mis neuras durante meses y han sido, en todo momento, unos profesionales cualificados, educados, eficientes,.... Me pongo de pie y les aplaudo virtualmente. Si en el futuro me piden ayuda en una empresa de estas saben que me tendrán listo para ello.

Objetivos cumplidos.

  • Hemos plantado la semilla de una conferencia a nivel nacional/internacional de ciencia con Python y otras herramientas de software libre.
  • Hemos recaudado dinero para NumFocus (todavia no conocemos el total).
  • Hemos recaudado dinero para la asociación Python España a traves de la venta de ropa de freewear (102€).
  • Hemos recaudado dinero para otros proyectos de software libre como Emacs, la PSF, Arch,..., a través de la venta de ropa de freewear (34€).
  • Hicimos una cena de ponentes y patrocinadores que se salía de las cenas normales (hicimos una cata de vinos solidaria que realizó una amiga) en un  sitio, Turkana,  que dona gran parte de la recaudación a un proyecto de cirugía en Kenia. Al final creo que se han ido unos 400€ de donación, principalmente donados por el 'trabajo gratuito' de mi amiga Ana con las catas, el fee de los vinos y el restaurante.
  • Creo que alguno de los asistentes disfrutó del evento.

Y seguimos trabajando en la sombra.

Nos queda cerrar el tema de los vídeos, cerrar algunas facturas, mandar algunos correos,...

A ver si salen los vídeos pronto porque me perdí la mayoría!!!!!

Materiales de las charlas.

Además de ponernos a organizar no se nos ocurre otra cosa que liarnos la manta a la cabeza con talleres y demás. Los materiales de mi taller sobre Pandas los tenéis aquí.

También dí una charla relámpago sobre brythonmagic un poco deslucida ya que en el portátil no me funcionó correctamente el mouse pad ni el monitor (cosas del directo). Estoy intentando repararla ya que está un poco rota. Avisaré por twitter cuando esté funcionando correctamente.

El resto de materiales de las charlas y talleres está en este repo de github.

Otros testimonios sobre la PyData Madrid 2016.

En el blog de Guillem.

En el blog de Álex.

¿Futuro?

Decididlo vosotros.

Como véis, esto no son tres dias de charlas y talleres, es el trabajo de mucha gente (decenas nombradas solo en este post) durante mucho tiempo.

Hay muchas semillas plantadas, mucha experiencia adquirida, mucho trabajo hecho,... 🙂

Espero que nos veamos pronto.

Instala pypy 5.0 y numpypy en un virtualenv y juega con Jupyter

Hoy voy a mostrar como usar la última versión de pypy y numpypy en vuestro linux. Para instalar pypy usaremos la versión portable creada por squeaky-pl. Numpypy lo instalaremos en un entorno virtual juntamente con Jupyter para poder hacer las pruebas en un entorno más amigable que la consola de pypy.

Requerimientos

Necesitaremos tener instalada una versión reciente de virtualenv y git.

Al lío

¡Si queréis la versión TL;DR pinchad aquí! Si sois un poco más pacientes y queréis entender un poco lo que vamos a hacer seguid leyento.

Todos los comandos que vienen a continuación los tenéis que meter en un terminal. Primero creamos un directorio que se llamará pypy50 en vuestro $HOME

mkdir $HOME/pypy50

Ahora nos vamos al directorio recién creado y nos descargamos el fichero comprimido que contiene el pypy portable de 64 bits

cd $HOME/pypy50
wget https://bitbucket.org/squeaky/portable-pypy/downloads/pypy-5.0-linux_x86_64-portable.tar.bz2

Lo desempaquetamos:

tar xvfj pypy-5.0-linux_x86_64-portable.tar.bz2

Ahora creamos un directorio bin en nuestro $HOME. Si ya existe te puedes saltar este paso:

mkdir $HOME/bin

Creamos un enlace simbólico al ejecutable del pypy portable que hemos descargado que se encontrará en la carpeta bin del directorio $HOME:

ln -s $HOME/pypy50/pypy-5.0-linux_x86_64-portable/bin/pypy $HOME/bin

Cambiamos los permisos al ejecutable para darle permisos de ejecución:

chmod +x $HOME/pypy50/pypy-5.0-linux_x86_64-portable/bin/pypy

Al final de nuestro .bashrc vamos a añadir unas pocas líneas para que se añada el directorio bin de nuestro $HOME al $PATH:

echo "" >> $HOME/.bashrc
echo "# Added path to include pypy by $USER" >> $HOME/.bashrc
echo "export PATH=$PATH:$HOME/bin" >> $HOME/.bashrc
source $HOME/.bashrc

Creamos el virtualenv con pypy (en este paso necesitaréis tener virtualenv instalado). El virtualenv se creará en la carpeta bin de nuestro $HOME y se llamará pypyvenv:

virtualenv -p pypy $HOME/bin/pypyvenv

Instalamos numpypy (numpy para pypy) en el nuevo virtualenv creado (aquí necesitarás tener git instalado). Para ello usamos el pip del entorno virtual.

$HOME/bin/pypyvenv/bin/pip install git+https://bitbucket.org/pypy/numpy.git

Instalamos Jupyter haciendo algo parecido a lo anterior (aunque esta vez lo instalamos desde pypi, no confundir con pypy):

$HOME/bin/pypyvenv/bin/pip install jupyter

Y, por último, hacemos un poco de limpieza eliminando el fichero comprimido del pypy portable que hemos descargado anteriormente:

rm $HOME/pypy50/pypy*.tar.bz2

¡¡¡Listo!!!

Usando pypy

Para usar pypy (sin numpy) puedes lanzar una consola con pypy 5.0 (compatible con CPython 2.7) escribiendo en el terminal:

pypy

Usando pypy con numpy en un notebook de jupyter

Activamos el entorno virtual recien creado. Desde el terminal escribimos:

. ~/bin/pypyvenv/bin/activate

Y arrancamos jupyter:

jupyter notebook

Y después venís aquí y me contáis vuestras experiencias con pypy y numpypy o, si habéis encontrado fallos o queréis añadir mejoras, os vais a github y abrís un issue o mandáis un Pull Request y salimos ganando todos.

Ideas para mejorar el script (con vuestros pull requests)

  • Que pregunte donde instalar el pypy portable.
  • Que pregunte si queremos una carpeta bin o no.
  • Que pregunte cómo queremos llamar al entorno virtual y dónde lo queremos instalar.
  • Que pregunte si queremos instalar Jupyter y/u otras librerías.
  • ...

Saludos.

 

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

Fórmula para el amor

Esta entrada se proyectó hace unos doscientos cuarenta y pico días.

Vamos a representar la siguiente fórmula:

\({x}^2 + (y - \sqrt{x^2})^2 = 1\)

Si despejamos la \(y\) nos quedarán las siguientes soluciones:

\(y_{1} = \sqrt{x^2} + \sqrt{1 - x^2}\)
\(y_{2} = \sqrt{x^2} - \sqrt{1 - x^2}\)

En código Python usando Numpy y Matplotlib tendremos lo siguiente:

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
x = np.linspace(-1,1,50)
y1 = np.sqrt(x * x) + np.sqrt(1 - x * x)
y2 = np.sqrt(x * x) - np.sqrt(1 - x * x)
plt.plot(x, y1, c='r', lw = 3)
plt.plot(x, y2, c='r', lw = 3)
plt.show()

Felicidades a quien corresponda.

Idea copiada literalmente de aquí.

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!

Cómo crear extensiones en C para Python usando CFFI y numba

Introducción

En este artículo vamos a ver cómo crear extensiones en C para Python usando CFFI y aceleradas con numba. El proyecto CFFI ("C Foreign Function Interface") pretende ofrecer una manera de llamar a bibliotecas escritas en C desde Python de una manera simple, mientras que numba, como podéis leer en nuestro blog, es un compilador JIT para código Python numérico. Mientras que hay algo de literatura sobre cómo usar CFFI, muy poco se ha escrito sobre cómo usar funciones CFFI desde numba, una característica que estaba desde las primeras versiones pero que no se completó hasta hace cuatro meses. Puede parecer contradictorio mezclar estos dos proyectos pero en seguida veremos la justificación y por qué hacerlo puede abrir nuevos caminos para escribir código Python extremadamente eficiente.

Este trabajo ha surgido a raíz de mis intentos de utilizar funciones hipergeométricas escritas en C desde funciones aceleradas con numba para el artículo que estoy escribiendo sobre poliastro. El resultado, si bien no es 100 % satisfactorio aún, es bastante bueno y ha sido relativamente fácil de conseguir, teniendo en cuenta que partía sin saber nada de C ni CFFI hace tres días.

¿Por qué CFFI + numba?

Como decíamos CFFI y numba, aunque tienen que ver con hacer nuestros programas más rápidos, tienen objetivos bastante diferentes:

  • CFFI nos permite usar C desde Python. De este modo, si encontramos algún algoritmo que merece la pena ser optimizado, lo podríamos escribir en C y llamarlo gracias a CFFI.
  • numba nos permite acelerar código Python numérico. Si encontramos algún algoritmo que merece la pena ser optimizado, adecentamos un poco la función correspondiente y un decorador la compilará a LLVM al vuelo.

Continue reading

Reglas para refactorizar funciones lambda

Un gran ejercicio que podéis hacer de vez en cuando es revisar la documentación oficial de Python. La misma me parece increiblemente completa aunque también un poco anárquica o sin un guión mínimamente claro para seguir diferentes tópicos.

Hoy, revisando el HOWTO de programación funcional, casi al final del documento y sin llamar la atención, he encontrado la siguiente documentación para refactorizar funciones lambda sugerida por Fredrik Lundh. Las reglas que propone para la refactorización de las funciones lambda dicen lo siguiente:

  1. Escribe una función Lambda.
  2. Escribe un comentario explicando qué se supone que hace la función lambda.
  3. Estudia el comentario durante un rato y piensa un nombre que capture la esencia del comentario.
  4. Convierte la función lambda a una declaración def usando el nombre pensado en el anterior paso.
  5. Elimina el comentario.

😛

Feliz año 2016.

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