ECOv002-calval-tables 1.5.0__py3-none-any.whl → 1.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,3 +3,4 @@ from .error_funcs import *
3
3
  from .plot_funcs import *
4
4
  from .ec_lib import *
5
5
  from .plot_single_model import *
6
+ from .upscale_to_daylight import *
@@ -6,6 +6,8 @@ import geopandas as gpd
6
6
 
7
7
  from shapely.geometry import Point
8
8
 
9
+ from .upscale_to_daylight import upscale_to_daylight
10
+
9
11
  def load_combined_eco_flux_ec_filtered() -> pd.DataFrame:
10
12
  """
11
13
  Load the filtered eddy covariance (EC) flux dataset used for ECOSTRESS Collection 2 ET product validation.
@@ -64,4 +66,6 @@ def load_calval_table() -> gpd.GeoDataFrame:
64
66
  # Convert merged DataFrame to GeoDataFrame
65
67
  gdf = gpd.GeoDataFrame(merged_df, geometry=merged_df["geometry"], crs="EPSG:4326")
66
68
 
69
+ gdf = upscale_to_daylight(gdf)
70
+
67
71
  return gdf
@@ -3,28 +3,51 @@ import matplotlib.pyplot as plt
3
3
  import numpy as np
4
4
  import matplotlib.lines as mlines
5
5
  import os
6
+ from . import error_funcs
7
+
6
8
  import pandas as pd
7
9
  from typing import Optional
8
- from . import error_funcs
9
10
 
10
11
  def quick_look_plot_single_model(
11
- big_df_ss: pd.DataFrame,
12
- time: str,
13
- model_col: str,
12
+ calval_table_df: pd.DataFrame,
13
+ model_output_variable_name: str,
14
14
  model_name: str,
15
- LE_var: str = 'LEcorr50',
16
- output_filename: Optional[str] = None
17
- ) -> None:
15
+ insitu_variable_name: str = 'LEcorr50'
16
+ ) -> plt.Figure:
18
17
  """
