1. Introduction

Créé début 2017, la plateforme ODRÉ (pour Open Data Réseaux Énergies) met à disposition de tous des données autour des thématiques de production, de consommation et de stockage des énergies dans nos territoires et nos régions. On peut également y retrouver des données météorologiques. La plateforme ODRÉ a vocation à s’enrichir avec de nouvelles données multi-énergies, multi-opérateurs et multi-réseaux mais pour l’instant seuls 13 jeux de données sont disponibles sur la plateforme. La plupart de ces jeux de données ont été fournis par les créateurs d’ODRÉ : les entreprises RTE, GRTgaz, et TIGF. A elles trois, ces entreprises gèrent entièrement les réseaux d’électricité et de gaz français.

Le réseau de transport d’électricité français est entièrement géré par RTE. En revanche le réseau de transport de gaz naturel est composé de trois zones d’équilibrage. Deux de ces zones sont gerées par GRTgaz tandis qu’une zone bien plus petite (le sud ouest) est gerée par TIGF.

Le but de ce projet “Open Data” va être de proposer une analyse pertinente des données disponibles sur la plateforme ODRÉ en utilisant les méthodes statistiques vues au cours de notre formation.

Dans un premier temps nous analyserons la consommation de gaz et d’électricité française puis nous essayerons de créer plusieurs modèles permettant de prédire les consommations d’électricité journalières. Notons que dans cette étude le mot énergie servira à nommer l’électricité et le gaz réunis.

2. Données

Sur la plateforme ODRÉ 13 jeux de données sont disponibles. Ces fichiers sont très difficiles à lier et à comparer entre eux. En effet ils sont déposés de manières indépendantes par les différents partenaires de la plateforme et par conséquent même si ils traitent du même sujet, ils ne concordent pas. Les périodes de temps ne sont pas les mêmes (parfois elles ne se croisent même pas), les chiffres correspondent à des zones différentes (France métropolitaine avec ou sans Corse, zone TIGF, etc). Nous avons même repéré des erreurs de description des fichiers.

Ainsi pour que notre étude puisse être pertinente, nous avons décidé de nous concentrer sur les 2 fichiers les plus complets :

  • Courbe de charge consommation brute (2012-2017) : ce fichier nous donne la consommation brute de gaz (au pas horaire en MW pour les secteurs respectifs de GRTgaz et TIGF) et d’électricité (au pas demi-horaire, en MW France métropolitaine hors Corse). Il comporte 103 728 lignes pour 9 colonnes

  • Courbe de charge consommation brute régionale (2013-2017) : C’est le même fichier que le précédent mais avec en plus un découpage régional.

Ces deux fichiers étaient uniquement disponibles au format csv. Pour réaliser les cartes nous avons récupéré d’autres fichiers au format shapefile de la plateforme et nous avons remplacé leurs valeurs par celles obtenues grâces aux fichiers “courbe de charge”.

Nous nous sommes également servis des fichiers suivants :

  • Température quotidienne régionale (2016-2017) : Ce jeu de données nous donne les températures minimales, maximales et moyennes quotidiennes (en degré celsius) par région, du 1er janvier 2016 au 30 novembre 2017. Il est basé sur les mesures officielles du réseau des stations météorologiques françaises.

  • Courbe d’évolution des stocks de gaz (2012-2017) : Ce jeu de données présente la quantité de gaz stockée par les expéditeurs en fin de journée gazière dans les stockages de TIGF depuis 2012 (au pas journalier, en GWh à 0°).

3. Analyse de la consommation de gaz et d’électricité en France

3.1. France entière

Nous allons commencer notre étude en analysant les consommations de gaz et d’électricité en France.

load("DATA/CdCConsoBrut.Rdata")
#- Mise les NA a 0
CdCConsoBrut[is.na(CdCConsoBrut[,5]), 5]=0
CdCConsoBrut[is.na(CdCConsoBrut[,7]), 7]=0
CdCConsoBrut[is.na(CdCConsoBrut[,3]), 3]=0

newDate = as.factor( paste( str_sub(CdCConsoBrut$date, 7, 10), "-", str_sub(CdCConsoBrut$date, 4, 5), sep=""))
newAnnee = as.factor( paste( str_sub(CdCConsoBrut$date, 7, 10), sep=""))
DATA = as.data.frame( cbind( CdCConsoBrut[,3]+CdCConsoBrut[,5], CdCConsoBrut[,7] ) )
DATA = cbind(DATA, newDate, newAnnee)
colnames(DATA) = c("Conso Gaz", "Conso Elec", "date", "annee")

# Trie par date
DATAsort <- DATA[sort(as.numeric(DATA$date), index.return=TRUE)$ix, ]

vect = as.vector(unique(DATAsort$date))
SerieGaz = rep(NA,71)
SerieElec = rep(NA,71)
for(i in 1:length(vect)){
  lign = which(DATAsort$date == vect[i])
  SerieGaz[i] = sum(DATAsort[lign, 1])/1000
  SerieElec[i] = sum(DATAsort[lign, 2])/1000
}

dates <- seq(as.Date("2012-01-01"), length=71, by="months")
xts0 <- xts(SerieGaz, order.by=dates)
colnames(xts0)=c("Consommation de gaz GW")
xts1 <- xts(SerieElec, order.by=dates)
colnames(xts1) = c("Consommation d'electricite GW")
dygraph(cbind(xts0, xts1), main="Evolution de la consommation d'electricite et de gaz")



