In [1]:
import numpy as np

import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.colors as colors
from matplotlib import gridspec
from brokenaxes import brokenaxes

import pandas as pd

import copy

In [2]:
df = pd.read_csv('drifts.csv')
df.set_index(df['model'] + ' (' + df['run'] + ')', drop=True, inplace=True)
df


Out[2]:
model run project ocean model OHC (J yr-1) barystatic OHC (J yr-1) thermal OHC (J yr-1) hfds (J yr-1) netTOA (J yr-1) wfo (kg yr-1) ... netTOA vs thermal OHC regression, CI upper wfo vs masso regression wfo vs masso regression, CI lower wfo vs masso regression, CI upper wfo vs soga regression wfo vs soga regression, CI lower wfo vs soga regression, CI upper hfds vs thermal OHC regression hfds vs thermal OHC regression, CI lower hfds vs thermal OHC regression, CI upper
ACCESS1-0 (r1i1p1) ACCESS1-0 r1i1p1 cmip5 MOM 1.880000e+21 1.470000e+20 1.730000e+21 2.290000e+21 7.820000e+21 -1.307750e+14 ... 0.964601 NaN NaN NaN NaN NaN NaN 0.639833 0.574713 0.704953
ACCESS1-3 (r1i1p1) ACCESS1-3 r1i1p1 cmip5 MOM -2.420000e+21 -6.240000e+20 -1.800000e+21 -1.290000e+21 1.650000e+21 -8.007490e+14 ... 0.930700 0.968262 0.954719 0.981805 1.243034 1.197895 1.288173 0.980196 0.963962 0.996429
BCC-CSM1-1 (r1i1p1) BCC-CSM1-1 r1i1p1 cmip5 MOM 9.410000e+19 1.890000e+19 7.520000e+19 1.810000e+21 -1.240000e+22 NaN ... 0.904610 NaN NaN NaN NaN NaN NaN 0.969455 0.957910 0.980999
CMCC-CESM (r1i1p1) CMCC-CESM r1i1p1 cmip5 OPA -2.180000e+20 0.000000e+00 -2.180000e+20 2.210000e+21 2.460000e+22 -8.718780e+11 ... 0.974210 NaN NaN NaN -2.266516 -6.613257 2.080225 1.002668 0.992860 1.012476
CMCC-CM (r1i1p1) CMCC-CM r1i1p1 cmip5 OPA 9.510000e+20 -3.410000e+19 9.850000e+20 1.260000e+21 NaN 2.750910e+15 ... NaN 0.968194 0.918007 1.018382 0.230724 0.129867 0.331581 0.342507 0.266613 0.418401
CMCC-CMS (r1i1p1) CMCC-CMS r1i1p1 cmip5 OPA 2.640000e+20 2.889130e+15 2.640000e+20 3.470000e+21 1.270000e+22 -9.149130e+11 ... 1.034425 -0.009841 -0.021859 0.002177 2.266191 -0.349258 4.881641 0.999708 0.994490 1.004926
GFDL-ESM2G (r1i1p1) GFDL-ESM2G r1i1p1 cmip5 GOLD -2.000000e+20 -1.430000e+18 -1.990000e+20 -4.180000e+20 -1.780000e+21 -2.957520e+14 ... 0.491047 0.974282 0.949536 0.999028 1.024668 0.029605 2.019731 0.413984 0.315318 0.512651
GFDL-ESM2M (r1i1p1) GFDL-ESM2M r1i1p1 cmip5 MOM 6.710000e+20 1.630000e+18 6.690000e+20 NaN -1.010000e+21 -2.573460e+14 ... 0.942400 0.881469 0.862184 0.900754 0.821174 0.803244 0.839104 NaN NaN NaN
MIROC-ESM (r1i1p1) MIROC-ESM r1i1p1 cmip5 COCO 8.840000e+21 -4.110000e+20 9.250000e+21 -6.600000e+23 -4.660000e+22 -2.712600e+15 ... 0.838118 0.684524 0.650472 0.718576 0.648500 0.618313 0.678687 0.422507 0.388235 0.456778
MIROC-ESM-CHEM (r1i1p1) MIROC-ESM-CHEM r1i1p1 cmip5 COCO 1.010000e+22 -1.690000e+20 1.030000e+22 -6.580000e+23 -4.940000e+22 -2.534180e+15 ... 1.057077 1.080301 1.036564 1.124038 1.044600 1.003455 1.085746 1.012367 0.979537 1.045197
MPI-ESM-LR (r1i1p1) MPI-ESM-LR r1i1p1 cmip5 MPI-OM 1.570000e+20 1.050000e+20 5.150000e+19 6.730000e+20 8.220000e+21 5.461240e+14 ... 0.964589 0.015231 -0.031119 0.061581 0.020491 -0.019806 0.060788 0.882950 0.873994 0.891905
MPI-ESM-MR (r1i1p1) MPI-ESM-MR r1i1p1 cmip5 MPI-OM 3.400000e+20 1.150000e+20 2.260000e+20 2.360000e+21 8.380000e+21 2.724420e+14 ... 0.954503 0.030773 -0.098090 0.159636 0.052498 -0.059899 0.164896 0.990806 0.980157 1.001454
MPI-ESM-P (r1i1p1) MPI-ESM-P r1i1p1 cmip5 MPI-OM 2.730000e+20 1.150000e+20 1.580000e+20 7.240000e+20 8.550000e+21 5.679700e+14 ... 0.877349 0.114403 0.085429 0.143376 0.099829 0.074409 0.125248 0.866181 0.850378 0.881983
NorESM1-M (r1i1p1) NorESM1-M r1i1p1 cmip5 MICOM-HAMOCC 1.470000e+21 -1.320000e+18 1.480000e+21 2.260000e+21 3.460000e+22 -1.916140e+14 ... 0.982404 0.029978 0.000062 0.059895 0.845039 0.829806 0.860273 0.969099 0.959723 0.978476
NorESM1-ME (r1i1p1) NorESM1-ME r1i1p1 cmip5 MICOM-HAMOCC 1.260000e+21 -9.200000e+17 1.260000e+21 2.030000e+21 3.440000e+22 -1.457050e+14 ... 0.988619 0.023860 -0.010513 0.058233 0.855441 0.834336 0.876547 1.021044 1.007632 1.034455
ACCESS-CM2 (r1i1p1f1) ACCESS-CM2 r1i1p1f1 cmip6 MOM 3.110000e+21 -4.480000e+19 3.160000e+21 1.080000e+21 6.680000e+21 -3.719200e+14 ... 0.978022 1.015648 0.991792 1.039504 0.910779 0.888021 0.933536 0.947820 0.933277 0.962364
ACCESS-ESM1-5 (r1i1p1f1) ACCESS-ESM1-5 r1i1p1f1 cmip6 MOM -1.670000e+19 -4.240000e+20 4.070000e+20 -6.850000e+20 -3.230000e+21 -5.898640e+14 ... 0.945090 0.929014 0.905937 0.952090 0.879622 0.851566 0.907678 0.949400 0.936223 0.962576
BCC-CSM2-MR (r1i1p1f1) BCC-CSM2-MR r1i1p1f1 cmip6 MOM 1.580000e+21 7.030000e+19 1.510000e+21 NaN -8.120000e+21 NaN ... 0.973180 NaN NaN NaN NaN NaN NaN NaN NaN NaN
BCC-ESM1 (r1i1p1f1) BCC-ESM1 r1i1p1f1 cmip6 MOM 3.400000e+20 1.540000e+20 1.860000e+20 NaN -5.890000e+21 NaN ... 0.967672 NaN NaN NaN NaN NaN NaN NaN NaN NaN
CNRM-CM6-1 (r1i1p1f2) CNRM-CM6-1 r1i1p1f2 cmip6 NEMO 1.990000e+21 1.200000e+20 1.870000e+21 2.560000e+21 2.420000e+22 -1.452520e+14 ... 1.228737 0.947334 0.933907 0.960761 0.871237 0.859910 0.882564 0.983416 0.980091 0.986740
CNRM-ESM2-1 (r1i1p1f2) CNRM-ESM2-1 r1i1p1f2 cmip6 NEMO 1.380000e+21 1.780000e+20 1.200000e+21 1.880000e+21 2.450000e+22 -8.695040e+13 ... 1.229799 0.895064 0.882551 0.907578 0.850546 0.842426 0.858666 0.986450 0.983338 0.989561
EC-Earth3 (r1i1p1f1) EC-Earth3 r1i1p1f1 cmip6 NEMO 1.340000e+21 2.560000e+19 1.320000e+21 -1.450000e+21 1.190000e+21 -5.651630e+14 ... 0.820641 0.884218 0.870280 0.898156 0.809699 0.797759 0.821639 0.897443 0.889392 0.905494
EC-Earth3-Veg (r1i1p1f1) EC-Earth3-Veg r1i1p1f1 cmip6 NEMO 1.350000e+21 2.810000e+19 1.330000e+21 -2.190000e+21 8.640000e+20 -6.025820e+14 ... 0.836422 0.872971 0.860611 0.885331 0.798802 0.786871 0.810732 0.941159 0.931687 0.950630
GFDL-CM4 (r1i1p1f1) GFDL-CM4 r1i1p1f1 cmip6 MOM 3.230000e+21 -3.470000e+18 3.240000e+21 4.080000e+21 4.690000e+21 NaN ... 0.989253 NaN NaN NaN NaN NaN NaN 0.996435 0.987525 1.005346
HadGEM3-GC31-LL (r1i1p1f1) HadGEM3-GC31-LL r1i1p1f1 cmip6 NEMO 1.970000e+21 5.420000e+19 1.910000e+21 -7.820000e+20 3.060000e+21 4.900000e+13 ... 0.968049 0.995410 0.984607 1.006213 0.855932 0.843750 0.868113 0.968853 0.960173 0.977534
IPSL-CM6A-LR (r1i1p1f1) IPSL-CM6A-LR r1i1p1f1 cmip6 NEMO -1.430000e+21 3.920000e+19 -1.470000e+21 -4.810000e+21 1.130000e+22 -1.676900e+15 ... 0.907606 0.947653 0.936275 0.959031 0.861454 0.851073 0.871835 0.956069 0.952344 0.959793
IPSL-CM6A-LR (r1i2p1f1) IPSL-CM6A-LR r1i2p1f1 cmip6 NEMO -1.190000e+21 3.920000e+19 -1.230000e+21 -4.560000e+21 1.150000e+22 -1.696750e+15 ... 0.921944 1.016649 0.986728 1.046571 0.936761 0.906700 0.966822 0.963661 0.956745 0.970577
UKESM1-0-LL (r1i1p1f2) UKESM1-0-LL r1i1p1f2 cmip6 NEMO -4.360000e+20 2.220000e+20 -6.580000e+20 NaN 3.500000e+20 NaN ... 0.976809 NaN NaN NaN NaN NaN NaN NaN NaN NaN

