<jupyter_start><jupyter_text>Trabajando con series temporales Pandas se desarrolló con objetivo del modelado financiero, por lo que, como era de esperar, contiene un conjunto bastante extenso de herramientas para trabajar con fechas, horas y datos indexados por tiempo.Los datos de fecha y hora vienen tienen más de una representación:- **Marcas temporales** hacen referencia a momentos particulares en el tiempo (por ejemplo, July 4th, 2015 at 7:00am).- **Intervalos de tiempo** y **periodos** hacen referencia a un período de tiempo entre dos puntos determinados; por ejemplo, el año 2015. Los *periods* generalmente hacen referencia a un caso especial de intervalos de tiempo en los que cada intervalo tiene una duración uniforme y no se superpone (por ejemplo, períodos de 24 horas que comprenden días).- **Deltas temporales** o **duraciones** hacen referencia a un período de tiempo exacto (por ejemplo, una duración de 22.56 segundos).En esta sección, veremos cómo trabajar con cada uno de estos tipos de datos de fecha/hora en Pandas.Esto no será una guía completa de las herramientas de series de tiempo disponibles en Python o Pandas, sino mñas bien una descripción general de cómo podemos abordar el trabajo con series temporales.Comenzaremos con las herramientas para lidiar con fechas y horas en Python, antes de pasar más específicamente a las herramientas proporcionadas por Pandas.Después de enumerar algunos recursos más profundos, veremos algunos ejemplos breves de cómo trabajar con datos temporales en Pandas.  Fechas y horas en PandasEl mundo de Python tiene varias representaciones disponibles de fechas, horas, deltas e intervalos de tiempo.Si bien las herramientas de series temporales proporcionadas por Pandas tienden a ser las más útiles para las aplicaciones de ciencia de datos, es útil ver su relación con otros paquetes utilizados en Python.  Fechas y horas nativas de Python: ``datetime`` y ``dateutil``Los objetos básicos de Python para trabajar con fechas y horas residen en el módulo built-in ``datetime``.Junto con el módulo de terceros ``dateutil``, podemos usarlos para realizar fácilmente una serie de funciones muy útiles con fechas y horas.Por ejemplo, podemos crear manualmente una fecha usando el tipo ``datetime``:<jupyter_code>from datetime import datetime
datetime(year=2015, month=7, day=4)<jupyter_output><empty_output><jupyter_text>O, usando el módulo ``dateutil``, puedes convertir a fecha desde strings con varios formatos:<jupyter_code>from dateutil import parser
date = parser.parse("4th of July, 2015")
date<jupyter_output><empty_output><jupyter_text>Pero puede inferir muchos más:<jupyter_code>print(parser.parse("2015/10/12"))
print(parser.parse("2015-10-12"))
# Ojo, por defecto, en este formato interpreta mes-día-año
print(parser.parse("12-10-2015"))<jupyter_output>2015-10-12 00:00:00
2015-10-12 00:00:00
2015-12-10 00:00:00<jupyter_text>Incluso podría sacar el día de hoy, ahora mismo:<jupyter_code>date = datetime.now()
date<jupyter_output><empty_output><jupyter_text>Una vez que tenemos el objeto ``datetime``, podemos hacer cosas como imprimir el día de la semana:<jupyter_code>date.strftime('%A')<jupyter_output><empty_output><jupyter_text>EJERCICIOInterpreta las siguientes fechas y obtén qué día de la semana fue o va a ser (algunas las tendrás que interpretar tú y otras directamente):1. "2020-09-15"2. "12th October, 1492"3. 20 de Enero de 19994. 7 de Marzo de 20775. "1512/02/01"6. "2021-05-22"<jupyter_code>print(parser.parse("2020-09-15"))
print(parser.parse("12th October, 1492"))
print(parser.parse("1999-01-20"))
print(parser.parse("2077-03-07"))
print(parser.parse("1512/02/01"))
print(parser.parse("2021-05-22"))<jupyter_output>2020-09-15 00:00:00
1492-10-12 00:00:00
1999-01-20 00:00:00
2077-03-07 00:00:00
1512-02-01 00:00:00
2021-05-22 00:00:00<jupyter_text>En la última línea, hemos usado uno de los códigos de formato de cadena estándar para imprimir fechas (``"% A"``), sobre el cual podemos leer en la [sección de strftime](https://docs.python.org/3/library/datetime.htmlstrftime-and-strptime-behavior) de la [documentación de Python](https://docs.python.org/3/library/datetime.html).La documentación de otras utilidades de fecha se puede encontrar en la [documentación en línea de dateutil](http://labix.org/python-dateutil).Un paquete relacionado a tener en cuenta es [``pytz``](http://pytz.sourceforge.net/), que contiene herramientas para trabajar con zonas horarias. Pero no solo podemos hacer esto, sino que también podemos sumar y restar deltas temporales. Para ello, nos ayudaremos de ``relativedelta``, al cual le pasaremos los días, meses, años... que queramos utilizar en nuestra operación temporal.[Aquí](http://labix.org/python-dateutilhead-ba5ffd4df8111d1b83fc194b97ebecf837add454) puedes consultar más sobre este objeto. A continuación, algunos ejemplos:<jupyter_code>from dateutil.relativedelta import relativedelta

# Restamos 8 días al 15 de enero de 2024:
nueva_fecha = datetime(year=2024, month=1, day=15) - relativedelta(days=8)
print(nueva_fecha)

# Restamos 13 meses al 20 de mayo de 1970:
nueva_fecha2 = datetime(year=1970, month=5, day=20) - relativedelta(months=13)
print(nueva_fecha2)

# Pero podemos juntar más cosas en una sola sentencia, y también funciona con horas:
# Restamos 1 año, 48 días y 53 minutos al 20 de mayo de 1970 a las 20:59:00:
nueva_fecha3 = datetime(year=1970, month=5, day=20, hour=20, minute=59, second=0) - relativedelta(years=1, days=48, minutes=53)
print(nueva_fecha3)<jupyter_output>2024-01-07 00:00:00
1969-04-20 00:00:00
1969-04-02 20:06:00<jupyter_text>EJERCICIOUtiliza las 4 primeras fechas del último ejercicio y calcula:1. Resta 24 días2. Suma 5 meses3. Suma 2 días y resta 4 meses4. Suma 1 año y 2 díasFinalmente, utiliza la fecha y hora actual para calcular:1. Mes pasado2. Año pasado3. Hace 2 horas Realmente, lo que hace ``relativedelta`` es crear un interfaz de manejo más sencillo sobre datetime (con el que podríamos trabajar directamente importando ``timedelta`` de ``datetime``), ya que, por defecto, datetime trabaja con la diferencia de fechas con días y segundos. Con ``relativedelta`` podemos utilizar meses y años para realizar operaciones con fechas.Por ello, si realizamos operaciones con funciones, obtenemos un objeto ``datetime.timedelta``:<jupyter_code>a = datetime.now()
b = datetime.now() - relativedelta(months=7, days=7, hours=7)

b - a<jupyter_output><empty_output><jupyter_text>Por ello, mejor que trabajemos con ``relativedelta``, sin embargo, también podríamos trabajar con diferencias de fechas directamente, tal como hemos visto en el paso anterior. Por ejemplo, si queremos saber cuánto tiempo llevamos de clase:<jupyter_code>comienzo = datetime(year=2020, month=12, day=17, hour=18, minute=5)
actual = datetime.now()

tiempo_de_clase = actual - comienzo
print(tiempo_de_clase)<jupyter_output>-1 day, 4:28:32.426621<jupyter_text>O podemos jugar con intervalos de tiempo en base a las fechas. Por ejemplo, si sabemos los días de comienzo y final de un evento que ha sucedido el año pasado, y queremos saber hasta cuándo durará el año que viene, suponiendo que lo han movido por culpa de la pandemia, podríamos hacer lo siguiente:<jupyter_code>comienzo_evento_pasado = datetime(year=2019, month=5, day=1)
fin_evento_pasado = datetime(year=2019, month=6, day=12)

comienzo_evento_next = datetime(year=2021, month=7, day=29)

fin_evento_next = comienzo_evento_next + (fin_evento_pasado - comienzo_evento_pasado)

print(fin_evento_next)<jupyter_output>2021-09-09 00:00:00<jupyter_text>El poder de ``datetime`` y ``dateutil`` radica en su flexibilidad y sintaxis sencilla: podemos usar estos objetos y sus métodos integrados para realizar fácilmente casi cualquier operación que nos pueda interesar.Donde fallan es cuando queremos trabajar con grandes conjuntos de fechas y horas:Así como las listas de variables numéricas de Python son subóptimas en comparación con los arrays numéricos de NumPy, las listas de objetos de fecha y hora de Python son subóptimas en comparación con los arrays tipados de fechas.  Arrays tipados de fechas: ``datetime64`` de NumpyLas debilidades del formato de fecha y hora de Python inspiraron al equipo de NumPy a agregar un conjunto de tipos de datos de series temporales nativos a su propia librería.El dtype ``datetime64`` codifica las fechas como enteros de 64 bits y, por lo tanto, permite que losa rrays de fechas se representen de manera muy compacta.El ``datetime64`` requiere un formato de entrada muy específico:<jupyter_code>import numpy as np
date = np.array('2015-07-04', dtype=np.datetime64)
date<jupyter_output><empty_output><jupyter_text>Sin embargo, una vez que tengamos esta fecha formateada, podemos realizar rápidamente operaciones vectorizadas con ella:<jupyter_code>date + np.arange(12) # suma días<jupyter_output><empty_output><jupyter_text>Gracias al tipado uniforme en los arrays de NumPy ``datetime64``, este tipo de operación se puede realizar mucho más rápido que si estuviéramos trabajando directamente con los objetos ``datetime`` de Python, especialmente a medida que los arrays se hacen más grandes.Un detalle de los objetos ``datetime64`` y ``timedelta64`` es que están construidos en una unidad de tiempo fundamental.Dado que el objeto ``datetime64`` está limitado a una precisión de 64 bits, el rango de tiempos codificables es $2^{64}$ veces esta unidad fundamental.En otras palabras, ``datetime64`` impone un trade-off entre la resolución de tiempo y el período de tiempo máximo.Por ejemplo, si deseas una resolución de tiempo de un nanosegundo, solo tiene suficiente información para codificar un rango de $2^{64}$ nanosegundos, que es algo menos de 600 años.NumPy inferirá la unidad deseada a partir de la entrada; por ejemplo, aquí hay una fecha a nivel de día:<jupyter_code>np.datetime64('2015-07-04') # guarda hasta el día<jupyter_output><empty_output><jupyter_text>Y aquí otra a nivel de minutos:<jupyter_code>np.datetime64('2015-07-04 12:00') # guarda hasta los minutos<jupyter_output><empty_output><jupyter_text>Observa que la zona horaria se establece automáticamente como la hora local del ordenador que ejecuta el código.Puedes forzar cualquier unidad fundamental utilizando uno de los muchos códigos de formato; por ejemplo, aquí forzaremos un tiempo basado en nanosegundos:<jupyter_code>np.datetime64('2015-07-04 12:59:59.50', 'ns') # fuerzo que se guarde en nanosegundos

# la precisión de la variable datetime64 depende de hasta dónde se guarde el detalle de la fecha
# a más detalle, menos periodo de tiempo abarcado<jupyter_output><empty_output><jupyter_text>La siguiente tabla, extraída de la [docuemntación de datetime64 de NumPy](http://docs.scipy.org/doc/numpy/reference/arrays.datetime.html), enumera los códigos de formato disponibles junto con los períodos de tiempo relativos y absolutos que puede codificar: |Code    | Meaning     | Time span (relative) | Time span (absolute)   ||--------|-------------|----------------------|------------------------|| ``Y``  | Year	       | ± 9.2e18 years       | [9.2e18 BC, 9.2e18 AD] || ``M``  | Month       | ± 7.6e17 years       | [7.6e17 BC, 7.6e17 AD] || ``W``  | Week	       | ± 1.7e17 years       | [1.7e17 BC, 1.7e17 AD] || ``D``  | Day         | ± 2.5e16 years       | [2.5e16 BC, 2.5e16 AD] || ``h``  | Hour        | ± 1.0e15 years       | [1.0e15 BC, 1.0e15 AD] || ``m``  | Minute      | ± 1.7e13 years       | [1.7e13 BC, 1.7e13 AD] || ``s``  | Second      | ± 2.9e12 years       | [ 2.9e9 BC, 2.9e9 AD]  || ``ms`` | Millisecond | ± 2.9e9 years        | [ 2.9e6 BC, 2.9e6 AD]  || ``us`` | Microsecond | ± 2.9e6 years        | [290301 BC, 294241 AD] || ``ns`` | Nanosecond  | ± 292 years          | [ 1678 AD, 2262 AD]    || ``ps`` | Picosecond  | ± 106 days           | [ 1969 AD, 1970 AD]    || ``fs`` | Femtosecond | ± 2.6 hours          | [ 1969 AD, 1970 AD]    || ``as`` | Attosecond  | ± 9.2 seconds        | [ 1969 AD, 1970 AD]    | Para los tipos de datos que vemos en el mundo real, el valor predeterminado es ``datetime64[ns]``, ya que puede codificar un rango útil de fechas con una precisión adecuada.Finalmente, notaremos que si bien el tipo de datos ``datetime64`` aborda algunas de las deficiencias del tipo incorporado de Python ``datetime``, carece de muchos de los métodos y funciones convenientes proporcionados por ``datetime`` y ``dateutil``.Puede encontrar más información en la [documentación del datetime64 de NumPy](http://docs.scipy.org/doc/numpy/reference/arrays.datetime.html).  EJERCICIOInterpreta las siguientes fechas como ``datetime64`` según el periodo relativo que indique o que mejor se adapte:1. "2020-09-15 00:00"2. 12th October, 1492 (al nanosegundo)3. 20 de Enero de 1999 a las 15:24:104. 7 de Marzo de 2077 01:01:01.000000015. "1512/02/01" a las 23:006. "1512/02/01 23:30:10.00000034"7. "1512/02/01 23:30:10.00000034" como segundos8. "2021-05-22" como microsegundos¿Has observado algo raro? ¿Entiendes por qué pasa?  Fechas y horas en Pandas: lo mejor de ambos mundosPandas se basa en todas las herramientas que acabamos de comentar para proporcionar un objeto ``Timestamp``, que combina la facilidad de uso de ``datetime`` y ``dateutil`` con el almacenamiento eficiente y la interfaz vectorizada de ``numpy.datetime64``.A partir de un grupo de estos objetos de ``Timestamp``, Pandas puede construir un ``DatetimeIndex``, que se puede usar para indexar datos en un ``Series`` o ``DataFrame``; veremos muchos ejemplos de esto a continuación.Por ejemplo, podemos usar las herramientas de Pandas para repetir la demostración de arriba.Podemos analizar una fecha de cadena con formato flexible y usar códigos de formato para generar el día de la semana:<jupyter_code>import pandas as pd
date = pd.to_datetime("4th of July, 2015")
date
date.strftime('%A')<jupyter_output><empty_output><jupyter_text>Si estamos con ``Timestamp`` y no con ``DatetimeIndex`` (es decir, que estamos con una fecha concreta y no con conjuntos de fechas), podemos utilizar el relativedelta:<jupyter_code>date - relativedelta(months=10)<jupyter_output><empty_output><jupyter_text>Con ello, podríamos hacer operaciones de conjuntos de datos basándonos en algo parecido a lo que hacíamos con las listas. Sin embargo, existe una forma de hacer operacinoes temporales al estilo de NumPy, y es con la función ``pd.to_timedelta``, tal como se ve a continuación:<jupyter_code>date + pd.to_timedelta(np.arange(12), 'd') # suma 12 días<jupyter_output><empty_output><jupyter_text>También podemos utilizar diferentes frecuencias, no solo diario, tal como indica esta tabla:| Code   | Description         ||--------|---------------------|| ``d``  | Calendar day        || ``w``  | Weekly              || ``h``  | Hours               || ``T``  | Minutes             || ``s``  | Seconds             || ``l``  | Milliseonds         || ``u``  | Microseconds        || ``n``  | nanoseconds         |  EJERCICIO1. Ayudándote de la tabla anterior, crea una función que tome como parámetro: - fecha: un string con el formato de fecha "YYYY-MM-DD" (año-mes-día) - valor: un entero con el que se hará una operación sobre la fecha recibida - unidad: un string que valdrá cualquiera de los siguietnes strings: "microsegundos", "milisegundos", "segundos", "minutos", "horas", "días" o "semanas" - operación": string que sea "+" o "-" La función deberá recoger un string con formato de fecha que debrá convertirse a tipo ``Timestamp``, tal como hemos visto con la función de Pandas. Después, se le sumará o restará "valor" con frecuencia "unidad", en función del parámetro "operación". Habrá que traducir el campo "unidad" a frecuencias entendibles por la función de Pandas.2. Modifica la función para aceptar una lista en el parámetro "valor", y que devuelva el DatetimeIndex correspondiente de hacer la operación con ese conjunto de fechas. Si quieres, puedes cambiar el nombre del parámetro a "valores" en lugar de "valor".  EJERCICIOHaz las siguientes operaciones y devuelve el día de la semana que fue esa fecha:1. "2020-09-15" + 15 días2. 20 de Enero de 1999 a las 15:24:10 + 2 minutos3. 7 de Marzo de 2077 01:01:01.00000001 - 1 año4. "1512/02/01" a las 23:00 + 5 nanosegundos5. "1984-10-01" - 370 semanas En la siguiente sección, veremos más de cerca la manipulación de datos de series de tiempo con las herramientas proporcionadas por Pandas.  Series temporales de Pandas: Indexando por tiempoDonde las herramientas de series temporales de Pandas se vuelven realmente útiles es cuando comenzamos a indexar datos por marcas temporales.Por ejemplo, podemos construir un objeto ``Series`` que tenga datos indexados por tiempo del siguiente modo:<jupyter_code>index = pd.DatetimeIndex(['2014-07-04', '2014-08-04',
                          '2015-07-04', '2015-08-04'])