Ce graphique représente l’évolution, entre janvier 2012 et novembre 2017, de la consommation d’électricité (courbe bleue) et de gaz (courbe verte) en France métropolitaine (hors Corse) en GW. On constate que la consommation de gaz est toujours inferieure à la consommation d’électricité. Il ressort clairement que ces deux consommations sont liées et en effet on trouve une corrélation de 0.985 entre ces deux séries temporelles. Ces deux séries ont également la même saisonnalité annuelle : la consommation en énergie est plus forte en hiver.

DATA2015 = DATA[which(DATA$annee==2015),]
DATA2016 = DATA[which(DATA$annee==2016),]
par(mfrow=c(1,2))
#Conso total dans l'annee
pie(apply(DATA2015[,c(1,2)],2,sum), 
     labels = c("Gaz", "Electricite"),
     main = "2015",
     col = c("#4fbeff", "#F7FE2E") )
text_pie(apply(DATA2016[,c(1,2)],2,sum), c("31.1%","68.9%"))
#Conso total dans l'annee
pie(apply(DATA2016[,c(1,2)],2,sum), 
     labels = c("Gaz", "Electricite"),
     main = "2016",
     col = c("#4fbeff", "#F7FE2E") )
text_pie(apply(DATA2016[,c(1,2)],2,sum), c("32.8%","67.2%"))

Ce graphique nous montre que ces dernières années on a consommé environ 2 fois plus d’électricité que de gaz. En 2016 ce sont 960640863 GW d’électricité qui ont été consommés contre 468357467 de gaz. Entre 2015 et 2016 la proportion de gaz consommé à légerement augmentée (+1.7%).

3.2. Comparaison entre les régions

Nous allons maintenant chercher les différences entre chaque régions.

#####
#- DONNEES POUR CARTE REGION 2016
#####
load("DATA/LatLongRegion.Rdata")
LatLongRegion = LatLongRegion[-12,]   #on enleve la corse
LatLongRegion = LatLongRegion[order(LatLongRegion$`code INSEE regin`),] #on trie par code

load("DATA/CdCConsoBrutReg.Rdata")
CdCConsoBrutReg[is.na(CdCConsoBrutReg[,5]), 5]=0
CdCConsoBrutReg[is.na(CdCConsoBrutReg[,7]), 7]=0
CdCConsoBrutReg[is.na(CdCConsoBrutReg[,9]), 9]=0

newAnnee = as.factor( paste( str_sub(CdCConsoBrutReg$date, 7, 10), sep=""))
DATA = as.data.frame( cbind( CdCConsoBrutReg[,3], CdCConsoBrutReg[,5]+CdCConsoBrutReg[,7], CdCConsoBrutReg[,9] ))
DATA = cbind(DATA, newAnnee)
colnames(DATA) = c("Insee Region", "Conso Gaz", "Conso Elec", "Annee")
DATA2016 = DATA[which(DATA$Annee==2016),]

CodeInsee = as.vector(unique(DATA2016$`Insee Region`))
vectGaz = rep(NA,12)
vectElec = rep(NA,12)
for(i in 1:12){
  lign = which(DATA2016$`Insee Region` == CodeInsee[i])
  vectGaz[i] = sum(DATA2016[lign, 2])/1000
  vectElec[i] = sum(DATA2016[lign, 3])/1000
}

NewData2016 = cbind(vectGaz, vectElec, CodeInsee)
NewData2016 = NewData2016[order(NewData2016[,3]),]  #on trie par code
NewData2016 = cbind(NewData2016, LatLongRegion[,c(3,4)])
colnames(NewData2016) = c("Gaz", "Electricite", "CodeInsee", "lat", "long")

#shapefile conso par region avec les mauvais chiffres
Regions  <- readOGR(dsn = 'DATA/shapefile', layer="consommation-annuelle-brute-regionale")
Regions2016  <- subset(Regions, Regions$annee==2016)
Regions2016  <- subset(Regions2016, Regions2016$code_insee_!=94)

#On met les bon chiffres
for (i in 1:12){
  code = Regions2016$code_insee_[i]
  Regions2016$consommatio.1[i] = NewData2016[which(NewData2016$CodeInsee==code),1]
  Regions2016$consommatio.2[i] = NewData2016[which(NewData2016$CodeInsee==code),2]
  Regions2016$consommatio.3[i] = NewData2016[which(NewData2016$CodeInsee==code),1] + NewData2016[which(NewData2016$CodeInsee==code),2]
}

# Decoupage en couleur 
paletteVal = classIntervals(Regions2016$consommatio.3, 4, style="quantile")
brks <- round(paletteVal$brks, 0) 
paletteVal = classIntervals(Regions2016$consommatio.3, 4, style="fixed", fixedBreaks=brks)
paletteCol = smoothColors('#E5FBFF', 4, "#006577")
col <- findColours( paletteVal, paletteCol )
leg <- findColours(paletteVal, paletteCol, under="moins de", over="plus de", between="e", cutlabels=FALSE)

