Introduction to Brightway2#

Read me first...

Authors

This chapter was created by Karin Treyer in 2024. It was edited for publication by Michael Weinold with the help of Maria Höller and Mehdi Iguider as part of the Brightcon 2024 Documentation Hackathon.

In order to complete this tutorial successfully, you will need:

  1. Basic knowledge of the Conda package manager (manage environments & install packages).

  2. A working installation of Brightway 2 (NOT 2.5!).

  3. Basic knowledge of Python data types.

  4. Basic understanding of matrix-based LCA data and calculations.

How to use this Tutorial…

  1. You could read it online on this website and copy/paste some snippets of code into a Jupyter Notebook to play around.

  2. If you click on the download button in the top right corner, you can download this section as a Jupyter Notebook (.ipynb).

Cheat Sheet

You can download a helpful cheat sheet for Brightway2 commands from the main documentation site: bw_cheatsheet_may_2024.pdf

1. Installing Activity Browser#

The Activity Browser (“AB”) is a tool that allows you to perform Brightway life-cycle assessment calculations using a visual interface. To install AB on your computer, you can follow the setup instructions here. There are different ways how you can install AB. You will already understand the “quick way” installation by now, which is shown here:

  1. Create a Conda environment named ab with the following command:

conda create -n ab -c conda-forge --solver libmamba activity-browser
  1. Activate the environment:

conda activate ab
  1. Start the Activity Browser:

activity-browser

Activity Browser “sees” all the Brightway projects you have on your computer, and adopts all new projects you create and all new databases as you are working on. This means that you can make edits to a Brightway project using the command line and these changes will be reflected in the Activity Browser interface.

2. Create an Environment Brightway#

You should create a separate environment for your Brightway installation. To install Brightway on your computer, you can follow the setup instruction here:

  1. Create a Conda environment named bw with the following command:

conda create -n bw -c conda-forge --solver libmamba brightway2=2.4.7 jupyterlab

Note that here we have installed both Brightway and the jupyterlab package, which is required to work with Jupyter Notebooks.

  1. Activate the environment:

conda activate bw
  1. Start Jupyter Lab so that you can work on a notebook:

jupyter lab

Now that you have understood how to get the Brightway packages on your computer and you want to do what your heart longs for: LCA 💚

3. Example Project#

We investigate Hydrogen production via water electrolysis technologies. It’s good practice to describe your project shortly, and which data you are using:

Origin of LCI data:

  • ecoinvent v3.9.1 cut-off system

  • literature data

  • original data from company X (confidential)

Goal of the study:

  • Import the biosphere, LCIA methods

  • Import the background database ecoinvent

  • Import the foreground inventories (you can look at all these databases in AB in a human friendly way)

  • Calculate the LCIA results, create an easy plot, but additionally output them an Excel file so that you can visualise it there, if you are not used to visualisation with Python.

3.1 Notebook Preparations#

First, we must import the required Brightway packages and set up the project:

import bw2io as bi
import bw2data as bd
import bw2calc as bc

Often, we need to use other helpful Python packages. For each of these, very helpful CheatSheets exist on the web! In our example, the Pandas library for working with tabulated data will be sufficient.

import pandas as pd

3.2 Setting up Projects#

Information

You can do the next steps in ActivityBrowser if you prefer. It’s possible to create, rename, and delete projects there.

You can check which projects exist on my computer - to see which ones are there, or if you have forgotten the name of your project. Of course, on your computer, you will see a different list of projects:

list(bd.projects) #the prefix "bw" indicates that "projects" is a method of the bw2data package
[Project: default,
 Project: ei_3.10,
 Project: debug,
 Project: banana,
 Project: ddd,
 Project: USEEIO-1.1,
 Project: bw_panel,
 Project: learn_brightway_bw2]

Let’s create a project (or activate it when you come back to work on your project):

bd.projects.set_current('learn_brightway_bw2') # activates a project, or creates it first if it doesn't exist yet

3.3 Filling your project with the Biosphere, LCIA Methods, and Ecoinvent#

Information

