ATLAS Ranking Tool

ArcheType Library for Alternative Seafood (ATLAS) is a resource for data about seafood archetypes with a focus on quantifying the impacts of conventional seafood production. This allows researchers and companies pursuing alternative seafood to focus on archetypes where new production methods will have the greatest positive impact.

However, since there's no "right" answer to the question of how to weight the various available metrics relative to one another, we built this tool so that you can decide for yourself.

There are nine different metrics by which archetypes can be ranked, divided into four general categories: Sustainability, Animal Welfare, Health, and US Market Size. Each of these categories is made up of a weighted average of several relevant metrics (except for Health, which is currently based on mercury content only). For more details on where these metrics come from, see the PISCES/ATLAS user guide. You are free to change the weights for the four categories as a whole, or change the weights for metrics within a category. Please note that the weights for individual metrics matter only relative to those in the same category.

For a given set of weights, this tool provides a visualization of how the different metrics contribute to the total score (for the top archetypes), as well as a ranked list of all the archetypes. It also displays a graphic of your chosen weights for both categories and metrics.

The widgets can take a couple minutes to load — please be patient!

In [48]:
# nbi:hide_in

# Imports

import os
import pandas as pd
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from IPython.core.display import display, HTML
from IPython.display import FileLink
In [49]:
# nbi:hide_in

# Load data

df = pd.read_csv('ATLAS-Scores Only.csv', index_col = 0)
df.drop(['Scientific Name (PISCES)', 'Average Score'], axis = 1, inplace = True)
df.columns = df.columns.str.replace('Score: *', '')

# df_details = pd.read_csv('ATLAS-Details View.csv', index_col = 0)
# This is the full view of ATLAS.
# You don't need it to use the ranking tool, but it can be loaded in if you need it for any other analyses.
In [50]:
# nbi:hide_in

# Define relationships between categories and metrics
categories = ['Sustainability', 'Animal Welfare', 'Health', 'US Market Size']
metrics = ['Overall Sustainability', 'GHG Emissions', 
           'Max Number of Individuals (Log)', 'Edible Coefficient', 
           'Mercury Concentration',  
           'U.S. Retail Sales (Log)', 'U.S. Menu Prevalence', 
           'Import Value (Log)', 'Import Volume (Log)']
metric_dict = {'Sustainability': ['Overall Sustainability', 'GHG Emissions'],
               'Animal Welfare': ['Edible Coefficient', 'Max Number of Individuals (Log)'],
               'Health': ['Mercury Concentration'],  
               'US Market Size': ['U.S. Retail Sales (Log)', 'U.S. Menu Prevalence', 
               'Import Value (Log)', 'Import Volume (Log)']}

# Widget outputs
output = widgets.Output()
graph_output = widgets.Output()
pie_output = widgets.Output()
links_output = widgets.Output()

# Make sliders
style = {'description_width' : '105px'}
sust_slider = widgets.FloatSlider(value = 0.25, min = 0, max = 1, step = 0.01, description = 'Sustainability', style = style, continuous_update = False)
welf_slider = widgets.FloatSlider(value = 0.25, min = 0, max = 1, step = 0.01, description = 'Animal Welfare', style = style, continuous_update = False)
health_slider = widgets.FloatSlider(value = 0.25, min = 0, max = 1, step = 0.01, description = 'Public Health', style = style, continuous_update = False)
market_slider = widgets.FloatSlider(value = 0.25, min = 0, max = 1, step = 0.01, description = 'US Market Size', style = style, continuous_update = False)

overall_slider = widgets.FloatSlider(value = 0.5, min = 0, max = 1, step = 0.01, description = 'Overall', style = style, continuous_update = False)
ghg_slider = widgets.FloatSlider(value = 0.5, min = 0, max = 1, step = 0.01, description = 'GHG Emissions', style = style, continuous_update = False)
individuals_slider = widgets.FloatSlider(value = 0.5, min = 0, max = 1, step = 0.01, description = 'Max # Individuals', style = style, continuous_update = False)
edible_slider = widgets.FloatSlider(value = 0.5, min = 0, max = 1, step = 0.01, description = 'Edible Portion', style = style, continuous_update = False)
retail_slider = widgets.FloatSlider(value = 0.33, min = 0, max = 1, step = 0.01, description = 'Retail Sales', style = style, continuous_update = False)
menu_slider = widgets.FloatSlider(value = 0.33, min = 0, max = 1, step = 0.01, description = 'Menu Prevalence', style = style, continuous_update = False)
value_slider = widgets.FloatSlider(value = 0.33, min = 0, max = 1, step = 0.01, description = 'Import Value', style = style, continuous_update = False)
volume_slider = widgets.FloatSlider(value = 0, min = 0, max = 1, step = 0.01, description = 'Import Volume', style = style, continuous_update = False)