#- plot de la carte
plot(Regions2016, col=col, border="#FFFFFF", lwd=0.1)
titre = paste("Consommation d'énergie en ", 2016)
title(main=titre)
legend('bottomleft', fill=attr(leg, "palette"), legend=gsub("\\.", ",", names(attr(leg,"table"))),
       title = "Consommation (GW)", bg='white', box.col=0, cex=0.8)

Cette carte nous montre que la proportion de gaz et d’électricité consommé dans chaque région est différente. On constate que la région parisienne est très gourmande en énergie malgré sa petite taille. C’est elle qui a consommé le plus d’énergie en 2016 : 226743 GW. Viens ensuite la région Auvergne Rhone Alpe avec 187125 GW consommé puis les Hauts-de-France avec 171947 GW consommé. La région ayant le moins consommé est le Centre-Val-de-Loire avec une consommation de seulement 54514 GW.

On va maintenant chercher a savoir si toutes les régions ont la même répartition de consommation entre électricité et gaz.

regions <- readOGR("DATA/shapefile/RegionShp/regions-20180101.shp", layer = "regions-20180101", GDAL1_integer64_policy = TRUE)
regionsMetro <- subset(regions, regions$code_insee %in% CodeInsee)
#- carte
colors <- c("#4fbeff", "#F7FE2E")
map <- leaflet(regionsMetro) %>%
       addTiles() %>%
       addPolygons( color = "#444444", weight = 1, smoothFactor = 0.5,
                    opacity = 1, fillOpacity = 0,
                    fillColor = "white",
                    highlightOptions = highlightOptions(color="white", weight=2, bringToFront=TRUE) ) %>%
      addMinicharts( lat=NewData2016$lat, lng=NewData2016$long, 
                     type = "pie",
                     chartdata = NewData2016[,c(1,2)], 
                     colorPalette=colors,
                     width = 95*sqrt(apply(NewData2016[,1:2],1,sum)) /sqrt(sum(apply(NewData2016[,1:2],1,sum))), 
                     transitionTime = 0 )
map



On remarque que ce sont les régions les plus gourmandes en énergie qui ont les plus grandes proportions de gaz. Les régions du sud-ouest, alimentées en partie par TIGF pour leur gaz, n’en consomment pas beaucoup. Les régions du nord-est utilisent plus de gaz que les régions du sud-ouest. Ceci peut s’expliquer par le fait que ce soit des régions plus industrielles.

4. Modélisation de la consommation d’électricité

4.1. Données

Nous allons maintenant essayer de modéliser la consommation d’électricité. Dans cette partie nous ne travaillerons que sur la période allant du 5 janvier 2016 au 31 novembre 2017 puisque c’est sur cette période que nos jeux de données se croisent.

Nous avons créé un nouveau jeu de données, regroupant toutes les informations nécéssaires qui étaient auparavant dispersées dans différents jeux de données.

### ------------------------
### Mise propre de CdCConsoBrut
### ------------------------
load("DATA/CdCConsoBrut.Rdata")

CdCConsoBrut$date = as.Date(CdCConsoBrut$date, format="%d/%m/%Y")

ConsoElec = CdCConsoBrut[,7]
jour = as.numeric(as.factor(paste(str_sub(CdCConsoBrut$date, 9, 10), sep="")))
mois = as.numeric(as.factor(paste(str_sub(CdCConsoBrut$date, 6, 7), sep="")))
annee = as.factor(paste(str_sub(CdCConsoBrut$date, 1, 4), sep=""))
dateNum =  as.numeric(CdCConsoBrut$date)
CdCConsoBrut = as.data.frame( cbind( ConsoElec, jour, mois, dateNum ) )
CdCConsoBrut = cbind( CdCConsoBrut, annee)
CdCConsoBrut = CdCConsoBrut[ which(CdCConsoBrut$annee==2016 | CdCConsoBrut$annee==2017), ]
#on remplace les valeurs manquantes par 0
CdCConsoBrut[is.na(CdCConsoBrut$ConsoElec), 1]=0
#On regroupe par jour
vect = as.vector(unique(CdCConsoBrut$dateNum))
consoData = cbind(rep(NA,700), rep(NA,700))
for(i in 1:length(vect)){
  lign = which(CdCConsoBrut$dateNum==vect[i])
  consoData[i,1] = sum(CdCConsoBrut[lign, 1])/1000
  consoData[i,2] = vect[i]
}
colnames(consoData) = c("ConsoElec","dateNum")
consoData = as.data.frame(consoData)

### ------------------------
###  Mise propre de TemperatureQuotiReg
### ------------------------
load("DATA/TemperatureQuotiReg.Rdata")
TemperatureQuotiReg$date = as.Date(TemperatureQuotiReg$date)
dateNum =  as.numeric(as.Date(TemperatureQuotiReg$date))

TemperatureQuotiReg = cbind(TemperatureQuotiReg, dateNum)
TemperatureQuotiReg = TemperatureQuotiReg[,-c(1,5,6)]

vect = as.vector(unique(TemperatureQuotiReg$dateNum))
tempData = cbind(rep(NA,731), rep(NA,731), rep(NA,731), rep(NA,731))
for(i in 1:length(vect)){
  lign = which(TemperatureQuotiReg$dateNum==vect[i])
  tempData[i,1] = mean(TemperatureQuotiReg[lign, 1])
  tempData[i,2] = mean(TemperatureQuotiReg[lign, 2])
  tempData[i,3] = mean(TemperatureQuotiReg[lign, 3])
  tempData[i,4] = vect[i]
}