28 rows × 35 columns


In [3]:
def plot_abline(ax, slope, intercept, static_bounds=True):
    """Plot a line from slope and intercept"""

    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    if type(xlim[0]) in (list, tuple):
        for lims in xlim:
            x_vals = np.array(lims)
            y_vals = intercept + slope * x_vals
            ax.plot(x_vals, y_vals, linestyle='--', c='0.5')
    else:
        x_vals = np.array(xlim)
        y_vals = intercept + slope * x_vals
        ax.plot(x_vals, y_vals, linestyle='--', c='0.5')

    if static_bounds:
        ax.set_xlim(xlim)
        ax.set_ylim(ylim)
    

def plot_shading(ax):
    """Plot shading to indicate dominant source of drift."""
    
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    
    x_vals = np.array(xlim)
    y_vals = x_vals * 2
    ax.fill_between(x_vals, 0, y_vals, alpha=0.3, color='0.5')

    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    
    
def plot_eei_shading(ax):
    """Plot shading to indicate netTOA / OHC valid range."""
    
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()
    
    x_vals = np.array(xlim)
    y_vals = x_vals * 0.8
    ax.fill_between(x_vals, x_vals, y_vals, alpha=0.3, color='0.5')
                      
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    
                      
def format_axis_label(orig_label, units, scale_factor):
    """Put LaTeX math into axis labels"""
    
    label = orig_label.split('(')[0] + '(' + units + ')'
    label = label.replace('(', '($').replace(')', '$)')
    label = label.replace('s-1', '\; s^{-1}')
    label = label.replace('m-2', '\; m^{-2}')
    label = label.replace('yr-1', '\; yr^{-1}')
    if scale_factor:
        scale_factor = int(scale_factor) * -1
        label = label.replace('($', '($10^{%s} \;' %(str(scale_factor)))
    
    return label

