Coupler Python et R : Rpy2¶

< Page précédente | Sommaire | | Retour Site | Page suivante >

rpy2 est un outil qui offre une passerelle entre les langages de programmation Python et R. Il permet aux utilisateurs d'exécuter du code R directement depuis Python, offrant ainsi la possibilité de tirer parti des capacités statistiques et graphiques de R dans un environnement Python. Cela facilite l'intégration des analyses statistiques complexes de R dans des flux de travail Python, ce qui est particulièrement utile pour les data scientists. Dans les faits, R étant un logiciel de statistiques, cette passerelle enrichit le langage Python d'approches statistiques spécialisées et pointues.

L'installation de Rpy2 se fait en utilisant pip :

In [ ]:
pip install rpy2
In [ ]:
import rpy2.robjects as ro
print(ro.r('R.version.string'))

Si RPy2 ne trouve pas R, c’est souvent à cause de l’absence de R dans le PATH. Ajouter R au PATH résout généralement le problème. Sous Windows :

In [ ]:
setx PATH "%PATH%;C:\Program Files\R\R-4.3.1\bin"

On peut aussi définir ce path dynamiquement dans le script Python avant d'importer RPy2. L'avantage c'est qu'on ne touche pas à ses Paths uniquement pour Rpy2. Il faudra en revanche le redéfinir à chaque fois.

In [1]:
import os
os.environ["R_HOME"] = "C:/Program Files/R/R-4.3.1"
In [2]:
import rpy2.robjects as ro
print(ro.r('R.version.string'))
[1] "R version 4.3.1 (2023-06-16 ucrt)"

Les bases pour interagir avec R¶

Avec RPy2, on utilise la syntaxe suivante pour appeler une fonction R depuis Python : r ['nom_de_la_fonction'] (arguments). r['nom_de_la_fonction'] cela revient à accéder à une fonction R depuis un grand dictionnaire de fonctions R ; (arguments) ce sont les paramètres que l'on passe à la fonction R. La seule difficulté ici, c'est la gestion des données qu'il faut convertir depuis des objets Python en des objets R. Pour une simple liste Python, on pourra utiliser les fonctions IntVector(), FloatVector() ou StrVector(). Toutes ces fonctions sont plus exactement des rpy2.robjects . Python gère bien l'affichage du résultat.

In [3]:
from rpy2.robjects import r, IntVector
ma_liste = [10, 20, 30, 40, 50] # Liste R
r_liste = IntVector(ma_liste) # Possibilité d'utiliser FloatVector()
resume = r['summary'](r_liste) # Appel de la fonction summury sur le vecteur/liste R 
print(resume) 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     10      20      30      30      40      50 

Plus classiquement, on utilisera la fonction rpy2.robjects qui permet de passer des DataFrame Python (de la bibliothèque pandas) en DataFrame R. On peut alors présenter comment faire une régression linéaire avec rpy2. C’est un exemple de gestion des arguments plus complexe permise par rpy2. Enfin, on peut présenter rx2() qui est une méthode de rpy2.robjects utilisée pour accéder aux éléments d'une liste.