colnames(tempData) = c("Tmin","Tmax", "Tmoy", "dateNum")
tempData = as.data.frame(tempData)

### ------------------------
###  Mise propre de Stock Gaz
### ------------------------
load("DATA/StockGaz.Rdata")
StockGaz$date = as.Date(StockGaz$date)
StockGaz$date = as.numeric(as.Date(StockGaz$date))
stockData = StockGaz
colnames(stockData)=c("dateNum", "StockGaz")
                      
### ------------------------
###  collage des 3 jeux de données
### ------------------------
dateCommune = intersect(as.numeric(consoData$dateNum), as.numeric(stockData$dateNum))
dateCommune = intersect(dateCommune, tempData$dateNum)

limMax = max(dateCommune)   # 17500  = 30/11/17
limMin = min(dateCommune)   # 16801  = 01/01/16

consoData = consoData[which(consoData$dateNum %in% dateCommune), ]
consoData = consoData[order(consoData$dateNum),]                      #on trie par code

stockData = stockData[which(stockData$dateNum %in% dateCommune), ]
stockData = stockData[order(stockData$dateNum),]                      #on trie par code

tempData = tempData[which(tempData$dateNum %in% dateCommune), ]
tempData = tempData[order(tempData$dateNum),]                      #on trie par code

DataSet = cbind(consoData[,2], consoData[,1], tempData[,1:3], stockData[,2])
colnames(DataSet) = c("dateNum", "ConsoElec", "Tmin", "Tmax", "Tmoy", "StockGaz")

### ------------------------
###  ajout des jour ferier
### ------------------------
JF2016 = read.csv2("DATA/feries_2016.csv")
JF2016$Date = as.numeric(as.Date(JF2016$Date, format="%d/%m/%Y"))
JF2016 = JF2016[,-1]

JF2017 = read.csv2("DATA/feries_2017.csv")
JF2017$Date = as.numeric(as.Date(JF2017$Date, format="%d/%m/%Y"))
JF2017 = JF2017[,-c(1,4,5,6,7)]

dateJF = c(JF2016$Date, JF2017$Date)
IsJF = rep(0,700)

IsJF[which(DataSet$dateNum %in% dateJF)] = 1

DataSet = cbind(DataSet, IsJF)

### -------------------------------------
### Modification dateNum 
### -------------------------------------
DataSet$dateNum = DataSet$dateNum-16801

### -------------------------------------
### Ajout de la date avec cos(dateNum%%365)
### -------------------------------------

#- On met la date sous la forme de nombre de jour apres 01/01/14
DataSet = cbind(DataSet, DataSet$dateNum)
colnames(DataSet)[8] = c("day")
DataSet$day = cos((DataSet$day)*2*pi/365)

### -------------------------------------
### Ajout du passé au temps +t
### -------------------------------------
FullDataSet = DataSet
t=4
DataSet = FullDataSet[(t+1):700,]
for (i in 1:t){
  Pass = FullDataSet[(t+1-i):(700-i),2]
  DataSet = cbind(DataSet, Pass)
}
colnames(DataSet) = c(colnames(DataSet[,1:8]),"Pass1","Pass2","Pass3","Pass4")

### ------------------------
###  Sauvegarde
### ------------------------
save(DataSet, file="DATA/DataSetMachineLearning.Rdata")
load(file="DATA/DataSetMachineLearning.Rdata")

Pour essayer de modéliser la consommation électrique journalière nous avons récupéré plusieurs informations :

  • dateNum : Le nombre de jour après le 1er janvier 2016
  • ConsoElec : La consommation d’électricité journalière en GW
  • Tmoy : Température moyenne journalière en °C
  • Tmin : Température minimale journalière en °C
  • Tmax : Température maximale journalière en °C
  • StockGaz : Stock de gaz TIGF en fin de journée en GWh
  • Pass1, Pass2, Pass3 et Pass4 : Les consommations électriques des 4 jours précédents en GW

Nous avons également créé de nouvelles variables :

  • IsJF : une variable binaire valant 1 si c’est un jour férié
  • day : 2x\(\pi\)xcos(dateNum%%365)/365 qui permet de faire apparaitre une saisonnalité annuelle sur la variable dateNum dans le temps.

Le jeu de données créé contiendra 696 lignes (une par jour), la variable à expliquer (ConsoElec) et 11 variables explicatives. Voici un extrait du jeu de données ainsi créé :

print(DataSet[1:5,])
##    dateNum ConsoElec     Tmin      Tmax     Tmoy StockGaz IsJF       day
## 17       4  3231.827 5.398462  9.999231 7.699231 19670.01    0 0.9976303
## 34       5  3266.917 4.943846 10.222308 7.582308 19508.59    0 0.9962982
## 20       6  3252.384 5.301538 12.130000 8.716154 19316.09    0 0.9946708
## 33       7  3208.979 4.110769 11.085385 7.598462 19086.50    0 0.9927487
## 28       8  2854.915 5.711538 12.044615 8.879231 18902.02    0 0.9905325
##       Pass1    Pass2    Pass3    Pass4
## 17 3092.978 2725.351 2663.011 2581.914
## 34 3231.827 3092.978 2725.351 2663.011
## 20 3266.917 3231.827 3092.978 2725.351
## 33 3252.384 3266.917 3231.827 3092.978
## 28 3208.979 3252.384 3266.917 3231.827

