bolster.data_sources.nisra.ashe

NISRA Annual Survey of Hours and Earnings (ASHE) Module.

This module provides access to Northern Ireland’s earnings statistics: - Median weekly, hourly, and annual earnings - Breakdowns by employment type, sector, geography, occupation, industry - Gender pay gap analysis - Historical timeseries from 1997 to present

Data is published annually in October by NISRA’s Economic & Labour Market Statistics Branch.

Data Source: Northern Ireland Statistics and Research Agency provides Annual Survey of Hours and Earnings statistics through their Work, Pay and Benefits section at https://www.nisra.gov.uk/statistics/work-pay-and-benefits/annual-survey-hours-and-earnings. ASHE data covers employee earnings across all sectors based on a sample survey of payroll records from HMRC PAYE data, providing comprehensive earnings statistics for Northern Ireland.

Update Frequency: Annual publications released in October each year, covering earnings data for the reference period of April. The dataset provides the most comprehensive and official source of employee earnings statistics for Northern Ireland, updated once per year with historical revisions as necessary.

Data Coverage:
  • Weekly Earnings: 1997 - Present (annual, full-time/part-time/all)

  • Hourly Earnings: 1997 - Present (annual, excluding overtime)

  • Annual Earnings: 1999 - Present (annual, full-time/part-time/all)

  • Geographic: 11 Local Government Districts (workplace vs residence basis)

  • Sector: Public vs Private sector comparison (2005 - Present)

Examples

>>> from bolster.data_sources.nisra import ashe
>>> # Get latest weekly earnings timeseries
>>> df = ashe.get_latest_ashe_timeseries(metric='weekly')
>>> sorted(df.columns.tolist())
['median_weekly_earnings', 'work_pattern', 'year']
>>> # Get geographic earnings by workplace
>>> df_geo = ashe.get_latest_ashe_geography(basis='workplace')
>>> 'median_weekly_earnings' in df_geo.columns
True
>>> # Get public vs private sector comparison
>>> df_sector = ashe.get_latest_ashe_sector()
>>> 'location' in df_sector.columns
True
Publication Details:
  • Frequency: Annual (October publication)

  • Reference period: April of each year

  • Published by: NISRA Economic & Labour Market Statistics Branch

  • Contact: economicstats@nisra.gov.uk

  • Base: Employee jobs in Northern Ireland (not self-employed)

Attributes

logger

ASHE_BASE_URL

Functions

get_latest_ashe_publication_url()

Get the URL of the latest ASHE publication and its year.

get_ashe_file_url(year[, file_type])

Construct URL for ASHE file based on year and file type.

parse_ashe_timeseries_weekly(file_path)

Parse ASHE weekly earnings timeseries.

parse_ashe_timeseries_hourly(file_path)

Parse ASHE hourly earnings timeseries.

parse_ashe_timeseries_annual(file_path)

Parse ASHE annual earnings timeseries.

parse_ashe_geography(file_path[, basis, year])

Parse ASHE geographic earnings data.

parse_ashe_sector(file_path)

Parse ASHE public vs private sector earnings.

get_latest_ashe_timeseries([metric, force_refresh])

Get the latest ASHE timeseries data.

get_latest_ashe_geography([basis, force_refresh])

Get the latest ASHE geographic earnings data.

get_latest_ashe_sector([force_refresh])

Get the latest ASHE public vs private sector earnings data.

get_earnings_by_year(df, year)

Filter earnings data for a specific year.

calculate_growth_rates(df[, periods])

Calculate year-on-year growth rates for earnings.

parse_ashe_gender_pay_gap(file_path)

Parse ASHE gender pay gap timeseries (NI and UK), any publication year.

parse_ashe_hourly_earnings_by_sector_gender(file_path)

Parse ASHE hourly earnings by sector and gender timeseries.

parse_ashe_hourly_earnings_by_age_gender(file_path)

Parse ASHE hourly earnings by age group and gender, latest year snapshot.

parse_ashe_hourly_earnings_by_occupation_gender(file_path)

Parse ASHE hourly earnings by occupation and gender, latest year snapshot.

parse_ashe_hourly_earnings_by_pattern_gender(file_path)

Parse ASHE hourly earnings by working pattern and gender, latest year snapshot.

parse_ashe_ni_uk_earnings_comparison(file_path)

Parse ASHE NI vs UK full-time weekly earnings timeseries.

