Handbook for hydrological data time series management


Librerías usadas

>>> import pandas as pd 
>>> import numpy as np 
>>> from matplotlib import pyplot as plt

Lectura de datos

Los datos utilizados para el manejo de datos pluviométricos fueron descargados del visor de estaciones de la Autoridad Nacional del Agua. El proceso de lectura se realizará para formato .csv o formato .txt. En este caso utilizará un formato .txt. Dado que el archivo cuenta con datos de días y las horas en columnas separadas, se sobreescribirá la columna de fecha para unir ambas variables. Por último, esta se convertirá a formato datetime.

Code 

>>> data = pd.read_csv("precip.txt", sep="\t")
>>> data.FECHA = data.FECHA + " " + data.HORA
>>> data.FECHA = pd.to_datetime(data.FECHA, dayfirst=True)
>>> data.head()

Output

                FECHA   HORA  VALOR
0 1971-04-03 07:00:00  07:00    0.0
1 1971-04-03 19:00:00  19:00    0.0
2 1971-04-04 07:00:00  07:00    0.0
3 1971-04-04 19:00:00  19:00    0.0
4 1971-04-05 07:00:00  07:00    0.0

Exploración de datos

Cuando se tiene un conjunto de datos, se debe realizar una exploración para identificar posibles valores atípicos o cierto comportamiento en la información. La función .describe() permite obtener la  principal información estadísticas de una variable numérica; y para este caso es útil en la variable VALOR, que representa la precipitación.

Code 

>>> data.describe()

Output

              VALOR
count  32035.000000
mean       0.253164
std        3.190159
min        0.000000
25%        0.000000
50%        0.000000
75%        0.000000
max      165.100000

Una de las ventajas de haber convertido la variables data.FECHA a datatime es que se puede hacer operaciones con ellas, por ejemplo, identificar la fecha mínima, máxima o la diferencia entre ellas. 

Code & Output

>>> print ('Primera fecha: ', data.FECHA.min())
Primera fecha:  1971-04-03 07:00:00
>>> print('Última fecha: ', data.FECHA.max())
Última fecha:  2017-12-31 19:00:00
>>> period = data.FECHA.max() - data.FECHA.min()
>>> print('Período: ', period)
Período:  17074 days 12:00:00

La exploración de datos también involucra verificar la existencia de datos faltantes, para ello se ejecuta lo siguiente.

Code & Output

>>> print('Cantidad de datos faltantes : ', sum(data.VALOR.isnull()))
Cantidad de datos faltantes :  0

Evidentemente no hay datos faltantes; sin embargo, puede ser que en los datos haya saltos temporales que no se muestran en la data procesada por ANA. Entonces para ello se va a comparar los días presentes en data.FECHA con la cantidad de valores que deberían haber si el tiempo estuviese completo. Esta operación requiere conocer la fecha mínima, máxima, que fueron calculadas anteriormente, y el periodo, que en este caso son 12 horas. 

Code

>>> range_data = pd.date_range(str(data.FECHA.min()), str(data.FECHA.max()), freq='12H')
>>> range_data

Output

DatetimeIndex(['1971-04-03 07:00:00', '1971-04-03 19:00:00',
               '1971-04-04 07:00:00', '1971-04-04 19:00:00',
               '1971-04-05 07:00:00', '1971-04-05 19:00:00',
               '1971-04-06 07:00:00', '1971-04-06 19:00:00',
               '1971-04-07 07:00:00', '1971-04-07 19:00:00',
               ...
               '2017-12-27 07:00:00', '2017-12-27 19:00:00',
               '2017-12-28 07:00:00', '2017-12-28 19:00:00',
               '2017-12-29 07:00:00', '2017-12-29 19:00:00',
               '2017-12-30 07:00:00', '2017-12-30 19:00:00',
               '2017-12-31 07:00:00', '2017-12-31 19:00:00'],
              dtype='datetime64[ns]', length=34150, freq='12H')

