Pandas como interfaz SQL

Mini tutorial de SQL para científicos

Hoy en día no se puede seguir trabajando con ficheros de texto habiendo tantas alternativas y, sobretodo, a medida que el tamaño de la información crece y crece y se hace inmanejable tratar ficheros de varios cientos de Mb. Es por ello que hoy vamos a ver por encima cómo podemos hacer consultas, modificar y crear nueva información en una base de datos SQL.
[Descargo de responsabilidad] Esto no pretende ser un tutorial ni algo serio y riguroso, solo un análisis superficial sobre lo que puede ofrecer el manejo de SQL para trabajar con datos y va dirigido, principalmente, a científicos (o no) que se manejan todo el día con ficheros *.dat, *.txt o *.csv.

¿Qué es SQL?

SQL es un acrónimo para Structured Query Language. Es un lenguaje que nos permite acceder a bases de datos SQL (bases de datos relacionales). Un RDBMS (Relational DataBase Management System) es un sistema que nos permite acceder, crear, editar y gestionar bases de datos relacionales. Existen RDBMS muy populares como MySQL (MariaDB), PostgreSQL o SQLite. En el presente tutorial vamos a trabajar con SQLite por simplicidad y porque viene disponible con CPython.

¿Y Pandas?

Pandas dispone de funcionalidad que nos permite leer y escribir información en bases de datos relacionales.

Vamos a crear una base de datos SQLite

Las bases de datos SQLite son bases de datos que no necesitan un servidor y que se guardan en disco. Para más información pulsa aquí. Podéis inspeccionar la base de datos que vayamos a crear, bastante simple, con SQLite manager, un addon para firefox.

Como siempre, primero importamos todo lo necesario

import sqlite3
import datetime as dt

import pandas as pd
import numpy as np
Mi configuración es la siguiente:

%load_ext version_information
%version_information pandas, numpy
Software Version
Python 3.4.0 64bit [GCC 4.8.2]
IPython 3.0.0
OS Linux 3.13.0 24 generic x86_64 with LinuxMint 17 qiana
pandas 0.15.2
numpy 1.9.2
Thu Mar 12 20:07:15 2015 CET
Primero necesitamos poder conectar con la base de datos. Esto es de lo poco que diferirá con respecto a otros RDBMSs.

conn = sqlite3.connect('pybodb.sqlite')

# ejemplo con PostgreSQL usando psycopg2
# import psycopg2
# conn = psycopg2.connect(database='ejemplodb', user='kiko') 

# ejemplo con MS ACCESS usando pyodbc (sí, en el trabajo tengo que usar estas cosas)
# import pyodbc
# conn = pyodbc.connect("Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=ejemplodb;") 

# ...
Ahora que ya tenemos una conexión a la base de datos Pandas se puede encargar del trabajo sucio de 'hablar' con la base de datos y ayudarnos a interactuar directamente con los datos de la forma habitual y potente de Pandas.

Vamos a crear un DataFrame que usaremos como una tabla para insertar en la base de datos. Este DataFrame tendrá una columna de fechas, una de medidas de temperatura promedio diaria (inventada), una de precipitación acumulada en 24h (inventada), una columna con el tipo de sensor que midió la temperatura ese día y una última con el sensor que midió la precipitación.

# fechas para cada día del 2014
fechas = pd.date_range(dt.datetime(2014, 1, 1), dt.datetime(2014, 12, 31))
# Temperatura media diaria durante 2014 en algún lugar del hemisferio Norte
tmed = (np.random.randint(-5, 5, size = 365) + 
        20 * np.cos(np.arange(0 - 180, 365 - 180) * 2 * np.pi / 365) 
        + 10)
# Precipitación acumulada en 24h
prec = (20 * np.abs(np.random.randn(365) * 
        np.cos(np.arange(0, 365) * 2 * np.pi / 365)))
# Sensor que midió la temperatura
marcaT = np.random.choice(['marcaT1', 'marcaT2', 'marcaT3'], size = 365)
# Sensor midió la precipitación
marcaP = np.random.choice(['marcaP1', 'marcaP2'], size = 365)

