├── .bumpversion.cfg ├── .github └── workflows │ ├── CI.yml │ ├── CI_conda.yml │ └── python-publish.yml ├── .gitignore ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── doc └── images │ ├── cef_country_map.py │ ├── cef_country_map.svg │ ├── cefs_scatter.png │ ├── elmada_logo.svg │ ├── elmada_scheme.svg │ ├── elmada_scheme_scribble.svg │ ├── merit_order.svg │ └── scheme_CEF_calculation.svg ├── elmada ├── __init__.py ├── cc_share.py ├── data │ ├── raw │ │ ├── destatis │ │ │ └── energiepreisentwicklung-xlsx-5619001.xls │ │ ├── sandbag │ │ │ └── eua-price.csv │ │ ├── smard │ │ │ ├── Day-ahead_prices_2015.csv │ │ │ ├── Day-ahead_prices_2018.csv │ │ │ └── readme.txt │ │ ├── tranberg │ │ │ └── specific_emission_factors.csv │ │ └── worldbank │ │ │ ├── Data_Extract_From_World_Development_Indicators │ │ │ ├── Data.csv │ │ │ └── Metadata.csv │ │ │ └── readme.txt │ └── safe_cache │ │ ├── 2017_AT_gen_entsoe.parquet │ │ ├── 2017_AT_installedGen_entsoe.parquet │ │ ├── 2017_BE_gen_entsoe.parquet │ │ ├── 2017_BE_installedGen_entsoe.parquet │ │ ├── 2017_BG_gen_entsoe.parquet │ │ ├── 2017_CH_gen_entsoe.parquet │ │ ├── 2017_CZ_gen_entsoe.parquet │ │ ├── 2017_CZ_installedGen_entsoe.parquet │ │ ├── 2017_DE_gen_entsoe.parquet │ │ ├── 2017_DE_installedGen_entsoe.parquet │ │ ├── 2017_DK_gen_entsoe.parquet │ │ ├── 2017_DK_installedGen_entsoe.parquet │ │ ├── 2017_EE_gen_entsoe.parquet │ │ ├── 2017_ES_gen_entsoe.parquet │ │ ├── 2017_ES_installedGen_entsoe.parquet │ │ ├── 2017_FI_gen_entsoe.parquet │ │ ├── 2017_FI_installedGen_entsoe.parquet │ │ ├── 2017_FR_gen_entsoe.parquet │ │ ├── 2017_FR_installedGen_entsoe.parquet │ │ ├── 2017_GB_gen_entsoe.parquet │ │ ├── 2017_GB_installedGen_entsoe.parquet │ │ ├── 2017_GR_gen_entsoe.parquet │ │ ├── 2017_GR_installedGen_entsoe.parquet │ │ ├── 2017_HU_gen_entsoe.parquet │ │ ├── 2017_HU_installedGen_entsoe.parquet │ │ ├── 2017_IE_gen_entsoe.parquet │ │ ├── 2017_IE_installedGen_entsoe.parquet │ │ ├── 2017_IT_gen_entsoe.parquet │ │ ├── 2017_IT_installedGen_entsoe.parquet │ │ ├── 2017_LT_gen_entsoe.parquet │ │ ├── 2017_LT_installedGen_entsoe.parquet │ │ ├── 2017_LV_gen_entsoe.parquet │ │ ├── 2017_ME_gen_entsoe.parquet │ │ ├── 2017_MK_gen_entsoe.parquet │ │ ├── 2017_NL_gen_entsoe.parquet │ │ ├── 2017_NL_installedGen_entsoe.parquet │ │ ├── 2017_NO_gen_entsoe.parquet │ │ ├── 2017_PL_gen_entsoe.parquet │ │ ├── 2017_PL_installedGen_entsoe.parquet │ │ ├── 2017_PT_gen_entsoe.parquet │ │ ├── 2017_PT_installedGen_entsoe.parquet │ │ ├── 2017_RO_gen_entsoe.parquet │ │ ├── 2017_RO_installedGen_entsoe.parquet │ │ ├── 2017_RS_gen_entsoe.parquet │ │ ├── 2017_RS_installedGen_entsoe.parquet │ │ ├── 2017_SE_gen_entsoe.parquet │ │ ├── 2017_SI_gen_entsoe.parquet │ │ ├── 2017_SI_installedGen_entsoe.parquet │ │ ├── 2017_SK_gen_entsoe.parquet │ │ ├── 2018_AT_gen_entsoe.parquet │ │ ├── 2018_AT_installedGen_entsoe.parquet │ │ ├── 2018_BA_gen_entsoe.parquet │ │ ├── 2018_BE_gen_entsoe.parquet │ │ ├── 2018_BE_installedGen_entsoe.parquet │ │ ├── 2018_BG_gen_entsoe.parquet │ │ ├── 2018_CH_gen_entsoe.parquet │ │ ├── 2018_CZ_gen_entsoe.parquet │ │ ├── 2018_CZ_installedGen_entsoe.parquet │ │ ├── 2018_DE_gen_entsoe.parquet │ │ ├── 2018_DE_installedGen_entsoe.parquet │ │ ├── 2018_DK_gen_entsoe.parquet │ │ ├── 2018_DK_installedGen_entsoe.parquet │ │ ├── 2018_ES_gen_entsoe.parquet │ │ ├── 2018_ES_installedGen_entsoe.parquet │ │ ├── 2018_FI_gen_entsoe.parquet │ │ ├── 2018_FI_installedGen_entsoe.parquet │ │ ├── 2018_FR_gen_entsoe.parquet │ │ ├── 2018_FR_installedGen_entsoe.parquet │ │ ├── 2018_GB_gen_entsoe.parquet │ │ ├── 2018_GB_installedGen_entsoe.parquet │ │ ├── 2018_GR_gen_entsoe.parquet │ │ ├── 2018_GR_installedGen_entsoe.parquet │ │ ├── 2018_HU_gen_entsoe.parquet │ │ ├── 2018_HU_installedGen_entsoe.parquet │ │ ├── 2018_IE_gen_entsoe.parquet │ │ ├── 2018_IE_installedGen_entsoe.parquet │ │ ├── 2018_IT_gen_entsoe.parquet │ │ ├── 2018_IT_installedGen_entsoe.parquet │ │ ├── 2018_LT_gen_entsoe.parquet │ │ ├── 2018_LT_installedGen_entsoe.parquet │ │ ├── 2018_LV_gen_entsoe.parquet │ │ ├── 2018_MK_gen_entsoe.parquet │ │ ├── 2018_NL_gen_entsoe.parquet │ │ ├── 2018_NL_installedGen_entsoe.parquet │ │ ├── 2018_NO_gen_entsoe.parquet │ │ ├── 2018_PL_gen_entsoe.parquet │ │ ├── 2018_PL_installedGen_entsoe.parquet │ │ ├── 2018_PT_gen_entsoe.parquet │ │ ├── 2018_PT_installedGen_entsoe.parquet │ │ ├── 2018_RO_gen_entsoe.parquet │ │ ├── 2018_RO_installedGen_entsoe.parquet │ │ ├── 2018_RS_gen_entsoe.parquet │ │ ├── 2018_RS_installedGen_entsoe.parquet │ │ ├── 2018_SE_gen_entsoe.parquet │ │ ├── 2018_SI_gen_entsoe.parquet │ │ ├── 2018_SI_installedGen_entsoe.parquet │ │ ├── 2018_SK_gen_entsoe.parquet │ │ ├── 2019_AT_gen_entsoe.parquet │ │ ├── 2019_AT_installedGen_entsoe.parquet │ │ ├── 2019_BA_gen_entsoe.parquet │ │ ├── 2019_BE_gen_entsoe.parquet │ │ ├── 2019_BE_installedGen_entsoe.parquet │ │ ├── 2019_BG_gen_entsoe.parquet │ │ ├── 2019_CH_gen_entsoe.parquet │ │ ├── 2019_CZ_gen_entsoe.parquet │ │ ├── 2019_CZ_installedGen_entsoe.parquet │ │ ├── 2019_DE_gen_entsoe.parquet │ │ ├── 2019_DE_installedGen_entsoe.parquet │ │ ├── 2019_DK_gen_entsoe.parquet │ │ ├── 2019_DK_installedGen_entsoe.parquet │ │ ├── 2019_EE_gen_entsoe.parquet │ │ ├── 2019_ES_gen_entsoe.parquet │ │ ├── 2019_ES_installedGen_entsoe.parquet │ │ ├── 2019_FI_gen_entsoe.parquet │ │ ├── 2019_FI_installedGen_entsoe.parquet │ │ ├── 2019_FR_gen_entsoe.parquet │ │ ├── 2019_FR_installedGen_entsoe.parquet │ │ ├── 2019_GB_gen_entsoe.parquet │ │ ├── 2019_GB_installedGen_entsoe.parquet │ │ ├── 2019_GR_gen_entsoe.parquet │ │ ├── 2019_GR_installedGen_entsoe.parquet │ │ ├── 2019_HU_gen_entsoe.parquet │ │ ├── 2019_HU_installedGen_entsoe.parquet │ │ ├── 2019_IE_gen_entsoe.parquet │ │ ├── 2019_IE_installedGen_entsoe.parquet │ │ ├── 2019_IT_gen_entsoe.parquet │ │ ├── 2019_IT_installedGen_entsoe.parquet │ │ ├── 2019_LT_gen_entsoe.parquet │ │ ├── 2019_LT_installedGen_entsoe.parquet │ │ ├── 2019_LV_gen_entsoe.parquet │ │ ├── 2019_ME_gen_entsoe.parquet │ │ ├── 2019_MK_gen_entsoe.parquet │ │ ├── 2019_NL_gen_entsoe.parquet │ │ ├── 2019_NL_installedGen_entsoe.parquet │ │ ├── 2019_NO_gen_entsoe.parquet │ │ ├── 2019_PL_gen_entsoe.parquet │ │ ├── 2019_PL_installedGen_entsoe.parquet │ │ ├── 2019_PT_gen_entsoe.parquet │ │ ├── 2019_PT_installedGen_entsoe.parquet │ │ ├── 2019_RO_gen_entsoe.parquet │ │ ├── 2019_RO_installedGen_entsoe.parquet │ │ ├── 2019_RS_gen_entsoe.parquet │ │ ├── 2019_RS_installedGen_entsoe.parquet │ │ ├── 2019_SE_gen_entsoe.parquet │ │ ├── 2019_SI_gen_entsoe.parquet │ │ ├── 2019_SI_installedGen_entsoe.parquet │ │ ├── 2019_SK_gen_entsoe.parquet │ │ ├── 2020_AT_gen_entsoe.parquet │ │ ├── 2020_AT_installedGen_entsoe.parquet │ │ ├── 2020_BE_gen_entsoe.parquet │ │ ├── 2020_BE_installedGen_entsoe.parquet │ │ ├── 2020_CZ_gen_entsoe.parquet │ │ ├── 2020_CZ_installedGen_entsoe.parquet │ │ ├── 2020_DE_gen_entsoe.parquet │ │ ├── 2020_DE_installedGen_entsoe.parquet │ │ ├── 2020_DK_gen_entsoe.parquet │ │ ├── 2020_DK_installedGen_entsoe.parquet │ │ ├── 2020_ES_gen_entsoe.parquet │ │ ├── 2020_ES_installedGen_entsoe.parquet │ │ ├── 2020_FI_gen_entsoe.parquet │ │ ├── 2020_FI_installedGen_entsoe.parquet │ │ ├── 2020_FR_gen_entsoe.parquet │ │ ├── 2020_FR_installedGen_entsoe.parquet │ │ ├── 2020_GB_gen_entsoe.parquet │ │ ├── 2020_GB_installedGen_entsoe.parquet │ │ ├── 2020_GR_gen_entsoe.parquet │ │ ├── 2020_GR_installedGen_entsoe.parquet │ │ ├── 2020_HU_gen_entsoe.parquet │ │ ├── 2020_HU_installedGen_entsoe.parquet │ │ ├── 2020_IE_gen_entsoe.parquet │ │ ├── 2020_IE_installedGen_entsoe.parquet │ │ ├── 2020_IT_gen_entsoe.parquet │ │ ├── 2020_IT_installedGen_entsoe.parquet │ │ ├── 2020_LT_gen_entsoe.parquet │ │ ├── 2020_LT_installedGen_entsoe.parquet │ │ ├── 2020_NL_gen_entsoe.parquet │ │ ├── 2020_NL_installedGen_entsoe.parquet │ │ ├── 2020_PL_gen_entsoe.parquet │ │ ├── 2020_PL_installedGen_entsoe.parquet │ │ ├── 2020_PT_gen_entsoe.parquet │ │ ├── 2020_PT_installedGen_entsoe.parquet │ │ ├── 2020_RO_gen_entsoe.parquet │ │ ├── 2020_RO_installedGen_entsoe.parquet │ │ ├── 2020_RS_gen_entsoe.parquet │ │ ├── 2020_RS_installedGen_entsoe.parquet │ │ ├── 2020_SI_gen_entsoe.parquet │ │ ├── 2020_SI_installedGen_entsoe.parquet │ │ ├── GEO-database.parquet │ │ ├── OPSD_conventional_power_plants_DE.csv │ │ ├── QUANDL_eua_prices.parquet │ │ ├── geo_list_coal.parquet │ │ ├── geo_list_gas.parquet │ │ ├── geo_list_nuclear.parquet │ │ ├── share_ccgt_wiki.parquet │ │ ├── transmission_efficiencies.parquet │ │ └── units_of_geo_list.parquet ├── eu_pwl.py ├── exceptions.py ├── from_entsoe.py ├── from_geo_scraped.py ├── from_geo_via_morph.py ├── from_opsd.py ├── from_other.py ├── from_smard.py ├── helper.py ├── main.py ├── mappings.py ├── mode.py ├── paths.py └── plots.py ├── environment.yml ├── paper ├── codemeta.json ├── paper.bib └── paper.md ├── pyproject.toml ├── pytest.ini ├── setup.py └── tests ├── __init__.py ├── common ├── CEF_hashes.csv ├── GEN_hashes.csv ├── __init__.py ├── averages.py ├── avgs.csv ├── geo_test_list.html └── hasher.py ├── test_cc_share.py ├── test_data.py ├── test_eu_pwl.py ├── test_from_entsoe.py ├── test_from_geo_scraped.py ├── test_from_geo_via_morph.py ├── test_from_opsd.py ├── test_from_other.py ├── test_from_smard.py ├── test_helper.py ├── test_main.py ├── test_mode.py └── test_plots.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.1 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:elmada/__init__.py] 9 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI with pip 2 | 3 | on: push 4 | 5 | jobs: 6 | unit-tests: 7 | name: Python ${{ matrix.python-version }}, ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: ["ubuntu-latest"] #, "windows-latest"] 13 | python-version: ["3.11"] # 3.7, 3.8, 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install -e .[dev] 24 | - name: Run pytest 25 | env: 26 | ENTSOE_API_KEY: ${{ secrets.ENTSOE_API_KEY }} 27 | MORPH_API_KEY: ${{ secrets.MORPH_API_KEY }} 28 | QUANDL_API_KEY: ${{ secrets.QUANDL_API_KEY }} 29 | shell: bash -l {0} 30 | run: | 31 | pytest -v -m="not apikey" . 32 | -------------------------------------------------------------------------------- /.github/workflows/CI_conda.yml: -------------------------------------------------------------------------------- 1 | name: CI with conda 2 | 3 | on: push 4 | 5 | jobs: 6 | unit-tests: 7 | name: Python ${{ matrix.python-version }}, ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: ["ubuntu-latest", "windows-latest"] 13 | python-version: ["3.9", "3.10", "3.11"] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: conda-incubator/setup-miniconda@v2 18 | with: 19 | miniconda-version: 'latest' 20 | environment-file: environment.yml 21 | auto-update-conda: true 22 | python-version: ${{ matrix.python-version }} 23 | auto-activate-base: false 24 | activate-environment: elmada 25 | - name: Run pytest and generate coverage report 26 | env: 27 | ENTSOE_API_KEY: ${{ secrets.ENTSOE_API_KEY }} 28 | MORPH_API_KEY: ${{ secrets.MORPH_API_KEY }} 29 | QUANDL_API_KEY: ${{ secrets.QUANDL_API_KEY }} 30 | shell: bash -l {0} 31 | run: | 32 | pytest -v --cov=elmada --cov-report=xml -m="not apikey" . 33 | - name: Upload coverage report to Codecov 34 | uses: codecov/codecov-action@v1 35 | with: 36 | # token: ${{ secrets.CODECOV_TOKEN }} 37 | fail_ci_if_error: false 38 | verbose: true 39 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install build 25 | - name: Build package 26 | run: python -m build 27 | - name: Publish package on Test-PyPi 28 | uses: pypa/gh-action-pypi-publish@v1.4.2 29 | with: 30 | user: __token__ 31 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 32 | repository_url: https://test.pypi.org/legacy/ 33 | - name: Publish package on PyPi 34 | uses: pypa/gh-action-pypi-publish@v1.4.2 35 | with: 36 | user: __token__ 37 | password: ${{ secrets.PYPI_API_TOKEN }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # symlink to cache directory 2 | cache 3 | 4 | # jupyter notebooks 5 | notebooks/ 6 | 7 | # Pycharm settings 8 | .idea/ 9 | 10 | # Visual Studio Code settings 11 | .vscode/ 12 | *.code 13 | 14 | # Spyder project settings 15 | .spyderproject 16 | .spyproject 17 | 18 | # Byte-compiled / optimized / DLL files 19 | __pycache__/ 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | 26 | # Distribution / packaging 27 | .Python 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | share/python-wheels/ 41 | *.egg-info/ 42 | .installed.cfg 43 | *.egg 44 | MANIFEST 45 | 46 | # PyInstaller 47 | # Usually these files are written by a python script from a template 48 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 49 | *.manifest 50 | *.spec 51 | 52 | # Installer logs 53 | pip-log.txt 54 | pip-delete-this-directory.txt 55 | 56 | # Unit test / coverage reports 57 | htmlcov/ 58 | .tox/ 59 | .nox/ 60 | .coverage 61 | .coverage.* 62 | .cache 63 | nosetests.xml 64 | coverage.xml 65 | *.cover 66 | .hypothesis/ 67 | .pytest_cache/ 68 | 69 | # Translations 70 | *.mo 71 | *.pot 72 | 73 | # Django stuff: 74 | *.log 75 | local_settings.py 76 | db.sqlite3 77 | 78 | # Flask stuff: 79 | instance/ 80 | .webassets-cache 81 | 82 | # Scrapy stuff: 83 | .scrapy 84 | 85 | # Sphinx documentation 86 | docs/_build/ 87 | 88 | # PyBuilder 89 | target/ 90 | 91 | # Jupyter Notebook 92 | .ipynb_checkpoints 93 | 94 | # IPython 95 | profile_default/ 96 | ipython_config.py 97 | 98 | # pyenv 99 | .python-version 100 | 101 | # celery beat schedule file 102 | celerybeat-schedule 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | .dmypy.json 125 | dmypy.json 126 | 127 | # Pyre type checker 128 | .pyre/ 129 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Fleschutz" 5 | given-names: "Markus" 6 | orcid: "https://orcid.org/0000-0002-8516-9635" 7 | - family-names: "Murphy" 8 | given-names: "Michael D." 9 | orcid: "https://orcid.org/0000-0002-4269-2581" 10 | title: "elmada: Dynamic electricity carbon emission factors and prices for Europe" 11 | version: 0.1.0 12 | doi: 10.5281/zenodo.5566694 13 | date-released: 2021-10-13 14 | url: "https://github.com/DrafProject/elmada" 15 | preferred-citation: 16 | type: article 17 | authors: 18 | - family-names: "Fleschutz" 19 | given-names: "Markus" 20 | orcid: "https://orcid.org/0000-0002-8516-9635" 21 | - family-names: "Murphy" 22 | given-names: "Michael D." 23 | orcid: "https://orcid.org/0000-0002-4269-2581" 24 | doi: "10.21105/joss.03625" 25 | journal: "Journal of Open Source Software" 26 | month: 10 27 | start: 3625 28 | title: "elmada: Dynamic electricity carbon emission factors and prices for Europe" 29 | issue: 66 30 | volume: 6 31 | year: 2021 32 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | To guarantee the further evolution of elmada in the long term, we depend on the support of volunteer developers. 4 | 5 | Some of the resources to look at if you're interested in contributing: 6 | * [Join us on Gitter to chat!](https://gitter.im/DrafProject/elmada) 7 | 8 | ## Licensing 9 | 10 | By contributing to elmada, e.g. through opening a pull request or submitting a patch, you represent that your contributions are your own original work and that you have the right to license them, and you agree that your contributions are licensed under the LGPL 3 license. 11 | 12 | ## Submitting bug reports 13 | 14 | [Open an issue on GitHub](https://github.com/DrafProject/elmada/issues/new) to report bugs or other problems. 15 | 16 | ## Submitting changes 17 | 18 | To contribute changes: 19 | 20 | 1. Fork the project on GitHub 21 | 1. Create a feature branch to work on in your fork (``git checkout -b new-fix-or-feature``) 22 | 1. Commit your changes to the feature branch after running black to format your code 23 | 1. Push the branch to GitHub (``git push origin new-fix-or-feature``) 24 | 1. On GitHub, create a new [pull request](https://github.com/DrafProject/elmada/pull/new/master) from the feature branch 25 | 26 | ### Pull requests 27 | 28 | Before submitting a pull request, check whether you have: 29 | 30 | * Added or updated documentation for your changes 31 | * Added tests if you implemented new functionality 32 | 33 | When opening a pull request, please provide a clear summary of your changes! 34 | 35 | ### Commit messages 36 | 37 | Please try to write clear commit messages. One-line messages are fine for small changes, but bigger changes should look like this: 38 | 39 | A brief summary of the commit 40 | 41 | A paragraph or bullet-point list describing what changed and its impact, 42 | covering as many lines as needed. 43 | 44 | ## Testing 45 | 46 | We have existing test coverage for the key functionality of elmada. 47 | 48 | All tests are in the ``elmada/tests`` directory and use [pytest](https://docs.pytest.org/en/latest/). 49 | 50 | Our test coverage is not perfect. An easy way to contribute code is to work on better tests. 51 | 52 | ## Coding conventions 53 | 54 | Start reading our code and you'll get the hang of it. 55 | 56 | We mostly follow the official [Style Guide for Python Code (PEP8)](https://www.python.org/dev/peps/pep-0008/). 57 | 58 | We have chosen to use the uncompromising code formatter, [`black`](https://github.com/psf/black/). 59 | If run from the root directory of this repo, `pyproject.toml` should ensure the line lengths are restricted to 100. 60 | The philosophy behind using black is to have uniform style throughout the project dictated by code. 61 | Since `black` is designed to minimise diffs, and make patches more human readable, this also makes code reviews more efficient. 62 | 63 | ## Attribution 64 | 65 | The layout and content of this document is based on the contribution guidelines of the projects [OpenGovernment](https://github.com/opengovernment/opengovernment/blob/master/CONTRIBUTING.md) and [Calliope](https://github.com/calliope-project/calliope/blob/master/CONTRIBUTING.md). 66 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise BASEd on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | BASEd on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work BASEd on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work BASEd 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work BASEd on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft doc/images 2 | graft elmada 3 | include LICENSE.md README.md 4 | -------------------------------------------------------------------------------- /doc/images/cef_country_map.py: -------------------------------------------------------------------------------- 1 | import elmada 2 | 3 | fig = elmada.plots.cef_country_map(year=2020, method="XEF_EP") 4 | fp = elmada.paths.BASE_DIR.parent / "doc/images/cef_country_map.svg" 5 | fig.write_image(str(fp)) 6 | -------------------------------------------------------------------------------- /doc/images/cefs_scatter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/doc/images/cefs_scatter.png -------------------------------------------------------------------------------- /doc/images/elmada_logo.svg: -------------------------------------------------------------------------------- 1 | elmada_logo_allelmadaCO2 -------------------------------------------------------------------------------- /elmada/__init__.py: -------------------------------------------------------------------------------- 1 | __title__ = "elmada" 2 | __summary__ = "Dynamic electricity carbon emission factors and prices for Europe" 3 | __uri__ = "https://github.com/DrafProject/elmada" 4 | 5 | __version__ = "0.1.1" 6 | 7 | __author__ = "Markus Fleschutz" 8 | __email__ = "mfleschutz@gmail.com" 9 | 10 | __license__ = "LGPLv3" 11 | __copyright__ = f"Copyright (C) 2021 {__author__}" 12 | 13 | # isort: off 14 | 15 | from .mode import get_mode, set_mode 16 | from .helper import set_api_keys, make_symlink_to_cache 17 | 18 | # isort: on 19 | 20 | from . import ( 21 | cc_share, 22 | eu_pwl, 23 | from_entsoe, 24 | from_geo_scraped, 25 | from_geo_via_morph, 26 | from_opsd, 27 | from_other, 28 | from_smard, 29 | helper, 30 | paths, 31 | plots, 32 | ) 33 | from .main import ( 34 | get_el_national_generation, 35 | get_emissions, 36 | get_merit_order, 37 | get_prices, 38 | get_residual_load, 39 | ) 40 | -------------------------------------------------------------------------------- /elmada/data/raw/destatis/energiepreisentwicklung-xlsx-5619001.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/raw/destatis/energiepreisentwicklung-xlsx-5619001.xls -------------------------------------------------------------------------------- /elmada/data/raw/smard/readme.txt: -------------------------------------------------------------------------------- 1 | Note from Markus Fleschutz: 2 | 3 | * data downloaded on 2021-06-10 4 | * from https://www.smard.de/en/downloadcenter/download-market-data#!?downloadAttributes=%7B%22selectedCategory%22:3,%22selectedSubCategory%22:8,%22selectedRegion%22:%22DE%22,%22from%22:1420066800000,%22to%22:1451602799999,%22selectedFileType%22:%22CSV%22%7D 5 | * License: CC BY 4.0 -------------------------------------------------------------------------------- /elmada/data/raw/tranberg/specific_emission_factors.csv: -------------------------------------------------------------------------------- 1 | # Specific emission factors per fuel type and country 2 | # Manually scraped from the supplementary material of Tranberg.2019 (https://doi.org/10.1016/j.esr.2019.100367) 3 | 4 | energy_type,AT,BE,BG,CZ,DE,DK,EE,ES,EU28,FI,FR,GB,GR,HU,IE,IT,LT,LV,ME,NL,NO,PL,PT,RO,RS,SE,SI,SK 5 | high-voltage_mix,119,185,606,729,649,425,1030,331,422,259,38.8,800,976,397,509,463,545,516,422,614,9.38,998,354,392,848,17.4,432,214 6 | wind,0.149,0.156,0.149,0.166,0.165,0.126,0.165,0.122,0.142,0.156,0.133,0.142,0.121,0.114,0.116,0.161,0.110,0.159,0.142,0.133,0.120,0.140,0.117,0.192,0.142,0.141,0.142,0.142 7 | nuclear,10.3,10.1,10.1,10.1,9.37,10.3,10.3,10.2,10.3,10.5,10.6,10.3,10.3,10.1,10.3,10.3,10.3,10.3,10.3,10.1,10.3,10.3,10.3,12.3,10.3,10.3,10.1,10.1 8 | geothermal,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664,0.00664 9 | biomass_cogeneration,50.4,50.4,53.4,50.4,50.4,50.4,53.4,50.4,50.5,50.4,50.4,50.4,50.5,50.4,50.4,50.4,53.4,53.4,50.5,50.4,50.5,50.4,50.4,50.4,50.5,50.4,50.4,50.4 10 | hydropower_pumped_storage,445,372,894,1140,958,611,611,539,611,611,70.8,611,1410,611,845,608,1030,611,611,611,35.2,1410,582,622,1210,611,610,678 11 | hydropower_reservoir,0.445,8.13,8.13,44.8,44.8,8.13,8.13,44.8,8.13,44.8,0.445,8.13,8.13,8.13,8.13,0.445,8.13,8.13,8.13,8.13,0.445,8.13,44.8,8.13,0.445,44.8,8.13,44.8 12 | hydropower_run-of-river,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253,0.0253 13 | coal,984,1120,1180,1180,1160,1160,1300,1210,1160,1080,1090,1140,1300,1400,1070,1150,1170,1160,1160,1030,1160,1160,1140,1140,1340,1170,1190,1160 14 | coal_cogeneration,1220,1210,1250,1710,1160,1050,1210,1210,1210,1100,1210,1210,1560,1230,1210,1260,1210,1210,1210,996,1490,1160,1210,1230,1230,1370,1240,1530 15 | gas,613,471,745,696,533,513,513,491,513,837,587,521,681,749,461,531,513,513,513,464,406,513,440,615,513,513,1090,694 16 | gas_cogeneration,527,501,932,835,347,452,419,167,471,528,668,471,167,645,167,493,625,596,471,449,520,540,471,682,805,551,432,649 17 | oil,1150,911,1660,1060,875,1240,1180,864,1010,446,951,1320,990,1130,917,1060,1010,1010,1010,1010,1010,1010,832,997,1010,852,1380,958 18 | oil_cogeneration,957,852,962,1520,678,963,871,933,933,949,768,933,1070,871,933,902,1530,933,933,1070,933,878,609,1250,933,835,871,1390 19 | low-voltage-mix,319,236,669,786,643,390,918,362,440,241,51.2,802,961,484,585,430,725,777,735,607,27.5,1030,396,468,937,39.3,444,455 20 | solar,0.00580,0.00502,0.00423,0.00642,0.00448,0.00349,0.00349,0.00234,0.00349,0.00349,0.00370,0.00349,0.00415,0.00349,0.00349,0.00166,0.00591,0.00349,0.00349,0.00591,0.00349,0.00349,0.00185,0.00478,0.00445,0.00565,0.00453,0.00493 -------------------------------------------------------------------------------- /elmada/data/raw/worldbank/Data_Extract_From_World_Development_Indicators/Metadata.csv: -------------------------------------------------------------------------------- 1 | Code,License Type,Indicator Name,Long definition,Source,Topic,Periodicity,Aggregation method,Statistical concept and methodology,Development relevance,Limitations and exceptions,General comments,License URL 2 | EG.ELC.LOSS.ZS,Use and distribution of these data are subject to IEA terms and conditions.,Electric power transmission and distribution losses (% of output),"Electric power transmission and distribution losses include losses in transmission between sources of supply and points of distribution and in the distribution to consumers, including pilferage.","IEA Statistics © OECD/IEA 2018 (http://www.iea.org/stats/index.asp), subject to https://www.iea.org/t&c/termsandconditions/",Environment: Energy production & use,Annual,Weighted average,Data on electric power production and consumption are collected from national energy agencies by the International Energy Agency (IEA) and adjusted by the IEA to meet international definitions. Electric power transmission and distribution losses percentage of output is the share of electric power transmission and distribution losses to electricity production which is the total number of GWh generated by power plants separated into electricity plants and CHP plants.,"An economy's production and consumption of electricity are basic indicators of its size and level of development. Although a few countries export electric power, most production is for domestic consumption. Expanding the supply of electricity to meet the growing demand of increasingly urbanized and industrialized economies without incurring unacceptable social, economic, and environmental costs is one of the great challenges facing developing countries. 3 | 4 | Modern societies are becoming increasing dependent on reliable and secure electricity supplies to underpin economic growth and community prosperity. This reliance is set to grow as more efficient and less carbon intensive forms of power are developed and deployed to help decarbonize economies. Maintaining reliable and secure electricity services while seeking to rapidly decarbonize power systems is a key challenge for countries throughout the world. 5 | 6 | In developing economies growth in energy use is closely related to growth in the modern sectors - industry, motorized transport, and urban areas - but energy use also reflects climatic, geographic, and economic factors (such as the relative price of energy). Energy use has been growing rapidly in low- and middle-income economies, but high-income economies still use almost five times as much energy on a per capita basis. 7 | 8 | Governments in many countries are increasingly aware of the urgent need to make better use of the world's energy resources. Improved energy efficiency is often the most economic and readily available means of improving energy security and reducing greenhouse gas emissions.","Electricity consumption is equivalent to production less power plants' own use and transmission, distribution, and transformation losses less exports plus imports. It includes consumption by auxiliary stations, losses in transformers that are considered integral parts of those stations, and electricity produced by pumping installations. Where data are available, it covers electricity generated by primary sources of energy - coal, oil, gas, nuclear, hydro, geothermal, wind, tide and wave, and combustible renewables. Neither production nor consumption data capture the reliability of supplies, including breakdowns, load factors, and frequency of outages.",Restricted use: Please contact the International Energy Agency for third-party use of these data.,http://www.iea.org/t&c/termsandconditions 9 | -------------------------------------------------------------------------------- /elmada/data/raw/worldbank/readme.txt: -------------------------------------------------------------------------------- 1 | Note from Markus Fleschutz: 2 | data downloaded on 2020-05-19 from https://databank.worldbank.org/reports.aspx?source=2&series=EG.ELC.LOSS.ZS by filtering out European countries. -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_AT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_AT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_AT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_AT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_BE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_BE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_BE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_BE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_BG_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_BG_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_CH_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_CH_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_CZ_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_CZ_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_CZ_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_CZ_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_DE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_DE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_DE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_DE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_DK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_DK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_DK_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_DK_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_EE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_EE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_ES_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_ES_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_ES_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_ES_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_FI_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_FI_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_FI_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_FI_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_FR_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_FR_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_FR_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_FR_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_GB_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_GB_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_GB_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_GB_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_GR_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_GR_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_GR_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_GR_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_HU_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_HU_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_HU_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_HU_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_IE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_IE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_IE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_IE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_IT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_IT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_IT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_IT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_LT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_LT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_LT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_LT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_LV_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_LV_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_ME_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_ME_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_MK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_MK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_NL_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_NL_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_NL_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_NL_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_NO_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_NO_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_PL_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_PL_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_PL_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_PL_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_PT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_PT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_PT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_PT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_RO_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_RO_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_RO_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_RO_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_RS_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_RS_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_RS_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_RS_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_SE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_SE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_SI_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_SI_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_SI_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_SI_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2017_SK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2017_SK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_AT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_AT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_AT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_AT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_BA_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_BA_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_BE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_BE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_BE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_BE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_BG_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_BG_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_CH_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_CH_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_CZ_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_CZ_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_CZ_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_CZ_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_DE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_DE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_DE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_DE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_DK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_DK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_DK_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_DK_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_ES_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_ES_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_ES_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_ES_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_FI_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_FI_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_FI_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_FI_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_FR_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_FR_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_FR_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_FR_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_GB_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_GB_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_GB_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_GB_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_GR_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_GR_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_GR_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_GR_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_HU_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_HU_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_HU_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_HU_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_IE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_IE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_IE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_IE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_IT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_IT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_IT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_IT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_LT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_LT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_LT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_LT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_LV_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_LV_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_MK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_MK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_NL_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_NL_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_NL_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_NL_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_NO_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_NO_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_PL_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_PL_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_PL_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_PL_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_PT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_PT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_PT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_PT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_RO_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_RO_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_RO_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_RO_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_RS_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_RS_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_RS_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_RS_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_SE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_SE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_SI_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_SI_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_SI_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_SI_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2018_SK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2018_SK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_AT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_AT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_AT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_AT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_BA_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_BA_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_BE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_BE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_BE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_BE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_BG_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_BG_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_CH_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_CH_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_CZ_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_CZ_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_CZ_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_CZ_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_DE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_DE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_DE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_DE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_DK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_DK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_DK_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_DK_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_EE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_EE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_ES_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_ES_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_ES_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_ES_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_FI_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_FI_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_FI_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_FI_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_FR_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_FR_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_FR_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_FR_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_GB_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_GB_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_GB_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_GB_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_GR_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_GR_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_GR_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_GR_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_HU_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_HU_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_HU_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_HU_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_IE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_IE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_IE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_IE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_IT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_IT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_IT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_IT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_LT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_LT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_LT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_LT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_LV_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_LV_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_ME_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_ME_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_MK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_MK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_NL_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_NL_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_NL_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_NL_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_NO_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_NO_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_PL_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_PL_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_PL_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_PL_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_PT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_PT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_PT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_PT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_RO_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_RO_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_RO_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_RO_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_RS_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_RS_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_RS_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_RS_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_SE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_SE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_SI_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_SI_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_SI_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_SI_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2019_SK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2019_SK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_AT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_AT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_AT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_AT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_BE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_BE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_BE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_BE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_CZ_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_CZ_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_CZ_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_CZ_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_DE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_DE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_DE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_DE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_DK_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_DK_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_DK_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_DK_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_ES_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_ES_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_ES_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_ES_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_FI_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_FI_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_FI_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_FI_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_FR_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_FR_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_FR_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_FR_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_GB_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_GB_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_GB_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_GB_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_GR_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_GR_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_GR_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_GR_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_HU_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_HU_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_HU_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_HU_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_IE_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_IE_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_IE_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_IE_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_IT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_IT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_IT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_IT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_LT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_LT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_LT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_LT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_NL_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_NL_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_NL_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_NL_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_PL_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_PL_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_PL_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_PL_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_PT_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_PT_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_PT_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_PT_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_RO_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_RO_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_RO_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_RO_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_RS_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_RS_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_RS_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_RS_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_SI_gen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_SI_gen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/2020_SI_installedGen_entsoe.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/2020_SI_installedGen_entsoe.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/GEO-database.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/GEO-database.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/QUANDL_eua_prices.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/QUANDL_eua_prices.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/geo_list_coal.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/geo_list_coal.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/geo_list_gas.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/geo_list_gas.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/geo_list_nuclear.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/geo_list_nuclear.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/share_ccgt_wiki.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/share_ccgt_wiki.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/transmission_efficiencies.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/transmission_efficiencies.parquet -------------------------------------------------------------------------------- /elmada/data/safe_cache/units_of_geo_list.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/elmada/data/safe_cache/units_of_geo_list.parquet -------------------------------------------------------------------------------- /elmada/eu_pwl.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from functools import lru_cache 3 | from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union 4 | 5 | import pandas as pd 6 | 7 | from elmada import cc_share, from_entsoe, from_geo_scraped, from_opsd, from_other 8 | from elmada import mappings as mp 9 | 10 | logger = logging.getLogger(__name__) 11 | logger.setLevel(level=logging.WARN) 12 | 13 | 14 | def prep_prices(year=2019, freq="60min", country="DE", **kwargs) -> pd.Series: 15 | return prep_CEFs(year=year, freq=freq, country=country, **kwargs)["marginal_cost"] 16 | 17 | 18 | def prep_CEFs( 19 | year: int = 2019, 20 | freq: str = "60min", 21 | country: str = "DE", 22 | validation_mode: bool = False, 23 | mo_P: Optional[pd.DataFrame] = None, 24 | **mo_kwargs, 25 | ) -> pd.DataFrame: 26 | """Prepares XEFs for European countries with piece-wise-linear approximation method""" 27 | 28 | if mo_P is None: 29 | mo_P = merit_order(year=year, country=country, validation_mode=validation_mode, **mo_kwargs) 30 | return from_opsd.get_CEFs_from_merit_order(mo_P=mo_P, year=year, freq=freq, country=country) 31 | 32 | 33 | def merit_order( 34 | year: int = 2019, 35 | country: str = "DE", 36 | approx_method: str = "regr", 37 | validation_mode: bool = False, 38 | overwrite_carbon_tax: Optional[float] = None, 39 | pp_size_method: str = "from_geo_scraped", 40 | ) -> pd.DataFrame: 41 | """Return a merit-order list. Virtual power plants are constructed through discretization.""" 42 | 43 | mo_f = merit_order_per_fuel( 44 | year=year, country=country, approx_method=approx_method, validation_mode=validation_mode 45 | ) 46 | 47 | df = discretize_merit_order_per_fuel(mo_f, pp_size_method=pp_size_method, country=country) 48 | 49 | df["x_k"] = df["fuel_draf"].map(from_other.get_fuel_prices(year=year, country=country)) 50 | 51 | DATA_QUASCH = from_other.get_emissions_per_fuel_quaschning() 52 | marginal_emissions_of_gen = df["fuel_draf"].map(DATA_QUASCH) / df["used_eff"] 53 | df["fuel_cost"] = df["x_k"] / df["used_eff"] 54 | 55 | carbon_price = ( 56 | from_other.get_ETS_price(year) if overwrite_carbon_tax is None else overwrite_carbon_tax 57 | ) 58 | df["GHG_cost"] = marginal_emissions_of_gen * carbon_price 59 | df["marginal_emissions"] = marginal_emissions_of_gen 60 | df["marginal_cost"] = df["fuel_cost"] + df["GHG_cost"] 61 | 62 | df = df.sort_values("marginal_cost").reset_index(drop=True) 63 | df["cumsum_capa"] = df["capa"].cumsum() 64 | df = df.dropna() 65 | return df.copy() 66 | 67 | 68 | def merit_order_per_fuel( 69 | year: int = 2019, 70 | country: str = "DE", 71 | approx_method: str = "regr", 72 | validation_mode: bool = False, 73 | ) -> pd.DataFrame: 74 | """Returns a non-discretized merit order on fuel-type level.""" 75 | eff_min, eff_max, __, __ = approximate_min_max_values(method=approx_method) 76 | 77 | source = "power_plant_list" if validation_mode else "entsoe" 78 | 79 | df = pd.DataFrame( 80 | dict( 81 | eff_min=eff_min, 82 | eff_max=eff_max, 83 | fuel_price=from_other.get_fuel_prices(year=year, country=country), 84 | emissions_for_gen=from_other.get_emissions_per_fuel_quaschning(), # in t_CO2eq / MWh_el 85 | capa=prep_installed_generation_capacity(year=year, country=country, source=source), 86 | ) 87 | ) 88 | 89 | logger.info(f"Available fuels for {year}, {country}: {list(df.index)}") 90 | 91 | df["eff_mean"] = (df["eff_min"] + df["eff_max"]) / 2 92 | df["fuel_cost"] = df["fuel_price"] / df["eff_mean"] 93 | df["GHG_cost"] = df["emissions_for_gen"] / df["eff_mean"] 94 | df["marginal_cost"] = df["fuel_cost"] + df["GHG_cost"] 95 | df = df.sort_values("marginal_cost") 96 | df["cumsum_capa"] = df["capa"].cumsum() 97 | return df.copy() 98 | 99 | 100 | @lru_cache(maxsize=2) 101 | def approximate_min_max_values(method="regr") -> Tuple[Dict, Dict, Dict, Dict]: 102 | """`method` must be either 'interp' for interpolation or 'regr' for linear regression.""" 103 | mo = get_clean_merit_order_for_min_max_approximation(sort_by_fuel=True) 104 | 105 | if method == "interp": 106 | grouper = mo.groupby("fuel_draf")["used_eff"] 107 | eff_min = grouper.min().to_dict() 108 | eff_max = grouper.max().to_dict() 109 | 110 | grouper = mo.groupby("fuel_draf")["cumsum_capa"] 111 | cumsum_capa_LHS = grouper.min().to_dict() 112 | cumsum_capa_RHS = grouper.max().to_dict() 113 | 114 | elif method == "regr": 115 | from scipy import stats 116 | 117 | eff_min = {} 118 | eff_max = {} 119 | cumsum_capa_LHS = {} 120 | cumsum_capa_RHS = {} 121 | 122 | for fuel in mo["fuel_draf"].unique(): 123 | mo_ = mo[mo["fuel_draf"] == fuel] 124 | slope, intercept, r_value, p_value, std_err = stats.linregress( 125 | mo_["cumsum_capa"], mo_["used_eff"] 126 | ) 127 | 128 | cumsum_capa_LHS[fuel] = mo_["cumsum_capa"].min() 129 | cumsum_capa_RHS[fuel] = mo_["cumsum_capa"].max() 130 | 131 | eff_min[fuel] = intercept + slope * cumsum_capa_RHS[fuel] 132 | eff_max[fuel] = intercept + slope * cumsum_capa_LHS[fuel] 133 | 134 | else: 135 | raise ValueError(f"Method must be either 'interp' or 'regr'. Given:'{method}'") 136 | 137 | return eff_min, eff_max, cumsum_capa_RHS, cumsum_capa_LHS 138 | 139 | 140 | def get_clean_merit_order_for_min_max_approximation(sort_by_fuel=False) -> pd.DataFrame: 141 | """Return a clean German merit order of the generation technologies.""" 142 | 143 | # The opsd-file is used here here! 144 | mo = from_opsd.merit_order(year=2019) 145 | 146 | if sort_by_fuel: 147 | groups = [] 148 | for fuel, group in mo.groupby("fuel_draf", sort=False): 149 | groups.append(group.sort_values(by="used_eff", ascending=False)) 150 | 151 | mo = pd.concat(groups, axis=0, ignore_index=True) 152 | 153 | mo["cumsum_capa"] = mo["capa"].cumsum() 154 | 155 | return mo 156 | 157 | 158 | @lru_cache(maxsize=3) 159 | def prep_installed_generation_capacity(year=2019, country="DE", source="entsoe") -> pd.Series: 160 | if source == "power_plant_list": 161 | ser = ( 162 | from_opsd.merit_order(year=year)[["fuel_draf", "capa"]] 163 | .groupby("fuel_draf") 164 | .sum()["capa"] 165 | ) 166 | return ser 167 | 168 | elif source == "entsoe": 169 | df = from_entsoe.load_installed_generation_capacity(year=year, country=country) 170 | df = from_entsoe.aggregate_to_standard_techs(df) 171 | ser = df.T.iloc[:, 0] 172 | 173 | ser = apply_share_cc_assumption(ser, country=country) 174 | 175 | logger.info(f"Available fuels for {year}, {country}: {list(ser.index)}") 176 | 177 | ser = pd.Series(index=mp.PWL_FUELS, data=ser) 178 | ser.fillna(0, inplace=True) 179 | return ser 180 | 181 | # elif source == "opsd": 182 | # ser = from_opsd.get_installed_generation_capacity(year=year, country=country) 183 | # ser = pd.Series(index=mp.PWL_FUELS, data=ser) 184 | # ser.fillna(0, inplace=True) 185 | # ser = apply_share_cc_assumption(ser, country=country) 186 | # return ser 187 | 188 | else: 189 | raise ValueError("Source must be either 'entsoe' or 'power_plant_list'") 190 | 191 | 192 | def apply_share_cc_assumption(ser, country: str) -> pd.Series: 193 | if "gas" in ser: 194 | share_cc = get_share_cc(country=country) 195 | ser["gas_cc"] = ser["gas"] * share_cc 196 | ser["gas"] = ser["gas"] * (1 - share_cc) 197 | 198 | return ser 199 | 200 | 201 | def get_share_cc(country: str) -> float: 202 | try: 203 | return cc_share.get_ccgt_shares_from_cascade().loc[country, "selected"] 204 | except KeyError: 205 | logger.warning("Unable to apply cascade method, German values returned") 206 | return cc_share.get_ccgt_DE() 207 | 208 | 209 | def discretize_merit_order_per_fuel( 210 | mo_f: pd.DataFrame, country: str, pp_size_method: str = "from_geo_scraped" 211 | ) -> pd.DataFrame: 212 | concat_list = [] 213 | # if country=="DE": 214 | # pp_size_method = "from_Germany" 215 | for fuel, row in mo_f.iterrows(): 216 | pp_size = get_pp_size(pp_size_method=pp_size_method, country=country, fuel=fuel) 217 | logger.info(f"pp_size_method={pp_size_method}") 218 | number_of_powerplants = int(row["capa"] // pp_size) 219 | capa_of_last_powerplant = row["capa"] % pp_size 220 | df = pd.DataFrame({"capa": [pp_size] * number_of_powerplants + [capa_of_last_powerplant]}) 221 | cumsum_capa_in_f = df["capa"].cumsum() 222 | df["used_eff"] = row["eff_max"] - (cumsum_capa_in_f / row["capa"]) * ( 223 | row["eff_max"] - row["eff_min"] 224 | ) 225 | df["fuel_draf"] = fuel 226 | concat_list.append(df) 227 | df = pd.concat(concat_list, ignore_index=True) 228 | return df 229 | 230 | 231 | def get_pp_size(pp_size_method: str, country: str, fuel: str) -> int: 232 | try: 233 | if pp_size_method == "from_geo_scraped": 234 | return from_geo_scraped.get_pp_sizes_for_pwl().loc[country, fuel] 235 | # elif pp_size_method == "from_Germany": 236 | # return get_pp_sizes_from_germany()[fuel] 237 | # elif pp_size_method == "from_geo": 238 | # return from_geo_via_morph.get_pp_sizes().loc[country, fuel] 239 | # elif pp_size_method == "from_ccgt": 240 | # return cc_share.get_pp_sizes().loc[country, fuel] 241 | else: 242 | raise ValueError(f"Invalid pp_size_method given: {pp_size_method}") 243 | except KeyError: 244 | logger.warning("Unable to apply pp-size, German values returned") 245 | return get_pp_sizes_from_germany()[fuel] 246 | 247 | 248 | @lru_cache(maxsize=1) 249 | def get_pp_sizes_from_germany() -> pd.Series: 250 | return from_opsd.merit_order().groupby("fuel_draf").capa.mean() 251 | -------------------------------------------------------------------------------- /elmada/exceptions.py: -------------------------------------------------------------------------------- 1 | class NoDataError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /elmada/from_geo_scraped.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import requests 3 | from bs4 import BeautifulSoup 4 | 5 | from elmada import from_geo_via_morph 6 | from elmada import helper as hp 7 | from elmada import mappings as mp 8 | from elmada import paths 9 | 10 | 11 | def get_pp_sizes_for_pwl() -> pd.DataFrame: 12 | df = get_pp_sizes() 13 | df = df.fillna(df.mean()).astype(int) 14 | return df 15 | 16 | 17 | def get_pp_sizes() -> pd.DataFrame: 18 | geo = get_units_of_geo_list() 19 | 20 | grouper = geo.groupby(["cy", "fuel"])["capa"] 21 | pp_sizes = grouper.mean().unstack() 22 | count = grouper.count().unstack() 23 | pp_sizes = pp_sizes.where(count >= 2) 24 | return pp_sizes[mp.PWL_FUELS] 25 | 26 | 27 | def get_units_of_geo_list(cache: bool = True) -> pd.DataFrame: 28 | fp = paths.mode_dependent_cache_dir() / "units_of_geo_list.parquet" 29 | 30 | if fp.exists() and cache: 31 | df = hp.read(fp) 32 | 33 | else: 34 | df = _query_geo_power_plant_data() 35 | if cache: 36 | hp.write(df, fp) 37 | 38 | df = df.rename( 39 | columns={ 40 | "Capacity (MWe)": "capa", 41 | "Unit Efficiency (%)": "eff", 42 | "Date Commissioned (yyyy-mm-dd)": "commissioned", 43 | "Unit #": "unit_no", 44 | }, 45 | ) 46 | 47 | interesting_cols = ["cy", "fuel", "geoid", "capa", "eff", "commissioned", "unit_no"] 48 | df = df.loc[df.capa != "", interesting_cols] 49 | 50 | df["capa"] = df["capa"].astype(float).astype(int) 51 | return df.reset_index(drop=True) 52 | 53 | 54 | def _query_geo_power_plant_data(): 55 | geo = from_geo_via_morph.get_geo_list() 56 | max_count = len(geo) 57 | print(f"Download {max_count} items:", end="") 58 | concat_list = [] 59 | for _, ser in geo.iterrows(): 60 | print(".", end="") 61 | geo_id = ser["id"] 62 | print(geo_id, end=", ") 63 | df = get_df_from_geo_id(geo_id) 64 | df["cy"] = ser["cy"] 65 | df["fuel"] = ser["fuel"] 66 | df["geoid"] = geo_id 67 | concat_list.append(df) 68 | df = pd.concat(concat_list) 69 | return df 70 | 71 | 72 | def get_df_from_geo_id(geo_id: int) -> pd.DataFrame: 73 | url = f"http://globalenergyobservatory.org/geoid/{geo_id}" 74 | page = requests.get(url).text 75 | soup = BeautifulSoup(page, "lxml") 76 | selector = soup.find("div", {"id": "UnitDescription_Block"}) 77 | 78 | table = selector.find("table") 79 | 80 | headers = [cell.get_text().strip() for cell in table.findAll("th")] 81 | ncols = len(headers) 82 | rows = table.findAll("tr") 83 | 84 | df = pd.DataFrame(columns=headers) 85 | for i, row in enumerate(rows): 86 | cells = row.find_all("td") 87 | if len(cells) == ncols: 88 | df.loc[i, :] = [cell.find("input").get("value") for cell in cells] 89 | return df 90 | -------------------------------------------------------------------------------- /elmada/from_geo_via_morph.py: -------------------------------------------------------------------------------- 1 | """Handles power plant list from Global Energy Observatory via morph.""" 2 | 3 | import collections 4 | import logging 5 | import sqlite3 6 | from functools import lru_cache 7 | from pathlib import Path 8 | from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union 9 | 10 | import numpy as np 11 | import pandas as pd 12 | import requests 13 | 14 | from elmada import helper as hp 15 | from elmada import mappings as mp 16 | from elmada import paths 17 | 18 | logger = logging.getLogger(__name__) 19 | logger.setLevel(logging.WARN) 20 | 21 | DB_FILE_NAME = Path("GEO-database.suffix") 22 | 23 | CC_WEIGHTING = { 24 | "Thermal and CCGT": 1, 25 | "Power and Heat Combined Cycle Gas Turbine": 1, 26 | "Combined Cycle Gas Engine (CCGE)": 1, 27 | "Combined Cycle Gas Turbine": 1, 28 | "OCGT and CCGT": 0.49, 29 | "Sub-critical Thermal": 0, 30 | "Power and Heat Open Cycle Gas Turbine": 0, 31 | "Open Cycle Gas Turbine": 0, 32 | "Heat and Power Steam Turbine": 0, 33 | "Super-critical Thermal": 0, 34 | "Gas Engines": 0, 35 | None: 0, 36 | } 37 | 38 | COLS = { 39 | "Name": "name", 40 | "Type": "fuel", 41 | "Country": "country", 42 | "Type_of_Plant_rng1": "plant_type", 43 | "Type_of_Fuel_rng1_Primary": "primary_fuel", 44 | "Design_Capacity_MWe_nbr": "capa", 45 | "GEO_Assigned_Identification_Number": "id", 46 | "Status_of_Plant_itf": "status", 47 | } 48 | 49 | NON_OPERATIONAL_STATUSES = [ 50 | "Built and In Test Stage", 51 | "Decommissioned", 52 | "Demolished", 53 | "Design and Planning Stage", 54 | "Final Bid and Approval Stage", 55 | "Mothballed Full", 56 | "Mothballed Partial", 57 | "Shutdown" "Under Construction", 58 | ] 59 | 60 | 61 | def get_ccgt_shares() -> pd.DataFrame: 62 | geo = get_geo_list() 63 | valid_countries = get_valid_countries() 64 | df = pd.DataFrame(index=valid_countries.keys()) 65 | 66 | for cy_short, cy_long in valid_countries.items(): 67 | is_gas = geo["fuel"].isin(["gas", "gas_cc"]) 68 | is_country = geo["country"] == cy_long 69 | geo_sub = geo[is_gas & is_country] 70 | df.loc[cy_short, "cy_long"] = cy_long 71 | df.loc[cy_short, "cc_capa"] = geo_sub["capa"] @ geo_sub["cc_weight"] 72 | df.loc[cy_short, "total_capa"] = geo_sub["capa"].sum() 73 | 74 | df["share_cc"] = df["cc_capa"] / df["total_capa"] 75 | return df 76 | 77 | 78 | def get_valid_countries(): 79 | df = get_geo_list() 80 | avail = set(df["country"].unique()) 81 | d = {short: long for short, long in mp.COUNTRIES_FOR_ANALYSIS.items() if long in avail} 82 | return collections.OrderedDict(sorted(d.items())) 83 | 84 | 85 | @lru_cache(maxsize=1) 86 | def get_geo_list(): 87 | df = get_geo_full_list() 88 | df = filter_relevant_plants(df) 89 | df = filter_operating_plants(df) 90 | df = set_lignite_plants(df) 91 | df = set_gasCC_plants(df) 92 | 93 | df["fuel"] = df["fuel"].str.lower() 94 | 95 | reverse_ana = {v: k for k, v in mp.COUNTRIES_FOR_ANALYSIS.items()} 96 | df.insert(loc=3, column="cy", value=df["country"].map(reverse_ana)) 97 | 98 | return df[df.capa.notnull()] 99 | 100 | 101 | def set_gasCC_plants(df: pd.DataFrame) -> pd.DataFrame: 102 | is_gas = df["fuel"] == "Gas" 103 | 104 | df["cc_weight"] = np.nan 105 | df.loc[is_gas, "cc_weight"] = df.loc[is_gas, "plant_type"].map(CC_WEIGHTING) 106 | # is_cc = df["cc_weight"][is_gas].apply(round).astype("bool") 107 | is_cc = df["cc_weight"] >= 0.5 108 | df.loc[is_gas & is_cc, "fuel"] = "Gas_cc" 109 | return df 110 | 111 | 112 | def set_lignite_plants(df: pd.DataFrame) -> pd.DataFrame: 113 | is_coal = df["fuel"] == "Coal" 114 | is_lignite = df["primary_fuel"].str.contains("lignite", case=False) 115 | is_peat = df["primary_fuel"].str.contains("peat", case=False) 116 | df.loc[is_coal & (is_lignite ^ is_peat), "fuel"] = "Lignite" 117 | return df 118 | 119 | 120 | def filter_operating_plants(df: pd.DataFrame) -> pd.DataFrame: 121 | no_status = df.status.isna() 122 | contains_shutdown = df.name.str.contains("Shutdown") 123 | df.loc[no_status & contains_shutdown, "status"] = "Shutdown" 124 | is_operating = ~df["status"].isin(NON_OPERATIONAL_STATUSES) 125 | return df[is_operating] 126 | 127 | 128 | def filter_relevant_plants(df: pd.DataFrame) -> pd.DataFrame: 129 | is_ana = df["Country"].isin(mp.COUNTRIES_FOR_ANALYSIS.values()) 130 | is_conv = df["Type"].isin(["Gas", "Coal", "Nuclear", "Oil"]) 131 | return df.loc[is_ana & is_conv, COLS.keys()].rename(columns=COLS) 132 | 133 | 134 | def get_geo_full_list(cache: bool = True) -> pd.DataFrame: 135 | 136 | fp = paths.mode_dependent_cache_dir() / DB_FILE_NAME.with_suffix(".parquet") 137 | 138 | if cache and fp.exists(): 139 | df = hp.read(fp) 140 | 141 | else: 142 | fp_db = paths.CACHE_DIR / DB_FILE_NAME.with_suffix(".db") 143 | 144 | if not fp_db.exists(): 145 | download_database(fp=fp_db) 146 | 147 | conn = sqlite3.connect(fp_db) 148 | df = pd.read_sql_query("SELECT * FROM powerplants", conn) 149 | conn.close() 150 | if cache: 151 | hp.write(df, fp) 152 | 153 | return df 154 | 155 | 156 | def download_database(fp: Path) -> None: 157 | url_base = "https://morph.io/coroa/global_energy_observatory_power_plants/" 158 | morph_api_key = hp.get_api_key("morph") 159 | url_ending = f"data.sqlite?key={morph_api_key}" 160 | url = url_base + url_ending 161 | 162 | response = requests.get(url) 163 | logger.info(f"{fp.name} downloaded from GEO via Morph.") 164 | fp.write_bytes(response.content) 165 | -------------------------------------------------------------------------------- /elmada/from_opsd.py: -------------------------------------------------------------------------------- 1 | """Reads power plant list from Open Power System Data (OPSD).""" 2 | 3 | import logging 4 | from pathlib import Path 5 | from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union 6 | 7 | import numpy as np 8 | import pandas as pd 9 | import requests 10 | from scipy import stats 11 | 12 | import elmada 13 | from elmada import from_entsoe, from_other 14 | from elmada import mappings as mp 15 | from elmada import paths 16 | 17 | logger = logging.getLogger(__name__) 18 | logger.setLevel(logging.WARN) 19 | 20 | 21 | def prep_prices(year=2019, freq="60min", country="DE", **mo_kwargs) -> pd.Series: 22 | """Convenience function to get the marginal costs for each timesteps.""" 23 | return prep_CEFs(year=year, freq=freq, country=country, **mo_kwargs)["marginal_cost"] 24 | 25 | 26 | def prep_CEFs(year=2019, freq="60min", country="DE", mo_P=None, **mo_kwargs) -> pd.DataFrame: 27 | """Prepares German CEFs from the power plant list from OPSD""" 28 | assert country == "DE", "this function only works for Germany" 29 | if mo_P is None: 30 | mo_P = merit_order(year=year, **mo_kwargs) 31 | return get_CEFs_from_merit_order(mo_P=mo_P, year=year, freq=freq, country=country) 32 | 33 | 34 | def get_CEFs_from_merit_order( 35 | mo_P: pd.DataFrame, year: int, freq: str, country: str, resi_T: Optional[pd.Series] = None 36 | ) -> pd.DataFrame: 37 | if resi_T is None: 38 | resi_T = from_entsoe.prep_residual_load(year=year, freq=freq, country=country) 39 | _warn_if_not_enough_capa(mo_P, resi_T) 40 | total_load_T = from_entsoe.load_el_national_generation( 41 | year=year, freq=freq, country=country 42 | ).sum(axis=1) 43 | cols = ["cumsum_capa", "marginal_emissions", "capa", "fuel_draf", "used_eff", "marginal_cost"] 44 | len_mo = len(mo_P) 45 | mo_P_arr = mo_P[cols].values 46 | transm_eff = from_other.get_transmission_efficiency(country=country) 47 | 48 | def get_data(resi_value: float, what: str = "marginal_emissions"): 49 | output_col_index = cols.index(what) 50 | for row in range(len_mo): 51 | if mo_P_arr[row, 0] > resi_value: 52 | return mo_P_arr[row, output_col_index] 53 | return mo_P_arr[-1, output_col_index] 54 | 55 | def get_emissions_for_xef(resi_value: float) -> float: 56 | # If the residual load is not positive there are no emissions: 57 | if resi_value <= 0.0: 58 | return 0.0 59 | 60 | emissions = 0.0 61 | for row in range(len_mo): 62 | emissions += mo_P_arr[row, 1] * mo_P_arr[row, 2] 63 | 64 | # If the cumsum_capa is above resi_value subtract the emissions of the additional 65 | # capacity and return the quotient of emissions and resi_value: 66 | if mo_P_arr[row, 0] > resi_value: 67 | emissions -= mo_P_arr[row, 1] * (mo_P_arr[row, 0] - resi_value) 68 | return emissions 69 | 70 | # If the following code is executed there is not enough generation capacity: 71 | return emissions 72 | 73 | df = pd.DataFrame( 74 | { 75 | "residual_load": resi_T, 76 | "total_load": total_load_T, 77 | "marginal_fuel": resi_T.apply(get_data, what="fuel_draf"), 78 | "efficiency": resi_T.apply(get_data, what="used_eff"), 79 | "marginal_cost": resi_T.apply(get_data, what="marginal_cost"), 80 | "MEFs": resi_T.apply(get_data, what="marginal_emissions") / transm_eff, 81 | "XEFs": (resi_T.apply(get_emissions_for_xef)) / total_load_T / transm_eff, 82 | } 83 | ) 84 | 85 | df["XEFs"] *= 1000 # convert from t/MWh to kg/MWh or g/kWh 86 | df["MEFs"] *= 1000 # convert from t/MWh to kg/MWh or g/kWh 87 | return df 88 | 89 | 90 | def merit_order( 91 | year=2019, 92 | efficiency_per_plant: bool = True, 93 | emission_data_source: str = "quaschning", 94 | overwrite_carbon_tax: Optional[float] = None, 95 | **preprocess_kwargs, 96 | ) -> pd.DataFrame: 97 | """Prepares the merit order from the German power plant list.""" 98 | 99 | df = get_current_active_power_plants(year) 100 | df = _rename_to_draf_fuels(df) 101 | df = _preprocess_efficiencies( 102 | df, efficiency_per_plant=efficiency_per_plant, **preprocess_kwargs 103 | ) 104 | df = _add_marginal_emissions_for_gen(df, emission_data_source) 105 | 106 | carbon_price = ( 107 | from_other.get_ETS_price(year) if overwrite_carbon_tax is None else overwrite_carbon_tax 108 | ) 109 | df["x_k"] = df["fuel_draf"].map(from_other.get_fuel_prices(year=year, country="DE")) 110 | df["fuel_cost"] = df["x_k"] / df["used_eff"] 111 | df["GHG_cost"] = df["marginal_emissions_for_gen"] * carbon_price 112 | df["marginal_emissions"] = df["marginal_emissions_for_gen"] 113 | df["marginal_cost"] = df["fuel_cost"] + df["GHG_cost"] 114 | 115 | # to be consistent with the PWL merit_order: 116 | df = df.rename(columns={"capacity_net_bnetza": "capa"}) 117 | 118 | df = df.sort_values("marginal_cost").reset_index(drop=True) 119 | df["cumsum_capa"] = df["capa"].cumsum() 120 | return df 121 | 122 | 123 | def get_summary(year=2019) -> pd.DataFrame: 124 | ca = get_current_active_power_plants(year) 125 | grouper = ca.groupby(["fuel", "technology"]) 126 | d = dict( 127 | counts=grouper.count().id, 128 | efficiency=grouper["efficiency_estimate"].mean(), 129 | sum_capa=grouper["capacity_net_bnetza"].sum(), 130 | ) 131 | return pd.concat(d.values(), axis=1, keys=d.keys()) 132 | 133 | 134 | def get_summary_of_opsd_raw() -> pd.DataFrame: 135 | df = read_opsd_powerplant_list(which="DE") 136 | df = df.rename(columns={"capacity_net_bnetza": "capa"}) 137 | df = df.groupby("energy_source_level_2").agg({"id": "size", "capa": "sum"}) 138 | df = df.sort_values("capa", ascending=False) 139 | df["capa_rel"] = df["capa"] / df["capa"].sum() 140 | return df 141 | 142 | 143 | def get_current_active_power_plants(year=2019) -> pd.DataFrame: 144 | """Returns a Dataframe with all active power plants for a specific year.""" 145 | df = read_opsd_powerplant_list(which="DE") 146 | 147 | used_fuels = mp.OPSD_TO_DRAF.keys() 148 | other_fuels = set(df["fuel"]) - set(used_fuels) 149 | is_german = df["country_code"] == "DE" 150 | after_commissioned = (df["commissioned"].notnull() & (df["commissioned"] < year)) | df[ 151 | "commissioned" 152 | ].isna() 153 | before_shutdown = (df["shutdown"].notnull() & (df["shutdown"] > year)) | df["shutdown"].isna() 154 | currently_active = after_commissioned & before_shutdown 155 | in_used_fuels = df["fuel"].isin(used_fuels) 156 | cond = is_german & currently_active & in_used_fuels 157 | ca = df[cond] 158 | 159 | logger.info( 160 | f"{(cond.sum() / len(df)):.2%} of data rows were used " 161 | f"({(~in_used_fuels).sum() / len(df):.2%} are not in used fuels). " 162 | f"Other (discarded) fuels are {other_fuels}." 163 | ) 164 | 165 | # interesting_cols = ["id", "name_bnetza", "status", "fuel", "technology", "type", "efficiency_estimate", "capacity_net_bnetza"] 166 | return ca 167 | 168 | 169 | def read_opsd_powerplant_list(which: str = "DE") -> pd.DataFrame: 170 | assert which in ("DE", "EU"), f"`{which}` is no valid value for `which`." 171 | 172 | fp = paths.mode_dependent_cache_dir() / f"OPSD_conventional_power_plants_{which}.csv" 173 | 174 | if not fp.exists(): 175 | download_powerplant_list(which=which, fp=fp) 176 | df = pd.read_csv(fp) 177 | 178 | if elmada.get_mode() == "live": 179 | df = df.rename(columns={"energy_source": "fuel", "country": "country_code"}) 180 | 181 | return df 182 | 183 | 184 | def download_powerplant_list(which: str, fp: Path) -> None: 185 | url = ( 186 | f"https://data.open-power-system-data.org/conventional_power_plants/latest/" 187 | f"conventional_power_plants_{which}.csv" 188 | ) 189 | response = requests.get(url, allow_redirects=True) 190 | fp.write_bytes(response.content) 191 | logger.info(f"{fp.name} downloaded from OPSD.") 192 | 193 | 194 | def _rename_to_draf_fuels( 195 | df: pd.DataFrame, minimum_efficiency_for_gas_cc: float = 0.5 196 | ) -> pd.DataFrame: 197 | def my_rename(row): 198 | if ( 199 | (row["fuel"] == "Natural gas") 200 | and (row["technology"] == "Combined cycle") 201 | and (row["efficiency_estimate"] >= minimum_efficiency_for_gas_cc) 202 | ): 203 | return "gas_cc" 204 | else: 205 | return mp.OPSD_TO_DRAF[row["fuel"]] 206 | 207 | df["fuel_draf"] = df.apply(my_rename, axis=1) 208 | return df 209 | 210 | 211 | def _preprocess_efficiencies( 212 | df: pd.DataFrame, 213 | efficiency_per_plant: bool = True, 214 | fill_missing_efficiencies: bool = True, 215 | fill_zscore_outlier: bool = True, 216 | zscore_threshold: float = 3, 217 | ensure_minimum_efficiency: bool = True, 218 | minimum_efficiency: float = 0.3, 219 | ) -> pd.DataFrame: 220 | filler_dic = df.groupby(by="fuel_draf")["efficiency_estimate"].mean().to_dict() 221 | df["filler"] = df["fuel_draf"].map(filler_dic) 222 | 223 | if fill_missing_efficiencies: 224 | number_of_nans = df["efficiency_estimate"].isna().sum() 225 | logger.info(f"{number_of_nans} nans in efficiencies of merit order filled") 226 | df["efficiency_estimate"].fillna(df["filler"], inplace=True) 227 | 228 | if fill_zscore_outlier: 229 | for fuel in df["fuel_draf"].unique(): 230 | ser = df["efficiency_estimate"] 231 | ser = ser[ser.notnull()] 232 | cond_zscore = np.abs(stats.zscore(ser)) > zscore_threshold 233 | cond_fuel = df["fuel_draf"] == fuel 234 | number_of_nans = len(df[cond_zscore & cond_fuel]) 235 | logger.info("f{number_of_nans} nans filled in fuel {fuel}.") 236 | df.loc[(cond_zscore & cond_fuel), "efficiency_estimate"] = filler_dic[fuel] 237 | 238 | df["eta_k"] = df["fuel_draf"].map(from_other.get_baumgaertner_data()["eta_k"]) 239 | df["used_eff"] = df["efficiency_estimate"] if efficiency_per_plant else df["eta_k"] 240 | 241 | if ensure_minimum_efficiency: 242 | df.loc[df["used_eff"] < minimum_efficiency, "used_eff"] = minimum_efficiency 243 | logger.info(f"Minimum_efficiency set to {minimum_efficiency}.") 244 | 245 | return df 246 | 247 | 248 | def _add_marginal_emissions_for_gen(df: pd.DataFrame, emission_data_source: str) -> pd.DataFrame: 249 | 250 | if emission_data_source == "baumgaertner": 251 | DATA_BAUMG = from_other.get_baumgaertner_data() 252 | df["mu_co2_k"] = df["fuel_draf"].map(DATA_BAUMG["mu_co2_k"]) 253 | df["H_u_k"] = df["fuel_draf"].map(DATA_BAUMG["H_u_k"]) 254 | df["marginal_emissions_for_gen"] = df["mu_co2_k"] / (df["H_u_k"] * df["used_eff"]) 255 | 256 | elif emission_data_source == "quaschning": 257 | # in the quaschning data the mu_co2_k and the H_u_k are already included 258 | DATA_QUASCH = from_other.get_emissions_per_fuel_quaschning() 259 | df["marginal_emissions_for_gen"] = df["fuel_draf"].map(DATA_QUASCH) / df["used_eff"] 260 | 261 | else: 262 | raise ValueError("Emission_data_source must be either 'baumgaertner' or 'quaschning'.") 263 | 264 | return df 265 | 266 | 267 | def _warn_if_not_enough_capa(mo_P: pd.DataFrame, resi_ser: pd.DataFrame) -> None: 268 | mo_max = mo_P["cumsum_capa"].max() / 1e3 269 | min_resi = resi_ser.min() / 1e3 270 | if mo_max < min_resi: 271 | logger.warning( 272 | f"Generation capacity ({mo_max:.2f} GW) is lower than " 273 | f"minimum residual load ({min_resi:.2f} GW)." 274 | ) 275 | -------------------------------------------------------------------------------- /elmada/from_other.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from io import StringIO 3 | from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union 4 | 5 | import numpy as np 6 | import pandas as pd 7 | 8 | from elmada import get_mode 9 | from elmada import helper as hp 10 | from elmada import mappings as mp 11 | from elmada import paths 12 | 13 | logger = logging.getLogger(__name__) 14 | logger.setLevel(logging.WARN) 15 | 16 | 17 | def get_ETS_price(year: int) -> float: 18 | prices = get_ETS_prices() 19 | try: 20 | return prices[year] 21 | except KeyError: 22 | max(prices.keys()) 23 | backup_value = prices[max(prices.keys())] 24 | logger.warning( 25 | f"No data for ETS price for {year} " 26 | f"==> Default value of latest available year with {backup_value:.2f} €/t is given." 27 | ) 28 | return backup_value 29 | 30 | 31 | def get_ETS_prices() -> Dict: 32 | """Returns carbon emission prices of the EU emissions trading system (ETS) for different years. 33 | 34 | Source: 35 | * EEX (https://www.eex.com/en/market-data/environmental-markets/auction-market/european-emission-allowances-auction) 36 | via Sandbag (https://sandbag.org.uk/carbon-price-viewer/) 37 | * ICE via Quandl 38 | """ 39 | mode = get_mode() 40 | if mode == "live": 41 | return get_ice_eua_prices() 42 | 43 | elif mode == "safe": 44 | return get_sandbag_eua_prices() 45 | else: 46 | raise RuntimeError() 47 | 48 | 49 | def get_ice_eua_prices(cache: bool = True) -> Dict: 50 | """Get EUA prices from ICE [1] via Quandl [2]. 51 | 52 | Sources: 53 | [1] https://www.theice.com/ 54 | [2] https://www.quandl.com/data/CHRIS/ICE_C1-ECX-EUA-Futures-Continuous-Contract-1-C1-Front-Month 55 | """ 56 | fp = paths.mode_dependent_cache_dir() / "QUANDL_eua_prices.parquet" 57 | 58 | if cache and fp.exists(): 59 | ser = hp.read(fp) 60 | else: 61 | import quandl 62 | 63 | quandl.ApiConfig.api_key = hp.get_api_key("quandl") 64 | df = quandl.get("CHRIS/ICE_C1") 65 | ser = df["Settle"].resample("y").mean().rename("Price") 66 | ser.index = ser.index.year 67 | if cache: 68 | hp.write(ser, fp) 69 | 70 | return ser.to_dict() 71 | 72 | 73 | def get_sandbag_eua_prices() -> Dict: 74 | """Get ETS EUA prices via Sandbag""" 75 | fp = paths.DATA_DIR / "sandbag/eua-price.csv" 76 | 77 | # # fix bad csv-syntax 78 | s = fp.read_text(encoding="utf8").replace('",', '";') 79 | 80 | io = StringIO(s) 81 | df = pd.read_csv(io, sep=";", decimal=",", index_col=0, skiprows=2) 82 | 83 | df.index = pd.to_datetime(df.index) 84 | df = df.resample("YE").mean() 85 | df = df.set_index(df.index.year, drop=True) 86 | return df.squeeze().to_dict() 87 | 88 | 89 | def _get_light_oil_conversion_factor(source: int = 2): 90 | """Get the calorific value for light oil in MWh/Hektoliter from different sources. 91 | 92 | Sources: 93 | [1]: Heizöl Total (https://www.heizoel.total.de/rund-um-heizoel/aktuelles-tipps/heizoelkauf-beratung/heizwert-und-brennwert-von-heizol/) 94 | raw value: 9.8 kWh/l 95 | 96 | [2]: Mineralöl Franken (https://www.mineraloel-franken.de/fileadmin/redaktion/downloads/Produktspezifikation_Heizoel_EL_Total.pdf) 97 | raw values: 42.6 MJ/kg for light oil with a density of 860 kg/m³. 98 | """ 99 | if source == 1: 100 | return 0.980 101 | 102 | if source == 2: 103 | return ( 104 | 42.6 # MJ/kg 105 | * 860 # kg/m³ ==> MJ/m³ 106 | / 3600 # MJ/MWh ==> MWh/m³ 107 | / 10 # Hektoliter/m³ ==> MWh/Hektoliter 108 | ) 109 | 110 | 111 | def _get_light_oil_price(year: int = 2019): 112 | """Get light oil price in €/MWh. 113 | 114 | Source: StatistischeBundesamt.2020 (https://www.destatis.de/DE/Themen/Wirtschaft/Preise/Publikationen/Energiepreise/energiepreisentwicklung-pdf-5619001.html) 115 | """ 116 | data = pd.Series( 117 | { 118 | 2005: 42.42, 119 | 2006: 47.58, 120 | 2007: 46.83, 121 | 2008: 61.76, 122 | 2009: 40.81, 123 | 2010: 52.31, 124 | 2011: 66.51, 125 | 2012: 72.94, 126 | 2013: 67.96, 127 | 2014: 61.88, 128 | 2015: 46.19, 129 | 2016: 38.40, 130 | 2017: 45.05, 131 | 2018: 55.27, 132 | 2019: 53.69, 133 | } 134 | ) 135 | return ( 136 | data[year] / _get_light_oil_conversion_factor() # €/Hektoliter # MWh/Hektoliter ==> €/MWh 137 | ) 138 | 139 | 140 | def _get_lignite_price(year: int = 2019, base_price: float = 6.3) -> float: 141 | """Get lignite price in €/MWh. Calculated as product of a destatis index and an baseprice for 142 | the year 2015. 143 | 144 | Source: 145 | indices: StatistischeBundesamt.2020 (https://www.destatis.de/DE/Themen/Wirtschaft/Preise/Publikationen/Energiepreise/energiepreisentwicklung-pdf-5619001.html) 146 | base_price: Konstantin.2017 (https://doi.org/10.1007/978-3-662-49823-1) 147 | """ 148 | fp = paths.DATA_DIR / "destatis/energiepreisentwicklung-xlsx-5619001.xls" 149 | xl = pd.ExcelFile(fp) 150 | df = xl.parse( 151 | sheet_name=7, skiprows=28, header=None, skipfooter=0, index_col=0, na_values="-" 152 | ).dropna(axis=0, how="all")[13] 153 | 154 | df.index = df.index.str.slice(0, 5).astype(int) 155 | return df[year] * base_price / 100 156 | 157 | 158 | def _get_coal_price(year: int = 2019, base_price: float = 10.12) -> float: 159 | """Get coal price in €/MWh. Calculated as product of a destatis index and an baseprice for 160 | the year 2015. 161 | 162 | Source: 163 | indices: StatistischeBundesamt.2020 (https://www.destatis.de/DE/Themen/Wirtschaft/Preise/Publikationen/Energiepreise/energiepreisentwicklung-pdf-5619001.html) 164 | base_price: Konstantin.2017 (https://doi.org/10.1007/978-3-662-49823-1) 165 | """ 166 | fp = paths.DATA_DIR / "destatis/energiepreisentwicklung-xlsx-5619001.xls" 167 | xl = pd.ExcelFile(fp) 168 | df = xl.parse( 169 | sheet_name=7, skiprows=7, header=None, skipfooter=21, index_col=0, na_values="-" 170 | ).dropna(axis=0, how="all")[13] 171 | 172 | df.index = df.index.str.slice(0, 5).astype(int) 173 | return df[year] * base_price / 100 174 | 175 | 176 | def _get_gas_price(year: int = 2019, country: str = "DE") -> float: 177 | """Get gas price in €/MWh. including excise taxes, without value-added tax. 178 | Data for most European countries in the years 2008 - 2019. 179 | 180 | Source: StatistischeBundesamt.2020 (https://www.destatis.de/DE/Themen/Wirtschaft/Preise/Publikationen/Energiepreise/energiepreisentwicklung-pdf-5619001.html) 181 | """ 182 | fp = paths.DATA_DIR / "destatis/energiepreisentwicklung-xlsx-5619001.xls" 183 | xl = pd.ExcelFile(fp) 184 | nblocks = 4 185 | blocksize = 26 186 | block_list = [] 187 | for i in range(nblocks): 188 | block_list.append( 189 | xl.parse( 190 | sheet_name=11, 191 | skiprows=4 + i * blocksize, 192 | skipfooter=blocksize * (nblocks - 1 - i) + 2, 193 | index_col=0, 194 | na_values="-", 195 | ).dropna(axis=0, how="all") 196 | ) 197 | df = pd.concat(block_list, sort=False, join="outer", axis=1).dropna(axis=1, how="all") 198 | df["year"] = df.index.str.slice(5).astype(int) 199 | df = df.groupby("year").mean() # get mean between year-halfs 200 | 201 | df = df / 100 * 1000 # convert cent/kWh into €/MWh 202 | 203 | default_value = df.mean().mean() 204 | warn_msg = ( 205 | f"No data for gas price for {year},{country} ==> " 206 | f"Default value {default_value:.2f} €/MWh is given." 207 | ) 208 | try: 209 | price = df.loc[year, mp.EU_de_for_gas_price[country]] 210 | except KeyError: 211 | logger.warning(warn_msg) 212 | return default_value 213 | 214 | if np.isnan(price): 215 | logger.warning(warn_msg) 216 | return default_value 217 | 218 | else: 219 | return price 220 | 221 | 222 | def get_fuel_prices(year: int = 2019, country: str = "DE") -> Dict: 223 | """Get x_k: Fuel price [€ / MWh] 224 | 225 | Source: 226 | Nuclear: Konstantin.2017 (https://doi.org/10.1007/978-3-662-49823-1) 227 | Rest: 228 | StatistischeBundesamt.2020 (https://www.destatis.de/DE/Themen/Wirtschaft/Preise/Publikationen/Energiepreise/energiepreisentwicklung-pdf-5619001.html) 229 | + Konstantin.2017 (https://doi.org/10.1007/978-3-662-49823-1) 230 | """ 231 | gas_price = _get_gas_price(year=year, country=country) 232 | try: 233 | return dict( 234 | oil=_get_light_oil_price(year=year), 235 | gas_cc=gas_price, 236 | gas=gas_price, 237 | coal=_get_coal_price(year=year), 238 | lignite=_get_lignite_price(year=year), 239 | nuclear=4.18, 240 | ) 241 | except KeyError: # if values for future years are missing 242 | return get_fuel_prices(year=2019) 243 | 244 | 245 | def get_baumgaertner_data() -> Dict: 246 | """Nomentclature 247 | 248 | Per Fuel-type k 249 | - x_k: Fuel price [€ / MWh] 250 | - mu_co2_k: Carbon emission intensity [t_CO2eq / t_k] 251 | - H_u_k: Calorific value [MWh / t_k] 252 | - eta_k: Efficiency [MWh_el / MWh] 253 | 254 | Source: Baumgärtner.2019 (https://doi.org/10.1016/j.apenergy.2019.04.029) 255 | fuel prices: Konstantin.2017 (https://doi.org/10.1007/978-3-662-49823-1) 256 | """ 257 | return dict( 258 | x_k=dict(oil=38.63, gas_cc=26.71, gas=29.81, coal=10.12, lignite=6.3, nuclear=4.18), 259 | mu_co2_k=dict(oil=3.15, gas_cc=1.93, gas=1.93, coal=2.6, lignite=1.39, nuclear=0.0), 260 | H_u_k=dict( 261 | oil=42.6 / 3.6, 262 | gas_cc=38.27 / 3.6, 263 | gas=38.27 / 3.6, 264 | coal=26.47 / 3.6, 265 | lignite=14.99 / 3.6, 266 | nuclear=41667.0, 267 | ), 268 | eta_k=dict(oil=0.373, gas_cc=0.50, gas=0.349, coal=0.377, lignite=0.356, nuclear=0.32), 269 | ) 270 | 271 | 272 | def get_emissions_per_fuel_quaschning() -> Dict: 273 | """Carbon emissions per fuel type [t_CO2eq / MWh] 274 | 275 | Source: Quaschning.2015 (https://www.volker-quaschning.de/datserv/CO2-spez/index_e.php)""" 276 | return dict(oil=0.28, gas_cc=0.25, gas=0.25, coal=0.34, lignite=0.36, nuclear=0.0) 277 | 278 | 279 | def prepare_transmission_losses() -> pd.DataFrame: 280 | """Source: WorldBank.2020 (https://databank.worldbank.org/reports.aspx?source=2&series=EG.ELC.LOSS.ZS)""" 281 | fp = paths.DATA_DIR / "worldbank/Data_Extract_From_World_Development_Indicators/Data.csv" 282 | df = pd.read_csv(fp, nrows=58, na_values="..", index_col=2).iloc[:, 3:] 283 | df = df.rename(columns={k: k[:4] for k in df.keys()}) 284 | df["mean"] = df.loc[:, "2010":"2014"].mean(1) 285 | return df 286 | 287 | 288 | def get_transmission_efficiency_series(fillna: bool = True, cache: bool = True) -> pd.Series: 289 | """Returns a Series with transmission efficiencies and long country names in index.""" 290 | 291 | fp = paths.mode_dependent_cache_dir() / "transmission_efficiencies.parquet" 292 | 293 | if cache and fp.exists(): 294 | ser = hp.read(fp) 295 | 296 | else: 297 | df = prepare_transmission_losses() 298 | ser = df["mean"] 299 | if cache: 300 | hp.write(ser, fp) 301 | 302 | if fillna: 303 | na_countries = list(ser[ser.isna()].index) 304 | filler = ser.mean() 305 | ser = ser.fillna(filler) 306 | logger.info(f"No data for transmission efficiency for {na_countries}") 307 | 308 | return 1 - (ser / 100) 309 | 310 | 311 | def get_transmission_efficiency(country: str) -> float: 312 | """Returns scalar value of transmission efficiency.""" 313 | ser = get_transmission_efficiency_series() 314 | 315 | try: 316 | cy_long = mp.EUROPE_COUNTRIES[country] 317 | return ser[cy_long] 318 | 319 | except KeyError: 320 | default = ser.mean() 321 | logger.warning( 322 | f"No transmission efficiency data for {country}. European mean of {default} given." 323 | ) 324 | return default 325 | -------------------------------------------------------------------------------- /elmada/from_smard.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import warnings 3 | 4 | import pandas as pd 5 | 6 | from elmada import helper as hp 7 | from elmada import paths 8 | 9 | logger = logging.getLogger(__name__) 10 | logger.setLevel(level=logging.WARN) 11 | 12 | 13 | def prep_dayahead_prices( 14 | year: int, freq: str = "60min", country: str = "DE", cache: bool = True 15 | ) -> pd.Series: 16 | 17 | assert year in range(2000, 2100), f"{year} is not a valid year" 18 | assert country == "DE", "Smard is only used for Germany!" 19 | 20 | fp = paths.CACHE_DIR / f"{year}_{freq}_{country}_dayahead_c_el_smard.parquet" 21 | 22 | if cache and fp.exists(): 23 | ser = hp.read(fp) 24 | 25 | else: 26 | fp_raw = paths.DATA_DIR / f"smard/Day-ahead_prices_{year}.csv" 27 | df = pd.read_csv( 28 | fp_raw, 29 | sep=";", 30 | usecols=["Germany/Luxembourg[€/MWh]", "Germany/Austria/Luxembourg[€/MWh]"], 31 | na_values="-", 32 | ) 33 | df = df.reset_index(drop=True) 34 | df = df.fillna(df.mean()) 35 | 36 | # add a merged column 37 | df["DE_merged"] = 0 38 | 39 | if year < 2018: 40 | df["DE_merged"] = df["Germany/Austria/Luxembourg[€/MWh]"] 41 | 42 | elif year == 2018: 43 | 44 | warnings.filterwarnings( 45 | "ignore", 46 | message="A value is trying to be set on a copy of a slice from a DataFrame", 47 | ) 48 | df.loc[:6552, "DE_merged"] = df.loc[:6552, "Germany/Austria/Luxembourg[€/MWh]"] 49 | df.loc[6552:, "DE_merged"] = df.loc[6552:, "Germany/Luxembourg[€/MWh]"] 50 | 51 | elif year > 2018: 52 | df["DE_merged"] = df["Germany/Luxembourg[€/MWh]"] 53 | 54 | ser = df["DE_merged"] 55 | if cache: 56 | hp.write(ser, fp) 57 | 58 | start_freq = hp.estimate_freq(ser) 59 | 60 | ser = hp.resample(ser, year=year, start_freq=start_freq, target_freq=freq) 61 | if start_freq != freq: 62 | logger.warning(f"Dayahead_prices were resampled from {start_freq} to {freq}") 63 | 64 | hp.warn_if_incorrect_index_length(ser, year, freq) 65 | return ser 66 | -------------------------------------------------------------------------------- /elmada/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union 3 | 4 | import pandas as pd 5 | 6 | import elmada 7 | from elmada import helper as hp 8 | from elmada import paths 9 | 10 | logger = logging.getLogger(__name__) 11 | logger.setLevel(level=logging.WARN) 12 | 13 | 14 | def get_emissions( 15 | year: int, 16 | freq: str = "60min", 17 | country: str = "DE", 18 | method: str = "XEF_PP", 19 | cache: bool = True, 20 | use_datetime: bool = False, 21 | **mo_kwargs, 22 | ) -> Union[pd.Series, pd.DataFrame]: 23 | """Returns dynamic carbon emisson factors in gCO2eq/kWh_el and optional data. 24 | 25 | Args: 26 | year: Year 27 | freq: Frequency, e.g. '60min' or '15min' 28 | country: alpha-2 country code, e.g. 'DE' 29 | method: A method string that consists of two parts joined by an underscore. The first 30 | part is the type of emission factor. Use 'XEF' for grid mix emission factors and 31 | 'MEF' for marginal emission factors. The second part determines the calculation method 32 | or data sources. 'EP' stands for ENTSO-E, 'PP' for power plant method, 'PWL' for piecewise 33 | linear method, 'PWLv' for the piecewise linear method in validation mode. 34 | For PP, PWL, and PWLv, the first can be omitted ('_PP', '_PWL', '_PWLv') to return 35 | a DataFrame including the columns 'residual_load', 'total_load', 'marginal_fuel', 36 | 'efficiency', 'marginal_cost', 'MEFs', 'XEFs'. 37 | The following combinations are possible: 38 | 39 | | Method | Description | 40 | | -------- | ---------------------------------------- | 41 | | XEF_EP | Series: XEFs using ENTSO-E data | 42 | | XEF_PP | Series: XEFs using PP method | 43 | | XEF_PWL | Series: XEFs using PWL method | 44 | | XEF_PWLv | Series: XEFs using PWLv method | 45 | | MEF_PP | Series: MEFs from PP method | 46 | | MEF_PWL | Series: MEFs using PWL method | 47 | | MEF_PWLv | Series: MEFs using PWLv method | 48 | | _PP | Dataframe: extended data for PP method | 49 | | _PWL | Dataframe: extended data for PWL method | 50 | | _PWLv | Dataframe: extended data for PWLv method | 51 | 52 | cache: If cache is used. 53 | use_datetime: If True, the index is a timezone agnostic datetime. If False, the index is 54 | 0, 1, 2, etc. 55 | **mo_kwargs: Keyword arguments for merit order creation such as 'overwrite_carbon_tax', 56 | 'efficiency_per_plant', 'emission_data_source'. 57 | """ 58 | first_method_part, last_method_part = method.split("_") 59 | 60 | fp = paths.CACHE_DIR / f"{year}_{country}_{freq}_CEFs_{last_method_part}.parquet" 61 | 62 | if cache and fp.exists() and not mo_kwargs: 63 | df = hp.read(fp, squeeze=False) 64 | else: 65 | df = _make_emissions( 66 | year=year, freq=freq, country=country, method=last_method_part, **mo_kwargs 67 | ) 68 | if cache: 69 | hp.write(df, fp) 70 | 71 | if use_datetime: 72 | df.index = hp.make_datetimeindex(year=year, freq=freq) 73 | 74 | if first_method_part in ("XEF", "MEF"): 75 | return df[first_method_part + "s"] 76 | else: 77 | return df 78 | 79 | 80 | def _make_emissions(year, freq, country, method, **mo_kwargs) -> pd.DataFrame: 81 | config = dict(year=year, freq=freq, country=country) 82 | 83 | if method == "EP": 84 | return elmada.from_entsoe.prep_XEFs(**config) 85 | # elif method == "SM": 86 | # return elmada.from_smard.prep_XEFs(**config) 87 | elif method == "PP": 88 | return elmada.from_opsd.prep_CEFs(**config, **mo_kwargs) 89 | elif method in ["PWL", "PWLv"]: 90 | is_vmode = bool(method == "PWLv") 91 | return elmada.eu_pwl.prep_CEFs(validation_mode=is_vmode, **config, **mo_kwargs) 92 | else: 93 | raise ValueError(f"Method {method} not implemented.") 94 | 95 | 96 | def get_prices( 97 | year: int, 98 | freq: str = "60min", 99 | country: str = "DE", 100 | method: str = "hist_EP", 101 | cache: bool = True, 102 | **mo_kwargs, 103 | ) -> pd.Series: 104 | """Returns the day-ahead spot-market electricity prices in €/MWh. 105 | 106 | Args: 107 | year: Year 108 | freq: Frequency, e.g. '60min' or '15min' 109 | country: alpha-2 country code, e.g. 'DE' 110 | method: 'PP' stands for power plant method, 'PWL' stands for piecewise 111 | linear method, 'PWLv' stands for the piecewise linear method in validation mode. 112 | 113 | | Method | Description | 114 | | ------- | --------------------------------------------------- | 115 | | PP | Using PP method | 116 | | PWL | Using PWL method | 117 | | PWLv | Using PWLv method | 118 | | hist_EP | Using historic ENTSO-E data | 119 | | hist_SM | Using historic Smard data only for DE, (2015, 2018 | 120 | 121 | cache: If data is cached 122 | 123 | Known data issues: 124 | - (2015, DE, entsoe)-prices: data missing until Jan 6th 125 | - (2018, DE, entsoe)-prices: data missing from Sep 30th 126 | """ 127 | 128 | # >> Workaround due to missing data for DE 129 | if year in [2015, 2018] and country == "DE" and method == "hist_EP": 130 | method = "hist_SM" 131 | logger.warning( 132 | f"The requested entsoe-data ({year}, {country}) is not complete. " 133 | f"Smard-data is given to you instead." 134 | ) 135 | # << 136 | 137 | config = dict(year=year, freq=freq, country=country, cache=cache) 138 | 139 | if method == "hist_EP": 140 | return elmada.from_entsoe.prep_dayahead_prices(**config) 141 | elif method == "hist_SM": 142 | return elmada.from_smard.prep_dayahead_prices(**config) 143 | elif method in ["PP", "PWL", "PWLv"]: 144 | df = get_emissions(method=f"_{method}", **config, **mo_kwargs) 145 | return df["marginal_cost"] 146 | else: 147 | raise ValueError(f"Method '{method}' not implemented.") 148 | 149 | 150 | def get_merit_order( 151 | year: int, country: str = "DE", method: str = "PP", **mo_kwargs 152 | ) -> pd.DataFrame: 153 | """Returns the merit order as DataFrame. 154 | 155 | Args: 156 | year: Year 157 | country: alpha-2 country code, e.g. 'DE' 158 | method: One of 'PP' (power plant method), 'PWL' (piecewise 159 | linear method), 'PWLv' (piecewise linear method in validation mode). 160 | **mo_kwargs: keyword arguments for merit order function depending on `method`. 161 | """ 162 | if method == "PP": 163 | assert country == "DE", f"PP-method only works for Germany and not for {country}" 164 | return elmada.from_opsd.merit_order(year=year, **mo_kwargs) 165 | elif method == "PWL": 166 | return elmada.eu_pwl.merit_order( 167 | year=year, country=country, validation_mode=False, **mo_kwargs 168 | ) 169 | elif method == "PWLv": 170 | return elmada.eu_pwl.merit_order( 171 | year=year, country=country, validation_mode=True, **mo_kwargs 172 | ) 173 | else: 174 | raise ValueError("`method` needs to be one of ['PP', 'PWL', 'PWLv'].") 175 | 176 | 177 | def get_residual_load(year: int, freq: str = "60min", country: str = "DE", **kwargs) -> pd.Series: 178 | return elmada.from_entsoe.prep_residual_load(year=year, freq=freq, country=country, **kwargs) 179 | 180 | 181 | def get_el_national_generation(year: int, freq: str = "60min", country: str = "DE") -> pd.DataFrame: 182 | return elmada.from_entsoe.load_el_national_generation(year=year, freq=freq, country=country) 183 | -------------------------------------------------------------------------------- /elmada/mappings.py: -------------------------------------------------------------------------------- 1 | """Various mappings including countries, fuel types, colors. 2 | 3 | ISO 3166-1 alpha-2 country codes are used, see https://en.wikipedia.org/wiki/ISO_3166-1 4 | 5 | # TODO: there is a python module for ISO 3166-1, see https://github.com/deactivated/python-iso3166 6 | 7 | """ 8 | 9 | import collections 10 | from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union 11 | 12 | # EU 27 13 | EU = { 14 | "AT": "Austria", 15 | "BE": "Belgium", 16 | "BG": "Bulgaria", 17 | "CY": "Cyprus", 18 | "CZ": "Czech Republic", 19 | "DE": "Germany", 20 | "DK": "Denmark", 21 | "EE": "Estonia", 22 | "ES": "Spain", 23 | "FI": "Finland", 24 | "FR": "France", 25 | "GR": "Greece", 26 | "HR": "Croatia", 27 | "HU": "Hungary", 28 | "IE": "Ireland", 29 | "IT": "Italy", 30 | "LT": "Lithuania", 31 | "LU": "Luxembourg", 32 | "LV": "Latvia", 33 | "MT": "Malta", 34 | "NL": "Netherlands", 35 | "PL": "Poland", 36 | "PT": "Portugal", 37 | "RO": "Romania", 38 | "SE": "Sweden", 39 | "SI": "Slovenia", 40 | "SK": "Slovakia", 41 | } 42 | 43 | # Non-EU27 European countries 44 | OTHER = { 45 | "AD": "Andorra", 46 | "AL": "Albania", 47 | "AM": "Armenia", 48 | "AZ": "Azerbaijan", 49 | "BA": "Bosnia and Herzegovina", 50 | "BY": "Belarus", 51 | "CH": "Switzerland", 52 | "FO": "Faeroe Islands", 53 | "GB": "United Kingdom", 54 | "GE": "Georgia", 55 | "GI": "Gibraltar", 56 | "IS": "Iceland", 57 | "KZ": "Kazakhstan", 58 | "LI": "Liechtenstein", 59 | "MC": "Monaco", 60 | "MD": "Moldova", 61 | "ME": "Montenegro", 62 | "MK": "Macedonia", 63 | "NO": "Norway", 64 | "RS": "Serbia", 65 | "RU": "Russian Federation", 66 | "SM": "San Marino", 67 | "TR": "Turkey", 68 | "UA": "Ukraine", 69 | "VA": "Vatican City State", 70 | "XK": "Kosovo", 71 | } 72 | 73 | # Countries that are excluded form the analysis 74 | EXCLUDED = { 75 | "AD": "No generation data available from ENTSO-E", 76 | "AM": "No generation data available from ENTSO-E", 77 | "AZ": "No generation data available from ENTSO-E", 78 | "CY": "No generation data available from ENTSO-E", 79 | "FO": "No generation data available from ENTSO-E", 80 | "GE": "No generation data available from ENTSO-E", 81 | "GI": "No generation data available from ENTSO-E", 82 | "IS": "No generation data available from ENTSO-E", 83 | "KZ": "No generation data available from ENTSO-E", 84 | "LI": "No generation data available from ENTSO-E", 85 | "MC": "No generation data available from ENTSO-E", 86 | "MD": "No generation data available from ENTSO-E", 87 | "SM": "No generation data available from ENTSO-E", 88 | "VA": "No generation data available from ENTSO-E", 89 | "XK": "No generation data available from ENTSO-E", 90 | "LU": "No generation data available from ENTSO-E", 91 | "RU": "No generation data available from ENTSO-E", 92 | "TR": "No generation data available from ENTSO-E", 93 | "MT": "No generation data available from ENTSO-E", 94 | "HR": "No generation data available from ENTSO-E", 95 | "UA": "No generation data available from ENTSO-E", 96 | "AL": "No generation data available from ENTSO-E", 97 | "BY": "No generation data available from ENTSO-E", 98 | "BA": "Only coal as conventional generation data from ENTSO-E.", 99 | "CH": "Only nuclear as conventional generation data from ENTSO-E.", 100 | "SE": "Only nuclear as conventional generation data from ENTSO-E.", 101 | "NO": "Only gas as conventional generation data from ENTSO-E.", 102 | "LV": "Only gas as conventional generation data from ENTSO-E.", 103 | "MK": "Only lignite as conventional generation data from ENTSO-E.", 104 | "ME": "Only lignite as conventional generation data from ENTSO-E.", 105 | "EE": "Only other_conv as conventional generation data from ENTSO-E.", 106 | "BG": "No installed capacity for 2019 from ENTSO-E", 107 | "SK": "No installed capacity for 2019 from ENTSO-E", 108 | } 109 | 110 | EUROPE_COUNTRIES = {**EU, **OTHER} 111 | 112 | EUROPE_COUNTRIES_LONG_TO_SHORT = {long: short for short, long in EUROPE_COUNTRIES.items()} 113 | 114 | d = {k: v for k, v in EUROPE_COUNTRIES.items() if k not in EXCLUDED} 115 | COUNTRIES_FOR_ANALYSIS = collections.OrderedDict(sorted(d.items())) 116 | 117 | NO_GEN_DAT = {k for k, v in EXCLUDED.items() if v == "No generation data available from ENTSO-E"} 118 | d = {k: v for k, v in EUROPE_COUNTRIES.items() if k not in NO_GEN_DAT} 119 | EUROPE30 = collections.OrderedDict(sorted(d.items())) 120 | 121 | # Modified entsoe-py mapping to match whole countries 122 | NEIGHBOURS_CY = { 123 | "AT": ["CH", "CZ", "DE", "HU", "IT", "SI"], 124 | "BA": ["HR", "ME", "RS"], 125 | "BE": ["NL", "DE", "FR", "GB"], 126 | "BG": ["GR", "MK", "RO", "RS", "TR"], 127 | "CH": ["AT", "DE", "FR", "IT"], 128 | "CZ": ["AT", "DE", "PL", "SK"], 129 | "DE": ["AT", "BE", "CH", "CZ", "DK", "FR", "IT", "NL", "PL", "SE", "SI"], 130 | "DK": ["DE", "NO", "SE"], 131 | "EE": ["FI", "LV", "RU"], 132 | "ES": ["FR", "PT"], 133 | "FI": ["EE", "NO", "RU", "SE", "SE"], 134 | "FR": ["BE", "CH", "DE", "ES", "GB", "IT"], 135 | "GB": ["BE", "FR", "IE", "NL"], 136 | "GR": ["AL", "BG", "IT", "MK", "TR"], 137 | "HU": ["AT", "HR", "RO", "RS", "SK", "UA"], 138 | "IE": ["GB"], 139 | "IT": ["AT", "FR", "DE", "GR", "MT", "ME", "SI", "CH"], 140 | "LT": ["BY", "LV", "PL", "RU", "SE"], 141 | "LV": ["EE", "LT", "RU"], 142 | "ME": ["AL", "BA", "RS"], 143 | "MK": ["BG", "GR", "RS"], 144 | "MT": ["IT"], 145 | "NL": ["BE", "DE", "DE", "GB", "NO"], 146 | "NO": ["SE", "FI", "DK", "NL"], 147 | "PL": ["CZ", "DE", "DE", "LT", "SE", "SK", "UA"], 148 | "PT": ["ES"], 149 | "RO": ["BG", "HU", "RS", "UA"], 150 | "RS": ["AL", "BA", "BG", "HR", "HU", "ME", "MK", "RO"], 151 | "SE": ["FI", "NO", "DE", "DK", "LT", "PL"], 152 | "SI": ["AT", "DE", "HR", "IT"], 153 | "SK": ["CZ", "HU", "PL", "UA"], 154 | } 155 | 156 | 157 | EU_de_for_gas_price = { 158 | "AT": "Österreich", 159 | "BE": "Belgien", 160 | "BG": "Bulgarien", 161 | "CZ": "Tschechiensche\nRepublik", 162 | "DE": "Deutschland", 163 | "DK": "Dänemark", 164 | "EE": "Estland", 165 | "ES": "Spanien", 166 | "FI": "Finnland", 167 | "FR": "Frankreich", 168 | "GB": "Vereinigtes\nKönigreich", 169 | "GE": "Georgien", 170 | "GR": "Griechenland", 171 | "HR": "Kroatien", 172 | "HU": "Ungarn", 173 | "IE": "Irland", 174 | "IT": "Italien", 175 | "LT": "Litauen", 176 | "LU": "Luxemburg", 177 | "LV": "Lettland", 178 | "MK": "Mazedonien", 179 | "NL": "Niederlande", 180 | "PO": "Polen", 181 | "PT": "Portugal", 182 | "RO": "Rumänien", 183 | "SE": "Schweden", 184 | "SI": "Slowenien", 185 | "SK": "Slowakei", 186 | "TR": "Türkei", 187 | "UA": "Ukraine", 188 | } 189 | 190 | TECH_COLORS = { 191 | "load": "grey", 192 | "biomass": "olive", 193 | "hydro": "lightseagreen", 194 | "solar": "gold", 195 | "wind_onshore": "royalblue", 196 | "wind_offshore": "navy", 197 | "other_RES": "lightgreen", 198 | "nuclear": "deeppink", 199 | "lignite": "chocolate", 200 | "coal": "saddlebrown", 201 | "pumped_hydro": "mediumseagreen", 202 | "gas": "darkgreen", 203 | "gas_cc": "lightgreen", 204 | "oil": "darkgrey", 205 | "other_conv": "lightgray", 206 | "nan": "red", 207 | } 208 | 209 | ORDERED_ENTSOE_FUELS = [ 210 | "Solar", 211 | "Wind Offshore", 212 | "Wind Onshore", 213 | "Other renewable", 214 | "Geothermal", 215 | "Waste", 216 | "Biomass", 217 | "Hydro Run-of-river and poundage", 218 | "Hydro Water Reservoir", 219 | "Marine", 220 | "Nuclear", 221 | "Fossil Brown coal/Lignite", 222 | "Fossil Hard coal", 223 | "Fossil Gas", 224 | "Fossil Oil", 225 | "Other_conventional", 226 | "Other", 227 | "Fossil Oil shale", 228 | "Fossil Coal-derived gas", 229 | "Fossil Peat", 230 | "Hydro Pumped Storage", 231 | ] 232 | 233 | 234 | FUEL_AGGREGATION = { 235 | "Other": "other_conv", 236 | "other_conventional": "other_conv", 237 | "Fossil Coal-derived gas": "other_conv", 238 | "Fossil Oil shale": "other_conv", 239 | "Fossil Peat": "other_conv", 240 | "Waste": "other_RES", 241 | "Geothermal": "other_RES", 242 | "Hydro Water Reservoir": "other_RES", 243 | "Marine": "other_RES", 244 | "Other renewable": "other_RES", 245 | "other renewables": "other_RES", 246 | } 247 | 248 | FUEL_RENAME = { 249 | "Biomass": "biomass", 250 | "Fossil Brown coal/Lignite": "lignite", 251 | "Fossil Oil": "oil", 252 | "Fossil Gas": "gas", 253 | "Fossil Hard coal": "coal", 254 | "Hydro Pumped Storage": "pumped_hydro", 255 | "Hydro Run-of-river and poundage": "hydro", 256 | "Nuclear": "nuclear", 257 | "Solar": "solar", 258 | "Wind Offshore": "wind_offshore", 259 | "Wind Onshore": "wind_onshore", 260 | } 261 | 262 | OPSD_TO_DRAF = { 263 | "Hard coal": "coal", 264 | "Lignite": "lignite", 265 | "Natural gas": "gas", 266 | "Nuclear": "nuclear", 267 | "Oil": "oil", 268 | } 269 | 270 | # Fuels after aggregation 271 | RES = ["hydro", "biomass", "solar", "wind_offshore", "wind_onshore", "other_RES"] 272 | CONV = ["nuclear", "lignite", "coal", "pumped_hydro", "gas", "oil", "other_conv"] 273 | DRAF_FUELS = RES + CONV 274 | 275 | # Fuels for calculation of residual load (simplified merit order simulation) 276 | CONV2 = ["nuclear", "lignite", "coal", "gas", "oil"] 277 | RES2 = [f for f in DRAF_FUELS if f not in CONV2] 278 | 279 | # Fuels for simplified merit oder simulation 280 | PWL_FUELS = ["nuclear", "lignite", "coal", "gas_cc", "gas", "oil"] 281 | 282 | 283 | def get_tech_colors(technologies: Iterable[str]) -> List[str]: 284 | """Returns list of colours for given list of technologies.""" 285 | return [TECH_COLORS[i] for i in technologies] 286 | -------------------------------------------------------------------------------- /elmada/mode.py: -------------------------------------------------------------------------------- 1 | class ConfigUtil: 2 | mode = "safe" 3 | 4 | 5 | def set_mode(mode: str): 6 | """Set data mode either to 'safe' or to 'live'.""" 7 | assert mode in ["safe", "live"] 8 | ConfigUtil.mode = mode 9 | 10 | 11 | def get_mode() -> str: 12 | return ConfigUtil.mode 13 | 14 | 15 | def is_safe_mode() -> bool: 16 | return get_mode() == "safe" 17 | -------------------------------------------------------------------------------- /elmada/paths.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Optional 3 | 4 | from appdirs import user_cache_dir, user_config_dir 5 | 6 | from elmada.mode import is_safe_mode 7 | from elmada.mappings import COUNTRIES_FOR_ANALYSIS 8 | 9 | 10 | def _get_cache_directory() -> Path: 11 | """Returns the path to cache directory and creates it, if not yet existing.""" 12 | fp = Path(user_cache_dir(appname="elmada", appauthor="DrafProject")) 13 | fp.mkdir(parents=True, exist_ok=True) 14 | return fp 15 | 16 | 17 | def _get_config_directory() -> Path: 18 | """Returns the path to config directory and creates it, if not yet existing.""" 19 | fp = Path(user_config_dir(appname="elmada", appauthor="DrafProject")) / "api_keys" 20 | fp.mkdir(parents=True, exist_ok=True) 21 | return fp 22 | 23 | 24 | BASE_DIR = Path(__file__).resolve().parent 25 | CACHE_DIR = _get_cache_directory() 26 | KEYS_DIR = _get_config_directory() 27 | DATA_DIR = BASE_DIR / "data/raw" 28 | SAFE_CACHE_DIR = BASE_DIR / "data/safe_cache" 29 | 30 | 31 | def mode_dependent_cache_dir(year: Optional[int] = None, country: Optional[str] = None): 32 | is_safe_year = year in range(2017, 2021) or year is None 33 | is_safe_country = country in COUNTRIES_FOR_ANALYSIS or country is None 34 | return SAFE_CACHE_DIR if (is_safe_mode() and is_safe_year and is_safe_country) else CACHE_DIR 35 | -------------------------------------------------------------------------------- /elmada/plots.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union 2 | 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | import pandas as pd 6 | from matplotlib.patches import Patch 7 | 8 | from elmada import helper as hp 9 | from elmada import mappings as mp 10 | from elmada.main import get_emissions, get_merit_order, get_residual_load 11 | 12 | 13 | def merit_order( 14 | year=2019, 15 | country="DE", 16 | method="PWL", 17 | ylim_left: Optional[float] = None, 18 | ylim_right: Optional[float] = None, 19 | ax: Optional["plt.axes"] = None, 20 | mo_P: Optional[pd.DataFrame] = None, 21 | include_histo: bool = False, 22 | include_legend: bool = True, 23 | legend_fontsize: float = 9, 24 | **mo_kwargs, 25 | ) -> Tuple[plt.Figure, plt.Axes, plt.Axes]: 26 | """Plot the merit order with the given method.""" 27 | 28 | plt.rcParams.update({"mathtext.default": "regular"}) 29 | plt.rc("text", usetex=False) 30 | plt.rc("font", family="serif") 31 | 32 | if mo_P is None: 33 | mo_P = get_merit_order(year=year, country=country, method=method, **mo_kwargs) 34 | 35 | mo_P = mo_P.copy() # avoids modifying lru_cache 36 | # the unit of mo_P.marginal_emissions is kg 37 | mo_P.cumsum_capa /= 1000 # from MW to GW 38 | mo_P.capa /= 1000 # from MW to GW 39 | 40 | if ax is not None: 41 | fig = ax.get_figure() 42 | else: 43 | fig, ax = plt.subplots(1, figsize=(7, 3)) 44 | 45 | ax_right = ax.twinx() 46 | 47 | color_left = "darkblue" 48 | color_right = "black" 49 | 50 | ax.step( 51 | x=hp.add_row_for_steps(mo_P.cumsum_capa), 52 | y=hp.add_row_for_steps(mo_P.marginal_cost), 53 | where="pre", 54 | label=r"pwl $c_p^m$", 55 | drawstyle="steps", 56 | color=color_left, 57 | linewidth=1.5, 58 | ) 59 | 60 | ax_right.step( 61 | x=hp.add_row_for_steps(mo_P.cumsum_capa), 62 | y=hp.add_row_for_steps(mo_P.marginal_emissions), 63 | where="pre", 64 | label=r"pwl $\varepsilon_p$", 65 | drawstyle="steps", 66 | color=color_right, 67 | linewidth=0.5, 68 | ) 69 | 70 | ax.set_xlabel(r"Net power [GW$_{el}$]") 71 | ax.set_ylabel(r"Marginal cost $c_p^m$" + "\n" + r"[€/$MWh_{el}$]") 72 | ax_right.set_ylabel( 73 | r"Marginal emissions $\varepsilon_p$" + "\n" + r"[$t_{CO_{2}eq}$/$MWh_{el}$]" 74 | ) 75 | 76 | plt.xlim((0, mo_P.cumsum_capa.max())) 77 | ax.set_ylim(bottom=0, top=ylim_left) 78 | ax_right.set_ylim(bottom=0, top=ylim_right) 79 | 80 | ax.yaxis.label.set_color(color_left) 81 | ax.tick_params(axis="y", colors=color_left) 82 | ax.spines["left"].set_color(color_left) 83 | 84 | ax_right.yaxis.label.set_color(color_right) 85 | ax_right.tick_params(axis="y", colors=color_right) 86 | ax_right.spines["right"].set_color(color_right) 87 | 88 | alpha_value_for_fuels = _make_colored_background_for_fuel_types(ax, mo_P) 89 | 90 | if include_legend: 91 | _add_legend(mo_P, legend_fontsize, alpha_value_for_fuels) 92 | 93 | if include_histo: 94 | _add_histo(year, country, ax_right) 95 | 96 | plt.tight_layout() 97 | 98 | return fig, ax, ax_right 99 | 100 | 101 | def _add_histo(year, country, ax_right): 102 | resi = get_residual_load(year=year, country=country) / 1000 103 | n, bins = np.histogram(resi, bins=20, density=True) 104 | ylim = ax_right.get_ylim()[1] 105 | max_loc = 0.15 106 | y = n * max_loc * (ylim / n.max()) 107 | ax_right.fill_between(bins[1:], y, step="post", color="black", alpha=0.5) 108 | 109 | 110 | def _add_legend(mo_P, legend_fontsize, alpha_value_for_fuels): 111 | fuels = mo_P.fuel_draf.unique() 112 | cdict = mp.TECH_COLORS 113 | legend_elements = [ 114 | Patch(facecolor=cdict[f], edgecolor=cdict[f], alpha=alpha_value_for_fuels, label=f) 115 | for f in fuels 116 | ] 117 | leg = plt.legend( 118 | handles=legend_elements, 119 | bbox_to_anchor=(0.0, 1.01, 1.0, 0.102), 120 | loc="lower left", 121 | ncol=6, 122 | mode="expand", 123 | borderaxespad=0.0, 124 | fontsize=legend_fontsize, 125 | framealpha=1.0, 126 | handletextpad=0.5, 127 | edgecolor="inherit", 128 | ) 129 | leg.get_frame().set_linewidth(0.5) 130 | 131 | 132 | def _make_colored_background_for_fuel_types(ax, mo_P): 133 | alpha_value_for_fuels = 0.7 134 | for i, row in mo_P.iterrows(): 135 | ax.axvspan( 136 | row["cumsum_capa"] - row["capa"], 137 | row["cumsum_capa"], 138 | facecolor=mp.TECH_COLORS[row["fuel_draf"]], 139 | linewidth=0, 140 | alpha=alpha_value_for_fuels, 141 | ymin=0, 142 | ymax=1, 143 | ) 144 | return alpha_value_for_fuels 145 | 146 | 147 | def cefs_scatter_plotly( 148 | year: int = 2019, freq: str = "60min", country: str = "DE", method: str = "MEF_PWL", **mo_kwargs 149 | ): 150 | """Optional interactive scatter plot. Works only if Plotly is installed.""" 151 | import plotly.express as px 152 | 153 | first_part, second_part = method.split("_") 154 | 155 | df = get_emissions(year=year, freq=freq, country=country, method=f"_{second_part}", **mo_kwargs) 156 | return px.scatter( 157 | df, y=f"{first_part}s", color="marginal_fuel", color_discrete_map=mp.TECH_COLORS 158 | ) 159 | 160 | 161 | def cefs_scatter( 162 | year=2019, 163 | freq: str = "60min", 164 | country: str = "DE", 165 | method: str = "MEF_PWL", 166 | figsize: Tuple = (8, 3), 167 | **mo_kwargs, 168 | ) -> None: 169 | """Plot the CEFs as scatter plot with colors refering to marginal power plant type.""" 170 | _, second_part = method.split("_") 171 | 172 | df = get_emissions(year=year, freq=freq, country=country, method=f"_{second_part}", **mo_kwargs) 173 | 174 | df = df.reset_index().set_index(["index", "marginal_fuel"])["MEFs"].unstack() 175 | df.index.name = None 176 | df.plot( 177 | marker="o", 178 | markersize=2.5, 179 | figsize=figsize, 180 | linewidth=0, 181 | color=mp.get_tech_colors(df.keys()), 182 | ) 183 | 184 | ax = plt.gca() 185 | ax.set(ylabel="Carbon emission factors\n[$g_{CO_{2}eq}$ / $kWh_{el}$]") 186 | ax.legend(ncol=6, loc="lower center", bbox_to_anchor=(0.5, 1), frameon=True) 187 | 188 | 189 | def _small(s: str): 190 | return f"{s}" 191 | 192 | 193 | def cef_country_map(year: int = 2019, method: str = "XEF_PWL", scope: str = "Europe30"): 194 | """Returns a choropleth figure. Works only if Plotly is installed. 195 | 196 | Args: 197 | year: Year for data 198 | method: One of XEF_EP, XEF_PWLv, MEF_PWL 199 | scope: One of Europe20, Europe30 200 | """ 201 | 202 | from iso3166 import countries 203 | import plotly.express as px 204 | 205 | d = mp.EUROPE30 if scope == "Europe30" else mp.COUNTRIES_FOR_ANALYSIS 206 | df = pd.DataFrame([(k, v) for k, v in d.items()], columns=["iso_alpha2", "country"]) 207 | df["iso_alpha3"] = df.iso_alpha2.apply(lambda x: countries.get(x).alpha3) 208 | df[method] = df.iso_alpha2.apply( 209 | lambda x: get_emissions(year=year, country=x, method=method).mean() 210 | ) 211 | small_adder = _small("(unsupported countries in light blue)") 212 | fig = px.choropleth( 213 | df, 214 | locations="iso_alpha3", 215 | color=method, 216 | hover_name="country", 217 | scope="europe", 218 | color_continuous_scale="OrRd", 219 | labels={method: f"{method}
gCO2eq/kWh"}, 220 | title=f"{year} mean {method} of supported countries
{small_adder}", 221 | width=700, 222 | ) 223 | fig.update_layout( 224 | margin=dict(l=0, r=0, t=0, b=0), 225 | title={"y": 0.12, "x": 0.5, "xanchor": "center", "yanchor": "top"}, 226 | coloraxis_colorbar=dict(xpad=0, x=0.95), 227 | ) 228 | return fig 229 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: elmada 2 | channels: 3 | - conda-forge 4 | - anaconda 5 | dependencies: # NOTE: here '=' (single) 6 | - appdirs 7 | - black 8 | - bs4 9 | - entsoe-py=0.2.10 10 | - ipython 11 | - iso3166 12 | - isort 13 | - lxml 14 | - matplotlib 15 | - mypy 16 | - numpy 17 | - pandas 18 | - pip 19 | - plotly 20 | - pyarrow 21 | - pytest 22 | - pytest-cov 23 | - pytest-mock 24 | - python>=3.7 25 | - quandl 26 | - requests 27 | - scipy 28 | - xlrd 29 | # - notebook 30 | - pip: # NOTE: here '==' (double) 31 | - pytest-responsemock 32 | - '--editable=.[dev]' # installs an editable full version of elmada -------------------------------------------------------------------------------- /paper/codemeta.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://raw.githubusercontent.com/codemeta/codemeta/master/codemeta.jsonld", 3 | "@type": "Code", 4 | "author": [ 5 | { 6 | "@id": "https://orcid.org/0000-0002-8516-9635", 7 | "@type": "Person", 8 | "name": "Markus Fleschutz", 9 | "affiliation": "Department of Process, Energy and Transport Engineering, Munster Technological University" 10 | }, 11 | { 12 | "@id": "https://orcid.org/0000-0002-4269-2581", 13 | "@type": "Person", 14 | "name": "Michael D. Murphy", 15 | "affiliation": "Department of Process, Energy and Transport Engineering, Munster Technological University" 16 | } 17 | ], 18 | "identifier": "http://doi.org/10.5281/zenodo.5566694", 19 | "codeRepository": "https://github.com/DrafProject/elmada", 20 | "datePublished": "2021-07-23", 21 | "dateModified": "2021-07-23", 22 | "dateCreated": "2021-07-23", 23 | "description": "Dynamic electricity carbon emission factors and prices for Europe", 24 | "keywords": "energy market data, energy systems, carbon emission factors, marginal emissions, demand response", 25 | "license": "LGPLv3", 26 | "title": "elmada", 27 | "version": "v0.1.0" 28 | } -------------------------------------------------------------------------------- /paper/paper.bib: -------------------------------------------------------------------------------- 1 | @article{Fleschutz2021, 2 | title = {The effect of price-based demand response on carbon emissions in European electricity markets: The importance of adequate carbon prices}, 3 | author = {Markus Fleschutz and Markus Bohlayer and Marco Braun and Gregor Henze and Michael D. Murphy}, 4 | journal = {Applied Energy}, 5 | volume = {295}, 6 | pages = {117040}, 7 | year = {2021}, 8 | issn = {0306-2619}, 9 | doi = {10.1016/j.apenergy.2021.117040}, 10 | } 11 | 12 | @online{ENTSOE, 13 | author = {{European Network of Transmission System Operators for Electricity (ENTSO-E)}}, 14 | title = {{ENTSOE Transparency Platform}}, 15 | year = 2021, 16 | url = {https://transparency.entsoe.eu}, 17 | urldate = {2021-07-10}, 18 | } 19 | 20 | @online{ElmadaGitHub, 21 | title = {Elmada}, 22 | author = {Fleschutz, Markus}, 23 | year = 2021, 24 | url = {https://github.com/DrafProject/elmada}, 25 | urldate = {2021-07-23} 26 | } 27 | 28 | @misc{ElmadaZenodo, 29 | title = {Elmada}, 30 | author = {Fleschutz, Markus}, 31 | year = 2021, 32 | doi = {10.5281/zenodo.5566694} 33 | } 34 | 35 | @online{ElecMapApi, 36 | year = {2021}, 37 | author = {{Tomorrow}}, 38 | title = {{electricityMap API}}, 39 | urldate = {2021-07-10}, 40 | url = {https://api.electricitymap.org/} 41 | } 42 | 43 | @online{WattTime, 44 | year = 2021, 45 | author = {{WattTime}}, 46 | title = {Automated Emissions Reduction}, 47 | urldate = {2021-07-10}, 48 | url = {https://www.watttime.org/aer/} 49 | } 50 | 51 | 52 | @article{Prina2020, 53 | doi = {10.1016/j.rser.2020.109917}, 54 | url = {https://doi.org/10.1016/j.rser.2020.109917}, 55 | year = {2020}, 56 | publisher = {Elsevier {BV}}, 57 | volume = {129}, 58 | pages = {109917}, 59 | author = {Matteo Giacomo Prina and Giampaolo Manzolini and David Moser and Benedetto Nastasi and Wolfram Sparber}, 60 | title = {Classification and challenges of bottom-up energy system models - A review}, 61 | journal = {Renewable and Sustainable Energy Reviews} 62 | } 63 | 64 | @article{Hawkes2010, 65 | doi = {10.1016/j.enpol.2010.05.053}, 66 | author = {Hawkes, A. D.}, 67 | year = {2010}, 68 | title = {Estimating marginal {CO2} emissions rates for national electricity systems}, 69 | keywords = {CO2;electricity;marginal}, 70 | pages = {5977--5987}, 71 | volume = {38}, 72 | number = {10}, 73 | issn = {03014215}, 74 | journal = {Energy Policy} 75 | } 76 | 77 | @article{Tranberg2019, 78 | author = {Tranberg, Bo and Corradi, Olivier and Lajoie, Bruno and Gibon, Thomas and Staffell, Iain and Andresen, Gorm Bruun}, 79 | year = {2019}, 80 | title = {{Real-time carbon accounting method for the European electricity markets}}, 81 | keywords = {Carbon accounting;Carbon emission;Carbon intensity;Flow tracing}, 82 | pages = {100367}, 83 | volume = {26}, 84 | issn = {2211467X}, 85 | journal = {{Energy Strategy Reviews}}, 86 | doi = {10.1016/j.esr.2019.100367}, 87 | } -------------------------------------------------------------------------------- /paper/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'elmada: Dynamic electricity carbon emission factors and prices for Europe' 3 | tags: 4 | - energy 5 | - electricity market 6 | - carbon emissions 7 | - marginal emissions 8 | - python 9 | authors: 10 | - name: Markus Fleschutz 11 | orcid: 0000-0002-8516-9635 12 | affiliation: 1, 2 13 | - name: Michael D. Murphy 14 | orcid: 0000-0002-4269-2581 15 | affiliation: 1 16 | affiliations: 17 | - name: Department of Process, Energy and Transport Engineering, Munster Technological University 18 | index: 1 19 | - name: Institute of Refrigeration, Air-Conditioning, and Environmental Engineering, Karlsruhe University of Applied Sciences 20 | index: 2 21 | date: 23 July 2021 22 | bibliography: paper.bib 23 | --- 24 | 25 | # Summary 26 | 27 | The expansion of intermittent renewable energy sources such as solar and wind requires increased operational flexibility in electricity systems. 28 | Energy system models at the scale of individual decentral energy hubs can help decision-makers of energy hubs such as city quarters or industrial sites evaluate the cost and carbon emission saving potentials of their flexibility. 29 | For national scale models, the carbon emissions of the electricity supply system are endogenously determined. 30 | However, low-level models (at the scale of decentral energy hubs) need this information as input. 31 | Since specific carbon emissions of national electricity supply systems fluctuate hourly, the usage of dynamic (i.e. at least hourly resolved) carbon emission factors (CEFs) is essential [@Prina2020]. 32 | 33 | `elmada` is an easy-to-use open-source Python package designed to provide dynamic electricity CEFs and prices for European countries. 34 | The target group includes modelers of distributed energy hubs who need electricity market data. 35 | This is where the name **elmada** comes from: **el**ectricity **ma**rket **da**ta. 36 | `elmada` is developed in the open on GitHub [@ElmadaGitHub]. 37 | Each release is archived on Zenodo [@ElmadaZenodo]. 38 | 39 | # Statement of Need 40 | 41 | Dynamic CEFs are important for the environmental assessment of electricity supply in not fully decarbonized energy systems. 42 | To the best of the authors' knowledge, `elmada` is the first free and open-source Python interface for dynamic CEFs in Europe. 43 | This makes `elmada` an important complement to existing commercial services. 44 | 45 | At the moment, there are two main commercial services that provide an Application Programming Interface (API) for historical dynamic CEFs: the `electricityMap` API [@ElecMapApi] and the Automated Emissions Reduction from WattTime [@WattTime]. 46 | The `electricityMap` is maintained by Tomorrow, a startup based in Denmark, and WattTime is a nonprofit organization in the USA. 47 | However, both focus on real-time CEFs as incentive signals for demand response answering the question "How clean is my electricity right now?". 48 | We elaborate more on `electricityMap` here, as they originate in Europe, which is also the focus of `elmada`. 49 | The services of WattTime are broadly similar. 50 | 51 | `electricityMap` is a software project that visualizes the carbon emission intensity linked to the generation and consumption of electricity on a global choropleth map. 52 | Additionally, the `electricityMap` API provides historical, real-time (current hour), forecast, and since recently also marginal data. The calculation methods consider international energy exchanges and the fact that the list of data sources is curated by Tomorrow (the company behind it) makes it save-to-use as a live incentive signal e.g. for carbon-based demand response applications. 53 | However, the use of `electricityMap` API requires a data-dependent payment even for the historic data, so it is not free of charge. 54 | 55 | There are two types of dynamic CEFs: 56 | 57 | * grid-mix emission factors (XEFs), which represent the emission intensity based on the current generation mix of the electricity system, 58 | * and marginal emission factors (MEFs), which quantify the emission intensity of the generators likely to react to a marginal system change. 59 | 60 | Currently, there is no multi-national solution for modelers of decentral energy hubs searching for free historical hourly CEFs (in particular MEFs). 61 | This gap often leads to the usage of yearly average CEFs, which are potentially misleading [@Hawkes2010]. 62 | We close this gap by providing the conveniently installable Python package `elmada` that calculates XEFs and MEFs in hourly (or higher) resolution for 30 European countries for free. 63 | `elmada` provides modelers a no-regret (free) entry point to European dynamic CEFs leaving it open to the modeler to later switch to a paid service that generate more accurate CEFs, e.g. through advanced methods such as the consideration of cross-border energy flows through flow tracing [@Tranberg2019]. 64 | 65 | # Functionality 66 | 67 | `elmada` calculates both types of dynamic CEFs: XEFs and MEFs. 68 | MEFs are more challenging to approximate than XEFs since MEFs require the identification of the marginal power plants per time step. 69 | In `elmada`, this is done through a merit order simulation within the power plant (PP) and piecewise linear (PWL) method described in [@Fleschutz2021]. 70 | 71 | Also, historical and simulated day-ahead electricity market prices are provided. 72 | They can be used either for the economic evaluation of electricity demands or to model the incentive signal of price-based demand response. 73 | 74 | Currently, `elmada` provides data for 30 European countries and for each year since 2017. 75 | `elmada` works mainly with data from the ENTSO-E Transparency Platform [@ENTSOE]. 76 | 77 | # Current and Future Usage 78 | 79 | So far, `elmada` has been used in a study where MEFs, XEFs and the results of load shift simulations based on them are compared across 20 European countries [@Fleschutz2021]. 80 | In ongoing research, `elmada` is used to quantify the costs and emission-saving potentials that arise from the exploitation of existing and future flexibility in decentral energy hubs. 81 | 82 | We hope that `elmada` reduces the difficulty associated with the use of dynamic CEFs and prices in the modeling of decentral energy systems. 83 | 84 | # Acknowledgements 85 | 86 | The author acknowledges funding by the MTU Risam scholarship scheme and the German Federal Ministry of the Environment, Nature Conservation and Nuclear Safety (BMU) via the project WIN4Climate (No. 03KF0094 A) as part of the National Climate Initiative. 87 | 88 | # References 89 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | skip-magic-trailing-comma = true 4 | exclude = ''' 5 | /( 6 | \.eggs 7 | | \.git 8 | | \.github 9 | | \.mypy_cache 10 | | \.pytest_cache 11 | | \.vscode 12 | | .*\.egg-info 13 | # directories without python source files 14 | | doc 15 | )/ 16 | ''' 17 | 18 | [tool.isort] 19 | line_length = 100 20 | include_trailing_comma = true 21 | profile = "black" 22 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = 3 | --strict-markers 4 | --cov=elmada 5 | --cov-report=term-missing 6 | ; --cov-report=html 7 | ; -m="not apikey" 8 | markers = 9 | apikey: marks tests that are dependent on an api key 10 | wantcache: marks test that are fast when CEFs are already cached 11 | python_files = test_*.py 12 | python_functions = test_* 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from setuptools import find_packages, setup 4 | 5 | long_description = Path("README.md").read_text().strip() 6 | 7 | setup( 8 | name="elmada", 9 | version="0.1.1", 10 | author="Markus Fleschutz", 11 | author_email="mfleschutz@gmail.com", 12 | description="Dynamic electricity carbon emission factors and prices for Europe", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | url="https://github.com/DrafProject/elmada", 16 | license="LGPLv3", 17 | packages=find_packages(exclude=["doc", "tests"]), 18 | python_requires=">=3.7", 19 | install_requires=[ 20 | "appdirs", 21 | "beautifulsoup4", 22 | "entsoe-py", 23 | "ipython", 24 | "lxml", 25 | "matplotlib", 26 | "numpy", 27 | "pandas", 28 | "pyarrow", 29 | "quandl", 30 | "requests", 31 | "scipy", 32 | "xlrd", 33 | ], 34 | extras_require={ 35 | "dev": [ 36 | "black", 37 | "iso3166", 38 | "isort", 39 | "mypy", 40 | "plotly", 41 | "pytest-cov", 42 | "pytest-mock", 43 | "pytest-responsemock", 44 | "pytest", 45 | ] 46 | }, 47 | include_package_data=True, 48 | package_data={"elmada": ["*.parquet", "*.csv", "*.txt", "*xls"]}, 49 | classifiers=[ 50 | "Development Status :: 4 - Beta", 51 | "Intended Audience :: Developers", 52 | "Intended Audience :: Science/Research", 53 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", 54 | "Natural Language :: English", 55 | "Operating System :: OS Independent", 56 | "Programming Language :: Python :: 3 :: Only", 57 | "Programming Language :: Python :: 3", 58 | "Topic :: Scientific/Engineering :: Information Analysis", 59 | "Topic :: Scientific/Engineering", 60 | "Topic :: Software Development :: Libraries :: Python Modules", 61 | ], 62 | keywords=["energy market data", "energy systems", "carbon emission factors", "demand response"], 63 | ) 64 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/tests/__init__.py -------------------------------------------------------------------------------- /tests/common/CEF_hashes.csv: -------------------------------------------------------------------------------- 1 | year,country,hash 2 | 2017,AT,1bb9addc1e 3 | 2017,BE,4042e619ec 4 | 2017,CZ,b0e2ce1686 5 | 2017,DE,d647ed210e 6 | 2017,DK,5a7ac0c19f 7 | 2017,ES,8d45a6ad21 8 | 2017,FI,ecf2892b92 9 | 2017,FR,73655f4685 10 | 2017,GB,3d1bef9034 11 | 2017,GR,c38a72b989 12 | 2017,HU,98f9b22f69 13 | 2017,IE,9d4c3084b7 14 | 2017,IT,0744c6f60a 15 | 2017,LT,a0082eb6e7 16 | 2017,NL,513b6287a2 17 | 2017,PL,913ce0c500 18 | 2017,PT,cb277b0911 19 | 2017,RO,0510717cc7 20 | 2017,RS,a593272138 21 | 2017,SI,fef58f4333 22 | 2018,AT,775d6aca97 23 | 2018,BE,5431c95125 24 | 2018,CZ,710cf6391a 25 | 2018,DE,fb0e86ab4a 26 | 2018,DK,f1172f4451 27 | 2018,ES,22b11c96e8 28 | 2018,FI,d6b1c5ad37 29 | 2018,FR,8e0b03e29d 30 | 2018,GB,1b0102e608 31 | 2018,GR,c3a01d7511 32 | 2018,HU,d27a910ce5 33 | 2018,IE,b7ebc7cc45 34 | 2018,IT,a7f60936fd 35 | 2018,LT,bccc1e5351 36 | 2018,NL,a4cf092415 37 | 2018,PL,6177bf9ff0 38 | 2018,PT,fe1b845335 39 | 2018,RO,adc088da0d 40 | 2018,RS,876c5c640a 41 | 2018,SI,86cabddf10 42 | 2019,AT,927088e3a5 43 | 2019,BE,666e86c022 44 | 2019,CZ,6001e05ad0 45 | 2019,DE,dc18650c05 46 | 2019,DK,99f24a6bca 47 | 2019,ES,3e00c63778 48 | 2019,FI,fe2ebc8e3e 49 | 2019,FR,2e1a7bea03 50 | 2019,GB,3849b8d15c 51 | 2019,GR,f1e93c0074 52 | 2019,HU,ae3e0e290d 53 | 2019,IE,1519d1a542 54 | 2019,IT,2357510854 55 | 2019,LT,1bc9360d8d 56 | 2019,NL,5ca410f7cb 57 | 2019,PL,93328aed5e 58 | 2019,PT,1c34a3f551 59 | 2019,RO,7ca2717477 60 | 2019,RS,2e526053e4 61 | 2019,SI,11811b7578 62 | 2020,AT,ac5fede203 63 | 2020,BE,bc23ed0bae 64 | 2020,CZ,83689b14ce 65 | 2020,DE,140ff5e062 66 | 2020,DK,7fc5af282d 67 | 2020,ES,5b529815f1 68 | 2020,FI,e527a9d374 69 | 2020,FR,71aeb8b006 70 | 2020,GB,fbd87e0351 71 | 2020,GR,b913a22b6e 72 | 2020,HU,3636284fb5 73 | 2020,IE,ae747993c0 74 | 2020,IT,07af4beb52 75 | 2020,LT,ba582d8030 76 | 2020,NL,4a1de84d1e 77 | 2020,PL,02c5d8b6bc 78 | 2020,PT,6579b5ad75 79 | 2020,RO,8486012d96 80 | 2020,RS,60bfe300f7 81 | 2020,SI,b1884fb6db 82 | -------------------------------------------------------------------------------- /tests/common/GEN_hashes.csv: -------------------------------------------------------------------------------- 1 | year,country,hash 2 | 2017,AT,fe66313ddf 3 | 2017,BE,808cd31201 4 | 2017,CZ,9f32abf640 5 | 2017,DE,b9b9582772 6 | 2017,DK,8c5382a651 7 | 2017,ES,68d0782345 8 | 2017,FI,bb0e6e1971 9 | 2017,FR,19deac2f57 10 | 2017,GB,221225874e 11 | 2017,GR,3330bf31fe 12 | 2017,HU,461a4e27c5 13 | 2017,IE,04861e5a83 14 | 2017,IT,1aee7f632d 15 | 2017,LT,b0a8f5d14c 16 | 2017,NL,12cd28396e 17 | 2017,PL,a7d9d0cce4 18 | 2017,PT,0e93cd9014 19 | 2017,RO,66c337624d 20 | 2017,RS,0e94b1076e 21 | 2017,SI,af86e269d6 22 | 2018,AT,a37190c476 23 | 2018,BE,c0324fe7b0 24 | 2018,CZ,a67879b56e 25 | 2018,DE,c2bdf939bf 26 | 2018,DK,45b6ad6c54 27 | 2018,ES,eb7b8b4ba8 28 | 2018,FI,9e886a535b 29 | 2018,FR,5aacaba48a 30 | 2018,GB,2600752851 31 | 2018,GR,97a42a9fcb 32 | 2018,HU,123e5031db 33 | 2018,IE,259874686e 34 | 2018,IT,69ed26e5ac 35 | 2018,LT,9b92905b1c 36 | 2018,NL,8c4310b299 37 | 2018,PL,32f055e886 38 | 2018,PT,d9fd771d52 39 | 2018,RO,4bbb6f554a 40 | 2018,RS,536dad0844 41 | 2018,SI,bde77fe111 42 | 2019,AT,568716c069 43 | 2019,BE,487519cfe3 44 | 2019,CZ,329f5235fd 45 | 2019,DE,e7384eadcf 46 | 2019,DK,23c2306a92 47 | 2019,ES,41aea05fa1 48 | 2019,FI,705ad8362e 49 | 2019,FR,29c9aac986 50 | 2019,GB,717da182c7 51 | 2019,GR,db8c98c92a 52 | 2019,HU,fb78d0a53d 53 | 2019,IE,fd7276fa80 54 | 2019,IT,9107b933ba 55 | 2019,LT,f738f0d6e6 56 | 2019,NL,47a4fa07a9 57 | 2019,PL,47a986b698 58 | 2019,PT,5bdcbaa107 59 | 2019,RO,bab341bcbf 60 | 2019,RS,c0db6df1af 61 | 2019,SI,6d4591ea55 62 | 2020,AT,8813bbb825 63 | 2020,BE,e8dab4bb36 64 | 2020,CZ,e0d3485da4 65 | 2020,DE,819a8148c6 66 | 2020,DK,768f6eac33 67 | 2020,ES,ba0a4f6ee1 68 | 2020,FI,f5eaecbd3b 69 | 2020,FR,00e513ee6e 70 | 2020,GB,83ac1a8f1b 71 | 2020,GR,d112f819b7 72 | 2020,HU,b7f758b8eb 73 | 2020,IE,bda2d42e84 74 | 2020,IT,3dc716dbda 75 | 2020,LT,3c5328fd71 76 | 2020,NL,3ab5aab28e 77 | 2020,PL,6d05f76515 78 | 2020,PT,98657b7329 79 | 2020,RO,ce2416fb8b 80 | 2020,RS,2fcfda9ed5 81 | 2020,SI,03133e7a1b 82 | -------------------------------------------------------------------------------- /tests/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrafProject/elmada/9af3cf4496ea10ff74e087283974813d6341eade/tests/common/__init__.py -------------------------------------------------------------------------------- /tests/common/averages.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pandas as pd 4 | 5 | import elmada 6 | from elmada import mappings as mp 7 | 8 | MYDIR = Path(__file__).resolve().parent 9 | 10 | 11 | def make_avgs() -> pd.DataFrame: 12 | return pd.Series( 13 | { 14 | (str(year), country, cef_type): int( 15 | elmada.get_emissions(year=year, country=country, method=f"{cef_type}_PWL").mean() 16 | ) 17 | for year in range(2017, 2021) 18 | for country in mp.COUNTRIES_FOR_ANALYSIS 19 | for cef_type in ("MEF", "XEF") 20 | }, 21 | ).unstack([0]) 22 | 23 | 24 | def save_expected_avgs() -> None: 25 | fp = MYDIR / "avgs.csv" 26 | if input("Do you really want to overwrite the data average table? (y/n)") == "y": 27 | make_avgs().to_csv(fp, header=True) 28 | else: 29 | print("Aborted.") 30 | 31 | 32 | def read_expected_avgs() -> pd.Series: 33 | fp = MYDIR / "avgs.csv" 34 | return pd.read_csv(fp, index_col=[0, 1]) 35 | -------------------------------------------------------------------------------- /tests/common/avgs.csv: -------------------------------------------------------------------------------- 1 | ,,2017,2018,2019,2020 2 | AT,MEF,591,574,550,501 3 | AT,XEF,145,131,139,125 4 | BE,MEF,448,260,473,449 5 | BE,XEF,88,35,103,69 6 | CZ,MEF,995,985,979,964 7 | CZ,XEF,399,407,395,355 8 | DE,MEF,871,826,890,896 9 | DE,XEF,416,415,349,342 10 | DK,MEF,791,821,796,790 11 | DK,XEF,234,266,191,186 12 | ES,MEF,569,559,624,681 13 | ES,XEF,272,233,270,199 14 | FI,MEF,731,672,724,693 15 | FI,XEF,143,159,149,100 16 | FR,MEF,74,62,59,5 17 | FR,XEF,2,2,1,0 18 | GB,MEF,700,660,586,626 19 | GB,XEF,298,252,209,208 20 | GR,MEF,807,867,918,889 21 | GR,XEF,718,679,667,620 22 | HU,MEF,710,780,726,709 23 | HU,XEF,349,317,329,315 24 | IE,MEF,536,535,568,495 25 | IE,XEF,423,398,352,351 26 | IT,MEF,505,497,483,494 27 | IT,XEF,384,348,354,366 28 | LT,MEF,728,723,726,743 29 | LT,XEF,252,195,223,323 30 | NL,MEF,614,573,530,533 31 | NL,XEF,448,417,524,525 32 | PL,MEF,840,826,880,877 33 | PL,XEF,808,820,785,766 34 | PT,MEF,587,595,610,620 35 | PT,XEF,397,328,348,314 36 | RO,MEF,1110,1112,1059,1064 37 | RO,XEF,454,423,407,362 38 | RS,MEF,1114,1104,1111,1130 39 | RS,XEF,757,714,744,766 40 | SI,MEF,1010,936,974,992 41 | SI,XEF,295,256,248,261 42 | -------------------------------------------------------------------------------- /tests/common/geo_test_list.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | List of Gas PowerPlants - GEO 12 | 13 | 16 | 17 | 18 | 19 | 20 | GEO Home 22 |
23 | 24 |
25 |

Current List of Gas PowerPlants

26 | 27 | 28 | 31 | 34 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
29 |

30 |
32 |

33 |
35 |

36 |
38 |

39 |
Northwest Kabul Power Plant Afghanistan
TEC Vlora CCGT Power Plant Albania
54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /tests/common/hasher.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from pathlib import Path 3 | 4 | import pandas as pd 5 | from numpy.core.arrayprint import str_format 6 | 7 | import elmada 8 | from elmada import mappings as mp 9 | 10 | HASH_DIR = Path(__file__).resolve().parent 11 | FILE_FMT = "{which}_hashes.csv" 12 | 13 | 14 | def read_expected_hashes(which: str) -> pd.Series: 15 | fp = HASH_DIR / FILE_FMT.format(which=which) 16 | return pd.read_csv(fp, index_col=[0, 1]).squeeze() 17 | 18 | 19 | def save_expected_hashes(which: str) -> None: 20 | fp = HASH_DIR / FILE_FMT.format(which=which) 21 | if input("Do you really want to overwrite the data hash table? (y/n)") == "y": 22 | make_hashes(which=which).to_csv(fp, header=True) 23 | else: 24 | print("Aborted.") 25 | 26 | 27 | def make_hashes(which) -> pd.Series: 28 | 29 | if which == "CEF": 30 | func = elmada.get_emissions 31 | kwargs = dict(freq="60min", method="_PWL", cache=True) 32 | 33 | elif which == "GEN": 34 | func = elmada.get_el_national_generation 35 | kwargs = dict(freq="60min") 36 | 37 | else: 38 | raise ValueError("`which` must be 'CEF' or 'GEN'.") 39 | 40 | return make_year_country_hashes(func=func, kwargs=kwargs) 41 | 42 | 43 | def make_year_country_hashes(func, kwargs) -> pd.Series: 44 | ser = pd.Series( 45 | { 46 | (year, country): get_hash(func(year=year, country=country, **kwargs)) 47 | for year in range(2017, 2021) 48 | for country in mp.COUNTRIES_FOR_ANALYSIS 49 | }, 50 | name="hash", 51 | ) 52 | ser.index.names = ["year", "country"] 53 | return ser 54 | 55 | 56 | def get_hash(df) -> str_format: 57 | return hashlib.sha256(pd.util.hash_pandas_object(df, index=True).values).hexdigest()[:10] 58 | -------------------------------------------------------------------------------- /tests/test_cc_share.py: -------------------------------------------------------------------------------- 1 | import collections 2 | from pathlib import Path 3 | 4 | import pandas as pd 5 | 6 | from elmada import cc_share 7 | 8 | 9 | def test_get_ccgt_shares_from_cascade(): 10 | df = cc_share.get_ccgt_shares_from_cascade() 11 | assert isinstance(df, pd.DataFrame) 12 | 13 | 14 | def test_get_wiki_shares(): 15 | result = cc_share.get_wiki_shares(cache=False) 16 | assert isinstance(result, pd.Series) 17 | 18 | 19 | def get_geo_shares_overview(): 20 | result = cc_share.get_geo_shares_overview() 21 | assert isinstance(result, pd.DataFrame) 22 | 23 | 24 | def test_get_valid_countries(): 25 | result = cc_share.get_valid_countries() 26 | assert isinstance(result, collections.OrderedDict) 27 | 28 | 29 | def test_get_geo_list(): 30 | result = cc_share.get_geo_list(cache=True) 31 | assert isinstance(result, pd.DataFrame) 32 | 33 | 34 | def _read_fake_geo_body(): 35 | fp = Path(__file__).parent / "common/geo_test_list.html" 36 | return fp.read_text() 37 | 38 | 39 | def test__scrape_geo_list(response_mock): 40 | url = "http://globalenergyobservatory.org/list.php?db=PowerPlants&type=Gas" 41 | fake_geo_body = _read_fake_geo_body() 42 | with response_mock(f"GET {url} -> 200 :{fake_geo_body}"): 43 | df = cc_share.get_geo_list(cache=False) 44 | assert isinstance(df, pd.DataFrame) 45 | assert df.loc[1, "country"] == "Albania" 46 | print(df) 47 | 48 | 49 | def test_get_geo_shares_overview(): 50 | cc_share.get_geo_shares_overview() 51 | -------------------------------------------------------------------------------- /tests/test_data.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from .common import averages, hasher 4 | 5 | 6 | def make_and_check_hashes(which: str) -> bool: 7 | current = hasher.make_hashes(which=which) 8 | expected = hasher.read_expected_hashes(which=which) 9 | return current.equals(expected) 10 | 11 | 12 | # @pytest.mark.wantcache 13 | # def test_emissions_data(): 14 | # assert make_and_check_hashes("CEF") 15 | 16 | 17 | def test_get_el_national_generation(): 18 | assert make_and_check_hashes("GEN") 19 | 20 | 21 | def test_averages(): 22 | current = averages.make_avgs() 23 | expected = averages.read_expected_avgs() 24 | assert current.equals(expected) 25 | -------------------------------------------------------------------------------- /tests/test_eu_pwl.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | 4 | from elmada import eu_pwl 5 | 6 | 7 | def test_prep_prices(mocker): 8 | mock = mocker.patch("elmada.eu_pwl.prep_CEFs", return_value={"marginal_cost": 42}) 9 | result = eu_pwl.prep_prices(year=2019, freq="60min", country="DE") 10 | assert result == 42 11 | mock.assert_called_once_with(year=2019, freq="60min", country="DE") 12 | 13 | 14 | @pytest.mark.wantcache 15 | def test_prep_CEFs(mocker): 16 | mock = mocker.patch("elmada.from_opsd.get_CEFs_from_merit_order") 17 | eu_pwl.prep_CEFs(year=2019, freq="60min", country="DE") 18 | mock.assert_called_once() 19 | 20 | 21 | def test_approximate_min_max_values_raises_error(): 22 | result = eu_pwl.approximate_min_max_values(method="interp") 23 | assert isinstance(result, tuple) 24 | 25 | with pytest.raises(ValueError): 26 | eu_pwl.approximate_min_max_values(method="this_method_is_not_implemented") 27 | 28 | 29 | def test_prep_installed_generation_capacity(): 30 | with pytest.raises(ValueError): 31 | eu_pwl.prep_installed_generation_capacity(source="this_method_is_not_implemented") 32 | 33 | 34 | def test_get_pp_size(): 35 | with pytest.raises(ValueError): 36 | eu_pwl.get_pp_size( 37 | pp_size_method="this_method_is_not_implemented", country="DE", fuel="gas" 38 | ) 39 | 40 | 41 | def test_get_pp_sizes_from_germany(): 42 | result = eu_pwl.get_pp_sizes_from_germany() 43 | assert isinstance(result, pd.Series) 44 | -------------------------------------------------------------------------------- /tests/test_from_entsoe.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import pytest 4 | from entsoe import EntsoePandasClient 5 | 6 | from elmada import exceptions, from_entsoe 7 | from elmada import helper as hp 8 | from elmada import mappings as mp 9 | from elmada import paths 10 | 11 | country_resi_means = [ 12 | ("AT", 1791), 13 | ("BE", 8285), 14 | ("CZ", 8143), 15 | ("DE", 31805), 16 | ("DK", 764), 17 | ("ES", 17111), 18 | ("FI", 4181), 19 | ("FR", 49036), 20 | ("GB", 20016), 21 | ("GR", 3244), 22 | ("HU", 3157), 23 | ("IE", 1560), 24 | ("IT", 18920), 25 | ("LT", 131), 26 | ("NL", 9014), 27 | ("PL", 14572), 28 | ("PT", 2804), 29 | ("RO", 3934), 30 | ("RS", 2786), 31 | ("SI", 1154), 32 | ] 33 | 34 | 35 | @pytest.mark.parametrize("country,mean", country_resi_means) 36 | def test_prep_residual_load(country, mean): 37 | assert mean == pytest.approx( 38 | from_entsoe.prep_residual_load(year=2019, country=country).mean(), 0.01 39 | ) 40 | 41 | 42 | def test_prep_residual_load_with_conv2(mocker): 43 | mock = mocker.patch("elmada.from_entsoe.get_conv2_generation") 44 | from_entsoe.prep_residual_load(year=2019, country="DE", method="conv2") 45 | mock.assert_called_once() 46 | 47 | 48 | def test_prep_residual_load_method_not_implemented(): 49 | with pytest.raises(ValueError): 50 | from_entsoe.prep_residual_load( 51 | year=2019, country="DE", method="this_method_is_not_implemented" 52 | ) 53 | 54 | 55 | def test_prep_XEFs(): 56 | result = from_entsoe.prep_XEFs(year=2019, freq="60min", country="DE") 57 | assert isinstance(result, pd.DataFrame) 58 | 59 | 60 | @pytest.mark.apikey 61 | def test_load_installed_generation_capacity(): 62 | result = from_entsoe.load_installed_generation_capacity(year=2019, country="DE", cache=False) 63 | assert isinstance(result, pd.DataFrame) 64 | 65 | 66 | def test_prep_dayahead_prices(mocker): 67 | dummy = pd.Series(30, index=range(8760)) 68 | dummy[2] = np.nan 69 | dummy[5] = 5000.0 70 | dummy.index = hp.make_datetimeindex(year=2019, freq="60min", tz="Europe/Berlin") 71 | assert isinstance(dummy.index, pd.DatetimeIndex) 72 | 73 | mocker.patch("elmada.from_entsoe._query_day_ahead_prices", return_value=dummy) 74 | result = from_entsoe.prep_dayahead_prices(year=2019, freq="60min", country="DE", cache=False) 75 | 76 | assert isinstance(result, pd.Series) 77 | assert result[2] == 30 78 | assert result[5] == 30 79 | assert isinstance(result.index, pd.RangeIndex) 80 | 81 | 82 | def test_get_empty_installed_generation_capacity_df(): 83 | result = from_entsoe._get_empty_installed_generation_capacity_df(ensure_std_techs=True) 84 | expected = pd.DataFrame(index=mp.DRAF_FUELS, columns=[""]).T 85 | assert result.equals(expected) 86 | 87 | with pytest.raises(exceptions.NoDataError): 88 | from_entsoe._get_empty_installed_generation_capacity_df(ensure_std_techs=False) 89 | 90 | 91 | def test_get_conv2_generation(): 92 | result = from_entsoe.get_conv2_generation(year=2019, freq="60min", country="DE") 93 | assert isinstance(result, pd.Series) 94 | 95 | 96 | def test_get_renewable_generation(): 97 | result = from_entsoe.get_renewable_generation(year=2019, freq="60min", country="DE") 98 | assert isinstance(result, pd.Series) 99 | 100 | 101 | @pytest.mark.apikey 102 | def test_load_el_national_generation(mocker): 103 | df = hp.read(paths.SAFE_CACHE_DIR / "2019_DE_gen_entsoe.parquet") 104 | mock = mocker.patch("entsoe.EntsoePandasClient.query_generation", return_value=df) 105 | result = from_entsoe.load_el_national_generation( 106 | year=2019, country="DE", freq="15min", cache=False, split_queries=False 107 | ) 108 | mock.assert_called() 109 | assert isinstance(result, pd.DataFrame) 110 | 111 | 112 | def test_get_bidding_zone(): 113 | assert from_entsoe.get_bidding_zone(country="AT", year=2017) == "DE-AT-LU" 114 | 115 | 116 | def test_query_day_ahead_prices(mocker): 117 | mocker.patch("elmada.from_entsoe._get_client", return_value=EntsoePandasClient) 118 | mock = mocker.patch("entsoe.EntsoePandasClient.query_day_ahead_prices") 119 | from_entsoe._query_day_ahead_prices( 120 | 2019, bidding_zone=from_entsoe.get_bidding_zone(country="DE", year=2019) 121 | ) 122 | mock.assert_called_once() 123 | 124 | 125 | def test__query_generation(mocker): 126 | mocker.patch("elmada.from_entsoe._get_client", return_value=EntsoePandasClient) 127 | mock = mocker.patch("entsoe.EntsoePandasClient.query_generation", return_value=pd.DataFrame()) 128 | from_entsoe._query_generation(year=2019, country="DE", split_queries=False) 129 | mock.assert_called_once() 130 | 131 | mock = mocker.patch("entsoe.EntsoePandasClient.query_generation", return_value=pd.DataFrame()) 132 | from_entsoe._query_generation(year=2019, country="DE", split_queries=True) 133 | assert mock.call_count == 12 134 | -------------------------------------------------------------------------------- /tests/test_from_geo_scraped.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from elmada import from_geo_scraped 4 | 5 | from .common.hasher import get_hash 6 | 7 | 8 | def test_get_pp_sizes_for_pwl(): 9 | df = from_geo_scraped.get_pp_sizes_for_pwl() 10 | assert get_hash(df) == "f8ea5251f2" 11 | 12 | 13 | def test_get_pp_sizes(): 14 | df = from_geo_scraped.get_pp_sizes() 15 | assert get_hash(df) == "fcf6b2ba28" 16 | 17 | 18 | def test_get_units_of_geo_list(): 19 | df = from_geo_scraped.get_units_of_geo_list(cache=True) 20 | expected = pd.Index( 21 | ["cy", "fuel", "geoid", "capa", "eff", "commissioned", "unit_no"], dtype="object" 22 | ) 23 | assert df.keys().equals(expected) 24 | 25 | 26 | def test__query_geo_power_plant_data(mocker): 27 | mock = mocker.patch("elmada.from_geo_scraped.get_df_from_geo_id", return_value=pd.DataFrame()) 28 | from_geo_scraped._query_geo_power_plant_data() 29 | mock.assert_called() 30 | 31 | 32 | def test_get_df_from_geo_id(): 33 | # NOTE: Requires internet connection 34 | df = from_geo_scraped.get_df_from_geo_id(45151) 35 | assert get_hash(df) == "f6be9a1b2b" 36 | -------------------------------------------------------------------------------- /tests/test_from_geo_via_morph.py: -------------------------------------------------------------------------------- 1 | from elmada import from_geo_via_morph, paths 2 | 3 | 4 | def test_download_database(mocker): 5 | mock = mocker.patch("elmada.helper.get_api_key", return_value="123") 6 | mock = mocker.patch("requests.get") 7 | type(mock.return_value).content = mocker.PropertyMock(return_value="xx".encode()) 8 | fp = paths.CACHE_DIR / "test_file.csv" 9 | from_geo_via_morph.download_database(fp=fp) 10 | assert fp.exists() 11 | fp.unlink() 12 | mock.assert_called_once() 13 | -------------------------------------------------------------------------------- /tests/test_from_opsd.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | from pytest_mock import MockerFixture 4 | 5 | import elmada 6 | from elmada import from_opsd 7 | from elmada import helper as hp 8 | from elmada import paths 9 | from elmada.mode import ConfigUtil 10 | 11 | 12 | def test_prep_prices(mocker): 13 | mock = mocker.patch("elmada.from_opsd.prep_CEFs", return_value={"marginal_cost": 42}) 14 | assert from_opsd.prep_prices(year=2019, freq="60min", country="DE") == 42 15 | mock.assert_called_once_with(year=2019, freq="60min", country="DE") 16 | 17 | 18 | def test_prep_CEFs(): 19 | cefs = from_opsd.prep_CEFs(year=2019, freq="60min", country="DE") 20 | 21 | expected = pd.Index( 22 | data=[ 23 | "residual_load", 24 | "total_load", 25 | "marginal_fuel", 26 | "efficiency", 27 | "marginal_cost", 28 | "MEFs", 29 | "XEFs", 30 | ], 31 | dtype="object", 32 | ) 33 | assert cefs.keys().equals(expected) 34 | 35 | assert hp.is_correct_length(cefs, year=2019, freq="60min") 36 | 37 | assert isinstance(cefs, pd.DataFrame) 38 | 39 | 40 | @pytest.mark.apikey 41 | def test_read_opsd_powerplant_list(mocker: MockerFixture): 42 | mocker.patch.object(ConfigUtil, "mode", "safe") 43 | assert elmada.get_mode() == "safe" 44 | df = from_opsd.read_opsd_powerplant_list() 45 | assert len(df) == 893 46 | assert "fuel" in df 47 | 48 | 49 | def test_get_summary(): 50 | result = from_opsd.get_summary() 51 | assert isinstance(result, pd.DataFrame) 52 | 53 | 54 | def test_get_summary_of_opsd_raw(): 55 | result = from_opsd.get_summary_of_opsd_raw() 56 | assert isinstance(result, pd.DataFrame) 57 | 58 | 59 | def test_download_powerplant_list(mocker): 60 | mock = mocker.patch("requests.get") 61 | type(mock.return_value).content = mocker.PropertyMock(return_value="xx".encode()) 62 | fp = paths.CACHE_DIR / "test_file.csv" 63 | from_opsd.download_powerplant_list(which="DE", fp=fp) 64 | assert fp.exists() 65 | fp.unlink() 66 | mock.assert_called_once() 67 | -------------------------------------------------------------------------------- /tests/test_from_other.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | import pandas as pd 4 | import pytest 5 | 6 | from elmada import from_other 7 | 8 | 9 | def test_get_transmission_efficiency_series(): 10 | assert isinstance(from_other.get_transmission_efficiency_series(cache=True), pd.Series) 11 | assert isinstance(from_other.get_transmission_efficiency_series(cache=False), pd.Series) 12 | 13 | 14 | def test_get_transmission_efficiency(): 15 | result = from_other.get_transmission_efficiency("THIS_IS_NOT_A_VALID_COUNTRY") 16 | assert isinstance(result, float) 17 | 18 | 19 | def test__get_light_oil_conversion_factor(): 20 | assert from_other._get_light_oil_conversion_factor(source=1) == 0.980 21 | 22 | 23 | def test_get_ice_eua_prices_with_cache(): 24 | result = from_other.get_ice_eua_prices(cache=True) 25 | assert isinstance(result, Dict) 26 | 27 | 28 | @pytest.mark.apikey 29 | def test_get_ice_eua_prices_without_cache(): 30 | result = from_other.get_ice_eua_prices(cache=False) 31 | assert isinstance(result, Dict) 32 | 33 | 34 | def test_get_sandbag_eua_prices(): 35 | result = from_other.get_sandbag_eua_prices() 36 | assert isinstance(result, Dict) 37 | -------------------------------------------------------------------------------- /tests/test_from_smard.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from elmada import from_smard 4 | 5 | 6 | def test_prep_dayahead_prices(): 7 | result = from_smard.prep_dayahead_prices(year=2018, country="DE", cache=False) 8 | assert isinstance(result, pd.Series) 9 | result = from_smard.prep_dayahead_prices(year=2015, country="DE", cache=False) 10 | assert isinstance(result, pd.Series) 11 | -------------------------------------------------------------------------------- /tests/test_helper.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pandas as pd 4 | import pytest 5 | 6 | from elmada import helper as hp 7 | from elmada import paths 8 | 9 | 10 | @pytest.mark.parametrize( 11 | "freq, year, month, day, expected", 12 | [["60min", 2020, 6, 6, 3768], ["30min", 1990, 3, 12, 3360], ["15min", 1999, 1, 1, 0]], 13 | ) 14 | def test_datetime_to_int(freq: str, year: int, month: int, day: int, expected: int): 15 | assert hp.datetime_to_int(freq, year, month, day) == expected 16 | 17 | 18 | @pytest.mark.parametrize( 19 | "freq, year, pos, expected", 20 | [ 21 | ["15min", 1999, 10, pd.Timestamp("1999-01-01 02:30:00")], 22 | ["45min", 2003, 34, pd.Timestamp("2003-01-02 01:30:00")], 23 | ["60min", 2020, 1, pd.Timestamp("2020-01-01 01:00:00")], 24 | ], 25 | ) 26 | def test_int_to_datetime(freq: str, year: int, pos: int, expected: pd.Timestamp): 27 | assert hp.int_to_datetime(freq=freq, year=year, pos=pos) == expected 28 | 29 | 30 | @pytest.mark.parametrize( 31 | "year, freq, tz", [[2019, "60min", None], [1999, "15min", None], [2099, "45min", None]] 32 | ) 33 | def test_make_datetimeindex(year: int, freq: str, tz: str): 34 | assert isinstance(hp.make_datetimeindex(year=year, freq=freq, tz=tz), pd.DatetimeIndex) 35 | 36 | 37 | @pytest.mark.parametrize( 38 | "freq, expected", [["60min", 60], ["30min", 30], ["45min", 45], ["15min", 15]] 39 | ) 40 | def test_int_from_freq(freq: str, expected: int): 41 | assert hp.int_from_freq(freq=freq) == expected 42 | 43 | 44 | @pytest.mark.parametrize("freq, expected", [["60min", 1.0], ["30min", 0.5], ["15min", 0.25]]) 45 | def test_freq_to_hours(freq: str, expected: float): 46 | assert hp.freq_to_hours(freq=freq) == expected 47 | 48 | 49 | def test_fill_outlier_and_nan(): 50 | ser = pd.Series(6 * [1] + [50] + 4 * [2]) 51 | assert ser[6] == 50 52 | assert hp.fill_outlier_and_nan(ser, method="linear")[6] == 1.5 53 | assert hp.fill_outlier_and_nan(ser, method="fill")[6] == 2 54 | 55 | 56 | def test_remove_outlier(): 57 | x = 6 * [1.0] + [50.0] + 4 * [2.0] 58 | df = pd.DataFrame.from_dict({"a": x, "b": x}) 59 | result = hp.remove_outlier(df) 60 | assert result.iloc[6].isna().sum() == 2 61 | 62 | 63 | def test_delete_cache(mocker, capsys): 64 | hp.delete_cache("XXXX") 65 | captured = capsys.readouterr() 66 | assert captured.out == "No file found containing 'XXXX'.\n" 67 | 68 | fp = paths.CACHE_DIR / "test_file_XXXX.parquet" 69 | tuplelist = [(True, "1 files deleted\n"), (False, "No files deleted\n")] 70 | 71 | for patch_value, msg in tuplelist: 72 | fp.touch(exist_ok=True) 73 | assert fp.exists() 74 | mocker.patch("elmada.helper.confirm_deletion", return_value=patch_value) 75 | hp.delete_cache("test_file_XXXX") 76 | captured = capsys.readouterr() 77 | assert msg in captured.out 78 | 79 | try: 80 | fp.unlink() 81 | except FileNotFoundError: 82 | pass 83 | 84 | assert not fp.exists() 85 | 86 | 87 | def test_print_error(capsys): 88 | a = pd.Series(10, range(10)) 89 | b = a * 1.1 90 | hp.print_error(a, b) 91 | captured = capsys.readouterr() 92 | captured.out == "relative error = 10.00%, absolute error= 1.00 t CO2/MWh.\n" 93 | 94 | 95 | def test_add_row_for_steps(): 96 | ser = pd.Series(range(5)) 97 | result = hp.add_row_for_steps(ser) 98 | assert len(result) == 6 99 | assert result.iloc[0] == 0 100 | 101 | 102 | def test__append_rows(): 103 | some_list = [1, 2, 3] 104 | ser = pd.Series(some_list) 105 | assert len(ser) == 3 106 | result = hp._append_rows(ser, 4) 107 | assert len(result) == 6 108 | 109 | df = pd.DataFrame(dict(a=ser, b=ser)) 110 | assert len(ser) == 3 111 | result = hp._append_rows(df, 4) 112 | assert len(result) == 6 113 | 114 | with pytest.raises(RuntimeError): 115 | hp._append_rows(some_list, 4) 116 | 117 | 118 | def test_sizeof_fmt(): 119 | assert hp.sizeof_fmt(5000) == "5.0 KB" 120 | assert hp.sizeof_fmt(2e24) == "2.0 YB" 121 | 122 | 123 | def test_write(): 124 | ser = pd.Series(range(4)) 125 | path_scheme = paths.CACHE_DIR / "test_file_XXX.suffix" 126 | 127 | with pytest.raises(ValueError): 128 | hp.write(ser, path_scheme) 129 | 130 | for suffix in [".parquet", ".csv"]: 131 | fp = path_scheme.with_suffix(suffix) 132 | assert not fp.exists() 133 | hp.write(ser, fp) 134 | assert fp.exists() 135 | fp.unlink() 136 | 137 | 138 | def test_read(mocker): 139 | suffices = ("parquet", "csv") 140 | funcs = ("pandas.read_parquet", "pandas.read_csv") 141 | for suffix, func in zip(suffices, funcs): 142 | fp = Path(f"_.{suffix}") 143 | mock = mocker.patch(func, return_value=pd.DataFrame({0: [1, 2, 3]})) 144 | result = hp.read(fp).squeeze() 145 | assert isinstance(result, pd.Series) 146 | result = hp.read(fp) 147 | assert isinstance(result, pd.DataFrame) 148 | assert mock.called 149 | 150 | with pytest.raises(ValueError): 151 | hp.read(Path("spam.egg")) 152 | 153 | 154 | def test_make_symlink_to_cache(mocker): 155 | mock = mocker.patch.object(Path, "symlink_to") 156 | hp.make_symlink_to_cache() 157 | mock.assert_called() 158 | 159 | 160 | def test_set_and_get_api_key(mocker, monkeypatch): 161 | key_dir = Path(__file__).parent / "common" 162 | mocker.patch("elmada.paths.KEYS_DIR", key_dir) 163 | 164 | # make sure there is no api key stored as environment variable 165 | monkeypatch.delenv(hp.APIS["entsoe"][2], raising=False) 166 | 167 | fp = key_dir / "entsoe.txt" 168 | if fp.exists(): 169 | fp.unlink() 170 | hp.set_api_keys(entsoe="123") 171 | assert hp.get_api_key(which="entsoe") == "123" 172 | fp.unlink() 173 | 174 | 175 | def test_estimate_freq(): 176 | ser = pd.Series(range(7000)) 177 | with pytest.raises(RuntimeError): 178 | hp.estimate_freq(ser) 179 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | 4 | import elmada 5 | 6 | 7 | def test_get_emissions(mocker): 8 | config = dict(year=2019, freq="60min", country="DE") 9 | 10 | methodtuples = [ 11 | ("_EP", "elmada.from_entsoe.prep_XEFs", config), 12 | ("_PP", "elmada.from_opsd.prep_CEFs", config), 13 | ("_PWL", "elmada.eu_pwl.prep_CEFs", dict(**config, validation_mode=False)), 14 | ("_PWLv", "elmada.eu_pwl.prep_CEFs", dict(**config, validation_mode=True)), 15 | ] 16 | 17 | for (method, func, kwargs) in methodtuples: 18 | print(method, func, kwargs) 19 | mock = mocker.patch(func) 20 | elmada.get_emissions(**config, cache=False, method=method) 21 | mock.assert_called_once_with(**kwargs) 22 | 23 | 24 | pp_keys = pd.Index( 25 | [ 26 | "id", 27 | "name_bnetza", 28 | "block_bnetza", 29 | "name_uba", 30 | "company", 31 | "street", 32 | "postcode", 33 | "city", 34 | "state", 35 | "country_code", 36 | "capa", 37 | "capacity_gross_uba", 38 | "fuel", 39 | "technology", 40 | "chp", 41 | "chp_capacity_uba", 42 | "commissioned", 43 | "commissioned_original", 44 | "retrofit", 45 | "shutdown", 46 | "status", 47 | "type", 48 | "lat", 49 | "lon", 50 | "eic_code_plant", 51 | "eic_code_block", 52 | "efficiency_data", 53 | "efficiency_source", 54 | "efficiency_estimate", 55 | "energy_source_level_1", 56 | "energy_source_level_2", 57 | "energy_source_level_3", 58 | "eeg", 59 | "network_node", 60 | "voltage", 61 | "network_operator", 62 | "merge_comment", 63 | "comment", 64 | "fuel_draf", 65 | "filler", 66 | "eta_k", 67 | "used_eff", 68 | "marginal_emissions_for_gen", 69 | "x_k", 70 | "fuel_cost", 71 | "GHG_cost", 72 | "marginal_emissions", 73 | "marginal_cost", 74 | "cumsum_capa", 75 | ], 76 | dtype="object", 77 | ) 78 | 79 | pwl_keys = pd.Index( 80 | [ 81 | "capa", 82 | "used_eff", 83 | "fuel_draf", 84 | "x_k", 85 | "fuel_cost", 86 | "GHG_cost", 87 | "marginal_emissions", 88 | "marginal_cost", 89 | "cumsum_capa", 90 | ], 91 | dtype="object", 92 | ) 93 | 94 | 95 | @pytest.mark.parametrize( 96 | "method,expected_keys", [("PP", pp_keys), ("PWL", pwl_keys), ("PWLv", pwl_keys)] 97 | ) 98 | def test_get_merit_order(method, expected_keys): 99 | df = elmada.get_merit_order(year=2019, country="DE", method=method) 100 | assert isinstance(df, pd.DataFrame) 101 | df.keys().equals(expected_keys) 102 | 103 | 104 | def test_get_residual_load(mocker): 105 | config = dict(year=2019, freq="60min", country="DE") 106 | mock = mocker.patch("elmada.from_entsoe.prep_residual_load", return_value=True) 107 | assert elmada.from_entsoe.prep_residual_load(**config) 108 | mock.assert_called_once_with(**config) 109 | 110 | 111 | def test_get_prices(mocker): 112 | config = dict(year=2019, freq="60min", country="DE", cache=True) 113 | ep_mock = mocker.patch("elmada.from_entsoe.prep_dayahead_prices", return_value=True) 114 | assert elmada.get_prices(**config, method="hist_EP") 115 | ep_mock.assert_called_once_with(**config) 116 | 117 | sm_mock = mocker.patch("elmada.from_smard.prep_dayahead_prices", return_value=True) 118 | assert elmada.get_prices(**config, method="hist_SM") 119 | sm_mock.assert_called_once_with(**config) 120 | 121 | pp_mock = mocker.patch("elmada.main.get_emissions", return_value={"marginal_cost": True}) 122 | elmada.get_prices(**config, method="PP") 123 | pp_mock.assert_called_once_with(**config, method="_PP") 124 | 125 | pwl_mock = mocker.patch("elmada.main.get_emissions", return_value={"marginal_cost": True}) 126 | elmada.get_prices(**config, method="PWL") 127 | pwl_mock.assert_called_once_with(**config, method="_PWL") 128 | -------------------------------------------------------------------------------- /tests/test_mode.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import elmada 4 | from elmada import mode 5 | 6 | 7 | def test_set_mode(): 8 | mode_backup = mode.get_mode() 9 | 10 | mode.set_mode("safe") 11 | assert mode.get_mode() == "safe" 12 | 13 | mode.set_mode("live") 14 | assert mode.get_mode() == "live" 15 | 16 | with pytest.raises(AssertionError): 17 | mode.set_mode("other") 18 | 19 | mode.set_mode(mode_backup) 20 | 21 | 22 | def test_is_safe_mode(mocker): 23 | mocker.patch("elmada.mode.get_mode", return_value="safe") 24 | assert mode.is_safe_mode() 25 | -------------------------------------------------------------------------------- /tests/test_plots.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import elmada 3 | 4 | 5 | def test_merit_order(): 6 | for method in ["PWL", "PP"]: 7 | plt.close() 8 | elmada.plots.merit_order(year=2019, country="DE", method=method, include_histo=True) 9 | assert plt.gcf().number == 1 10 | plt.close() 11 | 12 | 13 | def test_cefs_scatter(): 14 | for method in ["XEF_PWL", "MEF_PWL", "XEF_PP", "MEF_PWL"]: 15 | plt.close() 16 | elmada.plots.cefs_scatter(year=2019, country="DE", method=method) 17 | assert plt.gcf().number == 1 18 | plt.close() 19 | 20 | 21 | def test_cef_country_map(): 22 | elmada.plots.cef_country_map(year=2019, method="XEF_PWL", scope="Europe20") 23 | 24 | 25 | def test_cefs_scatter_plotly(): 26 | elmada.plots.cefs_scatter_plotly(year=2019, freq="60min", country="DE", method="MEF_PWL") 27 | --------------------------------------------------------------------------------