You can do the next steps in ActivityBrowser if you prefer. It’s possible to import biosphere, LCIA methods, and ecoinvent (and other databases) there.

You only have to import the databases once. After that, they will be present in your project.

list(bd.databases) # check if there are databases in the project, and how they are named. 

Ok, nothing is there yet. There are now two ways to import the necessary Ecoinvent database. You can choose either option A or option B, but you don’t have to execute both!

Option A#

This option imports the biosphere, LCIA methods, and ecoinvent all in one go!

Change Placeholder Text!

Below, change the placeholder text JohnDoe to your own Ecoinvent username and the password 1234 to your own Ecoinvent password.

if 'ecoinvent-3.9.1-cutoff' in bd.databases:
    print('ecoinvent 3.9.1 is already present in the project')
else:
    bi.import_ecoinvent_release(
        version='3.9.1',
        system_model='cutoff', # can be cutoff / apos / consequential / EN15804
        username='JohnDoe',
        password='1234'
    )
Applying strategy: normalize_units
Applying strategy: drop_unspecified_subcategories
Applying strategy: ensure_categories_are_tuples
Applied 3 strategies in 0.00 seconds
4718 datasets
0 exchanges
0 unlinked exchanges
  
Warning: No valid output stream.
Title: Writing activities to SQLite3 database:
  Started: 09/24/2024 10:10:17
  Finished: 09/24/2024 10:10:17
  Total time elapsed: 00:00:00
  CPU %: 99.30
  Memory %: 1.64
Created database: ecoinvent-3.9.1-biosphere
Extracting XML data from 21238 datasets
Extracted 21238 datasets in 18.87 seconds
Applying strategy: normalize_units
Applying strategy: update_ecoinvent_locations
Applying strategy: remove_zero_amount_coproducts
Applying strategy: remove_zero_amount_inputs_with_no_activity
Applying strategy: remove_unnamed_parameters
Applying strategy: es2_assign_only_product_with_amount_as_reference_product
Applying strategy: assign_single_product_as_activity
Applying strategy: create_composite_code
Applying strategy: drop_unspecified_subcategories
Applying strategy: fix_ecoinvent_flows_pre35
Applying strategy: drop_temporary_outdated_biosphere_flows
Applying strategy: link_biosphere_by_flow_uuid
Applying strategy: link_internal_technosphere_by_composite_code
Applying strategy: delete_exchanges_missing_activity
Applying strategy: delete_ghost_exchanges
Applying strategy: remove_uncertainty_from_negative_loss_exchanges
Applying strategy: fix_unreasonably_high_lognormal_uncertainties
Applying strategy: convert_activity_parameters_to_list
Applying strategy: add_cpc_classification_from_single_reference_product
Applying strategy: delete_none_synonyms
Applying strategy: update_social_flows_in_older_consequential
Applying strategy: set_lognormal_loc_value
Applied 22 strategies in 3.79 seconds
21238 datasets
674593 exchanges
0 unlinked exchanges
  
Warning: No valid output stream.
Title: Writing activities to SQLite3 database:
  Started: 09/24/2024 10:10:46
  Finished: 09/24/2024 10:11:02
  Total time elapsed: 00:00:16
  CPU %: 98.70
  Memory %: 10.41
Created database: ecoinvent-3.9.1-cutoff

Let’s check if the databases are now there…

list(bd.databases)
['ecoinvent-3.9.1-biosphere', 'ecoinvent-3.9.1-cutoff']

Option B#

In most older notebooks you will find on the web, you will still see the old way of importing all these. There, we first import the biosphere and LCIA methods:

if any("biosphere" in db for db in bd.databases):
    print('Biosphere is already present in the project.')
else:
    bi.bw2setup()
Biosphere database already present!!! No setup is needed

Only now, we import the Ecoinvent data. First, download the Ecoinvent data from the Ecoinvent website and unzip the file. Then, provide the path to the unzipped folder datasets in the code below:

if 'ecoinvent-3.9.1-cutoff' in bd.databases:
    print('Ecoinvent 3.9.1 is already present in the project.')