parse_ashe_uk_regional_pay_ratio(file_path)

Parse ASHE high-to-low pay ratio by UK region, latest year snapshot.

parse_ashe_hours_distribution(file_path)

Parse ASHE distribution of total weekly paid hours, NI, latest year snapshot.

parse_ashe_working_pattern_pay_gap(file_path)

Parse ASHE working pattern pay gap timeseries, NI vs UK.

parse_ashe_mean_hours_by_pattern_gender(file_path)

Parse ASHE mean weekly paid hours by work pattern and gender, NI, latest year.

get_gender_pay_gap([force_refresh])

Get ASHE gender pay gap timeseries for NI and the UK.

get_hourly_earnings_by_sector_gender([force_refresh])

Get ASHE hourly earnings by sector and gender timeseries for NI.

get_hourly_earnings_by_age_gender([force_refresh])

Get ASHE hourly earnings by age group and gender for NI, latest year snapshot.

get_hourly_earnings_by_occupation_gender([force_refresh])

Get ASHE hourly earnings by occupation and gender for NI, latest year snapshot.

get_hourly_earnings_by_pattern_gender([force_refresh])

Get ASHE hourly earnings by working pattern and gender for NI, latest year snapshot.

get_ni_uk_earnings_comparison([force_refresh])

Get NI vs UK full-time weekly earnings timeseries.

get_uk_regional_pay_ratio([force_refresh])

Get high-to-low pay ratio by UK region, latest year snapshot.

get_hours_distribution([force_refresh])

Get distribution of total weekly paid hours for NI employees, latest year snapshot.

get_working_pattern_pay_gap([force_refresh])

Get working pattern pay gap timeseries for NI vs UK.

get_mean_hours_by_pattern_gender([force_refresh])

Get mean weekly paid hours by work pattern and gender for NI, latest year snapshot.

parse_ashe_real_earnings(file_path)

Parse ASHE Figure 2: nominal vs real weekly earnings timeseries for NI.

parse_ashe_real_earnings_change_by_pattern(file_path)

Parse ASHE Figure 3: annual % change in weekly earnings by work pattern (NI).

parse_ashe_real_earnings_index_by_sector(file_path)

Parse ASHE Figure 6: real earnings index (2019=100) by sector for NI.

parse_ashe_annual_change_by_occupation(file_path)

Parse ASHE Figure 7: annual % change in weekly earnings by occupation (NI, FT).

parse_ashe_annual_change_by_industry(file_path)

Parse ASHE Figure 8: annual % change in weekly earnings by industry (NI, FT).

parse_ashe_pay_distribution_timeseries(file_path)

Parse ASHE Figure 11: proportion of low/middle/high-paid employee jobs in NI.

parse_ashe_pay_distribution_by_classification(file_path)

Parse ASHE Figure 12: pay distribution by working pattern, age, industry, occupation.

get_real_earnings([force_refresh])

Get ASHE nominal vs real weekly earnings timeseries for NI (Figure 2).

get_real_earnings_change_by_pattern([force_refresh])

Get ASHE annual % change in weekly earnings by work pattern (Figure 3).

get_real_earnings_index_by_sector([force_refresh])

Get ASHE real earnings index by sector for NI (Figure 6).

get_annual_change_by_occupation([force_refresh])

Get ASHE annual % change in weekly earnings by occupation, NI (Figure 7).

get_annual_change_by_industry([force_refresh])

Get ASHE annual % change in weekly earnings by industry, NI (Figure 8).

get_pay_distribution_timeseries([force_refresh])

Get ASHE low/middle/high-paid proportion timeseries for NI (Figure 11).

get_pay_distribution_by_classification([force_refresh])

Get ASHE pay distribution by classification, NI, latest year (Figure 12).

validate_ashe_data(df)

Validate ASHE earnings data integrity.

Module Contents

bolster.data_sources.nisra.ashe.logger[source]
bolster.data_sources.nisra.ashe.ASHE_BASE_URL = 'https://www.nisra.gov.uk/statistics/work-pay-and-benefits/annual-survey-hours-and-earnings'[source]
bolster.data_sources.nisra.ashe.get_latest_ashe_publication_url()[source]

Get the URL of the latest ASHE publication and its year.

Scrapes the NISRA ASHE page to find the most recent publication.

Returns:

Tuple of (publication_url, year)

