Quantcast
Channel: PortailSIG - Systèmes d'Information Géographique
Viewing all 43 articles
Browse latest View live

QGIS et R la main dans la main avec le module processing (Boîte à outils de Traitements)

$
0
0
Niveau Intermédiaire
Logiciels utilisésQGIS
R
Plateforme Windows | Mac | Linux | FreeBSD

Nous avons vu dans QGIS et géologie structurale: création automatique de rosaces directionnelles et de canevas stéréographiques avec PyQGIS comment créer des scripts Python avec interface dans la Boîte à outils de Traitements (Processing). Il est aussi possible de créer des scripts R de manière comparable. Ce sont des fichiers avec une extension .rsx et ils sont situés dans le dossier /.../.qgis2/processing/rscripts. 

Si l’on connait R, ils sont même plus faciles à implémenter.

Principes

Il faut bien entendu que R soit installé et accessible en ligne de commande. Les principes du traitement par le module processing ont été exposés dans QGIS, le cas de l’extension Sextante sur Mac OS X ou comment la connaissance de Python permet de résoudre les problèmes, sans uniquement se lamenter... . Rappelons-les:

  1. Processing utilise le module Python subprocess pour exécuter directement les commandes R (et pas le module rpy2 qui permet de traiter les commandes R en Python);
  2. Cela ce fait par un fichier intermédiaire /.../.qgis2/processing/processing_script.r qui est exécuté directement par R, d’où la nécessité que R soit disponible en ligne de commande pour exécuter R processing_script.r
  3. Si l’on veut utiliser des packages R, il faut donc les installer dans R (ils n’ont rien à voir avec QGIS).

En résumé, le module processing va utiliser directement ce qui est présent dans votre installation de R.

Exemple concret avant les explications: du krigeage « à la ArcGIS »

L’interpolation par krigeage géostatistique est très à la mode chez les utilisateurs d’ArcGIS du fait d'une l’interface conviviale (ou non, voir Du krigeage et du libre et/ou gratuit: inventaire des solutions dans le domaine spatial (logiciels SIGs, applications, librairies, mais sans doute non exhaustif...), même si, pour la plupart, ils ne connaissent que peu de choses sur la géostatistique. Il y a moyen de faire la même chose avec le package automap, vu dans la référence précédente. En pratique, vous lui fournissez des données et il calcule automatiquement le « meilleur » variogramme et sur cette base, il fournit les valeurs de krigeage, de variance et d’erreurs standards en utilisant toutes les arcanes de gstat (rappelons qu'il est toujours possible de décomposer la procédure suivie et de l'adapter si nécessaire).

J’ai été amené à corriger un des scripts qui sont fournis par défaut, Kriging.rsx (voir Bug report #14608: Processing: Kriging rscripts/Kriging.rsx Automap problem and correction )

Au départ, je dispose d’une couche de points avec une valeur z dans la table d’attributs:

Je lance mon script R 

et j’obtiens automatiquement le « meilleur » variogramme

Ainsi que le résultat de krigeage (rasters et points sur une grille)

Explications

Le script n’est pas complexe si l’on connait R

1) création de l’interface

##Basic statistics=group
##showplots
##Layer=vector
##Field=Field Layer
##by=number 0.1
##Outputr=output raster
##Outputv = output vector

showplot indique que je veux afficher les figures (variogramme), Layer et Field correspondent à la couche et au champ traité, by est la pas de la grille utlisée et Outputr et Outputv les couches raster et vecteur en sortie

2) traitement R

library("automap")
library(raster)
Y<-as.factor(Layer[[Field]])
attribut<-as.data.frame(Y)
A<-as.numeric(Y)
for(j in (1:length(levels(Y))))
for(i in 1:dim(attribut)[1]){
if (attribut[i,1]==levels(Y)[j]){
A[i]=j
}
}

J’utilise les packages automap et raster et je crée la grille d’interpolation

coords<-coordinates(Layer)
MinX<-min(coords[,1])
MinY<-min(coords[,2])
MaxX<-max(coords[,1])
MaxY<-max(coords[,2])
Seqx<-seq(MinX, MaxX, by=by)
Seqy<-seq(MinY, MaxY, by=by)
MSeqx<-rep(Seqx, length(Seqy))
MSeqy<-rep(Seqy, length(Seqx))
MSeqy <- sort(MSeqy, decreasing=F)
Grille <- data.frame(X=MSeqx, Y=MSeqy)
coordinates(Grille)=c("X","Y")
gridded(Grille)<-TRUE
Mesure<- data.frame(LON=coords[,1], LAT=coords[,2],A)
coordinates(Mesure)<-c("LON","LAT")
variogram = autofitVariogram(A~1, Mesure)
plot(variogram)
kriging_result = autoKrige(A~1, Mesure, Grille,model=c("Cir","Lin","Bes","Wav","Hol","Leg","Per","Pen","Mat","Exc","Spl","Ste"))
prediction = raster(kriging_result$krige_output)

3) affichage des couches dans QGIS

L’affichage est automatique (prediction est de la classe RasterLayer de raster) et je transforme les points de la grille avec les valeurs en SpatialPointsDataFrame)

Outputr = prediction
Outputv = SpatialPointsDataFrame(kriging_result$krige_output, as.data.frame(kriging_result$krige_output))

Pour aller plus loin

