George H. Hargreaves et son équipe ont fait un travail colossal sur Haïti dans les années '80. Ils ont pu réunir un ensemble des données de pluviométrie et de température pour un total de 135 stations. Ce document a été publié en 1983. Sa numérisation n'a pas facilité son exploitation par les professionnels. La raison est simple: le format reste non structuré même si les données sont représentées dans des tableaux.

Ce travail d'extraction vise à mettre les données à la disposition de ceux-là qui souhaitent en faire usage de données climatiques (historiques) sur Haïti. Dans un premier temps un certain pourcentage de données seront extraites du fichier pdf disponible ici (Manuel Hargreaves) et dans un second temps(dans un autre projet) une application web permetra mieux l'accès aux différents jeux de données.

Extraction des données du pdf

Le pdf_text de pdftools s'est révélé assez efficace pour la lecture des caractères présents dans ce document publié dans les années 80. On a aussi désactivé les avertissements de R.

In [281]:
library(pdftools)
library(tidyverse)
library(readxl)
options(warn=-1)

Pour une meilleure "reproduciblité", le fichier en question est téléchargé via un lien internet.

In [282]:
link <- "https://www.dropbox.com/s/jmecb4msz2n1pld/hargreaves_data.pdf?raw=1"
download.file(link, "dataset/hargreaves.pdf", mode = "wb")

La fonction pdf_text de pdf_tools fait tout le travail de lecture des caractères. Les données des stations qui n' ont pas pu être lues seront laissées de coté.

In [283]:
all_lines <- pdf_text("dataset/hargreaves.pdf") %>%
  readr::read_lines()%>%
  str_squish()

