diff --git a/dev/3E7C6000 b/dev/3E7C6000 new file mode 100644 index 0000000..7e3223a Binary files /dev/null and b/dev/3E7C6000 differ diff --git a/dev/65BA2400 b/dev/65BA2400 new file mode 100644 index 0000000..aa127a7 Binary files /dev/null and b/dev/65BA2400 differ diff --git a/dev/78A69000 b/dev/78A69000 new file mode 100644 index 0000000..8018687 Binary files /dev/null and b/dev/78A69000 differ diff --git a/dev/A8415100 b/dev/A8415100 new file mode 100644 index 0000000..13fc192 Binary files /dev/null and b/dev/A8415100 differ diff --git a/dev/aktuell/activity_filter.py b/dev/aktuell/activity_filter.py new file mode 100644 index 0000000..ff9425d --- /dev/null +++ b/dev/aktuell/activity_filter.py @@ -0,0 +1,139 @@ +# Imports +# ------- + +import yaml + +# Sector filter functions from premise +# --------------------------------------------------- + + +def _act_fltr( + database: list, + fltr=None, + mask=None, +): + """Filter `database` for activities_list matching field contents given by `fltr` excluding strings in `mask`. + `fltr`: string, list of strings or dictionary. + If a string is provided, it is used to match the name field from the start (*startswith*). + If a list is provided, all strings in the lists are used and dataframes_dict are joined (*or*). + A dict can be given in the form : to filter for in . + `mask`: used in the same way as `fltr`, but filters add up with each other (*and*). + `filter_exact` and `mask_exact`: boolean, set `True` to only allow for exact matches. + + :param database: A lice cycle inventory database + :type database: brightway2 database object + :param fltr: value(s) to filter with. + :type fltr: Union[str, lst, dict] + :param mask: value(s) to filter with. + :type mask: Union[str, lst, dict] + :return: list of activity data set names + :rtype: list + + """ + if fltr is None: + fltr = {} + if mask is None: + mask = {} + + # default field is name + if isinstance(fltr, (list, str)): + fltr = {"name": fltr} + if isinstance(mask, (list, str)): + mask = {"name": mask} + + assert len(fltr) > 0, "Filter dict must not be empty." + + # find `act` in `database` that match `fltr` + # and do not match `mask` + filters = database + for field, value in fltr.items(): + if isinstance(value, list): + for val in value: + filters = [a for a in filters if val in a[field]] + + # filters.extend([ws.either(*[ws.contains(field, v) for v in value])]) + else: + filters = [a for a in filters if value in a[field]] + + # filters.append(ws.contains(field, value)) + + if mask: + for field, value in mask.items(): + if isinstance(value, list): + for val in value: + filters = [f for f in filters if val not in f[field]] + # filters.extend([ws.exclude(ws.contains(field, v)) for v in value]) + else: + filters = [f for f in filters if value not in f[field]] + # filters.append(ws.exclude(ws.contains(field, value))) + + return filters + + +def generate_sets_from_filters(yaml_filepath, database) -> dict: + """ + Generate a dictionary with sets of activity names for + technologies from the filter specifications. + + :param filtr: + :func:`activity_maps.InventorySet._act_fltr`. + :return: dictionary with the same keys as provided in filter + and a set of activity data set names as values. + :rtype: dict + """ + + filtr = _get_mapping(yaml_filepath, var="ecoinvent_aliases") + + names = [] + + for entry in filtr.values(): + if "fltr" in entry: + if isinstance(entry["fltr"], dict): + if "name" in entry["fltr"]: + names.extend(entry["fltr"]["name"]) + elif isinstance(entry["fltr"], list): + names.extend(entry["fltr"]) + else: + names.append(entry["fltr"]) + + # subset = list( + # ws.get_many( + # database, + # ws.either(*[ws.contains("name", name) for name in names]), + # ) + # ) + + subset = [a for a in database if any(x in a["name"] for x in names)] + + techs = { + tech: _act_fltr(subset, fltr.get("fltr"), fltr.get("mask")) + for tech, fltr in filtr.items() + } + + mapping = {tech: {act for act in actlst} for tech, actlst in techs.items()} + + return mapping + + +def _get_mapping(filepath, var): + """ + Loa a YAML file and return a dictionary given a variable. + :param filepath: YAML file path + :param var: variable to return the dictionary for. + :param model: if provided, only return the dictionary for this model. + :return: a dictionary + """ + + with open(filepath, "r", encoding="utf-8") as stream: + techs = yaml.full_load(stream) + + mapping = {} + for key, val in techs.items(): + if var in val: + mapping[key] = val[var] + + return mapping + + +# Example on how to call the functions to create a set of filtered activities_list +# set_from_fltrs = generate_sets_from_filters(yaml_filepath, database=ei39SSP) diff --git a/dev/aktuell/compare_db_to_xcl.py b/dev/aktuell/compare_db_to_xcl.py new file mode 100644 index 0000000..062cb54 --- /dev/null +++ b/dev/aktuell/compare_db_to_xcl.py @@ -0,0 +1,176 @@ +import pandas as pd + +import re +import pandas as pd +from dopo import generate_sets_from_filters +from dopo import compare_activities_multiple_methods +from dopo import small_inputs_to_other_column +import openpyxl +from openpyxl import load_workbook +from openpyxl.chart import ScatterChart, Reference, Series +from openpyxl.chart import BarChart, Reference + +def _lca_scores_compare(database_dict, method_dict): + # Dictionary to store DataFrames for each sector + sector_dataframes = {} + + # Labels for the DataFrame columns + labels = [ + "activity", + "activity key", + "reference product", + "location", + "method name", + "method unit", + "total", + ] + + # Loop through each sector in the database_dict + for sector, sector_data in database_dict.items(): + # Initialize a dictionary to hold DataFrames for each method in the current sector + method_dataframes = {} + + # Loop through each method in method_dict + for meth_key, meth_info in method_dict.items(): + data = [] # Initialize a new list to hold data for the current method + + # Extract the 'method name' tuple from the current method info + method_name = meth_info['method name'] + method_unit = meth_info['unit'] + + # Now loop through each activity in the sector + for act in sector_data['activities']: + # Ensure the activity is an instance of the expected class + if not isinstance(act, bd.backends.peewee.proxies.Activity): + raise ValueError("`activities` must be an iterable of `Activity` instances") + + # Perform LCA calculations + lca = bw.LCA({act: 1}, method_name) + lca.lci() + lca.lcia() + + # Collect data for the current activity and method + data.append([ + act["name"], + act.key, + act.get("reference product"), + act.get("location", "")[:25], + method_name, + method_unit, + lca.score, + ]) + + # Convert the data list to a DataFrame and store it in the sector's dictionary + method_dataframes[meth_key] = pd.DataFrame(data, columns=labels) + + # Store the method_dataframes dictionary in the sector_dataframes dictionary + sector_dataframes[sector] = method_dataframes + + # Now `sector_dataframes` is a dictionary where each key is a sector, and the value is another dictionary with method names and their corresponding DataFrames + return sector_dataframes + + +import pandas as pd + +def _relative_changes_df(database_dict_eco, database_dict_premise): + + ecoinvent_scores = _lca_scores_compare(database_dict_eco) + premise_scores = _lca_scores_compare(database_dict_premise) + + relative_dict = {} + + # Iterate over sectors + for sector_key in ecoinvent_scores: + # Initialize the sector key in the output dictionary + if sector_key not in relative_dict: + relative_dict[sector_key] = {} + + # Iterate over methods within the sector + for method_key in ecoinvent_scores[sector_key]: + # Check if the method_key exists in both dictionaries to avoid KeyError + if method_key in premise_scores.get(sector_key, {}): + # Get the corresponding DataFrames + df_ei = ecoinvent_scores[sector_key][method_key] + df_premise = premise_scores[sector_key][method_key] + + #print(df_ei['activity key']) + #print(df_premise) + + # Split the 'activity key' to extract the second part + df_ei['activity_code'] = df_ei['activity key'].apply(lambda x: x[1]) # Access the second element of the tuple + df_premise['activity_code'] = df_premise['activity key'].apply(lambda x: x[1]) + + # Merge the two dataframes based on the activity code and method name + merged_df = pd.merge(df_ei, df_premise, on=['activity_code', 'method name'], suffixes=('_ei', '_premise')) + + # Calculate the relative change + merged_df['relative_change'] = ((merged_df['total_premise'] - merged_df['total_ei']) / merged_df['total_ei']) * 100 + + # Store the result in the dictionary + relative_dict[sector_key][method_key] = merged_df + + return relative_dict + +def _add_sector_marker(df, sector): + ''' + It is called in the function sector_lca_scores_to_excel_and_column_positions. + + It adds information about the sector for titel and labeling in plotting. + + Returns df with added column. + ''' + + # Add sector marker column + df['sector']=str(sector) # potentially remove! + # Reorder the columns to move 'sector' after 'product' + columns = list(df.columns) + + if 'product' in df.columns: + product_index = columns.index('product') + # Insert 'sector' after 'product' + columns.insert(product_index + 1, columns.pop(columns.index('sector'))) + else: + # If 'product' does not exist, 'sector' remains in the last column + columns.append(columns.pop(columns.index('sector'))) + + # Reassign the DataFrame with the new column order + df = df[columns] + return df + +def relative_changes_db(database_dict_eco, database_dict_premise, excel_file): + + relative_dict = (_relative_changes_df(database_dict_eco, database_dict_premise)) + + # Prepare to save each LCA score table to a different worksheet in the same Excel file + + column_positions = {} #stores the indexes of columns for plotting + with pd.ExcelWriter(excel_file, engine='openpyxl') as writer: + for sector in relative_dict.keys(): + relative_changes = relative_dict[sector] + + for method, table in relative_changes.items(): + # Create a DataFrame for the current LCA score table + df = pd.DataFrame(table) + + # Add sector marker + df = _add_sector_marker(df, sector) #!! ADJUST + + # Sort the DataFrame by 'relative_change' from largest negative to largest positive + df = df.sort_values(by='relative_change', ascending=False) + + # Add a 'rank' column based on the 'relative_change', ranking from most negative to least negative + df['rank'] = df['relative_change'].rank(ascending=False, method='dense').astype(int) + + # Get the index values of columns + columns_of_interest = ["rank", "relative_change", "method", "method unit", ] + positions = {col: df.columns.get_loc(col) for col in columns_of_interest if col in df.columns} + column_positions[method] = positions + + # Generate worksheet name + worksheet_name = f"{sector}_{method}" + if len(worksheet_name) > 31: + worksheet_name = worksheet_name[:31] + + # Save the DataFrame to the Excel file in a new worksheet + df.to_excel(writer, sheet_name=worksheet_name, index=False) + return column_positions \ No newline at end of file diff --git a/dev/aktuell/filter_sectors.py b/dev/aktuell/filter_sectors.py new file mode 100644 index 0000000..dedf8ff --- /dev/null +++ b/dev/aktuell/filter_sectors.py @@ -0,0 +1,48 @@ +import re +import pandas as pd +from dopo import generate_sets_from_filters +from dopo import compare_activities_multiple_methods +from dopo import small_inputs_to_other_column +import openpyxl +from openpyxl import load_workbook +from openpyxl.chart import ScatterChart, Reference, Series +from openpyxl.chart import BarChart, Reference +import copy + +def process_yaml_files(files_dict, database): + ''' + - Runs through the files_dict reading the defined filters in the yaml files. + - With another function a list that contains the filtered activities is created from the chosen database. + - This activity list is saved within the corresponding key (sector) in the dictionary main_dict which is based on the files_dict. + + :param files_dict: dictionary of dictionaries. It should hold the yaml file path and the title in the first row of the yaml file. + Like so: files_dict['Cement']={'yaml': 'yamls\cement_small.yaml', 'yaml identifier': 'Cement'} + :param database: premise or ecoinvent database of choice. + + It returns an updated dictionary which contains filtered activity lists for each sector. + ''' + + main_dict = copy.deepcopy(files_dict) + + for key, value in main_dict.items(): + yaml_file = value['yaml'] + yaml_identifier = value['yaml identifier'] + + #debug + print(f"Processing {key} with database {database.name}") # check for right database + + # Generate the sector activities + sector_activities = generate_sets_from_filters(yaml_file, database) + + #debug + print(f"Activities for {key}:") + for activity in sector_activities[yaml_identifier]: + print(f" {activity.key}") + + # Convert the set of activities to a list + activities_list = list(sector_activities[yaml_identifier]) + + # Add to the sectors_dict + main_dict[key]['activities'] = activities_list + + return main_dict \ No newline at end of file diff --git a/dev/aktuell/lca_to_xcl.py b/dev/aktuell/lca_to_xcl.py new file mode 100644 index 0000000..9d38265 --- /dev/null +++ b/dev/aktuell/lca_to_xcl.py @@ -0,0 +1,208 @@ + +from dopo import compare_activities_multiple_methods +from dopo import small_inputs_to_other_column +import pandas as pd +import re +import pandas as pd +from dopo import generate_sets_from_filters +from dopo import compare_activities_multiple_methods +from dopo import small_inputs_to_other_column +import openpyxl +from openpyxl import load_workbook +from openpyxl.chart import ScatterChart, Reference, Series +from openpyxl.chart import BarChart, Reference + +def sector_lca_scores(main_dict, method_dict, cutoff=0.02): + ''' + Generates the LCA score tables for activity list of each sector. + The tables contain total scores and cpc input contributions. + This is done by each method defined in the method dictionary. + + :param main_dict: dictionary which is returned by process_yaml_files function + :param method_dict: dictionary which is created with MethodFinder class + :param cutoff: cutoff value to summarize inputs below or equal to this threshhold in a "other" column + + It returns the main dictionary updated as scores dictionary which also holds the former information for each sector. + The LCA scores are stored by method name in the respective sector dictionary within the main dictionary. + ''' + + # Initialize scores_dict as a copy of main_dict + scores_dict = main_dict.copy() + + # Loop through each sector in main_dict + for sector in scores_dict.keys(): + # Extract activities for the current sector + sector_activities = scores_dict[sector]['activities'] + + # Calculate LCA scores using the specified method + lca_scores = compare_activities_multiple_methods( + activities_list=sector_activities, + methods=method_dict, + identifier=sector, + mode='absolute' + ) + + # Apply the small_inputs_to_other_column function with the cutoff value + lca_scores_cut = small_inputs_to_other_column(lca_scores, cutoff) + + # Save the LCA scores to the scores_dict + scores_dict[sector]['lca_scores'] = lca_scores_cut + + return scores_dict + +# ----------------------------------------- +# CREATING EXCEL SHEETS WITH LCA TABLES +# ----------------------------------------- + +def sector_lca_scores_to_excel_and_column_positions(scores_dict, excel_file_name): + """ + What it does: + - Creates a dataframe for each method and sector from the lca scores dictionary + - Before storing each df in a worksheet in an excel file it: + - shortens the column labels of the input (removing cpc code) + - adds a sector name marker for keeping track in excel (when plotting can use it for labeling) + - adds statistics for plotting + - creates a dictionary which holds the indexes to the columns we need to call for plotting, this makes it dynamic. Otherwise need to hardcode index column number for openpxyl. + What it returns: + - Returns the index positions dictionary where the key is "sector_method" + - Creates excel file as defined by user + """ + + # Prepare to save each LCA score table to a different worksheet in the same Excel file + excel_file = excel_file_name + column_positions = {} #stores the indexes of columns for plotting + with pd.ExcelWriter(excel_file, engine='openpyxl') as writer: + for sector in scores_dict.keys(): + lca_scores = scores_dict[sector]['lca_scores'] + for method, table in lca_scores.items(): + # Create a DataFrame for the current LCA score table + df = pd.DataFrame(table) + + # Add sector marker + df = _add_sector_marker(df, sector) #!! ADJUST POSITION + + # Add statistics to the DataFrame + df = _add_statistics(df) + + # Get the index values of columns + columns_of_interest = ["total", "rank", "mean", "2std_abv", "2std_blw", "q1", "q3", "method", "method unit"] + positions = {col: df.columns.get_loc(col) for col in columns_of_interest if col in df.columns} + column_positions[method] = positions + + # Find the first input column and add it to the positions dictionary + first_input_col_index = _find_first_input_column(df) + if first_input_col_index is not None: + positions["first_input"] = first_input_col_index + + # Store the positions for this method + column_positions[method] = positions + + # remove cpc from input labels + df = _clean_column_labels(df) + + # Generate a worksheet name + worksheet_name = f"{method}" #f"{sector}_{method}" + if len(worksheet_name) > 31: + worksheet_name = worksheet_name[:31] + + # Save the DataFrame to the Excel file in a new worksheet + df.to_excel(writer, sheet_name=worksheet_name, index=False) + return column_positions + + +def _add_statistics(df, column_name='total'): + + ''' + It is called in the function sector_lca_scores_to_excel_and_column_positions + + It adds statistical indicators to a dataframe based on total column which are used for plotting. + + returns updated dataframe + ''' + + #Need a rank row to plot the total LCA scores in descending order (satter opepyxl function takes in non categorial values) + df['rank'] = df[column_name].rank(method="first", ascending=False) + + # Calculate mean, standard deviation, and IQR + df['mean'] = df[column_name].mean() + df['2std_abv'] = df['mean'] + df[column_name].std() * 2 + df['2std_blw'] = df['mean'] - df[column_name].std() * 2 + df['q1'] = df[column_name].quantile(0.25) + df['q3'] = df[column_name].quantile(0.75) + + # Reorder the columns to place the new columns after 'total' + cols = df.columns.tolist() + total_index = cols.index(column_name) + 1 + new_cols = ['rank', 'mean', '2std_abv', '2std_blw', 'q1', 'q3'] + cols = cols[:total_index] + new_cols + cols[total_index:-len(new_cols)] + + return df[cols] + + +def _find_first_input_column(df): + ''' + It is called in the function sector_lca_scores_to_excel_and_column_positions. Needs to be called before _clean_column_labels function. + Detects the first column in the dataframe which contains input contribution data and saves its index. + This is relevant for calling the right column for defining the to be plotted data dynamically as not all dataframes have the same column order (some contain "direct emissions" for instance). + ''' + + def clean_label(label): + return label if label is not None else 'Unnamed' + + # Apply the cleaning function to all column names + df.columns = [clean_label(col) for col in df.columns] + + # Regular expression pattern to match "Number: Name" + pattern = r'^\d+:\s*' + + for idx, column in enumerate(df.columns): + if (column is not None and re.match(pattern, column)) or column == 'Unnamed' or column == 'direct emissions': + return idx + + return None + +def _clean_column_labels(df): + + ''' + It is called in the function sector_lca_scores_to_excel_and_column_positions. Needs to be run after _find_first_input_column. + + It removes unnecessary numbers in the column header. + + Returns df with formated column labels. + ''' + # Function to remove numbers and colon from column names + def clean_label(label): + if label is None: + return 'Unnamed' # or return 'Unnamed' if you prefer a placeholder + return re.sub(r'^\d+:\s*', '', str(label)) + + # Apply the cleaning function to all column names + df.columns = [clean_label(col) for col in df.columns] + + return df + +def _add_sector_marker(df, sector): + ''' + It is called in the function sector_lca_scores_to_excel_and_column_positions. + + It adds information about the sector for titel and labeling in plotting. + + Returns df with added column. + ''' + + # Add sector marker column + df['sector']=str(sector) # potentially remove! + # Reorder the columns to move 'sector' after 'product' + columns = list(df.columns) + + if 'product' in df.columns: + product_index = columns.index('product') + # Insert 'sector' after 'product' + columns.insert(product_index + 1, columns.pop(columns.index('sector'))) + else: + # If 'product' does not exist, 'sector' remains in the last column + columns.append(columns.pop(columns.index('sector'))) + + # Reassign the DataFrame with the new column order + df = df[columns] + return df diff --git a/dev/aktuell/methods.py b/dev/aktuell/methods.py new file mode 100644 index 0000000..1c48194 --- /dev/null +++ b/dev/aktuell/methods.py @@ -0,0 +1,62 @@ +# Dependencies +# ------------ + +# brightway +import brightway2 as bw +import bw2analyzer as ba +import bw2data as bd + +# Class for generating method dictionary +# -------------------------------------- + + +class MethodFinder: + def __init__(self): + self.all_methods = {} + self.method_counter = 0 + + def find_and_create_method(self, criteria, exclude=None, custom_key=None): + methods = bw.methods + # Start with all methods + filtered_methods = methods + # Apply inclusion criteria + for criterion in criteria: + filtered_methods = [m for m in filtered_methods if criterion in str(m)] + # Apply exclusion criteria if provided + if exclude: + for exclusion in exclude: + filtered_methods = [ + m for m in filtered_methods if exclusion not in str(m) + ] + # Check if we found exactly one method + if len(filtered_methods) == 0: + raise ValueError("No methods found matching the given criteria.") + elif len(filtered_methods) > 1: + raise ValueError( + f"Multiple methods found: {filtered_methods}. Please provide more specific criteria." + ) + # Get the first (and only) method + selected_method = filtered_methods[0] + # Create the Brightway Method object + method_object = bw.Method(selected_method) + + # Generate a key for storing the method + if custom_key is None: + self.method_counter += 1 + key = f"method_{self.method_counter}" + else: + key = custom_key + + # Store the method object and additional information in the dictionary + self.all_methods[key] = { + "object": method_object, + "method name": method_object.name, + "short name": method_object.name[2], + "unit": method_object.metadata.get("unit", "Unknown"), + } + + # Return both the method object and its key + return {key: self.all_methods[key]} + + def get_all_methods(self): + return self.all_methods diff --git a/dev/aktuell/plots_in_xcl.py b/dev/aktuell/plots_in_xcl.py new file mode 100644 index 0000000..7709063 --- /dev/null +++ b/dev/aktuell/plots_in_xcl.py @@ -0,0 +1,439 @@ +import re +import pandas as pd +from dopo import generate_sets_from_filters +from dopo import compare_activities_multiple_methods +from dopo import small_inputs_to_other_column +import openpyxl +from openpyxl import load_workbook +from openpyxl.chart import ScatterChart, Reference, Series +from openpyxl.chart import BarChart, Reference + +# IN EXCEL +def _categorize_sheets_by_sector(file_path): + # Load the workbook + workbook = load_workbook(filename=file_path, read_only=True) + + # Initialize a dictionary to hold sectors and their corresponding sheet names + worksheet_dict = {} + + # Iterate over all sheet names in the workbook + for sheet_name in workbook.sheetnames: + # Split the sheet name to extract the sector (assumes sector is the first part) + sector = sheet_name.split('_')[0] + + # Add the sheet name to the corresponding sector in the dictionary + if sector in worksheet_dict: + worksheet_dict[sector].append(sheet_name) + else: + worksheet_dict[sector] = [sheet_name] + + return worksheet_dict + + +# ---- +#PLOTS +# ---- + +from openpyxl import load_workbook +from openpyxl.chart import ScatterChart, Reference, Series + +def dot_plots_xcl(filepath_workbook, index_positions): + + worksheet_dict = _categorize_sheets_by_sector(filepath_workbook) + + # Load the workbook + wb = load_workbook(filepath_workbook) + + # Iterate over each sector and its associated worksheets + for sector, worksheet_names in worksheet_dict.items(): + + # Create or get the chart sheet for the current sector + chart_sheet_name = f"{sector}_charts" + if chart_sheet_name in wb.sheetnames: + ws_charts = wb[chart_sheet_name] + else: + ws_charts = wb.create_sheet(chart_sheet_name) + + # Initial position for the first chart + current_row = 1 # Start placing charts from row 1 + current_col = 1 # Start placing charts from column 1 + chart_height = 30 # Number of rows a chart occupies + chart_width = 12 # Number of columns a chart occupies + charts_per_row = 3 # Number of charts per row + + # Iterate over each worksheet name in the current sector + for i, worksheet_name in enumerate(worksheet_names): + ws = wb[worksheet_name] + + # Find min_row, max_row and max_column + max_row = ws.max_row + max_column = ws.max_column + min_row = 1 + + # Find the key in index_positions that contains worksheet_name + matching_key = None + for key in index_positions.keys(): + if worksheet_name in key: + matching_key = key + break + + if not matching_key: + print(f"Warning: No matching key found for worksheet '{worksheet_name}'. Skipping...") + continue + + # Retrieve the column positions from the index_positions dictionary + positions = index_positions[matching_key] + total_col = positions.get("total", None) + 1 + rank_col = positions.get("rank", None) + 1 + mean_col = positions.get("mean", None) + 1 + std_adv_col = positions.get("2std_abv", None) + 1 + std_blw_col = positions.get("2std_blw", None) + 1 + q1_col = positions.get("q1", None) + 1 + q3_col = positions.get("q3", None) + 1 + method_col = positions.get("method", None) + 1 + method_unit_col = positions.get("method unit", None) + 1 + + # Ensure that all required columns are present + if None in [total_col, rank_col, mean_col, std_adv_col, std_blw_col, q1_col, q3_col, method_col, method_unit_col]: + print(f"Warning: Missing columns in worksheet '{worksheet_name}' for sector '{sector}'. Skipping...") + continue + + # Create a ScatterChart (or other chart type as needed) + chart = ScatterChart() + + # Chart titles + method_value = ws.cell(row=2, column=method_col).value + chart.title = f"{method_value} LCA scores for {sector} sector" + + method_unit_value = ws.cell(row=2, column=method_unit_col).value + chart.y_axis.title = f"{method_unit_value}" + chart.x_axis.title = 'activity rank' + # Avoid overlap + chart.title.overlay = False + chart.x_axis.title.overlay = False + chart.y_axis.title.overlay = False + + # Define the data range for the chart + y_values = Reference(ws, min_col=total_col, min_row=min_row, max_row=max_row) + x_values = Reference(ws, min_col=rank_col, min_row=min_row, max_row=max_row) + + # Create a series and add it to the chart + series = Series(y_values, x_values, title_from_data=True) + chart.series.append(series) + chart.style = 9 + + # Customize the series to show only markers (dots) + series.marker.symbol = "circle" + series.marker.size = 5 + series.graphicalProperties.line.noFill = True + + # ADJUST X-AXIS + chart.x_axis.tickLblPos = "low" + chart.x_axis.majorGridlines = None + chart.x_axis.tickMarkSkip = 1 # Show all tick marks, this adresses the tick lines + chart.x_axis.tickLblSkip = 1 # Show all labels, doesnt work + + chart.x_axis.scaling.orientation = "minMax" + chart.x_axis.crosses = "autoZero" + chart.x_axis.axPos = "b" + chart.x_axis.delete = False + + # ADJUST Y-AXIS + chart.y_axis.tickLblPos = "nextTo" # Position the labels next to the tick marks + chart.y_axis.delete = False # Ensure axis is not deleted + chart.y_axis.number_format = '0.00000' + chart.y_axis.majorGridlines = None + + # ADD STATS + # MEAN + mean_y = Reference(ws, min_col=mean_col, min_row=min_row, max_row=max_row) + mean_series = Series(mean_y, x_values, title_from_data="True") + chart.series.append(mean_series) + mean_series.marker.symbol = "none" # No markers, just a line + mean_series.graphicalProperties.line.solidFill = "FF0000" # Red line for mean value + mean_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + + # IQR + iqr1 = Reference(ws, min_col=q1_col, min_row=min_row, max_row=max_row) + iqr3 = Reference(ws, min_col=q3_col, min_row=min_row, max_row=max_row) + iqr1_series = Series(iqr1, x_values, title_from_data="True") + iqr3_series = Series(iqr3, x_values, title_from_data="True") + chart.series.append(iqr1_series) + chart.series.append(iqr3_series) + iqr1_series.marker.symbol = "none" # No markers, just a line + iqr3_series.marker.symbol = "none" + iqr1_series.graphicalProperties.line.solidFill = "6082B6" # Blue line + iqr3_series.graphicalProperties.line.solidFill = "6082B6" + iqr1_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + iqr3_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + + # STD + std_abv = Reference(ws, min_col=std_adv_col, min_row=min_row, max_row=max_row) + std_blw = Reference(ws, min_col=std_blw_col, min_row=min_row, max_row=max_row) + std_abv_series = Series(std_abv, x_values, title_from_data="True") + std_blw_series = Series(std_blw, x_values, title_from_data="True") + chart.series.append(std_abv_series) + chart.series.append(std_blw_series) + std_abv_series.marker.symbol = "none" # No markers, just a line + std_blw_series.marker.symbol = "none" + std_abv_series.graphicalProperties.line.solidFill = "FFC300" # yellow line + std_blw_series.graphicalProperties.line.solidFill = "FFC300" + std_abv_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + std_blw_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + + # Set legend position to the right of the plot area + chart.legend.position = 'r' # 'r' for right + chart.legend.overlay = False + + # Adjust chart dimensions + chart.width = 20 # Width of the chart + chart.height = 14 # Height of the chart + + # Calculate the position for this chart + position = ws_charts.cell(row=current_row, column=current_col).coordinate + ws_charts.add_chart(chart, position) + + # Update position for the next chart + current_col += chart_width +1 + if (i + 1) % charts_per_row == 0: # Move to the next row after placing `charts_per_row` charts + current_row += chart_height +1 + current_col = 1 # Reset to the first column + + # Move the chart sheet to the first position + wb._sheets.remove(ws_charts) + wb._sheets.insert(0, ws_charts) + + wb.save(filepath_workbook) + return current_row + + +from openpyxl import load_workbook +from openpyxl.chart import BarChart, Reference + +def stacked_bars_xcl(filepath_workbook, index_positions, current_row_dot_plot): + + worksheet_dict = _categorize_sheets_by_sector(filepath_workbook) + # Load the workbook + wb = load_workbook(filepath_workbook) + + # Iterate over each sector and its associated worksheets + for sector, worksheet_names in worksheet_dict.items(): + + # Create or get the chart sheet for the current sector + chart_sheet_name = f"{sector}_charts" + if chart_sheet_name in wb.sheetnames: + ws_charts = wb[chart_sheet_name] + else: + ws_charts = wb.create_sheet(chart_sheet_name) + + # Initial position for the first chart + chart_height = 30 # Number of rows a chart occupies + chart_width = 12 # Number of columns a chart occupies + current_row = current_row_dot_plot + chart_height # Start placing charts from row where dot plots have left of + current_col = 1 # Start placing charts from column 1 + charts_per_row = 3 # Number of charts per row + + # Iterate over each worksheet name in the current sector + for i, worksheet_name in enumerate(worksheet_names): + ws = wb[worksheet_name] + + # Find the key in index_positions that contains worksheet_name + matching_key = None + for key in index_positions.keys(): + if worksheet_name in key: + matching_key = key + break + + if not matching_key: + print(f"Warning: No matching key found for worksheet '{worksheet_name}'. Skipping...") + continue + + # Retrieve the column positions from the index_positions dictionary + positions = index_positions[matching_key] + + # Find min_row, max_row and max_column + max_row = ws.max_row + max_column = ws.max_column + input_min_col = positions.get("first_input", None) + 1 + rank_col = positions.get("rank", None) + 1 + method_col = positions.get("method", None) + 1 + method_unit_col = positions.get("method unit", None) + 1 + + chart = BarChart() + chart.type = "bar" + chart.style = 2 + chart.grouping = "stacked" + chart.overlap = 100 + + # Chart titles + method_value = ws.cell(row=2, column=method_col).value + chart.title = f"{sector} sector inputs contributions to {method_value}" + + method_unit_value = ws.cell(row=2, column=method_unit_col).value + chart.y_axis.title = f"{method_unit_value}" + + chart.x_axis.title = 'activity index' + + # Avoid overlap + chart.title.overlay = False + chart.x_axis.title.overlay = False + chart.y_axis.title.overlay = False + chart.legend.overlay = False + + # Define data + data = Reference(ws, min_col=input_min_col, min_row=1, max_row=max_row, max_col=max_column) + cats = Reference(ws, min_col=rank_col, min_row=2, max_row=max_row) + + chart.add_data(data, titles_from_data=True) + chart.set_categories(cats) + chart.shape = 4 + + # Modify each series in the chart to disable the inversion of negative values + for series in chart.series: + series.invertIfNegative = False + + # y-axis ticks + chart.y_axis.tickLblPos = "nextTo" + chart.y_axis.delete = False # Ensure axis is not deleted + chart.y_axis.number_format = '0.000' + + # Adjust chart dimensions + chart.width = 20 # Width of the chart + chart.height = 14 # Height of the chart + + # Add the chart to the chart worksheet + # Calculate the position for this chart + position = ws_charts.cell(row=current_row, column=current_col).coordinate + ws_charts.add_chart(chart, position) + + # Update position for the next chart + current_col += chart_width +1 + if (i + 1) % charts_per_row == 0: # Move to the next row after placing `charts_per_row` charts + current_row += chart_height +1 + current_col = 1 # Reset to the first column + + # Move the chart sheet to the first position + wb._sheets.remove(ws_charts) + wb._sheets.insert(0, ws_charts) + + wb.save(filepath_workbook) + +# Plot 3: Comparing databases + +import pandas as pd +import openpyxl +from openpyxl.chart import BarChart, Reference + +def barchart_compare_db_xcl(filename, worksheet_dict=None, index_positions=None): + + # Load the workbook and select the sheet + wb = openpyxl.load_workbook(filename) + + # Iterate over each sector and its associated worksheets + for sector, worksheet_names in worksheet_dict.items(): + + # Create or get the chart sheet for the current sector + chart_sheet_name = f"{sector}_charts" + if chart_sheet_name in wb.sheetnames: + ws_charts = wb[chart_sheet_name] + else: + ws_charts = wb.create_sheet(chart_sheet_name) + + # Initial position for the first chart + current_row = 1 # Start placing charts from row 1 + current_col = 1 # Start placing charts from column 1 + chart_height = 30 # Number of rows a chart occupies + chart_width = 12 # Number of columns a chart occupies + charts_per_row = 2 # Number of charts per row + + # Iterate over each worksheet name in the current sector + for i, worksheet_name in enumerate(worksheet_names): + ws = wb[worksheet_name] + + # # Find the key in index_positions that contains worksheet_name + # matching_key = None + # for key in index_positions.keys(): + # if worksheet_name in key: + # matching_key = key + # break + + # if not matching_key: + # print(f"Warning: No matching key found for worksheet '{worksheet_name}'. Skipping...") + # continue + + # Retrieve the column positions from the index_positions dictionary + # positions = index_positions[matching_key] + + # Find min_row, max_row and max_column + min_col_data = 15 #positions.get("relative_change", None) + 1 + rank_col = 17#positions.get("rank", None) + 1 + method_col = 5#positions.get("method", None) + 1 + method_unit_col = 6#positions.get("method unit", None) + 1 + + # Create a bar chart + chart = BarChart() + chart.type="bar" + chart.style=2 + chart.overlap= 100 + chart.title = "Relative Change in LCA Scores" + chart.x_axis.title = "Activity" + chart.y_axis.title = "Relative Change (%)" + + # Set the data for the chart + data = Reference(ws, min_col=min_col_data, min_row=1, max_row=ws.max_row) + categories = Reference(ws, min_col=rank_col, min_row=2, max_row=ws.max_row) + chart.add_data(data, titles_from_data=True) + chart.set_categories(categories) + + # Modify each series in the chart to disable the inversion of negative values + for series in chart.series: + series.invertIfNegative = False + + # x-axis tickes + chart.x_axis.tickLblPos = "low" + chart.x_axis.majorGridlines = None + chart.x_axis.tickMarkSkip = 1 # Show all tick marks, this adresses the tick lines + chart.x_axis.tickLblSkip = 1 # Show all labels, doesnt work + chart.x_axis.delete = False # Ensure axis is not deleted + + # Chart titles + method_value = ws.cell(row=2, column=method_col).value + chart.title = f"{sector} {method_value} database lca scores relative changes" + + method_unit_value = ws.cell(row=2, column=method_unit_col).value + chart.x_axis.title = f"{method_unit_value}" + + chart.y_axis.title = 'relative change (%)' #its switched..... should be x_axis + + # Avoid overlap + chart.title.overlay = False + chart.x_axis.title.overlay = False + chart.y_axis.title.overlay = False + chart.legend.overlay = False + + # Adjust chart dimensions + chart.width = 20 # Width of the chart + chart.height = 14 # Height of the chart + + # Calculate the position for this chart + position = ws_charts.cell(row=current_row, column=current_col).coordinate + ws_charts.add_chart(chart, position) + + # Update position for the next chart + current_col += chart_width +1 + if (i + 1) % charts_per_row == 0: # Move to the next row after placing `charts_per_row` charts + current_row += chart_height +1 + current_col = 1 # Reset to the first column + + # Move the chart sheet to the first position + wb._sheets.remove(ws_charts) + wb._sheets.insert(0, ws_charts) + + # Add the chart to a new worksheet + # new_sheet = wb.create_sheet(title="LCA Chart") + # new_sheet.add_chart(chart, "A1") + + # Save the workbook + wb.save(filename) + + print(f"Results and chart saved to {filename}") \ No newline at end of file diff --git a/dev/aktuell/sector_score_dict.py b/dev/aktuell/sector_score_dict.py new file mode 100644 index 0000000..f6221bb --- /dev/null +++ b/dev/aktuell/sector_score_dict.py @@ -0,0 +1,154 @@ +# Inputs +# ------ +from premise import * + +# data?? +import os +import yaml +import peewee as pw + +# brightway +import brightway2 as bw +import bw2analyzer as ba +import bw2data as bd + +# common +import pandas as pd +import numpy as np + +# plotting +import matplotlib.pyplot as plt +import seaborn as sns + +# to be completed +import ast + + +# Function based on brightways bw2analyzer (ba) function for generating dataframe containing total score and contribution by inputs +# ----------------------------------------------------------------------------------------------------------------------------- + + +def compare_activities_multiple_methods( + activities_list, methods, identifier, output_format="pandas", mode="absolute" +): + """ + Compares a set of activities by multiple methods, stores each generated dataframe as a variable (the method is the variable name) in a dictionary. + + :param activities_list: List of activities to compare + :param methods: List of Brightway Method objects + :param identifier: A string used in defining the variable names to better identify comparisons (e.g. sector name). + :param output_format: Output format for the comparison (default: 'pandas') + :param mode: Mode for the comparison (default: 'absolute'; others: 'relative') + :return: Dictionary of resulting dataframes from the comparisons + """ + dataframes_dict = {} + + for method_key, method_details in methods.items(): + result = ba.comparisons.compare_activities_by_grouped_leaves( + activities_list, + method_details["object"].name, + output_format=output_format, + mode=mode, + ) + + # Create a variable name using the method name tuple and identifier + method_name = method_details["object"].name[2].replace(" ", "_").lower() + var_name = f"{identifier}_{method_name}" + + # add two columns method and method unit to the df + result["method"] = str(method_details["object"].name[2]) + result["method unit"] = str(method_details["object"].metadata["unit"]) + + # order the columns after column unit + cols = list(result.columns) + unit_index = cols.index("unit") + cols.insert(unit_index + 1, cols.pop(cols.index("method"))) + cols.insert(unit_index + 2, cols.pop(cols.index("method unit"))) + result = result[cols] + + # Order the rows based on 'activity' and 'location' columns + result = result.sort_values(["activity", "location"]) + + # Reset the index numbering + result = result.reset_index(drop=True) + + # Store the result in the dictionary + dataframes_dict[var_name] = result + + return dataframes_dict + + +# Function for creating 'other' category for insignificant input contributions (for dataframes generated with compare_activities_multiple_methods) +# ------------------------------------------------------------------------------------------------------------------------------------------------- + + +def small_inputs_to_other_column(dataframes_dict, cutoff=0.01): + ''' + Aggregate values into a new 'other' column for those contributing less than or equal to the cutoff value to the 'total' column value. + Set the aggregated values to zero in their original columns. + Remove any columns that end up containing only zeros. + + Additionally, if a column is named None or "Unnamed", its values will be added to the 'other' column and then the original column will be deleted. + + :param dataframes_dict: the dictionary + ''' + + processed_dict = {} + + for key, df in dataframes_dict.items(): + # Identify the 'total' column + total_col_index = df.columns.get_loc('total') + + # Separate string and numeric columns + string_cols = df.iloc[:, :total_col_index] + numeric_cols = df.iloc[:, total_col_index:] + numeric_cols = numeric_cols.astype(float) + + # Calculate the threshold for each row (cutoff% of total) + threshold = numeric_cols['total'] * cutoff + + # Create 'other' column + numeric_cols['other'] = 0.0 + print(numeric_cols['other']) + # Identify and handle columns that are None or called "Unnamed" + columns_to_remove = [] + for col in df.columns: + if col is None or col == "None" or str(col).startswith("Unnamed"): + numeric_cols['other'] += df[col].fillna(0) + print(numeric_cols['other']) # Add the values to the 'other' column, NaN values to zero to avoid complications of present + columns_to_remove.append(col) + + print(columns_to_remove) + + # Drop the identified columns + numeric_cols.drop(columns=columns_to_remove, inplace=True) + + # Process each numeric column (except 'total' and 'other') + for col in numeric_cols.columns[1:-1]: # Skip 'total' and 'other' + # Identify values less than the threshold + mask = abs(numeric_cols[col]) < threshold # abs() to include negative contributions + + # Add these values to 'other' + numeric_cols.loc[mask, 'other'] += numeric_cols.loc[mask, col] + + # Set these values to zero in the original column + numeric_cols.loc[mask, col] = 0 + + # Remove columns with all zeros (except 'total' and 'other') + cols_to_keep = ['total'] + [col for col in numeric_cols.columns[1:-1] + if not (numeric_cols[col] == 0).all()] + cols_to_keep.append('other') + + numeric_cols = numeric_cols[cols_to_keep] + + # Combine string and processed numeric columns + processed_df = pd.concat([string_cols, numeric_cols], axis=1) + + # Sort DataFrame by total (optional) + processed_df = processed_df.sort_values('total', ascending=False) + + # Store the processed DataFrame in the result dictionary + processed_dict[key] = processed_df + + return processed_dict + diff --git a/dev/activity_filter.py b/dev/archiv/archiv py/activity_filter.py similarity index 100% rename from dev/activity_filter.py rename to dev/archiv/archiv py/activity_filter.py diff --git a/dev/compare_databases_to_excel.py b/dev/archiv/archiv py/compare_databases_to_excel.py similarity index 100% rename from dev/compare_databases_to_excel.py rename to dev/archiv/archiv py/compare_databases_to_excel.py diff --git a/dev/cpc_inputs.py b/dev/archiv/archiv py/cpc_inputs.py similarity index 100% rename from dev/cpc_inputs.py rename to dev/archiv/archiv py/cpc_inputs.py diff --git a/dev/archiv/archiv py/dopo_excel.py b/dev/archiv/archiv py/dopo_excel.py new file mode 100644 index 0000000..ce7b839 --- /dev/null +++ b/dev/archiv/archiv py/dopo_excel.py @@ -0,0 +1,553 @@ +# Functions for dopo in excel +# dependencies + +import re +import pandas as pd +from dopo import generate_sets_from_filters +from dopo import compare_activities_multiple_methods +from dopo import small_inputs_to_other_column +import openpyxl +from openpyxl import load_workbook +from openpyxl.chart import ScatterChart, Reference, Series +from openpyxl.chart import BarChart, Reference + +import copy + +def process_yaml_files(files_dict, database): + ''' + - Runs through the files_dict reading the defined filters in the yaml files. + - With another function a list that contains the filtered activities is created from the chosen database. + - This activity list is saved within the corresponding key (sector) in the dictionary main_dict which is based on the files_dict. + + :param files_dict: dictionary of dictionaries. It should hold the yaml file path and the title in the first row of the yaml file. + Like so: files_dict['Cement']={'yaml': 'yamls\cement_small.yaml', 'yaml identifier': 'Cement'} + :param database: premise or ecoinvent database of choice. + + It returns an updated dictionary which contains filtered activity lists for each sector. + ''' + + main_dict = copy.deepcopy(files_dict) + + for key, value in main_dict.items(): + yaml_file = value['yaml'] + yaml_identifier = value['yaml identifier'] + + #debug + print(f"Processing {key} with database {database.name}") # check for right database + + # Generate the sector activities + sector_activities = generate_sets_from_filters(yaml_file, database) + + #debug + print(f"Activities for {key}:") + for activity in sector_activities[yaml_identifier]: + print(f" {activity.key}") + + # Convert the set of activities to a list + activities_list = list(sector_activities[yaml_identifier]) + + # Add to the sectors_dict + main_dict[key]['activities'] = activities_list + + return main_dict + +def sector_lca_scores(main_dict, method_dict): + ''' + Generates the LCA score tables for activity list of each sector. + The tables contain total scores and cpc input contributions. + This is done by each method defined in the method dictionary. + + :param main_dict: dictionary which is returned by process_yaml_files function + :param method_dict: dictionary which is created with MethodFinder class + + It returns the main dictionary updated as scores dictionary which also holds the former information for each sector. + The LCA scores are stored by method name in the respective sector dictionary within the main dictionary. + ''' + + # Initialize scores_dict as a copy of main_dict + scores_dict = main_dict.copy() + + # Loop through each sector in main_dict + for sector in scores_dict.keys(): + # Extract activities for the current sector + sector_activities = scores_dict[sector]['activities'] + + # Calculate LCA scores using the specified method + lca_scores = compare_activities_multiple_methods( + activities_list=sector_activities, + methods=method_dict, + identifier=sector, + mode='absolute' + ) + + # Apply the small_inputs_to_other_column function with the cutoff value + lca_scores = small_inputs_to_other_column(lca_scores, cutoff=0.02) + + # Save the LCA scores to the scores_dict + scores_dict[sector]['lca_scores'] = lca_scores + + return scores_dict + +# ----------------------------------------- +# CREATING EXCEL SHEETS WITH LCA TABLES +# ----------------------------------------- + +def sector_lca_scores_to_excel_and_column_positions(scores_dict, excel_file_name): + """ + What it does: + - Creates a dataframe for each method and sector from the lca scores dictionary + - Before storing each df in a worksheet in an excel file it: + - shortens the column labels of the input (removing cpc code) + - adds a sector name marker for keeping track in excel (when plotting can use it for labeling) + - adds statistics for plotting + - creates a dictionary which holds the indexes to the columns we need to call for plotting, this makes it dynamic. Otherwise need to hardcode index column number for openpxyl. + What it returns: + - Returns the index positions dictionary where the key is "sector_method" + - Creates excel file as defined by user + """ + + # Prepare to save each LCA score table to a different worksheet in the same Excel file + excel_file = excel_file_name + column_positions = {} #stores the indexes of columns for plotting + with pd.ExcelWriter(excel_file, engine='openpyxl') as writer: + for sector in scores_dict.keys(): + lca_scores = scores_dict[sector]['lca_scores'] + for method, table in lca_scores.items(): + # Create a DataFrame for the current LCA score table + df = pd.DataFrame(table) + + # Add sector marker + df = add_sector_marker(df, sector) #!! ADJUST POSITION + + # Add statistics to the DataFrame + df = add_statistics(df) + + # Get the index values of columns + columns_of_interest = ["total", "rank", "mean", "2std_abv", "2std_blw", "q1", "q3", "method", "method unit"] + positions = {col: df.columns.get_loc(col) for col in columns_of_interest if col in df.columns} + column_positions[method] = positions + + # Find the first input column and add it to the positions dictionary + first_input_col_index = find_first_input_column(df) + if first_input_col_index is not None: + positions["first_input"] = first_input_col_index + + # Store the positions for this method + column_positions[method] = positions + + # remove cpc from input labels + df = clean_column_labels(df) + + # Generate a worksheet name + worksheet_name = f"{method}" #f"{sector}_{method}" + if len(worksheet_name) > 31: + worksheet_name = worksheet_name[:31] + + # Save the DataFrame to the Excel file in a new worksheet + df.to_excel(writer, sheet_name=worksheet_name, index=False) + return column_positions + + +def add_statistics(df, column_name='total'): + + ''' + It is called in the function sector_lca_scores_to_excel_and_column_positions + + It adds statistical indicators to a dataframe based on total column which are used for plotting. + + returns updated dataframe + ''' + + #Need a rank row to plot the total LCA scores in descending order (satter opepyxl function takes in non categorial values) + df['rank'] = df[column_name].rank(method="first", ascending="False") + + # Calculate mean, standard deviation, and IQR + df['mean'] = df[column_name].mean() + df['2std_abv'] = df['mean'] + df[column_name].std() * 2 + df['2std_blw'] = df['mean'] - df[column_name].std() * 2 + df['q1'] = df[column_name].quantile(0.25) + df['q3'] = df[column_name].quantile(0.75) + + # Reorder the columns to place the new columns after 'total' + cols = df.columns.tolist() + total_index = cols.index(column_name) + 1 + new_cols = ['rank', 'mean', '2std_abv', '2std_blw', 'q1', 'q3'] + cols = cols[:total_index] + new_cols + cols[total_index:-len(new_cols)] + + return df[cols] + + +def find_first_input_column(df): + ''' + It is called in the function sector_lca_scores_to_excel_and_column_positions. Needs to be called before clean_column_labels function. + Detects the first column in the dataframe which contains input contribution data and saves its index. + This is relevant for calling the right column for defining the to be plotted data dynamically as not all dataframes have the same column order (some contain "direct emissions" for instance). + ''' + + def clean_label(label): + return label if label is not None else 'Unnamed' + + # Apply the cleaning function to all column names + df.columns = [clean_label(col) for col in df.columns] + + # Regular expression pattern to match "Number: Name" + pattern = r'^\d+:\s*' + + for idx, column in enumerate(df.columns): + if (column is not None and re.match(pattern, column)) or column == 'Unnamed' or column == 'direct emissions': + return idx + + return None + +def clean_column_labels(df): + + ''' + It is called in the function sector_lca_scores_to_excel_and_column_positions. Needs to be run after find_first_input_column. + + It removes unnecessary numbers in the column header. + + Returns df with formated column labels. + ''' + # Function to remove numbers and colon from column names + def clean_label(label): + if label is None: + return 'Unnamed' # or return 'Unnamed' if you prefer a placeholder + return re.sub(r'^\d+:\s*', '', str(label)) + + # Apply the cleaning function to all column names + df.columns = [clean_label(col) for col in df.columns] + + return df + +def add_sector_marker(df, sector): + ''' + It is called in the function sector_lca_scores_to_excel_and_column_positions. + + It adds information about the sector for titel and labeling in plotting. + + Returns df with added column. + ''' + + # Add sector marker column + df['sector']=str(sector) # potentially remove! + # Reorder the columns to move 'sector' after 'product' + columns = list(df.columns) + + if 'product' in df.columns: + product_index = columns.index('product') + # Insert 'sector' after 'product' + columns.insert(product_index + 1, columns.pop(columns.index('sector'))) + else: + # If 'product' does not exist, 'sector' remains in the last column + columns.append(columns.pop(columns.index('sector'))) + + # Reassign the DataFrame with the new column order + df = df[columns] + return df + +# IN EXCEL +def categorize_sheets_by_sector(file_path): + # Load the workbook + workbook = load_workbook(filename=file_path, read_only=True) + + # Initialize a dictionary to hold sectors and their corresponding sheet names + worksheet_dict = {} + + # Iterate over all sheet names in the workbook + for sheet_name in workbook.sheetnames: + # Split the sheet name to extract the sector (assumes sector is the first part) + sector = sheet_name.split('_')[0] + + # Add the sheet name to the corresponding sector in the dictionary + if sector in worksheet_dict: + worksheet_dict[sector].append(sheet_name) + else: + worksheet_dict[sector] = [sheet_name] + + return worksheet_dict + + +# ---- +#PLOTS +# ---- + +from openpyxl import load_workbook +from openpyxl.chart import ScatterChart, Reference, Series + +def dot_plots(filepath_workbook, worksheet_dict, index_positions): + + # Load the workbook + wb = load_workbook(filepath_workbook) + + # Iterate over each sector and its associated worksheets + for sector, worksheet_names in worksheet_dict.items(): + + # Create or get the chart sheet for the current sector + chart_sheet_name = f"{sector}_charts" + if chart_sheet_name in wb.sheetnames: + ws_charts = wb[chart_sheet_name] + else: + ws_charts = wb.create_sheet(chart_sheet_name) + + # Initial position for the first chart + current_row = 1 # Start placing charts from row 1 + current_col = 1 # Start placing charts from column 1 + chart_height = 30 # Number of rows a chart occupies + chart_width = 12 # Number of columns a chart occupies + charts_per_row = 3 # Number of charts per row + + # Iterate over each worksheet name in the current sector + for i, worksheet_name in enumerate(worksheet_names): + ws = wb[worksheet_name] + + # Find min_row, max_row and max_column + max_row = ws.max_row + max_column = ws.max_column + min_row = 1 + + # Find the key in index_positions that contains worksheet_name + matching_key = None + for key in index_positions.keys(): + if worksheet_name in key: + matching_key = key + break + + if not matching_key: + print(f"Warning: No matching key found for worksheet '{worksheet_name}'. Skipping...") + continue + + # Retrieve the column positions from the index_positions dictionary + positions = index_positions[matching_key] + total_col = positions.get("total", None) + 1 + rank_col = positions.get("rank", None) + 1 + mean_col = positions.get("mean", None) + 1 + std_adv_col = positions.get("2std_abv", None) + 1 + std_blw_col = positions.get("2std_blw", None) + 1 + q1_col = positions.get("q1", None) + 1 + q3_col = positions.get("q3", None) + 1 + method_col = positions.get("method", None) + 1 + method_unit_col = positions.get("method unit", None) + 1 + + # Ensure that all required columns are present + if None in [total_col, rank_col, mean_col, std_adv_col, std_blw_col, q1_col, q3_col, method_col, method_unit_col]: + print(f"Warning: Missing columns in worksheet '{worksheet_name}' for sector '{sector}'. Skipping...") + continue + + # Create a ScatterChart (or other chart type as needed) + chart = ScatterChart() + + # Chart titles + method_value = ws.cell(row=2, column=method_col).value + chart.title = f"{method_value} LCA scores for {sector} sector" + + method_unit_value = ws.cell(row=2, column=method_unit_col).value + chart.y_axis.title = f"{method_unit_value}" + chart.x_axis.title = 'activity rank' + # Avoid overlap + chart.title.overlay = False + chart.x_axis.title.overlay = False + chart.y_axis.title.overlay = False + + # Define the data range for the chart + y_values = Reference(ws, min_col=total_col, min_row=min_row, max_row=max_row) + x_values = Reference(ws, min_col=rank_col, min_row=min_row, max_row=max_row) + + # Create a series and add it to the chart + series = Series(y_values, x_values, title_from_data=True) + chart.series.append(series) + chart.style = 9 + + # Customize the series to show only markers (dots) + series.marker.symbol = "circle" + series.marker.size = 5 + series.graphicalProperties.line.noFill = True + + # ADJUST X-AXIS + chart.x_axis.tickLblPos = "low" + chart.x_axis.majorGridlines = None + chart.x_axis.tickMarkSkip = 1 # Show all tick marks, this adresses the tick lines + chart.x_axis.tickLblSkip = 1 # Show all labels, doesnt work + + chart.x_axis.scaling.orientation = "minMax" + chart.x_axis.crosses = "autoZero" + chart.x_axis.axPos = "b" + chart.x_axis.delete = False + + # ADJUST Y-AXIS + chart.y_axis.tickLblPos = "nextTo" # Position the labels next to the tick marks + chart.y_axis.delete = False # Ensure axis is not deleted + chart.y_axis.number_format = '0.00000' + chart.y_axis.majorGridlines = None + + # ADD STATS + # MEAN + mean_y = Reference(ws, min_col=mean_col, min_row=min_row, max_row=max_row) + mean_series = Series(mean_y, x_values, title_from_data="True") + chart.series.append(mean_series) + mean_series.marker.symbol = "none" # No markers, just a line + mean_series.graphicalProperties.line.solidFill = "FF0000" # Red line for mean value + mean_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + + # IQR + iqr1 = Reference(ws, min_col=q1_col, min_row=min_row, max_row=max_row) + iqr3 = Reference(ws, min_col=q3_col, min_row=min_row, max_row=max_row) + iqr1_series = Series(iqr1, x_values, title_from_data="True") + iqr3_series = Series(iqr3, x_values, title_from_data="True") + chart.series.append(iqr1_series) + chart.series.append(iqr3_series) + iqr1_series.marker.symbol = "none" # No markers, just a line + iqr3_series.marker.symbol = "none" + iqr1_series.graphicalProperties.line.solidFill = "6082B6" # Blue line + iqr3_series.graphicalProperties.line.solidFill = "6082B6" + iqr1_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + iqr3_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + + # STD + std_abv = Reference(ws, min_col=std_adv_col, min_row=min_row, max_row=max_row) + std_blw = Reference(ws, min_col=std_blw_col, min_row=min_row, max_row=max_row) + std_abv_series = Series(std_abv, x_values, title_from_data="True") + std_blw_series = Series(std_blw, x_values, title_from_data="True") + chart.series.append(std_abv_series) + chart.series.append(std_blw_series) + std_abv_series.marker.symbol = "none" # No markers, just a line + std_blw_series.marker.symbol = "none" + std_abv_series.graphicalProperties.line.solidFill = "FFC300" # yellow line + std_blw_series.graphicalProperties.line.solidFill = "FFC300" + std_abv_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + std_blw_series.graphicalProperties.line.width = 10000 # Set line width (default units are EMUs) + + # Set legend position to the right of the plot area + chart.legend.position = 'r' # 'r' for right + chart.legend.overlay = False + + # Adjust chart dimensions + chart.width = 20 # Width of the chart + chart.height = 14 # Height of the chart + + # Calculate the position for this chart + position = ws_charts.cell(row=current_row, column=current_col).coordinate + ws_charts.add_chart(chart, position) + + # Update position for the next chart + current_col += chart_width +1 + if (i + 1) % charts_per_row == 0: # Move to the next row after placing `charts_per_row` charts + current_row += chart_height +1 + current_col = 1 # Reset to the first column + + # Move the chart sheet to the first position + wb._sheets.remove(ws_charts) + wb._sheets.insert(0, ws_charts) + + wb.save(filepath_workbook) + return current_row + + +from openpyxl import load_workbook +from openpyxl.chart import BarChart, Reference + +def stacked_bars(filepath_workbook, worksheet_dict, index_positions, current_row_dot_plot): + # Load the workbook + wb = load_workbook(filepath_workbook) + + # Iterate over each sector and its associated worksheets + for sector, worksheet_names in worksheet_dict.items(): + + # Create or get the chart sheet for the current sector + chart_sheet_name = f"{sector}_charts" + if chart_sheet_name in wb.sheetnames: + ws_charts = wb[chart_sheet_name] + else: + ws_charts = wb.create_sheet(chart_sheet_name) + + # Initial position for the first chart + chart_height = 30 # Number of rows a chart occupies + chart_width = 12 # Number of columns a chart occupies + current_row = current_row_dot_plot + chart_height # Start placing charts from row where dot plots have left of + current_col = 1 # Start placing charts from column 1 + charts_per_row = 3 # Number of charts per row + + # Iterate over each worksheet name in the current sector + for i, worksheet_name in enumerate(worksheet_names): + ws = wb[worksheet_name] + + # Find the key in index_positions that contains worksheet_name + matching_key = None + for key in index_positions.keys(): + if worksheet_name in key: + matching_key = key + break + + if not matching_key: + print(f"Warning: No matching key found for worksheet '{worksheet_name}'. Skipping...") + continue + + # Retrieve the column positions from the index_positions dictionary + positions = index_positions[matching_key] + + # Find min_row, max_row and max_column + max_row = ws.max_row + max_column = ws.max_column + input_min_col = positions.get("first_input", None) + 1 + rank_col = positions.get("rank", None) + 1 + method_col = positions.get("method", None) + 1 + method_unit_col = positions.get("method unit", None) + 1 + + chart = BarChart() + chart.type = "bar" + chart.style = 2 + chart.grouping = "stacked" + chart.overlap = 100 + + # Chart titles + method_value = ws.cell(row=2, column=method_col).value + chart.title = f"{sector} sector inputs contributions to {method_value}" + + method_unit_value = ws.cell(row=2, column=method_unit_col).value + chart.y_axis.title = f"{method_unit_value}" + + chart.x_axis.title = 'activity index' + + # Avoid overlap + chart.title.overlay = False + chart.x_axis.title.overlay = False + chart.y_axis.title.overlay = False + chart.legend.overlay = False + + # Define data + data = Reference(ws, min_col=input_min_col, min_row=1, max_row=max_row, max_col=max_column) + cats = Reference(ws, min_col=rank_col, min_row=2, max_row=max_row) + + chart.add_data(data, titles_from_data=True) + chart.set_categories(cats) + chart.shape = 4 + + # Modify each series in the chart to disable the inversion of negative values + for series in chart.series: + series.invertIfNegative = False + + # y-axis ticks + chart.y_axis.tickLblPos = "nextTo" + chart.y_axis.delete = False # Ensure axis is not deleted + chart.y_axis.number_format = '0.000' + + # Adjust chart dimensions + chart.width = 20 # Width of the chart + chart.height = 14 # Height of the chart + + # Add the chart to the chart worksheet + # Calculate the position for this chart + position = ws_charts.cell(row=current_row, column=current_col).coordinate + ws_charts.add_chart(chart, position) + + # Update position for the next chart + current_col += chart_width +1 + if (i + 1) % charts_per_row == 0: # Move to the next row after placing `charts_per_row` charts + current_row += chart_height +1 + current_col = 1 # Reset to the first column + + # Move the chart sheet to the first position + wb._sheets.remove(ws_charts) + wb._sheets.insert(0, ws_charts) + + wb.save(filepath_workbook) + diff --git a/dev/functions_v2.py b/dev/archiv/archiv py/functions_v2.py similarity index 97% rename from dev/functions_v2.py rename to dev/archiv/archiv py/functions_v2.py index 0d776f6..39f939d 100644 --- a/dev/functions_v2.py +++ b/dev/archiv/archiv py/functions_v2.py @@ -1,940 +1,940 @@ -""" -This module contains functions for the premise validation framework. -It includes functions for filtering of premise transformed ecoinvent databases, collecting relevant -data in a suitable format, and visualizing datasets. -Some of the functions included are brightway2 functions. - -""" -#NOTE: The comments are not all up to date. - -# Python import dependencies -# -------------------------- -from premise import * - -# data?? -import os -import yaml -import peewee as pw - -#brightway -import brightway2 as bw -import bw2analyzer as ba -import bw2data as bd - -#common -import pandas as pd -import numpy as np - -#plotting -import matplotlib.pyplot as plt -import seaborn as sns - -#to be completed -import ast - -# ----------------------------------------------------------------------------- -# DATABASE FILTERING -# ----------------------------------------------------------------------------- - - -# Sector filter functions from premise -# --------------------------------------------------- - -def act_fltr( - database: list, - fltr = None, - mask = None, -): - """Filter `database` for activities_list matching field contents given by `fltr` excluding strings in `mask`. - `fltr`: string, list of strings or dictionary. - If a string is provided, it is used to match the name field from the start (*startswith*). - If a list is provided, all strings in the lists are used and dataframes_dict are joined (*or*). - A dict can be given in the form : to filter for in . - `mask`: used in the same way as `fltr`, but filters add up with each other (*and*). - `filter_exact` and `mask_exact`: boolean, set `True` to only allow for exact matches. - - :param database: A lice cycle inventory database - :type database: brightway2 database object - :param fltr: value(s) to filter with. - :type fltr: Union[str, lst, dict] - :param mask: value(s) to filter with. - :type mask: Union[str, lst, dict] - :return: list of activity data set names - :rtype: list - - """ - if fltr is None: - fltr = {} - if mask is None: - mask = {} - - # default field is name - if isinstance(fltr, (list, str)): - fltr = {"name": fltr} - if isinstance(mask, (list, str)): - mask = {"name": mask} - - assert len(fltr) > 0, "Filter dict must not be empty." - - # find `act` in `database` that match `fltr` - # and do not match `mask` - filters = database - for field, value in fltr.items(): - if isinstance(value, list): - for val in value: - filters = [a for a in filters if val in a[field]] - - #filters.extend([ws.either(*[ws.contains(field, v) for v in value])]) - else: - filters = [ - a for a in filters if value in a[field] - ] - - #filters.append(ws.contains(field, value)) - - - if mask: - for field, value in mask.items(): - if isinstance(value, list): - for val in value: - filters = [f for f in filters if val not in f[field]] - #filters.extend([ws.exclude(ws.contains(field, v)) for v in value]) - else: - filters = [f for f in filters if value not in f[field]] - #filters.append(ws.exclude(ws.contains(field, value))) - - return filters - - -def generate_sets_from_filters(yaml_filepath, database=None) -> dict: - """ - Generate a dictionary with sets of activity names for - technologies from the filter specifications. - - :param filtr: - :func:`activity_maps.InventorySet.act_fltr`. - :return: dictionary with the same keys as provided in filter - and a set of activity data set names as values. - :rtype: dict - """ - - filtr=get_mapping(yaml_filepath, var='ecoinvent_aliases') - - names = [] - - for entry in filtr.values(): - if "fltr" in entry: - if isinstance(entry["fltr"], dict): - if "name" in entry["fltr"]: - names.extend(entry["fltr"]["name"]) - elif isinstance(entry["fltr"], list): - names.extend(entry["fltr"]) - else: - names.append(entry["fltr"]) - - #subset = list( - # ws.get_many( - # database, - # ws.either(*[ws.contains("name", name) for name in names]), - # ) - #) - - subset=[ - a for a in database if any( - - - x in a["name"] for x in names - ) - ] - - - techs = { - tech: act_fltr(subset, fltr.get("fltr"), fltr.get("mask")) - for tech, fltr in filtr.items() - } - - mapping = { - tech: {act for act in actlst} for tech, actlst in techs.items() - } - - - return mapping - -def get_mapping(filepath, var): - """ - Loa a YAML file and return a dictionary given a variable. - :param filepath: YAML file path - :param var: variable to return the dictionary for. - :param model: if provided, only return the dictionary for this model. - :return: a dictionary - """ - - with open(filepath, "r", encoding="utf-8") as stream: - techs = yaml.full_load(stream) - - mapping = {} - for key, val in techs.items(): - if var in val: - mapping[key] = val[var] - - return mapping - - -# Example on how to call the functions to create a set of filtered activities_list -#set_from_fltrs = generate_sets_from_filters(filtr=get_mapping(yaml_filepath, "ecoinvent_aliases"), database=ei39SSP - -# ----------------------------------------------------------------------------- -# METHODS -# ----------------------------------------------------------------------------- - -# Class for generating method dictionary -# -------------------------------------- -class MethodFinder: - def __init__(self): - self.all_methods = {} - self.method_counter = 0 - - def find_and_create_method(self, criteria, exclude=None, custom_key=None): - methods = bw.methods - # Start with all methods - filtered_methods = methods - # Apply inclusion criteria - for criterion in criteria: - filtered_methods = [m for m in filtered_methods if criterion in str(m)] - # Apply exclusion criteria if provided - if exclude: - for exclusion in exclude: - filtered_methods = [m for m in filtered_methods if exclusion not in str(m)] - # Check if we found exactly one method - if len(filtered_methods) == 0: - raise ValueError("No methods found matching the given criteria.") - elif len(filtered_methods) > 1: - raise ValueError(f"Multiple methods found: {filtered_methods}. Please provide more specific criteria.") - # Get the first (and only) method - selected_method = filtered_methods[0] - # Create the Brightway Method object - method_object = bw.Method(selected_method) - - # Generate a key for storing the method - if custom_key is None: - self.method_counter += 1 - key = f"method_{self.method_counter}" - else: - key = custom_key - - # Store the method object and additional information in the dictionary - self.all_methods[key] = { - 'object': method_object, - 'method name': str(method_object.name), - 'short name' : str(method_object.name[2]), - 'unit': str(method_object.metadata.get('unit', 'Unknown')) - } - - # Return both the method object and its key - return {key: self.all_methods[key]} - - def get_all_methods(self): - return self.all_methods - -# Setting up the methods for outlier detection -# --------------------------------------------------------------------- - -def find_and_create_method(criteria, exclude=None): - """ - Find a method based on given criteria and create a Brightway Method object. This will choose the first method. - Thus, filter criteria need to be defined precisely to pick the right method. - - :param criteria: List of strings that should be in the method name - :param exclude: List of strings that should not be in the method name (optional) - :return: Brightway Method object - """ - methods = bw.methods - - # Start with all methods - filtered_methods = methods - - # Apply inclusion criteria - for criterion in criteria: - filtered_methods = [m for m in filtered_methods if criterion in str(m)] - - # Apply exclusion criteria if provided - if exclude: - for exclusion in exclude: - filtered_methods = [m for m in filtered_methods if exclusion not in str(m)] - - # Check if we found exactly one method - if len(filtered_methods) == 0: - raise ValueError("No methods found matching the given criteria.") - elif len(filtered_methods) > 1: - raise ValueError(f"Multiple methods found: {filtered_methods}. Please provide more specific criteria.") - - # Get the first (and only) method - selected_method = filtered_methods[0] - - # Create and return the Brightway Method object storing it in a defined variable outside of the funciton. - return bw.Method(selected_method) - -#NOTE: Would a yaml filter make it easier? OR Could have predefined methods?""" - -# Function for creating method dictionaries which holds method name and unit for later tracking of methods. -# --------------------------------------------------------------------------------------------------------- - -def create_method_dict(selected_methods_list): - ''' - :selected_methods_list: a list of variables which contain the selected methods - - ''' - method_dict = {} - for method in selected_methods_list: - method_dict[method] = { - 'short name': str(method.name[2]), - 'method name': str(method.name), - 'method unit': str(method.metadata['unit']) - } - - return method_dict - -# ------------------------------------------------------------------------------------------------------------------------------ -# CALCULATIONS -# ------------------------------------------------------------------------------------------------------------------------------ - -# Function based on brightways bw2analyzer (ba) function for generating dataframe containing total score and contribution by inputs -# ----------------------------------------------------------------------------------------------------------------------------- - -def compare_activities_multiple_methods(activities_list, methods, identifier, output_format='pandas', mode='absolute'): - """ - Compares a set of activities by multiple methods, stores each generated dataframe as a variable (the method is the variable name) in a dictionary. - - :param activities_list: List of activities to compare - :param methods: List of Brightway Method objects - :param identifier: A string used in defining the variable names to better identify comparisons (e.g. sector name). - :param output_format: Output format for the comparison (default: 'pandas') - :param mode: Mode for the comparison (default: 'absolute'; others: 'relative') - :return: Dictionary of resulting dataframes from the comparisons - """ - dataframes_dict = {} - - for method_key, method_details in methods.items(): - result = ba.comparisons.compare_activities_by_grouped_leaves( - activities_list, - method_details['object'].name, - output_format=output_format, - mode=mode - ) - - # Create a variable name using the method name tuple and identifier - method_name = method_details['object'].name[2].replace(' ', '_').lower() - var_name = f"{identifier}_{method_name}" - - #add two columns method and method unit to the df - result['method'] = str(method_details['object'].name[2]) - result['method unit'] = str(method_details['object'].metadata['unit']) - - #order the columns after column unit - cols = list(result.columns) - unit_index = cols.index('unit') - cols.insert(unit_index + 1, cols.pop(cols.index('method'))) - cols.insert(unit_index + 2, cols.pop(cols.index('method unit'))) - result = result[cols] - - # Order the rows based on 'activity' and 'location' columns - result = result.sort_values(['activity', 'location']) - - # Reset the index numbering - result = result.reset_index(drop=True) - - # Store the result in the dictionary - dataframes_dict[var_name] = result - - return dataframes_dict - - -# Function for creating 'other' category for insignificant input contributions (for dataframes generated with compare_activities_multiple_methods) -# ------------------------------------------------------------------------------------------------------------------------------------------------- - -def small_inputs_to_other_column(dataframes_dict, cutoff=0.01): - ''' - Aggregate values into a new 'other' column for those contributing less than or equal to the cutoff value to the 'total' column value. - Set the aggregated values to zero in their original columns. - Remove any columns that end up containing only zeros. - - :param dataframes_dict: the dictionary - - ''' - - processed_dict = {} - - for key, df in dataframes_dict.items(): - # Identify the 'total' column - total_col_index = df.columns.get_loc('total') - - # Separate string and numeric columns - string_cols = df.iloc[:, :total_col_index] - numeric_cols = df.iloc[:, total_col_index:] - numeric_cols = numeric_cols.astype(float) - - # Calculate the threshold for each row (1% of total) - threshold = numeric_cols['total'] * cutoff - - # Create 'other' column - numeric_cols['other'] = 0.0 - - # Process each numeric column (except 'total' and 'other') - for col in numeric_cols.columns[1:-1]: # Skip 'total' and 'other' - # Identify values less than the threshold - mask = abs(numeric_cols[col]) < threshold #abs() to include negative contributions - - # Add these values to 'other' - numeric_cols.loc[mask, 'other'] += numeric_cols.loc[mask, col] - - # Set these values to zero in the original column - numeric_cols.loc[mask, col] = 0 - - # Remove columns with all zeros (except 'total' and 'other') - cols_to_keep = ['total'] + [col for col in numeric_cols.columns[1:-1] - if not (numeric_cols[col] == 0).all()] - cols_to_keep.append('other') - - numeric_cols = numeric_cols[cols_to_keep] - - # Combine string and processed numeric columns - processed_df = pd.concat([string_cols, numeric_cols], axis=1) - - #Sort columns by total - processed_df = processed_df.sort_values('total', ascending=False) - - # Store the processed DataFrame in the result dictionary - processed_dict[key] = processed_df - - return processed_dict - -# Function for saving created sector impact score dataframes to excel -# ------------------------------------------------------------------- - -def save_dataframes_to_excel(dataframes_dict, filepath, filename): - ''' - :param dataframes_dict: processed with other catgeory or not processed - :param filename: should contain ".xlsx" - ''' - # Ensure the directory exists - os.makedirs(filepath, exist_ok=True) - - # Create the full path for the Excel file - full_path = os.path.join(filepath, filename) - - # Create a Pandas Excel writer using XlsxWriter as the engine - with pd.ExcelWriter(full_path, engine='xlsxwriter') as writer: - # Iterate through the dictionary - for original_name, df in dataframes_dict.items(): - # Truncate the sheet name to 31 characters - sheet_name = original_name[:31] - - # Write each dataframe to a different worksheet - df.to_excel(writer, sheet_name=sheet_name, index=False) - - # If the sheet name was truncated, print a warning - if sheet_name != original_name: - print(f"Warning: Sheet name '{original_name}' was truncated to '{sheet_name}'") - - print(f"Excel file '{full_path}' has been created successfully.") - - -# -------------------------------------------------------------------------------------------------- -# PLOTS -# -------------------------------------------------------------------------------------------------- - -# GENERAL LEGEND -# -------------- -# Level 1-2.3 plots dependency: Legend to map indexes on x-axis to activities -# --------------------------------------------------------------------------------------- - -def generate_legend_text(data): - ''' - Maps the indexes on the x-axis to the activities to list them in a legend. - - :param data: it can take in a dictionary of dataframes or just a single dataframe - ''' - - legend_text = [] - - # Check if the input is a dictionary or a DataFrame - if isinstance(data, dict): - # Use the first DataFrame in the dictionary - first_key = next(iter(data)) - df = data[first_key] - elif isinstance(data, pd.DataFrame): - # Use the input DataFrame directly - df = data - else: - raise ValueError("Input must be either a dictionary of DataFrames or a DataFrame") - - # Create a list of tuples with (index, activity, location) - items = [(str(i), row['activity'], row['location']) for i, row in df.iterrows()] - # Sort the items based on the index - sorted_items = sorted(items, key=lambda x: x[0]) - # Add sorted items to legend_text - for i, activity, location in sorted_items: - legend_text.append(f"{i}: {activity} - {location}") - return legend_text - -# LEVEL 1 -# ------- -# Function for plotting: Level 1 dot plot with standard deviation and IQR range -# ------------------------------------------------------------------------------ - -def lvl1_plot(dataframes_dict, title_key=None): - ''' - Plots the total score value for each activity sorted from largest to smallest. Visualizes IQR and standard deviation. - Generates as many plots as methods were defined. - - :param dataframes_dict: dictionary resulting from the function "compare_activities_multiple_methods" (and subsequently "small_inputs_to_other_column") - :param title_key: some string for the plot titles (e.g. sector name) - - ''' - #NOTE: Units are not correctly shown on the y-axis yet. - - # Iterate over each dataframe and create individual plots - for idx, df in dataframes_dict.items(): - # Create a new figure for each plot - fig, ax = plt.subplots(figsize=(12, 6)) - - # Sort the DataFrame in descending order based on the 'total' column - sorted_df = df.sort_values(by='total', ascending=False) - - # Save the sorted index to order variable and call order variable in sns.swarmplot - order = sorted_df.index.tolist() - - # Calculate statistics - q1 = df['total'].quantile(0.25) - q3 = df['total'].quantile(0.75) - mean_gwp = df['total'].mean() - std_gwp = df['total'].std() - - # Plot using seaborn swarmplot - sns.swarmplot(data=df, x=df.index, y='total', dodge=True, ax=ax, order=order) - - # Add mean line - ax.axhline(mean_gwp, color='grey', linestyle='--', linewidth=1, label='Mean') - - # Add horizontal lines for Q1 and Q3 - ax.hlines(y=q3, xmin=-0.5, xmax=len(df)-0.5, color='lightblue', linestyle='dotted', linewidth=1, label='Q3 (75th percentile)') - ax.hlines(y=q1, xmin=-0.5, xmax=len(df)-0.5, color='lightblue', linestyle='dotted', linewidth=1, label='Q1 (25th percentile)') - - # Add horizontal shading for areas above and below 2 standard deviations from the mean - ax.axhspan(mean_gwp - 2 * std_gwp, mean_gwp - 3 * std_gwp, color='grey', alpha=0.2, label=">2 std below mean") - ax.axhspan(mean_gwp + 2 * std_gwp, mean_gwp + 3 * std_gwp, color='grey', alpha=0.2, label=">2 std above mean") - - # Add titles and labels - ax.set_title(f"{str(title_key)} - {df['method'].iloc[0]} in {df['method unit'].iloc[0]}") - ax.set_xlabel('Activity/ Dataset') - ax.set_ylabel(f"{df['method unit'].iloc[0]}") - - # Rotate x-axis labels if needed - ax.tick_params(axis='x', rotation=90) - - # Add legend - ax.legend() - - # Generate the legend text using the first dataframe - legend_text = generate_legend_text(dataframes_dict) - - # Add the legend text to the right of the plot - plt.text(1.02, 0.5, '\n'.join(legend_text), transform=ax.transAxes, ha='left', va='center', fontsize=11, bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) - - # Show the plot - plt.tight_layout() - plt.show() - -# LEVEL 2.1 -# -------- -# Function for plotting: Level 2.1 Absolute stacked bar plots -# ------------------------------------------------------------ - -def lvl21_plot_stacked_absolute(dataframes_dict, title_key=None): - ''' - Comparing activities and the input contributions to the total score by plotting a stacked absolute bar plot for each method. - - :param dataframes_dict: dictionary resulting from the function "compare_activities_multiple_methods" (and subsequently "small_inputs_to_other_column") - :param title_key: some string for the plot titles - ''' - - # Step 1: Collect all unique categories - all_categories = set() - - for df in dataframes_dict.values(): - if 'total' in df.columns: - total_index = df.columns.get_loc('total') - relevant_columns = df.columns[total_index + 1:] - else: - relevant_columns = df.columns - - # Update all_categories set with relevant columns - all_categories.update(relevant_columns) - - all_categories = list(all_categories) - - # Step 2: Create a consistent color palette and color map - distinct_colors = generate_distinct_colors(len(all_categories)) - color_map = dict(zip(all_categories, distinct_colors)) - - # Step 3: Plot each DataFrame - for key, df in dataframes_dict.items(): - if 'total' in df.columns: - df_og = df.copy() #for calling method and informative column in title and axis - total_index = df.columns.get_loc('total') - df = df.iloc[:, total_index + 1:] - - # Create a new figure for each plot - fig, ax = plt.subplots(figsize=(20, 10)) - - # Ensure columns match the categories used in the color map - df = df[[col for col in df.columns if col in color_map]] - - # Plotting the DataFrame with the custom color map - df.plot(kind='bar', stacked=True, ax=ax, color=[color_map[col] for col in df.columns]) - - # Add titles and labels - ax.set_title(f"{str(title_key)} - {df_og['method'].iloc[0]} in {df_og['method unit'].iloc[0]}") - ax.set_xlabel('Activity/ Dataset') - ax.set_ylabel(f"{df_og['method unit'].iloc[0]}") - - # First legend: Categories - first_legend = ax.legend(title='Categories', loc='center left', bbox_to_anchor=(1, 0.5), fontsize='small') - - # Add the first legend manually - ax.add_artist(first_legend) - - # Generate the legend text using the first dataframe - legend_text = generate_legend_text(dataframes_dict) - - # Create a second legend below the first one - fig.text(1.02, 0.1, '\n'.join(legend_text), transform=ax.transAxes, fontsize=11, - verticalalignment='bottom', bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) - - # Rotate x-axis labels for better readability - plt.xticks(rotation=90, ha='right') - - # Adjust layout to make room for both legends - plt.tight_layout() - plt.subplots_adjust(right=0.75, bottom=0.2) - - # Display the plot - plt.show() - -# LEVEL 2.2 -# ---------- -# Function for plotting: Level 2.2 bar plot comparing one input characterized by one method across sector/ activity list -# ---------------------------------------------------------------------------------------------------------------------- - -def lvl22_plot_input_comparison_with_method(dataframes_dict, dataframe_key, input_number): - """ - Comparing one specific cpc input among activities for each method. - - :param dataframes_dict:dictionary resulting from the function "compare_activities_multiple_methods" (and subsequently "small_inputs_to_other_column") - :param dataframe_key: Key to access a specific DataFrame from the dictionary. - :param input_number: Unique cpc identifier number of the input that should be plotted. - """ - # Access the specific DataFrame - df = dataframes_dict.get(dataframe_key) - - if df is None: - print(f"No DataFrame found for key: {dataframe_key}") - return - - # Filter columns based on the input_number - columns_to_plot = [col for col in df.columns if str(input_number) in str(col)] - - if not columns_to_plot: - print(f"No columns found containing input number: {input_number}") - return - - # Plot the filtered columns - ax = df[columns_to_plot].plot(kind='bar', figsize=(14, 6)) - plt.xlabel('Activity/ Dataset') - plt.ylabel(f"{df['method unit'].iloc[0]}") - plt.title(f'Comparison Plot for Input Number {input_number}') - - # Add legend for identifying activities_list from index - # Generate the legend text using the first dataframe - legend_text = generate_legend_text(dataframes_dict.get(dataframe_key)) - - # Add the legend text to the right of the plot - plt.text(1.02, 0.5, '\n'.join(legend_text), transform=ax.transAxes, ha='left', va='center', fontsize=11, bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) - - plt.show() - -# Level 2.2 plot dependencies: Function for formating plot: Unique colors for Level 2.1 Absolute stacked bar plots -# ----------------------------------------------------------------------------------- - -def generate_distinct_colors(n): - """Generate n distinct colors using HSV color space.""" - hues = np.linspace(0, 1, n, endpoint=False) - colors = [plt.cm.hsv(h) for h in hues] - return colors - -# LEVEL 2.3 -# --------- -# Function for plotting: Level 2.3 bar plot comparing input not characterized across sector/ activity list -# -------------------------------------------------------------------------------------------------------- - -def lvl23_plot_input_comparison_plot_no_method(activities_list, input_type, input_number,): - ''' - Comparing one specific cpc input among activities without method. - - :param activities_list: list of activities to plot inputs for. Perhabs the one defined at the beginning. - :param input_type: type of the activities input default 'list', other 'dict' - :param input_number: the cpc code of the input that is supposed to be plotted - - ''' - cpc_input_dataframe = get_cpc_inputs_of_activities(activities_list, input_type) - - x_input_fltr= [x for x in cpc_input_dataframe.columns if str(input_number) in str(x)][0] - - df= cpc_input_dataframe[x_input_fltr] - - df = df.sort_values(ascending=False) - ax = df.plot(kind='bar', x=x_input_fltr, figsize=(14, 6)) - ax.set_xlabel('Activity/ Dataset') - ax.set_ylabel(f"{cpc_input_dataframe['unit'].iloc[0]}") - ax.set_title(f'Comparison Plot for not characterized Input - {x_input_fltr}') - - # Generate the legend text to map index to activity - legend_text = generate_legend_text(cpc_input_dataframe) - # Add the legend text to the right of the plot - ax.text(1.02, 0.5, '\n'.join(legend_text), transform=ax.transAxes, ha='left', va='center', fontsize=11, bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) - - -# Level 2.3 plot dependencies: Function to generate dataframes containing inputs in cpc format not characterized from an activity list -# --------------------------------------------------------------------------------------------------------- - -def get_cpc_inputs_of_activities(activities_list, input_type='list'): - - ''' - for param description see function lvl23_plot_input_comparison_plot_no_method - - !!! NOTE: Adapt this function to get the outputs !!! - - ''' - - def activity_list_inputs_cpc(activities_list, input_type): - all_inputs = [] - - if input_type == 'list': - activity_iterator = activities_list - elif input_type == 'dict': - activity_iterator = activities_list.values() - else: - raise ValueError("input_type must be either 'list' or 'dict'") - - for activity in activity_iterator: - inputs_keys = pd.Series({bw.get_activity(exc.input).key: exc.amount for exc in activity.technosphere()}, - name=activity['name'] + ', ' + activity['location']) - - # Adjust the way the key is presented - inputs_keys = inputs_keys.reset_index() - inputs_keys['full_key'] = inputs_keys.apply(lambda row: f"('{row['level_0']}', '{row['level_1']}')", axis=1) - inputs_keys = inputs_keys.drop(['level_0', 'level_1'], axis=1).set_index('full_key') - - # Add empty cpc column and activity information - inputs_keys.insert(0, 'identifier', activity['name'] + ', ' + activity['location']) - inputs_keys.insert(1, 'activity', activity['name']) - inputs_keys.insert(2, 'location', activity['location']) - inputs_keys.insert(3, 'unit', activity['unit']) - inputs_keys.insert(4, 'cpc', None) - - all_inputs.append(inputs_keys) - - # Combine all inputs into a single DataFrame - combined_inputs = pd.concat(all_inputs, axis=0) - - return combined_inputs - - def update_cpc_information(combined_inputs): - for index, row in combined_inputs.iterrows(): - # Transform each key to tuple - tuple_key = ast.literal_eval(index) - - # Get input activity for the key - input_activity = bw.get_activity(tuple_key) - - # Get cpc name for activity - cpc_name = ba.comparisons.get_cpc(input_activity) - - # Store cpc_name in the 'cpc' column of the combined_inputs dataframe - combined_inputs.at[index, 'cpc'] = cpc_name - - return combined_inputs - - def transform_dataframe(combined_inputs): - # Set 'identifier' as the new index and drop the 'full_key' index - combined_inputs = combined_inputs.reset_index().set_index('identifier').drop('full_key', axis=1) - - # Determine the index of the 'unit' column - unit_index = combined_inputs.columns.get_loc('unit') - - # Split the dataframe into two parts - combined_inputs_identifier = combined_inputs.iloc[:, :unit_index+1] - combined_inputs_cpc = combined_inputs.iloc[:, unit_index+1:] - #set index of to 'cpc' in combined_input_cpc - combined_inputs_cpc = combined_inputs_cpc.set_index('cpc') - - # Combine rows with the same index value in combined_inputs_cpc - combined_inputs_cpc = combined_inputs_cpc.groupby(level=0).agg(lambda x: np.sum(x) if x.dtype.kind in 'biufc' else x.iloc[0]) - # Transpose combined_inputs_cpc - combined_inputs_cpc_trans = combined_inputs_cpc.T - - # Merge combined_inputs_identifier and combined_inputs_cpc_trans - result = combined_inputs_identifier.join(combined_inputs_cpc_trans) - result = result.drop_duplicates() - - # Sort dataframe by activity and location aplphabetically and reset the index - result = result.sort_values(by=['activity', 'location']) - result = result.reset_index(drop=True) - return result - - # Execute the workflow - combined_inputs = activity_list_inputs_cpc(activities_list, input_type) - combined_inputs_with_cpc = update_cpc_information(combined_inputs) - final_result = transform_dataframe(combined_inputs_with_cpc) - - return final_result - - -# LEVEL 3 -# -------- -# Function for plotting: Level 3 S-curve difference of og database and premise adapted database by one meth -# ------------------------------------------------------------------------------------------------------------ - -def lvl3_plot_relative_changes(database, premise_database, method): - - ''' - A function that plots the relative changes in activitiy LCA scores (for one defined method) between a "raw" ecoinvent database and a premise transformed ecoinvent database. - - :param database: an ecoinvent database or set of activities from an ecoinvent database. - :premise_database: a premise transformed database or a set of activities which has intersections with the ecoinvent database. - :method: a method the relative changes should be calculated and plotted for. - - ''' - - ecoinvent_scores = calculate_lca_ecoinvent_scores(database, method) - premise_scores = calculate_lca_premise_scores(premise_database, method) - - relative_changes = calc_relative_changes(ecoinvent_scores, premise_scores) - - # Filter out entries where the value is a tuple (method) - filtered_changes = {k: v for k, v in relative_changes.items() if not isinstance(v, tuple)} - - # Sort the relative changes by magnitude - sorted_changes = sorted(filtered_changes.items(), key=lambda x: x[1]) - - # Prepare data for plotting - activities_list = [f"{key}" for key, _ in sorted_changes] - changes = [change for _, change in sorted_changes] - - # Create the plot - fig, ax = plt.subplots(figsize=(12, len(activities_list) * 0.4)) # Adjust figure height based on number of activities_list - fig.suptitle(f"Relative Changes in LCA Scores {relative_changes['method']}") - y_pos = np.arange(len(activities_list)) - ax.barh(y_pos, changes, align='center', color='lightgrey', alpha=0.7) - - # Plot curve through datapoints - ax.plot(changes, y_pos, color='darkblue', linewidth=2, marker='o', markersize=6) - - # Set labels and title - ax.set_yticks(y_pos) - ax.set_yticklabels(activities_list) - ax.invert_yaxis() # Labels read top-to-bottom - ax.set_xlabel('Relative Change') - - - # Add a vertical line at x=0 - ax.axvline(x=0, color='k', linestyle='--') - - # Adjust layout and display - plt.tight_layout() - plt.show() - -# Level 3 plot dependencies: Functions for generating lca scores of ecoinvent and premise database to plot their relative changes -# ------------------------------------------------------------------------------------------------------------------------------- - -def calculate_lca_ecoinvent_scores(database, method): - - ecoinvent_scores= {} - ecoinvent_scores['method']=method #save the method used for plotting the data - all_activities=[x for x in database] - - for activity in all_activities: - activity_LCA = bw.LCA({activity:1}, bw.Method(method).name) - activity_LCA.lci() - activity_LCA.lcia() - score=activity_LCA.score - - # Create a tuple key with relevant information - key = (activity['name'], activity['unit'], activity['location'], activity.get('reference product')) - - ecoinvent_scores[key]=score - - return ecoinvent_scores - -def calculate_lca_premise_scores(premise_database, method): - - premise_scores= {} - - premise_scores['method']=method #save the method used for plotting the data - - all_activities=[x for x in premise_database] - - for activity in all_activities: - activity_LCA = bw.LCA({activity:1}, bw.Method(method).name) - activity_LCA.lci() - activity_LCA.lcia() - score=activity_LCA.score - - # Create a tuple key with relevant information - key = (activity['name'], activity['unit'], activity['location'], activity.get('reference product')) - - premise_scores[key]=score - - return premise_scores - - -# relative_changes contains the activity names as keys and their relative changes as values - -def compute_relative_change(original, transformed): - if original == 0: - return float('inf') if transformed != 0 else 0 - return (transformed - original) / original - - -def calc_relative_changes(ecoinvent_scores, premise_scores): - - # Match activities_list and calculate relative changes - relative_changes = {} - relative_changes['method']=ecoinvent_scores['method'] - - # Track additional keys in premise_scores - additional_premise_keys = [] - - for key, original_score in ecoinvent_scores.items(): - if key in premise_scores: #activities only in premise_scores are according to this logic neglected. - # Skip if original_score is a tuple due to information tuple key - if isinstance(original_score, tuple): - continue - - transformed_score = premise_scores[key] - relative_change = compute_relative_change(original_score, transformed_score) - relative_changes[key] = relative_change - - # Identify additional keys in premise_scores - for key in premise_scores.keys(): - if key not in ecoinvent_scores: - additional_premise_keys.append(key) - - # Print the dataframes_dict - for key, change in relative_changes.items(): - print(f"{key}: {change}") - - if additional_premise_keys: - print("Additional keys in premise_scores not found in ecoinvent_scores:", additional_premise_keys) - +""" +This module contains functions for the premise validation framework. +It includes functions for filtering of premise transformed ecoinvent databases, collecting relevant +data in a suitable format, and visualizing datasets. +Some of the functions included are brightway2 functions. + +""" +#NOTE: The comments are not all up to date. + +# Python import dependencies +# -------------------------- +from premise import * + +# data?? +import os +import yaml +import peewee as pw + +#brightway +import brightway2 as bw +import bw2analyzer as ba +import bw2data as bd + +#common +import pandas as pd +import numpy as np + +#plotting +import matplotlib.pyplot as plt +import seaborn as sns + +#to be completed +import ast + +# ----------------------------------------------------------------------------- +# DATABASE FILTERING +# ----------------------------------------------------------------------------- + + +# Sector filter functions from premise +# --------------------------------------------------- + +def act_fltr( + database: list, + fltr = None, + mask = None, +): + """Filter `database` for activities_list matching field contents given by `fltr` excluding strings in `mask`. + `fltr`: string, list of strings or dictionary. + If a string is provided, it is used to match the name field from the start (*startswith*). + If a list is provided, all strings in the lists are used and dataframes_dict are joined (*or*). + A dict can be given in the form : to filter for in . + `mask`: used in the same way as `fltr`, but filters add up with each other (*and*). + `filter_exact` and `mask_exact`: boolean, set `True` to only allow for exact matches. + + :param database: A lice cycle inventory database + :type database: brightway2 database object + :param fltr: value(s) to filter with. + :type fltr: Union[str, lst, dict] + :param mask: value(s) to filter with. + :type mask: Union[str, lst, dict] + :return: list of activity data set names + :rtype: list + + """ + if fltr is None: + fltr = {} + if mask is None: + mask = {} + + # default field is name + if isinstance(fltr, (list, str)): + fltr = {"name": fltr} + if isinstance(mask, (list, str)): + mask = {"name": mask} + + assert len(fltr) > 0, "Filter dict must not be empty." + + # find `act` in `database` that match `fltr` + # and do not match `mask` + filters = database + for field, value in fltr.items(): + if isinstance(value, list): + for val in value: + filters = [a for a in filters if val in a[field]] + + #filters.extend([ws.either(*[ws.contains(field, v) for v in value])]) + else: + filters = [ + a for a in filters if value in a[field] + ] + + #filters.append(ws.contains(field, value)) + + + if mask: + for field, value in mask.items(): + if isinstance(value, list): + for val in value: + filters = [f for f in filters if val not in f[field]] + #filters.extend([ws.exclude(ws.contains(field, v)) for v in value]) + else: + filters = [f for f in filters if value not in f[field]] + #filters.append(ws.exclude(ws.contains(field, value))) + + return filters + + +def generate_sets_from_filters(yaml_filepath, database=None) -> dict: + """ + Generate a dictionary with sets of activity names for + technologies from the filter specifications. + + :param filtr: + :func:`activity_maps.InventorySet.act_fltr`. + :return: dictionary with the same keys as provided in filter + and a set of activity data set names as values. + :rtype: dict + """ + + filtr=get_mapping(yaml_filepath, var='ecoinvent_aliases') + + names = [] + + for entry in filtr.values(): + if "fltr" in entry: + if isinstance(entry["fltr"], dict): + if "name" in entry["fltr"]: + names.extend(entry["fltr"]["name"]) + elif isinstance(entry["fltr"], list): + names.extend(entry["fltr"]) + else: + names.append(entry["fltr"]) + + #subset = list( + # ws.get_many( + # database, + # ws.either(*[ws.contains("name", name) for name in names]), + # ) + #) + + subset=[ + a for a in database if any( + + + x in a["name"] for x in names + ) + ] + + + techs = { + tech: act_fltr(subset, fltr.get("fltr"), fltr.get("mask")) + for tech, fltr in filtr.items() + } + + mapping = { + tech: {act for act in actlst} for tech, actlst in techs.items() + } + + + return mapping + +def get_mapping(filepath, var): + """ + Loa a YAML file and return a dictionary given a variable. + :param filepath: YAML file path + :param var: variable to return the dictionary for. + :param model: if provided, only return the dictionary for this model. + :return: a dictionary + """ + + with open(filepath, "r", encoding="utf-8") as stream: + techs = yaml.full_load(stream) + + mapping = {} + for key, val in techs.items(): + if var in val: + mapping[key] = val[var] + + return mapping + + +# Example on how to call the functions to create a set of filtered activities_list +#set_from_fltrs = generate_sets_from_filters(filtr=get_mapping(yaml_filepath, "ecoinvent_aliases"), database=ei39SSP + +# ----------------------------------------------------------------------------- +# METHODS +# ----------------------------------------------------------------------------- + +# Class for generating method dictionary +# -------------------------------------- +class MethodFinder: + def __init__(self): + self.all_methods = {} + self.method_counter = 0 + + def find_and_create_method(self, criteria, exclude=None, custom_key=None): + methods = bw.methods + # Start with all methods + filtered_methods = methods + # Apply inclusion criteria + for criterion in criteria: + filtered_methods = [m for m in filtered_methods if criterion in str(m)] + # Apply exclusion criteria if provided + if exclude: + for exclusion in exclude: + filtered_methods = [m for m in filtered_methods if exclusion not in str(m)] + # Check if we found exactly one method + if len(filtered_methods) == 0: + raise ValueError("No methods found matching the given criteria.") + elif len(filtered_methods) > 1: + raise ValueError(f"Multiple methods found: {filtered_methods}. Please provide more specific criteria.") + # Get the first (and only) method + selected_method = filtered_methods[0] + # Create the Brightway Method object + method_object = bw.Method(selected_method) + + # Generate a key for storing the method + if custom_key is None: + self.method_counter += 1 + key = f"method_{self.method_counter}" + else: + key = custom_key + + # Store the method object and additional information in the dictionary + self.all_methods[key] = { + 'object': method_object, + 'method name': str(method_object.name), + 'short name' : str(method_object.name[2]), + 'unit': str(method_object.metadata.get('unit', 'Unknown')) + } + + # Return both the method object and its key + return {key: self.all_methods[key]} + + def get_all_methods(self): + return self.all_methods + +# Setting up the methods for outlier detection +# --------------------------------------------------------------------- + +def find_and_create_method(criteria, exclude=None): + """ + Find a method based on given criteria and create a Brightway Method object. This will choose the first method. + Thus, filter criteria need to be defined precisely to pick the right method. + + :param criteria: List of strings that should be in the method name + :param exclude: List of strings that should not be in the method name (optional) + :return: Brightway Method object + """ + methods = bw.methods + + # Start with all methods + filtered_methods = methods + + # Apply inclusion criteria + for criterion in criteria: + filtered_methods = [m for m in filtered_methods if criterion in str(m)] + + # Apply exclusion criteria if provided + if exclude: + for exclusion in exclude: + filtered_methods = [m for m in filtered_methods if exclusion not in str(m)] + + # Check if we found exactly one method + if len(filtered_methods) == 0: + raise ValueError("No methods found matching the given criteria.") + elif len(filtered_methods) > 1: + raise ValueError(f"Multiple methods found: {filtered_methods}. Please provide more specific criteria.") + + # Get the first (and only) method + selected_method = filtered_methods[0] + + # Create and return the Brightway Method object storing it in a defined variable outside of the funciton. + return bw.Method(selected_method) + +#NOTE: Would a yaml filter make it easier? OR Could have predefined methods?""" + +# Function for creating method dictionaries which holds method name and unit for later tracking of methods. +# --------------------------------------------------------------------------------------------------------- + +def create_method_dict(selected_methods_list): + ''' + :selected_methods_list: a list of variables which contain the selected methods + + ''' + method_dict = {} + for method in selected_methods_list: + method_dict[method] = { + 'short name': str(method.name[2]), + 'method name': str(method.name), + 'method unit': str(method.metadata['unit']) + } + + return method_dict + +# ------------------------------------------------------------------------------------------------------------------------------ +# CALCULATIONS +# ------------------------------------------------------------------------------------------------------------------------------ + +# Function based on brightways bw2analyzer (ba) function for generating dataframe containing total score and contribution by inputs +# ----------------------------------------------------------------------------------------------------------------------------- + +def compare_activities_multiple_methods(activities_list, methods, identifier, output_format='pandas', mode='absolute'): + """ + Compares a set of activities by multiple methods, stores each generated dataframe as a variable (the method is the variable name) in a dictionary. + + :param activities_list: List of activities to compare + :param methods: List of Brightway Method objects + :param identifier: A string used in defining the variable names to better identify comparisons (e.g. sector name). + :param output_format: Output format for the comparison (default: 'pandas') + :param mode: Mode for the comparison (default: 'absolute'; others: 'relative') + :return: Dictionary of resulting dataframes from the comparisons + """ + dataframes_dict = {} + + for method_key, method_details in methods.items(): + result = ba.comparisons.compare_activities_by_grouped_leaves( + activities_list, + method_details['object'].name, + output_format=output_format, + mode=mode + ) + + # Create a variable name using the method name tuple and identifier + method_name = method_details['object'].name[2].replace(' ', '_').lower() + var_name = f"{identifier}_{method_name}" + + #add two columns method and method unit to the df + result['method'] = str(method_details['object'].name[2]) + result['method unit'] = str(method_details['object'].metadata['unit']) + + #order the columns after column unit + cols = list(result.columns) + unit_index = cols.index('unit') + cols.insert(unit_index + 1, cols.pop(cols.index('method'))) + cols.insert(unit_index + 2, cols.pop(cols.index('method unit'))) + result = result[cols] + + # Order the rows based on 'activity' and 'location' columns + result = result.sort_values(['activity', 'location']) + + # Reset the index numbering + result = result.reset_index(drop=True) + + # Store the result in the dictionary + dataframes_dict[var_name] = result + + return dataframes_dict + + +# Function for creating 'other' category for insignificant input contributions (for dataframes generated with compare_activities_multiple_methods) +# ------------------------------------------------------------------------------------------------------------------------------------------------- + +def small_inputs_to_other_column(dataframes_dict, cutoff=0.01): + ''' + Aggregate values into a new 'other' column for those contributing less than or equal to the cutoff value to the 'total' column value. + Set the aggregated values to zero in their original columns. + Remove any columns that end up containing only zeros. + + :param dataframes_dict: the dictionary + + ''' + + processed_dict = {} + + for key, df in dataframes_dict.items(): + # Identify the 'total' column + total_col_index = df.columns.get_loc('total') + + # Separate string and numeric columns + string_cols = df.iloc[:, :total_col_index] + numeric_cols = df.iloc[:, total_col_index:] + numeric_cols = numeric_cols.astype(float) + + # Calculate the threshold for each row (1% of total) + threshold = numeric_cols['total'] * cutoff + + # Create 'other' column + numeric_cols['other'] = 0.0 + + # Process each numeric column (except 'total' and 'other') + for col in numeric_cols.columns[1:-1]: # Skip 'total' and 'other' + # Identify values less than the threshold + mask = abs(numeric_cols[col]) < threshold #abs() to include negative contributions + + # Add these values to 'other' + numeric_cols.loc[mask, 'other'] += numeric_cols.loc[mask, col] + + # Set these values to zero in the original column + numeric_cols.loc[mask, col] = 0 + + # Remove columns with all zeros (except 'total' and 'other') + cols_to_keep = ['total'] + [col for col in numeric_cols.columns[1:-1] + if not (numeric_cols[col] == 0).all()] + cols_to_keep.append('other') + + numeric_cols = numeric_cols[cols_to_keep] + + # Combine string and processed numeric columns + processed_df = pd.concat([string_cols, numeric_cols], axis=1) + + #Sort columns by total + processed_df = processed_df.sort_values('total', ascending=False) + + # Store the processed DataFrame in the result dictionary + processed_dict[key] = processed_df + + return processed_dict + +# Function for saving created sector impact score dataframes to excel +# ------------------------------------------------------------------- + +def save_dataframes_to_excel(dataframes_dict, filepath, filename): + ''' + :param dataframes_dict: processed with other catgeory or not processed + :param filename: should contain ".xlsx" + ''' + # Ensure the directory exists + os.makedirs(filepath, exist_ok=True) + + # Create the full path for the Excel file + full_path = os.path.join(filepath, filename) + + # Create a Pandas Excel writer using XlsxWriter as the engine + with pd.ExcelWriter(full_path, engine='xlsxwriter') as writer: + # Iterate through the dictionary + for original_name, df in dataframes_dict.items(): + # Truncate the sheet name to 31 characters + sheet_name = original_name[:31] + + # Write each dataframe to a different worksheet + df.to_excel(writer, sheet_name=sheet_name, index=False) + + # If the sheet name was truncated, print a warning + if sheet_name != original_name: + print(f"Warning: Sheet name '{original_name}' was truncated to '{sheet_name}'") + + print(f"Excel file '{full_path}' has been created successfully.") + + +# -------------------------------------------------------------------------------------------------- +# PLOTS +# -------------------------------------------------------------------------------------------------- + +# GENERAL LEGEND +# -------------- +# Level 1-2.3 plots dependency: Legend to map indexes on x-axis to activities +# --------------------------------------------------------------------------------------- + +def generate_legend_text(data): + ''' + Maps the indexes on the x-axis to the activities to list them in a legend. + + :param data: it can take in a dictionary of dataframes or just a single dataframe + ''' + + legend_text = [] + + # Check if the input is a dictionary or a DataFrame + if isinstance(data, dict): + # Use the first DataFrame in the dictionary + first_key = next(iter(data)) + df = data[first_key] + elif isinstance(data, pd.DataFrame): + # Use the input DataFrame directly + df = data + else: + raise ValueError("Input must be either a dictionary of DataFrames or a DataFrame") + + # Create a list of tuples with (index, activity, location) + items = [(str(i), row['activity'], row['location']) for i, row in df.iterrows()] + # Sort the items based on the index + sorted_items = sorted(items, key=lambda x: x[0]) + # Add sorted items to legend_text + for i, activity, location in sorted_items: + legend_text.append(f"{i}: {activity} - {location}") + return legend_text + +# LEVEL 1 +# ------- +# Function for plotting: Level 1 dot plot with standard deviation and IQR range +# ------------------------------------------------------------------------------ + +def lvl1_plot(dataframes_dict, title_key=None): + ''' + Plots the total score value for each activity sorted from largest to smallest. Visualizes IQR and standard deviation. + Generates as many plots as methods were defined. + + :param dataframes_dict: dictionary resulting from the function "compare_activities_multiple_methods" (and subsequently "small_inputs_to_other_column") + :param title_key: some string for the plot titles (e.g. sector name) + + ''' + #NOTE: Units are not correctly shown on the y-axis yet. + + # Iterate over each dataframe and create individual plots + for idx, df in dataframes_dict.items(): + # Create a new figure for each plot + fig, ax = plt.subplots(figsize=(12, 6)) + + # Sort the DataFrame in descending order based on the 'total' column + sorted_df = df.sort_values(by='total', ascending=False) + + # Save the sorted index to order variable and call order variable in sns.swarmplot + order = sorted_df.index.tolist() + + # Calculate statistics + q1 = df['total'].quantile(0.25) + q3 = df['total'].quantile(0.75) + mean_gwp = df['total'].mean() + std_gwp = df['total'].std() + + # Plot using seaborn swarmplot + sns.swarmplot(data=df, x=df.index, y='total', dodge=True, ax=ax, order=order) + + # Add mean line + ax.axhline(mean_gwp, color='grey', linestyle='--', linewidth=1, label='Mean') + + # Add horizontal lines for Q1 and Q3 + ax.hlines(y=q3, xmin=-0.5, xmax=len(df)-0.5, color='lightblue', linestyle='dotted', linewidth=1, label='Q3 (75th percentile)') + ax.hlines(y=q1, xmin=-0.5, xmax=len(df)-0.5, color='lightblue', linestyle='dotted', linewidth=1, label='Q1 (25th percentile)') + + # Add horizontal shading for areas above and below 2 standard deviations from the mean + ax.axhspan(mean_gwp - 2 * std_gwp, mean_gwp - 3 * std_gwp, color='grey', alpha=0.2, label=">2 std below mean") + ax.axhspan(mean_gwp + 2 * std_gwp, mean_gwp + 3 * std_gwp, color='grey', alpha=0.2, label=">2 std above mean") + + # Add titles and labels + ax.set_title(f"{str(title_key)} - {df['method'].iloc[0]} in {df['method unit'].iloc[0]}") + ax.set_xlabel('Activity/ Dataset') + ax.set_ylabel(f"{df['method unit'].iloc[0]}") + + # Rotate x-axis labels if needed + ax.tick_params(axis='x', rotation=90) + + # Add legend + ax.legend() + + # Generate the legend text using the first dataframe + legend_text = generate_legend_text(dataframes_dict) + + # Add the legend text to the right of the plot + plt.text(1.02, 0.5, '\n'.join(legend_text), transform=ax.transAxes, ha='left', va='center', fontsize=11, bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) + + # Show the plot + plt.tight_layout() + plt.show() + +# LEVEL 2.1 +# -------- +# Function for plotting: Level 2.1 Absolute stacked bar plots +# ------------------------------------------------------------ + +def lvl21_plot_stacked_absolute(dataframes_dict, title_key=None): + ''' + Comparing activities and the input contributions to the total score by plotting a stacked absolute bar plot for each method. + + :param dataframes_dict: dictionary resulting from the function "compare_activities_multiple_methods" (and subsequently "small_inputs_to_other_column") + :param title_key: some string for the plot titles + ''' + + # Step 1: Collect all unique categories + all_categories = set() + + for df in dataframes_dict.values(): + if 'total' in df.columns: + total_index = df.columns.get_loc('total') + relevant_columns = df.columns[total_index + 1:] + else: + relevant_columns = df.columns + + # Update all_categories set with relevant columns + all_categories.update(relevant_columns) + + all_categories = list(all_categories) + + # Step 2: Create a consistent color palette and color map + distinct_colors = generate_distinct_colors(len(all_categories)) + color_map = dict(zip(all_categories, distinct_colors)) + + # Step 3: Plot each DataFrame + for key, df in dataframes_dict.items(): + if 'total' in df.columns: + df_og = df.copy() #for calling method and informative column in title and axis + total_index = df.columns.get_loc('total') + df = df.iloc[:, total_index + 1:] + + # Create a new figure for each plot + fig, ax = plt.subplots(figsize=(20, 10)) + + # Ensure columns match the categories used in the color map + df = df[[col for col in df.columns if col in color_map]] + + # Plotting the DataFrame with the custom color map + df.plot(kind='bar', stacked=True, ax=ax, color=[color_map[col] for col in df.columns]) + + # Add titles and labels + ax.set_title(f"{str(title_key)} - {df_og['method'].iloc[0]} in {df_og['method unit'].iloc[0]}") + ax.set_xlabel('Activity/ Dataset') + ax.set_ylabel(f"{df_og['method unit'].iloc[0]}") + + # First legend: Categories + first_legend = ax.legend(title='Categories', loc='center left', bbox_to_anchor=(1, 0.5), fontsize='small') + + # Add the first legend manually + ax.add_artist(first_legend) + + # Generate the legend text using the first dataframe + legend_text = generate_legend_text(dataframes_dict) + + # Create a second legend below the first one + fig.text(1.02, 0.1, '\n'.join(legend_text), transform=ax.transAxes, fontsize=11, + verticalalignment='bottom', bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) + + # Rotate x-axis labels for better readability + plt.xticks(rotation=90, ha='right') + + # Adjust layout to make room for both legends + plt.tight_layout() + plt.subplots_adjust(right=0.75, bottom=0.2) + + # Display the plot + plt.show() + +# LEVEL 2.2 +# ---------- +# Function for plotting: Level 2.2 bar plot comparing one input characterized by one method across sector/ activity list +# ---------------------------------------------------------------------------------------------------------------------- + +def lvl22_plot_input_comparison_with_method(dataframes_dict, dataframe_key, input_number): + """ + Comparing one specific cpc input among activities for each method. + + :param dataframes_dict:dictionary resulting from the function "compare_activities_multiple_methods" (and subsequently "small_inputs_to_other_column") + :param dataframe_key: Key to access a specific DataFrame from the dictionary. + :param input_number: Unique cpc identifier number of the input that should be plotted. + """ + # Access the specific DataFrame + df = dataframes_dict.get(dataframe_key) + + if df is None: + print(f"No DataFrame found for key: {dataframe_key}") + return + + # Filter columns based on the input_number + columns_to_plot = [col for col in df.columns if str(input_number) in str(col)] + + if not columns_to_plot: + print(f"No columns found containing input number: {input_number}") + return + + # Plot the filtered columns + ax = df[columns_to_plot].plot(kind='bar', figsize=(14, 6)) + plt.xlabel('Activity/ Dataset') + plt.ylabel(f"{df['method unit'].iloc[0]}") + plt.title(f'Comparison Plot for Input Number {input_number}') + + # Add legend for identifying activities_list from index + # Generate the legend text using the first dataframe + legend_text = generate_legend_text(dataframes_dict.get(dataframe_key)) + + # Add the legend text to the right of the plot + plt.text(1.02, 0.5, '\n'.join(legend_text), transform=ax.transAxes, ha='left', va='center', fontsize=11, bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) + + plt.show() + +# Level 2.2 plot dependencies: Function for formating plot: Unique colors for Level 2.1 Absolute stacked bar plots +# ----------------------------------------------------------------------------------- + +def generate_distinct_colors(n): + """Generate n distinct colors using HSV color space.""" + hues = np.linspace(0, 1, n, endpoint=False) + colors = [plt.cm.hsv(h) for h in hues] + return colors + +# LEVEL 2.3 +# --------- +# Function for plotting: Level 2.3 bar plot comparing input not characterized across sector/ activity list +# -------------------------------------------------------------------------------------------------------- + +def lvl23_plot_input_comparison_plot_no_method(activities_list, input_type, input_number,): + ''' + Comparing one specific cpc input among activities without method. + + :param activities_list: list of activities to plot inputs for. Perhabs the one defined at the beginning. + :param input_type: type of the activities input default 'list', other 'dict' + :param input_number: the cpc code of the input that is supposed to be plotted + + ''' + cpc_input_dataframe = get_cpc_inputs_of_activities(activities_list, input_type) + + x_input_fltr= [x for x in cpc_input_dataframe.columns if str(input_number) in str(x)][0] + + df= cpc_input_dataframe[x_input_fltr] + + df = df.sort_values(ascending=False) + ax = df.plot(kind='bar', x=x_input_fltr, figsize=(14, 6)) + ax.set_xlabel('Activity/ Dataset') + ax.set_ylabel(f"{cpc_input_dataframe['unit'].iloc[0]}") + ax.set_title(f'Comparison Plot for not characterized Input - {x_input_fltr}') + + # Generate the legend text to map index to activity + legend_text = generate_legend_text(cpc_input_dataframe) + # Add the legend text to the right of the plot + ax.text(1.02, 0.5, '\n'.join(legend_text), transform=ax.transAxes, ha='left', va='center', fontsize=11, bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) + + +# Level 2.3 plot dependencies: Function to generate dataframes containing inputs in cpc format not characterized from an activity list +# --------------------------------------------------------------------------------------------------------- + +def get_cpc_inputs_of_activities(activities_list, input_type='list'): + + ''' + for param description see function lvl23_plot_input_comparison_plot_no_method + + !!! NOTE: Adapt this function to get the outputs !!! + + ''' + + def activity_list_inputs_cpc(activities_list, input_type): + all_inputs = [] + + if input_type == 'list': + activity_iterator = activities_list + elif input_type == 'dict': + activity_iterator = activities_list.values() + else: + raise ValueError("input_type must be either 'list' or 'dict'") + + for activity in activity_iterator: + inputs_keys = pd.Series({bw.get_activity(exc.input).key: exc.amount for exc in activity.technosphere()}, + name=activity['name'] + ', ' + activity['location']) + + # Adjust the way the key is presented + inputs_keys = inputs_keys.reset_index() + inputs_keys['full_key'] = inputs_keys.apply(lambda row: f"('{row['level_0']}', '{row['level_1']}')", axis=1) + inputs_keys = inputs_keys.drop(['level_0', 'level_1'], axis=1).set_index('full_key') + + # Add empty cpc column and activity information + inputs_keys.insert(0, 'identifier', activity['name'] + ', ' + activity['location']) + inputs_keys.insert(1, 'activity', activity['name']) + inputs_keys.insert(2, 'location', activity['location']) + inputs_keys.insert(3, 'unit', activity['unit']) + inputs_keys.insert(4, 'cpc', None) + + all_inputs.append(inputs_keys) + + # Combine all inputs into a single DataFrame + combined_inputs = pd.concat(all_inputs, axis=0) + + return combined_inputs + + def update_cpc_information(combined_inputs): + for index, row in combined_inputs.iterrows(): + # Transform each key to tuple + tuple_key = ast.literal_eval(index) + + # Get input activity for the key + input_activity = bw.get_activity(tuple_key) + + # Get cpc name for activity + cpc_name = ba.comparisons.get_cpc(input_activity) + + # Store cpc_name in the 'cpc' column of the combined_inputs dataframe + combined_inputs.at[index, 'cpc'] = cpc_name + + return combined_inputs + + def transform_dataframe(combined_inputs): + # Set 'identifier' as the new index and drop the 'full_key' index + combined_inputs = combined_inputs.reset_index().set_index('identifier').drop('full_key', axis=1) + + # Determine the index of the 'unit' column + unit_index = combined_inputs.columns.get_loc('unit') + + # Split the dataframe into two parts + combined_inputs_identifier = combined_inputs.iloc[:, :unit_index+1] + combined_inputs_cpc = combined_inputs.iloc[:, unit_index+1:] + #set index of to 'cpc' in combined_input_cpc + combined_inputs_cpc = combined_inputs_cpc.set_index('cpc') + + # Combine rows with the same index value in combined_inputs_cpc + combined_inputs_cpc = combined_inputs_cpc.groupby(level=0).agg(lambda x: np.sum(x) if x.dtype.kind in 'biufc' else x.iloc[0]) + # Transpose combined_inputs_cpc + combined_inputs_cpc_trans = combined_inputs_cpc.T + + # Merge combined_inputs_identifier and combined_inputs_cpc_trans + result = combined_inputs_identifier.join(combined_inputs_cpc_trans) + result = result.drop_duplicates() + + # Sort dataframe by activity and location aplphabetically and reset the index + result = result.sort_values(by=['activity', 'location']) + result = result.reset_index(drop=True) + return result + + # Execute the workflow + combined_inputs = activity_list_inputs_cpc(activities_list, input_type) + combined_inputs_with_cpc = update_cpc_information(combined_inputs) + final_result = transform_dataframe(combined_inputs_with_cpc) + + return final_result + + +# LEVEL 3 +# -------- +# Function for plotting: Level 3 S-curve difference of og database and premise adapted database by one meth +# ------------------------------------------------------------------------------------------------------------ + +def lvl3_plot_relative_changes(database, premise_database, method): + + ''' + A function that plots the relative changes in activitiy LCA scores (for one defined method) between a "raw" ecoinvent database and a premise transformed ecoinvent database. + + :param database: an ecoinvent database or set of activities from an ecoinvent database. + :premise_database: a premise transformed database or a set of activities which has intersections with the ecoinvent database. + :method: a method the relative changes should be calculated and plotted for. + + ''' + + ecoinvent_scores = calculate_lca_ecoinvent_scores(database, method) + premise_scores = calculate_lca_premise_scores(premise_database, method) + + relative_changes = calc_relative_changes(ecoinvent_scores, premise_scores) + + # Filter out entries where the value is a tuple (method) + filtered_changes = {k: v for k, v in relative_changes.items() if not isinstance(v, tuple)} + + # Sort the relative changes by magnitude + sorted_changes = sorted(filtered_changes.items(), key=lambda x: x[1]) + + # Prepare data for plotting + activities_list = [f"{key}" for key, _ in sorted_changes] + changes = [change for _, change in sorted_changes] + + # Create the plot + fig, ax = plt.subplots(figsize=(12, len(activities_list) * 0.4)) # Adjust figure height based on number of activities_list + fig.suptitle(f"Relative Changes in LCA Scores {relative_changes['method']}") + y_pos = np.arange(len(activities_list)) + ax.barh(y_pos, changes, align='center', color='lightgrey', alpha=0.7) + + # Plot curve through datapoints + ax.plot(changes, y_pos, color='darkblue', linewidth=2, marker='o', markersize=6) + + # Set labels and title + ax.set_yticks(y_pos) + ax.set_yticklabels(activities_list) + ax.invert_yaxis() # Labels read top-to-bottom + ax.set_xlabel('Relative Change') + + + # Add a vertical line at x=0 + ax.axvline(x=0, color='k', linestyle='--') + + # Adjust layout and display + plt.tight_layout() + plt.show() + +# Level 3 plot dependencies: Functions for generating lca scores of ecoinvent and premise database to plot their relative changes +# ------------------------------------------------------------------------------------------------------------------------------- + +def calculate_lca_ecoinvent_scores(database, method): + + ecoinvent_scores= {} + ecoinvent_scores['method']=method #save the method used for plotting the data + all_activities=[x for x in database] + + for activity in all_activities: + activity_LCA = bw.LCA({activity:1}, bw.Method(method).name) + activity_LCA.lci() + activity_LCA.lcia() + score=activity_LCA.score + + # Create a tuple key with relevant information + key = (activity['name'], activity['unit'], activity['location'], activity.get('reference product')) + + ecoinvent_scores[key]=score + + return ecoinvent_scores + +def calculate_lca_premise_scores(premise_database, method): + + premise_scores= {} + + premise_scores['method']=method #save the method used for plotting the data + + all_activities=[x for x in premise_database] + + for activity in all_activities: + activity_LCA = bw.LCA({activity:1}, bw.Method(method).name) + activity_LCA.lci() + activity_LCA.lcia() + score=activity_LCA.score + + # Create a tuple key with relevant information + key = (activity['name'], activity['unit'], activity['location'], activity.get('reference product')) + + premise_scores[key]=score + + return premise_scores + + +# relative_changes contains the activity names as keys and their relative changes as values + +def compute_relative_change(original, transformed): + if original == 0: + return float('inf') if transformed != 0 else 0 + return (transformed - original) / original + + +def calc_relative_changes(ecoinvent_scores, premise_scores): + + # Match activities_list and calculate relative changes + relative_changes = {} + relative_changes['method']=ecoinvent_scores['method'] + + # Track additional keys in premise_scores + additional_premise_keys = [] + + for key, original_score in ecoinvent_scores.items(): + if key in premise_scores: #activities only in premise_scores are according to this logic neglected. + # Skip if original_score is a tuple due to information tuple key + if isinstance(original_score, tuple): + continue + + transformed_score = premise_scores[key] + relative_change = compute_relative_change(original_score, transformed_score) + relative_changes[key] = relative_change + + # Identify additional keys in premise_scores + for key in premise_scores.keys(): + if key not in ecoinvent_scores: + additional_premise_keys.append(key) + + # Print the dataframes_dict + for key, change in relative_changes.items(): + print(f"{key}: {change}") + + if additional_premise_keys: + print("Additional keys in premise_scores not found in ecoinvent_scores:", additional_premise_keys) + return relative_changes \ No newline at end of file diff --git a/dev/archiv/archiv py/lca_scores.py b/dev/archiv/archiv py/lca_scores.py new file mode 100644 index 0000000..00a2cf7 --- /dev/null +++ b/dev/archiv/archiv py/lca_scores.py @@ -0,0 +1,123 @@ +# imports +# ------- +from premise import * + +# brightway +import brightway2 as bw +import bw2analyzer as ba +import bw2data as bd + +# common +import pandas as pd +import numpy as np + +# plotting +import matplotlib.pyplot as plt +import seaborn as sns + +# to be completed +import ast + + +# Functions for generating lca scores of ecoinvent and premise database to plot their relative changes +# Level 3 plot dependency +# ------------------------------------------------------------------------------------------------------------------------------- + + +def _calculate_lca_ecoinvent_scores(database, method): + ecoinvent_scores = {} + ecoinvent_scores["method"] = method # save the method used for plotting the data + all_activities = [x for x in database] + + for activity in all_activities: + activity_LCA = bw.LCA({activity: 1}, bw.Method(method).name) + activity_LCA.lci() + activity_LCA.lcia() + score = activity_LCA.score + + # Create a tuple key with relevant information + key = ( + activity["name"], + activity["unit"], + activity["location"], + activity.get("reference product"), + ) + + ecoinvent_scores[key] = score + + return ecoinvent_scores + + +def _calculate_lca_premise_scores(premise_database, method): + premise_scores = {} + + premise_scores["method"] = method # save the method used for plotting the data + + all_activities = [x for x in premise_database] + + for activity in all_activities: + activity_LCA = bw.LCA({activity: 1}, bw.Method(method).name) + activity_LCA.lci() + activity_LCA.lcia() + score = activity_LCA.score + + # Create a tuple key with relevant information + key = ( + activity["name"], + activity["unit"], + activity["location"], + activity.get("reference product"), + ) + + premise_scores[key] = score + + return premise_scores + + +# relative_changes contains the activity names as keys and their relative changes as values + + +def _compute_relative_change(original, transformed): + if original == 0: + return float("inf") if transformed != 0 else 0 + return (transformed - original) / original + + +def _calc_relative_changes(ecoinvent_scores, premise_scores): + # Match activities_list and calculate relative changes + relative_changes = {} + relative_changes["method"] = ecoinvent_scores["method"] + + # Track additional keys in premise_scores + additional_premise_keys = [] + + for key, original_score in ecoinvent_scores.items(): + if ( + key in premise_scores + ): # activities only in premise_scores are according to this logic neglected. + # Skip if original_score is a tuple due to information tuple key + if isinstance(original_score, tuple): + continue + + transformed_score = premise_scores[key] + relative_change = _compute_relative_change( + original_score, transformed_score + ) + relative_changes[key] = relative_change + + # Identify additional keys in premise_scores + for key in premise_scores.keys(): + if key not in ecoinvent_scores: + additional_premise_keys.append(key) + + # Print the dataframes_dict + for key, change in relative_changes.items(): + print(f"{key}: {change}") + + if additional_premise_keys: + print( + "Additional keys in premise_scores not found in ecoinvent_scores:", + additional_premise_keys, + ) + + return relative_changes diff --git a/dev/lca_to_excl.py b/dev/archiv/archiv py/lca_to_excl.py similarity index 100% rename from dev/lca_to_excl.py rename to dev/archiv/archiv py/lca_to_excl.py diff --git a/dev/methods.py b/dev/archiv/archiv py/methods.py similarity index 100% rename from dev/methods.py rename to dev/archiv/archiv py/methods.py diff --git a/dev/plots.py b/dev/archiv/archiv py/plots.py similarity index 61% rename from dev/plots.py rename to dev/archiv/archiv py/plots.py index 19365c0..8a42527 100644 --- a/dev/plots.py +++ b/dev/archiv/archiv py/plots.py @@ -1,28 +1,31 @@ # Imports # ------- -#common +# common import pandas as pd import numpy as np -#plotting +# plotting import matplotlib.pyplot as plt import seaborn as sns # LEVEL 1 # ------- -# Function for plotting: Level 1 dot plot with standard deviation and IQR range +# Function for plotting: Level 1 dot plot with standard deviation +# and IQR range # ------------------------------------------------------------------------------ -def lvl1_plot(dataframes_dict, title_key=None): - ''' - Plots the total score value for each activity sorted from largest to smallest. Visualizes IQR and standard deviation. + +def scores_across_activities(dataframes_dict: dict, title_key: str=None) -> None: + """ + Plots the total score value for each activity sorted from largest + to smallest. Visualizes IQR and standard deviation. Generates as many plots as methods were defined. :param dataframes_dict: dictionary resulting from the function "compare_activities_multiple_methods" (and subsequently "small_inputs_to_other_column") :param title_key: some string for the plot titles (e.g. sector name) - ''' + """ # Iterate over each dataframe and create individual plots for idx, df in dataframes_dict.items(): @@ -30,38 +33,83 @@ def lvl1_plot(dataframes_dict, title_key=None): fig, ax = plt.subplots(figsize=(12, 6)) # Sort the DataFrame in descending order based on the 'total' column - sorted_df = df.sort_values(by='total', ascending=False) - + sorted_df = df.sort_values(by="total", ascending=False) + # Save the sorted index to order variable and call order variable in sns.swarmplot order = sorted_df.index.tolist() # Calculate statistics - q1 = df['total'].quantile(0.25) - q3 = df['total'].quantile(0.75) - mean_gwp = df['total'].mean() - std_gwp = df['total'].std() - + q1 = df["total"].quantile(0.25) + q3 = df["total"].quantile(0.75) + mean_gwp = df["total"].mean() + std_gwp = df["total"].std() + # Plot using seaborn swarmplot - sns.swarmplot(data=df, x=df.index, y='total', dodge=True, ax=ax, order=order) + sns.swarmplot( + data=df, + x=df.index, + y="total", + dodge=True, + ax=ax, + order=order + ) # Add mean line - ax.axhline(mean_gwp, color='grey', linestyle='--', linewidth=1, label='Mean') + ax.axhline( + mean_gwp, + color="grey", + linestyle="--", + linewidth=1, + label="Mean" + ) # Add horizontal lines for Q1 and Q3 - ax.hlines(y=q3, xmin=-0.5, xmax=len(df)-0.5, color='lightblue', linestyle='dotted', linewidth=1, label='Q3 (75th percentile)') - ax.hlines(y=q1, xmin=-0.5, xmax=len(df)-0.5, color='lightblue', linestyle='dotted', linewidth=1, label='Q1 (25th percentile)') - - # Add horizontal shading for areas above and below 2 standard deviations from the mean - ax.axhspan(mean_gwp - 2 * std_gwp, mean_gwp - 3 * std_gwp, color='grey', alpha=0.2, label=">2 std below mean") - ax.axhspan(mean_gwp + 2 * std_gwp, mean_gwp + 3 * std_gwp, color='grey', alpha=0.2, label=">2 std above mean") + ax.hlines( + y=q3, + xmin=-0.5, + xmax=len(df) - 0.5, + color="lightblue", + linestyle="dotted", + linewidth=1, + label="Q3 (75th percentile)", + ) + ax.hlines( + y=q1, + xmin=-0.5, + xmax=len(df) - 0.5, + color="lightblue", + linestyle="dotted", + linewidth=1, + label="Q1 (25th percentile)", + ) + + # Add horizontal shading for areas above + # and below 2 standard deviations from the mean + ax.axhspan( + mean_gwp - 2 * std_gwp, + mean_gwp - 3 * std_gwp, + color="grey", + alpha=0.2, + label=">2 std below mean", + ) + ax.axhspan( + mean_gwp + 2 * std_gwp, + mean_gwp + 3 * std_gwp, + color="grey", + alpha=0.2, + label=">2 std above mean", + ) # Add titles and labels - ax.set_title(f"{str(title_key)} - {df['method'].iloc[0]} in {df['method unit'].iloc[0]}") - ax.set_xlabel('Activity/ Dataset') + ax.set_title( + f"{str(title_key)} - {df['method'].iloc[0]} " + f"in {df['method unit'].iloc[0]}" + ) + ax.set_xlabel("Activity/ Dataset") ax.set_ylabel(f"{df['method unit'].iloc[0]}") # Rotate x-axis labels if needed - ax.tick_params(axis='x', rotation=90) + ax.tick_params(axis="x", rotation=90) # Add legend ax.legend() @@ -70,176 +118,242 @@ def lvl1_plot(dataframes_dict, title_key=None): legend_text = generate_legend_text(dataframes_dict) # Add the legend text to the right of the plot - plt.text(1.02, 0.5, '\n'.join(legend_text), transform=ax.transAxes, ha='left', va='center', fontsize=11, bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) + plt.text( + 1.02, + 0.5, + "\n".join(legend_text), + transform=ax.transAxes, + ha="left", + va="center", + fontsize=11, + bbox=dict(facecolor="white", alpha=0.2, edgecolor="grey"), + ) # Show the plot plt.tight_layout() plt.show() + # LEVEL 2.1 # --------- # Function for plotting: Level 2.1 Absolute stacked bar plots # ------------------------------------------------------------ -def lvl21_plot_stacked_absolute(dataframes_dict, title_key=None): - ''' - Comparing activities and the input contributions to the total score by plotting a stacked absolute bar plot for each method. + +def inputs_contributions(dataframes_dict: dict, title_key: str = None) -> None: + """ + Comparing activities and the input contributions to + the total score by plotting a stacked absolute + bar plot for each method. :param dataframes_dict: dictionary resulting from the function "compare_activities_multiple_methods" (and subsequently "small_inputs_to_other_column") :param title_key: some string for the plot titles - ''' + """ # Step 1: Collect all unique categories all_categories = set() - + for df in dataframes_dict.values(): - if 'total' in df.columns: - total_index = df.columns.get_loc('total') - relevant_columns = df.columns[total_index + 1:] + if "total" in df.columns: + total_index = df.columns.get_loc("total") + relevant_columns = df.columns[total_index + 1 :] else: relevant_columns = df.columns - + # Update all_categories set with relevant columns all_categories.update(relevant_columns) - + all_categories = list(all_categories) - + # Step 2: Create a consistent color palette and color map distinct_colors = generate_distinct_colors(len(all_categories)) color_map = dict(zip(all_categories, distinct_colors)) # Step 3: Plot each DataFrame for key, df in dataframes_dict.items(): - if 'total' in df.columns: - df_og = df.copy() #for calling method and informative column in title and axis - total_index = df.columns.get_loc('total') - df = df.iloc[:, total_index + 1:] - + if "total" in df.columns: + df_og = ( + df.copy() + ) # for calling method and informative column in title and axis + total_index = df.columns.get_loc("total") + df = df.iloc[:, total_index + 1 :] + # Create a new figure for each plot fig, ax = plt.subplots(figsize=(20, 10)) - + # Ensure columns match the categories used in the color map df = df[[col for col in df.columns if col in color_map]] - + # Plotting the DataFrame with the custom color map - df.plot(kind='bar', stacked=True, ax=ax, color=[color_map[col] for col in df.columns]) + df.plot( + kind="bar", + stacked=True, + ax=ax, + color=[color_map[col] for col in df.columns], + ) # Add titles and labels - ax.set_title(f"{str(title_key)} - {df_og['method'].iloc[0]} in {df_og['method unit'].iloc[0]}") - ax.set_xlabel('Activity/ Dataset') + ax.set_title( + f"{str(title_key)} - {df_og['method'].iloc[0]} " + f"in {df_og['method unit'].iloc[0]}" + ) + ax.set_xlabel("Activity/ Dataset") ax.set_ylabel(f"{df_og['method unit'].iloc[0]}") - + # First legend: Categories - first_legend = ax.legend(title='Categories', loc='center left', bbox_to_anchor=(1, 0.5), fontsize='small') - + first_legend = ax.legend( + title="Categories", + loc="center left", + bbox_to_anchor=(1, 0.5), + fontsize="small", + ) + # Add the first legend manually ax.add_artist(first_legend) - + # Generate the legend text using the first dataframe legend_text = generate_legend_text(dataframes_dict) - + # Create a second legend below the first one - fig.text(1.02, 0.1, '\n'.join(legend_text), transform=ax.transAxes, fontsize=11, - verticalalignment='bottom', bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) - + fig.text( + 1.02, + 0.1, + "\n".join(legend_text), + transform=ax.transAxes, + fontsize=11, + verticalalignment="bottom", + bbox=dict(facecolor="white", alpha=0.2, edgecolor="grey"), + ) + # Rotate x-axis labels for better readability - plt.xticks(rotation=90, ha='right') - + plt.xticks(rotation=90, ha="right") + # Adjust layout to make room for both legends plt.tight_layout() plt.subplots_adjust(right=0.75, bottom=0.2) - + # Display the plot plt.show() -# LEVEL 2.3 -# --------- -# Function for plotting: Level 2.3 bar plot comparing input not characterized across sector/ activity list -# -------------------------------------------------------------------------------------------------------- - -def lvl23_plot_input_comparison_plot_no_method(activities_list, input_type, input_number,): - ''' - Comparing one specific cpc input among activities without method. - - :param activities_list: list of activities to plot inputs for. Perhabs the one defined at the beginning. - :param input_type: type of the activities input default 'list', other 'dict' - :param input_number: the cpc code of the input that is supposed to be plotted - - ''' - cpc_input_dataframe = get_cpc_inputs_of_activities(activities_list, input_type) - - x_input_fltr= [x for x in cpc_input_dataframe.columns if str(input_number) in str(x)][0] - - df= cpc_input_dataframe[x_input_fltr] - - df = df.sort_values(ascending=False) - ax = df.plot(kind='bar', x=x_input_fltr, figsize=(14, 6)) - ax.set_xlabel('Activity/ Dataset') - ax.set_ylabel(f"{cpc_input_dataframe['unit'].iloc[0]}") - ax.set_title(f'Comparison Plot for not characterized Input - {x_input_fltr}') - - # Generate the legend text to map index to activity - legend_text = generate_legend_text(cpc_input_dataframe) - # Add the legend text to the right of the plot - ax.text(1.02, 0.5, '\n'.join(legend_text), transform=ax.transAxes, ha='left', va='center', fontsize=11, bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) # LEVEL 2.2 # ---------- # Function for plotting: Level 2.2 bar plot comparing one input characterized by one method across sector/ activity list # ---------------------------------------------------------------------------------------------------------------------- -def lvl22_plot_input_comparison_with_method(dataframes_dict, dataframe_key, input_number): + +def inputs_contribution( + dataframes_dict: dict, dataframe_key: str, input_number: str +) -> None: """ Comparing one specific cpc input among activities for each method. :param dataframes_dict:dictionary resulting from the function "compare_activities_multiple_methods" (and subsequently "small_inputs_to_other_column") - :param dataframe_key: Key to access a specific DataFrame from the dictionary. + :param dataframe_key: Key to access a specific DataFrame from the dictionary. :param input_number: Unique cpc identifier number of the input that should be plotted. """ # Access the specific DataFrame df = dataframes_dict.get(dataframe_key) - + if df is None: print(f"No DataFrame found for key: {dataframe_key}") return # Filter columns based on the input_number columns_to_plot = [col for col in df.columns if str(input_number) in str(col)] - + if not columns_to_plot: print(f"No columns found containing input number: {input_number}") return - + # Plot the filtered columns - ax = df[columns_to_plot].plot(kind='bar', figsize=(14, 6)) - plt.xlabel('Activity/ Dataset') + ax = df[columns_to_plot].plot(kind="bar", figsize=(14, 6)) + plt.xlabel("Activity/ Dataset") plt.ylabel(f"{df['method unit'].iloc[0]}") - plt.title(f'Comparison Plot for Input Number {input_number}') + plt.title(f"Comparison Plot for Input Number {input_number}") # Add legend for identifying activities_list from index # Generate the legend text using the first dataframe legend_text = generate_legend_text(dataframes_dict.get(dataframe_key)) - + # Add the legend text to the right of the plot - plt.text(1.02, 0.5, '\n'.join(legend_text), transform=ax.transAxes, ha='left', va='center', fontsize=11, bbox=dict(facecolor='white', alpha=0.2, edgecolor='grey')) - + plt.text( + 1.02, + 0.5, + "\n".join(legend_text), + transform=ax.transAxes, + ha="left", + va="center", + fontsize=11, + bbox=dict(facecolor="white", alpha=0.2, edgecolor="grey"), + ) + plt.show() + +# LEVEL 2.3 +# --------- +# Function for plotting: Level 2.3 bar plot comparing input not characterized across sector/ activity list +# -------------------------------------------------------------------------------------------------------- + + +def input_contribution_across_activities( + activities_list: list, + input_type, + input_number: str, +) -> None: + """ + Comparing one specific cpc input among activities without method. + + :param activities_list: list of activities to plot inputs for. Perhabs the one defined at the beginning. + :param input_type: type of the activities input default 'list', other 'dict' + :param input_number: the cpc code of the input that is supposed to be plotted + + """ + cpc_input_dataframe = get_cpc_inputs_of_activities(activities_list, input_type) + + x_input_fltr = [ + x for x in cpc_input_dataframe.columns if str(input_number) in str(x) + ][0] + + df = cpc_input_dataframe[x_input_fltr] + + df = df.sort_values(ascending=False) + ax = df.plot(kind="bar", x=x_input_fltr, figsize=(14, 6)) + ax.set_xlabel("Activity/ Dataset") + ax.set_ylabel(f"{cpc_input_dataframe['unit'].iloc[0]}") + ax.set_title(f"Comparison Plot for not characterized Input - {x_input_fltr}") + + # Generate the legend text to map index to activity + legend_text = generate_legend_text(cpc_input_dataframe) + # Add the legend text to the right of the plot + ax.text( + 1.02, + 0.5, + "\n".join(legend_text), + transform=ax.transAxes, + ha="left", + va="center", + fontsize=11, + bbox=dict(facecolor="white", alpha=0.2, edgecolor="grey"), + ) + + # LEVEL 3 # -------- # Function for plotting: Level 3 S-curve difference of og database and premise adapted database by one meth # ------------------------------------------------------------------------------------------------------------ -def lvl3_plot_relative_changes(database, premise_database, method): - ''' +def activities_across_databases(database, premise_database, method): + """ A function that plots the relative changes in activitiy LCA scores (for one defined method) between a "raw" ecoinvent database and a premise transformed ecoinvent database. :param database: an ecoinvent database or set of activities from an ecoinvent database. :premise_database: a premise transformed database or a set of activities which has intersections with the ecoinvent database. :method: a method the relative changes should be calculated and plotted for. - ''' + """ ecoinvent_scores = calculate_lca_ecoinvent_scores(database, method) premise_scores = calculate_lca_premise_scores(premise_database, method) @@ -247,7 +361,9 @@ def lvl3_plot_relative_changes(database, premise_database, method): relative_changes = calc_relative_changes(ecoinvent_scores, premise_scores) # Filter out entries where the value is a tuple (method) - filtered_changes = {k: v for k, v in relative_changes.items() if not isinstance(v, tuple)} + filtered_changes = { + k: v for k, v in relative_changes.items() if not isinstance(v, tuple) + } # Sort the relative changes by magnitude sorted_changes = sorted(filtered_changes.items(), key=lambda x: x[1]) @@ -255,21 +371,23 @@ def lvl3_plot_relative_changes(database, premise_database, method): # Prepare data for plotting activities_list = [f"{key}" for key, _ in sorted_changes] changes = [change for _, change in sorted_changes] - + # Create the plot - fig, ax = plt.subplots(figsize=(12, len(activities_list) * 0.4)) # Adjust figure height based on number of activities_list + fig, ax = plt.subplots( + figsize=(12, len(activities_list) * 0.4) + ) # Adjust figure height based on number of activities_list fig.suptitle(f"Relative Changes in LCA Scores {relative_changes['method']}") y_pos = np.arange(len(activities_list)) - ax.barh(y_pos, changes, align='center', color='lightgrey', alpha=0.7) + ax.barh(y_pos, changes, align="center", color="lightgrey", alpha=0.7) # Plot curve through datapoints - ax.plot(changes, y_pos, color='darkblue', linewidth=2, marker='o', markersize=6) + ax.plot(changes, y_pos, color="darkblue", linewidth=2, marker="o", markersize=6) # Set labels and title ax.set_yticks(y_pos) ax.set_yticklabels(activities_list) ax.invert_yaxis() # Labels read top-to-bottom - ax.set_xlabel('Relative Change') + ax.set_xlabel("Relative Change") # Formatting @@ -277,15 +395,16 @@ def lvl3_plot_relative_changes(database, premise_database, method): # Level 1-2.3 plots dependency: Legend to map indexes on x-axis to activities # --------------------------------------------------------------------------------------- + def generate_legend_text(data): - ''' + """ Maps the indexes on the x-axis to the activities to list them in a legend. :param data: it can take in a dictionary of dataframes or just a single dataframe - ''' + """ legend_text = [] - + # Check if the input is a dictionary or a DataFrame if isinstance(data, dict): # Use the first DataFrame in the dictionary @@ -295,10 +414,12 @@ def generate_legend_text(data): # Use the input DataFrame directly df = data else: - raise ValueError("Input must be either a dictionary of DataFrames or a DataFrame") - + raise ValueError( + "Input must be either a dictionary of DataFrames or a DataFrame" + ) + # Create a list of tuples with (index, activity, location) - items = [(str(i), row['activity'], row['location']) for i, row in df.iterrows()] + items = [(str(i), row["activity"], row["location"]) for i, row in df.iterrows()] # Sort the items based on the index sorted_items = sorted(items, key=lambda x: x[0]) # Add sorted items to legend_text @@ -306,11 +427,13 @@ def generate_legend_text(data): legend_text.append(f"{i}: {activity} - {location}") return legend_text + # Level 2.1 plot dependencies: Function for formating plot: Unique colors for Level 2.1 Absolute stacked bar plots # ----------------------------------------------------------------------------------- + def generate_distinct_colors(n): """Generate n distinct colors using HSV color space.""" hues = np.linspace(0, 1, n, endpoint=False) colors = [plt.cm.hsv(h) for h in hues] - return colors + return colors \ No newline at end of file diff --git a/dev/plots_excl.py b/dev/archiv/archiv py/plots_excl.py similarity index 100% rename from dev/plots_excl.py rename to dev/archiv/archiv py/plots_excl.py diff --git a/dev/sector_score_dict.py b/dev/archiv/archiv py/sector_score_dict.py similarity index 86% rename from dev/sector_score_dict.py rename to dev/archiv/archiv py/sector_score_dict.py index a829443..02c61aa 100644 --- a/dev/sector_score_dict.py +++ b/dev/archiv/archiv py/sector_score_dict.py @@ -85,8 +85,9 @@ def small_inputs_to_other_column(dataframes_dict, cutoff=0.01): Set the aggregated values to zero in their original columns. Remove any columns that end up containing only zeros. - :param dataframes_dict: the dictionary + Additionally, if a column is named None or "Unnamed", its values will be added to the 'other' column and then the original column will be deleted. + :param dataframes_dict: the dictionary ''' processed_dict = {} @@ -100,16 +101,26 @@ def small_inputs_to_other_column(dataframes_dict, cutoff=0.01): numeric_cols = df.iloc[:, total_col_index:] numeric_cols = numeric_cols.astype(float) - # Calculate the threshold for each row (1% of total) + # Calculate the threshold for each row (cutoff% of total) threshold = numeric_cols['total'] * cutoff # Create 'other' column numeric_cols['other'] = 0.0 + # Identify and handle columns that are None or called "Unnamed" + columns_to_remove = [] + for col in df.columns: + if col is None or col.startswith("Unnamed"): + numeric_cols['other'] += df[col] # Add the values to the 'other' column + columns_to_remove.append(col) + + # Drop the identified columns + df.drop(columns=columns_to_remove, inplace=True) + # Process each numeric column (except 'total' and 'other') for col in numeric_cols.columns[1:-1]: # Skip 'total' and 'other' # Identify values less than the threshold - mask = abs(numeric_cols[col]) < threshold #abs() to include negative contributions + mask = abs(numeric_cols[col]) < threshold # abs() to include negative contributions # Add these values to 'other' numeric_cols.loc[mask, 'other'] += numeric_cols.loc[mask, col] @@ -127,10 +138,10 @@ def small_inputs_to_other_column(dataframes_dict, cutoff=0.01): # Combine string and processed numeric columns processed_df = pd.concat([string_cols, numeric_cols], axis=1) - #Sort columns by total + # Sort DataFrame by total (optional) processed_df = processed_df.sort_values('total', ascending=False) # Store the processed DataFrame in the result dictionary processed_dict[key] = processed_df - return processed_dict \ No newline at end of file + return processed_dict diff --git a/dev/dopo_excel.py b/dev/archiv/dopo_excel.py similarity index 100% rename from dev/dopo_excel.py rename to dev/archiv/dopo_excel.py diff --git a/dev/cpc inputs code/cpc_inputs.py b/dev/cpc inputs code/cpc_inputs.py new file mode 100644 index 0000000..2bbe1d0 --- /dev/null +++ b/dev/cpc inputs code/cpc_inputs.py @@ -0,0 +1,126 @@ +# Imports +# -------- + +# brightway +import brightway2 as bw +import bw2analyzer as ba + +# common +import pandas as pd +import numpy as np + +# to be completed +import ast + +# Function to generate dataframes containing inputs in cpc format not characterized from an activity list +# Level 2.3 plot dependency +# ------------------------------------------------------------------------------------------------------------------------------------ + + +def _get_cpc_inputs_of_activities(activities_list, input_type="list"): + """ + for param description see function lvl23_plot_input_comparison_plot_no_method + + NOTE: could adapt this function to get the outputs, or create another one. At the moment only inputs are considered. + + """ + + def _activity_list_inputs_cpc(activities_list, input_type): + all_inputs = [] + + if input_type == "list": + activity_iterator = activities_list + elif input_type == "dict": + activity_iterator = activities_list.values() + else: + raise ValueError("input_type must be either 'list' or 'dict'") + + for activity in activity_iterator: + inputs_keys = pd.Series( + { + bw.get_activity(exc.input).key: exc.amount + for exc in activity.technosphere() + }, + name=activity["name"] + ", " + activity["location"], + ) + + # Adjust the way the key is presented + inputs_keys = inputs_keys.reset_index() + inputs_keys["full_key"] = inputs_keys.apply( + lambda row: f"('{row['level_0']}', '{row['level_1']}')", axis=1 + ) + inputs_keys = inputs_keys.drop(["level_0", "level_1"], axis=1).set_index( + "full_key" + ) + + # Add empty cpc column and activity information + inputs_keys.insert( + 0, "identifier", activity["name"] + ", " + activity["location"] + ) + inputs_keys.insert(1, "activity", activity["name"]) + inputs_keys.insert(2, "location", activity["location"]) + inputs_keys.insert(3, "unit", activity["unit"]) + inputs_keys.insert(4, "cpc", None) + + all_inputs.append(inputs_keys) + + # Combine all inputs into a single DataFrame + combined_inputs = pd.concat(all_inputs, axis=0) + + return combined_inputs + + def _update_cpc_information(combined_inputs): + for index, row in combined_inputs.iterrows(): + # Transform each key to tuple + tuple_key = ast.literal_eval(index) + + # Get input activity for the key + input_activity = bw.get_activity(tuple_key) + + # Get cpc name for activity + cpc_name = ba.comparisons.get_cpc(input_activity) + + # Store cpc_name in the 'cpc' column of the combined_inputs dataframe + combined_inputs.at[index, "cpc"] = cpc_name + + return combined_inputs + + def _transform_dataframe(combined_inputs): + # Set 'identifier' as the new index and drop the 'full_key' index + combined_inputs = ( + combined_inputs.reset_index() + .set_index("identifier") + .drop("full_key", axis=1) + ) + + # Determine the index of the 'unit' column + unit_index = combined_inputs.columns.get_loc("unit") + + # Split the dataframe into two parts + combined_inputs_identifier = combined_inputs.iloc[:, : unit_index + 1] + combined_inputs_cpc = combined_inputs.iloc[:, unit_index + 1 :] + # set index of to 'cpc' in combined_input_cpc + combined_inputs_cpc = combined_inputs_cpc.set_index("cpc") + + # Combine rows with the same index value in combined_inputs_cpc + combined_inputs_cpc = combined_inputs_cpc.groupby(level=0).agg( + lambda x: np.sum(x) if x.dtype.kind in "biufc" else x.iloc[0] + ) + # Transpose combined_inputs_cpc + combined_inputs_cpc_trans = combined_inputs_cpc.T + + # Merge combined_inputs_identifier and combined_inputs_cpc_trans + result = combined_inputs_identifier.join(combined_inputs_cpc_trans) + result = result.drop_duplicates() + + # Sort dataframe by activity and location aplphabetically and reset the index + result = result.sort_values(by=["activity", "location"]) + result = result.reset_index(drop=True) + return result + + # Execute the workflow + combined_inputs = _activity_list_inputs_cpc(activities_list, input_type) + combined_inputs_with_cpc = _update_cpc_information(combined_inputs) + final_result = _transform_dataframe(combined_inputs_with_cpc) + + return final_result diff --git a/dev/lca_scores.py b/dev/lca_scores.py deleted file mode 100644 index bdfecbf..0000000 --- a/dev/lca_scores.py +++ /dev/null @@ -1,106 +0,0 @@ -# imports -# ------- -from premise import * - -#brightway -import brightway2 as bw -import bw2analyzer as ba -import bw2data as bd - -#common -import pandas as pd -import numpy as np - -#plotting -import matplotlib.pyplot as plt -import seaborn as sns - -#to be completed -import ast - - -# Functions for generating lca scores of ecoinvent and premise database to plot their relative changes -# Level 3 plot dependency -# ------------------------------------------------------------------------------------------------------------------------------- - -def calculate_lca_ecoinvent_scores(database, method): - - ecoinvent_scores= {} - ecoinvent_scores['method']=method #save the method used for plotting the data - all_activities=[x for x in database] - - for activity in all_activities: - activity_LCA = bw.LCA({activity:1}, bw.Method(method).name) - activity_LCA.lci() - activity_LCA.lcia() - score=activity_LCA.score - - # Create a tuple key with relevant information - key = (activity['name'], activity['unit'], activity['location'], activity.get('reference product')) - - ecoinvent_scores[key]=score - - return ecoinvent_scores - -def calculate_lca_premise_scores(premise_database, method): - - premise_scores= {} - - premise_scores['method']=method #save the method used for plotting the data - - all_activities=[x for x in premise_database] - - for activity in all_activities: - activity_LCA = bw.LCA({activity:1}, bw.Method(method).name) - activity_LCA.lci() - activity_LCA.lcia() - score=activity_LCA.score - - # Create a tuple key with relevant information - key = (activity['name'], activity['unit'], activity['location'], activity.get('reference product')) - - premise_scores[key]=score - - return premise_scores - - -# relative_changes contains the activity names as keys and their relative changes as values - -def compute_relative_change(original, transformed): - if original == 0: - return float('inf') if transformed != 0 else 0 - return (transformed - original) / original - - -def calc_relative_changes(ecoinvent_scores, premise_scores): - - # Match activities_list and calculate relative changes - relative_changes = {} - relative_changes['method']=ecoinvent_scores['method'] - - # Track additional keys in premise_scores - additional_premise_keys = [] - - for key, original_score in ecoinvent_scores.items(): - if key in premise_scores: #activities only in premise_scores are according to this logic neglected. - # Skip if original_score is a tuple due to information tuple key - if isinstance(original_score, tuple): - continue - - transformed_score = premise_scores[key] - relative_change = compute_relative_change(original_score, transformed_score) - relative_changes[key] = relative_change - - # Identify additional keys in premise_scores - for key in premise_scores.keys(): - if key not in ecoinvent_scores: - additional_premise_keys.append(key) - - # Print the dataframes_dict - for key, change in relative_changes.items(): - print(f"{key}: {change}") - - if additional_premise_keys: - print("Additional keys in premise_scores not found in ecoinvent_scores:", additional_premise_keys) - - return relative_changes \ No newline at end of file diff --git a/dev/notebook tests/compare_scores_plot_v1.ipynb b/dev/notebook tests/compare_scores_plot_v1.ipynb new file mode 100644 index 0000000..95b41c5 --- /dev/null +++ b/dev/notebook tests/compare_scores_plot_v1.ipynb @@ -0,0 +1,1373 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "from functions_v2 import*\n", + "from methods import MethodFinder\n", + "\n", + "import brightway2 as bw\n", + "import bw2data as bd\n", + "import bw2analyzer as ba\n", + "import bw2calc as bc\n", + "\n", + "#reduce?\n", + "import ast\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "\n", + "import dopo" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Biosphere database already present!!! No setup is needed\n" + ] + } + ], + "source": [ + "bd.projects.set_current(\"premise-validation-try1\")\n", + "bw.bw2setup()\n", + "\n", + "bio3=bw.Database('biosphere3')\n", + "ei39=bw.Database('ecoinvent 3.9.1 cutoff')\n", + "ei39SSP2=bw.Database('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "#sector filters file names/paths\n", + "\n", + "cement = 'cement_small.yaml'\n", + "electricity = 'electricity_small.yaml'\n", + "fuels= 'fuels_small.yaml'\n", + "steel = 'steel_small.yaml'\n", + "transport = 'transport_small.yaml'" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Cement': {'yaml': 'yamls\\\\cement_small.yaml', 'yaml identifier': 'Cement'},\n", + " 'Electricity': {'yaml': 'yamls\\\\electricity_small.yaml',\n", + " 'yaml identifier': 'Electricity'}}" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "files_dict={}\n", + "files_dict['Cement']={'yaml': 'yamls\\cement_small.yaml',\n", + " 'yaml identifier': 'Cement'}\n", + "files_dict['Electricity']= {'yaml':'yamls\\electricity_small.yaml',\n", + " 'yaml identifier': 'Electricity'} #yaml identifier is the name of the filter in the yaml file, in the first line.\n", + "files_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "sectors_dict_premise = dopo.dopo_excel.process_yaml_files(files_dict=files_dict, database=ei39SSP2)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Cement': {'yaml': 'yamls\\\\cement_small.yaml',\n", + " 'yaml identifier': 'Cement',\n", + " 'activities': ['cement production, Portland' (kilogram, CH, None),\n", + " 'cement production, Portland' (kilogram, US, None),\n", + " 'cement production, Portland' (kilogram, IN, None),\n", + " 'cement production, Portland' (kilogram, CA-QC, None),\n", + " 'cement production, Portland' (kilogram, BR, None),\n", + " 'cement production, Portland' (kilogram, ZA, None),\n", + " 'cement production, Portland' (kilogram, PE, None)]},\n", + " 'Electricity': {'yaml': 'yamls\\\\electricity_small.yaml',\n", + " 'yaml identifier': 'Electricity',\n", + " 'activities': []}}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sectors_dict_premise" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "sectors_dict_ecoinvent = dopo.dopo_excel.process_yaml_files(files_dict=files_dict, database=ei39)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'method_1': {'object': Brightway2 Method: IPCC 2013: climate change: global warming potential (GWP100),\n", + " 'method name': ('IPCC 2013',\n", + " 'climate change',\n", + " 'global warming potential (GWP100)'),\n", + " 'short name': 'global warming potential (GWP100)',\n", + " 'unit': 'kg CO2-Eq'},\n", + " 'method_2': {'object': Brightway2 Method: EN15804: inventory indicators ISO21930: Cumulative Energy Demand - non-renewable energy resources,\n", + " 'method name': ('EN15804',\n", + " 'inventory indicators ISO21930',\n", + " 'Cumulative Energy Demand - non-renewable energy resources'),\n", + " 'short name': 'Cumulative Energy Demand - non-renewable energy resources',\n", + " 'unit': 'megajoule'},\n", + " 'method_3': {'object': Brightway2 Method: selected LCI results: resource: land occupation,\n", + " 'method name': ('selected LCI results', 'resource', 'land occupation'),\n", + " 'short name': 'land occupation',\n", + " 'unit': 'square meter-year'},\n", + " 'method_4': {'object': Brightway2 Method: EN15804: inventory indicators ISO21930: use of net fresh water,\n", + " 'method name': ('EN15804',\n", + " 'inventory indicators ISO21930',\n", + " 'use of net fresh water'),\n", + " 'short name': 'use of net fresh water',\n", + " 'unit': 'cubic meter'}}" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Get Methods\n", + "finder=MethodFinder()\n", + "finder.find_and_create_method(criteria=['IPCC', '2013', 'GWP100'], exclude=['no LT'])\n", + "finder.find_and_create_method(criteria=['EN15804','Cumulative', 'non-renewable' ])\n", + "finder.find_and_create_method(criteria=['land occupation','selected'])\n", + "finder.find_and_create_method(criteria=['EN15804','fresh water'])\n", + "method_dict=finder.get_all_methods()\n", + "method_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "lcia_method=method_dict['method_1']['method name']" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('IPCC 2013', 'climate change', 'global warming potential (GWP100)')" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lcia_method" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [], + "source": [ + "lcia_method_unit=method_dict['method_1']['unit']" + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "metadata": {}, + "outputs": [], + "source": [ + "activities=sectors_dict_premise[\"Cement\"]['activities']" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "list" + ] + }, + "execution_count": 93, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(activities_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('ecoinvent 3.9.1 cutoff', 'df49e8f525497f2fbd56bcdc80ff0cde')" + ] + }, + "execution_count": 103, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "activities_list[0].key" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "LCA Scores" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def compare_activities_by_grouped_leaves(\n", + " activities,\n", + " lcia_method,\n", + " mode=\"relative\",\n", + " max_level=4,\n", + " cutoff=7.5e-3,\n", + " output_format=\"list\",\n", + " str_length=50,\n", + "):\n", + " \"\"\"Compare activities by the impact of their different inputs, aggregated by the product classification of those inputs.\n", + "\n", + " Args:\n", + " activities: list of ``Activity`` instances.\n", + " lcia_method: tuple. LCIA method to use when traversing supply chain graph.\n", + " mode: str. If \"relative\" (default), results are returned as a fraction of total input. Otherwise, results are absolute impact per input exchange.\n", + " max_level: int. Maximum level in supply chain to examine.\n", + " cutoff: float. Fraction of total impact to cutoff supply chain graph traversal at.\n", + " output_format: str. See below.\n", + " str_length; int. If ``output_format`` is ``html``, this controls how many characters each column label can have.\n", + "\n", + " Raises:\n", + " ValueError: ``activities`` is malformed.\n", + "\n", + " Returns:\n", + " Depends on ``output_format``:\n", + "\n", + " * ``list``: Tuple of ``(column labels, data)``\n", + " * ``html``: HTML string that will print nicely in Jupyter notebooks.\n", + " * ``pandas``: a pandas ``DataFrame``.\n", + "\n", + " \"\"\"\n", + " for act in activities:\n", + " if not isinstance(act, bd.backends.proxies.Activity):\n", + " raise ValueError(\"`activities` must be an iterable of `Activity` instances\")\n", + "\n", + " objs = [\n", + " group_leaves(find_leaves(act, lcia_method, max_level=max_level, cutoff=cutoff))\n", + " for act in activities\n", + " ]\n", + " sorted_keys = sorted(\n", + " [\n", + " (max([el[0] for obj in objs for el in obj if el[2] == key]), key)\n", + " for key in {el[2] for obj in objs for el in obj}\n", + " ],\n", + " reverse=True,\n", + " )\n", + " name_common = commonprefix([act[\"name\"] for act in activities])\n", + "\n", + " if \" \" not in name_common:\n", + " name_common = \"\"\n", + " else:\n", + " last_space = len(name_common) - operator.indexOf(reversed(name_common), \" \")\n", + " name_common = name_common[:last_space]\n", + " print(\"Omitting activity name common prefix: '{}'\".format(name_common))\n", + "\n", + " product_common = commonprefix(\n", + " [act.get(\"reference product\", \"\") for act in activities]\n", + " )\n", + "\n", + " lca = bc.LCA({act: 1 for act in activities}, lcia_method)\n", + " lca.lci()\n", + " lca.lcia()\n", + "\n", + " labels = [\n", + " \"activity\",\n", + " \"product\",\n", + " \"location\",\n", + " \"unit\",\n", + " \"total\",\n", + " \"direct emissions\",\n", + " ] + [key for _, key in sorted_keys]\n", + " data = []\n", + " for act, lst in zip(activities, objs):\n", + " lca.redo_lcia({act.id: 1})\n", + " data.append(\n", + " [\n", + " act[\"name\"].replace(name_common, \"\"),\n", + " act.get(\"reference product\", \"\").replace(product_common, \"\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " act.get(\"unit\", \"\"),\n", + " lca.score,\n", + " ]\n", + " + [\n", + " (\n", + " lca.characterization_matrix\n", + " * lca.biosphere_matrix\n", + " * lca.demand_array\n", + " ).sum()\n", + " ]\n", + " + [get_value_for_cpc(lst, key) for _, key in sorted_keys]\n", + " )\n", + "\n", + " data.sort(key=lambda x: x[4], reverse=True)\n", + "\n", + " if mode == \"relative\":\n", + " for row in data:\n", + " for index, point in enumerate(row[5:]):\n", + " row[index + 5] = point / row[4]\n", + "\n", + " if output_format == \"list\":\n", + " return labels, data\n", + " elif output_format == \"pandas\":\n", + " return pd.DataFrame(data, columns=labels)\n", + " elif output_format == \"html\":\n", + " return tabulate.tabulate(\n", + " data,\n", + " [x[:str_length] for x in labels],\n", + " tablefmt=\"html\",\n", + " floatfmt=\".3f\",\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "\n", + "for act in activities_list:\n", + " if not isinstance(act, bd.backends.peewee.proxies.Activity):\n", + " raise ValueError(\"`activities` must be an iterable of `Activity` instances\")\n", + "\n", + "data=[]\n", + "\n", + "labels = [\n", + " \"activity\",\n", + " \"reference product\",\n", + " \"location\",\n", + " \"method name\",\n", + " \"method unit\",\n", + " \"total\",\n", + "] #+ [key for _, key in sorted_keys]\n", + "\n", + "# for act in activities_list:\n", + "# activities=[act for act in ei39SSP2 if act in activities_list]\n", + "# print(type(act.metadata))\n", + "# print(activities)\n", + "for act in activities:\n", + " lca = bc.LCA({act: 1}, lcia_method)\n", + " lca.lci()\n", + " lca.lcia()\n", + "\n", + " data.append(\n", + " [ #adapt this to access method better\n", + " act[\"name\"],\n", + " act.get(\"reference product\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " lcia_method,\n", + " lcia_method_unit,\n", + " lca.score,\n", + " ]\n", + " )\n", + "\n", + "data_premise=pd.DataFrame(data, columns=labels)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityreference productlocationmethod namemethod unittotal
0cement production, Portlandcement, PortlandCH(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.742421
1cement production, Portlandcement, PortlandUS(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.885515
2cement production, Portlandcement, PortlandIN(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.891756
3cement production, Portlandcement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.845772
4cement production, Portlandcement, PortlandBR(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.851799
5cement production, Portlandcement, PortlandZA(IPCC 2013, climate change, global warming pot...kg CO2-Eq1.000588
6cement production, Portlandcement, PortlandPE(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.895198
\n", + "
" + ], + "text/plain": [ + " activity reference product location \\\n", + "0 cement production, Portland cement, Portland CH \n", + "1 cement production, Portland cement, Portland US \n", + "2 cement production, Portland cement, Portland IN \n", + "3 cement production, Portland cement, Portland CA-QC \n", + "4 cement production, Portland cement, Portland BR \n", + "5 cement production, Portland cement, Portland ZA \n", + "6 cement production, Portland cement, Portland PE \n", + "\n", + " method name method unit total \n", + "0 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.742421 \n", + "1 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.885515 \n", + "2 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.891756 \n", + "3 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.845772 \n", + "4 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.851799 \n", + "5 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 1.000588 \n", + "6 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.895198 " + ] + }, + "execution_count": 113, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_premise" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [], + "source": [ + "activities_ecoinvent=sectors_dict_ecoinvent['Cement']['activities'][:]" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": {}, + "outputs": [], + "source": [ + "for act in activities_ecoinvent:\n", + " if not isinstance(act, bd.backends.peewee.proxies.Activity):\n", + " raise ValueError(\"`activities` must be an iterable of `Activity` instances\")\n", + "\n", + "data_ecoinvent=[]\n", + "\n", + "labels = [\n", + " \"activity\",\n", + " \"reference product\",\n", + " \"location\",\n", + " \"method name\",\n", + " \"method unit\",\n", + " \"total\",\n", + "] #+ [key for _, key in sorted_keys]\n", + "\n", + "activities_ecoinvent=[act for act in ei39SSP2 if act in activities_ecoinvent]\n", + "for act in activities_ecoinvent:\n", + " lca = bc.LCA({act: 1} , lcia_method)\n", + " lca.lci()\n", + " lca.lcia()\n", + "\n", + " data.append(\n", + " [ #adapt this to access method better\n", + " act[\"name\"],\n", + " act.get(\"reference product\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " lcia_method,\n", + " lcia_method_unit,\n", + " lca.score,\n", + " ]\n", + " )\n", + "\n", + "data_ecoinvent=pd.DataFrame(data, columns=labels)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityreference productlocationmethod namemethod unittotal
0cement production, Portlandcement, PortlandCH(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.742421
1cement production, Portlandcement, PortlandUS(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.885515
2cement production, Portlandcement, PortlandIN(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.891756
3cement production, Portlandcement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.845772
4cement production, Portlandcement, PortlandBR(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.851799
5cement production, Portlandcement, PortlandZA(IPCC 2013, climate change, global warming pot...kg CO2-Eq1.000588
6cement production, Portlandcement, PortlandPE(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.895198
7cement production, Portlandcement, PortlandCH(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.742421
8cement production, Portlandcement, PortlandUS(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.885515
9cement production, Portlandcement, PortlandBR(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.851799
10cement production, Portlandcement, PortlandIN(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.891756
11cement production, Portlandcement, PortlandZA(IPCC 2013, climate change, global warming pot...kg CO2-Eq1.000588
12cement production, Portlandcement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.845772
13cement production, Portlandcement, PortlandPE(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.895198
\n", + "
" + ], + "text/plain": [ + " activity reference product location \\\n", + "0 cement production, Portland cement, Portland CH \n", + "1 cement production, Portland cement, Portland US \n", + "2 cement production, Portland cement, Portland IN \n", + "3 cement production, Portland cement, Portland CA-QC \n", + "4 cement production, Portland cement, Portland BR \n", + "5 cement production, Portland cement, Portland ZA \n", + "6 cement production, Portland cement, Portland PE \n", + "7 cement production, Portland cement, Portland CH \n", + "8 cement production, Portland cement, Portland US \n", + "9 cement production, Portland cement, Portland BR \n", + "10 cement production, Portland cement, Portland IN \n", + "11 cement production, Portland cement, Portland ZA \n", + "12 cement production, Portland cement, Portland CA-QC \n", + "13 cement production, Portland cement, Portland PE \n", + "\n", + " method name method unit total \n", + "0 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.742421 \n", + "1 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.885515 \n", + "2 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.891756 \n", + "3 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.845772 \n", + "4 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.851799 \n", + "5 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 1.000588 \n", + "6 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.895198 \n", + "7 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.742421 \n", + "8 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.885515 \n", + "9 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.851799 \n", + "10 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.891756 \n", + "11 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 1.000588 \n", + "12 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.845772 \n", + "13 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.895198 " + ] + }, + "execution_count": 117, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_ecoinvent" + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityreference productlocationmethod namemethod unittotal
0cement production, Portlandcement, PortlandCH(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.742421
1cement production, Portlandcement, PortlandUS(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.885515
2cement production, Portlandcement, PortlandIN(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.891756
3cement production, Portlandcement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.845772
4cement production, Portlandcement, PortlandBR(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.851799
5cement production, Portlandcement, PortlandZA(IPCC 2013, climate change, global warming pot...kg CO2-Eq1.000588
6cement production, Portlandcement, PortlandPE(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.895198
7cement production, Portlandcement, PortlandCH(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.742421
8cement production, Portlandcement, PortlandUS(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.885515
9cement production, Portlandcement, PortlandBR(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.851799
10cement production, Portlandcement, PortlandIN(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.891756
11cement production, Portlandcement, PortlandZA(IPCC 2013, climate change, global warming pot...kg CO2-Eq1.000588
12cement production, Portlandcement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.845772
13cement production, Portlandcement, PortlandPE(IPCC 2013, climate change, global warming pot...kg CO2-Eq0.895198
\n", + "
" + ], + "text/plain": [ + " activity reference product location \\\n", + "0 cement production, Portland cement, Portland CH \n", + "1 cement production, Portland cement, Portland US \n", + "2 cement production, Portland cement, Portland IN \n", + "3 cement production, Portland cement, Portland CA-QC \n", + "4 cement production, Portland cement, Portland BR \n", + "5 cement production, Portland cement, Portland ZA \n", + "6 cement production, Portland cement, Portland PE \n", + "7 cement production, Portland cement, Portland CH \n", + "8 cement production, Portland cement, Portland US \n", + "9 cement production, Portland cement, Portland BR \n", + "10 cement production, Portland cement, Portland IN \n", + "11 cement production, Portland cement, Portland ZA \n", + "12 cement production, Portland cement, Portland CA-QC \n", + "13 cement production, Portland cement, Portland PE \n", + "\n", + " method name method unit total \n", + "0 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.742421 \n", + "1 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.885515 \n", + "2 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.891756 \n", + "3 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.845772 \n", + "4 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.851799 \n", + "5 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 1.000588 \n", + "6 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.895198 \n", + "7 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.742421 \n", + "8 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.885515 \n", + "9 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.851799 \n", + "10 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.891756 \n", + "11 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 1.000588 \n", + "12 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.845772 \n", + "13 (IPCC 2013, climate change, global warming pot... kg CO2-Eq 0.895198 " + ] + }, + "execution_count": 115, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_ecoinvent" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['activity', 'product', 'location', 'unit', 'total', 'direct emissions']" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "labels" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'objs' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[48], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m data \u001b[38;5;241m=\u001b[39m []\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m act, lst \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(activities, objs):\n\u001b[0;32m 3\u001b[0m lca\u001b[38;5;241m.\u001b[39mredo_lcia({act\u001b[38;5;241m.\u001b[39mid: \u001b[38;5;241m1\u001b[39m})\n\u001b[0;32m 4\u001b[0m data\u001b[38;5;241m.\u001b[39mappend(\n\u001b[0;32m 5\u001b[0m [\n\u001b[0;32m 6\u001b[0m act[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m\"\u001b[39m]\u001b[38;5;241m.\u001b[39mreplace(name_common, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m\"\u001b[39m),\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 19\u001b[0m \u001b[38;5;241m+\u001b[39m [get_value_for_cpc(lst, key) \u001b[38;5;28;01mfor\u001b[39;00m _, key \u001b[38;5;129;01min\u001b[39;00m sorted_keys]\n\u001b[0;32m 20\u001b[0m )\n", + "\u001b[1;31mNameError\u001b[0m: name 'objs' is not defined" + ] + } + ], + "source": [ + "data = []\n", + "for act, lst in zip(activities, objs):\n", + " lca.redo_lcia({act.id: 1})\n", + " data.append(\n", + " [\n", + " act[\"name\"].replace(name_common, \"\"),\n", + " act.get(\"reference product\", \"\").replace(product_common, \"\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " act.get(\"unit\", \"\"),\n", + " lca.score,\n", + " ]\n", + " + [\n", + " (\n", + " lca.characterization_matrix\n", + " * lca.biosphere_matrix\n", + " * lca.demand_array\n", + " ).sum()\n", + " ]\n", + " + [get_value_for_cpc(lst, key) for _, key in sorted_keys]\n", + " )\n", + "\n", + "data.sort(key=lambda x: x[4], reverse=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def sector_lca_scores(main_dict, method_dict):\n", + " '''\n", + " Generates the LCA scores for the sectors activities in the main dictionary \n", + " for the different methods in the method dictionary.\n", + "\n", + " It returns the main dictionary updated as scores dictionary which also holds the former information for each sector.\n", + " The LCA scores are stored by method name in the respective sector dictionary within the main dictionary.\n", + " '''\n", + "\n", + " # Initialize scores_dict as a copy of main_dict\n", + " scores_dict = main_dict.copy()\n", + "\n", + " #Loop through methods\n", + " for method_key in method_dict.keys():\n", + " method = method_dict[method_key]['method name']\n", + "\n", + " # Loop through each sector in main_dict\n", + " for sector in scores_dict.keys():\n", + " # Extract activities for the current sector\n", + " sector_activities = scores_dict[sector]['activities']\n", + "\n", + " if method not in scores_dict[sector]:\n", + " scores_dict[sector][method] = {}\n", + "\n", + " # Initialize a list to store activitiy scores by method\n", + " if 'lca_score' not in scores_dict[sector][method]:\n", + " scores_dict[sector][method]['lca_score'] = []\n", + " \n", + " for activity in sector_activities:\n", + " activity_LCA = bw.LCA({activity: 1}, method)\n", + " activity_LCA.lci()\n", + " activity_LCA.lcia()\n", + " score = activity_LCA.score\n", + "\n", + " # Create a tuple key with relevant information\n", + " activity_score= {\n", + " 'activity':activity,\n", + " 'score':score,\n", + " 'method':method\n", + " #'unit': activity.metadata\n", + " # activity[\"name\"],\n", + " # activity[\"unit\"],\n", + " # activity[\"location\"],\n", + " # activity.get(\"reference product\"),\n", + " }\n", + "\n", + " scores_dict[sector][method]['lca_score'].append(activity_score)\n", + " \n", + " #ecoinvent_scores[key] = score\n", + "\n", + " return scores_dict\n", + "\n", + " # Calculate LCA scores using the specified method\n", + " # lca_scores = compare_activities_multiple_methods(\n", + " # activities_list=sector_activities,\n", + " # methods=method_dict,\n", + " # identifier=sector,\n", + " # mode='absolute'\n", + " # )\n", + " \n", + " # # Apply the small_inputs_to_other_column function with the cutoff value\n", + " # lca_scores = small_inputs_to_other_column(lca_scores, cutoff=0.02)\n", + " \n", + " # # Save the LCA scores to the scores_dict\n", + " # scores_dict[sector]['lca_scores'] = lca_scores\n", + "\n", + " # return scores_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lca_premise_dict=sector_lca_scores(sectors_dict_premise, method_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lca_ecoinvent_dict=sector_lca_scores(sectors_dict_premise, method_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lca_premise_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lca_ecoinvent_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(lca_ecoinvent_dict['Cement']['IPCC 2013', 'climate change', 'global warming potential (GWP100)'][0][\"activitiy\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Iterating through the dictionary to access the scores\n", + "for key in lca_ecoinvent_dict.items():\n", + " \n", + " for method, method_data in lca_ecoinvent_dict.items():\n", + " # Check if the key is a tuple (representing a method)\n", + " if isinstance(method, tuple):\n", + " print(f\" Method: {method}\")\n", + " \n", + " for score_entry in method_data.get('lca_score', []):\n", + " activity = score_entry.get('activity')\n", + " score = score_entry.get('score')\n", + " print(f\" Activity: {activity}, Score: {score}\")\n", + "\n", + " #####################################################\n", + " ####################################################\n", + " #####################################################" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def _compute_relative_change(original, transformed):\n", + " if original == 0:\n", + " return float(\"inf\") if transformed != 0 else 0\n", + " return (transformed - original) / original\n", + "\n", + "\n", + "def _calc_relative_changes_new(ecoinvent_dict, premise_dict):\n", + " # Match activities_list and calculate relative changes\n", + " relative_changes = {}\n", + " relative_changes[\"method\"] = ecoinvent_scores[\"method\"]\n", + " print(relative_changes)\n", + "\n", + " # Iterating through the dictionary to access the scores\n", + " for key in ecoinvent_dict.items():\n", + " \n", + " for method, method_data in ecoinvent_dict.items():\n", + " # Check if the key is a tuple (representing a method)\n", + " if isinstance(method, tuple):\n", + " print(f\" Method: {method}\")\n", + " \n", + " for score_entry in method_data.get('lca_score', []):\n", + " activity = score_entry.get('activity')\n", + " score = score_entry.get('score')\n", + " print(f\" Activity: {activity}, Score: {score}\")\n", + "\n", + " #####################################################\n", + " ####################################################\n", + " #####################################################\n", + "\n", + " # Track additional keys in premise_scores\n", + " additional_premise_keys = []\n", + "\n", + " for key, original_score in ecoinvent_scores.items():\n", + " if (\n", + " key in premise_scores\n", + " ): # activities only in premise_scores are according to this logic neglected.\n", + " # Skip if original_score is a tuple due to information tuple key\n", + " if isinstance(original_score, tuple):\n", + " continue\n", + "\n", + " transformed_score = premise_scores[key]\n", + " relative_change = _compute_relative_change(\n", + " original_score, transformed_score\n", + " )\n", + " relative_changes[key] = relative_change\n", + "\n", + " # Identify additional keys in premise_scores\n", + " for key in premise_scores.keys():\n", + " if key not in ecoinvent_scores:\n", + " additional_premise_keys.append(key)\n", + "\n", + " # Print the dataframes_dict\n", + " for key, change in relative_changes.items():\n", + " print(f\"{key}: {change}\")\n", + "\n", + " if additional_premise_keys:\n", + " print(\n", + " \"Additional keys in premise_scores not found in ecoinvent_scores:\",\n", + " additional_premise_keys,\n", + " )\n", + "\n", + " return relative_changes\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lca_smpl #which database is used??" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "premise", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/dev/notebook tests/comparing dbs_v2.ipynb b/dev/notebook tests/comparing dbs_v2.ipynb new file mode 100644 index 0000000..f7b4611 --- /dev/null +++ b/dev/notebook tests/comparing dbs_v2.ipynb @@ -0,0 +1,2365 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "#from functions_v2 import*\n", + "from methods import MethodFinder\n", + "\n", + "import brightway2 as bw\n", + "import bw2data as bd\n", + "import bw2analyzer as ba\n", + "import bw2calc as bc\n", + "\n", + "#reduce?\n", + "import ast\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "\n", + "import dopo" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Biosphere database already present!!! No setup is needed\n" + ] + } + ], + "source": [ + "bd.projects.set_current(\"premise-validation-try1\")\n", + "bw.bw2setup()\n", + "\n", + "bio3=bw.Database('biosphere3')\n", + "ei39=bw.Database('ecoinvent 3.9.1 cutoff')\n", + "ei39SSP2=bw.Database('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "activities_premise=[x for x in ei39SSP2 if 'cement' in x['name'] and 'Portland' in x['reference product'] and 'market' not in x['name']]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27',\n", + " '86841f8c7ee2668f244d3b8e34f41932')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[x for x in activities_premise if 'ZA' in x['location']][0].key" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27',\n", + " '86841f8c7ee2668f244d3b8e34f41932')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "activities_premise[0].key #checking database" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "activities_eco=[x for x in ei39 if 'cement' in x['name'] and 'Portland' in x['reference product'] and 'market' not in x['name']]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('ecoinvent 3.9.1 cutoff', '86841f8c7ee2668f244d3b8e34f41932')" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[x for x in activities_eco if 'ZA' in x['location']][0].key" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('ecoinvent 3.9.1 cutoff', 'a3c2064d83411f7963af550c04c869a1')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "activities_eco[0].key #checking database" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'method_1': {'object': Brightway2 Method: IPCC 2013: climate change: global warming potential (GWP100),\n", + " 'method name': ('IPCC 2013',\n", + " 'climate change',\n", + " 'global warming potential (GWP100)'),\n", + " 'short name': 'global warming potential (GWP100)',\n", + " 'unit': 'kg CO2-Eq'},\n", + " 'method_2': {'object': Brightway2 Method: EN15804: inventory indicators ISO21930: Cumulative Energy Demand - non-renewable energy resources,\n", + " 'method name': ('EN15804',\n", + " 'inventory indicators ISO21930',\n", + " 'Cumulative Energy Demand - non-renewable energy resources'),\n", + " 'short name': 'Cumulative Energy Demand - non-renewable energy resources',\n", + " 'unit': 'megajoule'},\n", + " 'method_3': {'object': Brightway2 Method: selected LCI results: resource: land occupation,\n", + " 'method name': ('selected LCI results', 'resource', 'land occupation'),\n", + " 'short name': 'land occupation',\n", + " 'unit': 'square meter-year'},\n", + " 'method_4': {'object': Brightway2 Method: EN15804: inventory indicators ISO21930: use of net fresh water,\n", + " 'method name': ('EN15804',\n", + " 'inventory indicators ISO21930',\n", + " 'use of net fresh water'),\n", + " 'short name': 'use of net fresh water',\n", + " 'unit': 'cubic meter'}}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Get Methods\n", + "finder=MethodFinder()\n", + "\n", + "finder.find_and_create_method(criteria=['IPCC', '2013', 'GWP100'], exclude=['no LT'])\n", + "finder.find_and_create_method(criteria=['EN15804','Cumulative', 'non-renewable' ])\n", + "finder.find_and_create_method(criteria=['land occupation','selected'])\n", + "finder.find_and_create_method(criteria=['EN15804','fresh water'])\n", + "\n", + "method_dict=finder.get_all_methods()\n", + "method_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "methods=[method_dict['method_1']['method name'], method_dict['method_2']['method name']]" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "for act in activities_premise:\n", + " if not isinstance(act, bd.backends.peewee.proxies.Activity):\n", + " raise ValueError(\"`activities` must be an iterable of `Activity` instances\")\n", + "\n", + "data_premise=[]\n", + "\n", + "labels_premise = [\n", + " \"activity\",\n", + " \"activity key\",\n", + " \"reference product\",\n", + " \"location\",\n", + " \"method name\",\n", + " #\"method unit\",\n", + " \"total\",\n", + "] #+ [key for _, key in sorted_keys]\n", + "\n", + "# for act in activities_list:\n", + "# activities=[act for act in ei39SSP2 if act in activities_list]\n", + "# print(type(act.metadata))\n", + "# print(activities)\n", + "for meth in methods:\n", + " for act in activities_premise:\n", + "\n", + " lca=bw.LCA({act: 1}, meth)\n", + " lca.lci()\n", + " lca.lcia()\n", + " # for category in methods:\n", + " # lca.switch_method(category)\n", + " # lca.lcia()\n", + "\n", + " data_premise.append(\n", + " [ #adapt this to access method better\n", + " act[\"name\"],\n", + " act.key,\n", + " act.get(\"reference product\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " meth,\n", + " #methods_unit,\n", + " lca.score,\n", + " ]\n", + " )\n", + "\n", + "data_premise=pd.DataFrame(data_premise, columns=labels_premise)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityactivity keyreference productlocationmethod nametotal
0cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandZA(IPCC 2013, climate change, global warming pot...0.814768
1cement production, Pozzolana Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, Pozzolana PortlandIN(IPCC 2013, climate change, global warming pot...0.536510
2cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandRoW(IPCC 2013, climate change, global warming pot...0.827825
3cement production, Portland Slag(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, Portland SlagIN(IPCC 2013, climate change, global warming pot...0.471488
4cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandEurope without Switzerlan(IPCC 2013, climate change, global warming pot...0.815993
5cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandUS(IPCC 2013, climate change, global warming pot...0.821278
6cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandPE(IPCC 2013, climate change, global warming pot...0.869433
7cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandCH(IPCC 2013, climate change, global warming pot...0.737734
8cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandIN(IPCC 2013, climate change, global warming pot...0.774079
9cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandBR(IPCC 2013, climate change, global warming pot...0.832335
10cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...0.841855
11cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandZA(EN15804, inventory indicators ISO21930, Cumul...4.025051
12cement production, Pozzolana Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, Pozzolana PortlandIN(EN15804, inventory indicators ISO21930, Cumul...2.503883
13cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandRoW(EN15804, inventory indicators ISO21930, Cumul...3.275430
14cement production, Portland Slag(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, Portland SlagIN(EN15804, inventory indicators ISO21930, Cumul...2.579477
15cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandEurope without Switzerlan(EN15804, inventory indicators ISO21930, Cumul...2.659676
16cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandUS(EN15804, inventory indicators ISO21930, Cumul...2.672874
17cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandPE(EN15804, inventory indicators ISO21930, Cumul...3.836765
18cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandCH(EN15804, inventory indicators ISO21930, Cumul...2.020362
19cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandIN(EN15804, inventory indicators ISO21930, Cumul...3.581588
20cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandBR(EN15804, inventory indicators ISO21930, Cumul...3.419633
21cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandCA-QC(EN15804, inventory indicators ISO21930, Cumul...3.997485
\n", + "
" + ], + "text/plain": [ + " activity \\\n", + "0 cement production, Portland \n", + "1 cement production, Pozzolana Portland \n", + "2 cement production, Portland \n", + "3 cement production, Portland Slag \n", + "4 cement production, Portland \n", + "5 cement production, Portland \n", + "6 cement production, Portland \n", + "7 cement production, Portland \n", + "8 cement production, Portland \n", + "9 cement production, Portland \n", + "10 cement production, Portland \n", + "11 cement production, Portland \n", + "12 cement production, Pozzolana Portland \n", + "13 cement production, Portland \n", + "14 cement production, Portland Slag \n", + "15 cement production, Portland \n", + "16 cement production, Portland \n", + "17 cement production, Portland \n", + "18 cement production, Portland \n", + "19 cement production, Portland \n", + "20 cement production, Portland \n", + "21 cement production, Portland \n", + "\n", + " activity key \\\n", + "0 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "1 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "2 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "3 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "4 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "5 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "6 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "7 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "8 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "9 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "10 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "11 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "12 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "13 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "14 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "15 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "16 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "17 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "18 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "19 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "20 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "21 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "\n", + " reference product location \\\n", + "0 cement, Portland ZA \n", + "1 cement, Pozzolana Portland IN \n", + "2 cement, Portland RoW \n", + "3 cement, Portland Slag IN \n", + "4 cement, Portland Europe without Switzerlan \n", + "5 cement, Portland US \n", + "6 cement, Portland PE \n", + "7 cement, Portland CH \n", + "8 cement, Portland IN \n", + "9 cement, Portland BR \n", + "10 cement, Portland CA-QC \n", + "11 cement, Portland ZA \n", + "12 cement, Pozzolana Portland IN \n", + "13 cement, Portland RoW \n", + "14 cement, Portland Slag IN \n", + "15 cement, Portland Europe without Switzerlan \n", + "16 cement, Portland US \n", + "17 cement, Portland PE \n", + "18 cement, Portland CH \n", + "19 cement, Portland IN \n", + "20 cement, Portland BR \n", + "21 cement, Portland CA-QC \n", + "\n", + " method name total \n", + "0 (IPCC 2013, climate change, global warming pot... 0.814768 \n", + "1 (IPCC 2013, climate change, global warming pot... 0.536510 \n", + "2 (IPCC 2013, climate change, global warming pot... 0.827825 \n", + "3 (IPCC 2013, climate change, global warming pot... 0.471488 \n", + "4 (IPCC 2013, climate change, global warming pot... 0.815993 \n", + "5 (IPCC 2013, climate change, global warming pot... 0.821278 \n", + "6 (IPCC 2013, climate change, global warming pot... 0.869433 \n", + "7 (IPCC 2013, climate change, global warming pot... 0.737734 \n", + "8 (IPCC 2013, climate change, global warming pot... 0.774079 \n", + "9 (IPCC 2013, climate change, global warming pot... 0.832335 \n", + "10 (IPCC 2013, climate change, global warming pot... 0.841855 \n", + "11 (EN15804, inventory indicators ISO21930, Cumul... 4.025051 \n", + "12 (EN15804, inventory indicators ISO21930, Cumul... 2.503883 \n", + "13 (EN15804, inventory indicators ISO21930, Cumul... 3.275430 \n", + "14 (EN15804, inventory indicators ISO21930, Cumul... 2.579477 \n", + "15 (EN15804, inventory indicators ISO21930, Cumul... 2.659676 \n", + "16 (EN15804, inventory indicators ISO21930, Cumul... 2.672874 \n", + "17 (EN15804, inventory indicators ISO21930, Cumul... 3.836765 \n", + "18 (EN15804, inventory indicators ISO21930, Cumul... 2.020362 \n", + "19 (EN15804, inventory indicators ISO21930, Cumul... 3.581588 \n", + "20 (EN15804, inventory indicators ISO21930, Cumul... 3.419633 \n", + "21 (EN15804, inventory indicators ISO21930, Cumul... 3.997485 " + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_premise" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "def lca_scores_database()\n", + "# Assuming `activities_eco` and `methods` are predefined\n", + "dataframes = {}\n", + "\n", + "# Labels for the DataFrame columns\n", + "labels_eco = [\n", + " \"activity\",\n", + " \"activity key\",\n", + " \"reference product\",\n", + " \"location\",\n", + " \"method name\",\n", + " \"total\",\n", + "]\n", + "\n", + "# Loop through each method\n", + "for meth in methods:\n", + " data_eco = [] # Initialize a new list to hold data for the current method\n", + " \n", + " for act in activities_eco:\n", + " # Ensure the activity is an instance of the expected class\n", + " if not isinstance(act, bd.backends.peewee.proxies.Activity):\n", + " raise ValueError(\"`activities` must be an iterable of `Activity` instances\")\n", + " \n", + " # Perform LCA calculations\n", + " lca = bw.LCA({act: 1}, meth)\n", + " lca.lci()\n", + " lca.lcia()\n", + " \n", + " # Collect data for the current activity and method\n", + " data_eco.append([\n", + " act[\"name\"],\n", + " act.key,\n", + " act.get(\"reference product\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " meth,\n", + " lca.score,\n", + " ])\n", + " \n", + " # Convert the data list to a DataFrame\n", + " dataframes[meth] = pd.DataFrame(data_eco, columns=labels_eco)\n", + "\n", + "# Now `dataframes` is a dictionary where each key is a method name and the value is the corresponding DataFrame\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityactivity keyreference productlocationmethod nametotal
0cement production, Portland(ecoinvent 3.9.1 cutoff, a3c2064d83411f7963af5...cement, PortlandBR(IPCC 2013, climate change, global warming pot...0.851799
1cement production, Portland(ecoinvent 3.9.1 cutoff, 7011b57a11b423e3a22f6...cement, PortlandRoW(IPCC 2013, climate change, global warming pot...0.915427
2cement production, Pozzolana Portland(ecoinvent 3.9.1 cutoff, dddde503e9bdf9cc25e2e...cement, Pozzolana PortlandIN(IPCC 2013, climate change, global warming pot...0.622312
3cement production, Portland(ecoinvent 3.9.1 cutoff, f8b84f45f50d3bd7ff4fe...cement, PortlandIN(IPCC 2013, climate change, global warming pot...0.891756
4cement production, Portland Slag(ecoinvent 3.9.1 cutoff, 461fa0b26e14337c73141...cement, Portland SlagIN(IPCC 2013, climate change, global warming pot...0.587870
5cement production, Portland(ecoinvent 3.9.1 cutoff, 86841f8c7ee2668f244d3...cement, PortlandZA(IPCC 2013, climate change, global warming pot...1.000588
6cement production, Portland(ecoinvent 3.9.1 cutoff, fcb666edf2a01467e555e...cement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...0.845772
7cement production, Portland(ecoinvent 3.9.1 cutoff, 36a53c174f34e672bc15b...cement, PortlandUS(IPCC 2013, climate change, global warming pot...0.885515
8cement production, Portland(ecoinvent 3.9.1 cutoff, df49e8f525497f2fbd56b...cement, PortlandCH(IPCC 2013, climate change, global warming pot...0.742421
9cement production, Portland(ecoinvent 3.9.1 cutoff, 19afbe085b8798fd8c85e...cement, PortlandEurope without Switzerlan(IPCC 2013, climate change, global warming pot...0.858576
10cement production, Portland(ecoinvent 3.9.1 cutoff, 3c16b45db40210cd97de6...cement, PortlandPE(IPCC 2013, climate change, global warming pot...0.895198
\n", + "
" + ], + "text/plain": [ + " activity \\\n", + "0 cement production, Portland \n", + "1 cement production, Portland \n", + "2 cement production, Pozzolana Portland \n", + "3 cement production, Portland \n", + "4 cement production, Portland Slag \n", + "5 cement production, Portland \n", + "6 cement production, Portland \n", + "7 cement production, Portland \n", + "8 cement production, Portland \n", + "9 cement production, Portland \n", + "10 cement production, Portland \n", + "\n", + " activity key \\\n", + "0 (ecoinvent 3.9.1 cutoff, a3c2064d83411f7963af5... \n", + "1 (ecoinvent 3.9.1 cutoff, 7011b57a11b423e3a22f6... \n", + "2 (ecoinvent 3.9.1 cutoff, dddde503e9bdf9cc25e2e... \n", + "3 (ecoinvent 3.9.1 cutoff, f8b84f45f50d3bd7ff4fe... \n", + "4 (ecoinvent 3.9.1 cutoff, 461fa0b26e14337c73141... \n", + "5 (ecoinvent 3.9.1 cutoff, 86841f8c7ee2668f244d3... \n", + "6 (ecoinvent 3.9.1 cutoff, fcb666edf2a01467e555e... \n", + "7 (ecoinvent 3.9.1 cutoff, 36a53c174f34e672bc15b... \n", + "8 (ecoinvent 3.9.1 cutoff, df49e8f525497f2fbd56b... \n", + "9 (ecoinvent 3.9.1 cutoff, 19afbe085b8798fd8c85e... \n", + "10 (ecoinvent 3.9.1 cutoff, 3c16b45db40210cd97de6... \n", + "\n", + " reference product location \\\n", + "0 cement, Portland BR \n", + "1 cement, Portland RoW \n", + "2 cement, Pozzolana Portland IN \n", + "3 cement, Portland IN \n", + "4 cement, Portland Slag IN \n", + "5 cement, Portland ZA \n", + "6 cement, Portland CA-QC \n", + "7 cement, Portland US \n", + "8 cement, Portland CH \n", + "9 cement, Portland Europe without Switzerlan \n", + "10 cement, Portland PE \n", + "\n", + " method name total \n", + "0 (IPCC 2013, climate change, global warming pot... 0.851799 \n", + "1 (IPCC 2013, climate change, global warming pot... 0.915427 \n", + "2 (IPCC 2013, climate change, global warming pot... 0.622312 \n", + "3 (IPCC 2013, climate change, global warming pot... 0.891756 \n", + "4 (IPCC 2013, climate change, global warming pot... 0.587870 \n", + "5 (IPCC 2013, climate change, global warming pot... 1.000588 \n", + "6 (IPCC 2013, climate change, global warming pot... 0.845772 \n", + "7 (IPCC 2013, climate change, global warming pot... 0.885515 \n", + "8 (IPCC 2013, climate change, global warming pot... 0.742421 \n", + "9 (IPCC 2013, climate change, global warming pot... 0.858576 \n", + "10 (IPCC 2013, climate change, global warming pot... 0.895198 " + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "first_method = list(dataframes.keys())[0] # Get the first key in the dictionary as a list\n", + "first_dataframe = dataframes[first_method] # Access the corresponding DataFrame\n", + "first_dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "for act in activities_eco:\n", + " if not isinstance(act, bd.backends.peewee.proxies.Activity):\n", + " raise ValueError(\"`activities` must be an iterable of `Activity` instances\")\n", + "\n", + "data_eco=[]\n", + "\n", + "labels_eco = [\n", + " \"activity\",\n", + " \"activity key\",\n", + " \"reference product\",\n", + " \"location\",\n", + " \"method name\",\n", + " #\"method unit\",\n", + " \"total\",\n", + "] #+ [key for _, key in sorted_keys]\n", + "\n", + "# for act in activities_list:\n", + "# activities=[act for act in ei39SSP2 if act in activities_list]\n", + "# print(type(act.metadata))\n", + "# print(activities)\n", + "for meth in methods:\n", + " for act in activities_eco:\n", + " lca=bw.LCA({act: 1}, meth)\n", + " lca.lci()\n", + " lca.lcia()\n", + " for category in methods:\n", + " lca.switch_method(category)\n", + " lca.lcia()\n", + "\n", + " data_eco.append(\n", + " [ #adapt this to access method better\n", + " act[\"name\"],\n", + " act.key,\n", + " act.get(\"reference product\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " meth,\n", + " #methods_unit,\n", + " lca.score,\n", + " ]\n", + " )\n", + "\n", + "data_eco=pd.DataFrame(data_eco, columns=labels_eco)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityactivity keyreference productlocationmethod nametotal
0cement production, Portland(ecoinvent 3.9.1 cutoff, a3c2064d83411f7963af5...cement, PortlandBR(IPCC 2013, climate change, global warming pot...3.606791
1cement production, Portland(ecoinvent 3.9.1 cutoff, 7011b57a11b423e3a22f6...cement, PortlandRoW(IPCC 2013, climate change, global warming pot...3.944373
2cement production, Pozzolana Portland(ecoinvent 3.9.1 cutoff, dddde503e9bdf9cc25e2e...cement, Pozzolana PortlandIN(IPCC 2013, climate change, global warming pot...3.381439
3cement production, Portland(ecoinvent 3.9.1 cutoff, f8b84f45f50d3bd7ff4fe...cement, PortlandIN(IPCC 2013, climate change, global warming pot...4.785940
4cement production, Portland Slag(ecoinvent 3.9.1 cutoff, 461fa0b26e14337c73141...cement, Portland SlagIN(IPCC 2013, climate change, global warming pot...3.766643
5cement production, Portland(ecoinvent 3.9.1 cutoff, 86841f8c7ee2668f244d3...cement, PortlandZA(IPCC 2013, climate change, global warming pot...6.186147
6cement production, Portland(ecoinvent 3.9.1 cutoff, fcb666edf2a01467e555e...cement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...3.992977
7cement production, Portland(ecoinvent 3.9.1 cutoff, 36a53c174f34e672bc15b...cement, PortlandUS(IPCC 2013, climate change, global warming pot...3.599198
8cement production, Portland(ecoinvent 3.9.1 cutoff, df49e8f525497f2fbd56b...cement, PortlandCH(IPCC 2013, climate change, global warming pot...2.164824
9cement production, Portland(ecoinvent 3.9.1 cutoff, 19afbe085b8798fd8c85e...cement, PortlandEurope without Switzerlan(IPCC 2013, climate change, global warming pot...3.350089
10cement production, Portland(ecoinvent 3.9.1 cutoff, 3c16b45db40210cd97de6...cement, PortlandPE(IPCC 2013, climate change, global warming pot...4.172537
11cement production, Portland(ecoinvent 3.9.1 cutoff, a3c2064d83411f7963af5...cement, PortlandBR(EN15804, inventory indicators ISO21930, Cumul...3.606791
12cement production, Portland(ecoinvent 3.9.1 cutoff, 7011b57a11b423e3a22f6...cement, PortlandRoW(EN15804, inventory indicators ISO21930, Cumul...3.944373
13cement production, Pozzolana Portland(ecoinvent 3.9.1 cutoff, dddde503e9bdf9cc25e2e...cement, Pozzolana PortlandIN(EN15804, inventory indicators ISO21930, Cumul...3.381439
14cement production, Portland(ecoinvent 3.9.1 cutoff, f8b84f45f50d3bd7ff4fe...cement, PortlandIN(EN15804, inventory indicators ISO21930, Cumul...4.785940
15cement production, Portland Slag(ecoinvent 3.9.1 cutoff, 461fa0b26e14337c73141...cement, Portland SlagIN(EN15804, inventory indicators ISO21930, Cumul...3.766643
16cement production, Portland(ecoinvent 3.9.1 cutoff, 86841f8c7ee2668f244d3...cement, PortlandZA(EN15804, inventory indicators ISO21930, Cumul...6.186147
17cement production, Portland(ecoinvent 3.9.1 cutoff, fcb666edf2a01467e555e...cement, PortlandCA-QC(EN15804, inventory indicators ISO21930, Cumul...3.992977
18cement production, Portland(ecoinvent 3.9.1 cutoff, 36a53c174f34e672bc15b...cement, PortlandUS(EN15804, inventory indicators ISO21930, Cumul...3.599198
19cement production, Portland(ecoinvent 3.9.1 cutoff, df49e8f525497f2fbd56b...cement, PortlandCH(EN15804, inventory indicators ISO21930, Cumul...2.164824
20cement production, Portland(ecoinvent 3.9.1 cutoff, 19afbe085b8798fd8c85e...cement, PortlandEurope without Switzerlan(EN15804, inventory indicators ISO21930, Cumul...3.350089
21cement production, Portland(ecoinvent 3.9.1 cutoff, 3c16b45db40210cd97de6...cement, PortlandPE(EN15804, inventory indicators ISO21930, Cumul...4.172537
\n", + "
" + ], + "text/plain": [ + " activity \\\n", + "0 cement production, Portland \n", + "1 cement production, Portland \n", + "2 cement production, Pozzolana Portland \n", + "3 cement production, Portland \n", + "4 cement production, Portland Slag \n", + "5 cement production, Portland \n", + "6 cement production, Portland \n", + "7 cement production, Portland \n", + "8 cement production, Portland \n", + "9 cement production, Portland \n", + "10 cement production, Portland \n", + "11 cement production, Portland \n", + "12 cement production, Portland \n", + "13 cement production, Pozzolana Portland \n", + "14 cement production, Portland \n", + "15 cement production, Portland Slag \n", + "16 cement production, Portland \n", + "17 cement production, Portland \n", + "18 cement production, Portland \n", + "19 cement production, Portland \n", + "20 cement production, Portland \n", + "21 cement production, Portland \n", + "\n", + " activity key \\\n", + "0 (ecoinvent 3.9.1 cutoff, a3c2064d83411f7963af5... \n", + "1 (ecoinvent 3.9.1 cutoff, 7011b57a11b423e3a22f6... \n", + "2 (ecoinvent 3.9.1 cutoff, dddde503e9bdf9cc25e2e... \n", + "3 (ecoinvent 3.9.1 cutoff, f8b84f45f50d3bd7ff4fe... \n", + "4 (ecoinvent 3.9.1 cutoff, 461fa0b26e14337c73141... \n", + "5 (ecoinvent 3.9.1 cutoff, 86841f8c7ee2668f244d3... \n", + "6 (ecoinvent 3.9.1 cutoff, fcb666edf2a01467e555e... \n", + "7 (ecoinvent 3.9.1 cutoff, 36a53c174f34e672bc15b... \n", + "8 (ecoinvent 3.9.1 cutoff, df49e8f525497f2fbd56b... \n", + "9 (ecoinvent 3.9.1 cutoff, 19afbe085b8798fd8c85e... \n", + "10 (ecoinvent 3.9.1 cutoff, 3c16b45db40210cd97de6... \n", + "11 (ecoinvent 3.9.1 cutoff, a3c2064d83411f7963af5... \n", + "12 (ecoinvent 3.9.1 cutoff, 7011b57a11b423e3a22f6... \n", + "13 (ecoinvent 3.9.1 cutoff, dddde503e9bdf9cc25e2e... \n", + "14 (ecoinvent 3.9.1 cutoff, f8b84f45f50d3bd7ff4fe... \n", + "15 (ecoinvent 3.9.1 cutoff, 461fa0b26e14337c73141... \n", + "16 (ecoinvent 3.9.1 cutoff, 86841f8c7ee2668f244d3... \n", + "17 (ecoinvent 3.9.1 cutoff, fcb666edf2a01467e555e... \n", + "18 (ecoinvent 3.9.1 cutoff, 36a53c174f34e672bc15b... \n", + "19 (ecoinvent 3.9.1 cutoff, df49e8f525497f2fbd56b... \n", + "20 (ecoinvent 3.9.1 cutoff, 19afbe085b8798fd8c85e... \n", + "21 (ecoinvent 3.9.1 cutoff, 3c16b45db40210cd97de6... \n", + "\n", + " reference product location \\\n", + "0 cement, Portland BR \n", + "1 cement, Portland RoW \n", + "2 cement, Pozzolana Portland IN \n", + "3 cement, Portland IN \n", + "4 cement, Portland Slag IN \n", + "5 cement, Portland ZA \n", + "6 cement, Portland CA-QC \n", + "7 cement, Portland US \n", + "8 cement, Portland CH \n", + "9 cement, Portland Europe without Switzerlan \n", + "10 cement, Portland PE \n", + "11 cement, Portland BR \n", + "12 cement, Portland RoW \n", + "13 cement, Pozzolana Portland IN \n", + "14 cement, Portland IN \n", + "15 cement, Portland Slag IN \n", + "16 cement, Portland ZA \n", + "17 cement, Portland CA-QC \n", + "18 cement, Portland US \n", + "19 cement, Portland CH \n", + "20 cement, Portland Europe without Switzerlan \n", + "21 cement, Portland PE \n", + "\n", + " method name total \n", + "0 (IPCC 2013, climate change, global warming pot... 3.606791 \n", + "1 (IPCC 2013, climate change, global warming pot... 3.944373 \n", + "2 (IPCC 2013, climate change, global warming pot... 3.381439 \n", + "3 (IPCC 2013, climate change, global warming pot... 4.785940 \n", + "4 (IPCC 2013, climate change, global warming pot... 3.766643 \n", + "5 (IPCC 2013, climate change, global warming pot... 6.186147 \n", + "6 (IPCC 2013, climate change, global warming pot... 3.992977 \n", + "7 (IPCC 2013, climate change, global warming pot... 3.599198 \n", + "8 (IPCC 2013, climate change, global warming pot... 2.164824 \n", + "9 (IPCC 2013, climate change, global warming pot... 3.350089 \n", + "10 (IPCC 2013, climate change, global warming pot... 4.172537 \n", + "11 (EN15804, inventory indicators ISO21930, Cumul... 3.606791 \n", + "12 (EN15804, inventory indicators ISO21930, Cumul... 3.944373 \n", + "13 (EN15804, inventory indicators ISO21930, Cumul... 3.381439 \n", + "14 (EN15804, inventory indicators ISO21930, Cumul... 4.785940 \n", + "15 (EN15804, inventory indicators ISO21930, Cumul... 3.766643 \n", + "16 (EN15804, inventory indicators ISO21930, Cumul... 6.186147 \n", + "17 (EN15804, inventory indicators ISO21930, Cumul... 3.992977 \n", + "18 (EN15804, inventory indicators ISO21930, Cumul... 3.599198 \n", + "19 (EN15804, inventory indicators ISO21930, Cumul... 2.164824 \n", + "20 (EN15804, inventory indicators ISO21930, Cumul... 3.350089 \n", + "21 (EN15804, inventory indicators ISO21930, Cumul... 4.172537 " + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_eco" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
method nameactivity_codetotal_eitotal_premiserelative_change
0(IPCC 2013, climate change, global warming pot...a3c2064d83411f7963af550c04c869a13.6067910.832335-76.923124
1(IPCC 2013, climate change, global warming pot...7011b57a11b423e3a22f6ba29e6bb1db3.9443730.827825-79.012507
2(IPCC 2013, climate change, global warming pot...dddde503e9bdf9cc25e2e54c2d3cbac53.3814390.536510-84.133681
3(IPCC 2013, climate change, global warming pot...f8b84f45f50d3bd7ff4feaabdb493f6a4.7859400.774079-83.825989
4(IPCC 2013, climate change, global warming pot...461fa0b26e14337c731415cbb2df33003.7666430.471488-87.482550
5(IPCC 2013, climate change, global warming pot...86841f8c7ee2668f244d3b8e34f419326.1861470.814768-86.829161
6(IPCC 2013, climate change, global warming pot...fcb666edf2a01467e555eeff5b4a5bbb3.9929770.841855-78.916614
7(IPCC 2013, climate change, global warming pot...36a53c174f34e672bc15b7e55563685e3.5991980.821278-77.181634
8(IPCC 2013, climate change, global warming pot...df49e8f525497f2fbd56bcdc80ff0cde2.1648240.737734-65.921770
9(IPCC 2013, climate change, global warming pot...19afbe085b8798fd8c85eb1d14ebcca63.3500890.815993-75.642651
10(IPCC 2013, climate change, global warming pot...3c16b45db40210cd97de6574b2f47aaf4.1725370.869433-79.162956
11(EN15804, inventory indicators ISO21930, Cumul...a3c2064d83411f7963af550c04c869a13.6067913.419633-5.189055
12(EN15804, inventory indicators ISO21930, Cumul...7011b57a11b423e3a22f6ba29e6bb1db3.9443733.275430-16.959422
13(EN15804, inventory indicators ISO21930, Cumul...dddde503e9bdf9cc25e2e54c2d3cbac53.3814392.503883-25.952148
14(EN15804, inventory indicators ISO21930, Cumul...f8b84f45f50d3bd7ff4feaabdb493f6a4.7859403.581588-25.164373
15(EN15804, inventory indicators ISO21930, Cumul...461fa0b26e14337c731415cbb2df33003.7666432.579477-31.517898
16(EN15804, inventory indicators ISO21930, Cumul...86841f8c7ee2668f244d3b8e34f419326.1861474.025051-34.934447
17(EN15804, inventory indicators ISO21930, Cumul...fcb666edf2a01467e555eeff5b4a5bbb3.9929773.9974850.112896
18(EN15804, inventory indicators ISO21930, Cumul...36a53c174f34e672bc15b7e55563685e3.5991982.672874-25.736953
19(EN15804, inventory indicators ISO21930, Cumul...df49e8f525497f2fbd56bcdc80ff0cde2.1648242.020362-6.673179
20(EN15804, inventory indicators ISO21930, Cumul...19afbe085b8798fd8c85eb1d14ebcca63.3500892.659676-20.608781
21(EN15804, inventory indicators ISO21930, Cumul...3c16b45db40210cd97de6574b2f47aaf4.1725373.836765-8.047184
\n", + "
" + ], + "text/plain": [ + " method name \\\n", + "0 (IPCC 2013, climate change, global warming pot... \n", + "1 (IPCC 2013, climate change, global warming pot... \n", + "2 (IPCC 2013, climate change, global warming pot... \n", + "3 (IPCC 2013, climate change, global warming pot... \n", + "4 (IPCC 2013, climate change, global warming pot... \n", + "5 (IPCC 2013, climate change, global warming pot... \n", + "6 (IPCC 2013, climate change, global warming pot... \n", + "7 (IPCC 2013, climate change, global warming pot... \n", + "8 (IPCC 2013, climate change, global warming pot... \n", + "9 (IPCC 2013, climate change, global warming pot... \n", + "10 (IPCC 2013, climate change, global warming pot... \n", + "11 (EN15804, inventory indicators ISO21930, Cumul... \n", + "12 (EN15804, inventory indicators ISO21930, Cumul... \n", + "13 (EN15804, inventory indicators ISO21930, Cumul... \n", + "14 (EN15804, inventory indicators ISO21930, Cumul... \n", + "15 (EN15804, inventory indicators ISO21930, Cumul... \n", + "16 (EN15804, inventory indicators ISO21930, Cumul... \n", + "17 (EN15804, inventory indicators ISO21930, Cumul... \n", + "18 (EN15804, inventory indicators ISO21930, Cumul... \n", + "19 (EN15804, inventory indicators ISO21930, Cumul... \n", + "20 (EN15804, inventory indicators ISO21930, Cumul... \n", + "21 (EN15804, inventory indicators ISO21930, Cumul... \n", + "\n", + " activity_code total_ei total_premise relative_change \n", + "0 a3c2064d83411f7963af550c04c869a1 3.606791 0.832335 -76.923124 \n", + "1 7011b57a11b423e3a22f6ba29e6bb1db 3.944373 0.827825 -79.012507 \n", + "2 dddde503e9bdf9cc25e2e54c2d3cbac5 3.381439 0.536510 -84.133681 \n", + "3 f8b84f45f50d3bd7ff4feaabdb493f6a 4.785940 0.774079 -83.825989 \n", + "4 461fa0b26e14337c731415cbb2df3300 3.766643 0.471488 -87.482550 \n", + "5 86841f8c7ee2668f244d3b8e34f41932 6.186147 0.814768 -86.829161 \n", + "6 fcb666edf2a01467e555eeff5b4a5bbb 3.992977 0.841855 -78.916614 \n", + "7 36a53c174f34e672bc15b7e55563685e 3.599198 0.821278 -77.181634 \n", + "8 df49e8f525497f2fbd56bcdc80ff0cde 2.164824 0.737734 -65.921770 \n", + "9 19afbe085b8798fd8c85eb1d14ebcca6 3.350089 0.815993 -75.642651 \n", + "10 3c16b45db40210cd97de6574b2f47aaf 4.172537 0.869433 -79.162956 \n", + "11 a3c2064d83411f7963af550c04c869a1 3.606791 3.419633 -5.189055 \n", + "12 7011b57a11b423e3a22f6ba29e6bb1db 3.944373 3.275430 -16.959422 \n", + "13 dddde503e9bdf9cc25e2e54c2d3cbac5 3.381439 2.503883 -25.952148 \n", + "14 f8b84f45f50d3bd7ff4feaabdb493f6a 4.785940 3.581588 -25.164373 \n", + "15 461fa0b26e14337c731415cbb2df3300 3.766643 2.579477 -31.517898 \n", + "16 86841f8c7ee2668f244d3b8e34f41932 6.186147 4.025051 -34.934447 \n", + "17 fcb666edf2a01467e555eeff5b4a5bbb 3.992977 3.997485 0.112896 \n", + "18 36a53c174f34e672bc15b7e55563685e 3.599198 2.672874 -25.736953 \n", + "19 df49e8f525497f2fbd56bcdc80ff0cde 2.164824 2.020362 -6.673179 \n", + "20 19afbe085b8798fd8c85eb1d14ebcca6 3.350089 2.659676 -20.608781 \n", + "21 3c16b45db40210cd97de6574b2f47aaf 4.172537 3.836765 -8.047184 " + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "# Assuming df_ei and df_premise are your dataframes for ecoinvent and premise respectively\n", + "df_ei= data_eco\n", + "df_premise=data_premise\n", + "\n", + "# Split the 'activity key' to extract the second part\n", + "df_ei['activity_code'] = df_ei['activity key'].apply(lambda x: x[1])\n", + "df_premise['activity_code'] = df_premise['activity key'].apply(lambda x: x[1])\n", + "\n", + "# Merge the two dataframes based on the activity code\n", + "merged_df = pd.merge(df_ei, df_premise, on=['activity_code', 'method name'], suffixes=('_ei', '_premise'))\n", + "\n", + "# Calculate the relative change\n", + "merged_df['relative_change'] = ((merged_df['total_premise'] - merged_df['total_ei']) / merged_df['total_ei']) * 100\n", + "\n", + "# Keep only relevant columns if needed\n", + "final_df = merged_df[['method name', 'activity_code', 'total_ei', 'total_premise', 'relative_change']]\n", + "\n", + "# Output the result\n", + "final_df\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activity_codetotal_eitotal_premiserelative_change
0a3c2064d83411f7963af550c04c869a13.6067910.832335-76.923124
17011b57a11b423e3a22f6ba29e6bb1db3.9443730.827825-79.012507
2dddde503e9bdf9cc25e2e54c2d3cbac53.3814390.536510-84.133681
3f8b84f45f50d3bd7ff4feaabdb493f6a4.7859400.774079-83.825989
4461fa0b26e14337c731415cbb2df33003.7666430.471488-87.482550
586841f8c7ee2668f244d3b8e34f419326.1861470.814768-86.829161
6fcb666edf2a01467e555eeff5b4a5bbb3.9929770.841855-78.916614
736a53c174f34e672bc15b7e55563685e3.5991980.821278-77.181634
8df49e8f525497f2fbd56bcdc80ff0cde2.1648240.737734-65.921770
919afbe085b8798fd8c85eb1d14ebcca63.3500890.815993-75.642651
103c16b45db40210cd97de6574b2f47aaf4.1725370.869433-79.162956
\n", + "
" + ], + "text/plain": [ + " activity_code total_ei total_premise relative_change\n", + "0 a3c2064d83411f7963af550c04c869a1 3.606791 0.832335 -76.923124\n", + "1 7011b57a11b423e3a22f6ba29e6bb1db 3.944373 0.827825 -79.012507\n", + "2 dddde503e9bdf9cc25e2e54c2d3cbac5 3.381439 0.536510 -84.133681\n", + "3 f8b84f45f50d3bd7ff4feaabdb493f6a 4.785940 0.774079 -83.825989\n", + "4 461fa0b26e14337c731415cbb2df3300 3.766643 0.471488 -87.482550\n", + "5 86841f8c7ee2668f244d3b8e34f41932 6.186147 0.814768 -86.829161\n", + "6 fcb666edf2a01467e555eeff5b4a5bbb 3.992977 0.841855 -78.916614\n", + "7 36a53c174f34e672bc15b7e55563685e 3.599198 0.821278 -77.181634\n", + "8 df49e8f525497f2fbd56bcdc80ff0cde 2.164824 0.737734 -65.921770\n", + "9 19afbe085b8798fd8c85eb1d14ebcca6 3.350089 0.815993 -75.642651\n", + "10 3c16b45db40210cd97de6574b2f47aaf 4.172537 0.869433 -79.162956" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "final_df" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "Value must be one of {'bar', 'col'}", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[23], line 16\u001b[0m\n\u001b[0;32m 14\u001b[0m \u001b[38;5;66;03m# Create a bar chart\u001b[39;00m\n\u001b[0;32m 15\u001b[0m chart \u001b[38;5;241m=\u001b[39m BarChart()\n\u001b[1;32m---> 16\u001b[0m chart\u001b[38;5;241m.\u001b[39mtype\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcolumn\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 17\u001b[0m chart\u001b[38;5;241m.\u001b[39mstyle\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m2\u001b[39m\n\u001b[0;32m 18\u001b[0m chart\u001b[38;5;241m.\u001b[39moverlap\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m100\u001b[39m\n", + "File \u001b[1;32mc:\\Users\\fried\\miniconda3\\envs\\premise\\Lib\\site-packages\\openpyxl\\descriptors\\base.py:231\u001b[0m, in \u001b[0;36mAlias.__set__\u001b[1;34m(self, instance, value)\u001b[0m\n\u001b[0;32m 230\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__set__\u001b[39m(\u001b[38;5;28mself\u001b[39m, instance, value):\n\u001b[1;32m--> 231\u001b[0m \u001b[38;5;28msetattr\u001b[39m(instance, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39malias, value)\n", + "File \u001b[1;32mc:\\Users\\fried\\miniconda3\\envs\\premise\\Lib\\site-packages\\openpyxl\\descriptors\\nested.py:33\u001b[0m, in \u001b[0;36mNested.__set__\u001b[1;34m(self, instance, value)\u001b[0m\n\u001b[0;32m 30\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTag does not match attribute\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 32\u001b[0m value \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfrom_tree(value)\n\u001b[1;32m---> 33\u001b[0m \u001b[38;5;28msuper\u001b[39m()\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__set__\u001b[39m(instance, value)\n", + "File \u001b[1;32mc:\\Users\\fried\\miniconda3\\envs\\premise\\Lib\\site-packages\\openpyxl\\descriptors\\base.py:132\u001b[0m, in \u001b[0;36mSet.__set__\u001b[1;34m(self, instance, value)\u001b[0m\n\u001b[0;32m 130\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__set__\u001b[39m(\u001b[38;5;28mself\u001b[39m, instance, value):\n\u001b[0;32m 131\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m value \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mvalues:\n\u001b[1;32m--> 132\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__doc__\u001b[39m)\n\u001b[0;32m 133\u001b[0m \u001b[38;5;28msuper\u001b[39m()\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__set__\u001b[39m(instance, value)\n", + "\u001b[1;31mValueError\u001b[0m: Value must be one of {'bar', 'col'}" + ] + } + ], + "source": [ + "import pandas as pd\n", + "import openpyxl\n", + "from openpyxl.chart import BarChart, Reference\n", + "\n", + "# Save the results to an Excel file\n", + "filename = \"LCA_comparison_v8.xlsx\"\n", + "with pd.ExcelWriter(filename, engine='openpyxl') as writer:\n", + " merged_df.to_excel(writer, index=False, sheet_name='LCA Results')\n", + "\n", + "# Load the workbook and select the sheet\n", + "wb = openpyxl.load_workbook(filename)\n", + "ws = wb['LCA Results']\n", + "\n", + "# Create a bar chart\n", + "chart = BarChart()\n", + "chart.type=\"column\"\n", + "chart.style=2\n", + "chart.overlap= 100\n", + "chart.title = \"Relative Change in LCA Scores\"\n", + "chart.x_axis.title = \"Activity\"\n", + "chart.y_axis.title = \"Relative Change (%)\"\n", + "\n", + "# Set the data for the chart\n", + "data = Reference(ws, min_col=14, min_row=1, max_row=ws.max_row)\n", + "categories = Reference(ws, min_col=4, min_row=2, max_row=ws.max_row)\n", + "chart.add_data(data, titles_from_data=True)\n", + "chart.set_categories(categories)\n", + "\n", + "# x-axis tickes\n", + "chart.x_axis.tickLblPos = \"nextTo\"\n", + "chart.x_axis.delete = False # Ensure axis is not deleted\n", + "chart.x_axis.number_format = '0.00'\n", + "\n", + "# Avoid overlap\n", + "chart.title.overlay = False\n", + "chart.x_axis.title.overlay = False\n", + "chart.y_axis.title.overlay = False \n", + "chart.legend.overlay = False\n", + "\n", + "for series in chart.series:\n", + " series.invertIfNegative = False\n", + "\n", + "# Adjust chart dimensions\n", + "chart.width = 20 # Width of the chart\n", + "chart.height = 14 # Height of the chart\n", + "\n", + "# Add the chart to a new worksheet\n", + "new_sheet = wb.create_sheet(title=\"LCA Chart\")\n", + "new_sheet.add_chart(chart, \"A1\")\n", + "\n", + "# Save the workbook\n", + "wb.save(filename)\n", + "\n", + "print(f\"Results and chart saved to {filename}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "WITH DICTIONARIES " + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "cement = 'cement_small.yaml'\n", + "electricity = 'electricity_small.yaml'\n", + "fuels= 'fuels_small.yaml'\n", + "steel = 'steel_small.yaml'\n", + "transport = 'transport_small.yaml'" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Cement': {'yaml': 'yamls\\\\cement_small.yaml', 'yaml identifier': 'Cement'},\n", + " 'Electricity': {'yaml': 'yamls\\\\electricity_small.yaml',\n", + " 'yaml identifier': 'Electricity'}}" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "files_dict={}\n", + "files_dict['Cement']={'yaml': 'yamls\\cement_small.yaml',\n", + " 'yaml identifier': 'Cement'}\n", + "files_dict['Electricity']= {'yaml':'yamls\\electricity_small.yaml',\n", + " 'yaml identifier': 'Electricity'} #yaml identifier is the name of the filter in the yaml file, in the first line.\n", + "files_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [], + "source": [ + "premise_dict = dopo.dopo_excel.process_yaml_files(files_dict=files_dict, database=ei39SSP2)" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [], + "source": [ + "eco_dict = dopo.dopo_excel.process_yaml_files(files_dict=files_dict, database=ei39)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27',\n", + " 'df49e8f525497f2fbd56bcdc80ff0cde')" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "premise_dict['Cement']['activities'][0].key" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "def lca_scores_compare(database_dict, method_dict):\n", + " # Dictionary to store DataFrames for each method\n", + " dataframes = {}\n", + "\n", + " # Labels for the DataFrame columns\n", + " labels = [\n", + " \"activity\",\n", + " \"activity key\",\n", + " \"reference product\",\n", + " \"location\",\n", + " \"method name\",\n", + " \"total\",\n", + " ]\n", + "\n", + " # Loop through each method in method_dict\n", + " for meth_key, meth_info in method_dict.items():\n", + " data = [] # Initialize a new list to hold data for the current method\n", + " \n", + " # Extract the 'method name' tuple from the current method info\n", + " method_name = meth_info['method name']\n", + "\n", + " # Loop through each sector in the database_dict\n", + " for sector, sector_data in database_dict.items():\n", + " # Now loop through each activity in the sector\n", + " for act in sector_data['activities']:\n", + " # Ensure the activity is an instance of the expected class\n", + " if not isinstance(act, bd.backends.peewee.proxies.Activity):\n", + " raise ValueError(\"`activities` must be an iterable of `Activity` instances\")\n", + " \n", + " # Perform LCA calculations\n", + " lca = bw.LCA({act: 1}, method_name)\n", + " lca.lci()\n", + " lca.lcia()\n", + " \n", + " # Collect data for the current activity and method\n", + " data.append([\n", + " act[\"name\"],\n", + " act.key,\n", + " act.get(\"reference product\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " method_name,\n", + " lca.score,\n", + " ])\n", + " \n", + " # Convert the data list to a DataFrame and store it in the dictionary\n", + " dataframes[meth_key] = pd.DataFrame(data, columns=labels)\n", + "\n", + " # Now `dataframes` is a dictionary where each key is a method name and the value is the corresponding DataFrame\n", + " return dataframes\n" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [], + "source": [ + "eco_scores=lca_scores_compare(eco_dict,method_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityactivity keyreference productlocationmethod nametotal
0cement production, Portland(ecoinvent 3.9.1 cutoff, 86841f8c7ee2668f244d3...cement, PortlandZA(IPCC 2013, climate change, global warming pot...1.000588
1cement production, Portland(ecoinvent 3.9.1 cutoff, 36a53c174f34e672bc15b...cement, PortlandUS(IPCC 2013, climate change, global warming pot...0.885515
2cement production, Portland(ecoinvent 3.9.1 cutoff, df49e8f525497f2fbd56b...cement, PortlandCH(IPCC 2013, climate change, global warming pot...0.742421
3cement production, Portland(ecoinvent 3.9.1 cutoff, a3c2064d83411f7963af5...cement, PortlandBR(IPCC 2013, climate change, global warming pot...0.851799
4cement production, Portland(ecoinvent 3.9.1 cutoff, f8b84f45f50d3bd7ff4fe...cement, PortlandIN(IPCC 2013, climate change, global warming pot...0.891756
5cement production, Portland(ecoinvent 3.9.1 cutoff, fcb666edf2a01467e555e...cement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...0.845772
6cement production, Portland(ecoinvent 3.9.1 cutoff, 3c16b45db40210cd97de6...cement, PortlandPE(IPCC 2013, climate change, global warming pot...0.895198
\n", + "
" + ], + "text/plain": [ + " activity \\\n", + "0 cement production, Portland \n", + "1 cement production, Portland \n", + "2 cement production, Portland \n", + "3 cement production, Portland \n", + "4 cement production, Portland \n", + "5 cement production, Portland \n", + "6 cement production, Portland \n", + "\n", + " activity key reference product \\\n", + "0 (ecoinvent 3.9.1 cutoff, 86841f8c7ee2668f244d3... cement, Portland \n", + "1 (ecoinvent 3.9.1 cutoff, 36a53c174f34e672bc15b... cement, Portland \n", + "2 (ecoinvent 3.9.1 cutoff, df49e8f525497f2fbd56b... cement, Portland \n", + "3 (ecoinvent 3.9.1 cutoff, a3c2064d83411f7963af5... cement, Portland \n", + "4 (ecoinvent 3.9.1 cutoff, f8b84f45f50d3bd7ff4fe... cement, Portland \n", + "5 (ecoinvent 3.9.1 cutoff, fcb666edf2a01467e555e... cement, Portland \n", + "6 (ecoinvent 3.9.1 cutoff, 3c16b45db40210cd97de6... cement, Portland \n", + "\n", + " location method name total \n", + "0 ZA (IPCC 2013, climate change, global warming pot... 1.000588 \n", + "1 US (IPCC 2013, climate change, global warming pot... 0.885515 \n", + "2 CH (IPCC 2013, climate change, global warming pot... 0.742421 \n", + "3 BR (IPCC 2013, climate change, global warming pot... 0.851799 \n", + "4 IN (IPCC 2013, climate change, global warming pot... 0.891756 \n", + "5 CA-QC (IPCC 2013, climate change, global warming pot... 0.845772 \n", + "6 PE (IPCC 2013, climate change, global warming pot... 0.895198 " + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eco_scores['method_1']" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [], + "source": [ + "premise_scores=lca_scores_compare(premise_dict,method_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityactivity keyreference productlocationmethod nametotal
0cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandCH(IPCC 2013, climate change, global warming pot...0.737734
1cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandBR(IPCC 2013, climate change, global warming pot...0.832335
2cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandUS(IPCC 2013, climate change, global warming pot...0.821278
3cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandIN(IPCC 2013, climate change, global warming pot...0.774079
4cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandCA-QC(IPCC 2013, climate change, global warming pot...0.841855
5cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandPE(IPCC 2013, climate change, global warming pot...0.869433
6cement production, Portland(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...cement, PortlandZA(IPCC 2013, climate change, global warming pot...0.814768
7electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageTUR(IPCC 2013, climate change, global warming pot...0.033589
8electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageSAF(IPCC 2013, climate change, global warming pot...0.032775
9electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageRUS(IPCC 2013, climate change, global warming pot...0.033344
10electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageKOR(IPCC 2013, climate change, global warming pot...0.032052
11electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageBRA(IPCC 2013, climate change, global warming pot...0.032326
12electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageUKR(IPCC 2013, climate change, global warming pot...0.033533
13electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageCEU(IPCC 2013, climate change, global warming pot...0.032553
14electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageRSAM(IPCC 2013, climate change, global warming pot...0.032650
15electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageUSA(IPCC 2013, climate change, global warming pot...0.031852
16electricity production, at biomass-fired IGCC ...(ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2...electricity, high voltageCHN(IPCC 2013, climate change, global warming pot...0.032993
\n", + "
" + ], + "text/plain": [ + " activity \\\n", + "0 cement production, Portland \n", + "1 cement production, Portland \n", + "2 cement production, Portland \n", + "3 cement production, Portland \n", + "4 cement production, Portland \n", + "5 cement production, Portland \n", + "6 cement production, Portland \n", + "7 electricity production, at biomass-fired IGCC ... \n", + "8 electricity production, at biomass-fired IGCC ... \n", + "9 electricity production, at biomass-fired IGCC ... \n", + "10 electricity production, at biomass-fired IGCC ... \n", + "11 electricity production, at biomass-fired IGCC ... \n", + "12 electricity production, at biomass-fired IGCC ... \n", + "13 electricity production, at biomass-fired IGCC ... \n", + "14 electricity production, at biomass-fired IGCC ... \n", + "15 electricity production, at biomass-fired IGCC ... \n", + "16 electricity production, at biomass-fired IGCC ... \n", + "\n", + " activity key \\\n", + "0 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "1 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "2 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "3 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "4 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "5 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "6 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "7 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "8 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "9 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "10 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "11 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "12 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "13 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "14 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "15 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "16 (ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-2... \n", + "\n", + " reference product location \\\n", + "0 cement, Portland CH \n", + "1 cement, Portland BR \n", + "2 cement, Portland US \n", + "3 cement, Portland IN \n", + "4 cement, Portland CA-QC \n", + "5 cement, Portland PE \n", + "6 cement, Portland ZA \n", + "7 electricity, high voltage TUR \n", + "8 electricity, high voltage SAF \n", + "9 electricity, high voltage RUS \n", + "10 electricity, high voltage KOR \n", + "11 electricity, high voltage BRA \n", + "12 electricity, high voltage UKR \n", + "13 electricity, high voltage CEU \n", + "14 electricity, high voltage RSAM \n", + "15 electricity, high voltage USA \n", + "16 electricity, high voltage CHN \n", + "\n", + " method name total \n", + "0 (IPCC 2013, climate change, global warming pot... 0.737734 \n", + "1 (IPCC 2013, climate change, global warming pot... 0.832335 \n", + "2 (IPCC 2013, climate change, global warming pot... 0.821278 \n", + "3 (IPCC 2013, climate change, global warming pot... 0.774079 \n", + "4 (IPCC 2013, climate change, global warming pot... 0.841855 \n", + "5 (IPCC 2013, climate change, global warming pot... 0.869433 \n", + "6 (IPCC 2013, climate change, global warming pot... 0.814768 \n", + "7 (IPCC 2013, climate change, global warming pot... 0.033589 \n", + "8 (IPCC 2013, climate change, global warming pot... 0.032775 \n", + "9 (IPCC 2013, climate change, global warming pot... 0.033344 \n", + "10 (IPCC 2013, climate change, global warming pot... 0.032052 \n", + "11 (IPCC 2013, climate change, global warming pot... 0.032326 \n", + "12 (IPCC 2013, climate change, global warming pot... 0.033533 \n", + "13 (IPCC 2013, climate change, global warming pot... 0.032553 \n", + "14 (IPCC 2013, climate change, global warming pot... 0.032650 \n", + "15 (IPCC 2013, climate change, global warming pot... 0.031852 \n", + "16 (IPCC 2013, climate change, global warming pot... 0.032993 " + ] + }, + "execution_count": 78, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "premise_scores['method_1'] #what is happening here?, something wrong with sector!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "premise", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/dev/notebook tests/comparing dbs_v3.ipynb b/dev/notebook tests/comparing dbs_v3.ipynb new file mode 100644 index 0000000..029ae52 --- /dev/null +++ b/dev/notebook tests/comparing dbs_v3.ipynb @@ -0,0 +1,800 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "#from functions_v2 import*\n", + "from methods import MethodFinder\n", + "\n", + "import brightway2 as bw\n", + "import bw2data as bd\n", + "import bw2analyzer as ba\n", + "import bw2calc as bc\n", + "\n", + "#reduce?\n", + "import ast\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "\n", + "import dopo\n", + "import activity_filter\n", + "from activity_filter import generate_sets_from_filters\n", + "\n", + "import copy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Setup up bw project and databases" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Biosphere database already present!!! No setup is needed\n" + ] + } + ], + "source": [ + "bd.projects.set_current(\"premise-validation-try1\")\n", + "bw.bw2setup()\n", + "\n", + "bio3=bw.Database('biosphere3')\n", + "ei39=bw.Database('ecoinvent 3.9.1 cutoff')\n", + "ei39SSP2=bw.Database('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Setup method dictionary" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'method_1': {'object': Brightway2 Method: IPCC 2013: climate change: global warming potential (GWP100),\n", + " 'method name': ('IPCC 2013',\n", + " 'climate change',\n", + " 'global warming potential (GWP100)'),\n", + " 'short name': 'global warming potential (GWP100)',\n", + " 'unit': 'kg CO2-Eq'},\n", + " 'method_2': {'object': Brightway2 Method: EN15804: inventory indicators ISO21930: Cumulative Energy Demand - non-renewable energy resources,\n", + " 'method name': ('EN15804',\n", + " 'inventory indicators ISO21930',\n", + " 'Cumulative Energy Demand - non-renewable energy resources'),\n", + " 'short name': 'Cumulative Energy Demand - non-renewable energy resources',\n", + " 'unit': 'megajoule'}}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Get Methods\n", + "finder=MethodFinder()\n", + "\n", + "finder.find_and_create_method(criteria=['IPCC', '2013', 'GWP100'], exclude=['no LT'])\n", + "finder.find_and_create_method(criteria=['EN15804','Cumulative', 'non-renewable' ])\n", + "# finder.find_and_create_method(criteria=['land occupation','selected'])\n", + "# finder.find_and_create_method(criteria=['EN15804','fresh water'])\n", + "\n", + "method_dict=finder.get_all_methods()\n", + "method_dict" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define sectors & setup databse dictionaries containing sector activity lists" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "cement = 'cement_small.yaml'\n", + "electricity = 'electricity_small.yaml'\n", + "fuels= 'fuels_small.yaml'\n", + "steel = 'steel_small.yaml'\n", + "transport = 'transport_small.yaml'" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Cement': {'yaml': 'yamls\\\\cement_small.yaml', 'yaml identifier': 'Cement'},\n", + " 'Steel': {'yaml': 'yamls\\\\steel_small.yaml', 'yaml identifier': 'Steel'}}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "files_dict={}\n", + "files_dict['Cement']={'yaml': 'yamls\\cement_small.yaml',\n", + " 'yaml identifier': 'Cement'}\n", + "#files_dict['Electricity']= {'yaml':'yamls\\electricity_small.yaml',\n", + " #'yaml identifier': 'Electricity'} #yaml identifier is the name of the filter in the yaml file, in the first line.\n", + "files_dict['Steel']={'yaml':'yamls\\steel_small.yaml',\n", + " 'yaml identifier': 'Steel'}\n", + "files_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def process_yaml_files(files_dict, database):\n", + " '''\n", + " - Runs through the files_dict reading the defined filters in the yaml files.\n", + " - With another function a list that contains the filtered activities is created from the chosen database.\n", + " - This activity list is saved within the corresponding key (sector) in the dictionary main_dict which is based on the files_dict.\n", + "\n", + " :param files_dict: dictionary of dictionaries. It should hold the yaml file path and the title in the first row of the yaml file. \n", + " Like so: files_dict['Cement']={'yaml': 'yamls\\cement_small.yaml', 'yaml identifier': 'Cement'}\n", + " :param database: premise or ecoinvent database of choice.\n", + "\n", + " It returns an updated dictionary which contains filtered activity lists for each sector.\n", + " '''\n", + "\n", + " main_dict = copy.deepcopy(files_dict)\n", + "\n", + " for key, value in main_dict.items():\n", + " yaml_file = value['yaml']\n", + " yaml_identifier = value['yaml identifier']\n", + " \n", + " #debug\n", + " print(f\"Processing {key} with database {database.name}\")\n", + " \n", + " # Generate the sector activities\n", + " sector_activities = generate_sets_from_filters(yaml_file, database)\n", + " \n", + " #debug\n", + " print(f\"Activities for {key}:\")\n", + " for activity in sector_activities[yaml_identifier]:\n", + " print(f\" {activity.key}\")\n", + "\n", + " # Convert the set of activities to a list\n", + " activities_list = list(sector_activities[yaml_identifier])\n", + " \n", + " # Add to the sectors_dict\n", + " main_dict[key]['activities'] = activities_list\n", + " \n", + " return main_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing Cement with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Cement:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'df49e8f525497f2fbd56bcdc80ff0cde')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'a3c2064d83411f7963af550c04c869a1')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '3c16b45db40210cd97de6574b2f47aaf')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'fcb666edf2a01467e555eeff5b4a5bbb')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '86841f8c7ee2668f244d3b8e34f41932')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'f8b84f45f50d3bd7ff4feaabdb493f6a')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '36a53c174f34e672bc15b7e55563685e')\n", + "Processing Steel with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Steel:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '2baa0deb3adc89dfe8cb89d5e078ba8d')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'af6bd1221fc0206541fbaf481397bf0d')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '1dffacc9e0ca08fb55c6b780d7e677dc')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '18b0dcf01dd401e1549b3796e3786213')\n" + ] + } + ], + "source": [ + "premise_dict = process_yaml_files(files_dict=files_dict, database=ei39SSP2)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing Cement with database ecoinvent 3.9.1 cutoff\n", + "Activities for Cement:\n", + " ('ecoinvent 3.9.1 cutoff', 'df49e8f525497f2fbd56bcdc80ff0cde')\n", + " ('ecoinvent 3.9.1 cutoff', 'f8b84f45f50d3bd7ff4feaabdb493f6a')\n", + " ('ecoinvent 3.9.1 cutoff', 'a3c2064d83411f7963af550c04c869a1')\n", + " ('ecoinvent 3.9.1 cutoff', 'fcb666edf2a01467e555eeff5b4a5bbb')\n", + " ('ecoinvent 3.9.1 cutoff', '86841f8c7ee2668f244d3b8e34f41932')\n", + " ('ecoinvent 3.9.1 cutoff', '3c16b45db40210cd97de6574b2f47aaf')\n", + " ('ecoinvent 3.9.1 cutoff', '36a53c174f34e672bc15b7e55563685e')\n", + "Processing Steel with database ecoinvent 3.9.1 cutoff\n", + "Activities for Steel:\n", + " ('ecoinvent 3.9.1 cutoff', '2baa0deb3adc89dfe8cb89d5e078ba8d')\n", + " ('ecoinvent 3.9.1 cutoff', '18b0dcf01dd401e1549b3796e3786213')\n", + " ('ecoinvent 3.9.1 cutoff', '1dffacc9e0ca08fb55c6b780d7e677dc')\n", + " ('ecoinvent 3.9.1 cutoff', 'af6bd1221fc0206541fbaf481397bf0d')\n" + ] + } + ], + "source": [ + "eco_dict = process_yaml_files(files_dict=files_dict, database=ei39)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "keys_prem = [activity.key for activity in premise_dict['Cement']['activities']]\n", + "keys_eco = [activity.key for activity in eco_dict['Cement']['activities']]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "keys_prem" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "keys_eco" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "premise_dict['Cement']['activities'][0].key" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eco_dict['Cement']['activities'][0].key" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calculate lca scores for each sectors activities, store them each in a dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "def lca_scores_compare(database_dict, method_dict):\n", + " # Dictionary to store DataFrames for each sector\n", + " sector_dataframes = {}\n", + "\n", + " # Labels for the DataFrame columns\n", + " labels = [\n", + " \"activity\",\n", + " \"activity key\",\n", + " \"reference product\",\n", + " \"location\",\n", + " \"method name\",\n", + " \"method unit\",\n", + " \"total\",\n", + " ]\n", + "\n", + " # Loop through each sector in the database_dict\n", + " for sector, sector_data in database_dict.items():\n", + " # Initialize a dictionary to hold DataFrames for each method in the current sector\n", + " method_dataframes = {}\n", + "\n", + " # Loop through each method in method_dict\n", + " for meth_key, meth_info in method_dict.items():\n", + " data = [] # Initialize a new list to hold data for the current method\n", + " \n", + " # Extract the 'method name' tuple from the current method info\n", + " method_name = meth_info['method name']\n", + " method_unit = meth_info['unit']\n", + "\n", + " # Now loop through each activity in the sector\n", + " for act in sector_data['activities']:\n", + " # Ensure the activity is an instance of the expected class\n", + " if not isinstance(act, bd.backends.peewee.proxies.Activity):\n", + " raise ValueError(\"`activities` must be an iterable of `Activity` instances\")\n", + " \n", + " # Perform LCA calculations\n", + " lca = bw.LCA({act: 1}, method_name)\n", + " lca.lci()\n", + " lca.lcia()\n", + " \n", + " # Collect data for the current activity and method\n", + " data.append([\n", + " act[\"name\"],\n", + " act.key,\n", + " act.get(\"reference product\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " method_name,\n", + " method_unit,\n", + " lca.score,\n", + " ])\n", + " \n", + " # Convert the data list to a DataFrame and store it in the sector's dictionary\n", + " method_dataframes[meth_key] = pd.DataFrame(data, columns=labels)\n", + "\n", + " # Store the method_dataframes dictionary in the sector_dataframes dictionary\n", + " sector_dataframes[sector] = method_dataframes\n", + "\n", + " # Now `sector_dataframes` is a dictionary where each key is a sector, and the value is another dictionary with method names and their corresponding DataFrames\n", + " return sector_dataframes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ecoinvent scores" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "eco_scores=lca_scores_compare(eco_dict,method_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eco_scores['Cement']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eco_scores['Cement']['method_1']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eco_scores['Steel']['method_2']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Premise scores" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "premise_scores=lca_scores_compare(premise_dict,method_dict) #dictionary containing sectors = keys and dataframes by method = values" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "premise_scores['Steel']['method_1'] #what is happening here?, something wrong with sector!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Relative changes" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "def relative_changes_df(ecoinvent_scores, premise_scores):\n", + "\n", + " dictionary = {}\n", + "\n", + " # Iterate over sectors\n", + " for sector_key in ecoinvent_scores:\n", + " # Initialize the sector key in the output dictionary\n", + " if sector_key not in dictionary:\n", + " dictionary[sector_key] = {}\n", + "\n", + " # Iterate over methods within the sector\n", + " for method_key in ecoinvent_scores[sector_key]:\n", + " # Check if the method_key exists in both dictionaries to avoid KeyError\n", + " if method_key in premise_scores.get(sector_key, {}):\n", + " # Get the corresponding DataFrames\n", + " df_ei = ecoinvent_scores[sector_key][method_key]\n", + " df_premise = premise_scores[sector_key][method_key]\n", + "\n", + " #print(df_ei['activity key'])\n", + " #print(df_premise)\n", + "\n", + " # Split the 'activity key' to extract the second part\n", + " df_ei['activity_code'] = df_ei['activity key'].apply(lambda x: x[1]) # Access the second element of the tuple\n", + " df_premise['activity_code'] = df_premise['activity key'].apply(lambda x: x[1])\n", + "\n", + " # Merge the two dataframes based on the activity code and method name\n", + " merged_df = pd.merge(df_ei, df_premise, on=['activity_code', 'method name'], suffixes=('_ei', '_premise'))\n", + "\n", + " # Calculate the relative change\n", + " merged_df['relative_change'] = ((merged_df['total_premise'] - merged_df['total_ei']) / merged_df['total_ei']) * 100\n", + "\n", + " # Store the result in the dictionary\n", + " dictionary[sector_key][method_key] = merged_df\n", + "\n", + " return dictionary\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "relative_dict = relative_changes_df(eco_scores, premise_scores)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "relative_dict['Cement']['method_1']" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'method_1': {'rank': 16, 'relative_change': 14}, 'method_2': {'rank': 16, 'relative_change': 14}}\n" + ] + } + ], + "source": [ + "from dopo_excel import add_sector_marker\n", + "\n", + "# Prepare to save each LCA score table to a different worksheet in the same Excel file\n", + "excel_file = 'compare_tables_v7.xlsx'\n", + "column_positions = {} #stores the indexes of columns for plotting\n", + "with pd.ExcelWriter(excel_file, engine='openpyxl') as writer:\n", + " for sector in relative_dict.keys():\n", + " relative_changes = relative_dict[sector]\n", + " \n", + " for method, table in relative_changes.items():\n", + " # Create a DataFrame for the current LCA score table\n", + " df = pd.DataFrame(table)\n", + "\n", + " # Add sector marker\n", + " df = add_sector_marker(df, sector) #!! ADJUST POSITION \n", + "\n", + " # Sort the DataFrame by 'relative_change' from largest negative to largest positive\n", + " df = df.sort_values(by='relative_change', ascending=False)\n", + "\n", + " # Add a 'rank' column based on the 'relative_change', ranking from most negative to least negative\n", + " df['rank'] = df['relative_change'].rank(ascending=False, method='dense').astype(int)\n", + " \n", + " # Get the index values of columns\n", + " columns_of_interest = [\"rank\", \"relative_change\", \"method\", \"method unit\", ]\n", + " positions = {col: df.columns.get_loc(col) for col in columns_of_interest if col in df.columns}\n", + " column_positions[method] = positions\n", + "\n", + " # Generate worksheet name\n", + " worksheet_name = f\"{sector}_{method}\"\n", + " if len(worksheet_name) > 31:\n", + " worksheet_name = worksheet_name[:31]\n", + "\n", + " # Save the DataFrame to the Excel file in a new worksheet\n", + " df.to_excel(writer, sheet_name=worksheet_name, index=False)\n", + "print(column_positions)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plots" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "from openpyxl import load_workbook\n", + "\n", + "def categorize_sheets_by_sector(file_path):\n", + " # Load the workbook\n", + " workbook = load_workbook(filename=file_path, read_only=True)\n", + " \n", + " # Initialize a dictionary to hold sectors and their corresponding sheet names\n", + " worksheet_dict = {}\n", + " \n", + " # Iterate over all sheet names in the workbook\n", + " for sheet_name in workbook.sheetnames:\n", + " # Split the sheet name to extract the sector (assumes sector is the first part)\n", + " sector = sheet_name.split('_')[0]\n", + " \n", + " # Add the sheet name to the corresponding sector in the dictionary\n", + " if sector in worksheet_dict:\n", + " worksheet_dict[sector].append(sheet_name)\n", + " else:\n", + " worksheet_dict[sector] = [sheet_name]\n", + " \n", + " return worksheet_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import openpyxl\n", + "from openpyxl.chart import BarChart, Reference\n", + "\n", + "def compare_database_charts(filename, worksheet_dict, index_positions=None):\n", + "\n", + " # Load the workbook and select the sheet\n", + " wb = openpyxl.load_workbook(filename)\n", + "\n", + " # Iterate over each sector and its associated worksheets\n", + " for sector, worksheet_names in worksheet_dict.items():\n", + " \n", + " # Create or get the chart sheet for the current sector\n", + " chart_sheet_name = f\"{sector}_charts\"\n", + " if chart_sheet_name in wb.sheetnames:\n", + " ws_charts = wb[chart_sheet_name]\n", + " else:\n", + " ws_charts = wb.create_sheet(chart_sheet_name) \n", + " \n", + " # Initial position for the first chart\n", + " current_row = 1 # Start placing charts from row 1\n", + " current_col = 1 # Start placing charts from column 1\n", + " chart_height = 30 # Number of rows a chart occupies\n", + " chart_width = 12 # Number of columns a chart occupies\n", + " charts_per_row = 2 # Number of charts per row\n", + " \n", + " # Iterate over each worksheet name in the current sector\n", + " for i, worksheet_name in enumerate(worksheet_names):\n", + " ws = wb[worksheet_name]\n", + "\n", + " # # Find the key in index_positions that contains worksheet_name\n", + " # matching_key = None\n", + " # for key in index_positions.keys():\n", + " # if worksheet_name in key:\n", + " # matching_key = key\n", + " # break\n", + "\n", + " # if not matching_key:\n", + " # print(f\"Warning: No matching key found for worksheet '{worksheet_name}'. Skipping...\")\n", + " # continue\n", + "\n", + " # Retrieve the column positions from the index_positions dictionary\n", + " # positions = index_positions[matching_key]\n", + "\n", + " # Find min_row, max_row and max_column\n", + " min_col_data = 15 #positions.get(\"relative_change\", None) + 1\n", + " rank_col = 17#positions.get(\"rank\", None) + 1\n", + " method_col = 5#positions.get(\"method\", None) + 1\n", + " method_unit_col = 6#positions.get(\"method unit\", None) + 1\n", + "\n", + " # Create a bar chart\n", + " chart = BarChart()\n", + " chart.type=\"bar\"\n", + " chart.style=2\n", + " chart.overlap= 100\n", + " chart.title = \"Relative Change in LCA Scores\"\n", + " chart.x_axis.title = \"Activity\"\n", + " chart.y_axis.title = \"Relative Change (%)\"\n", + "\n", + " # Set the data for the chart\n", + " data = Reference(ws, min_col=min_col_data, min_row=1, max_row=ws.max_row)\n", + " categories = Reference(ws, min_col=rank_col, min_row=2, max_row=ws.max_row)\n", + " chart.add_data(data, titles_from_data=True)\n", + " chart.set_categories(categories)\n", + "\n", + " # Modify each series in the chart to disable the inversion of negative values \n", + " for series in chart.series:\n", + " series.invertIfNegative = False\n", + "\n", + " # x-axis tickes\n", + " chart.x_axis.tickLblPos = \"low\"\n", + " chart.x_axis.majorGridlines = None \n", + " chart.x_axis.tickMarkSkip = 1 # Show all tick marks, this adresses the tick lines \n", + " chart.x_axis.tickLblSkip = 1 # Show all labels, doesnt work\n", + " chart.x_axis.delete = False # Ensure axis is not deleted\n", + "\n", + " # Chart titles\n", + " method_value = ws.cell(row=2, column=method_col).value\n", + " chart.title = f\"{sector} {method_value} database lca scores relative changes\"\n", + "\n", + " method_unit_value = ws.cell(row=2, column=method_unit_col).value\n", + " chart.x_axis.title = f\"{method_unit_value}\"\n", + " \n", + " chart.y_axis.title = 'relative change (%)' #its switched..... should be x_axis\n", + "\n", + " # Avoid overlap\n", + " chart.title.overlay = False\n", + " chart.x_axis.title.overlay = False\n", + " chart.y_axis.title.overlay = False \n", + " chart.legend.overlay = False\n", + "\n", + " # Adjust chart dimensions\n", + " chart.width = 20 # Width of the chart\n", + " chart.height = 14 # Height of the chart\n", + "\n", + " # Calculate the position for this chart\n", + " position = ws_charts.cell(row=current_row, column=current_col).coordinate\n", + " ws_charts.add_chart(chart, position)\n", + "\n", + " # Update position for the next chart\n", + " current_col += chart_width +1 \n", + " if (i + 1) % charts_per_row == 0: # Move to the next row after placing `charts_per_row` charts\n", + " current_row += chart_height +1\n", + " current_col = 1 # Reset to the first column\n", + "\n", + " # Move the chart sheet to the first position\n", + " wb._sheets.remove(ws_charts)\n", + " wb._sheets.insert(0, ws_charts)\n", + "\n", + " # Add the chart to a new worksheet\n", + " # new_sheet = wb.create_sheet(title=\"LCA Chart\")\n", + " # new_sheet.add_chart(chart, \"A1\")\n", + "\n", + " # Save the workbook\n", + " wb.save(filename)\n", + "\n", + " print(f\"Results and chart saved to {filename}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Cement': ['Cement_method_1', 'Cement_method_2'],\n", + " 'Steel': ['Steel_method_1', 'Steel_method_2'],\n", + " 'LCA Chart': ['LCA Chart']}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "categorize_sheets_by_sector('compare_tables_v4.xlsx')" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Results and chart saved to compare_tables_v7.xlsx\n" + ] + } + ], + "source": [ + "compare_database_charts('compare_tables_v7.xlsx',categorize_sheets_by_sector('compare_tables_v7.xlsx')) #index_positions=column_positions)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "premise", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/dev/general_scan_v5.ipynb b/dev/notebook tests/general_scan_v5.ipynb similarity index 100% rename from dev/general_scan_v5.ipynb rename to dev/notebook tests/general_scan_v5.ipynb diff --git a/dev/jupyter_dopo_test1.ipynb b/dev/notebook tests/jupyter_dopo_test1.ipynb similarity index 100% rename from dev/jupyter_dopo_test1.ipynb rename to dev/notebook tests/jupyter_dopo_test1.ipynb diff --git a/dev/test_excel_flow.ipynb b/dev/notebook tests/test_excel_flow.ipynb similarity index 100% rename from dev/test_excel_flow.ipynb rename to dev/notebook tests/test_excel_flow.ipynb diff --git a/dev/test_function_flow_v4.ipynb b/dev/notebook tests/test_function_flow_v4.ipynb similarity index 100% rename from dev/test_function_flow_v4.ipynb rename to dev/notebook tests/test_function_flow_v4.ipynb diff --git a/dev/notebook tests/test_unnamed_column.ipynb b/dev/notebook tests/test_unnamed_column.ipynb new file mode 100644 index 0000000..93a742e --- /dev/null +++ b/dev/notebook tests/test_unnamed_column.ipynb @@ -0,0 +1,962 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import brightway2 as bw\n", + "import bw2data as bd\n", + "import bw2analyzer as ba\n", + "\n", + "#reduce?\n", + "import ast\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "import dopo\n", + "from dopo import*" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Biosphere database already present!!! No setup is needed\n" + ] + } + ], + "source": [ + "bd.projects.set_current(\"premise-validation-try1\")\n", + "bw.bw2setup()\n", + "\n", + "bio3=bw.Database('biosphere3')\n", + "ei39=bw.Database('ecoinvent 3.9.1 cutoff')\n", + "ei39SSP2=bw.Database('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "FILTERS" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "#sector filters file names/paths\n", + "\n", + "cement = 'cement_small.yaml'\n", + "electricity = 'electricity_small.yaml'\n", + "fuels= 'fuels_small.yaml'\n", + "steel = 'steel_small.yaml'\n", + "transport = 'transport_small.yaml'" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Cement': {'yaml': 'yamls\\\\cement_small.yaml', 'yaml identifier': 'Cement'},\n", + " 'Steel': {'yaml': 'yamls\\\\steel_small.yaml', 'yaml identifier': 'Steel'}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "files_dict={}\n", + "files_dict['Cement']={'yaml': 'yamls\\cement_small.yaml', \n", + " 'yaml identifier': 'Cement'}\n", + "files_dict['Steel']= {'yaml':'yamls\\steel_small.yaml',\n", + " 'yaml identifier': 'Steel'} #yaml identifier is the name of the filter in the yaml file, in the first line.\n", + "files_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing Cement with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Cement:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '86841f8c7ee2668f244d3b8e34f41932')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'df49e8f525497f2fbd56bcdc80ff0cde')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '3c16b45db40210cd97de6574b2f47aaf')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'fcb666edf2a01467e555eeff5b4a5bbb')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'f8b84f45f50d3bd7ff4feaabdb493f6a')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'a3c2064d83411f7963af550c04c869a1')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '36a53c174f34e672bc15b7e55563685e')\n", + "Processing Steel with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Steel:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'af6bd1221fc0206541fbaf481397bf0d')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '2baa0deb3adc89dfe8cb89d5e078ba8d')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '1dffacc9e0ca08fb55c6b780d7e677dc')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '18b0dcf01dd401e1549b3796e3786213')\n", + "Processing Cement with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Cement:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '86841f8c7ee2668f244d3b8e34f41932')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'df49e8f525497f2fbd56bcdc80ff0cde')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '3c16b45db40210cd97de6574b2f47aaf')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'fcb666edf2a01467e555eeff5b4a5bbb')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'f8b84f45f50d3bd7ff4feaabdb493f6a')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'a3c2064d83411f7963af550c04c869a1')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '36a53c174f34e672bc15b7e55563685e')\n", + "Processing Steel with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Steel:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '18b0dcf01dd401e1549b3796e3786213')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '2baa0deb3adc89dfe8cb89d5e078ba8d')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '1dffacc9e0ca08fb55c6b780d7e677dc')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'af6bd1221fc0206541fbaf481397bf0d')\n", + "Processing Cement with database ecoinvent 3.9.1 cutoff\n", + "Activities for Cement:\n", + " ('ecoinvent 3.9.1 cutoff', 'f8b84f45f50d3bd7ff4feaabdb493f6a')\n", + " ('ecoinvent 3.9.1 cutoff', 'a3c2064d83411f7963af550c04c869a1')\n", + " ('ecoinvent 3.9.1 cutoff', '36a53c174f34e672bc15b7e55563685e')\n", + " ('ecoinvent 3.9.1 cutoff', '3c16b45db40210cd97de6574b2f47aaf')\n", + " ('ecoinvent 3.9.1 cutoff', '86841f8c7ee2668f244d3b8e34f41932')\n", + " ('ecoinvent 3.9.1 cutoff', 'df49e8f525497f2fbd56bcdc80ff0cde')\n", + " ('ecoinvent 3.9.1 cutoff', 'fcb666edf2a01467e555eeff5b4a5bbb')\n", + "Processing Steel with database ecoinvent 3.9.1 cutoff\n", + "Activities for Steel:\n", + " ('ecoinvent 3.9.1 cutoff', '1dffacc9e0ca08fb55c6b780d7e677dc')\n", + " ('ecoinvent 3.9.1 cutoff', 'af6bd1221fc0206541fbaf481397bf0d')\n", + " ('ecoinvent 3.9.1 cutoff', '2baa0deb3adc89dfe8cb89d5e078ba8d')\n", + " ('ecoinvent 3.9.1 cutoff', '18b0dcf01dd401e1549b3796e3786213')\n" + ] + } + ], + "source": [ + "import dopo.filter_sectors\n", + "\n", + "#for plot 1 and 2\n", + "dictionary_one = dopo.filter_sectors.process_yaml_files(files_dict, ei39SSP2)\n", + "\n", + "#for comparison\n", + "premise_dict = dopo.filter_sectors.process_yaml_files(files_dict, ei39SSP2)\n", + "ecoinvent_dict = dopo.filter_sectors.process_yaml_files(files_dict, ei39)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'method_1': {'object': Brightway2 Method: IPCC 2013: climate change: global warming potential (GWP100),\n", + " 'method name': ('IPCC 2013',\n", + " 'climate change',\n", + " 'global warming potential (GWP100)'),\n", + " 'short name': 'global warming potential (GWP100)',\n", + " 'unit': 'kg CO2-Eq'},\n", + " 'method_2': {'object': Brightway2 Method: EN15804: inventory indicators ISO21930: Cumulative Energy Demand - non-renewable energy resources,\n", + " 'method name': ('EN15804',\n", + " 'inventory indicators ISO21930',\n", + " 'Cumulative Energy Demand - non-renewable energy resources'),\n", + " 'short name': 'Cumulative Energy Demand - non-renewable energy resources',\n", + " 'unit': 'megajoule'}}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "finder=dopo.methods.MethodFinder()\n", + "\n", + "finder.find_and_create_method(criteria=['IPCC', '2013', 'GWP100'], exclude=['no LT'])\n", + "finder.find_and_create_method(criteria=['EN15804','Cumulative', 'non-renewable' ])\n", + "#finder.find_and_create_method(criteria=['land occupation','selected'])\n", + "#finder.find_and_create_method(criteria=['EN15804','fresh water'])\n", + "method_dict=finder.get_all_methods()\n", + "method_dict" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "LCA Tables" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "import operator\n", + "from os.path import commonprefix\n", + "\n", + "import bw2calc as bc\n", + "import bw2data as bd\n", + "import numpy as np\n", + "import pandas as pd\n", + "import tabulate\n", + "from pandas import DataFrame\n", + "\n", + "\n", + "def aggregated_dict(activity):\n", + " \"\"\"Return dictionary of inputs aggregated by input reference product.\"\"\"\n", + " results = {}\n", + " for exc in activity.technosphere():\n", + " results[exc.input[\"reference product\"]] = (\n", + " results.setdefault(exc.input[\"reference product\"], 0) + exc[\"amount\"]\n", + " )\n", + "\n", + " for exc in activity.biosphere():\n", + " results[exc.input[\"name\"]] = (\n", + " results.setdefault(exc.input[\"name\"], 0) + exc[\"amount\"]\n", + " )\n", + "\n", + " return results\n", + "\n", + "\n", + "\n", + "def compare_dictionaries(one, two, rel_tol=1e-4, abs_tol=1e-9):\n", + " \"\"\"Compare two dictionaries with form ``{str: float}``, and return a set of keys where differences where present.\n", + "\n", + " Tolerance values are inputs to `math.isclose `__.\"\"\"\n", + " return (\n", + " set(one)\n", + " .symmetric_difference(set(two))\n", + " .union(\n", + " {\n", + " key\n", + " for key in one\n", + " if key in two\n", + " and not math.isclose(\n", + " a=one[key], b=two[key], rel_tol=rel_tol, abs_tol=abs_tol\n", + " )\n", + " }\n", + " )\n", + " )\n", + "\n", + "\n", + "\n", + "\n", + "def find_differences_in_inputs(\n", + " activity, rel_tol=1e-4, abs_tol=1e-9, locations=None, as_dataframe=False\n", + "):\n", + " \"\"\"Given an ``Activity``, try to see if other activities in the same database (with the same name and\n", + " reference product) have the same input levels.\n", + "\n", + " Tolerance values are inputs to `math.isclose `__.\n", + "\n", + " If differences are present, a difference dictionary is constructed, with the form:\n", + "\n", + " .. code-block:: python\n", + "\n", + " {Activity instance: [(name of input flow (str), amount)]}\n", + "\n", + " Note that this doesn't reference a specific exchange, but rather sums **all exchanges with the same input reference product**.\n", + "\n", + " Assumes that all similar activities produce the same amount of reference product.\n", + "\n", + " ``(x, y)``, where ``x`` is the number of similar activities, and ``y`` is a dictionary of the differences. This dictionary is empty if no differences are found.\n", + "\n", + " Args:\n", + " activity: ``Activity``. Activity to analyze.\n", + " rel_tol: float. Relative tolerance to decide if two inputs are the same. See above.\n", + " abs_tol: float. Absolute tolerance to decide if two inputs are the same. See above.\n", + " locations: list, optional. Locations to restrict comparison to, if present.\n", + " as_dataframe: bool. Return results as pandas DataFrame.\n", + "\n", + " Returns:\n", + " dict or ``pandas.DataFrame``.\n", + "\n", + "\n", + " \"\"\"\n", + " assert isinstance(activity, bd.backends.proxies.Activity)\n", + "\n", + " try:\n", + " similar = [\n", + " obj\n", + " for obj in bd.Database(activity[\"database\"])\n", + " if obj != activity\n", + " and obj.get(\"reference product\") == activity.get(\"reference product\")\n", + " and obj.get(\"name\") == activity[\"name\"]\n", + " and (not locations or obj.get(\"location\") in locations)\n", + " ]\n", + " except KeyError:\n", + " raise ValueError(\"Given activity has no `name`; can't find similar names\")\n", + "\n", + " result = {}\n", + "\n", + " origin_dict = aggregated_dict(activity)\n", + "\n", + " for target in similar:\n", + " target_dict = aggregated_dict(target)\n", + " difference = compare_dictionaries(origin_dict, target_dict, rel_tol, abs_tol)\n", + " if difference:\n", + " if activity not in result:\n", + " result[activity] = {}\n", + " result[activity].update(\n", + " {key: value for key, value in origin_dict.items() if key in difference}\n", + " )\n", + " result[target] = {\n", + " key: value for key, value in target_dict.items() if key in difference\n", + " }\n", + "\n", + " if as_dataframe:\n", + " df = DataFrame(\n", + " [{\"location\": obj.get(\"location\"), **result[obj]} for obj in result]\n", + " )\n", + " df.set_index(\"location\", inplace=True)\n", + " return df\n", + " else:\n", + " return result\n", + "\n", + "\n", + "\n", + "\n", + "def compare_activities_by_lcia_score(activities, lcia_method, band=0.1):\n", + " \"\"\"Compare selected activities to see if they are substantially different.\n", + "\n", + " Substantially different means that all LCIA scores lie within a band of ``band * max_lcia_score``.\n", + "\n", + " Inputs:\n", + "\n", + " ``activities``: List of ``Activity`` objects.\n", + " ``lcia_method``: Tuple identifying a ``Method``\n", + "\n", + " Returns:\n", + "\n", + " Nothing, but prints to stdout.\n", + "\n", + " \"\"\"\n", + " import bw2calc as bc\n", + "\n", + " activities = [bd.get_activity(obj) for obj in activities]\n", + "\n", + " lca = bc.LCA({a: 1 for a in activities}, lcia_method)\n", + " lca.lci()\n", + " lca.lcia()\n", + "\n", + " # First pass: Are all scores close?\n", + " scores = []\n", + "\n", + " for a in activities:\n", + " lca.redo_lcia({a.key: 1})\n", + " scores.append(lca.score)\n", + "\n", + " if abs(max(scores) - min(scores)) < band * abs(max(scores)):\n", + " print(\"All activities similar\")\n", + " return\n", + " else:\n", + " print(\"Differences observed. LCA scores:\")\n", + " for x, y in zip(scores, activities):\n", + " print(\"\\t{:5.3f} -> {}\".format(x, y.key))\n", + "\n", + "\n", + "\n", + "\n", + "def find_leaves(\n", + " activity,\n", + " lcia_method,\n", + " results=None,\n", + " lca_obj=None,\n", + " amount=1,\n", + " total_score=None,\n", + " level=0,\n", + " max_level=3,\n", + " cutoff=2.5e-2,\n", + "):\n", + " \"\"\"Traverse the supply chain of an activity to find leaves - places where the impact of that\n", + " component falls below a threshold value.\n", + "\n", + " Returns a list of ``(impact of this activity, amount consumed, Activity instance)`` tuples.\"\"\"\n", + " first_level = results is None\n", + "\n", + " activity = bd.get_activity(activity)\n", + "\n", + " if first_level:\n", + " level = 0\n", + " results = []\n", + "\n", + " lca_obj = bc.LCA({activity: amount}, lcia_method)\n", + " lca_obj.lci()\n", + " lca_obj.lcia()\n", + " total_score = lca_obj.score\n", + " else:\n", + " lca_obj.redo_lcia({activity.key: amount})\n", + "\n", + " # If this is a leaf, add the leaf and return\n", + " if abs(lca_obj.score) <= abs(total_score * cutoff) or level >= max_level:\n", + "\n", + " # Only add leaves with scores that matter\n", + " if abs(lca_obj.score) > abs(total_score * 1e-4):\n", + " results.append((lca_obj.score, amount, activity))\n", + " return results\n", + "\n", + " else:\n", + " # Add direct emissions from this demand\n", + " direct = (\n", + " lca_obj.characterization_matrix\n", + " * lca_obj.biosphere_matrix\n", + " * lca_obj.demand_array\n", + " ).sum()\n", + " if abs(direct) >= abs(total_score * 1e-4):\n", + " results.append((direct, amount, activity))\n", + "\n", + " for exc in activity.technosphere():\n", + " find_leaves(\n", + " activity=exc.input,\n", + " lcia_method=lcia_method,\n", + " results=results,\n", + " lca_obj=lca_obj,\n", + " amount=amount * exc[\"amount\"],\n", + " total_score=total_score,\n", + " level=level + 1,\n", + " max_level=max_level,\n", + " cutoff=cutoff,\n", + " )\n", + "\n", + " return sorted(results, reverse=True)\n", + "\n", + "\n", + "\n", + "def get_cpc(activity):\n", + " try:\n", + " return next(\n", + " cl[1] for cl in activity.get(\"classifications\", []) if cl[0] == \"CPC\"\n", + " )\n", + " except StopIteration:\n", + " return\n", + "\n", + "\n", + "\n", + "def get_value_for_cpc(lst, label):\n", + " for elem in lst:\n", + " if elem[2] == label:\n", + " return elem[0]\n", + " return 0\n", + "\n", + "\n", + "\n", + "def group_leaves(leaves):\n", + " \"\"\"Group elements in ``leaves`` by their `CPC (Central Product Classification) `__ code.\n", + "\n", + " Returns a list of ``(fraction of total impact, specific impact, amount, Activity instance)`` tuples.\"\"\"\n", + " results = {}\n", + "\n", + " for leaf in leaves:\n", + " cpc = get_cpc(leaf[2])\n", + " if cpc not in results:\n", + " results[cpc] = np.zeros((2,))\n", + " results[cpc] += np.array(leaf[:2])\n", + "\n", + " return sorted([v.tolist() + [k] for k, v in results.items()], reverse=True)\n", + "\n", + "\n", + "\n", + "def compare_activities_by_grouped_leaves(\n", + " activities,\n", + " lcia_method,\n", + " mode=\"relative\",\n", + " max_level=4,\n", + " cutoff=0.2,\n", + " output_format=\"list\",\n", + " str_length=50,\n", + "):\n", + " \"\"\"Compare activities by the impact of their different inputs, aggregated by the product classification of those inputs.\n", + "\n", + " Args:\n", + " activities: list of ``Activity`` instances.\n", + " lcia_method: tuple. LCIA method to use when traversing supply chain graph.\n", + " mode: str. If \"relative\" (default), results are returned as a fraction of total input. Otherwise, results are absolute impact per input exchange.\n", + " max_level: int. Maximum level in supply chain to examine.\n", + " cutoff: float. Fraction of total impact to cutoff supply chain graph traversal at.\n", + " output_format: str. See below.\n", + " str_length; int. If ``output_format`` is ``html``, this controls how many characters each column label can have.\n", + "\n", + " Raises:\n", + " ValueError: ``activities`` is malformed.\n", + "\n", + " Returns:\n", + " Depends on ``output_format``:\n", + "\n", + " * ``list``: Tuple of ``(column labels, data)``\n", + " * ``html``: HTML string that will print nicely in Jupyter notebooks.\n", + " * ``pandas``: a pandas ``DataFrame``.\n", + "\n", + " \"\"\"\n", + " for act in activities:\n", + " if not isinstance(act, bd.backends.peewee.proxies.Activity):\n", + " raise ValueError(\"`activities` must be an iterable of `Activity` instances\")\n", + "\n", + " objs = [\n", + " group_leaves(find_leaves(act, lcia_method, max_level=max_level, cutoff=cutoff))\n", + " for act in activities\n", + " ]\n", + " sorted_keys = sorted(\n", + " [\n", + " (max([el[0] for obj in objs for el in obj if el[2] == key]), key)\n", + " for key in {el[2] for obj in objs for el in obj}\n", + " ],\n", + " reverse=True,\n", + " )\n", + " name_common = commonprefix([act[\"name\"] for act in activities])\n", + "\n", + " if \" \" not in name_common:\n", + " name_common = \"\"\n", + " else:\n", + " last_space = len(name_common) - operator.indexOf(reversed(name_common), \" \")\n", + " name_common = name_common[:last_space]\n", + " print(\"Omitting activity name common prefix: '{}'\".format(name_common))\n", + "\n", + " product_common = commonprefix(\n", + " [act.get(\"reference product\", \"\") for act in activities]\n", + " )\n", + "\n", + " lca = bc.LCA({act: 1 for act in activities}, lcia_method)\n", + " lca.lci()\n", + " lca.lcia()\n", + "\n", + " labels = [\n", + " \"activity\",\n", + " \"product\",\n", + " \"location\",\n", + " \"unit\",\n", + " \"total\",\n", + " \"direct emissions\",\n", + " ] + [key for _, key in sorted_keys]\n", + " data = []\n", + " for act, lst in zip(activities, objs):\n", + " lca.redo_lcia({act.key: 1})\n", + " data.append(\n", + " [\n", + " act[\"name\"].replace(name_common, \"\"),\n", + " act.get(\"reference product\", \"\").replace(product_common, \"\"),\n", + " act.get(\"location\", \"\")[:25],\n", + " act.get(\"unit\", \"\"),\n", + " lca.score,\n", + " ]\n", + " + [\n", + " (\n", + " lca.characterization_matrix\n", + " * lca.biosphere_matrix\n", + " * lca.demand_array\n", + " ).sum()\n", + " ]\n", + " + [get_value_for_cpc(lst, key) for _, key in sorted_keys]\n", + " )\n", + "\n", + " data.sort(key=lambda x: x[4], reverse=True)\n", + "\n", + " if mode == \"relative\":\n", + " for row in data:\n", + " for index, point in enumerate(row[5:]):\n", + " row[index + 5] = point / row[4]\n", + "\n", + " if output_format == \"list\":\n", + " return labels, data\n", + " elif output_format == \"pandas\":\n", + " return pd.DataFrame(data, columns=labels)\n", + " elif output_format == \"html\":\n", + " return tabulate.tabulate(\n", + " data,\n", + " [x[:str_length] for x in labels],\n", + " tablefmt=\"html\",\n", + " floatfmt=\".3f\",\n", + " )\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "def compare_activities_multiple_methods(\n", + " activities_list, methods, identifier, output_format=\"pandas\", mode=\"absolute\"\n", + "):\n", + " \"\"\"\n", + " Compares a set of activities by multiple methods, stores each generated dataframe as a variable (the method is the variable name) in a dictionary.\n", + "\n", + " :param activities_list: List of activities to compare\n", + " :param methods: List of Brightway Method objects\n", + " :param identifier: A string used in defining the variable names to better identify comparisons (e.g. sector name).\n", + " :param output_format: Output format for the comparison (default: 'pandas')\n", + " :param mode: Mode for the comparison (default: 'absolute'; others: 'relative')\n", + " :return: Dictionary of resulting dataframes from the comparisons\n", + " \"\"\"\n", + " dataframes_dict = {}\n", + "\n", + " for method_key, method_details in methods.items():\n", + " result = compare_activities_by_grouped_leaves(\n", + " activities_list,\n", + " method_details[\"object\"].name,\n", + " output_format=output_format,\n", + " mode=mode,\n", + " )\n", + "\n", + " # Create a variable name using the method name tuple and identifier\n", + " method_name = method_details[\"object\"].name[2].replace(\" \", \"_\").lower()\n", + " var_name = f\"{identifier}_{method_name}\"\n", + "\n", + " # add two columns method and method unit to the df\n", + " result[\"method\"] = str(method_details[\"object\"].name[2])\n", + " result[\"method unit\"] = str(method_details[\"object\"].metadata[\"unit\"])\n", + "\n", + " # order the columns after column unit\n", + " cols = list(result.columns)\n", + " unit_index = cols.index(\"unit\")\n", + " cols.insert(unit_index + 1, cols.pop(cols.index(\"method\")))\n", + " cols.insert(unit_index + 2, cols.pop(cols.index(\"method unit\")))\n", + " result = result[cols]\n", + "\n", + " # Order the rows based on 'activity' and 'location' columns\n", + " result = result.sort_values([\"activity\", \"location\"])\n", + "\n", + " # Reset the index numbering\n", + " result = result.reset_index(drop=True)\n", + "\n", + " # Store the result in the dictionary\n", + " dataframes_dict[var_name] = result\n", + "\n", + " return dataframes_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "def small_inputs_to_other_column(dataframes_dict, cutoff=0.01):\n", + " \"\"\"\n", + " Aggregate values into a new 'other' column for those contributing less than or equal to the cutoff value to the 'total' column value.\n", + " Set the aggregated values to zero in their original columns.\n", + " Remove any columns that end up containing only zeros.\n", + "\n", + " :param dataframes_dict: the dictionary\n", + "\n", + " \"\"\"\n", + "\n", + " processed_dict = {}\n", + "\n", + " for key, df in dataframes_dict.items():\n", + " # Identify the 'total' column\n", + " total_col_index = df.columns.get_loc(\"total\")\n", + "\n", + " # Separate string and numeric columns\n", + " string_cols = df.iloc[:, :total_col_index]\n", + " numeric_cols = df.iloc[:, total_col_index:]\n", + " numeric_cols = numeric_cols.astype(float)\n", + "\n", + " # Calculate the threshold for each row (1% of total)\n", + " threshold = numeric_cols[\"total\"] * cutoff\n", + "\n", + " # Create 'other' column\n", + " numeric_cols[\"other\"] = 0.0\n", + "\n", + " # Process each numeric column (except 'total' and 'other')\n", + " for col in numeric_cols.columns[1:-1]: # Skip 'total' and 'other'\n", + " # Identify values less than the threshold\n", + " mask = (\n", + " abs(numeric_cols[col]) < threshold\n", + " ) # abs() to include negative contributions\n", + "\n", + " # Add these values to 'other'\n", + " numeric_cols.loc[mask, \"other\"] += numeric_cols.loc[mask, col]\n", + "\n", + " # Set these values to zero in the original column\n", + " numeric_cols.loc[mask, col] = 0\n", + "\n", + " # Remove columns with all zeros (except 'total' and 'other')\n", + " cols_to_keep = [\"total\"] + [\n", + " col\n", + " for col in numeric_cols.columns[1:-1]\n", + " if not (numeric_cols[col] == 0).all()\n", + " ]\n", + " cols_to_keep.append(\"other\")\n", + "\n", + " numeric_cols = numeric_cols[cols_to_keep]\n", + "\n", + " # Combine string and processed numeric columns\n", + " processed_df = pd.concat([string_cols, numeric_cols], axis=1)\n", + "\n", + " # Sort columns by total\n", + " processed_df = processed_df.sort_values(\"total\", ascending=False)\n", + "\n", + " # Store the processed DataFrame in the result dictionary\n", + " processed_dict[key] = processed_df\n", + "\n", + " return processed_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "def sector_lca_scores(main_dict, method_dict):\n", + " '''\n", + " Generates the LCA score tables for activity list of each sector.\n", + " The tables contain total scores and cpc input contributions.\n", + " This is done by each method defined in the method dictionary.\n", + "\n", + " :param main_dict: dictionary which is returned by process_yaml_files function\n", + " :param method_dict: dictionary which is created with MethodFinder class\n", + "\n", + " It returns the main dictionary updated as scores dictionary which also holds the former information for each sector.\n", + " The LCA scores are stored by method name in the respective sector dictionary within the main dictionary.\n", + " '''\n", + "\n", + " # Initialize scores_dict as a copy of main_dict\n", + " scores_dict = main_dict.copy()\n", + "\n", + " # Loop through each sector in main_dict\n", + " for sector in scores_dict.keys():\n", + " # Extract activities for the current sector\n", + " sector_activities = scores_dict[sector]['activities']\n", + " \n", + " # Calculate LCA scores using the specified method\n", + " lca_scores = compare_activities_multiple_methods(\n", + " activities_list=sector_activities,\n", + " methods=method_dict,\n", + " identifier=sector,\n", + " mode='absolute'\n", + " )\n", + " \n", + " # Apply the small_inputs_to_other_column function with the cutoff value\n", + " lca_scores = small_inputs_to_other_column(lca_scores, cutoff=0.02)\n", + " \n", + " # Save the LCA scores to the scores_dict\n", + " scores_dict[sector]['lca_scores'] = lca_scores\n", + "\n", + " return scores_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Omitting activity name common prefix: 'cement production, '\n", + "Omitting activity name common prefix: 'cement production, '\n", + "Omitting activity name common prefix: 'steel production, electric, '\n", + "Omitting activity name common prefix: 'steel production, electric, '\n" + ] + } + ], + "source": [ + "scores_dict = sector_lca_scores(dictionary_one, method_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Omitting activity name common prefix: 'cement production, '\n", + "Omitting activity name common prefix: 'cement production, '\n", + "Omitting activity name common prefix: 'steel production, electric, '\n", + "Omitting activity name common prefix: 'steel production, electric, '\n" + ] + } + ], + "source": [ + "scores_dict_cutoff = sector_lca_scores(dictionary_one, method_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "index_pos=dopo.sector_lca_scores_to_excel_and_column_positions(scores_dict, 'test_dopo_unnamed_1.xlsx')" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Cement_global_warming_potential_(gwp100)': {'total': 7,\n", + " 'rank': 8,\n", + " 'mean': 9,\n", + " '2std_abv': 10,\n", + " '2std_blw': 11,\n", + " 'q1': 12,\n", + " 'q3': 13,\n", + " 'method': 5,\n", + " 'method unit': 6,\n", + " 'first_input': 14},\n", + " 'Cement_cumulative_energy_demand_-_non-renewable_energy_resources': {'total': 7,\n", + " 'rank': 8,\n", + " 'mean': 9,\n", + " '2std_abv': 10,\n", + " '2std_blw': 11,\n", + " 'q1': 12,\n", + " 'q3': 13,\n", + " 'method': 5,\n", + " 'method unit': 6,\n", + " 'first_input': 14},\n", + " 'Steel_global_warming_potential_(gwp100)': {'total': 7,\n", + " 'rank': 8,\n", + " 'mean': 9,\n", + " '2std_abv': 10,\n", + " '2std_blw': 11,\n", + " 'q1': 12,\n", + " 'q3': 13,\n", + " 'method': 5,\n", + " 'method unit': 6,\n", + " 'first_input': 14},\n", + " 'Steel_cumulative_energy_demand_-_non-renewable_energy_resources': {'total': 7,\n", + " 'rank': 8,\n", + " 'mean': 9,\n", + " '2std_abv': 10,\n", + " '2std_blw': 11,\n", + " 'q1': 12,\n", + " 'q3': 13,\n", + " 'method': 5,\n", + " 'method unit': 6,\n", + " 'first_input': 14}}" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dopo.sector_lca_scores_to_excel_and_column_positions(scores_dict, 'test_dopo_unnamed_2.xlsx')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import dopo.plots_in_xcl\n", + "\n", + "\n", + "current_row=dopo.plots_in_xcl.dot_plots_xcl('test_dopo_3.xlsx', index_pos) #update\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning: No matching key found for worksheet 'Steel_charts'. Skipping...\n", + "Warning: No matching key found for worksheet 'Cement_charts'. Skipping...\n" + ] + } + ], + "source": [ + "dopo.plots_in_xcl.stacked_bars_xcl('test_dopo_3.xlsx', index_pos, current_row)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "comparison of databases" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'function' object has no attribute 'relative_changes_db'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[12], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m dopo\u001b[38;5;241m.\u001b[39mrelative_changes_db\u001b[38;5;241m.\u001b[39mrelative_changes_db(ecoinvent_dict, premise_dict, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mpart2_test_dopo_2\u001b[39m\u001b[38;5;124m'\u001b[39m)\n", + "\u001b[1;31mAttributeError\u001b[0m: 'function' object has no attribute 'relative_changes_db'" + ] + } + ], + "source": [ + "dopo.relative_changes_db.relative_changes_db(ecoinvent_dict, premise_dict, 'part2_test_dopo_2')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dopo.plots_in_xcl.barchart_compare_db_xcl('part2_test_dopo_1')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "premise", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/dev/source code bw2analyzer.py b/dev/source code bw2analyzer.py new file mode 100644 index 0000000..ba18318 --- /dev/null +++ b/dev/source code bw2analyzer.py @@ -0,0 +1,391 @@ +import math +import operator +from os.path import commonprefix + +import bw2calc as bc +import bw2data as bd +import numpy as np +import pandas as pd +import tabulate +from pandas import DataFrame + + + +[docs] +def aggregated_dict(activity): + """Return dictionary of inputs aggregated by input reference product.""" + results = {} + for exc in activity.technosphere(): + results[exc.input["reference product"]] = ( + results.setdefault(exc.input["reference product"], 0) + exc["amount"] + ) + + for exc in activity.biosphere(): + results[exc.input["name"]] = ( + results.setdefault(exc.input["name"], 0) + exc["amount"] + ) + + return results + + + + +[docs] +def compare_dictionaries(one, two, rel_tol=1e-4, abs_tol=1e-9): + """Compare two dictionaries with form ``{str: float}``, and return a set of keys where differences where present. + + Tolerance values are inputs to `math.isclose `__.""" + return ( + set(one) + .symmetric_difference(set(two)) + .union( + { + key + for key in one + if key in two + and not math.isclose( + a=one[key], b=two[key], rel_tol=rel_tol, abs_tol=abs_tol + ) + } + ) + ) + + + + +[docs] +def find_differences_in_inputs( + activity, rel_tol=1e-4, abs_tol=1e-9, locations=None, as_dataframe=False +): + """Given an ``Activity``, try to see if other activities in the same database (with the same name and + reference product) have the same input levels. + + Tolerance values are inputs to `math.isclose `__. + + If differences are present, a difference dictionary is constructed, with the form: + + .. code-block:: python + + {Activity instance: [(name of input flow (str), amount)]} + + Note that this doesn't reference a specific exchange, but rather sums **all exchanges with the same input reference product**. + + Assumes that all similar activities produce the same amount of reference product. + + ``(x, y)``, where ``x`` is the number of similar activities, and ``y`` is a dictionary of the differences. This dictionary is empty if no differences are found. + + Args: + activity: ``Activity``. Activity to analyze. + rel_tol: float. Relative tolerance to decide if two inputs are the same. See above. + abs_tol: float. Absolute tolerance to decide if two inputs are the same. See above. + locations: list, optional. Locations to restrict comparison to, if present. + as_dataframe: bool. Return results as pandas DataFrame. + + Returns: + dict or ``pandas.DataFrame``. + + + """ + assert isinstance(activity, bd.backends.proxies.Activity) + + try: + similar = [ + obj + for obj in bd.Database(activity["database"]) + if obj != activity + and obj.get("reference product") == activity.get("reference product") + and obj.get("name") == activity["name"] + and (not locations or obj.get("location") in locations) + ] + except KeyError: + raise ValueError("Given activity has no `name`; can't find similar names") + + result = {} + + origin_dict = aggregated_dict(activity) + + for target in similar: + target_dict = aggregated_dict(target) + difference = compare_dictionaries(origin_dict, target_dict, rel_tol, abs_tol) + if difference: + if activity not in result: + result[activity] = {} + result[activity].update( + {key: value for key, value in origin_dict.items() if key in difference} + ) + result[target] = { + key: value for key, value in target_dict.items() if key in difference + } + + if as_dataframe: + df = DataFrame( + [{"location": obj.get("location"), **result[obj]} for obj in result] + ) + df.set_index("location", inplace=True) + return df + else: + return result + + + + +[docs] +def compare_activities_by_lcia_score(activities, lcia_method, band=0.1): + """Compare selected activities to see if they are substantially different. + + Substantially different means that all LCIA scores lie within a band of ``band * max_lcia_score``. + + Inputs: + + ``activities``: List of ``Activity`` objects. + ``lcia_method``: Tuple identifying a ``Method`` + + Returns: + + Nothing, but prints to stdout. + + """ + import bw2calc as bc + + activities = [bd.get_activity(obj) for obj in activities] + + lca = bc.LCA({a: 1 for a in activities}, lcia_method) + lca.lci() + lca.lcia() + + # First pass: Are all scores close? + scores = [] + + for a in activities: + lca.redo_lcia({a.id: 1}) + scores.append(lca.score) + + if abs(max(scores) - min(scores)) < band * abs(max(scores)): + print("All activities similar") + return + else: + print("Differences observed. LCA scores:") + for x, y in zip(scores, activities): + print("\t{:5.3f} -> {}".format(x, y.key)) + + + + +[docs] +def find_leaves( + activity, + lcia_method, + results=None, + lca_obj=None, + amount=1, + total_score=None, + level=0, + max_level=3, + cutoff=2.5e-2, +): + """Traverse the supply chain of an activity to find leaves - places where the impact of that + component falls below a threshold value. + + Returns a list of ``(impact of this activity, amount consumed, Activity instance)`` tuples.""" + first_level = results is None + + activity = bd.get_activity(activity) + + if first_level: + level = 0 + results = [] + + lca_obj = bc.LCA({activity: amount}, lcia_method) + lca_obj.lci() + lca_obj.lcia() + total_score = lca_obj.score + else: + lca_obj.redo_lcia({activity.id: amount}) + + # If this is a leaf, add the leaf and return + if abs(lca_obj.score) <= abs(total_score * cutoff) or level >= max_level: + + # Only add leaves with scores that matter + if abs(lca_obj.score) > abs(total_score * 1e-4): + results.append((lca_obj.score, amount, activity)) + return results + + else: + # Add direct emissions from this demand + direct = ( + lca_obj.characterization_matrix + * lca_obj.biosphere_matrix + * lca_obj.demand_array + ).sum() + if abs(direct) >= abs(total_score * 1e-4): + results.append((direct, amount, activity)) + + for exc in activity.technosphere(): + find_leaves( + activity=exc.input, + lcia_method=lcia_method, + results=results, + lca_obj=lca_obj, + amount=amount * exc["amount"], + total_score=total_score, + level=level + 1, + max_level=max_level, + cutoff=cutoff, + ) + + return sorted(results, reverse=True) + + + + +[docs] +def get_cpc(activity): + try: + return next( + cl[1] for cl in activity.get("classifications", []) if cl[0] == "CPC" + ) + except StopIteration: + return + + + + +[docs] +def get_value_for_cpc(lst, label): + for elem in lst: + if elem[2] == label: + return elem[0] + return 0 + + + + +[docs] +def group_leaves(leaves): + """Group elements in ``leaves`` by their `CPC (Central Product Classification) `__ code. + + Returns a list of ``(fraction of total impact, specific impact, amount, Activity instance)`` tuples.""" + results = {} + + for leaf in leaves: + cpc = get_cpc(leaf[2]) + if cpc not in results: + results[cpc] = np.zeros((2,)) + results[cpc] += np.array(leaf[:2]) + + return sorted([v.tolist() + [k] for k, v in results.items()], reverse=True) + + + + +[docs] +def compare_activities_by_grouped_leaves( + activities, + lcia_method, + mode="relative", + max_level=4, + cutoff=0.2, + output_format="list", + str_length=50, +): + """Compare activities by the impact of their different inputs, aggregated by the product classification of those inputs. + + Args: + activities: list of ``Activity`` instances. + lcia_method: tuple. LCIA method to use when traversing supply chain graph. + mode: str. If "relative" (default), results are returned as a fraction of total input. Otherwise, results are absolute impact per input exchange. + max_level: int. Maximum level in supply chain to examine. + cutoff: float. Fraction of total impact to cutoff supply chain graph traversal at. + output_format: str. See below. + str_length; int. If ``output_format`` is ``html``, this controls how many characters each column label can have. + + Raises: + ValueError: ``activities`` is malformed. + + Returns: + Depends on ``output_format``: + + * ``list``: Tuple of ``(column labels, data)`` + * ``html``: HTML string that will print nicely in Jupyter notebooks. + * ``pandas``: a pandas ``DataFrame``. + + """ + for act in activities: + if not isinstance(act, bd.backends.proxies.Activity): + raise ValueError("`activities` must be an iterable of `Activity` instances") + + objs = [ + group_leaves(find_leaves(act, lcia_method, max_level=max_level, cutoff=cutoff)) + for act in activities + ] + sorted_keys = sorted( + [ + (max([el[0] for obj in objs for el in obj if el[2] == key]), key) + for key in {el[2] for obj in objs for el in obj} + ], + reverse=True, + ) + name_common = commonprefix([act["name"] for act in activities]) + + if " " not in name_common: + name_common = "" + else: + last_space = len(name_common) - operator.indexOf(reversed(name_common), " ") + name_common = name_common[:last_space] + print("Omitting activity name common prefix: '{}'".format(name_common)) + + product_common = commonprefix( + [act.get("reference product", "") for act in activities] + ) + + lca = bc.LCA({act: 1 for act in activities}, lcia_method) + lca.lci() + lca.lcia() + + labels = [ + "activity", + "product", + "location", + "unit", + "total", + "direct emissions", + ] + [key for _, key in sorted_keys] + data = [] + for act, lst in zip(activities, objs): + lca.redo_lcia({act.id: 1}) + data.append( + [ + act["name"].replace(name_common, ""), + act.get("reference product", "").replace(product_common, ""), + act.get("location", "")[:25], + act.get("unit", ""), + lca.score, + ] + + [ + ( + lca.characterization_matrix + * lca.biosphere_matrix + * lca.demand_array + ).sum() + ] + + [get_value_for_cpc(lst, key) for _, key in sorted_keys] + ) + + data.sort(key=lambda x: x[4], reverse=True) + + if mode == "relative": + for row in data: + for index, point in enumerate(row[5:]): + row[index + 5] = point / row[4] + + if output_format == "list": + return labels, data + elif output_format == "pandas": + return pd.DataFrame(data, columns=labels) + elif output_format == "html": + return tabulate.tabulate( + data, + [x[:str_length] for x in labels], + tablefmt="html", + floatfmt=".3f", + ) diff --git a/dev/test_charts_v1.xlsx b/dev/test_charts_v1.xlsx deleted file mode 100644 index abd284f..0000000 Binary files a/dev/test_charts_v1.xlsx and /dev/null differ diff --git a/dev/xcl tests/LCA_comparison.xlsx b/dev/xcl tests/LCA_comparison.xlsx new file mode 100644 index 0000000..0391da5 Binary files /dev/null and b/dev/xcl tests/LCA_comparison.xlsx differ diff --git a/dev/xcl tests/LCA_comparison_v11.xlsx b/dev/xcl tests/LCA_comparison_v11.xlsx new file mode 100644 index 0000000..9441d38 Binary files /dev/null and b/dev/xcl tests/LCA_comparison_v11.xlsx differ diff --git a/dev/xcl tests/LCA_comparison_v12.xlsx b/dev/xcl tests/LCA_comparison_v12.xlsx new file mode 100644 index 0000000..b67354b Binary files /dev/null and b/dev/xcl tests/LCA_comparison_v12.xlsx differ diff --git a/dev/xcl tests/compare_tables.xlsx b/dev/xcl tests/compare_tables.xlsx new file mode 100644 index 0000000..40aa27a Binary files /dev/null and b/dev/xcl tests/compare_tables.xlsx differ diff --git a/dev/xcl tests/compare_tables_v7.xlsx b/dev/xcl tests/compare_tables_v7.xlsx new file mode 100644 index 0000000..eda9331 Binary files /dev/null and b/dev/xcl tests/compare_tables_v7.xlsx differ diff --git a/dev/xcl tests/output_v2_2.xlsx b/dev/xcl tests/output_v2_2.xlsx new file mode 100644 index 0000000..58a7596 Binary files /dev/null and b/dev/xcl tests/output_v2_2.xlsx differ diff --git a/dev/xcl tests/output_v52_2.xlsx b/dev/xcl tests/output_v52_2.xlsx new file mode 100644 index 0000000..d3f54e2 Binary files /dev/null and b/dev/xcl tests/output_v52_2.xlsx differ diff --git a/dev/xcl tests/output_v53_2.xlsx b/dev/xcl tests/output_v53_2.xlsx new file mode 100644 index 0000000..fab820c Binary files /dev/null and b/dev/xcl tests/output_v53_2.xlsx differ diff --git a/dev/xcl tests/output_v54_2.xlsx b/dev/xcl tests/output_v54_2.xlsx new file mode 100644 index 0000000..35dd0b1 Binary files /dev/null and b/dev/xcl tests/output_v54_2.xlsx differ diff --git a/dev/xcl tests/test_charts_v1.xlsx b/dev/xcl tests/test_charts_v1.xlsx new file mode 100644 index 0000000..72b7c13 Binary files /dev/null and b/dev/xcl tests/test_charts_v1.xlsx differ diff --git a/dev/xcl tests/test_dopo_unnamed_1.xlsx b/dev/xcl tests/test_dopo_unnamed_1.xlsx new file mode 100644 index 0000000..37088b7 Binary files /dev/null and b/dev/xcl tests/test_dopo_unnamed_1.xlsx differ diff --git a/dev/xcl tests/test_dopo_unnamed_2.xlsx b/dev/xcl tests/test_dopo_unnamed_2.xlsx new file mode 100644 index 0000000..4df5664 Binary files /dev/null and b/dev/xcl tests/test_dopo_unnamed_2.xlsx differ diff --git a/dev/xcl tests/~$output_v2.xlsx b/dev/xcl tests/~$output_v2.xlsx new file mode 100644 index 0000000..ff55ad2 Binary files /dev/null and b/dev/xcl tests/~$output_v2.xlsx differ diff --git a/dopo/activity_filter.py b/dopo/activity_filter.py index 80e7113..ff9425d 100644 --- a/dopo/activity_filter.py +++ b/dopo/activity_filter.py @@ -70,7 +70,7 @@ def _act_fltr( return filters -def generate_sets_from_filters(yaml_filepath, database=None) -> dict: +def generate_sets_from_filters(yaml_filepath, database) -> dict: """ Generate a dictionary with sets of activity names for technologies from the filter specifications. diff --git a/dopo/lca_to_xcl.py b/dopo/lca_to_xcl.py index 5b463f3..9d38265 100644 --- a/dopo/lca_to_xcl.py +++ b/dopo/lca_to_xcl.py @@ -12,7 +12,7 @@ from openpyxl.chart import ScatterChart, Reference, Series from openpyxl.chart import BarChart, Reference -def sector_lca_scores(main_dict, method_dict): +def sector_lca_scores(main_dict, method_dict, cutoff=0.02): ''' Generates the LCA score tables for activity list of each sector. The tables contain total scores and cpc input contributions. @@ -20,6 +20,7 @@ def sector_lca_scores(main_dict, method_dict): :param main_dict: dictionary which is returned by process_yaml_files function :param method_dict: dictionary which is created with MethodFinder class + :param cutoff: cutoff value to summarize inputs below or equal to this threshhold in a "other" column It returns the main dictionary updated as scores dictionary which also holds the former information for each sector. The LCA scores are stored by method name in the respective sector dictionary within the main dictionary. @@ -42,10 +43,10 @@ def sector_lca_scores(main_dict, method_dict): ) # Apply the small_inputs_to_other_column function with the cutoff value - lca_scores = small_inputs_to_other_column(lca_scores, cutoff=0.02) + lca_scores_cut = small_inputs_to_other_column(lca_scores, cutoff) # Save the LCA scores to the scores_dict - scores_dict[sector]['lca_scores'] = lca_scores + scores_dict[sector]['lca_scores'] = lca_scores_cut return scores_dict @@ -120,7 +121,7 @@ def _add_statistics(df, column_name='total'): ''' #Need a rank row to plot the total LCA scores in descending order (satter opepyxl function takes in non categorial values) - df['rank'] = df[column_name].rank(method="first", ascending="False") + df['rank'] = df[column_name].rank(method="first", ascending=False) # Calculate mean, standard deviation, and IQR df['mean'] = df[column_name].mean() diff --git a/dopo/plots.py b/dopo/plots.py index 53a7728..8a42527 100644 --- a/dopo/plots.py +++ b/dopo/plots.py @@ -436,4 +436,4 @@ def generate_distinct_colors(n): """Generate n distinct colors using HSV color space.""" hues = np.linspace(0, 1, n, endpoint=False) colors = [plt.cm.hsv(h) for h in hues] - return colors + return colors \ No newline at end of file diff --git a/dopo/sector_score_dict.py b/dopo/sector_score_dict.py index 51bb47b..f6221bb 100644 --- a/dopo/sector_score_dict.py +++ b/dopo/sector_score_dict.py @@ -83,62 +83,72 @@ def compare_activities_multiple_methods( def small_inputs_to_other_column(dataframes_dict, cutoff=0.01): - """ + ''' Aggregate values into a new 'other' column for those contributing less than or equal to the cutoff value to the 'total' column value. Set the aggregated values to zero in their original columns. Remove any columns that end up containing only zeros. - :param dataframes_dict: the dictionary - - """ + Additionally, if a column is named None or "Unnamed", its values will be added to the 'other' column and then the original column will be deleted. + :param dataframes_dict: the dictionary + ''' + processed_dict = {} for key, df in dataframes_dict.items(): # Identify the 'total' column - total_col_index = df.columns.get_loc("total") - + total_col_index = df.columns.get_loc('total') + # Separate string and numeric columns string_cols = df.iloc[:, :total_col_index] numeric_cols = df.iloc[:, total_col_index:] numeric_cols = numeric_cols.astype(float) - - # Calculate the threshold for each row (1% of total) - threshold = numeric_cols["total"] * cutoff - + + # Calculate the threshold for each row (cutoff% of total) + threshold = numeric_cols['total'] * cutoff + # Create 'other' column - numeric_cols["other"] = 0.0 + numeric_cols['other'] = 0.0 + print(numeric_cols['other']) + # Identify and handle columns that are None or called "Unnamed" + columns_to_remove = [] + for col in df.columns: + if col is None or col == "None" or str(col).startswith("Unnamed"): + numeric_cols['other'] += df[col].fillna(0) + print(numeric_cols['other']) # Add the values to the 'other' column, NaN values to zero to avoid complications of present + columns_to_remove.append(col) + + print(columns_to_remove) + + # Drop the identified columns + numeric_cols.drop(columns=columns_to_remove, inplace=True) # Process each numeric column (except 'total' and 'other') for col in numeric_cols.columns[1:-1]: # Skip 'total' and 'other' # Identify values less than the threshold - mask = ( - abs(numeric_cols[col]) < threshold - ) # abs() to include negative contributions - + mask = abs(numeric_cols[col]) < threshold # abs() to include negative contributions + # Add these values to 'other' - numeric_cols.loc[mask, "other"] += numeric_cols.loc[mask, col] - + numeric_cols.loc[mask, 'other'] += numeric_cols.loc[mask, col] + # Set these values to zero in the original column numeric_cols.loc[mask, col] = 0 - + # Remove columns with all zeros (except 'total' and 'other') - cols_to_keep = ["total"] + [ - col - for col in numeric_cols.columns[1:-1] - if not (numeric_cols[col] == 0).all() - ] - cols_to_keep.append("other") - + cols_to_keep = ['total'] + [col for col in numeric_cols.columns[1:-1] + if not (numeric_cols[col] == 0).all()] + cols_to_keep.append('other') + numeric_cols = numeric_cols[cols_to_keep] - + # Combine string and processed numeric columns processed_df = pd.concat([string_cols, numeric_cols], axis=1) - - # Sort columns by total - processed_df = processed_df.sort_values("total", ascending=False) - + + # Sort DataFrame by total (optional) + processed_df = processed_df.sort_values('total', ascending=False) + # Store the processed DataFrame in the result dictionary processed_dict[key] = processed_df - + return processed_dict + diff --git a/dopo/source code bw2analyzer.py b/dopo/source code bw2analyzer.py new file mode 100644 index 0000000..ba18318 --- /dev/null +++ b/dopo/source code bw2analyzer.py @@ -0,0 +1,391 @@ +import math +import operator +from os.path import commonprefix + +import bw2calc as bc +import bw2data as bd +import numpy as np +import pandas as pd +import tabulate +from pandas import DataFrame + + + +[docs] +def aggregated_dict(activity): + """Return dictionary of inputs aggregated by input reference product.""" + results = {} + for exc in activity.technosphere(): + results[exc.input["reference product"]] = ( + results.setdefault(exc.input["reference product"], 0) + exc["amount"] + ) + + for exc in activity.biosphere(): + results[exc.input["name"]] = ( + results.setdefault(exc.input["name"], 0) + exc["amount"] + ) + + return results + + + + +[docs] +def compare_dictionaries(one, two, rel_tol=1e-4, abs_tol=1e-9): + """Compare two dictionaries with form ``{str: float}``, and return a set of keys where differences where present. + + Tolerance values are inputs to `math.isclose `__.""" + return ( + set(one) + .symmetric_difference(set(two)) + .union( + { + key + for key in one + if key in two + and not math.isclose( + a=one[key], b=two[key], rel_tol=rel_tol, abs_tol=abs_tol + ) + } + ) + ) + + + + +[docs] +def find_differences_in_inputs( + activity, rel_tol=1e-4, abs_tol=1e-9, locations=None, as_dataframe=False +): + """Given an ``Activity``, try to see if other activities in the same database (with the same name and + reference product) have the same input levels. + + Tolerance values are inputs to `math.isclose `__. + + If differences are present, a difference dictionary is constructed, with the form: + + .. code-block:: python + + {Activity instance: [(name of input flow (str), amount)]} + + Note that this doesn't reference a specific exchange, but rather sums **all exchanges with the same input reference product**. + + Assumes that all similar activities produce the same amount of reference product. + + ``(x, y)``, where ``x`` is the number of similar activities, and ``y`` is a dictionary of the differences. This dictionary is empty if no differences are found. + + Args: + activity: ``Activity``. Activity to analyze. + rel_tol: float. Relative tolerance to decide if two inputs are the same. See above. + abs_tol: float. Absolute tolerance to decide if two inputs are the same. See above. + locations: list, optional. Locations to restrict comparison to, if present. + as_dataframe: bool. Return results as pandas DataFrame. + + Returns: + dict or ``pandas.DataFrame``. + + + """ + assert isinstance(activity, bd.backends.proxies.Activity) + + try: + similar = [ + obj + for obj in bd.Database(activity["database"]) + if obj != activity + and obj.get("reference product") == activity.get("reference product") + and obj.get("name") == activity["name"] + and (not locations or obj.get("location") in locations) + ] + except KeyError: + raise ValueError("Given activity has no `name`; can't find similar names") + + result = {} + + origin_dict = aggregated_dict(activity) + + for target in similar: + target_dict = aggregated_dict(target) + difference = compare_dictionaries(origin_dict, target_dict, rel_tol, abs_tol) + if difference: + if activity not in result: + result[activity] = {} + result[activity].update( + {key: value for key, value in origin_dict.items() if key in difference} + ) + result[target] = { + key: value for key, value in target_dict.items() if key in difference + } + + if as_dataframe: + df = DataFrame( + [{"location": obj.get("location"), **result[obj]} for obj in result] + ) + df.set_index("location", inplace=True) + return df + else: + return result + + + + +[docs] +def compare_activities_by_lcia_score(activities, lcia_method, band=0.1): + """Compare selected activities to see if they are substantially different. + + Substantially different means that all LCIA scores lie within a band of ``band * max_lcia_score``. + + Inputs: + + ``activities``: List of ``Activity`` objects. + ``lcia_method``: Tuple identifying a ``Method`` + + Returns: + + Nothing, but prints to stdout. + + """ + import bw2calc as bc + + activities = [bd.get_activity(obj) for obj in activities] + + lca = bc.LCA({a: 1 for a in activities}, lcia_method) + lca.lci() + lca.lcia() + + # First pass: Are all scores close? + scores = [] + + for a in activities: + lca.redo_lcia({a.id: 1}) + scores.append(lca.score) + + if abs(max(scores) - min(scores)) < band * abs(max(scores)): + print("All activities similar") + return + else: + print("Differences observed. LCA scores:") + for x, y in zip(scores, activities): + print("\t{:5.3f} -> {}".format(x, y.key)) + + + + +[docs] +def find_leaves( + activity, + lcia_method, + results=None, + lca_obj=None, + amount=1, + total_score=None, + level=0, + max_level=3, + cutoff=2.5e-2, +): + """Traverse the supply chain of an activity to find leaves - places where the impact of that + component falls below a threshold value. + + Returns a list of ``(impact of this activity, amount consumed, Activity instance)`` tuples.""" + first_level = results is None + + activity = bd.get_activity(activity) + + if first_level: + level = 0 + results = [] + + lca_obj = bc.LCA({activity: amount}, lcia_method) + lca_obj.lci() + lca_obj.lcia() + total_score = lca_obj.score + else: + lca_obj.redo_lcia({activity.id: amount}) + + # If this is a leaf, add the leaf and return + if abs(lca_obj.score) <= abs(total_score * cutoff) or level >= max_level: + + # Only add leaves with scores that matter + if abs(lca_obj.score) > abs(total_score * 1e-4): + results.append((lca_obj.score, amount, activity)) + return results + + else: + # Add direct emissions from this demand + direct = ( + lca_obj.characterization_matrix + * lca_obj.biosphere_matrix + * lca_obj.demand_array + ).sum() + if abs(direct) >= abs(total_score * 1e-4): + results.append((direct, amount, activity)) + + for exc in activity.technosphere(): + find_leaves( + activity=exc.input, + lcia_method=lcia_method, + results=results, + lca_obj=lca_obj, + amount=amount * exc["amount"], + total_score=total_score, + level=level + 1, + max_level=max_level, + cutoff=cutoff, + ) + + return sorted(results, reverse=True) + + + + +[docs] +def get_cpc(activity): + try: + return next( + cl[1] for cl in activity.get("classifications", []) if cl[0] == "CPC" + ) + except StopIteration: + return + + + + +[docs] +def get_value_for_cpc(lst, label): + for elem in lst: + if elem[2] == label: + return elem[0] + return 0 + + + + +[docs] +def group_leaves(leaves): + """Group elements in ``leaves`` by their `CPC (Central Product Classification) `__ code. + + Returns a list of ``(fraction of total impact, specific impact, amount, Activity instance)`` tuples.""" + results = {} + + for leaf in leaves: + cpc = get_cpc(leaf[2]) + if cpc not in results: + results[cpc] = np.zeros((2,)) + results[cpc] += np.array(leaf[:2]) + + return sorted([v.tolist() + [k] for k, v in results.items()], reverse=True) + + + + +[docs] +def compare_activities_by_grouped_leaves( + activities, + lcia_method, + mode="relative", + max_level=4, + cutoff=0.2, + output_format="list", + str_length=50, +): + """Compare activities by the impact of their different inputs, aggregated by the product classification of those inputs. + + Args: + activities: list of ``Activity`` instances. + lcia_method: tuple. LCIA method to use when traversing supply chain graph. + mode: str. If "relative" (default), results are returned as a fraction of total input. Otherwise, results are absolute impact per input exchange. + max_level: int. Maximum level in supply chain to examine. + cutoff: float. Fraction of total impact to cutoff supply chain graph traversal at. + output_format: str. See below. + str_length; int. If ``output_format`` is ``html``, this controls how many characters each column label can have. + + Raises: + ValueError: ``activities`` is malformed. + + Returns: + Depends on ``output_format``: + + * ``list``: Tuple of ``(column labels, data)`` + * ``html``: HTML string that will print nicely in Jupyter notebooks. + * ``pandas``: a pandas ``DataFrame``. + + """ + for act in activities: + if not isinstance(act, bd.backends.proxies.Activity): + raise ValueError("`activities` must be an iterable of `Activity` instances") + + objs = [ + group_leaves(find_leaves(act, lcia_method, max_level=max_level, cutoff=cutoff)) + for act in activities + ] + sorted_keys = sorted( + [ + (max([el[0] for obj in objs for el in obj if el[2] == key]), key) + for key in {el[2] for obj in objs for el in obj} + ], + reverse=True, + ) + name_common = commonprefix([act["name"] for act in activities]) + + if " " not in name_common: + name_common = "" + else: + last_space = len(name_common) - operator.indexOf(reversed(name_common), " ") + name_common = name_common[:last_space] + print("Omitting activity name common prefix: '{}'".format(name_common)) + + product_common = commonprefix( + [act.get("reference product", "") for act in activities] + ) + + lca = bc.LCA({act: 1 for act in activities}, lcia_method) + lca.lci() + lca.lcia() + + labels = [ + "activity", + "product", + "location", + "unit", + "total", + "direct emissions", + ] + [key for _, key in sorted_keys] + data = [] + for act, lst in zip(activities, objs): + lca.redo_lcia({act.id: 1}) + data.append( + [ + act["name"].replace(name_common, ""), + act.get("reference product", "").replace(product_common, ""), + act.get("location", "")[:25], + act.get("unit", ""), + lca.score, + ] + + [ + ( + lca.characterization_matrix + * lca.biosphere_matrix + * lca.demand_array + ).sum() + ] + + [get_value_for_cpc(lst, key) for _, key in sorted_keys] + ) + + data.sort(key=lambda x: x[4], reverse=True) + + if mode == "relative": + for row in data: + for index, point in enumerate(row[5:]): + row[index + 5] = point / row[4] + + if output_format == "list": + return labels, data + elif output_format == "pandas": + return pd.DataFrame(data, columns=labels) + elif output_format == "html": + return tabulate.tabulate( + data, + [x[:str_length] for x in labels], + tablefmt="html", + floatfmt=".3f", + ) diff --git a/dopo/test-1.ipynb b/dopo/test-1.ipynb new file mode 100644 index 0000000..911773c --- /dev/null +++ b/dopo/test-1.ipynb @@ -0,0 +1,903 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import brightway2 as bw\n", + "import bw2data as bd\n", + "import bw2analyzer as ba\n", + "\n", + "#reduce?\n", + "import ast\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "import dopo\n", + "from dopo import*" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Biosphere database already present!!! No setup is needed\n" + ] + } + ], + "source": [ + "bd.projects.set_current(\"premise-validation-try1\")\n", + "bw.bw2setup()\n", + "\n", + "bio3=bw.Database('biosphere3')\n", + "ei39=bw.Database('ecoinvent 3.9.1 cutoff')\n", + "ei39SSP2=bw.Database('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "FILTERS" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "#sector filters file names/paths\n", + "\n", + "cement = 'cement_small.yaml'\n", + "electricity = 'electricity_small.yaml'\n", + "fuels= 'fuels_small.yaml'\n", + "steel = 'steel_small.yaml'\n", + "transport = 'transport_small.yaml'" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Cement': {'yaml': 'yamls\\\\cement_small.yaml', 'yaml identifier': 'Cement'},\n", + " 'Steel': {'yaml': 'yamls\\\\steel_small.yaml', 'yaml identifier': 'Steel'}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "files_dict={}\n", + "files_dict['Cement']={'yaml': 'yamls\\cement_small.yaml', \n", + " 'yaml identifier': 'Cement'}\n", + "files_dict['Steel']= {'yaml':'yamls\\steel_small.yaml',\n", + " 'yaml identifier': 'Steel'} #yaml identifier is the name of the filter in the yaml file, in the first line.\n", + "files_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing Cement with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Cement:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '86841f8c7ee2668f244d3b8e34f41932')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '3c16b45db40210cd97de6574b2f47aaf')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'fcb666edf2a01467e555eeff5b4a5bbb')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'df49e8f525497f2fbd56bcdc80ff0cde')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'f8b84f45f50d3bd7ff4feaabdb493f6a')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '36a53c174f34e672bc15b7e55563685e')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'a3c2064d83411f7963af550c04c869a1')\n", + "Processing Steel with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Steel:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '2baa0deb3adc89dfe8cb89d5e078ba8d')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'af6bd1221fc0206541fbaf481397bf0d')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '18b0dcf01dd401e1549b3796e3786213')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '1dffacc9e0ca08fb55c6b780d7e677dc')\n", + "Processing Cement with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Cement:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '86841f8c7ee2668f244d3b8e34f41932')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '3c16b45db40210cd97de6574b2f47aaf')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'df49e8f525497f2fbd56bcdc80ff0cde')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'f8b84f45f50d3bd7ff4feaabdb493f6a')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'fcb666edf2a01467e555eeff5b4a5bbb')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '36a53c174f34e672bc15b7e55563685e')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'a3c2064d83411f7963af550c04c869a1')\n", + "Processing Steel with database ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27\n", + "Activities for Steel:\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '2baa0deb3adc89dfe8cb89d5e078ba8d')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', 'af6bd1221fc0206541fbaf481397bf0d')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '18b0dcf01dd401e1549b3796e3786213')\n", + " ('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27', '1dffacc9e0ca08fb55c6b780d7e677dc')\n", + "Processing Cement with database ecoinvent 3.9.1 cutoff\n", + "Activities for Cement:\n", + " ('ecoinvent 3.9.1 cutoff', 'fcb666edf2a01467e555eeff5b4a5bbb')\n", + " ('ecoinvent 3.9.1 cutoff', 'f8b84f45f50d3bd7ff4feaabdb493f6a')\n", + " ('ecoinvent 3.9.1 cutoff', '3c16b45db40210cd97de6574b2f47aaf')\n", + " ('ecoinvent 3.9.1 cutoff', 'df49e8f525497f2fbd56bcdc80ff0cde')\n", + " ('ecoinvent 3.9.1 cutoff', '36a53c174f34e672bc15b7e55563685e')\n", + " ('ecoinvent 3.9.1 cutoff', 'a3c2064d83411f7963af550c04c869a1')\n", + " ('ecoinvent 3.9.1 cutoff', '86841f8c7ee2668f244d3b8e34f41932')\n", + "Processing Steel with database ecoinvent 3.9.1 cutoff\n", + "Activities for Steel:\n", + " ('ecoinvent 3.9.1 cutoff', '18b0dcf01dd401e1549b3796e3786213')\n", + " ('ecoinvent 3.9.1 cutoff', '2baa0deb3adc89dfe8cb89d5e078ba8d')\n", + " ('ecoinvent 3.9.1 cutoff', 'af6bd1221fc0206541fbaf481397bf0d')\n", + " ('ecoinvent 3.9.1 cutoff', '1dffacc9e0ca08fb55c6b780d7e677dc')\n" + ] + } + ], + "source": [ + "import dopo.filter_sectors\n", + "\n", + "#for plot 1 and 2\n", + "dictionary_one = dopo.filter_sectors.process_yaml_files(files_dict, ei39SSP2)\n", + "\n", + "#for comparison\n", + "premise_dict = dopo.filter_sectors.process_yaml_files(files_dict, ei39SSP2)\n", + "ecoinvent_dict = dopo.filter_sectors.process_yaml_files(files_dict, ei39)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'method_1': {'object': Brightway2 Method: IPCC 2013: climate change: global warming potential (GWP100),\n", + " 'method name': ('IPCC 2013',\n", + " 'climate change',\n", + " 'global warming potential (GWP100)'),\n", + " 'short name': 'global warming potential (GWP100)',\n", + " 'unit': 'kg CO2-Eq'},\n", + " 'method_2': {'object': Brightway2 Method: EN15804: inventory indicators ISO21930: Cumulative Energy Demand - non-renewable energy resources,\n", + " 'method name': ('EN15804',\n", + " 'inventory indicators ISO21930',\n", + " 'Cumulative Energy Demand - non-renewable energy resources'),\n", + " 'short name': 'Cumulative Energy Demand - non-renewable energy resources',\n", + " 'unit': 'megajoule'}}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "finder=dopo.methods.MethodFinder()\n", + "\n", + "finder.find_and_create_method(criteria=['IPCC', '2013', 'GWP100'], exclude=['no LT'])\n", + "finder.find_and_create_method(criteria=['EN15804','Cumulative', 'non-renewable' ])\n", + "#finder.find_and_create_method(criteria=['land occupation','selected'])\n", + "#finder.find_and_create_method(criteria=['EN15804','fresh water'])\n", + "method_dict=finder.get_all_methods()\n", + "method_dict" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "LCA Tables" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Omitting activity name common prefix: 'cement production, '\n", + "Omitting activity name common prefix: 'cement production, '\n", + "0 0.0\n", + "1 0.0\n", + "2 0.0\n", + "3 0.0\n", + "4 0.0\n", + "5 0.0\n", + "6 0.0\n", + "Name: other, dtype: float64\n", + "0 0.008358\n", + "1 0.006612\n", + "2 0.006691\n", + "3 0.003714\n", + "4 0.006260\n", + "5 0.003002\n", + "6 0.005879\n", + "Name: other, dtype: float64\n", + "[None]\n", + "0 0.0\n", + "1 0.0\n", + "2 0.0\n", + "3 0.0\n", + "4 0.0\n", + "5 0.0\n", + "6 0.0\n", + "Name: other, dtype: float64\n", + "0 0.083118\n", + "1 0.109749\n", + "2 0.272431\n", + "3 0.141921\n", + "4 0.114098\n", + "5 0.104153\n", + "6 0.292700\n", + "Name: other, dtype: float64\n", + "[None]\n", + "Omitting activity name common prefix: 'steel production, electric, '\n", + "Omitting activity name common prefix: 'steel production, electric, '\n", + "0 0.0\n", + "1 0.0\n", + "2 0.0\n", + "3 0.0\n", + "Name: other, dtype: float64\n", + "0 -0.031801\n", + "1 -0.004385\n", + "2 -0.032627\n", + "3 0.018912\n", + "Name: other, dtype: float64\n", + "[None]\n", + "0 0.0\n", + "1 0.0\n", + "2 0.0\n", + "3 0.0\n", + "Name: other, dtype: float64\n", + "0 0.342651\n", + "1 0.360257\n", + "2 0.390817\n", + "3 1.127125\n", + "Name: other, dtype: float64\n", + "[None]\n" + ] + } + ], + "source": [ + "scores_dictionary_one = dopo.sector_lca_scores(dictionary_one, method_dict, cutoff=0.01)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityproductlocationunitmethodmethod unittotaldirect emissions41116: Ferrous products obtained by direct reduction of iron ore and other spongy ferrous products, in lumps, pellets[…]17300: Steam and hot water...41115: Other ferro-alloys543: Site preparation services14220: Nickel ores and concentrates39310: Slag, dross, scalings and other waste from the manufacture of iron or steel41431: Unwrought aluminium531: Buildings53290: Other civil engineering works31230: Wood in chips or particles33370: Fuel oils n.e.c.other
3low-alloyedINkilogramglobal warming potential (GWP100)kg CO2-Eq1.9733870.0894251.0655580.113106...0.0241680.0000000.0000000.0000000.000000.0000000.0000000.00000.0000000.151441
1low-alloyedCA-QCkilogramglobal warming potential (GWP100)kg CO2-Eq0.6849170.0420000.0000000.168680...0.0000000.0150910.0122390.0090250.010980.0000000.0078030.00780.0000000.059272
2low-alloyedCHkilogramglobal warming potential (GWP100)kg CO2-Eq0.3368250.0712750.0000000.021566...0.0000000.0121420.0000000.0000000.000000.0092340.0000000.00000.004835-0.009858
0low-alloyedATkilogramglobal warming potential (GWP100)kg CO2-Eq0.1867540.0533180.0000000.016080...0.0000000.0000000.000000-0.0042550.000000.0029810.0000000.00000.003300-0.018065
\n", + "

4 rows × 33 columns

\n", + "
" + ], + "text/plain": [ + " activity product location unit method \\\n", + "3 low-alloyed IN kilogram global warming potential (GWP100) \n", + "1 low-alloyed CA-QC kilogram global warming potential (GWP100) \n", + "2 low-alloyed CH kilogram global warming potential (GWP100) \n", + "0 low-alloyed AT kilogram global warming potential (GWP100) \n", + "\n", + " method unit total direct emissions \\\n", + "3 kg CO2-Eq 1.973387 0.089425 \n", + "1 kg CO2-Eq 0.684917 0.042000 \n", + "2 kg CO2-Eq 0.336825 0.071275 \n", + "0 kg CO2-Eq 0.186754 0.053318 \n", + "\n", + " 41116: Ferrous products obtained by direct reduction of iron ore and other spongy ferrous products, in lumps, pellets[…] \\\n", + "3 1.065558 \n", + "1 0.000000 \n", + "2 0.000000 \n", + "0 0.000000 \n", + "\n", + " 17300: Steam and hot water ... 41115: Other ferro-alloys \\\n", + "3 0.113106 ... 0.024168 \n", + "1 0.168680 ... 0.000000 \n", + "2 0.021566 ... 0.000000 \n", + "0 0.016080 ... 0.000000 \n", + "\n", + " 543: Site preparation services 14220: Nickel ores and concentrates \\\n", + "3 0.000000 0.000000 \n", + "1 0.015091 0.012239 \n", + "2 0.012142 0.000000 \n", + "0 0.000000 0.000000 \n", + "\n", + " 39310: Slag, dross, scalings and other waste from the manufacture of iron or steel \\\n", + "3 0.000000 \n", + "1 0.009025 \n", + "2 0.000000 \n", + "0 -0.004255 \n", + "\n", + " 41431: Unwrought aluminium 531: Buildings \\\n", + "3 0.00000 0.000000 \n", + "1 0.01098 0.000000 \n", + "2 0.00000 0.009234 \n", + "0 0.00000 0.002981 \n", + "\n", + " 53290: Other civil engineering works 31230: Wood in chips or particles \\\n", + "3 0.000000 0.0000 \n", + "1 0.007803 0.0078 \n", + "2 0.000000 0.0000 \n", + "0 0.000000 0.0000 \n", + "\n", + " 33370: Fuel oils n.e.c. other \n", + "3 0.000000 0.151441 \n", + "1 0.000000 0.059272 \n", + "2 0.004835 -0.009858 \n", + "0 0.003300 -0.018065 \n", + "\n", + "[4 rows x 33 columns]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scores_dictionary_one['Steel']['lca_scores']['Steel_global_warming_potential_(gwp100)']" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
activityproductlocationunitmethodmethod unittotal37430: Cement clinkers33: Coke oven products; refined petroleum products; nuclear fuel6511: Road transport services of freight11010: Hard coal12020: Natural gas, liquefied or in the gaseous state17300: Steam and hot water34510: Wood charcoal12010: Petroleum oils and oils obtained from bituminous minerals, crude15200: Gypsum; anhydrite; limestone flux; limestone and other calcareous stone, of a kind used for the manufacture of[…]other
4PortlandPEkilogramglobal warming potential (GWP100)kg CO2-Eq0.8694330.8247940.0000000.0000000.0000000.0156990.000000.0000000.0000000.0000000.028290
1PortlandCA-QCkilogramglobal warming potential (GWP100)kg CO2-Eq0.8418550.7815890.0000000.0000000.0090850.0000000.000000.0000000.0113800.0000000.038992
0PortlandBRkilogramglobal warming potential (GWP100)kg CO2-Eq0.8323350.7424140.0438550.0000000.0000000.0000000.000000.0142780.0000000.0000000.031024
5PortlandUSkilogramglobal warming potential (GWP100)kg CO2-Eq0.8212780.7591660.0000000.0000000.0000000.0000000.014680.0000000.0109020.0000000.035653
6PortlandZAkilogramglobal warming potential (GWP100)kg CO2-Eq0.8147680.7586330.0000000.0270840.0000000.0000000.000000.0000000.0000000.0000000.028688
3PortlandINkilogramglobal warming potential (GWP100)kg CO2-Eq0.7740790.7183960.0081600.0000000.0160890.0000000.000000.0000000.0113000.0086050.011077
2PortlandCHkilogramglobal warming potential (GWP100)kg CO2-Eq0.7377340.7012630.0000000.0000000.0092930.0000000.000000.0000000.0000000.0000000.026736
\n", + "
" + ], + "text/plain": [ + " activity product location unit method \\\n", + "4 Portland PE kilogram global warming potential (GWP100) \n", + "1 Portland CA-QC kilogram global warming potential (GWP100) \n", + "0 Portland BR kilogram global warming potential (GWP100) \n", + "5 Portland US kilogram global warming potential (GWP100) \n", + "6 Portland ZA kilogram global warming potential (GWP100) \n", + "3 Portland IN kilogram global warming potential (GWP100) \n", + "2 Portland CH kilogram global warming potential (GWP100) \n", + "\n", + " method unit total 37430: Cement clinkers \\\n", + "4 kg CO2-Eq 0.869433 0.824794 \n", + "1 kg CO2-Eq 0.841855 0.781589 \n", + "0 kg CO2-Eq 0.832335 0.742414 \n", + "5 kg CO2-Eq 0.821278 0.759166 \n", + "6 kg CO2-Eq 0.814768 0.758633 \n", + "3 kg CO2-Eq 0.774079 0.718396 \n", + "2 kg CO2-Eq 0.737734 0.701263 \n", + "\n", + " 33: Coke oven products; refined petroleum products; nuclear fuel \\\n", + "4 0.000000 \n", + "1 0.000000 \n", + "0 0.043855 \n", + "5 0.000000 \n", + "6 0.000000 \n", + "3 0.008160 \n", + "2 0.000000 \n", + "\n", + " 6511: Road transport services of freight 11010: Hard coal \\\n", + "4 0.000000 0.000000 \n", + "1 0.000000 0.009085 \n", + "0 0.000000 0.000000 \n", + "5 0.000000 0.000000 \n", + "6 0.027084 0.000000 \n", + "3 0.000000 0.016089 \n", + "2 0.000000 0.009293 \n", + "\n", + " 12020: Natural gas, liquefied or in the gaseous state \\\n", + "4 0.015699 \n", + "1 0.000000 \n", + "0 0.000000 \n", + "5 0.000000 \n", + "6 0.000000 \n", + "3 0.000000 \n", + "2 0.000000 \n", + "\n", + " 17300: Steam and hot water 34510: Wood charcoal \\\n", + "4 0.00000 0.000000 \n", + "1 0.00000 0.000000 \n", + "0 0.00000 0.014278 \n", + "5 0.01468 0.000000 \n", + "6 0.00000 0.000000 \n", + "3 0.00000 0.000000 \n", + "2 0.00000 0.000000 \n", + "\n", + " 12010: Petroleum oils and oils obtained from bituminous minerals, crude \\\n", + "4 0.000000 \n", + "1 0.011380 \n", + "0 0.000000 \n", + "5 0.010902 \n", + "6 0.000000 \n", + "3 0.011300 \n", + "2 0.000000 \n", + "\n", + " 15200: Gypsum; anhydrite; limestone flux; limestone and other calcareous stone, of a kind used for the manufacture of[…] \\\n", + "4 0.000000 \n", + "1 0.000000 \n", + "0 0.000000 \n", + "5 0.000000 \n", + "6 0.000000 \n", + "3 0.008605 \n", + "2 0.000000 \n", + "\n", + " other \n", + "4 0.028290 \n", + "1 0.038992 \n", + "0 0.031024 \n", + "5 0.035653 \n", + "6 0.028688 \n", + "3 0.011077 \n", + "2 0.026736 " + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scores_dictionary_one['Cement']['lca_scores']['Cement_global_warming_potential_(gwp100)']" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "17" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(scores_dictionary_one['Cement']['lca_scores']['Cement_global_warming_potential_(gwp100)'].columns)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "df = scores_dictionary_one['Cement']['lca_scores']['Cement_global_warming_potential_(gwp100)']" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(type(df.columns[-2]))" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "index_pos=dopo.sector_lca_scores_to_excel_and_column_positions(scores_dictionary_one, 'test_dopo_7.xlsx')" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "import dopo.plots_in_xcl\n", + "\n", + "\n", + "current_row=dopo.plots_in_xcl.dot_plots_xcl('test_dopo_7.xlsx', index_pos) #update\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning: No matching key found for worksheet 'Steel_charts'. Skipping...\n", + "Warning: No matching key found for worksheet 'Cement_charts'. Skipping...\n" + ] + }, + { + "ename": "PermissionError", + "evalue": "[Errno 13] Permission denied: 'test_dopo_6.xlsx'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mPermissionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[16], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m dopo\u001b[38;5;241m.\u001b[39mplots_in_xcl\u001b[38;5;241m.\u001b[39mstacked_bars_xcl(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mtest_dopo_6.xlsx\u001b[39m\u001b[38;5;124m'\u001b[39m, index_pos, current_row)\n", + "File \u001b[1;32m~\\premise_validation\\dopo\\plots_in_xcl.py:319\u001b[0m, in \u001b[0;36mstacked_bars_xcl\u001b[1;34m(filepath_workbook, index_positions, current_row_dot_plot)\u001b[0m\n\u001b[0;32m 316\u001b[0m wb\u001b[38;5;241m.\u001b[39m_sheets\u001b[38;5;241m.\u001b[39mremove(ws_charts)\n\u001b[0;32m 317\u001b[0m wb\u001b[38;5;241m.\u001b[39m_sheets\u001b[38;5;241m.\u001b[39minsert(\u001b[38;5;241m0\u001b[39m, ws_charts)\n\u001b[1;32m--> 319\u001b[0m wb\u001b[38;5;241m.\u001b[39msave(filepath_workbook)\n", + "File \u001b[1;32mc:\\Users\\fried\\miniconda3\\envs\\premise\\Lib\\site-packages\\openpyxl\\workbook\\workbook.py:386\u001b[0m, in \u001b[0;36mWorkbook.save\u001b[1;34m(self, filename)\u001b[0m\n\u001b[0;32m 384\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mwrite_only \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mworksheets:\n\u001b[0;32m 385\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcreate_sheet()\n\u001b[1;32m--> 386\u001b[0m save_workbook(\u001b[38;5;28mself\u001b[39m, filename)\n", + "File \u001b[1;32mc:\\Users\\fried\\miniconda3\\envs\\premise\\Lib\\site-packages\\openpyxl\\writer\\excel.py:291\u001b[0m, in \u001b[0;36msave_workbook\u001b[1;34m(workbook, filename)\u001b[0m\n\u001b[0;32m 279\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21msave_workbook\u001b[39m(workbook, filename):\n\u001b[0;32m 280\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Save the given workbook on the filesystem under the name filename.\u001b[39;00m\n\u001b[0;32m 281\u001b[0m \n\u001b[0;32m 282\u001b[0m \u001b[38;5;124;03m :param workbook: the workbook to save\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 289\u001b[0m \n\u001b[0;32m 290\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[1;32m--> 291\u001b[0m archive \u001b[38;5;241m=\u001b[39m ZipFile(filename, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mw\u001b[39m\u001b[38;5;124m'\u001b[39m, ZIP_DEFLATED, allowZip64\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[0;32m 292\u001b[0m workbook\u001b[38;5;241m.\u001b[39mproperties\u001b[38;5;241m.\u001b[39mmodified \u001b[38;5;241m=\u001b[39m datetime\u001b[38;5;241m.\u001b[39mdatetime\u001b[38;5;241m.\u001b[39mnow(tz\u001b[38;5;241m=\u001b[39mdatetime\u001b[38;5;241m.\u001b[39mtimezone\u001b[38;5;241m.\u001b[39mutc)\u001b[38;5;241m.\u001b[39mreplace(tzinfo\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[0;32m 293\u001b[0m writer \u001b[38;5;241m=\u001b[39m ExcelWriter(workbook, archive)\n", + "File \u001b[1;32mc:\\Users\\fried\\miniconda3\\envs\\premise\\Lib\\zipfile.py:1294\u001b[0m, in \u001b[0;36mZipFile.__init__\u001b[1;34m(self, file, mode, compression, allowZip64, compresslevel, strict_timestamps, metadata_encoding)\u001b[0m\n\u001b[0;32m 1292\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m 1293\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m-> 1294\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfp \u001b[38;5;241m=\u001b[39m io\u001b[38;5;241m.\u001b[39mopen(file, filemode)\n\u001b[0;32m 1295\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m:\n\u001b[0;32m 1296\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m filemode \u001b[38;5;129;01min\u001b[39;00m modeDict:\n", + "\u001b[1;31mPermissionError\u001b[0m: [Errno 13] Permission denied: 'test_dopo_6.xlsx'" + ] + } + ], + "source": [ + "dopo.plots_in_xcl.stacked_bars_xcl('test_dopo_6.xlsx', index_pos, current_row)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "premise", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/dopo/test_dopo_5.xlsx b/dopo/test_dopo_5.xlsx new file mode 100644 index 0000000..287881d Binary files /dev/null and b/dopo/test_dopo_5.xlsx differ diff --git a/dopo/test_dopo_6.xlsx b/dopo/test_dopo_6.xlsx new file mode 100644 index 0000000..daa17e9 Binary files /dev/null and b/dopo/test_dopo_6.xlsx differ diff --git a/dopo/test_dopo_7.xlsx b/dopo/test_dopo_7.xlsx new file mode 100644 index 0000000..d5062d8 Binary files /dev/null and b/dopo/test_dopo_7.xlsx differ diff --git a/dopo/yamls/cement_concrete.yaml b/dopo/yamls/cement_concrete.yaml new file mode 100644 index 0000000..50367e1 --- /dev/null +++ b/dopo/yamls/cement_concrete.yaml @@ -0,0 +1,218 @@ +Cement: + ecoinvent_aliases: + fltr: + name: cement production + reference product: cement + mask: + name: + - carbon dioxide + - new alternative + reference product: + - slag + - nickel + - hard coal ash + +Concrete: + ecoinvent_aliases: + fltr: + name: concrete production + reference product: concrete + + mask: + name: + - treatment + - market + - fibre + reference product: + - waste + +Steel: + ecoinvent_aliases: + fltr: + name: steel production + reference product: steel + + mask: + reference product: + - heat, district or industrial, other than natural gas + - vanadium + +Electricity production all: + ecoinvent_aliases: + fltr: + name: electricity production + +Electricity production fossil: + ecoinvent_aliases: + fltr: + - electricity production + mask: + name: + - biomass + - biomethane + - hydro + - geothermal + - photovoltaic + - solar + - wind + - wood + - nuclear + - Evolutionary + - refinery operation + - compressed air + - aluminium industry + +Electricity production renewables: + ecoinvent_aliases: + fltr: + name: electricity production + + mask: + name: + - coal + - lignite + - natural gas + - oil + - nuclear + - Evolutionary + - Reactor + - petroleum + +Electricity production nuclear: + ecoinvent_aliases: + fltr: + name: electricity production + + mask: + name: + - coal + - lignite + - natural gas + - oil + - hydro + - wind + - solar + - photovoltaic + - geothermal + - wood + - biomass + - wave energy + - biomethane + - petroleum + - compressed air + - aluminium industry + - peat + +Electricity production biomass: + ecoinvent_aliases: + fltr: + name: + - electricity production + - biomass + +Electricity production biomethane: + ecoinvent_aliases: + fltr: + name: + - electricity production + - biomethane + +Electricity production hydro: + ecoinvent_aliases: + fltr: + name: + - electricity production + - hydro + mask: + name: hydrogen-fired + +Electricity production geothermal: + ecoinvent_aliases: + fltr: + name: + - electricity production + - geothermal + +Electricity production photovoltaic: + ecoinvent_aliases: + fltr: + name: + - electricity production + - photovoltaic + +Electricity production solar: + ecoinvent_aliases: + fltr: + name: + - electricity production + - solar + +Electricity production wind: + ecoinvent_aliases: + fltr: + name: + - electricity production + - wind + +Electricity production wood: + ecoinvent_aliases: + fltr: + name: + - electricity production + - wood + +Electricity production wave energy: + ecoinvent_aliases: + fltr: + name: + - electricity production + - wave energy + +Electricity production coal: + ecoinvent_aliases: + fltr: + name: + - electricity production + - coal + mask: + reference product: internal use in coal mining + +Electricity production lignite: + ecoinvent_aliases: + fltr: + name: + - electricity production + - lignite + +Electricity production natural gas: + ecoinvent_aliases: + fltr: + name: + - electricity production + - natural gas + mask: + name: hydrogen + + +Electricity production oil: + ecoinvent_aliases: + fltr: + name: + - electricity production + - oil + mask: + name: nuclear + +Electricity production petroleum: + ecoinvent_aliases: + fltr: + name: + - electricity production + - petroleum + + + + + + + diff --git a/dopo/yamls/cement_concrete_steel.yaml b/dopo/yamls/cement_concrete_steel.yaml new file mode 100644 index 0000000..ee3f0a2 --- /dev/null +++ b/dopo/yamls/cement_concrete_steel.yaml @@ -0,0 +1,39 @@ +Cement: + ecoinvent_aliases: + fltr: + name: cement production + reference product: cement + mask: + name: + - carbon dioxide + - new alternative + reference product: + - slag + - nickel + - hard coal ash + +Concrete: + ecoinvent_aliases: + fltr: + name: concrete production + reference product: concrete + + mask: + name: + - treatment + - market + - fibre + reference product: + - waste + +Steel: + ecoinvent_aliases: + fltr: + name: steel production + reference product: steel + + mask: + reference product: + - heat, district or industrial, other than natural gas + - vanadium + diff --git a/dopo/yamls/cement_small.yaml b/dopo/yamls/cement_small.yaml new file mode 100644 index 0000000..22294f4 --- /dev/null +++ b/dopo/yamls/cement_small.yaml @@ -0,0 +1,13 @@ +Cement: + ecoinvent_aliases: + fltr: + name: + - cement production + - Portland + mask: + name: + - Slag + - Pozzolana + location: + - RoW + - Europe without Switzerland \ No newline at end of file diff --git a/dopo/yamls/electricity.yaml b/dopo/yamls/electricity.yaml new file mode 100644 index 0000000..afab6fa --- /dev/null +++ b/dopo/yamls/electricity.yaml @@ -0,0 +1,179 @@ +Electricity production all: + ecoinvent_aliases: + fltr: + name: electricity production + +Electricity production fossil: + ecoinvent_aliases: + fltr: + - electricity production + mask: + name: + - biomass + - biomethane + - hydro + - geothermal + - photovoltaic + - solar + - wind + - wood + - nuclear + - Evolutionary + - refinery operation + - compressed air + - aluminium industry + +Electricity production renewables: + ecoinvent_aliases: + fltr: + name: electricity production + + mask: + name: + - coal + - lignite + - natural gas + - oil + - nuclear + - Evolutionary + - Reactor + - petroleum + +Electricity production nuclear: + ecoinvent_aliases: + fltr: + name: electricity production + + mask: + name: + - coal + - lignite + - natural gas + - oil + - hydro + - wind + - solar + - photovoltaic + - geothermal + - wood + - biomass + - wave energy + - biomethane + - petroleum + - compressed air + - aluminium industry + - peat + +Electricity production biomass: + ecoinvent_aliases: + fltr: + name: + - electricity production + - biomass + +Electricity production biomethane: + ecoinvent_aliases: + fltr: + name: + - electricity production + - biomethane + +Electricity production hydro: + ecoinvent_aliases: + fltr: + name: + - electricity production + - hydro + mask: + name: hydrogen-fired + +Electricity production geothermal: + ecoinvent_aliases: + fltr: + name: + - electricity production + - geothermal + +Electricity production photovoltaic: + ecoinvent_aliases: + fltr: + name: + - electricity production + - photovoltaic + +Electricity production solar: + ecoinvent_aliases: + fltr: + name: + - electricity production + - solar + +Electricity production wind: + ecoinvent_aliases: + fltr: + name: + - electricity production + - wind + +Electricity production wood: + ecoinvent_aliases: + fltr: + name: + - electricity production + - wood + +Electricity production wave energy: + ecoinvent_aliases: + fltr: + name: + - electricity production + - wave energy + +Electricity production coal: + ecoinvent_aliases: + fltr: + name: + - electricity production + - coal + mask: + reference product: internal use in coal mining + +Electricity production lignite: + ecoinvent_aliases: + fltr: + name: + - electricity production + - lignite + +Electricity production natural gas: + ecoinvent_aliases: + fltr: + name: + - electricity production + - natural gas + mask: + name: hydrogen + + +Electricity production oil: + ecoinvent_aliases: + fltr: + name: + - electricity production + - oil + mask: + name: nuclear + +Electricity production petroleum: + ecoinvent_aliases: + fltr: + name: + - electricity production + - petroleum + + + + + + + diff --git a/dopo/yamls/electricity_small.yaml b/dopo/yamls/electricity_small.yaml new file mode 100644 index 0000000..08af869 --- /dev/null +++ b/dopo/yamls/electricity_small.yaml @@ -0,0 +1,27 @@ +Electricity: + ecoinvent_aliases: + fltr: + name: + - electricity production + - biomass + mask: + name: pipeline + location: + - RER + - INDO + - NAF + - RSAS + - STAN + - SEAS + - WAF + - EAF + - MEX + - CAN + - WEU + - INDIA + - JAP + - World + - OCE + - RSAF + - RCAM + - ME \ No newline at end of file diff --git a/dopo/yamls/fuels_small.yaml b/dopo/yamls/fuels_small.yaml new file mode 100644 index 0000000..a0de899 --- /dev/null +++ b/dopo/yamls/fuels_small.yaml @@ -0,0 +1,17 @@ +Fuels: + ecoinvent_aliases: + fltr: + name: + - heavy fuel oil production + + mask: + location: + - RoW + - Europe without Switzerland + + + + + + + diff --git a/dopo/yamls/steel_small.yaml b/dopo/yamls/steel_small.yaml new file mode 100644 index 0000000..ef3a3a5 --- /dev/null +++ b/dopo/yamls/steel_small.yaml @@ -0,0 +1,20 @@ +Steel: + ecoinvent_aliases: + fltr: + name: + - steel production + - electric + reference product: steel, low-alloyed + + mask: + location: + - RoW + - Europe without Switzerland and Austria + + + + + + + + diff --git a/dopo/yamls/transport_small.yaml b/dopo/yamls/transport_small.yaml new file mode 100644 index 0000000..60b96fa --- /dev/null +++ b/dopo/yamls/transport_small.yaml @@ -0,0 +1,21 @@ +Transport: + ecoinvent_aliases: + fltr: + name: + - transport + - freight + - lorry + - plugin diesel hybrid + + mask: + name: + - 7.5t + - 3.5t + - 18t + + + + + + + diff --git a/test_plts2.ipynb b/test_plts2.ipynb new file mode 100644 index 0000000..861f24b --- /dev/null +++ b/test_plts2.ipynb @@ -0,0 +1,148 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 11, + "id": "be2b7557-388d-486b-81a7-006a3bf59728", + "metadata": {}, + "outputs": [], + "source": [ + "import brightway2 as bw\n", + "import bw2data as bd\n", + "import bw2analyzer as ba\n", + "import bw2calc as bc\n", + "\n", + "import pandas as pd\n", + "import dopo\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "45500028-4a69-4b00-b2e2-88faecc07d2c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Biosphere database already present!!! No setup is needed\n" + ] + } + ], + "source": [ + "bd.projects.set_current(\"premise-validation-try1\")\n", + "bw.bw2setup()\n", + "\n", + "bio3=bw.Database('biosphere3')\n", + "ei39=bw.Database('ecoinvent 3.9.1 cutoff')\n", + "ei39SSP2=bw.Database('ei_cutoff_3.9_image_SSP2-RCP19_2050 2024-06-27')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0a4de86b-8bd9-4c28-9706-a16618c3eda7", + "metadata": {}, + "outputs": [], + "source": [ + "cement = 'cement_small.yaml'\n", + "electricity = 'electricity_small.yaml'\n", + "fuels= 'fuels_small.yaml'\n", + "steel = 'steel_small.yaml'\n", + "transport = 'transport_small.yaml'" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b36d2166-dd57-404c-9cf7-74aae78ee5f1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Cement': {'yaml': 'yamls\\\\cement_small.yaml', 'yaml identifier': 'Cement'},\n", + " 'Steel': {'yaml': 'yamls\\\\steel_small.yaml', 'yaml identifier': 'Steel'}}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "files_dict={}\n", + "files_dict['Cement']={'yaml': 'yamls\\cement_small.yaml',\n", + " 'yaml identifier': 'Cement'}\n", + "#files_dict['Electricity']= {'yaml':'yamls\\electricity_small.yaml',\n", + " #'yaml identifier': 'Electricity'} #yaml identifier is the name of the filter in the yaml file, in the first line.\n", + "files_dict['Steel']={'yaml':'yamls\\steel_small.yaml',\n", + " 'yaml identifier': 'Steel'}\n", + "files_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e8324229-ab42-45b3-aa73-73f56cb14609", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "module 'dopo' has no attribute 'filter_sectors'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[12], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m sector_dict_premise\u001b[38;5;241m=\u001b[39mdopo\u001b[38;5;241m.\u001b[39mfilter_sectors\u001b[38;5;241m.\u001b[39mprocess_yaml_files(files_dict, ei39SSP2)\n", + "\u001b[1;31mAttributeError\u001b[0m: module 'dopo' has no attribute 'filter_sectors'" + ] + } + ], + "source": [ + "sector_dict_premise=dopo.filter_sectors.process_yaml_files(files_dict, ei39SSP2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0323ae99-836e-4378-983e-08d669466412", + "metadata": {}, + "outputs": [], + "source": [ + "#Get Methods\n", + "finder=MethodFinder()\n", + "\n", + "finder.find_and_create_method(criteria=['IPCC', '2013', 'GWP100'], exclude=['no LT'])\n", + "finder.find_and_create_method(criteria=['EN15804','Cumulative', 'non-renewable' ])\n", + "# finder.find_and_create_method(criteria=['land occupation','selected'])\n", + "# finder.find_and_create_method(criteria=['EN15804','fresh water'])\n", + "\n", + "method_dict=finder.get_all_methods()\n", + "method_dict" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}