else:
    ei = bi.SingleOutputEcospold2Importer(dirpath=r'C:\Users\johndoe\Downloads\ecoinvent\ecoinvent 3.9.1_cutoff_ecoSpold02\datasets', db_name='ev391cutoff') #recommendation for consistent databases naming: database name (ecoinvent), version number, system model
    ei.apply_strategies() #fixing some issues when ecoinvent and brightway have to talk together by going through all datasets and manipulating them in a specific way
    ei.statistics() #checking if everything worked out with strategies and linking
    ei.write_database() #save the database to our hard drive
ecoinvent 3.9.1 is already present in the project

You may want to switch to AB now to look at the databases you have just imported. In case they are not yet displayed in your project, switch to another project and switch back for the new database(s) to appear

3.4 Importing your own Data#

File Download

You can download the example files required for this section here:
lci_hydrogen_electrolysis.xlsx
lci_rawdata_import.xlsx

imp = bi.ExcelImporter(r'C:\Users\johndoe\Downloads\lci_hydrogen_electrolysis.xlsx') # the path to your inventory excel file
imp.apply_strategies()
imp.match_database("ecoinvent-3.9.1-cutoff", fields=('name', 'unit', 'location', 'reference product')) # 'reference product'
imp.match_database(fields=('name', 'unit', 'location'))
imp.statistics()
imp.write_excel(only_unlinked=True)
list(imp.unlinked)
imp.write_database()
Extracted 1 worksheets in 0.10 seconds
Applying strategy: csv_restore_tuples
Applying strategy: csv_restore_booleans
Applying strategy: csv_numerize
Applying strategy: csv_drop_unknown
Applying strategy: csv_add_missing_exchanges_section
Applying strategy: normalize_units
Applying strategy: normalize_biosphere_categories
Applying strategy: normalize_biosphere_names
Applying strategy: strip_biosphere_exc_locations
Applying strategy: set_code_by_activity_hash
Applying strategy: link_iterable_by_fields
Applying strategy: assign_only_product_as_production
Applying strategy: link_technosphere_by_activity_hash
Applying strategy: drop_falsey_uncertainty_fields_but_keep_zeros
Applying strategy: convert_uncertainty_types_to_integers
Applying strategy: convert_activity_parameters_to_list
Applied 16 strategies in 2.70 seconds
Applying strategy: link_iterable_by_fields
Applying strategy: link_iterable_by_fields
17 datasets
343 exchanges
0 unlinked exchanges
  
Wrote matching file to:
/Users/michaelweinold/Library/Application Support/Brightway3/learn_brightway_bw2.64db5ecb6d8079342bb61bf76f7f8046/output/db-matching-h2_electrolysis-unlinked.xlsx
Warning: No valid output stream.
Title: Writing activities to SQLite3 database:
  Started: 09/24/2024 11:32:27
  Finished: 09/24/2024 11:32:27
  Total time elapsed: 00:00:00
  CPU %: 40.50
  Memory %: 6.93
Created database: h2_electrolysis

This should have worked smoothly. Again, you can also look at this database in the AB.

HOWEVER, importing from Excel won’t always be so easy! This is demonstrated here:

imp = bi.ExcelImporter(r'/Users/michaelweinold/github/brightway-book/content/chapters/BW2/_data/lci_rawdata_import.xlsx')
imp.apply_strategies()
imp.match_database("ecoinvent-3.9.1-cutoff", fields=('name','unit','location', 'reference product'))
imp.match_database(fields=('name', 'unit', 'location'))
imp.statistics()
imp.write_excel() #(only_unlinked=True)
list(imp.unlinked)