In [11]:
zoom_limits = {'thermal energy conservation': [-0.15, 0.15],
               'mass conservation': [-2e7, 2e7],
               'salt conservation': [-7e-13, 7e-13],
               'barystatic consistency': [-1e-12, 1e-12],
               'planetary energy imbalance': [-0.15, 0.15]}

ocean_model_colors = {'MOM': 'red',
                      'GOLD': 'gold',
                      'NEMO': 'blue',
                      'OPA': 'green',
                      'COCO': 'chocolate',
                      'MPI-OM': 'purple',
                      'MICOM-HAMOCC': 'teal',
                      'POP': 'lime'}

markers = ['o', '<', '^', '>', 'v', 's', 'p', 'D',
           'o', '<', '^', '>', 'v', 's', 'p', 'D',
           'o', '<', '^', '>', 'v', 's', 'p', 'D']
          

def plot_aesthetics(ax, yvar, xvar, units, scinotation, shading, scale_factor,
                    xpad=None, ypad=None, non_square=True):
    """Set the plot aesthetics"""
    
    if shading:
        plot_shading(ax)
    if 'netTOA' in xvar:
        plot_eei_shading(ax)
    else:
        plot_abline(ax, 1, 0, static_bounds=non_square)
    ax.axhline(y=0, color='0.5', linewidth=1.0)
    ax.axvline(x=0, color='0.5', linewidth=1.0)
    #ax.yaxis.major.formatter._useMathText = True
    #ax.xaxis.major.formatter._useMathText = True

    ylabel = format_axis_label(yvar, units, scale_factor)
    if ypad:
        ax.set_ylabel(ylabel, labelpad=ypad)
    else:
        ax.set_ylabel(ylabel)
    xlabel = format_axis_label(xvar, units, scale_factor)
    if xpad:
        ax.set_xlabel(xlabel, labelpad=xpad)
    else:
        ax.set_xlabel(xlabel)
    ax.set_xlabel(xlabel, labelpad=xpad)
    #plt.sca(ax)
    if scinotation:
        plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0), useMathText=True)
        plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0), useMathText=True)
    
    # Shrink current axis by 20%
   #box = ax.get_position()
   #ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])


def get_units(column_header):
    """Get the units from the column header."""
    
    units = column_header.split('(')[-1].split(')')[0]
    
    return units
    
    
def convert_units(value, start_units, end_units, ocean_area=None):
    """Convert units."""
    
    sec_in_year = 365.25 * 24 * 60 * 60
    
    if start_units == end_units:
        new_value = value
    else:    
        assert start_units in ['J yr-1', 'm yr-1', 'kg yr-1', 'g/kg yr-1', 'm yr-1']
        assert end_units in ['PW', 'W m-2', 'mm yr-1', 'kg s-1', 'g/kg s-1', 'm s-1']

        if start_units == 'J yr-1':
            new_value = value / sec_in_year 
            if end_units == 'W m-2':
                earth_surface_area = 5.1e14
                new_value = new_value / earth_surface_area
            elif end_units == 'PW':
                new_value = new_value / 1e15
                
        elif (start_units == 'm yr-1') and (end_units == 'mm yr-1'):
            new_value = value * 1000

        elif (start_units == 'kg yr-1') and (end_units == 'mm yr-1'):
            assert ocean_area
            new_value = value / ocean_area
            
        elif (start_units == 'kg yr-1') and (end_units == 'kg s-1'):
            new_value = value / sec_in_year 
            
        elif (start_units == 'g/kg yr-1') and (end_units == 'g/kg s-1'):
            new_value = value / sec_in_year
            
        elif (start_units == 'm yr-1') and (end_units == 'm s-1'):
            new_value = value / sec_in_year
            
    return new_value

    
