diff --git a/.gitignore b/.gitignore index 5a98549..d2b9cc0 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,5 @@ dmypy.json # Ephemeral .nfs files .nfs* + +.DSStore diff --git a/README.md b/README.md index c546181..df2771d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This Project Pythia Cookbook covers paleoclimate model-data comparison using spa Paleoclimate observations obtained from tree rings or the geochemical composition of speleothems, ice and sediments provide an out-of-sample validation of climate models. However, comparing the output of climate models directly with the paleoclimate observations is difficult: (1) they are often expressed in different quantities (i.e., temperature vs ring width), (2) paleoclimate observations have large time uncertainties, (3) paleoclimate observations often incorporate more than one environmental signal (i.e., temperature AND moisture). -Recently, [Steinman et al. (2022)](https://www.pnas.org/doi/full/10.1073/pnas.2120015119) used PCA analysis to compare model and data output. Here, we use a similar approach with the [CESM Last Millennium simulation](https://www2.cesm.ucar.edu/models/cesm1.2/) and proxy records stored on the [LiPDverse](https://lipdverse.org). This repository contains paleoclimate datasets that have been curated by the community and are archived in a standard format, facilitating analysis. +Recently, [Steinman et al. (2022)](https://doi.org/10.1073/pnas.2120015119) used PCA analysis to compare model and data output. Here, we use a similar approach with the [CESM Last Millennium simulation](https://www2.cesm.ucar.edu/models/cesm1.2/) and proxy records stored on the [LiPDverse](https://lipdverse.org). This repository contains paleoclimate datasets that have been curated by the community and are archived in a standard format, facilitating analysis. ## Authors diff --git a/_config.yml b/_config.yml index 031f813..fceb4e4 100644 --- a/_config.yml +++ b/_config.yml @@ -8,7 +8,7 @@ copyright: "2024" execute: # To execute notebooks via a Binder instead, replace 'cache' with 'binder' - execute_notebooks: cache + execute_notebooks: off timeout: 600 allow_errors: False # cells with expected failures must set the `raises-exception` cell tag @@ -27,7 +27,7 @@ parse: sphinx: config: - linkcheck_ignore: ["https://doi.org/*", "https://zenodo.org/badge/*"] # don't run link checker on DOI links since they are immutable + linkcheck_ignore: ["https://doi.org/*", "https://zenodo.org/badge/*","https://linkedearth.graphdb.mint.isi.edu/"] # don't run link checker on DOI links since they are immutable nb_execution_raise_on_error: true # raise exception in build if there are notebook errors (this flag is ignored if building on binder) html_favicon: notebooks/images/icons/favicon.ico html_last_updated_fmt: "%-d %B %Y" diff --git a/notebooks/.virtual_documents/.DS_Store b/notebooks/.virtual_documents/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/notebooks/.virtual_documents/.DS_Store differ diff --git a/notebooks/.virtual_documents/cesmLME.ipynb b/notebooks/.virtual_documents/cesmLME.ipynb deleted file mode 100644 index 11e7c3b..0000000 --- a/notebooks/.virtual_documents/cesmLME.ipynb +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - -import s3fs -import fsspec -import xarray as xr -import glob - - - - - -URL = 'https://js2.jetstream-cloud.org:8001/' - - - - - - - - -path = f'pythia/cesmLME' - - - - - - - - - - - -fs = fsspec.filesystem("s3", anon=True, client_kwargs=dict(endpoint_url=URL)) - - -pattern = f's3://{path}/*.nc' - - -files = sorted(fs.glob(pattern)) - - - - - -files[0:2] - - -fileset = [fs.open(file) for file in files[0:2]] - - -data = xr.open_dataset(fileset[0]) - - -data - - - - - -# This works -dataMul = xr.open_mfdataset(fileset[:3], combine='by_coords') - - -dataMul - - - - - - - - - - - - diff --git a/notebooks/.virtual_documents/notebook-template.ipynb b/notebooks/.virtual_documents/notebook-template.ipynb deleted file mode 100644 index e644bea..0000000 --- a/notebooks/.virtual_documents/notebook-template.ipynb +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -#To deal with model data -import s3fs -import fsspec -import xarray as xr -import glob - -#To deal with proxy data -import pandas as pd -import numpy as np -import json -import requests -import pandas as pd -import io -import ast - -#To deal with analysis -import pyleoclim as pyleo -from eofs.xarray import Eof - -#Plotting and mapping -import cartopy.crs as ccrs -import matplotlib.pyplot as plt -import nc_time_axis - - - - - - - - -URL = 'https://js2.jetstream-cloud.org:8001/' #Locate and read a file - - -path = f'pythia/cesmLME' # specify data location - - -fs = fsspec.filesystem("s3", anon=True, client_kwargs=dict(endpoint_url=URL)) -pattern = f's3://{path}/*.nc' -files = sorted(fs.glob(pattern)) - - - - - -base_name = 'pythia/cesmLME/b.ie12.B1850C5CN.f19_g16.LME.002.cam.h0.' -time_period = '085001-184912' - -names = [name for name in files if base_name in name and time_period in name] - -names - - -fileset = [fs.open(file) for file in names] - - - - - -%%time -for idx,item in enumerate(fileset): - ds_u = xr.open_dataset(item) - var_name = names[idx].split('.')[-3] - da = ds_u[var_name] - try: - ds[var_name]= da - except: - ds = da.to_dataset() - ds.attrs = ds_u.attrs - ds_u.close() - da.close() - - - - - -ds - - - - - -ds_geo = ds.sel(lat=slice(-27,27), lon=slice(250,330)) - - - - - -#ds_geo.to_netcdf(path='../data/LME.002.cam.h0.precip_iso.085001-184912.nc') - - - - - -%%time -p16O = ds_geo['PRECRC_H216Or'] + ds_geo['PRECSC_H216Os'] + ds_geo['PRECRL_H216OR'] + ds_geo['PRECSL_H216OS'] -p18O = ds_geo['PRECRC_H218Or'] + ds_geo['PRECSC_H218Os'] + ds_geo['PRECRL_H218OR'] + ds_geo['PRECSL_H218OS'] - -p16O = p16O.where(p16O > 1e-18, 1e-18) -p18O = p18O.where(p18O > 1e-18, 1e-18) - -d18Op = (p18O / p16O - 1)*1000 - - - - - -d18Oa = (d18Op - d18Op.mean(dim='time'))/d18Op.std(dim='time') - - - - - -solver = Eof(d18Oa, weights=None) - - - - - -eof1 = solver.eofsAsCovariance(neofs=3) - - - - - - - - -url = 'https://linkedearth.graphdb.mint.isi.edu/repositories/LiPDVerse-dynamic' - -query = """PREFIX le: -PREFIX wgs84: -PREFIX rdfs: -SELECT distinct?varID ?dataSetName ?lat ?lon ?varname ?interpLabel ?val ?varunits ?timevarname ?timeval ?timeunits ?archiveType where{ - - ?ds a le:Dataset . - ?ds le:hasName ?dataSetName . - OPTIONAL{?ds le:hasArchiveType ?archiveTypeObj . - ?archiveTypeObj rdfs:label ?archiveType .} - - - ?ds le:hasLocation ?loc . - ?loc wgs84:lat ?lat . - FILTER(?lat<26 && ?lat>-26) - ?loc wgs84:long ?lon . - FILTER(?lon<-70 && ?lon>-150) - - ?ds le:hasPaleoData ?data . - ?data le:hasMeasurementTable ?table . - ?table le:hasVariable ?var . - ?var le:hasName ?varname . - VALUES ?varname {"d18O"} . - ?var le:partOfCompilation ?comp . - ?comp le:hasName ?compName . - VALUES ?compName {"iso2k" "Pages2kTemperature" "CoralHydro2k" "SISAL-LiPD"} . - ?var le:hasInterpretation ?interp . - ?interp le:hasVariable ?interpVar . - ?interpVar rdfs:label ?interpLabel . - FILTER (REGEX(?interpLabel, "precipitation.*", "i")) - ?var le:hasVariableId ?varID . - ?var le:hasValues ?val . - OPTIONAL{?var le:hasUnits ?varunitsObj . - ?varunitsObj rdfs:label ?varunits .} - - ?table le:hasVariable ?timevar . - ?timevar le:hasName ?timevarname . - VALUES ?timevarname {"year" "age"} . - ?timevar le:hasValues ?timeval . - OPTIONAL{?timevar le:hasUnits ?timeunitsObj . - ?timeunitsObj rdfs:label ?timeunits .} -}""" - - - - - -response = requests.post(url, data = {'query': query}) - -data = io.StringIO(response.text) -df_res = pd.read_csv(data, sep=",") - -df_res['val']=df_res['val'].apply(lambda row : json.loads(row) if isinstance(row, str) else row) -df_res['timeval']=df_res['timeval'].apply(lambda row : json.loads(row) if isinstance(row, str) else row) - -df_res.head() - - - - - -len(df_res) - - - - - -df = df_res[~df_res['varID'].duplicated()] - - -len(df) - - - - - -df['timevarname'].unique() - - - - - -df['timeunits'].unique() - - - - - -df['timeval'] = df['timeval'].apply(np.array) - -def adjust_timeval(row): - if row['timevarname'] == 'age': - return 1950 - row['timeval'] - else: - return row['timeval'] - -# Apply the function across the DataFrame rows -df['timeval'] = df.apply(adjust_timeval, axis=1) - - - - - -def range_within_limits(array, lower = 0, upper = 2000, threshold = 1500): - filtered_values = array[(array >= lower) & (array <= upper)] - if filtered_values.size > 0: # Check if there are any values within the range - return np.ptp(filtered_values) >= threshold # np.ptp() returns the range of values - return False # If no values within the range, filter out the row - - -# Apply the function to filter the DataFrame -filtered_df = df[df['timeval'].apply(range_within_limits)] - - - - - -len(filtered_df) - - - - - -def array_range_exceeds(array, threshold=1500): - return np.max(array) - np.min(array) > threshold - -filt_df = filtered_df[filtered_df['timeval'].apply(array_range_exceeds)] - - - - - -len(filt_df) - - - - - -def min_resolution(array, min_res=60): - if len(array) > 1: # Ensure there are at least two points to calculate a difference - # Calculate differences between consecutive elements - differences = np.mean(np.diff(array)) - # Check if the minimum difference is at least 50 - return abs(differences) <= min_res - return False # If less than two elements, can't compute difference - -# Apply the function and filter the DataFrame -filtered_df2 = filt_df[filt_df['timeval'].apply(min_resolution)] - - - - - -len(filtered_df2) - - - - - -ts_list = [] -for _, row in filtered_df2.iterrows(): - ts_list.append(pyleo.GeoSeries(time=row['timeval'],value=row['val'], - time_name='year',value_name=row['varname'], - time_unit='CE', value_unit=row['varunits'], - lat = row['lat'], lon = row['lon'], - archiveType = row['archiveType'], verbose = False, - label=row['dataSetName']+'_'+row['varname'])) - - #print(row['timeval']) - - - - - -mgs = pyleo.MultipleGeoSeries(ts_list, label='HydroAm2k', time_unit='year CE') - - - - - -mgs.map() - - - - - -fig, ax = mgs.sel(time=slice(0,2000)).stackplot(v_shift_factor=1.2) -plt.show(fig) - - - - - -mgs_common = mgs.common_time().standardize() - - -pca = mgs_common.pca() - - - - - -pca.screeplot() - - - - - -pca.modeplot() - - - - - -pca.modeplot(index=1) - - - - - -pca.modeplot(index=2) - - - - - - - - -clevs = np.linspace(-1, 1, 20) -proj = ccrs.PlateCarree(central_longitude=290) -fig, ax = plt.subplots(figsize=[10,4], subplot_kw=dict(projection=proj)) -ax.coastlines() -eof1[0].plot.contourf(ax=ax, levels = clevs, cmap=plt.cm.RdBu_r, - transform=ccrs.PlateCarree(), add_colorbar=True) -fig.axes[1].set_ylabel('') -fig.axes[1].set_yticks(np.arange(-1,1.2,0.2)) -ax.set_title('EOF1 expressed as covariance', fontsize=16) -plt.show() - - - - - -pcs = solver.pcs(npcs=3, pcscaling=1) - - -fig, ax = plt.subplots(figsize=[20,4]) -pcs[:, 0].plot(ax=ax, linewidth=1) - -ax = plt.gca() -ax.axhline(0, color='k') -ax.set_ylim(-3, 3) -ax.set_xlabel('Year') -ax.set_ylabel('Normalized Units') -ax.set_title('PC1 Time Series', fontsize=16) - - - - - - - - - - - - diff --git a/notebooks/.virtual_documents/paleoPCA.ipynb b/notebooks/.virtual_documents/paleoPCA.ipynb deleted file mode 100644 index c4ab882..0000000 --- a/notebooks/.virtual_documents/paleoPCA.ipynb +++ /dev/null @@ -1,482 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -#To deal with model data -import s3fs -import fsspec -import xarray as xr -import glob - -#To deal with proxy data -import pandas as pd -import numpy as np -import json -import requests -import pandas as pd -import io -import ast - -#To deal with analysis -import pyleoclim as pyleo -from eofs.xarray import Eof - -#Plotting and mapping -import cartopy.crs as ccrs -import matplotlib.pyplot as plt -import nc_time_axis -from matplotlib import gridspec -from matplotlib.colors import Normalize - - - - - - - - -url = 'https://linkedearth.graphdb.mint.isi.edu/repositories/LiPDVerse-dynamic' - -query = """PREFIX le: -PREFIX wgs84: -PREFIX rdfs: -SELECT distinct?varID ?dataSetName ?lat ?lon ?varname ?interpLabel ?val ?varunits ?timevarname ?timeval ?timeunits ?archiveType where{ - - ?ds a le:Dataset . - ?ds le:hasName ?dataSetName . - OPTIONAL{?ds le:hasArchiveType ?archiveTypeObj . - ?archiveTypeObj rdfs:label ?archiveType .} - - - ?ds le:hasLocation ?loc . - ?loc wgs84:lat ?lat . - FILTER(?lat<26 && ?lat>-26) - ?loc wgs84:long ?lon . - FILTER(?lon<-70 && ?lon>-150) - - ?ds le:hasPaleoData ?data . - ?data le:hasMeasurementTable ?table . - ?table le:hasVariable ?var . - ?var le:hasName ?varname . - VALUES ?varname {"d18O"} . - ?var le:partOfCompilation ?comp . - ?comp le:hasName ?compName . - VALUES ?compName {"iso2k" "Pages2kTemperature" "CoralHydro2k" "SISAL-LiPD"} . - ?var le:hasInterpretation ?interp . - ?interp le:hasVariable ?interpVar . - ?interpVar rdfs:label ?interpLabel . - FILTER (REGEX(?interpLabel, "precipitation.*", "i")) - ?var le:hasVariableId ?varID . - ?var le:hasValues ?val . - OPTIONAL{?var le:hasUnits ?varunitsObj . - ?varunitsObj rdfs:label ?varunits .} - - ?table le:hasVariable ?timevar . - ?timevar le:hasName ?timevarname . - VALUES ?timevarname {"year" "age"} . - ?timevar le:hasValues ?timeval . - OPTIONAL{?timevar le:hasUnits ?timeunitsObj . - ?timeunitsObj rdfs:label ?timeunits .} -}""" - - - - - -response = requests.post(url, data = {'query': query}) - -data = io.StringIO(response.text) -df_res = pd.read_csv(data, sep=",") - -df_res['val']=df_res['val'].apply(lambda row : json.loads(row) if isinstance(row, str) else row) -df_res['timeval']=df_res['timeval'].apply(lambda row : json.loads(row) if isinstance(row, str) else row) - -df_res.head() - - - - - -len(df_res) - - - - - -df = df_res[~df_res['varID'].duplicated()] - - -len(df) - - - - - -df['timevarname'].unique() - - - - - -df['timeunits'].unique() - - - - - -df['timeval'] = df['timeval'].apply(np.array) - -def adjust_timeval(row): - if row['timevarname'] == 'age': - return 1950 - row['timeval'] - else: - return row['timeval'] - -# Apply the function across the DataFrame rows -df['timeval'] = df.apply(adjust_timeval, axis=1) - - - - - -def range_within_limits(array, lower = 0, upper = 2000, threshold = 1500): - filtered_values = array[(array >= lower) & (array <= upper)] - if filtered_values.size > 0: # Check if there are any values within the range - return np.ptp(filtered_values) >= threshold # np.ptp() returns the range of values - return False # If no values within the range, filter out the row - - -# Apply the function to filter the DataFrame -filtered_df = df[df['timeval'].apply(range_within_limits)] - - - - - -len(filtered_df) - - - - - -def array_range_exceeds(array, threshold=1500): - return np.max(array) - np.min(array) > threshold - -filt_df = filtered_df[filtered_df['timeval'].apply(array_range_exceeds)] - - - - - -len(filt_df) - - - - - -def min_resolution(array, min_res=60): - if len(array) > 1: # Ensure there are at least two points to calculate a difference - # Calculate differences between consecutive elements - differences = np.mean(np.diff(array)) - # Check if the minimum difference is at least 50 - return abs(differences) <= min_res - return False # If less than two elements, can't compute difference - -# Apply the function and filter the DataFrame -filtered_df2 = filt_df[filt_df['timeval'].apply(min_resolution)] - - - - - -len(filtered_df2) - - - - - -ts_list = [] -for _, row in filtered_df2.iterrows(): - ts_list.append(pyleo.GeoSeries(time=row['timeval'],value=row['val'], - time_name='year',value_name=row['varname'], - time_unit='CE', value_unit=row['varunits'], - lat = row['lat'], lon = row['lon'], - archiveType = row['archiveType'], verbose = False, - label=row['dataSetName']+'_'+row['varname'])) - - #print(row['timeval']) - - - - - -mgs = pyleo.MultipleGeoSeries(ts_list, label='HydroAm2k', time_unit='year CE') - - - - - -mgs.map() - - - - - -fig, ax = mgs.sel(time=slice(0,2000)).stackplot(v_shift_factor=1.2) -plt.show(fig) - - - - - -mgs_common = mgs.sel(time=slice(850,2000)).common_time().standardize() - - -pca = mgs_common.pca() - - - - - -pca.screeplot() - - - - - -pca.modeplot() - - - - - -pca.modeplot(index=1) - - - - - -pca.modeplot(index=2) - - - - - - - - - - - -URL = 'https://js2.jetstream-cloud.org:8001/' #Locate and read a file - - -path = f'pythia/cesmLME' # specify data location - - -fs = fsspec.filesystem("s3", anon=True, client_kwargs=dict(endpoint_url=URL)) -pattern = f's3://{path}/*.nc' -files = sorted(fs.glob(pattern)) - - - - - -base_name = 'pythia/cesmLME/b.ie12.B1850C5CN.f19_g16.LME.002.cam.h0.' -time_period = '085001-184912' - -names = [name for name in files if base_name in name and time_period in name] - -names - - -fileset = [fs.open(file) for file in names] - - - - - -%%time -for idx,item in enumerate(fileset): - ds_u = xr.open_dataset(item) - var_name = names[idx].split('.')[-3] - da = ds_u[var_name] - try: - ds[var_name]= da - except: - ds = da.to_dataset() - ds.attrs = ds_u.attrs - ds_u.close() - da.close() - - - - - -ds - - - - - -ds_geo_all = ds.sel(lat=slice(-27,27), lon=slice(250,330)) - - - - - -#ds_geo.to_netcdf(path='../data/LME.002.cam.h0.precip_iso.085001-184912.nc') - - - - - -print("The minimum time is: "+str(np.min(mgs_common.series_list[0].time))) -print("The maximum time is: "+str(np.max(mgs_common.series_list[0].time))) -print("The resolution is: "+str(np.mean(np.diff(mgs_common.series_list[0].time)))) - - -ds_geo_time = ds_geo_all.sel(time=slice("0910-01-01 00:00:00" ,"1642-12-01 00:00:00")) - - - - - -ds_geo = ds_geo_time.resample(time='20A').mean() - -ds_geo - - - - - -%%time -p16O = ds_geo['PRECRC_H216Or'] + ds_geo['PRECSC_H216Os'] + ds_geo['PRECRL_H216OR'] + ds_geo['PRECSL_H216OS'] -p18O = ds_geo['PRECRC_H218Or'] + ds_geo['PRECSC_H218Os'] + ds_geo['PRECRL_H218OR'] + ds_geo['PRECSL_H218OS'] - -p16O = p16O.where(p16O > 1e-18, 1e-18) -p18O = p18O.where(p18O > 1e-18, 1e-18) - -d18Op = (p18O / p16O - 1)*1000 - - - - - -d18Oa = (d18Op - d18Op.mean(dim='time'))/d18Op.std(dim='time') - - - - - -solver = Eof(d18Oa, weights=None) - - - - - -eof1 = solver.eofsAsCovariance(neofs=3) - - - - - -clevs = np.linspace(-1, 1, 20) -proj = ccrs.PlateCarree(central_longitude=290) -fig, ax = plt.subplots(figsize=[10,4], subplot_kw=dict(projection=proj)) -ax.coastlines() -eof1[0].plot.contourf(ax=ax, levels = clevs, cmap=plt.cm.RdBu_r, - transform=ccrs.PlateCarree(), add_colorbar=True) -fig.axes[1].set_ylabel('') -fig.axes[1].set_yticks(np.arange(-1,1.2,0.2)) -ax.set_title('EOF1 expressed as covariance', fontsize=16) -plt.show() - - - - - -pcs = solver.pcs(npcs=3, pcscaling=1) - - -fig, ax = plt.subplots(figsize=[20,4]) -pcs[:, 0].plot(ax=ax, linewidth=1) -ax = plt.gca() -ax.axhline(0, color='k') -ax.set_ylim(-3, 3) -ax.set_xlabel('Year') -ax.set_ylabel('Normalized Units') -ax.set_title('PC1 Time Series', fontsize=16) - - - - - -# Create a figure - -fig = plt.figure(figsize=[20,8]) - -# Define the GridSpec -gs = gridspec.GridSpec(1, 2, figure=fig) - -# Add a geographic map in the first subplot using Cartopy - -ax1 = fig.add_subplot(gs[0, 0], projection=ccrs.PlateCarree(central_longitude=290)) -ax1.coastlines() # Add coastlines to the map - -# Plot the model results -norm = Normalize(vmin=-1, vmax=1) -eof1[0].plot.contourf(ax=ax1, levels = clevs, cmap=plt.cm.RdBu_r, - transform=ccrs.PlateCarree(), add_colorbar=True, norm=norm) -ax1.set_title('EOF1 expressed as covariance', fontsize=16) -fig.axes[1].set_ylabel('') -fig.axes[1].set_yticks(np.arange(-1,1.2,0.2)) - -#Now let's scatter the proxy data -EOF = pca.eigvecs[:, 0] -ax1.scatter(filtered_df2['lon'],filtered_df2['lat'], c =EOF, cmap=plt.cm.RdBu_r, transform=ccrs.PlateCarree(), norm=norm, s=400, edgecolor='k', linewidth=3) - -## Let's plot the PCS! -PC = pca.pcs[:, 0] - - -ax2 = fig.add_subplot(gs[0, 1:],) -time_model = np.arange(910,1660,20) -ts1 = pyleo.Series(time = time_model, value = pcs[:, 0], time_name = 'Years', time_unit = 'CE', value_name='PC1', label = 'CESM-LEM',verbose=False) -ts2 = pyleo.Series(time = mgs_common.series_list[0].time, value = PC, time_name = 'Years', time_unit = 'CE', value_name='PC1', label = 'Proxy Data', verbose = False) -ts1.plot(ax=ax2, legend = True) -ts2.plot(ax=ax2, legend = True) -ax2.set_ylim([-1,1]) -ax2.legend() -# Layout adjustments and display the figure -plt.tight_layout() -plt.show() - - - - - - - - - - - - diff --git a/notebooks/paleoPCA.ipynb b/notebooks/paleoPCA.ipynb index c9fb20d..32362ed 100644 --- a/notebooks/paleoPCA.ipynb +++ b/notebooks/paleoPCA.ipynb @@ -28,7 +28,7 @@ "source": [ "## Overview\n", "\n", - "This CookBook demonstrates how to compare paleoclimate model output and proxy observations using EOF to identify large-scale spatio-temporal patterns in the data. It is inspired from a study by [Steinman et al. (2022)](https://www.pnas.org/doi/full/10.1073/pnas.2120015119) although it reuses different datasets. " + "This CookBook demonstrates how to compare paleoclimate model output and proxy observations using EOF to identify large-scale spatio-temporal patterns in the data. It is inspired from a study by [Steinman et al. (2022)](https://doi.org/10.1073/pnas.2120015119) although it reuses different datasets. " ] }, { @@ -2802,13 +2802,13 @@ "- CESM LME: Otto-Bliesner, B.L., E.C. Brady, J. Fasullo, A. Jahn, L. Landrum, S. Stevenson, N. Rosenbloom, A. Mai, G. Strand. Climate Variability and Change since 850 C.E. : An Ensemble Approach with the Community Earth System Model (CESM), Bulletin of the American Meteorological Society, 735-754 (May 2016 issue)\n", " \n", "### Proxy Compilations\n", - "- [Iso2k](https://lipdverse.org/project/iso2k/): Konecky, B. L., McKay, N. P., Churakova (Sidorova), O. V., Comas-Bru, L., Dassié, E. P., DeLong, K. L., Falster, G. M., Fischer, M. J., Jones, M. D., Jonkers, L., Kaufman, D. S., Leduc, G., Managave, S. R., Martrat, B., Opel, T., Orsi, A. J., Partin, J. W., Sayani, H. R., Thomas, E. K., Thompson, D. M., Tyler, J. J., Abram, N. J., Atwood, A. R., Cartapanis, O., Conroy, J. L., Curran, M. A., Dee, S. G., Deininger, M., Divine, D. V., Kern, Z., Porter, T. J., Stevenson, S. L., von Gunten, L., and Iso2k Project Members: The Iso2k database: a global compilation of paleo-δ18O and δ2H records to aid understanding of Common Era climate, Earth Syst. Sci. Data, 12, 2261–2288, [https://doi.org/10.5194/essd-12-2261-2020](https://essd.copernicus.org/articles/12/2261/2020/), 2020.\n", + "- [Iso2k](https://lipdverse.org/project/iso2k/): Konecky, B. L., McKay, N. P., Churakova (Sidorova), O. V., Comas-Bru, L., Dassié, E. P., DeLong, K. L., Falster, G. M., Fischer, M. J., Jones, M. D., Jonkers, L., Kaufman, D. S., Leduc, G., Managave, S. R., Martrat, B., Opel, T., Orsi, A. J., Partin, J. W., Sayani, H. R., Thomas, E. K., Thompson, D. M., Tyler, J. J., Abram, N. J., Atwood, A. R., Cartapanis, O., Conroy, J. L., Curran, M. A., Dee, S. G., Deininger, M., Divine, D. V., Kern, Z., Porter, T. J., Stevenson, S. L., von Gunten, L., and Iso2k Project Members: The Iso2k database: a global compilation of paleo-δ18O and δ2H records to aid understanding of Common Era climate, Earth Syst. Sci. Data, 12, 2261–2288, [https://doi.org/10.5194/essd-12-2261-2020](https://doi.org/10.5194/essd-12-2261-2020), 2020.\n", "\n", - "- [PAGES2kTemperature](https://lipdverse.org/project/pages2k/): PAGES2k Consortium. A global multiproxy database for temperature reconstructions of the Common Era. Sci. Data 4:170088 [doi: 10.1038/sdata.2017.88](https://www.nature.com/articles/sdata201788) (2017).\n", + "- [PAGES2kTemperature](https://lipdverse.org/project/pages2k/): PAGES2k Consortium. A global multiproxy database for temperature reconstructions of the Common Era. Sci. Data 4:170088 [doi: 10.1038/sdata.2017.88](https://doi.org/10.1038/sdata.2017.88) (2017).\n", "\n", - "- CoralHydro2k: Walter, R. M., Sayani, H. R., Felis, T., Cobb, K. M., Abram, N. J., Arzey, A. K., Atwood, A. R., Brenner, L. D., Dassié, É. P., DeLong, K. L., Ellis, B., Emile-Geay, J., Fischer, M. J., Goodkin, N. F., Hargreaves, J. A., Kilbourne, K. H., Krawczyk, H., McKay, N. P., Moore, A. L., Murty, S. A., Ong, M. R., Ramos, R. D., Reed, E. V., Samanta, D., Sanchez, S. C., Zinke, J., and the PAGES CoralHydro2k Project Members: The CoralHydro2k database: a global, actively curated compilation of coral δ18O and Sr ∕ Ca proxy records of tropical ocean hydrology and temperature for the Common Era, Earth Syst. Sci. Data, 15, 2081–2116, [https://doi.org/10.5194/essd-15-2081-2023](https://essd.copernicus.org/articles/15/2081/2023/essd-15-2081-2023.html), 2023.\n", + "- CoralHydro2k: Walter, R. M., Sayani, H. R., Felis, T., Cobb, K. M., Abram, N. J., Arzey, A. K., Atwood, A. R., Brenner, L. D., Dassié, É. P., DeLong, K. L., Ellis, B., Emile-Geay, J., Fischer, M. J., Goodkin, N. F., Hargreaves, J. A., Kilbourne, K. H., Krawczyk, H., McKay, N. P., Moore, A. L., Murty, S. A., Ong, M. R., Ramos, R. D., Reed, E. V., Samanta, D., Sanchez, S. C., Zinke, J., and the PAGES CoralHydro2k Project Members: The CoralHydro2k database: a global, actively curated compilation of coral δ18O and Sr ∕ Ca proxy records of tropical ocean hydrology and temperature for the Common Era, Earth Syst. Sci. Data, 15, 2081–2116, [https://doi.org/10.5194/essd-15-2081-2023](https://doi.org/10.5194/essd-15-2081-2023), 2023.\n", "\n", - "- SISAL: omas-Bru, L., Rehfeld, K., Roesch, C., Amirnezhad-Mozhdehi, S., Harrison, S. P., Atsawawaranunt, K., Ahmad, S. M., Brahim, Y. A., Baker, A., Bosomworth, M., Breitenbach, S. F. M., Burstyn, Y., Columbu, A., Deininger, M., Demény, A., Dixon, B., Fohlmeister, J., Hatvani, I. G., Hu, J., Kaushal, N., Kern, Z., Labuhn, I., Lechleitner, F. A., Lorrey, A., Martrat, B., Novello, V. F., Oster, J., Pérez-Mejías, C., Scholz, D., Scroxton, N., Sinha, N., Ward, B. M., Warken, S., Zhang, H., and SISAL Working Group members: SISALv2: a comprehensive speleothem isotope database with multiple age–depth models, Earth Syst. Sci. Data, 12, 2579–2606, [https://doi.org/10.5194/essd-12-2579-2020](https://essd.copernicus.org/articles/12/2579/2020/), 2020.\n", + "- SISAL: omas-Bru, L., Rehfeld, K., Roesch, C., Amirnezhad-Mozhdehi, S., Harrison, S. P., Atsawawaranunt, K., Ahmad, S. M., Brahim, Y. A., Baker, A., Bosomworth, M., Breitenbach, S. F. M., Burstyn, Y., Columbu, A., Deininger, M., Demény, A., Dixon, B., Fohlmeister, J., Hatvani, I. G., Hu, J., Kaushal, N., Kern, Z., Labuhn, I., Lechleitner, F. A., Lorrey, A., Martrat, B., Novello, V. F., Oster, J., Pérez-Mejías, C., Scholz, D., Scroxton, N., Sinha, N., Ward, B. M., Warken, S., Zhang, H., and SISAL Working Group members: SISALv2: a comprehensive speleothem isotope database with multiple age–depth models, Earth Syst. Sci. Data, 12, 2579–2606, [https://doi.org/10.5194/essd-12-2579-2020](https://doi.org/10.5194/essd-12-2579-2020), 2020.\n", "\n", "\n", "### Software\n", @@ -2817,11 +2817,11 @@ "\n", "- [Pyleoclim](https://pyleoclim-util.readthedocs.io/en/latest/):\n", "\n", - "Khider, D., Emile-Geay, J., Zhu, F., James, A., Landers, J., Ratnakar, V., & Gil, Y. (2022). Pyleoclim: Paleoclimate timeseries analysis and visualization with Python. Paleoceanography and Paleoclimatology, 37, e2022PA004509. [https://doi.org/10.1029/2022PA004509](https://agupubs.onlinelibrary.wiley.com/doi/full/10.1029/2022PA004509)\n", + "Khider, D., Emile-Geay, J., Zhu, F., James, A., Landers, J., Ratnakar, V., & Gil, Y. (2022). Pyleoclim: Paleoclimate timeseries analysis and visualization with Python. Paleoceanography and Paleoclimatology, 37, e2022PA004509. [https://doi.org/10.1029/2022PA004509](https://doi.org/10.1029/2022PA004509)\n", "\n", "Khider, D., Emile-Geay, J., Zhu, F., James, A., Landers, J., Kwan, M., Athreya, P., McGibbon, R., & Voirol, L. (2024). Pyleoclim: A Python package for the analysis and visualization of paleoclimate data (Version v1.0.0) [Computer software]. https://doi.org/10.5281/zenodo.1205661\n", "\n", - "- [eofs](https://ajdawson.github.io/eofs/latest/): Dawson, A. (2016) ‘eofs: A Library for EOF Analysis of Meteorological, Oceanographic, and Climate Data’, Journal of Open Research Software, 4(1), p. e14. Available at: [https://doi.org/10.5334/jors.122](https://openresearchsoftware.metajnl.com/articles/10.5334/jors.122)." + "- [eofs](https://ajdawson.github.io/eofs/latest/): Dawson, A. (2016) ‘eofs: A Library for EOF Analysis of Meteorological, Oceanographic, and Climate Data’, Journal of Open Research Software, 4(1), p. e14. Available at: [https://doi.org/10.5334/jors.122](https://doi.org/10.5334/jors.122)." ] }, {