# 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

# 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 = [[0, 1]], index_col = 0,
                   date_parser = dateparse)

"""
Explanation: Nuevamente, vamos a leer primero unos datos...
Lectura de un fichero de datos
End of explanation
"""


wind[0:10]

"""
Explanation: Seleccionando datos
Acceso a los valores como si fuera un numpy array
Podemos acceder a los datos usando indexación como haríamos con un numpy array o como se hace normalmente en Python:

En Python la indexación empieza por 0 y el último elemento de la 'rebanada' (slice) no se incluye.
End of explanation
"""


wind['2013-09-04 00:00:00':'2013-09-04 01:30:00']

"""
Explanation: Los índices de un numpy array solo pueden ser enteros.
Acceso a los valores mediante indexación de índices
Además, a diferencia de numpy, podemos acceder por índices que no necesariamente sean enteros:
End of explanation
"""


wind['wspd'].head(3)

"""
Explanation: En este segundo ejemplo la indexación se hace mediante strings que son la representación de los índices (etiquetas de las posiciones). Otra cosa a resaltar es que en el slice el último elemento SÍ se incluye.
Selección básica de columna (DataFrame)
En ejemplos anteriores, también hemos visto que podíamos seleccionar columnas usando el nombre de la columna:
End of explanation
"""


# Esto es equivalente a lo de la celda anterior
wind.wspd.head(3)

# Un ejemplo de potencial error
df1 = pd.DataFrame(np.random.randn(5,2), columns = [1, 2])
df1

# Esto dará error
df1.1

# Para que no dé error
df1[1]

"""
Explanation: Depende como sean los nombres de las columnas, se puede acceder a la columna usando la notación 'de punto' (dot notation) pero como no siempre funciona y es propenso a errores os recomiendo no usar esta forma:
End of explanation
"""


# Creamos una serie
wspd = wind['wspd']

# Accedemos a los elementos situados en las posiciones 0, 100 y 1000
print(wspd[[0, 100, 1000]])
print('\n' * 3)

# Usamos los índices en las posiciones 0, 100 y 1000
idx = wspd[[0, 100, 1000]].index
print(idx)
print('\n' * 3)

# Accedemos a los mismos que inicialmente pero usando los índices en lugar de 
# las posiciones de los valores
print(wspd[idx])

"""
Explanation: Fancy indexing con Series
También se puede utilizar Fancy indexing con series, como si estuviéramos indexando con una lista o con un array de booleanos:
End of explanation
"""


# Intentadlo...


"""
Explanation: Con DataFrames el fancy indexing puede resultar ambiguo y nos dará error de indexación.
End of explanation
"""


idx = wind['wspd'] > 35
wind[idx]

"""
Explanation: Indexación booleana
Como con numpy, también podemos acceder a los valores mediante condiciones booleanas:
End of explanation
"""


idx = (wind['wspd'] > 35) & (wind['wdir'] > 225)
wind[idx]

"""
Explanation: Podemos encadenar diferentes condiciones. Por ejemplo, vamos a refinar el resultado anterior:
End of explanation
"""


# Para que sea más eficiente hay que tener instalado 'numexpr'
# Que es el engine por defecto. Si no lo tenemos instalado y
# no indicamos el engine ('python') nos dará ImportError
wind.query('wspd > 35 and wdir > 225', engine = 'python')

"""
Explanation: Usando condiciones booleanas puede resultar menos legible. Desde la versión de pandas 0.13 podemos usar el método query para hacerlo un poco más legible.
End of explanation
"""


s1 = pd.Series(np.arange(0,10), index = np.arange(0,10))
s2 = pd.Series(np.arange(10,20), index = np.arange(5,15))
print(s1)
print(s2)

"""
Explanation: Mediante estas formas de selección pueden llegar a existir ciertas ambigüedades. Vamos a hacer un pequeño inciso para después volver a formas de selección más avanzadas.
INCISO: Alineamiento de datos al operar con estructuras de datos de Pandas
Cuando realizamos una operación entre dos estructuras de datos de Pandas se produce un efecto de alineamiento muy práctico. Veamos esto mediante ejemplos:
End of explanation
"""


s1 + s2

"""
Explanation: Si ahora realizamos una operación entre ambas Series, donde es posible realizar la operación se realiza y en los índices donde no es posible se representan pero no desaparecen ni nos proporciona un error:
End of explanation
"""


wind['wspd_std']

"""
Explanation: Volvemos a la indexación (reordenamos lo visto hasta ahora)
Una de las características básicas de pandas es el etiquetado de filas y de columnas, ello provoca que la indexación sea más compleja que con numpy. Hemos de distinguir entre:

selección por etiqueta
selección por posición (numpy)

La indexación en Series es más simple puesto que las etiquetas siempre se refieren a los índices ya que la columna es única. Como hemos ido viendo anteriormente de forma un poco difusa, para un DataFrame, la indexación básica selecciona las columnas.
Para elegir una sola columna, como ya hemos visto anteriormente:
End of explanation
"""


wind[['wspd', 'wspd_std']]

"""
Explanation: O, podemos elegir varias columnas:
End of explanation
"""


wind['2015/01/01 00:00':'2015/01/01 02:00']

"""
Explanation: Pero mediante una 'rebanada' (slicing) accedemos a los índices:
End of explanation
"""


wind['wspd':'wdir']

wind[['wspd':'wdir']]

"""
Explanation: Por lo que esto daría error
End of explanation
"""


wind.loc['2013-09-04 00:00:00':'2013-09-04 00:20:00', 'wspd':'wspd_max']

wind.iloc[0:3, 0:2] # similar a indexación con numpy arrays wind.values[0:3, 0:2]

wind.ix[0:3, 'wspd':'wspd_max']

"""
Explanation: Vaya lío, ¿no?
Indexación avanzada
Para realizar una indexación más avanzada y menos ambigua disponemos de una serie de métodos:


loc: está pensado para basar nuestra indexación en las etiquetas (aunque puede aceptar arrays booleanos).


iloc: esta opción se basa en usar las posiciones mediante enteros (como si fuera un numpy array).


ix: soporta combinar las dos anteriores.


Estos métodos también están disponibles en las Series pero no serían necesarios ya que la indexación en las Series no debe resultar ambigua.
Veamos como funcionan estos métodos en un DataFrame...
Seleccionamos los primeros tres elementos de las dos primeras columnas ('wspd', 'wspd_max'):
End of explanation
"""


wind[0:3][['wspd', 'wspd_max']]

wind[['wspd', 'wspd_max']][0:3]

"""
Explanation: Una cuarta forma que no hemos visto hasta ahora de forma deliberada:
End of explanation
"""


wind.between_time('00:00', '00:30').head(20)

# También funciona en series:
wind['wspd'].between_time('00:00', '00:30').head(20)

"""
Explanation: Practiquemos un poco todo esto


Devolved todos los datos de enero de 2014


Calculad la velocidad media de febrero de 2014


Usad el método query para obtener todas las velocidades que provienen del norte (en un rango de $\pm$ 10 º considerando el norte orientado a 0º) y cuya velocidad sea superior a 10 m/s


Lo mismo que el punto anterior pero usando la notación booleana típica de numpy


Todo lo anterior lo podéis hacer usando loc, iloc y/o ix. Practicad con las tres posibilidades.


Una última curiosidad útil si trabajamos con series temporales
Las estructuras de datos de pandas disponen de un método para seleccionar entre horas:
End of explanation
"""


