BenPortner / ecoinvent_electricity_comparison

What is the most sustainable way to generate electricity? A comparison of ecoinvent datasets.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.

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.

# 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.

###### 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()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
(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:

# 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()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
(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)?

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?

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.

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()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
(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:

# sum
df_normalized[("SUM"," "," ")] = df_normalized.sum(axis=1)

# sort ascending
df_normalized.sort_values(by=("SUM"," "," "), ascending=True, inplace=True)

df_normalized.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
(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.

# 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.

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)
<div class="bk-root">
    <a href="https://bokeh.pydata.org" target="_blank" class="bk-logo bk-logo-small bk-logo-notebook"></a>
    <span id="1631">Loading BokehJS ...</span>
</div>

Save the figure to disk.

bokeh.io.output_file("output/heatmap.html")
path = bokeh.io.save(f)

Discussion

About

What is the most sustainable way to generate electricity? A comparison of ecoinvent datasets.

License:GNU General Public License v3.0


Languages

Language:HTML 65.3%Language:Jupyter Notebook 34.7%