Je vais vous présenter ici quelques traitements supplémentaires sur le raster obtenu par krigeage avec quelques lignes de script pour vous donner l’envie de continuer et d'aller plus loin ...

Je peux représenter mon raster en 3D (pour le plaisir)

Le script

##Basic statistics=group
##showplots
##Layer=raster
dem = Layer
dem_df = as.data.frame(dem, xy = TRUE)
dem_df = dem_df[complete.cases(dem_df), ]
colnames(dem_df)[3] = "z"
x_range = diff(range(dem_df$x, na.rm = TRUE))
y_range = diff(range(dem_df$y, na.rm = TRUE))
z_range = diff(range(dem_df$z, na.rm = TRUE))
library(lattice)
wireframe(z ~ x * y, data = dem_df,drape = TRUE, colorkey = TRUE,col.regions = terrain.colors(100),screen = list(z = 165, x = -60, y = 0),aspect = c(y_range/x_range, 7*(z_range/x_range)),zoom = 1.1)

Extraire les courbes de niveau

 

Le script:

##Basic statistics=group
##showplots
##layer=raster
##Output = output vector
library(raster)
dem <- layer[[1]]
dem_contour = rasterToContour(dem, levels = seq(10, 600, 10))
plot(dem)
plot(dem_contour, add=TRUE)
Output=dem_contour

ou créer un relief ombragé (hillshade)

Le script

##Basic statistics=group
##showplots
##test=raster
##Output = output raster
library(raster)
slope = terrain(test,"slope")
aspect= terrain(test,"aspect")
#plot(stack(slope,aspect))
hill = hillShade(slope,aspect,20,235)
plot(hill)
Output=hill

Conclusions préliminaires