# Creamos el dataframe y lo guardamos en una tabla llamada 'datos'
df = pd.DataFrame(
        np.array([fechas.values, tmed, prec, marcaT, marcaP]).T,
        columns = ['fecha', 'tmedia','precipitacion','sensorT','sensorP'])
df['fecha'] = pd.to_datetime(df['fecha'])
df.to_sql('datos', con = conn, dtype = {'time': 'TIMESTAMP'})
Vamos a crear una segunda tabla para añadir un poco de complejidad a las consultas que hagamos posteriormente a la base de datos. Esta tabla contendrá información de los sensores usados para las medidas.

# fechas para cada día del 2014
sensores = ['marcaT1', 'marcaT2', 'marcaT3',
            'marcaP1', 'marcaP2']

# Precisión de los sensores
precision = [0.1, 0.5, 1,
             2, 5]

df = pd.DataFrame({'sensores': sensores, 'precision': precision})
df.to_sql('sensores', con = conn)

Consultando la base de datos

Dentro de SQL tenemos comandos que se pueden integrar en diferentes categorías. Ahora vamos a usar dql (data query language) que se usa para hacer consultas a la base de datos. Mirad en esta chuleta para conocer más.

Como SQL no hace distinción de mayúsculas y minúsculas usaré mayúsculas para las palabras clave de SQL y así las distinguiremos un poco mejor. Para hacer una petición se usa SELECT. Veamos como es una consulta para conocer las tablas que existen en la base de datos (esta consulta es específica para SQLite):

# Esto es específico para sqlite
print(pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn))
# Para otras BBDD puedes buscar en internet :-)
       name
0     datos
1  sensores
En la consulta anterior hemos usados varias palabras clave: SELECT, FROM, WHERE. Este tipo de consultas serán de lo más habitual. Vamos a explotar lo que ya sabemos.
Quiero los datos de todas las columnas de la tabla sensores (y pandas me lo meterá en un DataFrame, maravilloso!!!)

df = pd.read_sql("SELECT * FROM sensores;", conn)
print(df)
   index  precision sensores
0      0        0.1  marcaT1
1      1        0.5  marcaT2
2      2        1.0  marcaT3
3      3        2.0  marcaP1
4      4        5.0  marcaP2
Ahora queremos los datos de precipitación de Junio junto con su fecha. Fijaos que el valor superior del rango no es inclusivo (BETWEEN '2014-06-01' AND '2014-07-01' nos da el dato hasta antes de fecha = '2014-07-01'):

df = pd.read_sql("SELECT fecha, precipitacion FROM datos WHERE fecha BETWEEN '2014-06-01' AND '2014-07-01';", conn)
print(df)
                  fecha  precipitacion
0   2014-06-01 00:00:00      21.090544
1   2014-06-02 00:00:00      19.514893
2   2014-06-03 00:00:00       1.356933
3   2014-06-04 00:00:00      14.466592
4   2014-06-05 00:00:00      12.780801
5   2014-06-06 00:00:00      28.293163
6   2014-06-07 00:00:00       7.841272
7   2014-06-08 00:00:00      22.278980
8   2014-06-09 00:00:00      24.386602
9   2014-06-10 00:00:00       4.158349
10  2014-06-11 00:00:00       8.850627
11  2014-06-12 00:00:00      24.572156
12  2014-06-13 00:00:00      30.675862
13  2014-06-14 00:00:00       5.986650
14  2014-06-15 00:00:00      19.186014
15  2014-06-16 00:00:00      36.980785
16  2014-06-17 00:00:00      11.822683
17  2014-06-18 00:00:00       1.774325
18  2014-06-19 00:00:00       8.043394
19  2014-06-20 00:00:00       9.909083
20  2014-06-21 00:00:00      26.050138
21  2014-06-22 00:00:00      32.829371
22  2014-06-23 00:00:00       1.067285
23  2014-06-24 00:00:00      41.178811
24  2014-06-25 00:00:00       4.357684
25  2014-06-26 00:00:00      36.863093
26  2014-06-27 00:00:00      18.923454
27  2014-06-28 00:00:00       5.641863
28  2014-06-29 00:00:00       5.831242
29  2014-06-30 00:00:00      45.159804
Ahora quiero los datos de temperatura de los sensores con una precisión superior a 0.5 (el sensor marcaT1 es el único que me da precisión superior a 0.5):

df = pd.read_sql("SELECT fecha, tmedia FROM datos WHERE datos.sensorT = 'marcaT1';", conn)
print(df)
                   fecha     tmedia
0    2014-01-01 00:00:00  -5.981482
1    2014-01-08 00:00:00  -9.733159
2    2014-01-09 00:00:00 -12.674186
3    2014-01-11 00:00:00  -5.538770
4    2014-01-13 00:00:00  -5.380197
5    2014-01-15 00:00:00 -13.198654
6    2014-01-17 00:00:00 -12.994357
7    2014-01-19 00:00:00  -8.767548
8    2014-01-31 00:00:00  -8.950818
9    2014-02-02 00:00:00  -4.575402
10   2014-02-03 00:00:00  -7.380298
11   2014-02-09 00:00:00  -4.109866
12   2014-02-10 00:00:00  -7.882079
13   2014-02-13 00:00:00  -8.172534
14   2014-02-16 00:00:00  -1.425199
15   2014-02-18 00:00:00  -0.906962
16   2014-02-20 00:00:00  -7.373428
17   2014-02-24 00:00:00  -1.263014
18   2014-02-27 00:00:00  -3.394876
19   2014-03-05 00:00:00  -1.577839
20   2014-03-07 00:00:00   4.049144
21   2014-03-08 00:00:00   0.366216
22   2014-03-20 00:00:00   3.320033
23   2014-03-24 00:00:00  10.681308
24   2014-03-28 00:00:00  11.053572
25   2014-03-29 00:00:00   5.397594
26   2014-03-31 00:00:00  14.086071
27   2014-04-08 00:00:00  15.830806
28   2014-04-15 00:00:00  16.190236
29   2014-04-18 00:00:00  11.180340
..                   ...        ...
94   2014-10-11 00:00:00   6.982189
95   2014-10-12 00:00:00   6.645535
96   2014-10-13 00:00:00   3.310172
97   2014-10-16 00:00:00   8.312816
98   2014-10-18 00:00:00   4.656174
99   2014-10-20 00:00:00  -1.992949
100  2014-10-22 00:00:00   3.366216
101  2014-10-30 00:00:00   1.900779
102  2014-11-04 00:00:00  -6.545832
103  2014-11-08 00:00:00  -4.642068
104  2014-11-11 00:00:00   0.574801
105  2014-11-15 00:00:00  -5.413343
106  2014-11-18 00:00:00  -8.109866
107  2014-11-21 00:00:00  -2.766101
108  2014-11-22 00:00:00  -4.975587
109  2014-11-25 00:00:00  -6.575402
110  2014-11-28 00:00:00  -9.131020
111  2014-11-30 00:00:00  -5.476142
112  2014-12-03 00:00:00  -5.954868
113  2014-12-10 00:00:00  -4.883750
114  2014-12-11 00:00:00 -13.994357
115  2014-12-12 00:00:00  -8.099335
116  2014-12-14 00:00:00 -13.292284
117  2014-12-16 00:00:00  -8.462367
118  2014-12-18 00:00:00  -8.609383
119  2014-12-21 00:00:00 -13.786284
120  2014-12-23 00:00:00 -12.874932
121  2014-12-25 00:00:00  -7.940023
122  2014-12-26 00:00:00 -11.963711
123  2014-12-27 00:00:00  -8.981482

[124 rows x 2 columns]
Si os fijáis, en la consulta anterior he usado datos.sensorT (tabla.columna). Esto solo será necesario si estamos trabajando con varias tablas y una columna se puede llamar igual en varias tablas. De esta forma no hay posibilidad de equivocarse ya que usamos 'nombres y apellidos'.
Vamos a hacer una consulta que nos dé el mismo resultado pero que enlaza varias consultas (un SELECT dentro de un SELECT). También la represento de forma un poco más estructurada para que sea más legible:

# La misma consulta de antes pero sin saber la precisión de cada uno de los sensores a priori
q = """
SELECT 
    fecha, tmedia 
FROM 
    datos 
WHERE 
    datos.sensorT = 
    (SELECT 
        sensores 
    FROM 
        sensores 
    WHERE 
        precision < 0.5);"""

df = pd.read_sql(q, conn)
print(df)
                   fecha     tmedia
0    2014-01-01 00:00:00  -5.981482
1    2014-01-08 00:00:00  -9.733159
2    2014-01-09 00:00:00 -12.674186
3    2014-01-11 00:00:00  -5.538770
4    2014-01-13 00:00:00  -5.380197
5    2014-01-15 00:00:00 -13.198654
6    2014-01-17 00:00:00 -12.994357
7    2014-01-19 00:00:00  -8.767548
8    2014-01-31 00:00:00  -8.950818
9    2014-02-02 00:00:00  -4.575402
10   2014-02-03 00:00:00  -7.380298
11   2014-02-09 00:00:00  -4.109866
12   2014-02-10 00:00:00  -7.882079
13   2014-02-13 00:00:00  -8.172534
14   2014-02-16 00:00:00  -1.425199
15   2014-02-18 00:00:00  -0.906962
16   2014-02-20 00:00:00  -7.373428
17   2014-02-24 00:00:00  -1.263014
18   2014-02-27 00:00:00  -3.394876
19   2014-03-05 00:00:00  -1.577839
20   2014-03-07 00:00:00   4.049144
21   2014-03-08 00:00:00   0.366216
22   2014-03-20 00:00:00   3.320033
23   2014-03-24 00:00:00  10.681308
24   2014-03-28 00:00:00  11.053572
25   2014-03-29 00:00:00   5.397594
26   2014-03-31 00:00:00  14.086071
27   2014-04-08 00:00:00  15.830806
28   2014-04-15 00:00:00  16.190236
29   2014-04-18 00:00:00  11.180340
..                   ...        ...
94   2014-10-11 00:00:00   6.982189
95   2014-10-12 00:00:00   6.645535
96   2014-10-13 00:00:00   3.310172
97   2014-10-16 00:00:00   8.312816
98   2014-10-18 00:00:00   4.656174
99   2014-10-20 00:00:00  -1.992949
100  2014-10-22 00:00:00   3.366216
101  2014-10-30 00:00:00   1.900779
102  2014-11-04 00:00:00  -6.545832
103  2014-11-08 00:00:00  -4.642068
104  2014-11-11 00:00:00   0.574801
105  2014-11-15 00:00:00  -5.413343
106  2014-11-18 00:00:00  -8.109866
107  2014-11-21 00:00:00  -2.766101
108  2014-11-22 00:00:00  -4.975587
109  2014-11-25 00:00:00  -6.575402
110  2014-11-28 00:00:00  -9.131020
111  2014-11-30 00:00:00  -5.476142
112  2014-12-03 00:00:00  -5.954868
113  2014-12-10 00:00:00  -4.883750
114  2014-12-11 00:00:00 -13.994357
115  2014-12-12 00:00:00  -8.099335
116  2014-12-14 00:00:00 -13.292284
117  2014-12-16 00:00:00  -8.462367
118  2014-12-18 00:00:00  -8.609383
119  2014-12-21 00:00:00 -13.786284
120  2014-12-23 00:00:00 -12.874932
121  2014-12-25 00:00:00  -7.940023
122  2014-12-26 00:00:00 -11.963711
123  2014-12-27 00:00:00  -8.981482

[124 rows x 2 columns]
Podemos decirle que nos pase solo una serie de valores. Por ejemplo, solo quiero los tres valores más altos de precipitación de diciembre:

q = """
SELECT 
    fecha, precipitacion 
FROM 
    datos 
WHERE 
    fecha > '2014-11-30'
ORDER BY
    precipitacion DESC
LIMIT
    3"""

df = pd.read_sql(q, conn)
print(df)
                 fecha  precipitacion
0  2014-12-31 00:00:00      50.047120
1  2014-12-01 00:00:00      43.913895
2  2014-12-04 00:00:00      37.344830
En la consulta anterior le hemos pedido que nos ordenase por los valores de precipitación de forma descendente (es decir, por el final) y le hemos pedido que nos limitase la búsqueda a tres valores, los tres valores más altos.

¿Os ha picado el gusanillo?

Todo esto era para ver si os picaba un poco el gusanillo y dejáis (dejamos) de usar tanto fichero de texto/excel/csv y usamos opciones más ricas y potentes que existen por ahí.

Enlaces

Y cerramos la conexión (literal) por hoy.

conn.close() # :-)

Y el notebook en el repositorio, como siempre.

Aprende (funcionalidad básica de) PyTables paso a paso (III)

En los anteriores capítulos vimos como crear una estructura básica para nuestro fichero h5 y como rellenar una tabla de datos. Ahora vamos a ver como volver a la tabla que ya rellenamos para añadir nuevos registros sin eliminar los anteriores.

Modificar tablas existentes

Podemos añadir datos a tablas existentes de la siguiente forma. Para hacerlo primero abrimos el fichero que ya creamos anteriormente en modo "append", obtenemos el nodo que nos lleva a la tabla en cuestión que queremos rellenar (tab será una instancia de la clase Table) y establecemos un 'puntero' donde guardar los nuevos datos (con ayuda de tab.row donde mis_datos serán una instancia de la clase Row):

# Si no habéis importado las librerías necesarias
# descomentad las siguientes tres líneas:
# import numpy as np
# import datetime as dt
# import tables as tb
h5file = tb.openFile("tabla_test.h5", mode = "a")
tab = h5file.getNode("/carpeta1/datos_carpeta1_tabla1")
mis_datos = tab.row

Ahora vamos a recuperar la última fecha que habíamos introducido en la columna fechas para seguir metiendo registros con fechas a continuación.

## En la siguiente línea recuperamos el último elemento
## de la columna fecha
## No os preocupéis por esto ahora, lo veremos más adelante
ult_fecha = str(tab.cols.fecha[-1])
yy = int(ult_fecha[:4])
mm = int(ult_fecha[4:6])
dd = int(ult_fecha[6:])
fecha0 = dt.date(yy, mm, dd) + dt.timedelta(days = 1)
## Creamos 2000 nuevas fechas a continuación de la última que
## habíamos introducido con sus valores x, y, z correspondientes
fechas = np.array([int((fecha0 +
                  dt.timedelta(days = 1) * i).strftime("%Y%m%d"))
                  for i in range(2000)],
                  dtype = np.int32)
x = np.random.randn(2000)
y = np.random.randn(2000)
z = np.random.randn(2000)

Y, finalmente, añadimos los nuevos valores tal como vimos en el anterior capítulo:

for i in range(2000):
    mis_datos['fecha'] = fechas[i]
    mis_datos['x'] = x[i]
    mis_datos['y'] = y[i]
    mis_datos['z'] = z[i]
    mis_datos.append()
tab.flush()

En la siguiente imagen se muestra un ejemplo de lo que acabamos de hacer:

hdfview_nuevos valores añadidos

¿Qué pasa si ahora queremos modificar un valor de una columna? Eso lo podemos hacer usando la clase `Cols`. Imaginad que los primeros diez elementos de la columna `x` son erróneos y los queremos representar con 'NaNs'. Lo podemos hacer así:

tab.cols.x[0:10] = np.repeat(np.NAN, 10)
tab.flush()

Si ahora inspeccionamos la tabla veremos que los diez primeros elementos de la columna 'x' han cambiado al valor NaN:

hdfview_reemplazar valores

¿Y si queremos cambiar una columna entera de datos? Pues siguiendo el mismo procedimiento podemos hacer lo siguiente para modificar la columna 'z':

tab.cols.z[:] = np.repeat(-999.99, tab.cols.z.shape[0])
tab.flush()

Con lo que la tabla quedaría con la última columna (columna 'z') con todos sus elementos con valor '-999.99'

hdfview_reemplazar columna

Finalmente, para terminar este capítulo, cerramos la tabla creada como vimos en anteiores capítulos.

h5file.close()

Veremos un poco más en los próximos días, estad atentos.

Saludos.

Aprende (funcionalidad básica de) PyTables paso a paso (II)

En el anterior capítulo vimos como crear una estructura básica para nuestro fichero h5. Ahora vamos a ver como rellenar tablas de ese mismo fichero.

Rellenar tablas

Ahora es tiempo de rellenar la tabla con datos sin sentido (datos aleatorios) y fechas. numpy y datetime nos facilitarán la tarea por lo que vamos a importarlos antes).

import numpy as np
import datetime as dt

Abrimos de nuevo el fichero h5 que creamos en el anterios capítulo, pero en lugar de abrirlo en modo "write" lo abriremos en modo "append".

h5file = tb.openFile("tabla_test.h5", mode = "a")

Recuperamos la tabla que queremos llenar (indicando la ruta en la estructura del fichero hdf5) y usando la función getNode:

tab = h5file.getNode("/carpeta1/datos_carpeta1_tabla1")

Para rellenar la tabla creamos un puntero (o constructor de líneas propio a la tabla) row.

mis_datos = tab.row

Creamos los datos que usaremos para rellenar la tabla:

fechas = np.array([int((dt.date(2000, 1, 1) +
                        dt.timedelta(days = 1) * i).strftime("%Y%m%d"))
                        for i in range(10000)],
                        dtype = np.int32)
x = np.random.randn(10000)
y = np.random.randn(10000)
z = np.random.randn(10000)

Llenamos la tabla de manera recursiva con el constructor de líneas mis_datos y el método append:

for i in range(10000):
    mis_datos['fecha'] = fechas[i]
    mis_datos['x'] = x[i]
    mis_datos['y'] = y[i]
    mis_datos['z'] = z[i]
    mis_datos.append()

Se llama al método flush sobre la tabla para volcar y registrar efectivamente los datos en la tabla.

tab.flush()

Si queremos, podemos añadir meta-información a la tabla:

tab.attrs.nombre_sensor="medidas de un escaterómetro"
tab.attrs.numero_columnas = 3

Además de la meta-información que hemos añadido, el objeto tabla contenía ya un cierto número de atributos que podemos ver escribiendo lo siguiente en IPython

tab.attrs

que nos mostrará lo siguiente:

/carpeta1/datos_carpeta1_tabla1._v_attrs (AttributeSet), 14 attributes:
   [CLASS := 'TABLE',
    FIELD_0_FILL := 0,
    FIELD_0_NAME := 'fecha',
    FIELD_1_FILL := 0.0,
    FIELD_1_NAME := 'x',
    FIELD_2_FILL := 0.0,
    FIELD_2_NAME := 'y',
    FIELD_3_FILL := 0.0,
    FIELD_3_NAME := 'z',
    NROWS := 0,
    TITLE := 'ejemplo de tabla',
    VERSION := '2.7',
    nombre_sensor := 'medidas de un escaterómetro',
    numero_columnas := 3]

Finalmente, cerramos la tabla creada como vimos en el primer capítulo.

h5file.close()

Si queréis usar una aplicación gráfica para ver el alrchivo que hemos creado podéis usar hdfview (sudo apt-get install hdfview) o vitables. En este caso os muestro un ejemplo con hdfview ya que vitables posíblemente lo veamos en uno de los próximos capítulos.

hdfview
[Pulsa sobre la imagen para agrandarla]
Bueno, ya tenemos algunos datos guardados en nuestro fichero HDF5. El próximo día veremos como anexar datos a una tabla ya existente.

Saludos.

Aprende (funcionalidad básica de) PyTables paso a paso (I)

HDF5

