# 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

"""
Explanation: Nuevamente, vamos a leer primero unos datos...
End of explanation
"""


# 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)

"""
Explanation: Lectura de un fichero de datos
End of explanation
"""


# 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')

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

model.head(3)

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

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

"""
Explanation: Lectura de un segundo fichero de datos
End of explanation
"""


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

"""
Explanation: 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)
<span style="color:#888">axis = 2 (solo para Panel)</span>


(fuente: http://stackoverflow.com/a/25774395/5216568).
<br>
<div class="alert alert-info">
<p><b>Regla nemotécnica:</b></p> 
<p>Puedes pensar que el '1' es como una columna.</p>
<p><b>Otras opciones:</b></p> 
<p>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).</p>.
<p>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.</p>
<p>Además, usar `'index'`, `'rows'`, `'columns'`,..., puede llegar a ser confuso ya que en muchos sitios nos encontraremos con *keywords* que usan esa nomenclatura.</p>.
</div>

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


df.sum()

# Lo anterior sería similar a 
df.sum(axis = 0)

"""
Explanation: 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:
End of explanation
"""


df.sum(axis = 1)

"""
Explanation: 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:
End of explanation
"""


df < 10

(df < 10).all()

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

# Probad operaciones sobre df usando axis = 0, 1, 'index', rows', columns'


"""
Explanation: Otro ejemplo:
End of explanation
"""


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

new.head(5)

new.tail(5)

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

"""
Explanation: 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
End of explanation
"""


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

new.head(5)

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

"""
Explanation: 
End of explanation
"""


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

df1

df2

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

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

"""
Explanation: 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:
End of explanation
"""


wind.append(model)

"""
Explanation: 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:
End of explanation
"""


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

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

"""
Explanation: 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
End of explanation
"""


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)

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

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


"""
Explanation: Imaginemos que queremos unir dos DataFrames usando valores de columnas:
End of explanation
"""


wind.join(model).head(10)

model.join(wind).head(10)

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

"""
Explanation: 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:
End of explanation
"""


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

wind.groupby(by = 'month').mean()

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

del wind['month']

# 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,...)


"""
Explanation: 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.
End of explanation
"""


grouped = wind.groupby(by=wind.index.month)

import inspect
info = inspect.getmembers(grouped, predicate=inspect.ismethod)

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

grouped

grouped.ngroups

grouped.groups.keys()

grouped.get_group(2)

"""
Explanation: Veamos lo estructura que nos devuelve groupby
End of explanation
"""


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

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

tmp.pivot(index = 'year', columns = 'month', values='wspd')

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


"""
Explanation: pandas.core.groupby.DataFrameGroupBy es una especie de diccionario con superpoderes!!!
Reformando/transformando/modelando nuestras estructuras de datos
<div class="alert alert-info">
<p>Prácticamente toda esta parte la he extraído del <a href="https://nikolaygrozev.wordpress.com/2015/07/01/reshaping-in-pandas-pivot-pivot-table-stack-and-unstack-explained-with-pictures/">excelente artículo</a>
<em>Reshaping in Pandas – Pivot, Pivot-Table, Stack and Unstack explained with Pictures</em> de <b>Nikolay Grozev</b>.</p>
<p>Kudos para Nikolay.</p>
<p>Kudos para mi por seguir los principios <a href="https://en.wikipedia.org/wiki/Don't_repeat_yourself">DRY</a> y <a href="https://en.wikipedia.org/wiki/KISS_principle">KISS</a>.</p>
</div>

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.
End of explanation
"""


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

tmp.pivot(index = 'level_1', columns = 'level_0')

tmp.pivot(index = 'level_1', columns = 'level_0').columns

"""
Explanation: Pivotando usando varias columnas:
End of explanation
"""


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

"""
Explanation: Qué pasa si en lo que combinamos encontramos índices repetidos. Por ejemplo:
End of explanation
"""


pivoted = df.pivot(index='Item', columns='CType', values='USD')

"""
Explanation: (fuente: https://nikolaygrozev.files.wordpress.com/2015/07/pivoting_simple_error.png)
End of explanation
"""


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

"""
Explanation: 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:
End of explanation
"""


