Repository: VIDA-NYU/bdi-kit (language: python)

Issue:
Support listing the top-k value matches for individual values in a column mapping



Discussion / hints:

Fix patch:
diff --git a/bdikit/api.py b/bdikit/api.py
index 0dbd3a6..92f0d9f 100644
--- a/bdikit/api.py
+++ b/bdikit/api.py
@@ -1,5 +1,7 @@
 from __future__ import annotations
+import logging
 from enum import Enum
+from collections import defaultdict
 from os.path import join, dirname
 from typing import (
     Union,
@@ -62,6 +64,7 @@
 GDC_DATA_PATH = join(dirname(__file__), "./resource/gdc_table.csv")
 DEFAULT_VALUE_MATCHING_METHOD = "tfidf"
 DEFAULT_SCHEMA_MATCHING_METHOD = "coma"
+logger = logging.getLogger(__name__)
 
 
 class SchemaMatchers(Enum):
@@ -308,57 +311,16 @@ def match_values(
         ValueError: If the target is neither a DataFrame nor a standard vocabulary name.
         ValueError: If the source column is not present in the source dataset.
     """
-    if isinstance(column_mapping, pd.DataFrame):
-        if not all(k in column_mapping.columns for k in ["source", "target"]):
-            raise ValueError(
-                "The column_mapping DataFrame must contain 'source' and 'target' columns."
-            )
-        mapping_df = column_mapping
-    elif isinstance(column_mapping, tuple):
-        mapping_df = pd.DataFrame(
-            [
-                {
-                    "source": column_mapping[0],
-                    "target": column_mapping[1],
-                }
-            ]
-        )
-    else:
-        raise ValueError(
-            "The column_mapping must be a DataFrame or a tuple of two strings "
-            "containing the 'source' and 'target' columns."
-        )
-
-    column_mapping_list = mapping_df.to_dict(orient="records")
-
-    for mapping in column_mapping_list:
-        source_column = mapping["source"]
-        if source_column not in source.columns:
-            raise ValueError(
-                f"The source column '{source_column}' is not present in the source dataset."
-            )
-
-    if isinstance(target, str) and target == "gdc":
-        column_names = mapping_df["target"].unique().tolist()
-        target_domain = get_gdc_data(column_names)
-    elif isinstance(target, pd.DataFrame):
-        target_domain = {
-            column_name: target[column_name].unique().tolist()
-            for column_name in target.columns
-        }
-    else:
-        raise ValueError(
-            "The target must be a DataFrame or a standard vocabulary name."
-        )
-
     if method_args is None:
         method_args = {}
-    value_matcher = ValueMatchers.get_instance(method, **method_args)
-    matches = _match_values(source, target_domain, column_mapping_list, value_matcher)
 
-    result = [
-        _value_matching_result_to_df(matching_result) for matching_result in matches
-    ]
+    if "top_n" in method_args and method_args["top_n"] > 1:
+        logger.warning(
+            f"Ignoring 'top_n' argument, use the 'top_value_matches()' method to get top-k value matches."
+        )
+        method_args["top_n"] = 1
+
+    matches = _match_values(source, target, column_mapping, method, method_args)
 
     if isinstance(column_mapping, tuple):
         if len(matches) == 0:
@@ -366,15 +328,84 @@ def match_values(
         # If only a single mapping is provided (as a tuple), we return the result
         # directly as a DataFrame to make it easier to display it in notebooks.
         assert (
-            len(result) == 1
-        ), f"Expected one result for a single column mapping, but got: {len(result)}"
-        return result[0]
+            len(matches) == 1
+        ), f"Expected one result for a single column mapping, but got: {len(matches)}"
+        return matches[0]
     else:
-        return result
+        return matches
+
+
+def top_value_matches(
+    source: pd.DataFrame,
+    target: Union[str, pd.DataFrame],
+    column_mapping: Union[Tuple[str, str], pd.DataFrame],
+    top_k: int = 5,
+    method: str = DEFAULT_VALUE_MATCHING_METHOD,
+    method_args: Optional[Dict[str, Any]] = None,
+) -> List[pd.DataFrame]:
+    """
+    Finds top value matches between column values from the source dataset and column
+    values of the target domain (a pd.DataFrame or a standard dictionary such
+    as 'gdc') using the method provided in `method`.
+
+    Args:
+        source (pd.DataFrame): The source dataset containing the columns to be
+          matched.
+
+        target (Union[str, pd.DataFrame]): The target domain to match the
+          values to. It can be either a DataFrame or a standard vocabulary name.
+
+        column_mapping (Union[Tuple[str, str], pd.DataFrame]): A tuple or a
+          DataFrame containing the mappings between source and target columns.
+
+          - If a tuple is provided, it should contain two strings where the first
+            is the source column and the second is the target column.
+          - If a DataFrame is provided, it should contain 'source' and 'target'
+            column names where each row specifies a column mapping.
+
+        top_k (int, optional): The number of top matches to return. Defaults to 5.
+
+        method (str, optional): The name of the method to use for value
+          matching.
+        method_args (Dict[str, Any], optional): The additional arguments of the
+            method for value matching.
+
+    Returns:
+        List[pd.DataFrame]: A list of DataFrame objects containing
+        the results of value matching between the source and target values.
+
+    Raises:
+        ValueError: If the column_mapping DataFrame does not contain 'source' and
+          'target' columns.
+        ValueError: If the target is neither a DataFrame nor a standard vocabulary name.
+        ValueError: If the source column is not present in the source dataset.
+    """
+    if method_args is None:
+        method_args = {}
+
+    if "top_k" in method_args:
+        logger.warning(
+            f"Ignoring 'top_k' argument, using top_k argument instead (top_k={top_k})"
+        )
+
+    method_args["top_k"] = top_k
+
+    matches = _match_values(source, target, column_mapping, method, method_args)
+
+    match_list = []
+    for match in matches:
+        for _, group in match.groupby("source", dropna=False):
+            match_list.append(
+                group.reset_index(drop=True).sort_values(
+                    by=["similarity"], ascending=False
+                )
+            )
+
+    return match_list
 
 
 def view_value_matches(
-    matches: Union[pd.DataFrame, List[pd.DataFrame]], edit: bool = True
+    matches: Union[pd.DataFrame, List[pd.DataFrame]], edit: bool = False
 ):
     """
     Shows the value match results in a DataFrame fashion.
@@ -389,64 +420,47 @@ def view_value_matches(
         match_list = [matches]
     elif isinstance(matches, list):
         match_list = matches
-
     else:
         raise ValueError("The matches must be a DataFrame or a list of DataFrames")
 
-    for match in match_list:
+    # Grouping DataFrames by metadata (source and target columns)
+    grouped_matches = defaultdict(list)
+    for match_df in match_list:
+        grouped_matches[match_df.attrs["source"], match_df.attrs["target"]].append(
+            match_df
+        )
+
+    # Display grouped DataFrames
+    for (source_col, target_col), match_dfs in grouped_matches.items():
         display(
             Markdown(
-                f"<br>**Source column:** {match.attrs['source']}<br>"
-                f"**Target column:** {match.attrs['target']}<br>"
+                f"<br>**Source column:** {source_col}<br>"
+                f"**Target column:** {target_col}<br>"
             )
         )
-        if edit:
-            match_widget = pn.widgets.Tabulator(match, disabled=not edit)
-            display(match_widget)
-        else:
-            display(match)
-
-
-def _value_matching_result_to_df(
-    matching_result: ValueMatchingResult, default_unmatched: Any = np.nan
-) -> pd.DataFrame:
-    """
-    Transforms the list of matches and unmatched values into a DataFrame.
-    """
-    matches_df = pd.DataFrame(
-        data=matching_result["matches"],
-        columns=["source", "target", "similarity"],
-    )
-
-    unmatched_values = matching_result["unmatch_values"]
-    unmatched_df = pd.DataFrame(
-        data=list(
-            zip(
-                unmatched_values,
-                [default_unmatched] * len(unmatched_values),
-                [default_unmatched] * len(unmatched_values),
-            )
-        ),
-        columns=["source", "target", "similarity"],
-    )
-
-    result = pd.concat([matches_df, unmatched_df], ignore_index=True)
-    result.attrs["source"] = matching_result["source"]
-    result.attrs["target"] = matching_result["target"]
-    result.attrs["coverage"] = matching_result["coverage"]
-    return result
+        for match_df in match_dfs:
+            if edit:
+                match_widget = pn.widgets.Tabulator(match_df, disabled=not edit)
+                display(match_widget)
+            else:
+                display(match_df)
 
 
 def _match_values(
-    dataset: pd.DataFrame,
-    target_domain: Dict[str, Optional[List[str]]],
-    column_mapping: List[Dict],
-    value_matcher: BaseValueMatcher,
-) -> List[ValueMatchingResult]:
+    source: pd.DataFrame,
+    target: Union[str, pd.DataFrame],
+    column_mapping: Union[Tuple[str, str], pd.DataFrame],
+    method: str,
+    method_args: Dict[str, Any],
+) -> List[pd.DataFrame]:
 
+    target_domain, column_mapping_list = _format_value_matching_input(
+        source, target, column_mapping
+    )
+    value_matcher = ValueMatchers.get_instance(method, **method_args)
     mapping_results: List[ValueMatchingResult] = []
 
-    for mapping in column_mapping:
+    for mapping in column_mapping_list:
         source_column, target_column = mapping["source"], mapping["target"]
 
         # 1. Select candidate columns for value mapping
@@ -454,7 +468,7 @@ def _match_values(
         if target_domain_list is None or len(target_domain_list) == 0:
             continue
 
-        unique_values = dataset[source_column].unique()
+        unique_values = source[source_column].unique()
         if _skip_values(unique_values):
             continue
 
@@ -481,9 +495,9 @@ def _match_values(
             )
 
         # 5. Calculate the coverage and unmatched values
-        coverage = len(matches) / len(source_values_dict)
         source_values = set(source_values_dict.values())
         match_values = set([x[0] for x in matches])
+        coverage = len(match_values) / len(source_values_dict)
 
         mapping_results.append(
             ValueMatchingResult(
@@ -496,7 +510,94 @@ def _match_values(
             )
         )
 
-    return mapping_results
+    mapping_df_list = [
+        _value_matching_result_to_df(mapping_result)
+        for mapping_result in mapping_results
+    ]
+
+    return mapping_df_list
+
+
+def _format_value_matching_input(
+    source: pd.DataFrame,
+    target: Union[str, pd.DataFrame],
+    column_mapping: Union[Tuple[str, str], pd.DataFrame],
+):
+    if isinstance(column_mapping, pd.DataFrame):
+        if not all(k in column_mapping.columns for k in ["source", "target"]):
+            raise ValueError(
+                "The column_mapping DataFrame must contain 'source' and 'target' columns."
+            )
+        mapping_df = column_mapping
+    elif isinstance(column_mapping, tuple):
+        mapping_df = pd.DataFrame(
+            [
+                {
+                    "source": column_mapping[0],
+                    "target": column_mapping[1],
+                }
+            ]
+        )
+    else:
+        raise ValueError(
+            "The column_mapping must be a DataFrame or a tuple of two strings "
+            "containing the 'source' and 'target' columns."
+        )
+
+    column_mapping_list = mapping_df.to_dict(orient="records")
+
+    for mapping in column_mapping_list:
+        source_column = mapping["source"]
+        if source_column not in source.columns:
+            raise ValueError(
+                f"The source column '{source_column}' is not present in the source dataset."
+            )
+
+    if isinstance(target, str) and target == "gdc":
+        column_names = mapping_df["target"].unique().tolist()
+        target_domain = get_gdc_data(column_names)
+    elif isinstance(target, pd.DataFrame):
+        target_domain = {
+            column_name: target[column_name].unique().tolist()
+            for column_name in target.columns
+        }
+    else:
+        raise ValueError(
+            "The target must be a DataFrame or a standard vocabulary name."
+        )
+
+    return target_domain, column_mapping_list
+
+
+def _value_matching_result_to_df(
+    matching_result: ValueMatchingResult, default_unmatched: Any = np.nan
+) -> pd.DataFrame:
+    """
+    Transforms the list of matches and unmatched values into a DataFrame.
+    """
+    matches_df = pd.DataFrame(
+        data=matching_result["matches"],
+        columns=["source", "target", "similarity"],
+    )
+
+    unmatched_values = matching_result["unmatch_values"]
+
+    unmatched_df = pd.DataFrame(
+        data=list(
+            zip(
+                unmatched_values,
+                [default_unmatched] * len(unmatched_values),
+                [default_unmatched] * len(unmatched_values),
+            )
+        ),
+        columns=["source", "target", "similarity"],
+    )
+
+    result = pd.concat([matches_df, unmatched_df], ignore_index=True)
+    result.attrs["source"] = matching_result["source"]
+    result.attrs["target"] = matching_result["target"]
+    result.attrs["coverage"] = matching_result["coverage"]
+    return result
 
 
 def _skip_values(unique_values: np.ndarray):
@@ -626,7 +727,7 @@ def check_duplicates(mappings: List[ColumnMappingSpec]):
         source_column = mapping["source"]
         target_column = mapping["target"]
 
-        # ignore duplicate mappings accross user and value mappings
+        # ignore duplicate mappings across user and value mappings
         key = create_key(source_column, target_column)
         if key in mapping_keys:
             continue
diff --git a/bdikit/mapping_algorithms/value_mapping/algorithms.py b/bdikit/mapping_algorithms/value_mapping/algorithms.py
index bb2cdd9..ef64b6d 100644
--- a/bdikit/mapping_algorithms/value_mapping/algorithms.py
+++ b/bdikit/mapping_algorithms/value_mapping/algorithms.py
@@ -1,4 +1,4 @@
-from typing import List, NamedTuple, Callable, Tuple, Union
+from typing import List, NamedTuple, Callable, Tuple
 import ast
 from openai import OpenAI
 from polyfuzz import PolyFuzz
@@ -59,11 +59,15 @@ def match(
 
         matches = []
         for _, row in match_results.iterrows():
-            source_value = row["From"]
-            target_value = row["To"]
-            similarity = row["Similarity"]
-            if similarity >= self.threshold:
-                matches.append(ValueMatch(source_value, target_value, similarity))
+            source = row[0]
+            top_matches = row[1:]
+            indexes = range(0, len(top_matches) - 1, 2)
+
+            for index in indexes:
+                target = top_matches[index]
+                similarity = top_matches[index + 1]
+                if similarity >= self.threshold:
+                    matches.append(ValueMatch(source, target, similarity))
 
         return matches
 
@@ -78,7 +82,7 @@ def __init__(
         n_gram_range: Tuple[int, int] = (1, 3),
         clean_string: bool = True,
         threshold: float = VALUE_MATCHING_THRESHOLD,
-        top_n: int = 1,
+        top_k: int = 1,
         cosine_method: str = "sparse",
     ):
 
@@ -88,7 +92,7 @@ def __init__(
                     n_gram_range=n_gram_range,
                     clean_string=clean_string,
                     min_similarity=threshold,
-                    top_n=top_n,
+                    top_n=top_k,
                     cosine_method=cosine_method,
                 )
             ),
@@ -128,14 +132,14 @@ def __init__(
         self,
         model_name: str = "bert-base-multilingual-cased",
         threshold: float = VALUE_MATCHING_THRESHOLD,
-        top_n: int = 1,
+        top_k: int = 1,
         cosine_method: str = "sparse",
     ):
         embeddings = TransformerWordEmbeddings(model_name)
         method = Embeddings(
             embeddings,
             min_similarity=threshold,
-            top_n=top_n,
+            top_n=top_k,
             cosine_method=cosine_method,
         )
         super().__init__(PolyFuzz(method), threshold)
@@ -150,14 +154,14 @@ def __init__(
         self,
         model_name: str = "en-crawl",
         threshold: float = VALUE_MATCHING_THRESHOLD,
-        top_n: int = 1,
+        top_k: int = 1,
         cosine_method: str = "sparse",
     ):
         embeddings = WordEmbeddings(model_name)
         method = Embeddings(
             embeddings,
             min_similarity=threshold,
-            top_n=top_n,
+            top_n=top_k,
             cosine_method=cosine_method,
         )
         super().__init__(PolyFuzz(method), threshold)
diff --git a/docs/source/examples.rst b/docs/source/examples.rst
index d85e218..5ae0131 100644
--- a/docs/source/examples.rst
+++ b/docs/source/examples.rst
@@ -1,7 +1,9 @@
 Examples
-=========
+========
 
 Here can find different Jupyter notebook examples about how to use `bdi-kit`:
 
+- `Changing  the parameters of the matching methods <https://github.com/VIDA-NYU/bdi-kit/blob/devel/examples/changing_parameters.ipynb>`__
+- `Getting the top-k value matches <https://github.com/VIDA-NYU/bdi-kit/blob/devel/examples/top_k_value_matches.ipynb>`__
 - `Analyzing one attribute/column at a time <https://github.com/VIDA-NYU/bdi-kit/blob/devel/examples/analyzing_one_attribute.ipynb>`__
-- `Changing  the parameters of the matching methods <https://github.com/VIDA-NYU/bdi-kit/blob/devel/examples/changing_parameters.ipynb>`__
\ No newline at end of file
+
diff --git a/examples/top_k_value_matches.ipynb b/examples/top_k_value_matches.ipynb
new file mode 100644
index 0000000..d8ad2df
--- /dev/null
+++ b/examples/top_k_value_matches.ipynb
@@ -0,0 +1,2676 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Getting Top-K Value Matches"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "In this example, we show how to search for possible value matches between values in a table column and the standard format specified in the [GDC Data Dictionary](https://docs.gdc.cancer.gov/Data_Dictionary/). We use data from the study published by Dou et al. (https://pubmed.ncbi.nlm.nih.gov/37567170/)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/javascript": [
+       "(function(root) {\n",
+       "  function now() {\n",
+       "    return new Date();\n",
+       "  }\n",
+       "\n",
+       "  var force = true;\n",
+       "  var py_version = '3.4.3'.replace('rc', '-rc.').replace('.dev', '-dev.');\n",
+       "  var reloading = false;\n",
+       "  var Bokeh = root.Bokeh;\n",
+       "\n",
+       "  if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n",
+       "    root._bokeh_timeout = Date.now() + 5000;\n",
+       "    root._bokeh_failed_load = false;\n",
+       "  }\n",
+       "\n",
+       "  function run_callbacks() {\n",
+       "    try {\n",
+       "      root._bokeh_onload_callbacks.forEach(function(callback) {\n",
+       "        if (callback != null)\n",
+       "          callback();\n",
+       "      });\n",
+       "    } finally {\n",
+       "      delete root._bokeh_onload_callbacks;\n",
+       "    }\n",
+       "    console.debug(\"Bokeh: all callbacks have finished\");\n",
+       "  }\n",
+       "\n",
+       "  function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n",
+       "    if (css_urls == null) css_urls = [];\n",
+       "    if (js_urls == null) js_urls = [];\n",
+       "    if (js_modules == null) js_modules = [];\n",
+       "    if (js_exports == null) js_exports = {};\n",
+       "\n",
+       "    root._bokeh_onload_callbacks.push(callback);\n",
+       "\n",
+       "    if (root._bokeh_is_loading > 0) {\n",
+       "      console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n",
+       "      return null;\n",
+       "    }\n",
+       "    if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n",
+       "      run_callbacks();\n",
+       "      return null;\n",
+       "    }\n",
+       "    if (!reloading) {\n",
+       "      console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n",
+       "    }\n",
+       "\n",
+       "    function on_load() {\n",
+       "      root._bokeh_is_loading--;\n",
+       "      if (root._bokeh_is_loading === 0) {\n",
+       "        console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n",
+       "        run_callbacks()\n",
+       "      }\n",
+       "    }\n",
+       "    window._bokeh_on_load = on_load\n",
+       "\n",
+       "    function on_error() {\n",
+       "      console.error(\"failed to load \" + url);\n",
+       "    }\n",
+       "\n",
+       "    var skip = [];\n",
+       "    if (window.requirejs) {\n",
+       "      window.requirejs.config({'packages': {}, 'paths': {'tabulator': 'https://cdn.jsdelivr.net/npm/tabulator-tables@5.5.0/dist/js/tabulator.min', 'moment': 'https://cdn.jsdelivr.net/npm/luxon/build/global/luxon.min'}, 'shim': {}});\n",
+       "      require([\"tabulator\"], function(Tabulator) {\n",
+       "\twindow.Tabulator = Tabulator\n",
+       "\ton_load()\n",
+       "      })\n",
+       "      require([\"moment\"], function(moment) {\n",
+       "\twindow.moment = moment\n",
+       "\ton_load()\n",
+       "      })\n",
+       "      root._bokeh_is_loading = css_urls.length + 2;\n",
+       "    } else {\n",
+       "      root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n",
+       "    }\n",
+       "\n",
+       "    var existing_stylesheets = []\n",
+       "    var links = document.getElementsByTagName('link')\n",
+       "    for (var i = 0; i < links.length; i++) {\n",
+       "      var link = links[i]\n",
+       "      if (link.href != null) {\n",
+       "\texisting_stylesheets.push(link.href)\n",
+       "      }\n",
+       "    }\n",
+       "    for (var i = 0; i < css_urls.length; i++) {\n",
+       "      var url = css_urls[i];\n",
+       "      if (existing_stylesheets.indexOf(url) !== -1) {\n",
+       "\ton_load()\n",
+       "\tcontinue;\n",
+       "      }\n",
+       "      const element = document.createElement(\"link\");\n",
+       "      element.onload = on_load;\n",
+       "      element.onerror = on_error;\n",
+       "      element.rel = \"stylesheet\";\n",
+       "      element.type = \"text/css\";\n",
+       "      element.href = url;\n",
+       "      console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n",
+       "      document.body.appendChild(element);\n",
+       "    }    if (((window.Tabulator !== undefined) && (!(window.Tabulator instanceof HTMLElement))) || window.requirejs) {\n",
+       "      var urls = ['https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/tabulator-tables@5.5.0/dist/js/tabulator.min.js'];\n",
+       "      for (var i = 0; i < urls.length; i++) {\n",
+       "        skip.push(urls[i])\n",
+       "      }\n",
+       "    }    if (((window.moment !== undefined) && (!(window.moment instanceof HTMLElement))) || window.requirejs) {\n",
+       "      var urls = ['https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/luxon/build/global/luxon.min.js'];\n",
+       "      for (var i = 0; i < urls.length; i++) {\n",
+       "        skip.push(urls[i])\n",
+       "      }\n",
+       "    }    var existing_scripts = []\n",
+       "    var scripts = document.getElementsByTagName('script')\n",
+       "    for (var i = 0; i < scripts.length; i++) {\n",
+       "      var script = scripts[i]\n",
+       "      if (script.src != null) {\n",
+       "\texisting_scripts.push(script.src)\n",
+       "      }\n",
+       "    }\n",
+       "    for (var i = 0; i < js_urls.length; i++) {\n",
+       "      var url = js_urls[i];\n",
+       "      if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n",
+       "\tif (!window.requirejs) {\n",
+       "\t  on_load();\n",
+       "\t}\n",
+       "\tcontinue;\n",
+       "      }\n",
+       "      var element = document.createElement('script');\n",
+       "      element.onload = on_load;\n",
+       "      element.onerror = on_error;\n",
+       "      element.async = false;\n",
+       "      element.src = url;\n",
+       "      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+       "      document.head.appendChild(element);\n",
+       "    }\n",
+       "    for (var i = 0; i < js_modules.length; i++) {\n",
+       "      var url = js_modules[i];\n",
+       "      if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n",
+       "\tif (!window.requirejs) {\n",
+       "\t  on_load();\n",
+       "\t}\n",
+       "\tcontinue;\n",
+       "      }\n",
+       "      var element = document.createElement('script');\n",
+       "      element.onload = on_load;\n",
+       "      element.onerror = on_error;\n",
+       "      element.async = false;\n",
+       "      element.src = url;\n",
+       "      element.type = \"module\";\n",
+       "      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+       "      document.head.appendChild(element);\n",
+       "    }\n",
+       "    for (const name in js_exports) {\n",
+       "      var url = js_exports[name];\n",
+       "      if (skip.indexOf(url) >= 0 || root[name] != null) {\n",
+       "\tif (!window.requirejs) {\n",
+       "\t  on_load();\n",
+       "\t}\n",
+       "\tcontinue;\n",
+       "      }\n",
+       "      var element = document.createElement('script');\n",
+       "      element.onerror = on_error;\n",
+       "      element.async = false;\n",
+       "      element.type = \"module\";\n",
+       "      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+       "      element.textContent = `\n",
+       "      import ${name} from \"${url}\"\n",
+       "      window.${name} = ${name}\n",
+       "      window._bokeh_on_load()\n",
+       "      `\n",
+       "      document.head.appendChild(element);\n",
+       "    }\n",
+       "    if (!js_urls.length && !js_modules.length) {\n",
+       "      on_load()\n",
+       "    }\n",
+       "  };\n",
+       "\n",
+       "  function inject_raw_css(css) {\n",
+       "    const element = document.createElement(\"style\");\n",
+       "    element.appendChild(document.createTextNode(css));\n",
+       "    document.body.appendChild(element);\n",
+       "  }\n",
+       "\n",
+       "  var js_urls = [\"https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/tabulator-tables@5.5.0/dist/js/tabulator.min.js\", \"https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/luxon/build/global/luxon.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.4.3.min.js\", \"https://cdn.holoviz.org/panel/1.4.5/dist/panel.min.js\"];\n",
+       "  var js_modules = [];\n",
+       "  var js_exports = {};\n",
+       "  var css_urls = [\"https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/tabulator-tables@5.5.0/dist/css/tabulator_simple.min.css?v=1.4.5\"];\n",
+       "  var inline_js = [    function(Bokeh) {\n",
+       "      Bokeh.set_log_level(\"info\");\n",
+       "    },\n",
+       "function(Bokeh) {} // ensure no trailing comma for IE\n",
+       "  ];\n",
+       "\n",
+       "  function run_inline_js() {\n",
+       "    if ((root.Bokeh !== undefined) || (force === true)) {\n",
+       "      for (var i = 0; i < inline_js.length; i++) {\n",
+       "\ttry {\n",
+       "          inline_js[i].call(root, root.Bokeh);\n",
+       "\t} catch(e) {\n",
+       "\t  if (!reloading) {\n",
+       "\t    throw e;\n",
+       "\t  }\n",
+       "\t}\n",
+       "      }\n",
+       "      // Cache old bokeh versions\n",
+       "      if (Bokeh != undefined && !reloading) {\n",
+       "\tvar NewBokeh = root.Bokeh;\n",
+       "\tif (Bokeh.versions === undefined) {\n",
+       "\t  Bokeh.versions = new Map();\n",
+       "\t}\n",
+       "\tif (NewBokeh.version !== Bokeh.version) {\n",
+       "\t  Bokeh.versions.set(NewBokeh.version, NewBokeh)\n",
+       "\t}\n",
+       "\troot.Bokeh = Bokeh;\n",
+       "      }} else if (Date.now() < root._bokeh_timeout) {\n",
+       "      setTimeout(run_inline_js, 100);\n",
+       "    } else if (!root._bokeh_failed_load) {\n",
+       "      console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n",
+       "      root._bokeh_failed_load = true;\n",
+       "    }\n",
+       "    root._bokeh_is_initializing = false\n",
+       "  }\n",
+       "\n",
+       "  function load_or_wait() {\n",
+       "    // Implement a backoff loop that tries to ensure we do not load multiple\n",
+       "    // versions of Bokeh and its dependencies at the same time.\n",
+       "    // In recent versions we use the root._bokeh_is_initializing flag\n",
+       "    // to determine whether there is an ongoing attempt to initialize\n",
+       "    // bokeh, however for backward compatibility we also try to ensure\n",
+       "    // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n",
+       "    // before older versions are fully initialized.\n",
+       "    if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n",
+       "      root._bokeh_is_initializing = false;\n",
+       "      root._bokeh_onload_callbacks = undefined;\n",
+       "      console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n",
+       "      load_or_wait();\n",
+       "    } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n",
+       "      setTimeout(load_or_wait, 100);\n",
+       "    } else {\n",
+       "      root._bokeh_is_initializing = true\n",
+       "      root._bokeh_onload_callbacks = []\n",
+       "      var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+       "      if (!reloading && !bokeh_loaded) {\n",
+       "\troot.Bokeh = undefined;\n",
+       "      }\n",
+       "      load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n",
+       "\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n",
+       "\trun_inline_js();\n",
+       "      });\n",
+       "    }\n",
+       "  }\n",
+       "  // Give older versions of the autoload script a head-start to ensure\n",
+       "  // they initialize before we start loading newer version.\n",
+       "  setTimeout(load_or_wait, 100)\n",
+       "}(window));"
+      ],
+      "application/vnd.holoviews_load.v0+json": "(function(root) {\n  function now() {\n    return new Date();\n  }\n\n  var force = true;\n  var py_version = '3.4.3'.replace('rc', '-rc.').replace('.dev', '-dev.');\n  var reloading = false;\n  var Bokeh = root.Bokeh;\n\n  if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n    root._bokeh_timeout = Date.now() + 5000;\n    root._bokeh_failed_load = false;\n  }\n\n  function run_callbacks() {\n    try {\n      root._bokeh_onload_callbacks.forEach(function(callback) {\n        if (callback != null)\n          callback();\n      });\n    } finally {\n      delete root._bokeh_onload_callbacks;\n    }\n    console.debug(\"Bokeh: all callbacks have finished\");\n  }\n\n  function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n    if (css_urls == null) css_urls = [];\n    if (js_urls == null) js_urls = [];\n    if (js_modules == null) js_modules = [];\n    if (js_exports == null) js_exports = {};\n\n    root._bokeh_onload_callbacks.push(callback);\n\n    if (root._bokeh_is_loading > 0) {\n      console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n      return null;\n    }\n    if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n      run_callbacks();\n      return null;\n    }\n    if (!reloading) {\n      console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n    }\n\n    function on_load() {\n      root._bokeh_is_loading--;\n      if (root._bokeh_is_loading === 0) {\n        console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n        run_callbacks()\n      }\n    }\n    window._bokeh_on_load = on_load\n\n    function on_error() {\n      console.error(\"failed to load \" + url);\n    }\n\n    var skip = [];\n    if (window.requirejs) {\n      window.requirejs.config({'packages': {}, 'paths': {'tabulator': 'https://cdn.jsdelivr.net/npm/tabulator-tables@5.5.0/dist/js/tabulator.min', 'moment': 'https://cdn.jsdelivr.net/npm/luxon/build/global/luxon.min'}, 'shim': {}});\n      require([\"tabulator\"], function(Tabulator) {\n\twindow.Tabulator = Tabulator\n\ton_load()\n      })\n      require([\"moment\"], function(moment) {\n\twindow.moment = moment\n\ton_load()\n      })\n      root._bokeh_is_loading = css_urls.length + 2;\n    } else {\n      root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n    }\n\n    var existing_stylesheets = []\n    var links = document.getElementsByTagName('link')\n    for (var i = 0; i < links.length; i++) {\n      var link = links[i]\n      if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n      }\n    }\n    for (var i = 0; i < css_urls.length; i++) {\n      var url = css_urls[i];\n      if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n      }\n      const element = document.createElement(\"link\");\n      element.onload = on_load;\n      element.onerror = on_error;\n      element.rel = \"stylesheet\";\n      element.type = \"text/css\";\n      element.href = url;\n      console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n      document.body.appendChild(element);\n    }    if (((window.Tabulator !== undefined) && (!(window.Tabulator instanceof HTMLElement))) || window.requirejs) {\n      var urls = ['https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/tabulator-tables@5.5.0/dist/js/tabulator.min.js'];\n      for (var i = 0; i < urls.length; i++) {\n        skip.push(urls[i])\n      }\n    }    if (((window.moment !== undefined) && (!(window.moment instanceof HTMLElement))) || window.requirejs) {\n      var urls = ['https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/luxon/build/global/luxon.min.js'];\n      for (var i = 0; i < urls.length; i++) {\n        skip.push(urls[i])\n      }\n    }    var existing_scripts = []\n    var scripts = document.getElementsByTagName('script')\n    for (var i = 0; i < scripts.length; i++) {\n      var script = scripts[i]\n      if (script.src != null) {\n\texisting_scripts.push(script.src)\n      }\n    }\n    for (var i = 0; i < js_urls.length; i++) {\n      var url = js_urls[i];\n      if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t  on_load();\n\t}\n\tcontinue;\n      }\n      var element = document.createElement('script');\n      element.onload = on_load;\n      element.onerror = on_error;\n      element.async = false;\n      element.src = url;\n      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n      document.head.appendChild(element);\n    }\n    for (var i = 0; i < js_modules.length; i++) {\n      var url = js_modules[i];\n      if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t  on_load();\n\t}\n\tcontinue;\n      }\n      var element = document.createElement('script');\n      element.onload = on_load;\n      element.onerror = on_error;\n      element.async = false;\n      element.src = url;\n      element.type = \"module\";\n      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n      document.head.appendChild(element);\n    }\n    for (const name in js_exports) {\n      var url = js_exports[name];\n      if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t  on_load();\n\t}\n\tcontinue;\n      }\n      var element = document.createElement('script');\n      element.onerror = on_error;\n      element.async = false;\n      element.type = \"module\";\n      console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n      element.textContent = `\n      import ${name} from \"${url}\"\n      window.${name} = ${name}\n      window._bokeh_on_load()\n      `\n      document.head.appendChild(element);\n    }\n    if (!js_urls.length && !js_modules.length) {\n      on_load()\n    }\n  };\n\n  function inject_raw_css(css) {\n    const element = document.createElement(\"style\");\n    element.appendChild(document.createTextNode(css));\n    document.body.appendChild(element);\n  }\n\n  var js_urls = [\"https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/tabulator-tables@5.5.0/dist/js/tabulator.min.js\", \"https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/luxon/build/global/luxon.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-3.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.4.3.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.4.3.min.js\", \"https://cdn.holoviz.org/panel/1.4.5/dist/panel.min.js\"];\n  var js_modules = [];\n  var js_exports = {};\n  var css_urls = [\"https://cdn.holoviz.org/panel/1.4.5/dist/bundled/datatabulator/tabulator-tables@5.5.0/dist/css/tabulator_simple.min.css?v=1.4.5\"];\n  var inline_js = [    function(Bokeh) {\n      Bokeh.set_log_level(\"info\");\n    },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n  ];\n\n  function run_inline_js() {\n    if ((root.Bokeh !== undefined) || (force === true)) {\n      for (var i = 0; i < inline_js.length; i++) {\n\ttry {\n          inline_js[i].call(root, root.Bokeh);\n\t} catch(e) {\n\t  if (!reloading) {\n\t    throw e;\n\t  }\n\t}\n      }\n      // Cache old bokeh versions\n      if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t  Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t  Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n      }} else if (Date.now() < root._bokeh_timeout) {\n      setTimeout(run_inline_js, 100);\n    } else if (!root._bokeh_failed_load) {\n      console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n      root._bokeh_failed_load = true;\n    }\n    root._bokeh_is_initializing = false\n  }\n\n  function load_or_wait() {\n    // Implement a backoff loop that tries to ensure we do not load multiple\n    // versions of Bokeh and its dependencies at the same time.\n    // In recent versions we use the root._bokeh_is_initializing flag\n    // to determine whether there is an ongoing attempt to initialize\n    // bokeh, however for backward compatibility we also try to ensure\n    // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n    // before older versions are fully initialized.\n    if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n      root._bokeh_is_initializing = false;\n      root._bokeh_onload_callbacks = undefined;\n      console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n      load_or_wait();\n    } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n      setTimeout(load_or_wait, 100);\n    } else {\n      root._bokeh_is_initializing = true\n      root._bokeh_onload_callbacks = []\n      var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n      if (!reloading && !bokeh_loaded) {\n\troot.Bokeh = undefined;\n      }\n      load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n      });\n    }\n  }\n  // Give older versions of the autoload script a head-start to ensure\n  // they initialize before we start loading newer version.\n  setTimeout(load_or_wait, 100)\n}(window));"
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/javascript": [
+       "\n",
+       "if ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n",
+       "  window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n",
+       "}\n",
+       "\n",
+       "\n",
+       "    function JupyterCommManager() {\n",
+       "    }\n",
+       "\n",
+       "    JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n",
+       "      if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n",
+       "        var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n",
+       "        comm_manager.register_target(comm_id, function(comm) {\n",
+       "          comm.on_msg(msg_handler);\n",
+       "        });\n",
+       "      } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n",
+       "        window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n",
+       "          comm.onMsg = msg_handler;\n",
+       "        });\n",
+       "      } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n",
+       "        google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n",
+       "          var messages = comm.messages[Symbol.asyncIterator]();\n",
+       "          function processIteratorResult(result) {\n",
+       "            var message = result.value;\n",
+       "            console.log(message)\n",
+       "            var content = {data: message.data, comm_id};\n",
+       "            var buffers = []\n",
+       "            for (var buffer of message.buffers || []) {\n",
+       "              buffers.push(new DataView(buffer))\n",
+       "            }\n",
+       "            var metadata = message.metadata || {};\n",
+       "            var msg = {content, buffers, metadata}\n",
+       "            msg_handler(msg);\n",
+       "            return messages.next().then(processIteratorResult);\n",
+       "          }\n",
+       "          return messages.next().then(processIteratorResult);\n",
+       "        })\n",
+       "      }\n",
+       "    }\n",
+       "\n",
+       "    JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n",
+       "      if (comm_id in window.PyViz.comms) {\n",
+       "        return window.PyViz.comms[comm_id];\n",
+       "      } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n",
+       "        var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n",
+       "        var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n",
+       "        if (msg_handler) {\n",
+       "          comm.on_msg(msg_handler);\n",
+       "        }\n",
+       "      } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n",
+       "        var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n",
+       "        comm.open();\n",
+       "        if (msg_handler) {\n",
+       "          comm.onMsg = msg_handler;\n",
+       "        }\n",
+       "      } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n",
+       "        var comm_promise = google.colab.kernel.comms.open(comm_id)\n",
+       "        comm_promise.then((comm) => {\n",
+       "          window.PyViz.comms[comm_id] = comm;\n",
+       "          if (msg_handler) {\n",
+       "            var messages = comm.messages[Symbol.asyncIterator]();\n",
+       "            function processIteratorResult(result) {\n",
+       "              var message = result.value;\n",
+       "              var content = {data: message.data};\n",
+       "              var metadata = message.metadata || {comm_id};\n",
+       "              var msg = {content, metadata}\n",
+       "              msg_handler(msg);\n",
+       "              return messages.next().then(processIteratorResult);\n",
+       "            }\n",
+       "            return messages.next().then(processIteratorResult);\n",
+       "          }\n",
+       "        }) \n",
+       "        var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n",
+       "          return comm_promise.then((comm) => {\n",
+       "            comm.send(data, metadata, buffers, disposeOnDone);\n",
+       "          });\n",
+       "        };\n",
+       "        var comm = {\n",
+       "          send: sendClosure\n",
+       "        };\n",
+       "      }\n",
+       "      window.PyViz.comms[comm_id] = comm;\n",
+       "      return comm;\n",
+       "    }\n",
+       "    window.PyViz.comm_manager = new JupyterCommManager();\n",
+       "    \n",
+       "\n",
+       "\n",
+       "var JS_MIME_TYPE = 'application/javascript';\n",
+       "var HTML_MIME_TYPE = 'text/html';\n",
+       "var EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\n",
+       "var CLASS_NAME = 'output';\n",
+       "\n",
+       "/**\n",
+       " * Render data to the DOM node\n",
+       " */\n",
+       "function render(props, node) {\n",
+       "  var div = document.createElement(\"div\");\n",
+       "  var script = document.createElement(\"script\");\n",
+       "  node.appendChild(div);\n",
+       "  node.appendChild(script);\n",
+       "}\n",
+       "\n",
+       "/**\n",
+       " * Handle when a new output is added\n",
+       " */\n",
+       "function handle_add_output(event, handle) {\n",
+       "  var output_area = handle.output_area;\n",
+       "  var output = handle.output;\n",
+       "  if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n",
+       "    return\n",
+       "  }\n",
+       "  var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n",
+       "  var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n",
+       "  if (id !== undefined) {\n",
+       "    var nchildren = toinsert.length;\n",
+       "    var html_node = toinsert[nchildren-1].children[0];\n",
+       "    html_node.innerHTML = output.data[HTML_MIME_TYPE];\n",
+       "    var scripts = [];\n",
+       "    var nodelist = html_node.querySelectorAll(\"script\");\n",
+       "    for (var i in nodelist) {\n",
+       "      if (nodelist.hasOwnProperty(i)) {\n",
+       "        scripts.push(nodelist[i])\n",
+       "      }\n",
+       "    }\n",
+       "\n",
+       "    scripts.forEach( function (oldScript) {\n",
+       "      var newScript = document.createElement(\"script\");\n",
+       "      var attrs = [];\n",
+       "      var nodemap = oldScript.attributes;\n",
+       "      for (var j in nodemap) {\n",
+       "        if (nodemap.hasOwnProperty(j)) {\n",
+       "          attrs.push(nodemap[j])\n",
+       "        }\n",
+       "      }\n",
+       "      attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n",
+       "      newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n",
+       "      oldScript.parentNode.replaceChild(newScript, oldScript);\n",
+       "    });\n",
+       "    if (JS_MIME_TYPE in output.data) {\n",
+       "      toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n",
+       "    }\n",
+       "    output_area._hv_plot_id = id;\n",
+       "    if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n",
+       "      window.PyViz.plot_index[id] = Bokeh.index[id];\n",
+       "    } else {\n",
+       "      window.PyViz.plot_index[id] = null;\n",
+       "    }\n",
+       "  } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n",
+       "    var bk_div = document.createElement(\"div\");\n",
+       "    bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n",
+       "    var script_attrs = bk_div.children[0].attributes;\n",
+       "    for (var i = 0; i < script_attrs.length; i++) {\n",
+       "      toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n",
+       "    }\n",
+       "    // store reference to server id on output_area\n",
+       "    output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n",
+       "  }\n",
+       "}\n",
+       "\n",
+       "/**\n",
+       " * Handle when an output is cleared or removed\n",
+       " */\n",
+       "function handle_clear_output(event, handle) {\n",
+       "  var id = handle.cell.output_area._hv_plot_id;\n",
+       "  var server_id = handle.cell.output_area._bokeh_server_id;\n",
+       "  if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n",
+       "  var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n",
+       "  if (server_id !== null) {\n",
+       "    comm.send({event_type: 'server_delete', 'id': server_id});\n",
+       "    return;\n",
+       "  } else if (comm !== null) {\n",
+       "    comm.send({event_type: 'delete', 'id': id});\n",
+       "  }\n",
+       "  delete PyViz.plot_index[id];\n",
+       "  if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n",
+       "    var doc = window.Bokeh.index[id].model.document\n",
+       "    doc.clear();\n",
+       "    const i = window.Bokeh.documents.indexOf(doc);\n",
+       "    if (i > -1) {\n",
+       "      window.Bokeh.documents.splice(i, 1);\n",
+       "    }\n",
+       "  }\n",
+       "}\n",
+       "\n",
+       "/**\n",
+       " * Handle kernel restart event\n",
+       " */\n",
+       "function handle_kernel_cleanup(event, handle) {\n",
+       "  delete PyViz.comms[\"hv-extension-comm\"];\n",
+       "  window.PyViz.plot_index = {}\n",
+       "}\n",
+       "\n",
+       "/**\n",
+       " * Handle update_display_data messages\n",
+       " */\n",
+       "function handle_update_output(event, handle) {\n",
+       "  handle_clear_output(event, {cell: {output_area: handle.output_area}})\n",
+       "  handle_add_output(event, handle)\n",
+       "}\n",
+       "\n",
+       "function register_renderer(events, OutputArea) {\n",
+       "  function append_mime(data, metadata, element) {\n",
+       "    // create a DOM node to render to\n",
+       "    var toinsert = this.create_output_subarea(\n",
+       "    metadata,\n",
+       "    CLASS_NAME,\n",
+       "    EXEC_MIME_TYPE\n",
+       "    );\n",
+       "    this.keyboard_manager.register_events(toinsert);\n",
+       "    // Render to node\n",
+       "    var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n",
+       "    render(props, toinsert[0]);\n",
+       "    element.append(toinsert);\n",
+       "    return toinsert\n",
+       "  }\n",
+       "\n",
+       "  events.on('output_added.OutputArea', handle_add_output);\n",
+       "  events.on('output_updated.OutputArea', handle_update_output);\n",
+       "  events.on('clear_output.CodeCell', handle_clear_output);\n",
+       "  events.on('delete.Cell', handle_clear_output);\n",
+       "  events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n",
+       "\n",
+       "  OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n",
+       "    safe: true,\n",
+       "    index: 0\n",
+       "  });\n",
+       "}\n",
+       "\n",
+       "if (window.Jupyter !== undefined) {\n",
+       "  try {\n",
+       "    var events = require('base/js/events');\n",
+       "    var OutputArea = require('notebook/js/outputarea').OutputArea;\n",
+       "    if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n",
+       "      register_renderer(events, OutputArea);\n",
+       "    }\n",
+       "  } catch(err) {\n",
+       "  }\n",
+       "}\n"
+      ],
+      "application/vnd.holoviews_load.v0+json": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n  window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n    function JupyterCommManager() {\n    }\n\n    JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n      if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n        var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n        comm_manager.register_target(comm_id, function(comm) {\n          comm.on_msg(msg_handler);\n        });\n      } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n        window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n          comm.onMsg = msg_handler;\n        });\n      } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n        google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n          var messages = comm.messages[Symbol.asyncIterator]();\n          function processIteratorResult(result) {\n            var message = result.value;\n            console.log(message)\n            var content = {data: message.data, comm_id};\n            var buffers = []\n            for (var buffer of message.buffers || []) {\n              buffers.push(new DataView(buffer))\n            }\n            var metadata = message.metadata || {};\n            var msg = {content, buffers, metadata}\n            msg_handler(msg);\n            return messages.next().then(processIteratorResult);\n          }\n          return messages.next().then(processIteratorResult);\n        })\n      }\n    }\n\n    JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n      if (comm_id in window.PyViz.comms) {\n        return window.PyViz.comms[comm_id];\n      } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n        var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n        var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n        if (msg_handler) {\n          comm.on_msg(msg_handler);\n        }\n      } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n        var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n        comm.open();\n        if (msg_handler) {\n          comm.onMsg = msg_handler;\n        }\n      } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n        var comm_promise = google.colab.kernel.comms.open(comm_id)\n        comm_promise.then((comm) => {\n          window.PyViz.comms[comm_id] = comm;\n          if (msg_handler) {\n            var messages = comm.messages[Symbol.asyncIterator]();\n            function processIteratorResult(result) {\n              var message = result.value;\n              var content = {data: message.data};\n              var metadata = message.metadata || {comm_id};\n              var msg = {content, metadata}\n              msg_handler(msg);\n              return messages.next().then(processIteratorResult);\n            }\n            return messages.next().then(processIteratorResult);\n          }\n        }) \n        var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n          return comm_promise.then((comm) => {\n            comm.send(data, metadata, buffers, disposeOnDone);\n          });\n        };\n        var comm = {\n          send: sendClosure\n        };\n      }\n      window.PyViz.comms[comm_id] = comm;\n      return comm;\n    }\n    window.PyViz.comm_manager = new JupyterCommManager();\n    \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n  var div = document.createElement(\"div\");\n  var script = document.createElement(\"script\");\n  node.appendChild(div);\n  node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n  var output_area = handle.output_area;\n  var output = handle.output;\n  if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n    return\n  }\n  var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n  var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n  if (id !== undefined) {\n    var nchildren = toinsert.length;\n    var html_node = toinsert[nchildren-1].children[0];\n    html_node.innerHTML = output.data[HTML_MIME_TYPE];\n    var scripts = [];\n    var nodelist = html_node.querySelectorAll(\"script\");\n    for (var i in nodelist) {\n      if (nodelist.hasOwnProperty(i)) {\n        scripts.push(nodelist[i])\n      }\n    }\n\n    scripts.forEach( function (oldScript) {\n      var newScript = document.createElement(\"script\");\n      var attrs = [];\n      var nodemap = oldScript.attributes;\n      for (var j in nodemap) {\n        if (nodemap.hasOwnProperty(j)) {\n          attrs.push(nodemap[j])\n        }\n      }\n      attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n      newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n      oldScript.parentNode.replaceChild(newScript, oldScript);\n    });\n    if (JS_MIME_TYPE in output.data) {\n      toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n    }\n    output_area._hv_plot_id = id;\n    if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n      window.PyViz.plot_index[id] = Bokeh.index[id];\n    } else {\n      window.PyViz.plot_index[id] = null;\n    }\n  } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n    var bk_div = document.createElement(\"div\");\n    bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n    var script_attrs = bk_div.children[0].attributes;\n    for (var i = 0; i < script_attrs.length; i++) {\n      toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n    }\n    // store reference to server id on output_area\n    output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n  }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n  var id = handle.cell.output_area._hv_plot_id;\n  var server_id = handle.cell.output_area._bokeh_server_id;\n  if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n  var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n  if (server_id !== null) {\n    comm.send({event_type: 'server_delete', 'id': server_id});\n    return;\n  } else if (comm !== null) {\n    comm.send({event_type: 'delete', 'id': id});\n  }\n  delete PyViz.plot_index[id];\n  if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n    var doc = window.Bokeh.index[id].model.document\n    doc.clear();\n    const i = window.Bokeh.documents.indexOf(doc);\n    if (i > -1) {\n      window.Bokeh.documents.splice(i, 1);\n    }\n  }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n  delete PyViz.comms[\"hv-extension-comm\"];\n  window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n  handle_clear_output(event, {cell: {output_area: handle.output_area}})\n  handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n  function append_mime(data, metadata, element) {\n    // create a DOM node to render to\n    var toinsert = this.create_output_subarea(\n    metadata,\n    CLASS_NAME,\n    EXEC_MIME_TYPE\n    );\n    this.keyboard_manager.register_events(toinsert);\n    // Render to node\n    var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n    render(props, toinsert[0]);\n    element.append(toinsert);\n    return toinsert\n  }\n\n  events.on('output_added.OutputArea', handle_add_output);\n  events.on('output_updated.OutputArea', handle_update_output);\n  events.on('clear_output.CodeCell', handle_clear_output);\n  events.on('delete.Cell', handle_clear_output);\n  events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n  OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n    safe: true,\n    index: 0\n  });\n}\n\nif (window.Jupyter !== undefined) {\n  try {\n    var events = require('base/js/events');\n    var OutputArea = require('notebook/js/outputarea').OutputArea;\n    if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n      register_renderer(events, OutputArea);\n    }\n  } catch(err) {\n  }\n}\n"
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<style>*[data-root-id],\n",
+       "*[data-root-id] > * {\n",
+       "  box-sizing: border-box;\n",
+       "  font-family: var(--jp-ui-font-family);\n",
+       "  font-size: var(--jp-ui-font-size1);\n",
+       "  color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
+       "}\n",
+       "\n",
+       "/* Override VSCode background color */\n",
+       ".cell-output-ipywidget-background:has(\n",
+       "    > .cell-output-ipywidget-background > .lm-Widget > *[data-root-id]\n",
+       "  ),\n",
+       ".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
+       "  background-color: transparent !important;\n",
+       "}\n",
+       "</style>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/vnd.holoviews_exec.v0+json": "",
+      "text/html": [
+       "<div id='a21e4506-b4e9-409c-ac8e-1221cad7da8f'>\n",
+       "  <div id=\"edaf4e76-e7ec-4cf7-b1f1-260fee5d5467\" data-root-id=\"a21e4506-b4e9-409c-ac8e-1221cad7da8f\" style=\"display: contents;\"></div>\n",
+       "</div>\n",
+       "<script type=\"application/javascript\">(function(root) {\n",
+       "  var docs_json = {\"baf9e065-d12d-494f-aba8-9d07257e2b05\":{\"version\":\"3.4.3\",\"title\":\"Bokeh Application\",\"roots\":[{\"type\":\"object\",\"name\":\"panel.models.browser.BrowserInfo\",\"id\":\"a21e4506-b4e9-409c-ac8e-1221cad7da8f\"},{\"type\":\"object\",\"name\":\"panel.models.comm_manager.CommManager\",\"id\":\"d3bd85ad-374f-49f4-8ec6-11c8a94757cb\",\"attributes\":{\"plot_id\":\"a21e4506-b4e9-409c-ac8e-1221cad7da8f\",\"comm_id\":\"affb8398e7ab40718848e2d040ab9802\",\"client_comm_id\":\"c881e2a6766c4b2bb3b2413ddc8fe0c4\"}}],\"defs\":[{\"type\":\"model\",\"name\":\"ReactiveHTML1\"},{\"type\":\"model\",\"name\":\"FlexBox1\",\"properties\":[{\"name\":\"align_content\",\"kind\":\"Any\",\"default\":\"flex-start\"},{\"name\":\"align_items\",\"kind\":\"Any\",\"default\":\"flex-start\"},{\"name\":\"flex_direction\",\"kind\":\"Any\",\"default\":\"row\"},{\"name\":\"flex_wrap\",\"kind\":\"Any\",\"default\":\"wrap\"},{\"name\":\"gap\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"justify_content\",\"kind\":\"Any\",\"default\":\"flex-start\"}]},{\"type\":\"model\",\"name\":\"FloatPanel1\",\"properties\":[{\"name\":\"config\",\"kind\":\"Any\",\"default\":{\"type\":\"map\"}},{\"name\":\"contained\",\"kind\":\"Any\",\"default\":true},{\"name\":\"position\",\"kind\":\"Any\",\"default\":\"right-top\"},{\"name\":\"offsetx\",\"kind\":\"Any\",\"default\":null},{\"name\":\"offsety\",\"kind\":\"Any\",\"default\":null},{\"name\":\"theme\",\"kind\":\"Any\",\"default\":\"primary\"},{\"name\":\"status\",\"kind\":\"Any\",\"default\":\"normalized\"}]},{\"type\":\"model\",\"name\":\"GridStack1\",\"properties\":[{\"name\":\"mode\",\"kind\":\"Any\",\"default\":\"warn\"},{\"name\":\"ncols\",\"kind\":\"Any\",\"default\":null},{\"name\":\"nrows\",\"kind\":\"Any\",\"default\":null},{\"name\":\"allow_resize\",\"kind\":\"Any\",\"default\":true},{\"name\":\"allow_drag\",\"kind\":\"Any\",\"default\":true},{\"name\":\"state\",\"kind\":\"Any\",\"default\":[]}]},{\"type\":\"model\",\"name\":\"drag1\",\"properties\":[{\"name\":\"slider_width\",\"kind\":\"Any\",\"default\":5},{\"name\":\"slider_color\",\"kind\":\"Any\",\"default\":\"black\"},{\"name\":\"value\",\"kind\":\"Any\",\"default\":50}]},{\"type\":\"model\",\"name\":\"click1\",\"properties\":[{\"name\":\"terminal_output\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"debug_name\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"clears\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"FastWrapper1\",\"properties\":[{\"name\":\"object\",\"kind\":\"Any\",\"default\":null},{\"name\":\"style\",\"kind\":\"Any\",\"default\":null}]},{\"type\":\"model\",\"name\":\"NotificationAreaBase1\",\"properties\":[{\"name\":\"js_events\",\"kind\":\"Any\",\"default\":{\"type\":\"map\"}},{\"name\":\"position\",\"kind\":\"Any\",\"default\":\"bottom-right\"},{\"name\":\"_clear\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"NotificationArea1\",\"properties\":[{\"name\":\"js_events\",\"kind\":\"Any\",\"default\":{\"type\":\"map\"}},{\"name\":\"notifications\",\"kind\":\"Any\",\"default\":[]},{\"name\":\"position\",\"kind\":\"Any\",\"default\":\"bottom-right\"},{\"name\":\"_clear\",\"kind\":\"Any\",\"default\":0},{\"name\":\"types\",\"kind\":\"Any\",\"default\":[{\"type\":\"map\",\"entries\":[[\"type\",\"warning\"],[\"background\",\"#ffc107\"],[\"icon\",{\"type\":\"map\",\"entries\":[[\"className\",\"fas fa-exclamation-triangle\"],[\"tagName\",\"i\"],[\"color\",\"white\"]]}]]},{\"type\":\"map\",\"entries\":[[\"type\",\"info\"],[\"background\",\"#007bff\"],[\"icon\",{\"type\":\"map\",\"entries\":[[\"className\",\"fas fa-info-circle\"],[\"tagName\",\"i\"],[\"color\",\"white\"]]}]]}]}]},{\"type\":\"model\",\"name\":\"Notification\",\"properties\":[{\"name\":\"background\",\"kind\":\"Any\",\"default\":null},{\"name\":\"duration\",\"kind\":\"Any\",\"default\":3000},{\"name\":\"icon\",\"kind\":\"Any\",\"default\":null},{\"name\":\"message\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"notification_type\",\"kind\":\"Any\",\"default\":null},{\"name\":\"_destroyed\",\"kind\":\"Any\",\"default\":false}]},{\"type\":\"model\",\"name\":\"TemplateActions1\",\"properties\":[{\"name\":\"open_modal\",\"kind\":\"Any\",\"default\":0},{\"name\":\"close_modal\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"BootstrapTemplateActions1\",\"properties\":[{\"name\":\"open_modal\",\"kind\":\"Any\",\"default\":0},{\"name\":\"close_modal\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"TemplateEditor1\",\"properties\":[{\"name\":\"layout\",\"kind\":\"Any\",\"default\":[]}]},{\"type\":\"model\",\"name\":\"MaterialTemplateActions1\",\"properties\":[{\"name\":\"open_modal\",\"kind\":\"Any\",\"default\":0},{\"name\":\"close_modal\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"copy_to_clipboard1\",\"properties\":[{\"name\":\"fill\",\"kind\":\"Any\",\"default\":\"none\"},{\"name\":\"value\",\"kind\":\"Any\",\"default\":null}]}]}};\n",
+       "  var render_items = [{\"docid\":\"baf9e065-d12d-494f-aba8-9d07257e2b05\",\"roots\":{\"a21e4506-b4e9-409c-ac8e-1221cad7da8f\":\"edaf4e76-e7ec-4cf7-b1f1-260fee5d5467\"},\"root_ids\":[\"a21e4506-b4e9-409c-ac8e-1221cad7da8f\"]}];\n",
+       "  var docs = Object.values(docs_json)\n",
+       "  if (!docs) {\n",
+       "    return\n",
+       "  }\n",
+       "  const py_version = docs[0].version.replace('rc', '-rc.').replace('.dev', '-dev.')\n",
+       "  async function embed_document(root) {\n",
+       "    var Bokeh = get_bokeh(root)\n",
+       "    await Bokeh.embed.embed_items_notebook(docs_json, render_items);\n",
+       "    for (const render_item of render_items) {\n",
+       "      for (const root_id of render_item.root_ids) {\n",
+       "\tconst id_el = document.getElementById(root_id)\n",
+       "\tif (id_el.children.length && id_el.children[0].hasAttribute('data-root-id')) {\n",
+       "\t  const root_el = id_el.children[0]\n",
+       "\t  root_el.id = root_el.id + '-rendered'\n",
+       "\t  for (const child of root_el.children) {\n",
+       "            // Ensure JupyterLab does not capture keyboard shortcuts\n",
+       "            // see: https://jupyterlab.readthedocs.io/en/4.1.x/extension/notebook.html#keyboard-interaction-model\n",
+       "\t    child.setAttribute('data-lm-suppress-shortcuts', 'true')\n",
+       "\t  }\n",
+       "\t}\n",
+       "      }\n",
+       "    }\n",
+       "  }\n",
+       "  function get_bokeh(root) {\n",
+       "    if (root.Bokeh === undefined) {\n",
+       "      return null\n",
+       "    } else if (root.Bokeh.version !== py_version) {\n",
+       "      if (root.Bokeh.versions === undefined || !root.Bokeh.versions.has(py_version)) {\n",
+       "\treturn null\n",
+       "      }\n",
+       "      return root.Bokeh.versions.get(py_version);\n",
+       "    } else if (root.Bokeh.version === py_version) {\n",
+       "      return root.Bokeh\n",
+       "    }\n",
+       "    return null\n",
+       "  }\n",
+       "  function is_loaded(root) {\n",
+       "    var Bokeh = get_bokeh(root)\n",
+       "    return (Bokeh != null && Bokeh.Panel !== undefined && ( root.Tabulator !== undefined) && ( root.Tabulator !== undefined))\n",
+       "  }\n",
+       "  if (is_loaded(root)) {\n",
+       "    embed_document(root);\n",
+       "  } else {\n",
+       "    var attempts = 0;\n",
+       "    var timer = setInterval(function(root) {\n",
+       "      if (is_loaded(root)) {\n",
+       "        clearInterval(timer);\n",
+       "        embed_document(root);\n",
+       "      } else if (document.readyState == \"complete\") {\n",
+       "        attempts++;\n",
+       "        if (attempts > 200) {\n",
+       "          clearInterval(timer);\n",
+       "\t  var Bokeh = get_bokeh(root)\n",
+       "\t  if (Bokeh == null || Bokeh.Panel == null) {\n",
+       "            console.warn(\"Panel: ERROR: Unable to run Panel code because Bokeh or Panel library is missing\");\n",
+       "\t  } else {\n",
+       "\t    console.warn(\"Panel: WARNING: Attempting to render but not all required libraries could be resolved.\")\n",
+       "\t    embed_document(root)\n",
+       "\t  }\n",
+       "        }\n",
+       "      }\n",
+       "    }, 25, root)\n",
+       "  }\n",
+       "})(window);</script>"
+      ]
+     },
+     "metadata": {
+      "application/vnd.holoviews_exec.v0+json": {
+       "id": "a21e4506-b4e9-409c-ac8e-1221cad7da8f"
+      }
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "import bdikit as bdi\n",
+    "import pandas as pd"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We start by loading the data file. We will focus on the columns shown below."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>Race</th>\n",
+       "      <th>Ethnicity</th>\n",
+       "      <th>FIGO_stage</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>White</td>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>IA</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>White</td>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>IA</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>White</td>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>IA</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>White</td>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>IA</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>5</th>\n",
+       "      <td>White</td>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>IA</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>6</th>\n",
+       "      <td>White</td>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>IA</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>7</th>\n",
+       "      <td>White</td>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>IA</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>8</th>\n",
+       "      <td>White</td>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>IIIA</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>9</th>\n",
+       "      <td>White</td>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>IA</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "    Race               Ethnicity FIGO_stage\n",
+       "0  White  Not-Hispanic or Latino         IA\n",
+       "1  White  Not-Hispanic or Latino         IA\n",
+       "2  White  Not-Hispanic or Latino         IA\n",
+       "3    NaN                     NaN        NaN\n",
+       "4  White  Not-Hispanic or Latino         IA\n",
+       "5  White  Not-Hispanic or Latino         IA\n",
+       "6  White  Not-Hispanic or Latino         IA\n",
+       "7  White  Not-Hispanic or Latino         IA\n",
+       "8  White  Not-Hispanic or Latino       IIIA\n",
+       "9  White  Not-Hispanic or Latino         IA"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "dataset = pd.read_csv(\"./datasets/dou.csv\")\n",
+    "columns = [\n",
+    "    \"Race\",\n",
+    "    \"Ethnicity\",\n",
+    "    \"FIGO_stage\",\n",
+    "]\n",
+    "\n",
+    "dataset[columns].head(10)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can send a tuple `(source column, target column)` as a parameter to the function `top_value_matches()`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "column_mapping = (\"FIGO_stage\", \"figo_stage\")\n",
+    "\n",
+    "value_mappings = bdi.top_value_matches(\n",
+    "    dataset,\n",
+    "    target=\"gdc\",\n",
+    "    column_mapping=column_mapping,\n",
+    "    top_k=5\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "As seen below, the function `view_value_matches()` helps us to visualize the output of the `top_value_matches()` function."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/markdown": [
+       "<br>**Source column:** FIGO_stage<br>**Target column:** figo_stage<br>"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Markdown object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IA</td>\n",
+       "      <td>0.586</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IIA</td>\n",
+       "      <td>0.563</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IIIA</td>\n",
+       "      <td>0.527</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IIIAi</td>\n",
+       "      <td>0.467</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IIIA2</td>\n",
+       "      <td>0.432</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source       target  similarity\n",
+       "0     IA     Stage IA       0.586\n",
+       "1     IA    Stage IIA       0.563\n",
+       "2     IA   Stage IIIA       0.527\n",
+       "3     IA  Stage IIIAi       0.467\n",
+       "4     IA  Stage IIIA2       0.432"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IB</td>\n",
+       "      <td>0.649</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IIB</td>\n",
+       "      <td>0.571</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IIIB</td>\n",
+       "      <td>0.528</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IB2</td>\n",
+       "      <td>0.441</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IB1</td>\n",
+       "      <td>0.441</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source      target  similarity\n",
+       "0     IB    Stage IB       0.649\n",
+       "1     IB   Stage IIB       0.571\n",
+       "2     IB  Stage IIIB       0.528\n",
+       "3     IB   Stage IB2       0.441\n",
+       "4     IB   Stage IB1       0.441"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage III</td>\n",
+       "      <td>0.687</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage IIIAii</td>\n",
+       "      <td>0.635</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage IIIA</td>\n",
+       "      <td>0.598</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage IIIC</td>\n",
+       "      <td>0.580</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage IIIAi</td>\n",
+       "      <td>0.566</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source        target  similarity\n",
+       "0     II     Stage III       0.687\n",
+       "1     II  Stage IIIAii       0.635\n",
+       "2     II    Stage IIIA       0.598\n",
+       "3     II    Stage IIIC       0.580\n",
+       "4     II   Stage IIIAi       0.566"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIA</td>\n",
+       "      <td>0.822</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIAii</td>\n",
+       "      <td>0.726</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIAi</td>\n",
+       "      <td>0.716</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIA2</td>\n",
+       "      <td>0.674</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIA1</td>\n",
+       "      <td>0.674</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source        target  similarity\n",
+       "0   IIIA    Stage IIIA       0.822\n",
+       "1   IIIA  Stage IIIAii       0.726\n",
+       "2   IIIA   Stage IIIAi       0.716\n",
+       "3   IIIA   Stage IIIA2       0.674\n",
+       "4   IIIA   Stage IIIA1       0.674"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage IIIB</td>\n",
+       "      <td>0.849</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage IIB</td>\n",
+       "      <td>0.728</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage III</td>\n",
+       "      <td>0.545</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage IIIA</td>\n",
+       "      <td>0.475</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage IIIAii</td>\n",
+       "      <td>0.471</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source        target  similarity\n",
+       "0   IIIB    Stage IIIB       0.849\n",
+       "1   IIIB     Stage IIB       0.728\n",
+       "2   IIIB     Stage III       0.545\n",
+       "3   IIIB    Stage IIIA       0.475\n",
+       "4   IIIB  Stage IIIAii       0.471"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IIIC1</td>\n",
+       "      <td>0.889</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IC1</td>\n",
+       "      <td>0.651</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IIIC</td>\n",
+       "      <td>0.647</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IIC</td>\n",
+       "      <td>0.538</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IIIC2</td>\n",
+       "      <td>0.536</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source       target  similarity\n",
+       "0  IIIC1  Stage IIIC1       0.889\n",
+       "1  IIIC1    Stage IC1       0.651\n",
+       "2  IIIC1   Stage IIIC       0.647\n",
+       "3  IIIC1    Stage IIC       0.538\n",
+       "4  IIIC1  Stage IIIC2       0.536"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IIIC2</td>\n",
+       "      <td>0.889</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IC2</td>\n",
+       "      <td>0.651</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IIIC</td>\n",
+       "      <td>0.647</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IIC</td>\n",
+       "      <td>0.538</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IIIC1</td>\n",
+       "      <td>0.536</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source       target  similarity\n",
+       "0  IIIC2  Stage IIIC2       0.889\n",
+       "1  IIIC2    Stage IC2       0.651\n",
+       "2  IIIC2   Stage IIIC       0.647\n",
+       "3  IIIC2    Stage IIC       0.538\n",
+       "4  IIIC2  Stage IIIC1       0.536"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IVB</td>\n",
+       "      <td>Stage IVB</td>\n",
+       "      <td>0.854</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IVB</td>\n",
+       "      <td>Stage IV</td>\n",
+       "      <td>0.448</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IVB</td>\n",
+       "      <td>Stage IVA</td>\n",
+       "      <td>0.325</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source     target  similarity\n",
+       "0    IVB  Stage IVB       0.854\n",
+       "1    IVB   Stage IV       0.448\n",
+       "2    IVB  Stage IVA       0.325"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>NaN</td>\n",
+       "      <td>Unknown</td>\n",
+       "      <td>0.35</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source   target  similarity\n",
+       "0    NaN  Unknown        0.35"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "bdi.view_value_matches(value_mappings)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can also send a `DataFrame` containing column mappings to `column_mapping` parameter of `top_value_matches()`.\n",
+    "Next we show how the dataframe returned by `match_schema()` can be used as input argument for `top_value_matches()`.\n",
+    "Let's start by creating a dataframe with column mappings.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>FIGO_stage</td>\n",
+       "      <td>figo_stage</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>Ethnicity</td>\n",
+       "      <td>ethnicity</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>Race</td>\n",
+       "      <td>race</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "       source      target\n",
+       "0  FIGO_stage  figo_stage\n",
+       "1   Ethnicity   ethnicity\n",
+       "2        Race        race"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "column_mappings = bdi.match_schema(dataset[columns], target=\"gdc\", method=\"coma\")\n",
+    "column_mappings"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now the `column_mappings` dataframe is used as an argument of `top_value_matches()`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "value_mappings = bdi.top_value_matches(\n",
+    "    dataset,\n",
+    "    target=\"gdc\",\n",
+    "    column_mapping=column_mappings,\n",
+    "    top_k=5\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/markdown": [
+       "<br>**Source column:** FIGO_stage<br>**Target column:** figo_stage<br>"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Markdown object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IA</td>\n",
+       "      <td>0.586</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IIA</td>\n",
+       "      <td>0.563</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IIIA</td>\n",
+       "      <td>0.527</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IIIAi</td>\n",
+       "      <td>0.467</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IA</td>\n",
+       "      <td>Stage IIIA2</td>\n",
+       "      <td>0.432</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source       target  similarity\n",
+       "0     IA     Stage IA       0.586\n",
+       "1     IA    Stage IIA       0.563\n",
+       "2     IA   Stage IIIA       0.527\n",
+       "3     IA  Stage IIIAi       0.467\n",
+       "4     IA  Stage IIIA2       0.432"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IB</td>\n",
+       "      <td>0.649</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IIB</td>\n",
+       "      <td>0.571</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IIIB</td>\n",
+       "      <td>0.528</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IB2</td>\n",
+       "      <td>0.441</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IB</td>\n",
+       "      <td>Stage IB1</td>\n",
+       "      <td>0.441</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source      target  similarity\n",
+       "0     IB    Stage IB       0.649\n",
+       "1     IB   Stage IIB       0.571\n",
+       "2     IB  Stage IIIB       0.528\n",
+       "3     IB   Stage IB2       0.441\n",
+       "4     IB   Stage IB1       0.441"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage III</td>\n",
+       "      <td>0.687</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage IIIAii</td>\n",
+       "      <td>0.635</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage IIIA</td>\n",
+       "      <td>0.598</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage IIIC</td>\n",
+       "      <td>0.580</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>II</td>\n",
+       "      <td>Stage IIIAi</td>\n",
+       "      <td>0.566</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source        target  similarity\n",
+       "0     II     Stage III       0.687\n",
+       "1     II  Stage IIIAii       0.635\n",
+       "2     II    Stage IIIA       0.598\n",
+       "3     II    Stage IIIC       0.580\n",
+       "4     II   Stage IIIAi       0.566"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIA</td>\n",
+       "      <td>0.822</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIAii</td>\n",
+       "      <td>0.726</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIAi</td>\n",
+       "      <td>0.716</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIA2</td>\n",
+       "      <td>0.674</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IIIA</td>\n",
+       "      <td>Stage IIIA1</td>\n",
+       "      <td>0.674</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source        target  similarity\n",
+       "0   IIIA    Stage IIIA       0.822\n",
+       "1   IIIA  Stage IIIAii       0.726\n",
+       "2   IIIA   Stage IIIAi       0.716\n",
+       "3   IIIA   Stage IIIA2       0.674\n",
+       "4   IIIA   Stage IIIA1       0.674"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage IIIB</td>\n",
+       "      <td>0.849</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage IIB</td>\n",
+       "      <td>0.728</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage III</td>\n",
+       "      <td>0.545</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage IIIA</td>\n",
+       "      <td>0.475</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IIIB</td>\n",
+       "      <td>Stage IIIAii</td>\n",
+       "      <td>0.471</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source        target  similarity\n",
+       "0   IIIB    Stage IIIB       0.849\n",
+       "1   IIIB     Stage IIB       0.728\n",
+       "2   IIIB     Stage III       0.545\n",
+       "3   IIIB    Stage IIIA       0.475\n",
+       "4   IIIB  Stage IIIAii       0.471"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IIIC1</td>\n",
+       "      <td>0.889</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IC1</td>\n",
+       "      <td>0.651</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IIIC</td>\n",
+       "      <td>0.647</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IIC</td>\n",
+       "      <td>0.538</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IIIC1</td>\n",
+       "      <td>Stage IIIC2</td>\n",
+       "      <td>0.536</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source       target  similarity\n",
+       "0  IIIC1  Stage IIIC1       0.889\n",
+       "1  IIIC1    Stage IC1       0.651\n",
+       "2  IIIC1   Stage IIIC       0.647\n",
+       "3  IIIC1    Stage IIC       0.538\n",
+       "4  IIIC1  Stage IIIC2       0.536"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IIIC2</td>\n",
+       "      <td>0.889</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IC2</td>\n",
+       "      <td>0.651</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IIIC</td>\n",
+       "      <td>0.647</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IIC</td>\n",
+       "      <td>0.538</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>IIIC2</td>\n",
+       "      <td>Stage IIIC1</td>\n",
+       "      <td>0.536</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source       target  similarity\n",
+       "0  IIIC2  Stage IIIC2       0.889\n",
+       "1  IIIC2    Stage IC2       0.651\n",
+       "2  IIIC2   Stage IIIC       0.647\n",
+       "3  IIIC2    Stage IIC       0.538\n",
+       "4  IIIC2  Stage IIIC1       0.536"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>IVB</td>\n",
+       "      <td>Stage IVB</td>\n",
+       "      <td>0.854</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>IVB</td>\n",
+       "      <td>Stage IV</td>\n",
+       "      <td>0.448</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>IVB</td>\n",
+       "      <td>Stage IVA</td>\n",
+       "      <td>0.325</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source     target  similarity\n",
+       "0    IVB  Stage IVB       0.854\n",
+       "1    IVB   Stage IV       0.448\n",
+       "2    IVB  Stage IVA       0.325"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>NaN</td>\n",
+       "      <td>Unknown</td>\n",
+       "      <td>0.35</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source   target  similarity\n",
+       "0    NaN  Unknown        0.35"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/markdown": [
+       "<br>**Source column:** Ethnicity<br>**Target column:** ethnicity<br>"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Markdown object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>Hispanic or Latino</td>\n",
+       "      <td>hispanic or latino</td>\n",
+       "      <td>1.000</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>Hispanic or Latino</td>\n",
+       "      <td>not hispanic or latino</td>\n",
+       "      <td>0.956</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "               source                  target  similarity\n",
+       "0  Hispanic or Latino      hispanic or latino       1.000\n",
+       "1  Hispanic or Latino  not hispanic or latino       0.956"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>Not reported</td>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "         source target  similarity\n",
+       "0  Not reported    NaN         NaN"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>not hispanic or latino</td>\n",
+       "      <td>0.935</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>Not-Hispanic or Latino</td>\n",
+       "      <td>hispanic or latino</td>\n",
+       "      <td>0.894</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "                   source                  target  similarity\n",
+       "0  Not-Hispanic or Latino  not hispanic or latino       0.935\n",
+       "1  Not-Hispanic or Latino      hispanic or latino       0.894"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
+       "      <td>NaN</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source target  similarity\n",
+       "0    NaN    NaN         NaN"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/markdown": [
+       "<br>**Source column:** Race<br>**Target column:** race<br>"
+      ],
+      "text/plain": [
+       "<IPython.core.display.Markdown object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>White</td>\n",
+       "      <td>white</td>\n",
+       "      <td>1.0</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "   source target  similarity\n",
+       "0   White  white         1.0"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>Asian</td>\n",
+       "      <td>asian</td>\n",
+       "      <td>1.000</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>Asian</td>\n",
+       "      <td>american indian or alaska native</td>\n",
+       "      <td>0.438</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>Asian</td>\n",
+       "      <td>native hawaiian or other pacific islander</td>\n",
+       "      <td>0.329</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source                                     target  similarity\n",
+       "0  Asian                                      asian       1.000\n",
+       "1  Asian           american indian or alaska native       0.438\n",
+       "2  Asian  native hawaiian or other pacific islander       0.329"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>Black or African American</td>\n",
+       "      <td>black or african american</td>\n",
+       "      <td>1.000</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>Black or African American</td>\n",
+       "      <td>american indian or alaska native</td>\n",
+       "      <td>0.605</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>Black or African American</td>\n",
+       "      <td>native hawaiian or other pacific islander</td>\n",
+       "      <td>0.399</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "                      source                                     target  \\\n",
+       "0  Black or African American                  black or african american   \n",
+       "1  Black or African American           american indian or alaska native   \n",
+       "2  Black or African American  native hawaiian or other pacific islander   \n",
+       "\n",
+       "   similarity  \n",
+       "0       1.000  \n",
+       "1       0.605  \n",
+       "2       0.399  "
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>Not Reported</td>\n",
+       "      <td>not reported</td>\n",
+       "      <td>1.0</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "         source        target  similarity\n",
+       "0  Not Reported  not reported         1.0"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>source</th>\n",
+       "      <th>target</th>\n",
+       "      <th>similarity</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>NaN</td>\n",
+       "      <td>american indian or alaska native</td>\n",
+       "      <td>0.359</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "  source                            target  similarity\n",
+       "0    NaN  american indian or alaska native       0.359"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "bdi.view_value_matches(value_mappings)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.12"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}


Test patch:
diff --git a/tests/test_api.py b/tests/test_api.py
index f14a57b..f4ad733 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,6 +1,7 @@
 import bdikit as bdi
 import numpy as np
 import pandas as pd
+import numpy as np
 from bdikit.mapping_algorithms.value_mapping.value_mappers import (
     FunctionValueMapper,
     IdentityValueMapper,
@@ -239,7 +240,7 @@ def test_end_to_end_api_integration():
     assert len(column_mappings.index) == 1
 
     # when: pass output of match_schema() directly to materialize_mapping(),
-    # the column must be ranamed to the target column without any value mapping
+    # the column must be renamed to the target column without any value mapping
     df_mapped = bdi.materialize_mapping(df_source, column_mappings)
     # then
     assert "tgt_column" in df_mapped.columns
@@ -275,10 +276,11 @@ def test_end_to_end_api_integration():
 
     # when: pass output of match_values() to merge_mappings() and then to
     # materialize_mapping()
+
     harmonization_spec = bdi.merge_mappings(value_mappings, [])
     df_mapped = bdi.materialize_mapping(df_source, harmonization_spec)
 
-    # then: the column must be ranamed and values must be mapped
+    # then: the column must be renamed and values must be mapped
     assert isinstance(df_mapped, pd.DataFrame)
     assert "tgt_column" in df_mapped.columns
     assert df_mapped["tgt_column"].tolist() == ["apple", "banana", "orange", np.nan]
@@ -298,7 +300,7 @@ def test_end_to_end_api_integration():
     harmonization_spec = bdi.merge_mappings(value_mappings, user_mappings)
     df_mapped = bdi.materialize_mapping(df_source, harmonization_spec)
 
-    # then: user mappings take precedence, so the column must be ranamed and
+    # then: user mappings take precedence, so the column must be renamed and
     # values must be mapped according the provide user_mappings
     assert "tgt_column" in df_mapped.columns
     assert df_mapped["tgt_column"].tolist() == ["APPLE", "BANANA", "ORANGE", np.nan]
@@ -339,3 +341,47 @@ def test_top_matches_and_match_values_integration():
         assert "similarity" in df.columns
         assert df.attrs["source"] == "fruits"
         assert df.attrs["target"] in ["fruit_types", "fruit_names", "fruit_id"]
+
+
+def test_top_value_matches():
+    # given
+    df_source = pd.DataFrame({"fruits": ["Applee", "Bananaa", "Oorange", "Strawberry"]})
+    df_target = pd.DataFrame(
+        {
+            "fruit_names": [
+                "apple",
+                "red apple",
+                "banana",
+                "mx banana",
+                "melon",
+                "kiwi",
+                "grapes",
+            ],
+            "fruit_id": ["1", "2", "3", "4", "5", "6", "7"],
+        }
+    )
+    column_mapping = ("fruits", "fruit_names")
+
+    # when
+    matches = bdi.top_value_matches(df_source, df_target, column_mapping)
+
+    # then
+    assert len(matches) == 4  # number of dataframes in the list
+
+    df_match = matches[0]  # top matches for apple
+    assert len(df_match) == 2
+    assert "source" in df_match.columns
+    assert "target" in df_match.columns
+    assert "similarity" in df_match.columns
+
+    df_match = matches[1]  # top matches for banana
+    assert len(df_match) == 2
+    assert "source" in df_match.columns
+    assert "target" in df_match.columns
+    assert "similarity" in df_match.columns
+
+    df_match = matches[2]  # top matches for orange
+    assert len(df_match) == 1
+    assert "source" in df_match.columns
+    assert "target" in df_match.columns
+    assert "similarity" in df_match.columns