Este artículo es, en parte, una actualización de este otro artículo: “Evolución de la demanda eléctrica en los primeros días del confinamiento en España (visto con Python)“.
En ese artículo le dimos un vistazo rápido a cómo había sido la demanda de electricidad en los primeros días del confinamiento. En este nuevo artículo vamos a ver dos meses completos de datos de demanda eléctrica.
Hace poco vimos como hacer gifs animados:
Ahora, además, vamos a crear una animación, un gif animado, usando una nueva opción. Esta vez voy a usar una biblioteca que se integra perfectamente con Matplotlib y que se llama gif.
La primera parte del código está relacionada con la descarga de los datos de la API de red eléctrica. A continuación pongo lo que vamos a importar y la parte de la descarga:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
from urllib.request import urlopen import json from time import sleep import datetime as dt from typing import Optional, List import numpy as np import pandas as pd import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec import gif # https://github.com/maxhumber/gif plt.style.use("bmh") # Download data during the period 2020/02/15 - 2020/04/30 df20 = pd.DataFrame( index=pd.date_range( "2020-02-14T23:00:00", "2020-04-30T23:50:00", freq="10T", tz="utc", ), columns=[ "Demanda real", "Demanda programada", "Demanda prevista", ], ) t = dt.datetime(2020,2,15,0,0) _dt = dt.timedelta(hours=23, minutes=50) tf = dt.datetime(2020,5,1,0,0) while t < tf: print(t) try: url = ( 'https://apidatos.ree.es/es/datos/demanda/demanda-tiempo-real?' f'start_date={t.strftime("%Y-%m-%dT%H:%M")}' f'&end_date={(t + _dt).strftime("%Y-%m-%dT%H:%M")}' '&time_trunc=hour' ) data = json.loads(urlopen(url).read()) for entry in data["included"]: tmp = pd.DataFrame(entry["attributes"]["values"]) tmp.set_index( pd.to_datetime(tmp["datetime"], utc=True), inplace=True, ) col = entry["attributes"]["title"] df20.loc[tmp.index, col] = tmp["value"] t += dt.timedelta(days=1) except: sleep(3) # Download data during the period 2019/02/15 - 2019/04/30 df19 = pd.DataFrame( index=pd.date_range( "2019-02-14T23:00:00", "2019-04-30T23:50:00", freq="10T", tz="utc", ), columns=[ "Demanda real", "Demanda programada", "Demanda prevista", ], ) t = dt.datetime(2019,2,15,0,0) _dt = dt.timedelta(hours=23, minutes=50) tf = dt.datetime(2019,5,1,0,0) while t < tf: print(t) try: url = ( 'https://apidatos.ree.es/es/datos/demanda/demanda-tiempo-real?' f'start_date={t.strftime("%Y-%m-%dT%H:%M")}' f'&end_date={(t + _dt).strftime("%Y-%m-%dT%H:%M")}' '&time_trunc=hour' ) data = json.loads(urlopen(url).read()) for entry in data["included"]: tmp = pd.DataFrame(entry["attributes"]["values"]) tmp.set_index( pd.to_datetime(tmp["datetime"], utc=True), inplace=True, ) col = entry["attributes"]["title"] df19.loc[tmp.index, col] = tmp["value"] t += dt.timedelta(days=1) except: sleep(3) |
Creamos dos DataFrames
, uno con los datos de marzo y abril de 2020, df20
, y otro con los datos de marzo y abril de 2019, df19
.
Ahora comento la parte que involucra a la biblioteca gif
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
@gif.frame def create_frame( x: np.array, y1: np.array, y2: np.array, week1: bool, week2: bool, label1: Optional[str] = "2020", label2: Optional[str] = "2019", ) -> None: #fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(15, 8)) fig = plt.figure(figsize=(15, 7.5)) gs = GridSpec(1, 4, figure=fig) ax1 = fig.add_subplot(gs[0, 0:3]) ax2 = fig.add_subplot(gs[0, -1]) if week1: symbol_code1 = "ko" hatch1 = None else: symbol_code1 = "ks" hatch1 = "/" plt.rcParams.update({'hatch.color': 'r'}) if week2: symbol_code2 = "ro" hatch2 = None else: symbol_code2 = "rs" hatch2 = "/" plt.rcParams.update({'hatch.color': 'k'}) ax1.plot(x, y1, color="k", label=label1) ax1.plot(x, y2, color="r", label=label2) ax1.plot(x[-1], y1[-1], symbol_code1) ax1.plot(x[-1], y2[-1], symbol_code2) ax1.legend() ax2.bar([0], [np.sum(y1)], color="k", hatch=hatch1) ax2.bar([1], [np.sum(y2)], color="r", hatch=hatch2) ax2.set_xticks([0, 1]) ax2.set_xticklabels([2020, 2019]) plt.rcParams.update({'hatch.color': 'k'}) return None |
La función anterior se encargará de dibujar un fotograma del gif animado. Esa función está decorada con el decorador gif.frame
. En cada fotograma creamos dos gráficos, uno con la evolución de la demanda eléctrica cada 3 horas y el otro con la suma acumulada de los GW/h acumulados. Cuando las barras de la derecha presentan un patrón (líneas oblicuas) indica que ese día corresponde a fin de semana. Los distingo así porque los días de fin de semana la demanda es bastante más baja. En el 2019 hubo 18 días de fin de semana entre marzo y abril mientras que en 2020 hubo 17 días de fin de semana. Esto también afectará al resultado final.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
frames: List = [] steps1 = pd.date_range("2020-03-02", "2020-04-30", freq="D", tz="utc") steps2 = pd.date_range("2019-02-16", "2019-04-30", freq="D", tz="utc") for step1, step2 in zip(df20.index[::18], df19.index[::18]): print(step1, step2) tmp20 = df20.loc[df20.index <= step1, "Demanda real"] tmp19 = df19.loc[df19.index <= step2, "Demanda real"] x = tmp20.index.values y1 = tmp20.values y2 = tmp19.values week1 = True if tmp20.index.weekday[-1] < 5 else False week2 = True if tmp19.index.weekday[-1] < 5 else False frame = create_frame(x, y1, y2, week1, week2) frames.append(frame) # I repeat the last frame so the end lasts a little bit longer for i in range(20): frames.append(frame) gif.save(frames, "ree_evolution.gif", duration=100) |
Una vez que sabemos como crear cada fotograma hacemos un bucle donde vamos leyendo datos de los DataFrame
s con los datos del 2019 y los datos del 2020 y esos datos los metemos como entrada a la función. Cada paso del bucle lee 3 horas más de datos.
Añado un nuevo bucle para repetir el último fotograma 20 veces más y que así dé tiempo de ver el resultado final.
El gif animado que me queda ocupa alrededor de 100Mb y no lo voy a subir aquí. Lo que he subido aquí es un vídeo obtenido a partir del gif animado usando ffmpeg
. Desde la línea de comandos puedes hacer lo siguiente para convertir el gif animado a mp4:
1 |
>>> ffmpeg -i ree_evolution.gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" ree_evolution.mp4 |
El resultado final de la animación lo puedes ver aquí:
Por último, si quieres ver el valor numérico de la demanda acumulada podrías usar el siguiente código:
1 2 3 4 5 |
print( df20.dropna().values.sum(), df19.dropna().values.sum(), df20.dropna().values.sum() / df19.dropna().values.sum(), ) |
El resultado es que la demanda ha bajado alrededor del 10% con respecto a los mismos meses del año pasado. Esto es solo para hacerse una idea ya que influyen muchos factores que no voy a comentar (como el número de días de fin de semana incluidos).
El código completo lo tenéis aquí:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
from urllib.request import urlopen import json from time import sleep import datetime as dt from typing import Optional, List import numpy as np import pandas as pd import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec import gif # https://github.com/maxhumber/gif plt.style.use("bmh") # Download data during the period 2020/02/15 - 2020/04/30 df20 = pd.DataFrame( index=pd.date_range( "2020-02-14T23:00:00", "2020-04-30T23:50:00", freq="10T", tz="utc", ), columns=[ "Demanda real", "Demanda programada", "Demanda prevista", ], ) t = dt.datetime(2020,2,15,0,0) _dt = dt.timedelta(hours=23, minutes=50) tf = dt.datetime(2020,5,1,0,0) while t < tf: print(t) try: url = ( 'https://apidatos.ree.es/es/datos/demanda/demanda-tiempo-real?' f'start_date={t.strftime("%Y-%m-%dT%H:%M")}' f'&end_date={(t + _dt).strftime("%Y-%m-%dT%H:%M")}' '&time_trunc=hour' ) data = json.loads(urlopen(url).read()) for entry in data["included"]: tmp = pd.DataFrame(entry["attributes"]["values"]) tmp.set_index( pd.to_datetime(tmp["datetime"], utc=True), inplace=True, ) col = entry["attributes"]["title"] df20.loc[tmp.index, col] = tmp["value"] t += dt.timedelta(days=1) except: sleep(3) # Download data during the period 2019/02/15 - 2019/04/30 df19 = pd.DataFrame( index=pd.date_range( "2019-02-14T23:00:00", "2019-04-30T23:50:00", freq="10T", tz="utc", ), columns=[ "Demanda real", "Demanda programada", "Demanda prevista", ], ) t = dt.datetime(2019,2,15,0,0) _dt = dt.timedelta(hours=23, minutes=50) tf = dt.datetime(2019,5,1,0,0) while t < tf: print(t) try: url = ( 'https://apidatos.ree.es/es/datos/demanda/demanda-tiempo-real?' f'start_date={t.strftime("%Y-%m-%dT%H:%M")}' f'&end_date={(t + _dt).strftime("%Y-%m-%dT%H:%M")}' '&time_trunc=hour' ) data = json.loads(urlopen(url).read()) for entry in data["included"]: tmp = pd.DataFrame(entry["attributes"]["values"]) tmp.set_index( pd.to_datetime(tmp["datetime"], utc=True), inplace=True, ) col = entry["attributes"]["title"] df19.loc[tmp.index, col] = tmp["value"] t += dt.timedelta(days=1) except: sleep(3) @gif.frame def create_frame( x: np.array, y1: np.array, y2: np.array, week1: bool, week2: bool, label1: Optional[str] = "2020", label2: Optional[str] = "2019", ) -> None: #fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(15, 8)) fig = plt.figure(figsize=(15, 7.5)) gs = GridSpec(1, 4, figure=fig) ax1 = fig.add_subplot(gs[0, 0:3]) ax2 = fig.add_subplot(gs[0, -1]) if week1: symbol_code1 = "ko" hatch1 = None else: symbol_code1 = "ks" hatch1 = "/" plt.rcParams.update({'hatch.color': 'r'}) if week2: symbol_code2 = "ro" hatch2 = None else: symbol_code2 = "rs" hatch2 = "/" plt.rcParams.update({'hatch.color': 'k'}) ax1.plot(x, y1, color="k", label=label1) ax1.plot(x, y2, color="r", label=label2) ax1.plot(x[-1], y1[-1], symbol_code1) ax1.plot(x[-1], y2[-1], symbol_code2) ax1.legend() ax2.bar([0], [np.sum(y1)], color="k", hatch=hatch1) ax2.bar([1], [np.sum(y2)], color="r", hatch=hatch2) ax2.set_xticks([0, 1]) ax2.set_xticklabels([2020, 2019]) plt.rcParams.update({'hatch.color': 'k'}) return None frames: List = [] steps1 = pd.date_range("2020-03-02", "2020-04-30", freq="D", tz="utc") steps2 = pd.date_range("2019-02-16", "2019-04-30", freq="D", tz="utc") for step1, step2 in zip(df20.index[::18], df19.index[::18]): print(step1, step2) tmp20 = df20.loc[df20.index <= step1, "Demanda real"] tmp19 = df19.loc[df19.index <= step2, "Demanda real"] x = tmp20.index.values y1 = tmp20.values y2 = tmp19.values week1 = True if tmp20.index.weekday[-1] < 5 else False week2 = True if tmp19.index.weekday[-1] < 5 else False frame = create_frame(x, y1, y2, week1, week2) frames.append(frame) # I repeat the last frame so the end lasts a little bit longer for i in range(20): frames.append(frame) gif.save(frames, "ree_evolution.gif", duration=100) print( df20.dropna().values.sum(), df19.dropna().values.sum(), df20.dropna().values.sum() / df19.dropna().values.sum(), ) |
Y eso es todo. El COVID-19 nos está afectando de muchas formas. Hasta ahora no me ha interesado meterme en las más macabras así que aquí solo vemos como ha afectado a la demanda eléctrica y, además, tenéis a vuestra disposición una nueva opción para crear animaciones 🙂