diff --git a/.github/workflows/test_navdat.yml b/.github/workflows/test_navdat.yml new file mode 100644 index 00000000..d2236a60 --- /dev/null +++ b/.github/workflows/test_navdat.yml @@ -0,0 +1,36 @@ +name: navdat.py test + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + linux: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.11'] + + steps: + - uses: actions/checkout@v5 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install . -r requirements.extras.txt + + - name: Test NAVDAT Backend + run: | + pip install pytest + pytest dsi/backends/tests/test_navdat.py -vv -s --maxfail=1 --full-trace --log-cli-level=INFO + env: + PYTHONUNBUFFERED: 1 \ No newline at end of file diff --git a/.github/workflows/test_user_stories.yml b/.github/workflows/test_user_stories.yml new file mode 100644 index 00000000..4cab94df --- /dev/null +++ b/.github/workflows/test_user_stories.yml @@ -0,0 +1,61 @@ +name: User Stories test + +on: + push: + branches: + - main + pull_request: + branches: + - main + + +jobs: + linux: + runs-on: ubuntu-latest + # strategy: + # matrix: + # python-version: ['3.11'] + + steps: + - uses: actions/checkout@v5 + # - name: Set up Python ${{ matrix.python-version }} + # uses: actions/setup-python@v6 + # with: + # python-version: ${{ matrix.python-version }} + - name: Check user stories structure + shell: bash + run: | + set -euo pipefail + + [[ -d tools ]] || { + echo "::error::tools/ directory does not exist." + exit 1 + } + + failed=0 + + while IFS= read -r -d '' tool_dir; do + has_readme=0 + + for file in "$tool_dir"/*; do + [[ -f "$file" ]] || continue + + filename="${file##*/}" + if [[ "${filename,,}" == "readme.md" ]]; then + has_readme=1 + break + fi + done + + if [[ "$has_readme" -eq 0 ]]; then + echo "::error file=$tool_dir::A README file is required (README.md or readme.md)." + failed=1 + fi + + if [[ ! -f "$tool_dir/requirements.txt" ]]; then + echo "::error file=$tool_dir::requirements.txt is required." + failed=1 + fi + done < <(find tools -mindepth 1 -maxdepth 1 -type d -print0) + + exit "$failed" \ No newline at end of file diff --git a/docs/backends.rst b/docs/backends.rst index e46340e2..e5d6f955 100644 --- a/docs/backends.rst +++ b/docs/backends.rst @@ -105,4 +105,12 @@ RCSBPDB (Read-only) .. automodule:: dsi.backends.rcsbpdb :members: RCSBPDB + :special-members: __init__ + + +NAVDAT (Read-only) +------------------- + +.. automodule:: dsi.backends.navdat + :members: NAVDAT :special-members: __init__ \ No newline at end of file diff --git a/docs/cr_intro.rst b/docs/cr_intro.rst index 0dbcc67b..0f1083b9 100644 --- a/docs/cr_intro.rst +++ b/docs/cr_intro.rst @@ -51,6 +51,7 @@ Webserver Backends - Create a connection to a remote data platform for users to - NDP (Read-only): Connection to data on the `National Data Platform `_ - Denodo (Read-only): Connection to a specific `Denodo Data Catalog `_ instance. - RCSBPDB (Read-only): Connection to data on the `Research Collaboratory for Structural Bioinformatics Protein Data Bank `_ + - NAVDAT (Read-only): Connection to data on the `North American Volcanic and Intrusive Rock Database `_ DSI Core ~~~~~~~~ diff --git a/docs/introduction.rst b/docs/introduction.rst index 7166bd31..eb509bec 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -114,6 +114,7 @@ Webserver Backends - Create a connection to a remote data platform for users to - NDP (Read-only): Connection to data on the `National Data Platform `_ - Denodo (Read-only): Connection to a specific `Denodo Data Catalog `_ instance. - RCSBPDB (Read-only): Connection to data on the `Research Collaboratory for Structural Bioinformatics Protein Data Bank `_ + - NAVDAT (Read-only): Connection to data on the `North American Volcanic and Intrusive Rock Database `_ DSI Core ~~~~~~~~ diff --git a/dsi/backends/navdat.py b/dsi/backends/navdat.py new file mode 100644 index 00000000..dcdc898b --- /dev/null +++ b/dsi/backends/navdat.py @@ -0,0 +1,876 @@ +"""NAVDAT webserver backend for DSI. + +The backend queries the EarthChem PetDB v4 API and exposes sample and +citation metadata as in-memory DSI tables. NAVDAT data is served through +PetDB because navdat.org does not provide an API. +""" + +from collections import OrderedDict +from urllib.parse import urlparse + +import numpy as np +import pandas as pd +import requests + +from dsi.backends.webserver import Webserver + + +class ValueObject: + """Container for results returned by the find methods.""" + def __init__(self): + self.t_name = "" + self.c_name = [] + self.row_num = None + self.value = None + self.type = "" + + +class NAVDAT(Webserver): + """Read-only PetDB v4 backend for NAVDAT geochemistry metadata. + + The ``samples`` table contains one row per sample. The ``citations`` + table contains one row per citation or dataset record. + """ + read_only = True + + def __init__(self, url=None, params=None, **kwargs): + """Initialize the backend and optionally load PetDB data. + + Parameters + ---------- + `url` : str, optional + Base API URL. Defaults to ``https://api.earthchem.org``. + `params` : dict or list of dict, optional + Search properties passed as GET query parameters to both + `/v4/locations/samples` and `/v4/citations`. + `**kwargs` : dict + Additional keyword arguments: + - verify_ssl: enable TLS certificate verification. + - fetch_citations: populate the ``citations`` table. + """ + + DEFAULT_URL = "https://api.earthchem.org" + + base_url = url or DEFAULT_URL + + self.verify_ssl = kwargs.get("verify_ssl", True) + self.fetch_citations = kwargs.get("fetch_citations", True) + + parsed = urlparse(base_url) + if not parsed.scheme or not parsed.netloc: + raise ValueError("Invalid base_url") + + self.base_url = base_url.rstrip("/") + + # Skip data retrieval when only validating the endpoint. + if kwargs.get("only_validate", False): + return + + self._cache = OrderedDict() + + self._loaded = False + self.params = params or {} + self.validate_error_msg = None + + if not self.validate_connection(): + self._loaded = False + raise ConnectionError(self.validate_error_msg or "Failed to connect to the NAVDAT API") + + if self.params: + try: + self._load_initial_data(self.params) + self._loaded = True + except Exception as e: + self._loaded = False + raise RuntimeError(f"Failed to load initial data: {e}") from e + else: + self._loaded = True + + def validate_connection(self): + """Return whether the PetDB v4 metrics endpoint is available.""" + try: + response = requests.get( + f"{self.base_url}/v4/metrics", + verify=self.verify_ssl, + timeout=10 + ) + response.raise_for_status() + data = response.json() + + if not isinstance(data, dict) or data.get("status") != "success": + self.validate_error_msg = f"Unexpected response envelope from {self.base_url}/v4/metrics: {data}" + return False + + return True + + except requests.exceptions.Timeout: + self.validate_error_msg = f"Connection timeout: Cannot reach {self.base_url} within 10 seconds" + return False + except requests.exceptions.ConnectionError: + self.validate_error_msg = f"Connection failed: Cannot connect to {self.base_url}. Check your network connection." + return False + except requests.exceptions.HTTPError as e: + self.validate_error_msg = f"HTTP {e.response.status_code} Error from {self.base_url}: {str(e)}" + return False + except requests.exceptions.RequestException as e: + self.validate_error_msg = f"Failed to validate connection to {self.base_url}: {str(e)}" + return False + except Exception as e: + self.validate_error_msg = f"Unable to connect to NAVDAT API: {e}" + return False + + def _load_initial_data(self, params): + """Load and deduplicate sample and citation data for one or more queries.""" + if isinstance(params, dict): + query_list = [params] + elif isinstance(params, list) and all(isinstance(p, dict) for p in params): + query_list = params + else: + raise TypeError("params must be a dict or a list of dicts") + + all_sample_rows = [] + all_citation_rows = [] + + for query_params in query_list: + all_sample_rows.extend(self._fetch_samples(query_params)) + if self.fetch_citations: + all_citation_rows.extend(self._fetch_citations(query_params)) + + unique_sample_rows = self._deduplicate_rows(all_sample_rows) + unique_citation_rows = self._deduplicate_rows(all_citation_rows) + + self._cache["samples"] = self._rows_to_table(unique_sample_rows) + if unique_citation_rows: + self._cache["citations"] = self._rows_to_table(unique_citation_rows) + + self._loaded = True + + # Query parameters supported by the PetDB search endpoints. + SEARCH_PROP_KEYS = [ + "sampleNames", "authors", "citationTitles", "journals", + "publicationYears", "laboratories", "dataSources", "expeditions", + "analysisTypes", "geoFeatures", "taxons", "variables", + "boundingBox", "polygons", "precision", + ] + + def _build_search_props(self, query_params): + """Return supported PetDB search properties from a query.""" + return {k: query_params[k] for k in self.SEARCH_PROP_KEYS if k in query_params} + + def _fetch_samples(self, query_params): + """Fetch sample groups and flatten them into individual sample rows.""" + rest_params = self._build_search_props(query_params) + if "size" in query_params: + rest_params["size"] = query_params["size"] + + try: + response = requests.get( + f"{self.base_url}/v4/locations/samples", + params=rest_params, + verify=self.verify_ssl, + timeout=30 + ) + response.raise_for_status() + payload = response.json() + except (requests.exceptions.RequestException, ValueError) as e: + print(f"Warning: Could not retrieve samples: {e}") + return [] + + if not isinstance(payload, dict) or payload.get("status") != "success": + print(f"Warning: Unexpected response from /v4/locations/samples: {payload}") + return [] + + groups = payload.get("data", []) + rows = [] + for group in groups: + if not isinstance(group, dict): + continue + point = group.get("point", {}) or {} + group_fields = { + "rootParent": group.get("rootParent"), + "groupDocCount": group.get("docCount"), + "sampleCount": group.get("sampleCount"), + "groupLat": point.get("lat"), + "groupLon": point.get("lon"), + } + for sample in group.get("samples", []) or []: + if not isinstance(sample, dict): + continue + geometry = sample.get("sampleGeometry", {}) or {} + coords = geometry.get("coordinates", [None, None]) + row = dict(group_fields) + row.update({ + "sampleName": sample.get("sampleName"), + "sampleId": sample.get("sampleId"), + "sampleDocCount": sample.get("docCount"), + "sampleLon": coords[0] if len(coords) > 0 else None, + "sampleLat": coords[1] if len(coords) > 1 else None, + }) + rows.append(row) + + return rows + + def _fetch_citations(self, query_params): + """Fetch citation records and flatten nested values into strings.""" + rest_params = self._build_search_props(query_params) + + try: + response = requests.get( + f"{self.base_url}/v4/citations", + params=rest_params, + verify=self.verify_ssl, + timeout=30 + ) + response.raise_for_status() + payload = response.json() + except (requests.exceptions.RequestException, ValueError) as e: + print(f"Warning: Could not retrieve citations: {e}") + return [] + + if not isinstance(payload, dict) or payload.get("status") != "success": + print(f"Warning: Unexpected response from /v4/citations: {payload}") + return [] + + records = payload.get("data", []) + rows = [] + for record in records: + if not isinstance(record, dict): + continue + + authors = record.get("citationAuthors", []) or [] + author_str = "; ".join(a.get("fullName", "") for a in authors if isinstance(a, dict)) + + methods = record.get("methods", []) or [] + method_str = ", ".join(m.get("methodName", "") for m in methods if isinstance(m, dict)) + + identifiers = record.get("citationIdentifiers", []) or [] + doi_str = ", ".join( + i.get("identifier", "") for i in identifiers + if isinstance(i, dict) and i.get("identifierType") == "DOI" + ) + + rows.append({ + "citationId": record.get("citationId"), + "citationTitle": record.get("citationTitle"), + "citationCode": record.get("citationCode"), + "citationContainerTitle": record.get("citationContainerTitle"), + "citationPublicationYear": record.get("citationPublicationYear"), + "citationVolume": record.get("citationVolume"), + "citationIssue": record.get("citationIssue"), + "citationPages": record.get("citationPages"), + "citationAuthors": author_str, + "citationDOIs": doi_str, + "datasetCode": record.get("datasetCode"), + "datasetTitle": record.get("datasetTitle"), + "datasetNum": record.get("datasetNum"), + "analysisType": record.get("analysisType"), + "methods": method_str, + }) + + return rows + + def _deduplicate_rows(self, rows): + """ + Removes exact-duplicate rows (by full content), since paginated + multi-query fetches can overlap. + + Parameters + ---------- + rows : list of dict + + Returns + ------- + list of dict + """ + seen = set() + unique_rows = [] + for row in rows: + key = tuple(sorted((k, str(v)) for k, v in row.items())) + if key not in seen: + seen.add(key) + unique_rows.append(row) + return unique_rows + + def _rows_to_table(self, rows): + """ + Convert list-of-dicts to column-oriented OrderedDict. + + Parameters + ---------- + `rows` : list of dict + + Returns + ------- + OrderedDict + Column-oriented table structure + """ + if not rows: + return OrderedDict() + + cols = [] + for row in rows: + for c in row.keys(): + if c not in cols: + cols.append(c) + + table = OrderedDict({c: [] for c in cols}) + for row in rows: + for c in cols: + table[c].append(row.get(c)) + + return table + + # ---------------------------------------------------------------------- + # Terminal Methods + # ---------------------------------------------------------------------- + def num_tables(self, **kwargs): + """ + Prints the number of tables currently loaded, or returns the number + of rows in a specific table if `table_name` is given. + + Returns + ------- + - If kwargs is empty: None (prints count) + - If kwargs has 'table_name': int (row count for that table, 0 if missing/empty) + """ + table_name = kwargs.get("table_name") + if isinstance(table_name, str): + if not self._loaded: + return 0 + table = self._cache.get(table_name, {}) + if not table: + return 0 + first_col = next(iter(table.values()), []) + return len(first_col) + + if not self._loaded: + print("NAVDAT Backend has 0 tables") + return + + table_count = sum(1 for t in self._cache.values() if t) + print(f"Database now has {table_count} tables") + + def get_table(self, table_name, dict_return=False): + """ + Returns all data from a specified table. + + Parameters + ---------- + table_name : str + Must be 'samples' or 'citations' + dict_return : bool, default False + If True, returns OrderedDict. If False, returns pandas DataFrame. + """ + if not self._loaded: + raise RuntimeError("No data loaded") + + if table_name not in self._cache: + raise ValueError( + f"Table '{table_name}' not found. " + f"Available tables: {list(self._cache.keys())}" + ) + + table = self._cache.get(table_name) + if not table: + raise ValueError(f"Table '{table_name}' is empty") + + return table if dict_return else pd.DataFrame(table) + + def get_schema(self, table_name: str | None = None): + """ + Returns schema information for all tables or a specific table in + SQLite CREATE TABLE format. + """ + def infer_sql_type(series): + pandas_dtype = str(series.dtype).lower() + if 'int' in pandas_dtype: + return 'INTEGER' + elif 'float' in pandas_dtype: + return 'REAL' + elif pandas_dtype == 'bool': + return 'BOOLEAN' + elif pandas_dtype == 'datetime64[ns]': + return 'DATETIME' + non_null = series.dropna() + if non_null.empty or all(isinstance(x, str) for x in non_null): + return 'TEXT' + return 'OBJECT' + + if not self._loaded or not self._cache: + return "-- No tables loaded\n" + + tables_to_render = {table_name: self._cache[table_name]} if table_name else self._cache + if table_name and table_name not in self._cache: + return f"-- Table '{table_name}' not found\n" + + schema_lines = [] + for tbl_name, table_data in tables_to_render.items(): + if not table_data: + continue + df = pd.DataFrame(table_data) + schema_lines.append(f"CREATE TABLE {tbl_name} (") + column_defs = [f" {col} {infer_sql_type(df[col])}" for col in df.columns] + schema_lines.append(",\n".join(column_defs)) + schema_lines.append(");") + schema_lines.append("") + + return "\n".join(schema_lines) if schema_lines else f"-- Table '{table_name}' is empty\n" + + # ---------------------------------------------------------------------- + # Query Interface (in-memory) + # ---------------------------------------------------------------------- + def query_artifacts(self, query, dict_return=True, **kwargs): + """ + Query not supported for NAVDAT backend (non-SQL backend). + + Raises + ------ + NotImplementedError + Always raised - use find(), search(), or find_relation() instead. + """ + raise NotImplementedError( + "query() is not supported for NAVDAT backend - it is a non-SQL backend.\n\n" + "Use these methods instead:\n" + " dsi.find('minage < 10') # Query with conditions\n" + " dsi.search('basalt') # Search across all tables\n" + " dsi.find_relation('samples', 'citations') # Query relationships\n" + ) + + def find_relation(self, column_name, relation, **kwargs): + """ + Find rows where column matches relation (e.g., 'minage > 5'). + """ + if not self._loaded: + raise RuntimeError( + "find_relation() ERROR: Cannot search an empty backend. " + "Ensure data is loaded first." + ) + + operator, value = self._parse_relation(relation) + matches = [] + + for table_name in self.list(collection=True): + df = self.get_table(table_name, dict_return=False) + + if column_name not in df.columns: + continue + + if operator in {'>', '<', '>=', '<=', 'range'}: + df[column_name] = pd.to_numeric(df[column_name], errors='coerce') + df = df.dropna(subset=[column_name]) + + filtered = self._apply_pandas_filter(df, column_name, operator, value) + + for idx, row in filtered.iterrows(): + vo = ValueObject() + vo.t_name = table_name + vo.c_name = list(df.columns) + vo.row_num = int(idx) + 1 + vo.value = row.tolist() + vo.type = "cell" + matches.append(vo) + + return matches + + def _apply_pandas_filter(self, df, column, operator, value): + if operator == '>': + return df[df[column] > value] + elif operator == '<': + return df[df[column] < value] + elif operator == '>=': + return df[df[column] >= value] + elif operator == '<=': + return df[df[column] <= value] + elif operator == '==': + return df[df[column] == value] + elif operator == '!=': + return df[df[column] != value] + elif operator == 'contains': + return df[df[column].astype(str).str.contains(str(value), case=False, na=False)] + elif operator == 'range': + min_val, max_val = value + return df[(df[column] >= min_val) & (df[column] <= max_val)] + return pd.DataFrame() + + def _parse_relation(self, relation): + relation = relation.strip() + if relation.startswith('>='): + return '>=', self._parse_value(relation[2:]) + elif relation.startswith('<='): + return '<=', self._parse_value(relation[2:]) + elif relation.startswith('=='): + return '==', self._parse_value(relation[2:]) + elif relation.startswith('!='): + return '!=', self._parse_value(relation[2:]) + elif relation.startswith('~~'): + return 'contains', self._parse_value(relation[2:]) + elif relation.startswith('>'): + return '>', self._parse_value(relation[1:]) + elif relation.startswith('<'): + return '<', self._parse_value(relation[1:]) + elif relation.startswith('(') and relation.endswith(')'): + parts = relation[1:-1].split(',') + if len(parts) == 2: + return 'range', (self._parse_value(parts[0]), self._parse_value(parts[1])) + raise ValueError(f"Unknown relation format: {relation}") + + def _parse_value(self, value_str): + value_str = str(value_str).strip() + if (value_str.startswith("'") and value_str.endswith("'")) or \ + (value_str.startswith('"') and value_str.endswith('"')): + value_str = value_str[1:-1] + try: + if '.' not in value_str: + return int(value_str) + return float(value_str) + except ValueError: + return value_str + + # ---------------------------------------------------------------------- + # Artifact Processing + # ---------------------------------------------------------------------- + def process_artifacts(self): + """ + Returns all cached tables. Useful for exporting to external systems. + + Returns + ------- + OrderedDict + """ + if not self._loaded: + return {} + return self._cache + + # ---------------------------------------------------------------------- + # Find Methods + # ---------------------------------------------------------------------- + def find(self, query_object, **kwargs): + """ + Searches for all instances of query_object across all tables, at + the table, column, and cell levels. + """ + query_str = str(query_object).lower() + return ( + self.find_table(query_str) + + self.find_column(query_str) + + self.find_cell(query_object) + ) + + def find_table(self, query_object, **kwargs): + """Finds all tables whose names contain the given query_object.""" + if not isinstance(query_object, str): + raise TypeError("find_table() ERROR: query_object must be a string") + + matches = [] + for table_name, table_data in self._cache.items(): + if query_object in table_name.lower(): + val = ValueObject() + val.t_name = table_name + val.c_name = list(table_data.keys()) + val.value = table_data + val.type = "table" + matches.append(val) + return matches + + def find_column(self, query_object, range=False, **kwargs): + """Finds all columns whose names contain the given query_object.""" + if not isinstance(query_object, str): + raise TypeError("find_column() ERROR: query_object must be a string") + + matches = [] + query = query_object.lower() + + for table_name, table_data in self._cache.items(): + for col_name, col_data in table_data.items(): + if query in col_name.lower(): + val = ValueObject() + val.t_name = table_name + val.c_name = [col_name] + val.row_num = None + val.value = col_data + val.type = "column" + + if range: + numeric_col = pd.to_numeric( + pd.Series(col_data), + errors="coerce", + ).dropna() + + if not numeric_col.empty: + val.value = { + "min": numeric_col.min(), + "max": numeric_col.max(), + } + + matches.append(val) + + return matches + + def find_cell(self, query_object, row=True, **kwargs): + """ + Finds all cells that match the given query_object. + + Parameters + ---------- + query_object : any + Value to search for. + row : bool, optional + If True, return full matching rows. + If False, return individual matching cells. + """ + matches = [] + seen_rows = set() + + is_str_query = isinstance(query_object, str) + query_lower = query_object.lower() if is_str_query else None + + for table_name, table_data in self._cache.items(): + if not table_data: + continue + + cols = list(table_data.keys()) + df = pd.DataFrame(table_data) + + if df.empty: + continue + + for row_idx, row_data in df.iterrows(): + row_matched = False + + for col in cols: + cell = row_data[col] + match = False + + if pd.isna(cell) and not is_str_query and pd.isna(query_object): + match = True + elif query_object == cell: + match = True + elif is_str_query and query_lower in str(cell).lower(): + match = True + elif is_str_query and isinstance(cell, (dict, list, tuple)): + if query_lower in str(cell).lower(): + match = True + + if not match: + continue + + if row: + row_matched = True + break + + val = ValueObject() + val.t_name = table_name + val.c_name = [col] + val.row_num = int(row_idx) + val.value = cell + val.type = "cell" + matches.append(val) + + if row and row_matched: + key = (table_name, int(row_idx)) + + if key in seen_rows: + continue + + seen_rows.add(key) + + val = ValueObject() + val.t_name = table_name + val.c_name = cols + val.row_num = int(row_idx) + val.value = row_data.to_dict() + val.type = "row" + matches.append(val) + + return matches + + # ---------------------------------------------------------------------- + # Utility / Display + # ---------------------------------------------------------------------- + def list(self, collection=False): + """ + Lists tables, or prints table names with dimensions. + """ + if collection: + return list(self._cache.keys()) + + for name, table in self._cache.items(): + df = pd.DataFrame(table) + print(f"Table: {name}") + print(f" - num of columns: {len(df.columns)}") + print(f" - num of rows: {len(df)}") + print() + + def summary(self, table_name=None): + """ + Returns detailed column-level statistics for tables. + """ + def is_complex_value(value): + return isinstance(value, (dict, list, tuple, set)) + + def safe_to_python(value): + if pd.isna(value): + return None + if isinstance(value, (np.integer, np.floating)): + return value.item() + return value + + def summarize_dataframe(df): + rows = [] + for column in df.columns: + original_series = df[column] + non_null = original_series.dropna() + has_complex_values = ( + non_null.apply(is_complex_value).any() if not non_null.empty else False + ) + safe_series = non_null.astype(str) if has_complex_values else non_null + + pandas_dtype = str(original_series.dtype).lower() + if 'int' in pandas_dtype: + dtype = 'INTEGER' + elif 'float' in pandas_dtype: + dtype = 'REAL' + elif pandas_dtype == 'bool': + dtype = 'BOOLEAN' + elif pandas_dtype == 'object': + dtype = 'TEXT' if (non_null.empty or all(isinstance(x, str) for x in non_null)) else 'OBJECT' + else: + dtype = 'OBJECT' + + row = { + "column": column, "type": dtype, + "unique": int(safe_series.nunique()) if not safe_series.empty else 0, + "min": None, "max": None, "avg": None, "std_dev": None, + } + + numeric_series = pd.to_numeric(non_null, errors="coerce").dropna() + if not non_null.empty and len(numeric_series) == len(non_null): + row["min"] = safe_to_python(numeric_series.min()) + row["max"] = safe_to_python(numeric_series.max()) + row["avg"] = safe_to_python(numeric_series.mean()) + row["std_dev"] = safe_to_python(numeric_series.std()) + elif not non_null.empty and not has_complex_values: + try: + row["min"] = safe_to_python(non_null.min()) + row["max"] = safe_to_python(non_null.max()) + except TypeError: + pass + + rows.append(row) + + summary_df = pd.DataFrame(rows, columns=["column", "type", "unique", "min", "max", "avg", "std_dev"]) + return summary_df.replace({np.nan: None}) + + if not self._loaded: + return pd.DataFrame() if table_name else [[], pd.DataFrame()] + + if table_name: + if table_name not in self._cache: + raise ValueError(f"Table '{table_name}' not found. Available: {list(self._cache.keys())}") + table = self._cache.get(table_name) + if not table: + raise ValueError(f"Table '{table_name}' is empty") + return summarize_dataframe(pd.DataFrame(table).infer_objects()) + + table_names, summary_dfs = [], [] + for name, table in self._cache.items(): + if not table: + continue + table_names.append(name) + summary_dfs.append(summarize_dataframe(pd.DataFrame(table).infer_objects())) + return [table_names] + summary_dfs + + def search(self, params, download=False): + """ + Retrieves metadata or data for entries in a specified dataset. + + Parameters + ---------- + `params` : dict + Identifies the dataset and, optionally, a specific entry. + For example, {"database_id": 1} retrieves metadata for all entries + in the dataset, while {"database_id": 1, "entry_id": 1} retrieves + data for a specific entry (if supported). + + `download` : bool, optional, default=False + If True, downloads specified data to a local directory. + If False, returns dataset's entry metadata when no entry is specified, + or references to a specified entry's data for separate download. + + Returns + ------- + pandas.DataFrame or list of pandas.DataFrame + Metadata for a dataset's entries or data references for a specified entry. + Returns a list of DataFrames when the metadata spans multiple tables. + + None + If no metadata found or data directly downloaded with (download=True) + """ + raise NotImplementedError + + def display(self, table_name, num_rows=25, display_cols=None): + """ + Return rows from a specified table as a DataFrame. + """ + if not self._loaded: + raise RuntimeError("No data loaded. Cannot display empty backend.") + + if not isinstance(table_name, str): + raise TypeError("display() ERROR: Input 'table_name' must be a string") + + if display_cols is not None and not isinstance(display_cols, list): + raise TypeError( + "display() ERROR: Input 'display_cols' must be a list of column names or None" + ) + + if num_rows is not None and (not isinstance(num_rows, int) or num_rows <= 0): + raise ValueError("display() ERROR: Input 'num_rows' must be a positive integer or None") + + if table_name not in self._cache: + raise ValueError(f"Table '{table_name}' not found. Available: {list(self._cache.keys())}") + + table = self._cache.get(table_name) + + if not table: + raise ValueError(f"Table '{table_name}' is empty") + + df = pd.DataFrame(table) + + if display_cols: + missing_cols = set(display_cols) - set(df.columns) + + if missing_cols: + raise ValueError(f"Columns not found in '{table_name}': {missing_cols}") + + df = df[display_cols] + + if num_rows: + df = df.head(num_rows) + + return df + + def notebook(self, **kwargs): + """Notebook generation not supported for NAVDAT backend.""" + raise NotImplementedError("Notebook generation not supported for NAVDAT backend") + + # ---------------------------------------------------------------------- + # Lifecycle + # ---------------------------------------------------------------------- + def close(self): + """Resets backend state and clears all cached data.""" + self._cache = OrderedDict() + self._loaded = False + + # ---------------------------------------------------------------------- + # Abstract Methods + # ---------------------------------------------------------------------- + def ingest_artifacts(self, artifacts, **kwargs) -> None: + """ + Ingest not supported for NAVDAT backend (read-only). + + Raises + ------ + NotImplementedError + Always raised as NAVDAT backend is read-only + """ + raise NotImplementedError("NAVDAT backend is read-only") diff --git a/dsi/backends/tests/test_navdat.py b/dsi/backends/tests/test_navdat.py new file mode 100644 index 00000000..5c9947fa --- /dev/null +++ b/dsi/backends/tests/test_navdat.py @@ -0,0 +1,343 @@ +""" +NAVDAT Backend Function Tests + +Essential tests for NAVDAT backend core functionality, against the live +PetDB v4 API (https://api.earthchem.org). + +Remote-server calls are minimized by sharing NAVDAT instances through +module-scoped pytest fixtures. Tests that close or otherwise invalidate +an instance use dedicated fixtures. + +NOTE: `authors` is confirmed (2026-08-24) to filter BOTH GET /v4/citations +and GET /v4/locations/samples (totalCount 149778 -> 1558 with +authors=Walker). See navdat.py's module docstring for the full record of +what's confirmed vs. still open. + +ENVIRONMENT NOTE: the truststore injection below is required on machines +behind a corporate TLS-inspecting proxy (e.g. Zscaler) - see navdat.py's +module docstring for the full diagnosis. It's applied here, scoped to the +test suite, rather than inside navdat.py itself, because it changes SSL +trust behavior for the whole Python process - that's reasonable to impose +on "running this test suite" but not on "importing this backend module" in +someone else's application. +""" + +# Must run before any `requests`/`ssl` usage in this process. See the +# ENVIRONMENT NOTE above and navdat.py's module docstring for why this is +# needed and how it was confirmed (not just assumed) to be necessary. +try: + import truststore + truststore.inject_into_ssl() +except ImportError: + pass # fine on machines that don't need OS-trust-store bridging + +from collections import OrderedDict + +import pandas as pd +import pytest + +from dsi.backends.navdat import NAVDAT + +# ============================================================================= +# Fixtures +# ============================================================================= + +@pytest.fixture(scope="module") +def backend(): + """ + Shared backend for non-destructive tests. + Do not call close() on this fixture from an individual test. + """ + instance = NAVDAT( + params={ + "authors": "Walker", + "size": 5, + } + ) + yield instance + instance.close() + + +@pytest.fixture(scope="module") +def filtered_backend(): + """ + Shared backend for filter and multiple-query tests. + """ + instance = NAVDAT( + params=[ + {"authors": "Hekinian", "size": 5}, + {"sampleNames": "(2.151) 12.40-12.85", "size": 5}, + ] + ) + yield instance + instance.close() + + +@pytest.fixture(scope="module") +def empty_backend(): + """Shared backend for tests requiring an empty query result.""" + instance = NAVDAT( + params={ + "sampleNames": "zzzzznonexistentsamplenamezzzzz", + "size": 5, + } + ) + yield instance + instance.close() + + +# ============================================================================= +# 1) Initialization & Connection +# ============================================================================= + +def test_navdat_initialization(backend): + """Test NAVDAT backend initializes correctly.""" + assert backend._loaded is True + assert len(backend._cache) > 0 + assert "samples" in backend._cache + + +def test_navdat_validate_connection(backend): + """Test connection validation to the PetDB v4 API.""" + assert backend.validate_connection() is True + + +# ============================================================================= +# 2) Data Loading & Structure +# ============================================================================= + +def test_navdat_samples_table_present(backend): + """Test that the samples table is populated with rows.""" + df = backend.get_table("samples", dict_return=False) + assert isinstance(df, pd.DataFrame) + assert not df.empty + # confirmed columns from the flattened /v4/locations/samples response + for expected_col in ("sampleName", "sampleId", "sampleLat", "sampleLon"): + assert expected_col in df.columns + + +def test_navdat_citations_table_filtered_by_authors(backend): + """ + Test that the citations table is populated and reflects the authors + filter (confirmed live: authors=Walker returns Walker-authored records). + """ + table_names = list(backend._cache.keys()) + if "citations" not in table_names: + pytest.skip("The query returned no citation records") + + citations = backend.get_table("citations", dict_return=False) + assert isinstance(citations, pd.DataFrame) + assert not citations.empty + assert "citationAuthors" in citations.columns + # every returned citation's author string should mention 'walker' + # (case-insensitive; API returns lowercase full names, e.g. "walker, d.") + assert citations["citationAuthors"].str.lower().str.contains("walker").all() + + +# ============================================================================= +# 3) Query Operations +# ============================================================================= + +def test_navdat_query_artifacts_not_supported(backend): + """Test that query_artifacts raises NotImplementedError.""" + with pytest.raises(NotImplementedError): + backend.query_artifacts("SELECT * FROM samples") + + +def test_navdat_get_table(backend): + """Test getting table data as DataFrame or OrderedDict.""" + df = backend.get_table("samples", dict_return=False) + assert isinstance(df, pd.DataFrame) + + dict_data = backend.get_table("samples", dict_return=True) + assert isinstance(dict_data, OrderedDict) + + with pytest.raises(ValueError): + backend.get_table("nonexistent_table") + + +def test_navdat_get_schema(backend): + """Test get_schema returns SQL-style CREATE TABLE format.""" + schema = backend.get_schema() + assert isinstance(schema, str) + assert "CREATE TABLE" in schema + assert "samples" in schema + + samples_schema = backend.get_schema("samples") + assert "CREATE TABLE samples" in samples_schema + + +# ============================================================================= +# 4) Find Operations +# ============================================================================= + +def test_navdat_find_methods(backend): + """Test all find methods work correctly.""" + results = backend.find("walker") + assert isinstance(results, list) + + tables = backend.find_table("samples") + assert isinstance(tables, list) + assert any("samples" in table.t_name for table in tables) + + columns = backend.find_column("sample") + assert isinstance(columns, list) + + cells = backend.find_cell("walker") + assert isinstance(cells, list) + assert all(cell.type == "row" for cell in cells) + +def test_navdat_find_column_with_range(backend): + """Test find_column range support for numeric columns.""" + matches = backend.find_column("sampleLat", range=True) + + assert isinstance(matches, list) + + if not matches: + pytest.skip("No sampleLat column found in loaded NAVDAT results") + + match = next(item for item in matches if "sampleLat" in item.c_name) + + assert match.type == "column" + assert isinstance(match.value, dict) + assert "min" in match.value + assert "max" in match.value + +def test_navdat_find_cell_cell_type_support(backend): + """Test find_cell(row=False) returns individual cell matches.""" + cells = backend.find_cell("walker", row=False) + + assert isinstance(cells, list) + + if not cells: + pytest.skip("Loaded NAVDAT results did not contain a walker cell match") + + assert all(cell.type == "cell" for cell in cells) + assert all(len(cell.c_name) == 1 for cell in cells) + +def test_navdat_find_relation_publication_year(backend): + """Test find_relation against a known numeric column, if citations exist.""" + if "citations" not in backend._cache: + pytest.skip("No citations table to test find_relation against") + + result = backend.find_relation("citationPublicationYear", "> 1900") + assert isinstance(result, list) + + +# ============================================================================= +# 5) Display & Summary +# ============================================================================= + +def test_navdat_list(backend): + """Test list method returns table names.""" + table_names = backend.list(collection=True) + assert isinstance(table_names, list) + assert "samples" in table_names + + +def test_navdat_summary(backend): + """Test summary returns proper format with SQL-style types.""" + summary_list = backend.summary() + assert isinstance(summary_list, list) + assert len(summary_list) >= 2 + + for dataframe in summary_list[1:]: + assert isinstance(dataframe, pd.DataFrame) + assert "column" in dataframe.columns + assert "type" in dataframe.columns + + summary_single = backend.summary("samples") + assert isinstance(summary_single, pd.DataFrame) + + +def test_navdat_display(backend): + """Test display method returns a DataFrame.""" + result = backend.display("samples", num_rows=5) + + assert isinstance(result, pd.DataFrame) + assert len(result) <= 5 + +def test_navdat_display_cols(backend): + """Test display with selected columns.""" + result = backend.display( + "samples", + num_rows=5, + display_cols=["sampleName", "sampleId"], + ) + + assert isinstance(result, pd.DataFrame) + assert list(result.columns) == ["sampleName", "sampleId"] + + +def test_navdat_display_requires_table_name(backend): + """Test display requires table_name.""" + with pytest.raises(TypeError): + backend.display() + + +# ============================================================================= +# 6) Filtering and Multiple Queries +# ============================================================================= + +def test_navdat_filters(filtered_backend): + """Test loading data from the configured filtered queries.""" + assert filtered_backend._loaded is True + assert "samples" in filtered_backend._cache + + +def test_navdat_multiple_queries_deduplicate(filtered_backend): + """Test that identical rows returned by overlapping queries are deduplicated.""" + samples = filtered_backend._cache.get("samples", {}) + if not samples: + pytest.skip("The queries returned no sample rows") + + df = pd.DataFrame(samples) + assert len(df) == len(df.drop_duplicates()) + + +# ============================================================================= +# 7) Read-Only Enforcement +# ============================================================================= + +def test_navdat_read_only(backend): + """Test that write operations raise NotImplementedError.""" + with pytest.raises(NotImplementedError): + backend.ingest_artifacts({}) + + +# ============================================================================= +# 8) Lifecycle +# ============================================================================= + +def test_navdat_close(): + """ + Test that close() properly resets backend state. + + This test must use its own instance because close() destructively + mutates the backend and would invalidate a shared fixture. + """ + backend = NAVDAT( + params={ + "sampleNames": "(2.151) 12.40-12.85", + "size": 1, + } + ) + assert backend._loaded is True + assert len(backend._cache) > 0 + + backend.close() + + assert backend._loaded is False + assert len(backend._cache) == 0 + + +# ============================================================================= +# 9) Edge Cases +# ============================================================================= + +def test_navdat_empty_results(empty_backend): + """Test handling of queries that return no results (confirmed live: 0 rows).""" + samples = empty_backend._cache.get("samples", {}) + if samples: + first_col = next(iter(samples.values())) + assert len(first_col) == 0 \ No newline at end of file diff --git a/dsi/core.py b/dsi/core.py index 58404584..747323fb 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -27,7 +27,7 @@ class Terminal: for more information. """ BACKEND_PREFIX = ['dsi.backends'] - BACKEND_IMPLEMENTATIONS = ['gufi', 'sqlite', 'duckdb', 'hpss', 'ndp', 'osti', 'oceans11', 'rcsbpdb', 'zenodo', 'denodo'] + BACKEND_IMPLEMENTATIONS = ['gufi', 'sqlite', 'duckdb', 'hpss', 'ndp', 'osti', 'oceans11', 'rcsbpdb', 'zenodo', 'navdat', 'denodo'] PLUGIN_PREFIX = ['dsi.plugins'] PLUGIN_IMPLEMENTATIONS = ['env', 'file_reader', 'file_writer', 'collection_reader', 'vtk_reader'] VALID_ENV = ['Hostname', 'SystemKernel', 'GitInfo'] @@ -36,7 +36,7 @@ class Terminal: VALID_DATACARDS = ['DublinCoreDatacard', 'SchemaOrgDatacard', 'GoogleDatacard', 'GenesisDatacard'] VALID_WRITERS = ['ER_Diagram', 'Table_Plot', 'Csv_Writer', 'Parquet_Writer'] VALID_PLUGINS = VALID_ENV + VALID_READERS + VALID_WRITERS + VALID_DATACARDS - VALID_BACKENDS = ['Gufi', 'Sqlite', 'DuckDB', 'SqlAlchemy', 'HPSS', 'NDP', 'OSTI', 'Oceans11', 'RCSBPDB', 'Zenodo', 'Denodo'] + VALID_BACKENDS = ['Gufi', 'Sqlite', 'DuckDB', 'SqlAlchemy', 'HPSS', 'NDP', 'OSTI', 'Oceans11', 'RCSBPDB', 'Zenodo', 'NAVDAT', 'Denodo'] VALID_MODULES = VALID_PLUGINS + VALID_BACKENDS VALID_MODULE_FUNCTIONS = {'plugin': ['reader', 'writer'], 'backend': ['back-read', 'back-write']} diff --git a/dsi/dsi.py b/dsi/dsi.py index 40443e18..d70437de 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -5,6 +5,7 @@ from dsi.backends.oceans11 import Oceans11 from dsi.backends.zenodo import Zenodo from dsi.backends.denodo import Denodo +from dsi.backends.navdat import NAVDAT from collections import OrderedDict import numpy as np import pandas as pd @@ -52,11 +53,12 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) - If backend_name = "Oceans11" → No filename input (read-only backend) - If backend_name = "Zenodo" → No filename input (read-only backend) - If backend_name = "Denodo" → No filename input (read-only backend) + - If backend_name = "NAVDAT" → No filename input (read-only backend) `backend_name` : str, optional, default is "Sqlite". Name of the backend to activate. - If using a DSI-supported backend, must be either "Sqlite", "DuckDB", "NDP", "OSTI", "Oceans11", "Zenodo", "Denodo" or "RCSBPDB". + If using a DSI-supported backend, must be either "Sqlite", "DuckDB", "NDP", "OSTI", "Oceans11", "Zenodo", "Denodo", "NAVDAT" or "RCSBPDB". If using an external backend, provide the relative path to the Python module with the backend. """ @@ -131,7 +133,7 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) if backend_name.lower() == "duckdb": raise RuntimeError("To use the DuckDB backend, download the DuckDB package with 'pip install duckdb'") raise RuntimeError("Please check the 'backend_name' argument as it is not supported by DSI\n" - "Eligible backend_names are: Sqlite, DuckDB, NDP, OSTI, Oceans11, RCSBPDB, Zenodo") + "Eligible backend_names are: Sqlite, DuckDB, NDP, OSTI, Oceans11, RCSBPDB, Zenodo, NAVDAT") backend_class = next(cls for name, cls in inspect.getmembers(backend_module, inspect.isclass) if cls.__module__ == backend_module.__name__ and cls.__name__.lower() == backend_name.lower()) @@ -178,8 +180,11 @@ def __init__(self, filename = ".temp_dsi.db", backend_name = "Sqlite", **kwargs) elif backend_name.lower() == "denodo": backend_name = "Denodo" query_params = kwargs.pop("params", {}) + elif backend_name.lower() == "navdat": + backend_name = "NAVDAT" + query_params = kwargs.pop("params", {}) else: - raise NotImplementedError("The currently supported read-only backends are NDP, OSTI, RCSBPDB, Zenodo, Denodo and Oceans11") + raise NotImplementedError("The currently supported read-only backends are Oceans11, OSTI, Zenodo, NDP, Denodo, RCSBPDB and NAVDAT") try: # Pass query params as 'params' argument @@ -268,6 +273,9 @@ def list_backends(self): n = Zenodo(only_validate=True) if n.validate_connection(): print("Zenodo : Read-only metadata backend for discovering and querying public Zenodo records.") + n = NAVDAT(only_validate=True) + if n.validate_connection(): + print("NAVDAT : Read-only metadata backend for discovering and querying NAVDAT/federated PetDB geochemistry data.") print() @@ -835,8 +843,9 @@ def search(self, query, collection = False): self.t.table_print_helper(search_result.columns.tolist(), search_result.values.tolist(), search_result.shape[0]) print() else: - msg = f"Return object for search() is a list of {len(search_result)} pandas.DataFrame" - logger.log(logging.INFO, msg) if isinstance(search_result, list) and self.silence_messages else print(msg) + if isinstance(search_result, list): + msg = f"Return object for search() is a list of {len(search_result)} pandas.DataFrame" + logger.log(logging.INFO, msg) if self.silence_messages else print(msg) return search_result else: if find_cell is None and find_col is None and find_table is None: @@ -1280,7 +1289,7 @@ def summary(self, table_name = None, collection = False): def num_tables(self, **kwargs): """ - Prints the number of tables in the active backend. + Prints and returns the number of tables in the active backend. """ if self.schema_read: raise RuntimeError("ERROR: Cannot call num_tables() until all associated data is loaded after a complex schema") diff --git a/examples/backends/navdat/1.load_basic.py b/examples/backends/navdat/1.load_basic.py new file mode 100644 index 00000000..ba535146 --- /dev/null +++ b/examples/backends/navdat/1.load_basic.py @@ -0,0 +1,29 @@ +# examples/navdat/1.load_basic.py +""" +Basic NAVDAT search using an author filter. +""" + +from dsi.dsi import DSI + + +def main(): + # Initialize NAVDAT backend with a basic author search. + # `authors` is confirmed (2026-08-24) to filter both the `samples` and + # `citations` tables. `size` controls the samples page size (single + # page only - see README "Known Limitations"). + dsi = DSI( + backend_name="NAVDAT", + params={"authors": "Walker", "size": 10} + ) + + print("\nTable List:") + dsi.list() + + print("\nTable Summary:") + dsi.summary() + + dsi.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/backends/navdat/2.display_tables.py b/examples/backends/navdat/2.display_tables.py new file mode 100644 index 00000000..79d633ff --- /dev/null +++ b/examples/backends/navdat/2.display_tables.py @@ -0,0 +1,58 @@ +# examples/navdat/2.display_tables.py +""" +Using display() to view samples and citations data with various column +selections. +""" + +from dsi.dsi import DSI + + +def main(): + # Initialize with a sample-name search + dsi = DSI( + backend_name="NAVDAT", + params={"sampleNames": "(2.151) 12.40-12.85", "size": 5} + ) + + # Display samples with all columns (default) + print("\nSamples Table (All Columns - Default):") + dsi.display('samples', num_rows=5) + + # Display samples with minimal selection + print("\nSamples (Minimal Selection):") + dsi.display('samples', num_rows=5, + display_cols=['sampleName', 'sampleId', 'sampleLat', 'sampleLon']) + + # Display samples with extended metadata + print("\nSamples (Extended Metadata):") + dsi.display('samples', num_rows=5, + display_cols=['sampleName', 'sampleId', 'rootParent', + 'groupDocCount', 'sampleDocCount']) + + dsi.close() + + # Separate query, since citations are best explored with an authors search + dsi = DSI( + backend_name="NAVDAT", + params={"authors": "Walker"} + ) + + if "citations" in dsi.list(collection=True): + print("\nCitations Table (All Columns - Default):") + dsi.display('citations', num_rows=3) + + print("\nCitations (Minimal Selection):") + dsi.display('citations', num_rows=3, + display_cols=['citationTitle', 'citationAuthors', + 'citationPublicationYear']) + + print("\nCitations (Extended Metadata):") + dsi.display('citations', num_rows=3, + display_cols=['citationTitle', 'citationContainerTitle', + 'citationDOIs', 'analysisType', 'methods']) + + dsi.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/backends/navdat/3.find_basic.py b/examples/backends/navdat/3.find_basic.py new file mode 100644 index 00000000..e550f377 --- /dev/null +++ b/examples/backends/navdat/3.find_basic.py @@ -0,0 +1,48 @@ +# examples/navdat/3.find_basic.py +""" +Basic find operations on NAVDAT citation and sample data. +Demonstrates practical string-based and numeric queries. +""" + +from dsi.dsi import DSI + + +def main(): + # Initialize NAVDAT backend with an author search (populates citations) + dsi = DSI( + backend_name="NAVDAT", + params={"authors": "Hekinian"} + ) + + print("\nData Summary:") + dsi.summary() + + # Numeric comparison - citations published after 1990 + print("\nQuery: citationPublicationYear > 1990") + dsi.find("citationPublicationYear > 1990") + + # Same query but return DataFrame + print("\nSame query with collection=True:") + results = dsi.find("citationPublicationYear > 1990", collection=True) + if results is not None and len(results) > 0: + print(f"Returned DataFrame with {len(results)} rows and {len(results.columns)} columns") + + # Partial string match - journals containing 'PETROL' + print("\nQuery: citationContainerTitle ~~ 'PETROL'") + results = dsi.find("citationContainerTitle ~~ 'PETROL'", collection=True) + if results is not None and len(results) > 0: + print(f"\nFound {len(results)} matches:") + print(results[['citationTitle', 'citationContainerTitle', 'citationPublicationYear']].head()) + + # Find citations using a specific analysis method + print("\nQuery: methods ~~ 'MICROPROBE'") + results = dsi.find("methods ~~ 'MICROPROBE'", collection=True) + if results is not None and len(results) > 0: + print(f"\nFound {len(results)} citations using microprobe methods") + print(results[['citationTitle', 'methods']].head()) + + dsi.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/backends/navdat/4.search_tables.py b/examples/backends/navdat/4.search_tables.py new file mode 100644 index 00000000..f5df4894 --- /dev/null +++ b/examples/backends/navdat/4.search_tables.py @@ -0,0 +1,53 @@ +# examples/navdat/4.search_tables.py +""" +Using search() to find values across all tables in NAVDAT. +""" + +from dsi.dsi import DSI + + +def main(): + search_term = "walker" + + # Initialize NAVDAT backend + dsi = DSI( + backend_name="NAVDAT", + params={"authors": "Hekinian"} + ) + + print(f"\n=== Searching for '{search_term}' ===\n") + + # Get results as collection for analysis + results = dsi.search(search_term, collection=True) + + if not results: + print(f"No matches found for '{search_term}'") + dsi.close() + return + + print(f"Found matches in {len(results)} table(s)\n") + + for result_df in results: + # Detect which table this is from + if 'citationTitle' in result_df.columns: + print("Citations Table:") + essential_cols = ['citationTitle', 'citationAuthors', 'citationPublicationYear'] + available_cols = [col for col in essential_cols if col in result_df.columns] + display_df = result_df[available_cols].drop_duplicates() + print(display_df.to_string(index=False)) + elif 'sampleName' in result_df.columns: + print("Samples Table:") + essential_cols = ['sampleName', 'sampleId', 'sampleLat', 'sampleLon'] + available_cols = [col for col in essential_cols if col in result_df.columns] + display_df = result_df[available_cols].drop_duplicates() + print(display_df.to_string(index=False)) + else: + print("Match (table/column-level):") + print(result_df) + print() + + dsi.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/backends/navdat/5.explore_metadata.py b/examples/backends/navdat/5.explore_metadata.py new file mode 100644 index 00000000..68426e09 --- /dev/null +++ b/examples/backends/navdat/5.explore_metadata.py @@ -0,0 +1,57 @@ +# examples/navdat/5.explore_metadata.py +""" +Explore available journals, analysis types, and publication years from +loaded citation data. Useful for discovering valid filter/context values +before making more targeted queries. + +NOTE: the PetDB v4 API documents standalone vocabulary endpoints (e.g. +GET /v4/journals, GET /v4/analysisTypes) that would normally be the "right" +way to do this without first loading a sample dataset. As of 2026-08-24, +GET /v4/authors returned an empty body in live testing and was not further +investigated - see README "Known Limitations". This example works around +that by exploring values from an already-loaded query instead, which is +confirmed to work. +""" + +from dsi.dsi import DSI + + +def main(): + print("Loading sample citation data...") + dsi = DSI( + backend_name="NAVDAT", + params={"authors": "Hekinian"} + ) + + if "citations" not in dsi.list(collection=True): + print("No citations table loaded - try a broader author search.") + dsi.close() + return + + citations_df = dsi.get_table("citations", collection=True) + + print("\n=== Journals (citationContainerTitle) ===") + print(citations_df['citationContainerTitle'].value_counts()) + + print("\n=== Publication Years ===") + years = citations_df['citationPublicationYear'].dropna() + if not years.empty: + print(f"Range: {int(years.min())} - {int(years.max())}") + print(years.value_counts().sort_index()) + + print("\n=== Analysis Types ===") + print(citations_df['analysisType'].value_counts()) + + print("\n=== Analytical Methods ===") + all_methods = [] + for method_str in citations_df['methods']: + if method_str: + all_methods.extend(m.strip() for m in method_str.split(',')) + unique_methods = sorted(set(all_methods)) + print(f"Found {len(unique_methods)} unique methods: {unique_methods}") + + dsi.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/backends/navdat/6.write_process.py b/examples/backends/navdat/6.write_process.py new file mode 100644 index 00000000..ecabacbe --- /dev/null +++ b/examples/backends/navdat/6.write_process.py @@ -0,0 +1,56 @@ +# examples/navdat/6.write_and_process.py +""" +Export NAVDAT data to CSV and convert to a local SQLite database for +offline analysis. +""" + +from dsi.dsi import DSI + + +def main(): + # Query citation data from NAVDAT (read-only) + dsi = DSI( + backend_name="NAVDAT", + params={"authors": "Walker"} + ) + + if "citations" not in dsi.list(collection=True): + print("No citations table loaded - try a broader author search.") + dsi.close() + return + + # Export citations to CSV + dsi.write( + filename="navdat_citations.csv", + writer_name="Csv", + table_name="citations" + ) + print("Exported citations to navdat_citations.csv") + + # Convert read-only NAVDAT data to a writable Sqlite backend + dsi.process( + backend_name="Sqlite", + filename="navdat_walker.db" + ) + dsi.close() + print("Processed NAVDAT data into navdat_walker.db") + + # Load the newly created local database + local_dsi = DSI( + backend_name="Sqlite", + filename="navdat_walker.db" + ) + + # Query the local database with real SQL (not supported directly on + # the read-only NAVDAT backend) + print("\nQuerying local SQLite copy:") + local_dsi.query( + "SELECT citationTitle, citationPublicationYear FROM citations " + "ORDER BY citationPublicationYear DESC" + ) + + local_dsi.close() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/backends/navdat/README.md b/examples/backends/navdat/README.md new file mode 100644 index 00000000..fc0d10fe --- /dev/null +++ b/examples/backends/navdat/README.md @@ -0,0 +1,284 @@ +# NAVDAT Backend for DSI + +A read-only backend for accessing NAVDAT and federated geochemistry data via the live PetDB v4 API. Metadata is retrieved and exposed as DSI-compatible tables: `samples` and `citations`. + +NAVDAT (navdat.org) was a web-accessible repository for age, chemical, and isotopic data from igneous rocks in western North America. **navdat.org itself has been static since 2014 and has no API.** Its data is now federated into PetDB 2.0 / EarthChem Synthesis alongside PetDB, GEOROC, SedDB, USGS, MetPetDB, and GANSEKI, and served live through the PetDB v4 API. This backend queries that live API rather than navdat.org directly. + +Useful resources: + +- [EarthChem / PetDB 2.0](https://earthchem.org/petdb): The current search interface for the federated data this backend queries. +- [PetDB API docs](https://earthchem.github.io/ec-doc/documentation/petdb_api.html): Documented endpoint/parameter reference (see "Known Limitations" below for what's confirmed vs. documented-only). +- [NAVDAT (legacy, static since 2014)](https://www.navdat.org/): Original site; data now lives in the federated system above. + +> **Note:** This backend is read-only. It retrieves and organizes metadata but does not modify remote data. + +
+API Reference (for developers) + +The backend uses the live PetDB v4 API: + +- Base URL: `https://api.earthchem.org` +- Connection check: `GET /v4/metrics` +- Samples: `GET /v4/locations/samples` +- Citations: `GET /v4/citations` +- Response envelope: `{"status": "success", "data": ...}` + +An earlier version of this backend targeted the legacy EarthChem Portal REST Search Service (`portal.earthchem.org/restsearchservice`), based on archived documentation. Live verification (2026-08-24) found that service now returns `404 Not Found` — it appears to have been decommissioned as part of the PetDB 2.0 migration (the original PetDB Search was announced deprecated as of Dec 31, 2025). See `navdat.py`'s module docstring for the full diagnosis trail. + +
+ +--- + +## Quick Start + +### Initialize the Backend + +```python +from dsi.dsi import DSI + +dsi = DSI( + backend_name="NAVDAT", + params={"authors": "Walker", "size": 10} +) +``` + +### List Available Tables + +```python +dsi.list() +``` + +### Access a Table + +```python +samples_df = dsi.get_table("samples", collection=True) +print(samples_df) +``` + +### Close the Backend + +```python +dsi.close() +``` + +--- + +## Supported Search Parameters + +**Confirmed working (live-tested, 2026-08-24):** + +| Parameter | Type | Applies to | Notes | +|-----------|------|------------|-------| +| `authors` | str | `samples` AND `citations` | Confirmed to filter both: totalCount for `/v4/locations/samples` drops from 149,778 (unfiltered) to 1,558 with `authors=Walker` | +| `sampleNames` | str | `samples` | Literal sample name, NOT a rock-type keyword. `sampleNames=basalt` returns 0 rows — sample names look like `"(2.151) 12.40-12.85"`, not rock types | +| `size` | int | `samples` only | Page size; single page only (see Known Limitations) | + +**Documented, passed through, but NOT yet confirmed live** — use with caution, and verify with a `curl` before relying on them: + +`citationTitles`, `journals`, `publicationYears`, `laboratories`, `dataSources`, `expeditions`, `analysisTypes`, `geoFeatures`, `taxons`, `variables`, `boundingBox`, `polygons`, `precision` + +The last four location/advanced filters use a documented `group::[value]` syntax (e.g. `taxons=igneous::[basalt]`) that has not been live-verified at all — this is the most likely place to find working rock-type filtering, but treat it as unproven until tested. + +### Example: Author Search + +```python +dsi = DSI( + backend_name="NAVDAT", + params={"authors": "Hekinian"} +) +``` + +### Example: Sample Name Lookup + +```python +dsi = DSI( + backend_name="NAVDAT", + params={"sampleNames": "(2.151) 12.40-12.85"} +) +``` + +### Multiple Independent Queries + +Like other DSI webserver backends, `params` can be a list of dicts (OR logic, deduplicated): + +```python +dsi = DSI( + backend_name="NAVDAT", + params=[ + {"authors": "Walker", "size": 5}, + {"authors": "Hekinian", "size": 5}, + ] +) +``` + +--- + +## Tables + +The backend returns up to two DSI tables: + +1. **samples** — flattened sample/location metadata +2. **citations** — flattened citation/dataset/method metadata (only populated when the query returns citation matches) + +**Important: `samples` and `citations` are NOT linked to each other.** No shared key was found in either endpoint's confirmed response shape (unlike, e.g., NDP's clean `datasets.id` → `resources.dataset_id` relationship). You cannot currently join "which samples came from which publication" through this backend. See Known Limitations. + +--- + +### samples Table + +One row per individual sample, flattened from `/v4/locations/samples`'s grouped/clustered response (each location group can contain multiple nested samples). + +| Column | Description | +|--------|-------------| +| rootParent | Name of the location cluster/group this sample belongs to | +| groupDocCount | Document count for the whole location group | +| sampleCount | Number of samples in this group | +| groupLat / groupLon | Group-level coordinates | +| sampleName | Individual sample name | +| sampleId | Unique sample identifier | +| sampleDocCount | Document count for this specific sample | +| sampleLon / sampleLat | Individual sample coordinates | + +**Example:** +```python +samples_df = dsi.get_table("samples", collection=True) +print(samples_df[["sampleName", "sampleLat", "sampleLon"]]) +``` + +--- + +### citations Table + +One row per citation/dataset record, flattened from `/v4/citations`. Nested arrays (`citationAuthors`, `methods`, `citationIdentifiers`) are flattened into joined-string columns. + +| Column | Description | +|--------|-------------| +| citationId | Unique citation identifier | +| citationTitle | Citation article title | +| citationCode | Short citation code (e.g. `HEKINIAN, 1987`) | +| citationContainerTitle | Journal name | +| citationPublicationYear | Publication year | +| citationVolume / citationIssue / citationPages | Publication details | +| citationAuthors | Semicolon-joined author full names | +| citationDOIs | Comma-joined DOIs (from `citationIdentifiers` where `identifierType == "DOI"`) | +| datasetCode | Dataset code within the citation | +| datasetTitle | Dataset title | +| datasetNum | Dataset number | +| analysisType | e.g. `"Rock Analysis"`, `"Mineral Analysis"` | +| methods | Comma-joined analytical method names | + +**Example:** +```python +citations_df = dsi.get_table("citations", collection=True) +print(citations_df[["citationTitle", "citationContainerTitle", "citationPublicationYear"]]) +``` + +--- + +## Common DSI Operations + +### List Tables +```python +dsi.list() +``` + +### View Backend Summary +```python +dsi.summary() +``` + +### View Table Schema +```python +print(dsi.schema("samples")) +``` + +### Retrieve a Table +```python +samples_df = dsi.get_table("samples", collection=True) +citations_df = dsi.get_table("citations", collection=True) +``` + +### Filter Data +```python +dsi.find("citationPublicationYear > 1990") +dsi.find("citationContainerTitle ~~ 'PETROL'") +``` +Supports operators: `>`, `<`, `>=`, `<=`, `==`, `!=`, `~~` (contains) + +### Search Loaded Metadata +```python +dsi.search("walker") +``` + +### Display Table Preview +```python +dsi.display("samples", num_rows=5) +dsi.display("citations", num_rows=5, display_cols=["citationTitle", "citationAuthors"]) +``` + +### Process to Writable Backend +```python +dsi.process(backend_name="Sqlite", filename="navdat_local.db") +``` + +### Export Data +```python +dsi.write(filename="citations.csv", writer_name="Csv", table_name="citations") +``` + +--- + +## Example Scripts + +All scripts are in `examples/navdat/`. + +### 1. load_basic.py +Initialize the backend with an author search and view available tables. + +### 2. display_tables.py +Preview samples and citations with different column selections. + +### 3. find_basic.py +Filter citations by publication year, journal name, and analytical method using `find()`. + +### 4. search_tables.py +Search across all loaded tables for a term with `search()`. + +### 5. explore_metadata.py +Discover available journals, publication years, and analysis methods from loaded citation data. (Note: this works around a documented-but-unverified `/v4/authors` vocabulary endpoint that returned an empty body in testing — see Known Limitations.) + +### 6. write_and_process.py +Export citations to CSV, and process the full loaded dataset into a local SQLite database for offline SQL querying. + +--- + +## Known Limitations + +Being explicit about these rather than letting the examples imply capabilities that don't exist: + +- **No ID-based single-record lookup.** Unlike NDP's `id` param, there's currently no way to fetch one specific sample or citation directly by ID (`/v4/samples/:id` and `/v4/citations/:id` are documented but not wired into this backend yet). +- **No pagination beyond one page.** `/v4/locations/samples` accepts `size` (confirmed working) but no confirmed offset/page parameter has been tested. `/v4/citations` returns an `afterKey` cursor whose reuse as a request parameter is inferred from its shape, not confirmed live. Large queries will silently return only the first page. +- **`samples` and `citations` are not linked.** No shared key was found between them in the confirmed response shapes. +- **Most documented search props beyond `authors`/`sampleNames` are unverified.** In particular, rock-type/geochemistry-style filtering (`taxons`, `analysisTypes`, `geoFeatures`) — arguably the most important filter category for NAVDAT's actual domain — uses a documented `group::[value]` syntax that has never been tested against the live API. +- **`GET /v4/authors` returned an empty response body** in testing (2026-08-24) and wasn't investigated further; other vocabulary endpoints (`/v4/journals`, `/v4/laboratories`, etc.) are unverified. + +--- + +## Troubleshooting + +### Corporate TLS-inspecting proxies (e.g. Zscaler) + +If `validate_connection()` fails with a generic connection/network error, but the same URL works fine in `curl` or a browser, this is very likely a masked SSL certificate verification error rather than an actual network problem — `requests` verifies against its own bundled `certifi` CA list, not the OS-native trust store, so it won't automatically trust a corporate proxy's intercepting root CA even when curl does (curl uses the OS trust store). + +**Confirmed fix:** install [`truststore`](https://pypi.org/project/truststore/) (Python 3.10+) and inject it before any `requests` calls: +```python +import truststore +truststore.inject_into_ssl() +``` +This is applied in `examples/navdat/` scripts' underlying test suite via `dsi/backends/tests/test_navdat.py`, scoped to test runs rather than hardcoded into `navdat.py` itself, since it changes SSL trust behavior for the whole process. See `navdat.py`'s module docstring for the full diagnosis trail — the first two things that looked like fixes (`REQUESTS_CA_BUNDLE`/`SSL_CERT_FILE` env vars) turned out not to work, confirmed via a clean isolated A/B test. + +### Empty Results +```python +dsi.list() # Check if tables exist at all +``` +Try broadening or removing filters, or double-check `sampleNames` isn't being used with a rock-type keyword (it needs a literal sample name). diff --git a/pyproject.toml b/pyproject.toml index b0c71ca6..3585ba9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ name="dsi-workflow" license= "BSD-3-Clause" description="A Data Science Infrastructure Project" readme = "README.rst" +requires-python = ">=3.11" authors=[ {name = 'Jesus Pulido', email = 'pulido@lanl.gov'}, {name = 'James Ahrens', email = 'ahrens@lanl.gov'}, diff --git a/requirements.extras.txt b/requirements.extras.txt index 2b577ed9..46f3df11 100644 --- a/requirements.extras.txt +++ b/requirements.extras.txt @@ -13,3 +13,4 @@ boto3>=1.42.91 coolname>=4.2.0 sqlglot>=30.0.0 pyxattr>=0.8.1; sys_platform != 'win32' +truststore>=0.9 \ No newline at end of file diff --git a/tools/navdat_geoscience_demo/NAVDAT_User_Story.ipynb b/tools/navdat_geoscience_demo/NAVDAT_User_Story.ipynb new file mode 100644 index 00000000..6a87e39b --- /dev/null +++ b/tools/navdat_geoscience_demo/NAVDAT_User_Story.ipynb @@ -0,0 +1,1725 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ad95e05e", + "metadata": {}, + "source": [ + "# Exploring Warren-Associated Geoscience Metadata with DSI and NAVDAT\n", + "\n", + "## User Story\n", + "\n", + "As a geoscientist evaluating federated EarthChem metadata, I want to retrieve\n", + "sample and citation records matching the author name `Warren`, examine their\n", + "geographic distribution, and summarize their analytical methods so that I can\n", + "understand the scope of records returned through the DSI NAVDAT backend.\n", + "\n", + "## Scientific Question\n", + "\n", + "What geographic regions, publications, analysis types, and analytical methods\n", + "are represented in PetDB records returned for the author query `Warren`?\n", + "\n", + "## Overall Story\n", + "\n", + "This notebook uses the DSI NAVDAT backend to query the EarthChem PetDB v4 API.\n", + "DSI converts the nested API responses into two tables:\n", + "\n", + "- `samples` — sample identifiers, location groups, coordinates, and document\n", + " counts.\n", + "- `citations` — publication, dataset, DOI, analysis-type, and method metadata.\n", + "\n", + "The notebook maps the returned samples, identifies the largest location\n", + "groups, and summarizes the citation and analytical-method metadata.\n", + "\n", + "Because PetDB is a federated system, the results are not necessarily limited\n", + "to records from the original legacy NAVDAT collection." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f7fba6d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using the macOS system certificate store.\n" + ] + } + ], + "source": [ + "# Configure Python to use the macOS system certificate store.\n", + "\n", + "import ssl\n", + "\n", + "try:\n", + " import truststore\n", + "\n", + " if not getattr(ssl, \"_navdat_truststore_active\", False):\n", + " truststore.inject_into_ssl()\n", + " ssl._navdat_truststore_active = True\n", + "\n", + " print(\"Using the macOS system certificate store.\")\n", + "except ImportError:\n", + " print(\n", + " \"truststore is not installed. \"\n", + " \"Python's default certificate store will be used.\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8b664b6c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Python environment: /Users/rsankararaman/Navdat_new/.venv/bin/python\n", + "Matplotlib backend: module://matplotlib_inline.backend_inline\n" + ] + } + ], + "source": [ + "# Import the packages used for retrieval, analysis, and visualization.\n", + "\n", + "import sys\n", + "from io import BytesIO\n", + "\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "from IPython.display import Image, display\n", + "\n", + "from dsi.dsi import DSI\n", + "\n", + "pd.set_option(\"display.max_columns\", 50)\n", + "pd.set_option(\"display.max_colwidth\", 120)\n", + "\n", + "\n", + "def display_figure(fig):\n", + " \"\"\"Render a Matplotlib figure directly inside the notebook.\"\"\"\n", + " image_buffer = BytesIO()\n", + "\n", + " fig.savefig(\n", + " image_buffer,\n", + " format=\"png\",\n", + " dpi=120,\n", + " bbox_inches=\"tight\",\n", + " )\n", + "\n", + " image_buffer.seek(0)\n", + " display(Image(data=image_buffer.getvalue()))\n", + " plt.close(fig)\n", + "\n", + "\n", + "print(\"Python environment:\", sys.executable)\n", + "print(\"Matplotlib backend:\", matplotlib.get_backend())" + ] + }, + { + "cell_type": "markdown", + "id": "7ce5afd0", + "metadata": {}, + "source": [ + "## 1. Define the Author Query\n", + "\n", + "The query uses `Warren` as an author search term and requests 100 sample\n", + "location groups.\n", + "\n", + "The author filter is surname-based and may return records associated with\n", + "different researchers named Warren. Pagination is not currently implemented,\n", + "so this notebook analyzes the returned API page rather than the complete\n", + "PetDB collection." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "793ca053", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'authors': 'Warren', 'size': 100}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Define the author query and the requested page size.\n", + "\n", + "AUTHOR_QUERY = \"Warren\"\n", + "SAMPLE_PAGE_SIZE = 100\n", + "\n", + "query_params = {\n", + " \"authors\": AUTHOR_QUERY,\n", + " \"size\": SAMPLE_PAGE_SIZE,\n", + "}\n", + "\n", + "query_params" + ] + }, + { + "cell_type": "markdown", + "id": "bbb0fe34", + "metadata": {}, + "source": [ + "## 2. Retrieve the Metadata Through DSI\n", + "\n", + "The read-only NAVDAT backend queries the EarthChem PetDB v4 API and normalizes\n", + "the returned sample and citation metadata into in-memory DSI tables." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "83ddec68", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created an instance of DSI with the NAVDAT read-only backend\n", + "NAVDAT initialized for author query: Warren\n" + ] + } + ], + "source": [ + "# Initialize the NAVDAT backend with the Warren author query.\n", + "\n", + "dsi = DSI(\n", + " backend_name=\"NAVDAT\",\n", + " params=query_params,\n", + ")\n", + "\n", + "print(f\"NAVDAT initialized for author query: {AUTHOR_QUERY}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cb231ed8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Table: samples\n", + " - num of columns: 10\n", + " - num of rows: 321\n", + "\n", + "Table: citations\n", + " - num of columns: 15\n", + " - num of rows: 71\n", + "\n", + "\n" + ] + } + ], + "source": [ + "# List the tables populated by the NAVDAT backend.\n", + "\n", + "dsi.list()" + ] + }, + { + "cell_type": "markdown", + "id": "eb8eff2c", + "metadata": {}, + "source": [ + "### Normalized DSI tables\n", + "\n", + "The backend returned:\n", + "\n", + "- **samples** — individual samples flattened from grouped location responses.\n", + "- **citations** — citation, dataset, analysis-type, and method records.\n", + "\n", + "The tables come from the same author query, but the API does not provide a\n", + "confirmed key for joining an individual sample to a citation." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cb804684", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Storing all data for the table: samples as a collection\n", + "Sample rows: 321\n" + ] + } + ], + "source": [ + "# Retrieve the normalized samples table.\n", + "\n", + "samples = dsi.get_table(\n", + " \"samples\",\n", + " collection=True,\n", + ").copy()\n", + "\n", + "print(\"Sample rows:\", len(samples))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "05c0b59f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Storing all data for the table: citations as a collection\n", + "Citation/dataset rows: 71\n" + ] + } + ], + "source": [ + "# Retrieve the citations table when the query returns citation records.\n", + "\n", + "try:\n", + " citations = dsi.get_table(\n", + " \"citations\",\n", + " collection=True,\n", + " ).copy()\n", + "except (ValueError, KeyError, RuntimeError):\n", + " citations = pd.DataFrame()\n", + " print(\"The query did not return a citations table.\")\n", + "\n", + "print(\"Citation/dataset rows:\", len(citations))" + ] + }, + { + "cell_type": "markdown", + "id": "62680ac2", + "metadata": {}, + "source": [ + "## 3. Inspect the Retrieved Samples\n", + "\n", + "The samples table contains sample identifiers, PetDB location groups,\n", + "coordinates, and document counts. A focused view is used to show the fields\n", + "needed for the geographic analysis." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a84dffe1", + "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", + "
sampleIdsampleNamerootParentsampleLatsampleLonsampleDocCountgroupDocCountsampleCount
0235a4b01cca43ead1a2435ec19c04babA2107-6-60-4AII0107-6-53.41809.198482
1c50eba02bbfa2d21fb54ec0977e952d5A2107-6-61-83AII0107-6-53.41809.198482
24158345f3687fcb84c1795cbf3c2e638CIRCE97-W-OLARGCIRC-097-17.050066.867441
3222a86bb1133ab54e1f848877d3bec6fNJ88-A1ARGNOVA-088-20.3800-173.270291044
4b3a5a8d174275337ad689abe950c67e9NJ88-D1ARGNOVA-088-20.3800-173.270261044
56ba2e728c50327b52067a8030fa08bffNJ88-C1ARGNOVA-088-20.3800-173.270251044
69b60f4db48131e756a15137b80c9020bNJ88-F1ARGNOVA-088-20.3800-173.270241044
7d5355e263584a35e4343c071127b8d2dBMRG08-82-2-8BMRG08MV-082-25.8542-175.420431263
\n", + "
" + ], + "text/plain": [ + " sampleId sampleName rootParent sampleLat \\\n", + "0 235a4b01cca43ead1a2435ec19c04bab A2107-6-60-4 AII0107-6 -53.4180 \n", + "1 c50eba02bbfa2d21fb54ec0977e952d5 A2107-6-61-83 AII0107-6 -53.4180 \n", + "2 4158345f3687fcb84c1795cbf3c2e638 CIRCE97-W-OL ARGCIRC-097 -17.0500 \n", + "3 222a86bb1133ab54e1f848877d3bec6f NJ88-A1 ARGNOVA-088 -20.3800 \n", + "4 b3a5a8d174275337ad689abe950c67e9 NJ88-D1 ARGNOVA-088 -20.3800 \n", + "5 6ba2e728c50327b52067a8030fa08bff NJ88-C1 ARGNOVA-088 -20.3800 \n", + "6 9b60f4db48131e756a15137b80c9020b NJ88-F1 ARGNOVA-088 -20.3800 \n", + "7 d5355e263584a35e4343c071127b8d2d BMRG08-82-2-8 BMRG08MV-082 -25.8542 \n", + "\n", + " sampleLon sampleDocCount groupDocCount sampleCount \n", + "0 9.198 4 8 2 \n", + "1 9.198 4 8 2 \n", + "2 66.867 4 4 1 \n", + "3 -173.270 29 104 4 \n", + "4 -173.270 26 104 4 \n", + "5 -173.270 25 104 4 \n", + "6 -173.270 24 104 4 \n", + "7 -175.420 43 126 3 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Select the sample fields most relevant to this user story.\n", + "\n", + "preferred_sample_columns = [\n", + " \"sampleId\",\n", + " \"sampleName\",\n", + " \"rootParent\",\n", + " \"sampleLat\",\n", + " \"sampleLon\",\n", + " \"sampleDocCount\",\n", + " \"groupDocCount\",\n", + " \"sampleCount\",\n", + "]\n", + "\n", + "sample_columns = [\n", + " column\n", + " for column in preferred_sample_columns\n", + " if column in samples.columns\n", + "]\n", + "\n", + "display(samples[sample_columns].head(8))" + ] + }, + { + "cell_type": "markdown", + "id": "e59f2b1e", + "metadata": {}, + "source": [ + "## 4. Validate the Coordinates\n", + "\n", + "Latitude and longitude are converted to numeric values and checked against\n", + "valid geographic ranges before the samples are plotted." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a690bec9", + "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", + "
metricvalue
0Retrieved sample rows321
1Unique sample IDs321
2Valid coordinate rows321
3Location groups100
\n", + "
" + ], + "text/plain": [ + " metric value\n", + "0 Retrieved sample rows 321\n", + "1 Unique sample IDs 321\n", + "2 Valid coordinate rows 321\n", + "3 Location groups 100" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Prepare valid coordinates and summarize the retrieved sample data.\n", + "\n", + "samples_work = samples.copy()\n", + "\n", + "samples_work[\"latitude\"] = pd.to_numeric(\n", + " samples_work[\"sampleLat\"],\n", + " errors=\"coerce\",\n", + ")\n", + "\n", + "samples_work[\"longitude\"] = pd.to_numeric(\n", + " samples_work[\"sampleLon\"],\n", + " errors=\"coerce\",\n", + ")\n", + "\n", + "valid_coordinates = (\n", + " samples_work[\"latitude\"].between(-90, 90)\n", + " & samples_work[\"longitude\"].between(-180, 180)\n", + ")\n", + "\n", + "samples_geo = samples_work.loc[valid_coordinates].copy()\n", + "\n", + "sample_summary = pd.DataFrame(\n", + " {\n", + " \"metric\": [\n", + " \"Retrieved sample rows\",\n", + " \"Unique sample IDs\",\n", + " \"Valid coordinate rows\",\n", + " \"Location groups\",\n", + " ],\n", + " \"value\": [\n", + " len(samples_work),\n", + " samples_work[\"sampleId\"].nunique(),\n", + " len(samples_geo),\n", + " samples_work[\"rootParent\"].nunique(),\n", + " ],\n", + " }\n", + ")\n", + "\n", + "sample_summary" + ] + }, + { + "cell_type": "markdown", + "id": "184a346a", + "metadata": {}, + "source": [ + "## 5. Map the Retrieved Samples\n", + "\n", + "The coordinate plot shows the geographic distribution of samples returned by\n", + "the `Warren` author query. Point color represents the sample-level document\n", + "count.\n", + "\n", + "The result is a global distribution, demonstrating that a surname query can\n", + "retrieve records associated with several authors and geographic regions." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0484ef0d", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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Y81btmyZ6ozeGpO+cOFCdwe6adOm1rBhwwztUzO9K22+Ro0a4ZnjEXzeJIDKxFDWBUA9Au0RcjP+roF6FDzqSVfMkZSUlKsnnd68ebNt377dgqBs2bJWtWpVS2QJmetbrlw569q1a1zrKrD2zxKfUfrQdezY8ai3g5yhIL1x48acflCPkONoj0A9QlDQHmWeJk2aWG6nwDjRg+MgSajJ5AAAAAAAyO0I1AEAAAAACBACdQAAAAAAAoRAHXlahQoV7MEHH3TfAeoRchLtEahHCAraIyDnJeSs7wAAAAAA5Fb0qAMAAAAAECAE6gAAAAAABAiBOgAAAAAAAUKgDgAAAABAgBTM6QIA2WHLli326quv2ocffmglSpSw5557LuZ6999/v61YsSLF8po1a9q//vWvFMu3b99uzz77rH3zzTduuxdddJF17NgxS44BOW/z5s02Y8YMW7RokZUvX97Gjh2b6rrbtm2ziRMn2rJly6xUqVLWvn17a9eu3VGvi8Tz0Ucf2WOPPRbzd0OHDrUGDRqkWD5r1iybPXu27d6920488UTr06ePlStXLhtKi6Bav369+9u2evVqO/bYY61bt27WsmXLnC4WAig5OdmmTp0a83fjxo1L8SScw4cP2yuvvGLz5s2zAwcO2GmnnWa9e/d21z0Asg6BOhLerbfe6oL0Tp06uYBaQVFqPvjgA9u6das98sgjEctLly6dYt21a9fa2WefbdWqVbMbbrjBva5Hjx7WuXNnmzRpUpYcC3KOAqG3337bunTpYp999pm7cEnNqlWr7JxzzrH69eu7123cuNG6d+9uPXv2tKeeeirD6yIxrVu3zl577TUbPXq01a5dO+J3sR4dqXqii+Z77rnHqlevbhMmTHCBvgL+4447LhtLjqBYvHixnX/++daqVSvXfixfvtzOPfdcGzx4sN177705XTwEzMqVK12boxs7ujnsFx18Hzx40F0/ffLJJ67NOeaYY+zxxx93N6rV5vB4WyAL6fFsQCJbtWpV6NChQ+7/SUlJoXLlyqW6bosWLULNmzePa7sXXnhhqHr16qHdu3eHlyUnJ+txh6EZM2ZkQskRJCtXrgwdPnzY/f+0004L1apVK9V1zz777FC9evVC+/btCy+bOnWqqxtz5szJ8LpITFOmTHHv95dffpnmul4bM23atPCyvXv3hurUqRM699xz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O9OOA3/X5QEDBiT0PnPiGBFfG/LNN98EYjtB32eQjR8/3p566qmcLgaATESgDiDX+vrrr23ixIl2+umnW/Pmze2YY46xwYMH24knnmg7d+6Meztr1qxxF3zbtm2z3GT//v3u+Dds2BDX+gquFXw/99xz7nXRVqxY4Zbr/Ol8Fi1a1Pr3729nnnmmHThwIGKdvHIzJDvr8ksvvZQl2549e7bdc8892brPWLJ7fxk9L4lIn/1YN/W8NmT9+vVHtf3M2k7Q95nV78fRmDdvnr377ruZtj0AOY9AHUCud91117mLnkGDBtmsWbPsxx9/tBdeeCHu12/dutVd8KUnuM+NZsyYYQcPHrQbb7zRnZ/Dhw/HXO/qq6925/PBBx+0N954wz799FN7/vnns728eUm7du1s9OjRWbLtL7/80qZNm5at+4wlu/eX0fOSiHRjLT039dKrePHiNmHCBDv55JOzZPuJJqvfDwCJoWBOFwAAMlODBg0sX758KVK71dPw3nvv2Z49e6xZs2Z21VVXWcGCBW3dunU2fPhwt869997reuVLly5tY8aMsc8//9zGjRvnUgrz5//ffc2HH37Y9Tp36dLF/bxq1SoXgGj566+/7nqdzznnHCtSpIjbxv/93//ZK6+84spUt25dFwSXKFEiRe+Q1tH6KtfFF19sbdq0iVhn8+bNrjf8559/tuOOO866d++ernOjYLtjx4528803uxTJ999/384777wjvqZFixbuIvyrr76Kez/JycnuOHr37m2vvfaaO+4TTjjB3VDRtjwff/yxOx7Ruapdu7Y7plq1akVsTxezGtLwyy+/uOPWjQS9V7fffrs1btw47nO4fft2u/vuu91737p160CVf9OmTS5wvOKKKyLKkFbdSasMSjfXtn777Tf3WlGGhI4tep/eOZo8ebJ9//337rPQoUMHt370uYmnTkeL3p9uFP33v/+1xYsXWygUsjPOOMM6d+7sPr9pvTfx7P9Ix3Kk85Ia1a9XX33VPvvsM7cvfZbUlsjUqVPd51L1y99retttt9mdd95pDRs2jCi/hpboZpnOiW6cqXdf71mdOnXczbHff//dxo4d6+rxJ598Ym+++aZbpu306tUrfKxendY+vv32W1e2woULW9euXe3UU08Np4irrsl//vMfdwwSK01aQxP0Pnhtm0fHp3ZuyJAhMc+Nhscs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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Plot the geographic distribution of samples with valid coordinates.\n", + "\n", + "if samples_geo.empty:\n", + " print(\"No valid coordinates are available for mapping.\")\n", + "else:\n", + " if \"sampleDocCount\" in samples_geo.columns:\n", + " document_counts = pd.to_numeric(\n", + " samples_geo[\"sampleDocCount\"],\n", + " errors=\"coerce\",\n", + " ).fillna(0).clip(lower=0)\n", + " else:\n", + " document_counts = pd.Series(\n", + " 0,\n", + " index=samples_geo.index,\n", + " dtype=float,\n", + " )\n", + "\n", + " color_values = np.log1p(document_counts)\n", + "\n", + " fig, ax = plt.subplots(figsize=(9, 7))\n", + "\n", + " points = ax.scatter(\n", + " samples_geo[\"longitude\"],\n", + " samples_geo[\"latitude\"],\n", + " c=color_values,\n", + " s=50,\n", + " alpha=0.75,\n", + " cmap=\"viridis\",\n", + " edgecolor=\"white\",\n", + " linewidth=0.4,\n", + " )\n", + "\n", + " ax.set_title(\n", + " \"Geographic Distribution of Samples Returned for Warren\"\n", + " )\n", + " ax.set_xlabel(\"Longitude\")\n", + " ax.set_ylabel(\"Latitude\")\n", + " ax.grid(alpha=0.25)\n", + "\n", + " colorbar = fig.colorbar(points, ax=ax)\n", + " colorbar.set_label(\"log(1 + sample document count)\")\n", + "\n", + " fig.text(\n", + " 0.5,\n", + " 0.01,\n", + " \"Returned API page; pagination is not currently implemented.\",\n", + " ha=\"center\",\n", + " fontsize=9,\n", + " )\n", + "\n", + " plt.tight_layout(rect=[0, 0.04, 1, 1])\n", + " display_figure(fig)" + ] + }, + { + "cell_type": "markdown", + "id": "e1c042a3", + "metadata": {}, + "source": [ + "## 6. Identify the Largest Location Groups\n", + "\n", + "PetDB organizes samples into location groups. The following summary identifies\n", + "the groups containing the largest number of sample records in the returned\n", + "page." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "5d54ffc5", + "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", + "
location_groupsample_recordsunique_samplescenter_latitudecenter_longitude
0BMRG08MV-0982121-19.25200-172.9480
1KNO0180-2-019161623.51890-45.3616
2PETDB-5036-F02-26151519.42446-155.2880
3KNO0180-2-021141423.36420-45.3770
4KNO0180-2-027141423.40490-45.4136
5KNO0162-9-0471111-52.5620012.6620
6PETDB-5036-AB21-4101019.33923-155.2807
7KNO0180-2-0289923.38340-45.3823
8KNO0180-2-0058823.47090-45.3579
9BMRG08MV-10677-17.41700-172.4090
\n", + "
" + ], + "text/plain": [ + " location_group sample_records unique_samples center_latitude \\\n", + "0 BMRG08MV-098 21 21 -19.25200 \n", + "1 KNO0180-2-019 16 16 23.51890 \n", + "2 PETDB-5036-F02-26 15 15 19.42446 \n", + "3 KNO0180-2-021 14 14 23.36420 \n", + "4 KNO0180-2-027 14 14 23.40490 \n", + "5 KNO0162-9-047 11 11 -52.56200 \n", + "6 PETDB-5036-AB21-4 10 10 19.33923 \n", + "7 KNO0180-2-028 9 9 23.38340 \n", + "8 KNO0180-2-005 8 8 23.47090 \n", + "9 BMRG08MV-106 7 7 -17.41700 \n", + "\n", + " center_longitude \n", + "0 -172.9480 \n", + "1 -45.3616 \n", + "2 -155.2880 \n", + "3 -45.3770 \n", + "4 -45.4136 \n", + "5 12.6620 \n", + "6 -155.2807 \n", + "7 -45.3823 \n", + "8 -45.3579 \n", + "9 -172.4090 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Summarize the returned samples by their location group.\n", + "\n", + "required_cluster_columns = {\n", + " \"rootParent\",\n", + " \"sampleId\",\n", + " \"latitude\",\n", + " \"longitude\",\n", + "}\n", + "\n", + "if (\n", + " samples_geo.empty\n", + " or not required_cluster_columns.issubset(samples_geo.columns)\n", + "):\n", + " cluster_summary = pd.DataFrame()\n", + " print(\"Location-group information is unavailable.\")\n", + "else:\n", + " cluster_input = samples_geo.copy()\n", + "\n", + " cluster_input[\"location_group\"] = (\n", + " cluster_input[\"rootParent\"]\n", + " .fillna(\"Unspecified\")\n", + " .astype(str)\n", + " )\n", + "\n", + " cluster_summary = (\n", + " cluster_input\n", + " .groupby(\"location_group\", dropna=False)\n", + " .agg(\n", + " sample_records=(\"sampleId\", \"size\"),\n", + " unique_samples=(\"sampleId\", \"nunique\"),\n", + " center_latitude=(\"latitude\", \"mean\"),\n", + " center_longitude=(\"longitude\", \"mean\"),\n", + " )\n", + " .sort_values(\n", + " [\"sample_records\", \"unique_samples\"],\n", + " ascending=False,\n", + " )\n", + " .reset_index()\n", + " )\n", + "\n", + " display(cluster_summary.head(10))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "fc3e0d13", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABKQAAALDCAYAAAArAtgIAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjIsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvgI3uAAAAAAlwSFlzAAASdAAAEnQB3mYfeAAAtQJJREFUeJzs3Qm4jPX///H34aAQJ0vJrkiWIrIkIntIVNKiTUJoo31fiEq7pJRWVERpoYgWW1SypSJ72ihbtmT+1+vz/d/zm5kzc2bmnOPO8Hxc11znnLnvue/Pfd9zTs3L+/O+0wKBQMAAAAAAAAAAn+Txa0cAAAAAAACAEEgBAAAAAADAVwRSAAAAAAAA8BWBFAAAAAAAAHxFIAUAAAAAAABfEUgBAAAAAADAVwRSAAAAAAAA8BWBFAAAAAAAAHxFIAUAAAAAAABfEUgBAAAAAADAV+n+7g4AgEPL2rVr7ffff7dKlSpZ8eLF/+vhpJxvv/3W9u7dG/w5T548VqRIEatYsaKlpx8Y/xuzcOFCS0tLs5NOOslS1fLly23Lli1Wu3bthM7r0qVLbefOnXbiiSdagQIF7GCwbds2++GHH6xUqVJWtmxZO1D9/fff9ssvv9iuXbvsqKOOshIlSrjfC2T277//2s8//2x//PGHHXbYYVajRg1fTtPixYtt9+7dVqtWLcuXL1+m5d99953t2LHDypUrZ0cffXSm5b/++qutX7/ejjnmGCtTpowvYwaA/wL/9QIAYD968MEHrV69evbee+8dsud537599tVXX7kP+8lq1qyZO3/eo27dulalShU74ogjrFu3bu6DeW6EXkuWLMn269u0aWPt2rWzVHbNNde487t58+aE1u/atatbf926dZYqtm7d6t6HGzZsiLr866+/dsf0xBNP2IH4OzR27Fhr1KiRFS1a1P0OKAxUmJE/f35r2LChPfTQQy78xv+8+eabVrp0aatQoYKdcsopdu655/p2aq6//nr3Xpo9e3amZQrYGzRo4Jbfe++9UV9/6623uuXTpk3zYbQA8N8hkAIAAPuVKgH04apXr17Z3kadOnVcGKUKHlWwqDpk9OjR1qJFC9uzZ0+OxqfQq1OnTtl+vcakSggc2BQO6H04fPjwqMsVcuo9pqqVA+33R+/Piy66yObMmWOBQMCOPfZYF0jpd0G+/PJLF2JMnz79vx7uAUFBdffu3V1Ap3Ol61qzZk3f9n/66ae7r59//nmmZd98841t377dVbVFWx76uqZNm+7nkQLAf+vAqHUHAADIwqxZs9yUG8/HH39snTt3tmXLltmUKVOsY8eO/9n50/6R+hRaqILqQNOjRw9XYanATBWXl156qZu2Glo9pZDj7bffjjr961A0f/58F+Rdd911/0nFmxckRQucvOe6dOlib731lm3cuNFNu/RoiuGqVatcMKqpyQBwMCOQAgDgAKMKCH0o0fQp9Q858sgjo66nKXDqe3PyySdb3rx53ZQk9awqWLCgqwrwaJ01a9ZYsWLF3BQWrweQepyo8iirKgP1XtH+s6oa0XhViaAPVhpvRkZGcNmmTZtcvxRvHKEf+GP1T0lE69at3VS5iRMn2sqVK2Oup/GrH0vhwoXdhzv1eorsGaQ+MzoXoWPTuLxj1lSu0P4zOlZN+9KxlixZMqEeUlmNQ9VemjKokOH444+PeS30ntBrQz+8JrL9SOr9pHOmgKN8+fLmF+139erVbmwaY2jAGOt9pfezqkm0fqFChWKu673HtU6049cy9cnyzmXota5WrZp7XSI9pBI5hr/++st++ukn9/5QDyC9vxQw/PPPP26qXTK9zxTEaqqe+nSp+klTzyKp0kbPR1sWORaFVxq/nldVX+hY9P7Re1t/J2Idv3ojaRvRKgL1O6GH3sOhgZn3d0p/azTWP//8062n8xztvRxKf9PUS0nTFPW3K6v3tahactGiRa5iTA4//PDgtdbfRB1bqESOOXL83t9ZvWfUGzAaTaHUVEpVtGmKXuh5ViClvxtXXnmlm1b4xRdfuHDd89lnn8WsjtLx6frpGmjf0fq3eQGlfr+rVq0aPE697zV90XsP6HuNQ+PT74f+e6NQNpT6ymlarvaj86f/zkTSf0v0N8x7rd5bumb6G6oeZwCQpQAAANhvevXqFdB/bl966aW46y5atChwxRVXBAoXLuxeo0daWlqgSZMmblmkpk2bunVWr14duOSSSwL58uVzP3ft2tUt37lzZ6Bnz56B/PnzB7fXoEGDwPfffx+oUaNGoECBAlHHMXr06MDxxx8ffI0exx57bOCNN97ItO4TTzwRKF26dNi6VapUCTz33HNuuY47dFno45FHHol7TooWLerW1bGE2rdvX6BevXpumcYb6cMPPwzUrl07bH/HHHNM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8avhrf6lX9VV3nQUNcVu166d61WiKRuaPqLmv6KwQBSm6IOs/vVfx1KzZk23f702J/2mNIVJFSQKvhTa9ezZ084++2x37lRtpuosNXnWh1mNQcegqU1qQK0PeV5PKjX91bXRlEP1wdK6GpfGl13ZGYeo2kHVImo07lVxRKuASnb7anKuHjR6Xj15FHRoP7qe+qCv66EPw8nSlE29T6PRdEI1oVZQoeb6mq6kyjmFKuPHj3cNzPUeVLDoUegyceJEFzKqaksfwnV99OFY1SDqfabm4qLfGb0n1aRd50gf1jUevYcjG8OL19xZ21fwpaom7V/byWraY7LHkJv0t0CNv1UdpfDr448/dudE02pVraUQ5ccff3ShokQGFlmpUqWK66Wk3x+FJAoBdS503vW3Q8GeesLpd8Kj86ZwrGvXru58aOqWqpeGDBnimoFnRedLlWyaZqi/FWrg74VU6kfn0fXTNVKVjt5D+l3U9fQaoHt/V7IjO8ecE/od0++l3pcTJkxw245sUO9VTHkVfZENzb3/Nmh6qioi9d8L/T7ofMS60UV2qTJLjdJVdai+Xbou+qoKXe1T0w/13xS9D0Mb/ANAtnGzQQAA9p9evXoFb7Ed6zF48GC3brdu3aIuv+uuu9yt1nXL9VBNmzZ1y//666+o+3711VcDefLkybS9yy67LFClSpVA0aJFM71myZIlgWOPPTbmWLXMo1uKx1rvlFNOCfz999/Bdfv3759pnUceeSTu+dMYtW7o7epDffTRR255tWrVAvv27XPPrV27NlC7du2YY9MtzXfv3h3cxmOPPZZpneuuuy64XLdl163NYzn66KPdreQjJTsOT9++fYPr1KxZM+Z+k92+bll/+OGHZ1qvZcuW7qHvddv5RHi3es/q4d2K/p577om6XLe3j/YeeP311wP58+eP+prQ6zJz5sxAenp6pnX03u7Xr5/7/r333gvbdv369TOtP2fOHLdsxowZ7ucBAwZkGlMyxzBu3Di37IEHHoh67goVKhSoWrVqIBkTJkwIlCxZMua51u/5xRdfnOlvQbyxbNiwwf3uRNum/t6sWLEibP3ff/89ULZs2UzrVq9ePfi+nTx5cthrTjvttODfuWj7ufHGG8PWf/TRR2MeZ5EiRQJz586Ne76effZZt76+5vSYvfFv27YtkB19+vQJbv+2226Lus4xxxwT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epx++ulWqFAhmz17dqYwShRWNWrUyE0FU+VPvOqehx56yLp27Ro1vFIwc/7557t1fvrpJ/ecwi7t+6qrrgoGMKJKo3z58rmAJ5SqhRTAxKNpaQrZIoOkU045xX395Zdfgs8lO4ZoWrduba+//nowjJK0tDTr0qWLO2+zZs2Ku439dS4mTJjgzoW2E1lhtXv3bnv++efjbgMAAAAAAIQjkMqG9957z4UomlKmsKRixYox133kkUfs559/tscffzzLbb7xxhv277//umlxsWiZ1tG64gU16msVSiGZppQlEuREo+lpFSpUsA8++MD27t0bfF7TCKVDhw7B53JjDLGqn9auXeu+FixYMO429te5+P33393Xo48+Ouz5UqVKBYNJAAAAAACQHAKpJCnYUK8o9Y369NNPrWTJklmurwqpzp07u8omNT+PZdGiRe5rrVq1Yq7jLdM0Mm9qXWg4Ekq9p9atW2fZoQqjGTNmuF5O1apVs0svvdRVe91xxx1u6qCm8nn21xh0rp5++mm3XVWixbO/xuE1kl+5cmXY897Pmm4YL9BaunRp2EPVcAAAAAAAHMroIZWkHTt2uKohTQNLT0/s9A0ZMsRV6dx///0uZIlm27Zt7mvotLVI3jLdhU527drlvkabelagQIG40wTjNRrXQ1PbTjrpJBesfP311+7ufwrYdNe9ZMdw9913ByuORHcf1F0II6lPlqYubtq0yTUcV1+oeNvYX+dC47jvvvtczyxN39N11/UfOHBg2PHHMnz4cPd6AAAAAADwfwikkqTm2/Xr17cBAwa4Jt/vvvtu3Clluptbz5493Z31rrvuuqjrFCtWzH39888/3TSzaLQsdF1vvWhhi4KzrMKtrCxYsMD1serbt6899dRTwed79Ojhwik1+dad7JIdw5gxY4L9r0TBTmQgpSmJF110kavQGjp0qKtGCxVrG/vrXFSuXNndQVG9qdQnTH20vvnmG3cNqlSpEvf1usuiemGFUoVUp06dsjUeAAAAAAAOBgRS2aBARlU311xzjbVr185VDcULPHTXOgUbt912m7s7WyT1PlLY8uWXX1q5cuWibkPLvHXFu7ubpqOpAiuUnsvu3d+mTJli+/btswsvvDBTsKZG5zpeTzJjeOCBB2zLli3BnzUdMJT2qebt48ePdxVJCv0ixdrG/joXXpVUmzZt3BRNhYLq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+us5GkM0f/31lzuHNWrUcMdauXJlFxDp3G3ZsiWhbSQzjmT2N2fOHGvWrFmmR7169dw1HDBgQMLjAwAAAAAA/0MglQMKQPr372+33nqr9e3b18aOHWv58+cPLk9LS7PnnnvOli9fntD2Fi5caHXq1LHFixfbJ5984iqkNm7caJ9++qktXbrULdM6oRTA3HPPPfbyyy+7qiBVam3bts1atmzpKn9Cvfjiiy6I+vLLL10IE8uePXusRYsWLoz59ttv3XYV1Jxxxhl2ySWX2IQJE7I9hmg++OADGzRokDuHf/75p3u8++67NmrUKDvzzDNdxVMiEh1HMvtr3ry5C7QiH126dHHLzz///ITGBgAAAAAA/g+BVDapCuqyyy6zxx9/3O69914bNmxYpml1HTp0cNPcbrvttrjb03S/c8891woWLGjTpk2zk08+ObisVq1aNnXqVLfsvPPOc+uKwhaFMAqJzjrrLPecKn2effZZV+H0/PPPh+1D4ZgqpOJN01MItWbNGrvqqqvshBNOcM8VKFDAhgwZ4r5/5513gusmO4ZoypQpY3PnzrU+ffq4/UiTJk1cxZJCsdmzZ8fdRjLjyOn+NM1P4aOq4hRgAQAAAACA5BBIZYOqbTp27GijR492gYeqcqI56qij7Oabb7a3337bBSBZ0ToKThSKqF9TJD1344032ooVK4IVSgquNJbIUETT1RSWqOonO4455hgXrm3evDnTVDfRtj25MQZVXlWvXj3q+RP124onmXHkdH+6Vjo3mlqpKZoAAAAAACA5BFJJUiijKWDTp0+3t956y3r37p3l+prSp4okhUlZ0fZE247FW+at+91337mvVapUybSunvOWJ6tcuXJ233332YgRI1xVlbajaYMXXXSRnXjiiXbDDTcE191fYxBVIXkVYvHkxjgS3Z+m9imwUyAFAAAAAACSx132kvTxxx+7r5qmpyl28Wia3f333289evRwTcw7d+4cdb0NGzYEp5PF4i1TbyTxKph0N7loFVVeRVN2XHvttbZ27VoXuKnx99atW12l0WuvvRZ2V7lkxuDdidCj12jb0ajflaqeunXrFhYyxdpGTs9FrP1FWrVqlc2YMcNVYim4i0e9rHSXwlCqcgMAAAAA4FBGhVSSdIc1TfdSv6LQXkpZufzyy92d6tRLSr2noklP/182GGt56LJ8+fL97+L9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K1bY1q1b7eWXX87pLgAAAAAAAHAQSc+NSqhIJUqUsAYNGliPHj2sTp06Od0FAAAAAAAADiI5DqTmzp2bOyMBAAAAAADAISHHgZQnEAjYjz/+aBs3bnQVUscff7ylpaXl1uYBAAAAAABwkMiVpuZvvfWWVahQwU444QRr3Lix+6qfx48fnxubBwAAAAAAwEEkxxVSkydPtgsuuMBOPvlk69mzp5UqVcp+++03mzBhgp1//vk2ZcoUa926de6MFgAAAAAAACkvx4HUgw8+aL1797bhw4eHPX/HHXdYnz59bODAgQRSAAAAAAAAyL1A6ptvvrE33ngj6rLbb7/dTd8DAAAAAAAAcq2HlJqZx2penidPHrccAAAAAAAAyLVAqlatWvbwww9HXfboo49a7dq1c7oLAAAAAAAAHERyHEjdfPPN9uSTT9ppp53mAqjRo0fbY489Zk2aNHFfb7nlFjsY/Pvvv1a2bFnr27dvpmXTp0+3Y4891tq0aWObN28OrqvH008/nWn9mTNnumUTJ07MtGzv3r320ksvWYcOHdx0x6pVq1r79u1t1KhRblk0M2bMcOtXrlzZTjnlFNe3a9euXZnW27Fjh40bN866dOli5cuXt5YtW8Y83p07d7qxN2/e3I4//ni33auuusq+//77HI0hllmzZlmvXr2sfv36Vr16devYsaO9++67Cb8+2XEks79kzhsAAAAAAPAhkOrcubMLS5YvX2433nijdevWzQYMGGA//vijvfLKK+6D/sFAUw9//vln27RpU9jzCirOPPNMd3fBsWPHWkZGRnBdPe69914XUoVSQKJlf//9d9jzGzdutMaNG9tNN93kwi3dqVAhSbt27Vywp2VaJ9Q777zjApIaNWrYhx9+6AKY5557zs4+++xM0yU1zrfeesvd/TBfvnz266+/xjxehWC33nqrXXzxxfb+++/b448/7sIo3U3xq6++yvYYolEPMh3btm3b3H7Gjx9vp556qp177rkuNEpUouNIdn/JnDcAAAAAAOBDU/PVq1dbq1at7JJLLrHFixfbn3/+acWLF7eaNWtaenqON39AGzFihKuYatu2rQumChYsGLa8YcOGNn/+fBs0aJA98sgjcbd34YUX2pIlS9xrqlWrFnxelVKqVKpXr55bZ+rUqe55VUxp/wphHnroIfecqpkUwihQUojStWvX4HY+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1arZiSeeaKVKlbLffvvNFi1aZCVKlLAJEybkzkgBAAAAAABwUMhxhVSlSpXsm2++sT59+tiePXvs22+/dV/79u3rnq9YsWLujBQAAAAAAAAHhRxXSEmZMmXsqaeeyo1NAQAAAAAA4CCX4wopAAAAAAAAYL9WSE2bNs19bdmyZdjPWfHWBQAAAAAAANKzc1c9CQQCYT9nxVsXAAAAAAAASDqQmjFjRpY/AwAAAAAAALkaSDVr1izLnwEAAAAAAID92tR8xIgROVoOAAAAAACAQ0uOA6mrr746R8sBAAAAAABwaMlxIJWVnTt3Wnp60rMCAQAAAAAAcBDLVlr06aefZvmz7N6926ZMmWLly5fP/ugAAAAAAABw0MlWIHXGGWdk+bMnX758NmzYMDsY7Nu3zy666CJr1KiRXXvttWHL1q1bZ3fccYcddthh9vDDD1uRIkXcunLllVdaq1atwtZfsmSJDRw40Pr162eNGzfOtK/vv//e3nnnHVuxYoUFAgGrXLmyde7c2U444YSoY/v9999t1KhR7nUZGRnWqVOnmM3mN2zYYOPGjbMvv/zSypQpY4888kiWx/3VV1+5sfz8889uHJdddpmVLVs2bJ3t27fbxIkTbcGCBe57rafjj1wvnrlz59qECRPs119/tdKlS9v5559vderUSWobyZwLz969e+2aa66xv/76y3r06GEtW7YMLtMxPfTQQzFf27BhQ7v++uuTGiMAAAAAAIe6bE3Zmzx5cvAR+bP3+Pzzz239+vXWs2dPO1gCqTfffNNmz54d9vyyZcvstNNOs48++sj1y1II4q2rh0InBR6hFLho2erVq8Oe//fff124ceKJJ7ogpH79+nbqqafaokWL3HNapnUiw63q1au7/Z9++ulWuHBha9u2rQvIInXr1s0aNGhgK1eudFVt3vWLRkHYdddd58KZPXv2uNAxT548LlzTeDzTp0934dEDDzxgRx99tJ1yyin22WefWZUqVezFF19M+Pzef//9Luz7448/rEmTJrZx40Z3/MkEmsmci1AKB0eOHOmuiULAUDomhVqRj7S0NLe+AjgAAAAAAOBDhZQ+5HueffbZsJ8PJaoyat++vRUtWtQFM6oMClWxYkX78ccf7fnnn7c+ffrE3V7//v3t6aeftvHjx9s555wTfF5VO126dLHzzjvPBUVPPvlkcJkqlkqV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UoXQpZdeak888YTdf//99vTTT2dZbXTPPfe4ShqFV1mZPHmy+5pVXyJv2Ycffui+epVD1apVC1svLS3NPadKp+xQHydVZY0YMcIFYOrjtHHjRhe+KbC55pprguvurzGIF7wdd9xxcddNZhzZGbNCLFVu6fjr1avnpjNGTrkEAAAAAACJSU9wPfx/6nGkgEYB01133RX3vJQsWdJuueUWu/POO23OnDl26qmnRl1v1apV7mu06iiPt2z16tXuq0IiOfLIIzOtq+e85dmh6Xl9+/Z1Tb/T09NdEFepUiU37U+VX579NYZp06a56XqqWlJj9XiSGUeyY1Z/MFVTaVqf+m69/fbb7np+/PHHNmXKlCwDyd9//z1TD7EVK1bEPR4AAAAAAA5mVEglSXdcU5WMpuEtWLAgoddoel+ZMmXspptuynIaoLsgWYQb3jJv3dDKnkh6LnK9RClwO/vss13w8sYbb7jeVaoaOumkk6x58+auv1S0/cUbQ+Td6lRxFc3333/vGrArzNOd9kLF20Yy5yLRdTWFT/29ypUrZ1WrVrXbb7/dBg4c6MK5t956y7Kivlg1a9YMe6iZOgAAAAAAhzICqWxMZ9P0rXz58lmLFi1s/vz5cV+jnktqbK7pb6qwikZhh6xduzbmdrxlZcuWDavw2bJlS6Z1N2/ebMWKFbPsUBClaYEPPfSQde3a1TVWVxilcEp9pNTLypPMGLw7D3qPyDsWysqVK61ly5bBKqny5cuHLY+1jWTGkRvnzes/9fnnn2e5nhrD6y6FoQ/dzQ8AAAAAgEMZgVQ26E5wn332mQuaFJ6oGXk8V1xxhauOufXWW930t0heCOP1TYrGW6a75InufCc//PBDpnV//PFHt7/sWLp0qft68sknhz2vXljqseQtT3YMixYtsm3btgUfzzzzTKbATRVYujOhwihv26FibSOZceTGedM0Rvn333+zXE938NP7JfRRuXLluNsHAAAAAOBgRiCVg0ophVIZGRnWpk2buJUymm6niiMFHpruF0lT5BRWPPLII5l6DomeUyNthSUdO3Z0zym8OeKII2zSpElh62pK3S+//GLnnHNOto5N0wu9qXOhFKSp/5G3PNkxKMALnW5XoECB4LINGza4ijOFTAqjateuHXVssbaRzDhy47x5jeUbNGgQd10AAAAAABCOQCoHdPc3BVFHH320nXnmmfbJJ59kuX67du1cGBJt2l7evHndVDlNBWzatKkLZVR9o8f06dPdc1o2fvx4t64XzjzwwAOux9FLL73kej+p4XnPnj1duNW9e/dsHdf555/vQic1bp85c6YbgwKx3r172/r1623AgAHBdXNjDGoirgqxP//80x13ZGVWIpIZRzLrTpgwwZ0HhXO6q54CM/W1Uj8wNVvv1q1b0mMFAAAAAOBQRyCVQxUqVHCVUurr1KFDB3fXtayoAipaM21Rw2w1Sm/fvr0LRzRFTo8ePXq4MEvLtE6o6667zk1bU5NtVQspUKlevboLdkIrkERBkldZpEqtZcuWBX/WnQA9RYsWtblz51rr1q1dD6nDDz/chW+aqvfuu++66YfZHUM0o0ePdmPZunWrNWnSJKwCSo9oFWXRJDOORNfVOdC0O50HVcPp3Kgf2NVXX+16iamnFgAAAAAASE5aILu3YjsEbd++3VUpRQtZNJ1NDbZVvaQAJ6t1d+zY4apyFDZ5vYii8foTeRVR8aiCR/uMZc+ePe4RjYKVWOGKji2rcSYzhliv2b17d8zlOofZ2Wair0l03WSvRywK9jT1snyPYZZWvGKOtoX9L3+egJUvHLC129Nsz77oYTIOHFyv1LO/rtnqIe1zbVsI/38C3QBE/0iT6P8b4L/D9Uo9XLPUwvVKLVyv/c/7rKubeanoIx7+TyIJqtaJeSLT08OWZ7WupowlItngI16oklXolJVk/ocz2eDIe012Xpdb40h03ZwGUQAAAAAA4H+YsgcAAAAAAABfEUgBAAAAAADAVwRSAAAAAAAA8BWBFAAAAAAAAHxFIAUAAAAAAABfEUgBAAAAAADAVwRSAAAAAAAA8BWBFAAAAAAAAHxFIAUAAAAAAABfEUgBAAAAAADAVwRSAAAAAAAA8BWBFAAAAAAAAHxFIAUAAAAAAABfEUgBAAAAAADAV+n+7g6AZ1K/xlarVi1OyAFu7969tnnzZsvIyLD0dP5kHui4XqmHawYAAHBookIKAAAAAAAAviKQAgAAAAAAgK8IpAAAAAAAAOArAikAAAAAAAD4ikAKAAAAAAAAviKQAgAAAAAAgK8IpAAAAAAAAOArAikAAAAAAAD4ikAKAAAAAAAAviKQAgAAAAAAgK/S/d0dAE/HYTMtrfh6TsgBLn+egJUvHLC129Nsz760/3o4iIPrtX+tHtKe9yAAAAByBRVSAAAAAAAA8BWBFAAAAAAAAHxFIAUAAAAAAABfEUgBAAAAAADAVwRSAAAAAAAA8BWBFAAAAAAAAHxFIAUAAAAAAABfEUgBAAAAAADAVwRSAAAAAAAA8BWBFAAAAAAAAHxFIAUAAAAAAABfEUgBAAAAAADAVwRSAAAAAAAA8BWBFAAAAAAAAHxFIAUAAAAAAABfEUgBAAAAAADAV+n+7i71/PPPP3bWWWeFPXf44Ydb2bJl7eyzz7aWLVvGXL9du3Z27bXXZtrmrFmz7IEHHnDf33///Va/fv2o+0pLS7PChQtbzZo17bLLLrOKFSvGHOeyZcts4sSJtnTpUtu+fbsdffTRVqFCBbe9k046KdP6q1atshEjRtgPP/xgGRkZds4551jHjh2jbvuTTz6xd955x37++WcrWrSo1alTx6644go3tpwe93vvvWfPPPOM3Xnnnda4ceOo+//rr7/soosusubNm9tNN90U8xx45+Gtt96yL7/80sqXL++OMZbVq1e75d9//707B506dXKPWHQedI51HipXrmw9evSwqlWrZjkeAAAAAACQGRVScfz777/20UcfucDl+uuvd49u3bpZ3rx5rU2bNta9e/eo63/88cf24IMP2t69ezNtUwHM1KlT3Xq///57zH0p1GnVqpW98cYbVqNGDZs9e3ambe3Zs8f69OnjQqeFCxe60ObKK6+0evXq2bx586x27dpuvKHmzp1rJ554ogujFHRVr17dLrzwQrvuuusybV/Bk44zX758bjva7mOPPWYnnHCCrV27NsfHfcopp7igZ9iwYTGvwWuvvWZTpkyxatWqWVYUEJ577rm2b98+W7Rokc2cOTPmuvPnz3fn7LvvvrNLL73UhX46vr59+2ZaV8ei86RQrHTp0u6cHHvssXb++efbN998k+WYAAAAAABAZlRIJeiYY46xtm3bBn9W8LF161Z76aWX7O67785UvXTGGWfY9OnTXZDSoUOH4PNbtmxxVTYKjqZNm5bQvlRxpADk9ttvt08//TRs3d69e7vA5oMPPrDWrVuHLbvqqqvs888/d0GQJxAIuEBFIdSECRMsT57/ZZLFihVz6+u4Tj/9dPecKodefvll69WrlwuhPA0bNrS6deva008/bY888kiOjlvHquNTBdamTZusePHimc7Hiy++aGXKlLEzzzzTsqLxqCpK3nzzzZjreeegSpUqbr/eOShRooR7XudA4/SoqkvrLViwwF2H0PO7c+fOLMcEAAAAAAAyo0IqBypVquS+bt68OdMyhR2NGjVygU6osWPHuq8XXHBBwvvR9EAFId9++23Y819//bULxPr165cpjPIoXBo1alTw5zlz5rigScGLF8SIqoMOO+ywsHW3bdvmvh533HFh29R0tdDlOT1uBTu7d++2119/PWolk6qdNF5VpWXFC6Pi0TY1tfHyyy8POweqgCpYsGDYOVDo+Oijj7oxhoZRkp6ebkcccURC+wQAAAAAAP+HQCqbFMa8/fbbLiyKNZVMIYp6JP3555/B5xQgde7c2fViSoZCr8jXjBs3LqFwq1ChQsHvNY1PTj755LB1FEbpONR7yaPpfprKpimDCmY8zz77rAtyNM0vN45blU+qgFIlVKQXXnjB9dLSNMTcEusc5M+f302NDD0Hqkj7+++/7dRTT3WVZpqmp35bAwcOtD/++CPXxgQAAAAAwKGEQCpBmoamaXR6qOqoXLlyVqRIEZsxY4YVKFAg6msUXqiKZsyYMe5n9StSGKLAJhnPPfec/fbbb9alS5dMDbwlmcbaasgtRx11VKZlaoS+YcOG4M/qG6VARiGNGqQ3bdrU7WvkyJGu75N+zo3jVuWTli1evNhVL3l27NjhwjA1js+qoXuykjkHy5cvd1/Vz0sVXJqGqPeAAjb14fKWx6JeWarGCn2sWLEi144FAAAAAIBURA+pBKl6SI3GRdPLFJ48/vjjds8997jpaQpvIimwUlWQlmtanUIMBVktWrRw/ZvihV/qdbRu3TrXfHzAgAE2ePDgsPW8/kWqbgqlZt6q4AmlO89pPGqY7i58euZLr2NQk/RQajauSqyePXu6IE7BmKaw3Xrrra6vUqlSpXLluFUBNWjQIFcRpcbp3phVmaXpch41kQ8NjHSevOuSqGTOgXeOFZopfPTOtaq6jj/+eLvhhhvs/fffj7mv4cOH23333ZfU+AAAAAAAONgRSCUostG47uim6WwKXtTk+5prron6OvUp0p3y1P9JFTYKXkL7FmUVfunOdaouUhClO+jp59Dgq2TJku7rxo0b3dTB0D5OXkijQETT57yQxet5pGlokbZv3+7CpNBg7N5773VNve+8886wY1ellPahCqbcOG5VQHl3FFTQp15OCqd0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1641uXLlMvPmzbvYhwIAQJYRkAIAALiMOY5jZs2aZe644w5TvHhxc+2115ratWuboUOHmq1bt17swwMAAFcoAlIAAACXsWeeecZ06dLFNG/e3GzcuNHs27fPjBkzxnz88cfmgQceuNiHBwAArlAEpAAAAC5Tx44dMxMmTDDt2rUzw4cPN2XLljUFChSwwakvvvjCdOjQ4WIfIgAAuEIRkAIAALiMA1Lnzp0zZcqUSfPaNddcY0aMGBGwrH///jY3kX5y585tbrjhBnPfffeZ9evXB6x3zz33mNtuu8388ssvplWrVqZQoUImPj7evPPOO/Z1DQW8//777fDA0qVLm/Hjx6d5f3cf27dvNy1btjQFCxY0JUuWtD26UlJSPH2+FStW2G0LFy5s8uXLZ+rUqWPefffdDLdz33vHjh2mbdu2Ji4uzrRu3dq+dv78efOPf/zD1KxZ0+7z+uuvNx07djRbtmwJ2IfWe+WVV+zwRwX59Dm7d+9uz4nrxIkTZvDgwaZChQomb968thx69eplDhw44Ftn79699nxrXwkJCeaWW26x67rnMikpydx1110mf/78dvuXXnrJDsMMVdYDBgwwFStWtOtWrlzZ9OvXz+4fAIBLEQEpAACAy5R6RCkYMmfOHLNhw4YM11dvKgU79HP27FmzZs0aG1Rq0aKF2b17d8C6Z86cMQMHDjQvvvii2bVrl+nWrZvp2rWrWbZsmQ2EKNj166+/mj59+thAyeeff57m/bSPvn372t5be/bsMRMnTjRvvPGGefTRRzM81rlz55qmTZvaAMymTZts4OWJJ56wxzF16tQMt9d769ieffZZs23bNtO5c2e7XL/1mbRcx6RhjjofjRo1sp9H9PeDDz5oj1vHrwDcl19+ac/T5MmT7ToKBOrvt956y34uBaFmz55tPv30U5vP6/jx4wHHo/OWmJho5s+fb/dVqlQp8/3339tglIKHX331lfn6669tsOrll19O83kU6FKScwWyDh8+bJYsWWKDW//6178yPBcAAFwUDgAAAC5b69atc+Lj49WlxqlWrZrTtWtXZ9KkSc727ds9bX/69GknT548ziuvvOJb1qxZM7u/TZs2+ZadO3fOKV68uFOoUCEnKSnJt/z8+fNOyZIlnW7dugXs193HmjVrApbrfbR89erVvmUdO3Z0qlat6vs7JSXFKVGihN1HsN69eztFixZ1UlNTw34m971XrVoVsHzhwoV2+bRp0wKWnzx50r5fv3797N/z5s2z673++uth32PWrFl2Hf32t2LFCrt81KhR9u89e/bYvytVqmTPlb/OnTs7cXFxzpEjRwKWP/LII3abhIQE3zKd4z59+oQ9HgAALjX0kAIAALiM1atXzw43W758uXnsscfsULM///nPpkqVKuaFF14IWHf//v22d5N6HaknjoaSafhXamqq+emnnwLWVQ8eDVdzXXXVVXaYmNavW7eub7mG/t10000hZ/QrVqyYadiwYcCy9u3b299Lly4N+5k0hFDJ2dVLKdRwPPVG+vbbb9M9L0WKFLG9nvyph5KONzjZu4bk3X777b5eXh9++KH9nV5PLvVQ0vkLztOl3lElSpSwr/vTkEG9d/A+mjRpYq677rqQ58hfrVq1zMyZM824ceOYPREAkCMQkAIAALjMKdBx5513miFDhphZs2bZ3EkaTvb888/bIWRy4cIFc++999rZ9958802TnJxsl2l4mnIpKSgVHJAKpuF94ZYfOXIkzXIFZsIt88+zFMzNi6ThcldffbUNhulHn7NTp072tYMHD6Z7TkLl1dJ+9ZmVO8vdr/apHw2Hc/epwJ2CRMp7FY7W1To6d8GUKyv484U6Hu0jvXPkb/r06TbX1V/+8hdTqVIlc+ONN5qnnnqKHFIAgEsWASkAAIArjAIpSrbtJgYX5S1SjiLlflJuJgVT1MNHQRrlWwqm10IJtzwU9XIKt0w9mMIpWrSo/a38TMrVpF5f+nEDaPq5++67033vPHnyhNyv8jWdOnXKt1/t092vm0dLPbuOHj1qTp48GXb/CmppnVDnTp/R/QzpHY/OQXrnKDhIpUCiAl3KN6Webjo/SvoOAMCliIAUAADAZUoBlNGjR4d8TYnIRTPU+VNAxp+CGtGiXljr1q0LWPbBBx/Y382aNQu7XYMGDWxQ6L333svW42nTpo2d4U+9odLjzsin3mbh6PgVxHI/j2v16tU2yJfe53MpqKahlppBz1/wPv2pV5eSmas3nJK8KzgVvD0AAJcCAlIAAACXKfXsGTZsmA1sLF682Pbo0TCw999/3wwaNMgOr9PMeHLzzTeb+Ph4M2rUKLN582Y7xG7atGlm1apVaXIYZZcaNWrYIWZr1661s84lJCTYmevatWtnczaFo2Fwr732ms3l9OSTT9rZ6NQTSXmq3n77bdO8efMsHU/btm3NQw89ZHr37m1n6tMseydOnLC9x5577jk7+54buFJuKJ3DKVOm2B5LGsanANXQoUPtOspvpcDZ008/bY9Tn2/lypV2FkDl2tJww4zoPTVU8uGHH7Z5wA4dOmRn2FPQzJ96cik3lWZT3Llzp31dMw8uXLjQ1KlTx8TFxWXpfAAAEE0EpAAAAC5TZcuWtQGlqlWrmgEDBthhXVqmIJXyDSk5ePHixe26SmK+YMECu44SjStoogDKjBkzonZ8Ciy9+uqrNgilvEoKBCnx+uzZszPcVonH9dk0RK1x48Y2aKa8WEoEPmbMmCwfk9575MiR5vXXX7eJ30uXLm0ef/xxm9hcw+DcYYkK/ui4x48fb8qXL29uvfVW88knn/gCTRqCp/xcXbp0MX369LFD+BSkUnBQx+0lSFS9enXz2Wef2SGENWvWtEFDBRXd4Zb+vaL0mefOnWsDU+r1plxaOh8fffRRls8FAADRlEtT7UX1HQAAAIAQs+GpF1ZSUhLnBgCAKxA9pAAAAAAAABBTBKQAAAAAAAAQUwSkAAAAAAAAEFPkkAIAAAAAAEBM0UMKAAAAAAAAMUVACgAAAAAAADFFQAoAAAAAAAAxRUAKAAAAAAAAMUVACgAAAAAAADFFQAoAAAAAAAAxRUAKAAAAAAAAMUVACgAAAAAAADFFQAoAAAAAAAAxRUAKAAAAAAAAMUVACgAAAAAAACaW/g9REvzt8xvnvgAAAABJRU5ErkJggg==", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Plot the location groups containing the most sample records.\n", + "\n", + "if cluster_summary.empty:\n", + " print(\"No location-group summary is available.\")\n", + "else:\n", + " top_clusters = (\n", + " cluster_summary\n", + " .head(10)\n", + " .sort_values(\"sample_records\")\n", + " .copy()\n", + " )\n", + "\n", + " top_clusters[\"display_name\"] = (\n", + " top_clusters[\"location_group\"]\n", + " .str.slice(0, 55)\n", + " )\n", + "\n", + " fig, ax = plt.subplots(figsize=(10, 6))\n", + "\n", + " ax.barh(\n", + " top_clusters[\"display_name\"],\n", + " top_clusters[\"sample_records\"],\n", + " )\n", + "\n", + " ax.set_title(\n", + " f\"Largest Retrieved Location Groups for {AUTHOR_QUERY}\"\n", + " )\n", + " ax.set_xlabel(\"Sample records\")\n", + " ax.set_ylabel(\"Location group\")\n", + " ax.grid(axis=\"x\", alpha=0.25)\n", + "\n", + " plt.tight_layout()\n", + " display_figure(fig)" + ] + }, + { + "cell_type": "markdown", + "id": "3fac93fd", + "metadata": {}, + "source": [ + "## 7. Examine the Citation Metadata\n", + "\n", + "The citations table contains publication, dataset, DOI, analysis-type, and\n", + "analytical-method metadata.\n", + "\n", + "A publication may appear in multiple rows because it can contain several\n", + "datasets or analysis types. The next step creates one row per citation for\n", + "publication-level analysis." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0306cf0a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Citation/dataset rows: 71\n", + "Unique citations: 15\n" + ] + } + ], + "source": [ + "# Create one row per citation for publication-level analysis.\n", + "\n", + "if citations.empty:\n", + " unique_citations = pd.DataFrame()\n", + " print(\"No citation records were returned.\")\n", + "else:\n", + " unique_citations = (\n", + " citations\n", + " .drop_duplicates(subset=[\"citationId\"])\n", + " .copy()\n", + " )\n", + "\n", + " unique_citations[\"publication_year\"] = pd.to_numeric(\n", + " unique_citations[\"citationPublicationYear\"],\n", + " errors=\"coerce\",\n", + " )\n", + "\n", + " print(\"Citation/dataset rows:\", len(citations))\n", + " print(\"Unique citations:\", len(unique_citations))" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "7f275a88", + "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", + "
citationTitlecitationAuthorscitationPublicationYearcitationContainerTitlecitationDOIs
69Electron Microprobe and Laser Ablation Inductively Coupled Mass Spectrometry analyses of glasses from the Kulanaokua...nalesnik, a.; lynn, k.; rose, t.; warren, j. m.; lin, k.2025None10.60520/IEDA/113888
68DEEP, HOT, ANCIENT MELTING RECORDED BY ULTRALOW OXYGEN FUGACITY IN PERIDOTITESbirner, s.; cottrell, e.; davis, f.; warren, j. m.2024NATURE10.1038/s41586-024-07603-w
61HIGH TEMPERATURE HYDROTHERMAL ALTERATION AND AMPHIBOLE FORMATION IN GAKKEL RIDGE ABYSSAL PERIDOTITESpatterson, s.; lynn, k.; prigent, c.; warren, j. m.2021LITHOS10.1016/j.lithos.2021.106107
62MELT ADDITION TO MID-OCEAN RIDGE PERIDOTITES INCREASES SPINEL CR# WITH NO SIGNIFICANT EFFECT ON RECORDED OXYGEN FUGA...birner, s.; cottrell, e.; warren, j. m.; kelley, k. a.; davis, f.2021EARTH AND PLANETARY SCIENCE LETTERS10.1016/j.epsl.2021.116951
46IN SITU MEASUREMENTS OF LEAD AND OTHER TRACE ELEMENTS IN ABYSSAL PERIDOTITE SULFIDESderrico, m.; warren, j. m.; coble, m.2019AMERICAN MINERALOGIST10.2138/am-2019-6516
42186OS–187OS AND HIGHLY SIDEROPHILE ELEMENT ABUNDANCE SYSTEMATICS OF THE MANTLE REVEALED BY ABYSSAL PERIDOTITES AND O...walker, r. j.; warren, j. m.; day, j. m. d.2017GEOCHIM COSMOCHIM ACTA10.1016/j.gca.2016.12.013
50FOREARC PERIDOTITES FROM TONGA RECORD HETEROGENEOUS OXIDATION OF THE MANTLE FOLLOWING SUBDUCTION INITIATIONbirner, s.; warren, j. m.; cottrell, e.; davis, f.; kelley, k. a.; falloon, t. j.2017JOURNAL OF PETROLOGY10.1093/petrology/egx072
28EVIDENCE FOR CHEMICALLY HETEROGENEOUS ARCTIC MANTLE BENEATH THE GAKKEL RIDGEderrico, m.; warren, j. m.; godard, m.2016GEOCHIM COSMOCHIM ACTA10.1016/j.gca.2015.11.017
37HYDROTHERMAL ALTERATION OF SEAFLOOR PERIDOTITES DOES NOT INFLUENCE OXYGEN FUGACITY RECORDED BY SPINELbirner, s.; warren, j. m.; cottrell, e.; davis, f.2016GEOLOGY10.1130/G38113.1
58TEPHROSTRATIGRAPHY AND DEPOSITIONAL ENVIRONMENT OF YOUNG (<2.94 MA) HADAR FORMATION DEPOSITS AT LEDI-GERARU, AFAR, E...di maggio, e.; arrowsmith, j.; campisano, c.; johnson, r.; deino, l.; warren, m.; fisseha, s.; cohen, a.2015JOURNAL OF AFRICAN EARTH SCIENCES10.1016/j.jafrearsci.2015.09.018
21LEAD AND OSMIUM ISOTOPIC CONSTRAINTS ON THE OCEANIC MANTLE FROM SINGLE ABYSSAL PERIDOTITE SULFIDESwarren, j. m.; shirey, s. b.2012EARTH PLANET SCI LETT10.1016/j.epsl.2012.09.055
6MANTLE MELTING, MELT TRANSPORT, AND DELIVERY BENEATH A SLOW-SPREADING RIDGE: THE PALEO-MAR FROM 23DEG15 N TO 23DEG45 Nlissenberg, c. j.; warren, j. e.; dick, h. j. b.2010J PETROL10.1093/petrology/egp088
5MANTLE DEFORMATION AND NOBLE GASES: HELIUM AND NEON IN OCEANIC MYLONITEScurtice, j.; kurz, m. d.; warren, j. e.2009CHEMICAL GEOLOGY10.1016/j.chemgeo.2008.12.018
23AN ASSESSMENT OF UPPER MANTLE HETEROGENEITY BASED ON ABYSSAL PERIDOTITE ISOTOPIC COMPOSITIONSwarren, j. m.; shimizu, n.; sakaguchi, c.; dick, h. j. b.; nakamura, e.2009J GEOPHYS RES10.1029/2008jb006186
0PYROXENITES FROM THE SOUTHWEST INDIAN RIDGE, 9-16 DEGREES EAST: CUMULATES FROM INCREMENTAL MELT FRACTIONS PRODUCES A...dantas, c.; ceuleneer, g.; gregoire, m.; python, m.; freydier, r.; warren, j. e.; dick, h. j. b.2007J PETROL10.1093/petrology/egl076
\n", + "
" + ], + "text/plain": [ + " citationTitle \\\n", + "69 Electron Microprobe and Laser Ablation Inductively Coupled Mass Spectrometry analyses of glasses from the Kulanaokua... \n", + "68 DEEP, HOT, ANCIENT MELTING RECORDED BY ULTRALOW OXYGEN FUGACITY IN PERIDOTITES \n", + "61 HIGH TEMPERATURE HYDROTHERMAL ALTERATION AND AMPHIBOLE FORMATION IN GAKKEL RIDGE ABYSSAL PERIDOTITES \n", + "62 MELT ADDITION TO MID-OCEAN RIDGE PERIDOTITES INCREASES SPINEL CR# WITH NO SIGNIFICANT EFFECT ON RECORDED OXYGEN FUGA... \n", + "46 IN SITU MEASUREMENTS OF LEAD AND OTHER TRACE ELEMENTS IN ABYSSAL PERIDOTITE SULFIDES \n", + "42 186OS–187OS AND HIGHLY SIDEROPHILE ELEMENT ABUNDANCE SYSTEMATICS OF THE MANTLE REVEALED BY ABYSSAL PERIDOTITES AND O... \n", + "50 FOREARC PERIDOTITES FROM TONGA RECORD HETEROGENEOUS OXIDATION OF THE MANTLE FOLLOWING SUBDUCTION INITIATION \n", + "28 EVIDENCE FOR CHEMICALLY HETEROGENEOUS ARCTIC MANTLE BENEATH THE GAKKEL RIDGE \n", + "37 HYDROTHERMAL ALTERATION OF SEAFLOOR PERIDOTITES DOES NOT INFLUENCE OXYGEN FUGACITY RECORDED BY SPINEL \n", + "58 TEPHROSTRATIGRAPHY AND DEPOSITIONAL ENVIRONMENT OF YOUNG (<2.94 MA) HADAR FORMATION DEPOSITS AT LEDI-GERARU, AFAR, E... \n", + "21 LEAD AND OSMIUM ISOTOPIC CONSTRAINTS ON THE OCEANIC MANTLE FROM SINGLE ABYSSAL PERIDOTITE SULFIDES \n", + "6 MANTLE MELTING, MELT TRANSPORT, AND DELIVERY BENEATH A SLOW-SPREADING RIDGE: THE PALEO-MAR FROM 23DEG15 N TO 23DEG45 N \n", + "5 MANTLE DEFORMATION AND NOBLE GASES: HELIUM AND NEON IN OCEANIC MYLONITES \n", + "23 AN ASSESSMENT OF UPPER MANTLE HETEROGENEITY BASED ON ABYSSAL PERIDOTITE ISOTOPIC COMPOSITIONS \n", + "0 PYROXENITES FROM THE SOUTHWEST INDIAN RIDGE, 9-16 DEGREES EAST: CUMULATES FROM INCREMENTAL MELT FRACTIONS PRODUCES A... \n", + "\n", + " citationAuthors \\\n", + "69 nalesnik, a.; lynn, k.; rose, t.; warren, j. m.; lin, k. \n", + "68 birner, s.; cottrell, e.; davis, f.; warren, j. m. \n", + "61 patterson, s.; lynn, k.; prigent, c.; warren, j. m. \n", + "62 birner, s.; cottrell, e.; warren, j. m.; kelley, k. a.; davis, f. \n", + "46 derrico, m.; warren, j. m.; coble, m. \n", + "42 walker, r. j.; warren, j. m.; day, j. m. d. \n", + "50 birner, s.; warren, j. m.; cottrell, e.; davis, f.; kelley, k. a.; falloon, t. j. \n", + "28 derrico, m.; warren, j. m.; godard, m. \n", + "37 birner, s.; warren, j. m.; cottrell, e.; davis, f. \n", + "58 di maggio, e.; arrowsmith, j.; campisano, c.; johnson, r.; deino, l.; warren, m.; fisseha, s.; cohen, a. \n", + "21 warren, j. m.; shirey, s. b. \n", + "6 lissenberg, c. j.; warren, j. e.; dick, h. j. b. \n", + "5 curtice, j.; kurz, m. d.; warren, j. e. \n", + "23 warren, j. m.; shimizu, n.; sakaguchi, c.; dick, h. j. b.; nakamura, e. \n", + "0 dantas, c.; ceuleneer, g.; gregoire, m.; python, m.; freydier, r.; warren, j. e.; dick, h. j. b. \n", + "\n", + " citationPublicationYear citationContainerTitle \\\n", + "69 2025 None \n", + "68 2024 NATURE \n", + "61 2021 LITHOS \n", + "62 2021 EARTH AND PLANETARY SCIENCE LETTERS \n", + "46 2019 AMERICAN MINERALOGIST \n", + "42 2017 GEOCHIM COSMOCHIM ACTA \n", + "50 2017 JOURNAL OF PETROLOGY \n", + "28 2016 GEOCHIM COSMOCHIM ACTA \n", + "37 2016 GEOLOGY \n", + "58 2015 JOURNAL OF AFRICAN EARTH SCIENCES \n", + "21 2012 EARTH PLANET SCI LETT \n", + "6 2010 J PETROL \n", + "5 2009 CHEMICAL GEOLOGY \n", + "23 2009 J GEOPHYS RES \n", + "0 2007 J PETROL \n", + "\n", + " citationDOIs \n", + "69 10.60520/IEDA/113888 \n", + "68 10.1038/s41586-024-07603-w \n", + "61 10.1016/j.lithos.2021.106107 \n", + "62 10.1016/j.epsl.2021.116951 \n", + "46 10.2138/am-2019-6516 \n", + "42 10.1016/j.gca.2016.12.013 \n", + "50 10.1093/petrology/egx072 \n", + "28 10.1016/j.gca.2015.11.017 \n", + "37 10.1130/G38113.1 \n", + "58 10.1016/j.jafrearsci.2015.09.018 \n", + "21 10.1016/j.epsl.2012.09.055 \n", + "6 10.1093/petrology/egp088 \n", + "5 10.1016/j.chemgeo.2008.12.018 \n", + "23 10.1029/2008jb006186 \n", + "0 10.1093/petrology/egl076 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Display the unique citations in reverse chronological order.\n", + "\n", + "if unique_citations.empty:\n", + " citation_view = pd.DataFrame()\n", + " print(\"No unique citations are available.\")\n", + "else:\n", + " citation_view_columns = [\n", + " \"citationTitle\",\n", + " \"citationAuthors\",\n", + " \"citationPublicationYear\",\n", + " \"citationContainerTitle\",\n", + " \"citationDOIs\",\n", + " ]\n", + "\n", + " citation_view_columns = [\n", + " column\n", + " for column in citation_view_columns\n", + " if column in unique_citations.columns\n", + " ]\n", + "\n", + " citation_view = (\n", + " unique_citations\n", + " .sort_values(\"publication_year\", ascending=False)\n", + " [citation_view_columns]\n", + " )\n", + "\n", + " display(citation_view)" + ] + }, + { + "cell_type": "markdown", + "id": "a4b82d9a", + "metadata": {}, + "source": [ + "## 8. Examine Publication Years\n", + "\n", + "The publication-year summary uses one row per citation so that publications\n", + "with multiple datasets or analysis types are counted only once." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "de66e2b2", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABCwAAAHTCAYAAADs7on7AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjIsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvgI3uAAAAAAlwSFlzAAASdAAAEnQB3mYfeAAAfGhJREFUeJzt3Qm8TPX/x/GPfc++Lwkl+5IUFSptSiSESpsibUoqQhEtVChpU9pLq4g2LVIU2qyFJEm2SnbC/B/vr/+5v7lzZ+6duTPuPde8no/HGPecMzNnvnPO95zzOd/v55srEAgEDAAAAAAAwEdyZ/cKAAAAAAAAhCJgAQAAAAAAfIeABQAAAAAA8B0CFgAAAAAAwHcIWAAAAAAAAN8hYAEAAAAAAHyHgAUAAAAAAPAdAhYAAAAAAMB3CFgAAAAAAADfIWABAAAAAAB8h4AFgByvb9++dvLJJ9vSpUsjLjNmzBi3zLvvvhv35z3wwAPuvVauXGk5Vb9+/dx3+OGHH8LOv+KKK9z8hx56KOz8l19+2c1/4oknLNk8+OCD7rvr8cILL0Rc7pNPPklZ7uabbza/uOGGG9w6bd++PVs+X2Wmz589e3bMr12+fLndf//91rVrVzv99NOtU6dOdv3119szzzxj//zzT6plhwwZ4j5n/fr1lp38sh45bVsJ3s/0OOWUU+yss85y9f3HH38c9/v//fff7n1vvfXWqF+zf/9+95revXtHNT07+W2dbr/9drc+b7/9dsRltm3b5n7jU0891TZs2JCl6wfAxwIAkMOddNJJAVVnc+fOjbjMdddd55Z5/PHH4/68q666yr3X999/H8ipBg4c6L7DAw88kGbe9u3bA3nz5nXzmzRpEvb1HTt2dPPfeeedQLLp3bu3++56NG7cOOJyF154Ycpyp59+elyfedttt7nt/K+//grEq3Xr1m6d/vnnn0B2uOeee2Ledvbs2RPo27dvIE+ePCllGvrInz9/4NNPP01T/r/++ushK8to3i/SeuQE2bmtBO9n4R6XX3554MCBA5l+/z///NO9z1lnnRX1a/777z/3mhNOOCGq6YfSzp073XZ3/fXXh52fHeuUnp9++ilQsGDBQJkyZQIbN24Mu0yfPn3cOg8ePDjL1w+Af9HCAgBidMcdd7i7w0cffXSOLbtWrVq55y+++CLNvDlz5ti+ffvc9/vxxx9t69atqeYHAgH78ssvLVeuXO6uZ7KqW7eua6GyaNGiNPN0t/+9995zyyTCkiVL7KuvvrK9e/dastH2duGFF9qECRPc3926dbPnn3/etWCZMmWKPfbYY3bNNddY0aJF7d9//0153YgRI9x+WrFixUNalhm9X6T1QHQGDRrkyk+Pt956y3r27OmmP/fcc/baa6/5ohjz5s3r1u+pp57K0hYU2u7C1T/ZtU7pqV27tt199922efNm13InlI5FTz75pNWrV8+1SgIAT96U/wEAolKrVi33yMlOOukky5MnjzvhPXDggOXO/b/4tddU/7bbbrOrr77aLXPOOeekzF+2bJk76axfv76VLl3akpUunBS8UheH0aNHp5qnC6k9e/bYZZdd5ppCI/MUkFDw54gjjrDp06e7ZuXhjB8/3nbt2pXy97HHHuuLYvfLeuRUCpwG/+bqBpQ/f36bOHGiC2B0797d/CDSdpmd/LZO6n6j32zy5Mku8NixY0c3Xfttr1693HHo2Wefdb8vAHhoYQEgqenunde/XDkwrrvuOmvbtq117tzZXnrpJXd3N5YcFjrxevjhh619+/buIl93ijZu3Oj67eo177//fqrldZIW6aTyjTfecPM++uijsPNnzZrl1ld9fvW45ZZbbPHixVF972LFilmTJk1sy5Ytae7Q6U5XzZo1XRnoBDK0FYb3d+vWrVOmqZxmzpzpcmOcd955bn10F+3rr78O+/nKP3DxxRe7/+suuf7fpk0b950VMNH31omrWneMGjXKOnTo4FpzqN+5R+XSp08fO/PMM11ZK8CyYsWKdPvhf//9965Pt37jiy66yN58802L547hCSec4PJ56G5nMLUAqFSpkvucSBYuXGh33nmnK2ctd/nll7vtJHib0117rbtavYjKwevTr20nlH4D9fFX+WvZwYMH22+//RZxHWItj2jL3OuPft9991m7du3s3HPPtXvuuSdNnomMqCz0+4v2q/QuwPLly+eCGpFyR8RSlon8bdLLYfHff//ZpEmT3EX3aaed5j5PgZedO3cmZDtWMFIXiFdddZXbJrp06eK2iczk31Hg8sorr3Sfeckll9gHH3yQZhnNV+ut4P00mF6j76CcI/HwAqi///67e/7555/d+yrvRTiqX7TNRqL9Vy0RFAw544wzXF3666+/JixfhH5PvX+PHj1c3XfppZe61gS7d+9OtZxatr3zzjuuXtd31EMX+aH5mXQc8b6PtoXgXB9eq5OM1mndunWuxYPqa/2m2mbD/abBuT60/eu46O0Xqmt++umnqMtJQXIvIKHX6vgjQ4cOdfWIcv00b948U/VNNPtsuH1p3rx57jP0GpUHAB/K7j4pAJCdOSw6dOjgpj/22GOuf21oP+lbbrkl6hwW6sPesGHDNO9RqVIl189d/580aVKq1xx33HFuejhjxoxx81588cVU0/ft2+f6b4fr160+/k899VRU5abvptc88sgjqXIFqByuuOIK93eDBg0CLVu2TPW67t27u9e9/vrrKdPOOeeciH3NR48eneazCxQoEKhdu3bK7+I99J2V20D/v+GGGwLHHHNMqvnqd7579+5U+SFC8xi89tprYfvhP/nkk4F8+fKlec3w4cMDmelbr/WcMGGC+/8HH3yQMv/nn3920wYMGOC2kXA5LIYNGxaxvLp06ZLSN3/Xrl0Rl9O241GZdOrUKexy+s5ap3jKI9YyX7duXeDoo49Os2yNGjUCN910U9Q5LBYuXOiWLVGiRGDv3r0x/U6huSOiLctE/zaRclhs3rw5Zf8PfdSqVSvN8rH+bqontN2Fe//cuXMHPv/88wzL0PvMhx9+2L0m9H3uuOOOVMs/9NBDKftxOGeeeWYgV65cgeXLl0e9n4XWmeLtd23atHF/z58/3/2t14SjuqZIkSJhc1icccYZgXbt2qX5bkWLFg3Mnj077hwWixYtChx55JFhf4fmzZunWrZOnToR9+FXX301ZTmVSUb1bXrr9PHHH7vvF+71PXv2TJUbxCsn/XbdunVLs7zK9bvvvgvEQtuqXqvjmH47HbdUXygvR2bqm2j32dDteuzYsany4lx00UUxfQ8AWYOABYAcLxEBC50QKvnaSy+9FHj//ffdxaZOZHSS/ttvv0UVsNCJnqaXK1fOnQjpfRQMqFChQsoFRiICFnfddZebXq9evcC4cePc57z33nuBO++8052EKmHm0qVLMyy3KVOmuPfp3LlzyjSdoGvas88+6/5WokOdIHonklKlShW3zPr161P9Bnofla8u3vXeSuyp9VEZBl8wewELlYnm6TPefvtt99l//PFHSsBC88uXL+8ugj766CM3Xxet3gWvEoIq0PThhx8Gpk6d6n4zBVv0WLNmTZqTU72fkoW+8sorgRkzZriAiC6e9P108ZiZgMXff//tXt+jR4+U+YMGDXLzdaESKWDRr18/V2b6jadNm+bWRxca1apVc8trOxSdaOt7K2ik6e+++677W48ffvgh5f1Uhpqv796/f//AG2+84d535MiR7mIpeFvNTHnEWubnn3++W75q1aqB8ePHu21Uv2OpUqVS9oVoAhZaNy172mmnBWIVGiiItiwT/dtEClh408uWLesCAtpvVLbe55x44omplo/1d1OZe/WRvoveX+t43333uUCgvltGgj9T9aM+U+9x7bXXus/UPG0LHu0PhQoVCtStWzfNe+n7a3+PNsllpICF6jZtV5p3++23xx2w0HfTQ0Fl1aPPP/+8K3vNU10XXPfFGrBQAmPv91SwzjsuaBvSuoYm7dW+qov4iRMnuqCC6kUl1VSdrrrUS366YcMGVyfqffUe3nanx9q1a9NdJyW8LFmypJvXqlUr9321LWhf9pItBwecgsupcOHC7jijcpo8eXJKOSngEwutm9Zbr61cubLblr744otM1zfR7rPhtuvzzjsv8PLLL7vPV2JQAP5DwAJAjpeIgIXuwoS65ppr3Dyd0GUUsNCFgk72dCK1atWqVMsr4KETvUQELHQCrBNXXRDs2LEjzWt0gqvX6OQ7I2oRohNFBQU8usDV61esWOH+1l09/e2NvvDLL7+4v3UBEGzLli1hP8N7vS6SQgMW3h2uUF7AIlxZ6mRbF2a6M6nWIKEUaNFr77333jQnp1dffXWa5bt27ZpysZmZgIV34amLtK1bt7qLWJ0ke6OrRApYRCovfV8tr+0y2Lnnnuum6+IhlKZp29Nj3rx5aeYryBN80RVrecRa5row1XZVvHjxVEEt72IzloCFLlYye+czUqAgvbJM9G8TaT1Wr17tyijSNl66dGn3muA7/LH+bqo3NE1lGGr//v2Bbdu2BTLifaYu6kLvUmufDnexqtZZoeseHMjThWcs+5nuvKuO10OBFq+lh8ro999/jztgES4orAtqr25+4YUXMh2wePTRR930Ro0auRY5of7999+otj2vrINbWej30zT9RuFEWqdRo0alBAG1HYQLECpQE1pO2l5nzpyZankFUI444ghXtrGO2KJWGV6AJHikk8zU8bHus952rRYjAPyPpJsAYOb6DIdSzgT1Ow7X9zzUN9984/ofK5HYUUcdlWpetWrV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6123jd69e9v111/vXh9Mf3/99dd277332i233OJaZzRr1swFLpSkEwAAAAAA5MCAhVohjBgxIuXvcePGWatWrezll1+2Rx55xM2PNWDRuHHjlOFBM6JAhhfM8FSpUsVGjhwZ9eeVKVPGtdQAAAAAAACHScBi4cKF1qhRo5SuHMoH8corr7igQf/+/dNNWgkAAAAAAHBIclgoZ4TyPcicOXNc0OKUU05xfxctWtR27NgR70cAAAAAAIAkE3fAom7duvbkk0+6QMWjjz5qDRo0cMktZcWKFXbMMcckYj0BAAAAAEASibtLyG233eYSZA4ePNgCgYA988wzKfPeeust69KlS7wfAQAAAAAAkkzcAQsNB/rll1+6IUyVy+Lss89OmacROzQ6BwAAAAAAQJYGLKRFixbuEeruu+9OxNsDAAAAAIAkE3cOCwAAAAAAAF+2sJg9e7Y9/fTTtmrVKtuyZUua+YsXL07ExwAAAAAAgCQRd8Di8ccft759+1q5cuWsdu3aVr169cSsGQAAAAAASFpxByweeOABu/32223EiBGWN29CGmwAAAAAAIAkF3cOi/Xr19sdd9xBsAIAAAAAAPgnYNGkSRPbsGFDYtYGAAAAAAAgEQGLcePG2dChQ2379u0UKAAAAAAASIi4k0688MILtm3bNpdss1WrVlaxYkXLlStXqmXGjx8f78cAAAAAAIAkEnfA4rnnnkv5/0cffRR2GQIWAAAAAAAgSwMWdAUBAAAAAAC+y2EBAAAAAADgy4DF6tWrrVevXnbUUUdZsWLF3LP+XrNmTSLeHgAAAAAAJJm4AxYrV6604447zl588UWrXLmynXXWWValShX3t6avWrUqMWsKAAAAAACSRtwBi0GDBlmtWrXsl19+sS+//NLefPNNmz17tvu7Ro0abj4AAAAAAECWJt389NNP3UOtKoLp76eeesratm3LLwIAAAAAALK2hcXWrVvtyCOPDDtP0zUfAAAAAAAgSwMW1atXtylTpoSdN23aNDcfAAAAAAAgS7uE9OzZ06699lo3IkinTp2sQoUKtmHDBnvnnXfs3nvvtTvvvDPejwAAAAAAAEkm7oDFHXfcYYsWLbKhQ4e6R7Du3bvbbbfdFu9HAAAAAACAJBN3wCJv3rw2efJk69+/v33yySf2999/W+nSpe3000+3448/PjFrCQAAAAAAkkrc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JBN3wKJBgwb24osvhp2n6fXr14/3IwAAAAAAQJKJeVjTUH379nXJNf/991/r0aOHVa5c2f744w975ZVXbMyYMTZx4sTErCkAAAAAAEgacQcsLr30Uvvxxx9t9OjRNmrUqJTpuXLlsv79+1vPnj3jev9du3a5bifFihWzPHnyxPz6/fv3u0f+/PkTuiwAAAAAAPBxlxB58MEH3fCmCljceuut9sADD7jhTBXEyIwtW7bYs88+a2effbaVKFHCSpYsGfPwqN9//721adPGChUq5IIdjRs3to8++ijuZQEAAAAAQA4JWIhyVQwYMMAFKW677TarV69ept/rrbfesi+//NL69etnAwcOjPn1v/zyi5166qlWvnx527x5s0v+qeDHeeed5943s8sCAAAAAIAcFrBIJOXE8FpY5M0be6+V4cOHu64dTz/9tB1xxBGui8d9991nNWvWdMGUzC4LAAAAAACSOGARj0AgYO+++661bt3aBSCCc2q0b9/e5s6da+vXr495WQAAAAAAkHUOu4DF2rVr3YglxxxzTJp5tWvXds+LFy+OeVkAAAAAAJCDRgnxm3/++cc9Fy9ePM08b5q3TCzLhrNx40bbtGlTqmkrV650z/v27XMPJI/8uQPZvQq+wHYPIBh1Y2LqRsqR44ufsD2yPQJZdTxMeMBi586dVrhwYcsu6s7hdfcI5U3zloll2XAmTJhgw4YNCztPyTs12klO0O2pudm9Cr7w2jUt4np9taIELCTe7Z7tMf5tkXI8iHJMDOpGf9SNHGPiL0PhGJOYupHtkfMdv2yP7NOWkH06K+k6OcsDFvPmzbOhQ4farFmzbPfu3SkX+9dee60NHjzYKleubFmlTJkyEVtG/P333+65bNmyMS8bTt++fa1Lly5pWlh07NjRDY+qIVlzgjXbIwdlkkm8vxflSDkmSiLqDrZHyjFRqBspR7+gbqQc/YS6kXL0ixI55JrTo+vkLA1YKDFlmzZt3LCm1113nT300EMp82rVquVaIYwcOdKySsWKFa1cuXK2dOnSNPM0LXfu3G5dY102HL1Wj3A0uklmRjjJDnsPELCQeH8vyvEgyjF+iag72B4px0Rhn6Yc/YK6kXL0E+pGytEv8uaQa87Mrm/cSTfVgqJ37962YMECe/DBB1PN07Ckb775ph1KatGhJoIamtTTuXNnmz17tm3YsCFl2t69e23KlCnWtm1bK126dKaWBQAAAAAAWSPugMXXX39tgwYNCpvr4aijjrLVq1fH/J4KPigIoYcCEsE5IbZu3Zpq2REjRljJkiVdS4/gIEqpUqXsoosusiVLltivv/5qV111lf311182evToVK+PZVkAAAAAAJBDAhYHDhywPHnypPwdHLjQCBqFChWK+T2///57q169unuMHz/ejdjRvn1793eTJk1SLav31/zgpiXq6qEARrVq1axdu3Z28sknu0DHV199ZQ0bNkz1+liWBQAAAAAAWSPuDi8NGjSwV1991W688cY0AQtNb9q0aczv2axZs6gzQd95553uEerII4+0F154Iar3iGVZAAAAAACQAwIW119/vV199dWuNYXyQShg8csvv9jkyZPdkJ+vvfZaYtYUAAAAAAAkjbgDFj179rRly5a5kUCUT8IbHUSBC7V8uOCCCxKxngAAAAAAIIkkZAyU++67zy677DKbPn26rV+/3sqUKWPnnXee1atXLxFvDwAAAAAAkkzCBm099thj3QMAAAAAACDbRwkBAAAAAADwXQuLtm3bZrjMzJkz4/0YAAAAAACQROIOWIQOPxoIBOyPP/6wDRs2WP369a1AgQLxfgQAAAAAAEgycQcsFixYEHb6/Pnz7f7777fHHnss3o8AAAAAAABJ5pDlsDj++OPt5ptvtj59+hyqjwAAAAAAAIepQ5p0s1GjRvbJJ58cyo8AAAAAAACHoUMasJg2bZoVKlToUH4EAAAAAAA4DMWdw0J5KkJt27bNFi1aZDNmzLDrr78+3o8AAAAAAABJJu6AxcCBA9NMy5Mnj1WrVs3uueceu/XWW+P9CAAAAAAAkGTiDlj8888/aaYVLVrU8uaN+60BAAAAAECSijuqUKJEicSsCQAAAAAAQFYk3QQAAAAAAMiWFhZt2rSJ+TWff/55vB8LAAAAAAAOY3EHLHbv3m27du2yhQsXWoUKFax8+fK2YcMGW79+vTVs2JBhTQEAAAAAQNZ3CXnppZescuXK9s0339iff/5pP/zwg3v++uuvrWLFivbKK6+4/wc/AAAAAAAADmnAok+fPnbXXXdZ8+bNU00/4YQT7O6777Zrr7023o8AAAAAAABJJu6AxZw5c6xOnTph59WtW9e+/PLLeD8CAAAAAAAkmbgDFsWKFbOZM2eGnffRRx+5+QAAAAAAAFmadLNHjx525ZVX2u+//27t27dPSbo5depU11WkV69e8X4EAAAAAABIMnEHLO677z5bvXq19evXzz2CderUye699954PwIAAAAAACSZuAMWBQsWtHfeeceNEvLpp5/aX3/9ZaVLl7bTTz89TSJOAAAAAACALAlYBI8KogcAAAAAAEC2J90EAAAAAADI9hYWl19+uXt+7rnnUv2dHm/ZWC1YsMA+//xz27dvnzVr1szatm0b1etGjhxpu3btijj/7rvvtrx5D351dWPRI5w77rjDihYtmql1BwAAAAAAWRiwCB3CNNKQpvG67rrrbNKkSdazZ08rUqSIde/e3XU5efvtty1//vzpvrZAgQIWCARSTduxY4fdf//9VqVKFbvnnntSpn/xxRcuwDFkyJA075srV64EfysAAAAAAHBIAhZr165N9+9EeP75523ChAk2bdo0O++889y0q666yho3buyCDcEBh3BuvfXWiK089D7hAhG33XYbrSkAAAAAAPAJX+awGD9+vNWuXTslWCF169a1c8891x5//HHXRSRWzzzzjOXOndsFLAAAAAAAgL/5LmCxc+dO++6776xFixZp5rVs2dINm7p06dKY3nP58uX25Zdf2jnnnGNVq1YNu8yTTz5pw4YNs8cee8yWLFmS6fUHAAAAAAA+GdZ09uzZ9vTTT9uqVatsy5YtaeYvXrw46vf6/fff7cCBA1apUqU08ypXruyeV69ebQ0bNoz6PZ999ln3fPXVV4edryCGghRly5a1N998066//nq77LLLbOLEiSnJOcPZuHGjbdq0KdW0lStXume1AslMS5DskD936nwfySre34typBwTJRF1B9sj5Zgo1I2Uo19QN1KOfkLdSDn6xb4ccs2Z2fWNO2ChLhp9+/a1cuXKuW4c1atXj7uFhZc4M1TBggVTLRON/fv32wsvvOACIMFdTDwXXXSR3XnnnZYvX75UXVJuuOEG9100okgkyrOhVhnhbNu2LWzwxo+qFSVgIfH+XpTjQZRj/BJRd7A9Uo6Jwj5NOfoFdSPl6CfUjZSjX2zJIdecwdfJWRqweOCBB+z222+3ESNGpNsaIVqFCxd2z3v27Ekzb/fu3amWicaMGTPszz//tMGDB1uePHnSzK9Tp06aaWph8cgjj7jkn+kFLBSo6dKlS5oWFh07drRixYpZiRIlLCdYs53R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fsG2SDn6CdvjoZcr4I3tCQAAAAAA4BO+zGEBAAAAAACSGwELAAAAAADgOwQsAAAAAACA7xCwAAAAAAAAvkPAAgAAAAAA+A4BC+QIU6dOtdtuuy27VwMAfGXz5s3ZvQqAs2fPHuvWrZstX76cEgGA/7d3717btm0b5REHAhbIEcGKLl262Lhx42z9+vXZvTo51tatW23WrFn27bff2v79+7N7dXKsn376yd544w2bO3cu5RiHtWvXujLUdonMmT59ujVt2tS2b99OEcZh5syZ1qFDB7vqqqsoxziCFSrDyZMn24MPPkg5xnmM+eSTT+zPP/+kHJGtRowYYb1797ZAIMAvEUewQtcw1IvxIWAB3wcrVFleccUVbqd/4oknsnuVcqRHHnnEKleubG3atLFmzZrZkUceac8880x2r1aOsnv3buvZs6c1atTILr30UmvZsqXVrl3b3nvvvexetRxFJ+Ht27e3qlWrujKsVKmSTZo0KbtXK0cGK3SB/fbbb1vRokWze3VyrLFjx9q1115rN954o02cODG7VydHByvKlCnjji0vvfSS/f3339m9WjmObsicfvrpVqdOHWvbtq2rG1VX/vrrr9m9ajnKxx9/bH369LEDBw5k96rkaDq3GTJkiD311FOuPAlaZD5YkT9/fleWyDwCFvB9sGLGjBn20EMPWYkSJVzAQhUAovfwww+7ctMdm7/++suee+4527Fjh/Xq1ct69OhBeUZJFzU7d+50TfB1R1t3EnWirhNKDkTR2bBhg5100kl2zDHH2MKFC90JkS5ytC1+88037NYxBitUfgpAInOWLVvm7iB+/vnn7kIxV65cFGUcwYrnn3/ebrjhBtu1axfBnxipubh3Q2Hjxo2ulYW612gfV5D8o48+YtuMwurVq932+OSTT9qVV15J0CIOJUuWtNy5c7vgGUGL+IIVr776quXNmzeenwMBwIfefffdQIUKFQLfffddyrTbbrtNbdICL774YrauW06ycePGQMGCBQPffvttqukqV01XeXbs2DGwf//+bFvHnOCXX34J5M6dO/D333+nmr5p06bAySef7Mrx9ttvz7b1yykuvPDCwI033phq2qeffurK76qrrsq29cpJ3nvvvUD58uUD8+fPTzX9t99+CzzxxBOBRx99NLB06dJsW7+cZMCAAYG+ffum/L179+7A/fffH2jevHmgadOmgSFDhgS2bduWrevoZyqvs846K3DxxRcH9u3b56b9+++/gWLFigWqVauWMg0Zu+uuuwKnnnpqmulXXnmlqx/z5csXmDlzJkWZgV69egXuvffeQLt27Vy5XXbZZZzfZNLWrVsDuXLlCsyePTulPK+55prAgQMH2A4zsGfPnsD5558f6Ny5c+C///5Lma5z7Y8++ijw8MMPB1577bXAzp07KcsoEbCA78ybNy9NsELWrl3rDtrNmjXLtnXLad588013kAlXKepEXQcjzddJOiKbOnWqC1hs3749zTyVbevWrV05vvTSSxRjBIsWLXJltGLFijTzjjzyyMBxxx1H2WVg7969gVq1agWOOOKIwJw5c9w0nTwOHz7c1Y158uRxZaxt9c4776Q8M9CpU6fAwIEDU/ZjBR8VqOjTp0+gQYMGriz1/Ndff1GWYXTr1i1VsMLTr18/V3Y6/iA6J554YuC6665LM33Lli1u39axulSpUoENGzZQpBH8+uuvgSpVqgR27NjhgmkELeJ31FFHBcaPH5+qPL2ghY5BP/74I9tjGG+88YYrKwUtdNyWVatWufMc7cveubfOfRYuXEgZRoGABXxHJz8rV64MO69Hjx5uJ/dO1pHxhbbK66233kozb8yYMYFzzjkn0KpVq0DRokVdNB3hff/9964cdQc7HJ1UHn300e6EknIM7+WXX3bbWThnnnmmOzFCdK19qlatmhK0uPnmmwPHH3+820Z190YB38aNG6e7veIgnXg3adLE/V/l2LVr15S7sTohVwsLlaMuzJGWgo/hWlHowlHBMx1bEB2V1THHHJNycROscuXKgZEjR7oyveWWWyjSCBTMef3111P+JmgRP7XAvfTSS9OU5wUXXBAoV65c4OOPP2Z7jEAtfbyghY7bCqbdc8897hxR54wPPfRQIH/+/G7/Vss0pI+ABXIUdW3gBDJ6qhh1Ea0Dy08//ZQyXSflJ510UuDJJ590zccV7Q0X1EAg5eKlfv36gdKlSwfWrFkTtljUbFLb5sSJEym2CJ555pmw0y+66CJ3EY7YghZFihRxz//880+q+WoRoJMg3b1BZO+8847bZydNmhQoU6ZMYMGCBWFbYeTNm9fdtUX0unTp4sr2hx9+oNiiMG7cuJS718FBIB1X1AVs165drrsDgd3YpBe0oCtsxoYOHRqoXbt2qvJU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vdudUNKyIr5yVBNJWlbEV4aiISZpWRF/OV577bW0rIizHDU0rFqd0bIi/nKkZUV8ZSgNGjSgZUUCyvHKK6+kZUUCzhsnT55MNxCfyBUgNTkOAY0eoOb3dAPJvF27dtl7773nutUg81atWuW61WgIWWTeJ598YrVr16YbSJzUPU53vugGknk6kdRoSTrGIPPWrFnj6sc2bdpQjHFQoKJmzZp0A4mTRla54IIL6AYSB3UzfPvtt61bt27x/hxJbe3atbZ8+XI77bTTsntV8P8IWAAAAAAAAN8hhwUAAAAAAPAdAhYAAAAAAMB3CFgAAAAAAADfIWABAAAAAAB8h4AFAAAAAADwHQIWAAAAAADAdwhYAAAAAAAA3yFgAQAAAAAAfIeABQAAAAAA8B0CFgAAICqXXHKJValSJaplr7/+eitRokSG07JKdn42AADIHAIWAAAchr7++mvLlStXyiN37txWunRpO+ecc2z27Nl2OOrVq5eVKVMmu1cDAAAkCAELAAAOY6NHj7ZAIGD//fefffrpp/bXX3/Z6aefbnPnzs3ydRk/frxt2bIlyz83uz8bAABkDgELAACSQJ48eaxRo0Y2ceJEF7zQBTwAAICfEbAAACCJHHvsse75999/d89t2rSxE088Mc1yI0aMcF1Jtm/fnmbexo0b7cILL7QjjjjCdcG45pprbOvWrZnOI/H999+79ytXrpwVLlzYjj/+eHvttdfSrIv3KF68uGslohYjnrZt29ozzzzjWpAEL7t58+Z0P/v999+3U045xYoWLWpFihSxk08+2aZPnx42d4daaFx88cXu80uVKuW6oOzcuTPd7zxy5EjXHWfFihVhW79oHZcuXZqqLDp27Oi67xQoUMDq1atnTz75ZKrXRVMeweutMujRo4eVLFnSlS0AADkFAQsAAJLIzz//7J6rVq2aqdcfOHDAevfubddee62tXbvWXnnlFZs6daqdd955bl6sdJGtgIlafXzyySe2adMmd4Gu9/zjjz/cMoMHD3bdWvTYt2+f/fDDD1arVi0799xzbdmyZW6ZmTNn2lVXXeUu9L1l9Ugvp8XkyZPdezRo0MB++uknW758uTVt2tTat29vL7/8cprvfd1119nll1/uvvdzzz3nvvvQoUPT/X5XX3215cuXzx5//PFU07Vu+p767nXr1nXTZs2aZS1atHCBCnXZUVkMGjTI+vfv74IUnmjKI3i9FVDSeq9atcoFbgAAyCkIWAAAkAT2799vCxcudBfQefPmdQGHzPjzzz/tsssucy0a1MLizDPPtHHjxrlEnu+++27M79enTx+rWbOmvfPOOy5woFYOChooGFC5cuWwXVuOOuood7FfrFgxe+GFFzL1PXSxf+utt1qTJk1swoQJriWCPu+RRx6xE044wQUJVGbB31stFs444wz3ueeff75169bNnn766XQ/R61G1HpEAY5du3alTP/www/tl19+cUEWj36T2rVru+9+zDHHuPJVi44BAwa4lhr//PNPzOXhrbd+J7Ww0G8HAEBOQcACAIDDmC521W1Ad/lbt27tug98/PHHrutDZqh7g1pTBOvQoYP7jNAuCRlRNwk9LrroInfhHYm6m+h76CK+YMGCKV0h1AJh5cqVmW5popYSnTp1SjNPAYYNGzbY4sWLU6YpyKOL/mD169d36+Z1O4lELTMUbAju5qIgiYIz+u6i4IVaR2h9QstCwaHdu3fbvHnzYi4PTQ/9vQAAyCkIWAAAkASjhKhrgC6adWdfeSsyoteEo9wNungPpotmtQbI6MI9XC4MCdeSIpgu4tVyYOzYsa7FgL6L1q969equK0lmKNeFVKhQIc08b1rw91FLidBAgr6zZDT6yEknneQSnnrdQtasWWMzZsywrl27ulYRsn79evc8fPhwV776LD0UIPKCS946x1IeZcuWtfz588dcPgAA+AEBCwAAkphaXGzbti3NdC9/RKi///7b5U0ItmfPHnfXX/kjYqGL6fQ+S9atW+dyW6iLRrt27Vy3BrUa0DqohURmKfAiakkRypsWnP9CnxmPvn372vz5823BggWu+4a6mwR3B/E+a9SoUe67ab4eXjBCDyXOjLU81LIGAICcioAFAABJTPkjVq9enWo0EN2l/+ijj8Iurwvo0FE0lLtCF9QaqSIW6tJw9NFHu+SXwfkiwlEiymDK8xAaOFEXCwVPoqFcEWrZMWXKlDTz3n77bStfvrwboSNRvNFFlO9Do5no89XyInh9VB5vvvlmVMlLoykPAAByOgIWAAAkMd3lVzJIJaBUl4Nff/3VTVMyynAqVqxozz//vLvLr5YZyodx0003WcuWLV0iylg98cQTLn+DujksWrTIduzYYd999527wFfLi0qVKtlxxx3nLvQ15KdacuiiftKkSS7YEppTQoGXzz77LMMAiLpaqDWDWjxo5Ax9llov9OvXz+bMmePmhXZ9iYeCKUp4+dJLL7kWHMGtKzxPPfWUG/FDyTx//PFHN2Squo/o+2roVX2nWMoDAICcjoAFAABJTK0IdBGthJm6GL7gggvs0ksvjRiw0IW+cjE8+uijbnldXGs4TbW6SC9xZiSnnXaaCxCoW4NyayhXhEYO0dCiXm4LtXhQDggte+SRR9obb7zhWmWEBhQUENC6d+7c2XWF0Huml1dDXSw0fKou/NW6QUODKrGlWoz07NnTEk3dQrROWu9w76+kqAqgaP7ZZ5/tuntomgIS999/f0r5RlseAADkdLkCkbJqAQAAIGHUiqNatWquJYqGcQUAAOmjhQUAAEAW8PJTXH311ZQ3AABRoIUFAADAIaZcFKeeeqpLvPntt9/GPeoIAADJgBYWAAAAh9Cxxx7r8mNo5JFXX32VYAUAAFGihQUAAAAAAPAdWlgAAAAAAADfIWABAAAAAAB8h4AFAAAAAADwHQIWAAAAAADAdwhYAAAAAAAA3yFgAQAAAAAAfIeABQAAAAAA8B0CFgAAAAAAwHcIWAAAAAAAAN8hYAEAAAAAAHyHgAUAAAAAADC/+T+vZztFcPUwKAAAAABJRU5ErkJggg==", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Plot the number of unique citations by publication year.\n", + "\n", + "if (\n", + " unique_citations.empty\n", + " or unique_citations[\"publication_year\"].dropna().empty\n", + "):\n", + " publication_counts = pd.Series(dtype=\"int64\")\n", + " print(\"Publication-year data is unavailable.\")\n", + "else:\n", + " publication_counts = (\n", + " unique_citations[\"publication_year\"]\n", + " .dropna()\n", + " .astype(int)\n", + " .value_counts()\n", + " .sort_index()\n", + " )\n", + "\n", + " fig, ax = plt.subplots(figsize=(9, 4))\n", + "\n", + " ax.bar(\n", + " publication_counts.index.astype(str),\n", + " publication_counts.values,\n", + " )\n", + "\n", + " ax.set_title(\n", + " \"Unique Warren-Matched Citations by Publication Year\"\n", + " )\n", + " ax.set_xlabel(\"Publication year\")\n", + " ax.set_ylabel(\"Unique citations\")\n", + " ax.grid(axis=\"y\", alpha=0.25)\n", + "\n", + " plt.xticks(rotation=45)\n", + " plt.tight_layout()\n", + " display_figure(fig)" + ] + }, + { + "cell_type": "markdown", + "id": "d452d11c", + "metadata": {}, + "source": [ + "## 9. Summarize Analysis Types and Methods\n", + "\n", + "The complete citations table is used for this section because each row\n", + "represents a citation, dataset, and analysis-type combination.\n", + "\n", + "The analysis-type counts describe the categories returned by PetDB. The\n", + "method counts summarize the analytical methods recorded in the citation and\n", + "dataset metadata.\n", + "\n", + "These values describe metadata records rather than individual laboratory\n", + "measurements." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "06c3e036", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Analysis types:\n" + ] + }, + { + "data": { + "text/plain": [ + "analysisType\n", + "Inclusion Analysis 1\n", + "Rock Analysis 22\n", + "Mineral Analysis 48\n", + "Name: count, dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Most frequent analytical methods:\n" + ] + }, + { + "data": { + "text/plain": [ + "methods\n", + "THERMAL IONIZATION MASS SPECTROMETRY ISOTOPE DILUTION 3\n", + "INDUCTIVELY COUPLED PLASMA MASS SPECTROMETRY 4\n", + "ION MICROPROBE 4\n", + "POINT COUNTING 5\n", + "LASER ABLATION INDUCTIVELY COUPLED PLASMA MASS SPECTROMETRY 7\n", + "THERMAL IONIZATION MASS SPECTROMETRY 7\n", + "CALCULATED 26\n", + "ELECTRON MICROPROBE 38\n", + "Name: count, dtype: int64" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Count analysis types and individual analytical methods.\n", + "\n", + "if citations.empty:\n", + " analysis_counts = pd.Series(dtype=\"int64\")\n", + " method_counts = pd.Series(dtype=\"int64\")\n", + "else:\n", + " analysis_counts = (\n", + " citations[\"analysisType\"]\n", + " .dropna()\n", + " .astype(str)\n", + " .str.strip()\n", + " .replace(\"\", np.nan)\n", + " .dropna()\n", + " .value_counts()\n", + " .sort_values()\n", + " )\n", + "\n", + " method_counts = (\n", + " citations[\"methods\"]\n", + " .dropna()\n", + " .astype(str)\n", + " .str.split(\",\")\n", + " .explode()\n", + " .str.strip()\n", + " .replace(\"\", np.nan)\n", + " .dropna()\n", + " .value_counts()\n", + " .head(8)\n", + " .sort_values()\n", + " )\n", + "\n", + "print(\"Analysis types:\")\n", + "display(analysis_counts)\n", + "\n", + "print(\"\\nMost frequent analytical methods:\")\n", + "display(method_counts)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "c6a2b9c9", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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vHhxkycPb3H6hmuG9xMAAAAAAEBsmHEAAAAAAAAAAABcJA4AAAAAAAAAAICLxAEAAAAAAAAAAHBRAB0AAABA3H766Sdz5ZVXRrXt448/bn755RczatQo88orr5jatWtn2OaOO+4wixYtMp999pl73xtvvGGf4+f+++83HTt29NxWaz6VK1fOnHnmmebqq682+fLlC3pu4PZjx4411atXz7D/AQMGmGXLlpnjjz/eTJw40bMN77zzjhk+fLhp0KCBb1udY/mdu59wz/vrr7/M66+/bubOnWu2b99u17lq2bKlueqqq0yZMmU891OkSBEzefJkU7BgwaDHb731VrNmzRrz/vvv+x472te7Z8+eZtCgQVH3k/r+pZdeiqo/dK4DBw40v/32W1BbHatXrzbjxo0z33//vdm/f7+pWLGivT4uvfTSDGuAaT9Lly41bdq0MUOHDg167J9//jGnn3666datm70uw3H2U6FCBfP2229neFyvU/fu3e0+L774YnP99dd77uemm24y33zzjbn22mtN7969fY+3ZMkS+7qvXbvW/v/EE080559/vmndunVC2yZyLKcPHAUKFLDvJ/V7u3btfLcL9fHHH9trNND69evttajPBq3nVrZsWVOjRg1zySWX2GspnusntK3VqlWzr43zWeIlnmsrnmsimj567733kn7O6t8bb7zRXH755ea6667LsB+9Drou9Xo+8MADUR0bAHDsInEAAAAAIG4Kig0bNizovn79+pk//vjDBrYCnXTSSWb27Nlm3rx5Zvfu3Z77W7FihX080IYNG+x9o0eP9gy4B94Xuu2BAwfs/xWQVXBRxw8M8Dnb58qVywZHH3rooQzHVnAuLS3NBtn8PPnkkzaYOH/+fBtk9trWOZbfufvxe96XX35pLrroIhu8HDx4sA2kbty40bb3kUcesed73nnnZdiPKHh/1113Be1v+fLl5rvvvgt7bK/X23H48GGbsFi3bp0NLMfST0rsBCZtdP2ce+659qaERigFQJXECPXoo4/aRJKO36dPH1OiRAl7PB1L7Z46dWrQcbQfnd+CBQts8LdRo0buY+np6faxOnXqeJ5LaHu0jyNHjtjAf/PmzYMeV8JJ+1KQuFmzZp772LJlixkxYoS9PpVk80sc3Hffffb1veKKK2x/FypUyHz44Ye2D6+55hrz/PPPx7VtosdS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Vq5cadu+a9cuU758eXueDRo08Nw22vNMT083zzzzjKldu7bp3Llzhv1s2bLFTJw40Z6rzjnVfZBon3tZs2aNbcOff/5pSpYsaSpVqmS3L1iwYMR2AQCyDxIHAAAAAADkEMOGDTObNm1yA81bt241t99+uylRooSZPHmyadKkibvtvHnzTP/+/c22bdtMhw4dTNGiRc0rr7xiA/J33323ueuuu2zyIXDfhw8fDgpi674VK1aYihUr2oB0oUKF3Mf27NljbrrpJnPLLbfYILYC1evWrXMf1//Hjh1rA+CtWrVy748UwF60aJENqq9evdq0a9fOHnvmzJn2VqNGDTNy5MigoHks5/nPP//YNl9yySWeiQO1X48PHTrUTRyksg8S7fNA3377rRk4cKBZtmyZOeOMM2zyREmX2bNnm927d5tevXrZvgMA5AwkDgAAAAAAyEHKli1rhg8f7v5/7dq1plGjRuayyy6zwXInmH7WWWfZ4PjChQtNkSJF3O0VyL766qttAmLEiBERj6dR+9r26aefNvfcc4/vdt27d7c3x6pVq+yx1A4Fw6Mxf/58u70SBjNmzLAj5h1//PGHTSjofJygeTLP82jpg1iO5/jqq69sn+n266+/mlKlSrmPaUbDq6++au69914SBwCQg7DGAQAAAAAAOVj16tXtSHsFqTds2GBH1V911VWmTJkytuxNYDBdNLr9P//5jw0iq7ROJLVq1TIXXniheeyxx+yo/lRx2q3EyKRJk4KSBqL/q4xQt27dgrZP1nkeDX0Qz/HUD3369DHlypWz/RaYNJBcuXKZvn37mk8++STFrQYAHE1IHAAAAAAAkMM56xuo7I1K06jEjUbbFy5c2HN7BdRFJX2iodI9qq//0EMPmVRRu3/88UebDPBrt0oOaX0CZ/tkn2dW90E8xwvst8CyRqHq16+fglYCAI5WJA4AAAAAAMjBVEf/iy++sIvgVq1a1XzzzTf2/ubNm4edpaAR/M620YyA1zoCL730kg3Wp0I07Y51+1jPM6v7IJ7jOeemBZATofUytLZC4O2nn35KaJ8AgKzDGgcAAAAAAOSwRIGzxoHK2EyYMMHs37/f1tJXWRo9LlowORw97mwbjQceeMCMGzfO3Hnnnebdd981yea0pXjx4jFtn+zzzMo+iOd4fv2mhZHHjBkTdN/5559vk0tenn/++UybTQEASD1mHAAAAAAAkIP8/fffZt26dfam+vZaPPfnn382Z599truwruzevTvsfvR40aJFoz6u1hK4/fbbzXvvvWcWLFhgks1pS6R2O1J1nlnZB/Ecz+mHPXv2BN2vslXOdTJ9+nRz0003uYtnexk4cKBZvnx50G3KlClJPiMAQGYhcQAAAAAAQA6ixYM140C3YcOGmX79+tn7HA0bNrT/fvfdd7772Lx5sy1N07hx45iOffPNN9uSSLfeeqtJNqct33//fVTbx3OeefLksbeDBw96bq81BaRgwYJZ0gfxHK9Ro0b236VLlwbdrxJNznVy2WWXRTyOrqF69eoF3WrWrJmkswAAZDYSBwAAAAAAwNW+fXtTvnx58/rrr9tR515Gjx5t/7388stj6jkF1B9++GEzf/58Owo+mc466yxTsWJFW3LJr92yZcuWhM7zhBNOsIsJe1m9erX9t0aNGlnSB/EcT/1WoUIF22+agQIAgJA4AAAAAAAArvz585sRI0aYVatWmRtuuMEcOXIkqHdmzZplhg4darp37266du0ac89deeWVpn79+rb+frLbrTr7KqczePDgDMmAQ4cOmTvuuMO88cYbCZ1n7969zQ8//GBmzpwZtP3evXvNiy++aIPwCsZnRR/Eczz1w6hRo+xixv/5z39sKatQoX0DAMj+WBwZAAAAAAAEufDCC82bb75pA/CzZ8825557rq3zv2jRIlu3XsFzBenT0tJi7jktwPz444+bc845J+m9rnaqfQMGDLCBff1fsxBUckij7RXcf+211xI6Ty00vGzZMtv+Sy65xNStW9f88ccfZvLkyWbfvn32eeFKFaW6D+I5ns77/ffft+sUaD2Dbt26mcqVK9v+UiJm2rRpNvGg+wAAOQOJAwAAAAAAcggFwqMdPX7ppZeaLl26mI8//tgudKvgeNu2bc2QIUPMiSeeGNW+dV/evHkzbNu5c2fzyCOPmG3bttl9elGNfc0EaN26tYmFZgh06tTJfPrpp+bbb7+1xzj++OPNc889Zzp27GhH2CdynjqfSZMmmcWLF5svvvjC/P7776ZQoUJ2doIC7vo5s/ogmX2utmuB7M8//9ye2/bt203x4sXt8+699153TQgAQM6Qlp6enp7VjQDioWmUGvFQpd8Ik1aqGp34/+XLlW6qFEk36/ekmUNHYh/9k50l0jfrhnUx2ZWmcO/cudN+KdBCb6BvYr1uVMtXn8f6oq1F8AAAAIBj+Xv7sfT9j+9z9HN2wbWcOZ/HsXxvZ40DAAAAAAAAAADgInEAAAAAAAAAAABcJA4AAAAAAAAAAICLxAEAAAAAAAAAAHCROAAAAAAAAAAAAC4SBwAAAAAAAAAAwEXiAAAAAAAAAAAAuEgcAAAAAAAAAAAAF4kDAAAAAAAAAADgInEAAAAAAAAAAABcJA4AAAAAAAAAAICLxAEAAAAAAAAAAHCROAAAAAAAAAAAAC4SBwAAAAAAAAAAwEXiAAAAAAAAAAAAuPL8+yMAAAAAAACQXFMHtzaNGjWiWwHgGMKMAwAAAAAAAAAA4CJxAAAAAAAAAAAAXCQOAAAAAAAAAACAi8QBAAAAAAAAAABwkTgAAAAAAAAAAAAuEgcAAAAAAAAAAMBF4gAAAAAAAAAAALhIHAAAAAAAAAAAABeJAwAAAAAAAAAA4CJxAAAAAAAAAAAAXHn+/REAAAAAAABIru4j5pq0UhuPim5dN6xLVjcBAI4JJA5wzJs6uLVp1KhRVjfjqHH48GGzc+dOU7x4cZMnD29x+gYAAAAAAACIDaWKAAAAAAAAAACAi8QBAAAAAAAAAABwkTgAAAAAAAAAAAAuEgcAAAAAAAAAAMBF4gAAAAAAAAAAALhIHAAAAAAAAAAAABeJAwAAAAAAAAAA4CJxAAAAAAAAAAAAXCQOAAAAAAAAAACAi8QBAAAAAAAAAABwkTgAAAAAAAAAAAAuEgcAAAAAAAAAAMBF4gAAAAAAAAAAALhIHAAAAAAAAAAAABeJAwAAAAAAAAAA4Mrz748AAAAAACA7ueKKK8w///xj3njjjQyPLVu2zN6/fPlyc+DAAVO5cmXTuXNnc9FFF5lcuXJl2M/atWvt4/fcc0/QY3/99Zfp0qWL6dmzpxk0aFDE9mg/tWrVMq+++mqGxzdv3mwuvvhik56ebq6++mp7i+Zcfv75ZzNhwgSzZMkSs2vXLlO+fHlTs2ZNc9lll5natWtnOL6kpaWZAgUKmBo1atjt2rZt69vuePoq8BhqS+/evc1pp52WZW2RvHnzmooVK9r99+nTxx4z3Pah5s6d6/sYACB7IXEAAAAAAEA2pUD64cOHg+5TUP7uu+82TzzxhA329+3b1xQtWtQsXrzYDB482N7//vvv2wBz4H5WrFhhvv76a3PppZfaALfj77//NvPmzTMtW7aMqj0rV66026sNSiAEGjt2rFmwYIFNELRv3z7iuciDDz5ohgwZYs455xzTo0cP224lICZOnGgefvhhM3r0aHuOzj52797tJh+2bdtmExinn366GT58uLnhhhuS0leBx9i6dat54YUXTKtWrczIkSPNwIEDs6wtBw8etIkH7Udt+eqrr0yhQoV8twcA5FwkDgAAAAAAyEGefPJJM2zYMBvMvvbaa937FahXUuCUU04xXbt2tUHlfPnyuY9XrVrV7Nixwwad33rrrbiP36BBA/P777+b1157zQb8A+m+Dh06mI8//jjqc3nooYfMc889Z4PngXr16mUmTZpktmzZEnR/wYIFTevWrd3/d+/e3dStW9fcd999ZsCAAUHnHG9fhR6jW7du5qSTTjJ33nmn6devn7ttVrTlrLPOsskC7WPatGnmkksuCds/AICciTUOAAAAAADIIf788087Cr9NmzZBwefA5MAjjzxivv32Wzv6P1Dp0qXNbbfdZoPxmnkQrzx58tig/rhx48yRI0fc+xXwXrVqlbnqqqui2s/OnTtt0kBld0KTBg6VPfI6z0C5c+c2LVq0sCPt169fn5S+CqUSQZqREXqMrGiLHH/88fbfvXv3RtwWAJAzkTgAAAAAACCH+OSTT8yePXsyjDIPDbar5v57772X4bFbbrnFVKhQwdx6660JtUPJgQ0bNpjPPvvMvU9lejTaXoHzaMyYMSPiuUj+/Pkj7mv79u323+LFiyetr/yOUaxYsSxti8oVvfLKK7Zf2rVrF7HdAICciVJFAAAAAADkEBrRL3Xq1PHdRoFt1clfvXp1hscKFy5s1xTQqPepU6fa0jrxqFevnmnatKlbmmj//v22/FHowsvhOO0LXPw4Hp9//rkNzHfs2NHOqkhWXwWaOXOm+fTTT+36BWXLls3UtmjmglN66NChQ+ann34yJUuWNF9++aWpVq1ahv0Ebh+oSZMmtiSUH63loHUaAulYAIBjE4kDAAAAAAByiAMHDkQ1Cl+PO9uG0qK8zzzzjLnjjjtMly5d4m7LlVdeaW6//Xaza9cuW2tfI+p79+7te9x4zyVcYFyj+3/88Ue7RsDzzz+ftL4KPIaC6Qqgn3322Xakf2a3pUyZMnZtBNGi07/88otdL+Gaa64xH330kalUqZLv9oGOO+64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AECO1qBBA9OjR48M91WuXNlcddVVGbZftWqVfeyFF17I8JwaNWqYli1bmhYtWthtFERt27atef75523AKzT4om0eeOABz3Z9/PHH9vFJkyYF3a+gzn333Wdq1aplkxd169a1gTwd/4knnrCBR+1T7XBuTZo0sfuqX79+0P2XXHJJhj5YsWKF3fbBBx/07TOn7TfffLPn+YfeunTpErHPdZ4Kbr322muexxw4cKCpXr26WbNmjQnnl19+Mf369bMJESUr1E8KyrVp08aMHTvWJn4CrV692vTs2dP2Zc2aNU3FihVtUO7WW2+1iYRAH374oT3v999/3/PYjz76qH38p59+ctui/+s1Uj80a9bMBgdr165txowZE9RmbafnhzuvwH2F3u644w7fbZs2bWrKli1rb926dTOTJ08O24d++0mk/aFOP/10+5w777zTdxtdyzovvZYKcOp10W3w4MH2fRjo/PPPt/tT27wCrnqd9dxw77lo2+UlM48fS7/Esm2ix1IfVK1a1b0m9bPeV/3797f78dsu9KbgdSB9dg4fPtw0atTIJq3q1atn39N6v95zzz1m48aN5tVXXw3ah/MZXKdOnQz792urru2rr77a/PHHH559oWTukCFD7HEVWD/ppJNse7p27Wq+/vrrpF4T0fRRKs5Zn+1qrz4TvXzwwQf2M/Kll17yvWYAANkHiQMAAADAg0ZUKpjtFRDyc+qpp9oglm4KlP3888/m0ksvtcEtBW81Yj4RGzZssMHbN954wzz11FM2eKR9aiSpAnlDhw61gZ1nn33WbYdub7/9tn2+kgGB98+aNSvDMU455RQb2H/99dczBNkdCu7rsWuuucb3/ANvS5YsiXhugwYNMp06dTI33HCDWbduXdBj06ZNM6+88ooNrun8/eh8GjdubH788UczdepUOyJXszaUbFACp2/fvkH7/vzzz+3rolkEep0VPFOfjh8/3kyZMsUG4n777TeTKCU91A8aXaxjKACqtuh1jHdfobfRo0f7bqtzUF989dVXNuiqpM2FF17o+/qmsv2ivl66dKkNvKqvvYKqGoWt6+HFF1+015tGd+s9pffknDlzPBNbStQpseaV2FGQNdLMk2jaFU5mHD+WfomnD+M9lkMj651rUtfcqFGj7Hs3NFEYuF3oLXDWivZx5plnmocfftjcdNNNNpmnzzz182OP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bh8EAAAAAAAAAAA4PDDjAAAAAAAAAAAABEgcAAAAAAAAAACAAIkDAAAAAAAAAAAQIHEAAAAAAAAAAAAChf/3TyBvWL9+vRs1apTbtGmTO+mkk1zbtm3dOeec4/KTb7/91o0bN84tXrzYnXbaaa5Pnz5x9928ebMbMWKEtVupUqVcy5YtXaNGjVwyWr58ufvggw/sXI877ji7Ltq0aeMKFSoUuv/06dPd+++/73755RdXo0YN17FjR1emTBmXjObNm2fnq9fNCSec4Bo2bOhatGgR2ja7d+92//73v93SpUvdkUce6Ro3buyuv/56V6BAAZfM9Fq59957ned5buDAga5ChQop9lmxYoV744033Pfff2+333TTTe6MM85wyebdd991Y8eODb3t1VdftfeSSIcOHbL9Z8+e7Q4cOODq1atnr6eiRYvm0BEDAADgcHLw4EH35ptvurlz59q/zz33XHfLLbe4o446KrcPLU9S/2P8+PHus88+szjI4MGD4+77008/WX9u9erV7phjjnHXXHONu+KKK3L0ePMa9WEUS/j888/dtm3b3CmnnOKuvvpqu27D7Nu3z40cOdItXLjQ+tR///vfLTYVL/aA/7du3Trra+q3+orqS6tPres0lvqY77zzjps1a5b766+/gj5msWLFaM4cxIwD5CnTpk1ztWvXdt98841r2rSpvZEoADps2DCXX+jDq0mTJm7nzp32BqqfeL744gtXq1Yt+3355Zfbm/Gll17qevfu7ZKNEgR169Z1a9assQ9tJQAeeOABV6dOHfuSFatHjx7WlqVLl3aXXXaZmzNnjrXVV1995ZKNviT27dvXPmB1rkcffbTr0qWLO+uss+w6iqQvSWeffbZ9CbroootctWrVXLdu3SzhpNdbslKyoEOHDm7SpEn2RebXX39NsY8SBvqyotv0/rN9+3ZrK+2fbPQ60nmpk6HXVuSPrp9I+/fvd1dddZW9pvTF7/zzz7f35Pr167sdO3bk2jkAAAAgd/z555/W73j44YfdmWee6f72t79ZoLtBgwY2aAvpo36K4h4aBKa+/dSpU+Puq2SB+rWKnShuULZsWevLde/enWaP44cffnDly5e3PnKRIkVs4Jz6MRdccIG77bbbUuy/a9cuSygMGjTIrm3FHB566CHrdyvAjXC9evWy5IqSLpdccomrXLmye+GFF1ylSpVsUGxsIkd9UV23GuR54YUXutdee8364+qHIwd5QB6xZ88e76STTvKaNWsWtf2+++7zjjzySG/Tpk1efrBmzZrg3+XKlfMaNGgQut/Bgwe9atWqeeeee6792/f88897BQoU8JYsWeIlk6ZNm3rz5s2L2rZhwwa7Ntq2bRu1fc6cOZ7e/l555ZVg2/79+71atWp59erV85LNli1bUmybNm2atUHPnj2jtrdp08YrVaqUt2PHjhTtNXz4cC9ZDRw40CtZsqTXpUsXO9eVK1dG3b5t2zavWLFiXqdOnaK2t2vXzv5u586dXjLp3bu3tcOuXbtS3feFF16wfRcsWBBs++mnn7xjjz3W69ixYzYfKQAAAA43ffv2tT7n0qVLg23ff/+9V7RoUa9r1665emx50dq1a71Dhw7Zv+vUqeNVqVIl7r7nnXeexQH+/PPPYNuoUaPs+/r06dNz5HjzmvXr13uXXnqpt3379qjtip2o3SZPnhy1/bbbbrO+oa5pn651XfPPPfdcjh13MsQl1G/U+0Ljxo2jtg8ZMsTafv78+cE2PT/qe7dv3z5Hjhf/jxkHyDM0bezHH390t956a9R2ZYA1omHMmDEuP6hevXqa9vvkk0/c119/7Tp37uwKFvzfS71Tp072f01dTCYqk6IsdKQqVapYdlpleiIpU60SPO3btw+2FS5c2NpmyZIlVqInmZQrVy7FNs3OEL2mfBr9o1Hm//jHP6JK0ai0la674cOHu2Sk0fUaDfX888/biJwwb7/9ttu7d2/o+4/abcKECS6/0utJo2w0gsynclgqhfXWW2+533//PVePDwAAADn//VAjsjXD2XfyySfbCGL129V/R9ppFnhaysauWrXKLViwwGYoHHHEEcF2jfJW9YFk7c9lRX/5ww8/dMcff3zU9mbNmtnvyHiCrl3NRNdtuqZ9utbVH6KNE7dzLPUbNdsjMi7hv4f4s9l9en5atWplfXOVV0bOIHGAPENT8kRTk2KDwyVLlrQPSKTeXiVKlLB1EZKtvXResfSh/t1339n1Eds2+hCKra/pr5WRbG0TRuuE6Mungru+L7/80srOxF4zftvo9mSbeqkpkKqpqCmmqpcYj64Z1atUgDyS2krtmKzXjMp93XDDDVauSskR1aeNtGfPHuugxLtmNA1Va48AAAAgf1Ap1A0bNsT9fqiAn8rpIOdiACq/o35MsvZZMkulWNVGsTQQUyLjCer7qA8U7/pW7X6VMoJL84DXjRs3Wjktn/qQWlswXhsrzrNs2TKaOIewODLyjC1bttjvE088McVtWhworI59fua3l9omlratXbvWJTvV9deH9oMPPpiibbQOQiy/rZLxWtKoeM2oUKBcX2b0W7UxteZFWq8Z/Y0W2tKIgGTx5JNP2oyDlStXJtxPbaNZGLFfKJV8OvbYY5PymtEaF0rM6j1XNSc1Uklf3lQv1U/Ubd261daHiHfNSDK2DQAAAMKl1qfwvx9qrTDkbNtr4V+kjQbMPfHEE1aZoHXr1mluY//6Pu6442jqOO68807rR+pHyRmtc9C1a9eoNSe0viJ9zMMDiQPkqTdujewNW6Vewbw//vgjV47rcOWPDNcHXX5sLwU3tQi0MtL33HNPiraJ1y6SjG2jURRa2FbZeY3wGTp0qOvXr58Fgv0pmaldM8nWNgsXLnR9+vRxzz77rDv11FMT7hvvmknW15NKMj322GPB/2+++WbXvHlzW/BLibhXXnklX14zAAAASIzvh4dv22v2sAaDxevX4H9uv/12mzmt/mLVqlXT3MZC/yexq666yv3666/uv//9r5UkGjhwoJUk8pOJtPHhhXcL5BnFixe3ka16E44tMaPa46VLl861Yztc20vC6ourvVTjMFnNnz/fprrpA15rY0TWd/TbJl67SDK2jdZ0UD1An0aP64O5R48ebvTo0Wm6ZpKpbfRlRCWKtNZD9+7dU90/3jUj2p4s7RJZazLWZZddZu2ldTD8xEF+umYAAACQOr4fHr5trzgKSYO0lWsdMWKErWentfDS08ZC/ycxDUaLTNCojJbWWVy/fr0NFqaNDy+scYA8Q3X5RTXrIyljrilO/u1I3F7+tmRtL40iv/LKK23hnVmzZoVOb9O5x2sX//ZkpzUeatas6ebOnZvma0YlecLaM6/WXlXJJiUjNfVUSRX9jBs3zm6/9957oxbPVttoVIR+Im3fvt2+NOaHa0bKlCkTVbOzQoUKlpTK768nAAAA/L/KlStblQC+H+a8/BoDyEo9e/Z0zz33nOvcubN7+eWX093GSszoNYC0UZKgadOmts6BX+JWi04XLVqU95DDBIkD5BmNGze233PmzIna/umnn1r5lcha7YjfXgqWKtGSjO21dOlSd/nll1twe/bs2faB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6n71ONo+ZMiQYNuMGTNsm34PGjTInXrqqa5QoULuq6++cnfffbc76qij3O+//+66dOnijjvuOFeiRAl3ww03uF27dqU4Hz1emzZt3IknnuiOOOIIV61aNde/f38bqRFpypQp7qyzzrL7VhukJ3HiH9Pu3btdp06dXOnSpV3lypWD299++23XsGFDV6xYMfu55JJL3Keffprift555x2b7XHMMce4UqVKuWbNmrkvv/wyuP3QoUPuueeeczVq1LDn4Pjjj7fSUuvWrYu6H23XcXzxxRd2f0cffbR77LHH7LbvvvvOtWjRwh5Dx3n77be7ffv2pTgWPZaSSHosHXOFChWsHWOfPwAAAABAzqtatapVDojtB8+ePdtdeuml1k9WP1Ulid57772ov5s5c6ZbsmRJ0G8vXrx4cHulSpWs7xdLfUz1NSOpv6mfb775xl111VXWz2zVqpUNRtT99u3b13388cdBX7t69epuwoQJof1P9f1Vgkn915IlS7rmzZu7NWvWRO2n+1UcQn3ZY4891rVs2dL99NNPaWqvyGNSeeWzzz7bYgSKVcgPP/xgMQaVhtL2ihUr2oyOP//8M+p+tm7dav1o3a5zUp9Z9xm5n2IX1113nfXrtU+tWrXs/CLjEIqB6Hg2bdpkj3PyySfb//1E0CuvvGKxCf29jlXPWRhVjVAMRjEaPY9169Z1L7/8MgmlJEbiAECu+frrr+33KaecEmxTkL5Ro0Zu3Lhx7qWXXnI///yzmz59ulu2bJn7+9//boFnLUT0ySefBEF4fxqlFu/JKI2e0Jegzz//3L5sKFju6969uwXWFQjXl6Bp06a5e+65J+rvdXznnnuu++2339ysWbPczp07Xb9+/dzTTz8dte+HH37orr32Wnfeeee5jRs32hetzz77LN1rNejLg45Jiyk//vjjtu1f//qXu+mmm+xLg7brC5U+yJU80GP4tP+NN95otSZXrlxp+3Xt2tW9+OKLUV/UtJ+mpP744492nEq8+McdSe2ixY5fffVVe1x9WdQXJT2PSjSoPfQYSvbEtptoBIsSB0r86IvY4sWL7Qug6mgCAAAAAHKP+trq12mQl4LovrFjx1qfUrMRVqxYYcHwf/7zn+76668PBsepf9i4cWNXr169oN+ugYIZpeRFt27dbLCayigpmO9btGiRDZCbNGmS27x5s5VcUlJCxxCpffv29vcalPf999/bsSuB8Le//c3uU7Seg5IT6qfrPNUXvu222yzYr8RDWim+oGNSqWYF+BX7UDKgfv36FoTXdsUO1F7aL/J8dA7qW2sg4JgxY9z27dvt3DSIUDESWb16tQ0cVCxD+/mJBsVGFMeI9cgjj1gyR8fy73//25IHAwcOdHfccYe7+eab7TzHjx9viQbddyTFOpQk0roXCxcutFjN6NGjLeESNlgRScIDgGw2fPhwpbq9qVOn2v//+OMP75NPPvEqV67snXTSSd4PP/wQ7NuvXz/bd9GiRVH3sXHjRq9QoULe0KFD7f/6e+03ZcqUFI83cuRIu23NmjVR2zdv3mzbBw8eHGybPn26bbv66qtT3E/37t3ttjFjxkRtv//+++1Y9u7dG2y78MILvUqVKnm///571L5DhgzxChYs6G3atMn+X7t2ba9WrVreoUOHgn30b23XY61fvz5hW/rHNGzYsND26dGjR4q/adiwodeoUSP797p16+x47rrrrriPsXz5cnuMRx55JGq7nqejjjrKu/HGG4NtpUuX9o4++mhv+/btUfs+/fTTdh8rVqyI2v7UU0/Z9t69ewfbmjRpYu0HAAAAAMg95cqV8xo0aBD8f9u2bdb/VR+uZ8+ewXb1e9UXvPLKK1PcR4cOHbyyZct6Bw8etP83btzYq1evXujjVaxY0WvdunWK7R07drT7j3T++efbcSxevDhq+65du2z7aaed5h04cCDYvnPnTu+II47wHn744WDbzJkzbV8/ruBTjKJ8+fL2uPLuu+/afuPHj4/ab8KECbY9sk8cxj+mChUqePv374+67eabb/aKFy/ubd26NWr7tGnT7G9mz55t/2/Xrp31tbds2RL3cVq0aGH3tWPHjhRxgwIFClj/XxQD0X3ruYyk57FEiRJey5Yto7b/9ttv3nHHHWcxBt/8+fPtPtSGyD+YcQAgxzRt2tQy2pr+ptHnGm2gDHyZMmWCfTSDQNMVlVmPpBJCKskzd+7cbDm2a665Ju5tGmkQSVP/NALhv//9r/1f2f358+fbDACNVIikERgakaDbNUJAI/z98ko+/VtTIzNzvFp4WsekckKxNMJDIwA0DVGzJXQ8bdu2jXvfmiEgKjMUSc+T1qaInbaokRmx00i1j56v2rVrR23XbItYmiKq43v00UetfWJLOwEAAAAAcobK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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Visualize the returned analysis types and analytical methods.\n", + "\n", + "if analysis_counts.empty and method_counts.empty:\n", + " print(\"No analysis-type or method metadata was returned.\")\n", + "else:\n", + " fig, axes = plt.subplots(\n", + " nrows=1,\n", + " ncols=2,\n", + " figsize=(13, 5),\n", + " )\n", + "\n", + " if analysis_counts.empty:\n", + " axes[0].text(\n", + " 0.5,\n", + " 0.5,\n", + " \"No analysis-type data\",\n", + " ha=\"center\",\n", + " va=\"center\",\n", + " )\n", + " axes[0].set_axis_off()\n", + " else:\n", + " axes[0].barh(\n", + " analysis_counts.index,\n", + " analysis_counts.values,\n", + " )\n", + " axes[0].set_title(\"Analysis Types\")\n", + " axes[0].set_xlabel(\"Returned records\")\n", + " axes[0].grid(axis=\"x\", alpha=0.25)\n", + "\n", + " if method_counts.empty:\n", + " axes[1].text(\n", + " 0.5,\n", + " 0.5,\n", + " \"No method data\",\n", + " ha=\"center\",\n", + " va=\"center\",\n", + " )\n", + " axes[1].set_axis_off()\n", + " else:\n", + " axes[1].barh(\n", + " method_counts.index,\n", + " method_counts.values,\n", + " )\n", + " axes[1].set_title(\"Most Frequent Analytical Methods\")\n", + " axes[1].set_xlabel(\"Returned records\")\n", + " axes[1].grid(axis=\"x\", alpha=0.25)\n", + "\n", + " plt.tight_layout()\n", + " display_figure(fig)" + ] + }, + { + "cell_type": "markdown", + "id": "853388b9", + "metadata": {}, + "source": [ + "## 10. Interpretation\n", + "\n", + "The `Warren` author query returned a geographically broad collection rather\n", + "than a collection limited to southwestern Nevada. The sample coordinates span\n", + "multiple oceanic and continental regions.\n", + "\n", + "The citation metadata shows that the returned publications are primarily\n", + "associated with researchers such as J. M. Warren and J. E. Warren. Many of the\n", + "publications concern oceanic mantle processes, peridotites, mid-ocean ridges,\n", + "and related geochemical studies.\n", + "\n", + "This result demonstrates an important property of author-based repository\n", + "searches: a surname query can match multiple researchers. The returned records\n", + "should therefore be interpreted as a `Warren` search cohort rather than as\n", + "records belonging to one specific researcher.\n", + "\n", + "Mineral Analysis is the most frequent analysis type in the returned metadata.\n", + "Electron Microprobe is the most frequently recorded analytical method.\n", + "\n", + "The results also have two important technical limitations:\n", + "\n", + "1. The samples and citations cannot currently be joined at the individual\n", + " record level.\n", + "2. The notebook analyzes only the API page returned by the current backend." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "9033526f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAVDAT USER STORY SUMMARY\n", + "----------------------------------------\n", + "Author query: Warren\n", + "Retrieved samples: 321\n", + "Location groups: 100\n", + "Samples with valid coordinates: 321\n", + "Unique citations: 15\n", + "Latitude range: -54.06 to 86.76\n", + "Longitude range: -175.42 to 66.87\n", + "Most frequent analysis type: Mineral Analysis\n", + "Most frequent analytical method: ELECTRON MICROPROBE\n" + ] + } + ], + "source": [ + "# Print the main results from the user story.\n", + "\n", + "print(\"NAVDAT USER STORY SUMMARY\")\n", + "print(\"-\" * 40)\n", + "print(f\"Author query: {AUTHOR_QUERY}\")\n", + "print(f\"Retrieved samples: {len(samples):,}\")\n", + "print(\n", + " \"Location groups:\",\n", + " f\"{samples['rootParent'].nunique():,}\",\n", + ")\n", + "print(\n", + " \"Samples with valid coordinates:\",\n", + " f\"{len(samples_geo):,}\",\n", + ")\n", + "print(\n", + " \"Unique citations:\",\n", + " f\"{len(unique_citations):,}\",\n", + ")\n", + "\n", + "if not samples_geo.empty:\n", + " print(\n", + " \"Latitude range:\",\n", + " f\"{samples_geo['latitude'].min():.2f}\",\n", + " \"to\",\n", + " f\"{samples_geo['latitude'].max():.2f}\",\n", + " )\n", + "\n", + " print(\n", + " \"Longitude range:\",\n", + " f\"{samples_geo['longitude'].min():.2f}\",\n", + " \"to\",\n", + " f\"{samples_geo['longitude'].max():.2f}\",\n", + " )\n", + "\n", + "if not analysis_counts.empty:\n", + " print(\n", + " \"Most frequent analysis type:\",\n", + " analysis_counts.idxmax(),\n", + " )\n", + "\n", + "if not method_counts.empty:\n", + " print(\n", + " \"Most frequent analytical method:\",\n", + " method_counts.idxmax(),\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "b84613f2", + "metadata": {}, + "source": [ + "## 11. Role of DSI and Current Limitations\n", + "\n", + "Without the DSI NAVDAT backend, a researcher would need to call separate PetDB\n", + "endpoints, traverse nested location groups, flatten sample records, normalize\n", + "citation arrays, and organize the results before beginning analysis.\n", + "\n", + "With DSI, the workflow becomes:\n", + "\n", + "**Author query → DSI NAVDAT → samples and citations → geographic and metadata analysis**\n", + "\n", + "DSI provides the repository-access and normalization layer. Pandas and\n", + "Matplotlib provide the downstream analysis and visualization.\n", + "\n", + "The current workflow has several limitations:\n", + "\n", + "- Pagination is not implemented, so the notebook analyzes one API page.\n", + "- Individual samples cannot be connected directly to citation records.\n", + "- A surname query may match several researchers.\n", + "- The backend returns metadata rather than measured geochemical values.\n", + "- The live PetDB service may change its data or response behavior." + ] + }, + { + "cell_type": "markdown", + "id": "bfb27053", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "This notebook used the DSI NAVDAT backend to retrieve and analyze sample and\n", + "citation metadata for the author query `Warren`.\n", + "\n", + "DSI converted nested PetDB responses into analysis-ready tables. The workflow\n", + "mapped the returned samples, identified the largest location groups, reviewed\n", + "the unique citations, and summarized the principal analysis types and\n", + "analytical methods.\n", + "\n", + "The global sample distribution and citation authors demonstrate that the\n", + "surname query represents several Warren-associated studies rather than one\n", + "specific southwestern Nevada researcher.\n", + "\n", + "The notebook therefore demonstrates both the value of DSI's normalized\n", + "repository access and the importance of validating broad search results before\n", + "using them for scientific interpretation." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "b25efcec", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Closing this instance of DSI()\n", + "NAVDAT backend closed.\n" + ] + } + ], + "source": [ + "# Close the backend and clear its in-memory state.\n", + "\n", + "dsi.close()\n", + "\n", + "print(\"NAVDAT backend closed.\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.11 (NAVDAT DSI)", + "language": "python", + "name": "navdat-dsi" + }, + "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 +} diff --git a/tools/navdat_geoscience_demo/README.md b/tools/navdat_geoscience_demo/README.md new file mode 100644 index 00000000..57dc0e86 --- /dev/null +++ b/tools/navdat_geoscience_demo/README.md @@ -0,0 +1,197 @@ +# Exploring Warren-Associated Geoscience Metadata with DSI and NAVDAT + +This notebook demonstrates how the DSI NAVDAT backend can be used to retrieve +and analyze sample and citation metadata from the EarthChem PetDB v4 API. + +The user story uses an author query for `Warren` and examines the geographic +distribution, publication history, analysis types, and analytical methods +represented in the returned records. + +## User Story + +As a geoscientist evaluating federated EarthChem metadata, I want to retrieve +sample and citation records matching the author name `Warren`, examine their +geographic distribution, and summarize their analytical methods so that I can +understand the scope of records returned through the DSI NAVDAT backend. + +## Scientific Question + +What geographic regions, publications, analysis types, and analytical methods +are represented in PetDB records returned for the author query `Warren`? + +Because the search is surname-based, the returned records may be associated +with multiple researchers named Warren. The notebook therefore treats the +results as a Warren-matched search cohort rather than records belonging to one +specific researcher. + +## Workflow + +**Author query → DSI NAVDAT backend → samples and citations → coordinate +validation → geographic and metadata analysis → visualization** + +The notebook demonstrates: + +- querying the NAVDAT backend through DSI; +- inspecting the normalized `samples` and `citations` tables; +- validating latitude and longitude values; +- mapping the geographic distribution of returned samples; +- identifying the largest returned location groups; +- reviewing unique citations and publication years; and +- summarizing analysis types and analytical methods. + +--- + +## Step 1 — Create and Activate a Python Environment + +From Terminal on macOS or Linux, create a Python 3.11 virtual environment: + +```bash +python3.11 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +``` + +--- + +## Step 2 — Clone and Install DSI + +Clone the DSI repository if it is not already available: + +```bash +git clone git@github.com:lanl/dsi.git +cd dsi +``` + +Install DSI and its core dependencies in editable mode: + +```bash +python -m pip install -e . +``` + +Return to the `geoscience_navdat_demo` directory before continuing. + +--- + +## Step 3 — Install the Notebook Dependencies + +Install the packages required for this user story: + +```bash +python -m pip install -r requirements.txt +``` + +The DSI package itself is installed separately in Step 2 because this example +is intended to run against the checked-out DSI source tree. + +--- + +## Step 4 — Register the Jupyter Kernel + +Register the active virtual environment as a Jupyter kernel: + +```bash +python -m ipykernel install --user \ + --name navdat-dsi \ + --display-name "Python 3.11 (NAVDAT DSI)" +``` + +--- + +## Step 5 — Start JupyterLab + +From the `geoscience_navdat_demo` directory, run: + +```bash +jupyter-lab +``` + +Open `NAVDAT_User_Story.ipynb` and select the following kernel: + +```text +Python 3.11 (NAVDAT DSI) +``` + +Run the notebook cells from top to bottom. + +--- + +## Notebook Sequence + +The notebook follows this sequence: + +1. Define the `Warren` author query. +2. Initialize the read-only DSI NAVDAT backend. +3. Retrieve the normalized `samples` and `citations` tables. +4. Inspect the sample fields required for the analysis. +5. Validate sample coordinates. +6. Map the geographic distribution of the returned samples. +7. Identify the largest returned location groups. +8. Review unique citations and publication years. +9. Summarize analysis types and analytical methods. +10. Interpret the results and document the current limitations. +11. Close the DSI backend. + +--- + +## Expected Outputs + +The notebook produces: + +- normalized sample and citation tables; +- a summary of retrieved samples, valid coordinates, and location groups; +- a geographic sample-distribution plot; +- a table and chart of the largest location groups; +- a deduplicated citation table; +- a publication-year chart; +- analysis-type and analytical-method summaries; and +- a concise final summary of the returned metadata. + +The exact record counts may change because the notebook queries the live PetDB +service. + +--- + +## Role of DSI + +DSI provides the repository-access and normalization layer in this workflow. +The NAVDAT backend calls the remote service, traverses the nested responses, +and exposes the results as analysis-ready `samples` and `citations` tables. + +In this example: + +- **DSI / NAVDAT backend** — retrieves and normalizes repository metadata. +- **pandas** — organizes, filters, deduplicates, and summarizes the tables. +- **NumPy** — supports numerical transformations used in visualization. +- **Matplotlib** — creates the geographic and metadata plots. +- **truststore** — allows the notebook to use the operating system certificate + store when needed for HTTPS access. + +This separation keeps repository access independent from the scientific +analysis performed after retrieval. + +--- + +## Current Limitations + +- Pagination is not currently implemented, so the notebook analyzes the API + page returned by the backend. +- The API response does not provide a confirmed key for connecting an + individual sample directly to a citation record. +- A surname query may match multiple researchers. +- The backend returns metadata rather than measured geochemical values. +- Results and response behavior may change because PetDB is a live service. + +--- + +## Summary + +This user story demonstrates an end-to-end geoscience metadata workflow in +which DSI retrieves and normalizes EarthChem PetDB records for downstream +analysis with standard Python tools. + +The results also demonstrate why broad repository searches must be validated: +the `Warren` surname query returns a geographically diverse collection +associated with several Warren researchers rather than one narrowly defined +regional collection. + +**Query → Retrieve → Normalize → Validate → Analyze → Visualize** diff --git a/tools/navdat_geoscience_demo/requirements.txt b/tools/navdat_geoscience_demo/requirements.txt new file mode 100644 index 00000000..5c1cb27e --- /dev/null +++ b/tools/navdat_geoscience_demo/requirements.txt @@ -0,0 +1,6 @@ +jupyterlab>=4.4,<5 +ipykernel>=6.29,<8 +pandas>=2.2,<3 +numpy>=1.26,<3 +matplotlib>=3.8,<4 +truststore>=0.10,<1 \ No newline at end of file diff --git a/tools/rcsbpdb_myoglobin_demo/RCSBPDB_User_Story.ipynb b/tools/rcsbpdb_myoglobin_demo/RCSBPDB_User_Story.ipynb new file mode 100644 index 00000000..32787152 --- /dev/null +++ b/tools/rcsbpdb_myoglobin_demo/RCSBPDB_User_Story.ipynb @@ -0,0 +1,4243 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f6c9d1ff", + "metadata": {}, + "source": [ + "# Exploring Myoglobin Mutations with DSI and RCSB PDB\n", + "\n", + "## User Story\n", + "\n", + "**As a structural biology researcher, I want to retrieve and compare related\n", + "myoglobin structures from the Protein Data Bank so that I can examine how\n", + "selected amino-acid substitutions relate to differences in protein structure.**\n", + "\n", + "This notebook demonstrates a complete scientific workflow using the DSI\n", + "RCSBPDB backend:\n", + "\n", + "**Scientific question → PDB structure retrieval → normalized DSI metadata →\n", + "structural resources → structure analysis → visualization**\n", + "\n", + "### Scientific Question\n", + "\n", + "How similar are the three-dimensional structures of selected recombinant sperm\n", + "whale myoglobin mutants?\n", + "\n", + "For this demonstration, four related PDB structures are examined:\n", + "\n", + "| PDB ID | Mutation |\n", + "|---|---|\n", + "| 1CH1 | L89G |\n", + "| 1CH2 | L89F |\n", + "| 1CH3 | L89W |\n", + "| 1CH5 | H97V |\n", + "\n", + "The purpose is not to establish the biological consequences of these mutations,\n", + "but to demonstrate how DSI can connect repository data discovery and access to\n", + "a downstream structural-analysis workflow.\n", + "\n", + "As a first choice, I chose the myoglobin mutant example because the dataset contains a compact group of closely related structures representing mutations of the same protein. That gives a controlled comparison: instead of comparing unrelated proteins, the demo looks at variants of one protein and asks whether their overall structures remain similar.\n", + "\n", + "### Overall Story\n", + "\n", + "In this user story, I selected four related sperm whale myoglobin mutant structures from PDB and used the DSI RCSBPDB backend to retrieve their metadata and available structure files in one workflow. From the DSI datasets table, I look at the basic information for each structure, and from the resources table, I identify and download the corresponding mmCIF coordinate files. Once the structural files are available, I use BioPython to parse them and compare the proteins. I first summarize basic properties such as chains, residues, atoms, and the presence of the heme group. Then I align corresponding C-alpha atoms between the structures and calculate RMSD values to quantify how similar or different their overall 3D structures are. Finally, I visualize those comparisons using an RMSD bar chart, a pairwise RMSD heatmap, and interactive 3D molecular views with py3Dmol. So overall, the demo shows the complete path from retrieving related PDB structures through DSI to performing a small structural comparison and visualizing the result." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8a04ccb4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Python environment: /Users/rsankararaman/PDB_new/venv/bin/python\n" + ] + } + ], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from dsi.dsi import DSI\n", + "\n", + "print(\"Python environment:\", sys.executable)" + ] + }, + { + "cell_type": "markdown", + "id": "84633d3c", + "metadata": {}, + "source": [ + "## 1. Select Related Myoglobin Structures\n", + "\n", + "The PDB contains multiple experimentally determined structures of recombinant\n", + "sperm whale myoglobin with substitutions at specific amino-acid positions.\n", + "\n", + "A small set of four structures is used here so that the workflow remains\n", + "reproducible and easy to interpret." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "749ecef6", + "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", + "
pdb_idmutation
01CH1L89G
11CH2L89F
21CH3L89W
31CH5H97V
\n", + "
" + ], + "text/plain": [ + " pdb_id mutation\n", + "0 1CH1 L89G\n", + "1 1CH2 L89F\n", + "2 1CH3 L89W\n", + "3 1CH5 H97V" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Defines the four selected myoglobin PDB structures and their corresponding mutations for this user story.\n", + "\n", + "selected_structures = pd.DataFrame(\n", + " {\n", + " \"pdb_id\": [\"1CH1\", \"1CH2\", \"1CH3\", \"1CH5\"],\n", + " \"mutation\": [\"L89G\", \"L89F\", \"L89W\", \"H97V\"],\n", + " }\n", + ")\n", + "\n", + "selected_structures" + ] + }, + { + "cell_type": "markdown", + "id": "e3cdcf3d", + "metadata": {}, + "source": [ + "## 2. Retrieve the Structures Through DSI\n", + "\n", + "The DSI RCSBPDB backend provides read-only programmatic access to RCSB PDB\n", + "metadata and downloadable structure resources.\n", + "\n", + "Rather than accessing each PDB record manually, the four PDB identifiers are\n", + "provided together to a single DSI backend instance." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2550be82", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created an instance of DSI with the RCSBPDB read-only backend\n" + ] + } + ], + "source": [ + "# Creates a DSI RCSBPDB backend instance to retrieve all four selected PDB entries in one request.\n", + "\n", + "pdb_ids = selected_structures[\"pdb_id\"].tolist()\n", + "\n", + "dsi = DSI(\n", + " backend_name=\"RCSBPDB\",\n", + " params={\n", + " \"pdb_id\": pdb_ids\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "dbfd2f67", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Table: datasets\n", + " - num of columns: 12\n", + " - num of rows: 4\n", + "\n", + "Table: resources\n", + " - num of columns: 8\n", + " - num of rows: 40\n", + "\n", + "Table: errors\n", + " - num of columns: 7\n", + " - num of rows: 0\n", + "\n", + "\n" + ] + } + ], + "source": [ + "dsi.list()" + ] + }, + { + "cell_type": "markdown", + "id": "a62535cc", + "metadata": {}, + "source": [ + "### DSI's normalized representation\n", + "\n", + "The RCSBPDB backend represents repository information using three tables:\n", + "\n", + "- **datasets** — one row describing each resolved PDB structure.\n", + "- **resources** — downloadable files associated with the structures.\n", + "- **errors** — identifiers that could not be resolved or processed.\n", + "\n", + "For successful lookups, the `datasets` and `resources` tables form the bridge\n", + "between repository metadata and the actual structural files used downstream." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cd147947", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Storing all data for the table: datasets as a collection\n" + ] + }, + { + "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", + "
dataset_iddoititledescriptionlanding_pagemetadata_urlexperimental_methodrelease_daterevision_dateresource_countraw_metadatanotes
01CH110.2210/pdb1ch1/pdbRecombinant sperm whale myoglobin L89G mutatnt...OXYGEN STORAGE/TRANSPORT: OXYGEN TRANSPORT, HE...https://www.rcsb.org/structure/1CH1https://data.rcsb.org/rest/v1/core/entry/1CH1X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:002026-08-12T00:00:00.000+00:0010{\"citation\": [{\"country\": \"US\", \"id\": \"primary...None
11CH210.2210/pdb1ch2/pdbRECOMBINANT SPERM WHALE MYOGLOBIN L89F MUTANT ...OXYGEN STORAGE/TRANSPORT: OXYGEN TRANSPORT, HE...https://www.rcsb.org/structure/1CH2https://data.rcsb.org/rest/v1/core/entry/1CH2X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:002026-08-12T00:00:00.000+00:0010{\"citation\": [{\"country\": \"US\", \"id\": \"primary...None
21CH310.2210/pdb1ch3/pdbRECOMBINANT SPERM WHALE MYOGLOBIN L89W MUTANT ...OXYGEN STORAGE/TRANSPORT: OXYGEN TRANSPORT, HE...https://www.rcsb.org/structure/1CH3https://data.rcsb.org/rest/v1/core/entry/1CH3X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:002026-08-12T00:00:00.000+00:0010{\"citation\": [{\"country\": \"US\", \"id\": \"primary...None
31CH510.2210/pdb1ch5/pdbRECOMBINANT SPERM WHALE MYOGLOBIN H97V MUTANT ...OXYGEN STORAGE/TRANSPORT: OXYGEN TRANSPORT, HE...https://www.rcsb.org/structure/1CH5https://data.rcsb.org/rest/v1/core/entry/1CH5X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:002026-08-12T00:00:00.000+00:0010{\"citation\": [{\"country\": \"US\", \"id\": \"primary...None
\n", + "
" + ], + "text/plain": [ + " dataset_id doi \\\n", + "0 1CH1 10.2210/pdb1ch1/pdb \n", + "1 1CH2 10.2210/pdb1ch2/pdb \n", + "2 1CH3 10.2210/pdb1ch3/pdb \n", + "3 1CH5 10.2210/pdb1ch5/pdb \n", + "\n", + " title \\\n", + "0 Recombinant sperm whale myoglobin L89G mutatnt... \n", + "1 RECOMBINANT SPERM WHALE MYOGLOBIN L89F MUTANT ... \n", + "2 RECOMBINANT SPERM WHALE MYOGLOBIN L89W MUTANT ... \n", + "3 RECOMBINANT SPERM WHALE MYOGLOBIN H97V MUTANT ... \n", + "\n", + " description \\\n", + "0 OXYGEN STORAGE/TRANSPORT: OXYGEN TRANSPORT, HE... \n", + "1 OXYGEN STORAGE/TRANSPORT: OXYGEN TRANSPORT, HE... \n", + "2 OXYGEN STORAGE/TRANSPORT: OXYGEN TRANSPORT, HE... \n", + "3 OXYGEN STORAGE/TRANSPORT: OXYGEN TRANSPORT, HE... \n", + "\n", + " landing_page \\\n", + "0 https://www.rcsb.org/structure/1CH1 \n", + "1 https://www.rcsb.org/structure/1CH2 \n", + "2 https://www.rcsb.org/structure/1CH3 \n", + "3 https://www.rcsb.org/structure/1CH5 \n", + "\n", + " metadata_url experimental_method \\\n", + "0 https://data.rcsb.org/rest/v1/core/entry/1CH1 X-RAY DIFFRACTION \n", + "1 https://data.rcsb.org/rest/v1/core/entry/1CH2 X-RAY DIFFRACTION \n", + "2 https://data.rcsb.org/rest/v1/core/entry/1CH3 X-RAY DIFFRACTION \n", + "3 https://data.rcsb.org/rest/v1/core/entry/1CH5 X-RAY DIFFRACTION \n", + "\n", + " release_date revision_date \\\n", + "0 1999-04-09T00:00:00.000+00:00 2026-08-12T00:00:00.000+00:00 \n", + "1 1999-04-09T00:00:00.000+00:00 2026-08-12T00:00:00.000+00:00 \n", + "2 1999-04-09T00:00:00.000+00:00 2026-08-12T00:00:00.000+00:00 \n", + "3 1999-04-09T00:00:00.000+00:00 2026-08-12T00:00:00.000+00:00 \n", + "\n", + " resource_count raw_metadata notes \n", + "0 10 {\"citation\": [{\"country\": \"US\", \"id\": \"primary... None \n", + "1 10 {\"citation\": [{\"country\": \"US\", \"id\": \"primary... None \n", + "2 10 {\"citation\": [{\"country\": \"US\", \"id\": \"primary... None \n", + "3 10 {\"citation\": [{\"country\": \"US\", \"id\": \"primary... None " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Retrieves the full normalized DSI datasets table containing metadata for the selected PDB structures.\n", + "\n", + "datasets = dsi.get_table(\n", + " \"datasets\",\n", + " collection=True\n", + ")\n", + "\n", + "datasets" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2da83d5b", + "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", + "
dataset_iddoititleexperimental_methodrelease_dateresource_count
01CH110.2210/pdb1ch1/pdbRecombinant sperm whale myoglobin L89G mutatnt...X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:0010
11CH210.2210/pdb1ch2/pdbRECOMBINANT SPERM WHALE MYOGLOBIN L89F MUTANT ...X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:0010
21CH310.2210/pdb1ch3/pdbRECOMBINANT SPERM WHALE MYOGLOBIN L89W MUTANT ...X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:0010
31CH510.2210/pdb1ch5/pdbRECOMBINANT SPERM WHALE MYOGLOBIN H97V MUTANT ...X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:0010
\n", + "
" + ], + "text/plain": [ + " dataset_id doi \\\n", + "0 1CH1 10.2210/pdb1ch1/pdb \n", + "1 1CH2 10.2210/pdb1ch2/pdb \n", + "2 1CH3 10.2210/pdb1ch3/pdb \n", + "3 1CH5 10.2210/pdb1ch5/pdb \n", + "\n", + " title experimental_method \\\n", + "0 Recombinant sperm whale myoglobin L89G mutatnt... X-RAY DIFFRACTION \n", + "1 RECOMBINANT SPERM WHALE MYOGLOBIN L89F MUTANT ... X-RAY DIFFRACTION \n", + "2 RECOMBINANT SPERM WHALE MYOGLOBIN L89W MUTANT ... X-RAY DIFFRACTION \n", + "3 RECOMBINANT SPERM WHALE MYOGLOBIN H97V MUTANT ... X-RAY DIFFRACTION \n", + "\n", + " release_date resource_count \n", + "0 1999-04-09T00:00:00.000+00:00 10 \n", + "1 1999-04-09T00:00:00.000+00:00 10 \n", + "2 1999-04-09T00:00:00.000+00:00 10 \n", + "3 1999-04-09T00:00:00.000+00:00 10 " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Selects the most relevant PDB metadata fields to create a cleaner dataset summary for the demo.\n", + "\n", + "dataset_view = datasets[\n", + " [\n", + " \"dataset_id\",\n", + " \"doi\",\n", + " \"title\",\n", + " \"experimental_method\",\n", + " \"release_date\",\n", + " \"resource_count\",\n", + " ]\n", + "].copy()\n", + "\n", + "dataset_view" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "faf6fe86", + "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", + "
dataset_idmutationtitleexperimental_methodrelease_dateresource_count
01CH1L89GRecombinant sperm whale myoglobin L89G mutatnt...X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:0010
11CH2L89FRECOMBINANT SPERM WHALE MYOGLOBIN L89F MUTANT ...X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:0010
21CH3L89WRECOMBINANT SPERM WHALE MYOGLOBIN L89W MUTANT ...X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:0010
31CH5H97VRECOMBINANT SPERM WHALE MYOGLOBIN H97V MUTANT ...X-RAY DIFFRACTION1999-04-09T00:00:00.000+00:0010
\n", + "
" + ], + "text/plain": [ + " dataset_id mutation title \\\n", + "0 1CH1 L89G Recombinant sperm whale myoglobin L89G mutatnt... \n", + "1 1CH2 L89F RECOMBINANT SPERM WHALE MYOGLOBIN L89F MUTANT ... \n", + "2 1CH3 L89W RECOMBINANT SPERM WHALE MYOGLOBIN L89W MUTANT ... \n", + "3 1CH5 H97V RECOMBINANT SPERM WHALE MYOGLOBIN H97V MUTANT ... \n", + "\n", + " experimental_method release_date resource_count \n", + "0 X-RAY DIFFRACTION 1999-04-09T00:00:00.000+00:00 10 \n", + "1 X-RAY DIFFRACTION 1999-04-09T00:00:00.000+00:00 10 \n", + "2 X-RAY DIFFRACTION 1999-04-09T00:00:00.000+00:00 10 \n", + "3 X-RAY DIFFRACTION 1999-04-09T00:00:00.000+00:00 10 " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Adds the known mutation labels to the retrieved PDB metadata so each structure can be connected to its variant.\n", + "\n", + "dataset_view = dataset_view.merge(\n", + " selected_structures,\n", + " left_on=\"dataset_id\",\n", + " right_on=\"pdb_id\",\n", + " how=\"left\"\n", + ")\n", + "\n", + "dataset_view = dataset_view[\n", + " [\n", + " \"dataset_id\",\n", + " \"mutation\",\n", + " \"title\",\n", + " \"experimental_method\",\n", + " \"release_date\",\n", + " \"resource_count\",\n", + " ]\n", + "]\n", + "\n", + "dataset_view" + ] + }, + { + "cell_type": "markdown", + "id": "eba4c30c", + "metadata": {}, + "source": [ + "## 3. Identify Downloadable Structural Resources\n", + "\n", + "Repository metadata describes the structures, but structural analysis requires the coordinate files themselves.\n", + "\n", + "The RCSBPDB backend exposes these files through the DSI `resources` table,\n", + "allowing the scientific workflow to move from metadata to downloadable data." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "fadcb743", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Storing all data for the table: resources as a collection\n" + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
resource_iddataset_idnamedownload_urlformatresource_typesourceraw_metadata
01CH1:11CH11ch1.cifhttps://files.rcsb.org/download/1CH1.cifcifPDBx/mmCIF Formatrcsb.download{\"content_type\": \"chemical/x-cif\", \"exists\": t...
11CH1:21CH11ch1.cif.gzhttps://files.rcsb.org/download/1CH1.cif.gzcif.gzPDBx/mmCIF Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
21CH1:31CH11ch1.pdbhttps://files.rcsb.org/download/1CH1.pdbpdbLegacy PDB Formatrcsb.download{\"content_type\": \"text/plain\", \"exists\": true,...
31CH1:41CH11ch1.pdb.gzhttps://files.rcsb.org/download/1CH1.pdb.gzpdb.gzLegacy PDB Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
41CH1:51CH11ch1.xml.gzhttps://files.rcsb.org/download/1CH1.xml.gzxml.gzPDBML/XML Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
51CH1:61CH11ch1-sf.cifhttps://files.rcsb.org/download/1CH1-sf.cifcifStructure Factors (CIF)rcsb.download{\"content_type\": \"chemical/x-cif\", \"exists\": t...
61CH1:71CH11ch1-sf.cif.gzhttps://files.rcsb.org/download/1CH1-sf.cif.gzcif.gzStructure Factors (CIF - gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
71CH1:81CH11ch1_validation.pdf.gzhttps://files.rcsb.org/pub/pdb/validation_repo...pdf.gzValidation Full (PDF - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
81CH1:91CH11ch1_validation.xml.gzhttps://files.rcsb.org/pub/pdb/validation_repo...xml.gzValidation (XML - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
91CH1:101CH11ch1_validation.cif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...cif.gzValidation (CIF - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
101CH2:11CH21ch2.cifhttps://files.rcsb.org/download/1CH2.cifcifPDBx/mmCIF Formatrcsb.download{\"content_type\": \"chemical/x-cif\", \"exists\": t...
111CH2:21CH21ch2.cif.gzhttps://files.rcsb.org/download/1CH2.cif.gzcif.gzPDBx/mmCIF Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
121CH2:31CH21ch2.pdbhttps://files.rcsb.org/download/1CH2.pdbpdbLegacy PDB Formatrcsb.download{\"content_type\": \"text/plain\", \"exists\": true,...
131CH2:41CH21ch2.pdb.gzhttps://files.rcsb.org/download/1CH2.pdb.gzpdb.gzLegacy PDB Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
141CH2:51CH21ch2.xml.gzhttps://files.rcsb.org/download/1CH2.xml.gzxml.gzPDBML/XML Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
151CH2:61CH21ch2-sf.cifhttps://files.rcsb.org/download/1CH2-sf.cifcifStructure Factors (CIF)rcsb.download{\"content_type\": \"chemical/x-cif\", \"exists\": t...
161CH2:71CH21ch2-sf.cif.gzhttps://files.rcsb.org/download/1CH2-sf.cif.gzcif.gzStructure Factors (CIF - gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
171CH2:81CH21ch2_validation.pdf.gzhttps://files.rcsb.org/pub/pdb/validation_repo...pdf.gzValidation Full (PDF - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
181CH2:91CH21ch2_validation.xml.gzhttps://files.rcsb.org/pub/pdb/validation_repo...xml.gzValidation (XML - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
191CH2:101CH21ch2_validation.cif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...cif.gzValidation (CIF - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
201CH3:11CH31ch3.cifhttps://files.rcsb.org/download/1CH3.cifcifPDBx/mmCIF Formatrcsb.download{\"content_type\": \"chemical/x-cif\", \"exists\": t...
211CH3:21CH31ch3.cif.gzhttps://files.rcsb.org/download/1CH3.cif.gzcif.gzPDBx/mmCIF Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
221CH3:31CH31ch3.pdbhttps://files.rcsb.org/download/1CH3.pdbpdbLegacy PDB Formatrcsb.download{\"content_type\": \"text/plain\", \"exists\": true,...
231CH3:41CH31ch3.pdb.gzhttps://files.rcsb.org/download/1CH3.pdb.gzpdb.gzLegacy PDB Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
241CH3:51CH31ch3.xml.gzhttps://files.rcsb.org/download/1CH3.xml.gzxml.gzPDBML/XML Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
251CH3:61CH31ch3-sf.cifhttps://files.rcsb.org/download/1CH3-sf.cifcifStructure Factors (CIF)rcsb.download{\"content_type\": \"chemical/x-cif\", \"exists\": t...
261CH3:71CH31ch3-sf.cif.gzhttps://files.rcsb.org/download/1CH3-sf.cif.gzcif.gzStructure Factors (CIF - gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
271CH3:81CH31ch3_validation.pdf.gzhttps://files.rcsb.org/pub/pdb/validation_repo...pdf.gzValidation Full (PDF - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
281CH3:91CH31ch3_validation.xml.gzhttps://files.rcsb.org/pub/pdb/validation_repo...xml.gzValidation (XML - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
291CH3:101CH31ch3_validation.cif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...cif.gzValidation (CIF - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
301CH5:11CH51ch5.cifhttps://files.rcsb.org/download/1CH5.cifcifPDBx/mmCIF Formatrcsb.download{\"content_type\": \"chemical/x-cif\", \"exists\": t...
311CH5:21CH51ch5.cif.gzhttps://files.rcsb.org/download/1CH5.cif.gzcif.gzPDBx/mmCIF Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
321CH5:31CH51ch5.pdbhttps://files.rcsb.org/download/1CH5.pdbpdbLegacy PDB Formatrcsb.download{\"content_type\": \"text/plain\", \"exists\": true,...
331CH5:41CH51ch5.pdb.gzhttps://files.rcsb.org/download/1CH5.pdb.gzpdb.gzLegacy PDB Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
341CH5:51CH51ch5.xml.gzhttps://files.rcsb.org/download/1CH5.xml.gzxml.gzPDBML/XML Format (gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
351CH5:61CH51ch5-sf.cifhttps://files.rcsb.org/download/1CH5-sf.cifcifStructure Factors (CIF)rcsb.download{\"content_type\": \"chemical/x-cif\", \"exists\": t...
361CH5:71CH51ch5-sf.cif.gzhttps://files.rcsb.org/download/1CH5-sf.cif.gzcif.gzStructure Factors (CIF - gz)rcsb.download{\"content_type\": \"application/gzip\", \"exists\":...
371CH5:81CH51ch5_validation.pdf.gzhttps://files.rcsb.org/pub/pdb/validation_repo...pdf.gzValidation Full (PDF - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
381CH5:91CH51ch5_validation.xml.gzhttps://files.rcsb.org/pub/pdb/validation_repo...xml.gzValidation (XML - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
391CH5:101CH51ch5_validation.cif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...cif.gzValidation (CIF - gz)rcsb.validation{\"content_type\": \"application/gzip\", \"exists\":...
\n", + "
" + ], + "text/plain": [ + " resource_id dataset_id name \\\n", + "0 1CH1:1 1CH1 1ch1.cif \n", + "1 1CH1:2 1CH1 1ch1.cif.gz \n", + "2 1CH1:3 1CH1 1ch1.pdb \n", + "3 1CH1:4 1CH1 1ch1.pdb.gz \n", + "4 1CH1:5 1CH1 1ch1.xml.gz \n", + "5 1CH1:6 1CH1 1ch1-sf.cif \n", + "6 1CH1:7 1CH1 1ch1-sf.cif.gz \n", + "7 1CH1:8 1CH1 1ch1_validation.pdf.gz \n", + "8 1CH1:9 1CH1 1ch1_validation.xml.gz \n", + "9 1CH1:10 1CH1 1ch1_validation.cif.gz \n", + "10 1CH2:1 1CH2 1ch2.cif \n", + "11 1CH2:2 1CH2 1ch2.cif.gz \n", + "12 1CH2:3 1CH2 1ch2.pdb \n", + "13 1CH2:4 1CH2 1ch2.pdb.gz \n", + "14 1CH2:5 1CH2 1ch2.xml.gz \n", + "15 1CH2:6 1CH2 1ch2-sf.cif \n", + "16 1CH2:7 1CH2 1ch2-sf.cif.gz \n", + "17 1CH2:8 1CH2 1ch2_validation.pdf.gz \n", + "18 1CH2:9 1CH2 1ch2_validation.xml.gz \n", + "19 1CH2:10 1CH2 1ch2_validation.cif.gz \n", + "20 1CH3:1 1CH3 1ch3.cif \n", + "21 1CH3:2 1CH3 1ch3.cif.gz \n", + "22 1CH3:3 1CH3 1ch3.pdb \n", + "23 1CH3:4 1CH3 1ch3.pdb.gz \n", + "24 1CH3:5 1CH3 1ch3.xml.gz \n", + "25 1CH3:6 1CH3 1ch3-sf.cif \n", + "26 1CH3:7 1CH3 1ch3-sf.cif.gz \n", + "27 1CH3:8 1CH3 1ch3_validation.pdf.gz \n", + "28 1CH3:9 1CH3 1ch3_validation.xml.gz \n", + "29 1CH3:10 1CH3 1ch3_validation.cif.gz \n", + "30 1CH5:1 1CH5 1ch5.cif \n", + "31 1CH5:2 1CH5 1ch5.cif.gz \n", + "32 1CH5:3 1CH5 1ch5.pdb \n", + "33 1CH5:4 1CH5 1ch5.pdb.gz \n", + "34 1CH5:5 1CH5 1ch5.xml.gz \n", + "35 1CH5:6 1CH5 1ch5-sf.cif \n", + "36 1CH5:7 1CH5 1ch5-sf.cif.gz \n", + "37 1CH5:8 1CH5 1ch5_validation.pdf.gz \n", + "38 1CH5:9 1CH5 1ch5_validation.xml.gz \n", + "39 1CH5:10 1CH5 1ch5_validation.cif.gz \n", + "\n", + " download_url format \\\n", + "0 https://files.rcsb.org/download/1CH1.cif cif \n", + "1 https://files.rcsb.org/download/1CH1.cif.gz cif.gz \n", + "2 https://files.rcsb.org/download/1CH1.pdb pdb \n", + "3 https://files.rcsb.org/download/1CH1.pdb.gz pdb.gz \n", + "4 https://files.rcsb.org/download/1CH1.xml.gz xml.gz \n", + "5 https://files.rcsb.org/download/1CH1-sf.cif cif \n", + "6 https://files.rcsb.org/download/1CH1-sf.cif.gz cif.gz \n", + "7 https://files.rcsb.org/pub/pdb/validation_repo... pdf.gz \n", + "8 https://files.rcsb.org/pub/pdb/validation_repo... xml.gz \n", + "9 https://files.rcsb.org/pub/pdb/validation_repo... cif.gz \n", + "10 https://files.rcsb.org/download/1CH2.cif cif \n", + "11 https://files.rcsb.org/download/1CH2.cif.gz cif.gz \n", + "12 https://files.rcsb.org/download/1CH2.pdb pdb \n", + "13 https://files.rcsb.org/download/1CH2.pdb.gz pdb.gz \n", + "14 https://files.rcsb.org/download/1CH2.xml.gz xml.gz \n", + "15 https://files.rcsb.org/download/1CH2-sf.cif cif \n", + "16 https://files.rcsb.org/download/1CH2-sf.cif.gz cif.gz \n", + "17 https://files.rcsb.org/pub/pdb/validation_repo... pdf.gz \n", + "18 https://files.rcsb.org/pub/pdb/validation_repo... xml.gz \n", + "19 https://files.rcsb.org/pub/pdb/validation_repo... cif.gz \n", + "20 https://files.rcsb.org/download/1CH3.cif cif \n", + "21 https://files.rcsb.org/download/1CH3.cif.gz cif.gz \n", + "22 https://files.rcsb.org/download/1CH3.pdb pdb \n", + "23 https://files.rcsb.org/download/1CH3.pdb.gz pdb.gz \n", + "24 https://files.rcsb.org/download/1CH3.xml.gz xml.gz \n", + "25 https://files.rcsb.org/download/1CH3-sf.cif cif \n", + "26 https://files.rcsb.org/download/1CH3-sf.cif.gz cif.gz \n", + "27 https://files.rcsb.org/pub/pdb/validation_repo... pdf.gz \n", + "28 https://files.rcsb.org/pub/pdb/validation_repo... xml.gz \n", + "29 https://files.rcsb.org/pub/pdb/validation_repo... cif.gz \n", + "30 https://files.rcsb.org/download/1CH5.cif cif \n", + "31 https://files.rcsb.org/download/1CH5.cif.gz cif.gz \n", + "32 https://files.rcsb.org/download/1CH5.pdb pdb \n", + "33 https://files.rcsb.org/download/1CH5.pdb.gz pdb.gz \n", + "34 https://files.rcsb.org/download/1CH5.xml.gz xml.gz \n", + "35 https://files.rcsb.org/download/1CH5-sf.cif cif \n", + "36 https://files.rcsb.org/download/1CH5-sf.cif.gz cif.gz \n", + "37 https://files.rcsb.org/pub/pdb/validation_repo... pdf.gz \n", + "38 https://files.rcsb.org/pub/pdb/validation_repo... xml.gz \n", + "39 https://files.rcsb.org/pub/pdb/validation_repo... cif.gz \n", + "\n", + " resource_type source \\\n", + "0 PDBx/mmCIF Format rcsb.download \n", + "1 PDBx/mmCIF Format (gz) rcsb.download \n", + "2 Legacy PDB Format rcsb.download \n", + "3 Legacy PDB Format (gz) rcsb.download \n", + "4 PDBML/XML Format (gz) rcsb.download \n", + "5 Structure Factors (CIF) rcsb.download \n", + "6 Structure Factors (CIF - gz) rcsb.download \n", + "7 Validation Full (PDF - gz) rcsb.validation \n", + "8 Validation (XML - gz) rcsb.validation \n", + "9 Validation (CIF - gz) rcsb.validation \n", + "10 PDBx/mmCIF Format rcsb.download \n", + "11 PDBx/mmCIF Format (gz) rcsb.download \n", + "12 Legacy PDB Format rcsb.download \n", + "13 Legacy PDB Format (gz) rcsb.download \n", + "14 PDBML/XML Format (gz) rcsb.download \n", + "15 Structure Factors (CIF) rcsb.download \n", + "16 Structure Factors (CIF - gz) rcsb.download \n", + "17 Validation Full (PDF - gz) rcsb.validation \n", + "18 Validation (XML - gz) rcsb.validation \n", + "19 Validation (CIF - gz) rcsb.validation \n", + "20 PDBx/mmCIF Format rcsb.download \n", + "21 PDBx/mmCIF Format (gz) rcsb.download \n", + "22 Legacy PDB Format rcsb.download \n", + "23 Legacy PDB Format (gz) rcsb.download \n", + "24 PDBML/XML Format (gz) rcsb.download \n", + "25 Structure Factors (CIF) rcsb.download \n", + "26 Structure Factors (CIF - gz) rcsb.download \n", + "27 Validation Full (PDF - gz) rcsb.validation \n", + "28 Validation (XML - gz) rcsb.validation \n", + "29 Validation (CIF - gz) rcsb.validation \n", + "30 PDBx/mmCIF Format rcsb.download \n", + "31 PDBx/mmCIF Format (gz) rcsb.download \n", + "32 Legacy PDB Format rcsb.download \n", + "33 Legacy PDB Format (gz) rcsb.download \n", + "34 PDBML/XML Format (gz) rcsb.download \n", + "35 Structure Factors (CIF) rcsb.download \n", + "36 Structure Factors (CIF - gz) rcsb.download \n", + "37 Validation Full (PDF - gz) rcsb.validation \n", + "38 Validation (XML - gz) rcsb.validation \n", + "39 Validation (CIF - gz) rcsb.validation \n", + "\n", + " raw_metadata \n", + "0 {\"content_type\": \"chemical/x-cif\", \"exists\": t... \n", + "1 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "2 {\"content_type\": \"text/plain\", \"exists\": true,... \n", + "3 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "4 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "5 {\"content_type\": \"chemical/x-cif\", \"exists\": t... \n", + "6 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "7 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "8 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "9 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "10 {\"content_type\": \"chemical/x-cif\", \"exists\": t... \n", + "11 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "12 {\"content_type\": \"text/plain\", \"exists\": true,... \n", + "13 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "14 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "15 {\"content_type\": \"chemical/x-cif\", \"exists\": t... \n", + "16 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "17 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "18 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "19 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "20 {\"content_type\": \"chemical/x-cif\", \"exists\": t... \n", + "21 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "22 {\"content_type\": \"text/plain\", \"exists\": true,... \n", + "23 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "24 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "25 {\"content_type\": \"chemical/x-cif\", \"exists\": t... \n", + "26 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "27 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "28 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "29 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "30 {\"content_type\": \"chemical/x-cif\", \"exists\": t... \n", + "31 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "32 {\"content_type\": \"text/plain\", \"exists\": true,... \n", + "33 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "34 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "35 {\"content_type\": \"chemical/x-cif\", \"exists\": t... \n", + "36 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "37 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "38 {\"content_type\": \"application/gzip\", \"exists\":... \n", + "39 {\"content_type\": \"application/gzip\", \"exists\":... " + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Retrieves the DSI resources table containing the downloadable files associated with each PDB structure.\n", + "resources = dsi.get_table(\n", + " \"resources\",\n", + " collection=True\n", + ")\n", + "\n", + "resources" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "83cc9dcb", + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
dataset_idnameformatresource_typedownload_url
01CH11ch1.cifcifPDBx/mmCIF Formathttps://files.rcsb.org/download/1CH1.cif
11CH11ch1.cif.gzcif.gzPDBx/mmCIF Format (gz)https://files.rcsb.org/download/1CH1.cif.gz
21CH11ch1.pdbpdbLegacy PDB Formathttps://files.rcsb.org/download/1CH1.pdb
31CH11ch1.pdb.gzpdb.gzLegacy PDB Format (gz)https://files.rcsb.org/download/1CH1.pdb.gz
41CH11ch1.xml.gzxml.gzPDBML/XML Format (gz)https://files.rcsb.org/download/1CH1.xml.gz
51CH11ch1-sf.cifcifStructure Factors (CIF)https://files.rcsb.org/download/1CH1-sf.cif
61CH11ch1-sf.cif.gzcif.gzStructure Factors (CIF - gz)https://files.rcsb.org/download/1CH1-sf.cif.gz
71CH11ch1_validation.pdf.gzpdf.gzValidation Full (PDF - gz)https://files.rcsb.org/pub/pdb/validation_repo...
81CH11ch1_validation.xml.gzxml.gzValidation (XML - gz)https://files.rcsb.org/pub/pdb/validation_repo...
91CH11ch1_validation.cif.gzcif.gzValidation (CIF - gz)https://files.rcsb.org/pub/pdb/validation_repo...
101CH21ch2.cifcifPDBx/mmCIF Formathttps://files.rcsb.org/download/1CH2.cif
111CH21ch2.cif.gzcif.gzPDBx/mmCIF Format (gz)https://files.rcsb.org/download/1CH2.cif.gz
121CH21ch2.pdbpdbLegacy PDB Formathttps://files.rcsb.org/download/1CH2.pdb
131CH21ch2.pdb.gzpdb.gzLegacy PDB Format (gz)https://files.rcsb.org/download/1CH2.pdb.gz
141CH21ch2.xml.gzxml.gzPDBML/XML Format (gz)https://files.rcsb.org/download/1CH2.xml.gz
151CH21ch2-sf.cifcifStructure Factors (CIF)https://files.rcsb.org/download/1CH2-sf.cif
161CH21ch2-sf.cif.gzcif.gzStructure Factors (CIF - gz)https://files.rcsb.org/download/1CH2-sf.cif.gz
171CH21ch2_validation.pdf.gzpdf.gzValidation Full (PDF - gz)https://files.rcsb.org/pub/pdb/validation_repo...
181CH21ch2_validation.xml.gzxml.gzValidation (XML - gz)https://files.rcsb.org/pub/pdb/validation_repo...
191CH21ch2_validation.cif.gzcif.gzValidation (CIF - gz)https://files.rcsb.org/pub/pdb/validation_repo...
201CH31ch3.cifcifPDBx/mmCIF Formathttps://files.rcsb.org/download/1CH3.cif
211CH31ch3.cif.gzcif.gzPDBx/mmCIF Format (gz)https://files.rcsb.org/download/1CH3.cif.gz
221CH31ch3.pdbpdbLegacy PDB Formathttps://files.rcsb.org/download/1CH3.pdb
231CH31ch3.pdb.gzpdb.gzLegacy PDB Format (gz)https://files.rcsb.org/download/1CH3.pdb.gz
241CH31ch3.xml.gzxml.gzPDBML/XML Format (gz)https://files.rcsb.org/download/1CH3.xml.gz
251CH31ch3-sf.cifcifStructure Factors (CIF)https://files.rcsb.org/download/1CH3-sf.cif
261CH31ch3-sf.cif.gzcif.gzStructure Factors (CIF - gz)https://files.rcsb.org/download/1CH3-sf.cif.gz
271CH31ch3_validation.pdf.gzpdf.gzValidation Full (PDF - gz)https://files.rcsb.org/pub/pdb/validation_repo...
281CH31ch3_validation.xml.gzxml.gzValidation (XML - gz)https://files.rcsb.org/pub/pdb/validation_repo...
291CH31ch3_validation.cif.gzcif.gzValidation (CIF - gz)https://files.rcsb.org/pub/pdb/validation_repo...
301CH51ch5.cifcifPDBx/mmCIF Formathttps://files.rcsb.org/download/1CH5.cif
311CH51ch5.cif.gzcif.gzPDBx/mmCIF Format (gz)https://files.rcsb.org/download/1CH5.cif.gz
321CH51ch5.pdbpdbLegacy PDB Formathttps://files.rcsb.org/download/1CH5.pdb
331CH51ch5.pdb.gzpdb.gzLegacy PDB Format (gz)https://files.rcsb.org/download/1CH5.pdb.gz
341CH51ch5.xml.gzxml.gzPDBML/XML Format (gz)https://files.rcsb.org/download/1CH5.xml.gz
351CH51ch5-sf.cifcifStructure Factors (CIF)https://files.rcsb.org/download/1CH5-sf.cif
361CH51ch5-sf.cif.gzcif.gzStructure Factors (CIF - gz)https://files.rcsb.org/download/1CH5-sf.cif.gz
371CH51ch5_validation.pdf.gzpdf.gzValidation Full (PDF - gz)https://files.rcsb.org/pub/pdb/validation_repo...
381CH51ch5_validation.xml.gzxml.gzValidation (XML - gz)https://files.rcsb.org/pub/pdb/validation_repo...
391CH51ch5_validation.cif.gzcif.gzValidation (CIF - gz)https://files.rcsb.org/pub/pdb/validation_repo...
\n", + "
" + ], + "text/plain": [ + " dataset_id name format resource_type \\\n", + "0 1CH1 1ch1.cif cif PDBx/mmCIF Format \n", + "1 1CH1 1ch1.cif.gz cif.gz PDBx/mmCIF Format (gz) \n", + "2 1CH1 1ch1.pdb pdb Legacy PDB Format \n", + "3 1CH1 1ch1.pdb.gz pdb.gz Legacy PDB Format (gz) \n", + "4 1CH1 1ch1.xml.gz xml.gz PDBML/XML Format (gz) \n", + "5 1CH1 1ch1-sf.cif cif Structure Factors (CIF) \n", + "6 1CH1 1ch1-sf.cif.gz cif.gz Structure Factors (CIF - gz) \n", + "7 1CH1 1ch1_validation.pdf.gz pdf.gz Validation Full (PDF - gz) \n", + "8 1CH1 1ch1_validation.xml.gz xml.gz Validation (XML - gz) \n", + "9 1CH1 1ch1_validation.cif.gz cif.gz Validation (CIF - gz) \n", + "10 1CH2 1ch2.cif cif PDBx/mmCIF Format \n", + "11 1CH2 1ch2.cif.gz cif.gz PDBx/mmCIF Format (gz) \n", + "12 1CH2 1ch2.pdb pdb Legacy PDB Format \n", + "13 1CH2 1ch2.pdb.gz pdb.gz Legacy PDB Format (gz) \n", + "14 1CH2 1ch2.xml.gz xml.gz PDBML/XML Format (gz) \n", + "15 1CH2 1ch2-sf.cif cif Structure Factors (CIF) \n", + "16 1CH2 1ch2-sf.cif.gz cif.gz Structure Factors (CIF - gz) \n", + "17 1CH2 1ch2_validation.pdf.gz pdf.gz Validation Full (PDF - gz) \n", + "18 1CH2 1ch2_validation.xml.gz xml.gz Validation (XML - gz) \n", + "19 1CH2 1ch2_validation.cif.gz cif.gz Validation (CIF - gz) \n", + "20 1CH3 1ch3.cif cif PDBx/mmCIF Format \n", + "21 1CH3 1ch3.cif.gz cif.gz PDBx/mmCIF Format (gz) \n", + "22 1CH3 1ch3.pdb pdb Legacy PDB Format \n", + "23 1CH3 1ch3.pdb.gz pdb.gz Legacy PDB Format (gz) \n", + "24 1CH3 1ch3.xml.gz xml.gz PDBML/XML Format (gz) \n", + "25 1CH3 1ch3-sf.cif cif Structure Factors (CIF) \n", + "26 1CH3 1ch3-sf.cif.gz cif.gz Structure Factors (CIF - gz) \n", + "27 1CH3 1ch3_validation.pdf.gz pdf.gz Validation Full (PDF - gz) \n", + "28 1CH3 1ch3_validation.xml.gz xml.gz Validation (XML - gz) \n", + "29 1CH3 1ch3_validation.cif.gz cif.gz Validation (CIF - gz) \n", + "30 1CH5 1ch5.cif cif PDBx/mmCIF Format \n", + "31 1CH5 1ch5.cif.gz cif.gz PDBx/mmCIF Format (gz) \n", + "32 1CH5 1ch5.pdb pdb Legacy PDB Format \n", + "33 1CH5 1ch5.pdb.gz pdb.gz Legacy PDB Format (gz) \n", + "34 1CH5 1ch5.xml.gz xml.gz PDBML/XML Format (gz) \n", + "35 1CH5 1ch5-sf.cif cif Structure Factors (CIF) \n", + "36 1CH5 1ch5-sf.cif.gz cif.gz Structure Factors (CIF - gz) \n", + "37 1CH5 1ch5_validation.pdf.gz pdf.gz Validation Full (PDF - gz) \n", + "38 1CH5 1ch5_validation.xml.gz xml.gz Validation (XML - gz) \n", + "39 1CH5 1ch5_validation.cif.gz cif.gz Validation (CIF - gz) \n", + "\n", + " download_url \n", + "0 https://files.rcsb.org/download/1CH1.cif \n", + "1 https://files.rcsb.org/download/1CH1.cif.gz \n", + "2 https://files.rcsb.org/download/1CH1.pdb \n", + "3 https://files.rcsb.org/download/1CH1.pdb.gz \n", + "4 https://files.rcsb.org/download/1CH1.xml.gz \n", + "5 https://files.rcsb.org/download/1CH1-sf.cif \n", + "6 https://files.rcsb.org/download/1CH1-sf.cif.gz \n", + "7 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "8 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "9 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "10 https://files.rcsb.org/download/1CH2.cif \n", + "11 https://files.rcsb.org/download/1CH2.cif.gz \n", + "12 https://files.rcsb.org/download/1CH2.pdb \n", + "13 https://files.rcsb.org/download/1CH2.pdb.gz \n", + "14 https://files.rcsb.org/download/1CH2.xml.gz \n", + "15 https://files.rcsb.org/download/1CH2-sf.cif \n", + "16 https://files.rcsb.org/download/1CH2-sf.cif.gz \n", + "17 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "18 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "19 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "20 https://files.rcsb.org/download/1CH3.cif \n", + "21 https://files.rcsb.org/download/1CH3.cif.gz \n", + "22 https://files.rcsb.org/download/1CH3.pdb \n", + "23 https://files.rcsb.org/download/1CH3.pdb.gz \n", + "24 https://files.rcsb.org/download/1CH3.xml.gz \n", + "25 https://files.rcsb.org/download/1CH3-sf.cif \n", + "26 https://files.rcsb.org/download/1CH3-sf.cif.gz \n", + "27 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "28 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "29 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "30 https://files.rcsb.org/download/1CH5.cif \n", + "31 https://files.rcsb.org/download/1CH5.cif.gz \n", + "32 https://files.rcsb.org/download/1CH5.pdb \n", + "33 https://files.rcsb.org/download/1CH5.pdb.gz \n", + "34 https://files.rcsb.org/download/1CH5.xml.gz \n", + "35 https://files.rcsb.org/download/1CH5-sf.cif \n", + "36 https://files.rcsb.org/download/1CH5-sf.cif.gz \n", + "37 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "38 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "39 https://files.rcsb.org/pub/pdb/validation_repo... " + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Creates a simplified view of the available PDB resources, including file type and download URL.\n", + "\n", + "resource_view = resources[\n", + " [\n", + " \"dataset_id\",\n", + " \"name\",\n", + " \"format\",\n", + " \"resource_type\",\n", + " \"download_url\",\n", + " ]\n", + "].copy()\n", + "\n", + "resource_view" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8935c468", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
identifiernormalized_identifierrepostatusendpoint_usedendpoint_variablesnotes
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: [identifier, normalized_identifier, repo, status, endpoint_used, endpoint_variables, notes]\n", + "Index: []" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "errors = dsi.get_table(\n", + " \"errors\",\n", + " collection=True\n", + ")\n", + "\n", + "errors" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "add61ab0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "All selected PDB identifiers were resolved successfully.\n" + ] + } + ], + "source": [ + "if errors.empty:\n", + " print(\"All selected PDB identifiers were resolved successfully.\")\n", + "else:\n", + " print(\"Some identifiers could not be resolved:\")\n", + " display(errors)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "0437f3f9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: biopython in ./PDB_new/venv/lib/python3.11/site-packages (1.88)\n", + "Requirement already satisfied: py3Dmol in ./PDB_new/venv/lib/python3.11/site-packages (2.5.5)\n", + "Requirement already satisfied: numpy in ./PDB_new/venv/lib/python3.11/site-packages (from biopython) (2.4.6)\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install biopython py3Dmol" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "50f806ef", + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "\n", + "from Bio.PDB import MMCIFParser\n", + "from Bio.PDB import Superimposer\n", + "\n", + "import py3Dmol" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "fd7d6397", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "format\n", + "cif.gz 12\n", + "cif 8\n", + "xml.gz 8\n", + "pdb 4\n", + "pdb.gz 4\n", + "pdf.gz 4\n", + "Name: count, dtype: int64" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "resources[\"format\"].value_counts(dropna=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "b716dd28", + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
dataset_idnameformatdownload_url
01CH11ch1.cifcifhttps://files.rcsb.org/download/1CH1.cif
11CH11ch1.cif.gzcif.gzhttps://files.rcsb.org/download/1CH1.cif.gz
21CH11ch1.pdbpdbhttps://files.rcsb.org/download/1CH1.pdb
31CH11ch1.pdb.gzpdb.gzhttps://files.rcsb.org/download/1CH1.pdb.gz
41CH11ch1.xml.gzxml.gzhttps://files.rcsb.org/download/1CH1.xml.gz
51CH11ch1-sf.cifcifhttps://files.rcsb.org/download/1CH1-sf.cif
61CH11ch1-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH1-sf.cif.gz
71CH11ch1_validation.pdf.gzpdf.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
81CH11ch1_validation.xml.gzxml.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
91CH11ch1_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
101CH21ch2.cifcifhttps://files.rcsb.org/download/1CH2.cif
111CH21ch2.cif.gzcif.gzhttps://files.rcsb.org/download/1CH2.cif.gz
121CH21ch2.pdbpdbhttps://files.rcsb.org/download/1CH2.pdb
131CH21ch2.pdb.gzpdb.gzhttps://files.rcsb.org/download/1CH2.pdb.gz
141CH21ch2.xml.gzxml.gzhttps://files.rcsb.org/download/1CH2.xml.gz
151CH21ch2-sf.cifcifhttps://files.rcsb.org/download/1CH2-sf.cif
161CH21ch2-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH2-sf.cif.gz
171CH21ch2_validation.pdf.gzpdf.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
181CH21ch2_validation.xml.gzxml.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
191CH21ch2_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
201CH31ch3.cifcifhttps://files.rcsb.org/download/1CH3.cif
211CH31ch3.cif.gzcif.gzhttps://files.rcsb.org/download/1CH3.cif.gz
221CH31ch3.pdbpdbhttps://files.rcsb.org/download/1CH3.pdb
231CH31ch3.pdb.gzpdb.gzhttps://files.rcsb.org/download/1CH3.pdb.gz
241CH31ch3.xml.gzxml.gzhttps://files.rcsb.org/download/1CH3.xml.gz
251CH31ch3-sf.cifcifhttps://files.rcsb.org/download/1CH3-sf.cif
261CH31ch3-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH3-sf.cif.gz
271CH31ch3_validation.pdf.gzpdf.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
281CH31ch3_validation.xml.gzxml.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
291CH31ch3_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
301CH51ch5.cifcifhttps://files.rcsb.org/download/1CH5.cif
311CH51ch5.cif.gzcif.gzhttps://files.rcsb.org/download/1CH5.cif.gz
321CH51ch5.pdbpdbhttps://files.rcsb.org/download/1CH5.pdb
331CH51ch5.pdb.gzpdb.gzhttps://files.rcsb.org/download/1CH5.pdb.gz
341CH51ch5.xml.gzxml.gzhttps://files.rcsb.org/download/1CH5.xml.gz
351CH51ch5-sf.cifcifhttps://files.rcsb.org/download/1CH5-sf.cif
361CH51ch5-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH5-sf.cif.gz
371CH51ch5_validation.pdf.gzpdf.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
381CH51ch5_validation.xml.gzxml.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
391CH51ch5_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
\n", + "
" + ], + "text/plain": [ + " dataset_id name format \\\n", + "0 1CH1 1ch1.cif cif \n", + "1 1CH1 1ch1.cif.gz cif.gz \n", + "2 1CH1 1ch1.pdb pdb \n", + "3 1CH1 1ch1.pdb.gz pdb.gz \n", + "4 1CH1 1ch1.xml.gz xml.gz \n", + "5 1CH1 1ch1-sf.cif cif \n", + "6 1CH1 1ch1-sf.cif.gz cif.gz \n", + "7 1CH1 1ch1_validation.pdf.gz pdf.gz \n", + "8 1CH1 1ch1_validation.xml.gz xml.gz \n", + "9 1CH1 1ch1_validation.cif.gz cif.gz \n", + "10 1CH2 1ch2.cif cif \n", + "11 1CH2 1ch2.cif.gz cif.gz \n", + "12 1CH2 1ch2.pdb pdb \n", + "13 1CH2 1ch2.pdb.gz pdb.gz \n", + "14 1CH2 1ch2.xml.gz xml.gz \n", + "15 1CH2 1ch2-sf.cif cif \n", + "16 1CH2 1ch2-sf.cif.gz cif.gz \n", + "17 1CH2 1ch2_validation.pdf.gz pdf.gz \n", + "18 1CH2 1ch2_validation.xml.gz xml.gz \n", + "19 1CH2 1ch2_validation.cif.gz cif.gz \n", + "20 1CH3 1ch3.cif cif \n", + "21 1CH3 1ch3.cif.gz cif.gz \n", + "22 1CH3 1ch3.pdb pdb \n", + "23 1CH3 1ch3.pdb.gz pdb.gz \n", + "24 1CH3 1ch3.xml.gz xml.gz \n", + "25 1CH3 1ch3-sf.cif cif \n", + "26 1CH3 1ch3-sf.cif.gz cif.gz \n", + "27 1CH3 1ch3_validation.pdf.gz pdf.gz \n", + "28 1CH3 1ch3_validation.xml.gz xml.gz \n", + "29 1CH3 1ch3_validation.cif.gz cif.gz \n", + "30 1CH5 1ch5.cif cif \n", + "31 1CH5 1ch5.cif.gz cif.gz \n", + "32 1CH5 1ch5.pdb pdb \n", + "33 1CH5 1ch5.pdb.gz pdb.gz \n", + "34 1CH5 1ch5.xml.gz xml.gz \n", + "35 1CH5 1ch5-sf.cif cif \n", + "36 1CH5 1ch5-sf.cif.gz cif.gz \n", + "37 1CH5 1ch5_validation.pdf.gz pdf.gz \n", + "38 1CH5 1ch5_validation.xml.gz xml.gz \n", + "39 1CH5 1ch5_validation.cif.gz cif.gz \n", + "\n", + " download_url \n", + "0 https://files.rcsb.org/download/1CH1.cif \n", + "1 https://files.rcsb.org/download/1CH1.cif.gz \n", + "2 https://files.rcsb.org/download/1CH1.pdb \n", + "3 https://files.rcsb.org/download/1CH1.pdb.gz \n", + "4 https://files.rcsb.org/download/1CH1.xml.gz \n", + "5 https://files.rcsb.org/download/1CH1-sf.cif \n", + "6 https://files.rcsb.org/download/1CH1-sf.cif.gz \n", + "7 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "8 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "9 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "10 https://files.rcsb.org/download/1CH2.cif \n", + "11 https://files.rcsb.org/download/1CH2.cif.gz \n", + "12 https://files.rcsb.org/download/1CH2.pdb \n", + "13 https://files.rcsb.org/download/1CH2.pdb.gz \n", + "14 https://files.rcsb.org/download/1CH2.xml.gz \n", + "15 https://files.rcsb.org/download/1CH2-sf.cif \n", + "16 https://files.rcsb.org/download/1CH2-sf.cif.gz \n", + "17 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "18 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "19 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "20 https://files.rcsb.org/download/1CH3.cif \n", + "21 https://files.rcsb.org/download/1CH3.cif.gz \n", + "22 https://files.rcsb.org/download/1CH3.pdb \n", + "23 https://files.rcsb.org/download/1CH3.pdb.gz \n", + "24 https://files.rcsb.org/download/1CH3.xml.gz \n", + "25 https://files.rcsb.org/download/1CH3-sf.cif \n", + "26 https://files.rcsb.org/download/1CH3-sf.cif.gz \n", + "27 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "28 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "29 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "30 https://files.rcsb.org/download/1CH5.cif \n", + "31 https://files.rcsb.org/download/1CH5.cif.gz \n", + "32 https://files.rcsb.org/download/1CH5.pdb \n", + "33 https://files.rcsb.org/download/1CH5.pdb.gz \n", + "34 https://files.rcsb.org/download/1CH5.xml.gz \n", + "35 https://files.rcsb.org/download/1CH5-sf.cif \n", + "36 https://files.rcsb.org/download/1CH5-sf.cif.gz \n", + "37 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "38 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "39 https://files.rcsb.org/pub/pdb/validation_repo... " + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Displays the structure IDs, filenames, formats, and download URLs available through the DSI resources table.\n", + "\n", + "resources[\n", + " [\"dataset_id\", \"name\", \"format\", \"download_url\"]\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "a74a05b6", + "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", + "
dataset_idnameformatdownload_url
01CH11ch1.cifcifhttps://files.rcsb.org/download/1CH1.cif
11CH11ch1.cif.gzcif.gzhttps://files.rcsb.org/download/1CH1.cif.gz
51CH11ch1-sf.cifcifhttps://files.rcsb.org/download/1CH1-sf.cif
61CH11ch1-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH1-sf.cif.gz
91CH11ch1_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
101CH21ch2.cifcifhttps://files.rcsb.org/download/1CH2.cif
111CH21ch2.cif.gzcif.gzhttps://files.rcsb.org/download/1CH2.cif.gz
151CH21ch2-sf.cifcifhttps://files.rcsb.org/download/1CH2-sf.cif
161CH21ch2-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH2-sf.cif.gz
191CH21ch2_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
201CH31ch3.cifcifhttps://files.rcsb.org/download/1CH3.cif
211CH31ch3.cif.gzcif.gzhttps://files.rcsb.org/download/1CH3.cif.gz
251CH31ch3-sf.cifcifhttps://files.rcsb.org/download/1CH3-sf.cif
261CH31ch3-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH3-sf.cif.gz
291CH31ch3_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
301CH51ch5.cifcifhttps://files.rcsb.org/download/1CH5.cif
311CH51ch5.cif.gzcif.gzhttps://files.rcsb.org/download/1CH5.cif.gz
351CH51ch5-sf.cifcifhttps://files.rcsb.org/download/1CH5-sf.cif
361CH51ch5-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH5-sf.cif.gz
391CH51ch5_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
\n", + "
" + ], + "text/plain": [ + " dataset_id name format \\\n", + "0 1CH1 1ch1.cif cif \n", + "1 1CH1 1ch1.cif.gz cif.gz \n", + "5 1CH1 1ch1-sf.cif cif \n", + "6 1CH1 1ch1-sf.cif.gz cif.gz \n", + "9 1CH1 1ch1_validation.cif.gz cif.gz \n", + "10 1CH2 1ch2.cif cif \n", + "11 1CH2 1ch2.cif.gz cif.gz \n", + "15 1CH2 1ch2-sf.cif cif \n", + "16 1CH2 1ch2-sf.cif.gz cif.gz \n", + "19 1CH2 1ch2_validation.cif.gz cif.gz \n", + "20 1CH3 1ch3.cif cif \n", + "21 1CH3 1ch3.cif.gz cif.gz \n", + "25 1CH3 1ch3-sf.cif cif \n", + "26 1CH3 1ch3-sf.cif.gz cif.gz \n", + "29 1CH3 1ch3_validation.cif.gz cif.gz \n", + "30 1CH5 1ch5.cif cif \n", + "31 1CH5 1ch5.cif.gz cif.gz \n", + "35 1CH5 1ch5-sf.cif cif \n", + "36 1CH5 1ch5-sf.cif.gz cif.gz \n", + "39 1CH5 1ch5_validation.cif.gz cif.gz \n", + "\n", + " download_url \n", + "0 https://files.rcsb.org/download/1CH1.cif \n", + "1 https://files.rcsb.org/download/1CH1.cif.gz \n", + "5 https://files.rcsb.org/download/1CH1-sf.cif \n", + "6 https://files.rcsb.org/download/1CH1-sf.cif.gz \n", + "9 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "10 https://files.rcsb.org/download/1CH2.cif \n", + "11 https://files.rcsb.org/download/1CH2.cif.gz \n", + "15 https://files.rcsb.org/download/1CH2-sf.cif \n", + "16 https://files.rcsb.org/download/1CH2-sf.cif.gz \n", + "19 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "20 https://files.rcsb.org/download/1CH3.cif \n", + "21 https://files.rcsb.org/download/1CH3.cif.gz \n", + "25 https://files.rcsb.org/download/1CH3-sf.cif \n", + "26 https://files.rcsb.org/download/1CH3-sf.cif.gz \n", + "29 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "30 https://files.rcsb.org/download/1CH5.cif \n", + "31 https://files.rcsb.org/download/1CH5.cif.gz \n", + "35 https://files.rcsb.org/download/1CH5-sf.cif \n", + "36 https://files.rcsb.org/download/1CH5-sf.cif.gz \n", + "39 https://files.rcsb.org/pub/pdb/validation_repo... " + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Filters the retrieved resources to keep only CIF/mmCIF coordinate files for structural analysis.\n", + "\n", + "cif_resources = resources[\n", + " resources[\"format\"]\n", + " .astype(str)\n", + " .str.lower()\n", + " .str.contains(\"cif\")\n", + "].copy()\n", + "\n", + "cif_resources[\n", + " [\"dataset_id\", \"name\", \"format\", \"download_url\"]\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "f5840f5e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Download directory: /Users/rsankararaman/downloaded_structures\n" + ] + } + ], + "source": [ + "download_dir = Path(\"downloaded_structures\")\n", + "download_dir.mkdir(exist_ok=True)\n", + "\n", + "print(\"Download directory:\", download_dir.resolve())" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "0ef692f7", + "metadata": {}, + "outputs": [], + "source": [ + "def download_resource(url, output_path):\n", + " response = requests.get(\n", + " url,\n", + " timeout=60\n", + " )\n", + " response.raise_for_status()\n", + "\n", + " output_path.write_bytes(response.content)\n", + "\n", + " return output_path" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "a49e014c", + "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", + "
dataset_idnameformatdownload_url
01CH11ch1.cifcifhttps://files.rcsb.org/download/1CH1.cif
11CH11ch1.cif.gzcif.gzhttps://files.rcsb.org/download/1CH1.cif.gz
51CH11ch1-sf.cifcifhttps://files.rcsb.org/download/1CH1-sf.cif
61CH11ch1-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH1-sf.cif.gz
91CH11ch1_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
101CH21ch2.cifcifhttps://files.rcsb.org/download/1CH2.cif
111CH21ch2.cif.gzcif.gzhttps://files.rcsb.org/download/1CH2.cif.gz
151CH21ch2-sf.cifcifhttps://files.rcsb.org/download/1CH2-sf.cif
161CH21ch2-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH2-sf.cif.gz
191CH21ch2_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
201CH31ch3.cifcifhttps://files.rcsb.org/download/1CH3.cif
211CH31ch3.cif.gzcif.gzhttps://files.rcsb.org/download/1CH3.cif.gz
251CH31ch3-sf.cifcifhttps://files.rcsb.org/download/1CH3-sf.cif
261CH31ch3-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH3-sf.cif.gz
291CH31ch3_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
301CH51ch5.cifcifhttps://files.rcsb.org/download/1CH5.cif
311CH51ch5.cif.gzcif.gzhttps://files.rcsb.org/download/1CH5.cif.gz
351CH51ch5-sf.cifcifhttps://files.rcsb.org/download/1CH5-sf.cif
361CH51ch5-sf.cif.gzcif.gzhttps://files.rcsb.org/download/1CH5-sf.cif.gz
391CH51ch5_validation.cif.gzcif.gzhttps://files.rcsb.org/pub/pdb/validation_repo...
\n", + "
" + ], + "text/plain": [ + " dataset_id name format \\\n", + "0 1CH1 1ch1.cif cif \n", + "1 1CH1 1ch1.cif.gz cif.gz \n", + "5 1CH1 1ch1-sf.cif cif \n", + "6 1CH1 1ch1-sf.cif.gz cif.gz \n", + "9 1CH1 1ch1_validation.cif.gz cif.gz \n", + "10 1CH2 1ch2.cif cif \n", + "11 1CH2 1ch2.cif.gz cif.gz \n", + "15 1CH2 1ch2-sf.cif cif \n", + "16 1CH2 1ch2-sf.cif.gz cif.gz \n", + "19 1CH2 1ch2_validation.cif.gz cif.gz \n", + "20 1CH3 1ch3.cif cif \n", + "21 1CH3 1ch3.cif.gz cif.gz \n", + "25 1CH3 1ch3-sf.cif cif \n", + "26 1CH3 1ch3-sf.cif.gz cif.gz \n", + "29 1CH3 1ch3_validation.cif.gz cif.gz \n", + "30 1CH5 1ch5.cif cif \n", + "31 1CH5 1ch5.cif.gz cif.gz \n", + "35 1CH5 1ch5-sf.cif cif \n", + "36 1CH5 1ch5-sf.cif.gz cif.gz \n", + "39 1CH5 1ch5_validation.cif.gz cif.gz \n", + "\n", + " download_url \n", + "0 https://files.rcsb.org/download/1CH1.cif \n", + "1 https://files.rcsb.org/download/1CH1.cif.gz \n", + "5 https://files.rcsb.org/download/1CH1-sf.cif \n", + "6 https://files.rcsb.org/download/1CH1-sf.cif.gz \n", + "9 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "10 https://files.rcsb.org/download/1CH2.cif \n", + "11 https://files.rcsb.org/download/1CH2.cif.gz \n", + "15 https://files.rcsb.org/download/1CH2-sf.cif \n", + "16 https://files.rcsb.org/download/1CH2-sf.cif.gz \n", + "19 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "20 https://files.rcsb.org/download/1CH3.cif \n", + "21 https://files.rcsb.org/download/1CH3.cif.gz \n", + "25 https://files.rcsb.org/download/1CH3-sf.cif \n", + "26 https://files.rcsb.org/download/1CH3-sf.cif.gz \n", + "29 https://files.rcsb.org/pub/pdb/validation_repo... \n", + "30 https://files.rcsb.org/download/1CH5.cif \n", + "31 https://files.rcsb.org/download/1CH5.cif.gz \n", + "35 https://files.rcsb.org/download/1CH5-sf.cif \n", + "36 https://files.rcsb.org/download/1CH5-sf.cif.gz \n", + "39 https://files.rcsb.org/pub/pdb/validation_repo... " + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cif_resources[\n", + " [\"dataset_id\", \"name\", \"format\", \"download_url\"]\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "ba2376b5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'1CH1': PosixPath('downloaded_structures/1CH1.cif'),\n", + " '1CH2': PosixPath('downloaded_structures/1CH2.cif'),\n", + " '1CH3': PosixPath('downloaded_structures/1CH3.cif'),\n", + " '1CH5': PosixPath('downloaded_structures/1CH5.cif')}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import gzip\n", + "\n", + "\n", + "structure_files = {}\n", + "\n", + "for _, row in cif_resources.iterrows():\n", + " pdb_id = row[\"dataset_id\"]\n", + " url = row[\"download_url\"]\n", + "\n", + " if pdb_id in structure_files:\n", + " continue\n", + "\n", + " if str(url).lower().endswith(\".gz\"):\n", + " gz_path = download_dir / f\"{pdb_id}.cif.gz\"\n", + " cif_path = download_dir / f\"{pdb_id}.cif\"\n", + "\n", + " download_resource(\n", + " url,\n", + " gz_path\n", + " )\n", + "\n", + " with gzip.open(gz_path, \"rb\") as source:\n", + " with open(cif_path, \"wb\") as destination:\n", + " destination.write(source.read())\n", + "\n", + " structure_files[pdb_id] = cif_path\n", + "\n", + " else:\n", + " cif_path = download_dir / f\"{pdb_id}.cif\"\n", + "\n", + " download_resource(\n", + " url,\n", + " cif_path\n", + " )\n", + "\n", + " structure_files[pdb_id] = cif_path\n", + "\n", + "\n", + "structure_files" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "2f8d128a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1CH1: 1CH1.cif (205.4 KB)\n", + "1CH2: 1CH2.cif (199.0 KB)\n", + "1CH3: 1CH3.cif (200.1 KB)\n", + "1CH5: 1CH5.cif (191.7 KB)\n" + ] + } + ], + "source": [ + "for pdb_id in pdb_ids:\n", + " path = structure_files.get(pdb_id)\n", + "\n", + " if path is None:\n", + " print(f\"{pdb_id}: no CIF resource selected\")\n", + " else:\n", + " print(\n", + " f\"{pdb_id}: {path.name} \"\n", + " f\"({path.stat().st_size / 1024:.1f} KB)\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "f3a75bd2", + "metadata": {}, + "source": [ + "## 4. Parse the Retrieved Protein Structures\n", + "\n", + "At this point DSI has completed its main role in the workflow:\n", + "\n", + "1. resolve the requested PDB structures,\n", + "2. retrieve normalized repository metadata,\n", + "3. expose their downloadable coordinate resources.\n", + "\n", + "The remainder of the notebook uses standard structural-biology Python tools to\n", + "analyze the files obtained through that DSI workflow.\n", + "\n", + "BioPython is used to parse the mmCIF coordinate files and perform simple\n", + "structural comparisons." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "9f05c0eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Parsed structures: ['1CH1', '1CH2', '1CH3', '1CH5']\n" + ] + } + ], + "source": [ + "parser = MMCIFParser(\n", + " QUIET=True\n", + ")\n", + "\n", + "parsed_structures = {}\n", + "\n", + "for pdb_id, path in structure_files.items():\n", + " parsed_structures[pdb_id] = parser.get_structure(\n", + " pdb_id,\n", + " str(path)\n", + " )\n", + "\n", + "print(\n", + " \"Parsed structures:\",\n", + " list(parsed_structures.keys())\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "27f1eeab", + "metadata": {}, + "outputs": [], + "source": [ + "def summarize_structure(structure):\n", + " models = list(structure.get_models())\n", + " chains = list(structure.get_chains())\n", + " atoms = list(structure.get_atoms())\n", + "\n", + " protein_residues = [\n", + " residue\n", + " for residue in structure.get_residues()\n", + " if residue.id[0] == \" \"\n", + " ]\n", + "\n", + " return {\n", + " \"models\": len(models),\n", + " \"chains\": len(chains),\n", + " \"protein_residues\": len(protein_residues),\n", + " \"atoms\": len(atoms),\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "bd4320af", + "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", + "
pdb_idmutationmodelschainsprotein_residuesatoms
01CH1L89G111541456
11CH2L89F111541428
21CH3L89W111541417
31CH5H97V111541360
\n", + "
" + ], + "text/plain": [ + " pdb_id mutation models chains protein_residues atoms\n", + "0 1CH1 L89G 1 1 154 1456\n", + "1 1CH2 L89F 1 1 154 1428\n", + "2 1CH3 L89W 1 1 154 1417\n", + "3 1CH5 H97V 1 1 154 1360" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "summary_rows = []\n", + "\n", + "for pdb_id, structure in parsed_structures.items():\n", + "\n", + " mutation = selected_structures.loc[\n", + " selected_structures[\"pdb_id\"] == pdb_id,\n", + " \"mutation\"\n", + " ].iloc[0]\n", + "\n", + " row = {\n", + " \"pdb_id\": pdb_id,\n", + " \"mutation\": mutation,\n", + " }\n", + "\n", + " row.update(\n", + " summarize_structure(structure)\n", + " )\n", + "\n", + " summary_rows.append(row)\n", + "\n", + "\n", + "structure_summary = pd.DataFrame(\n", + " summary_rows\n", + ")\n", + "\n", + "structure_summary" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "22941a93", + "metadata": {}, + "outputs": [], + "source": [ + "def non_water_hetero_residues(structure):\n", + "\n", + " residues = []\n", + "\n", + " for residue in structure.get_residues():\n", + "\n", + " if (\n", + " residue.id[0] != \" \"\n", + " and residue.resname != \"HOH\"\n", + " ):\n", + " residues.append(\n", + " residue.resname\n", + " )\n", + "\n", + " return sorted(\n", + " set(residues)\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "32175ba0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1CH1 ['HEM', 'SO4']\n", + "1CH2 ['HEM', 'SO4']\n", + "1CH3 ['HEM', 'SO4']\n", + "1CH5 ['HEM', 'SO4']\n" + ] + } + ], + "source": [ + "for pdb_id, structure in parsed_structures.items():\n", + "\n", + " print(\n", + " pdb_id,\n", + " non_water_hetero_residues(structure)\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "5d851dc8", + "metadata": {}, + "source": [ + "## 5. Compare Overall Structural Similarity\n", + "\n", + "All four entries represent variants of the same protein. A simple way to\n", + "compare their overall three-dimensional folds is to align corresponding\n", + "alpha-carbon (Cα) atoms and calculate the root-mean-square deviation (RMSD).\n", + "\n", + "RMSD provides a quantitative measure of structural similarity after alignment.\n", + "\n", + "- Lower RMSD indicates greater overall structural similarity.\n", + "- Higher RMSD indicates greater overall structural deviation.\n", + "\n", + "This demonstration focuses on global backbone similarity. It does not attempt\n", + "to establish the biological effect of an individual mutation, which would\n", + "require detailed analysis of local interactions, side-chain geometry,\n", + "experimental conditions, and other structural features." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "4c6f7e81", + "metadata": {}, + "outputs": [], + "source": [ + "def get_ca_atom_map(structure):\n", + "\n", + " ca_atoms = {}\n", + "\n", + " for model in structure:\n", + " for chain in model:\n", + " for residue in chain:\n", + "\n", + " if (\n", + " residue.id[0] == \" \"\n", + " and \"CA\" in residue\n", + " ):\n", + "\n", + " key = (\n", + " chain.id,\n", + " residue.id[1],\n", + " residue.id[2]\n", + " )\n", + "\n", + " ca_atoms[key] = residue[\"CA\"]\n", + "\n", + " break\n", + "\n", + " return ca_atoms" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "185aba39", + "metadata": {}, + "outputs": [], + "source": [ + "def calculate_ca_rmsd(\n", + " reference_structure,\n", + " mobile_structure\n", + "):\n", + "\n", + " reference_atoms = get_ca_atom_map(\n", + " reference_structure\n", + " )\n", + "\n", + " mobile_atoms = get_ca_atom_map(\n", + " mobile_structure\n", + " )\n", + "\n", + " shared_residues = sorted(\n", + " set(reference_atoms)\n", + " & set(mobile_atoms)\n", + " )\n", + "\n", + " fixed = [\n", + " reference_atoms[key]\n", + " for key in shared_residues\n", + " ]\n", + "\n", + " moving = [\n", + " mobile_atoms[key]\n", + " for key in shared_residues\n", + " ]\n", + "\n", + " if len(fixed) < 3:\n", + " raise ValueError(\n", + " \"Not enough shared C-alpha atoms \"\n", + " \"for structural alignment.\"\n", + " )\n", + "\n", + " superimposer = Superimposer()\n", + "\n", + " superimposer.set_atoms(\n", + " fixed,\n", + " moving\n", + " )\n", + "\n", + " return {\n", + " \"rmsd\": superimposer.rms,\n", + " \"matched_residues\": len(shared_residues),\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "884e2350", + "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", + "
referencecomparisonmutationmatched_ca_atomsrmsd_angstrom
01CH11CH2L89F1540.175338
11CH11CH3L89W1540.167421
21CH11CH5H97V1540.527322
\n", + "
" + ], + "text/plain": [ + " reference comparison mutation matched_ca_atoms rmsd_angstrom\n", + "0 1CH1 1CH2 L89F 154 0.175338\n", + "1 1CH1 1CH3 L89W 154 0.167421\n", + "2 1CH1 1CH5 H97V 154 0.527322" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "reference_id = \"1CH1\"\n", + "\n", + "comparison_rows = []\n", + "\n", + "for pdb_id in pdb_ids:\n", + "\n", + " if pdb_id == reference_id:\n", + " continue\n", + "\n", + " result = calculate_ca_rmsd(\n", + " parsed_structures[reference_id],\n", + " parsed_structures[pdb_id]\n", + " )\n", + "\n", + " mutation = selected_structures.loc[\n", + " selected_structures[\"pdb_id\"] == pdb_id,\n", + " \"mutation\"\n", + " ].iloc[0]\n", + "\n", + " comparison_rows.append(\n", + " {\n", + " \"reference\": reference_id,\n", + " \"comparison\": pdb_id,\n", + " \"mutation\": mutation,\n", + " \"matched_ca_atoms\":\n", + " result[\"matched_residues\"],\n", + " \"rmsd_angstrom\":\n", + " result[\"rmsd\"],\n", + " }\n", + " )\n", + "\n", + "\n", + "rmsd_results = pd.DataFrame(\n", + " comparison_rows\n", + ")\n", + "\n", + "rmsd_results" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "43c51802", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAArIAAAGGCAYAAACHemKmAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvctoD+AAAAAlwSFlzAAAPYQAAD2EBqD+naQAAQ1RJREFUeJzt3QeUE1XbwPFn6R0pS116b4JUUZAmSlV4QaoISPG1oogCggIKUmwgL6JUUREQRFCqdFmVDiKKUqQsSG9Lbzvfee45yZfsJrvJJtlNdv+/cwKbKXfuTDLJkzvPvRNmWZYlAAAAQIhJk9wVAAAAABKDQBYAAAAhiUAWAAAAIYlAFgAAACGJQBYAAAAhiUAWAAAAIYlAFgAAACGJQBYAAAAhiUAWAAAAIYlAFiHjhRdekHvuuSfBaUiaYx+MZborNym3FQxWrlwp9913n2TOnFnCwsLk8OHDSbbtYD0mnhgzZow5XhcvXpSUyJfX5sknn5SIiIiAbwfwFoFsKrJx40b5z3/+IwULFpQMGTJIgQIFzPOffvpJUoMffvhBmjRpYvY/a9asUrlyZXnppZfkzz//dFqud+/ekjdv3mSrZ7DVI1DHOaVK7tft/Pnz8sQTT0j9+vXlwoULonchL168eIp5vZL7+KqhQ4eagFcff/zxh8tlSpcubeZXq1YtyeuXEiXVMU/q91cwvJ9DHYFsKjFq1Chp0KCBFCpUSDZs2CBXrlyRyMhI8wu7YcOGZn5KNnbsWHnsscfk3nvvlV9++UXOnj0rn376qezYsUMaN26c3NVLlcf5f//7X0BavgJVbnJvy1M7d+6Uy5cvm2A2U6ZM8S7LeeEbDfw///xzl40GBw8eNPNTo0CeFxxzxEYgmwp8//335tfswIEDzQdM2bJlTYus/nr9+OOPzXSdry0zKZG2SI0ePVrq1KkjH330kZQoUcJccq1Xr54J6vv06ZPcVUwROM7BQX88KH2Px4fXy3d6Reurr76Su3fvOk2fOXOmVK1a1XzGwr845oiNQDYVePvtt02+0ltvveVyvk7X+SNGjDDP9f80adLIP//8E2fZDz/80Fy6cbzsqC1Abdq0kTx58kjGjBmlUqVK8tlnn7nMr9Iv2S5dukiuXLmkVq1aZt7IkSPtl4z0kTNnTnOpc+3atX7Z/9u3b5sWqsKFC8eZlzZtWnnnnXfszx9++GGZPn26nDt3zqlOtuAgvv3Qlu37778/zjZs+6et4I70uLVr107y5csnWbJkMeXMnTvXo3p4s62kOvbeHGd3eXS2up45c0bat28vOXLkMM8nT55s5p86dcq0NGo9w8PDZdiwYYnOz/N03+M7frG3Fd/rplc99Lzav39/nLq89957cc4rV5YvX25SBrJly2ZapvTH2NKlS+3za9asKZ06dTJ/ax21zGbNmvnl9fL0XHfH03V9OS+82c7XX38tFStWNK3WusyiRYvEWz169JCTJ0/KihUr7NOuXr0q8+fPl549e8ZZ3tv3QEKvt82XX34ZZ1+8yff1dDvq9OnT5vXRc1Mvifft21eio6M9Pre1Pl27djXnW+7cuc2l9WvXrkmgjrk3n5cJvb+8/cxIaF8T2t7ff/9tPu9saT96pUvfJzdu3PD4eKUKFlK0M2fOWPoyt23bNt7l2rRpY5bT5Y8ePWqlSZPGGjp0aJzlKleubNWtW9f+fP369VbGjBmtDh06WH///bd16dIl66uvvrKyZs1qvfPOO/blunbtahUsWNDUY+XKldb58+etzz//PE75d+7csf755x+rb9++VqZMmaw///zTPu/555+3cubM6bS8q2mu1KlTx9Rp7dq1CS7bq1cvK0+ePC7nxbcfDRo0MNuJTY+DHtvLly/bp61Zs8bKkCGD1bp1a2v37t3WlStXrO3bt1udO3e2jh07lmA9vNlWoI69r8fZVZm2unbq1MnasGGDeT9NnDjR7NPixYutVq1ambJ1+uTJk830uXPnJlhuQvWPb9/jO36uynX3up06dcq85q+88orT9JiYGKtUqVLW/fffH+/x0v0MCwuznn32WSsqKsq8T1588UUzTc85mzlz5pjjsnXrVsufr5en57qrY+Lpur6eF55u54svvjDHaODAgdaJEyesQ4cOmddZt6vTL1y4EO+xGDJkiFlOX4cHHnjAat++vX3ezJkzrfTp05vP0qpVq5pHYt4Dnr7euj2tyxtvvGGdPHnSOnz4sNWjRw+X++LqtfF0O7bzQL8rVq1aZY6tng/58+e36tevb929ezfe7djW79Kli/Xjjz9a0dHR5pzOnDmz9eqrr8Z7vH055t5+Xsb3/vL2M8OTfXW3vdu3b1tFihSxmjVrZu3fv9+6fv269ccff1iDBg2yFi1alGD9UhMC2RROvwT0RH35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zeHTzmFNmjSJs62WLVuakQ80oHWkqQraWqvnkfbWBoKFdrzSFlX9PzbNmXX8Qaa53bNmzTLng1610HNLc1xd3UlS7+6lHSh1WDp3NNDVVBsd4aNnz55+3Cv4U5gOJuvXEgEAAIAkQDs5AAAAQhKBLAAAAEISgSwAAABCEoEsAAAAQhKBLAAAAEISgSwAAABCEoEsAAAAQhKBLAAk0R3tbPd995TeKEFv/OHvcpNSsNcPQGgjkAWQohw/flzmzp0rK1eujDNP7xan8/QRExOTpPXasGGDPPfcc16to/vQv39/v5eblAJVv3/++Ud++OEHv5cLILQQyAJIUbZu3SqdO3eW1q1by6lTp5zmzZ8/38zTx61btyQlKFGiRLz3i0+p9Vu7dq28+OKLfi8XQGghkAWQItWtW1e++OILp2nTp0+XBg0a2J9fuHDBtM5evXrVablz586Z6deuXbNP2717tyxcuNDcd91Va65O+/nnn2X58uWmVfjYsWPy3XffJVjPhMpVt2/flm3btsnixYvl5MmTTvOKFi0qjz76aJx0hDt37siOHTtk6dKlpj7unD9/Ps6+Oh4DPTa6TVtLttbhr7/+ilOObbta140bN8o333xj/o5dP2/KcrcP+rf+YNG62crau3ev230EkIJZAJCCfPfdd5Z+tM2aNcsqX768ffr+/futTJkyWVOmTDHzr1+/bt26dcsKDw+3pk+f7lTGyJEjrVKlSlkxMTHW7du3rbZt21q5cuWymjdvbhUuXNiqUaOGdfr0afvyWlbDhg2tvHnzmmUiIiKspk2bWnny5LEvM3PmTLOujSflTpw40cqfP79VvXp168EHH7Tq1q1rZc6c2Vq8eLHbcnWdAgUKWPfff7/VoEED89D9dlzH0Y0bN6x77rnH+vLLL52mjxs3zipWrJg5Brt27bI6duxoHq1atTJ17tKli5kXu661atWy6tevb5a9evVqnPp5WlZ8+7B161aznSxZstjLcrd/AFI2AlkAKTKQvXTpkgkkf/75ZzN94MCBVufOna358+fbA1k1YMAAEyQ6Kl26tDVq1Cjz9//+9z8rd+7c1pEjR8zz6Oho695777X69OljX/7DDz80gdeJEyfM83PnzlklSpSIN5D1pFwN6LSu7733nn3asGHDzLauXbvmslzbOgsWLLBP033Ust3p1auX1axZM6dpVatWtQYPHuxy+TNnzlgFCxZ02oZtu1ofR7Hr501Z8e3D1KlTTaANIHUjtQBAipQhQwZ58sknTTqBXqKeNWuW9OrVK85yvXv3ll9++UX27dtn75ykPe27d+9unutlay1HL5Gr7Nmzy0svvSTz5s2zl7FgwQKzTIECBczz3Llzu9yWI0/KVRkzZnTKBR0wYICcPn1afvrpJ7dlh4eHS7t27ezPGzZsKH///bfb5bUeq1atMuWqP/74Q3777Tcz3UbTBDZt2mTSIFavXi1FihSRLVu2OJWTI0cO+3GLjydlebsPAFInAlkAKZYGk5qrqUFjpkyZpHHjxnGWKVeunDz44IMyY8YM81z/f+SRR6Rw4cLm+ZEjR6RkyZJO65QqVUqio6NNjq2KioqS4sWLOy0T+3lsnpSrChYsaIJZm2zZspkgT9d3RwNpR7r+zZs33S6vecOFChWyB9GzZ8+W++67TypWrGie//7776auTz31lMycOVMWLVpkOtLZAl8bDeTDwsLi3W9Py/J2HwCkTgSyAFKsKlWqSIUKFeSFF16Qp59+2m2Q1adPH9MxTANIbV3VZW3y5s1rOkQ50ufp06c3LZC2oOvixYtOyzgGo654Uq6KXa6mhOk0Xd9f9LjoSA4awGr5X3/9tVNr7JAhQ8yPAG211iGv9IdB+fLlzbKxy0mIp2UBgCcIZAGkaBo4NWvWTHr27Ol2mSeeeML02tcANkuWLPLYY4/Z59WrV8+0GjqOKKDDeOmoCGnTpjXPtUU39qD/Cd0EwJNylQat69evtz/X3vy6Tq1atcSfNHDdvHmzScHQERc0sHUcaUBbrh2f6wgNieGvsrRlWscFBpC6pUvuCgBAIOkYpgmNY5o5c2bp2rWrfPLJJ/Lyyy+b/FqboUOHmgCzRYsW8p///McMk6XDRjkGlwMHDpRq1apJmzZtpGXLlrJmzRrZuXNnvC2UnpSrsmbNai7Da/6sBrBjxowxf2teqb9br/Wh+bjaYqopDTa6Xx988IFJz0iXLp1MnDjR/J8Y/iqrevXqcubMGRk5cqSULl1aqlatalrfAaQuBLIAUpSIiAjp2LGjU6umIw0AXc3XAEsDWce0ApUvXz7ZtWuXfPbZZxIZGWnyQHVsU70cbqMdtnRc08mTJ5v/GzVqJPXr1zcBm7sbA3hSbtmyZeWZZ56RHj16mEvw//77r3z44YdOHapil6vraDDtSMvWfU7IG2+8YVqJdXuOBg0aZI6rdoTTXNUJEyaYDnFp0qSJd7uu6pfYsmLvgy6jrdPa8q2d07QlnUAWSH3CdOiC5K4EACQ3vRWsjl6gvekTQ/NbHTsoaWCs+a7a6goACAxaZAGkatoKqjman376qRnhILF0pAPNrdVL8itXrjS3UF23bp1f6woAcEZnLwCpmo6XqukAU6ZMkVatWiW6HL3ErZfHtUVX8zX1cneNGjX8WlcAgDNSCwAAABCSaJEFAABASCKQBQAAQEgikAUAAEBIIpAFAABASCKQBQAAQEgikAUAAEBIIpAFAABASCKQBQAAQEgikAUAAICEov8DvFT7KNegIf4AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "plt.figure(\n", + " figsize=(7, 4)\n", + ")\n", + "\n", + "plt.bar(\n", + " rmsd_results[\"mutation\"],\n", + " rmsd_results[\"rmsd_angstrom\"]\n", + ")\n", + "\n", + "plt.xlabel(\n", + " \"Myoglobin variant\"\n", + ")\n", + "\n", + "plt.ylabel(\n", + " \"Cα RMSD relative to 1CH1 (Å)\"\n", + ")\n", + "\n", + "plt.title(\n", + " \"Overall Structural Similarity of \"\n", + " \"Selected Myoglobin Mutants\"\n", + ")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "5893d179", + "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", + "
1CH11CH21CH31CH5
1CH10.0000.1750.1670.527
1CH20.1750.0000.1240.470
1CH30.1670.1240.0000.484
1CH50.5270.4700.4840.000
\n", + "
" + ], + "text/plain": [ + " 1CH1 1CH2 1CH3 1CH5\n", + "1CH1 0.000 0.175 0.167 0.527\n", + "1CH2 0.175 0.000 0.124 0.470\n", + "1CH3 0.167 0.124 0.000 0.484\n", + "1CH5 0.527 0.470 0.484 0.000" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rmsd_matrix = pd.DataFrame(\n", + " index=pdb_ids,\n", + " columns=pdb_ids,\n", + " dtype=float\n", + ")\n", + "\n", + "\n", + "for pdb_a in pdb_ids:\n", + "\n", + " for pdb_b in pdb_ids:\n", + "\n", + " if pdb_a == pdb_b:\n", + "\n", + " rmsd_matrix.loc[\n", + " pdb_a,\n", + " pdb_b\n", + " ] = 0.0\n", + "\n", + " else:\n", + "\n", + " result = calculate_ca_rmsd(\n", + " parsed_structures[pdb_a],\n", + " parsed_structures[pdb_b]\n", + " )\n", + "\n", + " rmsd_matrix.loc[\n", + " pdb_a,\n", + " pdb_b\n", + " ] = result[\"rmsd\"]\n", + "\n", + "\n", + "rmsd_matrix.round(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "befe0621", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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2VFbX73nfXl2jRIRERE5z2DUqUULBo2OU1N5dphIREREVAFmlIiIiNyMdNPWasjM4OE5FQZKREREbsZg9FKLVsfyZJ792xMRERFVgBklIiIiN6OHTi1aHcuTMaNEREREZAczSkRERG6GNUraYaBERETkZvQaDpnp4dk49EZERERkBzNKREREboZDb9phRomIiIjIDmaUiIiI3Ize6KUWrY7lyRgoERERuRkjdDBoVMxtZB8lIiIiIrKFGSUiIiI3w6E37Xj2wCMRERFRBZhRIiIicjMGo04tWh3LkzFQIiIicjN6eKlFq2N5Ms/+7YmIiIgqwIwSERGRm+HQm3YYKBEREbkZA7zUotWxPJln//ZEREREFWBGiYiIyM3ojTq1aHUsT8aMEhEREZEdzCgRERG5GRZza4eBEhERkZsxGr1gMHppdixP5tm/PREREVEFmFEiIiJyM3ro1KLVsTwZAyUiIiLSVFJSEn766SdkZWWhV69eanHUkiVLsGzZMgwaNAidO3eu8meGQ29ERERuxmAsLeg+/wVOWbt2LZo3b46FCxfi0KFDGDhwIB5//HGH9j18+DBGjhyJF154ARs2bEB1wECJiIjIzUght5aLM+666y6VDfrll18wY8YMfPPNN3jllVewZcuWCvcrLi7GLbfcgv/973/w8ak+A14MlIiIiEgTBw8exObNmzFmzBjzuquuugp16tTB3LlzK9x3ypQpSEhIwKhRo6rVs1F9QjYiIiLShAE6tWh1LJGZmQlL/v7+arG0Z88e9bVp06bmdTqdDo0bNzbfZstff/2FWbNmqSCrumFGiYiIiM6pXr16CA8PNy/Tpk0rt012drb6KrdbioiIMN9WVnJysqpL+vzzzxEVFVXtnglmlIiIiNxMZVzr7dixYwgLCzOvL5tNEiEhIeprRkaGCo5M0tPTER8fb/P4r776KgIDA/HPP/+oxVSvJMXghYWFuO+++1CVGCgRERG5GVeKsCs6lpAgyTJQskVmu4l9+/YhMTFRfW80GnHgwAFceumlNve57LLLEBwcjOqKgRIRERFpolGjRujQoQM+/fRT9O3bV61btGgRTpw4gaFDh5q3k9sl+zRs2DBcffXVarH00ksvqZlzMoOuqjFQIiIicsdibo2G3gxOFoXPnDkT/fr1U/2T6tevj2+//RaPPvoo2rdvbxUoxcXFqUCpumOgRERE5GaMGs56Mzp5nG7dumH37t3mztxSa9S7d2+rbaR9gKmeyZannnqqWnTlFgyUiIiISFN16tTBxIkT7d5u2WfJXqBUXTBQIiIicjOmy49odSxPxj5KRERERHYwo3QeUkd3hbdfwPkcgjQS8+FqnstqJH9Q16p+CPSf33LL97qhqpGbq6/R7QE8FQMlIiIiN8OhN+14dphIREREVAFmlIiIiNxMZVwU11Mxo0RERERkBzNKREREboY1StphoERERORmGChph0NvRERERHYwo0RERORmmFHSDjNKRERERHYwo0RERORmmFHSDgMlIiIiN2PUsP+REZ6NQ29EREREdjCjRERE5GY49KYdBkpERERuhoGSdjj0RkRERGQHM0pERERuhhkl7TCjRERERGQHM0pERERuhhkl7TBQIiIicjNGo04tWh3Lk3HojYiIiMgOZpSIiIjcjHTl1qozt0Gj49RUzCgRERER2cGMEhERkZthMbd2GCgRERG5GRZza4dDb0RERER2MKNERETkZjj0ph1mlIiIiIjsYEaJiIjIzbBGSTsMlIiIiNwwUJLhN62O5ck49EZERERkBzNKREREbsaoMkHaHcuTMVAiIiJyM3LZEfmn1bE8GYfeiIiIiOxgRomIiMjNcNabdphRIiIiIrKDGSUiIiI3I60BdBpN6zd4eHsABkpERERuRma8aTbrzQiPxqE3IiIiIjuYUSIiInIzLObWDjNKRERERHYwo0RERORmmFHSDgMlIiIiN8NZb9phoOQmjHo9krctQ+aRXZBu8+GJrVGr7cXQeXnb3UdfVICz+zapxcvHB40Hji+3TWHWWSRvXYa81BOQAwfFJKBWuz7wC4mo5N+oZis05uMgdiELZ+EDPySgIWrrEs57H1eOS0B2+nEcP7AM+blpCAyORt0mlyE4LP6c+yQdWYPs9GOo16wfYuLbWN1eXJSPU0fWIOPMARgMxQiJSEBC4z7w8w/lKT+HfxZm4M8f0pGbrUfzDoG4+Z5aCI1w7O3ou/dSsGJRJq4fG43Lri15HcrO1OPxWw7b3P7aUVHod0MknxOq2TVKJ06cwPHjx8+5XUpKCk6fPl1ufWFhIfbv36++liXHlf3KMhgMSEpKgl6vhzs4tvx7nNmxErXaXYJabXojeetSHF/5U4X77P3xTeSmHINfWBTyz562GUjtm/8O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saWXkzSklIaWf/31lyr0luzS2LFjUakZpdatW+O3335TU/Es5eXlqeZOLVq0cPaQRERERC4ZNGgQdu/erTp7Sx+lyy+/XHX4lhlxbdq0wb333qtqqBcvXqyG4OwlezTLKF166aWqVXiXLl3Ug5Jru8m4n6SzxA033ODsIYmIiMhNOnNXBckejR8/Xi2ZmZlqdEuuFSedvaU0SGqVXOV0oCTdLuVSJZMnT8ZLL72EM2fOICIiAoMHD8Yrr7xyXm3CiYiISAMeNPRWlvR1lIvzasWlS5hIU6f3339fLdIWwNvbW7MHRERERFRdnPe13hgkERERVTMenFGq8kDpyy+/xBtvvGH3dumS+eCDD57v4yIiIiKqeYFSq1at1DQ8S9JkUuqWzp49W66/EhEREV1gzChVXaAk/QpsdbacMmWKuv6br6+vVo+NiIiIXOFhs95MpA3A0qVLsX37djXrLS4uDpdddpmarV9lNUomMvVuwIAB6joqcvFcIiIiogtl27ZtGDp0KPbv369m6EviprCwUMUnEydOxFtvveVSXbXrjQXKMBqN2LBhAzNKREREVczTrvWWn5+v2hRJ5mj16tUqQCooKEBKSgpmzJiBr776Cm+++eaFySgtXLgQX3/9tdU6aREgaS5pPPnee++59ECIiIiIXCFXBgkODlYxip+fn3m9dOEeN26cGoKbMGECJk2apLJNlZpRkgcQEhJitcg13m677Tbs2LFDdeomIiKialDMrdVSza1cuVLNurcMkixJtklGvly5Hq3TGaX+/furhYiIiKg6OHHiBPr27VvhNs2aNVPbySVNKjWj9M477+DJJ590djciIiKiSpGXl3fOS6gFBASomXCVnlGSa6hs2rTJ6TsiIiKiC0OqcLQqwtah+pNhtXXr1qn2APZIYbdsV+mB0tVXX40XXngBW7Zs0fSiczWRV7G0RajqR0Eif1BXnohqJGDhuqp+CPSfqHeyeS6qCX9vw4W7Mw/so/TMM89UynGdDpSkkZNMu5MO3G3atEGtWrWsbh82bJiqMCciIiK6EObMmaNik3MJDw+v/EBJptjdfPPNdm+vW7eu0w+CiIiINORhlzAJCwurtGM7HShJTwLJGtm6ptuuXbtUQRURERHRhW46Kd24Td23pV5p5syZqlSoY8eOuOuuu1SXbmc5vYdc/Pabb76xe9u3337r9IMg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1kdHRyM9PR01ETNKRERE7qYSLmFy7NgxhIWFmVeXzSZZrsvOzkZERIR5vfwcGRmJmogZJSIiIjqnsLAwq8VWoNS4cWP19ejRo1brjxw5gkaNGtXIs8xAiYiIyE2v9abV4iipRWrQoAG+//5787qNGzfiwIEDGDhwoHmdFHxLn6WagENvREREpAmdTqd6Jt1www3w8vJC/fr18eabb+L6669Hnz59zNs988wzqk9Sr1691M9bt25V7QRMxeDz589XdVA9e/ZUx6hKDJSIiIjcTSXUKDnq2muvVdkiaSK5efNmPPXUUxg9erTVNn379rWqYdqxY4cKjsTw4cNV/yWRmJjIQImIiIi0pTOULFody1ldu3ZViz3PPvus1c8333yzWqoj1igRERER2cGhNyIiIndThUNv7oaBEhERkbuxaBSpybE8GIfeiIiIiOxgRomIiMjNOHsx23Mdy5Mxo0RERERkBzNKbiQn14C1m/Kh0wHdOwUgMLDiOHjhnzkoLrb+pNC6uR+aNvKzWncmVY+tuwoQHOSFdi39znlcKpGWUoRDuwoQGuGNJm0C4OWlc/jUbPwnG/piI7peHmped3hPPpKOFNrcvkf/0usvkW2H9xch6VgxEhJ9UL+Rr8OnKS/XgHXL8xFdyxttOllfssFgMGLfziKkndGr25u28lUN96hiRqMRmUhDMYoQikj46fw12ceV47otFnNrhoGSm/hndR5uuCMJCfE+0OuBlFQ95n0ej+4XBdjd55a7TqnAKK526X+D224MNQdK+fkG3PNECv5YmouWTf1w8pQeyWeK8fGbsbimf/AF+b1qqvmfpeKL106jUasAJJ8oQkSMD57/LBHh0ef+k/vzh7N496kk+Pjq8OO2lub1e7bkYd3iLKttd23KhY+fjoFSBfR6I5578AxWLclD05Z+2LujEJdeGYQnX412KHh99ak0/LkgB10vDsTrn9Y2rz9+pAiTRiejsMCIxMa+OLi3CCGhXnjj89qIS+BLqz0FxjxswnIVzAQgEFnIQHNjeyToGp3XPq4c163JZ2CN+ijBs0feqjZQkuj/999/x+zZs1GnTh1MnTrV5nbp6en49ttvVYdPg8GAJk2aqPbopovvycX2HnvsMbz88suqi6elJ598Ul2I74477lA/FxQUYOTIkeXuY8KECVbt1WuSggIjbp14CjcPDcX0F2qp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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Visualizes the pairwise RMSD matrix as a heatmap to show which myoglobin structures are most structurally similar.\n", + "\n", + "fig, ax = plt.subplots(\n", + " figsize=(6, 5)\n", + ")\n", + "\n", + "image = ax.imshow(\n", + " rmsd_matrix.values\n", + ")\n", + "\n", + "ax.set_xticks(\n", + " range(len(pdb_ids))\n", + ")\n", + "\n", + "ax.set_yticks(\n", + " range(len(pdb_ids))\n", + ")\n", + "\n", + "ax.set_xticklabels(\n", + " pdb_ids\n", + ")\n", + "\n", + "ax.set_yticklabels(\n", + " pdb_ids\n", + ")\n", + "\n", + "ax.set_xlabel(\n", + " \"PDB structure\"\n", + ")\n", + "\n", + "ax.set_ylabel(\n", + " \"PDB structure\"\n", + ")\n", + "\n", + "ax.set_title(\n", + " \"Pairwise Cα RMSD\"\n", + ")\n", + "\n", + "for i in range(len(pdb_ids)):\n", + "\n", + " for j in range(len(pdb_ids)):\n", + "\n", + " ax.text(\n", + " j,\n", + " i,\n", + " f\"{rmsd_matrix.iloc[i, j]:.2f}\",\n", + " ha=\"center\",\n", + " va=\"center\"\n", + " )\n", + "\n", + "\n", + "plt.colorbar(\n", + " image,\n", + " label=\"RMSD (Å)\"\n", + ")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "4d67bd67", + "metadata": {}, + "source": [ + "## 6. Interactive 3D Visualization\n", + "\n", + "Numerical RMSD values summarize overall structural similarity, but protein\n", + "structures are inherently three-dimensional.\n", + "\n", + "The retrieved mmCIF coordinate files can therefore be passed directly to an\n", + "interactive molecular viewer." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "42d3de6e", + "metadata": {}, + "outputs": [ + { + "data": { + "application/3dmoljs_load.v0": "
\n

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n
\n", + "text/html": [ + "
\n", + "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", + "
\n", + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Creates an interactive 3D molecular visualization of the 1CH1 myoglobin structure with the heme group highlighted.\n", + "\n", + "pdb_id = \"1CH1\"\n", + "\n", + "viewer = py3Dmol.view(\n", + " width=800,\n", + " height=500\n", + ")\n", + "\n", + "viewer.addModel(\n", + " structure_files[pdb_id].read_text(),\n", + " \"cif\"\n", + ")\n", + "\n", + "viewer.setStyle(\n", + " {\n", + " \"protein\": True\n", + " },\n", + " {\n", + " \"cartoon\": {}\n", + " }\n", + ")\n", + "\n", + "viewer.setStyle(\n", + " {\n", + " \"resn\": \"HEM\"\n", + " },\n", + " {\n", + " \"stick\": {}\n", + " }\n", + ")\n", + "\n", + "viewer.zoomTo()\n", + "\n", + "viewer.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "ad53d1cf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1CH1 — L89G\n" + ] + }, + { + "data": { + "application/3dmoljs_load.v0": "
\n

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n
\n", + "text/html": [ + "
\n", + "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", + "
\n", + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1CH2 — L89F\n" + ] + }, + { + "data": { + "application/3dmoljs_load.v0": "
\n

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n
\n", + "text/html": [ + "
\n", + "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", + "
\n", + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1CH3 — L89W\n" + ] + }, + { + "data": { + "application/3dmoljs_load.v0": "
\n

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n
\n", + "text/html": [ + "
\n", + "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", + "
\n", + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1CH5 — H97V\n" + ] + }, + { + "data": { + "application/3dmoljs_load.v0": "
\n

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n
\n", + "text/html": [ + "
\n", + "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", + "
\n", + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Generates interactive 3D molecular visualizations for all four myoglobin variants for side-by-side structural inspection.\n", + "\n", + "for pdb_id in pdb_ids:\n", + "\n", + " mutation = selected_structures.loc[\n", + " selected_structures[\"pdb_id\"]\n", + " == pdb_id,\n", + " \"mutation\"\n", + " ].iloc[0]\n", + "\n", + " print(\n", + " f\"{pdb_id} — {mutation}\"\n", + " )\n", + "\n", + " viewer = py3Dmol.view(\n", + " width=650,\n", + " height=400\n", + " )\n", + "\n", + " viewer.addModel(\n", + " structure_files[pdb_id].read_text(),\n", + " \"cif\"\n", + " )\n", + "\n", + " viewer.setStyle(\n", + " {\"protein\": True},\n", + " {\"cartoon\": {}}\n", + " )\n", + "\n", + " viewer.setStyle(\n", + " {\"resn\": \"HEM\"},\n", + " {\"stick\": {}}\n", + " )\n", + "\n", + " viewer.zoomTo()\n", + "\n", + " viewer.show()" + ] + }, + { + "cell_type": "markdown", + "id": "4e47160b", + "metadata": {}, + "source": [ + "## 7. Interpretation\n", + "\n", + "The selected structures represent closely related recombinant sperm whale\n", + "myoglobin variants.\n", + "\n", + "The structural comparison asks whether the proteins retain a similar global\n", + "backbone architecture despite substitutions at selected amino-acid positions.\n", + "\n", + "The pairwise Cα RMSD values quantify overall structural similarity, while the\n", + "interactive molecular views provide a qualitative view of the protein fold and\n", + "heme-containing structure.\n", + "\n", + "The results should be interpreted as a demonstration of structural-data\n", + "retrieval and comparison rather than as evidence for the biological\n", + "consequences of the mutations. More detailed conclusions would require local\n", + "analysis around the mutation sites, comparison of side-chain interactions,\n", + "heme geometry, crystallographic resolution, and experimental conditions." + ] + }, + { + "cell_type": "markdown", + "id": "d47dc6da", + "metadata": {}, + "source": [ + "## 8. Role of DSI in the Workflow\n", + "\n", + "This notebook separates **scientific data access** from **scientific analysis**.\n", + "\n", + "### Without the DSI backend\n", + "\n", + "A researcher would need to independently locate each PDB entry, inspect its\n", + "metadata, determine which structural files are available, retrieve those files,\n", + "and organize the information before beginning analysis.\n", + "\n", + "### With the DSI RCSBPDB backend\n", + "\n", + "The workflow becomes:\n", + "\n", + "**PDB identifiers → DSI RCSBPDB → normalized datasets/resources →\n", + "coordinate files → structural analysis**\n", + "\n", + "DSI does not replace specialized structural-biology software. Instead, the\n", + "RCSBPDB backend provides a consistent repository-access layer that allows PDB\n", + "data to participate in the same DSI-oriented workflow as other scientific data\n", + "repositories.\n", + "\n", + "In this demonstration:\n", + "\n", + "- the **datasets** table describes each PDB structure,\n", + "- the **resources** table exposes downloadable structural files,\n", + "- the **errors** table records unsuccessful resolutions,\n", + "- BioPython performs the downstream structural analysis, and\n", + "- py3Dmol provides interactive molecular visualization." + ] + }, + { + "cell_type": "markdown", + "id": "63ed9cc3", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "This demonstration follows a complete path from a scientific question to a\n", + "small structural analysis using the DSI RCSBPDB backend.\n", + "\n", + "Four related sperm whale myoglobin variants were retrieved through a single DSI\n", + "workflow. DSI exposed normalized PDB metadata and downloadable coordinate\n", + "resources, which were then used for downstream structural analysis.\n", + "\n", + "The demonstration shows three stages of scientific data use:\n", + "\n", + "1. **Access** — retrieve related PDB records through DSI.\n", + "2. **Organize** — examine normalized dataset and resource metadata.\n", + "3. **Analyze** — use the retrieved coordinate files for structural comparison\n", + " and visualization.\n", + "\n", + "The same pattern can be extended to larger collections of protein structures,\n", + "including conformational variants, protein-ligand complexes, and other\n", + "structure families available through RCSB PDB." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46cda0de", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (DSI venv)", + "language": "python", + "name": "dsi-venv" + }, + "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 +} diff --git a/tools/rcsbpdb_myoglobin_demo/README.md b/tools/rcsbpdb_myoglobin_demo/README.md new file mode 100644 index 00000000..be298417 --- /dev/null +++ b/tools/rcsbpdb_myoglobin_demo/README.md @@ -0,0 +1,175 @@ +# Exploring Myoglobin Mutations with DSI and RCSB PDB + +This notebook demonstrates how the DSI RCSB PDB backend can be used to retrieve +and analyze experimentally determined protein structures from the RCSB Protein +Data Bank. + +The user story focuses on a set of myoglobin mutant structures and shows how DSI +can be used as the data-access layer for a downstream structural biology +workflow. + +## User Story + +As a structural biology researcher, I want to retrieve related myoglobin mutant +structures from the RCSB Protein Data Bank so that I can compare their +three-dimensional structures and examine how mutations affect structural +similarity. + +## Scientific Question + +How structurally similar are the selected myoglobin mutant proteins, and what +differences can be observed after aligning their C-alpha atoms? + +The notebook uses the following RCSB PDB structures: + +- `1CH1` +- `1CH2` +- `1CH3` +- `1CH5` + +## Workflow + +**RCSB PDB IDs → DSI RCSB PDB Backend → datasets/resources → mmCIF files → +BioPython structural parsing → C-alpha alignment → RMSD comparison → +visualization** + +The notebook demonstrates: + +- retrieving protein structure metadata through the DSI RCSB PDB backend; +- inspecting the normalized `datasets` and `resources` tables; +- accessing the corresponding mmCIF structure files; +- parsing structures with BioPython; +- aligning C-alpha atoms from related myoglobin structures; +- calculating pairwise RMSD values; +- visualizing structural differences with plots; and +- displaying protein structures with py3Dmol. + +--- + +## Step 1 — Create and Activate a Python Environment + +Using Python's built-in `venv` module: + +```bash +python3 -m venv mydsi +source mydsi/bin/activate +``` + +On Windows: + +```bash +mydsi\Scripts\activate +``` + +--- + +## Step 2 — Clone and Install DSI + +Clone the DSI repository: + +```bash +git clone git@github.com:lanl/dsi.git +cd dsi +``` + +Install DSI and its core dependencies: + +```bash +pip install -e . +``` + +--- + +## Step 3 — Install the Notebook Dependencies + +Install the libraries required specifically for this RCSB PDB user story: + +```bash +pip install -r requirements.txt +``` + +These packages provide the notebook environment, structural biology tools, +data-processing libraries, plotting support, and 3D visualization used in the +example. + +--- + +## Step 4 — Start JupyterLab + +From the activated environment, run: + +```bash +jupyter-lab +``` + +When JupyterLab opens in your browser, navigate to: + +```text +RCSBPDB_User_Story.ipynb +``` + +Open the notebook and run the cells from top to bottom. + +--- + +## Step 5 — Run the User Story + +The notebook follows this sequence: + +1. Initialize the DSI RCSB PDB backend. +2. Retrieve the selected myoglobin mutant records. +3. Inspect the normalized `datasets` and `resources` tables. +4. Access the associated mmCIF structure files. +5. Parse the structures using BioPython. +6. Select corresponding C-alpha atoms. +7. Perform structural alignment. +8. Calculate RMSD values between structures. +9. Compare the structures using plots and a pairwise RMSD heatmap. +10. Visualize the protein structures interactively with py3Dmol. + +--- + +## Expected Outputs + +The notebook produces: + +- DSI-normalized metadata tables for the selected RCSB PDB structures; +- downloaded or accessible mmCIF structure files; +- C-alpha RMSD values for structural comparison; +- RMSD comparison plots; +- a pairwise RMSD heatmap; and +- interactive 3D protein structure visualizations. + +> **Note:** py3Dmol visualizations are interactive inside Jupyter but become +> static or may not render fully when the notebook is exported to PDF. + +--- + +## Role of DSI + +DSI serves as the data-access and organization layer in this workflow. + +The RCSB PDB backend provides a consistent way to retrieve repository metadata +and associated structure resources, while downstream scientific libraries +perform the structural analysis. + +In this example: + +- **DSI / RCSB PDB backend** — discovers and organizes protein structure data. +- **BioPython** — parses mmCIF files and performs structural alignment. +- **NumPy / pandas** — support numerical and tabular analysis. +- **Matplotlib / Seaborn** — visualize RMSD results. +- **py3Dmol** — provides interactive 3D protein structure visualization. + +This separation allows the repository-access workflow to remain independent +from the scientific analysis performed after the structures are retrieved. + +--- + +## Summary + +This user story demonstrates an end-to-end workflow in which DSI provides +structured access to RCSB PDB data and passes the retrieved scientific resources +to standard Python structural-biology tools for analysis. + +**Discover → Retrieve → Parse → Align → Compare → Visualize** diff --git a/tools/rcsbpdb_myoglobin_demo/requirements.txt b/tools/rcsbpdb_myoglobin_demo/requirements.txt new file mode 100644 index 00000000..b4f89fc7 --- /dev/null +++ b/tools/rcsbpdb_myoglobin_demo/requirements.txt @@ -0,0 +1,7 @@ +jupyterlab>=4.4,<5 +pandas>=2.2,<3 +numpy>=1.26,<3 +matplotlib>=3.8,<4 +seaborn>=0.13,<1 +biopython>=1.83,<2 +py3Dmol>=2.4,<3 \ No newline at end of file diff --git a/tools/zenodo_waves_demo/README.md b/tools/zenodo_waves_demo/README.md new file mode 100644 index 00000000..065a0b53 --- /dev/null +++ b/tools/zenodo_waves_demo/README.md @@ -0,0 +1,159 @@ +# Retrieving a Reproducible Scientific Software Release with DSI and Zenodo + +This example demonstrates how the DSI Zenodo backend can retrieve a versioned +scientific software release from Zenodo and expose both record-level metadata and +the files attached to that record. + +The user story uses a versioned **LANL WAVES** release to demonstrate a +reproducibility-oriented workflow in which DSI identifies the exact software +record, exposes its archived source resource, and provides the checksum and +download URL needed for downstream verification. + +## User Story + +As a computational researcher, I want to retrieve a specific archived version of +LANL scientific software from Zenodo through DSI so that I can identify the exact +software release, citation metadata, and source archive needed to support a +reproducible computational workflow. + +## Reproducibility Question + +Can DSI retrieve a specific citable WAVES software release from Zenodo and +identify the exact archived source artifact associated with that release? + +## Workflow + +**Software DOI → DSI Zenodo backend → datasets/resources → source archive → +download → checksum verification → archive inspection** + +The notebook demonstrates: + +- retrieval of a Zenodo software record by DOI; +- inspection of normalized `datasets` and `resources` tables; +- identification of the archived source-code resource; +- use of the DSI-provided download URL in a downstream Python step; +- checksum verification of the downloaded artifact; and +- inspection of the archived software-release contents. + +> **Note:** The DSI Zenodo backend is read-only and does not automatically save +> attached research files to disk. It exposes resource metadata and the +> `download_url`; the notebook performs the actual file download with `requests`. + +--- + +## Step 1 — Create and Activate a Python Environment + +```bash +python3 -m venv mydsi +source mydsi/bin/activate +``` + +On Windows: + +```bash +mydsi\Scripts\activate +``` + +--- + +## Step 2 — Clone and Install DSI + +```bash +git clone git@github.com:lanl/dsi.git +cd dsi +pip install -e . +``` + +--- + +## Step 3 — Install the Notebook Dependencies + +From the `zenodo_waves_demo` directory: + +```bash +pip install -r requirements.txt +``` + +The notebook uses pandas for tabular inspection and requests for the downstream +file download. Checksum verification, ZIP inspection, and path handling use +Python standard-library modules. + +--- + +## Step 4 — Start JupyterLab + +```bash +jupyter-lab +``` + +Open: + +```text +Zenodo_User_Story.ipynb +``` + +and run the cells from top to bottom. + +--- + +## Step 5 — Run the User Story + +The notebook follows this sequence: + +1. Select a versioned LANL WAVES release by DOI. +2. Initialize the DSI Zenodo backend. +3. Retrieve the normalized `datasets` and `resources` tables. +4. Inspect software-release metadata such as DOI, version, creators, license, + and publication date. +5. Identify the archived source ZIP from the `resources` table. +6. Use the exposed `download_url` to retrieve the archive. +7. Compare the downloaded file's MD5 checksum with the checksum reported by + Zenodo. +8. Inspect the archive contents and summarize the file types present. + +--- + +## Expected Outputs + +The notebook produces: + +- a normalized DSI `datasets` row for the selected WAVES release; +- a normalized `resources` row for its archived source ZIP; +- the source archive's file name, format, size, checksum, and download URL; +- a locally downloaded ZIP archive; +- an MD5 checksum comparison showing whether the downloaded file matches the + Zenodo metadata; and +- a short inspection of the software archive contents. + +--- + +## Role of DSI + +DSI serves as the repository-access and organization layer in this workflow. + +The Zenodo backend converts repository-specific API responses into two stable +DSI tables: + +- **`datasets`** — record-level metadata for the Zenodo research object. +- **`resources`** — file-level metadata for resources attached to that record. + +In this example: + +- **DSI / Zenodo backend** — retrieves and normalizes the software record and + attached resource metadata. +- **requests** — downloads the selected archive using the URL exposed by DSI. +- **hashlib** — verifies the downloaded archive checksum. +- **zipfile / pathlib** — inspect and manage the local archive. +- **pandas** — displays and summarizes metadata and archive contents. + +This separation keeps repository access independent from the downstream +software-reproducibility workflow. + +--- + +## Summary + +This example demonstrates how DSI can connect a persistent Zenodo DOI to the +exact archived scientific-software resource associated with that release. + +**Identify → Retrieve → Resolve → Download → Verify → Inspect** diff --git a/tools/zenodo_waves_demo/Zenodo_User_Story.ipynb b/tools/zenodo_waves_demo/Zenodo_User_Story.ipynb new file mode 100644 index 00000000..4272c465 --- /dev/null +++ b/tools/zenodo_waves_demo/Zenodo_User_Story.ipynb @@ -0,0 +1,1280 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cd8a1faa", + "metadata": {}, + "source": [ + "# Retrieving a Reproducible Scientific Software Release with DSI and Zenodo\n", + "\n", + "This notebook demonstrates how the **DSI Zenodo backend** can be used to retrieve\n", + "a versioned scientific software record from Zenodo, inspect its normalized\n", + "metadata and attached resources, and pass the selected software archive into a\n", + "downstream reproducibility workflow.\n", + "\n", + "Unlike a domain-specific scientific analysis, this example focuses on a core\n", + "Zenodo use case: **preserving and citing an exact version of research software**.\n", + "\n", + "## Why this user story?\n", + "\n", + "The larger DOI collection used to identify potential Zenodo examples contains\n", + "more than 12,000 research records overall, including many Zenodo-associated\n", + "research outputs.\n", + "\n", + "These include scientific software, datasets, simulations, notebooks, and model\n", + "results from areas such as:\n", + "\n", + "- ocean modeling,\n", + "- plasma physics,\n", + "- climate science,\n", + "- quantum computing,\n", + "- geoscience,\n", + "- accelerator science,\n", + "- machine learning,\n", + "- astrophysics, and\n", + "- spectroscopy.\n", + "\n", + "For this demonstration, **LANL WAVES** was selected because it provides a compact\n", + "software-oriented example.\n", + "\n", + "Zenodo preserves the release as a citable research object with a persistent DOI\n", + "and an archived source-code resource.\n", + "\n", + "## User Story\n", + "\n", + "**As a computational researcher, I want to retrieve a specific archived version\n", + "of LANL scientific software from Zenodo through DSI so that I can identify the\n", + "exact software release, citation metadata, and source archive needed to support\n", + "a reproducible computational workflow.**\n", + "\n", + "## Reproducibility Question\n", + "\n", + "**Can DSI retrieve a specific citable WAVES software release from Zenodo and\n", + "identify the exact archived source artifact associated with that release?**\n", + "\n", + "## Workflow\n", + "\n", + "**Software DOI → DSI Zenodo backend → `datasets` metadata → `resources` metadata\n", + "→ source archive → download → checksum verification → archive inspection**\n", + "\n", + "## What this notebook demonstrates\n", + "\n", + "- retrieving a Zenodo software record by DOI;\n", + "- inspecting DSI-normalized record-level metadata;\n", + "- inspecting file-level resource metadata;\n", + "- identifying the archived source-code resource;\n", + "- downloading the resource using the URL exposed by DSI;\n", + "- verifying the downloaded archive against the checksum reported by Zenodo; and\n", + "- inspecting the archive contents to confirm that it contains a structured\n", + " software release.\n", + "\n", + "> **Important:** The Zenodo backend itself is read-only and does **not**\n", + "> automatically save attached research files to disk. It exposes the metadata\n", + "> and `download_url` required for a downstream workflow to retrieve the selected\n", + "> resource. The actual file download in this notebook is performed with\n", + "> `requests`." + ] + }, + { + "cell_type": "markdown", + "id": "637194a5", + "metadata": {}, + "source": [ + "## Environment and Dependencies\n", + "\n", + "This example assumes that DSI has already been cloned and installed and that the\n", + "notebook-specific packages from `requirements.txt` are available.\n", + "\n", + "The notebook uses:\n", + "\n", + "- **DSI Zenodo backend** for repository access and metadata normalization;\n", + "- **pandas** for tabular inspection;\n", + "- **requests** for the downstream resource download;\n", + "- Python's built-in **hashlib** for checksum verification; and\n", + "- Python's built-in **zipfile** and **pathlib** modules for archive inspection\n", + " and local file handling.\n", + "\n", + "The Zenodo backend is the data-access layer.\n", + "\n", + "Downloading, verification, and archive inspection remain downstream operations\n", + "so that repository access stays separate from use of the retrieved artifact." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "02194047", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment ready.\n" + ] + } + ], + "source": [ + "# Standard-library utilities for local files, ZIP inspection,\n", + "# and checksum verification.\n", + "\n", + "from pathlib import Path\n", + "from zipfile import ZipFile\n", + "import hashlib\n", + "\n", + "# Third-party packages used by the downstream workflow.\n", + "\n", + "import pandas as pd\n", + "import requests\n", + "\n", + "# DSI backend used to retrieve and normalize the Zenodo record.\n", + "\n", + "from dsi.backends.zenodo import Zenodo\n", + "\n", + "print(\"Environment ready.\")" + ] + }, + { + "cell_type": "markdown", + "id": "e75d7874", + "metadata": {}, + "source": [ + "## 1. Select a Versioned WAVES Release\n", + "\n", + "The selected Zenodo record is:\n", + "\n", + "- **Title:** `lanl/waves: 0.6.21`\n", + "- **DOI:** `10.5281/zenodo.10016558`\n", + "- **Research object type:** software\n", + "\n", + "Using a specific version DOI is important for reproducibility.\n", + "\n", + "A software project may continue to change over time, but a version-specific\n", + "Zenodo DOI identifies an archived release that can be cited and retrieved\n", + "independently of later development.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "765ac9c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Selected software DOI: 10.5281/zenodo.10016558\n" + ] + } + ], + "source": [ + "waves_doi = \"10.5281/zenodo.10016558\"\n", + "\n", + "print(\"Selected software DOI:\", waves_doi)\n" + ] + }, + { + "cell_type": "markdown", + "id": "e1a4d21c", + "metadata": {}, + "source": [ + "## 2. Retrieve the Software Record Through DSI\n", + "\n", + "The Zenodo backend is a **read-only DSI Webserver backend**.\n", + "\n", + "For this workflow, it uses the Zenodo Records API to retrieve the record\n", + "associated with the supplied DOI and normalize the returned repository metadata.\n", + "\n", + "The backend exposes two stable tables:\n", + "\n", + "- `datasets` — record-level metadata\n", + "- `resources` — file-level metadata\n", + "\n", + "For a software release, the `datasets` table describes the release itself, while\n", + "the `resources` table identifies the files attached to that release.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4942d711", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/rsankararaman/PDB_new/venv/lib/python3.11/site-packages/urllib3/connectionpool.py:1110: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n", + "/Users/rsankararaman/PDB_new/venv/lib/python3.11/site-packages/urllib3/connectionpool.py:1110: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n", + " warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created Zenodo backend instance.\n" + ] + } + ], + "source": [ + "zenodo = Zenodo(\n", + " params={\n", + " \"doi\": waves_doi\n", + " },\n", + " verify_ssl=False\n", + ")\n", + "\n", + "print(\"Created Zenodo backend instance.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e241bd01", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "datasets: (1 rows, 23 cols)\n", + "resources: (1 rows, 14 cols)\n" + ] + } + ], + "source": [ + "zenodo.list()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d5ddcfb7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Datasets shape: (1, 23)\n", + "Resources shape: (1, 14)\n" + ] + } + ], + "source": [ + "datasets = zenodo.get_table(\n", + " \"datasets\"\n", + ")\n", + "\n", + "resources = zenodo.get_table(\n", + " \"resources\"\n", + ")\n", + "\n", + "print(\n", + " \"Datasets shape:\",\n", + " datasets.shape\n", + ")\n", + "\n", + "print(\n", + " \"Resources shape:\",\n", + " resources.shape\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "05ed5285", + "metadata": {}, + "source": [ + "### How DSI Represents a Zenodo Record\n", + "\n", + "The Zenodo backend separates each research object into two stable tables.\n", + "\n", + "### `datasets`\n", + "\n", + "The `datasets` table contains **record-level metadata**.\n", + "\n", + "Examples include:\n", + "\n", + "- DOI,\n", + "- title,\n", + "- version,\n", + "- creators,\n", + "- license,\n", + "- publication date,\n", + "- access rights, and\n", + "- number of attached resources.\n", + "\n", + "### `resources`\n", + "\n", + "The `resources` table contains **file-level metadata** for resources attached to\n", + "the Zenodo record.\n", + "\n", + "Examples include:\n", + "\n", + "- file name,\n", + "- file format,\n", + "- file size,\n", + "- checksum,\n", + "- MIME type, and\n", + "- download URL.\n", + "\n", + "The two tables are connected through `dataset_id`.\n", + "\n", + "This separation is useful because a single Zenodo record may contain one or many\n", + "files with different formats and purposes.\n" + ] + }, + { + "cell_type": "markdown", + "id": "9260b6e5", + "metadata": {}, + "source": [ + "## 3. Inspect the Archived Software Metadata\n", + "\n", + "A reproducible software citation requires more than the name of a software\n", + "project.\n", + "\n", + "For a versioned software release, useful information includes:\n", + "\n", + "- DOI,\n", + "- software version,\n", + "- publication date,\n", + "- creators,\n", + "- license,\n", + "- resource type,\n", + "- access rights, and\n", + "- number of archived resources.\n", + "\n", + "DSI exposes these fields through the normalized `datasets` table." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "7c80ae36", + "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", + "
dataset_iddoititleversionpublication_dateresource_typeaccess_rightlicensecreatorsresource_countlanding_page
01001655810.5281/zenodo.10016558lanl/waves: 0.6.210.6.212023-10-17{'title': 'Software', 'type': 'software'}open{'id': 'cc-by-4.0'}[{'name': 'Kyle Brindley', 'affiliation': 'Los...1https://zenodo.org/records/10016558
\n", + "
" + ], + "text/plain": [ + " dataset_id doi title version \\\n", + "0 10016558 10.5281/zenodo.10016558 lanl/waves: 0.6.21 0.6.21 \n", + "\n", + " publication_date resource_type access_right \\\n", + "0 2023-10-17 {'title': 'Software', 'type': 'software'} open \n", + "\n", + " license creators \\\n", + "0 {'id': 'cc-by-4.0'} [{'name': 'Kyle Brindley', 'affiliation': 'Los... \n", + "\n", + " resource_count landing_page \n", + "0 1 https://zenodo.org/records/10016558 " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "software_metadata_columns = [\n", + " column\n", + " for column in [\n", + " \"dataset_id\",\n", + " \"doi\",\n", + " \"title\",\n", + " \"version\",\n", + " \"publication_date\",\n", + " \"resource_type\",\n", + " \"access_right\",\n", + " \"license\",\n", + " \"creators\",\n", + " \"resource_count\",\n", + " \"landing_page\",\n", + " ]\n", + " if column in datasets.columns\n", + "]\n", + "\n", + "software_metadata = datasets[\n", + " software_metadata_columns\n", + "].copy()\n", + "\n", + "software_metadata" + ] + }, + { + "cell_type": "markdown", + "id": "28eeb580", + "metadata": {}, + "source": [ + "The returned record confirms that DSI retrieved the specific WAVES release\n", + "associated with the requested DOI.\n", + "\n", + "The important point is that the backend is not returning only an opaque Zenodo\n", + "JSON response. It exposes commonly useful fields in a normalized tabular form\n", + "that can be inspected using standard DSI and pandas workflows.\n" + ] + }, + { + "cell_type": "markdown", + "id": "a0d2a8f3", + "metadata": {}, + "source": [ + "## 4. Identify the Archived Source Resource\n", + "\n", + "The record metadata tells us **which software release** was retrieved.\n", + "\n", + "The `resources` table tells us **which files belong to that release**.\n", + "\n", + "For this example, the key resource is the archived WAVES source-code ZIP file.\n", + "\n", + "The resource table also exposes metadata that is useful for reproducibility,\n", + "including the archive size, checksum, and download URL." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "279c9ccc", + "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", + "
resource_iddataset_idnameformatsizechecksumdownload_url
010016558:110016558lanl/waves-0.6.21.zipzip407368md5:e90f3f2f9fe1b7ea2d7eca4786e38563https://zenodo.org/api/records/10016558/files/...
\n", + "
" + ], + "text/plain": [ + " resource_id dataset_id name format size \\\n", + "0 10016558:1 10016558 lanl/waves-0.6.21.zip zip 407368 \n", + "\n", + " checksum \\\n", + "0 md5:e90f3f2f9fe1b7ea2d7eca4786e38563 \n", + "\n", + " download_url \n", + "0 https://zenodo.org/api/records/10016558/files/... " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "resource_columns = [\n", + " column\n", + " for column in [\n", + " \"resource_id\",\n", + " \"dataset_id\",\n", + " \"name\",\n", + " \"format\",\n", + " \"size\",\n", + " \"checksum\",\n", + " \"download_url\",\n", + " ]\n", + " if column in resources.columns\n", + "]\n", + "\n", + "resource_view = resources[\n", + " resource_columns\n", + "].copy()\n", + "\n", + "resource_view" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "6e24a081", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Archived software resources found: 1\n" + ] + }, + { + "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", + "
nameformatsizechecksumdownload_url
0lanl/waves-0.6.21.zipzip407368md5:e90f3f2f9fe1b7ea2d7eca4786e38563https://zenodo.org/api/records/10016558/files/...
\n", + "
" + ], + "text/plain": [ + " name format size checksum \\\n", + "0 lanl/waves-0.6.21.zip zip 407368 md5:e90f3f2f9fe1b7ea2d7eca4786e38563 \n", + "\n", + " download_url \n", + "0 https://zenodo.org/api/records/10016558/files/... " + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "archive_resources = resources[\n", + " resources[\"format\"]\n", + " .astype(str)\n", + " .str.lower()\n", + " .isin([\n", + " \"zip\",\n", + " \"tar\",\n", + " \"gz\",\n", + " \"tar.gz\"\n", + " ])\n", + "].copy()\n", + "\n", + "print(\n", + " \"Archived software resources found:\",\n", + " len(archive_resources)\n", + ")\n", + "\n", + "archive_resources[\n", + " [\n", + " column\n", + " for column in [\n", + " \"name\",\n", + " \"format\",\n", + " \"size\",\n", + " \"checksum\",\n", + " \"download_url\",\n", + " ]\n", + " if column in archive_resources.columns\n", + " ]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "5462ba7e", + "metadata": {}, + "source": [ + "For this release, DSI identifies one archived software resource.\n", + "\n", + "This is an important distinction in the workflow:\n", + "\n", + "- the Zenodo record represents the **versioned research object**;\n", + "- the ZIP file represents the **actual archived software artifact**.\n", + "\n", + "DSI connects these through the normalized `dataset_id` relationship." + ] + }, + { + "cell_type": "markdown", + "id": "d95f58d6", + "metadata": {}, + "source": [ + "## 5. Select and Download the Exact Archived Release\n", + "\n", + "The source archive is selected from the DSI `resources` table rather than by\n", + "manually copying a file URL from the Zenodo website.\n", + "\n", + "This preserves the relationship:\n", + "\n", + "**software DOI → normalized record → archived resource**\n", + "\n", + "The selection of the archive comes from DSI.\n", + "\n", + "The actual file download in the next step is a **downstream notebook operation**.\n", + "The notebook uses the DSI-provided `download_url` with `requests` to save the\n", + "resource locally.\n", + "\n", + "The Zenodo backend itself does not write the file to disk." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a7b1b806", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Selected archive: lanl/waves-0.6.21.zip\n", + "Expected checksum: md5:e90f3f2f9fe1b7ea2d7eca4786e38563\n", + "Download URL: https://zenodo.org/api/records/10016558/files/lanl/waves-0.6.21.zip/content\n" + ] + } + ], + "source": [ + "if archive_resources.empty:\n", + " raise RuntimeError(\n", + " \"No software archive was found in the Zenodo resources table.\"\n", + " )\n", + "\n", + "selected_archive = archive_resources.iloc[0]\n", + "\n", + "archive_name = selected_archive[\n", + " \"name\"\n", + "]\n", + "\n", + "archive_url = selected_archive[\n", + " \"download_url\"\n", + "]\n", + "\n", + "expected_checksum = selected_archive.get(\n", + " \"checksum\"\n", + ")\n", + "\n", + "print(\n", + " \"Selected archive:\",\n", + " archive_name\n", + ")\n", + "\n", + "print(\n", + " \"Expected checksum:\",\n", + " expected_checksum\n", + ")\n", + "\n", + "print(\n", + " \"Download URL:\",\n", + " archive_url\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "fc047c16", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Zenodo resource name: lanl/waves-0.6.21.zip\n", + "Local filename: lanl_waves-0.6.21.zip\n", + "Downloaded: downloaded_waves_release/lanl_waves-0.6.21.zip\n", + "File size: 397.8 KB\n" + ] + } + ], + "source": [ + "# Create a directory for the downloaded software release.\n", + "\n", + "download_dir = Path(\n", + " \"downloaded_waves_release\"\n", + ")\n", + "\n", + "download_dir.mkdir(\n", + " parents=True,\n", + " exist_ok=True\n", + ")\n", + "\n", + "# Zenodo resource names may contain \"/\" characters.\n", + "# Replace them so that the resource can be stored as one local file.\n", + "\n", + "safe_archive_name = archive_name.replace(\n", + " \"/\",\n", + " \"_\"\n", + ")\n", + "\n", + "archive_path = (\n", + " download_dir\n", + " / safe_archive_name\n", + ")\n", + "\n", + "# Download the resource using the URL exposed by the DSI resources table.\n", + "\n", + "response = requests.get(\n", + " archive_url,\n", + " timeout=60\n", + ")\n", + "\n", + "response.raise_for_status()\n", + "\n", + "archive_path.write_bytes(\n", + " response.content\n", + ")\n", + "\n", + "print(\n", + " \"Zenodo resource name:\",\n", + " archive_name\n", + ")\n", + "\n", + "print(\n", + " \"Local filename:\",\n", + " safe_archive_name\n", + ")\n", + "\n", + "print(\n", + " \"Downloaded:\",\n", + " archive_path\n", + ")\n", + "\n", + "print(\n", + " \"File size:\",\n", + " f\"{archive_path.stat().st_size / 1024:.1f} KB\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "33e610b0", + "metadata": {}, + "source": [ + "## 6. Verify the Downloaded Artifact\n", + "\n", + "Zenodo provides a checksum for archived resources.\n", + "\n", + "The checksum allows us to verify that the file downloaded in the notebook is the\n", + "same artifact described by the Zenodo resource metadata.\n", + "\n", + "In this workflow:\n", + "\n", + "- the **DOI** identifies the software release;\n", + "- the **resource metadata** identifies the associated archive; and\n", + "- the **checksum** verifies the integrity of the downloaded file.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "2a64391f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Calculated MD5: e90f3f2f9fe1b7ea2d7eca4786e38563\n", + "Expected MD5: e90f3f2f9fe1b7ea2d7eca4786e38563\n", + "Checksum match: True\n" + ] + } + ], + "source": [ + "def calculate_md5(path):\n", + " md5 = hashlib.md5()\n", + "\n", + " with open(\n", + " path,\n", + " \"rb\"\n", + " ) as file:\n", + "\n", + " for chunk in iter(\n", + " lambda: file.read(\n", + " 1024 * 1024\n", + " ),\n", + " b\"\"\n", + " ):\n", + " md5.update(\n", + " chunk\n", + " )\n", + "\n", + " return md5.hexdigest()\n", + "\n", + "\n", + "actual_md5 = calculate_md5(\n", + " archive_path\n", + ")\n", + "\n", + "print(\n", + " \"Calculated MD5:\",\n", + " actual_md5\n", + ")\n", + "\n", + "if isinstance(\n", + " expected_checksum,\n", + " str\n", + "):\n", + "\n", + " expected_md5 = (\n", + " expected_checksum\n", + " .replace(\n", + " \"md5:\",\n", + " \"\"\n", + " )\n", + " .strip()\n", + " )\n", + "\n", + " print(\n", + " \"Expected MD5: \",\n", + " expected_md5\n", + " )\n", + "\n", + " print(\n", + " \"Checksum match:\",\n", + " actual_md5 == expected_md5\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "741ea66a", + "metadata": {}, + "source": [ + "### Interpreting the Checksum Result\n", + "\n", + "A `True` checksum match confirms that the locally downloaded archive is\n", + "byte-for-byte consistent with the MD5 checksum exposed by the Zenodo resource\n", + "metadata.\n", + "\n", + "This provides a simple reproducibility check:\n", + "\n", + "**persistent DOI → archived resource → verified local artifact**\n", + "\n", + "The checksum does not evaluate the scientific correctness of the software. It\n", + "only confirms that the retrieved file matches the archived resource described by\n", + "Zenodo." + ] + }, + { + "cell_type": "markdown", + "id": "7b981b0d", + "metadata": {}, + "source": [ + "## 7. Inspect the Archived Software Release\n", + "\n", + "The goal of this user story is not to install or execute WAVES.\n", + "\n", + "Instead, the notebook performs a lightweight inspection of the downloaded ZIP\n", + "archive to confirm that it contains a structured software release.\n", + "\n", + "This keeps the demonstration focused on:\n", + "\n", + "**repository access → resource identification → reproducible retrieval**\n", + "\n", + "rather than turning it into a WAVES installation or execution tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d473ce72", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files/directories in archive: 315\n", + "\n", + "First 25 archive entries:\n", + "- lanl-waves-1b9dee0/\n", + "- lanl-waves-1b9dee0/.gitattributes\n", + "- lanl-waves-1b9dee0/.github/\n", + "- lanl-waves-1b9dee0/.github/workflows/\n", + "- lanl-waves-1b9dee0/.github/workflows/pages.yml\n", + "- lanl-waves-1b9dee0/.github/workflows/release.yml\n", + "- lanl-waves-1b9dee0/.gitignore\n", + "- lanl-waves-1b9dee0/.gitlab-ci.yml\n", + "- lanl-waves-1b9dee0/CITATION.bib\n", + "- lanl-waves-1b9dee0/CITATION.cff\n", + "- lanl-waves-1b9dee0/LICENSE.txt\n", + "- lanl-waves-1b9dee0/MANIFEST.in\n", + "- lanl-waves-1b9dee0/README.rst\n", + "- lanl-waves-1b9dee0/SConscript\n", + "- lanl-waves-1b9dee0/SConstruct\n", + "- lanl-waves-1b9dee0/docs/\n", + "- lanl-waves-1b9dee0/docs/SConscript\n", + "- lanl-waves-1b9dee0/docs/_static/\n", + "- lanl-waves-1b9dee0/docs/_static/custom.css\n", + "- lanl-waves-1b9dee0/docs/_static/index.html\n", + "- lanl-waves-1b9dee0/docs/aea_environment_activation.txt\n", + "- lanl-waves-1b9dee0/docs/aea_release_discussion.txt\n", + "- lanl-waves-1b9dee0/docs/api.rst\n", + "- lanl-waves-1b9dee0/docs/build.txt\n", + "- lanl-waves-1b9dee0/docs/changelog.rst\n" + ] + } + ], + "source": [ + "if archive_path.suffix.lower() != \".zip\":\n", + "\n", + " print(\n", + " \"Archive inspection in this demo is implemented for ZIP files.\"\n", + " )\n", + "\n", + "else:\n", + "\n", + " with ZipFile(\n", + " archive_path,\n", + " \"r\"\n", + " ) as archive:\n", + "\n", + " archived_files = archive.namelist()\n", + "\n", + " print(\n", + " \"Files/directories in archive:\",\n", + " len(archived_files)\n", + " )\n", + "\n", + " print()\n", + " print(\n", + " \"First 25 archive entries:\"\n", + " )\n", + "\n", + " for name in archived_files[:25]:\n", + " print(\n", + " \"-\",\n", + " name\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "2cde8f3d", + "metadata": {}, + "source": [ + "The archive contains recognizable software-project content such as:\n", + "\n", + "- source-code directories,\n", + "- documentation,\n", + "- citation files,\n", + "- licensing information,\n", + "- build/configuration files, and\n", + "- example or input files.\n", + "\n", + "This confirms that the selected Zenodo resource is a preserved software source\n", + "release rather than an unrelated attachment." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "79931218", + "metadata": {}, + "outputs": [], + "source": [ + "if archive_path.suffix.lower() == \".zip\":\n", + "\n", + " suffix_counts = {}\n", + "\n", + " for name in archived_files:\n", + "\n", + " path = Path(\n", + " name\n", + " )\n", + "\n", + " if name.endswith(\n", + " \"/\"\n", + " ):\n", + " continue\n", + "\n", + " suffix = (\n", + " path.suffix.lower()\n", + " if path.suffix\n", + " else \"[no extension]\"\n", + " )\n", + "\n", + " suffix_counts[\n", + " suffix\n", + " ] = (\n", + " suffix_counts.get(\n", + " suffix,\n", + " 0\n", + " )\n", + " + 1\n", + " )\n", + "\n", + " archive_summary = (\n", + " pd.DataFrame(\n", + " [\n", + " {\n", + " \"file_type\": suffix,\n", + " \"file_count\": count,\n", + " }\n", + " for suffix, count\n", + " in suffix_counts.items()\n", + " ]\n", + " )\n", + " .sort_values(\n", + " \"file_count\",\n", + " ascending=False\n", + " )\n", + " .reset_index(\n", + " drop=True\n", + " )\n", + " )\n", + "\n", + " archive_summary.head(\n", + " 15\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "001cbc65", + "metadata": {}, + "source": [ + "### What the Archive Inspection Shows\n", + "\n", + "The archive contains a structured software release rather than a single opaque\n", + "binary file.\n", + "\n", + "The presence of Python source files, documentation, citation files, licensing\n", + "information, build/configuration files, and other supporting resources shows\n", + "that the Zenodo artifact is a preserved software snapshot suitable for\n", + "downstream inspection or reproducibility workflows.\n", + "\n", + "The notebook intentionally stops at archive inspection.\n", + "\n", + "Installing, building, or executing WAVES would be a separate software-use\n", + "workflow and is outside the scope of this Zenodo backend demonstration." + ] + }, + { + "cell_type": "markdown", + "id": "29a77199", + "metadata": {}, + "source": [ + "## 8. Role of DSI in the Workflow\n", + "\n", + "DSI serves as the **repository-access and organization layer** in this example.\n", + "\n", + "### Without the DSI Zenodo Backend\n", + "\n", + "A researcher would need to work directly with the Zenodo website or API to:\n", + "\n", + "1. locate the correct software record;\n", + "2. confirm the intended software version;\n", + "3. interpret Zenodo-specific metadata;\n", + "4. identify the attached software archive;\n", + "5. obtain the file's download URL;\n", + "6. retain the checksum and other resource metadata; and\n", + "7. connect the downloaded file back to the cited release.\n", + "\n", + "### With the DSI Zenodo Backend\n", + "\n", + "The workflow becomes:\n", + "\n", + "**DOI → DSI Zenodo → `datasets` / `resources` → selected archive → downstream verification**\n", + "\n", + "In this demonstration:\n", + "\n", + "- **`datasets`** identifies the versioned WAVES software record;\n", + "- **`resources`** identifies the exact archived source artifact;\n", + "- **DOI and version metadata** establish which software release was retrieved;\n", + "- **checksum and download URL** provide the information required to retrieve and\n", + " verify the corresponding file; and\n", + "- standard Python tools perform the downstream download, checksum validation,\n", + " and archive inspection.\n", + "\n", + "The backend does not replace Zenodo, GitHub, package managers, or software build\n", + "tools.\n", + "\n", + "Its role is to provide a consistent DSI representation of Zenodo research\n", + "objects so that repository-specific access remains separate from whatever\n", + "scientific or software workflow happens afterward." + ] + }, + { + "cell_type": "markdown", + "id": "4ee72fc9", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "This user story demonstrates an end-to-end reproducibility workflow for a\n", + "versioned scientific software release stored in Zenodo.\n", + "\n", + "A specific LANL WAVES release is retrieved by DOI through the DSI Zenodo\n", + "backend.\n", + "\n", + "DSI exposes:\n", + "\n", + "- normalized release metadata in `datasets`; and\n", + "- the attached source archive in `resources`.\n", + "\n", + "The notebook then uses the DSI-provided resource metadata to download the\n", + "archive, verify its checksum, and inspect its contents.\n", + "\n", + "The complete workflow can be summarized as:\n", + "\n", + "**Identify → Retrieve metadata → Resolve resource → Download → Verify → Inspect**\n", + "\n", + "The main value of the Zenodo backend is not to install or execute the software\n", + "itself.\n", + "\n", + "Instead, it provides a consistent and reproducible bridge between a citable\n", + "Zenodo research object and the downstream tools that use its archived\n", + "resources." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "64716501", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Zenodo backend closed.\n" + ] + } + ], + "source": [ + "zenodo.close()\n", + "\n", + "print(\n", + " \"Zenodo backend closed.\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38b940f4", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (Zenodo DSI venv)", + "language": "python", + "name": "zenodo-dsi-venv" + }, + "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 +} diff --git a/tools/zenodo_waves_demo/requirements.txt b/tools/zenodo_waves_demo/requirements.txt new file mode 100644 index 00000000..7429d78a --- /dev/null +++ b/tools/zenodo_waves_demo/requirements.txt @@ -0,0 +1,3 @@ +jupyterlab>=4.4,<5 +pandas>=2.2,<3 +requests>=2.32,<3 \ No newline at end of file