In [4]:
import pandas as pd
from rpy2.robjects import pandas2ri, r
# Activer la conversion automatique pandas -> R
pandas2ri.activate()
# DataFrame en Python
data = pd.DataFrame({
    'x': [1, 2, 3, 4, 5],
    'y': [3, 5, 7, 9, 10]
})
# Convertir en DataFrame R
r_dataframe = pandas2ri.py2rpy(data)
In [5]:
# Faire la régression linéaire avec la fonction lm
modele = r['lm'](formula='y ~ x', data=r_dataframe)
# Afficher le résumé du modèle
resume = r['summary'](modele)
print(resume)
# Recupérer les coefficents de la regession avec la fonction coefficients
coefficients = r['coefficients'](modele)
print("Coefficients :")
print(coefficients)
# Recupérer le R2 avec la méthode rx2
r_squared = resume.rx2('r.squared')[0]
print(f"\nR² : {r_squared}")
Call:
(function (formula, data, subset, weights, na.action, method = "qr", 
    model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE, 
    contrasts = NULL, offset, ...) 
{
    ret.x <- x
    ret.y <- y
    cl <- match.call()
    mf <- match.call(expand.dots = FALSE)
    m <- match(c("formula", "data", "subset", "weights", "na.action", 
        "offset"), names(mf), 0L)
    mf <- mf[c(1L, m)]
    mf$drop.unused.levels <- TRUE
    mf[[1L]] <- quote(stats::model.frame)
    mf <- eval(mf, parent.frame())
    if (method == "model.frame") 
        return(mf)
    else if (method != "qr") 
        warning(gettextf("method = '%s' is not supported. Using 'qr'", 
            method), domain = NA)
    mt <- attr(mf, "terms")
    y <- model.response(mf, "numeric")
    w <- as.vector(model.weights(mf))
    if (!is.null(w) && !is.numeric(w)) 
        stop("'weights' must be a numeric vector")
    offset <- model.offset(mf)
    mlm <- is.matrix(y)
    ny <- if (mlm) 
        nrow(y)
    else length(y)
    if (!is.null(offset)) {
        if (!mlm) 
            offset <- as.vector(offset)
        if (NROW(offset) != ny) 
            stop(gettextf("number of offsets is %d, should equal %d (number of observations)", 
                NROW(offset), ny), domain = NA)
    }
    if (is.empty.model(mt)) {
        x <- NULL
        z <- list(coefficients = if (mlm) matrix(NA_real_, 0, 
            ncol(y)) else numeric(), residuals = y, fitted.values = 0 * 
            y, weights = w, rank = 0L, df.residual = if (!is.null(w)) sum(w != 
            0) else ny)
        if (!is.null(offset)) {
            z$fitted.values <- offset
            z$residuals <- y - offset
        }
    }
    else {
        x <- model.matrix(mt, mf, contrasts)
        z <- if (is.null(w)) 
            lm.fit(x, y, offset = offset, singular.ok = singular.ok, 
                ...)
        else lm.wfit(x, y, w, offset = offset, singular.ok = singular.ok, 
            ...)
    }
    class(z) <- c(if (mlm) "mlm", "lm")
    z$na.action <- attr(mf, "na.action")
    z$offset <- offset
    z$contrasts <- attr(x, "contrasts")
    z$xlevels <- .getXlevels(mt, mf)
    z$call <- cl
    z$terms <- mt
    if (model) 
        z$model <- mf
    if (ret.x) 
        z$x <- x
    if (ret.y) 
        z$y <- y
    if (!qr) 
        z$qr <- NULL
    z
})(formula = "y ~ x", data = structure(list(x = 1:5, y = c(3L, 
5L, 7L, 9L, 10L)), class = "data.frame", row.names = c("0", "1", 
"2", "3", "4")))

Residuals:
         0          1          2          3          4 
-2.000e-01 -2.026e-15  2.000e-01  4.000e-01 -4.000e-01 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   1.4000     0.3830   3.656 0.035353 *  
x             1.8000     0.1155  15.588 0.000574 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.3651 on 3 degrees of freedom
Multiple R-squared:  0.9878,	Adjusted R-squared:  0.9837 
F-statistic:   243 on 1 and 3 DF,  p-value: 0.0005737


Coefficients :
[1.4 1.8]

R² : 0.9878048780487805

r() peut être utilisée aussi plus classiquement comme une fonction Python exécutant du code R. Ainsi, on peut écrire plusieurs lignes de code R dans des parenthèses en utilisant une chaîne multilignes ou des points-virgules. Ça permet de combiner des étapes complexes en une seule commande. Et surtout, si on connait bien R, cela permet de moins utiliser Rpy2 et nécessite donc peu d'apprentissage de Rpy2.

In [6]:
from rpy2.robjects import r

r('''
    # Définir un vecteur
    x <- c(1, 2, 3, 4, 5)
    
    # Calculer la moyenne
    moy <- mean(x)
    
    # Afficher la moyenne
    print(moy)
''')
[1] 3