imp.write_database()
Extracted 1 worksheets in 0.02 seconds
Applying strategy: csv_restore_tuples
Applying strategy: csv_restore_booleans
Applying strategy: csv_numerize
Applying strategy: csv_drop_unknown
Applying strategy: csv_add_missing_exchanges_section
Applying strategy: normalize_units
Applying strategy: normalize_biosphere_categories
Applying strategy: normalize_biosphere_names
Applying strategy: strip_biosphere_exc_locations
Applying strategy: set_code_by_activity_hash
Applying strategy: link_iterable_by_fields
Applying strategy: assign_only_product_as_production
Applying strategy: link_technosphere_by_activity_hash
Applying strategy: drop_falsey_uncertainty_fields_but_keep_zeros
Applying strategy: convert_uncertainty_types_to_integers
Applying strategy: convert_activity_parameters_to_list
Applied 16 strategies in 2.77 seconds
Applying strategy: link_iterable_by_fields
Applying strategy: link_iterable_by_fields
2 datasets
42 exchanges
3 unlinked exchanges
  Type biosphere: 1 unique unlinked exchanges
  Type technosphere: 2 unique unlinked exchanges
Wrote matching file to:
/Users/michaelweinold/Library/Application Support/Brightway3/learn_brightway_bw2.64db5ecb6d8079342bb61bf76f7f8046/output/db-matching-hydrogen_demo.xlsx
Warning: No valid output stream.
---------------------------------------------------------------------------
InvalidExchange                           Traceback (most recent call last)
Cell In[21], line 9
      6 imp.write_excel() #(only_unlinked=True)
      7 list(imp.unlinked)
----> 9 imp.write_database()

File /opt/homebrew/Caskroom/miniconda/base/envs/env_bw2/lib/python3.11/site-packages/bw2io/importers/excel.py:284, in ExcelImporter.write_database(self, **kwargs)
    282 """Same as base ``write_database`` method, but ``activate_parameters`` is True by default."""
    283 kwargs["activate_parameters"] = kwargs.get("activate_parameters", True)
--> 284 super(ExcelImporter, self).write_database(**kwargs)

File /opt/homebrew/Caskroom/miniconda/base/envs/env_bw2/lib/python3.11/site-packages/bw2io/importers/base_lci.py:273, in LCIImporter.write_database(self, data, delete_existing, backend, activate_parameters, **kwargs)
    270 self.write_database_parameters(activate_parameters, delete_existing)
    272 existing.update(data)
--> 273 db.write(existing)
    275 if activate_parameters:
    276     self._write_activity_parameters(activity_parameters)

File /opt/homebrew/Caskroom/miniconda/base/envs/env_bw2/lib/python3.11/site-packages/bw2data/project.py:358, in writable_project(wrapped, instance, args, kwargs)
    356 if projects.read_only:
    357     raise ReadOnlyProject(READ_ONLY_PROJECT)
--> 358 return wrapped(*args, **kwargs)

File /opt/homebrew/Caskroom/miniconda/base/envs/env_bw2/lib/python3.11/site-packages/bw2data/backends/peewee/database.py:260, in SQLiteBackend.write(self, data, process)
    258 if data:
    259     try:
--> 260         self._efficient_write_many_data(data)
    261     except:
    262         # Purge all data from database, then reraise
    263         self.delete(warn=False)

File /opt/homebrew/Caskroom/miniconda/base/envs/env_bw2/lib/python3.11/site-packages/bw2data/backends/peewee/database.py:204, in SQLiteBackend._efficient_write_many_data(self, data, indices)
    197     self.pbar = pyprind.ProgBar(
    198         len(data),
    199         title="Writing activities to SQLite3 database:",
    200         monitor=True
    201     )
    203 for index, (key, ds) in enumerate(data.items()):
--> 204     exchanges, activities = self._efficient_write_dataset(
    205         index, key, ds, exchanges, activities
    206     )
    208 if not getattr(config, "is_test", None):
    209     print(self.pbar)

File /opt/homebrew/Caskroom/miniconda/base/envs/env_bw2/lib/python3.11/site-packages/bw2data/backends/peewee/database.py:156, in SQLiteBackend._efficient_write_dataset(self, index, key, ds, exchanges, activities)
    154 for exchange in ds.get('exchanges', []):
    155     if 'input' not in exchange or 'amount' not in exchange:
--> 156         raise InvalidExchange
    157     if 'type' not in exchange:
    158         raise UntypedExchange

InvalidExchange: 

As you can see, there are some unlinked flows. This means that no corresponding dataset for an exchange listed in the spreadsheet could be found. Usually, this is because of types, wrong type, wrong unit, etc.