Raises:

NISRADataNotFoundError – If unable to find the latest publication

Return type:

tuple[str, int]

Example

>>> url, year = get_latest_ashe_publication_url()
>>> url.startswith('https://')
True
bolster.data_sources.nisra.ashe.get_ashe_file_url(year, file_type='timeseries')[source]

Construct URL for ASHE file based on year and file type.

Parameters:
  • year (int) – Publication year (e.g., 2025)

  • file_type (str) – Type of file - ‘timeseries’ or ‘linked’

Returns:

URL to the Excel file

Return type:

str

Example

>>> url = get_ashe_file_url(2025, 'timeseries')
>>> url.startswith('https://')
True
bolster.data_sources.nisra.ashe.parse_ashe_timeseries_weekly(file_path)[source]

Parse ASHE weekly earnings timeseries.

Extracts the weekly earnings data from the timeseries Excel file.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE timeseries Excel file

Returns:

  • year: int

  • work_pattern: str (‘Full-time’, ‘Part-time’, ‘All’)

  • median_weekly_earnings: float (£)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'timeseries'), cache_ttl_hours=90*24)
>>> df = parse_ashe_timeseries_weekly(path)
>>> sorted(df.columns.tolist())
['median_weekly_earnings', 'work_pattern', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_timeseries_hourly(file_path)[source]

Parse ASHE hourly earnings timeseries.

Extracts the hourly earnings data (excluding overtime) from the timeseries Excel file.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE timeseries Excel file

Returns:

  • year: int

  • work_pattern: str (‘Full-time’, ‘Part-time’, ‘All’)

  • median_hourly_earnings: float (£)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'timeseries'), cache_ttl_hours=90*24)
>>> df = parse_ashe_timeseries_hourly(path)
>>> sorted(df.columns.tolist())
['median_hourly_earnings', 'work_pattern', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_timeseries_annual(file_path)[source]

Parse ASHE annual earnings timeseries.

Extracts the annual earnings data from the timeseries Excel file.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE timeseries Excel file

Returns:

  • year: int

  • work_pattern: str (‘Full-time’, ‘Part-time’, ‘All’)

  • median_annual_earnings: float (£)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'timeseries'), cache_ttl_hours=90*24)