def plot_comparison(df, title, xvar, yvar, plot_units, scale_factor=0,
                    scinotation=False, shading=False, zoom=None, outfile=None):
    """Plot comparison for given x and y variables.
    
    Data are multiplied by 10^scale_factor.
    
    """
    
    if zoom:
        fig = plt.figure(figsize=[14, 5])
        ax1 = fig.add_subplot(1, 2, 1)
        ax2 = fig.add_subplot(1, 2, 2)
    else:
        fig = plt.figure(figsize=[7, 5])
        ax1 = fig.add_subplot(1, 1, 1)
    
    #colormap = cm.gist_rainbow
    #colorlist = [colors.rgb2hex(colormap(i)) for i in np.linspace(0, 0.9, len(df['model']))]

    x_input_units = get_units(xvar) 
    y_input_units = get_units(yvar)
    for dotnum in range(len(df['model'])):
        area = df['ocean area (m2)'][dotnum]
        x = convert_units(df[xvar][dotnum], x_input_units, plot_units, ocean_area=area) * 10**scale_factor
        y = convert_units(df[yvar][dotnum], y_input_units, plot_units, ocean_area=area) * 10**scale_factor
        marker = markers[dotnum]
        label = df['model'][dotnum] + ' (' + df['run'][dotnum] + ')'
        ocean_model = df['ocean model'][dotnum]
        color = ocean_model_colors[ocean_model]
        if df['project'][dotnum] == 'cmip6':
            facecolors = color
            edgecolors ='none'
        else:
            facecolors = 'none'
            edgecolors = color
        ax1.scatter(x, y, label=label, s=130, linewidth=1.2, marker=marker,
                    facecolors=facecolors, edgecolors=edgecolors)
        if zoom:
            ax2.scatter(x, y, label=label, s=130, linewidth=1.2, marker=marker,
                        facecolors=facecolors, edgecolors=edgecolors)

    plot_aesthetics(ax1, yvar, xvar, plot_units, scinotation, shading, scale_factor)
    if zoom:
        ax2.set_xlim(zoom)
        ax2.set_ylim(zoom)
        plot_aesthetics(ax2, yvar, xvar, plot_units, scinotation, shading, scale_factor)
        ax1.set_title('all models')
        ax2.set_title('zoomed in')
        plt.suptitle(title)
        ax2.legend(loc='center left', bbox_to_anchor=(1, 0.5))
    else:
        plt.title(title)
        ax1.legend(loc='center left', bbox_to_anchor=(1, 0.5))
    
    if outfile:
        plt.savefig(outfile, bbox_inches='tight', dpi=400)
        plt.show()
    else:
        plt.show()
    

def plot_regression_coefficients(df, comparison_list, title, outfile=None, ylim=None):
    """Plot the regression coefficient for each model"""
    
    labels = {"netTOA vs hfds regression": "cumulative netTOA ($Q_r$) vs. cumulative surface heat flux ($Q_h$)",
              "hfds vs thermal OHC regression": "cumulative surface heat flux ($Q_h$) vs. thermal OHC ($H_T$)",
              "netTOA vs thermal OHC regression": "cumulative netTOA ($Q_r$) vs. thermal OHC ($H_T$)",
              "wfo vs masso regression": "cumulative freshwater flux ($Q_m$) vs. ocean mass ($M$)",
              "wfo vs soga regression": "cumulative freshwater flux ($Q_m$) vs. ocean salinity ($S$)",
              "masso vs soga regression": "ocean mass ($M$) vs. ocean salinity ($S$)"}
    
    nvars = len(comparison_list)
    assert nvars <= 3
    
    x = np.arange(df.shape[0])
    if nvars in [1, 3]:
        x0 = x - 0.2
        x1 = x + 0.2
        xlist = [x, x0, x1]
    else:
        x0 = x - 0.1
        x1 = x + 0.1
        xlist = [x0, x1]
    
    plt.figure(figsize=[14, 5])
    plt.axhline(y=1.0, color='0.5', linewidth=0.5)
    plt.axvline(x=14.5, color='0.5', linewidth=2.0)
    colors = ['cadetblue', 'seagreen', 'mediumpurple']
    linestyles = ['--', '-.', ':']
    for pnum, var in enumerate(comparison_list):
        y = df[var].to_numpy()
        color = colors[pnum]
        linestyle = linestyles[pnum]
        markerline, stemlines, baseline = plt.stem(xlist[pnum], y, color, basefmt=" ", bottom=1.0, label=labels[var])
        plt.setp(stemlines, color=color, linestyle=linestyle)
        plt.setp(markerline, color=color)

    if ylim:
        plt.ylim(ylim)
    plt.axvline(x=x[0]-0.5, color='0.5', linewidth=0.5)
    for val in x:
        plt.axvline(x=val+0.5, color='0.5', linewidth=0.5)
    #plt.grid(True, axis='x')
    plt.legend()
    plt.xticks(x, df.index.to_list(), rotation=90)
    plt.ylabel('regression coefficient')
    plt.title(title)

    if outfile:
        plt.savefig(outfile, bbox_inches='tight', dpi=400)
        plt.show()
    else:
        plt.show()