Voici une première analyse de nos variables explicatives : la matrice des corrélations.

MAT = cbind(DataSet$ConsoElec, DataSet[,-2])
colnames(MAT)[1] = "ConsoElec"
corrplot(cor(MAT ))



Sur la première ligne on peut voir la corrélation de chaque variables avec la consommation électrique. Les corrélations des consommations des jours antérieurs sont très élevées positivement alors que celles des températures sont très élevées négativement. La variable day est également fortement corrélée. Les autres variables semblent avoir un lien linéaire plus faible.

4.2. Découpage entrainement/test

Nous avons décidé de réserver 10% des dernières dates (du 22 septembre au 30 novembre 2017) pour tester nos modèles. le code est disponible ci dessous.

n = length(DataSet[,1])
ntrain = round(n*0.9)
ntest = n-ntrain

train = DataSet[1:ntrain,]
test = DataSet[(ntrain+1):n,]

Ytrain = train$ConsoElec
Xtrain = train[,-2]
Ytest = test$ConsoElec
Xtest = test[,-2]

4.3. Modèles de séries chronologiques

Dans cette partie nous allons modéliser la consommation électrique journalière via des modèles de séries chronologiques. Regardons à quoi ressemble la série chronologique de la consommation électrique journalière.

#- Serie consommation
SCconso <- xts(DataSet[,2], order.by = as.Date(DataSet[,1],origin = "01/01/2016"))
colnames(SCconso) = "Consommation en électricité(GW)"
dygraph(SCconso, main = "Evolution de la consommation journaliere en electricite (GW)")

4.3.1. Modèle Auto-Regressif

Nous allons modéliser la série chronologique de la consommation électrique journalière par un modèle auto-régressif d’ordre p noté AR(p)

Pour rappel un AR(p) s’écrit :

\[ X_t = \mu + \epsilon_{t} + \sum^{p}_{j=1}a_{j}X_{t-j} \]

Pour s’assurer que la série est stationnaire nous allons d’abord regarder l’autocorrélogramme associé.

pacf(SCconso)

A première vue la série semble stationnaire car les valeurs d’autocorrélation-partielles sont en grande partie entre les deux lignes horizontales (en pointillés bleus) qui représentent le seuil critique à partir duquel l’autocorrélation est considérée comme significative.

Pour en être certain on réalise un test de stationnarité à l’aide du package “tserie”.

adf.test(SCconso)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  SCconso
## Dickey-Fuller = -2.0468, Lag order = 8, p-value = 0.5585
## alternative hypothesis: stationary

On obtient une p-value de 0.56, donc on ne peut pas rejeter l’hypothèse de stationnarité de notre série chronologique.

On va maintenant, à l’aide de la fonction “ar” modéliser le processus auto-régressif de la série en fixant le degré maximal à p=5.

out <- ar(SCconso[1:ntrain,], aic = TRUE, order.max=5, dmean=TRUE)
out
## 
## Call:
## ar(x = SCconso[1:ntrain, ], aic = TRUE, order.max = 5, dmean = TRUE)
## 
## Coefficients:
##       1        2        3        4        5  
##  1.0967  -0.5341   0.3611  -0.2500   0.2945  
## 
## Order selected 5  sigma^2 estimated as  26741

On obtient ainsi un modèle AR(5) et les 5 coefficients du modèle ont été estimé. D’une façon générale, plus on s’éloigne dans le temps moins le coefficient est grand (en valeur absolue) ce qui signifie que l’observation est moins importante pour la prédiction.

On va ainsi pouvoir prédire avec ce modèle notre échantillon de test.

####prédiction de l'ensemble test
pred_test <- rep(NA,ntest)
for(j in 1:ntest){
  pred_test[j]<-predict(out, newdata=SCconso[1:(ntrain-1+j),], n.ahead =1)$pred
}

errTest <- sqrt(mean((pred_test-Ytest)^2))
errTest
## [1] 172.9477
plot(1:ntest, Ytest, col="red", ylim=c(1700, 3700), xlab="temps", ylab="Consommation electrique (GW)")
lines(1:ntest, pred_test, col=1)
legend( "topleft", legend=c("Vraie consommation", "Consommation prédite"), pch=20, col=c(2,1))
title(main="Prédiction avec modèle AR(5)")

Avec ce modèle on trouve une erreur test de 172,9. La prédiction n’est pas mauvaise.

4.3.2. Modèle MARIMA

Nous allons maintenant créer un modèle MARIMA avec la série des températures et la série de consommation d’électicité journalière.

Un fichier de données contenant les températures moyennes par région pour les années 2016 et 2017 est présent sur la plateforme ODRÉ. Nous allons essayer de vérifier notre première intuition : la consommation électrique est liée à la température moyenne. Nous avons fait une moyenne entre les régions pour estimer les valeurs des températures de la France entière.

#- Serie Temperature
SCtemperature <- xts(DataSet[,5], order.by=as.Date(DataSet[,1], origin="01/01/2016"))
colnames(SCtemperature)="Temperature moyenne en France(eC)"
dygraph(SCtemperature, main = "Evolution journaliere de la temperature en France (°C)")

