ALIGNED project: OTA tutorial¶
Aligning Life Cycle Assessment methods and bio-based sectors for improved environmental performance
Horizon Europe grant agreement N° 101059430. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Research Executive Agency.
WP1 Shared modelling framework and learnings¶
Task 1.4 Framework for interpreting uncertainty¶
Deliverable 1.2 Description of scientific methods¶
Tutorial for performing One At Time (OAT) sensitivity analysis¶
Massimo Pizzol, Aalborg University (AAU), 2024¶
This notebook show how to perform a simple One A Time (OAT) sensitivity analysis for a example product system of a biobased product, and calculated sensitivity ratios for each parameter.
#importing packages
import brightway2 as bw
import pandas as pd
import numpy as np
from scipy import stats
from lci_to_bw2 import * # import all the functions of this module
# open a project with ecoinvent v.3.9.1 consequential system model
bw.projects.set_current('advlca23')
We start by importing data about a fictional ("dummy") product system for a biobased product.
The product system includes different activities such as the production, use, and end of life of the biobased product.
# Import the dummy product system
# import data from csv
mydata = pd.read_csv('ALIGNED-LCI-biobased-product-dummy.csv', header = 0, sep = ",") # using csv file avoids encoding problem
mydata.head()
# keep only the columns not needed
mydb = mydata[['Activity database','Activity code','Activity name','Activity unit','Activity type',
'Exchange database','Exchange input','Exchange amount','Exchange unit','Exchange type',
'Exchange uncertainty type','Exchange loc','Exchange scale','Exchange negative',
'Simapro name', 'Simapro unit', 'Simapro type']].copy()
mydb = mydata.copy()
mydb['Exchange uncertainty type'] = mydb['Exchange uncertainty type'].fillna(0).astype(int) # uncertainty as integers
# Note: to avoid having both nan and values in the uncertainty column I use zero as default
# Create dictionary in bw format and write database to disk.
# Shut down all other notebooks using the same project before doing this
bw2_db = lci_to_bw2(mydb) # a function from the lci_to_bw2 module
# write database
bw.Database('ALIGNED-biob-prod-dummy').write(bw2_db)
# check what foreground activities are included
for act in bw.Database('ALIGNED-biob-prod-dummy'):
print(act, act['code'])
Writing activities to SQLite3 database: 0% [#####] 100% | ETA: 00:00:00 Total time elapsed: 00:00:00
Title: Writing activities to SQLite3 database: Started: 03/14/2024 20:23:04 Finished: 03/14/2024 20:23:04 Total time elapsed: 00:00:00 CPU %: 84.30 Memory %: 1.25 'Biobased-product-eol' (kilogram, None, None) c8301e73-d521-4a89-998b-30b7e7751011 'Biomass-growth' (kilogram, None, None) a7d34649-9c10-4423-bac3-ecab9b43b20c 'Biobased-product-manufacturing' (kilogram, None, None) a37d149a-6508-4563-8af6-e5a39b4176df 'Biobased-product-use' (year, None, None) f9eabf64-b899-40c0-9f9f-2009dbb0a0b2 'Biomass-processing' (kilogram, None, None) 403a5c32-c769-46fc-8b9a-74b8eb3c79d1
# More info
myact = bw.Database('ALIGNED-biob-prod-dummy').get('f9eabf64-b899-40c0-9f9f-2009dbb0a0b2') # Biobased-product-use
myact._data
{'name': 'Biobased-product-use', 'unit': 'year', 'type': 'process', 'database': 'ALIGNED-biob-prod-dummy', 'code': 'f9eabf64-b899-40c0-9f9f-2009dbb0a0b2'}
We calculate a static climate impact score for the fictional biobased product, to be used for reference later on.
# calculation of static LCA score
mymethod = ('IPCC 2013', 'climate change', 'global warming potential (GWP100)')
myact = bw.Database('ALIGNED-biob-prod-dummy').get('f9eabf64-b899-40c0-9f9f-2009dbb0a0b2') # Biobased-product-use
functional_unit = {myact: 1}
LCA = bw.LCA(functional_unit, mymethod)
LCA.lci()
LCA.lcia()
print("The static Global Warming impact score is", LCA.score, bw.methods[mymethod]['unit'])
The static Global Warming impact score is 121.67148851736351 kg CO2-Eq
Now perform sensitivity analysis¶
The procedure is in two steps:
A simulation is performed where the initial parameter (value of each exchange) is increased by 10%. New results are calculated.
Sensitivity rations are calculated for each parameter and then ranked.
Step 1¶
Iteration through all parameters, change the technology matrix and recaltulate results.
# Iterate through all exchanges in the foreground system and change the value up by 10%
par_name = []
par_values = []
par_upper_values = []
score_values = []
OAT_values = []
mymethod = ('IPCC 2013', 'climate change', 'global warming potential (GWP100)')
myact = bw.Database('ALIGNED-biob-prod-dummy').get('f9eabf64-b899-40c0-9f9f-2009dbb0a0b2') # Biobased-product-use
functional_unit = {myact: 1}
LCA = bw.LCA(functional_unit, mymethod)
for act in bw.Database('ALIGNED-biob-prod-dummy'): # for all activities...
for exc in act.exchanges(): # for all exchanges...
LCA.lci()
LCA.lcia()
print("initial value", LCA.score)
par_name.append((act['code'], exc['input'][1]))
par_values.append(exc['amount'])
par_upper_values.append(exc['amount'] * 1.1)
score_values.append(LCA.score)
if exc['type'] == "biosphere":
col = LCA.activity_dict[exc['output']] # find column index of A matrix for the activity
row = LCA.biosphere_dict[exc['input']] # find row index of B matrix for the exchange
LCA.biosphere_matrix[row,col] = LCA.biosphere_matrix[row,col] * 1.1 # print the initial and final value (to be substituted)
else:
col = LCA.activity_dict[exc['output']] # find column index of A matrix for the activity
row = LCA.activity_dict[exc['input']] # find row index of A matrix for the exchange
LCA.technosphere_matrix[row,col] = LCA.technosphere_matrix[row,col] * 1.1
LCA.redo_lci() # uses the new A matrix
LCA.lcia()
OAT_values.append(LCA.score)
print('end value', LCA.score)
print("---")
initial value 121.67148851736351 end value 117.00918701648808 --- initial value 121.67148851736351 end value 129.18423230271452 --- initial value 121.67148851736351 end value 121.78727638297558 --- initial value 121.67148851736351 end value 119.17148851736351 --- initial value 121.67148851736351 end value 117.0329460340928 --- initial value 121.67148851736351 end value 121.7738852489613 --- initial value 121.67148851736351 end value 126.67148851736353 --- initial value 121.67148851736351 end value 120.00530969230418 --- initial value 121.67148851736351 end value 121.72268688316248 --- initial value 121.67148851736351 end value 123.45308685913 --- initial value 121.67148851736351 end value 115.2499202536125 --- initial value 121.67148851736351 end value 126.80002016832663 --- initial value 121.67148851736351 end value 123.50428522492882 --- initial value 121.67148851736351 end value 121.7738852489613 --- initial value 121.67148851736351 end value 110.61044410669406 --- initial value 121.67148851736351 end value 128.73521360748964 --- initial value 121.67148851736351 end value 126.7738852489613 --- initial value 121.67148851736351 end value 121.67251554737607 ---
Let's look at the results.
Initial and increased ('upper') parameter values and initial and end values of the Global Warming Impact (GWI) scores.
sr = pd.DataFrame([par_name, par_values, par_upper_values, score_values, OAT_values],
index = ['par_name','par_initial', 'par_upper', 'GWI_initial', 'GWI_end']).T
sr.head()
par_name | par_initial | par_upper | GWI_initial | GWI_end | |
---|---|---|---|---|---|
0 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, a7d3464... | 1.0 | 1.1 | 121.671489 | 117.009187 |
1 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 97601e6... | 0.5 | 0.55 | 121.671489 | 129.184232 |
2 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 8e1f53c... | 0.5 | 0.55 | 121.671489 | 121.787276 |
3 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 349b29d... | -1.0 | -1.1 | 121.671489 | 119.171489 |
4 | (c8301e73-d521-4a89-998b-30b7e7751011, c8301e7... | 1.0 | 1.1 | 121.671489 | 117.032946 |
Step 2¶
Sensitivity ratios (SR) are calculated using the formula for discrete distributions, see equation (2) in Bisinella et al. (2016)
Bisinella, V., Conradsen, K., Christensen, T.H., Astrup, T.F., 2016. A global approach for sparse representation of uncertainty in Life Cycle Assessments of waste management systems. International Journal of Life Cycle Assessment. https://doi.org/10.1007/s11367-015-1014-4
# calcualte sensitivity ratios
sr['SR'] = ((sr['GWI_end']-sr['GWI_initial'])/sr['GWI_initial']) / ((sr['par_upper']-sr['par_initial'])/sr['par_initial'])
sr['SR_abs'] = abs(sr['SR'])
sr.head()
par_name | par_initial | par_upper | GWI_initial | GWI_end | SR | SR_abs | |
---|---|---|---|---|---|---|---|
0 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, a7d3464... | 1.0 | 1.1 | 121.671489 | 117.009187 | -0.383188 | 0.383188 |
1 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 97601e6... | 0.5 | 0.55 | 121.671489 | 129.184232 | 0.617461 | 0.617461 |
2 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 8e1f53c... | 0.5 | 0.55 | 121.671489 | 121.787276 | 0.009516 | 0.009516 |
3 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 349b29d... | -1.0 | -1.1 | 121.671489 | 119.171489 | -0.205471 | 0.205471 |
4 | (c8301e73-d521-4a89-998b-30b7e7751011, c8301e7... | 1.0 | 1.1 | 121.671489 | 117.032946 | -0.381235 | 0.381235 |
We can rank the parameters to see the most sensitive ones. I use the absolute value of SR in this case.
sr_sorted = sr.sort_values('SR_abs', ascending = False, ignore_index = True).copy() # important to re-index...
sr_sorted
par_name | par_initial | par_upper | GWI_initial | GWI_end | SR | SR_abs | |
---|---|---|---|---|---|---|---|
0 | (f9eabf64-b899-40c0-9f9f-2009dbb0a0b2, f9eabf6... | 1.0 | 1.1 | 121.671489 | 110.610444 | -0.909091 | 0.909091 |
1 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 97601e6... | 0.5 | 0.55 | 121.671489 | 129.184232 | 0.617461 | 0.617461 |
2 | (f9eabf64-b899-40c0-9f9f-2009dbb0a0b2, a37d149... | 50.0 | 55.0 | 121.671489 | 128.735214 | 0.580557 | 0.580557 |
3 | (a37d149a-6508-4563-8af6-e5a39b4176df, a37d149... | 1.0 | 1.1 | 121.671489 | 115.24992 | -0.527779 | 0.527779 |
4 | (a37d149a-6508-4563-8af6-e5a39b4176df, a7d3464... | 0.5 | 0.55 | 121.671489 | 126.80002 | 0.421506 | 0.421506 |
5 | (f9eabf64-b899-40c0-9f9f-2009dbb0a0b2, c8301e7... | 50.0 | 55.0 | 121.671489 | 126.773885 | 0.419358 | 0.419358 |
6 | (c8301e73-d521-4a89-998b-30b7e7751011, 349b29d... | 1.0 | 1.1 | 121.671489 | 126.671489 | 0.410943 | 0.410943 |
7 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, a7d3464... | 1.0 | 1.1 | 121.671489 | 117.009187 | -0.383188 | 0.383188 |
8 | (c8301e73-d521-4a89-998b-30b7e7751011, c8301e7... | 1.0 | 1.1 | 121.671489 | 117.032946 | -0.381235 | 0.381235 |
9 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 349b29d... | -1.0 | -1.1 | 121.671489 | 119.171489 | -0.205471 | 0.205471 |
10 | (a37d149a-6508-4563-8af6-e5a39b4176df, 403a5c3... | 0.5 | 0.55 | 121.671489 | 123.504285 | 0.150635 | 0.150635 |
11 | (403a5c32-c769-46fc-8b9a-74b8eb3c79d1, 98f11d5... | 0.5 | 0.55 | 121.671489 | 123.453087 | 0.146427 | 0.146427 |
12 | (403a5c32-c769-46fc-8b9a-74b8eb3c79d1, 403a5c3... | 1.0 | 1.1 | 121.671489 | 120.00531 | -0.136941 | 0.136941 |
13 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 8e1f53c... | 0.5 | 0.55 | 121.671489 | 121.787276 | 0.009516 | 0.009516 |
14 | (a37d149a-6508-4563-8af6-e5a39b4176df, f4dc7d2... | 0.5 | 0.55 | 121.671489 | 121.773885 | 0.008416 | 0.008416 |
15 | (c8301e73-d521-4a89-998b-30b7e7751011, f4dc7d2... | 0.5 | 0.55 | 121.671489 | 121.773885 | 0.008416 | 0.008416 |
16 | (403a5c32-c769-46fc-8b9a-74b8eb3c79d1, f4dc7d2... | 0.5 | 0.55 | 121.671489 | 121.722687 | 0.004208 | 0.004208 |
17 | (f9eabf64-b899-40c0-9f9f-2009dbb0a0b2, e23cd9a... | 0.1 | 0.11 | 121.671489 | 121.672516 | 0.000084 | 0.000084 |
What are the most sensitive parameters in plain English?
#scroll through the table, identify the specific exchanges.
testing = []
for i in range(0, sr_sorted.shape[0]):
par = sr_sorted.iloc[i,0]
for i in bw.Database('ALIGNED-biob-prod-dummy').get(par[0]).exchanges():
if i['input'][1] == par[1]:
print(i)
testing.append(str(i))
Exchange: 1.0 year 'Biobased-product-use' (year, None, None) to 'Biobased-product-use' (year, None, None)> Exchange: 0.5 kilogram 'nutrient supply from NPK (26-15-15) fertiliser' (kilogram, RER, None) to 'Biomass-growth' (kilogram, None, None)> Exchange: 50.0 kilogram 'Biobased-product-manufacturing' (kilogram, None, None) to 'Biobased-product-use' (year, None, None)> Exchange: 1.0 kilogram 'Biobased-product-manufacturing' (kilogram, None, None) to 'Biobased-product-manufacturing' (kilogram, None, None)> Exchange: 0.5 kilogram 'Biomass-growth' (kilogram, None, None) to 'Biobased-product-manufacturing' (kilogram, None, None)> Exchange: 50.0 kilogram 'Biobased-product-eol' (kilogram, None, None) to 'Biobased-product-use' (year, None, None)> Exchange: 1.0 kilogram 'Carbon dioxide, fossil' (kilogram, None, ('air',)) to 'Biobased-product-eol' (kilogram, None, None)> Exchange: 1.0 kilogram 'Biomass-growth' (kilogram, None, None) to 'Biomass-growth' (kilogram, None, None)> Exchange: 1.0 kilogram 'Biobased-product-eol' (kilogram, None, None) to 'Biobased-product-eol' (kilogram, None, None)> Exchange: -1.0 kilogram 'Carbon dioxide, fossil' (kilogram, None, ('air',)) to 'Biomass-growth' (kilogram, None, None)> Exchange: 0.5 kilogram 'Biomass-processing' (kilogram, None, None) to 'Biobased-product-manufacturing' (kilogram, None, None)> Exchange: 0.5 kilogram 'hydrochloric acid production, from the reaction of hydrogen with chlorine' (kilogram, RoW, None) to 'Biomass-processing' (kilogram, None, None)> Exchange: 1.0 kilogram 'Biomass-processing' (kilogram, None, None) to 'Biomass-processing' (kilogram, None, None)> Exchange: 0.5 cubic meter 'market for irrigation' (cubic meter, CH, None) to 'Biomass-growth' (kilogram, None, None)> Exchange: 0.5 kilowatt hour 'market for electricity, medium voltage' (kilowatt hour, DK, None) to 'Biobased-product-manufacturing' (kilogram, None, None)> Exchange: 0.5 kilowatt hour 'market for electricity, medium voltage' (kilowatt hour, DK, None) to 'Biobased-product-eol' (kilogram, None, None)> Exchange: 0.5 kilowatt hour 'market for electricity, medium voltage' (kilowatt hour, DK, None) to 'Biomass-processing' (kilogram, None, None)> Exchange: 0.1 ton kilometer 'market for transport, freight, lorry >32 metric ton, EURO6' (ton kilometer, RER, None) to 'Biobased-product-use' (year, None, None)>
sr_sorted['par_long-name'] = testing
sr_sorted
par_name | par_initial | par_upper | GWI_initial | GWI_end | SR | SR_abs | par_long-name | |
---|---|---|---|---|---|---|---|---|
0 | (f9eabf64-b899-40c0-9f9f-2009dbb0a0b2, f9eabf6... | 1.0 | 1.1 | 121.671489 | 110.610444 | -0.909091 | 0.909091 | Exchange: 1.0 year 'Biobased-product-use' (yea... |
1 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 97601e6... | 0.5 | 0.55 | 121.671489 | 129.184232 | 0.617461 | 0.617461 | Exchange: 0.5 kilogram 'nutrient supply from N... |
2 | (f9eabf64-b899-40c0-9f9f-2009dbb0a0b2, a37d149... | 50.0 | 55.0 | 121.671489 | 128.735214 | 0.580557 | 0.580557 | Exchange: 50.0 kilogram 'Biobased-product-manu... |
3 | (a37d149a-6508-4563-8af6-e5a39b4176df, a37d149... | 1.0 | 1.1 | 121.671489 | 115.24992 | -0.527779 | 0.527779 | Exchange: 1.0 kilogram 'Biobased-product-manuf... |
4 | (a37d149a-6508-4563-8af6-e5a39b4176df, a7d3464... | 0.5 | 0.55 | 121.671489 | 126.80002 | 0.421506 | 0.421506 | Exchange: 0.5 kilogram 'Biomass-growth' (kilog... |
5 | (f9eabf64-b899-40c0-9f9f-2009dbb0a0b2, c8301e7... | 50.0 | 55.0 | 121.671489 | 126.773885 | 0.419358 | 0.419358 | Exchange: 50.0 kilogram 'Biobased-product-eol'... |
6 | (c8301e73-d521-4a89-998b-30b7e7751011, 349b29d... | 1.0 | 1.1 | 121.671489 | 126.671489 | 0.410943 | 0.410943 | Exchange: 1.0 kilogram 'Carbon dioxide, fossil... |
7 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, a7d3464... | 1.0 | 1.1 | 121.671489 | 117.009187 | -0.383188 | 0.383188 | Exchange: 1.0 kilogram 'Biomass-growth' (kilog... |
8 | (c8301e73-d521-4a89-998b-30b7e7751011, c8301e7... | 1.0 | 1.1 | 121.671489 | 117.032946 | -0.381235 | 0.381235 | Exchange: 1.0 kilogram 'Biobased-product-eol' ... |
9 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 349b29d... | -1.0 | -1.1 | 121.671489 | 119.171489 | -0.205471 | 0.205471 | Exchange: -1.0 kilogram 'Carbon dioxide, fossi... |
10 | (a37d149a-6508-4563-8af6-e5a39b4176df, 403a5c3... | 0.5 | 0.55 | 121.671489 | 123.504285 | 0.150635 | 0.150635 | Exchange: 0.5 kilogram 'Biomass-processing' (k... |
11 | (403a5c32-c769-46fc-8b9a-74b8eb3c79d1, 98f11d5... | 0.5 | 0.55 | 121.671489 | 123.453087 | 0.146427 | 0.146427 | Exchange: 0.5 kilogram 'hydrochloric acid prod... |
12 | (403a5c32-c769-46fc-8b9a-74b8eb3c79d1, 403a5c3... | 1.0 | 1.1 | 121.671489 | 120.00531 | -0.136941 | 0.136941 | Exchange: 1.0 kilogram 'Biomass-processing' (k... |
13 | (a7d34649-9c10-4423-bac3-ecab9b43b20c, 8e1f53c... | 0.5 | 0.55 | 121.671489 | 121.787276 | 0.009516 | 0.009516 | Exchange: 0.5 cubic meter 'market for irrigati... |
14 | (a37d149a-6508-4563-8af6-e5a39b4176df, f4dc7d2... | 0.5 | 0.55 | 121.671489 | 121.773885 | 0.008416 | 0.008416 | Exchange: 0.5 kilowatt hour 'market for electr... |
15 | (c8301e73-d521-4a89-998b-30b7e7751011, f4dc7d2... | 0.5 | 0.55 | 121.671489 | 121.773885 | 0.008416 | 0.008416 | Exchange: 0.5 kilowatt hour 'market for electr... |
16 | (403a5c32-c769-46fc-8b9a-74b8eb3c79d1, f4dc7d2... | 0.5 | 0.55 | 121.671489 | 121.722687 | 0.004208 | 0.004208 | Exchange: 0.5 kilowatt hour 'market for electr... |
17 | (f9eabf64-b899-40c0-9f9f-2009dbb0a0b2, e23cd9a... | 0.1 | 0.11 | 121.671489 | 121.672516 | 0.000084 | 0.000084 | Exchange: 0.1 ton kilometer 'market for transp... |