Il y aurait bien sur moyen d’améliorer tous les scripts comme dans le calcul des courbes de niveau où je fixe les niveaux ou dans le relief ombragé où je fixe l’angle et la direction à l’aide de l’interface (choix des valeurs dans l'interface).

Où trouver des scripts ?

La Boîte à outils dispose d’un script pour créer un nouveau script (R scripts/Outils/Créer un nouveau script) et un autre pour installer les scripts disponibles à QGIS-Processing/rscripts (R scripts/Outils/Obtenir des scripts)

La liste est disponible à list.txt. D’autres scripts spécialisés en géostatistique sont disponibles à INTA SUELOS: QGIS_R_Geostatistics et pour le reste, il faut en trouver ou les créer....

Conclusions

Si vous connaissez R et QGIS, j’espère que ces petits exemples vous donneront des idées pour continuer et aller plus loin. C’est dans l’ensemble beaucoup plus facile qu’avec Python puisque les principaux packages R spatiaux ont déjà tout ce qu’il faut, sans devoir programmer des fonctions équivalentes.

Pour moi, c’est le plus beau traitement du module processing car il ouvre, en particulier, la voie vers des traitements non disponibles dans QGIS ou plus faciles que les traitements des autres Fournisseurs (voir R - spatial : principes généraux et exemples de représentations cartographiques brutes (sans traitements (geo)statistiques) sur le Portail).

Le plus difficile est cependant de convertir les résultats en objets spatiaux R qui sont alors directement affichables dans QGIS. Tous les traitements peuvent être effectués préalablement avec RStudio et si vous observez les scripts disponibles, beaucoup ne font qu'afficher des figures et/ou des résultats comme l'Analyse en Composantes Principales, l'Analyse des Correspondances ou le calcul de diverses valeurs).

Tous les traitements on été faits sur Mac OS X et/ou Linux avec diverses versions de R (3.x.x)

Annexe

Dans le cas du krigeage, le script résultant ( /.../.qgis2/processing/processing_script.r) qui est effectué par R est

> options("repos"="http://cran.at.r-project.org/")
> tryCatch(find.package("automap"), error=function(e) install.packages("automap", dependencies=TRUE))
[1] "/Library/Frameworks/R.framework/Versions/3.2/Resources/library/automap"> tryCatch(find.package("raster"), error=function(e) install.packages("raster", dependencies=TRUE))
[1] "/Library/Frameworks/R.framework/Versions/3.2/Resources/library/raster"> tryCatch(find.package("rgdal"), error=function(e) install.packages("rgdal", dependencies=TRUE))
[1] "/Library/Frameworks/R.framework/Versions/3.2/Resources/library/rgdal"> tryCatch(find.package("raster"), error=function(e) install.packages("raster", dependencies=TRUE))
[1] "/Library/Frameworks/R.framework/Versions/3.2/Resources/library/raster"> library("raster")
Le chargement a n'ecessit'e le package : sp
> library("rgdal")
rgdal: version: 1.0-4, (SVN revision 548)
 Geospatial Data Abstraction Library extensions to R successfully loaded
 Loaded GDAL runtime: GDAL 1.11.2, released 2015/02/10
 Path to GDAL shared files: /Library/Frameworks/R.framework/Versions/3.2/Resources/library/rgdal/gdal
 Loaded PROJ.4 runtime: Rel. 4.9.1, 04 March 2015, [PJ_VERSION: 491]
 Path to PROJ.4 shared files: /Library/Frameworks/R.framework/Versions/3.2/Resources/library/rgdal/proj
 Linking to sp version: 1.1-1 
> Layer = readOGR("/Users/Shared/telechargement/11_04_16/aricle_portail",layer="points_shape")
OGR data source with driver: ESRI Shapefile 
Source: "/Users/Shared/telechargement/11_04_16/article_portail", layer: "points_shape"
with 606 features
It has 2 fields
> Field="VALUE"> by=10
> png("/var/folders/k9/7s3l5fvd5g1dy31b6sms18fr0000gn/T/processing909608e607164409bac78043816115a7/d6d96219f7af4d149e617db7843ff003/RPLOTS.html.png")
> library("automap")
> library(raster)
> Y<-as.factor(Layer[[Field]])
> attribut<-as.data.frame(Y)
> A<-as.numeric(Y)
> for(j in (1:length(levels(Y))))
+ for(i in 1:dim(attribut)[1]){
+ if (attribut[i,1]==levels(Y)[j]){
+ A[i]=j
+ }
+ }
> coords<-coordinates(Layer)
> MinX<-min(coords[,1])
> MinY<-min(coords[,2])
> MaxX<-max(coords[,1])
> MaxY<-max(coords[,2])
> Seqx<-seq(MinX, MaxX, by=by)
> Seqy<-seq(MinY, MaxY, by=by)
> MSeqx<-rep(Seqx, length(Seqy))
> MSeqy<-rep(Seqy, length(Seqx))
> MSeqy <- sort(MSeqy, decreasing=F)
> Grille <- data.frame(X=MSeqx, Y=MSeqy)
> coordinates(Grille)=c("X","Y")
> gridded(Grille)<-TRUE
> Mesure<- data.frame(LON=coords[,1], LAT=coords[,2],A)
> coordinates(Mesure)<-c("LON","LAT")
> variogram = autofitVariogram(A~1, Mesure)
> plot(variogram)
> kriging_result = autoKrige(A~1, Mesure, Grille,model=c("Cir","Lin","Bes","Wav","Hol","Leg","Per","Pen","Mat","Exc","Spl","Ste"))
[using ordinary kriging]
Warning message:
In autofitVariogram(formula, data_variogram, model = model, kappa = kappa,  :
  Some models where removed for being either NULL or having a negative sill/range/nugget, 
	set verbose == TRUE for more information
> prediction = raster(kriging_result$krige_output)
> Output<-prediction
> Output2<-SpatialPointsDataFrame(kriging_result$krige_output, as.data.frame(kriging_result$krige_output))
> writeRaster(Output,"/var/folders/k9/7s3l5fvd5g1dy31b6sms18fr0000gn/T/processing909608e607164409bac78043816115a7/2f7b4b596595455db7d3298c6a24ff25/Output.tif", overwrite=TRUE)
> writeOGR(Output2 ,"/var/folders/k9/7s3l5fvd5g1dy31b6sms18fr0000gn/T/processing909608e607164409bac78043816115a7/5bf8086827ee48bda05c5b503554c3a3/Output2.shp","Output2", driver="ESRI Shapefile")
> dev.off()
null device

et dans le fichier qui recense tous les algorithmes effectués (/.../.qgis2/processing/processing.log).

processing.runalg("r:monkrigenew2","/Users/Shared/telechargement/11_04_16/article_portail/points_shape.shp","VALUE",10,None,None,None)

 

Site officiel : Use R scripts in Processing
Site officiel :CRAN Task View: Mapping tools and services


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France


QGIS: des Ellipses de Déviation Standard (SDE), un plugin, « Standard Deviational Ellipse », des scripts R (processing) et Python et une approche critique...

$
0
0
Niveau Intermédiaire
Logiciels utilisésQGIS
R
Python
QGIS Plugin: Standard Deviational Ellipse
Plateforme Windows | Mac | Linux | FreeBSD

Parmi les commandes d’ArcGIS qui manquaient dans QGIS, figurait l’ Ellipse de Déviation Standard (Standard Deviational Ellipse ou SDE). Ce n’est plus le cas actuellement puisque c’était déjà possible de le faire avec des scripts R dans la Boîte à outils de Traitements (Processing) avec des packages dédiés et le nouveau plugin Standard Deviational Ellipse. Ses premiers résultats ont suscité des réactions de ma part (problem with the rsd values compared with the aspace or phonTools R packages ) qui ont amené Håvard Tveite à améliorer et compléter son plugin. 

Je vais essayer ici d’expliquer le pourquoi des choses, de manière visuelle et sans trop de statistiques, car ce concept a sérieusement été remis en cause récemment par Gong, J (2002), Clarifying the standard deviational ellipse. Geographical Analysis 34, 155–167.(pdf) et Wang B, Shi W, Miao Z (2015) Confidence Analysis of Standard Deviational Ellipse and Its Extension into Higher Dimensional Euclidean Space. PLoS ONE 10(3): e0118537. doi:10.1371/journal.pone.0118537,

C’est quoi ?

Si la dispersion d’une variable en une dimension est facile à calculer et à visualiser (histogrammes, etc.), c’est moins le cas pour des distributions bivariées (ou multivariées). La solution classique est de calculer séparément les écarts types de chaque variable et de représenter leur distribution par des ellipses (ou des ellipsoïdes) dont les axes sont ces écarts types. La méthode permet aussi de mesurer une tendance directionnelle (angle de l’ellipse).

Dans la réalité ce sont en fait des isocontours d’une surface, similaires à des courbes de niveau.

Et le graphique réalisé avec le package R psych  qui illustre le fait que cette démarche n'est pas limitée au domaine géospatial, loin de là.

Cette ellipse est donc théoriquement déterminée par trois mesures :

  1. calcul du point « moyen » du nuage des points (cela peut être le centroïde, le barycentre ...)
  2. calcul de l’angle de rotation de l’ellipse (orientation)
  3. calcul de la dispersion par les écarts-types des coordonnées x et y à partir du  point « moyen ». Ceux-ci vont définir les axes de l’ellipse (c’est pourquoi ESRI nomme aussi l’ellipse  « ellipse de l’écart type ».

Toutes les combinaisons sont possibles: affecter une masse (valeur) à chaque point, utiliser des moyennes pondérées, des médianes ou la covariance des données (pondérée ou non). Le calcul peut aussi varier en fonction de la répartition des points et il est possible d’utiliser soit la distance euclidienne soit d'autres distances ( comme la Distance de Mahalanobis ou la Distance de Manhattan).        

Je n’illustrerai ici que le cas de valeurs non pondérées (le reste n'est qu'un facteur d'échelle et de rotation).

Statistisquement parlant, l'objectif recherché est d'obtenir les valeurs de  la classique Règle 68-95-99.7 (3 sigma) empirique ( graphique réalisé avec Python et le module matplotlib) dans le cas d'une distribution univariée, et pas bivariée (Wang et al. (2015) contestent ces valeurs et en proposent des nouvelles pour les distributions multivariées, voir plus bas)

Un peu d’histoire ou comment une ellipse n’est pas une ellipse, mais...

Proposée dès 1926 par Lefever (Measuring Geographic Concentration by Means of the Standard Deviational Ellipse. American Journal of Sociology. 1926; 32(1):88–94), il a vite été démontré que le résultat n’était pas une ellipse (Furfey PH. A Note on Lefever’s "Standard Deviational Ellipse". American Journal of Sociology. 1927; 33(1):94–8), mais à ce qui est nommé actuellement une « Courbe de Déviation Standard ».  Depuis lors, bien évidemment, de nombreuses corrections ont été apportées pour tenter de résoudre le problème avec des résultats variables, mais la controverse continue... 

Il faut noter que le concept est né sous l’hypothèse que les variables observées suivaient la loi normale, mais que cette condition n’est plus indispensable avec les algorithmes utilisés actuellement. Elle est théoriquement dérivée de la distribution bivariée (ou bidimensionnelle)

A l’heure actuelle, dans le monde geospatial, ne subsistent que deux grands algorithmes et des solutions intermédiaires:

  • celui proposé par Yuill, R. S.(1971) dans The Standard Deviational Ellipse: An Updated Tool for Spatial Description, Geografiska Annaler 53B(1),28-39) qui est une énième adaptation réussie de la solution de Lefever, avec de nombreuses variantes postérieures;
  • celui implanté  dans le  logiciel CrimeStat III (Ned Levine, 2010, A Spatial Statistics Program for the Analysis of Crime Incident Locations (version 3.3).  Ned Levine & Associates, Houston, TX.; National Institute of Justice, Washington, DC.), spécialement dans la note 4 de Part II: Spatial Description (pdf).

Wang et al. (2015) ont récemment proposé un nouvel algorithme basé sur la décomposition spectrale de la covariance, plus exact selon eux.

Pourtant les géomaticiens continuent à utiliser cette méthode dans de nombreux domaines, sans se préoccuper ni de l’algorithme utilisé, ni de la signification statistique réelle du résultat.

Préambules statistiques: l’éllipse de covariance

Au départ, pour toute distribution bivariée, il est possible de calculer l’ellipse de covariance (ou ellipse de dispersion, ou ellipse des erreurs, ou...). Une matrice de covariance peut en effet être décomposée en (voir A geometric interpretation of the covariance matrix, par example):

  • vecteurs propres qui permettent de calculer les 2 angles de l’ellipse. Le premier vecteur (violet) est la direction dans laquelle les données varient le plus, le second (bleu) est orthogonal au premier.
  • valeurs propres qui sont les 2 diamètres de l’ellipse. Les rayons sont les racines carrées des valeurs propres et correspondent aux écarts types modifiés (translation + rotation) de x et y, σx' et σy’

La courbe limite de l’ellipse (Δχ2) correspond à un contour de probabilité de 1 écart type avec une probabilité de ± 39,35 % (c'est le genre de phrase qui nécessiterait de très nombreuses explications qu’il n’est pas possible de développer ici et je vous renvoie à vos manuels de statistiques).

 

Un script Python qui permet de le faire est disponible à par_covar.py. Le même résultat peut être obtenu avec les packages R ellipse:ellipse, siar: standard.ellipse , PhonTools: sdellipse, mvdalab: ellipse.mvdalab et autres.

Résultats:

Centre.xCentre.yσxσxAngle (en degrés par rapport au N)AireExcentricité
320,66215.6645,67112.8677.1216194.140,9144

En multipliant les valeurs obtenues par des facteurs d’échelle appropriés (loi normale, voir How to draw a covariance error ellipse?), il est donc possible de calculer des ellipses d’équiprobabilités. Ceci sera fait avec le package R car: dataEllipse()qui permet de superposer les contours d'une distributionbivariée normale de deux variables sur un semis de points. Un script R pour tracer ces ellipses dans la Boîte à Outils est disponible à car_ellipses.rsx.

Et il est possible de constater que l’ellipse de covariance correspond bien à une probabilité de ± 39,35 %.


Ces ellipses vont nous servir de repère pour superposer les différentes ellipses calculées dans la suite. Elles ne sont en effet que des adaptions et c'est pourquoi les coordonnées du point « moyen »,  les angles et l’excentricité restent les mêmes pour toutes les solutions.

L'interface du plugin

L’ellipse de Yuill

Les calculs d’estimation des rayons/écarts types (standard deviation) sont:

Elle est calculée avec l'option Yulldu plugin.

Résultats (pour rappel, les valeurs des angles et de l’excentricité sont les mêmes que celles obtenues pour l’ellipse de covariance): 

σxσyAire
44,12109,0315114,53

 C’est aussi l’ellipse calculée par: 

L’ellipse de CrimeStats III

Ned Levine, dans le cas de CrimeStats III, démontre dans la note 4 (voir référence plus haut) qu’avec les traitements précédents, l’ellipse résultante est trop petite (d’un facteur racine carrée de 2) pour deux raisons statistiques, le calcul des axes et le fait que les autres solutions ne sont pas correctes au niveau des degrés de liberté (n-2 au lieu de n). Les résultats sont beaucoup plus grands qu’avec l’algorithme de Yull.

Elle peut être calculée avec le package R aspace: calc_sde()  (un script R pour la Boîte à Outils de Traitement (Processing) est disponible à aspace_SDE.rsx ) ou avec l'option CrimeStat du plugin.

Résultats: 

σxσyAire
67,03165,6334879.69

 

La solution d’ArcGIS  10.x

Entre la version 9.x et la version 10.x, ArcGIS a modifié son algorithme en utilisant une des corrections de CrimeStats (racine carrée de 2) sans autre explication ni justification (ArcMap 10.x: Fonctionnement de Directional Distribution (Standard Deviational Ellipse))

Elle est calculée avec les options Yuill + sqrt(2)correction  du plugin et avec divers packages R (ellipse pour 1 écart type ici)

 

Résultats:

σxσyAire
62,40154,19630229.06

Le plugin offre aussi la possibilité d’utiliser la deuxième correction de CrimeStats seule avec l’option Yull + DF correction.
 

L'ellispse de Wang et al. (2015)

Le  nouvel algorithme proposé par Wang et al (2015) se base aussi sur la matrice de covariance, mais en modifiant le cacul  (1/n au lieu de 1/n-1 pour les connaisseurs). Ensuite le traitement est le même, voir le script ellipse_wang.py.

Résultats (c'est le même que celui de Yuill): 

σxσyAire
44,12109,0315114,53

L'important est qu'ils calculent les valeurs de  la classique règle empirique des 3 sigmas pour des distributions multivariées. Ce sont les valeurs approximatives de 39,35%, 84,47% et 98,89% et non 68,27%, 95,45% et 99.73% comme le stipule ESRI.

Conclusions

Que d’ellipses, me direz-vous, mais elles ne se différencient que par les résultats du calcul des axes de l’ellipse et donc de l’aire. Du  fait de la signification statistique des résultats, c’est là où naissent les problèmes et les controverses.

  • dans le cas d’ESRI, il est stipulé que « un polygone d’ellipse d’un écart type recouvre approximativement 68 pour cent des entités» (que ce soit avec les résultats de la version 9.x ou ceux la version 10.x) , en supposant que variables concernées suivent une distribution spatiale normale;
  • Cette conclusion est remise en question par Wang et all (2015) qui opinent que les conclusions d’ESRI correspondent à la classique Règle 68-95-99.7 (3 sigma) des distributions univariées normales, mais pas pour des  distributions bivariées (ou multivariées). Ils proposent donc des nouvelles valeurs: "Obviously, such confusing interpretation may mislead the GIS users to believe the univariate 3-sigma rule remains valid in two-dimensional Euclidean space, or even higher dimensions". Ils calculent des valeurs correctes selon eux;
  • la solution de CrimeStat s'éloigne des précédentes;
  • les ellipses basées sur la seule matrice de covariance sont théoriquement les plus correctes.

Alors quelle est la bonne ellipse ? Le nouveau plugin permet de toutes les figurer (hormis les eclipses de covariance et les axes) et à vous choisir la bonne...

Pour ceux qui ne s’intéressent qu’aux orientations (angles et excentricité), comme moi, elles sont toutes bonnes, mais pour ceux qui traitent les surfaces et leurs interprétations statistiques, c’est autre chose... Il serait nécessaire en tout cas de toujours spécifier la solution retenue comme dans la majorités des articles scientifiques.

Et oui, il n'y a donc pas une seule Elipse de Deviation Standard comme pourraient le penser les utisateurs d'ArcGIS avec leur commande standardisée. Je remercie aussi Håvard Tveite d'avoir tenu compte de mes remarques (j'utilise depuis longtemps ce genre d'ellipse dans un contexte non spatial).

En route pour les ellipses de covariance 3D et les ellipses de Deviation Standard bayesiennes.

Annexe:

Sommaire des résultats obtenus avec les points utilisés

Pour rappel, les coordonnées du point « moyen », les angles et l'excentricité sont les mêmes pour toutes les solutions (77,12° par rapport au N et 0,9144).

solutionσxσyAire3σ: σ1 spécifié
Plugin: Yuill44,13109,0315114,53variable
Arcview 3.3 (ArcMap 9.x)44,13109,0315114,5368,23%
Wang et al (2015)44,13109,0315114,5339,35%
R avec le package siar45,67112,8616194.14 
Covariance45,67112,8616194.1439,35%
Plugin:Yuill avec l'option DF47,40117,1217439.85 
ArcMap 10.x62,40154,2030229.0668,23%
Plugin:Yuill avec l'option sqrt(2)62,40154,2030229.06 
R avec le package aspace67,03165,6334879.69 
Plugin avec l'option "CrimeStat"67,03165,6334879.69 

 

Tous les traitements présentés ont été effectués sur Mac OS X ou Linux avec QGIS 2.14.x, R version 3.x et Python 2.7.x. ArcGIS 10.1 sur Windows a été utilisé pour obtenir des ellipses pour comparer les résultats. LaTeX a été utilisé pour les équations.

Site officiel : Calculating and visualizing standard deviations in two dimensions


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

Python: GeoPandas ou le Pandas spatial

$
0
0
Niveau Intermédiaire
Logiciels utilisésPython 2.7.x ou Python 3.x
Pandas (module Python)
GeoPandas (module Python)
Plateforme Windows | Mac | Linux | FreeBSD

Dans le monde géomatique, il y a ceux qui préfèrent les boutons, les menus, les pointer-cliquer, ou glisser-déplacer, le tout dans une jolie interface graphique (les SIGs quoi) et ceux qui préfèrent autre chose, comme la programmation directe et moi, malheureusement ou heureusement, je suis tombé dedans quand j’étais petit...

Parmi les nombreux modules Python existants, il y en a un qui me séduit de plus en plus, c’est GeoPandas (vu sommairement dans Python à finalité géospatiale: pourquoi débuter avec le module ogr alors qu’il existe des alternatives plus simples et plus didactiques ?) de Kelsey Jordahl, car il permet de pratiquement tout faire (attributs, géométries, jointures, etc.). Comme son nom l’indique, il est intrinsèquement lié à Pandas, mais force est de constater que la plupart des géomaticiens qui utilisent Python dans leurs logiciels SIG ne connaissent pas ce dernier module et pourtant...

Créé il y a relativement peu de temps, Pandas connaît un développement accéléré au point de devenir aujourd’hui l’un des principaux modules Python pour traiter les données de tout type ("Big Data", "Machine Learning", scientifique, économique, sociologique ...). Il s’appuie sur NumPy et permet de manipuler de grands volumes de données, structurées de manière simple et intuitive sous forme de tableaux.

Les exemples/tutoriels/videos sur Pandas sont innombrables sur Internet ainsi que des livres, aussi je me limiterai ici à une simple présentation de la structure des données pour passer ensuite à GeoPandas.

Pandas

Figure modifiée de Cheat Sheet: The pandas DataFrame Object ( Dr. Stephen Sauchi Le)

Le type de base est la Serie (Pandas Series) qui peut être considérée comme un tableau de données à une dimension (grossièrement équivalent à une liste). L’extension en deux dimensions est un DataFrame (Pandas DataFrame). C’est un simple tableau dont les séries constituent les colonnes. Les index sont:

- index de colonne (df.columns()): les noms des colonnes (généralement des chaînes de caractères);
- index de ligne (df.index()): nombres entiers (numéros des lignes), chaînes de caractères, DatetimeIndex ou PeriodIndex pour les séries temporelles (time series).

C’est l’équivalent d’une matrice NumPy dont chaque colonne (Serie) est de même type (n’importe quel type de données, objets Python, nombres, dates, textes, binaires) et qui peut contenir des valeurs manquantes. C’est aussi l’équivalent des Data Frame de R (il y a aussi moyen de travailler avec des données selon 3 dimensions ou plus avec les Pandas Panels, mais le sujet ne sera pas abordé ici).

Cette manière de représenter les données oblige à changer quelque peu les habitudes de programmation. On travaille sur des colonnes, des lignes ou des cellules au lieu de parcourir l’ensemble des données avec des boucles et/ou des boucles imbriquées. Par exemple, pour modifier toutes les valeurs d'une colonne on utilisera pandas.Series.map(fonction).

Pandas permet nativement:

Cette librairie n'est pas isolée, car un grand nombre de modules se sont développes autour de sa structure pour offrir des traitements supplémentaires. C'est l'Ecosystème Pandas. Il est de plus, tout à fait possible d'utiliser Pandas avec une multitude d'autres librairies Python.  Autant dire que ses possibilités deviennent presque infinies, d'où son succès actuel.

"Essentially, you can think of pandas as a Numpy "on steroids" with a focus on real-world data. It encapsulates and wraps around much of the low-level functionality of numpy, scipy and matplotlib, exposing it to the end-user in a much friendlier way." (tiré de Brief introduction to the Python stack for scientific computing)

GeoPandas

Figure réalisée avec GeoPandas (Delaunay, Voronoï) en s'inspirant de Origami Panda Print

Le monde géospatial ne pouvait pas rester en reste d'où la création de GeoPandas qui est une extension geospatiale de Pandas. Elle rajoute les GeoSeries (géométries, en bleu clair) et les GeoDataFrames ou DataFrames avec une colonne GeoSerie aux structures de Pandas (GeoPandas: Data Structures). Le résultat final est une structure où tous les traitements de Pandas sont possibles sur les colonnes oranges (attributs, Panda Series) et les traitements géomatiques sur la colonne bleu clair (geospatial, Geopandas GeoSerie). En pratique une GeoSerie est constituée de géométriesShapely.  Une ligne d'un GeoDataFrame contient donc les attributs et la géométrie d'un élément.

GeoPandas utilise donc Shapely (pour les traitements géométriques), mais aussi Fiona (pour l'importation/exportation des données géospatiales), pyproj (pour le traitement des projections), éventuellement Rtree (facultatif, pour implémenter les index spatiaux) et matplotlib et descartes pour les graphismes. Hormis Rtree, tous ces modules doivent être préalablement installés, Pandas y compris, bien évidemment. Le tout est particulièrement bien adapté aux Jupyter/IPython notebooks (voir Les notebooks IPython comme outil de travail dans le domaine géospatial ou le plaisir de partager, de collaborer et de communiquer) sur le Portail).

Quelques principes

Ouvrir et sauvegarder un fichier shapefile (ou autre) est enfantin et a été vu dans Python à finalité géospatiale: pourquoi débuter avec le module ogr alors qu'il existe des alternatives plus simples et plus didactiques ?  

Le fichier shapefile d'origine

  

Transformation en GeoDataFrame

import geopandas as gpd# Création d'un GeoDataFramegdf = gpd.read_file("test_portail.shp")gdf.head()

Quelques manipulation avec Pandas

À ce stade il est possible de modifier la table (ajout/suppression d'un champ par exemple), d'appliquer toutes les fonctions de Pandas pour ajouter/manipuler les attributs, de traiter l'ensemble d'une colonne (somme, moyenne, ...) ou de plusieurs colonnes (corrélations, ...)

  • Ajout d'un nouveau champ, soit avec une fonction de Pandas soit avec une fonction de GeoPandas (ici):
gdf["surface"] = gdf['geometry'].areagdf.head(2)# 2 premières lignes

  • Quelques statistiques basiques:
gdf[['Zn','Pb']].describe().transpose()

 

gdf[['Zn','Pb']].cov()# covariance

 

À tout moment il y a la possibilité de sauver le GeoDataFrame résultant

gdf.to_file("resultat.shp")

Je vous laisse découvrir Pandas pour tous les traitements possibles (une multitude), car ce qui nous intéresse ici ce sont les traitements géomatiques.

Quelques manipulations géométriques avec GeoPandas

Je vais vous montrer ici comment convertir ce GeoDataFrame en 4 fichiers shapefiles tout en gardant les attributs en illustrant les manières de procéder (je repars du fichier original):

  • la transformation de polygones en points se fera avec la fonction centroid de GeoPandas;
  • la transformation de polygones en lignes se fera avec Shapely et la commande map de GeoPandas et les fonctions lambda puisque la commande native n'existe pas et qu'il est nécessaire d'appliquer la fonction à chaque élément de la GeoSerie;
  • la transformation de lignes en points se fera de la même manière, mais le résultat aura évidemment plus de lignes.

Je vais sans doute être un peu long pour certains, mais il faut bien comprendre ici les manipulations géométriques sous peine d’être perdu dans la suite.

  • Transformation du GeoDataFrame en fichier shapefile de type point
# copie du GeoDataFrame originalpoints = gdf.copy()points.geometry = points['geometry'].centroid # même crs que l'originalpoints.crs = gdf.crs# sauvetage du fichier shapefilepoints.to_file('centroid_portail.shp')points.head()

  • Transformation du GeoDataFrame en shapefile de type polylignes. Ici il est nécessaire d'utiliser une fonction extérieure puis d'utiliser la fonction map
from shapely.geometryimport LineString# la fonction de transformation de polygones en polyligneslinear = gdf.copy()def convert(geom):    return LineString(list(geom.exterior.coords))# application de la fonction à toutes les lignes de la colonne geometrylinear.geometry= linear.geometry.map(convert, linear['geometry'])# ou directement avec les fonctions lambdaslinear.geometry= linear.geometry.map(lambda x: LineString(list(x.exterior.coords)))linear.crs = gdf.crslinear.head()

  • Extraction des noeuds des polylignes: ici il faut extraire les noeuds des lignes (LineString) de linear et créer un nouveau GeoDataFrame
col = linear.columns.tolist()[0:4]print col[u'POLY_NUM_B', u'Pb', u'Zn', 'geometry']# création d'un nouveau GeoDataFrame avec ces colonnesnoeuds = gpd.GeoDataFrame(columns=col)# extraction des noeuds à partir des lignes présentes dans linear et des valeurs d'attributs et intégration dans le nouveau GeoDataFramefor index, row in linear.iterrows():    for j inlist(row['geometry'].coords):         noeuds = noeuds.append({'POLY_NUM_B': int(row['POLY_NUM_B']), 'Pb':row['Pb'],'Zn':row['Zn'], 'geometry':Point(j)},ignore_index=True)noeuds.crs = {'init' :'epsg:31370'}# autre manière de spécifier les coordonnées

Comme je veux être sûr que mes valeurs soient de type entier, je le spécifie:

noeuds['POLY_NUM_B'] = noeuds['POLY_NUM_B'].astype('int')noeuds['Pb'] = noeuds['Pb'].astype('int')noeuds['Zn'] = noeuds['Zn'].astype('int')# affichage des éléments 8 à 16noeuds[8:16]

  • je crée maintenant un buffer autour de ces points
buffer = df.copy()buffer.geometry = buffer['geometry'].buffer(5)etc.
  • Résultat

En pratique

1) Une des plus belles illustrations de la puissance de GeoPandas m'a été apportée suite à une de mes réponses sur GIS Stack Exchange. La question posée était "More Efficient Spatial join in Python without QGIS, ArcGIS, PostGIS, etc". J'ai répondu avec une solution classique de boucles imbriquées en parcourant les 2 fichiers shapefiles (ma réponse) à comparer avec la réponse avec GeoPandas qui illustre les jointures spatiales (GeoPandas: spatial joins)

points = geopandas.GeoDataFrame.from_file('points.shp')# or geojson etcpolys = geopandas.GeoDataFrame.from_file('polys.shp')pointInPoly = gpd.sjoin(points, polys, how='left',op='within')

Et vous obtiendrez un GeoDataFrame avec tous les points de 'points.shp' qui sont dans les polygones de 'polys.shp'.

2) Transformer un fichier csv ou Excel en GeoDataFrame est très facile:

import pandas as pd# transformation du fichier csv en Pandas DataFramepoints = pd.read_csv("strati.csv")#transformation en GeoDataFramefrom shapely.geometryimport Pointgeometrie = [Point(xy)for xy inzip(points.x, points.y)]# colonnes du DataFrame résultantspoints = gpd.GeoDataFrame(points,geometry=geometrie)# ou directementpoints = pd.read_csv("strati.csv")points['geometry'] = points.apply(lambda p: Point(p.x, p.y), axis=1)

3) fusionner 2 ou plusieurs shapefiles, quelque soit le nombre et le type de champs, pourvu que le type de géométrie soit le même:

a = gpd.GeoDataFrame.from_file("shape1.shp")b = gpd.GeoDataFrame.from_file("shape2.shp")import pandas as pdresult = pd.concat([a,b],axis=0)result.to_file("merged.shp")

4) un cas pratique auquel j'ai été confronté. J'ai un fichier shapefile reprenant les affleurements géologiques d'une carte (lignes) et un fichier csv avec leurs descriptions et je voudrai extraire les valeurs de schistosité de chacun d'entre eux pour créer un nouveau fichier shapefile de type point:

import geopandas as gpdimport pandas as pdimportreaffl = gpd.GeoDataFrame.from_file("aff.shp")affl.head(2)

 

Traitement du fichier csv

affleurs = "schisto.csv"# je choisis les colonnes que je veux importeru_cols = ['IDENT','desc']affleurs = pd.read_csv(aff, sep='\t', names=u_cols)regex = re.compile("S1.\d{1,2}\W*\d{1,3}")# je saute la partie du script pour chercher le regex et le résultat estaffleurs.head(2)# les 2 premiers éléments

 

Je joins les 2 DataFrames sur base du champ 'IDENT' et je modifie la géométrie

df2 = gpd.GeoDataFrame( pd.merge(affl, affleurs, on='IDENT'))df2['geometry'] = df2['geometry'].representative_point()df2.head(2)

 

Résultat:

4) Pour le reste je vous renvoie (le choix n'est pas exhaustif):

- aux exemples de Geopandas (notebooks Jupyter/IPython):

- à mes réponses ou à celles d'autres personnes sur GIS Stack Exchange ou Stack Overflow:

- à des exemples de combinaisons avec d'autres librairies:

Conclusions

Contrairement à la plupart des tutoriels, je n'ai pas développé ici les fonctionnalités graphiques de la librairie puisque j'utilise un logiciel SIG pour visualiser les résultats. GeoPandas est encore jeune, mais ses possibilités sont déjà énormes, si vous aimez programmer, bien entendu. Dans mon cas, les traitements sont beaucoup plus rapides qu'avec QGIS pour ce que je veux faire (je ne dois pas utiliser PyQGIS, PyGRASS et leurs contraintes pour travailler sur des fichiers shapefiles. Tant que la colonne geometry d'un GeoDataFrame demeure inchangée, il y a moyen de faire ce que l'on veut avec les attributs).

Du fait que la librairie utilise Fiona, elle est sujette aux mêmes restrictions (pas d'accès direct aux bases de données spatiales par exemple), mais il est possible de contourner cet aspect avec d'autres modules plus adéquats. De part sa jeunesse elle est encore sujette à des erreurs non rédhibitoires (voir GeoPandas commits).

Signalons aussi OSMnx, pour le traitement des couches OpenStreetMap avec Pandas et  GeoRasters qui se veut l'équivalent de GeoPandas pour les rasters.

Tous les traitements présentés ont été effectués sur Mac OS X, Linux ou Windows avec les versions 0.1.x, 0.2.0 et 0.2.1 de GeoPandas et les versions 0.18.x puis 0.19.0 de Pandas.

Site officiel :Using geopandas on Windows
Site officiel :Pandas DataFrame Notes (PDF)
Site officiel :DataFrame et Matrice
Site officiel :DataFrame et Graphes
Site officiel :Le pandas c’est bon, mangez en
Site officiel :Introduction à Pandas


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

Viewing all 43 articles
Browse latest View live