>>> df = parse_ashe_timeseries_annual(path)
>>> sorted(df.columns.tolist())
['median_annual_earnings', 'work_pattern', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_geography(file_path, basis='workplace', year=None)[source]

Parse ASHE geographic earnings data.

Extracts earnings by Local Government District from the linked tables file.

Parameters:
  • file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

  • basis (str) – ‘workplace’ (MapA) or ‘residence’ (MapB)

  • year (int) – Year of the data (if not provided, will be extracted from file)

Returns:

  • year: int

  • lgd: str (Local Government District name)

  • basis: str (‘workplace’ or ‘residence’)

  • median_weekly_earnings: float (£)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_geography(path, basis='workplace', year=year)
>>> 'median_weekly_earnings' in df.columns
True
bolster.data_sources.nisra.ashe.parse_ashe_sector(file_path)[source]

Parse ASHE public vs private sector earnings.

Extracts public and private sector earnings timeseries from the linked tables file.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • year: int

  • location: str (‘Northern Ireland’ or ‘United Kingdom’)

  • sector: str (‘Public’ or ‘Private’)

  • median_weekly_earnings: float (£)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_sector(path)
>>> 'location' in df.columns
True
bolster.data_sources.nisra.ashe.get_latest_ashe_timeseries(metric='weekly', force_refresh=False)[source]

Get the latest ASHE timeseries data.

Downloads and parses the most recent ASHE timeseries publication. Results are cached for 90 days unless force_refresh=True.

Parameters:
  • metric (str) – Type of earnings - ‘weekly’, ‘hourly’, or ‘annual’

  • force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

DataFrame with timeseries earnings data (1997-present for weekly/hourly, 1999-present for annual)

Return type:

pandas.DataFrame

Example

>>> df = get_latest_ashe_timeseries(metric='weekly')
>>> sorted(df.columns.tolist())
['median_weekly_earnings', 'work_pattern', 'year']
bolster.data_sources.nisra.ashe.get_latest_ashe_geography(basis='workplace', force_refresh=False)[source]

Get the latest ASHE geographic earnings data.

Downloads and parses the most recent ASHE linked tables publication. Results are cached for 90 days unless force_refresh=True.

Parameters:
  • basis (str) – ‘workplace’ (where employees work) or ‘residence’ (where employees live)

  • force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

DataFrame with earnings by Local Government District

Return type:

pandas.DataFrame

Example

>>> df = get_latest_ashe_geography(basis='workplace')
>>> 'median_weekly_earnings' in df.columns
True
bolster.data_sources.nisra.ashe.get_latest_ashe_sector(force_refresh=False)[source]

Get the latest ASHE public vs private sector earnings data.

Downloads and parses the most recent ASHE linked tables publication. Results are cached for 90 days unless force_refresh=True.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

DataFrame with public and private sector earnings timeseries (2005-present)

Return type:

pandas.DataFrame

Example

>>> df = get_latest_ashe_sector()
>>> 'location' in df.columns
True
bolster.data_sources.nisra.ashe.get_earnings_by_year(df, year)[source]

Filter earnings data for a specific year.

Parameters:
Returns:

DataFrame with only the specified year’s data

Return type:

pandas.DataFrame

Example

>>> df = get_latest_ashe_timeseries('weekly')
>>> df_2025 = get_earnings_by_year(df, 2025)
>>> 'median_weekly_earnings' in df_2025.columns
True
bolster.data_sources.nisra.ashe.calculate_growth_rates(df, periods=1)[source]

Calculate year-on-year growth rates for earnings.

Parameters:
  • df (pandas.DataFrame) – ASHE DataFrame with ‘year’ and earnings column

  • periods (int) – Number of years for comparison (default: 1 for YoY)

Returns:

DataFrame with additional growth rate column

Return type:

pandas.DataFrame

Example

>>> df = get_latest_ashe_timeseries('weekly')
>>> df_growth = calculate_growth_rates(df)
>>> 'earnings_yoy_growth' in df_growth.columns
True
bolster.data_sources.nisra.ashe.parse_ashe_gender_pay_gap(file_path)[source]

Parse ASHE gender pay gap timeseries (NI and UK), any publication year.

Extracts the NI and UK all-employee gender pay gap from 2005 to present. The gap is defined as the difference between male and female median hourly earnings as a percentage of male median hourly earnings (all employees, excluding overtime).

Note: methodological changes occurred in 2006, 2011 and 2021 — these are annotated in NISRA publications and should be considered when interpreting trend breaks.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • year: int

  • location: str (‘Northern Ireland’ or ‘United Kingdom’)

  • gender_pay_gap_pct: float — GPG as % of male earnings (positive = men paid more)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_gender_pay_gap(path)
>>> sorted(df.columns.tolist())
['gender_pay_gap_pct', 'location', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_hourly_earnings_by_sector_gender(file_path)[source]

Parse ASHE hourly earnings by sector and gender timeseries.

Identified by column signature [‘Year’, ‘Male public’, ‘Female public’, ‘Male private’, ‘Female private’] — stable across all publication years.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • year: int

  • sector: str (‘Public’ or ‘Private’)

  • sex: str (‘Male’ or ‘Female’)

  • median_hourly_earnings: float (£, excluding overtime)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_hourly_earnings_by_sector_gender(path)
>>> sorted(df.columns.tolist())
['median_hourly_earnings', 'sector', 'sex', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_hourly_earnings_by_age_gender(file_path)[source]

Parse ASHE hourly earnings by age group and gender, latest year snapshot.

Identified by column signature [‘Age group’, ‘Female’, ‘Male’] with subtitle containing ‘age’. Present in all publication years, though in different figure slots.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • age_group: str (e.g. ‘18-21’, ‘22-29’, ‘30-39’, ‘40-49’, ‘50-59’, ‘60+’)

  • sex: str (‘Male’ or ‘Female’)

  • median_hourly_earnings: float (£, excluding overtime)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_hourly_earnings_by_age_gender(path)
>>> sorted(df.columns.tolist())
['age_group', 'median_hourly_earnings', 'sex']
bolster.data_sources.nisra.ashe.parse_ashe_hourly_earnings_by_occupation_gender(file_path)[source]

Parse ASHE hourly earnings by occupation and gender, latest year snapshot.

Identified by column signature [‘Occupation’, ‘Female’, ‘Male’] with subtitle containing ‘occupation’. Present in all publication years.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • occupation: str (SOC major group label)

  • sex: str (‘Male’ or ‘Female’)

  • median_hourly_earnings: float (£, excluding overtime)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_hourly_earnings_by_occupation_gender(path)
>>> sorted(df.columns.tolist())
['median_hourly_earnings', 'occupation', 'sex']
bolster.data_sources.nisra.ashe.parse_ashe_hourly_earnings_by_pattern_gender(file_path)[source]

Parse ASHE hourly earnings by working pattern and gender, latest year snapshot.

Identified by column signature [‘Working pattern’, ‘Female’, ‘Male’] with subtitle containing ‘working pattern’ (excluding pay gap / hours tables which share similar columns). Present in all publication years.

Note: part-time females earn more per hour than part-time males in NI — a reversal of the full-time pattern, documented across 2022–2025.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • work_pattern: str (‘Full-time’, ‘Part-time’, ‘All Employees’)

  • sex: str (‘Male’ or ‘Female’)

  • median_hourly_earnings: float (£, excluding overtime)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_hourly_earnings_by_pattern_gender(path)
>>> sorted(df.columns.tolist())
['median_hourly_earnings', 'sex', 'work_pattern']
bolster.data_sources.nisra.ashe.parse_ashe_ni_uk_earnings_comparison(file_path)[source]

Parse ASHE NI vs UK full-time weekly earnings timeseries.

Identified by column signature [‘Year’, ‘UK’, ‘NI’] with subtitle containing ‘weekly’ and ‘full-time’. Stable across all publication years (always Figure 1).

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • year: int

  • location: str (‘NI’ or ‘UK’)

  • median_weekly_earnings_fulltime: float (£)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_ni_uk_earnings_comparison(path)
>>> sorted(df.columns.tolist())
['location', 'median_weekly_earnings_fulltime', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_uk_regional_pay_ratio(file_path)[source]

Parse ASHE high-to-low pay ratio by UK region, latest year snapshot.

Identified by column signature [‘Region’, ‘Ratio’]. Present in all years but in different figure slots (Figure 14 in 2022, Figure 16 in 2023, Figure 13 in 2024–2025).

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • region: str (UK region name)

  • ratio: float (high-paid / low-paid jobs ratio)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_uk_regional_pay_ratio(path)
>>> sorted(df.columns.tolist())
['ratio', 'region']
bolster.data_sources.nisra.ashe.parse_ashe_hours_distribution(file_path)[source]

Parse ASHE distribution of total weekly paid hours, NI, latest year snapshot.

Identified by column signature [‘Paid hours worked’, ‘Percentage’]. Present in all years but in different figure slots (Figure 3 in 2022–2023, Figure 9 in 2024–2025).

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • paid_hours_worked: int (hours 0–80)

  • percentage: float (% of employees)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_hours_distribution(path)
>>> sorted(df.columns.tolist())
['paid_hours_worked', 'percentage']
bolster.data_sources.nisra.ashe.parse_ashe_working_pattern_pay_gap(file_path)[source]

Parse ASHE working pattern pay gap timeseries, NI vs UK.

Identified by column signature [‘Year’, ‘UK’, ‘NI’] with subtitle containing ‘working pattern pay gap’. Present from 2023 onwards (Figure 23 in 2023, Figure 19 in 2024–2025).

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • year: int

  • location: str (‘NI’ or ‘UK’)

  • working_pattern_pay_gap_pct: float (%)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_working_pattern_pay_gap(path)
>>> sorted(df.columns.tolist())
['location', 'working_pattern_pay_gap_pct', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_mean_hours_by_pattern_gender(file_path)[source]

Parse ASHE mean weekly paid hours by work pattern and gender, NI, latest year.

Identified by column signature [‘Working pattern’, ‘Males’, ‘Females’, ‘All’]. Present in all years (Figure 21 in 2022–2023, Figure 20 in 2024–2025).

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • work_pattern: str (‘Part-time’, ‘Full-time’, ‘All Employees’)

  • male_mean_hours: float

  • female_mean_hours: float

  • all_mean_hours: float

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_mean_hours_by_pattern_gender(path)
>>> 'work_pattern' in df.columns
True
bolster.data_sources.nisra.ashe.get_gender_pay_gap(force_refresh=False)[source]

Get ASHE gender pay gap timeseries for NI and the UK.

Returns the population-level GPG derived from NISRA’s ASHE survey — the difference between male and female median hourly earnings as a percentage of male earnings, for all employees.

This is survey-based (HMRC PAYE sample) and covers the whole NI economy, complementing the mandatory employer-reported GPG data available via bolster.data_sources.gender_pay_gap (which covers named employers with 250+ staff only).

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

  • year: int (2005–present)

  • location: str (‘Northern Ireland’ or ‘United Kingdom’)

  • gender_pay_gap_pct: float

Return type:

DataFrame with columns

Example

>>> df = get_gender_pay_gap()
>>> 'gender_pay_gap_pct' in df.columns
True
bolster.data_sources.nisra.ashe.get_hourly_earnings_by_sector_gender(force_refresh=False)[source]

Get ASHE hourly earnings by sector and gender timeseries for NI.

Returns median gross hourly earnings (excl. overtime) for NI employees by public/private sector and sex, from 2005 to present.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

  • year: int (2005–present)

  • sector: str (‘Public’ or ‘Private’)

  • sex: str (‘Male’ or ‘Female’)

  • median_hourly_earnings: float (£)

Return type:

DataFrame with columns

Example

>>> df = get_hourly_earnings_by_sector_gender()
>>> 'median_hourly_earnings' in df.columns
True
bolster.data_sources.nisra.ashe.get_hourly_earnings_by_age_gender(force_refresh=False)[source]

Get ASHE hourly earnings by age group and gender for NI, latest year snapshot.

Returns median gross hourly earnings (excl. overtime) for NI employees by age band and sex.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

  • age_group: str

  • sex: str (‘Male’ or ‘Female’)

  • median_hourly_earnings: float (£)

Return type:

DataFrame with columns

Example

>>> df = get_hourly_earnings_by_age_gender()
>>> 'median_hourly_earnings' in df.columns
True
bolster.data_sources.nisra.ashe.get_hourly_earnings_by_occupation_gender(force_refresh=False)[source]

Get ASHE hourly earnings by occupation and gender for NI, latest year snapshot.

Returns median gross hourly earnings (excl. overtime) for NI employees by SOC major occupation group and sex.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

  • occupation: str

  • sex: str (‘Male’ or ‘Female’)

  • median_hourly_earnings: float (£)

Return type:

DataFrame with columns

Example

>>> df = get_hourly_earnings_by_occupation_gender()
>>> 'median_hourly_earnings' in df.columns
True
bolster.data_sources.nisra.ashe.get_hourly_earnings_by_pattern_gender(force_refresh=False)[source]

Get ASHE hourly earnings by working pattern and gender for NI, latest year snapshot.

Returns median gross hourly earnings (excl. overtime) for NI employees by full-time/part-time and sex.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

  • work_pattern: str (‘Full-time’, ‘Part-time’, ‘All Employees’)

  • sex: str (‘Male’ or ‘Female’)

  • median_hourly_earnings: float (£)

Return type:

DataFrame with columns

Example

>>> df = get_hourly_earnings_by_pattern_gender()
>>> 'median_hourly_earnings' in df.columns
True
bolster.data_sources.nisra.ashe.get_ni_uk_earnings_comparison(force_refresh=False)[source]

Get NI vs UK full-time weekly earnings timeseries.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

year, location (‘NI’/’UK’), median_weekly_earnings_fulltime

Return type:

DataFrame with columns

Example

>>> df = get_ni_uk_earnings_comparison()
>>> 'median_weekly_earnings_fulltime' in df.columns
True
bolster.data_sources.nisra.ashe.get_uk_regional_pay_ratio(force_refresh=False)[source]

Get high-to-low pay ratio by UK region, latest year snapshot.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

region, ratio

Return type:

DataFrame with columns

Example

>>> df = get_uk_regional_pay_ratio()
>>> 'ratio' in df.columns
True
bolster.data_sources.nisra.ashe.get_hours_distribution(force_refresh=False)[source]

Get distribution of total weekly paid hours for NI employees, latest year snapshot.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

paid_hours_worked, percentage

Return type:

DataFrame with columns

Example

>>> df = get_hours_distribution()
>>> 'percentage' in df.columns
True
bolster.data_sources.nisra.ashe.get_working_pattern_pay_gap(force_refresh=False)[source]

Get working pattern pay gap timeseries for NI vs UK.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

year, location (‘NI’/’UK’), working_pattern_pay_gap_pct

Return type:

DataFrame with columns

Example

>>> df = get_working_pattern_pay_gap()
>>> 'working_pattern_pay_gap_pct' in df.columns
True
bolster.data_sources.nisra.ashe.get_mean_hours_by_pattern_gender(force_refresh=False)[source]

Get mean weekly paid hours by work pattern and gender for NI, latest year snapshot.

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

work_pattern, male_mean_hours, female_mean_hours, all_mean_hours

Return type:

DataFrame with columns

Example

>>> df = get_mean_hours_by_pattern_gender()
>>> 'male_mean_hours' in df.columns
True
bolster.data_sources.nisra.ashe.parse_ashe_real_earnings(file_path)[source]

Parse ASHE Figure 2: nominal vs real weekly earnings timeseries for NI.

Identified by column signature [‘Year’, ‘Nominal earnings’, ‘Real earnings’]. Real earnings are inflation-adjusted to the latest publication year using NISRA’s deflator. The series covers full-time employees, April 2005 onwards.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • year: int

  • nominal_weekly_earnings: float (£, in nominal terms)

  • real_weekly_earnings: float (£, inflation-adjusted to latest year)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_real_earnings(path)
>>> sorted(df.columns.tolist())
['nominal_weekly_earnings', 'real_weekly_earnings', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_real_earnings_change_by_pattern(file_path)[source]

Parse ASHE Figure 3: annual % change in weekly earnings by work pattern (NI).

Snapshot for the latest reference year showing nominal vs real (inflation- adjusted) annual change for each working pattern.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • work_pattern: str (‘Part-time’, ‘Full-time’, ‘All employees’)

  • nominal_change_pct: float (annual % change in nominal terms)

  • real_change_pct: float (annual % change in real terms)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_real_earnings_change_by_pattern(path)
>>> sorted(df.columns.tolist())
['nominal_change_pct', 'real_change_pct', 'work_pattern']
bolster.data_sources.nisra.ashe.parse_ashe_real_earnings_index_by_sector(file_path)[source]

Parse ASHE Figure 6: real earnings index (2019=100) by sector for NI.

Identified by column signature [‘Year’, ‘Public’, ‘Private’] with subtitle containing ‘annual index’. Indexed real (inflation-adjusted) median weekly earnings for full-time employees, base year = six years before the latest publication year (typically 2019 in the 2025 publication).

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • year: int

  • sector: str (‘Public’ or ‘Private’)

  • real_earnings_index: float (index, base year = 100)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_real_earnings_index_by_sector(path)
>>> sorted(df.columns.tolist())
['real_earnings_index', 'sector', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_annual_change_by_occupation(file_path)[source]

Parse ASHE Figure 7: annual % change in weekly earnings by occupation (NI, FT).

Full-time employees only. NISRA labels the column “Industry” in the source spreadsheet, but the rows are SOC major occupation groups — disambiguated from Figure 8 by subtitle keyword ‘by occupation’.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • occupation: str (SOC major group label)

  • annual_change_pct: float (% change in median gross weekly earnings)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_annual_change_by_occupation(path)
>>> sorted(df.columns.tolist())
['annual_change_pct', 'occupation']
bolster.data_sources.nisra.ashe.parse_ashe_annual_change_by_industry(file_path)[source]

Parse ASHE Figure 8: annual % change in weekly earnings by industry (NI, FT).

Full-time employees by SIC industry section. Disambiguated from Figure 7 by subtitle keyword ‘by industry’.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • industry: str (SIC section label)

  • annual_change_pct: float (% change in median gross weekly earnings)

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_annual_change_by_industry(path)
>>> sorted(df.columns.tolist())
['annual_change_pct', 'industry']
bolster.data_sources.nisra.ashe.parse_ashe_pay_distribution_timeseries(file_path)[source]

Parse ASHE Figure 11: proportion of low/middle/high-paid employee jobs in NI.

Identified by column signature [‘Year’, ‘Low-paid jobs’, ‘Middle-paid jobs’, ‘High-paid jobs’]. Covers April 2005 to the latest publication year.

NISRA defines pay bands relative to UK median hourly earnings: low-paid = below two-thirds, high-paid = above 1.5x, middle-paid = the rest.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • year: int

  • low_paid_pct: float

  • middle_paid_pct: float

  • high_paid_pct: float

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_pay_distribution_timeseries(path)
>>> sorted(df.columns.tolist())
['high_paid_pct', 'low_paid_pct', 'middle_paid_pct', 'year']
bolster.data_sources.nisra.ashe.parse_ashe_pay_distribution_by_classification(file_path)[source]

Parse ASHE Figure 12: pay distribution by working pattern, age, industry, occupation.

Cross-sectional breakdown of low/middle/high-paid proportions for the latest reference year, with multiple classification axes stacked in a single sheet.

Parameters:

file_path (str | pathlib.Path) – Path to the ASHE linked tables Excel file

Returns:

  • classification: str (e.g. ‘Working pattern’, ‘Age group’, ‘Industry’, ‘Occupation’)

  • subset: str (the specific category, e.g. ‘Full-time’, ‘22-29’, ‘Education’)

  • low_paid_pct: float

  • middle_paid_pct: float

  • high_paid_pct: float

Return type:

DataFrame with columns

Example

>>> _, year = get_latest_ashe_publication_url()
>>> path = download_file(get_ashe_file_url(year, 'linked'), cache_ttl_hours=90*24)
>>> df = parse_ashe_pay_distribution_by_classification(path)
>>> sorted(df.columns.tolist())
['classification', 'high_paid_pct', 'low_paid_pct', 'middle_paid_pct', 'subset']
bolster.data_sources.nisra.ashe.get_real_earnings(force_refresh=False)[source]

Get ASHE nominal vs real weekly earnings timeseries for NI (Figure 2).

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

year, nominal_weekly_earnings, real_weekly_earnings

Return type:

DataFrame with columns

Example

>>> df = get_real_earnings()
>>> 'real_weekly_earnings' in df.columns
True
bolster.data_sources.nisra.ashe.get_real_earnings_change_by_pattern(force_refresh=False)[source]

Get ASHE annual % change in weekly earnings by work pattern (Figure 3).

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

work_pattern, nominal_change_pct, real_change_pct

Return type:

DataFrame with columns

Example

>>> df = get_real_earnings_change_by_pattern()
>>> 'real_change_pct' in df.columns
True
bolster.data_sources.nisra.ashe.get_real_earnings_index_by_sector(force_refresh=False)[source]

Get ASHE real earnings index by sector for NI (Figure 6).

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

year, sector, real_earnings_index

Return type:

DataFrame with columns

Example

>>> df = get_real_earnings_index_by_sector()
>>> 'real_earnings_index' in df.columns
True
bolster.data_sources.nisra.ashe.get_annual_change_by_occupation(force_refresh=False)[source]

Get ASHE annual % change in weekly earnings by occupation, NI (Figure 7).

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

occupation, annual_change_pct

Return type:

DataFrame with columns

Example

>>> df = get_annual_change_by_occupation()
>>> 'annual_change_pct' in df.columns
True
bolster.data_sources.nisra.ashe.get_annual_change_by_industry(force_refresh=False)[source]

Get ASHE annual % change in weekly earnings by industry, NI (Figure 8).

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

industry, annual_change_pct

Return type:

DataFrame with columns

Example

>>> df = get_annual_change_by_industry()
>>> 'annual_change_pct' in df.columns
True
bolster.data_sources.nisra.ashe.get_pay_distribution_timeseries(force_refresh=False)[source]

Get ASHE low/middle/high-paid proportion timeseries for NI (Figure 11).

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

year, low_paid_pct, middle_paid_pct, high_paid_pct

Return type:

DataFrame with columns

Example

>>> df = get_pay_distribution_timeseries()
>>> 'low_paid_pct' in df.columns
True
bolster.data_sources.nisra.ashe.get_pay_distribution_by_classification(force_refresh=False)[source]

Get ASHE pay distribution by classification, NI, latest year (Figure 12).

Parameters:

force_refresh (bool) – If True, bypass cache and download fresh data

Returns:

classification, subset, low_paid_pct, middle_paid_pct, high_paid_pct

Return type:

DataFrame with columns

Example

>>> df = get_pay_distribution_by_classification()
>>> 'classification' in df.columns
True
bolster.data_sources.nisra.ashe.validate_ashe_data(df)[source]

Validate ASHE earnings data integrity.

Parameters:

df (pandas.DataFrame) – DataFrame from ASHE functions

Returns:

True if validation passes, False otherwise

Return type:

bool