def get_reference_emission_levels_for_region(region: str, year: int) -> float: """ Return the total CO2 emissions (in kT) emitted by a series of countries in a given region for a given year. Parameters ---------- region: str Region consisting of one or several countries. year: int Year Returns ------- emission_ref: float Total Co2 emissions in kT """ return sum([ get_co2_emission_level_for_country(country, year) for country in get_subregions(region) ])
def build_data(self, use_ex_cap: bool, min_cap_pot: List[float] = None, compute_load: bool = True, regions_shapes: pd.DataFrame = None): """Preprocess data. Parameters: ----------- use_ex_cap: bool Whether to compute or not existing capacity and use it in optimization. min_cap_pot: List[float] (default: None) List of thresholds per technology. Points with capacity potential under this threshold will be removed. """ # TODO: this function needs to take as argument a vector data specifying which data it must compute # Compute total load (in GWh) for each region load_df = pd.DataFrame(0., index=self.timestamps, columns=self.regions) if compute_load: load_df = get_load(timestamps=self.timestamps, regions=self.regions, missing_data='interpolate') # Get shape of regions and list of subregions onshore_technologies = [ get_config_values(tech, ["onshore"]) for tech in self.technologies ] if regions_shapes is None: regions_shapes = pd.DataFrame(columns=["onshore", "offshore"], index=self.regions) all_subregions = [] for region in self.regions: subregions = get_subregions(region) all_subregions.extend(subregions) shapes = get_shapes(subregions, save=True) if any(onshore_technologies): regions_shapes.loc[region, "onshore"] = unary_union( shapes[~shapes['offshore']]['geometry']) if not all(onshore_technologies): regions_shapes.loc[region, "offshore"] = unary_union( shapes[shapes['offshore']]['geometry']) else: all_subregions = self.regions # Divide the union of all regions shapes into grid cells of a given spatial resolution # TODO: this is shitty because you cannot add different technologies in separate regions grid_cells_ds = get_grid_cells(self.technologies, self.spatial_res, regions_shapes["onshore"].dropna(), regions_shapes["offshore"].dropna()) # Compute capacities potential tech_config = get_config_dict(self.technologies, ['filters', 'power_density']) cap_potential_ds = pd.Series(index=grid_cells_ds.index) for tech in self.technologies: cap_potential_ds[tech] = \ get_capacity_potential_for_shapes(grid_cells_ds[tech].values, tech_config[tech]["filters"], tech_config[tech]["power_density"]) # Compute legacy capacity existing_cap_ds = pd.Series(0., index=cap_potential_ds.index) if use_ex_cap: for tech in self.technologies: tech_existing_cap_ds = \ get_legacy_capacity_in_regions(tech, grid_cells_ds.loc[tech].reset_index(drop=True), all_subregions, raise_error=False) existing_cap_ds[tech] = tech_existing_cap_ds.values # Update capacity potential if existing capacity is bigger underestimated_capacity_indexes = existing_cap_ds > cap_potential_ds cap_potential_ds[underestimated_capacity_indexes] = existing_cap_ds[ underestimated_capacity_indexes] # Remove sites that have a potential capacity under the desired value or equal to 0 if min_cap_pot is None: min_cap_pot = [0] * len(self.technologies) assert len(min_cap_pot) == len(self.technologies), \ "Error: If you specify threshold on capacity potentials, you need to specify it for each technology." min_cap_pot_dict = dict(zip(self.technologies, min_cap_pot)) sites_to_drop = pd.DataFrame(cap_potential_ds).apply( lambda x: x[0] < min_cap_pot_dict[x.name[0]] or x[0] == 0, axis=1) # Don't drop sites with existing capacity # TODO: this is probably a shitty way to do it sites_to_drop = pd.DataFrame(sites_to_drop).apply( lambda x: (existing_cap_ds[x.name] == 0 and x[0]), axis=1) cap_potential_ds = cap_potential_ds[~sites_to_drop] existing_cap_ds = existing_cap_ds[~sites_to_drop] grid_cells_ds = grid_cells_ds[~sites_to_drop] # Compute capacity factors for each site tech_points_dict = {} techs = set(grid_cells_ds.index.get_level_values(0)) for tech in techs: tech_points_dict[tech] = list(grid_cells_ds[tech].index) cap_factor_df = compute_capacity_factors(tech_points_dict, self.spatial_res, self.timestamps) # Associating coordinates to regions tech_points_regions_ds = pd.Series(index=grid_cells_ds.index) sites_index = tech_points_regions_ds.index for tech in set(sites_index.get_level_values(0)): on_off = 'onshore' if get_config_values( tech, ['onshore']) else 'offshore' tech_sites_index = sites_index[sites_index.get_level_values(0) == tech] points = list( zip(tech_sites_index.get_level_values(1), tech_sites_index.get_level_values(2))) tech_points_regions_ds[tech] = match_points_to_regions( points, regions_shapes[on_off].dropna()).values cap_credit_ds = compute_capacity_credit_from_potential( load_df, cap_factor_df, tech_points_regions_ds) # Save all data in object self.use_ex_cap = use_ex_cap self.min_cap_pot_dict = min_cap_pot_dict self.tech_points_tuples = grid_cells_ds.index.values self.tech_points_dict = tech_points_dict self.initial_sites_ds = grid_cells_ds self.tech_points_regions_ds = tech_points_regions_ds self.data_dict["load"] = load_df self.data_dict["cap_potential_ds"] = cap_potential_ds.round(3) self.data_dict["existing_cap_ds"] = existing_cap_ds.round(3) self.data_dict["cap_factor_df"] = cap_factor_df.round(3) self.data_dict["capacity_credit_ds"] = cap_credit_ds.round(3)
Longitudinal range over which to display the map. Automatically set if not specified. lat_range: List[float] (default: None) Latitudinal range over which to display the map. Automatically set if not specified. """ df = get_powerplants(tech, countries)[["ISO2", "Capacity"]] df = df.groupby(["ISO2"]).sum() / 1000.0 df = df[df["Capacity"] != 0] df["ISO_3"] = convert_country_codes(df.index.values, 'alpha_2', 'alpha_3') fig = go.Figure(data=go.Choropleth( locations=df['ISO_3'], # Spatial coordinates z=df['Capacity'], # Data to be color-coded text=[f"{cap} GW" for cap in df["Capacity"].values], colorscale='Reds', colorbar_title=f"Capacity (GW)")) fig.update_layout(geo=dict(lonaxis=dict(range=lon_range, ), lataxis=dict(range=lat_range, ), scope='europe')) fig.show() if __name__ == '__main__': regions = get_subregions("EU2") plot_capacity_per_country("nuclear", regions, lon_range=[-12, 30], lat_range=[35, 75])
def test_get_eez_and_land_union_shapes(): codes = get_subregions("EU2") ds = get_eez_and_land_union_shapes(codes) assert isinstance(ds, pd.Series) assert not (set(ds.index).symmetric_difference(set(codes)))
def get_load(timestamps: pd.DatetimeIndex = None, years_range: List[int] = None, countries: List[str] = None, regions: List[str] = None, missing_data: str = "error") -> pd.DataFrame: """ Compute hourly load time series (in GWh) for given countries or regions. The desired time slice can be either given as as series of time stamps or as a range of years for which we want full data. Parameters ---------- timestamps: pd.DatetimeIndex (default: None) Datetime index years_range: List[int] Range of years (if you desire to obtain data for only one year just pass a list with twice the year) countries: List[str] (default: None) ISO codes of countries regions: List[str] (default: None) List of codes referring to regions made of several countries defined in 'data_path'/geographics/region_definition.csv missing_data: str (default: error) Defines how to deal with missing data. If value is 'error', throws an error. If value is 'interpolate', uses data from another country Returns ------- pd.DataFrame (index = timestamps, columns = regions or countries) DataFrame associating to each country or region the corresponding its hourly load (in GWh) """ assert (countries is None) != (regions is None), "Error: You must either specify a list of countries or " \ "a list of regions made of countries, but not both." assert (timestamps is None) != (years_range is None), "Error: You must either specify a range of years or " \ "a series of time stamps, but not both." assert years_range is None or (len(years_range) == 2 and years_range[0] <= years_range[1]), \ f"The desired years range must be specified as a list of two ints (the first one being smaller" \ f" or equal to the second one, received {years_range}" assert missing_data in [ "error", "interpolate" ], f"Error: missing_data must be one of 'error' or 'interpolate'" if years_range is not None: timestamps = pd.date_range(f"{years_range[0]}-01-01 00:00:00", f"{years_range[1]}-12-31 23:00:00", freq='1H') opsd_load_fn = f"{data_path}load/generated/opsd_load.csv" load = pd.read_csv(opsd_load_fn, index_col=0, engine='python') load.index = pd.DatetimeIndex(load.index) missing_timestamps = set(timestamps) - set(load.index) assert not missing_timestamps, f"Error: Load is not available " \ f"for the following timestamps {sorted(list(missing_timestamps))}" # Slice on time and remove columns in which we don't have available data for the full time period load = load.loc[timestamps].dropna(axis=1) # Convert to GWh load = load * 1e-3 def get_countries_load(countries_: List[str]): countries_load = pd.DataFrame(index=timestamps, columns=countries_) missing_countries = set(countries_) - set(load.columns) if missing_countries: if missing_data == "error": raise ValueError( f"Error: Load is not available for countries {sorted(list(missing_countries))} " f"for the required timestamps.") else: countries_load[list(missing_countries)] = \ get_load_from_source_country(list(missing_countries), load.index) countries_with_data = list(set(countries) - set(missing_countries)) countries_load[countries_with_data] = load[countries_with_data] return countries_load # Get load per country if countries is not None: return get_countries_load(countries).round(6) # Get load aggregated by region elif regions is not None: load_per_region = pd.DataFrame(columns=regions, index=timestamps) for region in regions: # Get load date for all subregions and sum it countries = get_subregions(region) load_per_region[region] = get_countries_load(countries).sum( axis=1).values return load_per_region.round(6)
tech_config_dict = get_config_dict( [tech], ["onshore", "power_density", "filters"])[tech] cap_pot_ds = get_capacity_potential_per_country( countries, tech_config_dict["onshore"], tech_config_dict["filters"], tech_config_dict["power_density"]) cap_pot_df = cap_pot_ds.to_frame() cap_pot_df["ISO_3"] = convert_country_codes(cap_pot_df.index.values, 'alpha_2', 'alpha_3') fig = go.Figure(data=go.Choropleth( locations=cap_pot_df['ISO_3'], # Spatial coordinates z=cap_pot_df[0], # Data to be color-coded text=[f"{cap} GW" for cap in cap_pot_df[0].values], colorscale='Reds', colorbar_title=f"Capacity (GW)")) fig.update_layout(geo=dict(lonaxis=dict(range=lon_range, ), lataxis=dict(range=lat_range, ), scope='europe'), title=f"Capacity potential for {tech}") fig.show() if __name__ == '__main__': from iepy.geographics import get_subregions countries_ = get_subregions("BENELUX") tech_ = "pv_residential" plot_capacity(tech_, countries_, lon_range=[-12, 30], lat_range=[35, 75])