On peut récupérer les objets du code R simplement en affectant la commande R de l'objet à un objet Python.

In [7]:
moyenne = r('moy')[0]
print(f"La moyenne est : {moyenne}")
La moyenne est : 3.0

Néanmoins, cela revient à faire du R dans Python. Si on utilise Rpy2 c'est a priori a minima pour utiliser une brique de code R avec des objets Python. Il faut donc au minimum savoir comment importer des objets Python dans R. Pour cela, on peut utiliser r.assign().

In [8]:
data = pd.DataFrame({
    'x': [1, 2, 3, 4, 5],
    'y': [3, 5, 7, 9, 10]
})
r_dataframe = pandas2ri.py2rpy(data)
r.assign('df', r_dataframe)
r('''
    x <- df$x
    y <- df$y
    reg <- lm(y ~ x)
    print(summary(reg))
''')
Call:
lm(formula = y ~ x)

Residuals:
         1          2          3          4          5 
-2.000e-01 -2.026e-15  2.000e-01  4.000e-01 -4.000e-01 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   1.4000     0.3830   3.656 0.035353 *  
x             1.8000     0.1155  15.588 0.000574 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.3651 on 3 degrees of freedom
Multiple R-squared:  0.9878,	Adjusted R-squared:  0.9837 
F-statistic:   243 on 1 and 3 DF,  p-value: 0.0005737

On appréciera aussi le Call beaucoup plus propre avec cette dernière solution pour faire une régression avec Rpy2...

In [9]:
r('''
    coef <- coefficients(reg)
    r2 <- summary(reg)$r.squared
''')
c = r('coef')
print("Coefficients  : ",c)
r2 = r('r2')
print("R2 : ", r2[0])
Coefficients  :  [1.4 1.8]
R2 :  0.9878048780487805

Jongler entre les données spatiales Python (GeoDataFrame) et R (SF)¶

Travailler avec des données spatiales va un peu complexifier l'utilisation de Rpy2. Premièrement, avec R, cela implique de charger des bibliothèques. Il n'y a aucune difficulté ici, car la fonction importr() permet de charger simplement des bibliothèques R. Ci-dessous, j'utilise libPaths() en amont pour indiquer à R où trouver ces bibliothèques, mais si vous utilisez le dossier par défaut de R, ne changez pas vos habitudes et passez cette ligne. On charge avec R les données géographiques suivantes (le shapefile des départements) : https://sergelhomme.fr/data/Donnees.zip

In [10]:
from rpy2.robjects import r
from rpy2.robjects.packages import importr
r('.libPaths("C:/Users/Serge/Documents/R/win-library/4.3")')
importr('sf')
importr('sp')
r('''
    adresse <- 'C:/Users/Serge/Desktop/Donnees/Departements.shp'
    dept <- st_read(adresse)
    print(head(dept))
''')
Reading layer `Departements' from data source 
  `C:\Users\Serge\Desktop\Donnees\Departements.shp' using driver `ESRI Shapefile'
Simple feature collection with 96 features and 17 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 99227.52 ymin: 6049649 xmax: 1242375 ymax: 7110525
Projected CRS: RGF93 v1 / Lambert-93
Simple feature collection with 6 features and 17 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 644569.2 ymin: 6272483 xmax: 1077506 ymax: 6997001
Projected CRS: RGF93 v1 / Lambert-93
  ID_GEOFLA CODE_DEPT                NOM_DEPT         NOM_CHF CODE_REG
1         1        01                     AIN BOURG-EN-BRESSE       82
2         2        02                   AISNE            LAON       22
3         3        03                  ALLIER         MOULINS       83
4         4        04 ALPES-DE-HAUTE-PROVENCE DIGNE-LES-BAINS       93
5         5        05            HAUTES-ALPES             GAP       93
6         6        06         ALPES-MARITIMES            NICE       93
                  NOM_REGION Pop_hommes Pop_femmes Pop_Etrang Pop_totale
1                RHONE-ALPES     287336     294023      45109     581355
2                   PICARDIE     262006     276794      13447     538790
3                   AUVERGNE     164392     178416       9394     342807
4 PROVENCE-ALPES-COTE-D'AZUR      76580      81387       6388     157965
5 PROVENCE-ALPES-COTE-D'AZUR      65494      68714       4192     134205
6 PROVENCE-ALPES-COTE-D'AZUR     511716     572712     100388    1084428
  Naissances  Deces Solde_nat Solde_mig Tx_Etrange Superfice population
1      61000  37114     23886     41991     0.0776  5784.556     581355
2      62669  48087     14582    -11105     0.0250  7419.598     538790
3      30652  38838     -8186      6378     0.0274  7380.145     342807
4      13741  13972      -231     18513     0.0404  6995.771     157965
5      12882  10793      2089     10485     0.0312  5689.465     134205
6     102510 101409      1101     71461     0.0926  4295.464    1084428
                        geometry
1 MULTIPOLYGON (((919195 6541...
2 MULTIPOLYGON (((735604.5 68...
3 MULTIPOLYGON (((753768.9 65...
4 MULTIPOLYGON (((992637.8 63...
5 MULTIPOLYGON (((1012912 640...
6 MULTIPOLYGON (((1018256 627...

L'affichage d'un simple plot() peut faire émerger les premières problématiques. En effet, plot() permet d'afficher un graphique R dans une fenêtre indépendante. Néanmoins, avec des objets géographiques reposant sur une colone geometry, cela peut vite générer des erreurs.

In [11]:
r('''
    plot(dept$geometry)
''')

Pour faire un affichage graphique R avec Python, il est plus stable de produire l'image puis de l'afficher.

In [11]:
r('''
png("C:/Users/Serge/Desktop/premiere-carte.png", width = 800, height = 600)  # Ouvre un dispositif graphique PNG
plot(dept$geometry)
dev.off()  # Ferme le dispositif graphique
''')
# Chargement de l'image dans Python
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread("C:/Users/Serge/Desktop/premiere-carte.png")
plt.imshow(img)
plt.axis("off")  # Masquer les axes
plt.show()
No description has been provided for this image

Maintenant, si l'on veut récupérer cet objet sf, qui n'est en théorie qu'un DataFrame avec une colonne geometry, il convient d'utiliser py2rpy() qui gère, comme on l'a vu plus haut, la conversion des DataFrame R et Python. Avant cela, dans R, on peut prendre la précaution de convertir son objet sf en DataFrame.

In [12]:
import pandas as pd
from rpy2.robjects import pandas2ri
shape = r('data.frame(dept)')
pd_from_r_df = pandas2ri.rpy2py(shape)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[12], line 4
      2 from rpy2.robjects import pandas2ri
      3 shape = r('data.frame(dept)')
----> 4 pd_from_r_df = pandas2ri.rpy2py(shape)

File ~\anaconda3\Lib\functools.py:909, in singledispatch.<locals>.wrapper(*args, **kw)
    905 if not args:
    906     raise TypeError(f'{funcname} requires at least '
    907                     '1 positional argument')
--> 909 return dispatch(args[0].__class__)(*args, **kw)

File ~\anaconda3\Lib\site-packages\rpy2\robjects\pandas2ri.py:332, in rpy2py_dataframe(obj)
    330 rpy2py = conversion.get_conversion().rpy2py
    331 colnames_lst = []
--> 332 od = OrderedDict(
    333     (i, rpy2py(col) if isinstance(col, rinterface.SexpVector) else col)
    334     for i, col in enumerate(_flatten_dataframe(obj, colnames_lst))
    335 )
    337 res = pandas.DataFrame.from_dict(od)
    338 res.columns = tuple('.'.join(_) if isinstance(_, list) else _ for _ in colnames_lst)

File ~\anaconda3\Lib\site-packages\rpy2\robjects\pandas2ri.py:332, in <genexpr>(.0)
    330 rpy2py = conversion.get_conversion().rpy2py
    331 colnames_lst = []
--> 332 od = OrderedDict(
    333     (i, rpy2py(col) if isinstance(col, rinterface.SexpVector) else col)
    334     for i, col in enumerate(_flatten_dataframe(obj, colnames_lst))
    335 )
    337 res = pandas.DataFrame.from_dict(od)
    338 res.columns = tuple('.'.join(_) if isinstance(_, list) else _ for _ in colnames_lst)

File ~\anaconda3\Lib\site-packages\rpy2\robjects\pandas2ri.py:319, in _flatten_dataframe(obj, colnames_lst)
    317 col = obj[i]
    318 if isinstance(col, ListSexpVector):
--> 319     _ = _records_to_columns(col)
    320     colnames_lst.extend((n, subn) for subn in _.keys())
    321     for subcol in _.values():

File ~\anaconda3\Lib\site-packages\rpy2\robjects\pandas2ri.py:274, in _records_to_columns(obj)
    272 for i, record in enumerate(obj_ri):
    273     checknames = set()
--> 274     for name in record.names:
    275         if name in checknames:
    276             raise ValueError(
    277                 f'The record {i} has "{name}" duplicated.'
    278             )

TypeError: 'NULLType' object is not iterable

Néanmoins, comme indiqué ci-dessus cela peut générer des erreurs car la gestion de la colonne geometry pose problème. En effet, py2rpy() gère bien les colonnes normales (texte, numérique), c'est un peu plus compliqué pour les géométries, les dates... Si vous faites le même code sans la colonne geometry, il n’y aura pas de problème. On va donc contourner le problème en transformant la colonne geometry en une colonne textuelle comportant l'information géométrique (cette astuce marche souvent pour contourner un problème de type de champs).

In [13]:
r('''
dept_df <- data.frame(dept)
dept_df$geometry <- st_as_text(dept_df$geometry)
''')
shape = r('dept_df')
pd_from_r_df = pandas2ri.rpy2py(shape)

On peut vérifier que tout c'est bien passé, en vérifiant que l'on bien à DataFrame Python.

In [14]:
print(pd_from_r_df.columns)
Index(['ID_GEOFLA', 'CODE_DEPT', 'NOM_DEPT', 'NOM_CHF', 'CODE_REG',
       'NOM_REGION', 'Pop_hommes', 'Pop_femmes', 'Pop_Etrang', 'Pop_totale',
       'Naissances', 'Deces', 'Solde_nat', 'Solde_mig', 'Tx_Etrange',
       'Superfice', 'population', 'geometry'],
      dtype='object')

Maintenant, on va juste revenir à une situation où notre colonne geometry redevient une colonne géométrique sous Python.

In [15]:
from shapely import wkt
pd_from_r_df['geometry'] = pd_from_r_df['geometry'].apply(wkt.loads)

C'est bon, on peut utiliser geopandas et faire nos traitements géométriques dans Python.

In [16]:
import geopandas as gpd
gdf = gpd.GeoDataFrame(pd_from_r_df, geometry='geometry')
gdf.plot()
Out[16]:
<Axes: >
No description has been provided for this image
In [17]:
import matplotlib.pyplot as plt
b = gdf.centroid
fig, ax = plt.subplots()
gdf.plot(ax=ax, color='white', edgecolor='black', linewidth=0.2)
b.plot(ax=ax, marker='o', color='red', markersize=3)
plt.show()
No description has been provided for this image

En sens inverse, si vous avez des données géographiques dans Python et que vous voulez utiliser R pour calculer vos buffers, il vous faudra aussi transformer votre champ geometry en texte, puis redéfinir dans R ce champ comme étant un champ géométrique. On utilise ci-dessous les villes de ce jeu de données : https://sergelhomme.fr/data/Reseau.zip

In [18]:
gdf = gpd.read_file('C:/Users/Serge/Desktop/Reseau/Villes.shp')
gdf.plot()
df = pd.DataFrame(gdf.drop(columns='geometry')) # Transformation du geoDataFrame en dataFrame sans la colonne geometry
df['geometry'] = gdf['geometry'].apply(str) # Ajout du champ geometry au format texte
pandas2ri.activate() # Activer la conversion automatique entre pandas et R
py_gdf = pandas2ri.py2rpy(df) # DataFrame de Python vers R
r.assign("py_gdf", py_gdf) #Import du DataFrame dans R
Out[18]:
R/rpy2 DataFrame (57 x 3)
ID Nom geometry
... ... ...
No description has been provided for this image

On passe dans R simplement et on utilise st_as_sfc() pour gérer notre colonne geometry.

In [19]:
r('''
py_gdf$geometry <- st_as_sfc(py_gdf$geometry)  # Ajuster le CRS si nécessaire st_as_sfc(py_gdf$geometry, crs = 27572)
sf_object <- st_as_sf(py_gdf)
buf <- st_buffer(sf_object, dist=50000)
''')

On affiche enfin le résultat dans Python :

In [20]:
r('''
png("C:/Users/Serge/Desktop/carte.png", width = 800, height = 600)
plot(buf$geometry)
plot(sf_object$geometry, add=TRUE)
dev.off() 
''')
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread("C:/Users/Serge/Desktop/carte.png")
plt.imshow(img)
plt.title("Carte")
plt.axis("off") 
plt.show()
No description has been provided for this image

Enrichir Python de fonctions de statistiques spatiales avancées¶

En théorie, une fois que l'on arrive à gérer le transfert des objets géographiques de R vers Python et inversement, il n'y a plus trop de difficultés. Ici, premier exemple, avec nos villes (https://sergelhomme.fr/data/Reseau.zip), pour lesquelles on calcule une fonction K de Ripley avec R.

In [21]:
#-------------------- Partie Import des données Python vers R --------------------
import geopandas as gpd
import pandas as pd
from rpy2.robjects import r, pandas2ri

r('.libPaths("C:/Users/Serge/Documents/R/win-library/4.3")')
importr('sf')
importr('sp')

pandas2ri.activate()
gdf = gpd.read_file('C:/Users/Serge/Desktop/Reseau/Villes.shp')
df = pd.DataFrame(gdf.drop(columns='geometry'))
df['geometry'] = gdf['geometry'].apply(str)
py_gdf = pandas2ri.py2rpy(df)
r.assign("py_gdf", py_gdf)

r('''
py_gdf$geometry <- st_as_sfc(py_gdf$geometry, crs = 27572) 
sf_object <- st_as_sf(py_gdf)
sp_object <- as_Spatial(sf_object)
''')
In [22]:
#-------------------- Partie Fonction K de Ripley --------------------
importr('spatstat')

r('''
    x <- sp_object$coords.x1
    y <- sp_object$coords.x2
    minx <- min(x)
    maxx <- max(x)
    miny <- min(y)
    maxy <- max(y)
    ppp <- as.ppp(data.frame(x,y), owin(c(minx,maxx), c(miny,maxy)))
    K <- Kest(ppp, correction='isotropic')
''')

r('''
    png("C:/Users/Serge/Desktop/K.png", width = 800, height = 600) 
    plot(K)
    dev.off() 
''')

import matplotlib.pyplot as plt
import matplotlib.image as mpimg

img = mpimg.imread("C:/Users/Serge/Desktop/K.png")
plt.imshow(img)
plt.title("Ripley K")
plt.axis("off")  # Masquer les axes
plt.show()
No description has been provided for this image
In [23]:
#-------------------- Partie Fonction K de Ripley avec enveloppe --------------------
importr('dbmss')
r('''
    wmppp <- as.wmppp(ppp)
    K_env <- KEnvelope(wmppp)
    png("C:/Users/Serge/Desktop/K_env.png", width = 800, height = 600) 
    plot(K_env)
    dev.off() 
''')
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

img = mpimg.imread("C:/Users/Serge/Desktop/K_env.png")
plt.imshow(img)
plt.title("Ripley K")
plt.axis("off")  # Masquer les axes
plt.show()
Generating 100 simulations by evaluating expression  ...
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,  100.

Done.
No description has been provided for this image

Deuxième exemple, à partir des iris parisiens (https://sergelhomme.fr/data/PARIS.zip), on calcule avec R une régression géographiquement pondérée.

In [24]:
importr('GWmodel')
importr('sp')

gdf = gpd.read_file('C:/Users/Serge/Desktop/PARIS/IRIS75C.shp')
df = pd.DataFrame(gdf.drop(columns='geometry'))
df['geometry'] = gdf['geometry'].apply(str)
py_gdf = pandas2ri.py2rpy(df)
r.assign("py_gdf", py_gdf)

r('''
py_gdf$geometry <- st_as_sfc(py_gdf$geometry, crs = 27572) 
iris75 <- st_as_sf(py_gdf)
rev <- iris75$Revenu
mono <- iris75$Tx_Mono
lrev <- log(rev) 
reg <- lm(lrev ~ mono)
irispa <- as_Spatial(iris75)
dist <- gw.dist(dp.locat=coordinates(irispa))
bw0 <- bw.gwr(reg, data=irispa, approach="AIC", kernel="bisquare", adaptive=TRUE, dMat=dist)
rgeo <- gwr.robust(reg, data=irispa, bw=bw0, kernel="bisquare", filtered=FALSE, adaptive=TRUE, dMat=dist)
#summary(rgeo)
r2 <- rgeo$SDF$Local_R2
a <- rgeo$SDF$mono
''')
Adaptive bandwidth (number of nearest neighbours): 541 AICc value: -209.4885 
Adaptive bandwidth (number of nearest neighbours): 342 AICc value: -312.853 
Adaptive bandwidth (number of nearest neighbours): 218 AICc value: -415.915 
Adaptive bandwidth (number of nearest neighbours): 142 AICc value: -518.7867 
Adaptive bandwidth (number of nearest neighbours): 94 AICc value: -612.9181 
Adaptive bandwidth (number of nearest neighbours): 65 AICc value: -679.7402 
Adaptive bandwidth (number of nearest neighbours): 46 AICc value: -719.5343 
Adaptive bandwidth (number of nearest neighbours): 35 AICc value: -740.1084 
Adaptive bandwidth (number of nearest neighbours): 28 AICc value: -750.2609 
Adaptive bandwidth (number of nearest neighbours): 23 AICc value: -750.5761 
Adaptive bandwidth (number of nearest neighbours): 21 AICc value: -744.107 
Adaptive bandwidth (number of nearest neighbours): 25 AICc value: -751.1441 
Adaptive bandwidth (number of nearest neighbours): 26 AICc value: -750.6209 
Adaptive bandwidth (number of nearest neighbours): 24 AICc value: -750.7929 
Adaptive bandwidth (number of nearest neighbours): 25 AICc value: -751.1441 
In [25]:
a = r('a')
ax = gdf.assign(a=a).plot(column='a', scheme='equal_interval', k=7, cmap='coolwarm', legend=True, edgecolor='white', linewidth=0.2, legend_kwds={"loc": "lower right"})
ax.set_axis_off()
plt.show()
r2 = r('r2')
ax = gdf.assign(r2=r2).plot(column='r2', scheme='equal_interval', k=4, cmap='Reds', legend=True, edgecolor='white', linewidth=0.2, legend_kwds={"loc": "lower right"})
ax.set_axis_off()
plt.show()
No description has been provided for this image
No description has been provided for this image

Avec des séries comportant des valeurs positives et négatives, il convient souvent de faire des classifications manuelles :

In [30]:
import mapclassify as mc
# Définition des seuils personnalisés
bins = [-4, -2, -1, 0, 1, 2, 4] 

# Appliquer la classification sur une colonne spécifique
gdf['a'] = a
gdf['classe'] = mc.UserDefined(gdf['a'], bins).yb

# Afficher la carte
gdf.plot(column="classe", cmap="coolwarm")
Out[30]:
<Axes: >
No description has been provided for this image

< Page précédente | Sommaire | | Retour Site | Page suivante >