In [5]:
def plot_broken_comparison(df, title, xvar, yvar, plot_units, scale_factor=0,
                           scinotation=False, shading=False, outfile=None,
                           xlims=None, ylims=None, xpad=None, ypad=None,
                           hspace=0.04, wspace=0.04):
    """Plot comparison for given x and y variables.
    
    Data are multiplied by 10^scale_factor.
    
    """
    
    fig = plt.figure(figsize=[10, 8])
    if xlims and ylims:
        bax = brokenaxes(xlims=xlims, ylims=ylims, hspace=hspace, wspace=wspace)
    else:
        bax = fig.add_subplot(1, 1, 1)
    
    x_input_units = get_units(xvar) 
    y_input_units = get_units(yvar)
    for dotnum in range(len(df['model'])):
        area = df['ocean area (m2)'][dotnum]
        x = convert_units(df[xvar][dotnum], x_input_units, plot_units, ocean_area=area) * 10**scale_factor
        y = convert_units(df[yvar][dotnum], y_input_units, plot_units, ocean_area=area) * 10**scale_factor
        marker = markers[dotnum]
        label = df['model'][dotnum] + ' (' + df['run'][dotnum] + ')'
        ocean_model = df['ocean model'][dotnum]
        color = ocean_model_colors[ocean_model]
        if df['project'][dotnum] == 'cmip6':
            facecolors = color
            edgecolors ='none'
        else:
            facecolors = 'none'
            edgecolors = color
        bax.scatter(x, y, label=label, s=130, linewidth=1.2, marker=marker,
                    facecolors=facecolors, edgecolors=edgecolors)
        print(label, ((x-y) / 1.8) * 100, '%')

    if xlims:
        non_square = False
    else:
        non_square = True
        bax.spines["top"].set_visible(False)
        bax.spines["right"].set_visible(False)
    plot_aesthetics(bax, yvar, xvar, plot_units, scinotation, shading, scale_factor,
                    xpad=xpad, ypad=ypad, non_square=non_square)
    plt.title(title)
    bax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
    
    if outfile:
        plt.savefig(outfile, bbox_inches='tight', dpi=400)
        plt.show()
    else:
        plt.show()

Thermal energy

Before dedrifting


In [158]:
plot_broken_comparison(df, 'thermal energy conservation', 'hfds (J yr-1)', 'thermal OHC (J yr-1)', 'W m-2')



In [159]:
plot_broken_comparison(df, 'thermal energy conservation', 'hfds (J yr-1)', 'thermal OHC (J yr-1)', 'W m-2',
                       xlims=[(-41.05, -40.82), (-0.35, 0.3)], ylims=[(-0.3, 0.25), (0.55, 0.65)],
                       wspace=0.08, hspace=0.08, xpad=20,
                       outfile='thermal_conservation.png')



In [141]:
plot_comparison(df, 'thermal energy conservation', 'hfds (J yr-1)', 'thermal OHC (J yr-1)', 'PW',
                shading=False, zoom=[-0.2, 0.2])


Below the 1:1 line indicates that the ocean model is losing heat.

Within the shaded area means the drift in surface forcing is greater than the internally generated ocean model drift.


In [14]:
plot_comparison(df, 'thermal energy conservation', 'hfds (J yr-1)', 'thermal OHC (J yr-1)', 'W m-2',
                shading=False, zoom=[-0.35, 0.35])


The planetary energy imbalance associated with climate change is 0.5 - 1.0 $W m^{-2}$.

After dedrifting


In [145]:
plot_regression_coefficients(df, ['hfds vs thermal OHC regression'],
                             'thermal energy conservation after drift removal',
                             outfile='thermal_regression.png')


Mass

Before dedrifting


In [10]:
plot_broken_comparison(df, 'mass conservation', 'wfo (kg yr-1)', 'masso (kg yr-1)', 'mm yr-1',
                       outfile='mass_conservation.png')


ACCESS1-0 (r1i1p1) -40.45864975166234 %
ACCESS1-3 (r1i1p1) -36.554004521491834 %
BCC-CSM1-1 (r1i1p1) nan %
CMCC-CESM (r1i1p1) -0.09890001442871804 %
CMCC-CM (r1i1p1) 315.54058008891354 %
CMCC-CMS (r1i1p1) -0.1040774053778963 %
GFDL-ESM2G (r1i1p1) -45.268006429780655 %
GFDL-ESM2M (r1i1p1) -28.326925080685978 %
MIROC-ESM (r1i1p1) -357.1990606624484 %
MIROC-ESM-CHEM (r1i1p1) -363.36189971470225 %
MPI-ESM-LR (r1i1p1) 48.92953137705544 %
MPI-ESM-MR (r1i1p1) 18.50202158454471 %
MPI-ESM-P (r1i1p1) 50.621610220824266 %
NorESM1-M (r1i1p1) -29.287701214129054 %
NorESM1-ME (r1i1p1) -22.28232222560591 %
ACCESS-CM2 (r1i1p1f1) -50.98972209307189 %
CNRM-CM6-1 (r1i1p1f2) -38.89104863471897 %
CNRM-ESM2-1 (r1i1p1f2) -37.86163105843169 %
GFDL-CM4 (r1i1p1f1) nan %
HadGEM3-GC31-LL (r1i1p1f1) -0.006119784375517116 %
IPSL-CM6A-LR (r1i1p1f1) -261.6610481105485 %
IPSL-CM6A-LR (r1i2p1f1) -264.6841888604981 %
UKESM1-0-LL (r1i1p1f2) nan %

