Nuevamente, vamos a leer primero unos datos...


In [ ]:
# primero hacemos los imports de turno
import os
import datetime as dt

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import display

np.random.seed(19760812)
%matplotlib inline

Lectura de un fichero de datos


In [ ]:
# Leemos los datos del fichero 'mast.txt'
ipath = os.path.join('Datos', 'mast.txt')

def dateparse(date, time):
    YY = 2000 + int(date[:2])
    MM = int(date[2:4])
    DD = int(date[4:])
    hh = int(time[:2])
    mm = int(time[2:])
    
    return dt.datetime(YY, MM, DD, hh, mm, 0)
    

cols = ['Date', 'time', 'wspd', 'wspd_max', 'wdir',
        'x1', 'x2', 'x3', 'x4', 'x5', 
        'wspd_std']
wind = pd.read_csv(ipath, sep = "\s*", names = cols, 
                   parse_dates = {'Timestamp': [0, 1]}, index_col = 0,
                   date_parser = dateparse)

Lectura de un segundo fichero de datos


In [ ]:
# Leemos los datos del fichero 'model.txt'
ipath = os.path.join('Datos', 'model.txt')

model = pd.read_csv(ipath, sep = "\s*", skiprows = 3,
                    parse_dates = {'Timestamp': [0, 1]}, index_col = 'Timestamp')

In [ ]:
for c in ['x1','x2','x3','x4','x5']: # Eliminamos unas columnas innecesarias
    _ = wind.pop(c)
wind.head(3)

In [ ]:
model.head(3)

In [ ]:
wind['Timestamp'] = wind.index
print(wind['Timestamp'].diff().min())
del wind['Timestamp']

In [ ]:
model['Timestamp'] = model.index
print(model['Timestamp'].diff().min())
del model['Timestamp']

Tenemos datos con una frecuencia temporal mínima de 10 minutos (wind) frente a unos segundos datos con una frecuencia temporal de 1 hora (model).

Inciso: axis 101

En muchas ocasiones vamos a encontrar una keyword llamada axis. Veamos en un momento cómo funciona en pandas para evitarnos posibles problemas e incongruencias:

Posibilidades

  • axis = 0 (actúa sobre las filas/rows)
  • axis = 1 (actúa sobre las columnas/columns)
  • axis = 2 (solo para `Panel`)