19
- Plots the results for a single model against flux tower LE.
20
- Parameters:
21
- big_df_ss: pd.DataFrame containing all data
22
- time: string for output file naming
23
- model_col: column name for model output
24
- model_name: display name for the model
25
- LE_var: column name for flux tower LE (default 'LEcorr50')
26
- output_filename: Optional path for output figure file. If None, figure is not saved.
18
+ Generate a scatter plot comparing model output to flux tower latent energy (LE) measurements, color-coded by vegetation type, with error bars and annotated performance metrics.
19
+
20
+ This function is designed for visual evaluation of model performance against observed data, and is suitable for use in Jupyter notebooks (returns a matplotlib Figure object for direct display).
21
+
22
+ Parameters
23
+ ----------
24
+ calval_table_df : pd.DataFrame
25
+ DataFrame containing all relevant data for plotting and analysis. Must include columns for vegetation type, model output, flux tower LE, and optionally uncertainty/error columns.
26
+ model_output_variable_name : str
27
+ Column name in `calval_table_df` for the model output to be plotted on the y-axis.
28
+ model_name : str
29
+ Display name for the model, used as the plot title.
30
+ insitu_variable_name : str, optional
31
+ Column name for the flux tower LE values to be plotted on the x-axis (default is 'LEcorr50').
32
+
33
+ Returns
34
+ -------
35
+ matplotlib.figure.Figure
36
+ The generated matplotlib Figure object, ready for display in a Jupyter notebook or further customization.
37
+
38
+ Notes
39
+ -----
40
+ - Points are color-coded by vegetation type using a predefined color map.
41
+ - Error bars are included if uncertainty columns are present in the DataFrame.
42
+ - The plot includes a 1:1 reference line, a regression line, and annotations for RMSE, bias, and R².
43
+ - A legend for vegetation types is included below the plot.
44
+
45
+ Example
46
+ -------
47
+ >>> fig = quick_look_plot_single_model(df, 'model_output', 'My Model')
48
+ >>> fig.show()
27
49
  """
50
+ # Define colors for each vegetation type for scatter plot visualization
28
51
  colors = {
29
52
  'CRO': '#FFEC8B', 'CSH': '#AB82FF', 'CVM': '#8B814C',
30
53
  'DBF': '#98FB98', 'EBF': '#7FFF00', 'ENF': '#006400',
@@ -32,38 +55,60 @@ def quick_look_plot_single_model(
32
55
  'SAV': '#FFD700', 'WAT': '#98F5FF', 'WET': '#4169E1',
33
56
  'WSA': '#CDAA7D'
34
57
  }
35
- scatter_colors = [colors.get(veg, 'gray') for veg in big_df_ss['vegetation']]
58
+ # Assign a color to each data point based on its vegetation type
59
+ scatter_colors = [colors.get(veg, 'gray') for veg in calval_table_df['vegetation']]
60
+ # Create a reference 1:1 line for the plot (where model output equals flux tower measurement)
36
61
  one2one = np.arange(-250, 1200, 5)
37
- def calculate_metrics(x, y):
62
+ # Helper function to calculate performance metrics between model and observed data
63
+ def calculate_metrics(x: np.ndarray, y: np.ndarray) -> tuple[float, float, float, float, float]:
64
+ # RMSE: Root Mean Square Error
38
65
  rmse = error_funcs.rmse(y, x)
66
+ # R2: Coefficient of Determination
39
67
  r2 = error_funcs.R2_fun(y, x)
68
+ # Slope and intercept from linear regression
40
69
  slope, intercept = error_funcs.lin_regress(y, x)
70
+ # Bias: Mean difference between model and observed
41
71
  bias = error_funcs.BIAS_fun(y,x)
42
72
  return rmse, r2, slope, intercept, bias
43
- x = big_df_ss[LE_var].to_numpy()
44
- y = big_df_ss[model_col].to_numpy()
45
- err = big_df_ss['ETinstUncertainty'].to_numpy() if 'ETinstUncertainty' in big_df_ss.columns else None
46
- xerr = big_df_ss[['LE_filt', 'LEcorr50', 'LEcorr_ann']].std(axis=1).to_numpy() if all(col in big_df_ss.columns for col in ['LE_filt', 'LEcorr50', 'LEcorr_ann']) else None
73
+ # Extract flux tower LE values and model output values from DataFrame
74
+ x = calval_table_df[insitu_variable_name].to_numpy()
75
+ y = calval_table_df[model_output_variable_name].to_numpy()
76
+ # Extract uncertainty in model output if available
77
+ err = calval_table_df['ETinstUncertainty'].to_numpy() if 'ETinstUncertainty' in calval_table_df.columns else None
78
+ # Calculate standard deviation across three LE columns for x error bars if all columns are present
79
+ xerr = calval_table_df[['LE_filt', 'LEcorr50', 'LEcorr_ann']].std(axis=1).to_numpy() if all(col in calval_table_df.columns for col in ['LE_filt', 'LEcorr50', 'LEcorr_ann']) else None
80
+ # Compute performance metrics for the plot annotation
47
81
  rmse, r2, slope, intercept, bias = calculate_metrics(x, y)
82
+ # Count number of valid (non-NaN) data points
48
83
  number_of_points = np.sum(~np.isnan(y) & ~np.isnan(x))
84
+ # Create the matplotlib figure and axis
49
85
  fig, ax = plt.subplots(figsize=(6, 6))
86
+ # Plot error bars if uncertainty data is available
50
87
  if err is not None and xerr is not None:
51
- ax.errorbar(x, y, yerr=err, xerr=xerr, fmt='none', ecolor='lightgray')
88
+ ax.errorbar(x, y, yerr=err, xerr=xerr, fmt='', ecolor='lightgray')
89
+ # Plot the scatter points, colored by vegetation type
52
90
  ax.scatter(x, y, c=scatter_colors, marker='o', s=6, zorder=4)
91
+ # Plot the 1:1 reference line (ideal fit)
53
92
  ax.plot(one2one, one2one, '--', c='k')
93
+ # Plot the regression line (actual fit)
54
94
  ax.plot(one2one, one2one * slope + intercept, '--', c='gray')
95
+ # Set plot title and axis limits
55
96
  ax.set_title(model_name)
56
97
  ax.set_xlim([-250, 1200])
57
98
  ax.set_ylim([-250, 1200])
99
+ # Set axis labels
58
100
  ax.set_ylabel('Model LE Wm$^{-2}$',fontsize=14)
59
101
  ax.set_xlabel('Flux Tower LE Wm$^{-2}$',fontsize=14)
102
+ # Add subplot label
60
103
  ax.text(-0.1, 1.1, 'a)', transform=ax.transAxes, fontsize=16, fontweight='bold', va='top', ha='right')
104
+ # Annotate plot with regression equation, RMSE, bias, and R2
61
105
  ax.text(500, -200, f'y = {slope:.1f}x + {intercept:.1f} \nRMSE: {rmse:.1f} Wm$^-$² \nbias: {bias:.1f} Wm$^-$² \nR$^2$: {r2:.2f}', fontsize=12)
106
+ # Create legend handles for each vegetation type
62
107
  scatter_handles = [mlines.Line2D([], [], color=color, marker='o', linestyle='None', markersize=6, label=veg) for veg, color in colors.items()]
108
+ # Add legend to the figure
63
109
  fig.legend(handles=scatter_handles, loc='lower center', bbox_to_anchor=(0.5, -0.05), ncol=7, title='Vegetation Type',fontsize=10)
64
- fig.subplots_adjust(bottom=0.2)
110
+ # Adjust layout for better appearance
65
111
  fig.tight_layout()
66
- if output_filename is not None:
67
- fig.savefig(output_filename, dpi=600, bbox_inches='tight')
68
- # Return the figure object for notebook display
112
+ # Prevent automatic display in Jupyter by closing the figure before returning
113
+ plt.close(fig)
69
114
  return fig
@@ -0,0 +1,18 @@
1
+ import pandas as pd
2
+ from daylight_evapotranspiration import daylight_ET_from_instantaneous_LE
3
+
4
+ def upscale_to_daylight(df: pd.DataFrame, prefix: str = "insitu_") -> pd.DataFrame:
5
+ daylight_results = daylight_ET_from_instantaneous_LE(
6
+ LE_instantaneous_Wm2=df.LEcorr50,
7
+ Rn_instantaneous_Wm2=df.NETRAD_filt,
8
+ G_instantaneous_Wm2=df.G_filt,
9
+ time_UTC=df.time_UTC,
10
+ geometry=df.geometry
11
+ )
12
+
13
+ daylight_results_prefixed = {f"{prefix}{k}": v for k, v in daylight_results.items()}
14
+
15
+ for key, value in daylight_results_prefixed.items():
16
+ df[key] = value
17
+
18
+ return df
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ECOv002-calval-tables
3
- Version: 1.5.0
3
+ Version: 1.7.1
4
4
  Summary: Priestley-Taylor Jet Propulsion Laboratory Soil Moisutre Evapotranspiration Model
5
5
  Author-email: Gregory Halverson <gregory.h.halverson@jpl.nasa.gov>, Zoe Pierrat <zoe.a.pierrat@jpl.nasa.gov>
6
6
  Project-URL: Homepage, https://github.com/gregory-halverson/ECOv002-calval-tables
@@ -9,6 +9,7 @@ Classifier: Operating System :: OS Independent
9
9
  Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
+ Requires-Dist: daylight-evapotranspiration>=v1.3.4
12
13
  Requires-Dist: pandas
13
14
  Requires-Dist: geopandas
14
15
  Requires-Dist: numpy
@@ -0,0 +1,20 @@
1
+ ECOv002_calval_tables/ECOv002_calval_tables.py,sha256=6yW19Mvgg94O9rZVCRRjiefjG-a6rwRSyFt-RqiXcIo,170
2
+ ECOv002_calval_tables/__init__.py,sha256=oO0pVascuJs247UwpXq2o23wQPThM1bWEwUhSYiNzMY,60
3
+ ECOv002_calval_tables/combined_eco_flux_EC_filtered.csv,sha256=uSRv1CBMJKUWBgtMlRHB1359kiUFUL1ez2c4m7C4DxU,745225
4
+ ECOv002_calval_tables/ec_lib.py,sha256=0k7CldHMFrcq77OqsXdreJQNEVUHVMCAHmUWNo-63fA,24676
5
+ ECOv002_calval_tables/error_funcs.py,sha256=WFsoYvZyJVF-ppBn0BV2JJADPZN48OJBG2QZ6Lt3uCk,11275
6
+ ECOv002_calval_tables/load_tables.py,sha256=mm41qAMhq7OUpzVeEnEbb2zX240RVOFr3rUe2qhmHqU,2775
7
+ ECOv002_calval_tables/metadata_ebc_filt.csv,sha256=74AeCH5DG2Zu8ZFBXUZAW95hlsCjK9K3t6BWRRReR3s,13975
8
+ ECOv002_calval_tables/plot_funcs.py,sha256=asu43YEtWhNehBRKx5ujIiqNn6iePbXW6fC_8Tl8yzI,23988
9
+ ECOv002_calval_tables/plot_single_model.py,sha256=NDCGmY5SfjhTzoH9NKa3uSH_QLvqdWdM-J7QMxKFkpo,5890
10
+ ECOv002_calval_tables/upscale_to_daylight.py,sha256=LrnZ0loI0o30bpT4coET7fmWMzHFVpaylZTfrdv4OqE,622
11
+ ECOv002_calval_tables/version.py,sha256=DOnf2BIAf7HAp9SIUIetrDgRoZDL0o7JuKSiC7_ZT_I,87
12
+ ecov002_calval_tables-1.7.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
13
+ tests/test_import_ECOv002_calval_tables.py,sha256=aGFA47BrECJroPH92aO5lT53TPNSd6TI1A9uNVT8pRY,74
14
+ tests/test_import_dependencies.py,sha256=zrPUyx8ikAIdpKVESnanD57T_ttXGE2lca9VoVCSBnM,339
15
+ tests/test_load_calval_table.py,sha256=1ioR2yzv8EfWEU2fSqNZv0yaJPgIn03IAOtacF1td3M,499
16
+ tests/test_load_metadata_ebc_filt.py,sha256=PrBxmR85Q_TgWHZx2sBrtvrlsoCm9FMkGFwSU1ySIVc,561
17
+ ecov002_calval_tables-1.7.1.dist-info/METADATA,sha256=F72ebTYBgNLbegVh7QGyXL9UIq2IP1CRbgrUhyibyt8,3363
18
+ ecov002_calval_tables-1.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ ecov002_calval_tables-1.7.1.dist-info/top_level.txt,sha256=Ps10Nh0VWk-CGqZd2xXwmvGGQ-MEhNaWT0BFGId_M3I,28
20
+ ecov002_calval_tables-1.7.1.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ def test_import_ECOv002_calval_tables():
2
+ import ECOv002_calval_tables
@@ -0,0 +1,17 @@
1
+ import pytest
2
+
3
+ # List of dependencies
4
+ dependencies = [
5
+ "pandas",
6
+ "geopandas",
7
+ "numpy",
8
+ "sklearn",
9
+ "scipy",
10
+ "matplotlib",
11
+ "shapely"
12
+ ]
13
+
14
+ # Generate individual test functions for each dependency
15
+ @pytest.mark.parametrize("dependency", dependencies)
16
+ def test_dependency_import(dependency):
17
+ __import__(dependency)
@@ -0,0 +1,14 @@
1
+ import unittest
2
+ import geopandas as gpd
3
+ from ECOv002_calval_tables import load_calval_table
4
+
5
+ class TestLoadCalvalTable(unittest.TestCase):
6
+ def test_returns_geodataframe_with_valid_geometry(self):
7
+ gdf = load_calval_table()
8
+ self.assertIsInstance(gdf, gpd.GeoDataFrame)
9
+ self.assertIn('geometry', gdf.columns)
10
+ self.assertTrue(all(gdf.geometry.geom_type == 'Point'))
11
+ self.assertTrue(gdf.geometry.is_valid.all())
12
+
13
+ if __name__ == "__main__":
14
+ unittest.main()
@@ -0,0 +1,15 @@
1
+ import unittest
2
+ import geopandas as gpd
3
+ from ECOv002_calval_tables import load_metadata_ebc_filt
4
+
5
+ class TestLoadMetadataEbcFilt(unittest.TestCase):
6
+ def test_returns_geodataframe_with_valid_geometry(self):
7
+ gdf = load_metadata_ebc_filt()
8
+ self.assertIsInstance(gdf, gpd.GeoDataFrame)
9
+ self.assertIn('geometry', gdf.columns)
10
+ # Check all geometries are valid Points
11
+ self.assertTrue(all(gdf.geometry.geom_type == 'Point'))
12
+ self.assertTrue(gdf.geometry.is_valid.all())
13
+
14
+ if __name__ == "__main__":
15
+ unittest.main()
@@ -1,15 +0,0 @@
1
- ECOv002_calval_tables/ECOv002_calval_tables.py,sha256=k8y7TEevSX3Y6NhEU1qg_CySd-f_T4omNNJFc7ZxXcY,135
2
- ECOv002_calval_tables/__init__.py,sha256=oO0pVascuJs247UwpXq2o23wQPThM1bWEwUhSYiNzMY,60
3
- ECOv002_calval_tables/combined_eco_flux_EC_filtered.csv,sha256=uSRv1CBMJKUWBgtMlRHB1359kiUFUL1ez2c4m7C4DxU,745225
4
- ECOv002_calval_tables/ec_lib.py,sha256=0k7CldHMFrcq77OqsXdreJQNEVUHVMCAHmUWNo-63fA,24676
5
- ECOv002_calval_tables/error_funcs.py,sha256=WFsoYvZyJVF-ppBn0BV2JJADPZN48OJBG2QZ6Lt3uCk,11275
6
- ECOv002_calval_tables/load_tables.py,sha256=m9UEz07yIrM-m2PaYv2g_TnBLN5Wjg7yUuMzs-T25Gk,2685
7
- ECOv002_calval_tables/metadata_ebc_filt.csv,sha256=74AeCH5DG2Zu8ZFBXUZAW95hlsCjK9K3t6BWRRReR3s,13975
8
- ECOv002_calval_tables/plot_funcs.py,sha256=asu43YEtWhNehBRKx5ujIiqNn6iePbXW6fC_8Tl8yzI,23988
9
- ECOv002_calval_tables/plot_single_model.py,sha256=9OyyqU3IsiuV20yFSULQFaec87xdZhbZevMTIG83xBg,3279
10
- ECOv002_calval_tables/version.py,sha256=DOnf2BIAf7HAp9SIUIetrDgRoZDL0o7JuKSiC7_ZT_I,87
11
- ecov002_calval_tables-1.5.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
- ecov002_calval_tables-1.5.0.dist-info/METADATA,sha256=3E1QUMG0_d2o-n2Bs_rGo0ZwmF6fvufK7xrQU44h7qI,3312
13
- ecov002_calval_tables-1.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- ecov002_calval_tables-1.5.0.dist-info/top_level.txt,sha256=ePbA84g_XQHWQUAQEVo6gmyuW0WLUzPg2IfHyoGqUqg,22
15
- ecov002_calval_tables-1.5.0.dist-info/RECORD,,