Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

K-Means Clustering of HSRL Observations

ARM Logo

K-Means Clustering of HSRL Observations

Setup for loading required data from ARM

Enter the start_date and end_date for desired date range. Requires Access Token from ARM

Enter ARM username:  zmahatab
Enter ARM token (hidden):  ········
[DOWNLOADING] bnfhsrlM1.a1.20260512.000000.nc
[DOWNLOADING] bnfhsrlM1.a1.20260513.000000.nc
[DOWNLOADING] bnfhsrlM1.a1.20260514.000000.nc

If you use these data to prepare a publication, please cite:

Holz, R., Garcia, J., Schuman, E., Bambha, R., Ermold, B., Eloranta, E., &
Garcia, J. High Spectral Resolution Lidar (HSRL), 2026-05-12 to 2026-05-14,
Bankhead National Forest, AL, USA; Long-term Mobile Facility (BNF), Bankhead
National Forest, AL, AMF3 (Main Site) (M1). Atmospheric Radiation Measurement
(ARM) User Facility. https://doi.org/10.5439/1462207

ERROR 1: PROJ: proj_create_from_database: Open of /opt/conda/share/proj failed
Loading...

Function for loading data with specific features

Loads function with only our desired features. Features are passed as a list argument

Loading...

Calculating and engineering desired features

Calculating Aerosol Scattering Ratio 532, Aerosol Scattering Ratio 1064, and Inverse Scattering Ratio

Aerosol Scattering Ratio:

This is the ratio of aerosol-backscattered photons to molecular-backscattered photons. A value of 0 means pure clean air (no aerosols), higher values mean more aerosol loading.

Inverse Scattering Ratio:

ISR is bounded between 0 and 1. When there’s no aerosol (ASR=0), ISR=1 (pure molecular). When there’s heavy aerosol/cloud (ASR→∞), ISR→0.

Loading...

Removing Negatives

Removing negative values for Linear Depolarization Ratio, Color Ratio and Inverse Scattering Ratio to reduce distance calculation inaccuracies during K-means clustering.

Negative values in lidar retrievals are physically impossible. They arise from noise, dark current subtraction, or retrieval artifacts. Keeping them would skew K-means cluster centroids since it’s a distance-based algorithm. Converting them to NaN effectively excludes them from clustering.

Calculating log Color Ratio and log Linear Depolarization

Even though Color Ratio and Linear Depolarization do not have a massive range, they are very top-heavy. We converted them to the log scale to create uniform distribution

Thresholding

We used the max values from the plots to capture the extremities and capture the dynamic range of the data

Plots

Plotting all the ratios as curtainplots

<Figure size 640x480 with 2 Axes>
<Figure size 640x480 with 2 Axes>
<Figure size 640x480 with 2 Axes>
<Figure size 640x480 with 2 Axes>
<Figure size 640x480 with 2 Axes>
<Figure size 640x480 with 2 Axes>

Setup for trying Clustering

We are flattening the features we want for clustering to create 1D Arrays and removing NaN values

Loading...

Flattening

We are flattening the cluster features so that they are stackable on top of each other to create an input array for our Clustering model

(5658828,)
(5658828,)
(5658828,)
{'log_linear_depol': array([ nan, nan, -1.37672 , ..., -1.0555027, nan, nan], dtype=float32), 'log_color_ratio': array([nan, nan, nan, ..., nan, nan, nan], dtype=float32), 'isr': array([ nan, 0.6928234, 0.6582819, ..., 1.1736106, nan, 1.1397537], dtype=float32)}

Stack the flattened features arrays

We take the flattened array for each feature and then stack them on top of each other to get a new stacked array of features

Reconstruct the 2D array with labels

We get the cluster labels and indices of valid data points. The original data points are put in the array with their clustered labels to use it for plotting later

Full array shape: (5658828, 3)
Clean array shape: (3188953, 3)
Valid points: 3188953
(3188953, 3)

Attempting KMeans Clustering

k=2: inertia=6921256.50
k=3: inertia=5314066.00
k=4: inertia=4050680.75
k=5: inertia=3411868.50
k=6: inertia=2809734.50
k=7: inertia=2517509.75
k=8: inertia=2253130.00
<Figure size 800x400 with 1 Axes>

Silhouette Score

The silhouette score is a metric used to evaluate the quality of K-Means clustering by measuring how similar a point is to its own cluster (cohesion) compared to other clusters (separation). Scores range from (-1) to (+1), where higher values indicate better-defined, distinct clusters.

k=2: silhouette=0.3825
k=3: silhouette=0.2827
k=4: silhouette=0.3165
k=5: silhouette=0.3028
k=6: silhouette=0.3155
k=7: silhouette=0.3004
k=8: silhouette=0.3095
<Figure size 800x400 with 1 Axes>
array([[-0.9897575 , 0.7185502 , -0.8707168 ], [ 1.3842938 , 1.0080837 , -4.0400553 ], [ 1.0877937 , 1.4420443 , 0.29235148], [-0.6662276 , -1.0364679 , 0.34019437], [ 0.3508883 , -0.14709923, 0.37027854]], dtype=float32)

Plotting the reconstructed cluster map

Full array shape: (5658828, 3)
Clean array shape: (3188953, 3)
Valid points: 3188953
Clean HSRL Array shape: (3188953, 3)
array([[-0.98975754, 0.7185502 , -0.87071687], [ 1.3842938 , 1.0080838 , -4.0400553 ], [ 1.0877938 , 1.4420445 , 0.2923515 ], [-0.6662276 , -1.0364679 , 0.3401944 ], [ 0.3508883 , -0.14709923, 0.37027854]], dtype=float32)
array([[nan, nan, nan, ..., nan, nan, nan], [nan, nan, nan, ..., nan, nan, nan], [nan, nan, nan, ..., nan, nan, nan], ..., [nan, nan, nan, ..., nan, nan, nan], [nan, nan, nan, ..., nan, nan, nan], [nan, nan, nan, ..., nan, nan, nan]])
Assigned: 3188953 / 5658828 (56.4%)
Unique clusters: [1. 2. 3. 4. 5.]
Cluster 1: 526682 pixels (16.5%)
Cluster 2: 107528 pixels (3.4%)
Cluster 3: 365011 pixels (11.4%)
Cluster 4: 782503 pixels (24.5%)
Cluster 5: 1407229 pixels (44.1%)

Plotting reconstructed 2D Map as clusters

<Figure size 1400x600 with 1 Axes>

Running HDBScan

<Figure size 1400x600 with 1 Axes>