In [147]:
plot_comparison(df, 'mass conservation', 'wfo (kg yr-1)', 'masso (kg yr-1)', 'kg s-1',
                scinotation=True, shading=False, zoom=[-1e7, 1e7])


Below the 1:1 line indicates that the ocean model is losing mass.

Within the shaded area means the drift in surface forcing is greater than the internally generated ocean model drift.


In [17]:
plot_comparison(df, 'mass conservation', 'wfo (kg yr-1)', 'masso (kg yr-1)', 'mm yr-1',
                shading=False, zoom=[-0.7, 0.7])


The current rate of global mean sea level rise is 3.4 mm/year.

An alternative unit for comparison (which has the advantage of avoiding using the global ocean area, which is different for different models) might be Gt/year (to compare with Antarctic and/or Greenland melt rates).

After dedrifting


In [55]:
plot_regression_coefficients(df, ['wfo vs masso regression'],
                             'mass conservation after drift removal')


/Users/damienirving/anaconda/envs/ocean/lib/python3.6/site-packages/ipykernel_launcher.py:180: UserWarning: In Matplotlib 3.3 individual lines on a stem plot will be added as a LineCollection instead of individual lines. This significantly improves the performance of a stem plot. To remove this warning and switch to the new behaviour, set the "use_line_collection" keyword argument to True.

Salt

Before dedrifting


In [132]:
plot_broken_comparison(df, 'salt conservation', 'masso (g/kg yr-1)', 'soga (g/kg yr-1)', 'g/kg s-1', scale_factor=13)



In [164]:
plot_broken_comparison(df, 'salt conservation', 'masso (g/kg yr-1)', 'soga (g/kg yr-1)', 'g/kg s-1', scale_factor=13,
                       xlims=[(-2, 5)], ylims=[(-19, -17.5), (-2.2, 5)], hspace=0.1, outfile='salt_conservation.png')



In [19]:
plot_comparison(df, 'salt conservation', 'masso (g/kg yr-1)', 'soga (g/kg yr-1)', 'g/kg s-1',
                scale_factor=13, zoom=[-1.2, 1.2])


Above the 1:1 line indicates that the ocean model is losing salt.

After dedrifting


In [8]:
def plot_broken_regression_coefficients(df, comparison_list, title, outfile=None, ylim=None):
    """Plot the regression coefficient for each model"""
    
    labels = {"netTOA vs hfds regression": "cumulative netTOA ($Q_r$) vs. cumulative surface heat flux ($Q_h$)",
              "hfds vs thermal OHC regression": "cumulative surface heat flux ($Q_h$) vs. thermal OHC ($H_T$)",
              "netTOA vs thermal OHC regression": "cumulative netTOA ($Q_r$) vs. thermal OHC ($H_T$)",
              "wfo vs masso regression": "cumulative freshwater flux ($Q_m$) vs. ocean mass ($M$)",
              "wfo vs soga regression": "cumulative freshwater flux ($Q_m$) vs. ocean salinity ($S$)",
              "masso vs soga regression": "ocean mass ($M$) vs. ocean salinity ($S$)"}
    
    nvars = len(comparison_list)
    assert nvars <= 3
    
    x = np.arange(df.shape[0])
    if nvars in [1, 3]:
        x0 = x - 0.2
        x1 = x + 0.2
        xlist = [x, x0, x1]
    else:
        x0 = x - 0.1
        x1 = x + 0.1
        xlist = [x0, x1]
    
    fig, (ax1, ax2, ax3) = plt.subplots(3, 1, sharex=True, figsize=(18,8), gridspec_kw={'height_ratios': [1, 5, 1]})
    ax1.spines['bottom'].set_visible(False)
    ax2.spines['bottom'].set_visible(False)
    ax1.tick_params(axis='x', which='both', bottom=False)
    ax2.tick_params(axis='x', which='both', bottom=False)
    ax2.spines['top'].set_visible(False)
    ax3.spines['top'].set_visible(False)
    ax1.set_ylim(1.5, 3.0)
    ax2.set_ylim(-0.1, 1.4)
    ax3.set_ylim(-5.5, -1)

    ax2.axhline(y=1.0, color='0.5', linewidth=0.5)
    ax1.axvline(x=14.5, color='0.5', linewidth=2.0)
    ax2.axvline(x=14.5, color='0.5', linewidth=2.0)
    ax3.axvline(x=14.5, color='0.5', linewidth=2.0)
    colors = ['cadetblue', 'seagreen', 'mediumpurple']
    linestyles = ['--', '-.', ':']
    for pnum, var in enumerate(comparison_list):
        y = df[var].to_numpy()
        color = colors[pnum]
        linestyle = linestyles[pnum]
        markerline, stemlines, baseline = ax2.stem(xlist[pnum], y, color, basefmt=" ", bottom=1.0, label=labels[var])
        plt.setp(stemlines, color=color, linestyle=linestyle)
        plt.setp(markerline, color=color)
        
        markerline, stemlines, baseline = ax1.stem(xlist[pnum], y, color, basefmt=" ", bottom=1.0, label=labels[var])
        plt.setp(stemlines, color=color, linestyle=linestyle)
        plt.setp(markerline, color=color)
        
        markerline, stemlines, baseline = ax3.stem(xlist[pnum], y, color, basefmt=" ", bottom=1.0, label=labels[var])
        plt.setp(stemlines, color=color, linestyle=linestyle)
        plt.setp(markerline, color=color)

    ax1.axvline(x=x[0]-0.5, color='0.5', linewidth=0.5)
    ax2.axvline(x=x[0]-0.5, color='0.5', linewidth=0.5)
    ax3.axvline(x=x[0]-0.5, color='0.5', linewidth=0.5)
    for val in x:
        ax1.axvline(x=val+0.5, color='0.5', linewidth=0.5)
        ax2.axvline(x=val+0.5, color='0.5', linewidth=0.5)
        ax3.axvline(x=val+0.5, color='0.5', linewidth=0.5)
    #plt.grid(True, axis='x')
    ax1.legend()
    plt.xticks(x, df.index.to_list(), rotation=90)
    ax2.set_ylabel('regression coefficient')
    ax1.set_title(title)
    plt.subplots_adjust(hspace=0.1)
    if outfile:
        plt.savefig(outfile, bbox_inches='tight', dpi=400)
        plt.show()
    else:
        plt.show()

