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
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)
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).
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:
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.
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.
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...
In [ ]:
wind.append(model)
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
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()
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!!!
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.
Reshaping (transformar) sirve para cambiar nuestra estructura de datos en una nueva para realizar nuevos análisis específicos con los nuevos datos recombinados.
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')
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
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.
Docs para stack.
Docs para unstack.
Recetas para stack/unstack.