Code & Output

>>> print('Debería haber ', len(range_data),' datos, pero hay', len(data), '\nHay ', len(range_data)-len(data), ' datos faltantes.')
Debería haber  34150  datos, pero hay 32035
Hay  2115  datos faltantes.
>>> print('Porcentaje de completitud de datos : ', round(len(data)/len(range_data)*100,1), '%')
Porcentaje de completitud de datos :  93.8 %

Entonces, se evidencia que aunque no había datos de precipitación faltantes en la data, el parámetro FECHA no contiene la información completa. Se observa un 93.8% de completitud de datos de fecha, mismo que debería someterse a diferentes pruebas para garantizar que sea fiable procesar la información. Ahora la pregunta es, ¿cómo se pueden conocer los datos de fecha faltantes? El proceso se realizará verificando si la información contenida en range_data está contenida en data.FECHA. En el caso que no, se almacenará en el array m_dates que representa las fechas faltantes (missing dates).

Code

>>> m_dates = np.array([])
>>> for i in range (len(range_data)):
...     if sum(range_data[i]==data.FECHA) == 0:
...         m_dates = np.append(m_dates, range_data[i])
... 
>>> print('Datos faltantes:\n', m_dates)

Output

Datos faltantes:
 [Timestamp('1975-06-01 07:00:00', freq='12H')
 Timestamp('1975-06-01 19:00:00', freq='12H')
 Timestamp('1975-06-02 07:00:00', freq='12H') ...
 Timestamp('2017-06-30 07:00:00', freq='12H')
 Timestamp('2017-06-30 19:00:00', freq='12H')
 Timestamp('2017-07-01 07:00:00', freq='12H')]

Para un mejor manejo de los datos puede crearse un data frame que almacene la información de los datos faltantes, para ello se propone el siguiente código, similar al anterior. 

Code

>>> m_Value = np.ones(len(range_data))
>>> for i in range (len(range_data)):
...     if sum(range_data[i]==data.FECHA) == 0:
...         m_Value[i] = 0
...
>>> m_data = pd.DataFrame()    
>>> m_data['Date'] = range_data
>>> m_data['Value'] = m_Value  
>>> m_data = m_data.set_index('Date')
>>> m_data.head()

Output

                     Value
Date
1971-04-03 07:00:00    1.0
1971-04-03 19:00:00    1.0
1971-04-04 07:00:00    1.0
1971-04-04 19:00:00    1.0
1971-04-05 07:00:00    1.0

Para este caso se ha asignado el valor de 1 a la fecha que sí se encuentra en la data original, y 0 a los que no se encuentran. Esto permitirá poder visualizar los datos en gráficos como el que se presenta a continuación. 

Code

>>> plt.subplot(211)
>>> m_bar = m_data.groupby(['Value']).Value.count() 
>>> m_bar.plot(kind='bar', color = ['red', 'green'])
>>> plt.xticks([0,1],['Missing', 'Present'])
>>> plt.title('Barplot Missing Dates')
>>> plt.ylabel('Amount of data')
>>>
>>> plt.subplot(212)
>>> plt.plot(m_data, color = 'black', marker = '|', linewidth = 0)
>>> plt.yticks([0,1],['Missing', 'Present'], rotation = 90)
>>> plt.title ("Time series - Missing data")
>>> plt.xlabel('Dates')
>>>
>>> plt.tight_layout()
>>> plt.show()

Output

Figura 1. Gráficos de datos faltantes.

Mediante gráficas se puede presentar información de manera resumida, y en este caso permiten observar la cantidad de datos faltantes y su disposición a lo largo del tiempo. Después de haber realizado la exploración de datos, debería someterse la data a una limpieza (data cleaning). Para esta oportunidad se va a omitir este proceso, asumiendo que los datos son representativos

Análisis de datos

Escalas temporales

Se puede analizar a diferentes escalas temporales según el tiempo de datos que tengamos. Para este caso se tiene una escala de dos datos por día, por lo que se podría analizar a escala diaria, mensual o anual, preferiblemente.

