https://github.com/benportner/ecoinvent_electricity_comparison
What is the most sustainable way to generate electricity? A comparison of ecoinvent datasets.
https://github.com/benportner/ecoinvent_electricity_comparison
Last synced: 6 months ago
JSON representation
What is the most sustainable way to generate electricity? A comparison of ecoinvent datasets.
- Host: GitHub
- URL: https://github.com/benportner/ecoinvent_electricity_comparison
- Owner: BenPortner
- License: gpl-3.0
- Created: 2019-08-21T11:37:20.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2019-08-21T16:31:20.000Z (almost 7 years ago)
- Last Synced: 2025-01-12T18:15:54.776Z (over 1 year ago)
- Language: HTML
- Size: 913 KB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
For a properly rendered version of the notebook, click here:https://nbviewer.jupyter.org/github/BenPortner/ecoinvent_electricity_comparison/blob/master/notebook.html
# What is the most sustainable way to generate electricity? A comparison of ecoinvent datasets.
Table of Contents
## Introduction
Which electricity generation technology is the most sustainable one? Is it photovoltaic panels on house roofs? Is it off-shore wind parks? Is it nuclear pressure water reactors?! The answer will depend on several aspects.
First of all, it depends on the models used to describe the different generation technologies. Here, I will use models and parameters as supplied by ecoinvent 3.5, allocation at the point of substitution system model (https://www.ecoinvent.org/).
Secondly, it will depend on how we define sustainability. For example, wind energy seems very sustainable from a climate change point of view (low green house gas emissions). However, wind turbines need large amounts of minerals and metals in their construction, making them seem less sustainable from a resource point of view. This is one example of how different indicators will yield different answers. For this study, I will use the 19 midpoint indicators recommended by the International Reference Life Cycle Data System, version 2.0, as implemented in ecoinvent 3.5. I will present results for each indicator. Additionally, I will present normalized, equal-weighted aggregates. The later is just *one* example of how to aggregate multiple indicators to yield one sustainability index. There are infinitely many ways to aggregate different indicators and none of them is preferable over the other. In the end, sustainability measures will always be a subjective construct because different stakeholders give different emphasis to different impact categories.
## Calculation
### ILCD scores
I use the brightway2 package for python for impact calculations (https://brightwaylca.org/). Brightway allows to
- read the database (ecoinvent 3.5 APOS)
- query the database for activities (all electricity production activities in the database)
- calculate the impact score of activities according to different methods (ILCD 2.0)
First, imports.
```python
import brightway2 as bw
import pandas as pd
import xlsxwriter
```
I'll skip the setup here. Please refer to the official brightway guide for details on how to import the ecoinvent 3.5 database etc.: https://nbviewer.jupyter.org/urls/bitbucket.org/cmutel/brightway2/raw/default/notebooks/Getting%20Started%20with%20Brightway2.ipynb
Let's start by getting all electricity production activities in the database.
```python
# setting the directory containing ecoinvent 3.5 APOS database
bw.projects.set_current("ecoinvent-import")
# querying
lActivities = [a for a in bw.Database("ecoinvent 3.5 APOS") if "electricity production" in a["name"]]
len(lActivities)
```
1488
ecoinvent knows 1,488 different activities that produce electricity! Let's go ahead and calculate their impacts.
**Note: Computation of all values may take up to an hour! I reduced the number of activities to the first 5 in the list to make the notebook runable. Feel free to delete the corresponding line to calculate all impacts on your system.**
```python
###### delete line below to calculate ALL impacts ######
lActivities = lActivities[:5]
########################################################
# get ILCD 2.0 midpoint methods
ilcd = [m for m in bw.methods if "ILCD" in str(m) and "2018" in str(m) and "LT" not in str(m)]
# compute all ILCD scores for all activities
ldScores = []
for a in lActivities:
oLCA = bw.LCA({a:1}, ilcd[0])
oLCA.lci()
oLCA.lcia()
dScores = {ilcd[0]:oLCA.score}
for oMethod in ilcd[1:]:
oLCA.switch_method(oMethod)
oLCA.lcia()
dScores[oMethod] = oLCA.score
ldScores.append(dScores)
# convert to dataframe
df = pd.DataFrame(ldScores)
df.head()
```
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
(ILCD 2.0 2018 midpoint, climate change, climate change biogenic)
(ILCD 2.0 2018 midpoint, climate change, climate change fossil)
(ILCD 2.0 2018 midpoint, climate change, climate change land use and land use change)
(ILCD 2.0 2018 midpoint, climate change, climate change total)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater and terrestrial acidification)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater ecotoxicity)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater eutrophication)
(ILCD 2.0 2018 midpoint, ecosystem quality, marine eutrophication)
(ILCD 2.0 2018 midpoint, ecosystem quality, terrestrial eutrophication)
(ILCD 2.0 2018 midpoint, human health, carcinogenic effects)
(ILCD 2.0 2018 midpoint, human health, ionising radiation)
(ILCD 2.0 2018 midpoint, human health, non-carcinogenic effects)
(ILCD 2.0 2018 midpoint, human health, ozone layer depletion)
(ILCD 2.0 2018 midpoint, human health, photochemical ozone creation)
(ILCD 2.0 2018 midpoint, human health, respiratory effects, inorganics)
(ILCD 2.0 2018 midpoint, resources, dissipated water)
(ILCD 2.0 2018 midpoint, resources, fossils)
(ILCD 2.0 2018 midpoint, resources, land use)
(ILCD 2.0 2018 midpoint, resources, minerals and metals)
0
0.000053
0.588143
0.000015
0.588211
0.000645
0.217072
0.000008
0.000180
0.001934
1.142199e-09
0.001369
1.202924e-08
5.099300e-08
0.000718
1.659245e-09
0.044559
9.281378
0.170848
1.040912e-07
1
0.000162
1.053454
0.000078
1.053694
0.010851
0.074229
0.000753
0.001528
0.015533
1.561707e-09
0.004844
4.135861e-08
6.808792e-09
0.004120
1.342487e-08
0.077180
15.350463
2.357585
1.142805e-07
2
0.000026
0.012671
0.000030
0.012726
0.000074
0.025228
0.000009
0.000060
0.000187
5.676048e-10
1.184924
3.716515e-09
5.734082e-08
0.000051
2.871032e-09
0.132819
14.255169
0.084286
5.122173e-08
3
0.000363
0.077957
0.000177
0.078496
0.000570
0.089036
0.000072
0.000101
0.000926
2.191182e-09
0.009163
2.707840e-08
8.781046e-09
0.000315
4.861033e-09
0.112100
1.200793
0.592213
3.434466e-06
4
0.000287
0.061599
0.000140
0.062025
0.000450
0.070353
0.000057
0.000080
0.000732
1.731384e-09
0.007240
2.139614e-08
6.938799e-09
0.000249
3.841037e-09
0.088576
0.948821
0.467936
2.713751e-06
Numbered indices are not very readable. Let's use metadata about the activities as the index instead:
```python
# get names
names = [a["name"].split(",") for a in lActivities]
df_names = pd.DataFrame(names).fillna(" ")
# split names at the commas to make reading and manipulation easier
col_names = [("name_"+str(c), " ", " ") for c in df_names.columns]
df[col_names] = df_names
# add units and locations
df[("unit"," "," ")] = [a["unit"] for a in lActivities]
df[("location"," "," ")] = [a["location"] for a in lActivities]
# set index
meta_data_cols = col_names + [("unit", " ", " "), ("location", " ", " ")]
df.set_index(meta_data_cols, inplace=True)
df.head()
```
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
(ILCD 2.0 2018 midpoint, climate change, climate change biogenic)
(ILCD 2.0 2018 midpoint, climate change, climate change fossil)
(ILCD 2.0 2018 midpoint, climate change, climate change land use and land use change)
(ILCD 2.0 2018 midpoint, climate change, climate change total)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater and terrestrial acidification)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater ecotoxicity)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater eutrophication)
(ILCD 2.0 2018 midpoint, ecosystem quality, marine eutrophication)
(ILCD 2.0 2018 midpoint, ecosystem quality, terrestrial eutrophication)
(ILCD 2.0 2018 midpoint, human health, carcinogenic effects)
(ILCD 2.0 2018 midpoint, human health, ionising radiation)
(ILCD 2.0 2018 midpoint, human health, non-carcinogenic effects)
(ILCD 2.0 2018 midpoint, human health, ozone layer depletion)
(ILCD 2.0 2018 midpoint, human health, photochemical ozone creation)
(ILCD 2.0 2018 midpoint, human health, respiratory effects, inorganics)
(ILCD 2.0 2018 midpoint, resources, dissipated water)
(ILCD 2.0 2018 midpoint, resources, fossils)
(ILCD 2.0 2018 midpoint, resources, land use)
(ILCD 2.0 2018 midpoint, resources, minerals and metals)
(name_0, , )
(name_1, , )
(name_2, , )
(name_3, , )
(name_4, , )
(name_5, , )
(unit, , )
(location, , )
electricity production
natural gas
conventional power plant
kilowatt hour
CN-JS
0.000053
0.588143
0.000015
0.588211
0.000645
0.217072
0.000008
0.000180
0.001934
1.142199e-09
0.001369
1.202924e-08
5.099300e-08
0.000718
1.659245e-09
0.044559
9.281378
0.170848
1.040912e-07
hard coal
kilowatt hour
RoW
0.000162
1.053454
0.000078
1.053694
0.010851
0.074229
0.000753
0.001528
0.015533
1.561707e-09
0.004844
4.135861e-08
6.808792e-09
0.004120
1.342487e-08
0.077180
15.350463
2.357585
1.142805e-07
nuclear
boiling water reactor
kilowatt hour
US-NPCC
0.000026
0.012671
0.000030
0.012726
0.000074
0.025228
0.000009
0.000060
0.000187
5.676048e-10
1.184924
3.716515e-09
5.734082e-08
0.000051
2.871032e-09
0.132819
14.255169
0.084286
5.122173e-08
photovoltaic
3kWp slanted-roof installation
multi-Si
panel
mounted
kilowatt hour
LV
0.000363
0.077957
0.000177
0.078496
0.000570
0.089036
0.000072
0.000101
0.000926
2.191182e-09
0.009163
2.707840e-08
8.781046e-09
0.000315
4.861033e-09
0.112100
1.200793
0.592213
3.434466e-06
IN-JH
0.000287
0.061599
0.000140
0.062025
0.000450
0.070353
0.000057
0.000080
0.000732
1.731384e-09
0.007240
2.139614e-08
6.938799e-09
0.000249
3.841037e-09
0.088576
0.948821
0.467936
2.713751e-06
That's it! These are impact scores for all electricity production activities in ecoinvent 3.5. I can use these to answer indicator-specific questions like: Which electricity generation technology has the lowest total global warming potential (GWP 100)?
```python
df[("ILCD 2.0 2018 midpoint", "climate change", "climate change total")].idxmin()
```
('electricity production',
' nuclear',
' boiling water reactor',
' ',
' ',
' ',
'kilowatt hour',
'US-NPCC')
Or statistical evaluations, like what is the average and standard deviation for the GWP 100 indicator for all electricity generation activities?
```python
df[("ILCD 2.0 2018 midpoint", "climate change", "climate change total")].describe()
```
count 5.000000
mean 0.359031
std 0.453299
min 0.012726
25% 0.062025
50% 0.078496
75% 0.588211
max 1.053694
Name: (ILCD 2.0 2018 midpoint, climate change, climate change total), dtype: float64
### Sustainability Index
Using the produced data we can rank the electricity generation datasets according to individual impact indicators. However, a single indicator does not give enough information to decide if a technology is sustainable or not. To get a bigger picture, I want to aggregate all indicators into one number. As mentioned in the introduction, there are infinitely many ways to do this. The one chosen here is not more right or wrong than any other way. Feel free to change this part according to your needs!
#### Normalization
For each indicator, I choose the minimum and the maximum value over all activities. I define the minimum as 0 and the maximum as 1. Then I use linear interpolation to project all other values into this [0, 1] range.
```python
df_normalized = df.copy()
for indicator in df.columns:
max_value = df[indicator].max()
min_value = df[indicator].min()
df_normalized[indicator] = (df[indicator] - min_value) / (max_value - min_value)
df_normalized.head()
```
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
(ILCD 2.0 2018 midpoint, climate change, climate change biogenic)
(ILCD 2.0 2018 midpoint, climate change, climate change fossil)
(ILCD 2.0 2018 midpoint, climate change, climate change land use and land use change)
(ILCD 2.0 2018 midpoint, climate change, climate change total)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater and terrestrial acidification)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater ecotoxicity)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater eutrophication)
(ILCD 2.0 2018 midpoint, ecosystem quality, marine eutrophication)
(ILCD 2.0 2018 midpoint, ecosystem quality, terrestrial eutrophication)
(ILCD 2.0 2018 midpoint, human health, carcinogenic effects)
(ILCD 2.0 2018 midpoint, human health, ionising radiation)
(ILCD 2.0 2018 midpoint, human health, non-carcinogenic effects)
(ILCD 2.0 2018 midpoint, human health, ozone layer depletion)
(ILCD 2.0 2018 midpoint, human health, photochemical ozone creation)
(ILCD 2.0 2018 midpoint, human health, respiratory effects, inorganics)
(ILCD 2.0 2018 midpoint, resources, dissipated water)
(ILCD 2.0 2018 midpoint, resources, fossils)
(ILCD 2.0 2018 midpoint, resources, land use)
(ILCD 2.0 2018 midpoint, resources, minerals and metals)
(name_0, , )
(name_1, , )
(name_2, , )
(name_3, , )
(name_4, , )
(name_5, , )
(unit, , )
(location, , )
electricity production
natural gas
conventional power plant
kilowatt hour
CN-JS
0.079120
0.552922
0.000000
0.552836
0.052945
1.000000
0.000000
0.081488
0.113848
0.353906
0.000000
0.220836
0.874380
0.163834
0.000000
0.000000
0.578584
0.038077
0.015627
hard coal
kilowatt hour
RoW
0.402550
1.000000
0.388987
1.000000
1.000000
0.255419
1.000000
1.000000
1.000000
0.612291
0.002936
1.000000
0.000000
1.000000
1.000000
0.369608
1.000000
1.000000
0.018639
nuclear
boiling water reactor
kilowatt hour
US-NPCC
0.000000
0.000000
0.087117
0.000000
0.000000
0.000000
0.001290
0.000000
0.000000
0.000000
1.000000
0.000000
1.000000
0.000000
0.102994
1.000000
0.923947
0.000000
0.000000
photovoltaic
3kWp slanted-roof installation
multi-Si
panel
mounted
kilowatt hour
LV
1.000000
0.062728
1.000000
0.063182
0.045976
0.332602
0.086409
0.027954
0.048144
1.000000
0.006586
0.620632
0.039030
0.064746
0.272131
0.765254
0.017496
0.223432
1.000000
IN-JH
0.773791
0.047011
0.770071
0.047359
0.034886
0.235214
0.066049
0.013513
0.035482
0.716799
0.004961
0.469677
0.002573
0.048516
0.185438
0.498725
0.000000
0.168763
0.786975
The result is a table where all impact scores range between zero and one. Zero means lowest impact with reference to the benchmark (i.e. all ecoinvent 3.5 electricity generation activities). One means highest impact with reference to the benchmark.
#### Weighing and aggregation
We still have 19 numbers, each of which describes a small part of the big picture "sustainability". I will now boil them down to one number by simply adding them up. I call the resulting number "sustainability index". Let me stress this again: This index is not more right or wrong than any other one. It is *one* rather arbitrary way to aggregate the individual impact scores.
The lowest possible number for our index is zero. Zero indicates a technology which achieves the *lowest* possible (with reference to the benchmark) impact score in all nineteen impact categories. The highest possible index value is nineteen. It indicates a technology which has the *highest* possible (with reference to the benchmark) impact score in all nineteen impact categories.
Let's see how the ecoinvent activities score in this index:
```python
# sum
df_normalized[("SUM"," "," ")] = df_normalized.sum(axis=1)
# sort ascending
df_normalized.sort_values(by=("SUM"," "," "), ascending=True, inplace=True)
df_normalized.head()
```
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
(ILCD 2.0 2018 midpoint, climate change, climate change biogenic)
(ILCD 2.0 2018 midpoint, climate change, climate change fossil)
(ILCD 2.0 2018 midpoint, climate change, climate change land use and land use change)
(ILCD 2.0 2018 midpoint, climate change, climate change total)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater and terrestrial acidification)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater ecotoxicity)
(ILCD 2.0 2018 midpoint, ecosystem quality, freshwater eutrophication)
(ILCD 2.0 2018 midpoint, ecosystem quality, marine eutrophication)
(ILCD 2.0 2018 midpoint, ecosystem quality, terrestrial eutrophication)
(ILCD 2.0 2018 midpoint, human health, carcinogenic effects)
(ILCD 2.0 2018 midpoint, human health, ionising radiation)
(ILCD 2.0 2018 midpoint, human health, non-carcinogenic effects)
(ILCD 2.0 2018 midpoint, human health, ozone layer depletion)
(ILCD 2.0 2018 midpoint, human health, photochemical ozone creation)
(ILCD 2.0 2018 midpoint, human health, respiratory effects, inorganics)
(ILCD 2.0 2018 midpoint, resources, dissipated water)
(ILCD 2.0 2018 midpoint, resources, fossils)
(ILCD 2.0 2018 midpoint, resources, land use)
(ILCD 2.0 2018 midpoint, resources, minerals and metals)
(SUM, , )
(name_0, , )
(name_1, , )
(name_2, , )
(name_3, , )
(name_4, , )
(name_5, , )
(unit, , )
(location, , )
electricity production
nuclear
boiling water reactor
kilowatt hour
US-NPCC
0.000000
0.000000
0.087117
0.000000
0.000000
0.000000
0.001290
0.000000
0.000000
0.000000
1.000000
0.000000
1.000000
0.000000
0.102994
1.000000
0.923947
0.000000
0.000000
4.115347
natural gas
conventional power plant
kilowatt hour
CN-JS
0.079120
0.552922
0.000000
0.552836
0.052945
1.000000
0.000000
0.081488
0.113848
0.353906
0.000000
0.220836
0.874380
0.163834
0.000000
0.000000
0.578584
0.038077
0.015627
4.678403
photovoltaic
3kWp slanted-roof installation
multi-Si
panel
mounted
kilowatt hour
IN-JH
0.773791
0.047011
0.770071
0.047359
0.034886
0.235214
0.066049
0.013513
0.035482
0.716799
0.004961
0.469677
0.002573
0.048516
0.185438
0.498725
0.000000
0.168763
0.786975
4.905804
LV
1.000000
0.062728
1.000000
0.063182
0.045976
0.332602
0.086409
0.027954
0.048144
1.000000
0.006586
0.620632
0.039030
0.064746
0.272131
0.765254
0.017496
0.223432
1.000000
6.676299
hard coal
kilowatt hour
RoW
0.402550
1.000000
0.388987
1.000000
1.000000
0.255419
1.000000
1.000000
1.000000
0.612291
0.002936
1.000000
0.000000
1.000000
1.000000
0.369608
1.000000
1.000000
0.018639
13.050429
### Export
Let's export the absolute and the normalized results to an excel file.
```python
# transform index into individual columns for easier manipulation
df.reset_index(inplace=True)
df_normalized.reset_index(inplace=True)
# make multi-level headers for better readability
df.columns = pd.MultiIndex.from_tuples(df.columns)
df_normalized.columns = pd.MultiIndex.from_tuples(df_normalized.columns)
# export to xlsx
writer = pd.ExcelWriter("output/ecoinvent_electricity_comparison.xlsx", engine='xlsxwriter')
df.to_excel(writer, sheet_name='abs')
df_normalized.to_excel(writer, sheet_name='norm')
writer.save()
```
### Visualization
Let's draw a heat map showing all normalized impacts for all activities and coloring them according to their magnitude.
```python
import bokeh.io
import bokeh.models
import bokeh.plotting
from bokeh.palettes import Reds9
import re
import numpy as np
# construct list of activity names for display
names = df.loc[:,meta_data_cols].apply(lambda x: re.sub(' +', ' '," ".join(x[1:])).strip(), axis=1).to_list()
# construct list of method names for display
methods = [", ".join(m[1:]) for m in ilcd]
# define tooltips to be displayed
TOOLTIPS = [
("activity", "@act"),
("impact category", "@cat"),
("normalized score", "@score"),
]
# make figure
f = bokeh.plotting.figure(
x_axis_label="ILCD 2.0 midpoint indicator", y_axis_label='ecoinvent activity',
plot_width=900, plot_height=800,
tooltips = TOOLTIPS,
y_range=bokeh.models.FactorRange(*names),
x_range=bokeh.models.FactorRange(*methods)
)
# define plot data
data = {
"score": [df_normalized.loc[i,m] for i in df_normalized.index for m in ilcd],
"act": [names[i] for i in df_normalized.index for m in ilcd],
"cat": [m for i in df_normalized.index for m in methods],
}
# define colormap
mapper = bokeh.models.LinearColorMapper(palette=Reds9, low=1, high=0)
# plot
f.rect(
source=data, x="cat", y="act", width=1, height=1,
fill_color={'field': 'score', 'transform': mapper},
line_color=None,
)
# rotate x-axis ticks
f.xaxis.major_label_orientation = np.pi / 4
# show plot
bokeh.io.output_notebook()
bokeh.io.show(f)
```
Save the figure to disk.
```python
bokeh.io.output_file("output/heatmap.html")
path = bokeh.io.save(f)
```
## Discussion