Fix EIC code aggregation and integrate JRC-PPDB-OPEN data source #306
Fix EIC code aggregation and integrate JRC-PPDB-OPEN data source #306stan-buren wants to merge 4 commits into
Conversation
- Fix column name mismatch: 'eic_code' -> 'EIC' in reduce_matched_dataframe - Add EIC pre-join: deterministic matches via shared EIC codes before Duke - Guard against division by zero in aggregate_units (Capacity=0) - New module eic_codes.py for EIC validation and pre-join logic Before: 19/165,064 records had real EIC codes (0.01%) After: 1,216 EIC codes preserved (0.7%, 64x improvement)
The JRC Open Power Plants Database (DOI: 10.5281/zenodo.3574566) provides EIC codes with geographic coordinates for ~70% of large European power plants. One deterministic JOIN on eic_p gives coordinates without geocoding or probabilistic matching. Together with the EIC fix, this brings EIC coverage from 19 (0.01%) to 1,904 records (57% of ENTSOE ceiling).
📝 Research Diary: The Missing Bridge Between ENTSO-E and Spatial DataFor the maintainers: I wanted to share the background research that led to this PR. Finding the right way to handle EIC codes turned into quite a rabbit hole, and I tried several dead-end approaches before landing on the JRC-PPDB-OPEN integration. Here is the full context of what I found. 1. Attempt 1: The OpenStreetMapI already knew that OSM data for power plants requires substantial curation to be usable at scale. However, I decided to dig into I checked OSM Taginfo and found 4,765 objects tagged with Why it failed: 2. Attempt 2: Brute-force Geocoding via NominatimSince ENTSO-E 14.1.B provides text-based location data, I tried geocoding it myself. The result: I successfully geocoded ~92% (19,473 records). 3. The Regulatory Rabbit Hole: Why doesn't ENTSO-E just publish coordinates?Working with ENTSO-E data daily, I already knew they don't include exact
4. The Breakthrough: JRC-PPDB-OPENIf TSOs legally can't publish coordinates, how do we map ENTSO-E data? Since they couldn't force ENTSO-E to release coordinates, they built the JRC Open Power Plants Database (DOI: Because JRC is an academic body compiling open data, they aren't bound by the TSO security directives. Their dataset provides a clean, validated CSV mapping By integrating this single CSV into 🚀 The Grand Vision: What EIC Preservation Unlocks for PyPSACurrently, PyPSA-Earth uses PPM strictly for static installed capacity. But by fixing the EIC aggregation bug in this PR and ensuring every matched plant retains its authoritative EIC code, PPM becomes a gateway to operational, real-time ENTSO-E data. In future PRs, this deterministic key allows to easily JOIN
I hope this context helps explain why I tackled the EIC issue from this specific angle. Happy to discuss the architecture further! |
PR PyPSA#289 (MaykThewessen) implements a more robust EIC-first matching with degree-1 uniqueness checks that handles Alpine hydro schemes correctly. Our eic_codes.py now provides only validation utilities (is_valid_eic, extract_eics) and the EIC_PATTERN constant. The matching.py country_link reverts to the original Duke-only path. EIC-based matching will be handled by _match_by_eic from PyPSA#289.
e7fee25 to
ab45a23
Compare
for more information, see https://pre-commit.ci
Follow-up: Relationship to PRs #289 and #301Two other open PRs are directly relevant to this work, and I wanted to flag how they intersect to ensure a smooth merge path: PR #289 (@MaykThewessen) — EIC-first deterministic matching ( The key finding from #289 is that Duke fuzzy matching currently misses 55 out of 371 EIC-confirmed pairs (34.6 GW of capacity). Our PRs are strictly complementary, not overlapping:
Suggested merge flow: Preserve EICs via #306 → Match deterministically via #289 → Join coordinates via JRC. PR #301 (@FabianHofmann) — Replace Duke with rapidfuzz Happy to rebase or restructure if the maintainers prefer a different split between these updates! |
|
Sounds good! |
🎯 TL;DR
This PR fixes a silent bug in
reduce_matched_dataframethat caused 99.98% of EIC codes to be dropped from the final output. It also integrates the JRC-PPDB-OPEN dataset to provide missing coordinates for ENTSO-E records.Addresses the data foundation for #287 (EIC-first matching). Complementary to #289 which implements the EIC-based matching pass
🐛 The Problem
Currently, the
EICcolumn inpowerplants.csvis populated almost entirely with{nan}. This happens due to two issues inmatching.py:reduce_matched_dataframe(), the aggregation rule is defined as"eic_code": set, but the actual column defined inconfig.yamlis"EIC". Thesetaggregation is never applied."first", sources with higher reliability scores (like GEM, score=6) overwrite ENTSO-E (score=5). Since GEM doesn't provide EIC codes, the valid ENTSO-E EICs are overwritten withNaN.Additionally, because ENTSO-E 14.1.B does not publish exact coordinates, Duke's spatial matching drops ~93% of ENTSO-E records during the matching phase.
🛠️ The Solution
This PR fixes the EIC pipeline at the data level:
1. Fix EIC Aggregation (
matching.py)Changed
"eic_code": setto"EIC": lambda x: set(...). EICs are now correctly collected from all matched sources into a set, rather than being overwritten by the highest-scored source. This alone recovers over 1,200 EIC codes for existing matched records.2. JRC-PPDB-OPEN Integration (
data.py)To solve the lack of ENTSO-E coordinates, this PR integrates the JRC Open Power Plants Database (v1.0, European Commission).
eic_pto exact WGS84 coordinates.JRC_PPDB_OPEN()function reads the dataset, aggregates generation units to production units, and produces a PPM-compatible DataFrame.eic_p.3. Edge Case Guard (
cleaning.py)Added
Capacity = Capacity.replace(0, pd.NA)to prevent division by zero in weighted capacity calculations.🔄 Backward Compatibility
set(...)lambda handles missing values gracefully.powerplants()orcollect(). The config update is purely additive.📈 Why this matters for PyPSA
Preserving the EIC code in the final
powerplants.csvis the missing bridge to operational ENTSO-E data. By having accurate EICs, the PyPSA-Earth community can now easily join the dataset with ENTSO-E 15.1 (Unavailability/Outages) and 16.1.A (Actual Generation) for advanced model validation and dynamic availability constraints.