data = pd.Series([0, 1, 2, 3], index=index)
data<jupyter_output><empty_output><jupyter_text>Ahora que tenemos estos datos en una ``Series``, podemos hacer uso de cualquiera de los patrones de indexación de las ``Series`` que discutimos en las secciones anteriores, pasando valores que se puedan convertir en fechas:<jupyter_code>data['2014-07-04':'2015-07-04']<jupyter_output><empty_output><jupyter_text>Hay operaciones adicionales de indexación especiales exclusivas de las fechas, como pasar un año para obtener una porción de todos los datos de ese año:<jupyter_code>data['2015']<jupyter_output><empty_output><jupyter_text>Más adelante, veremos ejemplos adicionales de la conveniencia de las fechas como índices.Pero antes, es interesante ver las estructuras de datos de series de tiempo disponibles.  Estructuras de datos de series temporales en PandasA continuación, presentaremos las estructuras de datos fundamentales de Pandas para trabajar con datos de series temporales:- Para *marcas temporales*, Pandas proporciona el tipo ``Timestamp``. Como se mencionó anteriormente, es esencialmente un reemplazo del ``datetime`` nativo de Python, pero se basa en el tipo de datos ``numpy.datetime64`` más eficiente. La estructura de índice asociada es `` DatetimeIndex ''.- Para *periodos de tiempo*, Pandas proporciona el tipo ``Period``. Esto codifica un intervalo de frecuencia fija basado en ``numpy.datetime64``. La estructura de índice asociada es ``PeriodIndex``.- Para *deltas de tiempo* o *duraciones*, Pandas proporciona el tipo ``Timedelta``. ``Timedelta`` es un reemplazo más eficiente para el tipo nativo de Python ``datetime.timedelta`` y se basa en ``numpy.timedelta64``. La estructura de índice asociada es ``TimedeltaIndex``. Lo más importante de estos objetos de fecha/hora son los objetos ``Timestamp`` y ``DatetimeIndex``.Si bien estos objetos se pueden invocar directamente instanciándolos desde su clase, es más común usar la función ``pd.to_datetime()``, que puede analizar una amplia variedad de formatos e inferirlos automáticamente.Pasar una sola fecha a ``pd.to_datetime()`` devuelve un ``Timestamp``; pasar una serie de fechas por defecto devuelve un ``DatetimeIndex``:<jupyter_code>from datetime import datetime 
dates = pd.to_datetime([datetime(2015, 7, 3), '4th of July, 2015',
                       '2015-Jul-6', '07-07-2015', '20150708'])