In [9]:
plot_broken_regression_coefficients(df, ['wfo vs masso regression', 'wfo vs soga regression', 'masso vs soga regression'],
                             'mass and salt conservation after drift removal',
                             outfile='mass_regression.png')


/g/data/r87/dbi599/miniconda3/envs/ocean/lib/python3.6/site-packages/ipykernel_launcher.py:45: UserWarning: In Matplotlib 3.3 individual lines on a stem plot will be added as a LineCollection instead of individual lines. This significantly improves the performance of a stem plot. To remove this warning and switch to the new behaviour, set the "use_line_collection" keyword argument to True.
/g/data/r87/dbi599/miniconda3/envs/ocean/lib/python3.6/site-packages/ipykernel_launcher.py:49: UserWarning: In Matplotlib 3.3 individual lines on a stem plot will be added as a LineCollection instead of individual lines. This significantly improves the performance of a stem plot. To remove this warning and switch to the new behaviour, set the "use_line_collection" keyword argument to True.
/g/data/r87/dbi599/miniconda3/envs/ocean/lib/python3.6/site-packages/ipykernel_launcher.py:53: UserWarning: In Matplotlib 3.3 individual lines on a stem plot will be added as a LineCollection instead of individual lines. This significantly improves the performance of a stem plot. To remove this warning and switch to the new behaviour, set the "use_line_collection" keyword argument to True.

In [6]:
plot_regression_coefficients(df, ['wfo vs masso regression', 'wfo vs soga regression', 'masso vs soga regression'],
                             'mass and salt conservation after drift removal',
                             outfile='mass_regression.png') 
                             #ylim=[-0.2, 1.2]


TOA

Before dedrifting


In [21]:
plot_comparison(df, 'planetary energy imbalance', 'netTOA (J yr-1)', 'thermal OHC (J yr-1)', 'PW')


Grey shading shows the region where $\delta H / \delta t$ = 80-100% of netTOA. Below that regions indicated that the model is losing energy between the top of the atmosphere and ocean (most models lose heat).


In [168]:
plot_broken_comparison(df, 'planetary energy imbalance', 'netTOA (J yr-1)', 'thermal OHC (J yr-1)', 'W m-2',
                       outfile='eei_conservation.png')


The planetary energy imbalance associated with climate change is 0.5 - 1.0 $W m^{-2}$.

After dedrifting


In [12]:
plot_regression_coefficients(df, ['netTOA vs hfds regression', 'hfds vs thermal OHC regression', 'netTOA vs thermal OHC regression'],
                             'thermal energy conservation after drift removal', outfile='eei_regression.png')


/g/data/r87/dbi599/miniconda3/envs/ocean/lib/python3.6/site-packages/ipykernel_launcher.py:196: UserWarning: In Matplotlib 3.3 individual lines on a stem plot will be added as a LineCollection instead of individual lines. This significantly improves the performance of a stem plot. To remove this warning and switch to the new behaviour, set the "use_line_collection" keyword argument to True.

Barystatic sea level


In [16]:
plot_comparison(df, 'barystatic consistency', 'masso (m yr-1)', 'zosbary (m yr-1)', 'm s-1',
                scale_factor=11, zoom=[-0.12, 0.12])


Contributions to OHC drift

OHC drift is dominated by the thermal (as opposed to barystatic) component.


In [13]:
df_ohc = df[['OHC (J yr-1)', 'thermal OHC (J yr-1)', 'barystatic OHC (J yr-1)']]
df_ohc.set_index(df['model'] + ' (' + df['run'] + ')', drop=True, inplace=True)

In [14]:
sec_in_year = 365.25 * 24 * 60 * 60
earth_surface_area = 5.1e14
df_ohc = (df_ohc / sec_in_year) / earth_surface_area
df_ohc = df_ohc.rename(columns={"OHC (J yr-1)": "change in OHC ($dH/dt$)",
                                "thermal OHC (J yr-1)": "change in thermal OHC ($dH_T/dt$)",
                                "barystatic OHC (J yr-1)": "change in barystatic OHC ($dH_m/dt$)"})

