From afacf4ff8a70234a80bdffeef46140f3fd97c3fb Mon Sep 17 00:00:00 2001 From: cafriedb Date: Mon, 26 Aug 2024 09:50:55 +0200 Subject: [PATCH] sorted files --- dev/3E7C6000 | Bin 0 -> 37057 bytes dev/65BA2400 | Bin 0 -> 40857 bytes dev/78A69000 | Bin 0 -> 46544 bytes dev/A8415100 | Bin 0 -> 16743 bytes dev/aktuell/activity_filter.py | 139 + dev/aktuell/compare_db_to_xcl.py | 176 ++ dev/aktuell/filter_sectors.py | 48 + dev/aktuell/lca_to_xcl.py | 208 ++ dev/aktuell/methods.py | 62 + dev/aktuell/plots_in_xcl.py | 439 +++ dev/aktuell/sector_score_dict.py | 154 ++ dev/{ => archiv/archiv py}/activity_filter.py | 0 .../archiv py}/compare_databases_to_excel.py | 0 dev/{ => archiv/archiv py}/cpc_inputs.py | 0 dev/archiv/archiv py/dopo_excel.py | 553 ++++ dev/{ => archiv/archiv py}/functions_v2.py | 1878 ++++++------- dev/archiv/archiv py/lca_scores.py | 123 + dev/{ => archiv/archiv py}/lca_to_excl.py | 0 dev/{ => archiv/archiv py}/methods.py | 0 dev/{ => archiv/archiv py}/plots.py | 353 ++- dev/{ => archiv/archiv py}/plots_excl.py | 0 .../archiv py}/sector_score_dict.py | 21 +- dev/{ => archiv}/dopo_excel.py | 0 dev/cpc inputs code/cpc_inputs.py | 126 + dev/lca_scores.py | 106 - .../compare_scores_plot_v1.ipynb | 1373 ++++++++++ dev/notebook tests/comparing dbs_v2.ipynb | 2365 +++++++++++++++++ dev/notebook tests/comparing dbs_v3.ipynb | 800 ++++++ .../general_scan_v5.ipynb | 0 .../jupyter_dopo_test1.ipynb | 0 .../test_excel_flow.ipynb | 0 .../test_function_flow_v4.ipynb | 0 dev/notebook tests/test_unnamed_column.ipynb | 962 +++++++ dev/source code bw2analyzer.py | 391 +++ dev/test_charts_v1.xlsx | Bin 39075 -> 0 bytes dev/xcl tests/LCA_comparison.xlsx | Bin 0 -> 8905 bytes dev/xcl tests/LCA_comparison_v11.xlsx | Bin 0 -> 7978 bytes dev/xcl tests/LCA_comparison_v12.xlsx | Bin 0 -> 9473 bytes dev/xcl tests/compare_tables.xlsx | Bin 0 -> 4787 bytes dev/xcl tests/compare_tables_v7.xlsx | Bin 0 -> 21600 bytes dev/xcl tests/output_v2_2.xlsx | Bin 0 -> 16056 bytes dev/xcl tests/output_v52_2.xlsx | Bin 0 -> 26173 bytes dev/xcl tests/output_v53_2.xlsx | Bin 0 -> 33980 bytes dev/xcl tests/output_v54_2.xlsx | Bin 0 -> 57167 bytes dev/xcl tests/test_charts_v1.xlsx | Bin 0 -> 67382 bytes dev/xcl tests/test_dopo_unnamed_1.xlsx | Bin 0 -> 12905 bytes dev/xcl tests/test_dopo_unnamed_2.xlsx | Bin 0 -> 12805 bytes dev/xcl tests/~$output_v2.xlsx | Bin 0 -> 165 bytes dopo/activity_filter.py | 2 +- dopo/lca_to_xcl.py | 9 +- dopo/plots.py | 2 +- dopo/sector_score_dict.py | 72 +- dopo/source code bw2analyzer.py | 391 +++ dopo/test-1.ipynb | 903 +++++++ dopo/test_dopo_5.xlsx | Bin 0 -> 13302 bytes dopo/test_dopo_6.xlsx | Bin 0 -> 21192 bytes dopo/test_dopo_7.xlsx | Bin 0 -> 21189 bytes dopo/yamls/cement_concrete.yaml | 218 ++ dopo/yamls/cement_concrete_steel.yaml | 39 + dopo/yamls/cement_small.yaml | 13 + dopo/yamls/electricity.yaml | 179 ++ dopo/yamls/electricity_small.yaml | 27 + dopo/yamls/fuels_small.yaml | 17 + dopo/yamls/steel_small.yaml | 20 + dopo/yamls/transport_small.yaml | 21 + test_plts2.ipynb | 148 ++ 66 files changed, 11136 insertions(+), 1202 deletions(-) create mode 100644 dev/3E7C6000 create mode 100644 dev/65BA2400 create mode 100644 dev/78A69000 create mode 100644 dev/A8415100 create mode 100644 dev/aktuell/activity_filter.py create mode 100644 dev/aktuell/compare_db_to_xcl.py create mode 100644 dev/aktuell/filter_sectors.py create mode 100644 dev/aktuell/lca_to_xcl.py create mode 100644 dev/aktuell/methods.py create mode 100644 dev/aktuell/plots_in_xcl.py create mode 100644 dev/aktuell/sector_score_dict.py rename dev/{ => archiv/archiv py}/activity_filter.py (100%) rename dev/{ => archiv/archiv py}/compare_databases_to_excel.py (100%) rename dev/{ => archiv/archiv py}/cpc_inputs.py (100%) create mode 100644 dev/archiv/archiv py/dopo_excel.py rename dev/{ => archiv/archiv py}/functions_v2.py (97%) create mode 100644 dev/archiv/archiv py/lca_scores.py rename dev/{ => archiv/archiv py}/lca_to_excl.py (100%) rename dev/{ => archiv/archiv py}/methods.py (100%) rename dev/{ => archiv/archiv py}/plots.py (61%) rename dev/{ => archiv/archiv py}/plots_excl.py (100%) rename dev/{ => archiv/archiv py}/sector_score_dict.py (86%) rename dev/{ => archiv}/dopo_excel.py (100%) create mode 100644 dev/cpc inputs code/cpc_inputs.py delete mode 100644 dev/lca_scores.py create mode 100644 dev/notebook tests/compare_scores_plot_v1.ipynb create mode 100644 dev/notebook tests/comparing dbs_v2.ipynb create mode 100644 dev/notebook tests/comparing dbs_v3.ipynb rename dev/{ => notebook tests}/general_scan_v5.ipynb (100%) rename dev/{ => notebook tests}/jupyter_dopo_test1.ipynb (100%) rename dev/{ => notebook tests}/test_excel_flow.ipynb (100%) rename dev/{ => notebook tests}/test_function_flow_v4.ipynb (100%) create mode 100644 dev/notebook tests/test_unnamed_column.ipynb create mode 100644 dev/source code bw2analyzer.py delete mode 100644 dev/test_charts_v1.xlsx create mode 100644 dev/xcl tests/LCA_comparison.xlsx create mode 100644 dev/xcl tests/LCA_comparison_v11.xlsx create mode 100644 dev/xcl tests/LCA_comparison_v12.xlsx create mode 100644 dev/xcl tests/compare_tables.xlsx create mode 100644 dev/xcl tests/compare_tables_v7.xlsx create mode 100644 dev/xcl tests/output_v2_2.xlsx create mode 100644 dev/xcl tests/output_v52_2.xlsx create mode 100644 dev/xcl tests/output_v53_2.xlsx create mode 100644 dev/xcl tests/output_v54_2.xlsx create mode 100644 dev/xcl tests/test_charts_v1.xlsx create mode 100644 dev/xcl tests/test_dopo_unnamed_1.xlsx create mode 100644 dev/xcl tests/test_dopo_unnamed_2.xlsx create mode 100644 dev/xcl tests/~$output_v2.xlsx create mode 100644 dopo/source code bw2analyzer.py create mode 100644 dopo/test-1.ipynb create mode 100644 dopo/test_dopo_5.xlsx create mode 100644 dopo/test_dopo_6.xlsx create mode 100644 dopo/test_dopo_7.xlsx create mode 100644 dopo/yamls/cement_concrete.yaml create mode 100644 dopo/yamls/cement_concrete_steel.yaml create mode 100644 dopo/yamls/cement_small.yaml create mode 100644 dopo/yamls/electricity.yaml create mode 100644 dopo/yamls/electricity_small.yaml create mode 100644 dopo/yamls/fuels_small.yaml create mode 100644 dopo/yamls/steel_small.yaml create mode 100644 dopo/yamls/transport_small.yaml create mode 100644 test_plts2.ipynb diff --git a/dev/3E7C6000 b/dev/3E7C6000 new file mode 100644 index 0000000000000000000000000000000000000000..7e3223a5ec37bcdd265af6e772da6a9826c22f5c GIT binary patch literal 37057 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 literal 0 HcmV?d00001 diff --git a/dev/65BA2400 b/dev/65BA2400 new file mode 100644 index 0000000000000000000000000000000000000000..aa127a7ef61bc05971f23959c2e414f07bbae43e GIT binary patch literal 40857 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∾V5E%vUF9(-gHzn4UE=1s3 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

o2zMx) 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 literal 0 HcmV?d00001 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 abd284ff5987efcfc3510f8f575fe5ed66d33fa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39075 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 diff --git a/dev/xcl tests/LCA_comparison.xlsx b/dev/xcl tests/LCA_comparison.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0391da53d6ed223f28aee48eecca1a6fd81ae3c9 GIT binary patch literal 8905 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 literal 0 HcmV?d00001 diff --git a/dev/xcl tests/LCA_comparison_v11.xlsx b/dev/xcl tests/LCA_comparison_v11.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9441d3803802a83e6dd4717c184de5ca5ad0dc67 GIT binary patch literal 7978 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 au*<0{p}3;xQe9k%m literal 0 HcmV?d00001 diff --git a/dev/xcl tests/LCA_comparison_v12.xlsx b/dev/xcl tests/LCA_comparison_v12.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b67354b4538c2b5e552723c58c9c07755674e00e GIT binary patch literal 9473 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 Yr6h|4ee>m8V+9Xjf&>5rQ-1sOKV;<0N&o-= literal 0 HcmV?d00001 diff --git a/dev/xcl tests/compare_tables.xlsx b/dev/xcl tests/compare_tables.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..40aa27a23ddcc4bc1b5c81d0a60f5b6030ef83b6 GIT binary patch literal 4787 zcmZ`-1yoe)79LtUhLE9@R2&+lTZSG6326`*VUQlAkra@U?vNHiIwU2eg`rVeLO?{) zLDDzsy3c!g_sv=VIqR%7-#-8T*52RV`-iFH;8FtsfGdEdN3@2JUQLxG%-axVQDBy} zlNHR>$r;Ia-`Sbh)4^U7rAge$PlCVn=B|4~S_E?e*&XSK>=qR7qbKz@9YRiawsB=0 zy}bGu@_3)Ja@MCR?eVTU2nqI*%1yDI$P5nTM&z#glncxUMpc2x3s{(Oy7C;R1D*(w zh`n2GsN9_3h1fhoB`bJTOK5X;EgJ|+RB^GC&M^{Etb23FL;}^tQ*Pn?h}0RR(aHWa z;B;n<_lBT}4)zZhK0WJ|*}-i67B&Dt`F9troLu2QeHe=GfOhedgqq{6Ei?^r%4w$% zhXYkqG_YMF8J!|+XKo1C9ia>0TOmtWVm%iJ$2F_?!JRT=$S*ib?uq4)MR(Qv@56DB zFaRO>swOuwZ*a(zrS;=kl+P7>*>~A;`jaA_mR6(;sR2p`;R>R(G!VhLUf{xV5P42j z)_vMQqx}h&pr$hU4TPmwi~|l`JFZoPj2~Se@7u-`F88kiVjR7z$rJn}YnACpuQ1R; zHnaJsN5s<{x0&u2Q6mKH1JM|K*9F>-k3yN_6WZ z$)jh{sGz1mbC29NfPb=+W|GN9fe!#=asdE93_G6od~UXI2l(&1z)ya*jf{~qV$_d! zDn>kQ&xe;A$BPa7~wQDRw-)RPQqv82j zzuZ9Y9Mfd!AzTgJY4-jJQkM&Dot`(KF?7MK7auw8oG87Pm5NGl`;&wZei)adB+ylY(MG`+ zg+|8}atNY!j)}IQQZ_95$(5!k)M|E=D_iqC+Q1L3rR>NyUD95|I-Lk%SIX(*X|HMCvnTZ|4rDs@VizL#sDIur7p0s>mj zM1De!+Mf~>LPpVgEpz!P1~gbHR~Lm}ih#SPJd@h@US&!#0jY)(iSgw_Q`kAFO2i*) zssJx;70z5_j?3*P^0A+&#XUpRuob&;4s8g5Iv^DckHT!nq`WWijx$Aw7~>e2ux%J> zAlE|>&AZGM>@n%DIw@}{$XL?f2D#SJ2G+Zjgjg6+^^AH#$7(6h49i3kWGwo=48=Vf zo{A%*j5ia-{C7AqmhugAsF+&%cAkp??s>LZmukmG(BOmf*N;Odh3vsGN6Hg)YOCa& z#j=}%!UXy0hJ>vl`gS=l!-QpCFbfjl?KLvXta*D9i6-~7z{-Psuzrlrh<28K4{8_p z=k-Gs?lc}s=Z-aqviaF`j7J(TKpc(Vo|X&DObhegu)60u)Yq$9k#O_9cuxD5b6c70 zz3r0^>>(BDhCFPvvFd>}^6anAA_sQ4-x6+E8*>m2>W;%jBdD?!7#ufszNsZmuT?bO zcr9hTx>=FyCIdztOPR0N4iO6}Hn^&Pu1{>M;}lanta|4RWgaAvw2_Kus`q%#Byx&2 zbzfyG3WBD zW&Vy-GE;B(e%|%3xuL13F2yh}AM;e3q#l?swS#>bI~dYn@p^g>Oub}n!SkFTxI!Cy z|E`{efN>Abt=K$YjVPrJ=k*j5^Oe!~mDwVAonysK4|)gIyW*XQRG>}izyiFgLyrli zXS`q8W+z=s14UgJzUs>%p@h;BQ>j=b(iN%Y9nkBvt>038t0(qGNvNJG(f%S22_e`x+>mNn({y`sJtMQ+o7Md!#iPSw|J=D>nS4!h z-Tcs0vHUT5e&xh5K~hB{)2o{j%S{QJEr}7ImQHXL_BB2hABZ)S*nejiJIK0-F(CDX zxOJbT@g~jiD;aTrC2(*k)NG7{iaMf_1n)YX>&nhff}Kay^a(i7Aw*ZudXjEH@o9&& zkgMW*U5F<-Y2nGeOm6`Gm9uUf1eRCTHm4dPVG9T?w}h7X7#&dhokp+`Kw+` z3)LiwAXSZo2?9FrLP8-vspa}s0(%(cP z-MsAK$e%TMUVk=XmY?FB<|Fxwgj5k1)qR+vMJ^9(eoyq1rO+M zk#fBLv0c>-|&@mtlEf z6Xbx1RJpXwiuo?33cmV}Tb+Y1?kDH7@I*hdeP$TIjmewZ4uf$m?c_d_;qG-ZweteYc+C2Gf}5NBY4dDvzr} z!x$Z-v;hsuxf4P$8_GWGr(@Ocw|ens-&IazTp^6MbOX{H{wQwPIL(=_hPD~~fCt!V zjQEpHMRIW&c@drzwmy@6LiTh{DW{Ai#VW(EH%q66*!^n3{#ewE;q7St9Xp((Y!-`> zowKfENUbmI0*>AA0qhTqW^@zD^6sqe(WyD3v3U(H9ttrS6JBOb_tu@?q_~5ReTRvU zy+sNAQH_n7%MyVrz)hBf)%_IAJ^Vu#m0reWsq47KUd<`~Fdk@lvw%#LhHI!~N$sAI zo|1O5uJV>dn!M(>SKz%AG-NH5gqsX<(rh+9UL?A@BK`cq$!I7nX)HrUPwfa4=e8KD zt+$ELafy}J44h$#%83b&!rkQXHU1VlF@cA0z+qz9Z9OMEYi8%|088G9)a&5Z4sm;++sj~MebcZJ&{ z`FMZcv*H^aG2OA8fA>f>Kf%cLa3S~}E$%UvejfRID!d4#tTz=ig)Xtg^j^|ZRCCM- zxoxq6`pA;2=M;AlMw73xRO&gNA%^1=$|u0gB4J~4cV?{=D~-T1a=i2p8d|Msi;)HQ z!cC^L3Z-ZHwB(%vDkL;q6c8%vLS(x`WB!6i2P7g&Yievs#vsq}wF~zp)zMs51KM<@ z{_#AfLVu=SbnN(Mg%xY9xADfYi>)qE%$yZRl#6BUg-B38`IpC++FhNp=iWjXrDDf8 zO8z^^eyWwFlhebWX%?+L`g0UGOX6mclf+F(ooeOBN}$jn*_L*gpERPn7t`F)#@wLS z&2HwY_SN3>EAODtUez@TPVE|JUS zhTT$+jnXiU{5Udb8*#|pGt%W2!@>JqR^+a!72!i}NpkrfW_lAR{ zb?MF3L;2E6BF4^~k@pJ2>vs&tpx^xdj-|h|{41{N;-txf`AMEsP^|$(U?qxp8m0!f zcq#A>*r=$w%ZVEJo?yjFxZSVL=JDU@hvwK)dpJI?KVXME#2>I8{IUZmFs_Z@9bVzr zvZE_oHVnmY8Edtph`-S!|6a&C`)gWWrqqR^+oO4euh@CD*e*GLi|Iy*lJ!kB8qlgn zr)~Z1j=Rf)8p(Mt1nDEVlbwyOE%#q@K*Wkbn*I!=R|%S!K%&5S2RG*61sgmsVz3}Kb;h#N z0+n7Q*cgq?3PESoo>S30(Jb>^XhCJClr21?tlY@@_CAqG+m{+!j{s`P#OhgIdtF@h zEWMsptuDcs;%IO!7}f#we0jqsacfej*43%RbJL4Kb5}F1-O^Ahy7Qf#8hyTq8r5`n z#}-TC=Q}SNks}s1th@%Wh|&;~V`rY_>+-1T;Cb`J+(HAI>@+5cLgc5>bj*;rjPxvGvdES+l8F7gcy( zum1MsR32&^+!i;qa!!H}T@AD}_=NiI(?j=zVR($}jM;kw+P`ZOWDfuhWLki^Pq9vC{L<3GI1(`AQ0`-cY@Fj*j1xZm?|it(<=16+PpH zFIEotD#ZJ1)IID!*C<^llN1R82q=>h2nZFhM();(&gLdICV%~8{=G_PTG~!4Txh-* z)l=?f_cpMD905%Ys7@BHs}1SFXL5ug$Qm)$9>!mT{klJMAdRKKpC!n)W$=N~9luP7 zeLg(m^0+x?FgywkPr7zDSY}*ZXm4iR*ivU9Ll$DfE~&okfhm&FYt*s;`oZNhf{OBE zEiej7+8u3dL1#-FhmmyQSHtSMTCjz69@b}L10Ov)bHOPUMJ+phqK17!)<^U)l^Bx( zU{rM>iq`Ok>nH1h*Od(clgVQqe3k%PCR`07zX`jNJvpuLZI!+6CE;06KS5=MrdcZBffPbBr{sk`HVjrGZ=w1$cR>wr#)Lmq*nQ(y&(n}x34TNp z$JxnrI&hk{et|3!pT1Y^SSv`?KnG66+GHzdryE#sPwu(;p2>%giZY%A3n3JmN=AWF z#_cOBf%;BcwDO)gD|ng2NcJrCHO``*w8WWW{D>8&SG*e6CCq$=&+{GpDU%%r?<+1o zh#79ZIB~E=`z1j&S?ss(eaN)J0*2TeIF5}N0Zk5N!3J6=gVXMEGY!bEnw9K{0tUmU z<6m7S7QP}NC+KkkzB>^WkK9cV$|d#fEZ;!`>@8dGhh*)-tG@(0#)hu($!`K8$30PpjuUZKN_C4KUI8z2Y6siKffIp^JIQuFd z1ADmr7+hlf+8!(4NhL4t zSGQ8N@M-TISLZnk&`mz^=^r+X!?KDrJ4*j*O6qE);F7wn{b?^pFb2nK#+QKK17J_O%seRw>@@$J6nEJI)UGGFYomn!3&5()9Gp zVXtCM!3AFm{!sXJKJj^8XxiitkuVQ0{WP=WK?OE68|z9kI`L+Mmc=VNv@K%;`drAM zYDJK1B~=4v?Lko5_1}Ws(kO)cb{z&c_m~rJqD10oy9;%lkp%hFj@8#&J*GxP=UvjMno?`0*#CD z{L`LcJFnqdsnI9C3vm01(FKLd>CKR%pheceffHjo?p|CZTDnCqKGOx*1go$Z&tr~? zMD+5rI*Kf*kl$3TP{4+)?x>8n<8)CNNdNG(&7!CLSk;^3Z@}aizbQ!HBB&2@%J6LI z6RZ5tSRi;}+2pog<=Ft$J3yrqDfI32iH!6|t}yIstCoKv&*W|Jx(Xo5cwJa6ykFmf z^Pa2FDl(=;{v-|QO_%^-6s>*BrXPo9_`SIH```*QoQJ>04P2fn*Jha5Hyqaas7me_ z7(1bP!TnFGbMWT(@N9Tr!S$Han>Y&gFn;ngwkA9M)V_>Y^*LD$pLyCKdg>VqzK^-@`n03;p~GVxSJi;y+R~7b`uMBB zE}{%OO0a1COx)ALDx;g64OmsoHau*st06t$F(UKX3>p;JW%Z@FFSqq36U}a_3&p&w zK8poGNt7ys( zph$gt<$$uUJf7x`oik4eant)#h)1KqSG~xN#dk~-I{CC=)93?=Qe>>GH|CZ_9@6vJtMoHpeY%fteExl3L_GK_MG4R@^bv*r`xKf|!A z9UN~12!k=)e}%!=@r#>$2z|2Sy)|ApPcihFMbebCpL-MR+&sDrzNojGirGl#MK#g4HWraFp{SG{k ziTFhx$AGfzp`20@Z5@@@-yr5J*#0i57oA<}RCM!`mdYnEu3|T6i8%Ti^FNuLfjmOc z5f3l*I=Cu4lwU1NmVtbY3|7<}6>)tPgxg2JMJF@aR$Luxu!NA?5E#c>XN?_p#+FX@ zKks$kW^XqtJb+gca!1W-1Ji2;rda2G2h*y9G3Rv3E0Ug=iK$QKlKxl@Nud`SGd>rH@$zrPmKkvHShi_tf04mu`waApZ=JKxrg-kvhUP@I#;tn;z3m02M>iRou;5a1k%V+Fk3p z#Jt!7*_nMOPa=%)3tB=ukoA^r@f3*diP>G}UP&iZC84(cTN-F_AR2O^1SB=7L^^_! zSUe4WVJy)dZs0VeGvhyJ>GnVp5(Ajs4dD5Emj99u{+j21XN5nrjG5$x`N4z)HdPVM z@4qPbm1MYTrUkyMhooAd|Cwl{ztv}rj4PgG|7TCoS5CdsHmpdOvhZ=nyjue+ShJ6(jS$F$1jue|{6L_kk5Dy^KNjo*is6!$)K|W3oedlX zQZTH!aI=o&*s-}6v6q7u|r4g>d1eye=IbbzXtWKTg8tCI`gQnYc;~-A* z&NPeD?Iw&%5VQY&wu7OhyNe)TYD3~ zkpK?UZ~=9(*^Xa-X``}tjO88PFGQ?E8AMFD@QAwY6;Ru;_SZ_?`gWqh)-b*a%Kw<( zgv#JBtpWI-O7>r6!@p$fKY5zrAfA zj)u~K$Wo$(*yf_^&BZ)SxXK2mXeH|F*3G9O0YBe}*;|5}9otTY^4oXa;^pU#Myj#- zAzL?mhB>7w-X|%O=L{~!l)AaFfsS@PbSd$ST$2?spqg_f3eAem)yxd^H$7kdV##9D zkmfFZ29v9oda0YC8w~3=ZilR&m?kdys~6w*bk<9+4=vS>3eMHYrBZ0LDLoF*X$0(i z0tx-XEniK0^dmfV76rWKWdixSD-ak8ZZJw=l*(Ynq=bHb5X=b{S&$cwJsxV-H_B>) zFMRq?{AfyV&z2@yw2Uw5PAJvMY!)4hQlYz;z2@=jP?)^QTLtMJR=W2Rdp|;-=X>d_ zd9E4h)P&;a87Wt7qlSI6$)8PrlTDX=dQl_qv@NSBlXLrVxHZWLdw-rJX0U}ZC7lnG z95Skn?%_HGTPm~JJyj@#(ljL z;sbj#tn#=I`{PdDD!oz`<_D;!ojNwYB~bg^V+cbHt&HA=Z2RynIelZ8KA;FtQQK;P z$7IRK05i1>XgrYD!`ksZm87e0UoWi^A4#D!A44@phDr`Kdn$=*LniFd)JrP+NrI|r z&2llJl18~KQWV1L3+cJ-lRk8{40%C^06Lf!X-Ec47ELQvrsN<4N@9ToW_BS9<%oG& zc^yUXdRg{nWDpqRqm^3U+am(BV@dJln|VL&McLSVy2m$2mOBEITA8}m=22-~o&3p; zmv<`&V0XLBHBRlW_Eydo;j`+GwA@=X6lolX^t?p3ySwx*>%{AofT7{D;jh)5Qo3*xWl@-cebWmkFG-|%L4 zE$W996#B~Ipud3CtrO_aLpBSAF~Jd=VVO58N2HNRc@q?Hg;T-yQK1pV3Sm*R8mxi00~!ckS=FF~64Wvv;Vs`BE z$sq~(TkQ%>U?ah|EwP-Q1V=EGFi%W<$qYJnOv)3lW}15>5|NFyxNMj<_{lxE1{wSW z76n09b;+Q`rg{4*61qp@r78l$>Q1d;?(Qo#y>S$VBTmDTv5xsyE>Zul5=Vp}(XEp; z(bV9?M0o~X!KMu@u&xCI7%xvN!5A-@8xZtO-f|7`>rpr%YY)($(G8I-^9NGC=u^s= zbH_M@BJAQtrxd`kdxqoq&#nN{31Gogahjv{*1=~ zF=@TOG@_I`WLh^xuX$uKY?$cq6+A2+JXjnU80uG4aJ zqXq9J!?BubREoOHK`xOn2L#G7=1`o@K84*NICk#h-dXXWyO_wq5L+e_v?)Mjeg&;| z6g-5)D^uN`;qHwOv9M}$yVlL^m!;ErOJn%r)GZlj$~%e?NywPX2n7d)4>3n1Ck1pJ zJaU=N$Y8kj+SV-@GM4=7U^ve;8xl(yITl%earPKW*Hj}@)MO5FiiFuA_(gg}=ZIHd z&OQ=-l8rsSaBl9_xd>{>_53Uf+?PAORRM@kKYnS1aDW~e9F;}jfLbg*or{MlMATBC zSSAD=2R5b#7%JEx$(|JPplCGIbSM&xm75?TDXRuRu)T&+Z!d|^bobny@EX=nnFh#r zLwEQl{LwoMC`ULfW7(>u@>-{&stH`3;{`pdGQH*WVZ&@=D@N7Y*lE$Zm(rbM_Oy~9 z7JZxd9{fF%bVg1>Oeyj5Qi!9AU7ZihY{c?r`=$$$yARNRR^VzXfi>X(hl(cupE?xV zZ-*NAW+lHXju!e6natQQLy`Uxtxh~+YXIB&72JmQ4i;zCXlG|>&LQ{pCH6-G&-u^b zUE-RrE?_=BwU-W7L%wF0mYCSAR?t9S zzjm#(M$2jEN=X#a(9wdIeejnDNX<$~lw3W?k1v>x($sXlmA~8QwpWqIOS2Epeo83K z%xU^#QR)^RS)X)EbMB^O1I_A^-t@kH@(@!s>&8^WJ!xIU6poXt`$e;!6e5subUgab zGhXQ3r7Bsi{mZ)i$NHt&b#0Cguqf7Msc&I*Z2*gMa=R2x zzTvxOX;lc_va^HHGl?L-*<8Ca(Hot^^SB!93h^+OdcBU>elBQ~NTCSXCg^Ogh`wj{ z*R^~V$6P|Eu)8SMHGWP>S@}`zs{5Bk9kR5uLjPO?ksir-dxazLynuB z3ojopMx=t1y>CV6LMq@<-$A9EE(Um^jD5KkzhoQ7C1adF;va=nu*B^q%5O;GK}0j7 z4@iJWV5ui5744)!h|Ul}$@#Jq4`uOADzbIaY#tHbgj?HK`>F8a$rSWX4}#_nMKl9Z8S~iNh1>bVZP&+#le=(W)-;M6 zx&JUwWEcm(s~iy=AL{ERwy6eGKUD8(klDiNVsgfkO6rH*FM5z|_<&0XDC0y2WQiRl zU~!TF=wRl>MZv+OK%{sDCVgSnfHmlLXyd3iPn*8D&SJ#=IC^SI-^&zEY=gD1a7S@u z@X7$fRj~FplfFrZpX|#V}l4eZG0?0!mPJMVnGQrqG1ITWxUA^-=L?GVioa3lfb{A!5|G0 zMJHhkoCmL8)(#*r$XwPg-UeLOlJ5s2mht9;Vk3tWh34r!Xc*PD*T~jXXj44m4e5x4 zwcdwL3Katu#(B3>2Cy*QvuE!hU`#>LEMU-(9l$xyOB^RmvBVG1xjJ#=g}{VX=W360 z`?c5t&Y*;zc#i-Nz3e462F_Cy1PW}s5{N^a#UvJF-c}kkeQZH>{V{~W%gfn2q&ExM zH zqOpX&LU>y*FKySGu(NtEK?t03UDl3pbw45p5HaU5gCir55Dby2DM74Y3B;ze{TRxs z!4+s)8Y_bqxc?@AB+yitdHF^Aa$0;=8ebgCqWkI~E6yfRcb%p8km~@4=V#+bvp`sy z?JdG=%md5%Ui`beV7iyo$@|jDgKN_Ikm*}@eN%Bs(Uotwv5D%QVu=kl)Ff=_kWr)G8&-QW2&~Ghm%c%MoblXUJ6q>Nh zMs71qpk!*%O#qHlZ4ma4UIPEt%Li|chzzG^*zurOx~-#xU&-A&$s1V+*ia^sU+26( zqFqmz(Sb4?*CEm?qCk`N>1Ej%E30=uguu@4MQPb0O(SB$AXbNArZ5x@*NYDB^E8ZI$2?PjoT)+Kyh?G+g;UAr?F0NHBsAY{EQjMj5+ZQl?RnBr`s+*sl>*(v3 zw+i3*RFf_ZyNTpz6-D@|!(Bc`eYbM;I1WaGyN`eCAH5<-ty7zM8*pJhB z=O;Y;RoSivy_;kvM#n;XIIVet=iA;q_EZ7=(eJIP68qheybDQwe?--N0mMXzZ##ev zJ?+`!05qGdD4u3_GQ-p=s}8P`pu0l9YN2Xjr#=u-^qLo8|Flv}TWy%MNn$TZ<9)~O zD-`MuptWwm(ov)*5+69q z1v0g0F%BH;EvYe@H50-y34T!-CA>QJfg+nt@+D%NyW^XK!&2uT?alE7%t3xZ0>%mj z6GX7td0M)|i`D%_6fWQO%YIJdwTXvOW;#fp=R8f_g`EJ74ou~%C9x0+?TXV$JD>{E8}!WKfK-|O_%F?(r8s=$L#n4km| z(Gn;S@hKq9wnmaEhuU;bT^&DiwAt1u@_D#Sh%TGw-+PTx#>E-U`C>)qaSHW7loN(G z1k8$`_ujWX#dq31)Gc1dyT);>#?*=+=Me-ClY=c~oOM;m*@~1i5LqU2zZEs=~`2isz3YWGtrz#N$unJ zDP}RmiPZ;Ce7B#lFL>u^*X)jvc359&%#bbHIe^jxaTi*QMr*#~^PJ;tN-o07tz_^* za-SZEAhfS9e)%E7Bd~JXA`$_KuRc9QoYvxUyM^PaGvq)`2E|PSv^Zr%GZda(&NEXG zRyi#a8%L06jOrObi3Qafr-`M&7|jLmeP`vgrIe!x>WLGz8A+ggJ1;SWe^V|H##`(i zvqqe}u#4c2P{a{3r@ZITiQ_5*-P9-?mbKe{)YdEJWpqg7w(dQw+w9e95|5zlAeyJ%Su7ies*&hK=*8`D?c#84TpV7a@h+_z zmKYrr9|8!3`QEDQ)*}OW-WdKOJSe~8?-AiC-#+Q{?4_ySaSt=^TZzW%`W@UeiN>$| zQVY%1Hj1VG(WG~j=H!j5%eiF!pT*NuwSI0=y-j>ZVAF)FG}Kt@Ip!Gd*(7#% znE7juLvhOCtLG!A#(I*!h~*S>vQe;{YSvut?00WghVC##O@WyDJ}LJ?L%|`{-lr4J zgGE&8gNo|UUGYkV6+f`eh$qLOe|eJ4&%Pi=nu+wN3{CuQ{^APxEhb_o9` zn}i&Hium+p#{w?2Q2mf=S<~_r4O$f4*ow9+y*lo5;}q#spf9Yzd%)c39dA(WyMqkD zsVy|>rqfR@u@jSF!q1cC?sR5KmWSPR<6}1yGAB<%dev}dET}~l2P~X!hN{NG8MB5f zw1**R(=1fdUQj^QsS#usaL;2_yM|NhuvrwXc$jjn!z_v&-YI3C0vA0KCm}D!JhSs5 z8@Zn_m7j?2a&8>HOn6!)Q;itwQP8--VCT3A^u~QMXZPjasumSUZ8Rz=CsX)U%hMV? z!3!wfi&^H91DQU_J5tRyps-lHz9`q%XV<}05_XsS=`Pq>LW@#|(Y*0S+@5y);FTjY zs+G9w^n7RVTezX^h0Pnt)h{(GDgA|a`{S1Z_k$zx&m*#GwPPX716#x5*&vf|5x(ie zg`2Y!8NVDEU$I|3x(yB$wZKBpKujf#iEk8mZiL066gVq%8Y&8D(B{8!Li$-CP84RB z4UdRX;0AV|qjwCWN*`>l>^9Dj*u=henQaQAvy=O_nzeW|)D>DQweaLVxF(;7z#F8t zEzg3xhOEA|TVP_J%_@04u2+Feih;*Ua-lvoNE)m$DB!%80?q*p#YTv|w_wk+Qfc0R zefJ2oMzx}zUF7&C;BB8mN0)FJWTdf%Zc(mhJSU2sv1ez-q9XZD=5%JT=$41d&=9#> zk2sMY-boY))yoA2<(|~Tg~Ys#^gALleIiu{V;1wUuY=}h_?V}4wEf`e>m-Rp z5vjTxtY*0w@@Sm{eAi0C=$^`J0ft55ao6|2a<}u@9yjPO6r4R!4@`H@oh3Xi*3a!c z3!=5N8E54K)+5II)e+3AWFtQrzFc22iLbwGV?CpSQQ0F27Q%Kg7=F&Ue3qr?;GPR^ z9=i2Xx!`nzCW$xbLlP6{F|XftyQ|fO(uw8YLOntSVGhU$8fvm8DBsDbQC0sSZvv+5 z2t^g(&$Ldku@B?}Qp5tq5(5*2roICG=`C+K$Pebgnh972XiDwdOE1*)Q`Jt=bZg1R z1n9+AAzcKn{W(&!FQ+fou(D0iu6GsCvL($cs!{U@;B^*cy3wSC*VIqLCU1n{h=th-pIvP_%P5C{T6u@8%Bj#QMBCN8xNBPD135ENc zYm74t)VsgcID1y;Hfo$1(BYf(o^Wbj9uUE95X_z_cCLoEz%dbm{FGJn_VV*qCtMXf zfEJ4uB77DwhO^T&vK|{8ErQ=^- z#t0eLv6S_tOtqfQc{krl=qz{JOQ0`PW0;P?q+%;mZwKjvHgV37{_XJxaRCx>9H5avdYpv+-hazWGU8Q6{m& zSqVD>Yl{U{r?ShfTCU+6W4AH5lvOf4qM4FtT`5G`ORduLUE%Xu*L%s2Ex&(uIGmw@ zsyqOvC&-|HfPSC91RTLLGXLTTI6d*_t>%DAd=vLsgGIS;VU-M& z#S}vE6|(NG^so6^)>XmB2i{d^dNFg9jQapa2XDs7tQ;>xa#}TmH8D_d3LE-le!R*| zD~jl4!w~i*w>BZMY^4kwJR8}8mH_R%a;-XtZ@$LBhVf=!##t#dMYUG=So1aZv1L-e zZPLroF$I_PRf3# zIIOM<6!V#>7S~h!I_*$#Gxw`~>Y6_vUST%WLhYKwSuq6hWQu{Q1JJE2nG9U98=)Sy z&-x=mP;LC;LY~mME=F15))__%2j2L?MzcW{_YlkEs5jO~Yx(qdwr(7F{qfJ(q?jj0 z!jnx#D&>AG%tq_TORA@%mej&GKX+e7^U@Uh6M3m8Ya&l=V-do#5B3C2&7f$jCyElIU`o$_Y7#=vK)W! zT8d3~2_zb;V#p?r*2M>dh>h9W6Mqp7u607Ee5po!uNQS~Kmuw9t{Z=2s1+V~x$|W* zb&BCTDn)z%`V^c+>(N|<%3yt?8K|{t5hk*zjHb|`v2@T)^a1}*ss7?u%#Q;|Rq{Ve z_0N{qx|%LPs%UStdINLBF|B8V{pCnyplrnOE4>Z;B%EEcojk72XDPRv7b|kqXnB&> zE1-<7-k)NtCtI5m(47L_+c3auif0D#-3~SuMJ9Ba!=zf=njl27(+8y7wG2ndBehLN zrEY|(AHIGLm&+dDp6s~BF}J*iQ)QlHWzN)U%{EvupvtZlBTqWM1XBH=mjX*SNF)1M zMIll1I)Mh6lOqT|SkJj$KlZ+@cp>sYe?n7Fk}<%pEM6`=Rbq6wxGVHA`=STMrKu)O z-=PJ}%j&=XElcT(cpa!|CAT1xJ#8@#78zq;y)<2q%R@Svo=ga6F>J5Smyyr!7%}1O z4W`Dh{mhvxfoVWD9PK_Dih7HPI;*(U)_9jM%Xa8!w4_UU)~uxLQgGl9 zNgGUo5tI-KL(Wnu`7V_XJ1v^jP6Unf>Qg7mTFda=W$pu9=Oxl}Cso(d{a&?#im#OqvaWGPad@G-3x7=Ndf*0-!@^`0#gMcc}@}c^4(2t%K5FN(3Vp)!bn&sH^ zW_Y2L^)7dDqtkmK<1JVkNn3lyLT1-&!~27~cR$;&S$g6A-~GK`x~!;rhsV7s;cRoh zLZaLBf%YKo4~kb`+(oEVy;p{4wkSr4Oe$OE-qu{6O^UqZ{!_rn`T@Dp00AFE{>NPV zXRmWzRn~45a8iv{FFtS8L<-hAtcN|mu}8tcrmkrz7Sw1u7DJOoqS593?e+_$MdbMu zJnyZc7}=eKfB>~eD~KZ}eNviB42L(57WblD$d+RW{3YaS{#Ev<66l0o8AigEPc3X|rWmCY}VL*)&p3YtX-ew^qi!Z3VC*k)gDUvQhjy~F1v zOX?jsQl?ox3(!uG@On`I;cXi%AFkM;Vt-(bjf&g&8RxW(Zx|kq?lc+3@eyoi=!VcC z#a=s4lfnSvJB*h|y{qYnIgh>&v8oG7?ymJy_eKVC*KRkl?Bg~DE`WsKhRTnx9g9$Y z=!Vj4ml=8Fd)`}K;Yr1(_cX3pQX8*TvX$KS_@Hh3oZ>Frx@wyRe*Qjk^GSoWylWFr z5`YH9)1{9{MmZ$%#hc~PoMFc-2wiS)&JA0F8N2l1U+bJJM(@a{c%EX0V2QJ*4s|B9 zyBY3crC*RtBvL#U_;JegetD=ew|!n9*fRYN5dyamf7(OmN$)Ap$K5(G{Pt z5%Vg==5YCLda=>XHi#30w{T`){t(DlZVrVAw23|$vvGPXN@M%U zipeVbj_ch-3Pj2-a_OxV^WTq3!kH;M1OUXx_MfxopKT0)(c8`bHu^lAUlhP{QG%tT zB*_SQlF=#fwQ;lTU4_N5_!Ao`?_RG7mAWO+7DNK4QM#Yp9@d^(qvk6$fS02nU}RRb zXq@;oG*@M?SO;LNnCzT`Mm42m*)`k>BOL<&Hu^5J0D#dC+LBRl62(F80*}@EAEu+L z<*%;rkt$YR!%;|&bHYYKQ(guqu|w@E_Y^*9rwG;&bvw{aV(ytQvbw zHsp{VJU%+RRAr$nw4Y0Bi7JQTSCZ@mOJ4(-XON#UKj{k=LHMN4Aa3Ew#wW$4p%K_C ziV>eCjQTBDArPIDKluuMNda=eat&v|Nbj>x5ZM>hM!8O;O6AtCD{{~P!xyYy0(v+R zRd8l#xIITdJ{eTU67sO2LvgfYIZ}ezIFGm5k>EAc53F^LHOWqXVv2N9Ezq^_Wyk4C zi(@&EseFKD&yZ6rdzTy$q&K7@z4A<~tz*lY?Sg_klG<*Z5rVE7S-O#GAEQF`hl)&jn7 z$BT)d8gYCrpBWF+B|jLqbBhPi@a zT204E#yuN*F`Yp45x+QH1@|uH1m*Uap{%7(^Ck8!c||7vNd5fbA$o60jN}S$y!xPP z7!vvLqHARS61`SY>h=KkDL9?BbvYfCLFPs=P%Fb?Xg9lrOYrK;m(>sGC%b><%Lfb9 zu{3~WHUG0@|MV{ZQJW5~xf&U&hR*?ii}!bBvLXD6tFK%gi=UO1@cxo@mXt!m79so6 zlSF{2L!@5V>1Je<1YUgA!(u zVUXShDlAeKU88Lgk*1@|47RgI3WQe*&oSCm#fVbU6QA^xy=A{;VC`0XcdqeB(LMQN z&9inA5R1?U3R8PJQEwfjvU|%Zn$#4-@V1D^~Awy0VYPw~)n%2hgGbp3&VTR!bde6gZvT;fZ*EJwZ=<@{>a z2xczM(J)N&Uy(a+FKCdTMH}A2KXC2bkb&;oGk9@ff=|CoJU>Q z56a>UO|j7Yt0}HUA0rymZLGG3wX{}AaO13T7qnr9zR^bvri%)quAJJr>852>V`dM( zi&v(?A&0e5p1+INC9}93(046u#wB+r;)T$4m&`ZQxw-XH zk8g%fRpDZvBqd`|u*E<7QQx84fsBg81A*IS&~3;>$DHJ5FWR3<&-61Y)4E?N(oz$4 zk9j)`I3T|{-}ic zu2(}cUY;L-4X&*S>giEW3S2YLk6?1G+A;n(>-9?+j>P~m;1z6Y-9$&r9e93Y!dlZI zjpNY@^YSO0iEY%fITn@B_EFSfJS`o_JE4Z5(xbD!Vg33B@lPcmX-VX{1C&hkpOyTl z{j{#O`G>O9>mtjDY@Aji_*ICN)G315%pqspMHe3vJY7zIav=Rya&QXCeNQp}CCbpl zZu^a!ex>mMOmw?-ZcpL&qPovP$xh?J%-WST5HCWUrNhdW%0UI+%dj}@DOFUZIpk*L z=>*}f=_)-Af)gdAGt9)%D)@p%lx|)^BETsw15wza*bNBEp8(>mWAfeB(4&L|#Q$=! zy5TMy_rpCwAR&Y=@X6_VjdC)FEQ}B1@q2D7%9_ht90KY1mvUJ%=^*$O&`}Yj&k?p6H0GEqgPm|c{J`~Z+N4Zxu3i|s z^RZJLT;_f&wQnN1V{--?v+0RW==)j2kN;AteEi41DOC>Ps{1!&iT!7#a{g|V z0w4=eCIBGocbUMvh7pu>*69WsM5-*lzZ60{ z=HgpoT<60&r=_>4D<=R zqPRZMx!bXmXk|>bm}Ss%;9igOL`|V62mk zn!q$&a%wtRuAbR+3p9$;|n#*Pp->I@w|Dl0FV_yxhz^2|Cz~8j^JhQs$egE z2&5al+E;3O<@ts3>p`yN{EBDqV1{%p^J#U#rFK?Xi{cH}z<~~On4Y+#uB-|c{#((U zdIvPsgAfQ>i*C6)D>FxxDiQ#)o)UO=Qp?c!Ub2&u;xT7ywzQ%t&vv(r79zTVyDB z`DaoAQl%(1!ckk{6er6u5sP}`EoTRtyK(CjvH>DOc%GU=th=17cG+1SYe20h7*apm z#Bl}iZ8EJ6h3#8=As%CFrrqUD%771>RbVCUOJvr~nKNWIBA}RQ99?;1mg$dXr>)5% z`BtLVs7t09b-yOkaEc+^G$yyB7X>@H3y(r1K~o!SyaW0Lnu;1^YU;GtH9H$71 z!+F{pelym>KNxG^XET7YfVN2B$eBK5JEH`HI>lycq*Y{v3G*odj71M%tVKaW6uUe>g$-W}o=YhF8hp2v4ACNpf^TNZ(>CljK z+CDRc3I}(Z3`@dv*Itt8vesrwPe}35k>&U z5=i*XSTbx5w~waPdR^}T#+v!VSoe&7G1g1()H}F`!ZZ>AP#29JPABhFgvwgiqCr!~ zk;Cg)5?Im(TJ47sqwl|6vYE|jhwukzkLf=rPwu~x=jI>AQWs{QlLg7dhzX;KQsH5f z1<~T@Cn&M^$$X=BY?5X=MA- zn>mAg*ZJ1>kpde-iOW=GjaxN@psY|YQ0CmqMwT{cpx9?S65gp`yUldcKXk zmEGly&h22vfNn*^b|we6a|3L17iKlu^=h>B{OPGSv{p@5v2_#t>8Uy)FO&qQ$d>%E zd*f5;G*#pErMcs&W)wdcFfRk?s6Kz;c)_%AZ_g*fBB2zYbBdv%?tYPEQAkD?ajfq%ma zaVkg@uE265cmuI9XqDfgnm(5TKNHSqL0IsB{@d?Y<-4=re*68bn_^`*(03ypix7c< zvS*cHp4%HGTkv_e-k8Fe^d`gunp8)<6o@m>2GSOts0|AKUr}ehm8`)c_dZu}MyD@C zRQBS5?M&Y=4Wm1~{z{khb%!B>OYreFjBXL=G#lL)kRq#AdqSUm+9)51f9i~k$I7=b zjemx=v?<1qYtPOP*cs?+`ar(k{AXeMvR!PS3Q(*nK(Xlmq5aSF|B|d>3q71rvKY;3 zFhy-+B@_WAXV-7P&&pzWyWn0uUU9)e1ih-kr0gQx%<6tIBp=7bTW|QAvCil+|1cIX zd9&Sb#&Y`2SOWmYa{HUHK>uQ_??jpoE-U}SSOvcsiv++}rv0A38S7KQKN$;&srxr$ zc`0k?aJT@`aG&bwJje^&Z@6ue>6WSIX7##|G{$q6sAuTN4F$cIxbYL(tED#X$W;-w zPWcb;aF=v7NW`Mr+%gA-@(Pd+5_uw5mN7%F@=Jlac@NT)(HusM!#@pg<|9|P0iCov z=cz~I&*DIvW?R*RsCeTaPTv^Ux}qer*ar91I}cvbAw+CUkPmLHQ4#9pmeCqku$Qm! z2qJBm1V~9upZepX`SW^h2D-R|-q2;4tqX41K-+%lKg)fwT$9K4SfU_nu-2YQey0=> zJDF(J?JurWWVxN->#D|!e4@^8&(s$|9$C-!7ynk&#*o9)*V`P_8=sQso*+vG>j8Fi zxK}Gr@JR~6gYHuM@>dC3)mgV6iUor5--t!c5&GuyTg!86r5>5T5liHgxMVp6O?2$L z(l6Q#Fu(=@Fw7c*YDYR2?t>5y<8~sWByzXR_pRkkP06Q6-GedO*s-G$L_%*s;hOZj zaFwxvW(O3m=9T#DhzI4SmV7zd_K21AJOI?W)Y_r#6V4;#;aqxLcm(q*$+XzXYALx1+p!WkHlvh6?d_6!430f z19vmAT6k^Q`ToC^{0U|G-czZ$)QDs2l|2)9I|=u(x&Z&MW(! zNP~?^)Q-Dka1Pt#EmkgYr?20Gj)NfzIhytnQtspLv(P(!eVr?JX3p5R7%P%npyh8^ zUAVKhgE$*CPANQ5wYiluw>qy$t!VY z@Z{0;n5F~$4ot#pMEm8!IMAw7Dc@DF=6Y&tHl0CiihDLtQPP*`+Y25;v^K@fcUsg% z!DGfR1X)No)97FXor(SCf(KrCbFAmjn)1h!#Ba*u!a3b)ONOEI9clIl({Nwj_m%`KpXAro{781=pN*Tq>HLayATcK zU5ll$D)d|NJJQA#zRq*|V@)>-J7JPndJjwM%BC23SW}uS{}=K>UG*2-i7mbyTLyDU z-H60ej@=mo*9~I>|6h++H!uFyZG@YqQRG;g}!k8NjBuix<$ z_|XTySeBMdrK*9F!;*VnQVT5SP0+6Davrm~qJr-;K@O|CG&o`BWz5tmTFBgL}1 zt5=E~;$g8p`1w%Q2rLB8xC)!1%CO#$lq|I7TYx2+$m1*~z6w{((GjqR%?OIF8i~|s z(Fq7LV4F5~8e^0=6{?5pEM^t)uQJLD+4)!V$T$dFNJz6HT#nE9u{_+sv5W0XktS>7 zxXM6E;%eK^}k-8U{+ z_*1(W@S|Sk-|RqX>W-#%Aot2}%7#;&J4go+9rMR&j94gCcR6YyFn;X5Q#q z7v}fCVI-Z=b6NNpGqLC1kgMs5$!SZ>v(JHNRL;?LWz*_7=QOiQ`JZqPI>AAAP2&Fi z5YdA6f09gsLn8l>zBNezr>5SF_EI=yO!4p#54?X2!vXNPv) zLTY_f))`>@oCPkYl7hq&Buyg67G!(nmA#Ay%1ujY?6cw88acDm7zF^DC zxq|oNt}xEF*fGKHZ0zO#f2Ul8bXr&VTM;+w4>ngmjZgfyEEmkJ4|vm%^nbx#=3BqnRrxd`1zl{u zdUQVvwtmd7-S_;buHNPQPnW-Z_w{2c!}IT_=A)+7sEDs|HNdp$06gpfbZAkubADb) zYFS``>c_{&l{Ns5~807El*5aTbIQ1n1`!x;w6Pl)zD=RrY%c5XwpBu zhB3!Pq9=0A64uP$*D_l+pX6RrGxhd^Qt$pi4NG3$ClYMur!GwPbzWDhmz0+|cWr8s z)64gqf|b+EW^ne}8gbl=Y>NIVx#4?te6yiaW5T)K+Q+Ps8*|THcvo~_McgBw9xY#2 zp3bgt)}1aZct5ZhGekcLRK2xYJ^EJSignKUb(+EKDyMe_O07&{oAsDaweVi8uT#RB z75*PRR5IWHIS^{y_@qDQ*1cY%)0^Z@oH|(FzyJD=75}|uIv+|aidXFT*q^L_#o+(N zsZZX^yYK#!Z*}UwN#AYl&d2Y(9!ZpoxX-yLvif0?t>>Sh4UE5&{$6N$dtdnN&40BQ z=Dn@|c6;;R^VQYgYTMtm^WE&1-PGP(-?m%)!SbNf-u0Jv8EWmAz;35qFOwD@;LXS+ z0$$33{U|YDjDbM|FbXjb7DLyKeu@`FFK{g(<6&gours~TwWA+!h0y*SXg!wWuFwrY zKLP+|z_vyWV2d4S0Q!bm@PZw5Q_#=nfSJ<3xSkVi3j8Pt)8$H2I3AaHeizw)j;s}V(^*~bW?DLRxcl_KcODR zY6|+6bcE*&fG67F3@xx(;1U$wS?GJe5e7;C&zD2>98zfEHV}RLHO#@vxTc3;0(jvFx*O2v`w=Dt8)BG% zHBQi{UlAs-nP8ZJH9pX%y$~i`FvBnbYh0jDM<7h7vcxa}JUM|L4(Q{-2otPrFiZfC z4x^iZKB|W>VY?lM3E(k4bQ91=Sr8^@qOO0U;x>lJxWe@c;^IChq_M literal 0 HcmV?d00001 diff --git a/dev/xcl tests/output_v2_2.xlsx b/dev/xcl tests/output_v2_2.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..58a7596c6216b1039a13e516c8ee508431c9acaa GIT binary patch literal 16056 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 z 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 literal 0 HcmV?d00001 diff --git a/dev/xcl tests/test_charts_v1.xlsx b/dev/xcl tests/test_charts_v1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..72b7c13e2160cef1ab849ce5bf2ab6d8ce61cefb GIT binary patch literal 67382 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
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \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 0000000000000000000000000000000000000000..287881de456171c67130fbf38f9e4b1da4199452 GIT binary patch literal 13302 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 o%ZO>bO-=%Gz?~fCFTe40sN;C1poj5 literal 0 HcmV?d00001 diff --git a/dopo/test_dopo_7.xlsx b/dopo/test_dopo_7.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d5062d85fad5f4c8f8ca5c4d6a506904b92558db GIT binary patch literal 21189 zcmeFYV{mO-w>BC(S+Q+<#kTFN*tTukcCupIwr$(iN^-OJKKGpSzI9I3_v5R&e{NMD zbIdWS|LE;$c-m~UH91LO5M%%V00;mleoS?dVQI0X@2AP{3;z2uv@wvgx3P7g)3ddu zb+xjTo|cC0qlW?8=~i%VP7B8^gcITp&+eF}bqZ>tunK&;z69a5c5@rS%BKw@ByCC+ zzNI~|VqzSI$@`QHX~N+@Ei?>>I=Y!#1(kTywfWD`Mg_ zoh;x|$01MJzo*8`QA>tbzJUz|f9y`e6M-bjoUt)P3~Vr zAi)e~&=u!IFtQo>}R{DB9XV>g;~ z=YtOlH=9|bG)qpvZx~#@{#?1(Rz3;*0L9fOw8T|&^~o>;`_~R(?dW}Al3^*{YSvC?x)6yZ+Nin5LV~Thv*68glP{P&J%JT zC6%sy_KB?GavutPKSV%;4k$A;gX-775nPRfeYmrD)V^ADd8Q<%(#_k;P3AIr z=6tm&Xcm|CiqYM;xkq!mN!`(7+fK9a=7lR!nMB3w`o&xSHRT1E!ShoN#`1O~oqt`B zjtc=V;J*Tt_A8SJ9t;2=lMDa==^G$dOFBnWBP*kSJ{kUk=u$(|VT}#h`?`9@)#S+v zdWglZsR7Bs%z3>bE$~tXF9<;`%F@j+F3_i2lm&h~8TK+>x-EkXg!=S-N?7#xgw5^# zidO$5I5hFjRdO1n|R44|LQYZM8wf5SflLedp^ zeCgMYCMF%x(pkg$rb?ihWgfbyp{|!UwJG0>lDvkE4t~QS9@7iTxKfl+0U(kpKWS@d z!`-Xp;GeZE9;4|~4je{5YkDj-e4ioy&|RgzfgW=cUKNYs**P&^|52|*&^Ga> zf_W4OMp#acEVU_T&!>UQoXD{?M`kO`o&8%^wx;p92wQBB9r^a`zN`n!?m~XZh61x0 z!Qvs3vb9Y3P#zVe&d;m3D=cS1`;ACxzH>X6*Rh;%s{$Dq0SL|U3pF@KnOJ;Kz z$;=}5S+SsS0IUPEQgWWv{7vCIeerX@Vwa{V8s#t{(ntIrMg0i|H>y7#Q`$!hncTCy zIHoKTeay-MOD@|prW5`+oE+qoBrHn{Oxbo&L*Bj>znHcR`G7M}RKgl^nmHi`7hd^81`^sPZd)R$wy{3z< zTOYXd=9*cFyi9uMBQ&=}tThMTs~FannQ6%lRP856hLx%lC|1~WdM-Xpd9rUW-&TnO ztJBqKh)`oC{p$INTmD3h-B1odo)~J9K#nWT8?lBXW(#0hpDI2|BrP9Sx01JTX&#(b z=Q;9FPrq{M95+ltGYK}^OP)0)b~Tc+Nj%g}*vjCJ!*H5##p5)&Kku=|OXTt!wVzd>5~JZF+L&NiFE^9D;EEE- zebpK%bkO>q(qubk7pbmfzq@r74cXVa_5ybU8n?)OLD~*(eTYMbdrO~i#h2O=?knRq zyX`v17O?ga618BVcdu7?xGzGvepg$y>?=toXM@MBAAZK4rS-z6%^eueg>sD|Ln;Jb zNpMfRcrb%V%?D^M+uhO{P@f&&>Su8q%Y*qXk9=-tbR?tdeY5-EXnQ9u9y{%?jw|A#d8f>waEe+xB7+u?u1C6P$f+{SyFmhQ=<$kOi4;TEH{Gg%*RE?5a z6cA-nyrt3e6m@%I&J?J42!tSBvAVpf);NEjr zV;{^$4;r#V-H0Q_lnui>?QlfOMJ%4Z8yGb145QvG{iKq>fZW`9rgzt)oN)e^hbBB=JfV`Z=MD0Ze;$;3%3c6r1kLELW~ z$|iaKXzAx4|08y5E}r@`*S!;fBOogIt+-Ah2Z8aTfkBxFK+j=(a-+Zte>S$$`3s=A zJ464NH5ddUQ;Fr>R$iy{3T@9RCT$-fJ?SUvb&1UoU9YtI$wwVXjZQd?54;8)D29OpH3g&H9Me|atJw}4X9ObQnrimil$F*yyOY_cC|8&MsF@D&Ig-Fnv80z zwY6wc8Xg5vhNmg^p$7OVS;!N*K!?D!vBCXsLb*H>21R`fbN_w)hfx2`iT@Gnzg+om zp`xZap}rGmkeTvOZr^2@IKq*txfa;29>Qv#fj9iofmW{#VzyYq!-<}NI9Bc7ZRp`n zrJ<8_c@Mf4(r5$u)E<=5QrJ(z!xyDj3|LZySBB`aRvts1%(}6nic-@9U!n3AB`=tZ zXY31j^iyfLwCM0iM*WT88_6!O>g~oZ{E-en+%h?P`1h8FcR~L(r~N+&NsNCbw*P-f z`u`>*{SE%*{~wb6Z-u18@W=iCqmcAB@c(Z}g7g17B&7kb0`Osy1>lh)z4 zd;{L;7LwtN+avLB&p0RhS0|WY1^d zccY?bl=&m}*$?z%sUK)Cp<#8qYrxjy?SCq?>f7;$T0^*|$o|KWgtv2{sQ?ZD5cwTl z|CcO@>93HKmG0$$$qFCRJqDX~+Q4Kr+wK^3nKmGe)GqDc>syi~a|{Dy?TMd{2@<6W-0!FiSU+*~o|S!K$~=j!=e-INF2?X*kT?KY>B#D@}I=Tv&y z2G;#dM{9b{vs^-K)x*S31gMw$-*isOKmG8dVx}29LI|*SZ_mH-zuIy*Cl0%QKNgi1 zA8u4md6cD0KIX3|TbpKYogKg42>N#;SGBO!A5qj#a?wp0Pey#KyaxeX+s{|bji}co z(`UA?l&H7Kns|YiGEcwNn;e!m*jr3`vN{0n8fmEYLt$Xee8ev)8>=u&V##&MP`P9< zCLULtEo!tUrGY!ze8$j5z`U{0I1iyH^EoFauj=2!%W3)0%o8@GcNZxaI^TCkybt@{ zJ%8nORA&Q?xtJY_V$XDZg$IK%@W?f~%dyFVf z^Q!WuklaSIHi@9rM1B0BuI#Y;k!;-)vr(N4E% zt!16w+J^@l0Yt@E-|l9*H2)B4!(+7z=K&~K$untn`T(uA-dL5jdSWSm`N{6xJq@0` zW?Y}2j`HNz+WR2rKpB3K2NI*TNeuNqXF1lka)E05&JE-Q^#Tb#=sKXbOi! zCSg~ZwW({a<4lH733_=T#VcX&>q8i(sWijZLU`2!tK%QpOnQtZ{!k!WN{wFNG}}#= zqxY!+1;FdgX4HMw1QH@AQZ(0=?SUPq_O(xO|q2z;Y_0rW2w3;CT}aN0jKHCdjEZ` zMO4tM!YYQ|{}P{e<3o}ov+RGuhd2~tAjd`=1Ir6^Z8S)PaLTBOLTU<20UA;|3O8WG ztl?i+A2lPK%R&pIY@vb>475!&DDKOw2R^<4nfgAiU$3)Efe5%EX?WFfxE z7JG@U`OH99W^R1j;uBMET zQ3S!+;rLI3$JYiXKh`aH<>KvTgZ^vLF@}g_A^L$1<=&L-@)4ukfC~r-WW_Qc+s;!| zbm)bIiTwi#nPozt)i)5Y^$G&Ln7s^rGyBJSxrczJZa_2NIGm*Joq0i=d@JPSTOqmM z3h_<8G=QRMReJxSJ|j$UZL5XD=~1IIPRsY-mq%PJ5ZJB*D=O5`jjQ!LAxO>w0cCz{ zJBV%PVN0ByT(CAdOefRBON}m!zk&^#NSIhN5OHH;?$mbf^m;N&YmwS8^5AdVc_f?q1BHh6w`a~=o$z09It>QNyFyfnTt zxYD~La8M$UVa03k=J4~`%BsPchEpkUnIw*!)|u~_0=QBN$BwW_Dn!f5t_mrXhHEwa zZ#Gp{$aOh4^h08@8;Y{myKvU+A-5GWflX4&{Xnbu8fl~^PC6$wOw(lwqnQtflV$G0 zchT-cQ}2?ys+w_oMUy0LzQkg?Goz<_8(yAnJLC4Rl#NcisfuSh)wWcNbv9owJ%IRQ z)=61eINqMtmw)>|;29fG-4qf4;FJ1)~4X$C^dm24GN;JFw=N{gk?biLvBnA~{jIf)|rn;smFRHq> z*G60U`Jqh!|IhaNh{846WESvIX?fas%&z3-0CV*JSXA+Jo69TauZ+ZY z&cU*&xeb}I-x@Ti;;#Z4Vvf}kdB&9Nh484l{wR#6iL~ne#=$$p~eX-VmCE3U{>0vBeE%A+8}JZ}Bq}woq%N zo4BHFRXMByiZSxZg10FSKc~1dj?(r&vUXp?!i+KIY9otvt=n_Ko%a*tD~GM7O^rDp{Lz z!q92c|7i{EzXne9d3Mk)Zu|h4Z>mw&I$&uVRpDjnW`%|@u8qWBR#)JeCJxwaC{{xU`qtlmt3iHs1@aNI2I08Gf zGTpn#-2CM$v9HDH)oV92yN?ohVAV*0MfA~~u-&uM)|9op){50PtL4gerLA8t4-`O> zB0%rVTwZ{sz__bDJt1~*v8J2=(rmj#-A#b>6&o3~^aTWFL%hmgixN{^L3$ zNDW9GH{pDOhX_nXSGi*BO8yHOs;;Ja)F(oDN%$&&jA+@O_afY?k2sFlIGwVubp?aV zl7%v(t$~JfE%DZ>Do}4HLR`cYyV>c>beNg)NXc$ec%n7VYLi!!fj`C87`a5jR6t$x z*?|+B5IG?eiLkVI0J$`tLUW!FoT=NMw4u`1D zOtOgvf&j)|DV(Xwpt=faV3vZAQ_%1@^a3bOi?FWTS-$23ISA57h)TL;*6MP1MiH$> z3d4YbB;AfQs-!DK2sfI3mxmO)xjvTJn33fdLJH)*#U<**3#RcO6nf~@Kt0q3^sXWDN%k9e^{@_kFux^7ew;l!Un4`h`;W*V ztYQ#yiW;JA?86ZW4NuU+x>-zd@3^hW5l18`)tDBt%xSW!if!z|BVFnBA&_ni2ktw+ z$>jzoiA2dKLXouJk}kO-RrhF)kVZD^CfcE`3#_59`?GhFtPAkVHH9sah6IQLr|9d( zM+Y_U!)dnRZV3Ho3B$ET;@%;3?~#7+%`okaDn*Y50G09w{{l#b2(%8pXwf2@iMCxi z=mCSw<4Ps)mVsaW2ju)H{7VrXjH#qJr8m#+n+849z*q@Lz=#NOn-1Ez7S&Kowt+y8 zRNFwvXNKJtEzE_%RDUG6C3Yghnex$}69-hfsCAJ2)P{C1MbO3sRxrlBnOlg)dHCcL zh8BvFf+WoNh&MpkcleGirRP>PM61BYb>I_*KT=0==@Ynhj}8H7sCOcV=vBg9+mOO~ zDlS73QYnjf1P4f;{$LY%7taMi|Kcx2*r4BY2emPlftM|r1CKdc>vW$%ymF{z?J@~1 zaS(FeJ%)ZZ1~VW>57KP7%l}1~T?M~t8w&YMzYBqMWzc8W&a8IZW3`ve=+_$Bep|fc zs`wxH#tv@Tp>2Hwy!98rTi*b8lrFKLF$Z_*7ZOMe4qY#@IC1~NF2!|KA-!rD7VkCR4Ng(Tz@HZ%#n_|<3!Vj#et2SORp z?QL;HAvrWLzdg($6jKo4UaRvQ|vA)a=G&2R# zPen34XST$PamjA#FI`~RfWlZ#e7n@t%rIYxKwg8n8Be@HWEY0wxbGXHG@grcgRwG@ z>R{j%<Vo$Jz9xnOWu;`5aQDMM>4&>xDIe79l@|>_P4$vMb!JiY6 zksokAY}VyWBD=T%iZZ>t=uQT66%1zDexu)FCh$iD&`Je2Fi3$Bb&!w>^2p)p73?CF%k#RC|)0?^L&OP)Lp8AQ;11A(W25X7JplLwi1KGYRrJ;i)Sc%4F z5Q_8$&nWRp9@^DaN+BN#4At~AJ3NzAXHy_P4cs3<{geaUXx~Y2D{x&~3f3%%Rq*Tb zM~MHVSynu{Lc!jM$lFz{z1iB$CSuoiW$#PERD-T8MwFuO_UQU=QvQekngLCzm@@|$ z`1XtraDl+Fcypb5WBg&Ku$k`&XY>G;*@+F7bbNg0@V`_^e*Rr0(SVg*mqPrcLM6-E zN`P9Gt^Vjj9u2gCI;?Nt&XOTj_YX2CX;O^#@tGz~9LjinLlGvIF+N&dT}^W{Hfzcm zKP&ZKXoLJ@os4+fA2OJq_{4@en=QyLepF$f2POVt^ivwEqmnehUFFoVgFgKTSVW91 zwOGBjw!ZV_B7bd__E8}_} z;JCfR65KN`R=<^T_~geyzcLkGFEs4L5uiGKY{VdC5@6jG^WX;8D^!}P464E6guZquFcS4(>L z#*IhKsWN*myq|FWdD}H2fn#HO&Bx8-k1_ zhx0|f_lqs(HuRh?Sj*@3M)jQLj?K=(k4m@07bjo3Pd{Ws4okZA+;Vt1si*%W^ZxEX z>KG2@wB&iW8d49sfO%jpmnQm+1A*op2&|=i*a4?KUH~`t4z?)~6Y!q0gVHr8V%R41 z3U8R?R_Pdvx}R%_`!ju|f|``BHvsgJU?dzrN7lqOJ+Jnp`16_f3xPrA^DU--RxjOgS4Pc;57N6b1 znX+`hy&9I`pS&qrVT<1Ij-Mg@bSa3|H?Ctm$A$}AH1 zd}wXyEuqI5czgHy?ZWDopOo;9zjoLQkp9#nX0fjsvgD*Ai9}Q-$G{c%uxGwqxt!4p zXB$^Bin*h|G`S9u@*VN}{k9+z`hh~{K6S)QLu!_1v0O^bo$0Auybq8;H{%|A-B{&q zOe6#rbR$P20*hw)LocTUmM|MUp@9YE#-Iw1@c=PD25Ptz-H+v1l{K`@!Vgzl$GDFW znq{7gPUlx^_QTAd#!|P))cOXENHm)a7rjThuh%`0P4Pqja}2Pmd&fHhbXB7$fTiDQ z6{!nUT(_3tIN(Y$XuCo{6L|D9lT@7o)S*?*Cd$)Nx10=$u`?*G^TcBxpLnRYlXGKw6eNS7(;9Hrm?P)yWq)8i`mn&l4Tt_ z$yuIO&L%_Wn+T|9>aSb-zJ6GRn%D?(oo9ySq@-1A1)SB4U$haCJhj0@PirZNWy`NTPzbf& zmb2bN91e;^nJ5v&u+c>5q(=~fI1b$>Fc#X`JI=ybl!=I;?}MNhs5z#ZT$e~7fDl5B z2ouqeR83>VCe22`7E2y1ulYK(rc15<8<~Vb?}q%y>T*5VS@UU7Abx{dR3C*%KTuuF z1-Gyoca_xwWhkHg;Dw(WxmoeKy@*DDG8IuTkZV|_lAJ8eWY=(+4Ert6gJ-v^lYn0^ zh+33D1mSQgI35^pDNok*015X!kc?-yRQ6?*jO!k9`ZBUaKn?-n0f0gkQ!;|`1xxwn zSzb6rolMyg114c|JTHQ#4aU=Q*65zL>BO;T7`4L*U5{9wm;y;Yw5zn@$)TgnvgleI z>vssP1|PR$ZP~gkniZ@2iN11c3~kGz4SH{Ps-2@F2~qj8_RSSgizSi#gGZ1Grh?<= z$`zCsx_0P&}#MGWu4SUYqAA}j4%tBan#{FTHsBzQa| z<@I?%%`Aq4dOp!)#BLQ0GEwI-y;OK|`w2kCVm8r|ZHDatQJei&6!KFh0Mt@u(Fb|;4WlUTa9z;B-Vm=Q26$c_m7;`Sv;Qx;cEW_jzP zPlwuX?V0QkUurbj#frID(h%{#XLGj`BSeJewAJAfbFmNudWPLtkrIsvMEPB9v8J^9 z=C_M0dH-{&PEfRO2HTe%ylDxr+oMAYF~|1X#x!Jh<}jh>`-#PZ*GjW5R?mQ>sFH-r z#9_{w-nwi~0~(7c>Xd_AdyQ!76j<0F^l)AShk16xvf|M<*i?oO7j6B$C}d^4baOoW zm^?_7I)rrcPvmIFIGf!;e?<_FP$#DzC5EorKv~O2cov|(@|d48o);$51{@3t(`c=I zKi0XSH9B(a6rir+PBNqsv7(1(z)xA4t1CB^eHOwl2-1{~c`XpGpUw%=d>iC%qi^6e z3O9k-2bF>l&=h%UMQfo~wdhk&5r1A{0izT^6;@rG#}-A6qdDUaNhXYR1#5YrcHNFx zFDE0Iu70O75ZO7($&$aW;(zd}Z!|3^78EQl7Vovm z(7N!-mk`{b5=NJ-8NV*mwjb9$j8Hk!G&C{$IU3F}@(3(9GYp_g)1BJrIiFZR=<~Ia zBCRNc78ApPb3R7S_bHdJ+TF#l4ejynzA^Rhy(`jt1JY)l z4fT^s099=QAWAR}QCNB&V9bkEHK8eDP@on=6iMBlg5B)%!#iiTEOnYqYoo77atbfc)Yx+w=vwY~;K3S!}o)%i}O5&%ag{Sg-qj{AfhgqQU zD<(-`_2gWI+Nzq3w{tPe(ztMzNf4K!c4O+K!ea-;{oQGQc=z2tINIt%*AlPVx>`<& zq8r7z>^)PEjYYKv>8cc}vI=T;ybGQR;#c(n^#Ty=uoG!c*^-HDyS--ekGJvOgMO%4 z)X`tj7L|m9cro^pN3BaBoL!ydT5ev7&`rEwGhOR!KTNFWC9IQPrCCa^c`HIuYzVLC zgtc`NW#8`x^K!tfOPtzjtH#J=%|uz3hTjD5osl4IO@iR&jVk#jZ+>;orlu`UpIiKt zn&s8R>K3zZBRMY1q#kk#*-6%{5eSr6Bxt@&9v`@~-B137te%q9Ze5yIaXl^` zva!V*ckhn7=>olMly!uo4{ptng!%(VAw)2_SZkKcUUIf9`wv~G*F`MXCwP4Gt76DO znC4x^Hh}{Nem{pHPn*$`;?LV5BNM2pjeHNY!zA^TomR~o=1Mfm$xbtk&ilKIb%8A- z`@N*Gaj5F9lyv-Y;X`F7jd8dSH0uw|!+?i5{XJ#-#H^?S7%khRXgj=cl=^T`Nc_%LCOK(f2$~p$^jAGEw z1>0ianWB@S%9DI4GTaI3vQ`-Zl0^fS;2$GXS>{9DAyd=9P9I$Ihf1i|TnBH5imxB7 zuMTNOHt|W@^`mtPN@dALs-2<;pEqU{zHC!Rhxw_@s3=CY;eOzvo%+P0`|=@HI$#PP zzwpt1;>ty=YZ41<+Lrj4QTw?<(T87`Wu86>95K5kdx_5DlXFBMI&&F4cB*zbDoDyE zP1bQKR~Mf8e16dJW4L{7!wGh`!G0)lmD>Bg5QXl8uP&aA0&I9&mR*T2XAtR*0z70N zYWU5~>o-ZQsOdYMmPHr>MAF?mzD)_*DTP`F`A>$Q)@hC96w?+|i_TKhpmO{S?VR#% zvZR+HUB!t86w?V7T%O5Nl9+BOPHyv9dM{Y47c-8dF@Celt<>o3v#ZkpMtaS$F zoSK1%g}LM8nKMbHB_xJ?B)wH>sav`F%kWj`SvuvSN8h0y8?-HCh%(Ei41LAHJ>3+e z1kbt?Zj851Ka-ABnl^pQy139=lhsjKKbLy&%QL1l|7KO;05f;A$9>TeRfd?mVrH5T zw*oSOV(md|pRZVnV>EVtl=BDKjs?X&zyhnFi6hujow0GMy+uo$_79Rb_wP2n_Im8>Ouku&n@2#rZKMAh?Ce* zIS6_TISpw62(W@WH|XKQd@Y9)rF!%@pmNj#ywUxsgUJqfu-Yv73<$_LWP2+y)L2t6 z$0E%6JO(vvcL*v8j~$Z|QfooC2ce1tLdY+f?_3a-WOT6HM5wQs{<6af!;$F|s-zCc zAY6S9zWW<7me9Pb6V!&pa74JnkwSi>o>q(@1dr^Hgnfdjq7!qT`!^r;rR4a5LpAio z1Y`Xyw-JY2_DH=k0AU1ry25AWqmj9cU^)4|&}Zv<(uf)B$2@|jM93_#l`W(ha53>T zlFEU)!_@hKxzRz&)}u|5i_sA+=Y&_Sq3^VNei5!Ggt)Dt!?b&05w2&1-i`=SpF-;e z8sy7f*aeo}Dh+;O7btNn)dUJTYIKB;kd`Fn$C_HX3uHS$MO&iYWaN+BdmtoBV{z!9 z%s0TOLMN@MEB6<kR~Q=J#-Z3K=f&iE9&rxrXRm01^|K@p}0Fse3*$6e~1J z`X4lQ)QF6@di^Li2yV3%``{~(YYfb~>X@0r zA*a~&Z1MWH`$$4Km*Li;P?&TeFroR7O#mC?{?rT3qJdwQzlO%_9oEvp6NT#;7;%YX zr^ibxqC^Q{pJ>A?l{eg)j~@)V1N9UF6^R4&j$$R&CWerUXHCH;&>Nz*ka40mH^hB<)z)RCSR;?)Bw-G&>1LI;cNp(WW)nV zBHt2Kebv*jH?&|qiP>TrWQAIe@Yc1~p5--*A3%#-E{R8LrSa6-hg?7vr&Pd47@L+{ zEle@jfmuh2SpnB}zMZ(QqQibd+{2~ii%})#FU1=G=-)tv_ErPxe=7k03DWY?B~K5p z85-6@=OxQ@8AlPb)pPt#vE+9|u z1^^vQL=d3MwtG+ER(>o~4e5aT#3tmv=K`z(7vcw3K6PDIE5wB!!oD61Oy39Vb8pco zOit~14f=B;n9z71RRV^hZvcgeoL9KPn22Bq?_b~>b>pYz`-7(vt9}0s#KVc~WjE8s zVJf~yXq|o}kM}eGIT#*4eAh4_2ZfRQK%B2vN{-P+*JXW53wx)BI8ImohIDo`+6anv zhyID3s3kjK#=OCBc`nL5%2v-dIw5tqiNvD{{h~b-oOUlS!u5a{h6_aLT1SAG z5jvZ%QgAP@7z(D*1U&*_YA&lI!m1Cb6YA6Rbn7iS_VCTzGNN@w7%QYOU9KL#a9H$4>l}76TCKm(O2iCL zy$Rw21E90{^j@+YN1!c+uqyG$s%0gc*Em-Ne7t7QE;LU^NlF>#Ji#x37+bhg1y9jA zv)}r&ziOqvd|2`7N;e;-ln(gNURUAcIosX(@&=uW^#%T~Wyg|rE|JDX1Dk}o3?F|BuR#6d z6+Jck)FDp4s9oO|(T^+;Rv{c{o0nfDA2~=tn!;5!H9MInMGkrGmRw0lekO(*3N$Q4 zKGcFcTZ*%fy{JpVP;N{KnQBcznImvHgClR>oy zd6ww9+v{4%^DAK4e1RLo0dQ^}x04f;j~lXfuWy8ciZZjoT8lJR9LfiX|8{|7inU$9 zZEPs9ybYj25zC;6kNKAfi77$6A+%(R7$5aRRE^Re>YMyZ44fsKR&#`PSHzVk+Gc>u zaT2_lBD1CeQGy#H`MO9vh7F>G)@-1XP7QXOFG&e8MQekiv44%XBDIKdfqnWDn^Q3Z z0t2W4)#!C*W7(zk&jqx>5Mw~&AE_b)9#T4)mv_Rg3iHy7BwkI(>_w+X?#G9@#Tm-n zC4tm*<1DJSPhe^|mAXz!Am!)WD(z8=jlE~?z!lM| zH5|15i;L?$WviChmp&jmz~xi{i;G}t%BnSGsJ&&4!{*Jk<`@+kUjtAkXyTvG8_7GZ zcrEeNc+ihh+?qN?QNN$8V~N+eRRWn+!F9>~M#} zxe$Inf*2R{4c{Yw9qeTf6f_l^r&(pKc1u%mubs`hDNy9Bj3f3IRhUE^HVVdO?1~Qh z`0lXycFb9T{%HDT+eQV72iq|ueRF$W-8Z;IXUe3U9Z}qiG;<%)^#S%s?1RkE0D;9H zPqz+-1;gkxy?kSP{TK=K;3+Mn2SA<1eazVRiC<}BWo!+C8``1g*k+3r3(w9|@CF+9 z_{+q1*jdZFHYEERuHfHF{8!w{btvDyqx(Pm&Ofcesq5C54Di7^MiH_0NvW{`8V%}U zvH95e!s3Y$Tln>yz7fkJO^}i~T?QTm*b9`FVaUR^wN&f-X}5oJ068N=l+uP7q6kJ5 zXdi^lOeb1ILZd@s6}*Ut@ofv~lqFzU7JZ4sWzx_KGnJ^49QJ-C=F4SZgzN;}Ma(rL zh6ya=1qEZky^Q6{)w-ewH7q5JRup*lVA8-d7D=tZH?Vx_&`pVpuBrlkghUH88_pi$ zlA24+L6!t}^4)0pVovx|MH0mC(8DWWQ^M01asT3ekq6a~QG($x{dwjWP2voaDt==M z;F?RoqOeLo1ggkG%;OalN|WpfICwgN)}XH{d8ZKF%IpM+EQzaV!1vR$=w~ibvZ176 zp#&dIW2J%B0`NmY4IWO5W81C1lU9xot6ljyu4gL`9AtX@D|SlSR7C$EZ8)7_g5A}i z(%LV1?gvPVQFVnodt1E(TRX)v5$8Pj&#xJ91}O%6ac7^o&Ntesa3l)D<$Hm&`XonXt&5h~X3fXna4md_4f zamzOi7Ms)>n|sa-VapQ;)i$qd-!{}XH7l;ZUpc~-&ws!2f~iQK92)%;z2UBDt5@5! z@>^WRR51~*F~9t4b0f7%rH{UFeFpKD#}%=wm1-w(`LF8 z7mHy{^WuI)zCF8B3b};2)^zgW_Yg|PY*-vGzjz8?)kE;kA`bKvZ^RA-j9dj|NT2>b zm{l?9%__4Y5SzD>-FB{DH?TPagFkUj6BY}I;gPuY&hGPLCeWR`^ne}!VOlqub>ch! z+OM^Z4Gvf1w~(FsqIKWE(_4~Z*!=U>96+3Qx7Q#%w@$^#Pk?`=Ak38@oxdnZ{C{?% zzk7)<|BZr9g>fW^9ubv%m_R8h zatE1|EL9FAIn<>qGG#vbi70N6|3E+az$30Ksn#y`k{x6U1?faf&*O!Ua)p>`swmLM zdeuywDT^T%3M~1x-dR+w*rPk$K2$29`@zTZ^=A6D&&}FoFqr`@pZi+Da-fo?E292j z(5#}0GMm+An-YEk$|s2bUf6i1O;b>Krcq*fFhGUF-xQ=J@fQUhIQ&IH6OaE%K~KN9 zepuuE805h@6#s`-m$Pm&D)=q`Wrn%u5AHmH+gQ}Js{Iys5miErYTDef)EYG=12$uJEOec|pEZEX3rH%W$8;CB^cpwS8evr~ zee{FwBMM(UtR-(Ye*6+gjR|ruj;pSk6H(%16+^tlq3TP^SY~jt zk=70?7CJHY+ne98UKr6X$9*ZJeFV!JhQ8fu-P!%r}EED!k zl5n2>Ej^T3Cs~4->Z`Al2EL(0S`C4z*F(2Gt7ZCIbxsVhPNT#7tN-WWK zvxzZbvO0wN@%b{vl=l`Dtw9mzzr(t?14>B9@?-_siAuqUA;t9{6&h;qROp`KF-ZZwjLS7YZ7^-_WG_i-H)x zDd_4yDacpZ3bb4Up@Jg^xV(PM?efktX7Q=^Os#?gOV_Exf6)%1;_6xLl11eiwfr5n zhC{vgtPn!Q<$JeExm(8YQ-kGo1xu zbD5rE_^K946lLj!ifLDoDjcsTlNyEW&1IA?Sf55_lL!C~3{57U?o{-2nGNg)KK05h zBA(g{%!NW16mv}RL9$2`p~E4z$hfYy+6{|wVfFmd@%xv|22FUp25X(Ln-Uv5~~nc zz%;i+3)dGf?gHNqEhIa1UoqqBR;)ws*rgN}re?V8)KA(M$6XeDJ$J`ZyMPmsD-%9Z zYi;j+(Jfk(e^2CguH-~Zzh{0?p#cD3|M?N*;OJ&)G3xP>xDhKxhA@gkWeD(;5$rAJE} zu*)&S6f=3?_GZfm*i8&Bi&)VW6Y#M6qTO-eqNbW_v`d%YifMNyr`VMzJoKtfIfD!pq%e~V6qd}qFzyP2K&G+N;q_IZDKac}zccWl~7P15^HDx_t;Q#w7| zEcp9b6cF6bgaK2&BagfVrmHt~+0$u)55-A=oNkpB{ouupRNJFj7`;BA{cREGTAM!F zPYUYugV`23;7rl?$Le0TTSH+QS#Pg0GB3)v%uqP*EvK_T>Z1YH!ffw&&{e8``V69K z?@$(Op`Lf(S@cBpvNj69F%QNa%7!te4{rzCsQw%nxw?%VfNzOADI2x(Q<6Qlm>ViX zeiVPgZ{6bjdTW2F=|=pCm*|n!!`QmEEld*9l3?P`i?cV!dP^k#4X!bzLln6B>sR}Tt;^= zHtI`(!%hY;=S4h%aQrT_?8F#pQde-1X8M*6H);$PBN2}BL#y)CdLHTS6I+hk%SPaL z!d!w_R$|pp!rL~20`k4hV#ip1C%<%@g_!dlsX=!2V67ti0&DsxWYC};>LC;6>9Dhd zm#F8EHWGCeWIK>rS1@%E0#_`vz%c-a+*s=S>)w!Od3QJ2qUEo^<$s@LPYG|w?)kQ5 zq;I?aHfw-?24DXkn9GW9weF{f;qw`s$)*RNA^3mRVVwF=jO8a|*dqQLLabe`bN8>m zwX|>ow`8TQjnEyrOoz7?OSW~dl=v$Ulo@q(2cyR})&9(L6P?yw(UCK4zvSR@JK1?d zl#ZQ)o@Cngy(wbLiWTh=t(Jy`$W`uC{-APvMDNNe( z&$Q_z|C>6r=40;Lx3gyhH!ueP_l@&IGB0SqZ&H4KHYoeE^pZ%i07kdw(-g&b;vFi~FUkCodO2{9F7f zsh4|Z*38|tv)VSXu0Cvjzj3n1+ml&hlV_QfuKDmCVf~C?X%J#Q>hQy07<3B3* z%U{fz;kL>nJ(b7G=efqCdH>wq;;%;>TB~2p-RCaL>BCa8@TlI3BbnSYtqdAYo(>ak zkC^tSIJ{SN(}uGwi*3Cc-mPl<^tvNY!R|-LmNcX* z?g_gSXDsJjx4@14s{Budj`(e!mHwVKZH3YK+r;muW`8+$r{n$l-)~Dzbz3rL#_nfE z?FOXQM`fJ>#@|^M1_mieJVMeea%@62d|uhh*JL2T{vlkkmcd`(qC`Sbq3Irn=Hu=Q zw!EAxcrWe><7|r^6a3D`UjF}gT6WdZxT4kUSy9dhSNv);x@a(47`Q`J%TqeHU`D=H z?$jqcx0p&qmThwD*mj%cO?lgi%kR%Dn8EXJ@`}}~bU&^+z57sOJmDvelPX?V0Y;39G#}qy29UzxLJR&x$U41cRL?bdkH^VNbwCY6~?%-OSwvbARx`@hav zdz5qOe)W_ok;fA>b-!sSq^5HwJ}p0B_dc2ToG0VSIoAKriMnpgJ$m82)`2y#k9ypL zMU2=JKTTaUg=a0Z-UgNrt4>K>jI+LZna{ac*6nllOfv=7b8|O($QECaEzj^g@h)13 z>3M2(r@NA;`*rq(x0#OE-`uiS`th_<`w3T_|H%D+^D*#0zx<((y;rzx?i~4jgS&K1 zeTn+l6;&) zcQh}n(BIb+8TUI_{+LhUO8%O#|Ndw90S^>lWD)@nhhjfG3>Y(D&;X1o%%jB6b)%o# z1<}jE(7<>YSvTwyFLdqbr&=MjKLJ{g<%}zI1JI8UfElo@QI!L10MhXYh=UQ(O+i1N z17=DC<04>U1v&$Mas-qC9y&%h1^vVcgejuHN($K&NZv;_3!L}S4Macf0b$@Ph=E9+ zgBXeMB31)&ht>`rtf2)S=0kTD?$9#jL-i-rhgeNP-wcoN+zMb#hcmPgyU5XF literal 0 HcmV?d00001 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 +}

EY-CYxNpj7(Oj8d8dR9Jy#eUVQ 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 W0|~l60sz#Yf8Ri}Bg@@iXa5VFEZ}?q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..37088b791e8d19b3206d334eaa2e026a15e9f192 GIT binary patch literal 12905 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 O7x`mW6=V7<_x}Kt*qh=2 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4df56645915f4af29540e6aa46ae8ee73ab75ab3 GIT binary patch literal 12805 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 literal 0 HcmV?d00001 diff --git a/dev/xcl tests/~$output_v2.xlsx b/dev/xcl tests/~$output_v2.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ff55ad2509386cdf7303df263b3770bee381cb1d GIT binary patch literal 165 zcmWe|PAtk-a4X78O-V{EN>43PAPNXDI5Q+N6fxv8C@{DI=}d-Hh7^V*AX&ta4ki_V HGSq?q3Q-lU literal 0 HcmV?d00001 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": [ + "