dates

# año mes día<jupyter_output><empty_output><jupyter_text>Cualquier ``DatetimeIndex`` puede ser convertido a ``PeriodIndex`` con la función ``to_period()`` añadiendo el código de la frecuencia. En este caso, usaremos ``'D'`` para indicar frecuencia diaria:<jupyter_code>dates.to_period('D')<jupyter_output><empty_output><jupyter_text>Fíjate que, si tenemos fechas diarias, utilizar una frecuencia no acorde con su distribución, como puede ser mensual, produce el mismo resultado si repetimos fechas del mismo mes:<jupyter_code>dates.to_period('M')<jupyter_output><empty_output><jupyter_text>Los ``TimedeltaIndex`` aparecen, por ejemplo, cuando se restan dos fechas:<jupyter_code>dates - dates[0]<jupyter_output><empty_output><jupyter_text>EJERCICIOSAhora que ya hemos aprendido un poco más de esto, vamos a hacer ejercicios que realmente sirvan para algo:1. Lee el dataset de reseñas de yelp! (../../data/yelp_academic_dataset_review.csv)2. Quédate solo con las columnas "stars" y "date"3. Convierte la columna "date" en formato fecha4. Quédate con 1 registro por día y haz la media de stars5. ¿Cuál es la mayor diferencia de días sin registros?6. Vuelve al df original y crea un DatetimeIndex a partir de la columna 'date'. (Puede que no valga directamente con usar el ``Series``, sino que haya que tener los datos en cierto tipo de variable, mira el ejemplo)7. Créate, a partir de esa variable DatetimeIndex, una nueva variable PeriodIndex con frecuencia mensual, y asígnala a una nueva columna "date_M"8. Agrupa por esta variable y obtén el máximo, el mínimo y el total de "stars" por mes. ¿Qué mes ha sido el que más reseñas ha recibido?  Secuencias temporales: ``pd.date_range()``Para crer secuencias temporales, existen otros métodos más convenientes, para los que Pandas ofrece las siguientes funciones: ``pd.date_range()`` para marcas de tiempo, ``pd.period_range ()`` para períodos y ``pd.timedelta_range()`` para deltas de tiempo.Hemos visto que tanto ``range()`` de Python como ``np.arange()`` de NumPy generan una secuencia a partirde un punto de inicio, un punto final y un tamaño de paso (opcional).De manera similar, ``pd.date_range()`` acepta una fecha de inicio, una fecha de final y un código de frecuencia (opcional) para crear una secuencia regular de fechas.Por defecto, la frecuencia es de un día:<jupyter_code>pd.date_range('2015-07-03', '2015-07-10')<jupyter_output><empty_output><jupyter_text>Alternativamente, el rango de fechas se puede especificar con un punto de inicio y un número de períodos, en lugar de un punto final:<jupyter_code>pd.date_range('2015-07-03', periods=8) # 8 días<jupyter_output><empty_output><jupyter_text>El espaciado se puede modificar alterando el argumento ``freq``, que por defecto es ``D`` (diario).Por ejemplo, si queremos crearnos un rango de marcas de tiempo por hora:<jupyter_code>pd.date_range('2015-07-03', periods=8, freq='H') # 8 horas<jupyter_output><empty_output><jupyter_text>Para crear secuencias regulares con valores ``Period`` o ``Timedelta``, podemos utilizar las funciones ``pd.period_range()`` y ``pd.timedelta_range()``, como se muestra en los siguientes ejemplos:<jupyter_code># Variando periodos de manera mensual:
pd.period_range('2015-07', periods=8, freq='M') # 8 meses, de tipo periodo<jupyter_output><empty_output><jupyter_text>And a sequence of durations increasing by an hour:<jupyter_code># Variando timedeltas de manera horaria:
pd.timedelta_range(0, periods=10, freq='H')<jupyter_output><empty_output><jupyter_text>Todos estos métodos requieren una comprensión de los códigos de frecuencia de Pandas, que resumiremos en la siguiente sección.  Frecuencias y OffsetsLos conceptos de frecuencia y offset (temporal) son básicos para entender estas herramientas de series de tiempo de Pandas que estamos viendo.Así como hemos visto los códigos ``D`` (día) y ``H`` (hora), podemos usar dichos códigos para especificar cualquier espaciado de frecuencia deseado.La siguiente tabla resume los principales códigos disponibles: | Code   | Description         | Code   | Description          ||--------|---------------------|--------|----------------------|| ``D``  | Calendar day        | ``B``  | Business day         || ``W``  | Weekly              |        |                      || ``M``  | Month end           | ``BM`` | Business month end   || ``Q``  | Quarter end         | ``BQ`` | Business quarter end || ``A``  | Year end            | ``BA`` | Business year end    || ``H``  | Hours               | ``BH`` | Business hours       || ``T``  | Minutes             |        |                      || ``S``  | Seconds             |        |                      || ``L``  | Milliseonds         |        |                      || ``U``  | Microseconds        |        |                      || ``N``  | nanoseconds         |        |                      |<jupyter_code># Variando periodos:
pd.period_range('2015-07-01', periods=8, freq='M')
# Variando marcas de tiempo:
pd.date_range("2020-1", periods=250, freq='BQ')<jupyter_output><empty_output><jupyter_text>Las frecuencias mensuales, trimestrales y anuales se marcan todas al final del período especificado.Al agregar el sufijo ``S`` a cualquiera de estos, hará que se devuelva la primera fecha de cada uno de ellos: | Code    | Description            || Code    | Description            ||---------|------------------------||---------|------------------------|| ``MS``  | Month start            ||``BMS``  | Business month start   || ``QS``  | Quarter start          ||``BQS``  | Business quarter start || ``AS``  | Year start             ||``BAS``  | Business year start    |<jupyter_code># Sacando el primer día laborable del año a lo largo de los próximos 20 años:
pd.date_range("2020-1", periods=20, freq='BAS')<jupyter_output><empty_output><jupyter_text>Además, podemos cambiar el mes utilizado para marcar cualquier código trimestral o anual agregando un código de mes de tres letras como sufijo:- ``Q-JAN``, ``BQ-FEB``, ``QS-MAR``, ``BQS-APR``, etc.- ``A-JAN``, ``BA-FEB``, ``AS-MAR``, ``BAS-APR``, etc.Del mismo modo, el día de la semana que se utiliza de referencia se puede modificar agregando un código de día de la semana de tres letras:- ``W-SUN``, ``W-MON``, ``W-TUE``, ``W-WED``, etc.Además de esto, los códigos se pueden combinar con números para especificar otras frecuencias.Por ejemplo, para una frecuencia de 2 horas 30 minutos, podemos combinar los códigos de hora (``H``) y minutos (``T``) de la siguiente manera:<jupyter_code>pd.timedelta_range(0, periods=9, freq="2H30T") # tengo 9 timedelta separados 2H30T<jupyter_output><empty_output><jupyter_text>Todos estos códigos hacen referencia a instancias específicas de offsets de series temporales de Pandas, que se pueden encontrar en el módulo ``pd.tseries.offsets``.Por ejemplo, podemos crear un offset de día laborable directamente de la siguiente manera:<jupyter_code>from pandas.tseries.offsets import BDay
pd.date_range('2015-07-01', periods=5, freq=BDay()) # me devuelve 5 business days seguidos<jupyter_output><empty_output><jupyter_text>Si quieres profundizar sobre las frecuencias y los offsets, puedes acceder a la documentación de Pandas en la sección [DateOffset objects](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.htmldateoffset-objects).  Remuestreo, desplazamiento y ventaneoLa capacidad de utilizar fechas y horas como índices para organizar y acceder a los datos de forma intuitiva es una pieza importante de las herramientas de series temporales de Pandas.Los beneficios de los datos indexados en general (alineación automática durante las operaciones, slicing y acceso intuitivo de datos, etc.) siguen aplicando, y Pandas proporciona varias operaciones adicionales específicas de series de tiempo para trabajar con ellos.Echaremos un vistazo a algunos de ellos aquí, usando algunos datos de precios de acciones, por ejemplo.Debido a que Pandas se desarrolló principalmente en un contexto financiero, incluye algunas herramientas muy específicas para datos financieros.Por ejemplo, el paquete adjunto ``pandas-datareader`` (instalable a través de `` conda install pandas-datareader ``), sabe cómo importar datos financieros de varias fuentes disponibles, incluidas Yahoo Finance, Google Finance y otras.Aquí cargaremos el historial de precios de cierre de Google:<jupyter_code>import pandas as pd 