La corrélation de cette série avec celle de la consommation est élevée : -0,84. Il est clair que lorsque la température augmente, la consommation baisse.

#Transformation temperature en variable quali
Temperature = DataSet[,5]
ConsommationElec = DataSet[,2]
quantileTemperature <- quantile(Temperature)
labelTemperature <- c("froid", "doux", "chaud", "tres chaud")
TemperatureQuali = rep(NA, length(Temperature))
for(i in 1:4){
  lign = which(Temperature>=quantileTemperature[i] & Temperature<=quantileTemperature[i+1])
  TemperatureQuali[lign] <- labelTemperature[i]
}
couleurTemp <- rep(NA,n)
couleurTemp[which(TemperatureQuali=="froid")] <- "#58D3F7"
couleurTemp[which(TemperatureQuali=="doux")] <- "#FFFF66"
couleurTemp[which(TemperatureQuali=="chaud")] <- "#FF9933"
couleurTemp[which(TemperatureQuali=="tres chaud")] <- "#FF3300"

par(mfrow=c(1,2))

plot(Temperature, ConsommationElec, xlab="Temperature moyenne journalière", ylim=c(1000,4500), ylab="Consommation d'electricite (GW)", col=4 )
title(main=paste("Consommation electrique journaliere","\n",sep=""), cex.main=1)
title(main=paste("\n","en fonction de la temperature",sep=""), cex.main=1)

plot(1:n, ConsommationElec, 
     col=couleurTemp, 
     pch=20, 
     ylab="Consommation electrique", 
     xlab="jours")
title(main=paste("Consommation electrique ","\n",sep=""), cex.main=1)
title(main=paste("\n","entre 2016 et 2018",sep=""), cex.main=1)
legend( "topleft", 
        legend=c("moins de 8eC", "entre 8 et 12°C","entre 12 et 18°C","plus de 18°C"), 
        pch=20, col=c("#58D3F7","#FFFF66","#FF9933","#FF3300") )

Le graphique de gauche représente la consommation d’électricité en fonction de la température moyenne en 2016 et 2017. On constate un lien non linéaire.