Primero se debe indexar los datos de FECHA para realizar el procesamiento sin errores. 

Code

>>> data = data.set_index('FECHA')

Posteriormente se realizará el remuestreo (resample) de los datos, el cual se maneja con la siguiente estructura en Python. 

file = data_frame.reample("Frecuencia").Operación()
  • Frecuencia: es la frecuencia a la cual se van a reordenar los datos, por ejemplo "d" es diario, "m" es mensual, y "y" es anual. Para establecer frecuencias diferentes, por ejemplo cada cinco meses, se puede especificar antes del código, "5m"
  • Operación: aquí se establece el tipo de operación correspondiente al grupo remuestreado, por ejemplo, para precipitaciones se utiliza la suma .sum(). También se puede obtener promedios como .mean(), máximos .max() y mínimos .min().  

Escala diaria

Code & Output

>>> daily_data = data.resample("d").sum()
>>> daily_data.head()

            VALOR
FECHA
1971-04-03    0.0
1971-04-04    0.0
1971-04-05    0.0
1971-04-06    1.4
1971-04-07    0.0

Escala mensual

Code & Output

>>> monthly_data = data.resample("m").sum()
>>> monthly_data.head()

            VALOR
FECHA
1971-04-30    3.3
1971-05-31    1.4
1971-06-30    0.5
1971-07-31    0.0
1971-08-31    0.0

Escala anual

Code & Output

>>> annual_data = data.resample("y").sum()
>>> annual_data.head()

            VALOR
FECHA
1971-12-31    7.0
1972-12-31  185.7
1973-12-31  137.8
1974-12-31   12.2
1975-12-31   37.7

Visualización de las series de tiempo

Plot

El ploteo de datos se realizará para el remuestreo anual. 

Code

>>> plt.plot(annual_data, "go-", label = "Rainfall")
>>> plt.title('Annual rainfall data')
>>> annual_data_mean = np.mean(annual_data.VALOR)
>>> annual_data_median = np.median(annual_data.VALOR)
>>> plt.axhline(annual_data_mean, color = "red", label= f"Mean {round(annual_data_mean,2)} mm")
>>> plt.axhline(annual_data_median, color = "blue", label = f"Median {round(annual_data_median,2)} mm")
>>> plt.xlabel("Year")
>>> plt.ylabel("Rainfall [mm/year]")
>>> plt.legend(), plt.grid(), plt.show()

Output

Figura 2. Gráfico de precipitación anual desde 1971 hasta 2017.

Diagrama de cajas

Los diagramas de caja (boxplot) permiten conocer la distribución de los datos, así como la identificación de datos atípicos. Estos pueden utilizarse a diferentes a escalas temporales, aunque dependiendo la cantidad de datos se puede observar un diagrama de cajas por representativo. En este caso, por ejemplo cuando es a escala mensual, casi todos los datos se encuentran fuera de la caja, mientras que en escala anual, los datos se pueden visualizar y analizar de mejor manera. 

Code

>>> plt.subplot(121)
>>> plt.boxplot(monthly_data.VALOR)
>>> plt.xticks([1], ["Data"])
>>> plt.ylabel('Monthly rainfall [mm/month]')
>>> plt.title('Monthly rainfall data')
>>> plt.subplot(122)
>>> plt.boxplot(annual_data.VALOR)
>>> plt.xticks([1], ["Data"])
>>> plt.ylabel('Annual rainfall [mm/year]')
>>> plt.title('Annual rainfall data')
>>>
>>> plt.tight_layout()
>>> plt.show()

Output

Figura 3. Diagrama de cajas de precipitación mensual y anual.





Comments

Popular posts from this blog

Creación de polígonos de Thiessen en QGIS y cálculo de precipitación en R

Cálculo poblacional (Método aritmético y geométrico)

¿Cómo descargar imágenes satelitales desde el USGS?