goog = pd.read_csv('GOOG.csv')
goog.info()
goog['Date'] =  pd.to_datetime(goog['Date'])

goog.set_index('Date', inplace=True)
goog<jupyter_output><empty_output><jupyter_text>Para simplificar, usaremos solo el precio de cierre:<jupyter_code>goog = goog['Close']
goog<jupyter_output><empty_output><jupyter_text>Podemos visualizar esto usando el método ``plot()``, para lo que teníamos que importar y realizar las configuraciones previas de Matplotlib:<jupyter_code>%matplotlib inline
import matplotlib.pyplot as plt
goog.plot(figsize=(12,9));<jupyter_output><empty_output><jupyter_text>Remuestreo y conversión de frecuenciasUna necesidad común para las series de datos temporales es el remuestreo a una frecuencia mayor o menor.Esto se puede hacer usando el método ``resample()`` o el método ``asfreq()``, que es más simple.La principal diferencia entre los dos es que ``resample()`` es fundamentalmente una *agregación de datos*, mientras que ``asfreq()`` es una *selección de datos*.Esto lo veremos mejor con un ejemplo, así que echando un vistazo al precio de cierre de Google, será interesante comparar lo que devuelven los dos cuando bajamos la muestra de los datos con ambos métodos.En este caso, vamos a muestrear los datos al final del año comercial:<jupyter_code>goog.plot(alpha=0.5, style='-',figsize=(12,9))
goog.resample('BA').mean().plot(style=':') # con una función de agregación, la media, BA es final de año
goog.asfreq('BA').plot(style='--'); # nos devolverá los valores de los puntos de fin de año (por lo que perderemos la info del resto de días)
plt.legend(['input', 'resample', 'asfreq'],
           loc='upper left');