El HDF5 (hierarchical dataset format, http://www.hdfgroup.org/HDF5/) es un formato que permite almacenar eficientemente grandes volúmenes de datos. Los datos se pueden almacenar de forma jerarquizada conjuntamente con metadatos. Es un formato portable que prácticamente no tiene límite en el tamaño de los datos.

PyTables

Pytables está programado sobre el formato hdf5 con ayuda de Python y Numpy. La creación, modificación y búsquedas sobre grandes conjuntos de datos es relativamente sencilla. La utilización de memoria RAM y compresión de datos están optimizados en segundo plano por la biblioteca. El espacio de disco utilizado es mucho más pequeño y el acceso a los datos suele ser más rápido comparado con bases de datos relacionales.

La versión actual es la 3.0 y es compatible con python 2.7 y 3.1 o superiores.

Para instalarlo en un linux basado en Debian podéis hacer lo siguiente (asumo que tenéis pip y numpy instalado en vuestro virtualenv científico):

sudo apt-get install libhdf5-serial-dev
pip install numexpr cython
pip install tables

Para instalarlo en Windows podéis hacerlo usando un ejecutable ya preparado por el gran Christoph Gohlke.

Si todo está correcto podemos empezar con el tutorial. Primero importamos la biblioteca

Creación de ficheros h5

import tables as tb

Ahora vamos a definir la estructura de una tabla creando una clase que deriva de la clase IsDescription (igual os suena a algo parecido a los modelos de Django). Nuestra tabla contará con una columna de fechas y tres columnas de datos 'float':

class EstructuraTabla(tb.IsDescription):
    fecha = tb.Int64Col(pos = 0)
    x = tb.Float32Col(pos = 1)
    y = tb.Float32Col(pos = 2)
    z = tb.Float32Col(pos = 3)

Esta clase es un constructor de tabla. Se dan nombre predefinidos a las columnas, el tipo de datos que contendrán y su posición.

Ahora creamos un nuevo fichero h5 en modo de escritura.

# Donde pone "tabla_test.h5" y "Ejemplo pybonaccico"
# puedes poner la ruta y nombre de fichero que quieras
h5file = tb.openFile("tabla_test.h5",
                     mode = "w",
                     title = "Ejemplo pybonaccico")

En este caso, en el título hemos puesto algo muy estúpido pero en ese campo se puede poner algo más serio como 'Datos aportados por Google, fecha: 2012/01/01 14.23h, proyecto PRISM ;-P'

Creamos un "grupo" en la raiz del h5 (parecido a una carpeta en un sistema de ficheros):

grupo1 = h5file.createGroup("/",
                            'carpeta1',
                            'carpeta para un primer grupo de datos')

carpeta1 será el nombre del grupo dentro del fichero h5 y carpeta para un primer grupo de datos serán los metadatos descriptivos que podemos asociar al grupo carpeta1.

Y ahora, dentro del grupo que acabamos de crear, creamos una tabla (que sería como un fichero en un sistema de ficheros) con la estructura de tabla que hemos definido anteriormente (clase EstructuraTabla):

tab = h5file.createTable(grupo1,
                         "datos_carpeta1_tabla1",
                         EstructuraTabla,
                         "ejemplo de tabla")

Aquí, datos_carpeta1_tabla1 sería como el nombre del fichero (tabla) dentro de un sistema de archivos (dentro del fichero h5), EstructuraTabla es la estructura de los datos de la tabla que acabamos de definir y ejemplo de tabla es la información que asociamos a esta tabla.

En cualquier momento podemos inspeccionar la estructura que va tomando nuestro fichero h5 haciendo un print del fichero h5.

print(h5file)

Lo anterior nos debería de mostrar algo parecido a:

tabla_test.h5 (File) 'Ejemplo pybonaccico'
Last modif.: 'Thu Jul  4 21:51:41 2013'
Object Tree:
/ (RootGroup) 'Ejemplo pybonaccico'
/carpeta1 (Group) 'carpeta para un primer grupo de datos'
/carpeta1/datos_carpeta1_tabla1 (Table(0,)) 'ejemplo de tabla'

Si queremos obtener más información podemos escribir simplemente en IPython:

h5file

que nos mostrará lo siguiente:

File(filename=tabla_test.h5, title='Ejemplo pybonaccico', mode='w', root_uep='/', filters=Filters(complevel=0, shuffle=False, fletcher32=False))
/ (RootGroup) 'Ejemplo pybonaccico'
/carpeta1 (Group) 'carpeta para un primer grupo de datos'
/carpeta1/datos_carpeta1_tabla1 (Table(0,)) 'ejemplo de tabla'
  description := {
  "fecha": Int64Col(shape=(), dflt=0, pos=0),
  "x": Float32Col(shape=(), dflt=0.0, pos=1),
  "y": Float32Col(shape=(), dflt=0.0, pos=2),
  "z": Float32Col(shape=(), dflt=0.0, pos=3)}
  byteorder := 'little'
  chunkshape := (3276,)

Finalmente, cerramos la tabla creada de la siguiente forma.

h5file.close()

Y ya es suficiente por hoy, el próximo día veremos como rellenar tablas con datos ya que, de momento, solo hemos creado una estructura básica.

Saludos.