Report generation#
- class echopop.reports.reporter.Reporter(save_directory: str | Path, verbose: bool = True)#
Reporter - utility class for writing a suite of Excel reports used in the FEAT pipeline.
This class centralizes common report generation routines (age-length tables, kriging inputs, haul counts, transect reports, etc.) and provides consistent validation and file handling.
- Attributes:
- save_directorypathlib.Path
Directory where generated reports will be saved. Created if it does not exist.
- verbosebool
If True, print brief status messages when files are written.
Methods
aged_length_haul_counts_report(filename, ...)Create and write an aged-length haul counts Excel report.
kriged_aged_biomass_mesh_report(filename, ...)Produce an Excel workbook containing kriged age-specific biomass sheets.
kriged_length_age_abundance_report(filename, ...)Create kriged age-length abundance reports and write a 3-sheet workbook.
kriged_length_age_biomass_report(filename, ...)Create kriged age-length biomass reports (values converted to metric megatonnes).
kriged_mesh_results_report(filename, ...)Write a single-sheet kriged mesh results report with uncertainty metrics.
kriging_input_report(filename, sheetname, ...)Save key kriging input variables to an Excel sheet.
total_length_haul_counts_report(filename, ...)Create an Excel report of combined specimen and length haul totals.
transect_aged_biomass_report(filename, ...)Produce a transect-level aged biomass workbook (three sex-specific sheets).
Write an un-kriged transect length-age abundance workbook (3 sheets).
transect_length_age_biomass_report(filename, ...)Write an un-kriged transect age-length biomass workbook.
transect_population_results_report(filename, ...)Write transect-level population results with FEAT-friendly column names.
Notes
Methods that accept datatables expect a dict-like container with at minimum an ‘aged’ DataFrame.
The helper function build_age_length_full_tables is used internally to consolidate logic used across multiple report methods (unifying indexing, stacking/unstacking and scaling).
Examples
Basic usage >>> from pathlib import Path >>> reports = Reporter(Path(“reports_out”), verbose=True) >>> # Write a kriging input sheet >>> reports.kriging_input_report(transect_df, “krig_input.xlsx”, “KrigeInput”)
Creating an aged-length haul counts report >>> # bio_data must include [‘sex’,’length’,’length_bin’,’haul_num’] >>> sheetnames = {“male”: “Male”, “female”: “Female”, “all”: “All”} >>> reports.aged_length_haul_counts_report(bio_data, “haul_counts.xlsx”, sheetnames)
Creating kriged age-length abundance/biomass reports >>> # datatables expected to contain keys ‘aged’ and ‘unaged’ (pandas DataFrames) >>> exclude_filter = {“stratum”: [“exclude_this_stratum”]} >>> reports.kriged_length_age_abundance_report(datatables, exclude_filter, “krig_abund.xlsx”, sheetnames) >>> reports.kriged_length_age_biomass_report(datatables, exclude_filter, “krig_biomass.xlsx”, sheetnames)
Preparing and writing transect reports >>> reports.transect_length_age_abundance_report(datatables, “transect_abund.xlsx”, sheetnames) >>> reports.transect_length_age_biomass_report(datatable, “transect_biomass.xlsx”, sheetnames)
- aged_length_haul_counts_report(filename: str, sheetnames: dict[str, str], bio_data: DataFrame) None#
Create and write an aged-length haul counts Excel report.
The method builds pivot tables of haul-level length counts stratified by sex and writes three sheets (male, female, all) to an Excel workbook in self.save_directory.
- Parameters:
- filenamestr
Name of the Excel file to create (relative to self.save_directory).
- sheetnamesdict
Mapping with keys ‘male’,’female’,’all’ and values for the worksheet names to use.
- bio_datapandas.DataFrame
Raw biological specimen data. Required columns: - ‘sex’ (values expected: ‘male’, ‘female’, potentially ‘unsexed’) - ‘length’ (numeric) - ‘length_bin’ (interval-like objects with .mid) - ‘haul_num’ (identifier for haul)
- Returns:
- None
- Raises:
- TypeError
If bio_data is not a pandas DataFrame or filename is not a str or sheetnames not a dict.
- KeyError
If bio_data is missing any of the required columns or sheetnames lacks required keys.
- ValueError
If bio_data has no rows after filtering to male/female (no content to pivot).
Examples
>>> from pathlib import Path >>> reports = Reporter(Path("out")) >>> sheetnames = {"male":"Male", "female":"Female", "all":"All"} >>> reports.aged_length_haul_counts_report(bio_df, "haul_counts.xlsx", sheetnames)
- kriged_aged_biomass_mesh_report(filename: str, sheetnames: dict[str, str], kriged_data: DataFrame, weight_data: DataArray, kriged_stratum_link: dict[str, str], exclude_filter: dict[str, Any] = None) None#
Produce an Excel workbook containing kriged age-specific biomass sheets.
High-level steps: - Normalize the weight/age distribution (age-weight proportions) per stratum - Rename kriged_data stratum columns using kriged_stratum_link - Multiply kriged biomass by age/sex proportions to produce sex-specific aged biomass - Format/rename columns and write three sheets (all/female/male)
- Parameters:
- filenamestr
Output filename within self.save_directory.
- sheetnamesdict
Mapping sex -> worksheet name with keys ‘all’, ‘female’, ‘male’.
- kriged_datapandas.DataFrame
Kriged mesh with one row per mesh cell and at least the stratum identifier and biomass-related columns. The stratum must be mapped to weight_data using kriged_stratum_link.
- weight_datapandas.DataFrame
Multi-indexed DataFrame representing age-weight distributions. Expected to have a MultiIndex for columns that includes ‘sex’ and a single stratum level (e.g. ‘stratum’).
- kriged_stratum_linkdict
Mapping from column names in kriged_data to the stratum names used by weight_data.
- exclude_filterdict, optional
Optional filters passed to utils.apply_filters to zero-out or exclude strata before computing age proportions.
- Returns:
- None
- Raises:
- TypeError
If kriged_data or weight_data are not pandas DataFrames or other args have wrong types.
- ValueError
If weight_data does not contain exactly one stratum-level name in its column MultiIndex.
- KeyError
If expected strata (after renaming) are missing.
Examples
>>> reports = Reporter("out") >>> sheetnames = {"all":"All", "female":"Fem", "male":"Male"} >>> reports.kriged_aged_biomass_mesh_report("krig_biomass.xlsx", kriged_df, weight_df, {'old':'stratum'}, sheetnames)
- kriged_length_age_abundance_report(filename: str, sheetnames: dict[str, str], datatables: dict[str, DataArray], exclude_filter: dict[str, Any] = None) None#
Create kriged age-length abundance reports and write a 3-sheet workbook.
This method:
Builds aged/unaged full tables (male/female/all), optionally redistributing using the provided
exclude_filtervia apportion.redistribute_population_tableConverts interval indices to numeric midpoints for table rows/columns
Writes per-sex pivot tables using internal write helpers
- Parameters:
- filenamestr
Output workbook filename.
- sheetnamesdict
Mapping sex -> sheet name (keys: ‘male’,’female’,’all’).
- datatablesdict
Dictionary with keys:
‘aged’ : pandas.DataFrame
‘unaged’ : pandas.DataFrame
- exclude_filterdict
Filter dictionary passed to
apportion.reallocate_population_table()to zero/exclude strata.
- Returns:
- None
- Raises:
- TypeError
If datatables is not a dict or other args are wrong types.
- KeyError
If required keys are missing from datatables (must include ‘aged’).
- ValueError
If there’s a structural mismatch during table building.
Examples
>>> reports = Reporter("out") >>> sheetnames = {"male":"Male","female":"Female","all":"All"} >>> reports.kriged_length_age_abundance_report({'aged':aged_df, 'unaged':unaged_df}, {'stratum':['S1']}, "krig_abund.xlsx", sheetnames)
- kriged_length_age_biomass_report(filename: str, sheetnames: dict[str, str], datatable: DataArray, exclude_filter: dict[str, Any] = None) None#
Create kriged age-length biomass reports (values converted to metric megatonnes).
This is similar to kriged_length_age_abundance_report but scales the tables by 1e-9 (to convert from grams/tonnes as required) before writing.
- Parameters:
- filenamestr
Output filename.
- sheetnamesdict
Mapping sex -> worksheet name.
- datatablexarray.DataArray
DataArray of consolidated aged and unaged biomass estimates.
- exclude_filterdict
Filter dict forwarded to
apportion.reallocate_population_table().
- Returns:
- None
- Raises:
- TypeError
If inputs have incorrect types.
- KeyError
If required keys are missing from datatables.
Examples
>>> reports = Reporter("out") >>> reports.kriged_length_age_biomass_report(da_full, {}, "krig_biomass.xlsx", sheetnames)
- kriged_mesh_results_report(filename: str, sheetname: str, kriged_data: DataFrame, kriged_stratum: str, kriged_variable: str, sigma_bs_data: DataFrame, sigma_bs_stratum: str) None#
Write a single-sheet kriged mesh results report with uncertainty metrics.
- Parameters:
- filenamestr
File name to save (in self.save_directory).
- sheetnamestr
Excel worksheet name to use.
- kriged_datapandas.DataFrame
Kriged mesh containing at minimum columns for latitude, longitude, kriged_variable, and cell_cv. The stratum column is indicated by kriged_stratum.
- kriged_stratumstr
Column name or index name in kriged_data identifying stratum; used to align sigma_bs_data.
- kriged_variablestr
Name of the kriged estimate column to include (e.g., ‘biomass’).
- sigma_bs_datapandas.DataFrame
DataFrame containing sigma_bs by stratum. Must include sigma_bs column and a column named by sigma_bs_stratum (or index).
- sigma_bs_stratumstr
Column or index name in sigma_bs_data that identifies strata.
- Returns:
- None
- Raises:
- TypeError
If inputs are not pandas DataFrames or strings.
- KeyError
If required columns are not found in kriged_data or sigma_bs_data.
- ValueError
If shapes or indexes cannot be aligned.
Examples
>>> reports = Reporter("out") >>> reports.kriged_mesh_results_report("krig_mesh.xlsx", "Mesh", krig_df, "stratum", "biomass", sigma_df, "stratum")
- kriging_input_report(filename: str, sheetname: str, transect_data: DataFrame) None#
Save key kriging input variables to an Excel sheet.
- Parameters:
- filenamestr
Output file name saved under self.save_directory.
- sheetnamestr
Worksheet name to use inside the workbook.
- transect_datapandas.DataFrame
Transect-level DataFrame that must contain the following columns: [‘latitude’,’longitude’,’biomass_density’,’nasc’,’number_density’].
- Returns:
- None
- Raises:
- TypeError
If transect_data is not a pandas DataFrame or filename/sheetname not strings.
- KeyError
If transect_data does not contain the required columns.
Examples
>>> reports = Reporter("out") >>> reports.kriging_input_report(transect_df, "krig_input.xlsx", "Input")
- total_length_haul_counts_report(filename: str, sheetnames: dict[str, str], bio_data: dict[str, DataFrame]) None#
Create an Excel report of combined specimen and length haul totals.
The method expects bio_data to contain DataFrames for ‘specimen’ and ‘length’ and will build pivot tables, combine sex-specific and ‘all’ columns and write three sheets.
- Parameters:
- filenamestr
File name to create in self.save_directory.
- sheetnamesDict[str, str]
Mapping sex -> sheet name.
- bio_datadict
Dictionary with keys: - ‘specimen’ : pandas.DataFrame containing specimen records (must include ‘sex’, ‘length_bin’, ‘haul_num’) - ‘length’ : pandas.DataFrame containing length counts (must include ‘sex’, ‘length_bin’, ‘haul_num’, ‘length_count’)
- Returns:
- None
- Raises:
- TypeError
If arguments have incorrect types.
- KeyError
If required keys/columns are missing.
Examples
>>> reports = Reporter("out") >>> sheetnames = {"male":"Male","female":"Female","all":"All"} >>> reports.total_length_haul_counts_report({'specimen':spec_df, 'length':len_df}, "total_counts.xlsx", sheetnames)
- transect_aged_biomass_report(filename: str, sheetnames: dict[str, str], transect_data: DataFrame, weight_data: DataArray, exclude_filter: dict[str, Any] = None) None#
Produce a transect-level aged biomass workbook (three sex-specific sheets).
Steps:
Create age-weight proportions per stratum from weight_data
Apply optional exclude_filter to zero/out strata
Multiply transect biomass by age/sex proportions and write per-sex sheets
- Parameters:
- filenamestr
Output filename.
- sheetnamesdict
Mapping sex -> worksheet name.
- transect_datapandas.DataFrame
Transect-level summary data with a stratum identifier consistent with weight_data.
- weight_datapandas.DataFrame
Multi-indexed age-weight table whose column names include ‘sex’ and exactly one stratum level.
- exclude_filterdict, optional
Passed to utils.apply_filters to zero excluded strata (default: {}).
- Returns:
- None
- Raises:
- TypeError
If inputs have wrong types.
- ValueError
If weight_data does not contain exactly one stratum-level in columns.
Examples
>>> reports = Reporter("out") >>> reports.transect_aged_biomass_report(transect_df, weight_df, "transect_biomass.xlsx", sheetnames)
- transect_length_age_abundance_report(filename: str, sheetnames: dict[str, str], datatables: dict[str, DataArray]) None#
Write an un-kriged transect length-age abundance workbook (3 sheets).
- Parameters:
- filenamestr
Output filename saved into self.save_directory.
- sheetnamesdict
Mapping sex -> worksheet name.
- datatablesdict
Dictionary with keys ‘aged’ (required) and ‘unaged’ (optional). Expected structure matches the other age-length methods (interval indices, age_bin and sex columns).
- Returns:
- None
- Raises:
- TypeError
If inputs are of incorrect types.
- KeyError
If required keys are missing.
Examples
>>> reports = Reporter("out") >>> reports.transect_length_age_abundance_report({'aged':aged_df,'unaged':unaged_df}, "transect_abund.xlsx", sheetnames)
- transect_length_age_biomass_report(filename: str, sheetnames: dict[str, str], datatable: DataArray) None#
Write an un-kriged transect age-length biomass workbook.
This function expects datatable to be a DataFrame with interval-like index for length bins and column levels for age_bin and sex. It converts interval midpoints, composes male/female/all tables and writes an Excel workbook with three sheets (male/female/all).
- Parameters:
- filenamestr
Output workbook filename (relative to save_directory).
- sheetnamesdict
Mapping sex -> worksheet name (keys: ‘male’,’female’,’all’).
- datatablepandas.DataFrame
A DataFrame representing aged counts/weights indexed by intervals (length_bin) with column levels [‘age_bin’,’sex’] (or compatible). Values are expected to be numeric.
- Returns:
- None
- Raises:
- TypeError
If datatable is not a pandas DataFrame or filename/sheetnames have wrong types.
- ValueError
If datatable lacks expected structure (index/columns).
Examples
>>> reports = Reporter("out") >>> reports.transect_length_age_biomass_report(datatable, "transect_biomass.xlsx", sheetnames)
- transect_population_results_report(filename: str, sheetname: str, transect_data: DataFrame, weight_strata_data: DataArray, sigma_bs_stratum: DataFrame, stratum_name: str) None#
Write transect-level population results with FEAT-friendly column names.
The method renames, aligns and exports the transect-level results along with computed sig_b and wgt_per_fish derived from sigma_bs_stratum and weight_strata_data respectively.
- Parameters:
- filenamestr
Output workbook filename.
- sheetnamestr
Worksheet name to use.
- transect_datapandas.DataFrame
Transect-level DataFrame. Must contain a stratum identifier that matches stratum_name.
- weight_strata_datapandas.DataFrame
DataFrame (possibly multi-indexed) providing weight-per-fish or related metrics.
- sigma_bs_stratumpandas.DataFrame
DataFrame indexed or columned by stratum that includes a numeric sigma_bs column.
- stratum_namestr
Column or index name to use as the stratum key to align data between inputs.
- Returns:
- None
- Raises:
- TypeError
If arguments are not the expected types.
- KeyError
If sigma_bs_stratum lacks sigma_bs or stratum_name cannot be found.
Examples
>>> reports = Reporter("out") >>> reports.transect_population_results_report(transect_df, weight_df, sigma_df, "stratum", "transect_pop.xlsx", "PopResults")
Report comparisons#
- echopop.reports.compare.compute_dataset_differences(echopro_datasets: dict[str, dict[int, GeoDataFrame]], echopop_datasets: dict[str, dict[int, GeoDataFrame]], columns: list = None) tuple[DataFrame, DataFrame]#
Compute magnitude and percent differences between EchoPro and Echopop reports.
Compute magnitude and percent differences between EchoPro and Echopop report outputs across survey years and report types (transect, kriging).
- Parameters:
- echopro_datasetsDict[str, Dict[int, geopandas.GeoDataFrame]]
Nested dictionary of EchoPro data keyed by report type (
"transect","kriging") and year.- echopop_datasetsDict[str, Dict[int, geopandas.GeoDataFrame]]
Nested dictionary of Echopop data keyed by report type and year.
- columnslist
Columns to sum and compare. Defaults to
["abundance", "biomass", "nasc"].
- Returns:
- Tuple[pandas.DataFrame, pandas.DataFrame]
differences: Magnitude differences (EchoPro - Echopop), indexed by(report_type, year).pct_diff: Percent differences relative to the mean of both datasets, indexed by(report_type, year).
- echopop.reports.compare.load_all_geodata_reports(years: list, echopro_root: Callable[[int], Path], echopop_root: Callable[[int], Path], echopop_patterns: dict[str, str], echopro_patterns: dict[str, str], cache_dir: Path | None = None, max_workers: int | None = None, verbose: bool = False) tuple[dict[str, dict[int, GeoDataFrame]], dict[str, dict[int, GeoDataFrame]]]#
Load all geodata reports across years and dataset types.
By default, files are loaded sequentially. Parallel loading can be enabled by setting
max_workersto an integer greater than 1, which uses a thread pool sized accordingly.- Parameters:
- yearslist
Survey years to process.
- echopro_rootCallable[[int], Path]
Function mapping year -> EchoPro report directory.
- echopop_rootCallable[[int], Path]
Function mapping year -> Echopop report directory.
- echopop_patternsDict[str, str]
Regex filename patterns for Echopop reports, keyed by type.
- echopro_patternsDict[str, str]
Regex filename patterns for EchoPro reports, keyed by type.
- cache_dirOptional[Path]
Directory for caching parquet files. If
None, caching is disabled.- max_workersOptional[int]
Number of threads for parallel loading. If
None(default), files are loaded sequentially. Set to a positive integer (e.g.8) to enable parallel loading.- verbosebool, default = False
Boolean argument that will iteratively print out the loaded filepaths when set to True.
- Returns:
- Dict[Tuple[str, str, int], geopandas.GeoDataFrame]
Dictionary keyed by
(dataset, type, year).
- echopop.reports.compare.plot_dataset_differences(percent_differences: DataFrame, save_filepath: Path | None = None, columns: list = None, figsize: tuple[int, int] = (14, 6)) None#
Plot the percent differences between EchoPro and Echopop population estimates.
Plot percent differences between EchoPro and Echopop report outputs as a heatmap grid, with one panel per report type (transect, kriging).
- Parameters:
- percent_differencespandas.DataFrame
Percent differences indexed by
(report_type, year), as returned bycompute_dataset_differences.- save_filepathOptional[Path]
If provided, the figure is saved to this path at 300 dpi. If
None, the figure is only displayed.- columnslist
Column display labels for the x-axis. Defaults to
["abundance", "biomass", "nasc"].- figsizeTuple[int, int]
Figure size in inches. Defaults to
(14, 6).
- echopop.reports.compare.plot_geodata(echopro: GeoDataFrame, echopop: GeoDataFrame, save_filepath: Path | dict[Any, Path], show_plot: bool = True, log_transform: bool = False) None#
Plot georeferenced transect and kriging mesh data.
- echopop.reports.compare.plot_haul_count_comparisons(echopro: dict[str, DataFrame], echopop: dict[str, DataFrame], save_filepath: Path | None = None, show_plot: bool = True)#
Plot the differences in fish counts per haul.
For each sex in the intersection of both dicts, plot:
EchoPro heatmap
EchoPop heatmap
Difference heatmap.
- Parameters:
- echoproDict[str, pd.DataFrame]
- echopopDict[str, pd.DataFrame]
- save_filepathOptional[Path]
If provided, the plot will be saved to this location.
- show_plotbool
If True, the plot will be rendered in the user’s session.
- echopop.reports.compare.plot_population_table_comparisons(echopro: dict[str, DataFrame], echopop: dict[str, DataFrame], save_filepath: Path | None = None, show_plot: bool = True, log_transform: bool = False)#
Plot the differences in population estimates.
For each sex in the intersection of both dicts, plot:
EchoPro heatmap
EchoPop heatmap
Difference heatmap.
- Parameters:
- echoproDict[str, pd.DataFrame]
- echopopDict[str, pd.DataFrame]
- save_filepathOptional[Path]
If provided, the plot will be saved to this location.
- show_plotbool
If True, the plot will be rendered in the user’s session.
- log_transform: bool
If True, al values within the DataFrames are log-transformed (base-10).
- echopop.reports.compare.read_aged_geodata(filepath: Path) dict[str, GeoDataFrame]#
Read in georeferenced aged population estimates from transects and kriged mesh nodes.
This outputs a
geopandas.GeoDataFrameobject with the geometry informed by columns associated with ‘longitude’ and ‘latitude’. The EPSG:4326 projection is automatically applied to the coordinates.
- echopop.reports.compare.read_geodata(filepath: Path) GeoDataFrame#
Read in georeferenced population estimates from along-transect intervals of kriging mesh nodes.
This outputs a geopandas.GeoDataFrame object with the geometry informed by columns associated with ‘longitude’ and ‘latitude’. The EPSG:4326 projection is automatically applied to the coordinates.
- echopop.reports.compare.read_pivot_table_report(filepath: Path) dict[str, DataFrame]#
Read all sheets from the aged length haul counts report Excel file.
This dynamically assigns sex to the sheet name and returns a dictionary {sex: DataFrame}. Drops last column and row (subtotal) for each column and row, respectively.