goog.resample('BA').mean()
goog.asfreq('BA')<jupyter_output><empty_output><jupyter_text>Fíjate que, en cada punto, ``resample`` devuelve la media del año completo, mientras que ``asfreq`` devuelve el valor al final del año (perdiendo info del resto de días del año). Para hacer un muestreo a órdenes superiores, ``resample()`` y ``asfreq()`` son prácticamente equivalentes, aunque ``resample`` tiene muchas más opciones disponibles.En este caso, el valor predeterminado para ambos métodos es dejar vacíos los puntos muestreados hacia arriba, es decir, rellenados con valores NA.Al igual que con la función ``pd.fillna()``, ``asfreq()`` acepta un argumento de ``método`` para especificar cómo se imputan los valores.Ahora, vamos a volver a muestrear los datos de los días hábiles originales con una frecuencia diaria (es decir, incluidos los fines de semana):<jupyter_code>fig, ax = plt.subplots(2, sharex=True)
data = goog.iloc[:10] # 10 primeros elementos de la serie

data.asfreq('D').plot(ax=ax[0], marker='o') # primer eje, con escala diaria, no sale el fin de semana

data.asfreq('D', method='bfill').plot(ax=ax[1], style='-o')
data.asfreq('D', method='ffill').plot(ax=ax[1], style='--o')
ax[1].legend(["back-fill", "forward-fill"]);

# ffill: rellenamos hacia adelante los nulos (es decir, con el valor que había antes del nulo), backfill / bfill: rellenamos hacia atrás (valor siguiente)<jupyter_output><empty_output><jupyter_text>La gráfica de arriba representa los datos predeterminados: los días no hábiles se dejan como valores NA y no aparecen en el gráfico.La gráfica inferior muestra las diferencias entre las dos estrategias para llenar los nulos: hacia adelante (ffill) y llenado hacia atrás (bfill).  EJERCICIOS 1.Lee el fichero "daily-minimum-temperatures.csv" y remuestréalo para quedarte con los máximos de temperaturas por mes. Almacena este nuevo DataFrame resultante en una variable llamada ``df_temp``.Asegúrate de tener el campo temporal como índice antes de hacer cosas.  2.Ahora, remuestrea el DataFrame de la variable ``df_temp`` de nuevo como si fuera diario y rellena los nulos con el método que creas más conveniente:  Desplazamentos de tiempoOtra operación específica bastante común con las series temporales es el desplazamiento de datos en el tiempo.Pandas tiene dos métodos estrechamente relacionados para ello: ``shift()`` y ``tshift()``En resumen, la diferencia entre ellos es que ``shift()`` *cambia los datos*, mientras que ``tshift()`` *cambia el índice*.En ambos casos, el cambio se especifica en múltiplos de la frecuencia.A continuación, tenemos un ejemplo de ``shift()`` y ``tshift()`` con 900 días:<jupyter_code>fig, ax = plt.subplots(3, sharey=True, figsize=(10, 10))

# apply a frequency to the data
goog = goog.asfreq('D', method='pad') # pad es relleno

goog.plot(ax=ax[0])
goog.shift(900).plot(ax=ax[1])
goog.tshift(900).plot(ax=ax[2])

# legends and annotations
local_max = pd.to_datetime('2007-11-05')
offset = pd.Timedelta(900, 'D')

ax[0].legend(['input'], loc=2)
ax[0].get_xticklabels()[2].set(weight='heavy', color='red')
ax[0].axvline(local_max, alpha=0.3, color='red')

ax[1].legend(['shift(900)'], loc=2)
ax[1].get_xticklabels()[2].set(weight='heavy', color='red')
ax[1].axvline(local_max + offset, alpha=0.3, color='red')

ax[2].legend(['tshift(900)'], loc=2)
ax[2].get_xticklabels()[1].set(weight='heavy', color='red')
ax[2].axvline(local_max + offset, alpha=0.3, color='red');<jupyter_output><empty_output><jupyter_text>Como puedes observar, ``shift(900)`` mueve los datos 900 días, eliminando parte del final del gráfico (y dejando los valores NA en el otro extremo), mientras que ``tshift(900)`` desplaza los valores del índice en 900 días, es decir, desplazamos todo y no perdemos información.Un contexto común para este tipo de desplazamiento es calcular las diferencias a lo largo del tiempo. Por ejemplo, usamos valores desplazados para calcular el retorno de la inversión de un año para las acciones de Google a lo largo del curso del dataset:<jupyter_code>ROI = 100 * (goog.tshift(-365) / goog - 1)
ROI.plot()
plt.ylabel('% Return on Investment');<jupyter_output><empty_output><jupyter_text>Esto nos permite ver la tendencia general de las acciones de Google: hasta ahora, los momentos más rentables para invertir en Google han sido (como era de esperar, en retrospectiva) poco después de su salida a bolsa y en medio de la recesión de 2009.  Ventanas temporalesLas estadísticas temporales son un tercer tipo de operación específica de series temporales implementada por Pandas.Esto se puede lograr mediante el atributo ``rolling()`` de los objetos ``Series`` y ``DataFrame``, que devuelve una vista similar a la que vimos con la operación ``groupby``.Esta vista pone a disposición una serie de operaciones de agregación de forma predeterminada.Por ejemplo, podríamos calcular la media móvil centrada en un año y la desviación estándar de los precios de las acciones de Google:<jupyter_code>rolling = goog.rolling(365, center=False)

data = pd.DataFrame({'input': goog,
                     'one-year rolling_mean': rolling.mean(),
                     'one-year rolling_std': rolling.std()})
ax = data.plot(style=['-', '--', ':'], figsize=(12,9))
ax.lines[0].set_alpha(0.3)
# Si queremos pintarlo como la media y a los lados las desviaciones típicas, podríamos hacer algo así:

rolling = goog.rolling(365)

data = pd.DataFrame({'input': goog,
                     'one-year rolling_mean': rolling.mean(),
                     'one-year rolling_std_max': rolling.mean() + rolling.std(),
                     'one-year rolling_std_min': rolling.mean() - rolling.std()})
ax = data.plot(style=['-', '--', ':', ':'], figsize=(10,5))
ax.lines[0].set_alpha(0.3)
rolling<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10340974212034384, 'fraction_numerical': 0.03573065902578797, 'mean_word_length': 4.748805798056333, 'pattern_counts': {'":': 1, '<': 164, '<?xml version=': 0, '>': 164, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '42', 'dir': 'jupyter-structured-clean-dedup', 'id': '495494', 'license': 'MIT', 'n_tokens_mistral': 12435, 'n_tokens_neox': 11618, 'n_words': 4155, 'path': 'semana_14/dia_1/1_Series_Temporales.ipynb', 'repo_name': 'franciscocanon-thebridge/DS-part-time-sep2020', 'size': '34900'}