category_sliders = [sust_slider, welf_slider, health_slider, market_slider]
metric_sliders = [overall_slider, ghg_slider, 
                  individuals_slider, edible_slider, 
                  retail_slider, menu_slider, value_slider, volume_slider]
category_slider_dict = {category: slider for category, slider in zip(categories, category_sliders)}
metric_slider_dict = {metric: slider for metric, slider in zip([m for m in metrics if m != 'Mercury Concentration'], metric_sliders)}

def compute_scores():  
    sum_of_cat_weights = sum([slider.value for slider in category_sliders])
    
    if sum_of_cat_weights == 0:
        with graph_output:
            display("Please don't set all the weights to zero. Fish are important.")
        return
        
    category_weights = []
    weight_list = []
    metric_list = []
    
    # Normalize weights within and between categories
    for category in categories:
        category_weight = category_slider_dict[category].value / sum_of_cat_weights
        category_metrics = metric_dict[category]
        if category == 'Health':
            weights = [category_weight]
        else:
            raw_weights = [metric_slider_dict[metric].value for metric in metric_dict[category]]
            sum_of_weights = sum(raw_weights)
            if sum_of_weights == 0:
                with graph_output:
                    display("If you're trying to set a category to zero, use the 'Categories' tab.")
                return
            weights = [(raw_weight / sum_of_weights) * category_weight for raw_weight in raw_weights]
        weight_list = weight_list + weights
        metric_list = metric_list + category_metrics
        category_weights.append(category_weight)
    
    # Calculate weighted scores for each metric, fill in missing values with means
    score_breakdown = pd.DataFrame([pd.to_numeric(df[metric]).fillna(pd.to_numeric(df[metric]).mean()) * weight for weight, metric in zip(weight_list, metric_list)]).T

    # Calculate weighted scores only for those with data - this will be used for the bar graph
    score_breakdown_wo_na = pd.DataFrame([pd.to_numeric(df[metric]) * weight for weight, metric in zip(weight_list, metric_list)]).T
    
    # Calculate total scores and sort dataframes by score
    score_breakdown['Score'] = score_breakdown.sum(axis = 1)    
    score_breakdown_wo_na['Score'] = score_breakdown['Score']   
    df['Score'] = score_breakdown['Score']
    
    score_breakdown.sort_values('Score', inplace = True, ascending = False)
    score_breakdown_wo_na.sort_values('Score', inplace = True, ascending = False)
    df.sort_values('Score', inplace = True, ascending = False)    

    # Make color palettes
    sust_colors = sns.cubehelix_palette(start = 1.8, rot = 0, dark = 0.3, light = 0.95, n_colors = 7)
    welf_colors = sns.cubehelix_palette(start = 0.5, rot = 0, dark = 0.3, light = 0.95, n_colors = 7)
    health_colors = sns.cubehelix_palette(start = 3, rot = 0, dark = 0.3, light = 0.95, n_colors = 7)
    market_colors = sns.cubehelix_palette(start = 2.5, rot = 0, dark = 0.3, light = 0.95, n_colors = 7)
    
    # Display ranked list of archetypes. Check whether both import value and volume are weighted and warn user if so.
    with output:
        with pd.option_context('display.max_rows', None, 'display.max_columns', None):
            greens = cmap = sns.light_palette(sust_colors[2], as_cmap=True)
            pinks = cmap = sns.light_palette(welf_colors[2], as_cmap=True)
            purples = cmap = sns.light_palette(health_colors[2], as_cmap=True)
            blues = cmap = sns.light_palette(market_colors[2], as_cmap=True)
            yellows = cmap = sns.light_palette('gold', as_cmap=True)
            pretty_df = df.style.format('{:.3}', na_rep="-").\
            background_gradient(subset = metric_dict['Sustainability'], cmap = greens).\
            background_gradient(subset = metric_dict['Animal Welfare'], cmap = pinks).\
            background_gradient(subset = metric_dict['Health'], cmap = purples).\
            background_gradient(subset = metric_dict['US Market Size'], cmap = blues).\
            background_gradient(subset = ['Score'], cmap = yellows).\
            highlight_null('white')
            display(pretty_df)
    with links_output:
        df.to_csv('Archetype_Ranking.csv')
        pd.DataFrame(weight_list, index = metric_list).T.to_csv('Weights.csv')
        display(FileLink('Archetype_Ranking.csv'))
        display(FileLink('Weights.csv'))
    with graph_output:
        if value_slider.value != 0 and volume_slider.value != 0:
            display('Warning: Import value and import volume are redundant. You probably should set one or the other to zero.')  

    # Define colors for bar graph
    colors = sust_colors[2:4] + welf_colors[2:4] + health_colors[2:3] + market_colors[2:6]

    # Plot score breakdown as bar graph
    n_archetypes = 20
    with graph_output:
        bottom = [0] * n_archetypes
        ind = np.arange(n_archetypes)
        score_breakdown = score_breakdown.head(n_archetypes)
        score_breakdown_wo_na = score_breakdown_wo_na.head(n_archetypes)
        plt.bar(ind, score_breakdown['Score'], color = 'lightgrey', label = 'No data — using mean score')
        score_breakdown.drop('Score', axis = 1, inplace = True)
        for column, color in zip(score_breakdown.columns, colors):
            plt.bar(ind, score_breakdown_wo_na[column], bottom = bottom, label = column, color = color)
            bottom = bottom + score_breakdown[column]
        plt.xticks(ind, score_breakdown.index[:n_archetypes], rotation = 90)
        ax = plt.gca()
        ax.set_title('Score Breakdown for Top 20 Archetypes')
        ax.set_ylabel('Score')
        plt.legend(loc = 'upper left', bbox_to_anchor = (1,1))
        plt.show()

    # Display weights as a donut chart
    with pie_output:
        display()
        plt.pie(category_weights, colors = [sust_colors[0], welf_colors[0], health_colors[0], market_colors[0]], 
                wedgeprops = {'width' : 0.2}, 
                labels = [cat if weight > 0 else '' for cat, weight in zip(categories, category_weights)], normalize = False)
        plt.pie(weight_list, colors = colors, radius = 0.75, wedgeprops = {'width' : 0.2}, 
                labels = [round(n, 3) if n > 0 else '' for n in weight_list], labeldistance = 0.9, normalize = False)
        plt.gca().set_title('Your chosen weights (out of 1)')
        plt.show()