To fix this, open the excel file with the unlinked flows. It will show you the lines where a problem has occurred. Did you find the errors?

In the first activity, I misspelled “granulate” in the reference product. In the second case, I put “biosphere” as type instead of “technosphere”. And in the third case, I am using a location for which no market for deionised water exists. It should be CH, not AT.

In case you do not want to fix the file yourself, here is the corrected version:

File Download

You can download the CORRECTED example file here:
lci_rawdata_import_corrected.xlsx

list(bd.databases)
['ecoinvent-3.9.1-biosphere',
 'ecoinvent-3.9.1-cutoff',
 'h2_electrolysis',
 'hydrogen_demo']

3.5 Looking at the Databases#

License

This is more “human friendly” in AB! For quick overviews, searches etc., AB is more convenient than the Jupyter Notebook.

First, we choose the activities we want to analyse later in the LCA. Here, I want to compare hydrogen production with different electrolysers.

h2elec =  bd.Database('h2_electrolysis')
h2prod = [a for a in h2elec if 'hydrogen production, gaseous' in a ['name']]
h2prod
['hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity' (kilogram, CH, None),
 'hydrogen production, gaseous, 30 bar, from PEM electrolysis, from grid electricity' (kilogram, RER, None),
 'hydrogen production, gaseous, 1 bar, from SOEC electrolysis, from grid electricity' (kilogram, CH, None),
 'hydrogen production, gaseous, 1 bar, from SOEC electrolysis, with steam input, from grid electricity' (kilogram, CH, None)]

We can look at one of these activities here. But you might prefer to do that in AB.

list(h2prod[0].technosphere())
[Exchange: 9.391435011269722e-07 unit 'electrolyzer production, 1MWe, AEC, Stack' (unit, RER, None) to 'hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity' (kilogram, CH, None)>,
 Exchange: 2.3478587528174306e-07 unit 'electrolyzer production, 1MWe, AEC, Balance of Plant' (unit, RER, None) to 'hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity' (kilogram, CH, None)>,
 Exchange: -9.391435011269722e-07 unit 'treatment of fuel cell stack, 1MWe, AEC' (unit, RER, None) to 'hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity' (kilogram, CH, None)>,
 Exchange: -2.3478587528174306e-07 unit 'treatment of fuel cell balance of plant, 1MWe, AEC' (unit, RER, None) to 'hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity' (kilogram, CH, None)>,
 Exchange: 51.8 kilowatt hour 'market for electricity, low voltage' (kilowatt hour, CH, None) to 'hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity' (kilogram, CH, None)>,
 Exchange: 0.0037 kilogram 'market for potassium hydroxide' (kilogram, GLO, None) to 'hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity' (kilogram, CH, None)>,
 Exchange: 14 kilogram 'market for water, deionised' (kilogram, Europe without Switzerland, None) to 'hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity' (kilogram, CH, None)>]

We also need to choose LCIA methods. Here, I only chose to compare the different IPCC GWP time horizons to make it simple

bd.methods # if you don't know the names of the different LCIA methods, you can check all methods in AB, or get a list of all of them here.
Methods dictionary with 762 objects, including:
	('CML v4.8 2016', 'acidification', 'acidification (incl. fate, average Europe total, A&B)')
	('CML v4.8 2016', 'climate change', 'global warming potential (GWP100)')
	('CML v4.8 2016', 'ecotoxicity: freshwater', 'freshwater aquatic ecotoxicity (FAETP inf)')
	('CML v4.8 2016', 'ecotoxicity: marine', 'marine aquatic ecotoxicity (MAETP inf)')
	('CML v4.8 2016', 'ecotoxicity: terrestrial', 'terrestrial ecotoxicity (TETP inf)')
	('CML v4.8 2016', 'energy resources: non-renewable', 'abiotic depletion potential (ADP): fossil fuels')
	('CML v4.8 2016', 'eutrophication', 'eutrophication (fate not incl.)')
	('CML v4.8 2016', 'human toxicity', 'human toxicity (HTP inf)')
	('CML v4.8 2016', 'material resources: metals/minerals', 'abiotic depletion potential (ADP): elements (ultimate reserves)')
	('CML v4.8 2016', 'ozone depletion', 'ozone layer depletion (ODP steady state)')