Pour créer le graphique de droite nous avons découpé les données des températures en variables qualitatives selon ses quantiles. On a trouvé : Q1=[-1°C;8°C[, Q2=[8°C;12°C], Q3=[12°C;18°C[ et Q4=[18°C,26°C[. Ainsi on constate une nouvelle fois le lien entre consommation électrique et température.

Nous allons maintenant estimer notre modèle MARIMA. Notons \(X_{t}^{1}\) la série chronologique des températures et \(X_{t}^{2}\) celle des consommations électriques.

On suppose \[X_t^{1} \sim AR(p) \quad{et}\quad X_t^{2} \sim AR(p)\]

Alors pour p=1, en regroupant nos deux séries chronologiques en un seul vecteur on a :

\[X_t= \begin{bmatrix} X_t^{1} \\ X_t^{2} \end{bmatrix} = a_1 A X_{t-1} \text{ avec } A\in \mathcal{M}_2 \text{ et } a_1 \text{ un réel } \]

Ainsi si on arrive à estimer la matrice A et le coefficient a1, on peut déterminer la cohérence entre les deux séries chronologiques. En effet on a :

\[X_t^2=a_t^1(A_{21}X_{t-1}^1 + A_{22}X_{t-1}^2) \]

Pour parvenir à estimer cette matrice A on utilise le package “marima” qui prend en entrée notre série chronoloqigue à deux variables ainsi que le modèle de la série chronologique, ici on a choisit AR(1).

## on concatène nos deux séries chronologiques
SC<-cbind(SCtemperature,SCconso)
 ## on définit le modèle ar 
ar<-c(1)
mod<-define.model(kvar=2,ar=ar)
Marima1  <- marima(SC, means=1, ar.pattern=mod$ar.pattern, ma.pattern=NULL, Check=FALSE, penalty=0.0)
Marima1$ar.estimates[,,2]
##            [,1]          [,2]
## [1,] -0.9361533  0.0005243761
## [2,] 16.4526213 -0.7725582720

La matrice A a été estimée par la fonction “marima”. On a aussi accès aux p-values associées aux coefficients, où l’hypothèse testée est la nullité des coefficients.

Marima1$ar.pvalues[,,2]
##               [,1]          [,2]
## [1,] 6.071683e-261  7.550055e-03
## [2,]  6.860916e-15 3.068261e-137

Toutes les p-values sont très faibles, donc on rejète l’hypothèse de nullité pour tous les coefficients. On constate donc bien qu’il y a un lien fort entre les deux séries chronologiques notemment ici avec un coefficient de 16 qui relie la série chronologique des températures à celle des consommations.

On va désormais essayer de prédire la consommation en électricité au jours j+1 sachant que l’on connaît la consommation et la température du jours j. Pour cela on va estimer la matrice A sur 90% de nos premières données et prédire le reste des données à l’aide de cette matrice. Quant au coefficient multiplicateur a1 devant la matrice on va prendre la moyenne des vrais coefficients calculés pour notre jeu de donnée contenant 90% des données.

## prévision avec selon le jours d'avant
## on créé le modèle avec seulement 90% nos donnée initiale(pour eviter le suraprentissage)
tr <- (1:ntrain)
train_sc <- SC[tr,]
test_sc <- SC[-tr,]

m <- ntrain

## on définit le modèle ar 
ar<-c(1)

mod<-define.model(kvar=2,ar=ar)

## modèle marima
Marima1  <- marima(train_sc, means=1, ar.pattern=mod$ar.pattern, ma.pattern=NULL, Check=FALSE, penalty=0.0)
Marima1$ar.estimates[,,2]
##            [,1]          [,2]
## [1,] -0.9328149  0.0005755421
## [2,] 14.9185703 -0.7883390957
## on estime dans un premier temps le coefficient multiplicateur a1 
coeff<-0
for(k in 2:m){

  conso_j<-(Marima1$ar.estimates[,,2]*(Marima1$ar.pvalues[,,2]<0.05))%*%matrix(SC[(k-1),],ncol=1)
  coeff<-c(coeff, train_sc[k,2]/conso_j[2])
}
coeff<-mean(coeff[-1]) ###a1

On a maintenant en notre possesion la matrice A et le coefficient a1, on peut donc prédire les données tests.

##on fait la prédiciton sur nos 10% de donnée restantes
pred_j <- rep(NA,ntest)
for(j in 1:ntest){
  conso_j <- (Marima1$ar.estimates[,,2]*(Marima1$ar.pvalues[,,2]<0.05))%*%matrix(SC[(m+j-1),], ncol=1)
  pred_j[j] <- coeff*conso_j[2] 
}

errTest <- sqrt(mean((pred_j-Ytest)^2))
errTest
## [1] 241.8545
plot(1:ntest, Ytest, col="red", ylim=c(1700, 3700), xlab="temps", ylab="Consommation electrique (GW)")
lines(1:ntest, pred_j, col=1)
legend( "topleft", legend=c("Vraie consommation", "Consommation prédite"), pch=20, col=c(2,1))
title(main="Prédiction avec modèle MARIMA(1,1)")

Avec ce modèle on trouve une erreur test de 241,8. C’est moins bien qu’avec le modèle AR(5). Cet écart est surement dû au fait qu’avec le modèle MARIMA(1,1) on regarde seulement le passé 1 contre 5 temps pour le AR(5).

4.4. Modélisation par apprentissage supervisé

4.4.1. Modèle linéaire

On va essayer de modéliser la consommation électrique avec un modèle linéaire classique. On réalise une sélection de variables par critère AIC.

mod = lm(ConsoElec ~ ., data = train)
mod_back = stepAIC(mod, direction="backward", trace=F)
Ypred = predict(mod_back, Xtest)

summary(mod_back)
## 
## Call:
## lm(formula = ConsoElec ~ Tmin + Tmax + Tmoy + IsJF + day + Pass1 + 
##     Pass2 + Pass3, data = train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -370.50 -111.84   -5.69  108.62  434.10 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  1.007e+03  9.298e+01  10.825  < 2e-16 ***
## Tmin         4.532e+03  2.217e+03   2.044   0.0414 *  
## Tmax         4.528e+03  2.217e+03   2.042   0.0415 *  
## Tmoy        -9.072e+03  4.434e+03  -2.046   0.0412 *  
## IsJF        -1.760e+02  3.766e+01  -4.673 3.65e-06 ***
## day          9.641e+01  2.057e+01   4.688 3.39e-06 ***
## Pass1        9.716e-01  3.852e-02  25.221  < 2e-16 ***
## Pass2       -4.861e-01  5.133e-02  -9.470  < 2e-16 ***
## Pass3        2.053e-01  3.717e-02   5.523 4.92e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 154.9 on 617 degrees of freedom
## Multiple R-squared:  0.9153, Adjusted R-squared:  0.9142 
## F-statistic: 833.9 on 8 and 617 DF,  p-value: < 2.2e-16
errTest = sqrt(mean((Ytest-Ypred)^2)) 
errTest
## [1] 164.522

Avec ce modèle on trouve une erreur test de 164,2. C’est un meilleur résultat que celui obtenu via série chronologique. On remarque que toutes les variables n’ont pas été selectionnées : dateNum, StockGaz et le passé d’ordre 4 ont été supprimées du modèle. On constate que les variables day et IsJF ainsi que le passé jusqu’au temps 3 sont très siginificatives (pvalue<10e-6). Les variables de température sont également significatives mais légèrement moins (pvalue=0.04).

plot(1:ntest, Ytest, col=2, ylim=c(1700, 3700), xlab="temps", ylab="Consommation electrique (GW)")
lines(1:ntest, Ypred, col=1)
legend( "topleft", legend=c("Vraie consommation", "Consommation prédite"), pch=20, col=c(2,1))
title(main="Prédiction avec modèle linéaire et selection AIC")

4.4.2. Modèle Random Forest

On va maintenant essayer de prédire la consommation électrique grâce à la méthode des forêts aléatoires.

mod = randomForest(ConsoElec ~ ., data=train)
Ypred = as.vector(predict(mod, Xtest))
errTest = sqrt(mean((Ytest-Ypred)^2))
errTest 
## [1] 144.5729
plot(1:ntest, Ytest, col=2, ylim=c(1700, 3700), xlab="temps", ylab="Consommation electrique (GW)")
lines(1:ntest, Ypred, col=1)
legend( "topleft", legend=c("Vraie consommation", "Consommation prédite"), pch=20, col=c(2,1))
title(main="Prédiction avec Random Forest")

Avec les paramètres de bases on trouve une erreur test proche: 144. C’est mieux qu’avec le modèle linéaire.

4.4.3. Modèle SVM

Nous allons essayer de modéliser la consommation électrique journalière avec le modèle des Support Vector Machine. Nous allons comparer ce modèle pour deux noyaux différents : le noyau linéaire et le noyau radial. On utilise ici les 11 variables explicatives.

Noyau linéaire

mod = svm(ConsoElec ~ ., data=train, kernel="linear", scale=TRUE, type="eps-regression")
Ypred = as.vector(predict(mod, Xtest))
errTest = sqrt(mean((Ytest-Ypred)^2))
errTest
## [1] 167.5848
plot(1:ntest, Ytest, col=2, ylim=c(1700, 3700), xlab="temps", ylab="Consommation electrique (GW)")
lines(1:ntest, Ypred, col=1)
legend( "topleft", legend=c("Vraie consommation", "Consommation prédite"), pch=20, col=c(2,1))
title(main="Prédiction avec SVM à noyau linéaire")

Avec ce noyau on trouve une erreur test de 167.6. Le noyau linéaire ne possédant pas de paramêtre il sera impossible de diminuer notre erreur test.

Noyau Radial

mod = svm(ConsoElec ~ ., data=train, kernel="radial", scale=TRUE, type="eps-regression")
Ypred = as.vector(predict(mod, Xtest))
errTest = sqrt(mean((Ytest-Ypred)^2))
errTest
## [1] 172.1189
plot(1:ntest, Ytest, col=2, ylim=c(1700, 3700), xlab="temps", ylab="Consommation electrique (GW)")
lines(1:ntest, Ypred, col=1)
legend( "topleft", legend=c("Vraie consommation", "Consommation prédite"), pch=20, col=c(2,1))
title(main="Prédiction avec SVM à noyau radial")

Avec ce noyau on trouve une erreur test de 172,1. C’est moins bien qu’avec le noyau linéaire mais le noyau radial possède un paramètre : gamma. Nous allons maintenant chercher la valeur de gamma qui minimise notre erreur test.

G = seq(0.001,0.1,by=0.002)
Err = rep(NA,50)
for (i in 1:50){
  mod = svm(ConsoElec ~ ., data=train, kernel="radial", scale=TRUE, type="eps-regression", gamma=G[i])
  Ypred = as.vector(predict(mod, Xtest))
  err = sqrt(mean((Ytest-Ypred)^2))
  Err[i]=err
}
plot(G,Err, xlab="Gamma", ylab="Erreur Test", type="l")
points(G[which.min(Err)],min(Err), col=2, pch=4)

min(Err)             #err = 137 pour un G optimal = 0.02
## [1] 137.0059
G[which.min(Err)]
## [1] 0.019

On a réussi a diminuer l’erreur test ! Avec gamma=0.02 on trouve une erreur test de 137 contre 172 avec le gamma par défault. C’est l’erreur test la plus faible trouvée jusque ici.

Voici le graphique des prédictions pour ce modèle, le meilleur obtenu.

mod = svm(ConsoElec ~ ., data=train, kernel="radial", scale=TRUE, type="eps-regression", gamma=0.02)
Ypred = as.vector(predict(mod, Xtest))
err = sqrt(mean((Ytest-Ypred)^2))
err
## [1] 136.9733
plot(1:ntest, Ytest, col=2, ylim=c(1700, 3700), xlab="temps", ylab="Consommation electrique (GW)")
lines(1:ntest, Ypred, col=1)
legend( "topleft", legend=c("Vraie consommation", "Consommation prédite"), pch=20, col=c(2,1))
title(main="Prédiction avec SVM à noyau radial optimisé")

On constate que le modèle est un bon prédicteur sauf lorsque qu’il y a une forte baisse de la consommation. On remarque qu’il y a une régularité dans ces baisses : 2 points plus bas tout les 5 points. On devine que ces deux points correspondent aux week-end. Peut être qu’en ajoutant une variable binaire qui indique si le jour fait parti du week end, on réussirait a améliorer notre prédicteur.

5. Conclusion

Les analyses descriptives de la première partie de ce projet nous ont permis de mieux comprendre les consommations de gaz et d’électricité en France. On a ainsi pu remarquer que ces deux consommations sont très liées et possèdent la même saisonnalité annuelle. En proportion c’est l’énergie électrique qui est la plus consommée (environ le double de la consommation de gaz). On a également constaté des disparités entre les régions : les régions consommant beaucoup d’énergie sont aussi celle qui consomment le plus de gaz. Ce sont souvent des régions industrielles.

Dans la deuxième partie nous avons essayé de modéliser la consommation électricitrique par plusieurs modèles statistiques. Le modèle MARIMA nous a montré la forte dépendance entre la série des températures et la série des consommations électriques. Le meilleur modèle trouvé est le modèle SVM avec un noyau radial entrainé sur onze variables. Pour améliorer ce modèle il faudrait trouver de nouvelles variables explicatives pertinentes, qui ajusteraient mieux le modèle lorsque la consommation électrique est particulièrement basse ou haute.

6. Sources

Toutes nos données proviennent du site Open Data Réseau Énergie.