# This runs every time you move a slider
def handler(change):
    output.clear_output(wait = True)
    graph_output.clear_output(wait = True)
    pie_output.clear_output(wait = True)
    links_output.clear_output(wait = True)
    compute_scores()

for slider in category_sliders + metric_sliders:
    slider.observe(handler, names = 'value')
    
# Make the layout pretty
categories_title = widgets.HTML("<i>Adjust the sliders to set the weights for each of these four categories. You can further customize how the scores for each category are calculated below.</i>")
metrics_title = widgets.HTML('<br><i>Within each column, adjust the sliders to customize the weights for individual metrics.</i>')

category_layout = widgets.VBox([categories_title, sust_slider, welf_slider, health_slider, market_slider])

sust_layout = widgets.VBox([widgets.HTML('<b><center>Sustainability</center></b>'), overall_slider, ghg_slider])
welf_layout = widgets.VBox([widgets.HTML('<b><center>Animal Welfare</center></b>'), individuals_slider, edible_slider])
market_layout = widgets.VBox([widgets.HTML('<b><center>Market Size</center></b>'), retail_slider, menu_slider, value_slider, volume_slider])
metric_layout = widgets.VBox([metrics_title, widgets.HBox([sust_layout, welf_layout, market_layout])])

layout = widgets.VBox([category_layout, metric_layout, widgets.HBox([graph_output, pie_output])])


# Calculate the initial scores
compute_scores()
In [51]:
# nbi:hide_in

display(layout)
In [52]:
# nbi:hide_in

display(output)
In [53]:
# nbi:hide_in
# nbi:hide_out

# Once you're happy with your chosen weights, you can download the full table with your customized scores here.
# These links will update automatically when you change the sliders.

display(links_output)
In [ ]:
# nbi:hide_in