Skip to content

Commit

Permalink
Merge pull request #182 from MichaelTiemannOSC/Infer-Scope2
Browse files Browse the repository at this point in the history
Merging in preparation for NZAOA discussions.
  • Loading branch information
MichaelTiemannOSC authored May 2, 2023
2 parents 0f5d5fc + 9807a8d commit 47058bc
Show file tree
Hide file tree
Showing 22 changed files with 615 additions and 412 deletions.
9 changes: 9 additions & 0 deletions ITR/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
This package helps companies and financial institutions to assess the temperature alignment of investment and lending
portfolios.
"""
import pandas as pd
from .data import osc_units
from . import data
from . import utils
Expand Down Expand Up @@ -34,3 +35,11 @@ def uarray(nom_vals, std_devs):

def umean(unquantified_data):
return mean(unquantified_data)

def recombine_nom_and_std(nom: pd.Series, std: pd.Series) -> pd.Series:
assert HAS_UNCERTAINTIES
if std.sum()==0:
return nom
assert not std.isna().any()
return pd.Series(data=uarray(nom.values, std.values), index=nom.index, name=nom.name)

4 changes: 2 additions & 2 deletions ITR/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class SectorsConfig:
STEEL = "Steel"
ALUMINUM = "Aluminum"
ENERGY = "Energy"
# OIL_AND_GAS = "Oil & Gas"
OIL_AND_GAS = "Oil & Gas"
COAL = "Coal"
OIL = "Oil"
GAS = "Gas"
Expand Down Expand Up @@ -133,7 +133,7 @@ def get_configured_sectors(cls) -> List[str]:
return [SectorsConfig.POWER_UTILITY, SectorsConfig.GAS_UTILITY, SectorsConfig.UTILITY,
SectorsConfig.STEEL, SectorsConfig.ALUMINUM,
SectorsConfig.ENERGY, SectorsConfig.COAL, SectorsConfig.OIL, SectorsConfig.GAS,
# SectorsConfig.OIL_AND_GAS,
SectorsConfig.OIL_AND_GAS,
SectorsConfig.AUTOMOBILE, SectorsConfig.TRUCKING,
SectorsConfig.CEMENT,
SectorsConfig.BUILDINGS_CONSTRUCTION, SectorsConfig.BUILDINGS_RESIDENTIAL, SectorsConfig.BUILDINGS_COMMERCIAL,
Expand Down
14 changes: 12 additions & 2 deletions ITR/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,19 @@
'RM':'MYR',
}

def escape_currency_symbols(text):
if '$' in text:
escaped_text = re.escape(text)
else:
escaped_text = text
if escaped_text[0].isalpha():
if escaped_text[-1].isalpha():
return r'\b' + escaped_text + r'\b'
return r'\b' + escaped_text
return escaped_text

currency_keep_regexp = re.compile(fr"({'|'.join([cur_abbrev for cur_abbrev in currency_dict.values()])})")
currency_split_regexp = re.compile(fr"(\$|US\$|{'|'.join([re.escape(currency_symbol) for currency_symbol in currency_dict])})")
currency_split_regexp = re.compile(fr"(\$|\bUS\$|{'|'.join([escape_currency_symbols(currency_symbol) for currency_symbol in currency_dict])})")

def translate_currency_symbols_1(text):
split_text = re.split(currency_split_regexp, text)
Expand Down Expand Up @@ -91,4 +102,3 @@ def translate_currency_symbols(text):
# FIXME: delay loading of pint_pandas until after we've initialized ourselves
from pint_pandas import PintType
PintType.ureg = ureg

175 changes: 136 additions & 39 deletions ITR/data/base_providers.py

Large diffs are not rendered by default.

21 changes: 14 additions & 7 deletions ITR/data/data_warehouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def _restore_historic_data(self):
c.projected_targets = orig_data['projected_targets']

def update_benchmarks(self, benchmark_projected_production: ProductionBenchmarkDataProvider,
benchmarks_projected_ei: IntensityBenchmarkDataProvider):
benchmarks_projected_ei: IntensityBenchmarkDataProvider):
"""
Update the benchmark data used in this instance of the DataWarehouse. If there is no change, do nothing.
"""
Expand Down Expand Up @@ -122,7 +122,6 @@ def update_benchmarks(self, benchmark_projected_production: ProductionBenchmarkD
# Target projections rely both on Production benchmark data and S3 estimated data
# Production-centric benchmarks shift S3 data after trajectory and targets have been projected
if new_production_bm:
# Note that _validate_projected_trajectories overwrites fields of companies, so no need to use return value
logger.info(f"new_production_bm calculating trajectories for {len(self.company_data._companies)} companies (times {len(EScope.get_scopes())} scopes times {self.company_data.projection_controls.TARGET_YEAR-self.company_data.projection_controls.BASE_YEAR} years)")
self.company_data._validate_projected_trajectories(self.company_data._companies, self.benchmarks_projected_ei._EI_df)

Expand Down Expand Up @@ -195,7 +194,8 @@ def update_benchmarks(self, benchmark_projected_production: ProductionBenchmarkD
# The alignment calculation: Company Scope-Sector emissions = Total Company Scope emissions * (BM Scope Sector / SUM(All Scope Sectors of Company))
aligned_em = [(sector, [(scope, list(map(lambda em: (em[0], em[1] * sector_em_df.loc[(sector, scope)].squeeze() / em_tot.loc[scope].squeeze()),
[(em.year, em.value) for em in historic_dict['+'.join([orig_id, sector])].emissions[scope.name]])))
for scope in em_tot.index])
for scope in em_tot.index
if em_tot.loc[scope].squeeze().m != 0.0])
for sector in sectors]

# Having done all scopes and sectors for this company above, replace historic Em and EI data below
Expand Down Expand Up @@ -223,7 +223,7 @@ def update_benchmarks(self, benchmark_projected_production: ProductionBenchmarkD
# Changes to production benchmark requires re-calculating targets (which are production-dependent)
if new_production_bm:
logger.info(f"projecting targets for {len(self.company_data._companies)} companies (times {len(EScope.get_scopes())} scopes times {self.company_data.projection_controls.TARGET_YEAR-self.company_data.projection_controls.BASE_YEAR} years)")
self.company_data._calculate_target_projections(benchmark_projected_production)
self.company_data._calculate_target_projections(benchmark_projected_production, benchmarks_projected_ei)

# If our benchmark is production-centric, migrate S3 data (including estimated S3 data) into S1S2
# If we shift before we project, then S3 targets will not be projected correctly.
Expand Down Expand Up @@ -611,9 +611,16 @@ def _get_cumulative_emissions(self, projected_ei: pd.DataFrame, projected_produc
scale_factor = projected_ei.iloc[:, 0].map(lambda ei: ei.u).combine(projected_production.iloc[:, 0].map(lambda pp: pp.u),
lambda ei_u, pp_u: Q_(1.0, (ei_u * pp_u)).to('t CO2e').m)
projected_t_CO2e = projected_ei.applymap(lambda x: x.m).mul(projected_production.applymap(lambda x: x.m)).mul(scale_factor, axis=0)
# At the last instance, convert our magnitudes to t CO2e everywhere
cumulative_emissions = projected_t_CO2e.cumsum(axis=1).astype('pint[t CO2e]')
return cumulative_emissions
if ITR.HAS_UNCERTAINTIES:
# Normalize uncertain NaNs...FIXME: should we instead allow and accept nan+/-nan?
projected_t_CO2e[projected_t_CO2e.applymap(lambda x: isinstance(x, ITR.UFloat) and ITR.isnan(x))] = ITR.ufloat(np.nan, 0.0)
# Sum both the nominal and std_dev values, because these series are completely correlated
nom_t_CO2e = projected_t_CO2e.apply(lambda x: ITR.nominal_values(x)).cumsum(axis=1)
err_t_CO2e = projected_t_CO2e.apply(lambda x: ITR.std_devs(x)).cumsum(axis=1)
cumulative_emissions = nom_t_CO2e.combine(err_t_CO2e, ITR.recombine_nom_and_std)
else:
cumulative_emissions = projected_t_CO2e.cumsum(axis=1)
return cumulative_emissions.astype('pint[t CO2e]')

def _get_exceedance_year(self, df_subject: pd.DataFrame, df_budget: pd.DataFrame, budget_year: int=None) -> pd.Series:
"""
Expand Down
16 changes: 13 additions & 3 deletions ITR/data/osc_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
PA_ = PintArray