In [15]:
df_ohc.plot.bar(figsize=(18,6), color=['black', 'red', 'blue'], width=0.9)
plt.axvline(x=14.5, color='0.5', linewidth=2.0)
plt.axhline(y=0.5, color='0.5', linewidth=0.5, linestyle='--')
#plt.title('drift in OHC and its components')
plt.ylabel('$W \; m^{-2}$')
plt.savefig('ohc_drift_breakdown.png', bbox_inches='tight', dpi=400)
#plt.show()



In [181]:
(df_ohc['thermal OHC'] / df_ohc['OHC'] ) * 100


Out[181]:
ACCESS1-0 (r1i1p1)             92.021277
ACCESS1-3 (r1i1p1)             74.380165
BCC-CSM1-1 (r1i1p1)            79.914984
CMCC-CESM (r1i1p1)            100.000000
CMCC-CM (r1i1p1)              103.575184
CMCC-CMS (r1i1p1)             100.000000
GFDL-ESM2G (r1i1p1)            99.500000
GFDL-ESM2M (r1i1p1)            99.701937
MIROC-ESM (r1i1p1)            104.638009
MIROC-ESM-CHEM (r1i1p1)       101.980198
MPI-ESM-LR (r1i1p1)            32.802548
MPI-ESM-MR (r1i1p1)            66.470588
MPI-ESM-P (r1i1p1)             57.875458
NorESM1-M (r1i1p1)            100.680272
NorESM1-ME (r1i1p1)           100.000000
ACCESS-CM2 (r1i1p1f1)         101.607717
CNRM-CM6-1 (r1i1p1f2)          93.969849
CNRM-ESM2-1 (r1i1p1f2)         86.956522
GFDL-CM4 (r1i1p1f1)           100.309598
HadGEM3-GC31-LL (r1i1p1f1)     96.954315
IPSL-CM6A-LR (r1i1p1f1)       102.797203
IPSL-CM6A-LR (r1i2p1f1)       103.361345
UKESM1-0-LL (r1i1p1f2)        150.917431
dtype: float64

Contributions to coupled model energy leakage

Coupled model energy leakage is dominated by the atmospheric / out-of-ocean-model leakage.


In [16]:
df['atmos energy leakage (J yr-1)'] = df['netTOA (J yr-1)'] - df['hfds (J yr-1)']
df['ocean energy leakage (J yr-1)'] = df['hfds (J yr-1)'] - df['thermal OHC (J yr-1)']
df['total energy leakage (J yr-1)'] = df['netTOA (J yr-1)'] - df['thermal OHC (J yr-1)']

In [17]:
df_leakage = df[['total energy leakage (J yr-1)', 'atmos energy leakage (J yr-1)', 'ocean energy leakage (J yr-1)']]
df_leakage.set_index(df['model'] + ' (' + df['run'] + ')', drop=True, inplace=True)

In [18]:
sec_in_year = 365.25 * 24 * 60 * 60
earth_surface_area = 5.1e14
df_leakage = (df_leakage / sec_in_year) / earth_surface_area
df_leakage = df_leakage.rename(columns={"total energy leakage (J yr-1)": "total leakage ($dQ_r/dt - dH_T/dt$)",
                                        "ocean energy leakage (J yr-1)": "ocean leakage ($dQ_h/dt - dH_T/dt$)",
                                        "atmos energy leakage (J yr-1)": "non-ocean leakage ($dQ_r/dt - dQ_h/dt$)",
                                       })

In [19]:
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, sharex=True, figsize=(18,8), gridspec_kw={'height_ratios': [1, 5, 1]})
ax1.spines['bottom'].set_visible(False)
ax2.spines['bottom'].set_visible(False)
ax1.tick_params(axis='x', which='both', bottom=False)
ax2.tick_params(axis='x', which='both', bottom=False)
ax2.spines['top'].set_visible(False)
ax3.spines['top'].set_visible(False)
ax1.set_ylim(30, 45)
ax2.set_ylim(-4, 3)
ax3.set_ylim(-45, -30)

df_leakage.plot(ax=ax3, kind='bar', color=['gold', 'green', 'blue'], width=0.9, legend=False)
df_leakage.plot(ax=ax2, kind='bar', color=['gold', 'green', 'blue'], width=0.9, legend=False)
df_leakage.plot(ax=ax1, kind='bar', color=['gold', 'green', 'blue'], width=0.9)
for tick in ax3.get_xticklabels():
    tick.set_rotation(90)

#d = .002  
#kwargs = dict(transform=ax1.transAxes, color='k', clip_on=False)
#ax1.plot((-d, +d), (-d, +d), **kwargs)      
#ax1.plot((1 - d, 1 + d), (-d, +d), **kwargs)
#kwargs.update(transform=ax2.transAxes)  
#ax2.plot((-d, +d), (1 - d, 1 + d), **kwargs)  
#ax2.plot((1 - d, 1 + d), (1 - d, 1 + d), **kwargs)

plt.subplots_adjust(hspace=0.15)

ax1.axvline(x=14.5, color='0.5', linewidth=2.0)
ax2.axvline(x=14.5, color='0.5', linewidth=2.0)
ax3.axvline(x=14.5, color='0.5', linewidth=2.0)
ax2.axhline(y=-0.5, color='0.5', linewidth=0.5, linestyle='--')
ax2.axhline(y=0.5, color='0.5', linewidth=0.5, linestyle='--')
#plt.title('energy leakage between netTOA and OHC')
ax2.set_ylabel('$W \; m^{-2}$')
plt.savefig('eei_leakage_breakdown.png', bbox_inches='tight', dpi=400)
plt.show()



In [ ]: