Landslide susceptibility#

© 2024 Exneyder A. Montoya-Araque, Daniel F. Ruiz y Universidad EAFIT.

This notebook can be interactively run in Google - Colab.

This notebook evaluates susceptibility through the information value method and the proposed methodology by Ciurleo et al. [2016] consisting of two steps. First, the production of a landslide susceptibility computational map, and second, the output of a susceptibility map for zoning purposes; in the latter, the terrain computational units (TCU) of the first map are aggregated into a larger terrain zoning unit (TZU).

The material used here has pedagogical-only purposes and was taken from the tutorial resources provided at the 2023 LAndslide Risk Assessment and Mitigation -LARAM- school.

Required modules and global setup for plots#

import os
import sys
import shutil
import subprocess
from IPython import get_ipython

if 'google.colab' in str(get_ipython()):
    print('Running on CoLab. Installing the required modules...')
    from subprocess import run
    # run('pip install ipympl', shell=True);
    run('pip install rasterio', shell=True);
    from google.colab import output, files
    output.enable_custom_widget_manager()
else:
    import tkinter as tk
    from tkinter.filedialog import askopenfilename
    
import numpy as np
import pandas as pd
import scipy as sp
import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn.metrics import roc_curve, roc_auc_score
import rasterio

# Figures setup
# %matplotlib widget
mpl.rcParams.update({
    "font.family": "serif",
    "font.serif": ["Computer Modern Roman", "cmr", "cmr10", "DejaVu Serif"],  # or 
    "mathtext.fontset": "cm",  # Use Computer Modern fonts for math
    "axes.formatter.use_mathtext": True,  # Use mathtext for axis labels
    "axes.unicode_minus": False,   # Use standard minus sign instead of a unicode character
})
tol_cols = ["#004488", "#DDAA33", "#BB5566"]

n_classes = 8  # number of classes to divide the conditioning factors

Functions#

def load_tiff(path):
    with rasterio.open(path) as src:
        raster = src.read(1)
        raster[raster == src.nodata] = np.nan
        return raster, src.transform, src.crs, src.bounds
    
def plot_field(field, bounds, title=None, cmap='viridis'):
    fig, ax = plt.subplots()
    if len(np.unique(field)) > n_classes:  # Plot contours only if it's a continuous conditioning factor
        im = ax.imshow(field, cmap=cmap, extent=(bounds.left, bounds.right, bounds.bottom, bounds.top))
        ax.contour(field, colors='black', alpha=0.5, origin='image',
                extent=(bounds.left, bounds.right, bounds.bottom, bounds.top))
        cbar = plt.colorbar(mappable=im, ax=ax, label=title)
    if len(np.unique(field)) <= n_classes:
        field_r, ticks_r, cmap_r = extract_from_discrete(field, cmap)
        im = ax.imshow(field_r, cmap=cmap_r, extent=(bounds.left, bounds.right, bounds.bottom, bounds.top))
        cbar = plt.colorbar(mappable=im, ax=ax, label=title, ticks=ticks_r)
    fig.canvas.header_visible = False
    fig.canvas.toolbar_position = 'bottom'
    plt.show()
    return fig

def extract_from_discrete(field, cmap='viridis'):
    ticks = np.unique(field)[~np.isnan(np.unique(field))]  # For colorbar
    n = len(ticks)
    field_r = np.full_like(field, np.nan)  # Empty reclasified field
    ticks_r = np.arange(n)  # Ticks to use in reclasified field
    for i in ticks_r:  #  Fill reclasified field
        field_r[field == ticks[i]] = i
    cmap_r = plt.colormaps.get_cmap(cmap).resampled(n)  # type: ignore
    return field_r, ticks_r, cmap_r

def nan_gaussian_filter(data, sigma):
    nan_mask = np.isnan(data)

    # Replace NaNs with 0 for convolution
    data_filled = np.where(nan_mask, 0, data)

    # Create a weight mask of valid data
    weights = (~nan_mask).astype(float)

    # Apply Gaussian filter to data and weights
    data_filtered = sp.ndimage.gaussian_filter(data_filled, sigma=sigma, mode='constant', cval=0.0)
    weights_filtered = sp.ndimage.gaussian_filter(weights, sigma=sigma, mode='constant', cval=0.0)

    # Normalize the result
    with np.errstate(invalid='ignore', divide='ignore'):
        normalized = data_filtered / weights_filtered
        normalized[weights_filtered == 0] = np.nan  # Restore NaNs

    return normalized

Reading the input data#

If working on Google Colab, set the testing_data variable in the following cell as False. Then you will be asked to upload your own raster files.

testing_data = True  # Set to False to use the GUI to load the data from an external file

Loading testing data#

if testing_data:
    url = "https://github.com/eamontoyaa/data4testing/raw/refs/heads/main/susceptibility/"
    # Load the data (conditioning factors)
    elevation, transform, crs, bounds = load_tiff(f"{url}/elevation.tif")
    slope, _, _, _ = load_tiff(f"{url}/slope.tif")
    aspect, _, _, _ = load_tiff(f"{url}/aspect.tif")
    curvature, _, _, _ = load_tiff(f"{url}/curvature.tif")
    flow_acc, _, _, _ = load_tiff(f"{url}/flow_acc.tif")
    tpi, _, _, _ = load_tiff(f"{url}/tpi.tif")
    roads_prox, _, _, _ = load_tiff(f"{url}/roads_prox.tif")
    rivers_prox, _, _, _ = load_tiff(f"{url}/rivers_prox.tif")
    geomorphology, _, _, _ = load_tiff(f"{url}/geomorphology.tif")
    
    df_cond_fact = pd.DataFrame({
        'elevation': elevation.flatten(),
        'slope': slope.flatten(),
        'aspect': aspect.flatten(),
        'curvature': curvature.flatten(),
        'flow_acc': flow_acc.flatten(),
        'tpi': tpi.flatten(),
        'roads_prox': roads_prox.flatten(),
        'rivers_prox': rivers_prox.flatten(),
        'geomorphology': geomorphology.flatten(),
    })
    independent_vars = df_cond_fact.columns.to_list()
    independent_vars_types = ['c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'd']

    # Mask of NaN values for all the conditioning factors
    mask_nan = df_cond_fact.isna().any(axis=1)
    mask_nan_mtx = mask_nan.values.reshape(elevation.shape)

    # Landslides
    landslides, _, _, _ = load_tiff(f"{url}/landslides.tif")
    landslides[np.isnan(landslides)] = 0
    landslides[mask_nan_mtx] = np.nan
    df_landslides = pd.Series(landslides.flatten())

    # Plot the data
    fig = plot_field(elevation, bounds, title='Elevation [m]', cmap='terrain')
    fig = plot_field(slope, bounds, title='Slope [deg]', cmap='RdYlGn_r')
    fig = plot_field(aspect, bounds, title='Aspect [deg]', cmap='twilight')
    fig = plot_field(curvature, bounds, title='Curvature', cmap='seismic')
    fig = plot_field(flow_acc, bounds, title='Flow Accumulation', cmap='Blues')
    fig = plot_field(tpi, bounds, title='Topographic Position Index', cmap='viridis')
    fig = plot_field(roads_prox, bounds, title='Roads Proximity', cmap='viridis')
    fig = plot_field(rivers_prox, bounds, title='Rivers Proximity', cmap='Blues_r')
    fig = plot_field(geomorphology, bounds, title='geomorphology', cmap='Accent')
    fig = plot_field(landslides, bounds, title='Landslides', cmap='binary')
../../_images/98c08dc76f106ed39a74097397fc90e0f9ab6ed6ac0f43581679f6219b7c0d15.png ../../_images/4e5749174ae0545124666dc6eac74fcaf1e86ae97bb9d19859e6236c3bbd990c.png ../../_images/3d1a0f817d490d5a6632c9714aa3714cd07775a8fd6ef08d2ad3203d4dc03b91.png ../../_images/40bbd6f9dd03edadcb1b7171271116348a5e416a6d1a02865b05bb0038e98d58.png ../../_images/cd6de39ca400201f27816f4bebf834c3cf13fe6d558154ff434277b858856aab.png ../../_images/4b77265bd1741367114786ee067fe92983da1f950a78c6024c1ff16c372bcf18.png ../../_images/8c98ede637da3d69959d480a6d390719725f8e1f04da2c6e5e27ee2dee8337a7.png ../../_images/857f2412caadc851294284ea926b1187eaa962a911ed222651323c8699dfa26f.png ../../_images/9528e246e4f2da4a5968301e81153be8e2671ac2f3a4738abe456df2059f9b4e.png ../../_images/5ec844bbea9bbb1b8e40558027014b7f67a907b70ef9852bda21ddb8616dbb57.png

Uploading the raster files of conditioning factors#

if testing_data is False and 'google.colab' in str(get_ipython()):
    df_cond_fact = pd.DataFrame()
    cond_factors_files = files.upload()
    for file in cond_factors_files:
        file_name = file.split('.')[0].split(' ')[0]
        raster, transform, crs, bounds = load_tiff(file)
        fig = plot_field(raster, bounds, title=file_name)
        df_cond_fact[file_name] = raster.flatten()
    independent_vars = df_cond_fact.columns.to_list()
    independent_vars_types = ['c']*len(independent_vars)
    # If unique values are less than or equal to n_classes, then it is a discrete variable
    for idx, col in enumerate(independent_vars):
        if len(df_cond_fact[col].unique()) <= n_classes:
            independent_vars_types[idx] = 'd'

    # Mask of NaN values for all the conditioning factors
    mask_nan = df_cond_fact.isna().any(axis=1)
    mask_nan_mtx = mask_nan.values.reshape(raster.shape)
    

Uploading the raster files of landslides#

if testing_data is False and 'google.colab' in str(get_ipython()):
    landslides_file = files.upload()
    landslides, transform, crs, bounds = load_tiff(list(landslides_file.keys())[0])
    landslides[np.isnan(landslides)] = 0
    landslides[mask_nan_mtx] = np.nan
    fig = plot_field(landslides, bounds, title='Landslides', cmap='binary')
    df_landslides = pd.Series(landslides.flatten())
    df_landslides.loc[mask_nan] = np.nan
mask_nan_cond_fact = df_cond_fact.isna().any(axis=1)
mask_nan_landslides = df_landslides.isna()
mask_nan = mask_nan_cond_fact | mask_nan_landslides
# Replace the NaN values in the conditioning factors and landslides with NaN
df_cond_fact.loc[mask_nan] = np.nan
df_landslides.loc[mask_nan] = np.nan
df_cond_fact
elevation slope aspect curvature flow_acc tpi roads_prox rivers_prox geomorphology
0 797.760010 18.761023 27.847687 -0.00147 2.12 1.128723 20.0 300.000000 1.0
1 794.750000 17.574442 15.301435 0.03129 3.20 -0.911255 0.0 300.000000 1.0
2 794.169983 17.088249 348.047455 0.03405 5.56 -1.530029 0.0 300.665924 1.0
3 797.669983 17.850204 311.438507 0.04068 3.59 -1.732483 0.0 297.321381 1.0
4 805.789978 14.410152 279.945099 0.00059 1.69 1.342468 20.0 284.253418 1.0
... ... ... ... ... ... ... ... ... ...
2495 831.929993 7.303904 235.462631 -0.03553 2.60 0.393738 320.0 672.309448 3.0
2496 833.359985 6.323634 200.697632 -0.03034 2.37 0.659973 340.0 680.294067 3.0
2497 833.030029 8.246736 176.506042 -0.00403 4.16 0.075073 360.0 688.767029 3.0
2498 832.500000 9.393118 181.905396 0.01264 7.15 -0.397461 380.0 697.710571 3.0
2499 833.030029 9.590951 189.481155 0.00197 7.73 0.198853 380.0 707.106812 3.0

2500 rows × 9 columns

independent_vars_types
['c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'd']
df_landslides
0       0.0
1       0.0
2       0.0
3       0.0
4       0.0
       ... 
2495    0.0
2496    0.0
2497    0.0
2498    0.0
2499    0.0
Length: 2500, dtype: float32

Percentile class for each independent variable#

The independent variables are classified according to a quantile criterion employing 8 classes

percentages = np.linspace(0, 100, n_classes+1)  # percentages for the percentiles to compute
df_cond_fact_p = df_cond_fact.copy()

for n_iv, iv in enumerate(independent_vars): # Variable whose percentiles is being classified
    p_class = np.full_like(df_landslides.to_numpy(), fill_value=-1, dtype=int)  # percentile classes
    if independent_vars_types[n_iv] == 'c':
        percentiles = np.nanpercentile(df_cond_fact[iv], percentages, method='linear')  # percentiles that limit the clases
        for p in np.arange(n_classes):
            p_class[df_cond_fact[iv].to_numpy() >= percentiles[p]] = p
    elif independent_vars_types[n_iv] == 'd':
        uvals = np.unique(df_cond_fact[iv])
        for n_uval, uval in enumerate(uvals):
            p_class[df_cond_fact[iv] == uval] = n_uval
    if any(p_class == -1):
        n_unclass = np.sum(p_class==-1) 
        print(f"There are {n_unclass} TCUs in the '{iv}' independent variable without percentile classification")
    df_cond_fact_p[iv] = p_class  # This plus one is to math the tutorial nomenclature

df_cond_fact_p
There are 2 TCUs in the 'elevation' independent variable without percentile classification
There are 2 TCUs in the 'slope' independent variable without percentile classification
There are 2 TCUs in the 'aspect' independent variable without percentile classification
There are 2 TCUs in the 'curvature' independent variable without percentile classification
There are 2 TCUs in the 'flow_acc' independent variable without percentile classification
There are 2 TCUs in the 'tpi' independent variable without percentile classification
There are 2 TCUs in the 'roads_prox' independent variable without percentile classification
There are 2 TCUs in the 'rivers_prox' independent variable without percentile classification
There are 2 TCUs in the 'geomorphology' independent variable without percentile classification
elevation slope aspect curvature flow_acc tpi roads_prox rivers_prox geomorphology
0 3 7 0 3 0 7 0 6 0
1 3 7 0 7 0 1 0 6 0
2 3 6 7 7 1 0 0 6 0
3 3 7 7 7 1 0 0 5 0
4 3 5 7 3 0 7 0 5 0
... ... ... ... ... ... ... ... ... ...
2495 5 0 3 0 0 5 7 7 2
2496 5 0 0 0 0 6 7 7 2
2497 5 0 0 2 1 4 7 7 2
2498 5 1 0 6 2 2 7 7 2
2499 5 1 0 4 2 4 7 7 2

2500 rows × 9 columns

\(N_{ij} = F_{ij} + NF_{ij}\) → TCUs belonging to the class \(j\) of the independent variable \(V_i\)#

  • \(F_{ij}\) → Number of TCUs with landslides belonging to the class \(j\) of the independent variable \(V_i\)

  • \(NF_{ij}\) → Number of TCUs without landslides belonging to the class \(j\) of the independent variable \(V_i\)

# number of TCUs with landslides belonging to the class j of the independent variable Vi
Fij = np.zeros((n_classes, len(independent_vars)))
# number of TCUs without landslides belonging to the class j of the independent variable Vi
NFij = np.zeros((n_classes, len(independent_vars)))

for i in np.arange(n_classes):  # iterate over percentile classes (rows)
    for j, iv in enumerate(independent_vars): # iterate over IV classes (columns)
        Fij[i, j] = np.nansum(np.logical_and(df_cond_fact_p[iv]==i, df_landslides==1))
        NFij[i, j] = np.nansum(np.logical_and(df_cond_fact_p[iv]==i, df_landslides==0))

# number of TCUs belonging to the class j of the independent variable Vi
Nij = Fij + NFij

pd.DataFrame(Nij, columns=independent_vars, index=[f'Class {i}' for i in np.arange(n_classes)])
elevation slope aspect curvature flow_acc tpi roads_prox rivers_prox geomorphology
Class 0 313.0 313.0 313.0 312.0 312.0 313.0 291.0 264.0 1493.0
Class 1 312.0 312.0 312.0 313.0 312.0 312.0 289.0 356.0 297.0
Class 2 312.0 312.0 312.0 312.0 313.0 310.0 284.0 299.0 708.0
Class 3 312.0 312.0 312.0 311.0 312.0 314.0 372.0 324.0 0.0
Class 4 312.0 312.0 312.0 312.0 312.0 312.0 325.0 301.0 0.0
Class 5 312.0 312.0 312.0 313.0 312.0 312.0 309.0 314.0 0.0
Class 6 311.0 312.0 312.0 312.0 312.0 312.0 295.0 325.0 0.0
Class 7 314.0 313.0 313.0 313.0 313.0 313.0 333.0 315.0 0.0

\(W_{ij} = \log\left(\frac{D_{ij}}{D^*}\right)\) → Weight of the class \(j\) of the independent variable \(V_i\)#

  • \(D_{ij} =\frac{F_{ij}}{N_{ij}}\) → Density of landslides within class \(j\) of the independent variable \(V_i\)

  • \(D^* = \frac{F_\mathrm{tot}}{N_\mathrm{tot}}\) → Average density of landslides within the test area

Dij = Fij / Nij
Ftot = np.nansum(Fij, axis=0)
NFtot = np.nansum(NFij, axis=0)
Ntot = Ftot + NFtot
Dast = Ftot / Ntot
Wij = np.log10(Dij / Dast)
# Correct weight where no landslides occurred
Wij_inf = np.isinf(Wij)
Wij[Wij_inf] = np.floor(np.nanmin(Wij[~Wij_inf]))

pd.DataFrame(Wij, columns=independent_vars, index=[f'Class {i}' for i in np.arange(n_classes)])
/tmp/ipykernel_1965/3450856213.py:1: RuntimeWarning: invalid value encountered in divide
  Dij = Fij / Nij
elevation slope aspect curvature flow_acc tpi roads_prox rivers_prox geomorphology
Class 0 0.185269 -0.046799 -0.105777 -0.270719 -0.502328 0.048730 -0.086135 0.255150 0.081188
Class 1 0.076936 0.063735 0.021537 -0.097952 -0.132943 0.036063 -0.001934 0.213565 -0.544598
Class 2 0.159571 0.050119 -0.018675 -0.005901 -0.151535 0.035990 0.008999 0.068603 -0.062326
Class 3 0.074328 0.063735 -0.015446 -0.033796 -0.070233 0.091994 -0.031837 -0.112953 NaN
Class 4 0.094769 0.044552 -0.045410 0.038911 0.018573 0.003439 0.037888 -0.238097 NaN
Class 5 0.018573 0.018573 -0.035190 0.070314 0.097258 -0.012241 0.043107 -0.424279 NaN
Class 6 -0.940266 -0.031837 0.092266 0.074328 0.135253 -0.041976 0.060396 -0.312251 NaN
Class 7 -0.617076 -0.233356 0.072938 0.112901 0.238436 -0.233356 -0.043736 0.093102 NaN

\(IS_\mathrm{TCU} = \sum_{i=0}^{n-1} W_{ik_{(i)}} \) → Susceptibility index#

df_cond_fact_w = df_cond_fact_p.copy()
for j, iv in enumerate(independent_vars): # iterate over independent variables (columns)
    df_cond_fact_w[iv] = Wij[df_cond_fact_p[iv], j]
df_cond_fact_w.loc[mask_nan] = np.nan
df_cond_fact_w["ISTCU"] = df_cond_fact_w.sum(axis=1)
df_cond_fact_w.loc[mask_nan, "ISTCU"] = np.nan
df_cond_fact_w
elevation slope aspect curvature flow_acc tpi roads_prox rivers_prox geomorphology ISTCU
0 0.074328 -0.233356 -0.105777 -0.033796 -0.502328 -0.233356 -0.086135 -0.312251 0.081188 -1.351483
1 0.074328 -0.233356 -0.105777 0.112901 -0.502328 0.036063 -0.086135 -0.312251 0.081188 -0.935365
2 0.074328 -0.031837 0.072938 0.112901 -0.132943 0.048730 -0.086135 -0.312251 0.081188 -0.173080
3 0.074328 -0.233356 0.072938 0.112901 -0.132943 0.048730 -0.086135 -0.424279 0.081188 -0.486628
4 0.074328 0.018573 0.072938 -0.033796 -0.502328 -0.233356 -0.086135 -0.424279 0.081188 -1.032867
... ... ... ... ... ... ... ... ... ... ...
2495 0.018573 -0.046799 -0.015446 -0.270719 -0.502328 -0.012241 -0.043736 0.093102 -0.062326 -0.841921
2496 0.018573 -0.046799 -0.105777 -0.270719 -0.502328 -0.041976 -0.043736 0.093102 -0.062326 -0.961987
2497 0.018573 -0.046799 -0.105777 -0.005901 -0.132943 0.003439 -0.043736 0.093102 -0.062326 -0.282369
2498 0.018573 0.063735 -0.105777 0.074328 -0.151535 0.035990 -0.043736 0.093102 -0.062326 -0.077647
2499 0.018573 0.063735 -0.105777 0.038911 -0.151535 0.003439 -0.043736 0.093102 -0.062326 -0.145615

2500 rows × 10 columns

\(\sigma_i\) → statistical indicator to assess the discriminant capability of \(W_i\)#

n = np.sum(~np.isnan(Wij), axis=0)  # Number of classes of the independent variable Vi
Wij_ast = Wij * Nij / (Ntot / n)  # normalized value of the weight assigned to the class j of the independent variable Vi
Wi = np.nanmean(Wij, axis=0)  # average value of the weights assigned to the classes of the independent variable Vi
sigma = np.sqrt(np.nansum((Wij_ast - Wi)**2, axis=0) / (n - 1))

pd.DataFrame([sigma], columns=independent_vars, index=['sigma'])
elevation slope aspect curvature flow_acc tpi roads_prox rivers_prox geomorphology
sigma 0.419273 0.10016 0.064685 0.123769 0.228635 0.099654 0.048868 0.25195 0.243137
# Bar plot representing sigma
fig, ax = plt.subplots(figsize=(8, 4), layout='constrained')
ax.bar(independent_vars, sigma, color=mpl.colors.to_rgba(tol_cols[1], alpha=0.5), edgecolor='black')
ax.set(ylabel='$\\sigma$', xlabel='Independent variables')
# rotate x labels
ax.set_xticklabels(independent_vars, rotation=45, ha='right')
fig.canvas.header_visible = False
fig.canvas.toolbar_position = 'bottom'
plt.show()
/tmp/ipykernel_1965/1568106826.py:6: UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.
  ax.set_xticklabels(independent_vars, rotation=45, ha='right')
../../_images/c2ae716992dddbc5a64e307e2e71685aef3d3a5a7f402a7f884e111333590cbd.png

Visualize the spatial distribution of the susceptibility index#

Raw results for terrain computational units (TCU)#

ISTCU = np.reshape(df_cond_fact_w['ISTCU'].to_numpy(), landslides.shape)

fig = plot_field(ISTCU, bounds, title='IS$_\\mathrm{TCU}$', cmap='Spectral_r')
../../_images/72567a2a496ae78761cc8fc6276f7e1f3344e776079c2af5ef0197fc8b41dba8.png

Processed results for larger terrain zoning unit (TZU)#

The focal statistics used here is a 2D Gaussian filter with standard deviation \(\sigma\) used to control the degree of smoothness.

To visualize the landslide boundaries over the map, uncomment the code in the following cell.

# ISTZU = sp.ndimage.gaussian_filter(ISTCU, sigma=1)
ISTZU = nan_gaussian_filter(ISTCU, sigma=2)

mask_nan_border = np.isnan(ISTZU).flatten()
df_cond_fact_w["ISTZU"] = ISTZU.flatten()

fig = plot_field(ISTZU, bounds, title='IS$_\\mathrm{TZU}$', cmap='Spectral_r')

# ax = fig.gca()
# ax.contour(landslides, colors='black', alpha=0.5, origin='image', levels=0, linewidths=5,
#            extent=(bounds.left, bounds.right, bounds.bottom, bounds.top))
../../_images/044269e33d9ca4ef82f97cf1f2e18b1464a57965ed9b375982fd4baf386a6d6d.png

Export results to raster file#

To save the susceptibility index map to a raster file, set the want2save variable in the next cell as True.

want2save = False

if want2save:
    spatial_data_path = os.path.join(os.getcwd(), 'spatial_data')
    src = list(landslides_file.keys())[0]
    dst = "ISTZU.tif"
    shutil.copy(src, dst)
    # Open the new raster in write mode
    with rasterio.open(dst, 'r+') as dst:
        dst.write(ISTZU, 1)  # Replace the existing array with the modified array

Performance validation of the susceptibility model#

fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(5, 5), layout='constrained')
plt.plot([0, 1], [0, 1], 'k--')

fpr, tpr, thresholds = roc_curve(df_landslides.loc[~mask_nan], df_cond_fact_w.loc[~mask_nan, "ISTZU"])  # Calculate the ROC curve
auc_score = roc_auc_score(df_landslides.loc[~mask_nan], df_cond_fact_w.loc[~mask_nan, "ISTZU"])  # Calculate the AUC score
ax.plot(fpr, tpr, label=f'AUC (ISTZU) = {auc_score:.3f}', c='k')

fpr, tpr, thresholds = roc_curve(df_landslides[~mask_nan], df_cond_fact_w.loc[~mask_nan, "ISTCU"])  # Calculate the ROC curve
auc_score = roc_auc_score(df_landslides[~mask_nan], df_cond_fact_w.loc[~mask_nan, "ISTCU"])  # Calculate the AUC score
ax.plot(fpr, tpr, label=f'AUC (ISTCU) = {auc_score:.3f}')

# All the independent variables
for iv in independent_vars:
    fpr, tpr, thresholds = roc_curve(df_landslides[~mask_nan], df_cond_fact_w.loc[~mask_nan, iv])  # Calculate the ROC curve
    auc_score = roc_auc_score(df_landslides[~mask_nan], df_cond_fact_w.loc[~mask_nan, iv])  # Calculate the AUC score
    ax.plot(fpr, tpr, label=f'AUC ({iv}) = {auc_score:.3f}')

ax.set_xlabel('False Positive Rate')
ax.set_ylabel('True Positive Rate')
ax.set_aspect('equal')
ax.legend()
ax.grid(True, color='0.3', ls='--', lw=0.7)

fig.canvas.header_visible = False
fig.canvas.toolbar_position = 'bottom'
plt.show()
../../_images/443ec568873e29bf55b3023f144a96af3ca41d7404622e222fd2b49e1f969de0.png