Use `list(this object)` to get the complete list.
ipcc = [m for m in bd.methods if 'IPCC' in str(m) and '2021' in str(m) and 'GWP' in str(m) and 'LT' not in str(m) and 'fossil' not in str(m) and 'biogenic' not in str(m) and 'land use' not in str(m) and 'SLCFs' not in str(m)]
ipcc
[('IPCC 2021', 'climate change', 'global warming potential (GWP100)'),
 ('IPCC 2021', 'climate change', 'global warming potential (GWP20)'),
 ('IPCC 2021', 'climate change', 'global warming potential (GWP500)')]

3.6 Performing an LCA#

FU = [{x:1} for x in h2prod] # defining the functional units: we want "1" of the activities which produce hydrogen.
bd.calculation_setups['GWPs_electrolysis'] = {'inv':FU, 'ia': ipcc}
mylca = bc.MultiLCA('GWPs_electrolysis')
mylca.results
array([[ 2.37768162,  2.92877936,  2.12446524],
       [19.44666019, 21.77269211, 18.49650242],
       [ 1.80959294,  2.23680139,  1.61681642],
       [ 3.44325776,  4.1221398 ,  3.15629171]])

That’s nice, but not very human friendly. Let’s look at a snippet of these results.

{k:v for k,v in zip(ipcc, mylca.results[0])}
{('IPCC 2021',
  'climate change',
  'global warming potential (GWP100)'): 19.44666019329231,
 ('IPCC 2021',
  'climate change',
  'global warming potential (GWP20)'): 21.772692106938454,
 ('IPCC 2021',
  'climate change',
  'global warming potential (GWP500)'): 18.496502417248085}

Better, but still not so convenient. We are using pandas to show the results in a way which is better defined.

mylcadf = pd.DataFrame(index = ipcc, columns = [(x['name'], x['location']) for y in FU for x in y], data=mylca.results.T)
mylcadf
(hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity, CH) (hydrogen production, gaseous, 30 bar, from PEM electrolysis, from grid electricity, RER) (hydrogen production, gaseous, 1 bar, from SOEC electrolysis, from grid electricity, CH) (hydrogen production, gaseous, 1 bar, from SOEC electrolysis, with steam input, from grid electricity, CH)
(IPCC 2021, climate change, global warming potential (GWP100)) 2.377682 19.446660 1.809593 3.443258
(IPCC 2021, climate change, global warming potential (GWP20)) 2.928779 21.772692 2.236801 4.122140
(IPCC 2021, climate change, global warming potential (GWP500)) 2.124465 18.496502 1.616816 3.156292
mylcadf.to_excel('lcia_results.xlsx') # export to excel, e.g. for creating figures

3.7 Plotting Results#

You can any Python plotting library, like seaborn or matplotlib for plotting. If you already have your data stored in a Pandas DataFrame, you can plot it directly with Pandas.

mylcadf # we are using the mylcadf dataframe created further above.
(hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity, CH) (hydrogen production, gaseous, 30 bar, from PEM electrolysis, from grid electricity, RER) (hydrogen production, gaseous, 1 bar, from SOEC electrolysis, from grid electricity, CH) (hydrogen production, gaseous, 1 bar, from SOEC electrolysis, with steam input, from grid electricity, CH)
(IPCC 2021, climate change, global warming potential (GWP100)) 2.377682 19.446660 1.809593 3.443258
(IPCC 2021, climate change, global warming potential (GWP20)) 2.928779 21.772692 2.236801 4.122140
(IPCC 2021, climate change, global warming potential (GWP500)) 2.124465 18.496502 1.616816 3.156292
print(mylcadf.columns.tolist())
[('hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity', 'CH'), ('hydrogen production, gaseous, 30 bar, from PEM electrolysis, from grid electricity', 'RER'), ('hydrogen production, gaseous, 1 bar, from SOEC electrolysis, from grid electricity', 'CH'), ('hydrogen production, gaseous, 1 bar, from SOEC electrolysis, with steam input, from grid electricity', 'CH')]

