# Some cells may generate warnings that we can ignore. Comment below lines to see.
import warnings
'ignore')
warnings.filterwarnings(
import os
from os.path import join, expanduser, splitext, basename
import numpy as np
import pandas as pd
import geopandas as gpd
from osgeo import gdal
import rasterio as rio
import rioxarray as rxr
import holoviews as hv
import hvplot.xarray
import hvplot.pandas
import geoviews as gv
import earthaccess
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from holoviews.streams import SingleTap
from scipy.stats import linregress
Santa Barbara County Vineyards Analysis 2023
Authors:
Gregory Halverson and Claire Villanueva-Weeks
Jet Propulsion Laboratory, California Institute of Technology
This research was carried out at the Jet Propulsion Laboratory, California Institute of Technology, and was sponsored by ECOSTRESS and the National Aeronautics and Space Administration (80NM0018D0004).
© 2023. All rights reserved.
Summary and Background
Santa Barbara County is a well-known for its viticulture. Positioned near the ocean, the unique topography of Santa Barbara allows for unique microclimates that yield diverse crops and wines. In this notebook, we aim to visualize and analyze Vineyard health using Earth observation data from two different satellite products: the EMIT L2A Reflectance product stored in NETCDF4 format and an ECOSTRESS L2 Land Surface Temperature product stored in GEOTIFF format. Our goal is to visualize Normalized Difference Vegetation Index (NDVI), Equivalent Water Thickness (EWT), Evapotranspiration (ET), and Surface Temperature (ST) interactively in holoviews
maps and generate linear regression plots of point source data.

Requirements
- NASA Earthdata Account
- No Python setup requirements if connected to the workshop cloud instance!
- Local Only Set up Python Environment - See setup_instructions.md in the
/setup/
folder to set up a local compatible Python environment
- Downloaded necessary files. This is done at the end of the 01_Finding_Concurrent_Data notebook.
Tutorial references
01_Finding_Concurrent_Data.ipynb - Finding and downloading concurrent EMIT and ECOSTRESS data with earthaccess
03_EMIT_CWC_from_Reflectance.ipynb - Calculating EWT of ROI
03_EMIT_Inreractive_Points.ipynb - Generating a map with clickable points
Required datasets
- EMIT Reflectance
- EMIT_L2A_RFL_001_20230401T203803_2309114_003.nc
- EMIT Canopy Water Content
- EMIT_L2A_RFL_001_20230401T203751_2309114_002_roi_bbox_cwc_merged.tif
- ECOSTRESS Land Surface Temperature
- ECOv002_L2T_LSTE_26860_001_10SGD_20230401T203733_0710_01_LST.tif
- ECOSTRESS Evapotranspiration
- ECOv002_L3T_JET_26860_001_10SGD_20230401T203732_0700_01_ETdaily.tif
- SB_ROI_ag.geojson: This file includes polygons of delineated agricultural field boundaries. It has been clipped to our ROI and fields were edited for ease of usability.
Tutorial Outline
5.1 Set up
5.2 Locating Data Sources and Loading Data
5.3 Color Hex-Codes for Raster Plots
5.4 Subsetting the data over our ROI
5.5 Interactive Maps
5.6 Scatter Plots
References
- Jackson, M. (2018). Santa Barbara County (Images of America). Arcadia Publishing.
- CalAgData
5.1 Set up
These are some built-in Python functions we need for this notebook, including functions for handling filenames and dates.
We’re using the rioxarray
package for loading raster data from a GeoTIFF file, and we’re importing it as rxr
. We’re using the numpy
library to handle arrays, and we’re importing it as np
. We’re using the rasterio
package to subset the data.
We’re using the geopandas
library to load vector data from GeoJSON files, and we’re importing it as gpd
. We’re using the shapely
library to handle vector data and the pyproj
library to handle projections.
Import the emit_tools
module and call use from emit_tools import emit_xarray help(emit_xarray) the help function to see how it can be used.
Note: This function currently works with L1B Radiance and L2A Reflectance Data.
Importing Custom EMIT Tools to handle the EMIT reflectance data
from modules.emit_tools import emit_xarray, ortho_xr
We can define these constants to prescribe the dimensions of our figures. Feel free to adjust these to fit your display. We’re setting the alpha to make the raster semi-transparent on top of the basemap.
= 1080
FIG_WIDTH_PX = 720
FIG_HEIGHT_PX = 16
FIG_WIDTH_IN = 9
FIG_HEIGHT_IN = 0.7
FIG_ALPHA = "EsriImagery"
BASEMAP = "whitegrid"
SEABORN_STYLE = "none"
FILL_COLOR = "red"
LINE_COLOR = 3
LINE_WIDTH = "EPSG:4326"
WEBMAP_PROJECTION sns.set_style(SEABORN_STYLE)
5.2 Locating Data Sources and Loading Data
Here we are defining location of EMIT and ECOSTRESS raster files.
= "../data/"
data_dir data_dir
Here we are loading in our vector data source. We can image the delineated agricultural field boundaries and take a look at the fields.
= join(data_dir,"SB_ROI_ag.geojson")
ag_file # cut out the agroi section
= gpd.read_file(ag_file)
ag_latlon
= ag_latlon.to_crs(WEBMAP_PROJECTION).hvplot.polygons(
ag_fig =BASEMAP,
tiles= LINE_COLOR,
line_color = LINE_WIDTH,
line_width = FILL_COLOR,
fill_color ='EPSG:4326'
crs
)
ag_fig
ag_latlon
Loading in EMIT reflectance data
This notebook requires downloading an EMIT scene if this was not done in a previous notebook.
# Get requests https Session using Earthdata Login Info
= 'https://data.lpdaac.earthdatacloud.nasa.gov/lp-prod-protected/EMITL2ARFL.001/EMIT_L2A_RFL_001_20230401T203803_2309114_003/EMIT_L2A_RFL_001_20230401T203803_2309114_003.nc'
url = earthaccess.get_requests_https_session()
fs # Retrieve granule asset ID from URL (to maintain existing naming convention)
= url.split('/')[-1]
granule_asset_id # Define Local Filepath
= join(data_dir, granule_asset_id)
fp # Download the Granule Asset if it doesn't exist
if not os.path.isfile(fp):
with fs.get(url,stream=True) as src:
with open(fp,'wb') as dst:
for chunk in src.iter_content(chunk_size=64*1024*1024):
dst.write(chunk)
To open up the EMIT .nc
file we will use the netCDF4
, xarray
and emit_tools
libraries.
= join(data_dir, "EMIT_L2A_RFL_001_20230401T203803_2309114_003.nc")
EMIT_fp EMIT_fp
= emit_xarray(EMIT_fp, ortho=True)
refl_ds refl_ds
Here some functions are set up to
5.3 Color Hex-Codes for Raster Plots
Here we have set up convenience functions for interpolating Color Hex-Codes for Raster Plots
def interpolate_hex(hex1, hex2, ratio):
= [int(hex1[i:i+2], 16) for i in (1, 3, 5)]
rgb1 = [int(hex2[i:i+2], 16) for i in (1, 3, 5)]
rgb2 = [int(rgb1[i] + (rgb2[i] - rgb1[i]) * ratio) for i in range(3)]
rgb
return '#{:02x}{:02x}{:02x}'.format(*rgb)
def create_gradient(colors, steps):
= []
gradient
for i in range(len(colors) - 1):
for j in range(steps):
= j / float(steps)
ratio +1], ratio))
gradient.append(interpolate_hex(colors[i], colors[i
-1])
gradient.append(colors[
return gradient
def plot_cmap(cmap):
= np.linspace(0, 1, 256) # Gradient from 0 to 1
gradient = np.vstack((gradient, gradient)) # Make 2D image
gradient
# Display the colormap
=(6, 2))
plt.figure(figsize='auto', cmap=cmap)
plt.imshow(gradient, aspect'off')
plt.axis( plt.show()
Near-Infrared and Red Bands in EMIT Hyperspectral Cube
These are the nearest
to 800 nm and 675 nm wavelengths, they can be used to calculate the NDVI using a ratio of the difference between the wavelengths to the sum of the wavelengths. NDVI is a metric by which we can estimate vegetation greenness.
The hvplot
package extends xarray
to allow us to plot maps. We’re reprojecting the raster geographic projection EPSG 4326 to overlay on the basemap with a latitude and longitude graticule. We will be using hvplot a few more times to look at the data we are using.
Defining Color-Map for Near Infrared
= ["#000000", "#FF0000"]
NIR_colors = create_gradient(NIR_colors, 100)
NIR_gradient = LinearSegmentedColormap.from_list(name="NIR", colors=NIR_colors)
NIR_cmap plot_cmap(NIR_cmap)
Extracting 800 nm Near Infrared from EMIT
= refl_ds.sel(wavelengths=800, method="nearest")
NIR "NIR_800.tif"))
NIR.rio.to_raster(join(data_dir,= rxr.open_rasterio(join(data_dir,"NIR_800.tif"), mask_and_scale=True).squeeze("band", drop=True)
NIR ==-9999] = np.nan
NIR.data[NIR.data= np.nanquantile(np.array(NIR), [0.02, 0.98])
NIR_vmin, NIR_vmax = f"{splitext(basename(EMIT_fp))[0]} ~800 nm"
NIR_title
NIR.rio.reproject(WEBMAP_PROJECTION).hvplot.image(=WEBMAP_PROJECTION,
crs=NIR_gradient,
cmap=FIG_ALPHA,
alpha=BASEMAP,
tiles=(NIR_vmin, NIR_vmax),
clim=NIR_title
title )
Defining Color-Map for Red
= ["#000000", "#FF0000"]
red_colors = create_gradient(red_colors, 100)
red_gradient = LinearSegmentedColormap.from_list(name="red", colors=red_colors)
red_cmap plot_cmap(red_cmap)
Extracting 675 nm Red from EMIT
= refl_ds.sel(wavelengths=675, method="nearest")
red "red_675.tif"))
red.rio.to_raster(join(data_dir,= rxr.open_rasterio(join(data_dir,"red_675.tif")).squeeze("band", drop=True)
red ==-9999] = np.nan
red.data[red.data= np.nanquantile(np.array(red), [0.02, 0.98])
red_vmin, red_vmax = f"{splitext(basename(EMIT_fp))[0]} ~675 nm"
red_title
red.rio.reproject(WEBMAP_PROJECTION).hvplot.image(=WEBMAP_PROJECTION,
crs=red_gradient,
cmap=FIG_ALPHA,
alpha=BASEMAP,
tiles=(red_vmin, red_vmax),
clim=red_title
title )
Defining Color-Map for Vegetation Index
=[
NDVI_colors"#0000ff",
"#000000",
"#745d1a",
"#e1dea2",
"#45ff01",
"#325e32"
]
= create_gradient(NDVI_colors, 100)
NDVI_gradient = LinearSegmentedColormap.from_list(name="NDVI", colors=NDVI_colors)
NDVI_cmap plot_cmap(NDVI_cmap)
Calculating Vegetation Index
= (NIR - red)/(NIR + red)
NDVI "NDVI.tif"))
NDVI.rio.to_raster(join(data_dir,= rxr.open_rasterio(join(data_dir,"NDVI.tif")).squeeze("band", drop=True)
NDVI = np.nanquantile(np.array(NDVI), [0.02, 0.98])
NDVI_vmin, NDVI_vmax = "Normalized Difference Vegetation Index"
NDVI_title
= NDVI.rio.reproject(WEBMAP_PROJECTION).hvplot.image(
NDVI_scene_map =WEBMAP_PROJECTION,
crs=NDVI_gradient,
cmap=FIG_ALPHA,
alpha=BASEMAP,
tiles=(NDVI_vmin, NDVI_vmax),
clim=NDVI_title
title
)
NDVI_scene_map
Loading in and visualizing our EWT data
We can now open up the EWT data that you calculated earlier with the LP DAAC EWT. The dataset is now in .tif format which means we can just open this one up
= join(data_dir,'EMIT_L2A_RFL_001_20230401T203751_2309114_002_roi_bbox_cwc_merged.tif')
EWT_filename EWT_filename
Defining Color-Map for EWT
= ["#FFFFFF", "#0000FF"]
EWT_colors = create_gradient(EWT_colors, 100)
EWT_gradient = LinearSegmentedColormap.from_list(name="EWT", colors=EWT_colors)
EWT_cmap plot_cmap(EWT_cmap)
Mapping EWT with color ramp
= rxr.open_rasterio(EWT_filename).squeeze("band", drop=True)
EWT = np.nanquantile(np.array(EWT), [0.02, 0.98])
EWT_vmin, EWT_vmax = "Equivalent Water Thickness "
EWT_title
=np.nan).hvplot.image(
EWT.rio.reproject(WEBMAP_PROJECTION,nodata=WEBMAP_PROJECTION,
crs=EWT_gradient,
cmap=FIG_ALPHA,
alpha=BASEMAP,
tiles=(EWT_vmin, EWT_vmax),
clim=EWT_title
title )
Loading an ECOSTRESS LST granule
= join(data_dir, "ECOv002_L2T_LSTE_26860_001_10SGD_20230401T203733_0710_01_LST.tif")
ST_filename ST_filename
The temperatures in the L2T_LSTE
product are given in Kelvin. To convert them to Celsius, we subtract 273.15.
= rxr.open_rasterio(ST_filename).squeeze('band', drop=True)
ST_K = ST_K - 273.15
ST_C
"ST.tif"))
ST_C.rio.to_raster(join(data_dir,
= np.nanquantile(np.array(ST_C), [0.02, 0.98])
ST_vmin, ST_vmax = "ECOSTRESS Surface Temperature (Celsius)"
ST_title
=np.nan).hvplot.image(
ST_C.rio.reproject(WEBMAP_PROJECTION,nodata=WEBMAP_PROJECTION,
crs="jet",
cmap=FIG_ALPHA,
alpha=BASEMAP,
tiles=(ST_vmin, ST_vmax),
clim=ST_title
title )
Defining Color Ramps
Color ramps or maps, are used to map valued over a range of colors based on the data and goals of visualization. Here we show how to create a custom color ramp for the data using hex codes. The examples shown are red-green colorblind friendly.
= [
ST_colors "#054fb9",
"#0073e6",
"#8babf1",
"#cccccc",
"#e1ad01",
"#f57600",
"#c44601"
]
= create_gradient(ST_colors, 100)
ST_gradient = LinearSegmentedColormap.from_list(name="ST", colors=ST_colors)
ST_cmap plot_cmap(ST_cmap)
= np.nanquantile(np.array(ST_C), [0.02, 0.98])
ST_vmin, ST_vmax = "ECOSTRESS Surface Temperature (Celsius)"
ST_title
ST_C.rio.reproject(WEBMAP_PROJECTION).hvplot.image(=WEBMAP_PROJECTION,
crs=ST_gradient,
cmap=FIG_ALPHA,
alpha=BASEMAP,
tiles=(ST_vmin, ST_vmax),
clim=ST_title
title )
Loading ECOSTRESS Evapotranspiration granule
= join(data_dir, "ECOv002_L3T_JET_26860_001_10SGD_20230401T203732_0700_01_ETdaily.tif")
ET_filename ET_filename
Now create a color ramp representing ECOSTRESS evapotranspiration.
= [
ET_colors "#f6e8c3",
"#d8b365",
"#99974a",
"#53792d",
"#6bdfd2",
"#1839c5"
]
= create_gradient(ET_colors, 100)
ET_gradient = LinearSegmentedColormap.from_list(name="ET", colors=ET_colors)
ET_cmap plot_cmap(ET_cmap)
The daily evapotranspiration product from ECOSTRESS is given in millimeters per day.
= rxr.open_rasterio(ET_filename).squeeze('band', drop=True)
ET = np.nanquantile(np.array(ET), [0.02, 0.98])
ET_vmin, ET_vmax = "ECOSTRESS Evapotranspiration (mm / day)"
ET_title
ET.rio.reproject(WEBMAP_PROJECTION).hvplot.image(=WEBMAP_PROJECTION,
crs=ET_gradient,
cmap=FIG_ALPHA,
alpha=BASEMAP,
tiles=(ET_vmin, ET_vmax),
clim=ET_title
title )
5.4. Subsetting the data over our ROI
To clip the raster image to the extent of the vector dataset, we want to subset the raster to the bounds of the vector dataset. This dataset is included here in GeoJSON format, which we’ll load in as a geodataframe using the geopandas
package.
The GeoJSON is provided by CalAg and includes information on agricultural field boundaries in the Santa Barbara County. We can use this code to select the largest polygons that are classified as vineyards.
Using the rioxarray clip
function, we can subset the data using a geojson. to visualize the area that are vineyards are located, I have selected a boundary polygon. Overlaid are the outlines of some of the larger vineyards in the scene.
= 'WINE'
target_text
= ag_latlon[ag_latlon['crop_list'].str.contains(target_text, case=False, na=False)]
filtered_gdf
= filtered_gdf['size'].quantile(0.25)
Q1 = filtered_gdf['size'].quantile(0.75)
Q3 = Q3 - Q1
IQR
# Define the threshold as Q3 + 1.5 * IQR (adjust multiplier as needed)
= Q3 + 1.5 * IQR
threshold_size
# Filter polygons based on size greater than the threshold
= filtered_gdf[filtered_gdf['size'] > threshold_size]
vineyard_polygons vineyard_polygons
= vineyard_polygons.to_crs("EPSG:4326").hvplot.polygons(
vineyard_plot =WEBMAP_PROJECTION,
crs=BASEMAP,
tiles=FIG_ALPHA,
alpha=FIG_WIDTH_PX,
width=FIG_HEIGHT_PX,
height= 'none',
fill_color = 'r',
line_color = 3,
line_width ="Vineyards of Acreage greater than IQR3",
title=1.5
fontscale
)
'bokeh')
hv.extension( vineyard_plot
Interactively mapping our data
Now that we have selected a subset of the agriculture in the area we can subset our data using the rioxarray
function clip
.
= NDVI.rio.clip(vineyard_polygons.geometry.values,vineyard_polygons.crs, all_touched=True)
NDVI_vineyard "NDVI_vineyard.tif"))
NDVI_vineyard.rio.to_raster(join(data_dir,= rxr.open_rasterio(join(data_dir,"NDVI_vineyard.tif")).squeeze("band", drop=True)
NDVI_vineyard = np.nanquantile(np.array(NDVI_vineyard), [0.02, 0.98])
NDVI_vmin, NDVI_vmax
= ST_C.rio.clip(vineyard_polygons.geometry.values,vineyard_polygons.crs, all_touched=True)
ST_vineyard "ST_vineyard.tif"))
ST_vineyard.rio.to_raster(join(data_dir,= rxr.open_rasterio(join(data_dir,"ST_vineyard.tif")).squeeze("band", drop=True)
ST_vineyard = np.nanquantile(np.array(ST_vineyard), [0.02, 0.98])
ST_vmin, ST_vmax
= EWT.rio.clip(vineyard_polygons.geometry.values,vineyard_polygons.crs, all_touched=True)
EWT_vineyard "EWT_vineyard.tif"))
EWT_vineyard.rio.to_raster(join(data_dir,= rxr.open_rasterio(join(data_dir,"EWT_vineyard.tif")).squeeze("band", drop=True)
EWT_vineyard = np.nanquantile(np.array(EWT_vineyard), [0.02, 0.98])
EWT_vmin, EWT_vmax
= ET.rio.clip(vineyard_polygons.geometry.values,vineyard_polygons.crs, all_touched=True)
ET_vineyard "ET_vineyard.tif"))
ET_vineyard.rio.to_raster(join(data_dir,= rxr.open_rasterio(join(data_dir,"ET_vineyard.tif")).squeeze("band", drop=True)
ET_vineyard = np.nanquantile(np.array(ET_vineyard), [0.02, 0.98]) ET_vmin, ET_vmax
5.5 Interactive Maps
We can use holoviews
to plot an interactive clickable map of these subsets over our ROI. When you click a point on the map, you can see the coordinates and data for that pixel. You can uncomment the crs and tiles option lines below to visualize the data on a basemap, but this breaks thee interactive feature. Additionally, the interactive feature seems to also affect the color bar min and max limits (clim) whether applied manually or automatically. This may cause the color ramp to display incorrectly. Selecting and exporting values will work properly.
= []
clicked_values
# Reproject raster data
= NDVI_vineyard.rio.reproject(WEBMAP_PROJECTION, nodata=np.nan)
NDVI_vineyard_projected = ST_vineyard.rio.reproject(WEBMAP_PROJECTION, nodata=np.nan)
ST_vineyard_projected = EWT_vineyard.rio.reproject(WEBMAP_PROJECTION, nodata=np.nan)
EWT_vineyard_projected = ET_vineyard.rio.reproject(WEBMAP_PROJECTION, nodata=np.nan)
ET_vineyard_projected
# Load geospatial data
= gpd.read_file(join(data_dir,"vineyard_polygons_filtered.geojson"))
vineyard_polygons
# Set up the SingleTap stream
= SingleTap()
stream
# Define a function to process clicks
def interactive_click(x, y):
if None not in [x, y]:
= NDVI_vineyard_projected.sel(x=x, y=y, method="nearest").values
NDVI_value = ST_vineyard_projected.sel(x=x, y=y, method="nearest").values
ST_value = ET_vineyard_projected.sel(x=x, y=y, method="nearest").values
ET_value = EWT_vineyard_projected.sel(x=x, y=y, method="nearest").values
EWT_value print(f"Lon: {x}, Lat: {y}, NDVI: {NDVI_value}, ST: {ST_value}, EWT: {EWT_value}, ET: {ET_value}")
"Lon": x, "Lat": y, "NDVI": NDVI_value, "ST": ST_value, "ET": ET_value, "EWT": EWT_value})
clicked_values.append({
return hv.Points((x, y)).opts(color='green', size=10)
# Create two separate plots and share the stream between them
= NDVI_vineyard_projected.hvplot.image(
NDVI_vineyard_map #crs=WEBMAP_PROJECTION,
=NDVI_gradient,
cmap=(NDVI_vmin, NDVI_vmax),
clim=FIG_ALPHA,
alpha#tiles=BASEMAP,
=NDVI_title
title* vineyard_polygons.to_crs(WEBMAP_PROJECTION).hvplot.polygons(
) #crs=WEBMAP_PROJECTION,
=LINE_COLOR,
line_color=LINE_WIDTH,
line_width=FILL_COLOR
fill_color=600, height=400, fontscale=1.5)
).opts(width
= ST_vineyard_projected.hvplot.image(
ST_vineyard_map #crs=WEBMAP_PROJECTION,
=ST_gradient,
cmap#clim=(ST_vmin, ST_vmax),
=FIG_ALPHA,
alpha#tiles=BASEMAP,
=ST_title
title* vineyard_polygons.to_crs(WEBMAP_PROJECTION).hvplot.polygons(
) #crs=WEBMAP_PROJECTION,
=LINE_COLOR,
line_color=LINE_WIDTH,
line_width=FILL_COLOR
fill_color=600, height=400, fontscale=1.5)
).opts(width
# Duplicate the plots for the second row
= EWT_vineyard_projected.hvplot.image(
EWT_vineyard_map #crs=WEBMAP_PROJECTION,
=EWT_gradient,
cmap#clim=(EWT_vmin, EWT_vmax),
=FIG_ALPHA,
alpha#tiles=BASEMAP,
=EWT_title
title* vineyard_polygons.to_crs(WEBMAP_PROJECTION).hvplot.polygons(
) #crs=WEBMAP_PROJECTION,
=LINE_COLOR,
line_color=LINE_WIDTH,
line_width=FILL_COLOR
fill_color=600, height=400, fontscale=1.5)
).opts(width
= ET_vineyard_projected.hvplot.image(
ET_vineyard_map #crs=WEBMAP_PROJECTION,
=True,
rasterize=ET_gradient,
cmap#clim=(ET_vmin, ET_vmax),
=FIG_ALPHA,
alpha#tiles=BASEMAP,
=ET_title
title* vineyard_polygons.to_crs(WEBMAP_PROJECTION).hvplot.polygons(
) #crs=WEBMAP_PROJECTION,
=LINE_COLOR,
line_color=LINE_WIDTH,
line_width=FILL_COLOR
fill_color=600, height=400, fontscale=1.5)
).opts(width
# Create a DynamicMap for interactive clicks
= hv.DynamicMap(interactive_click, streams=[stream])
click
# Display the plots in a 2x2 grid with two rows along with the interactive click behavior
= (click * NDVI_vineyard_map * click + click * ST_vineyard_map * click +
layout * EWT_vineyard_map * click + click * ET_vineyard_map * click).cols(2)
click layout
Save the data from our clicked points into a csv
that we can use for plotting.
# Save the clicked values to a CSV file after all clicks are processed
if clicked_values:
= pd.DataFrame(clicked_values)
df "clicked_values.csv"), index=False)
df.to_csv(join(data_dir,print("Clicked values saved to 'clicked_values.csv'")
else:
print("No clicked values to save.")
5.6 Scatter Plots
Now we can use the data saved into our csv to create scatter plots to visualize the correlations among ET, ST, EWT, and NDVI.
# Replace "your_data.csv" with the actual CSV file containing your data
= pd.read_csv(join(data_dir,"clicked_values.csv"))
df
# Filter out Lat/Lon Columns
= df.columns[2:]
filtered_columns
# Get combinations of all pairs of columns
= [(col1, col2) for i, col1 in enumerate(filtered_columns) for col2 in filtered_columns[i + 1:]]
combinations
# Set up subplots without equal aspect ratio and sharex/sharey set to False
= plt.subplots(nrows=3, ncols=2, figsize=(10, 12), sharex=False, sharey=False)
fig, axes =0.5)
fig.subplots_adjust(hspace
# Iterate over combinations and create scatter plots with best-fit lines
for (col1, col2), ax in zip(combinations, axes.flatten()):
=col1, y=col2, data=df, ax=ax, ci=None, line_kws={"color": "red"})
sns.regplot(x
# Filter out rows with missing values in the selected columns
= df[[col1, col2]].dropna()
filtered_df
# Calculate and plot the best-fit line using linear regression
= linregress(filtered_df[col1], filtered_df[col2])
slope, intercept, r_value, p_value, std_err = np.linspace(filtered_df[col1].min(), filtered_df[col1].max(), 100)
x_range = slope * x_range + intercept
y_fit ='blue', linestyle='--', label=f'Best Fit (R={r_value:.2f})')
ax.plot(x_range, y_fit, color
ax.legend()
ax.set_xlabel(col1)
ax.set_ylabel(col2)f"{col1} vs {col2}")
ax.set_title(
# Set x and y axis ticks with different values
min(), filtered_df[col1].max(), 5))
ax.set_xticks(np.linspace(filtered_df[col1].min(), filtered_df[col2].max(), 5))
ax.set_yticks(np.linspace(filtered_df[col2].
# Adjust layout
plt.tight_layout()
# Show the plot
plt.show()