(fuente: http://stackoverflow.com/a/25774395/5216568).


Regla nemotécnica:

Puedes pensar que el '1' es como una columna.

Otras opciones:

Otra opción sería usar `axis = 'index'` (similar a `axis = 0`) o `axis = 'columns'` (similar a `axis = 1`) para `DataFrame`s. Para `Panel`es tendríamos `items`, `minor`, `major` (similar a las opciones 0, 1 o 2).

.

Para `DataFrame`s también podéis usar `index = 'rows'`, que me parece que es más evidente que `'index'` pero no lo recomiendo ya que no está documentado en ningún sitio.

Además, usar `'index'`, `'rows'`, `'columns'`,..., puede llegar a ser confuso ya que en muchos sitios nos encontraremos con *keywords* que usan esa nomenclatura.

.

Pero, que significa que 'actúa sobre las filas/columnas'. Veamos algunos ejemplos simples para ver si nos queda un poco más claro:


In [ ]:
df = pd.DataFrame(np.array([[1, 10], [2, 20], [3,30]]), columns = ['A', 'B'])
df

Si no indicamos nada, por defecto, las operaciones se realizan sobre las filas (axis = 0), es decir, se cogen todos los elementos de fila de cada columna:


In [ ]:
df.sum()

In [ ]:
# Lo anterior sería similar a 
df.sum(axis = 0)

Si queremos que nos dé el resultado de cada fila, es decir, se cogen todos los elementos de columna de una fila, debemos indicar que axis = 1:


In [ ]:
df.sum(axis = 1)

Otro ejemplo:


In [ ]:
df < 10

In [ ]:
(df < 10).all()

In [ ]:
(df < 10).all(axis = 'columns') # en lugar de axis = 1 usamos axis = 'columns'

In [ ]:
# Probad operaciones sobre df usando axis = 0, 1, 'index', rows', columns'

Espero que haya quedado un poco claro con estos ejemplos simples.

Uniendo estructuras de datos pandas

Lo que vamos a ver no es evidente y, en algunos casos, es conveniente conocer algebral relacional para poder enterder qué es lo que está pasando.

Uniendo datos usando concat


In [ ]:
new = pd.concat([wind, model], axis = 0, join = 'outer')

In [ ]:
new.head(5)

In [ ]:
new.tail(5)

In [ ]:
new.loc['2014/01/01 00:00':'2014/01/01 02:00']


In [ ]:
new = pd.concat([wind, model], axis = 1, join = 'inner')

In [ ]:
new.head(5)

In [ ]:
new.loc['2014/01/01 00:00':'2014/01/01 02:00']

concat permite 'unir' estructuras de datos pandas usando filas o columnas.

¿¿¿¡¡¡Y lo anterior no os ha quedado nada claro!!!??? ¿¿¿¡¡¡Y no habéis preguntado!!!???

Veamos un ejemplo más simple:


In [ ]:
df1 = pd.DataFrame(np.random.randn(10,2), 
                   columns = ['A', 'B'], 
                   index = np.arange(10))
df2 = pd.DataFrame(np.random.randn(4,3), 
                   columns = ['A', 'B', 'C'], 
                   index = np.arange(8, 12))

In [ ]:
df1

In [ ]:
df2

In [ ]:
new = pd.concat([df1, df2], axis = 0, join = 'inner')
new

In [ ]:
new = pd.concat([df1, df2], axis = 1, join = 'inner')
new

Normalmente uso esta última opción con nombres de columnas diferentes porque normalmente es lo que quiero hacer...

Concatenando usando el método append

Podemos hacer algo parecido a lo anterior usando el método append de las estructuras de datos:


In [ ]:
wind.append(model)

Normalmente esto no es lo que quiero hacer. Normalmente quiero juntar con cierta lógica estructuras de datos pandas y para ello podemos usar pd.merge...

Usando pd.merge como en una base de datos SQL


In [ ]:
pd.merge(wind, model, left_index = True, right_index = True, how = 'inner').head(5)

In [ ]:
(pd.merge(wind, model, left_index = True, right_index = True, how = 'inner') == 
 pd.concat([wind, model], axis = 1, join = 'inner')).all().all()

Imaginemos que queremos unir dos DataFrames usando valores de columnas:


In [ ]:
df1 = pd.DataFrame(
    np.array([
        np.arange(1, 11),
        np.random.choice([1,2,3], size = 10),
        np.arange(1, 11) * 10
    ]).T,
    columns = ['A', 'col', 'B']
)
df2 = pd.DataFrame(
    np.array([
        np.arange(11, 21),
        np.random.choice([1,2,3], size = 10),
        np.arange(1, 11) * 100
    ]).T,
    columns = ['A', 'col', 'B']
)
display(df1)
display(df2)

In [ ]:
pd.merge(df1, df2, on = ['col'])

In [ ]:
# Jugad un poco y mirad las keywords del pd.merge para ver otras opciones

Combinando usando el método join

Un poco más de lo mismo. El método join nos ayuda, nuevamente, a unir estructuras de datos pandas. Vamos a ver unos pocos ejemplos rápidos:


In [ ]:
wind.join(model).head(10)

In [ ]:
model.join(wind).head(10)

In [ ]:
joinA  = wind.join(model, how = 'inner') 
joinB = model.join(wind, how = 'inner').loc[:,joinA.columns]
(joinA == joinB).all().all()

Agrupando

Podemos agrupar información de nuestras estructuras de datos de forma muy sencilla mediante el método groupby. Normalmente se sigue una estrategia de separar-aplicar-combinar (split-apply-combine). Lo que se hace es separar los datos iniciales en grupos de interés, sobre cada grupo se aplica cierta funcionalidad y el resultado se combina en una nueva estructura de datos.


In [ ]:
wind['month'] = wind.index.month
wind.iloc[[0, 1000, 10000, 30000]]

In [ ]:
wind.groupby(by = 'month').mean()

In [ ]:
wind.groupby(by = [wind.index.year, 'month']).mean()

In [ ]:
del wind['month']

In [ ]:
# Jugad un poco agrupando 
# (sacad valores medios diarios de la velocidad del viento, 
# la velocidad promedio de los martes cuando la dirección es mayor que 300 y menos que 360,...)

Veamos lo estructura que nos devuelve groupby


In [ ]:
grouped = wind.groupby(by=wind.index.month)

In [ ]:
import inspect
info = inspect.getmembers(grouped, predicate=inspect.ismethod)

for stuff in info:
    print(stuff[0])

In [ ]:
grouped

In [ ]:
grouped.ngroups

In [ ]:
grouped.groups.keys()

In [ ]:
grouped.get_group(2)

pandas.core.groupby.DataFrameGroupBy es una especie de diccionario con superpoderes!!!

Reformando/transformando/modelando nuestras estructuras de datos

Prácticamente toda esta parte la he extraído del excelente artículo Reshaping in Pandas – Pivot, Pivot-Table, Stack and Unstack explained with Pictures de Nikolay Grozev.

Kudos para Nikolay.

Kudos para mi por seguir los principios DRY y KISS.

Reshaping (transformar) sirve para cambiar nuestra estructura de datos en una nueva para realizar nuevos análisis específicos con los nuevos datos recombinados.

Pivot

Obtenemos una nueva tabla derivada de nuestra tabla inicial de datos. Por ejemplo, imaginad que quiero una tabla de velocidades medias mensuales por cada año.


In [ ]:
wind['year'] = wind.index.year
wind['month'] = wind.index.month
tmp = wind.groupby(by = ['year', 'month']).mean()
del wind['year']
del wind['month']
tmp

In [ ]:
tmp['year'] = tmp.index.get_level_values(0)
tmp['month'] = tmp.index.get_level_values(1)
tmp

In [ ]:
tmp.pivot(index = 'year', columns = 'month', values='wspd')

In [ ]:
# Obtened la velocidad media de cada año 
# partiendo de tmp.pivot(index='level_0', columns='level_1', values='wspd')

Pivotando usando varias columnas:


In [ ]:
tmp = wind.groupby(by = [wind.index.year, wind.index.month])
tmp = tmp.agg({'wspd': np.mean, 'wspd_max': 'max'})
tmp.reset_index(inplace = True)
tmp

In [ ]:
tmp.pivot(index = 'level_1', columns = 'level_0')

In [ ]:
tmp.pivot(index = 'level_1', columns = 'level_0').columns

Qué pasa si en lo que combinamos encontramos índices repetidos. Por ejemplo:


In [ ]:
from collections import OrderedDict
table = OrderedDict((
    ("Item", ['Item0', 'Item0', 'Item0', 'Item1']),
    ('CType',['Gold', 'Bronze', 'Gold', 'Silver']),
    ('USD',  ['1$', '2$', '3$', '4$']),
    ('EU',   ['1€', '2€', '3€', '4€'])
))
df = pd.DataFrame(table)
df

In [ ]:
pivoted = df.pivot(index='Item', columns='CType', values='USD')

pivot_table al rescate para resolver el anterior error

El anterior error lo podemos resolver usando pivot_table que es un poco más flexible que pivot:


In [ ]:
table = OrderedDict((
    ("Item", ['Item0', 'Item0', 'Item0', 'Item1']),
    ('CType',['Gold', 'Bronze', 'Gold', 'Silver']),
    ('USD',  [1, 2, 3, 4]),
    ('EU',   [1.1, 2.2, 3.3, 4.4])
))
df = pd.DataFrame(table)
pivoted = df.pivot_table(index='Item', columns='CType', values='USD', aggfunc=np.min)
pivoted

Stack y Unstack

Lo vamos a ver muy brevemente para no complicar más el asunto ya que envuelve varios niveles de MultiIndex que me salto de forma explícita en este tutorial.

(fuente: https://nikolaygrozev.files.wordpress.com/2015/07/stack-unstack1.png)

Docs para stack.

Docs para unstack.

Recetas para stack/unstack.