ureg.define("CO2e = CO2 = CO2eq = CO2_eq")
# openscm_units does this for all gas species...we just have to keep up.
ureg.define("tCO2e = t CO2e")

ureg.define("LNG = 3.44 / 2.75 CH4")
# with ureg.context("CH4_conversions"):
# print(ureg("t LNG").to("t CO2"))
Expand All @@ -46,7 +49,8 @@
ureg.define('percent = 1e-2 frac = pct = percentage')
ureg.define('ppm = 1e-6 fraction')

# USD are the reserve currency of the ITR tool
# By default, USD are the reserve currency of the ITR tool. But data template can change that
base_currency_unit = 'USD'
ureg.define("USD = [currency] = $")
for currency_symbol, currency_abbrev in ITR.data.currency_dict.items():
ureg.define(f"{currency_abbrev} = nan USD = {currency_symbol}")
Expand Down Expand Up @@ -690,8 +694,14 @@ def validate(cls, quantity):
quantity = q
if not isinstance(quantity, Quantity):
raise TypeError('pint.Quantity required')
if quantity.is_compatible_with('USD'):
return quantity
try:
if quantity.is_compatible_with('USD'):
return quantity
except RecursionError:
breakpoint()
for currency in ITR.data.currency_dict.values():
if quantity.is_compatible_with(currency):
return quantity
raise DimensionalityError (quantity, 'USD', dim1='', dim2='', extra_msg=f"Dimensionality must be 'dimensionless' or compatible with [{ITR.data.currency_dict.values()}]")

def __repr__(self):
Expand Down
Loading

0 comments on commit 47058bc

Please sign in to comment.