We can now look at the long names of the impact assessment methods:

ipcc
[('IPCC 2021', 'climate change', 'global warming potential (GWP100)'),
 ('IPCC 2021', 'climate change', 'global warming potential (GWP20)'),
 ('IPCC 2021', 'climate change', 'global warming potential (GWP500)')]

…and come up with shorter, more “human-friendly” descriptions:

labels_methods = {
    ('IPCC 2021', 'climate change', 'global warming potential (GWP100)'): "IPCC GWP100", 
    ('IPCC 2021', 'climate change', 'global warming potential (GWP20)'): 'IPCC GWP20',
    ('IPCC 2021', 'climate change', 'global warming potential (GWP500)'): 'IPCC GWP500',  
}

Similarly, we can look at the long names of the activities:

h2prod
['hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity' (kilogram, CH, None),
 'hydrogen production, gaseous, 30 bar, from PEM electrolysis, from grid electricity' (kilogram, RER, None),
 'hydrogen production, gaseous, 1 bar, from SOEC electrolysis, from grid electricity' (kilogram, CH, None),
 'hydrogen production, gaseous, 1 bar, from SOEC electrolysis, with steam input, from grid electricity' (kilogram, CH, None)]

…and come up with shorter, more “human-friendly” descriptions:

labels_act = {
    ('hydrogen production, gaseous, 1 bar, from SOEC electrolysis, from grid electricity', 'CH'): "SOEC_with_steam",
    ('hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity', 'CH'): "AEC", 
    ('hydrogen production, gaseous, 30 bar, from PEM electrolysis, from grid electricity', 'RER'): "PEM",
    ('hydrogen production, gaseous, 1 bar, from SOEC electrolysis, with steam input, from grid electricity', 'CH'): "SOEC"
}

Now, we can update the labels in the dataframe:

mylcadf
(hydrogen production, gaseous, 20 bar, from AEC electrolysis, from grid electricity, CH) (hydrogen production, gaseous, 30 bar, from PEM electrolysis, from grid electricity, RER) (hydrogen production, gaseous, 1 bar, from SOEC electrolysis, from grid electricity, CH) (hydrogen production, gaseous, 1 bar, from SOEC electrolysis, with steam input, from grid electricity, CH)
(IPCC 2021, climate change, global warming potential (GWP100)) 2.377682 19.446660 1.809593 3.443258
(IPCC 2021, climate change, global warming potential (GWP20)) 2.928779 21.772692 2.236801 4.122140
(IPCC 2021, climate change, global warming potential (GWP500)) 2.124465 18.496502 1.616816 3.156292
df = mylcadf.rename(columns=labels_act, index=labels_methods)
df
AEC PEM SOEC_with_steam SOEC
IPCC GWP100 2.377682 19.446660 1.809593 3.443258
IPCC GWP20 2.928779 21.772692 2.236801 4.122140
IPCC GWP500 2.124465 18.496502 1.616816 3.156292

Now can we plot the dataframe. Here, we are using the plotting functionality of Pandas itself:

df.plot.bar(
    xlabel='Impact category',
    ylabel='Impact score',
    figsize=(14,8)
)
<Axes: xlabel='Impact category', ylabel='Impact score'>
../../../_images/06f0f26f0d60ad7c71b1e82e23bdd1ffe2a047876f83bcf4c848d1ef0c9701dd.png

We can also normalise our data:

df_norm = (df.T / df.abs().max(axis=1)).T

…and plot it again:

df_norm.plot.bar(
    xlabel='Impact category',
    ylabel='Impact score',
    figsize=(14,8)
)
<Axes: xlabel='Impact category', ylabel='Impact score'>
../../../_images/43c034cd3d15a759a88a4753c0fb1b8404c5a0d2011fbf11b8ed1fa2018a6124.png

As you can see, the y-axis only goes up to 1.0. This is because we normalised the data.