Voici les 35 premières lignes du fichier lu. Elles représentent les données pour une station(Anse-à-Galets). Les lignes qui vont nous intéresser sont celles où se trouvent les informations sur la station (nom, latitude, longitude, et l' age des données collectées) et les informations en rapport à l'analyse des données à savoir la pluviométrie moyenne (MEAN), l'évapotranspiration (ETP), la température(TEM), la pluviométrie maximale et minimale(PMAX, PMIN).

In [284]:
for(line in all_lines[1:35])
    print(line)
[1] "A-4"
[1] ""
[1] ""
[1] ""
[1] "STATION. ANSE-A-GALETS LAT 18 50 LONG 72 52 ELEV 5.OM 41 YEARS"
[1] ""
[1] ""
[1] "PROB JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC ANN"
[1] "95. 0. 0. 0. 1. 1. 10. 5. 5. 12. 9. 0. 0. 206."
[1] "90. 0. 0. 0. 4. 4. 16. 10. 10. 20. 18. 1. 0. 284."
[1] "80. 0. 0. 1. 9. 13. 28. 20. 21. 36. 35. 4. 1. 407."
[1] "75. 1. 0. 2. 12. 19. 34. 26. 28. 43. 44. 6. 2. 462."
[1] "70. 1. 1. 3. 15. 26. 40. 32. 35. 51. 54. 8. 3. 516."
[1] "60. 3. 2. 5. 23. 44. 52. 45. 50. 69. 75. 15. 6. 624."
[1] "50. 5. 5. 10. 32. 69. 66. 61. 68. 88. 100. 23. 10. 738."
[1] "40. 10. 9. 16. 44. 102. 82. 80. 91. 111. 130. 35. 16. 865."
[1] "30. 18. 17. 26. 60. 148. 102. 104. 121. 139. 168. 52. 24. 1017."
[1] ""
[1] "25. 23. 23. 33. 70. 179. 114. 119. 139. 157. 191. 63. 30. 1108."
[1] "20. 31. 30. 42. 82. 217. 129. 138. 162. 178. 220. 78. 37. 1216."
[1] "10. 58. 59. 73. 122. 343. 174. 197. 234. 243. 310. 125. 61. 1530."
[1] "5. 89. 92. 107. 162. 476. 217. 255. 306. 306. 398. 175. 87. 1825."
[1] "DATA AND ANALYSIS"
[1] "MEAN 20. 20. 26. 50. 132. 84. 86. 101. 114. 138. 47. 23. 840."
[1] "ETP 112. 121. 150. 151. 161. 163. 173. 171. 154. 136. 110. 108. 1713."
[1] "TEM 24.3 24.6 25.3 26.0 26.8 27.6 27.7 27.9 27.6 27.2 26.0 24.8 26.3"
[1] "PMAX 150. 101. 125. 215. 937. 263. 315. 251. 460. 645. 240. 91. 2061."
[1] "PMIN 0. 0. 0. 0. 0. 3. 0. 0. 4. 3. 0. 0. 57."
[1] "MAI 0.01 0.00 0.01 0.08 0.1?*************************************************"
[1] "****************************~~"
[1] "0.21 0.15 0.16 0.28 0.33 0.05 0.02 0.27"
[1] ""
[1] ""
[1] ""
[1] ""

Vue globale des données

Les patterns décelés permettent de poser les premières bases pour l'extraction des données. Ici, le mot "DATA" apparait avant chaque tableau de données climatiques et le terme "YEAR" est dans la même que les données sur les stations. Ainsi, nous avons 135 jeux de données.

In [285]:
id<-grep('DATA',all_lines)
station<-grep('YEAR',all_lines)
In [286]:
length(id)
135
In [287]:
length(station)
135

Ici on présente un extrait des lignes comportant les données sur les stations. On remarque le même pattern dans presque tous les cas.

In [288]:
for(stat in station[1:10])
    print(all_lines[stat])
[1] "STATION. ANSE-A-GALETS LAT 18 50 LONG 72 52 ELEV 5.OM 41 YEARS"
[1] "STATION 50901 ANSE A PITRES LAT 18 2 LONG 71 46 ELEV 5.OM 10 YEARS"
[1] "STATION ANSE A VEAU LAT 18 30 LONG 73 21 ELEV 15.OM 48 YEARS"
[1] "STATION 70701 ANSE D HAINAULT LAT 18 29 LONG 74 27 ELEV 1O.OM 6 YEARS"
[1] "5.OM 11 YEARS"
[1] ";TATION 40901 ARCAHAIE LAT 18 46 LONG 72 31 ELEV 1O.OM 59 YEARS"
[1] "STATION 20702 AUBERT LAT 19 55 LUNG 72 50 ELEV 140.OM 4 YEARS"
[1] "STATION 50101 BAINET LAT 18 11 LONG 72 45 ELEV 25.OM 54 YEARS"
[1] "STATION 50902 GANANE LAT 18 8 LONG 71 45 ELEV 340.OM 24 YEARS"
[1] "STATION 30241 BAPTISTE LAT 18 48 LONG 71 47 ELEV 1080.OM 7 YEARS"

Extraction du nom des stations

Les expressions régulières ont permis d'extraire le nom des différentes stations.

In [289]:
listStation<-list()
number_year<-NULL
lat<-NULL
long<-NULL
nameStation<-NULL
for (i in 1:length(id))
{
  station_split<-all_lines[station[i]]%>%
    strsplit(split = " ")
  station_line<-all_lines[station[i]]
 

 
  pattern<-"ON(.)*LAT"
  station_name<-regmatches(station_line, regexpr(pattern, station_line))%>%
    str_replace("LAT","")%>%
    str_replace("\\d+","")%>%
    str_replace("ON","")%>%
    str_replace("\\.","")%>%
    str_squish()

nameStation[i]<-ifelse(length(station_name)==0,"No Name",station_name)

 
  }

Ici, on représente le nom de 10 stations. A remarquer que les noms de certaines stations n'ont pas pu être lus ou ont été mal lus. C'est le cas de la 5e et de la neuvième station.

In [290]:
print(matrix((nameStation[1:10])))
      [,1]             
 [1,] "ANSE-A-GALETS"  
 [2,] "ANSE A PITRES"  
 [3,] "ANSE A VEAU"    
 [4,] "ANSE D HAINAULT"
 [5,] "No Name"        
 [6,] "ARCAHAIE"       
 [7,] "AUBERT"         
 [8,] "BAINET"         
 [9,] "GANANE"         
[10,] "BAPTISTE"       

Extraction des données climatiques

Pour les données climatiques, les enregistrements qui n'ont pas de données sur tous les mois seront écartés.

In [291]:
for (i in 1:length(id))
{
lst<-all_lines[(id[i]+1):(id[i]+5)]%>%
    strsplit(split = " ")# remove empty spaces
  if(length(lst[[1]])!=14|length(lst[[2]])!=14|length(lst[[3]])!=14|
     length(lst[[4]])!=14|length(lst[[5]])!=14)
     { 
  next
  }
  else
  {
  df<-as.data.frame(lst,fix.empty.names =FALSE)
  tdf<-t(df)
 
  colnames(tdf)<-c('facteur','jan','feb','mar','apr','may','jun',
                   'jul','aug','sept','oct','nov','dec',
                   'year')
  df_last<-apply(tdf[,-1],2,as.numeric)
  df_last<-as.data.frame(df_last)
  df_last$facteur<-tdf[,1]
  df_last<-df_last[,1:13]
  rownames(df_last)<-c("PMean","ETP",
                  "Temp","PMAX","PMin")
  listStation[[i]]<-df_last
  names(listStation)[i]<-nameStation[i]
  
  }
  #print( listStation)
}

Une vue des données par station est fournie ici:

In [292]:
head(listStation)
$`ANSE-A-GALETS`
A data.frame: 5 × 13
janfebmaraprmayjunjulaugseptoctnovdecyear
<dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl>
PMean 20.0 20.0 26.0 50132.0 84.0 86.0101.0114.0138.0 47 23.0 840.0
ETP112.0121.0150.0151161.0163.0173.0171.0154.0136.0110108.01713.0
Temp 24.3 24.6 25.3 26 26.8 27.6 27.7 27.9 27.6 27.2 26 24.8 26.3
PMAX150.0101.0125.0215937.0263.0315.0251.0460.0645.0240 91.02061.0
PMin 0.0 0.0 0.0 0 0.0 3.0 0.0 0.0 4.0 3.0 0 0.0 57.0
$`ANSE A PITRES`
A data.frame: 5 × 13
janfebmaraprmayjunjulaugseptoctnovdecyear
<dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl>
PMean 11.0 25.0 18.0 11 40.0 20.0 19.0 47.0140.0136.0 51 25.0 542.0
ETP114.0122.0151.0151161.0163.0173.0171.0155.0137.0112110.01720.0
Temp 24.3 24.6 25.3 26 26.8 27.6 27.7 27.9 27.6 27.2 26 24.8 26.3
PMAX 28.0110.0 72.0 32139.0 40.0 45.0184.0489.0425.0191143.0 538.0
PMin 0.0 0.0 0.0 0 0.0 0.0 0.0 1.0 10.0 0.0 0 0.0 176.0
$`ANSE A VEAU`
A data.frame: 5 × 13
janfebmaraprmayjunjulaugseptoctnovdecyear
<dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl>
PMean 41.0 37.0 51.0102170.0125.0119.0130.0117.0128.0 86 30.01135.0
ETP113.0121.0150.0150161.0163.0173.0171.0154.0136.0111109.01713.0
Temp 24.2 24.5 25.2 26 26.7 27.5 27.7 27.8 27.6 27.2 26 24.8 26.2
PMAX237.0206.0281.0414445.0376.0247.0314.0331.0527.0525160.01594.0
PMin 0.0 0.0 0.0 10 41.0 8.0 29.0 38.0 23.0 5.0 0 0.0 683.0
$`ANSE D HAINAULT`
A data.frame: 5 × 13
janfebmaraprmayjunjulaugseptoctnovdecyear
<dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl>
PMean 49.0 57.0 41.0 68.0276124.0163.0187185.0101.0199.027.01475.0
ETP 90.0 91.0113.0110.0118121.0124.0128118.0117.0 99.093.01324.0
Temp 25.1 25.6 25.2 25.5 26 26.9 26.7 27 26.8 27.1 27.126.2 26.3
PMAX105.0143.0 58.0177.0606169.0228.0287256.0143.0397.066.01235.0
PMin 18.0 0.0 28.0 21.0142 81.0104.0119100.0 70.0 47.0 4.01235.0
[[5]]
NULL
[[6]]
NULL

On remarquera que certains éléments de la liste n'ont pas de valeur. Faisons un peu de nettoyage. Sur les 135 stations, les données extraites concernent un total de 96.

In [293]:
cleanStation<-listStation[names(listStation) != ""] 
length(cleanStation)
96

Extraction des données sur les stations

Les expressions régulières ont encore facilité l'extraction de l'age des données collectées, la longitude, la latitude des stations concernées.

In [294]:
for (i in 1:length(id))
{
lst<-all_lines[(id[i]+1):(id[i]+5)]%>%
    strsplit(split = " ")
  if(length(lst[[1]])!=14|length(lst[[2]])!=14|length(lst[[3]])!=14|
     length(lst[[4]])!=14|length(lst[[5]])!=14)
     { 
  next
  }
  else
  {
  station_line<-all_lines[station[i]]
  patyear<-"([[:digit:]]+)([[:space:]]+)YEAR"
  number_year[i]<-regmatches(station_line, regexpr(patyear, station_line))%>%
    str_replace("YEAR","")%>%
    str_squish()
  patlat<-"LAT(.){6}"
  lat[i]<-regmatches(station_line, regexpr(patlat, station_line))%>%
    str_replace("[A-Z]*","")%>%
    str_squish()%>%
    str_replace(" ",".")
  #print(lat[i])
  patlon<-"(.){6}\\bELE"
  long[i]<-regmatches(station_line, regexpr(patlon, station_line))%>%
    str_replace_all("[A-Z]*","")%>%
    str_squish()%>%
    str_replace(" ",".")
  }
  #print( listStation)
}

Par exemple, nous avons ici l'age des données collectées pour les dix premières stations.

In [295]:
number_year[1:10]
  1. '41'
  2. '10'
  3. '48'
  4. '6'
  5. NA
  6. NA
  7. '4'
  8. '54'
  9. '24'
  10. '7'

Dataframe des données sur les stations

Les données sur les stations sont pour la plupart des données numériques. Faisons la transformation et un peu de nettoyage.

In [296]:
year_clean<-as.numeric(number_year[!is.na(number_year)])
lat_clean<-as.numeric(lat[!is.na(lat)])
long_clean<-as.numeric(long[!is.na(long)])
df_extra<-data.frame(names(cleanStation),lat_clean,long_clean,year_clean)
names(df_extra)<-c("stationName","Latitude","Longitude","numberYear")

Voici les 10 premiers enregistrements du dataframe formant les données sur les stations.

In [297]:
head(df_extra)
A data.frame: 6 × 4
stationNameLatitudeLongitudenumberYear
<chr><dbl><dbl><dbl>
1ANSE-A-GALETS 18.5072.5241
2ANSE A PITRES 18.2071.4610
3ANSE A VEAU 18.3073.2148
4ANSE D HAINAULT18.2974.27 6
5AUBERT 19.5572.50 4
6BAINET 18.1172.4554

Structuration des données pour un meilleur partage

Le fichier pdf a été lu à l'aide de la fonction pdf_text de pdf_tools. Certaines données incomplètes ont été écartées. Aucune donnée n'a été saisie manuellement pour compléter les jeux extraits. L'extraction automatique a été faite pour 96 sur 135 jeux de données disponibles dans le manuel de George H. Hargreaves.

La structuration des données non structurées requiert du temps. Chaque cas présente ses caractéristiques propres. Autrement dit, les traitements effectués sur le présent fichier pdf ne seront pas automatiquement valables pour un autre ensemble de données non structurées.

La structuration automatique des données de ce fichier a été le pas indispensable pour la construction d'une application web disponible ici (HargreavesApp). Celle-ci contribuera à une plus large accessibilité aux données historiques climatiques de Hargreaves.